diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 4c4af34..a2d75e3 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ To watch repositories simply add them to the list of arguments `-r=kubernetes/ku ``` docker run --rm -e GITHUB_TOKEN=XXX -e SLACK_HOOK=https://hooks.slack.com/... justwatch/github-releases-notifier -r=kubernetes/kubernetes ``` +If you need to follow by tag you can use +``` +docker run --rm -e TAG_CHECKER=true -e GITHUB_TOKEN=XXX -e SLACK_HOOK=https://hooks.slack.com/... justwatch/github-releases-notifier -r=kubernetes/kubernetes +``` #### docker-compose diff --git a/main.go b/main.go index 6f9bcc3..6b5f1bc 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ type Config struct { LogLevel string `arg:"env:LOG_LEVEL"` Repositories []string `arg:"-r,separate"` SlackHook string `arg:"env:SLACK_HOOK"` + IsTagChecker bool `arg:"env:TAG_CHECKER"` } // Token returns an oauth2 token or an error. @@ -63,7 +64,7 @@ func main() { } releases := make(chan Repository) - go checker.Run(c.Interval, c.Repositories, releases) + go checker.Run(c.Interval, c.Repositories, releases, c.IsTagChecker) slack := SlackSender{Hook: c.SlackHook} diff --git a/redis.go b/redis.go new file mode 100644 index 0000000..5a3ce12 --- /dev/null +++ b/redis.go @@ -0,0 +1,50 @@ +package main + +import ( + "fmt" + "github.com/go-redis/redis" + "os" +) + +//Client redis Client +var Client *redis.Client + +//SetKey set key in redis +func SetKey(key string, value string) { + var err error + + if GetValue(key) != "" { + Client.Del(key) + } + + err = Client.Set(key, value, 0).Err() + check(err) +} + +//GetValue get value from redis +func GetValue(key string) string { + return Client.Get(key).Val() +} + +//СonnectToRedis connect to redis +func СonnectToRedis() redis.Client { + var host = os.Getenv("REDIS_HOST") + var port = os.Getenv("REDIS_PORT") + + Client = redis.NewClient(&redis.Options{ + Addr: host + ":" + port, + Password: "", + DB: 11, + }) + + pong, err := Client.Ping().Result() + fmt.Println(pong, err) + + return *Client +} + +func check(e error) { + if e != nil { + fmt.Println(e) + } +} diff --git a/releasechecker.go b/releasechecker.go index d455df2..fb1b3c5 100644 --- a/releasechecker.go +++ b/releasechecker.go @@ -20,7 +20,7 @@ type Checker struct { } // Run the queries and comparisons for the given repositories in a given interval. -func (c *Checker) Run(interval time.Duration, repositories []string, releases chan<- Repository) { +func (c *Checker) Run(interval time.Duration, repositories []string, releases chan<- Repository, tag bool) { if c.releases == nil { c.releases = make(map[string]Repository) } @@ -31,6 +31,14 @@ func (c *Checker) Run(interval time.Duration, repositories []string, releases ch owner, name := s[0], s[1] nextRepo, err := c.query(owner, name) + if true == tag { + nextRepo, err = APIV3tagChecker(owner, name) + if nextRepo != (Repository{}) { + releases <- nextRepo + c.releases[repoName] = nextRepo + } + } + if err != nil { level.Warn(c.logger).Log( "msg", "failed to query the repository's releases", diff --git a/tagchecker.go b/tagchecker.go new file mode 100644 index 0000000..bf0c184 --- /dev/null +++ b/tagchecker.go @@ -0,0 +1,71 @@ +package main + +import ( + "context" + "fmt" + "github.com/google/go-github/github" + "golang.org/x/oauth2" + "net/url" + "os" + "time" +) + +//APIV3tagChecker Function for working with github api v3 and check if new tags are published +func APIV3tagChecker(owner, name string) (Repository, error) { + СonnectToRedis() + githubToken, ok := os.LookupEnv("GITHUB_TOKEN") + + if !ok { + fmt.Errorf("GITHUB_TOKEN environment variable not set") + } + + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: githubToken}, + ) + + tc := oauth2.NewClient(ctx, ts) + client := github.NewClient(tc) + + opt := &github.ListOptions{} + + repo, _, err := client.Repositories.Get(ctx, owner, name) + tags, _, err := client.Repositories.ListTags(ctx, owner, name, opt) + + if err != nil { + return Repository{}, fmt.Errorf("cant find repo") + } + + var LastTag github.RepositoryTag + for _, tag := range tags { + LastTag = *tag + break + } + + repourl, err := url.ParseRequestURI(repo.GetURL()) + if err != nil { + return Repository{}, fmt.Errorf("cant convert url") + } + + if GetValue(name) == LastTag.GetName() { + return Repository{}, nil + } + + SetKey(name, LastTag.GetName()) + + return Repository{ + ID: string(repo.GetID()), + Name: string(repo.GetName()), + Owner: owner, + Description: string(repo.GetDescription()), + URL: *repourl, + + Release: Release{ + ID: "1", + Name: LastTag.GetName(), + Description: "It is Tag", + URL: *repourl, + PublishedAt: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + }, nil +} diff --git a/vendor/github.com/go-redis/redis/.gitignore b/vendor/github.com/go-redis/redis/.gitignore new file mode 100644 index 0000000..ebfe903 --- /dev/null +++ b/vendor/github.com/go-redis/redis/.gitignore @@ -0,0 +1,2 @@ +*.rdb +testdata/*/ diff --git a/vendor/github.com/go-redis/redis/.travis.yml b/vendor/github.com/go-redis/redis/.travis.yml new file mode 100644 index 0000000..f49927e --- /dev/null +++ b/vendor/github.com/go-redis/redis/.travis.yml @@ -0,0 +1,21 @@ +sudo: false +language: go + +services: + - redis-server + +go: + - 1.4.x + - 1.7.x + - 1.8.x + - 1.9.x + - tip + +matrix: + allow_failures: + - go: 1.4.x + - go: tip + +install: + - go get github.com/onsi/ginkgo + - go get github.com/onsi/gomega diff --git a/vendor/github.com/go-redis/redis/LICENSE b/vendor/github.com/go-redis/redis/LICENSE new file mode 100644 index 0000000..298bed9 --- /dev/null +++ b/vendor/github.com/go-redis/redis/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2013 The github.com/go-redis/redis Authors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/go-redis/redis/Makefile b/vendor/github.com/go-redis/redis/Makefile new file mode 100644 index 0000000..50fdc55 --- /dev/null +++ b/vendor/github.com/go-redis/redis/Makefile @@ -0,0 +1,19 @@ +all: testdeps + go test ./... + go test ./... -short -race + go vet + +testdeps: testdata/redis/src/redis-server + +bench: testdeps + go test ./... -test.run=NONE -test.bench=. -test.benchmem + +.PHONY: all test testdeps bench + +testdata/redis: + mkdir -p $@ + wget -qO- https://github.com/antirez/redis/archive/unstable.tar.gz | tar xvz --strip-components=1 -C $@ + +testdata/redis/src/redis-server: testdata/redis + sed -i 's/libjemalloc.a/libjemalloc.a -lrt/g' $ +} + +func ExampleClient() { + err := client.Set("key", "value", 0).Err() + if err != nil { + panic(err) + } + + val, err := client.Get("key").Result() + if err != nil { + panic(err) + } + fmt.Println("key", val) + + val2, err := client.Get("key2").Result() + if err == redis.Nil { + fmt.Println("key2 does not exist") + } else if err != nil { + panic(err) + } else { + fmt.Println("key2", val2) + } + // Output: key value + // key2 does not exist +} +``` + +## Howto + +Please go through [examples](https://godoc.org/github.com/go-redis/redis#pkg-examples) to get an idea how to use this package. + +## Look and feel + +Some corner cases: + + SET key value EX 10 NX + set, err := client.SetNX("key", "value", 10*time.Second).Result() + + SORT list LIMIT 0 2 ASC + vals, err := client.Sort("list", redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result() + + ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2 + vals, err := client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{ + Min: "-inf", + Max: "+inf", + Offset: 0, + Count: 2, + }).Result() + + ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM + vals, err := client.ZInterStore("out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2").Result() + + EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello" + vals, err := client.Eval("return {KEYS[1],ARGV[1]}", []string{"key"}, "hello").Result() + +## Benchmark + +go-redis vs redigo: + +``` +BenchmarkSetGoRedis10Conns64Bytes-4 200000 7621 ns/op 210 B/op 6 allocs/op +BenchmarkSetGoRedis100Conns64Bytes-4 200000 7554 ns/op 210 B/op 6 allocs/op +BenchmarkSetGoRedis10Conns1KB-4 200000 7697 ns/op 210 B/op 6 allocs/op +BenchmarkSetGoRedis100Conns1KB-4 200000 7688 ns/op 210 B/op 6 allocs/op +BenchmarkSetGoRedis10Conns10KB-4 200000 9214 ns/op 210 B/op 6 allocs/op +BenchmarkSetGoRedis100Conns10KB-4 200000 9181 ns/op 210 B/op 6 allocs/op +BenchmarkSetGoRedis10Conns1MB-4 2000 583242 ns/op 2337 B/op 6 allocs/op +BenchmarkSetGoRedis100Conns1MB-4 2000 583089 ns/op 2338 B/op 6 allocs/op +BenchmarkSetRedigo10Conns64Bytes-4 200000 7576 ns/op 208 B/op 7 allocs/op +BenchmarkSetRedigo100Conns64Bytes-4 200000 7782 ns/op 208 B/op 7 allocs/op +BenchmarkSetRedigo10Conns1KB-4 200000 7958 ns/op 208 B/op 7 allocs/op +BenchmarkSetRedigo100Conns1KB-4 200000 7725 ns/op 208 B/op 7 allocs/op +BenchmarkSetRedigo10Conns10KB-4 100000 18442 ns/op 208 B/op 7 allocs/op +BenchmarkSetRedigo100Conns10KB-4 100000 18818 ns/op 208 B/op 7 allocs/op +BenchmarkSetRedigo10Conns1MB-4 2000 668829 ns/op 226 B/op 7 allocs/op +BenchmarkSetRedigo100Conns1MB-4 2000 679542 ns/op 226 B/op 7 allocs/op +``` + +Redis Cluster: + +``` +BenchmarkRedisPing-4 200000 6983 ns/op 116 B/op 4 allocs/op +BenchmarkRedisClusterPing-4 100000 11535 ns/op 117 B/op 4 allocs/op +``` + +## See also + +- [Golang PostgreSQL ORM](https://github.com/go-pg/pg) +- [Golang msgpack](https://github.com/vmihailenco/msgpack) +- [Golang message task queue](https://github.com/go-msgqueue/msgqueue) diff --git a/vendor/github.com/go-redis/redis/bench_test.go b/vendor/github.com/go-redis/redis/bench_test.go new file mode 100644 index 0000000..f6b75c7 --- /dev/null +++ b/vendor/github.com/go-redis/redis/bench_test.go @@ -0,0 +1,216 @@ +package redis_test + +import ( + "bytes" + "testing" + "time" + + "github.com/go-redis/redis" +) + +func benchmarkRedisClient(poolSize int) *redis.Client { + client := redis.NewClient(&redis.Options{ + Addr: ":6379", + DialTimeout: time.Second, + ReadTimeout: time.Second, + WriteTimeout: time.Second, + PoolSize: poolSize, + }) + if err := client.FlushDB().Err(); err != nil { + panic(err) + } + return client +} + +func BenchmarkRedisPing(b *testing.B) { + client := benchmarkRedisClient(10) + defer client.Close() + + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if err := client.Ping().Err(); err != nil { + b.Fatal(err) + } + } + }) +} + +func BenchmarkRedisSetString(b *testing.B) { + client := benchmarkRedisClient(10) + defer client.Close() + + value := string(bytes.Repeat([]byte{'1'}, 10000)) + + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if err := client.Set("key", value, 0).Err(); err != nil { + b.Fatal(err) + } + } + }) +} + +func BenchmarkRedisGetNil(b *testing.B) { + client := benchmarkRedisClient(10) + defer client.Close() + + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if err := client.Get("key").Err(); err != redis.Nil { + b.Fatal(err) + } + } + }) +} + +func benchmarkSetRedis(b *testing.B, poolSize, payloadSize int) { + client := benchmarkRedisClient(poolSize) + defer client.Close() + + value := string(bytes.Repeat([]byte{'1'}, payloadSize)) + + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if err := client.Set("key", value, 0).Err(); err != nil { + b.Fatal(err) + } + } + }) +} + +func BenchmarkSetRedis10Conns64Bytes(b *testing.B) { + benchmarkSetRedis(b, 10, 64) +} + +func BenchmarkSetRedis100Conns64Bytes(b *testing.B) { + benchmarkSetRedis(b, 100, 64) +} + +func BenchmarkSetRedis10Conns1KB(b *testing.B) { + benchmarkSetRedis(b, 10, 1024) +} + +func BenchmarkSetRedis100Conns1KB(b *testing.B) { + benchmarkSetRedis(b, 100, 1024) +} + +func BenchmarkSetRedis10Conns10KB(b *testing.B) { + benchmarkSetRedis(b, 10, 10*1024) +} + +func BenchmarkSetRedis100Conns10KB(b *testing.B) { + benchmarkSetRedis(b, 100, 10*1024) +} + +func BenchmarkSetRedis10Conns1MB(b *testing.B) { + benchmarkSetRedis(b, 10, 1024*1024) +} + +func BenchmarkSetRedis100Conns1MB(b *testing.B) { + benchmarkSetRedis(b, 100, 1024*1024) +} + +func BenchmarkRedisSetGetBytes(b *testing.B) { + client := benchmarkRedisClient(10) + defer client.Close() + + value := bytes.Repeat([]byte{'1'}, 10000) + + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if err := client.Set("key", value, 0).Err(); err != nil { + b.Fatal(err) + } + + got, err := client.Get("key").Bytes() + if err != nil { + b.Fatal(err) + } + if !bytes.Equal(got, value) { + b.Fatalf("got != value") + } + } + }) +} + +func BenchmarkRedisMGet(b *testing.B) { + client := benchmarkRedisClient(10) + defer client.Close() + + if err := client.MSet("key1", "hello1", "key2", "hello2").Err(); err != nil { + b.Fatal(err) + } + + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if err := client.MGet("key1", "key2").Err(); err != nil { + b.Fatal(err) + } + } + }) +} + +func BenchmarkSetExpire(b *testing.B) { + client := benchmarkRedisClient(10) + defer client.Close() + + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if err := client.Set("key", "hello", 0).Err(); err != nil { + b.Fatal(err) + } + if err := client.Expire("key", time.Second).Err(); err != nil { + b.Fatal(err) + } + } + }) +} + +func BenchmarkPipeline(b *testing.B) { + client := benchmarkRedisClient(10) + defer client.Close() + + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, err := client.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Set("key", "hello", 0) + pipe.Expire("key", time.Second) + return nil + }) + if err != nil { + b.Fatal(err) + } + } + }) +} + +func BenchmarkZAdd(b *testing.B) { + client := benchmarkRedisClient(10) + defer client.Close() + + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if err := client.ZAdd("key", redis.Z{float64(1), "hello"}).Err(); err != nil { + b.Fatal(err) + } + } + }) +} diff --git a/vendor/github.com/go-redis/redis/cluster.go b/vendor/github.com/go-redis/redis/cluster.go new file mode 100644 index 0000000..c81fc1d --- /dev/null +++ b/vendor/github.com/go-redis/redis/cluster.go @@ -0,0 +1,1242 @@ +package redis + +import ( + "fmt" + "math/rand" + "net" + "sync" + "sync/atomic" + "time" + + "github.com/go-redis/redis/internal" + "github.com/go-redis/redis/internal/hashtag" + "github.com/go-redis/redis/internal/pool" + "github.com/go-redis/redis/internal/proto" +) + +var errClusterNoNodes = fmt.Errorf("redis: cluster has no nodes") +var errNilClusterState = fmt.Errorf("redis: cannot load cluster slots") + +// ClusterOptions are used to configure a cluster client and should be +// passed to NewClusterClient. +type ClusterOptions struct { + // A seed list of host:port addresses of cluster nodes. + Addrs []string + + // The maximum number of retries before giving up. Command is retried + // on network errors and MOVED/ASK redirects. + // Default is 16. + MaxRedirects int + + // Enables read-only commands on slave nodes. + ReadOnly bool + // Allows routing read-only commands to the closest master or slave node. + RouteByLatency bool + + // Following options are copied from Options struct. + + OnConnect func(*Conn) error + + MaxRetries int + MinRetryBackoff time.Duration + MaxRetryBackoff time.Duration + Password string + + DialTimeout time.Duration + ReadTimeout time.Duration + WriteTimeout time.Duration + + // PoolSize applies per cluster node and not for the whole cluster. + PoolSize int + PoolTimeout time.Duration + IdleTimeout time.Duration + IdleCheckFrequency time.Duration +} + +func (opt *ClusterOptions) init() { + if opt.MaxRedirects == -1 { + opt.MaxRedirects = 0 + } else if opt.MaxRedirects == 0 { + opt.MaxRedirects = 16 + } + + if opt.RouteByLatency { + opt.ReadOnly = true + } + + switch opt.ReadTimeout { + case -1: + opt.ReadTimeout = 0 + case 0: + opt.ReadTimeout = 3 * time.Second + } + switch opt.WriteTimeout { + case -1: + opt.WriteTimeout = 0 + case 0: + opt.WriteTimeout = opt.ReadTimeout + } + + switch opt.MinRetryBackoff { + case -1: + opt.MinRetryBackoff = 0 + case 0: + opt.MinRetryBackoff = 8 * time.Millisecond + } + switch opt.MaxRetryBackoff { + case -1: + opt.MaxRetryBackoff = 0 + case 0: + opt.MaxRetryBackoff = 512 * time.Millisecond + } +} + +func (opt *ClusterOptions) clientOptions() *Options { + const disableIdleCheck = -1 + + return &Options{ + OnConnect: opt.OnConnect, + + MaxRetries: opt.MaxRetries, + MinRetryBackoff: opt.MinRetryBackoff, + MaxRetryBackoff: opt.MaxRetryBackoff, + Password: opt.Password, + readOnly: opt.ReadOnly, + + DialTimeout: opt.DialTimeout, + ReadTimeout: opt.ReadTimeout, + WriteTimeout: opt.WriteTimeout, + + PoolSize: opt.PoolSize, + PoolTimeout: opt.PoolTimeout, + IdleTimeout: opt.IdleTimeout, + + IdleCheckFrequency: disableIdleCheck, + } +} + +//------------------------------------------------------------------------------ + +type clusterNode struct { + Client *Client + Latency time.Duration + + loading time.Time + generation uint32 +} + +func newClusterNode(clOpt *ClusterOptions, addr string) *clusterNode { + opt := clOpt.clientOptions() + opt.Addr = addr + node := clusterNode{ + Client: NewClient(opt), + } + + if clOpt.RouteByLatency { + node.updateLatency() + } + + return &node +} + +func (n *clusterNode) updateLatency() { + const probes = 10 + for i := 0; i < probes; i++ { + start := time.Now() + n.Client.Ping() + n.Latency += time.Since(start) + } + n.Latency = n.Latency / probes +} + +func (n *clusterNode) Loading() bool { + return !n.loading.IsZero() && time.Since(n.loading) < time.Minute +} + +func (n *clusterNode) Generation() uint32 { + return n.generation +} + +func (n *clusterNode) SetGeneration(gen uint32) { + if gen < n.generation { + panic("gen < n.generation") + } + n.generation = gen +} + +//------------------------------------------------------------------------------ + +type clusterNodes struct { + opt *ClusterOptions + + mu sync.RWMutex + addrs []string + nodes map[string]*clusterNode + closed bool + + generation uint32 +} + +func newClusterNodes(opt *ClusterOptions) *clusterNodes { + return &clusterNodes{ + opt: opt, + nodes: make(map[string]*clusterNode), + } +} + +func (c *clusterNodes) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return nil + } + c.closed = true + + var firstErr error + for _, node := range c.nodes { + if err := node.Client.Close(); err != nil && firstErr == nil { + firstErr = err + } + } + c.addrs = nil + c.nodes = nil + + return firstErr +} + +func (c *clusterNodes) Addrs() ([]string, error) { + c.mu.RLock() + closed := c.closed + addrs := c.addrs + c.mu.RUnlock() + + if closed { + return nil, pool.ErrClosed + } + if len(addrs) == 0 { + return nil, errClusterNoNodes + } + return addrs, nil +} + +func (c *clusterNodes) NextGeneration() uint32 { + c.generation++ + return c.generation +} + +// GC removes unused nodes. +func (c *clusterNodes) GC(generation uint32) error { + var collected []*clusterNode + c.mu.Lock() + for i := 0; i < len(c.addrs); { + addr := c.addrs[i] + node := c.nodes[addr] + if node.Generation() >= generation { + i++ + continue + } + + c.addrs = append(c.addrs[:i], c.addrs[i+1:]...) + delete(c.nodes, addr) + collected = append(collected, node) + } + c.mu.Unlock() + + var firstErr error + for _, node := range collected { + if err := node.Client.Close(); err != nil && firstErr == nil { + firstErr = err + } + } + + return firstErr +} + +func (c *clusterNodes) All() ([]*clusterNode, error) { + c.mu.RLock() + defer c.mu.RUnlock() + + if c.closed { + return nil, pool.ErrClosed + } + + nodes := make([]*clusterNode, 0, len(c.nodes)) + for _, node := range c.nodes { + nodes = append(nodes, node) + } + return nodes, nil +} + +func (c *clusterNodes) GetOrCreate(addr string) (*clusterNode, error) { + var node *clusterNode + var ok bool + + c.mu.RLock() + if !c.closed { + node, ok = c.nodes[addr] + } + c.mu.RUnlock() + if ok { + return node, nil + } + + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return nil, pool.ErrClosed + } + + node, ok = c.nodes[addr] + if ok { + return node, nil + } + + c.addrs = append(c.addrs, addr) + node = newClusterNode(c.opt, addr) + c.nodes[addr] = node + return node, nil +} + +func (c *clusterNodes) Random() (*clusterNode, error) { + addrs, err := c.Addrs() + if err != nil { + return nil, err + } + + var nodeErr error + for i := 0; i <= c.opt.MaxRedirects; i++ { + n := rand.Intn(len(addrs)) + node, err := c.GetOrCreate(addrs[n]) + if err != nil { + return nil, err + } + + nodeErr = node.Client.ClusterInfo().Err() + if nodeErr == nil { + return node, nil + } + } + return nil, nodeErr +} + +//------------------------------------------------------------------------------ + +type clusterState struct { + nodes *clusterNodes + masters []*clusterNode + slaves []*clusterNode + + slots [][]*clusterNode + + generation uint32 +} + +func newClusterState(nodes *clusterNodes, slots []ClusterSlot, origin string) (*clusterState, error) { + c := clusterState{ + nodes: nodes, + generation: nodes.NextGeneration(), + + slots: make([][]*clusterNode, hashtag.SlotNumber), + } + + isLoopbackOrigin := isLoopbackAddr(origin) + for _, slot := range slots { + var nodes []*clusterNode + for i, slotNode := range slot.Nodes { + addr := slotNode.Addr + if !isLoopbackOrigin && isLoopbackAddr(addr) { + addr = origin + } + + node, err := c.nodes.GetOrCreate(addr) + if err != nil { + return nil, err + } + + node.SetGeneration(c.generation) + nodes = append(nodes, node) + + if i == 0 { + c.masters = appendNode(c.masters, node) + } else { + c.slaves = appendNode(c.slaves, node) + } + } + + for i := slot.Start; i <= slot.End; i++ { + c.slots[i] = nodes + } + } + + return &c, nil +} + +func (c *clusterState) slotMasterNode(slot int) (*clusterNode, error) { + nodes := c.slotNodes(slot) + if len(nodes) > 0 { + return nodes[0], nil + } + return c.nodes.Random() +} + +func (c *clusterState) slotSlaveNode(slot int) (*clusterNode, error) { + nodes := c.slotNodes(slot) + switch len(nodes) { + case 0: + return c.nodes.Random() + case 1: + return nodes[0], nil + case 2: + if slave := nodes[1]; !slave.Loading() { + return slave, nil + } + return nodes[0], nil + default: + var slave *clusterNode + for i := 0; i < 10; i++ { + n := rand.Intn(len(nodes)-1) + 1 + slave = nodes[n] + if !slave.Loading() { + break + } + } + return slave, nil + } +} + +func (c *clusterState) slotClosestNode(slot int) (*clusterNode, error) { + const threshold = time.Millisecond + + nodes := c.slotNodes(slot) + if len(nodes) == 0 { + return c.nodes.Random() + } + + var node *clusterNode + for _, n := range nodes { + if n.Loading() { + continue + } + if node == nil || node.Latency-n.Latency > threshold { + node = n + } + } + return node, nil +} + +func (c *clusterState) slotNodes(slot int) []*clusterNode { + if slot >= 0 && slot < len(c.slots) { + return c.slots[slot] + } + return nil +} + +//------------------------------------------------------------------------------ + +// ClusterClient is a Redis Cluster client representing a pool of zero +// or more underlying connections. It's safe for concurrent use by +// multiple goroutines. +type ClusterClient struct { + cmdable + + opt *ClusterOptions + nodes *clusterNodes + _state atomic.Value + + cmdsInfoOnce internal.Once + cmdsInfo map[string]*CommandInfo + + // Reports whether slots reloading is in progress. + reloading uint32 +} + +// NewClusterClient returns a Redis Cluster client as described in +// http://redis.io/topics/cluster-spec. +func NewClusterClient(opt *ClusterOptions) *ClusterClient { + opt.init() + + c := &ClusterClient{ + opt: opt, + nodes: newClusterNodes(opt), + } + c.setProcessor(c.Process) + + // Add initial nodes. + for _, addr := range opt.Addrs { + _, _ = c.nodes.GetOrCreate(addr) + } + + // Preload cluster slots. + for i := 0; i < 10; i++ { + state, err := c.reloadState() + if err == nil { + c._state.Store(state) + break + } + } + + if opt.IdleCheckFrequency > 0 { + go c.reaper(opt.IdleCheckFrequency) + } + + return c +} + +// Options returns read-only Options that were used to create the client. +func (c *ClusterClient) Options() *ClusterOptions { + return c.opt +} + +func (c *ClusterClient) retryBackoff(attempt int) time.Duration { + return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff) +} + +func (c *ClusterClient) state() (*clusterState, error) { + v := c._state.Load() + if v != nil { + return v.(*clusterState), nil + } + + _, err := c.nodes.Addrs() + if err != nil { + return nil, err + } + + c.lazyReloadState() + return nil, errNilClusterState +} + +func (c *ClusterClient) cmdInfo(name string) *CommandInfo { + err := c.cmdsInfoOnce.Do(func() error { + node, err := c.nodes.Random() + if err != nil { + return err + } + + cmdsInfo, err := node.Client.Command().Result() + if err != nil { + return err + } + + c.cmdsInfo = cmdsInfo + return nil + }) + if err != nil { + return nil + } + info := c.cmdsInfo[name] + if info == nil { + internal.Logf("info for cmd=%s not found", name) + } + return info +} + +func (c *ClusterClient) cmdSlot(cmd Cmder) int { + cmdInfo := c.cmdInfo(cmd.Name()) + firstKey := cmd.stringArg(cmdFirstKeyPos(cmd, cmdInfo)) + return hashtag.Slot(firstKey) +} + +func (c *ClusterClient) cmdSlotAndNode(state *clusterState, cmd Cmder) (int, *clusterNode, error) { + cmdInfo := c.cmdInfo(cmd.Name()) + firstKey := cmd.stringArg(cmdFirstKeyPos(cmd, cmdInfo)) + slot := hashtag.Slot(firstKey) + + if cmdInfo != nil && cmdInfo.ReadOnly && c.opt.ReadOnly { + if c.opt.RouteByLatency { + node, err := state.slotClosestNode(slot) + return slot, node, err + } + + node, err := state.slotSlaveNode(slot) + return slot, node, err + } + + node, err := state.slotMasterNode(slot) + return slot, node, err +} + +func (c *ClusterClient) Watch(fn func(*Tx) error, keys ...string) error { + if len(keys) == 0 { + return fmt.Errorf("redis: keys don't hash to the same slot") + } + + state, err := c.state() + if err != nil { + return err + } + + slot := hashtag.Slot(keys[0]) + for _, key := range keys[1:] { + if hashtag.Slot(key) != slot { + return fmt.Errorf("redis: Watch requires all keys to be in the same slot") + } + } + + node, err := state.slotMasterNode(slot) + if err != nil { + return err + } + + for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { + if attempt > 0 { + time.Sleep(c.retryBackoff(attempt)) + } + + err = node.Client.Watch(fn, keys...) + if err == nil { + break + } + + moved, ask, addr := internal.IsMovedError(err) + if moved || ask { + c.lazyReloadState() + node, err = c.nodes.GetOrCreate(addr) + if err != nil { + return err + } + continue + } + + return err + } + + return err +} + +// Close closes the cluster client, releasing any open resources. +// +// It is rare to Close a ClusterClient, as the ClusterClient is meant +// to be long-lived and shared between many goroutines. +func (c *ClusterClient) Close() error { + return c.nodes.Close() +} + +func (c *ClusterClient) Process(cmd Cmder) error { + state, err := c.state() + if err != nil { + cmd.setErr(err) + return err + } + + _, node, err := c.cmdSlotAndNode(state, cmd) + if err != nil { + cmd.setErr(err) + return err + } + + var ask bool + for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { + if attempt > 0 { + time.Sleep(c.retryBackoff(attempt)) + } + + if ask { + pipe := node.Client.Pipeline() + pipe.Process(NewCmd("ASKING")) + pipe.Process(cmd) + _, err = pipe.Exec() + pipe.Close() + ask = false + } else { + err = node.Client.Process(cmd) + } + + // If there is no error - we are done. + if err == nil { + break + } + + // If slave is loading - read from master. + if c.opt.ReadOnly && internal.IsLoadingError(err) { + // TODO: race + node.loading = time.Now() + continue + } + + if internal.IsRetryableError(err, true) { + var nodeErr error + node, nodeErr = c.nodes.Random() + if nodeErr != nil { + break + } + continue + } + + var moved bool + var addr string + moved, ask, addr = internal.IsMovedError(err) + if moved || ask { + c.lazyReloadState() + + var nodeErr error + node, nodeErr = c.nodes.GetOrCreate(addr) + if nodeErr != nil { + break + } + continue + } + + break + } + + return cmd.Err() +} + +// ForEachMaster concurrently calls the fn on each master node in the cluster. +// It returns the first error if any. +func (c *ClusterClient) ForEachMaster(fn func(client *Client) error) error { + state, err := c.state() + if err != nil { + return err + } + + var wg sync.WaitGroup + errCh := make(chan error, 1) + for _, master := range state.masters { + wg.Add(1) + go func(node *clusterNode) { + defer wg.Done() + err := fn(node.Client) + if err != nil { + select { + case errCh <- err: + default: + } + } + }(master) + } + wg.Wait() + + select { + case err := <-errCh: + return err + default: + return nil + } +} + +// ForEachSlave concurrently calls the fn on each slave node in the cluster. +// It returns the first error if any. +func (c *ClusterClient) ForEachSlave(fn func(client *Client) error) error { + state, err := c.state() + if err != nil { + return err + } + + var wg sync.WaitGroup + errCh := make(chan error, 1) + for _, slave := range state.slaves { + wg.Add(1) + go func(node *clusterNode) { + defer wg.Done() + err := fn(node.Client) + if err != nil { + select { + case errCh <- err: + default: + } + } + }(slave) + } + wg.Wait() + + select { + case err := <-errCh: + return err + default: + return nil + } +} + +// ForEachNode concurrently calls the fn on each known node in the cluster. +// It returns the first error if any. +func (c *ClusterClient) ForEachNode(fn func(client *Client) error) error { + state, err := c.state() + if err != nil { + return err + } + + var wg sync.WaitGroup + errCh := make(chan error, 1) + worker := func(node *clusterNode) { + defer wg.Done() + err := fn(node.Client) + if err != nil { + select { + case errCh <- err: + default: + } + } + } + + for _, node := range state.masters { + wg.Add(1) + go worker(node) + } + for _, node := range state.slaves { + wg.Add(1) + go worker(node) + } + + wg.Wait() + select { + case err := <-errCh: + return err + default: + return nil + } +} + +// PoolStats returns accumulated connection pool stats. +func (c *ClusterClient) PoolStats() *PoolStats { + var acc PoolStats + + state, _ := c.state() + if state == nil { + return &acc + } + + for _, node := range state.masters { + s := node.Client.connPool.Stats() + acc.Hits += s.Hits + acc.Misses += s.Misses + acc.Timeouts += s.Timeouts + + acc.TotalConns += s.TotalConns + acc.FreeConns += s.FreeConns + acc.StaleConns += s.StaleConns + } + + for _, node := range state.slaves { + s := node.Client.connPool.Stats() + acc.Hits += s.Hits + acc.Misses += s.Misses + acc.Timeouts += s.Timeouts + + acc.TotalConns += s.TotalConns + acc.FreeConns += s.FreeConns + acc.StaleConns += s.StaleConns + } + + return &acc +} + +func (c *ClusterClient) lazyReloadState() { + if !atomic.CompareAndSwapUint32(&c.reloading, 0, 1) { + return + } + + go func() { + defer atomic.StoreUint32(&c.reloading, 0) + + for { + state, err := c.reloadState() + if err == pool.ErrClosed { + return + } + + if err != nil { + time.Sleep(time.Millisecond) + continue + } + + c._state.Store(state) + time.Sleep(5 * time.Second) + c.nodes.GC(state.generation) + break + } + }() +} + +// Not thread-safe. +func (c *ClusterClient) reloadState() (*clusterState, error) { + node, err := c.nodes.Random() + if err != nil { + return nil, err + } + + slots, err := node.Client.ClusterSlots().Result() + if err != nil { + return nil, err + } + + return newClusterState(c.nodes, slots, node.Client.opt.Addr) +} + +// reaper closes idle connections to the cluster. +func (c *ClusterClient) reaper(idleCheckFrequency time.Duration) { + ticker := time.NewTicker(idleCheckFrequency) + defer ticker.Stop() + + for range ticker.C { + nodes, err := c.nodes.All() + if err != nil { + break + } + + for _, node := range nodes { + _, err := node.Client.connPool.(*pool.ConnPool).ReapStaleConns() + if err != nil { + internal.Logf("ReapStaleConns failed: %s", err) + } + } + } +} + +func (c *ClusterClient) Pipeline() Pipeliner { + pipe := Pipeline{ + exec: c.pipelineExec, + } + pipe.setProcessor(pipe.Process) + return &pipe +} + +func (c *ClusterClient) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { + return c.Pipeline().Pipelined(fn) +} + +func (c *ClusterClient) pipelineExec(cmds []Cmder) error { + cmdsMap, err := c.mapCmdsByNode(cmds) + if err != nil { + setCmdsErr(cmds, err) + return err + } + + for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { + if attempt > 0 { + time.Sleep(c.retryBackoff(attempt)) + } + + failedCmds := make(map[*clusterNode][]Cmder) + + for node, cmds := range cmdsMap { + cn, _, err := node.Client.getConn() + if err != nil { + setCmdsErr(cmds, err) + continue + } + + err = c.pipelineProcessCmds(node, cn, cmds, failedCmds) + if err == nil || internal.IsRedisError(err) { + _ = node.Client.connPool.Put(cn) + } else { + _ = node.Client.connPool.Remove(cn) + } + } + + if len(failedCmds) == 0 { + break + } + cmdsMap = failedCmds + } + + return firstCmdsErr(cmds) +} + +func (c *ClusterClient) mapCmdsByNode(cmds []Cmder) (map[*clusterNode][]Cmder, error) { + state, err := c.state() + if err != nil { + setCmdsErr(cmds, err) + return nil, err + } + + cmdsMap := make(map[*clusterNode][]Cmder) + for _, cmd := range cmds { + slot := c.cmdSlot(cmd) + node, err := state.slotMasterNode(slot) + if err != nil { + return nil, err + } + cmdsMap[node] = append(cmdsMap[node], cmd) + } + return cmdsMap, nil +} + +func (c *ClusterClient) pipelineProcessCmds( + node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds map[*clusterNode][]Cmder, +) error { + cn.SetWriteTimeout(c.opt.WriteTimeout) + if err := writeCmd(cn, cmds...); err != nil { + setCmdsErr(cmds, err) + failedCmds[node] = cmds + return err + } + + // Set read timeout for all commands. + cn.SetReadTimeout(c.opt.ReadTimeout) + + return c.pipelineReadCmds(cn, cmds, failedCmds) +} + +func (c *ClusterClient) pipelineReadCmds( + cn *pool.Conn, cmds []Cmder, failedCmds map[*clusterNode][]Cmder, +) error { + for _, cmd := range cmds { + err := cmd.readReply(cn) + if err == nil { + continue + } + + if c.checkMovedErr(cmd, err, failedCmds) { + continue + } + + if internal.IsRedisError(err) { + continue + } + + return err + } + return nil +} + +func (c *ClusterClient) checkMovedErr( + cmd Cmder, err error, failedCmds map[*clusterNode][]Cmder, +) bool { + moved, ask, addr := internal.IsMovedError(err) + + if moved { + c.lazyReloadState() + + node, err := c.nodes.GetOrCreate(addr) + if err != nil { + return false + } + + failedCmds[node] = append(failedCmds[node], cmd) + return true + } + + if ask { + node, err := c.nodes.GetOrCreate(addr) + if err != nil { + return false + } + + failedCmds[node] = append(failedCmds[node], NewCmd("ASKING"), cmd) + return true + } + + return false +} + +// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC. +func (c *ClusterClient) TxPipeline() Pipeliner { + pipe := Pipeline{ + exec: c.txPipelineExec, + } + pipe.setProcessor(pipe.Process) + return &pipe +} + +func (c *ClusterClient) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { + return c.TxPipeline().Pipelined(fn) +} + +func (c *ClusterClient) txPipelineExec(cmds []Cmder) error { + state, err := c.state() + if err != nil { + return err + } + + cmdsMap := c.mapCmdsBySlot(cmds) + for slot, cmds := range cmdsMap { + node, err := state.slotMasterNode(slot) + if err != nil { + setCmdsErr(cmds, err) + continue + } + cmdsMap := map[*clusterNode][]Cmder{node: cmds} + + for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { + if attempt > 0 { + time.Sleep(c.retryBackoff(attempt)) + } + + failedCmds := make(map[*clusterNode][]Cmder) + + for node, cmds := range cmdsMap { + cn, _, err := node.Client.getConn() + if err != nil { + setCmdsErr(cmds, err) + continue + } + + err = c.txPipelineProcessCmds(node, cn, cmds, failedCmds) + if err == nil || internal.IsRedisError(err) { + _ = node.Client.connPool.Put(cn) + } else { + _ = node.Client.connPool.Remove(cn) + } + } + + if len(failedCmds) == 0 { + break + } + cmdsMap = failedCmds + } + } + + return firstCmdsErr(cmds) +} + +func (c *ClusterClient) mapCmdsBySlot(cmds []Cmder) map[int][]Cmder { + cmdsMap := make(map[int][]Cmder) + for _, cmd := range cmds { + slot := c.cmdSlot(cmd) + cmdsMap[slot] = append(cmdsMap[slot], cmd) + } + return cmdsMap +} + +func (c *ClusterClient) txPipelineProcessCmds( + node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds map[*clusterNode][]Cmder, +) error { + cn.SetWriteTimeout(c.opt.WriteTimeout) + if err := txPipelineWriteMulti(cn, cmds); err != nil { + setCmdsErr(cmds, err) + failedCmds[node] = cmds + return err + } + + // Set read timeout for all commands. + cn.SetReadTimeout(c.opt.ReadTimeout) + + if err := c.txPipelineReadQueued(cn, cmds, failedCmds); err != nil { + setCmdsErr(cmds, err) + return err + } + + return pipelineReadCmds(cn, cmds) +} + +func (c *ClusterClient) txPipelineReadQueued( + cn *pool.Conn, cmds []Cmder, failedCmds map[*clusterNode][]Cmder, +) error { + // Parse queued replies. + var statusCmd StatusCmd + if err := statusCmd.readReply(cn); err != nil { + return err + } + + for _, cmd := range cmds { + err := statusCmd.readReply(cn) + if err == nil { + continue + } + + if c.checkMovedErr(cmd, err, failedCmds) || internal.IsRedisError(err) { + continue + } + + return err + } + + // Parse number of replies. + line, err := cn.Rd.ReadLine() + if err != nil { + if err == Nil { + err = TxFailedErr + } + return err + } + + switch line[0] { + case proto.ErrorReply: + err := proto.ParseErrorReply(line) + for _, cmd := range cmds { + if !c.checkMovedErr(cmd, err, failedCmds) { + break + } + } + return err + case proto.ArrayReply: + // ok + default: + err := fmt.Errorf("redis: expected '*', but got line %q", line) + return err + } + + return nil +} + +func (c *ClusterClient) pubSub(channels []string) *PubSub { + opt := c.opt.clientOptions() + + var node *clusterNode + return &PubSub{ + opt: opt, + + newConn: func(channels []string) (*pool.Conn, error) { + if node == nil { + var slot int + if len(channels) > 0 { + slot = hashtag.Slot(channels[0]) + } else { + slot = -1 + } + + state, err := c.state() + if err != nil { + return nil, err + } + + masterNode, err := state.slotMasterNode(slot) + if err != nil { + return nil, err + } + node = masterNode + } + return node.Client.newConn() + }, + closeConn: func(cn *pool.Conn) error { + return node.Client.connPool.CloseConn(cn) + }, + } +} + +// Subscribe subscribes the client to the specified channels. +// Channels can be omitted to create empty subscription. +func (c *ClusterClient) Subscribe(channels ...string) *PubSub { + pubsub := c.pubSub(channels) + if len(channels) > 0 { + _ = pubsub.Subscribe(channels...) + } + return pubsub +} + +// PSubscribe subscribes the client to the given patterns. +// Patterns can be omitted to create empty subscription. +func (c *ClusterClient) PSubscribe(channels ...string) *PubSub { + pubsub := c.pubSub(channels) + if len(channels) > 0 { + _ = pubsub.PSubscribe(channels...) + } + return pubsub +} + +func isLoopbackAddr(addr string) bool { + host, _, err := net.SplitHostPort(addr) + if err != nil { + return false + } + + ip := net.ParseIP(host) + if ip == nil { + return false + } + + return ip.IsLoopback() +} + +func appendNode(nodes []*clusterNode, node *clusterNode) []*clusterNode { + for _, n := range nodes { + if n == node { + return nodes + } + } + return append(nodes, node) +} diff --git a/vendor/github.com/go-redis/redis/cluster_commands.go b/vendor/github.com/go-redis/redis/cluster_commands.go new file mode 100644 index 0000000..dff62c9 --- /dev/null +++ b/vendor/github.com/go-redis/redis/cluster_commands.go @@ -0,0 +1,22 @@ +package redis + +import "sync/atomic" + +func (c *ClusterClient) DBSize() *IntCmd { + cmd := NewIntCmd("dbsize") + var size int64 + err := c.ForEachMaster(func(master *Client) error { + n, err := master.DBSize().Result() + if err != nil { + return err + } + atomic.AddInt64(&size, n) + return nil + }) + if err != nil { + cmd.setErr(err) + return cmd + } + cmd.val = size + return cmd +} diff --git a/vendor/github.com/go-redis/redis/cluster_test.go b/vendor/github.com/go-redis/redis/cluster_test.go new file mode 100644 index 0000000..6f3677b --- /dev/null +++ b/vendor/github.com/go-redis/redis/cluster_test.go @@ -0,0 +1,787 @@ +package redis_test + +import ( + "bytes" + "fmt" + "net" + "strconv" + "strings" + "sync" + "testing" + "time" + + "github.com/go-redis/redis" + "github.com/go-redis/redis/internal/hashtag" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +type clusterScenario struct { + ports []string + nodeIds []string + processes map[string]*redisProcess + clients map[string]*redis.Client +} + +func (s *clusterScenario) masters() []*redis.Client { + result := make([]*redis.Client, 3) + for pos, port := range s.ports[:3] { + result[pos] = s.clients[port] + } + return result +} + +func (s *clusterScenario) slaves() []*redis.Client { + result := make([]*redis.Client, 3) + for pos, port := range s.ports[3:] { + result[pos] = s.clients[port] + } + return result +} + +func (s *clusterScenario) addrs() []string { + addrs := make([]string, len(s.ports)) + for i, port := range s.ports { + addrs[i] = net.JoinHostPort("127.0.0.1", port) + } + return addrs +} + +func (s *clusterScenario) clusterClient(opt *redis.ClusterOptions) *redis.ClusterClient { + opt.Addrs = s.addrs() + return redis.NewClusterClient(opt) +} + +func startCluster(scenario *clusterScenario) error { + // Start processes and collect node ids + for pos, port := range scenario.ports { + process, err := startRedis(port, "--cluster-enabled", "yes") + if err != nil { + return err + } + + client := redis.NewClient(&redis.Options{ + Addr: ":" + port, + }) + + info, err := client.ClusterNodes().Result() + if err != nil { + return err + } + + scenario.processes[port] = process + scenario.clients[port] = client + scenario.nodeIds[pos] = info[:40] + } + + // Meet cluster nodes. + for _, client := range scenario.clients { + err := client.ClusterMeet("127.0.0.1", scenario.ports[0]).Err() + if err != nil { + return err + } + } + + // Bootstrap masters. + slots := []int{0, 5000, 10000, 16384} + for pos, master := range scenario.masters() { + err := master.ClusterAddSlotsRange(slots[pos], slots[pos+1]-1).Err() + if err != nil { + return err + } + } + + // Bootstrap slaves. + for idx, slave := range scenario.slaves() { + masterId := scenario.nodeIds[idx] + + // Wait until master is available + err := eventually(func() error { + s := slave.ClusterNodes().Val() + wanted := masterId + if !strings.Contains(s, wanted) { + return fmt.Errorf("%q does not contain %q", s, wanted) + } + return nil + }, 10*time.Second) + if err != nil { + return err + } + + err = slave.ClusterReplicate(masterId).Err() + if err != nil { + return err + } + } + + // Wait until all nodes have consistent info. + for _, client := range scenario.clients { + err := eventually(func() error { + res, err := client.ClusterSlots().Result() + if err != nil { + return err + } + wanted := []redis.ClusterSlot{ + {0, 4999, []redis.ClusterNode{{"", "127.0.0.1:8220"}, {"", "127.0.0.1:8223"}}}, + {5000, 9999, []redis.ClusterNode{{"", "127.0.0.1:8221"}, {"", "127.0.0.1:8224"}}}, + {10000, 16383, []redis.ClusterNode{{"", "127.0.0.1:8222"}, {"", "127.0.0.1:8225"}}}, + } + return assertSlotsEqual(res, wanted) + }, 30*time.Second) + if err != nil { + return err + } + } + + return nil +} + +func assertSlotsEqual(slots, wanted []redis.ClusterSlot) error { +outer_loop: + for _, s2 := range wanted { + for _, s1 := range slots { + if slotEqual(s1, s2) { + continue outer_loop + } + } + return fmt.Errorf("%v not found in %v", s2, slots) + } + return nil +} + +func slotEqual(s1, s2 redis.ClusterSlot) bool { + if s1.Start != s2.Start { + return false + } + if s1.End != s2.End { + return false + } + if len(s1.Nodes) != len(s2.Nodes) { + return false + } + for i, n1 := range s1.Nodes { + if n1.Addr != s2.Nodes[i].Addr { + return false + } + } + return true +} + +func stopCluster(scenario *clusterScenario) error { + for _, client := range scenario.clients { + if err := client.Close(); err != nil { + return err + } + } + for _, process := range scenario.processes { + if err := process.Close(); err != nil { + return err + } + } + return nil +} + +//------------------------------------------------------------------------------ + +var _ = Describe("ClusterClient", func() { + var opt *redis.ClusterOptions + var client *redis.ClusterClient + + assertClusterClient := func() { + It("should GET/SET/DEL", func() { + val, err := client.Get("A").Result() + Expect(err).To(Equal(redis.Nil)) + Expect(val).To(Equal("")) + + val, err = client.Set("A", "VALUE", 0).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("OK")) + + Eventually(func() string { + return client.Get("A").Val() + }, 30*time.Second).Should(Equal("VALUE")) + + cnt, err := client.Del("A").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cnt).To(Equal(int64(1))) + }) + + It("follows redirects", func() { + Expect(client.Set("A", "VALUE", 0).Err()).NotTo(HaveOccurred()) + + slot := hashtag.Slot("A") + client.SwapSlotNodes(slot) + + Eventually(func() string { + return client.Get("A").Val() + }, 30*time.Second).Should(Equal("VALUE")) + }) + + It("distributes keys", func() { + for i := 0; i < 100; i++ { + err := client.Set(fmt.Sprintf("key%d", i), "value", 0).Err() + Expect(err).NotTo(HaveOccurred()) + } + + for _, master := range cluster.masters() { + Eventually(func() string { + return master.Info("keyspace").Val() + }, 30*time.Second).Should(Or( + ContainSubstring("keys=31"), + ContainSubstring("keys=29"), + ContainSubstring("keys=40"), + )) + } + }) + + It("distributes keys when using EVAL", func() { + script := redis.NewScript(` + local r = redis.call('SET', KEYS[1], ARGV[1]) + return r + `) + + var key string + for i := 0; i < 100; i++ { + key = fmt.Sprintf("key%d", i) + err := script.Run(client, []string{key}, "value").Err() + Expect(err).NotTo(HaveOccurred()) + } + + for _, master := range cluster.masters() { + Eventually(func() string { + return master.Info("keyspace").Val() + }, 30*time.Second).Should(Or( + ContainSubstring("keys=31"), + ContainSubstring("keys=29"), + ContainSubstring("keys=40"), + )) + } + }) + + It("supports Watch", func() { + var incr func(string) error + + // Transactionally increments key using GET and SET commands. + incr = func(key string) error { + err := client.Watch(func(tx *redis.Tx) error { + n, err := tx.Get(key).Int64() + if err != nil && err != redis.Nil { + return err + } + + _, err = tx.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Set(key, strconv.FormatInt(n+1, 10), 0) + return nil + }) + return err + }, key) + if err == redis.TxFailedErr { + return incr(key) + } + return err + } + + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer GinkgoRecover() + defer wg.Done() + + err := incr("key") + Expect(err).NotTo(HaveOccurred()) + }() + } + wg.Wait() + + n, err := client.Get("key").Int64() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(100))) + }) + + Describe("pipelining", func() { + var pipe *redis.Pipeline + + assertPipeline := func() { + keys := []string{"A", "B", "C", "D", "E", "F", "G"} + + It("follows redirects", func() { + for _, key := range keys { + slot := hashtag.Slot(key) + client.SwapSlotNodes(slot) + } + + for i, key := range keys { + pipe.Set(key, key+"_value", 0) + pipe.Expire(key, time.Duration(i+1)*time.Hour) + } + cmds, err := pipe.Exec() + Expect(err).NotTo(HaveOccurred()) + Expect(cmds).To(HaveLen(14)) + + for _, key := range keys { + slot := hashtag.Slot(key) + client.SwapSlotNodes(slot) + } + + for _, key := range keys { + pipe.Get(key) + pipe.TTL(key) + } + cmds, err = pipe.Exec() + Expect(err).NotTo(HaveOccurred()) + Expect(cmds).To(HaveLen(14)) + + for i, key := range keys { + get := cmds[i*2].(*redis.StringCmd) + Expect(get.Val()).To(Equal(key + "_value")) + + ttl := cmds[(i*2)+1].(*redis.DurationCmd) + dur := time.Duration(i+1) * time.Hour + Expect(ttl.Val()).To(BeNumerically("~", dur, 5*time.Second)) + } + }) + + It("works with missing keys", func() { + pipe.Set("A", "A_value", 0) + pipe.Set("C", "C_value", 0) + _, err := pipe.Exec() + Expect(err).NotTo(HaveOccurred()) + + a := pipe.Get("A") + b := pipe.Get("B") + c := pipe.Get("C") + cmds, err := pipe.Exec() + Expect(err).To(Equal(redis.Nil)) + Expect(cmds).To(HaveLen(3)) + + Expect(a.Err()).NotTo(HaveOccurred()) + Expect(a.Val()).To(Equal("A_value")) + + Expect(b.Err()).To(Equal(redis.Nil)) + Expect(b.Val()).To(Equal("")) + + Expect(c.Err()).NotTo(HaveOccurred()) + Expect(c.Val()).To(Equal("C_value")) + }) + } + + Describe("with Pipeline", func() { + BeforeEach(func() { + pipe = client.Pipeline().(*redis.Pipeline) + }) + + AfterEach(func() { + Expect(pipe.Close()).NotTo(HaveOccurred()) + }) + + assertPipeline() + }) + + Describe("with TxPipeline", func() { + BeforeEach(func() { + pipe = client.TxPipeline().(*redis.Pipeline) + }) + + AfterEach(func() { + Expect(pipe.Close()).NotTo(HaveOccurred()) + }) + + assertPipeline() + }) + }) + + It("supports PubSub", func() { + pubsub := client.Subscribe("mychannel") + defer pubsub.Close() + + Eventually(func() error { + _, err := client.Publish("mychannel", "hello").Result() + if err != nil { + return err + } + + msg, err := pubsub.ReceiveTimeout(time.Second) + if err != nil { + return err + } + + _, ok := msg.(*redis.Message) + if !ok { + return fmt.Errorf("got %T, wanted *redis.Message", msg) + } + + return nil + }, 30*time.Second).ShouldNot(HaveOccurred()) + }) + } + + Describe("ClusterClient", func() { + BeforeEach(func() { + opt = redisClusterOptions() + client = cluster.clusterClient(opt) + + _ = client.ForEachMaster(func(master *redis.Client) error { + return master.FlushDB().Err() + }) + }) + + AfterEach(func() { + _ = client.ForEachMaster(func(master *redis.Client) error { + return master.FlushDB().Err() + }) + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + It("returns pool stats", func() { + Expect(client.PoolStats()).To(BeAssignableToTypeOf(&redis.PoolStats{})) + }) + + It("removes idle connections", func() { + stats := client.PoolStats() + Expect(stats.TotalConns).NotTo(BeZero()) + Expect(stats.FreeConns).NotTo(BeZero()) + + time.Sleep(2 * time.Second) + + stats = client.PoolStats() + Expect(stats.TotalConns).To(BeZero()) + Expect(stats.FreeConns).To(BeZero()) + }) + + It("returns an error when there are no attempts left", func() { + opt := redisClusterOptions() + opt.MaxRedirects = -1 + client := cluster.clusterClient(opt) + + slot := hashtag.Slot("A") + client.SwapSlotNodes(slot) + + err := client.Get("A").Err() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("MOVED")) + + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + It("calls fn for every master node", func() { + for i := 0; i < 10; i++ { + Expect(client.Set(strconv.Itoa(i), "", 0).Err()).NotTo(HaveOccurred()) + } + + err := client.ForEachMaster(func(master *redis.Client) error { + return master.FlushDB().Err() + }) + Expect(err).NotTo(HaveOccurred()) + + size, err := client.DBSize().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(size).To(Equal(int64(0))) + }) + + It("should CLUSTER SLOTS", func() { + res, err := client.ClusterSlots().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(HaveLen(3)) + + wanted := []redis.ClusterSlot{ + {0, 4999, []redis.ClusterNode{{"", "127.0.0.1:8220"}, {"", "127.0.0.1:8223"}}}, + {5000, 9999, []redis.ClusterNode{{"", "127.0.0.1:8221"}, {"", "127.0.0.1:8224"}}}, + {10000, 16383, []redis.ClusterNode{{"", "127.0.0.1:8222"}, {"", "127.0.0.1:8225"}}}, + } + Expect(assertSlotsEqual(res, wanted)).NotTo(HaveOccurred()) + }) + + It("should CLUSTER NODES", func() { + res, err := client.ClusterNodes().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(res)).To(BeNumerically(">", 400)) + }) + + It("should CLUSTER INFO", func() { + res, err := client.ClusterInfo().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(ContainSubstring("cluster_known_nodes:6")) + }) + + It("should CLUSTER KEYSLOT", func() { + hashSlot, err := client.ClusterKeySlot("somekey").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(hashSlot).To(Equal(int64(hashtag.Slot("somekey")))) + }) + + It("should CLUSTER COUNT-FAILURE-REPORTS", func() { + n, err := client.ClusterCountFailureReports(cluster.nodeIds[0]).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(0))) + }) + + It("should CLUSTER COUNTKEYSINSLOT", func() { + n, err := client.ClusterCountKeysInSlot(10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(0))) + }) + + It("should CLUSTER SAVECONFIG", func() { + res, err := client.ClusterSaveConfig().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + }) + + It("should CLUSTER SLAVES", func() { + nodesList, err := client.ClusterSlaves(cluster.nodeIds[0]).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(nodesList).Should(ContainElement(ContainSubstring("slave"))) + Expect(nodesList).Should(HaveLen(1)) + }) + + assertClusterClient() + }) + + Describe("ClusterClient failover", func() { + BeforeEach(func() { + opt = redisClusterOptions() + client = cluster.clusterClient(opt) + + _ = client.ForEachMaster(func(master *redis.Client) error { + return master.FlushDB().Err() + }) + + _ = client.ForEachSlave(func(slave *redis.Client) error { + Eventually(func() int64 { + return client.DBSize().Val() + }, 30*time.Second).Should(Equal(int64(0))) + return slave.ClusterFailover().Err() + }) + }) + + AfterEach(func() { + _ = client.ForEachMaster(func(master *redis.Client) error { + return master.FlushDB().Err() + }) + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + assertClusterClient() + }) + + Describe("ClusterClient with RouteByLatency", func() { + BeforeEach(func() { + opt = redisClusterOptions() + opt.RouteByLatency = true + client = cluster.clusterClient(opt) + + _ = client.ForEachMaster(func(master *redis.Client) error { + return master.FlushDB().Err() + }) + + _ = client.ForEachSlave(func(slave *redis.Client) error { + Eventually(func() int64 { + return client.DBSize().Val() + }, 30*time.Second).Should(Equal(int64(0))) + return nil + }) + }) + + AfterEach(func() { + _ = client.ForEachMaster(func(master *redis.Client) error { + return master.FlushDB().Err() + }) + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + assertClusterClient() + }) +}) + +var _ = Describe("ClusterClient without nodes", func() { + var client *redis.ClusterClient + + BeforeEach(func() { + client = redis.NewClusterClient(&redis.ClusterOptions{}) + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + It("Ping returns an error", func() { + err := client.Ping().Err() + Expect(err).To(MatchError("redis: cluster has no nodes")) + }) + + It("pipeline returns an error", func() { + _, err := client.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Ping() + return nil + }) + Expect(err).To(MatchError("redis: cluster has no nodes")) + }) +}) + +var _ = Describe("ClusterClient without valid nodes", func() { + var client *redis.ClusterClient + + BeforeEach(func() { + client = redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: []string{redisAddr}, + }) + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + It("returns an error", func() { + err := client.Ping().Err() + Expect(err).To(MatchError("redis: cannot load cluster slots")) + }) + + It("pipeline returns an error", func() { + _, err := client.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Ping() + return nil + }) + Expect(err).To(MatchError("redis: cannot load cluster slots")) + }) +}) + +var _ = Describe("ClusterClient timeout", func() { + var client *redis.ClusterClient + + AfterEach(func() { + _ = client.Close() + }) + + testTimeout := func() { + It("Ping timeouts", func() { + err := client.Ping().Err() + Expect(err).To(HaveOccurred()) + Expect(err.(net.Error).Timeout()).To(BeTrue()) + }) + + It("Pipeline timeouts", func() { + _, err := client.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Ping() + return nil + }) + Expect(err).To(HaveOccurred()) + Expect(err.(net.Error).Timeout()).To(BeTrue()) + }) + + It("Tx timeouts", func() { + err := client.Watch(func(tx *redis.Tx) error { + return tx.Ping().Err() + }, "foo") + Expect(err).To(HaveOccurred()) + Expect(err.(net.Error).Timeout()).To(BeTrue()) + }) + + It("Tx Pipeline timeouts", func() { + err := client.Watch(func(tx *redis.Tx) error { + _, err := tx.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Ping() + return nil + }) + return err + }, "foo") + Expect(err).To(HaveOccurred()) + Expect(err.(net.Error).Timeout()).To(BeTrue()) + }) + } + + const pause = time.Second + + Context("read/write timeout", func() { + BeforeEach(func() { + opt := redisClusterOptions() + opt.ReadTimeout = 100 * time.Millisecond + opt.WriteTimeout = 100 * time.Millisecond + opt.MaxRedirects = 1 + client = cluster.clusterClient(opt) + + err := client.ForEachNode(func(client *redis.Client) error { + return client.ClientPause(pause).Err() + }) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + client.ForEachNode(func(client *redis.Client) error { + Eventually(func() error { + return client.Ping().Err() + }, 2*pause).ShouldNot(HaveOccurred()) + return nil + }) + }) + + testTimeout() + }) +}) + +//------------------------------------------------------------------------------ + +func BenchmarkRedisClusterPing(b *testing.B) { + if testing.Short() { + b.Skip("skipping in short mode") + } + + cluster := &clusterScenario{ + ports: []string{"8220", "8221", "8222", "8223", "8224", "8225"}, + nodeIds: make([]string, 6), + processes: make(map[string]*redisProcess, 6), + clients: make(map[string]*redis.Client, 6), + } + + if err := startCluster(cluster); err != nil { + b.Fatal(err) + } + defer stopCluster(cluster) + + client := cluster.clusterClient(redisClusterOptions()) + defer client.Close() + + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if err := client.Ping().Err(); err != nil { + b.Fatal(err) + } + } + }) +} + +func BenchmarkRedisClusterSetString(b *testing.B) { + if testing.Short() { + b.Skip("skipping in short mode") + } + + cluster := &clusterScenario{ + ports: []string{"8220", "8221", "8222", "8223", "8224", "8225"}, + nodeIds: make([]string, 6), + processes: make(map[string]*redisProcess, 6), + clients: make(map[string]*redis.Client, 6), + } + + if err := startCluster(cluster); err != nil { + b.Fatal(err) + } + defer stopCluster(cluster) + + client := cluster.clusterClient(redisClusterOptions()) + defer client.Close() + + value := string(bytes.Repeat([]byte{'1'}, 10000)) + + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if err := client.Set("key", value, 0).Err(); err != nil { + b.Fatal(err) + } + } + }) +} diff --git a/vendor/github.com/go-redis/redis/command.go b/vendor/github.com/go-redis/redis/command.go new file mode 100644 index 0000000..601a288 --- /dev/null +++ b/vendor/github.com/go-redis/redis/command.go @@ -0,0 +1,1024 @@ +package redis + +import ( + "bytes" + "fmt" + "strconv" + "strings" + "time" + + "github.com/go-redis/redis/internal" + "github.com/go-redis/redis/internal/pool" + "github.com/go-redis/redis/internal/proto" +) + +type Cmder interface { + Name() string + Args() []interface{} + stringArg(int) string + + readReply(*pool.Conn) error + setErr(error) + + readTimeout() *time.Duration + + Err() error + fmt.Stringer +} + +func setCmdsErr(cmds []Cmder, e error) { + for _, cmd := range cmds { + if cmd.Err() == nil { + cmd.setErr(e) + } + } +} + +func firstCmdsErr(cmds []Cmder) error { + for _, cmd := range cmds { + if err := cmd.Err(); err != nil { + return err + } + } + return nil +} + +func writeCmd(cn *pool.Conn, cmds ...Cmder) error { + cn.Wb.Reset() + for _, cmd := range cmds { + if err := cn.Wb.Append(cmd.Args()); err != nil { + return err + } + } + + _, err := cn.Write(cn.Wb.Bytes()) + return err +} + +func cmdString(cmd Cmder, val interface{}) string { + var ss []string + for _, arg := range cmd.Args() { + ss = append(ss, fmt.Sprint(arg)) + } + s := strings.Join(ss, " ") + if err := cmd.Err(); err != nil { + return s + ": " + err.Error() + } + if val != nil { + switch vv := val.(type) { + case []byte: + return s + ": " + string(vv) + default: + return s + ": " + fmt.Sprint(val) + } + } + return s + +} + +func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int { + switch cmd.Name() { + case "eval", "evalsha": + if cmd.stringArg(2) != "0" { + return 3 + } else { + return -1 + } + case "publish": + return 1 + } + if info == nil { + return -1 + } + return int(info.FirstKeyPos) +} + +//------------------------------------------------------------------------------ + +type baseCmd struct { + _args []interface{} + err error + + _readTimeout *time.Duration +} + +var _ Cmder = (*Cmd)(nil) + +func (cmd *baseCmd) Err() error { + return cmd.err +} + +func (cmd *baseCmd) Args() []interface{} { + return cmd._args +} + +func (cmd *baseCmd) stringArg(pos int) string { + if pos < 0 || pos >= len(cmd._args) { + return "" + } + s, _ := cmd._args[pos].(string) + return s +} + +func (cmd *baseCmd) Name() string { + if len(cmd._args) > 0 { + // Cmd name must be lower cased. + s := internal.ToLower(cmd.stringArg(0)) + cmd._args[0] = s + return s + } + return "" +} + +func (cmd *baseCmd) readTimeout() *time.Duration { + return cmd._readTimeout +} + +func (cmd *baseCmd) setReadTimeout(d time.Duration) { + cmd._readTimeout = &d +} + +func (cmd *baseCmd) setErr(e error) { + cmd.err = e +} + +//------------------------------------------------------------------------------ + +type Cmd struct { + baseCmd + + val interface{} +} + +func NewCmd(args ...interface{}) *Cmd { + return &Cmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *Cmd) Val() interface{} { + return cmd.val +} + +func (cmd *Cmd) Result() (interface{}, error) { + return cmd.val, cmd.err +} + +func (cmd *Cmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *Cmd) readReply(cn *pool.Conn) error { + cmd.val, cmd.err = cn.Rd.ReadReply(sliceParser) + if cmd.err != nil { + return cmd.err + } + if b, ok := cmd.val.([]byte); ok { + // Bytes must be copied, because underlying memory is reused. + cmd.val = string(b) + } + return nil +} + +//------------------------------------------------------------------------------ + +type SliceCmd struct { + baseCmd + + val []interface{} +} + +var _ Cmder = (*SliceCmd)(nil) + +func NewSliceCmd(args ...interface{}) *SliceCmd { + return &SliceCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *SliceCmd) Val() []interface{} { + return cmd.val +} + +func (cmd *SliceCmd) Result() ([]interface{}, error) { + return cmd.val, cmd.err +} + +func (cmd *SliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *SliceCmd) readReply(cn *pool.Conn) error { + var v interface{} + v, cmd.err = cn.Rd.ReadArrayReply(sliceParser) + if cmd.err != nil { + return cmd.err + } + cmd.val = v.([]interface{}) + return nil +} + +//------------------------------------------------------------------------------ + +type StatusCmd struct { + baseCmd + + val string +} + +var _ Cmder = (*StatusCmd)(nil) + +func NewStatusCmd(args ...interface{}) *StatusCmd { + return &StatusCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *StatusCmd) Val() string { + return cmd.val +} + +func (cmd *StatusCmd) Result() (string, error) { + return cmd.val, cmd.err +} + +func (cmd *StatusCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StatusCmd) readReply(cn *pool.Conn) error { + cmd.val, cmd.err = cn.Rd.ReadStringReply() + return cmd.err +} + +//------------------------------------------------------------------------------ + +type IntCmd struct { + baseCmd + + val int64 +} + +var _ Cmder = (*IntCmd)(nil) + +func NewIntCmd(args ...interface{}) *IntCmd { + return &IntCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *IntCmd) Val() int64 { + return cmd.val +} + +func (cmd *IntCmd) Result() (int64, error) { + return cmd.val, cmd.err +} + +func (cmd *IntCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *IntCmd) readReply(cn *pool.Conn) error { + cmd.val, cmd.err = cn.Rd.ReadIntReply() + return cmd.err +} + +//------------------------------------------------------------------------------ + +type DurationCmd struct { + baseCmd + + val time.Duration + precision time.Duration +} + +var _ Cmder = (*DurationCmd)(nil) + +func NewDurationCmd(precision time.Duration, args ...interface{}) *DurationCmd { + return &DurationCmd{ + baseCmd: baseCmd{_args: args}, + precision: precision, + } +} + +func (cmd *DurationCmd) Val() time.Duration { + return cmd.val +} + +func (cmd *DurationCmd) Result() (time.Duration, error) { + return cmd.val, cmd.err +} + +func (cmd *DurationCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *DurationCmd) readReply(cn *pool.Conn) error { + var n int64 + n, cmd.err = cn.Rd.ReadIntReply() + if cmd.err != nil { + return cmd.err + } + cmd.val = time.Duration(n) * cmd.precision + return nil +} + +//------------------------------------------------------------------------------ + +type TimeCmd struct { + baseCmd + + val time.Time +} + +var _ Cmder = (*TimeCmd)(nil) + +func NewTimeCmd(args ...interface{}) *TimeCmd { + return &TimeCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *TimeCmd) Val() time.Time { + return cmd.val +} + +func (cmd *TimeCmd) Result() (time.Time, error) { + return cmd.val, cmd.err +} + +func (cmd *TimeCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *TimeCmd) readReply(cn *pool.Conn) error { + var v interface{} + v, cmd.err = cn.Rd.ReadArrayReply(timeParser) + if cmd.err != nil { + return cmd.err + } + cmd.val = v.(time.Time) + return nil +} + +//------------------------------------------------------------------------------ + +type BoolCmd struct { + baseCmd + + val bool +} + +var _ Cmder = (*BoolCmd)(nil) + +func NewBoolCmd(args ...interface{}) *BoolCmd { + return &BoolCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *BoolCmd) Val() bool { + return cmd.val +} + +func (cmd *BoolCmd) Result() (bool, error) { + return cmd.val, cmd.err +} + +func (cmd *BoolCmd) String() string { + return cmdString(cmd, cmd.val) +} + +var ok = []byte("OK") + +func (cmd *BoolCmd) readReply(cn *pool.Conn) error { + var v interface{} + v, cmd.err = cn.Rd.ReadReply(nil) + // `SET key value NX` returns nil when key already exists. But + // `SETNX key value` returns bool (0/1). So convert nil to bool. + // TODO: is this okay? + if cmd.err == Nil { + cmd.val = false + cmd.err = nil + return nil + } + if cmd.err != nil { + return cmd.err + } + switch v := v.(type) { + case int64: + cmd.val = v == 1 + return nil + case []byte: + cmd.val = bytes.Equal(v, ok) + return nil + default: + cmd.err = fmt.Errorf("got %T, wanted int64 or string", v) + return cmd.err + } +} + +//------------------------------------------------------------------------------ + +type StringCmd struct { + baseCmd + + val []byte +} + +var _ Cmder = (*StringCmd)(nil) + +func NewStringCmd(args ...interface{}) *StringCmd { + return &StringCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *StringCmd) Val() string { + return internal.BytesToString(cmd.val) +} + +func (cmd *StringCmd) Result() (string, error) { + return cmd.Val(), cmd.err +} + +func (cmd *StringCmd) Bytes() ([]byte, error) { + return cmd.val, cmd.err +} + +func (cmd *StringCmd) Int64() (int64, error) { + if cmd.err != nil { + return 0, cmd.err + } + return strconv.ParseInt(cmd.Val(), 10, 64) +} + +func (cmd *StringCmd) Uint64() (uint64, error) { + if cmd.err != nil { + return 0, cmd.err + } + return strconv.ParseUint(cmd.Val(), 10, 64) +} + +func (cmd *StringCmd) Float64() (float64, error) { + if cmd.err != nil { + return 0, cmd.err + } + return strconv.ParseFloat(cmd.Val(), 64) +} + +func (cmd *StringCmd) Scan(val interface{}) error { + if cmd.err != nil { + return cmd.err + } + return proto.Scan(cmd.val, val) +} + +func (cmd *StringCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StringCmd) readReply(cn *pool.Conn) error { + cmd.val, cmd.err = cn.Rd.ReadBytesReply() + return cmd.err +} + +//------------------------------------------------------------------------------ + +type FloatCmd struct { + baseCmd + + val float64 +} + +var _ Cmder = (*FloatCmd)(nil) + +func NewFloatCmd(args ...interface{}) *FloatCmd { + return &FloatCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *FloatCmd) Val() float64 { + return cmd.val +} + +func (cmd *FloatCmd) Result() (float64, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *FloatCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *FloatCmd) readReply(cn *pool.Conn) error { + cmd.val, cmd.err = cn.Rd.ReadFloatReply() + return cmd.err +} + +//------------------------------------------------------------------------------ + +type StringSliceCmd struct { + baseCmd + + val []string +} + +var _ Cmder = (*StringSliceCmd)(nil) + +func NewStringSliceCmd(args ...interface{}) *StringSliceCmd { + return &StringSliceCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *StringSliceCmd) Val() []string { + return cmd.val +} + +func (cmd *StringSliceCmd) Result() ([]string, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *StringSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StringSliceCmd) ScanSlice(container interface{}) error { + return proto.ScanSlice(cmd.Val(), container) +} + +func (cmd *StringSliceCmd) readReply(cn *pool.Conn) error { + var v interface{} + v, cmd.err = cn.Rd.ReadArrayReply(stringSliceParser) + if cmd.err != nil { + return cmd.err + } + cmd.val = v.([]string) + return nil +} + +//------------------------------------------------------------------------------ + +type BoolSliceCmd struct { + baseCmd + + val []bool +} + +var _ Cmder = (*BoolSliceCmd)(nil) + +func NewBoolSliceCmd(args ...interface{}) *BoolSliceCmd { + return &BoolSliceCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *BoolSliceCmd) Val() []bool { + return cmd.val +} + +func (cmd *BoolSliceCmd) Result() ([]bool, error) { + return cmd.val, cmd.err +} + +func (cmd *BoolSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *BoolSliceCmd) readReply(cn *pool.Conn) error { + var v interface{} + v, cmd.err = cn.Rd.ReadArrayReply(boolSliceParser) + if cmd.err != nil { + return cmd.err + } + cmd.val = v.([]bool) + return nil +} + +//------------------------------------------------------------------------------ + +type StringStringMapCmd struct { + baseCmd + + val map[string]string +} + +var _ Cmder = (*StringStringMapCmd)(nil) + +func NewStringStringMapCmd(args ...interface{}) *StringStringMapCmd { + return &StringStringMapCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *StringStringMapCmd) Val() map[string]string { + return cmd.val +} + +func (cmd *StringStringMapCmd) Result() (map[string]string, error) { + return cmd.val, cmd.err +} + +func (cmd *StringStringMapCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StringStringMapCmd) readReply(cn *pool.Conn) error { + var v interface{} + v, cmd.err = cn.Rd.ReadArrayReply(stringStringMapParser) + if cmd.err != nil { + return cmd.err + } + cmd.val = v.(map[string]string) + return nil +} + +//------------------------------------------------------------------------------ + +type StringIntMapCmd struct { + baseCmd + + val map[string]int64 +} + +var _ Cmder = (*StringIntMapCmd)(nil) + +func NewStringIntMapCmd(args ...interface{}) *StringIntMapCmd { + return &StringIntMapCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *StringIntMapCmd) Val() map[string]int64 { + return cmd.val +} + +func (cmd *StringIntMapCmd) Result() (map[string]int64, error) { + return cmd.val, cmd.err +} + +func (cmd *StringIntMapCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StringIntMapCmd) readReply(cn *pool.Conn) error { + var v interface{} + v, cmd.err = cn.Rd.ReadArrayReply(stringIntMapParser) + if cmd.err != nil { + return cmd.err + } + cmd.val = v.(map[string]int64) + return nil +} + +//------------------------------------------------------------------------------ + +type StringStructMapCmd struct { + baseCmd + + val map[string]struct{} +} + +var _ Cmder = (*StringStructMapCmd)(nil) + +func NewStringStructMapCmd(args ...interface{}) *StringStructMapCmd { + return &StringStructMapCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *StringStructMapCmd) Val() map[string]struct{} { + return cmd.val +} + +func (cmd *StringStructMapCmd) Result() (map[string]struct{}, error) { + return cmd.val, cmd.err +} + +func (cmd *StringStructMapCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StringStructMapCmd) readReply(cn *pool.Conn) error { + var v interface{} + v, cmd.err = cn.Rd.ReadArrayReply(stringStructMapParser) + if cmd.err != nil { + return cmd.err + } + cmd.val = v.(map[string]struct{}) + return nil +} + +//------------------------------------------------------------------------------ + +type ZSliceCmd struct { + baseCmd + + val []Z +} + +var _ Cmder = (*ZSliceCmd)(nil) + +func NewZSliceCmd(args ...interface{}) *ZSliceCmd { + return &ZSliceCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *ZSliceCmd) Val() []Z { + return cmd.val +} + +func (cmd *ZSliceCmd) Result() ([]Z, error) { + return cmd.val, cmd.err +} + +func (cmd *ZSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *ZSliceCmd) readReply(cn *pool.Conn) error { + var v interface{} + v, cmd.err = cn.Rd.ReadArrayReply(zSliceParser) + if cmd.err != nil { + return cmd.err + } + cmd.val = v.([]Z) + return nil +} + +//------------------------------------------------------------------------------ + +type ScanCmd struct { + baseCmd + + page []string + cursor uint64 + + process func(cmd Cmder) error +} + +var _ Cmder = (*ScanCmd)(nil) + +func NewScanCmd(process func(cmd Cmder) error, args ...interface{}) *ScanCmd { + return &ScanCmd{ + baseCmd: baseCmd{_args: args}, + process: process, + } +} + +func (cmd *ScanCmd) Val() (keys []string, cursor uint64) { + return cmd.page, cmd.cursor +} + +func (cmd *ScanCmd) Result() (keys []string, cursor uint64, err error) { + return cmd.page, cmd.cursor, cmd.err +} + +func (cmd *ScanCmd) String() string { + return cmdString(cmd, cmd.page) +} + +func (cmd *ScanCmd) readReply(cn *pool.Conn) error { + cmd.page, cmd.cursor, cmd.err = cn.Rd.ReadScanReply() + return cmd.err +} + +// Iterator creates a new ScanIterator. +func (cmd *ScanCmd) Iterator() *ScanIterator { + return &ScanIterator{ + cmd: cmd, + } +} + +//------------------------------------------------------------------------------ + +type ClusterNode struct { + Id string + Addr string +} + +type ClusterSlot struct { + Start int + End int + Nodes []ClusterNode +} + +type ClusterSlotsCmd struct { + baseCmd + + val []ClusterSlot +} + +var _ Cmder = (*ClusterSlotsCmd)(nil) + +func NewClusterSlotsCmd(args ...interface{}) *ClusterSlotsCmd { + return &ClusterSlotsCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *ClusterSlotsCmd) Val() []ClusterSlot { + return cmd.val +} + +func (cmd *ClusterSlotsCmd) Result() ([]ClusterSlot, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *ClusterSlotsCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *ClusterSlotsCmd) readReply(cn *pool.Conn) error { + var v interface{} + v, cmd.err = cn.Rd.ReadArrayReply(clusterSlotsParser) + if cmd.err != nil { + return cmd.err + } + cmd.val = v.([]ClusterSlot) + return nil +} + +//------------------------------------------------------------------------------ + +// GeoLocation is used with GeoAdd to add geospatial location. +type GeoLocation struct { + Name string + Longitude, Latitude, Dist float64 + GeoHash int64 +} + +// GeoRadiusQuery is used with GeoRadius to query geospatial index. +type GeoRadiusQuery struct { + Radius float64 + // Can be m, km, ft, or mi. Default is km. + Unit string + WithCoord bool + WithDist bool + WithGeoHash bool + Count int + // Can be ASC or DESC. Default is no sort order. + Sort string + Store string + StoreDist string +} + +type GeoLocationCmd struct { + baseCmd + + q *GeoRadiusQuery + locations []GeoLocation +} + +var _ Cmder = (*GeoLocationCmd)(nil) + +func NewGeoLocationCmd(q *GeoRadiusQuery, args ...interface{}) *GeoLocationCmd { + args = append(args, q.Radius) + if q.Unit != "" { + args = append(args, q.Unit) + } else { + args = append(args, "km") + } + if q.WithCoord { + args = append(args, "withcoord") + } + if q.WithDist { + args = append(args, "withdist") + } + if q.WithGeoHash { + args = append(args, "withhash") + } + if q.Count > 0 { + args = append(args, "count", q.Count) + } + if q.Sort != "" { + args = append(args, q.Sort) + } + if q.Store != "" { + args = append(args, "store") + args = append(args, q.Store) + } + if q.StoreDist != "" { + args = append(args, "storedist") + args = append(args, q.StoreDist) + } + return &GeoLocationCmd{ + baseCmd: baseCmd{_args: args}, + q: q, + } +} + +func (cmd *GeoLocationCmd) Val() []GeoLocation { + return cmd.locations +} + +func (cmd *GeoLocationCmd) Result() ([]GeoLocation, error) { + return cmd.locations, cmd.err +} + +func (cmd *GeoLocationCmd) String() string { + return cmdString(cmd, cmd.locations) +} + +func (cmd *GeoLocationCmd) readReply(cn *pool.Conn) error { + var v interface{} + v, cmd.err = cn.Rd.ReadArrayReply(newGeoLocationSliceParser(cmd.q)) + if cmd.err != nil { + return cmd.err + } + cmd.locations = v.([]GeoLocation) + return nil +} + +//------------------------------------------------------------------------------ + +type GeoPos struct { + Longitude, Latitude float64 +} + +type GeoPosCmd struct { + baseCmd + + positions []*GeoPos +} + +var _ Cmder = (*GeoPosCmd)(nil) + +func NewGeoPosCmd(args ...interface{}) *GeoPosCmd { + return &GeoPosCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *GeoPosCmd) Val() []*GeoPos { + return cmd.positions +} + +func (cmd *GeoPosCmd) Result() ([]*GeoPos, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *GeoPosCmd) String() string { + return cmdString(cmd, cmd.positions) +} + +func (cmd *GeoPosCmd) readReply(cn *pool.Conn) error { + var v interface{} + v, cmd.err = cn.Rd.ReadArrayReply(geoPosSliceParser) + if cmd.err != nil { + return cmd.err + } + cmd.positions = v.([]*GeoPos) + return nil +} + +//------------------------------------------------------------------------------ + +type CommandInfo struct { + Name string + Arity int8 + Flags []string + FirstKeyPos int8 + LastKeyPos int8 + StepCount int8 + ReadOnly bool +} + +type CommandsInfoCmd struct { + baseCmd + + val map[string]*CommandInfo +} + +var _ Cmder = (*CommandsInfoCmd)(nil) + +func NewCommandsInfoCmd(args ...interface{}) *CommandsInfoCmd { + return &CommandsInfoCmd{ + baseCmd: baseCmd{_args: args}, + } +} + +func (cmd *CommandsInfoCmd) Val() map[string]*CommandInfo { + return cmd.val +} + +func (cmd *CommandsInfoCmd) Result() (map[string]*CommandInfo, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *CommandsInfoCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *CommandsInfoCmd) readReply(cn *pool.Conn) error { + var v interface{} + v, cmd.err = cn.Rd.ReadArrayReply(commandInfoSliceParser) + if cmd.err != nil { + return cmd.err + } + cmd.val = v.(map[string]*CommandInfo) + return nil +} diff --git a/vendor/github.com/go-redis/redis/command_test.go b/vendor/github.com/go-redis/redis/command_test.go new file mode 100644 index 0000000..e42375e --- /dev/null +++ b/vendor/github.com/go-redis/redis/command_test.go @@ -0,0 +1,60 @@ +package redis_test + +import ( + "github.com/go-redis/redis" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Cmd", func() { + var client *redis.Client + + BeforeEach(func() { + client = redis.NewClient(redisOptions()) + Expect(client.FlushDB().Err()).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + It("implements Stringer", func() { + set := client.Set("foo", "bar", 0) + Expect(set.String()).To(Equal("set foo bar: OK")) + + get := client.Get("foo") + Expect(get.String()).To(Equal("get foo: bar")) + }) + + It("has val/err", func() { + set := client.Set("key", "hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + get := client.Get("key") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("hello")) + + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + }) + + It("has helpers", func() { + set := client.Set("key", "10", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + + n, err := client.Get("key").Int64() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(10))) + + un, err := client.Get("key").Uint64() + Expect(err).NotTo(HaveOccurred()) + Expect(un).To(Equal(uint64(10))) + + f, err := client.Get("key").Float64() + Expect(err).NotTo(HaveOccurred()) + Expect(f).To(Equal(float64(10))) + }) + +}) diff --git a/vendor/github.com/go-redis/redis/commands.go b/vendor/github.com/go-redis/redis/commands.go new file mode 100644 index 0000000..aa98aa7 --- /dev/null +++ b/vendor/github.com/go-redis/redis/commands.go @@ -0,0 +1,2146 @@ +package redis + +import ( + "io" + "time" + + "github.com/go-redis/redis/internal" +) + +func readTimeout(timeout time.Duration) time.Duration { + if timeout == 0 { + return 0 + } + return timeout + 10*time.Second +} + +func usePrecise(dur time.Duration) bool { + return dur < time.Second || dur%time.Second != 0 +} + +func formatMs(dur time.Duration) int64 { + if dur > 0 && dur < time.Millisecond { + internal.Logf( + "specified duration is %s, but minimal supported value is %s", + dur, time.Millisecond, + ) + } + return int64(dur / time.Millisecond) +} + +func formatSec(dur time.Duration) int64 { + if dur > 0 && dur < time.Second { + internal.Logf( + "specified duration is %s, but minimal supported value is %s", + dur, time.Second, + ) + } + return int64(dur / time.Second) +} + +type Cmdable interface { + Pipeline() Pipeliner + Pipelined(fn func(Pipeliner) error) ([]Cmder, error) + + TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) + TxPipeline() Pipeliner + + ClientGetName() *StringCmd + Echo(message interface{}) *StringCmd + Ping() *StatusCmd + Quit() *StatusCmd + Del(keys ...string) *IntCmd + Unlink(keys ...string) *IntCmd + Dump(key string) *StringCmd + Exists(keys ...string) *IntCmd + Expire(key string, expiration time.Duration) *BoolCmd + ExpireAt(key string, tm time.Time) *BoolCmd + Keys(pattern string) *StringSliceCmd + Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd + Move(key string, db int64) *BoolCmd + ObjectRefCount(key string) *IntCmd + ObjectEncoding(key string) *StringCmd + ObjectIdleTime(key string) *DurationCmd + Persist(key string) *BoolCmd + PExpire(key string, expiration time.Duration) *BoolCmd + PExpireAt(key string, tm time.Time) *BoolCmd + PTTL(key string) *DurationCmd + RandomKey() *StringCmd + Rename(key, newkey string) *StatusCmd + RenameNX(key, newkey string) *BoolCmd + Restore(key string, ttl time.Duration, value string) *StatusCmd + RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd + Sort(key string, sort Sort) *StringSliceCmd + SortInterfaces(key string, sort Sort) *SliceCmd + TTL(key string) *DurationCmd + Type(key string) *StatusCmd + Scan(cursor uint64, match string, count int64) *ScanCmd + SScan(key string, cursor uint64, match string, count int64) *ScanCmd + HScan(key string, cursor uint64, match string, count int64) *ScanCmd + ZScan(key string, cursor uint64, match string, count int64) *ScanCmd + Append(key, value string) *IntCmd + BitCount(key string, bitCount *BitCount) *IntCmd + BitOpAnd(destKey string, keys ...string) *IntCmd + BitOpOr(destKey string, keys ...string) *IntCmd + BitOpXor(destKey string, keys ...string) *IntCmd + BitOpNot(destKey string, key string) *IntCmd + BitPos(key string, bit int64, pos ...int64) *IntCmd + Decr(key string) *IntCmd + DecrBy(key string, decrement int64) *IntCmd + Get(key string) *StringCmd + GetBit(key string, offset int64) *IntCmd + GetRange(key string, start, end int64) *StringCmd + GetSet(key string, value interface{}) *StringCmd + Incr(key string) *IntCmd + IncrBy(key string, value int64) *IntCmd + IncrByFloat(key string, value float64) *FloatCmd + MGet(keys ...string) *SliceCmd + MSet(pairs ...interface{}) *StatusCmd + MSetNX(pairs ...interface{}) *BoolCmd + Set(key string, value interface{}, expiration time.Duration) *StatusCmd + SetBit(key string, offset int64, value int) *IntCmd + SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd + SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd + SetRange(key string, offset int64, value string) *IntCmd + StrLen(key string) *IntCmd + HDel(key string, fields ...string) *IntCmd + HExists(key, field string) *BoolCmd + HGet(key, field string) *StringCmd + HGetAll(key string) *StringStringMapCmd + HIncrBy(key, field string, incr int64) *IntCmd + HIncrByFloat(key, field string, incr float64) *FloatCmd + HKeys(key string) *StringSliceCmd + HLen(key string) *IntCmd + HMGet(key string, fields ...string) *SliceCmd + HMSet(key string, fields map[string]interface{}) *StatusCmd + HSet(key, field string, value interface{}) *BoolCmd + HSetNX(key, field string, value interface{}) *BoolCmd + HVals(key string) *StringSliceCmd + BLPop(timeout time.Duration, keys ...string) *StringSliceCmd + BRPop(timeout time.Duration, keys ...string) *StringSliceCmd + BRPopLPush(source, destination string, timeout time.Duration) *StringCmd + LIndex(key string, index int64) *StringCmd + LInsert(key, op string, pivot, value interface{}) *IntCmd + LInsertBefore(key string, pivot, value interface{}) *IntCmd + LInsertAfter(key string, pivot, value interface{}) *IntCmd + LLen(key string) *IntCmd + LPop(key string) *StringCmd + LPush(key string, values ...interface{}) *IntCmd + LPushX(key string, value interface{}) *IntCmd + LRange(key string, start, stop int64) *StringSliceCmd + LRem(key string, count int64, value interface{}) *IntCmd + LSet(key string, index int64, value interface{}) *StatusCmd + LTrim(key string, start, stop int64) *StatusCmd + RPop(key string) *StringCmd + RPopLPush(source, destination string) *StringCmd + RPush(key string, values ...interface{}) *IntCmd + RPushX(key string, value interface{}) *IntCmd + SAdd(key string, members ...interface{}) *IntCmd + SCard(key string) *IntCmd + SDiff(keys ...string) *StringSliceCmd + SDiffStore(destination string, keys ...string) *IntCmd + SInter(keys ...string) *StringSliceCmd + SInterStore(destination string, keys ...string) *IntCmd + SIsMember(key string, member interface{}) *BoolCmd + SMembers(key string) *StringSliceCmd + SMembersMap(key string) *StringStructMapCmd + SMove(source, destination string, member interface{}) *BoolCmd + SPop(key string) *StringCmd + SPopN(key string, count int64) *StringSliceCmd + SRandMember(key string) *StringCmd + SRandMemberN(key string, count int64) *StringSliceCmd + SRem(key string, members ...interface{}) *IntCmd + SUnion(keys ...string) *StringSliceCmd + SUnionStore(destination string, keys ...string) *IntCmd + ZAdd(key string, members ...Z) *IntCmd + ZAddNX(key string, members ...Z) *IntCmd + ZAddXX(key string, members ...Z) *IntCmd + ZAddCh(key string, members ...Z) *IntCmd + ZAddNXCh(key string, members ...Z) *IntCmd + ZAddXXCh(key string, members ...Z) *IntCmd + ZIncr(key string, member Z) *FloatCmd + ZIncrNX(key string, member Z) *FloatCmd + ZIncrXX(key string, member Z) *FloatCmd + ZCard(key string) *IntCmd + ZCount(key, min, max string) *IntCmd + ZLexCount(key, min, max string) *IntCmd + ZIncrBy(key string, increment float64, member string) *FloatCmd + ZInterStore(destination string, store ZStore, keys ...string) *IntCmd + ZRange(key string, start, stop int64) *StringSliceCmd + ZRangeWithScores(key string, start, stop int64) *ZSliceCmd + ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd + ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd + ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd + ZRank(key, member string) *IntCmd + ZRem(key string, members ...interface{}) *IntCmd + ZRemRangeByRank(key string, start, stop int64) *IntCmd + ZRemRangeByScore(key, min, max string) *IntCmd + ZRemRangeByLex(key, min, max string) *IntCmd + ZRevRange(key string, start, stop int64) *StringSliceCmd + ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd + ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd + ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd + ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd + ZRevRank(key, member string) *IntCmd + ZScore(key, member string) *FloatCmd + ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd + PFAdd(key string, els ...interface{}) *IntCmd + PFCount(keys ...string) *IntCmd + PFMerge(dest string, keys ...string) *StatusCmd + BgRewriteAOF() *StatusCmd + BgSave() *StatusCmd + ClientKill(ipPort string) *StatusCmd + ClientList() *StringCmd + ClientPause(dur time.Duration) *BoolCmd + ConfigGet(parameter string) *SliceCmd + ConfigResetStat() *StatusCmd + ConfigSet(parameter, value string) *StatusCmd + DBSize() *IntCmd + FlushAll() *StatusCmd + FlushAllAsync() *StatusCmd + FlushDB() *StatusCmd + FlushDBAsync() *StatusCmd + Info(section ...string) *StringCmd + LastSave() *IntCmd + Save() *StatusCmd + Shutdown() *StatusCmd + ShutdownSave() *StatusCmd + ShutdownNoSave() *StatusCmd + SlaveOf(host, port string) *StatusCmd + Time() *TimeCmd + Eval(script string, keys []string, args ...interface{}) *Cmd + EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd + ScriptExists(scripts ...string) *BoolSliceCmd + ScriptFlush() *StatusCmd + ScriptKill() *StatusCmd + ScriptLoad(script string) *StringCmd + DebugObject(key string) *StringCmd + Publish(channel string, message interface{}) *IntCmd + PubSubChannels(pattern string) *StringSliceCmd + PubSubNumSub(channels ...string) *StringIntMapCmd + PubSubNumPat() *IntCmd + ClusterSlots() *ClusterSlotsCmd + ClusterNodes() *StringCmd + ClusterMeet(host, port string) *StatusCmd + ClusterForget(nodeID string) *StatusCmd + ClusterReplicate(nodeID string) *StatusCmd + ClusterResetSoft() *StatusCmd + ClusterResetHard() *StatusCmd + ClusterInfo() *StringCmd + ClusterKeySlot(key string) *IntCmd + ClusterCountFailureReports(nodeID string) *IntCmd + ClusterCountKeysInSlot(slot int) *IntCmd + ClusterDelSlots(slots ...int) *StatusCmd + ClusterDelSlotsRange(min, max int) *StatusCmd + ClusterSaveConfig() *StatusCmd + ClusterSlaves(nodeID string) *StringSliceCmd + ClusterFailover() *StatusCmd + ClusterAddSlots(slots ...int) *StatusCmd + ClusterAddSlotsRange(min, max int) *StatusCmd + GeoAdd(key string, geoLocation ...*GeoLocation) *IntCmd + GeoPos(key string, members ...string) *GeoPosCmd + GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd + GeoRadiusRO(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd + GeoRadiusByMember(key, member string, query *GeoRadiusQuery) *GeoLocationCmd + GeoRadiusByMemberRO(key, member string, query *GeoRadiusQuery) *GeoLocationCmd + GeoDist(key string, member1, member2, unit string) *FloatCmd + GeoHash(key string, members ...string) *StringSliceCmd + Command() *CommandsInfoCmd +} + +type StatefulCmdable interface { + Cmdable + Auth(password string) *StatusCmd + Select(index int) *StatusCmd + ClientSetName(name string) *BoolCmd + ReadOnly() *StatusCmd + ReadWrite() *StatusCmd +} + +var _ Cmdable = (*Client)(nil) +var _ Cmdable = (*Tx)(nil) +var _ Cmdable = (*Ring)(nil) +var _ Cmdable = (*ClusterClient)(nil) + +type cmdable struct { + process func(cmd Cmder) error +} + +func (c *cmdable) setProcessor(fn func(Cmder) error) { + c.process = fn +} + +type statefulCmdable struct { + cmdable + process func(cmd Cmder) error +} + +func (c *statefulCmdable) setProcessor(fn func(Cmder) error) { + c.process = fn + c.cmdable.setProcessor(fn) +} + +//------------------------------------------------------------------------------ + +func (c *statefulCmdable) Auth(password string) *StatusCmd { + cmd := NewStatusCmd("auth", password) + c.process(cmd) + return cmd +} + +func (c *cmdable) Echo(message interface{}) *StringCmd { + cmd := NewStringCmd("echo", message) + c.process(cmd) + return cmd +} + +func (c *cmdable) Ping() *StatusCmd { + cmd := NewStatusCmd("ping") + c.process(cmd) + return cmd +} + +func (c *cmdable) Wait(numSlaves int, timeout time.Duration) *IntCmd { + cmd := NewIntCmd("wait", numSlaves, int(timeout/time.Millisecond)) + c.process(cmd) + return cmd +} + +func (c *cmdable) Quit() *StatusCmd { + panic("not implemented") +} + +func (c *statefulCmdable) Select(index int) *StatusCmd { + cmd := NewStatusCmd("select", index) + c.process(cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c *cmdable) Del(keys ...string) *IntCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "del" + for i, key := range keys { + args[1+i] = key + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) Unlink(keys ...string) *IntCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "unlink" + for i, key := range keys { + args[1+i] = key + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) Dump(key string) *StringCmd { + cmd := NewStringCmd("dump", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) Exists(keys ...string) *IntCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "exists" + for i, key := range keys { + args[1+i] = key + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) Expire(key string, expiration time.Duration) *BoolCmd { + cmd := NewBoolCmd("expire", key, formatSec(expiration)) + c.process(cmd) + return cmd +} + +func (c *cmdable) ExpireAt(key string, tm time.Time) *BoolCmd { + cmd := NewBoolCmd("expireat", key, tm.Unix()) + c.process(cmd) + return cmd +} + +func (c *cmdable) Keys(pattern string) *StringSliceCmd { + cmd := NewStringSliceCmd("keys", pattern) + c.process(cmd) + return cmd +} + +func (c *cmdable) Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd { + cmd := NewStatusCmd( + "migrate", + host, + port, + key, + db, + formatMs(timeout), + ) + cmd.setReadTimeout(readTimeout(timeout)) + c.process(cmd) + return cmd +} + +func (c *cmdable) Move(key string, db int64) *BoolCmd { + cmd := NewBoolCmd("move", key, db) + c.process(cmd) + return cmd +} + +func (c *cmdable) ObjectRefCount(key string) *IntCmd { + cmd := NewIntCmd("object", "refcount", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) ObjectEncoding(key string) *StringCmd { + cmd := NewStringCmd("object", "encoding", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) ObjectIdleTime(key string) *DurationCmd { + cmd := NewDurationCmd(time.Second, "object", "idletime", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) Persist(key string) *BoolCmd { + cmd := NewBoolCmd("persist", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) PExpire(key string, expiration time.Duration) *BoolCmd { + cmd := NewBoolCmd("pexpire", key, formatMs(expiration)) + c.process(cmd) + return cmd +} + +func (c *cmdable) PExpireAt(key string, tm time.Time) *BoolCmd { + cmd := NewBoolCmd( + "pexpireat", + key, + tm.UnixNano()/int64(time.Millisecond), + ) + c.process(cmd) + return cmd +} + +func (c *cmdable) PTTL(key string) *DurationCmd { + cmd := NewDurationCmd(time.Millisecond, "pttl", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) RandomKey() *StringCmd { + cmd := NewStringCmd("randomkey") + c.process(cmd) + return cmd +} + +func (c *cmdable) Rename(key, newkey string) *StatusCmd { + cmd := NewStatusCmd("rename", key, newkey) + c.process(cmd) + return cmd +} + +func (c *cmdable) RenameNX(key, newkey string) *BoolCmd { + cmd := NewBoolCmd("renamenx", key, newkey) + c.process(cmd) + return cmd +} + +func (c *cmdable) Restore(key string, ttl time.Duration, value string) *StatusCmd { + cmd := NewStatusCmd( + "restore", + key, + formatMs(ttl), + value, + ) + c.process(cmd) + return cmd +} + +func (c *cmdable) RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd { + cmd := NewStatusCmd( + "restore", + key, + formatMs(ttl), + value, + "replace", + ) + c.process(cmd) + return cmd +} + +type Sort struct { + By string + Offset, Count float64 + Get []string + Order string + IsAlpha bool + Store string +} + +func (sort *Sort) args(key string) []interface{} { + args := []interface{}{"sort", key} + if sort.By != "" { + args = append(args, "by", sort.By) + } + if sort.Offset != 0 || sort.Count != 0 { + args = append(args, "limit", sort.Offset, sort.Count) + } + for _, get := range sort.Get { + args = append(args, "get", get) + } + if sort.Order != "" { + args = append(args, sort.Order) + } + if sort.IsAlpha { + args = append(args, "alpha") + } + if sort.Store != "" { + args = append(args, "store", sort.Store) + } + return args +} + +func (c *cmdable) Sort(key string, sort Sort) *StringSliceCmd { + cmd := NewStringSliceCmd(sort.args(key)...) + c.process(cmd) + return cmd +} + +func (c *cmdable) SortInterfaces(key string, sort Sort) *SliceCmd { + cmd := NewSliceCmd(sort.args(key)...) + c.process(cmd) + return cmd +} + +func (c *cmdable) TTL(key string) *DurationCmd { + cmd := NewDurationCmd(time.Second, "ttl", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) Type(key string) *StatusCmd { + cmd := NewStatusCmd("type", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) Scan(cursor uint64, match string, count int64) *ScanCmd { + args := []interface{}{"scan", cursor} + if match != "" { + args = append(args, "match", match) + } + if count > 0 { + args = append(args, "count", count) + } + cmd := NewScanCmd(c.process, args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) SScan(key string, cursor uint64, match string, count int64) *ScanCmd { + args := []interface{}{"sscan", key, cursor} + if match != "" { + args = append(args, "match", match) + } + if count > 0 { + args = append(args, "count", count) + } + cmd := NewScanCmd(c.process, args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) HScan(key string, cursor uint64, match string, count int64) *ScanCmd { + args := []interface{}{"hscan", key, cursor} + if match != "" { + args = append(args, "match", match) + } + if count > 0 { + args = append(args, "count", count) + } + cmd := NewScanCmd(c.process, args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZScan(key string, cursor uint64, match string, count int64) *ScanCmd { + args := []interface{}{"zscan", key, cursor} + if match != "" { + args = append(args, "match", match) + } + if count > 0 { + args = append(args, "count", count) + } + cmd := NewScanCmd(c.process, args...) + c.process(cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c *cmdable) Append(key, value string) *IntCmd { + cmd := NewIntCmd("append", key, value) + c.process(cmd) + return cmd +} + +type BitCount struct { + Start, End int64 +} + +func (c *cmdable) BitCount(key string, bitCount *BitCount) *IntCmd { + args := []interface{}{"bitcount", key} + if bitCount != nil { + args = append( + args, + bitCount.Start, + bitCount.End, + ) + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) bitOp(op, destKey string, keys ...string) *IntCmd { + args := make([]interface{}, 3+len(keys)) + args[0] = "bitop" + args[1] = op + args[2] = destKey + for i, key := range keys { + args[3+i] = key + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) BitOpAnd(destKey string, keys ...string) *IntCmd { + return c.bitOp("and", destKey, keys...) +} + +func (c *cmdable) BitOpOr(destKey string, keys ...string) *IntCmd { + return c.bitOp("or", destKey, keys...) +} + +func (c *cmdable) BitOpXor(destKey string, keys ...string) *IntCmd { + return c.bitOp("xor", destKey, keys...) +} + +func (c *cmdable) BitOpNot(destKey string, key string) *IntCmd { + return c.bitOp("not", destKey, key) +} + +func (c *cmdable) BitPos(key string, bit int64, pos ...int64) *IntCmd { + args := make([]interface{}, 3+len(pos)) + args[0] = "bitpos" + args[1] = key + args[2] = bit + switch len(pos) { + case 0: + case 1: + args[3] = pos[0] + case 2: + args[3] = pos[0] + args[4] = pos[1] + default: + panic("too many arguments") + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) Decr(key string) *IntCmd { + cmd := NewIntCmd("decr", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) DecrBy(key string, decrement int64) *IntCmd { + cmd := NewIntCmd("decrby", key, decrement) + c.process(cmd) + return cmd +} + +func (c *cmdable) Get(key string) *StringCmd { + cmd := NewStringCmd("get", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) GetBit(key string, offset int64) *IntCmd { + cmd := NewIntCmd("getbit", key, offset) + c.process(cmd) + return cmd +} + +func (c *cmdable) GetRange(key string, start, end int64) *StringCmd { + cmd := NewStringCmd("getrange", key, start, end) + c.process(cmd) + return cmd +} + +func (c *cmdable) GetSet(key string, value interface{}) *StringCmd { + cmd := NewStringCmd("getset", key, value) + c.process(cmd) + return cmd +} + +func (c *cmdable) Incr(key string) *IntCmd { + cmd := NewIntCmd("incr", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) IncrBy(key string, value int64) *IntCmd { + cmd := NewIntCmd("incrby", key, value) + c.process(cmd) + return cmd +} + +func (c *cmdable) IncrByFloat(key string, value float64) *FloatCmd { + cmd := NewFloatCmd("incrbyfloat", key, value) + c.process(cmd) + return cmd +} + +func (c *cmdable) MGet(keys ...string) *SliceCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "mget" + for i, key := range keys { + args[1+i] = key + } + cmd := NewSliceCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) MSet(pairs ...interface{}) *StatusCmd { + args := make([]interface{}, 1+len(pairs)) + args[0] = "mset" + for i, pair := range pairs { + args[1+i] = pair + } + cmd := NewStatusCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) MSetNX(pairs ...interface{}) *BoolCmd { + args := make([]interface{}, 1+len(pairs)) + args[0] = "msetnx" + for i, pair := range pairs { + args[1+i] = pair + } + cmd := NewBoolCmd(args...) + c.process(cmd) + return cmd +} + +// Redis `SET key value [expiration]` command. +// +// Use expiration for `SETEX`-like behavior. +// Zero expiration means the key has no expiration time. +func (c *cmdable) Set(key string, value interface{}, expiration time.Duration) *StatusCmd { + args := make([]interface{}, 3, 4) + args[0] = "set" + args[1] = key + args[2] = value + if expiration > 0 { + if usePrecise(expiration) { + args = append(args, "px", formatMs(expiration)) + } else { + args = append(args, "ex", formatSec(expiration)) + } + } + cmd := NewStatusCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) SetBit(key string, offset int64, value int) *IntCmd { + cmd := NewIntCmd( + "setbit", + key, + offset, + value, + ) + c.process(cmd) + return cmd +} + +// Redis `SET key value [expiration] NX` command. +// +// Zero expiration means the key has no expiration time. +func (c *cmdable) SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd { + var cmd *BoolCmd + if expiration == 0 { + // Use old `SETNX` to support old Redis versions. + cmd = NewBoolCmd("setnx", key, value) + } else { + if usePrecise(expiration) { + cmd = NewBoolCmd("set", key, value, "px", formatMs(expiration), "nx") + } else { + cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "nx") + } + } + c.process(cmd) + return cmd +} + +// Redis `SET key value [expiration] XX` command. +// +// Zero expiration means the key has no expiration time. +func (c *cmdable) SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd { + var cmd *BoolCmd + if expiration == 0 { + cmd = NewBoolCmd("set", key, value, "xx") + } else { + if usePrecise(expiration) { + cmd = NewBoolCmd("set", key, value, "px", formatMs(expiration), "xx") + } else { + cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "xx") + } + } + c.process(cmd) + return cmd +} + +func (c *cmdable) SetRange(key string, offset int64, value string) *IntCmd { + cmd := NewIntCmd("setrange", key, offset, value) + c.process(cmd) + return cmd +} + +func (c *cmdable) StrLen(key string) *IntCmd { + cmd := NewIntCmd("strlen", key) + c.process(cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c *cmdable) HDel(key string, fields ...string) *IntCmd { + args := make([]interface{}, 2+len(fields)) + args[0] = "hdel" + args[1] = key + for i, field := range fields { + args[2+i] = field + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) HExists(key, field string) *BoolCmd { + cmd := NewBoolCmd("hexists", key, field) + c.process(cmd) + return cmd +} + +func (c *cmdable) HGet(key, field string) *StringCmd { + cmd := NewStringCmd("hget", key, field) + c.process(cmd) + return cmd +} + +func (c *cmdable) HGetAll(key string) *StringStringMapCmd { + cmd := NewStringStringMapCmd("hgetall", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) HIncrBy(key, field string, incr int64) *IntCmd { + cmd := NewIntCmd("hincrby", key, field, incr) + c.process(cmd) + return cmd +} + +func (c *cmdable) HIncrByFloat(key, field string, incr float64) *FloatCmd { + cmd := NewFloatCmd("hincrbyfloat", key, field, incr) + c.process(cmd) + return cmd +} + +func (c *cmdable) HKeys(key string) *StringSliceCmd { + cmd := NewStringSliceCmd("hkeys", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) HLen(key string) *IntCmd { + cmd := NewIntCmd("hlen", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) HMGet(key string, fields ...string) *SliceCmd { + args := make([]interface{}, 2+len(fields)) + args[0] = "hmget" + args[1] = key + for i, field := range fields { + args[2+i] = field + } + cmd := NewSliceCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) HMSet(key string, fields map[string]interface{}) *StatusCmd { + args := make([]interface{}, 2+len(fields)*2) + args[0] = "hmset" + args[1] = key + i := 2 + for k, v := range fields { + args[i] = k + args[i+1] = v + i += 2 + } + cmd := NewStatusCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) HSet(key, field string, value interface{}) *BoolCmd { + cmd := NewBoolCmd("hset", key, field, value) + c.process(cmd) + return cmd +} + +func (c *cmdable) HSetNX(key, field string, value interface{}) *BoolCmd { + cmd := NewBoolCmd("hsetnx", key, field, value) + c.process(cmd) + return cmd +} + +func (c *cmdable) HVals(key string) *StringSliceCmd { + cmd := NewStringSliceCmd("hvals", key) + c.process(cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c *cmdable) BLPop(timeout time.Duration, keys ...string) *StringSliceCmd { + args := make([]interface{}, 1+len(keys)+1) + args[0] = "blpop" + for i, key := range keys { + args[1+i] = key + } + args[len(args)-1] = formatSec(timeout) + cmd := NewStringSliceCmd(args...) + cmd.setReadTimeout(readTimeout(timeout)) + c.process(cmd) + return cmd +} + +func (c *cmdable) BRPop(timeout time.Duration, keys ...string) *StringSliceCmd { + args := make([]interface{}, 1+len(keys)+1) + args[0] = "brpop" + for i, key := range keys { + args[1+i] = key + } + args[len(keys)+1] = formatSec(timeout) + cmd := NewStringSliceCmd(args...) + cmd.setReadTimeout(readTimeout(timeout)) + c.process(cmd) + return cmd +} + +func (c *cmdable) BRPopLPush(source, destination string, timeout time.Duration) *StringCmd { + cmd := NewStringCmd( + "brpoplpush", + source, + destination, + formatSec(timeout), + ) + cmd.setReadTimeout(readTimeout(timeout)) + c.process(cmd) + return cmd +} + +func (c *cmdable) LIndex(key string, index int64) *StringCmd { + cmd := NewStringCmd("lindex", key, index) + c.process(cmd) + return cmd +} + +func (c *cmdable) LInsert(key, op string, pivot, value interface{}) *IntCmd { + cmd := NewIntCmd("linsert", key, op, pivot, value) + c.process(cmd) + return cmd +} + +func (c *cmdable) LInsertBefore(key string, pivot, value interface{}) *IntCmd { + cmd := NewIntCmd("linsert", key, "before", pivot, value) + c.process(cmd) + return cmd +} + +func (c *cmdable) LInsertAfter(key string, pivot, value interface{}) *IntCmd { + cmd := NewIntCmd("linsert", key, "after", pivot, value) + c.process(cmd) + return cmd +} + +func (c *cmdable) LLen(key string) *IntCmd { + cmd := NewIntCmd("llen", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) LPop(key string) *StringCmd { + cmd := NewStringCmd("lpop", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) LPush(key string, values ...interface{}) *IntCmd { + args := make([]interface{}, 2+len(values)) + args[0] = "lpush" + args[1] = key + for i, value := range values { + args[2+i] = value + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) LPushX(key string, value interface{}) *IntCmd { + cmd := NewIntCmd("lpushx", key, value) + c.process(cmd) + return cmd +} + +func (c *cmdable) LRange(key string, start, stop int64) *StringSliceCmd { + cmd := NewStringSliceCmd( + "lrange", + key, + start, + stop, + ) + c.process(cmd) + return cmd +} + +func (c *cmdable) LRem(key string, count int64, value interface{}) *IntCmd { + cmd := NewIntCmd("lrem", key, count, value) + c.process(cmd) + return cmd +} + +func (c *cmdable) LSet(key string, index int64, value interface{}) *StatusCmd { + cmd := NewStatusCmd("lset", key, index, value) + c.process(cmd) + return cmd +} + +func (c *cmdable) LTrim(key string, start, stop int64) *StatusCmd { + cmd := NewStatusCmd( + "ltrim", + key, + start, + stop, + ) + c.process(cmd) + return cmd +} + +func (c *cmdable) RPop(key string) *StringCmd { + cmd := NewStringCmd("rpop", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) RPopLPush(source, destination string) *StringCmd { + cmd := NewStringCmd("rpoplpush", source, destination) + c.process(cmd) + return cmd +} + +func (c *cmdable) RPush(key string, values ...interface{}) *IntCmd { + args := make([]interface{}, 2+len(values)) + args[0] = "rpush" + args[1] = key + for i, value := range values { + args[2+i] = value + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) RPushX(key string, value interface{}) *IntCmd { + cmd := NewIntCmd("rpushx", key, value) + c.process(cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c *cmdable) SAdd(key string, members ...interface{}) *IntCmd { + args := make([]interface{}, 2+len(members)) + args[0] = "sadd" + args[1] = key + for i, member := range members { + args[2+i] = member + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) SCard(key string) *IntCmd { + cmd := NewIntCmd("scard", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) SDiff(keys ...string) *StringSliceCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "sdiff" + for i, key := range keys { + args[1+i] = key + } + cmd := NewStringSliceCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) SDiffStore(destination string, keys ...string) *IntCmd { + args := make([]interface{}, 2+len(keys)) + args[0] = "sdiffstore" + args[1] = destination + for i, key := range keys { + args[2+i] = key + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) SInter(keys ...string) *StringSliceCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "sinter" + for i, key := range keys { + args[1+i] = key + } + cmd := NewStringSliceCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) SInterStore(destination string, keys ...string) *IntCmd { + args := make([]interface{}, 2+len(keys)) + args[0] = "sinterstore" + args[1] = destination + for i, key := range keys { + args[2+i] = key + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) SIsMember(key string, member interface{}) *BoolCmd { + cmd := NewBoolCmd("sismember", key, member) + c.process(cmd) + return cmd +} + +// Redis `SMEMBERS key` command output as a slice +func (c *cmdable) SMembers(key string) *StringSliceCmd { + cmd := NewStringSliceCmd("smembers", key) + c.process(cmd) + return cmd +} + +// Redis `SMEMBERS key` command output as a map +func (c *cmdable) SMembersMap(key string) *StringStructMapCmd { + cmd := NewStringStructMapCmd("smembers", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) SMove(source, destination string, member interface{}) *BoolCmd { + cmd := NewBoolCmd("smove", source, destination, member) + c.process(cmd) + return cmd +} + +// Redis `SPOP key` command. +func (c *cmdable) SPop(key string) *StringCmd { + cmd := NewStringCmd("spop", key) + c.process(cmd) + return cmd +} + +// Redis `SPOP key count` command. +func (c *cmdable) SPopN(key string, count int64) *StringSliceCmd { + cmd := NewStringSliceCmd("spop", key, count) + c.process(cmd) + return cmd +} + +// Redis `SRANDMEMBER key` command. +func (c *cmdable) SRandMember(key string) *StringCmd { + cmd := NewStringCmd("srandmember", key) + c.process(cmd) + return cmd +} + +// Redis `SRANDMEMBER key count` command. +func (c *cmdable) SRandMemberN(key string, count int64) *StringSliceCmd { + cmd := NewStringSliceCmd("srandmember", key, count) + c.process(cmd) + return cmd +} + +func (c *cmdable) SRem(key string, members ...interface{}) *IntCmd { + args := make([]interface{}, 2+len(members)) + args[0] = "srem" + args[1] = key + for i, member := range members { + args[2+i] = member + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) SUnion(keys ...string) *StringSliceCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "sunion" + for i, key := range keys { + args[1+i] = key + } + cmd := NewStringSliceCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) SUnionStore(destination string, keys ...string) *IntCmd { + args := make([]interface{}, 2+len(keys)) + args[0] = "sunionstore" + args[1] = destination + for i, key := range keys { + args[2+i] = key + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +// Z represents sorted set member. +type Z struct { + Score float64 + Member interface{} +} + +// ZStore is used as an arg to ZInterStore and ZUnionStore. +type ZStore struct { + Weights []float64 + // Can be SUM, MIN or MAX. + Aggregate string +} + +func (c *cmdable) zAdd(a []interface{}, n int, members ...Z) *IntCmd { + for i, m := range members { + a[n+2*i] = m.Score + a[n+2*i+1] = m.Member + } + cmd := NewIntCmd(a...) + c.process(cmd) + return cmd +} + +// Redis `ZADD key score member [score member ...]` command. +func (c *cmdable) ZAdd(key string, members ...Z) *IntCmd { + const n = 2 + a := make([]interface{}, n+2*len(members)) + a[0], a[1] = "zadd", key + return c.zAdd(a, n, members...) +} + +// Redis `ZADD key NX score member [score member ...]` command. +func (c *cmdable) ZAddNX(key string, members ...Z) *IntCmd { + const n = 3 + a := make([]interface{}, n+2*len(members)) + a[0], a[1], a[2] = "zadd", key, "nx" + return c.zAdd(a, n, members...) +} + +// Redis `ZADD key XX score member [score member ...]` command. +func (c *cmdable) ZAddXX(key string, members ...Z) *IntCmd { + const n = 3 + a := make([]interface{}, n+2*len(members)) + a[0], a[1], a[2] = "zadd", key, "xx" + return c.zAdd(a, n, members...) +} + +// Redis `ZADD key CH score member [score member ...]` command. +func (c *cmdable) ZAddCh(key string, members ...Z) *IntCmd { + const n = 3 + a := make([]interface{}, n+2*len(members)) + a[0], a[1], a[2] = "zadd", key, "ch" + return c.zAdd(a, n, members...) +} + +// Redis `ZADD key NX CH score member [score member ...]` command. +func (c *cmdable) ZAddNXCh(key string, members ...Z) *IntCmd { + const n = 4 + a := make([]interface{}, n+2*len(members)) + a[0], a[1], a[2], a[3] = "zadd", key, "nx", "ch" + return c.zAdd(a, n, members...) +} + +// Redis `ZADD key XX CH score member [score member ...]` command. +func (c *cmdable) ZAddXXCh(key string, members ...Z) *IntCmd { + const n = 4 + a := make([]interface{}, n+2*len(members)) + a[0], a[1], a[2], a[3] = "zadd", key, "xx", "ch" + return c.zAdd(a, n, members...) +} + +func (c *cmdable) zIncr(a []interface{}, n int, members ...Z) *FloatCmd { + for i, m := range members { + a[n+2*i] = m.Score + a[n+2*i+1] = m.Member + } + cmd := NewFloatCmd(a...) + c.process(cmd) + return cmd +} + +// Redis `ZADD key INCR score member` command. +func (c *cmdable) ZIncr(key string, member Z) *FloatCmd { + const n = 3 + a := make([]interface{}, n+2) + a[0], a[1], a[2] = "zadd", key, "incr" + return c.zIncr(a, n, member) +} + +// Redis `ZADD key NX INCR score member` command. +func (c *cmdable) ZIncrNX(key string, member Z) *FloatCmd { + const n = 4 + a := make([]interface{}, n+2) + a[0], a[1], a[2], a[3] = "zadd", key, "incr", "nx" + return c.zIncr(a, n, member) +} + +// Redis `ZADD key XX INCR score member` command. +func (c *cmdable) ZIncrXX(key string, member Z) *FloatCmd { + const n = 4 + a := make([]interface{}, n+2) + a[0], a[1], a[2], a[3] = "zadd", key, "incr", "xx" + return c.zIncr(a, n, member) +} + +func (c *cmdable) ZCard(key string) *IntCmd { + cmd := NewIntCmd("zcard", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZCount(key, min, max string) *IntCmd { + cmd := NewIntCmd("zcount", key, min, max) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZLexCount(key, min, max string) *IntCmd { + cmd := NewIntCmd("zlexcount", key, min, max) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZIncrBy(key string, increment float64, member string) *FloatCmd { + cmd := NewFloatCmd("zincrby", key, increment, member) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZInterStore(destination string, store ZStore, keys ...string) *IntCmd { + args := make([]interface{}, 3+len(keys)) + args[0] = "zinterstore" + args[1] = destination + args[2] = len(keys) + for i, key := range keys { + args[3+i] = key + } + if len(store.Weights) > 0 { + args = append(args, "weights") + for _, weight := range store.Weights { + args = append(args, weight) + } + } + if store.Aggregate != "" { + args = append(args, "aggregate", store.Aggregate) + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) zRange(key string, start, stop int64, withScores bool) *StringSliceCmd { + args := []interface{}{ + "zrange", + key, + start, + stop, + } + if withScores { + args = append(args, "withscores") + } + cmd := NewStringSliceCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZRange(key string, start, stop int64) *StringSliceCmd { + return c.zRange(key, start, stop, false) +} + +func (c *cmdable) ZRangeWithScores(key string, start, stop int64) *ZSliceCmd { + cmd := NewZSliceCmd("zrange", key, start, stop, "withscores") + c.process(cmd) + return cmd +} + +type ZRangeBy struct { + Min, Max string + Offset, Count int64 +} + +func (c *cmdable) zRangeBy(zcmd, key string, opt ZRangeBy, withScores bool) *StringSliceCmd { + args := []interface{}{zcmd, key, opt.Min, opt.Max} + if withScores { + args = append(args, "withscores") + } + if opt.Offset != 0 || opt.Count != 0 { + args = append( + args, + "limit", + opt.Offset, + opt.Count, + ) + } + cmd := NewStringSliceCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd { + return c.zRangeBy("zrangebyscore", key, opt, false) +} + +func (c *cmdable) ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd { + return c.zRangeBy("zrangebylex", key, opt, false) +} + +func (c *cmdable) ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd { + args := []interface{}{"zrangebyscore", key, opt.Min, opt.Max, "withscores"} + if opt.Offset != 0 || opt.Count != 0 { + args = append( + args, + "limit", + opt.Offset, + opt.Count, + ) + } + cmd := NewZSliceCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZRank(key, member string) *IntCmd { + cmd := NewIntCmd("zrank", key, member) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZRem(key string, members ...interface{}) *IntCmd { + args := make([]interface{}, 2+len(members)) + args[0] = "zrem" + args[1] = key + for i, member := range members { + args[2+i] = member + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZRemRangeByRank(key string, start, stop int64) *IntCmd { + cmd := NewIntCmd( + "zremrangebyrank", + key, + start, + stop, + ) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZRemRangeByScore(key, min, max string) *IntCmd { + cmd := NewIntCmd("zremrangebyscore", key, min, max) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZRemRangeByLex(key, min, max string) *IntCmd { + cmd := NewIntCmd("zremrangebylex", key, min, max) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZRevRange(key string, start, stop int64) *StringSliceCmd { + cmd := NewStringSliceCmd("zrevrange", key, start, stop) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd { + cmd := NewZSliceCmd("zrevrange", key, start, stop, "withscores") + c.process(cmd) + return cmd +} + +func (c *cmdable) zRevRangeBy(zcmd, key string, opt ZRangeBy) *StringSliceCmd { + args := []interface{}{zcmd, key, opt.Max, opt.Min} + if opt.Offset != 0 || opt.Count != 0 { + args = append( + args, + "limit", + opt.Offset, + opt.Count, + ) + } + cmd := NewStringSliceCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd { + return c.zRevRangeBy("zrevrangebyscore", key, opt) +} + +func (c *cmdable) ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd { + return c.zRevRangeBy("zrevrangebylex", key, opt) +} + +func (c *cmdable) ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd { + args := []interface{}{"zrevrangebyscore", key, opt.Max, opt.Min, "withscores"} + if opt.Offset != 0 || opt.Count != 0 { + args = append( + args, + "limit", + opt.Offset, + opt.Count, + ) + } + cmd := NewZSliceCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZRevRank(key, member string) *IntCmd { + cmd := NewIntCmd("zrevrank", key, member) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZScore(key, member string) *FloatCmd { + cmd := NewFloatCmd("zscore", key, member) + c.process(cmd) + return cmd +} + +func (c *cmdable) ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd { + args := make([]interface{}, 3+len(keys)) + args[0] = "zunionstore" + args[1] = dest + args[2] = len(keys) + for i, key := range keys { + args[3+i] = key + } + if len(store.Weights) > 0 { + args = append(args, "weights") + for _, weight := range store.Weights { + args = append(args, weight) + } + } + if store.Aggregate != "" { + args = append(args, "aggregate", store.Aggregate) + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c *cmdable) PFAdd(key string, els ...interface{}) *IntCmd { + args := make([]interface{}, 2+len(els)) + args[0] = "pfadd" + args[1] = key + for i, el := range els { + args[2+i] = el + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) PFCount(keys ...string) *IntCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "pfcount" + for i, key := range keys { + args[1+i] = key + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) PFMerge(dest string, keys ...string) *StatusCmd { + args := make([]interface{}, 2+len(keys)) + args[0] = "pfmerge" + args[1] = dest + for i, key := range keys { + args[2+i] = key + } + cmd := NewStatusCmd(args...) + c.process(cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c *cmdable) BgRewriteAOF() *StatusCmd { + cmd := NewStatusCmd("bgrewriteaof") + c.process(cmd) + return cmd +} + +func (c *cmdable) BgSave() *StatusCmd { + cmd := NewStatusCmd("bgsave") + c.process(cmd) + return cmd +} + +func (c *cmdable) ClientKill(ipPort string) *StatusCmd { + cmd := NewStatusCmd("client", "kill", ipPort) + c.process(cmd) + return cmd +} + +func (c *cmdable) ClientList() *StringCmd { + cmd := NewStringCmd("client", "list") + c.process(cmd) + return cmd +} + +func (c *cmdable) ClientPause(dur time.Duration) *BoolCmd { + cmd := NewBoolCmd("client", "pause", formatMs(dur)) + c.process(cmd) + return cmd +} + +// ClientSetName assigns a name to the connection. +func (c *statefulCmdable) ClientSetName(name string) *BoolCmd { + cmd := NewBoolCmd("client", "setname", name) + c.process(cmd) + return cmd +} + +// ClientGetName returns the name of the connection. +func (c *cmdable) ClientGetName() *StringCmd { + cmd := NewStringCmd("client", "getname") + c.process(cmd) + return cmd +} + +func (c *cmdable) ConfigGet(parameter string) *SliceCmd { + cmd := NewSliceCmd("config", "get", parameter) + c.process(cmd) + return cmd +} + +func (c *cmdable) ConfigResetStat() *StatusCmd { + cmd := NewStatusCmd("config", "resetstat") + c.process(cmd) + return cmd +} + +func (c *cmdable) ConfigSet(parameter, value string) *StatusCmd { + cmd := NewStatusCmd("config", "set", parameter, value) + c.process(cmd) + return cmd +} + +// Deperecated. Use DBSize instead. +func (c *cmdable) DbSize() *IntCmd { + return c.DBSize() +} + +func (c *cmdable) DBSize() *IntCmd { + cmd := NewIntCmd("dbsize") + c.process(cmd) + return cmd +} + +func (c *cmdable) FlushAll() *StatusCmd { + cmd := NewStatusCmd("flushall") + c.process(cmd) + return cmd +} + +func (c *cmdable) FlushAllAsync() *StatusCmd { + cmd := NewStatusCmd("flushall", "async") + c.process(cmd) + return cmd +} + +// Deprecated. Use FlushDB instead. +func (c *cmdable) FlushDb() *StatusCmd { + return c.FlushDB() +} + +func (c *cmdable) FlushDB() *StatusCmd { + cmd := NewStatusCmd("flushdb") + c.process(cmd) + return cmd +} + +func (c *cmdable) FlushDBAsync() *StatusCmd { + cmd := NewStatusCmd("flushdb", "async") + c.process(cmd) + return cmd +} + +func (c *cmdable) Info(section ...string) *StringCmd { + args := []interface{}{"info"} + if len(section) > 0 { + args = append(args, section[0]) + } + cmd := NewStringCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) LastSave() *IntCmd { + cmd := NewIntCmd("lastsave") + c.process(cmd) + return cmd +} + +func (c *cmdable) Save() *StatusCmd { + cmd := NewStatusCmd("save") + c.process(cmd) + return cmd +} + +func (c *cmdable) shutdown(modifier string) *StatusCmd { + var args []interface{} + if modifier == "" { + args = []interface{}{"shutdown"} + } else { + args = []interface{}{"shutdown", modifier} + } + cmd := NewStatusCmd(args...) + c.process(cmd) + if err := cmd.Err(); err != nil { + if err == io.EOF { + // Server quit as expected. + cmd.err = nil + } + } else { + // Server did not quit. String reply contains the reason. + cmd.err = internal.RedisError(cmd.val) + cmd.val = "" + } + return cmd +} + +func (c *cmdable) Shutdown() *StatusCmd { + return c.shutdown("") +} + +func (c *cmdable) ShutdownSave() *StatusCmd { + return c.shutdown("save") +} + +func (c *cmdable) ShutdownNoSave() *StatusCmd { + return c.shutdown("nosave") +} + +func (c *cmdable) SlaveOf(host, port string) *StatusCmd { + cmd := NewStatusCmd("slaveof", host, port) + c.process(cmd) + return cmd +} + +func (c *cmdable) SlowLog() { + panic("not implemented") +} + +func (c *cmdable) Sync() { + panic("not implemented") +} + +func (c *cmdable) Time() *TimeCmd { + cmd := NewTimeCmd("time") + c.process(cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c *cmdable) Eval(script string, keys []string, args ...interface{}) *Cmd { + cmdArgs := make([]interface{}, 3+len(keys)+len(args)) + cmdArgs[0] = "eval" + cmdArgs[1] = script + cmdArgs[2] = len(keys) + for i, key := range keys { + cmdArgs[3+i] = key + } + pos := 3 + len(keys) + for i, arg := range args { + cmdArgs[pos+i] = arg + } + cmd := NewCmd(cmdArgs...) + c.process(cmd) + return cmd +} + +func (c *cmdable) EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd { + cmdArgs := make([]interface{}, 3+len(keys)+len(args)) + cmdArgs[0] = "evalsha" + cmdArgs[1] = sha1 + cmdArgs[2] = len(keys) + for i, key := range keys { + cmdArgs[3+i] = key + } + pos := 3 + len(keys) + for i, arg := range args { + cmdArgs[pos+i] = arg + } + cmd := NewCmd(cmdArgs...) + c.process(cmd) + return cmd +} + +func (c *cmdable) ScriptExists(scripts ...string) *BoolSliceCmd { + args := make([]interface{}, 2+len(scripts)) + args[0] = "script" + args[1] = "exists" + for i, script := range scripts { + args[2+i] = script + } + cmd := NewBoolSliceCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) ScriptFlush() *StatusCmd { + cmd := NewStatusCmd("script", "flush") + c.process(cmd) + return cmd +} + +func (c *cmdable) ScriptKill() *StatusCmd { + cmd := NewStatusCmd("script", "kill") + c.process(cmd) + return cmd +} + +func (c *cmdable) ScriptLoad(script string) *StringCmd { + cmd := NewStringCmd("script", "load", script) + c.process(cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c *cmdable) DebugObject(key string) *StringCmd { + cmd := NewStringCmd("debug", "object", key) + c.process(cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +// Publish posts the message to the channel. +func (c *cmdable) Publish(channel string, message interface{}) *IntCmd { + cmd := NewIntCmd("publish", channel, message) + c.process(cmd) + return cmd +} + +func (c *cmdable) PubSubChannels(pattern string) *StringSliceCmd { + args := []interface{}{"pubsub", "channels"} + if pattern != "*" { + args = append(args, pattern) + } + cmd := NewStringSliceCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) PubSubNumSub(channels ...string) *StringIntMapCmd { + args := make([]interface{}, 2+len(channels)) + args[0] = "pubsub" + args[1] = "numsub" + for i, channel := range channels { + args[2+i] = channel + } + cmd := NewStringIntMapCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) PubSubNumPat() *IntCmd { + cmd := NewIntCmd("pubsub", "numpat") + c.process(cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c *cmdable) ClusterSlots() *ClusterSlotsCmd { + cmd := NewClusterSlotsCmd("cluster", "slots") + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterNodes() *StringCmd { + cmd := NewStringCmd("cluster", "nodes") + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterMeet(host, port string) *StatusCmd { + cmd := NewStatusCmd("cluster", "meet", host, port) + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterForget(nodeID string) *StatusCmd { + cmd := NewStatusCmd("cluster", "forget", nodeID) + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterReplicate(nodeID string) *StatusCmd { + cmd := NewStatusCmd("cluster", "replicate", nodeID) + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterResetSoft() *StatusCmd { + cmd := NewStatusCmd("cluster", "reset", "soft") + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterResetHard() *StatusCmd { + cmd := NewStatusCmd("cluster", "reset", "hard") + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterInfo() *StringCmd { + cmd := NewStringCmd("cluster", "info") + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterKeySlot(key string) *IntCmd { + cmd := NewIntCmd("cluster", "keyslot", key) + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterCountFailureReports(nodeID string) *IntCmd { + cmd := NewIntCmd("cluster", "count-failure-reports", nodeID) + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterCountKeysInSlot(slot int) *IntCmd { + cmd := NewIntCmd("cluster", "countkeysinslot", slot) + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterDelSlots(slots ...int) *StatusCmd { + args := make([]interface{}, 2+len(slots)) + args[0] = "cluster" + args[1] = "delslots" + for i, slot := range slots { + args[2+i] = slot + } + cmd := NewStatusCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterDelSlotsRange(min, max int) *StatusCmd { + size := max - min + 1 + slots := make([]int, size) + for i := 0; i < size; i++ { + slots[i] = min + i + } + return c.ClusterDelSlots(slots...) +} + +func (c *cmdable) ClusterSaveConfig() *StatusCmd { + cmd := NewStatusCmd("cluster", "saveconfig") + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterSlaves(nodeID string) *StringSliceCmd { + cmd := NewStringSliceCmd("cluster", "slaves", nodeID) + c.process(cmd) + return cmd +} + +func (c *statefulCmdable) ReadOnly() *StatusCmd { + cmd := NewStatusCmd("readonly") + c.process(cmd) + return cmd +} + +func (c *statefulCmdable) ReadWrite() *StatusCmd { + cmd := NewStatusCmd("readwrite") + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterFailover() *StatusCmd { + cmd := NewStatusCmd("cluster", "failover") + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterAddSlots(slots ...int) *StatusCmd { + args := make([]interface{}, 2+len(slots)) + args[0] = "cluster" + args[1] = "addslots" + for i, num := range slots { + args[2+i] = num + } + cmd := NewStatusCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) ClusterAddSlotsRange(min, max int) *StatusCmd { + size := max - min + 1 + slots := make([]int, size) + for i := 0; i < size; i++ { + slots[i] = min + i + } + return c.ClusterAddSlots(slots...) +} + +//------------------------------------------------------------------------------ + +func (c *cmdable) GeoAdd(key string, geoLocation ...*GeoLocation) *IntCmd { + args := make([]interface{}, 2+3*len(geoLocation)) + args[0] = "geoadd" + args[1] = key + for i, eachLoc := range geoLocation { + args[2+3*i] = eachLoc.Longitude + args[2+3*i+1] = eachLoc.Latitude + args[2+3*i+2] = eachLoc.Name + } + cmd := NewIntCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd { + cmd := NewGeoLocationCmd(query, "georadius", key, longitude, latitude) + c.process(cmd) + return cmd +} + +func (c *cmdable) GeoRadiusRO(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd { + cmd := NewGeoLocationCmd(query, "georadius_ro", key, longitude, latitude) + c.process(cmd) + return cmd +} + +func (c *cmdable) GeoRadiusByMember(key, member string, query *GeoRadiusQuery) *GeoLocationCmd { + cmd := NewGeoLocationCmd(query, "georadiusbymember", key, member) + c.process(cmd) + return cmd +} + +func (c *cmdable) GeoRadiusByMemberRO(key, member string, query *GeoRadiusQuery) *GeoLocationCmd { + cmd := NewGeoLocationCmd(query, "georadiusbymember_ro", key, member) + c.process(cmd) + return cmd +} + +func (c *cmdable) GeoDist(key string, member1, member2, unit string) *FloatCmd { + if unit == "" { + unit = "km" + } + cmd := NewFloatCmd("geodist", key, member1, member2, unit) + c.process(cmd) + return cmd +} + +func (c *cmdable) GeoHash(key string, members ...string) *StringSliceCmd { + args := make([]interface{}, 2+len(members)) + args[0] = "geohash" + args[1] = key + for i, member := range members { + args[2+i] = member + } + cmd := NewStringSliceCmd(args...) + c.process(cmd) + return cmd +} + +func (c *cmdable) GeoPos(key string, members ...string) *GeoPosCmd { + args := make([]interface{}, 2+len(members)) + args[0] = "geopos" + args[1] = key + for i, member := range members { + args[2+i] = member + } + cmd := NewGeoPosCmd(args...) + c.process(cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c *cmdable) Command() *CommandsInfoCmd { + cmd := NewCommandsInfoCmd("command") + c.process(cmd) + return cmd +} diff --git a/vendor/github.com/go-redis/redis/commands_test.go b/vendor/github.com/go-redis/redis/commands_test.go new file mode 100644 index 0000000..7153795 --- /dev/null +++ b/vendor/github.com/go-redis/redis/commands_test.go @@ -0,0 +1,2968 @@ +package redis_test + +import ( + "encoding/json" + "fmt" + "reflect" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/go-redis/redis" +) + +var _ = Describe("Commands", func() { + var client *redis.Client + + BeforeEach(func() { + client = redis.NewClient(redisOptions()) + Expect(client.FlushDB().Err()).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + Describe("server", func() { + + It("should Auth", func() { + cmds, err := client.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Auth("password") + pipe.Auth("") + return nil + }) + Expect(err).To(MatchError("ERR Client sent AUTH, but no password is set")) + Expect(cmds[0].Err()).To(MatchError("ERR Client sent AUTH, but no password is set")) + Expect(cmds[1].Err()).To(MatchError("ERR Client sent AUTH, but no password is set")) + + stats := client.PoolStats() + Expect(stats.Hits).To(Equal(uint32(1))) + Expect(stats.Misses).To(Equal(uint32(1))) + Expect(stats.Timeouts).To(Equal(uint32(0))) + Expect(stats.TotalConns).To(Equal(uint32(1))) + Expect(stats.FreeConns).To(Equal(uint32(1))) + }) + + It("should Echo", func() { + pipe := client.Pipeline() + echo := pipe.Echo("hello") + _, err := pipe.Exec() + Expect(err).NotTo(HaveOccurred()) + + Expect(echo.Err()).NotTo(HaveOccurred()) + Expect(echo.Val()).To(Equal("hello")) + }) + + It("should Ping", func() { + ping := client.Ping() + Expect(ping.Err()).NotTo(HaveOccurred()) + Expect(ping.Val()).To(Equal("PONG")) + }) + + It("should Wait", func() { + // assume testing on single redis instance + start := time.Now() + wait := client.Wait(1, time.Second) + Expect(wait.Err()).NotTo(HaveOccurred()) + Expect(wait.Val()).To(Equal(int64(0))) + Expect(time.Now()).To(BeTemporally("~", start.Add(time.Second), 800*time.Millisecond)) + }) + + It("should Select", func() { + pipe := client.Pipeline() + sel := pipe.Select(1) + _, err := pipe.Exec() + Expect(err).NotTo(HaveOccurred()) + + Expect(sel.Err()).NotTo(HaveOccurred()) + Expect(sel.Val()).To(Equal("OK")) + }) + + It("should BgRewriteAOF", func() { + Skip("flaky test") + + val, err := client.BgRewriteAOF().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(ContainSubstring("Background append only file rewriting")) + }) + + It("should BgSave", func() { + Skip("flaky test") + + // workaround for "ERR Can't BGSAVE while AOF log rewriting is in progress" + Eventually(func() string { + return client.BgSave().Val() + }, "30s").Should(Equal("Background saving started")) + }) + + It("should ClientKill", func() { + r := client.ClientKill("1.1.1.1:1111") + Expect(r.Err()).To(MatchError("ERR No such client")) + Expect(r.Val()).To(Equal("")) + }) + + It("should ClientPause", func() { + err := client.ClientPause(time.Second).Err() + Expect(err).NotTo(HaveOccurred()) + + start := time.Now() + err = client.Ping().Err() + Expect(err).NotTo(HaveOccurred()) + Expect(time.Now()).To(BeTemporally("~", start.Add(time.Second), 800*time.Millisecond)) + }) + + It("should ClientSetName and ClientGetName", func() { + pipe := client.Pipeline() + set := pipe.ClientSetName("theclientname") + get := pipe.ClientGetName() + _, err := pipe.Exec() + Expect(err).NotTo(HaveOccurred()) + + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(BeTrue()) + + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("theclientname")) + }) + + It("should ConfigGet", func() { + val, err := client.ConfigGet("*").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).NotTo(BeEmpty()) + }) + + It("should ConfigResetStat", func() { + r := client.ConfigResetStat() + Expect(r.Err()).NotTo(HaveOccurred()) + Expect(r.Val()).To(Equal("OK")) + }) + + It("should ConfigSet", func() { + configGet := client.ConfigGet("maxmemory") + Expect(configGet.Err()).NotTo(HaveOccurred()) + Expect(configGet.Val()).To(HaveLen(2)) + Expect(configGet.Val()[0]).To(Equal("maxmemory")) + + configSet := client.ConfigSet("maxmemory", configGet.Val()[1].(string)) + Expect(configSet.Err()).NotTo(HaveOccurred()) + Expect(configSet.Val()).To(Equal("OK")) + }) + + It("should DBSize", func() { + size, err := client.DBSize().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(size).To(Equal(int64(0))) + }) + + It("should Info", func() { + info := client.Info() + Expect(info.Err()).NotTo(HaveOccurred()) + Expect(info.Val()).NotTo(Equal("")) + }) + + It("should Info cpu", func() { + info := client.Info("cpu") + Expect(info.Err()).NotTo(HaveOccurred()) + Expect(info.Val()).NotTo(Equal("")) + Expect(info.Val()).To(ContainSubstring(`used_cpu_sys`)) + }) + + It("should LastSave", func() { + lastSave := client.LastSave() + Expect(lastSave.Err()).NotTo(HaveOccurred()) + Expect(lastSave.Val()).NotTo(Equal(0)) + }) + + It("should Save", func() { + // workaround for "ERR Background save already in progress" + Eventually(func() string { + return client.Save().Val() + }, "10s").Should(Equal("OK")) + }) + + It("should SlaveOf", func() { + slaveOf := client.SlaveOf("localhost", "8888") + Expect(slaveOf.Err()).NotTo(HaveOccurred()) + Expect(slaveOf.Val()).To(Equal("OK")) + + slaveOf = client.SlaveOf("NO", "ONE") + Expect(slaveOf.Err()).NotTo(HaveOccurred()) + Expect(slaveOf.Val()).To(Equal("OK")) + }) + + It("should Time", func() { + tm, err := client.Time().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(tm).To(BeTemporally("~", time.Now(), 3*time.Second)) + }) + + It("Should Command", func() { + cmds, err := client.Command().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(cmds)).To(BeNumerically("~", 180, 10)) + + cmd := cmds["mget"] + Expect(cmd.Name).To(Equal("mget")) + Expect(cmd.Arity).To(Equal(int8(-2))) + Expect(cmd.Flags).To(ContainElement("readonly")) + Expect(cmd.FirstKeyPos).To(Equal(int8(1))) + Expect(cmd.LastKeyPos).To(Equal(int8(-1))) + Expect(cmd.StepCount).To(Equal(int8(1))) + + cmd = cmds["ping"] + Expect(cmd.Name).To(Equal("ping")) + Expect(cmd.Arity).To(Equal(int8(-1))) + Expect(cmd.Flags).To(ContainElement("stale")) + Expect(cmd.Flags).To(ContainElement("fast")) + Expect(cmd.FirstKeyPos).To(Equal(int8(0))) + Expect(cmd.LastKeyPos).To(Equal(int8(0))) + Expect(cmd.StepCount).To(Equal(int8(0))) + }) + + }) + + Describe("debugging", func() { + + It("should DebugObject", func() { + debug := client.DebugObject("foo") + Expect(debug.Err()).To(HaveOccurred()) + Expect(debug.Err().Error()).To(Equal("ERR no such key")) + + client.Set("foo", "bar", 0) + debug = client.DebugObject("foo") + Expect(debug.Err()).NotTo(HaveOccurred()) + Expect(debug.Val()).To(ContainSubstring(`serializedlength:4`)) + }) + + }) + + Describe("keys", func() { + + It("should Del", func() { + err := client.Set("key1", "Hello", 0).Err() + Expect(err).NotTo(HaveOccurred()) + err = client.Set("key2", "World", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + n, err := client.Del("key1", "key2", "key3").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(2))) + }) + + It("should Unlink", func() { + err := client.Set("key1", "Hello", 0).Err() + Expect(err).NotTo(HaveOccurred()) + err = client.Set("key2", "World", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + n, err := client.Unlink("key1", "key2", "key3").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(2))) + }) + + It("should Dump", func() { + set := client.Set("key", "hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + dump := client.Dump("key") + Expect(dump.Err()).NotTo(HaveOccurred()) + Expect(dump.Val()).NotTo(BeEmpty()) + }) + + It("should Exists", func() { + set := client.Set("key1", "Hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + n, err := client.Exists("key1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(1))) + + n, err = client.Exists("key2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(0))) + + n, err = client.Exists("key1", "key2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(1))) + + n, err = client.Exists("key1", "key1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(2))) + }) + + It("should Expire", func() { + set := client.Set("key", "Hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + expire := client.Expire("key", 10*time.Second) + Expect(expire.Err()).NotTo(HaveOccurred()) + Expect(expire.Val()).To(Equal(true)) + + ttl := client.TTL("key") + Expect(ttl.Err()).NotTo(HaveOccurred()) + Expect(ttl.Val()).To(Equal(10 * time.Second)) + + set = client.Set("key", "Hello World", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + ttl = client.TTL("key") + Expect(ttl.Err()).NotTo(HaveOccurred()) + Expect(ttl.Val() < 0).To(Equal(true)) + }) + + It("should ExpireAt", func() { + set := client.Set("key", "Hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + n, err := client.Exists("key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(1))) + + expireAt := client.ExpireAt("key", time.Now().Add(-time.Hour)) + Expect(expireAt.Err()).NotTo(HaveOccurred()) + Expect(expireAt.Val()).To(Equal(true)) + + n, err = client.Exists("key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(0))) + }) + + It("should Keys", func() { + mset := client.MSet("one", "1", "two", "2", "three", "3", "four", "4") + Expect(mset.Err()).NotTo(HaveOccurred()) + Expect(mset.Val()).To(Equal("OK")) + + keys := client.Keys("*o*") + Expect(keys.Err()).NotTo(HaveOccurred()) + Expect(keys.Val()).To(ConsistOf([]string{"four", "one", "two"})) + + keys = client.Keys("t??") + Expect(keys.Err()).NotTo(HaveOccurred()) + Expect(keys.Val()).To(Equal([]string{"two"})) + + keys = client.Keys("*") + Expect(keys.Err()).NotTo(HaveOccurred()) + Expect(keys.Val()).To(ConsistOf([]string{"four", "one", "three", "two"})) + }) + + It("should Migrate", func() { + migrate := client.Migrate("localhost", redisSecondaryPort, "key", 0, 0) + Expect(migrate.Err()).NotTo(HaveOccurred()) + Expect(migrate.Val()).To(Equal("NOKEY")) + + set := client.Set("key", "hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + migrate = client.Migrate("localhost", redisSecondaryPort, "key", 0, 0) + Expect(migrate.Err()).To(MatchError("IOERR error or timeout writing to target instance")) + Expect(migrate.Val()).To(Equal("")) + }) + + It("should Move", func() { + move := client.Move("key", 2) + Expect(move.Err()).NotTo(HaveOccurred()) + Expect(move.Val()).To(Equal(false)) + + set := client.Set("key", "hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + move = client.Move("key", 2) + Expect(move.Err()).NotTo(HaveOccurred()) + Expect(move.Val()).To(Equal(true)) + + get := client.Get("key") + Expect(get.Err()).To(Equal(redis.Nil)) + Expect(get.Val()).To(Equal("")) + + pipe := client.Pipeline() + pipe.Select(2) + get = pipe.Get("key") + pipe.FlushDB() + + _, err := pipe.Exec() + Expect(err).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("hello")) + }) + + It("should Object", func() { + set := client.Set("key", "hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + refCount := client.ObjectRefCount("key") + Expect(refCount.Err()).NotTo(HaveOccurred()) + Expect(refCount.Val()).To(Equal(int64(1))) + + err := client.ObjectEncoding("key").Err() + Expect(err).NotTo(HaveOccurred()) + + idleTime := client.ObjectIdleTime("key") + Expect(idleTime.Err()).NotTo(HaveOccurred()) + Expect(idleTime.Val()).To(Equal(time.Duration(0))) + }) + + It("should Persist", func() { + set := client.Set("key", "Hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + expire := client.Expire("key", 10*time.Second) + Expect(expire.Err()).NotTo(HaveOccurred()) + Expect(expire.Val()).To(Equal(true)) + + ttl := client.TTL("key") + Expect(ttl.Err()).NotTo(HaveOccurred()) + Expect(ttl.Val()).To(Equal(10 * time.Second)) + + persist := client.Persist("key") + Expect(persist.Err()).NotTo(HaveOccurred()) + Expect(persist.Val()).To(Equal(true)) + + ttl = client.TTL("key") + Expect(ttl.Err()).NotTo(HaveOccurred()) + Expect(ttl.Val() < 0).To(Equal(true)) + }) + + It("should PExpire", func() { + set := client.Set("key", "Hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + expiration := 900 * time.Millisecond + pexpire := client.PExpire("key", expiration) + Expect(pexpire.Err()).NotTo(HaveOccurred()) + Expect(pexpire.Val()).To(Equal(true)) + + ttl := client.TTL("key") + Expect(ttl.Err()).NotTo(HaveOccurred()) + Expect(ttl.Val()).To(Equal(time.Second)) + + pttl := client.PTTL("key") + Expect(pttl.Err()).NotTo(HaveOccurred()) + Expect(pttl.Val()).To(BeNumerically("~", expiration, 10*time.Millisecond)) + }) + + It("should PExpireAt", func() { + set := client.Set("key", "Hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + expiration := 900 * time.Millisecond + pexpireat := client.PExpireAt("key", time.Now().Add(expiration)) + Expect(pexpireat.Err()).NotTo(HaveOccurred()) + Expect(pexpireat.Val()).To(Equal(true)) + + ttl := client.TTL("key") + Expect(ttl.Err()).NotTo(HaveOccurred()) + Expect(ttl.Val()).To(Equal(time.Second)) + + pttl := client.PTTL("key") + Expect(pttl.Err()).NotTo(HaveOccurred()) + Expect(pttl.Val()).To(BeNumerically("~", expiration, 10*time.Millisecond)) + }) + + It("should PTTL", func() { + set := client.Set("key", "Hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + expiration := time.Second + expire := client.Expire("key", expiration) + Expect(expire.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + pttl := client.PTTL("key") + Expect(pttl.Err()).NotTo(HaveOccurred()) + Expect(pttl.Val()).To(BeNumerically("~", expiration, 10*time.Millisecond)) + }) + + It("should RandomKey", func() { + randomKey := client.RandomKey() + Expect(randomKey.Err()).To(Equal(redis.Nil)) + Expect(randomKey.Val()).To(Equal("")) + + set := client.Set("key", "hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + randomKey = client.RandomKey() + Expect(randomKey.Err()).NotTo(HaveOccurred()) + Expect(randomKey.Val()).To(Equal("key")) + }) + + It("should Rename", func() { + set := client.Set("key", "hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + status := client.Rename("key", "key1") + Expect(status.Err()).NotTo(HaveOccurred()) + Expect(status.Val()).To(Equal("OK")) + + get := client.Get("key1") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("hello")) + }) + + It("should RenameNX", func() { + set := client.Set("key", "hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + renameNX := client.RenameNX("key", "key1") + Expect(renameNX.Err()).NotTo(HaveOccurred()) + Expect(renameNX.Val()).To(Equal(true)) + + get := client.Get("key1") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("hello")) + }) + + It("should Restore", func() { + err := client.Set("key", "hello", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + dump := client.Dump("key") + Expect(dump.Err()).NotTo(HaveOccurred()) + + err = client.Del("key").Err() + Expect(err).NotTo(HaveOccurred()) + + restore, err := client.Restore("key", 0, dump.Val()).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(restore).To(Equal("OK")) + + type_, err := client.Type("key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(type_).To(Equal("string")) + + val, err := client.Get("key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("hello")) + }) + + It("should RestoreReplace", func() { + err := client.Set("key", "hello", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + dump := client.Dump("key") + Expect(dump.Err()).NotTo(HaveOccurred()) + + restore, err := client.RestoreReplace("key", 0, dump.Val()).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(restore).To(Equal("OK")) + + type_, err := client.Type("key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(type_).To(Equal("string")) + + val, err := client.Get("key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("hello")) + }) + + It("should Sort", func() { + size, err := client.LPush("list", "1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(size).To(Equal(int64(1))) + + size, err = client.LPush("list", "3").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(size).To(Equal(int64(2))) + + size, err = client.LPush("list", "2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(size).To(Equal(int64(3))) + + els, err := client.Sort("list", redis.Sort{ + Offset: 0, + Count: 2, + Order: "ASC", + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(els).To(Equal([]string{"1", "2"})) + }) + + It("should Sort and Get", func() { + size, err := client.LPush("list", "1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(size).To(Equal(int64(1))) + + size, err = client.LPush("list", "3").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(size).To(Equal(int64(2))) + + size, err = client.LPush("list", "2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(size).To(Equal(int64(3))) + + err = client.Set("object_2", "value2", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + { + els, err := client.Sort("list", redis.Sort{ + Get: []string{"object_*"}, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(els).To(Equal([]string{"", "value2", ""})) + } + + { + els, err := client.SortInterfaces("list", redis.Sort{ + Get: []string{"object_*"}, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(els).To(Equal([]interface{}{nil, "value2", nil})) + } + }) + + It("should TTL", func() { + ttl := client.TTL("key") + Expect(ttl.Err()).NotTo(HaveOccurred()) + Expect(ttl.Val() < 0).To(Equal(true)) + + set := client.Set("key", "hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + expire := client.Expire("key", 60*time.Second) + Expect(expire.Err()).NotTo(HaveOccurred()) + Expect(expire.Val()).To(Equal(true)) + + ttl = client.TTL("key") + Expect(ttl.Err()).NotTo(HaveOccurred()) + Expect(ttl.Val()).To(Equal(60 * time.Second)) + }) + + It("should Type", func() { + set := client.Set("key", "hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + type_ := client.Type("key") + Expect(type_.Err()).NotTo(HaveOccurred()) + Expect(type_.Val()).To(Equal("string")) + }) + + }) + + Describe("scanning", func() { + + It("should Scan", func() { + for i := 0; i < 1000; i++ { + set := client.Set(fmt.Sprintf("key%d", i), "hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + } + + keys, cursor, err := client.Scan(0, "", 0).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(keys).NotTo(BeEmpty()) + Expect(cursor).NotTo(BeZero()) + }) + + It("should SScan", func() { + for i := 0; i < 1000; i++ { + sadd := client.SAdd("myset", fmt.Sprintf("member%d", i)) + Expect(sadd.Err()).NotTo(HaveOccurred()) + } + + keys, cursor, err := client.SScan("myset", 0, "", 0).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(keys).NotTo(BeEmpty()) + Expect(cursor).NotTo(BeZero()) + }) + + It("should HScan", func() { + for i := 0; i < 1000; i++ { + sadd := client.HSet("myhash", fmt.Sprintf("key%d", i), "hello") + Expect(sadd.Err()).NotTo(HaveOccurred()) + } + + keys, cursor, err := client.HScan("myhash", 0, "", 0).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(keys).NotTo(BeEmpty()) + Expect(cursor).NotTo(BeZero()) + }) + + It("should ZScan", func() { + for i := 0; i < 1000; i++ { + sadd := client.ZAdd("myset", redis.Z{float64(i), fmt.Sprintf("member%d", i)}) + Expect(sadd.Err()).NotTo(HaveOccurred()) + } + + keys, cursor, err := client.ZScan("myset", 0, "", 0).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(keys).NotTo(BeEmpty()) + Expect(cursor).NotTo(BeZero()) + }) + + }) + + Describe("strings", func() { + + It("should Append", func() { + n, err := client.Exists("key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(0))) + + append := client.Append("key", "Hello") + Expect(append.Err()).NotTo(HaveOccurred()) + Expect(append.Val()).To(Equal(int64(5))) + + append = client.Append("key", " World") + Expect(append.Err()).NotTo(HaveOccurred()) + Expect(append.Val()).To(Equal(int64(11))) + + get := client.Get("key") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("Hello World")) + }) + + It("should BitCount", func() { + set := client.Set("key", "foobar", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + bitCount := client.BitCount("key", nil) + Expect(bitCount.Err()).NotTo(HaveOccurred()) + Expect(bitCount.Val()).To(Equal(int64(26))) + + bitCount = client.BitCount("key", &redis.BitCount{0, 0}) + Expect(bitCount.Err()).NotTo(HaveOccurred()) + Expect(bitCount.Val()).To(Equal(int64(4))) + + bitCount = client.BitCount("key", &redis.BitCount{1, 1}) + Expect(bitCount.Err()).NotTo(HaveOccurred()) + Expect(bitCount.Val()).To(Equal(int64(6))) + }) + + It("should BitOpAnd", func() { + set := client.Set("key1", "1", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + set = client.Set("key2", "0", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + bitOpAnd := client.BitOpAnd("dest", "key1", "key2") + Expect(bitOpAnd.Err()).NotTo(HaveOccurred()) + Expect(bitOpAnd.Val()).To(Equal(int64(1))) + + get := client.Get("dest") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("0")) + }) + + It("should BitOpOr", func() { + set := client.Set("key1", "1", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + set = client.Set("key2", "0", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + bitOpOr := client.BitOpOr("dest", "key1", "key2") + Expect(bitOpOr.Err()).NotTo(HaveOccurred()) + Expect(bitOpOr.Val()).To(Equal(int64(1))) + + get := client.Get("dest") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("1")) + }) + + It("should BitOpXor", func() { + set := client.Set("key1", "\xff", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + set = client.Set("key2", "\x0f", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + bitOpXor := client.BitOpXor("dest", "key1", "key2") + Expect(bitOpXor.Err()).NotTo(HaveOccurred()) + Expect(bitOpXor.Val()).To(Equal(int64(1))) + + get := client.Get("dest") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("\xf0")) + }) + + It("should BitOpNot", func() { + set := client.Set("key1", "\x00", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + bitOpNot := client.BitOpNot("dest", "key1") + Expect(bitOpNot.Err()).NotTo(HaveOccurred()) + Expect(bitOpNot.Val()).To(Equal(int64(1))) + + get := client.Get("dest") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("\xff")) + }) + + It("should BitPos", func() { + err := client.Set("mykey", "\xff\xf0\x00", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + pos, err := client.BitPos("mykey", 0).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(pos).To(Equal(int64(12))) + + pos, err = client.BitPos("mykey", 1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(pos).To(Equal(int64(0))) + + pos, err = client.BitPos("mykey", 0, 2).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(pos).To(Equal(int64(16))) + + pos, err = client.BitPos("mykey", 1, 2).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(pos).To(Equal(int64(-1))) + + pos, err = client.BitPos("mykey", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(pos).To(Equal(int64(16))) + + pos, err = client.BitPos("mykey", 1, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(pos).To(Equal(int64(-1))) + + pos, err = client.BitPos("mykey", 0, 2, 1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(pos).To(Equal(int64(-1))) + + pos, err = client.BitPos("mykey", 0, 0, -3).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(pos).To(Equal(int64(-1))) + + pos, err = client.BitPos("mykey", 0, 0, 0).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(pos).To(Equal(int64(-1))) + }) + + It("should Decr", func() { + set := client.Set("key", "10", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + decr := client.Decr("key") + Expect(decr.Err()).NotTo(HaveOccurred()) + Expect(decr.Val()).To(Equal(int64(9))) + + set = client.Set("key", "234293482390480948029348230948", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + decr = client.Decr("key") + Expect(decr.Err()).To(MatchError("ERR value is not an integer or out of range")) + Expect(decr.Val()).To(Equal(int64(0))) + }) + + It("should DecrBy", func() { + set := client.Set("key", "10", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + decrBy := client.DecrBy("key", 5) + Expect(decrBy.Err()).NotTo(HaveOccurred()) + Expect(decrBy.Val()).To(Equal(int64(5))) + }) + + It("should Get", func() { + get := client.Get("_") + Expect(get.Err()).To(Equal(redis.Nil)) + Expect(get.Val()).To(Equal("")) + + set := client.Set("key", "hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + get = client.Get("key") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("hello")) + }) + + It("should GetBit", func() { + setBit := client.SetBit("key", 7, 1) + Expect(setBit.Err()).NotTo(HaveOccurred()) + Expect(setBit.Val()).To(Equal(int64(0))) + + getBit := client.GetBit("key", 0) + Expect(getBit.Err()).NotTo(HaveOccurred()) + Expect(getBit.Val()).To(Equal(int64(0))) + + getBit = client.GetBit("key", 7) + Expect(getBit.Err()).NotTo(HaveOccurred()) + Expect(getBit.Val()).To(Equal(int64(1))) + + getBit = client.GetBit("key", 100) + Expect(getBit.Err()).NotTo(HaveOccurred()) + Expect(getBit.Val()).To(Equal(int64(0))) + }) + + It("should GetRange", func() { + set := client.Set("key", "This is a string", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + getRange := client.GetRange("key", 0, 3) + Expect(getRange.Err()).NotTo(HaveOccurred()) + Expect(getRange.Val()).To(Equal("This")) + + getRange = client.GetRange("key", -3, -1) + Expect(getRange.Err()).NotTo(HaveOccurred()) + Expect(getRange.Val()).To(Equal("ing")) + + getRange = client.GetRange("key", 0, -1) + Expect(getRange.Err()).NotTo(HaveOccurred()) + Expect(getRange.Val()).To(Equal("This is a string")) + + getRange = client.GetRange("key", 10, 100) + Expect(getRange.Err()).NotTo(HaveOccurred()) + Expect(getRange.Val()).To(Equal("string")) + }) + + It("should GetSet", func() { + incr := client.Incr("key") + Expect(incr.Err()).NotTo(HaveOccurred()) + Expect(incr.Val()).To(Equal(int64(1))) + + getSet := client.GetSet("key", "0") + Expect(getSet.Err()).NotTo(HaveOccurred()) + Expect(getSet.Val()).To(Equal("1")) + + get := client.Get("key") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("0")) + }) + + It("should Incr", func() { + set := client.Set("key", "10", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + incr := client.Incr("key") + Expect(incr.Err()).NotTo(HaveOccurred()) + Expect(incr.Val()).To(Equal(int64(11))) + + get := client.Get("key") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("11")) + }) + + It("should IncrBy", func() { + set := client.Set("key", "10", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + incrBy := client.IncrBy("key", 5) + Expect(incrBy.Err()).NotTo(HaveOccurred()) + Expect(incrBy.Val()).To(Equal(int64(15))) + }) + + It("should IncrByFloat", func() { + set := client.Set("key", "10.50", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + incrByFloat := client.IncrByFloat("key", 0.1) + Expect(incrByFloat.Err()).NotTo(HaveOccurred()) + Expect(incrByFloat.Val()).To(Equal(10.6)) + + set = client.Set("key", "5.0e3", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + incrByFloat = client.IncrByFloat("key", 2.0e2) + Expect(incrByFloat.Err()).NotTo(HaveOccurred()) + Expect(incrByFloat.Val()).To(Equal(float64(5200))) + }) + + It("should IncrByFloatOverflow", func() { + incrByFloat := client.IncrByFloat("key", 996945661) + Expect(incrByFloat.Err()).NotTo(HaveOccurred()) + Expect(incrByFloat.Val()).To(Equal(float64(996945661))) + }) + + It("should MSetMGet", func() { + mSet := client.MSet("key1", "hello1", "key2", "hello2") + Expect(mSet.Err()).NotTo(HaveOccurred()) + Expect(mSet.Val()).To(Equal("OK")) + + mGet := client.MGet("key1", "key2", "_") + Expect(mGet.Err()).NotTo(HaveOccurred()) + Expect(mGet.Val()).To(Equal([]interface{}{"hello1", "hello2", nil})) + }) + + It("should MSetNX", func() { + mSetNX := client.MSetNX("key1", "hello1", "key2", "hello2") + Expect(mSetNX.Err()).NotTo(HaveOccurred()) + Expect(mSetNX.Val()).To(Equal(true)) + + mSetNX = client.MSetNX("key2", "hello1", "key3", "hello2") + Expect(mSetNX.Err()).NotTo(HaveOccurred()) + Expect(mSetNX.Val()).To(Equal(false)) + }) + + It("should Set with expiration", func() { + err := client.Set("key", "hello", 100*time.Millisecond).Err() + Expect(err).NotTo(HaveOccurred()) + + val, err := client.Get("key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("hello")) + + Eventually(func() error { + return client.Get("foo").Err() + }, "1s", "100ms").Should(Equal(redis.Nil)) + }) + + It("should SetGet", func() { + set := client.Set("key", "hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + get := client.Get("key") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("hello")) + }) + + It("should SetNX", func() { + setNX := client.SetNX("key", "hello", 0) + Expect(setNX.Err()).NotTo(HaveOccurred()) + Expect(setNX.Val()).To(Equal(true)) + + setNX = client.SetNX("key", "hello2", 0) + Expect(setNX.Err()).NotTo(HaveOccurred()) + Expect(setNX.Val()).To(Equal(false)) + + get := client.Get("key") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("hello")) + }) + + It("should SetNX with expiration", func() { + isSet, err := client.SetNX("key", "hello", time.Second).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(isSet).To(Equal(true)) + + isSet, err = client.SetNX("key", "hello2", time.Second).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(isSet).To(Equal(false)) + + val, err := client.Get("key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("hello")) + }) + + It("should SetXX", func() { + isSet, err := client.SetXX("key", "hello2", 0).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(isSet).To(Equal(false)) + + err = client.Set("key", "hello", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + isSet, err = client.SetXX("key", "hello2", 0).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(isSet).To(Equal(true)) + + val, err := client.Get("key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("hello2")) + }) + + It("should SetXX with expiration", func() { + isSet, err := client.SetXX("key", "hello2", time.Second).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(isSet).To(Equal(false)) + + err = client.Set("key", "hello", time.Second).Err() + Expect(err).NotTo(HaveOccurred()) + + isSet, err = client.SetXX("key", "hello2", time.Second).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(isSet).To(Equal(true)) + + val, err := client.Get("key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("hello2")) + }) + + It("should SetRange", func() { + set := client.Set("key", "Hello World", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + range_ := client.SetRange("key", 6, "Redis") + Expect(range_.Err()).NotTo(HaveOccurred()) + Expect(range_.Val()).To(Equal(int64(11))) + + get := client.Get("key") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("Hello Redis")) + }) + + It("should StrLen", func() { + set := client.Set("key", "hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + strLen := client.StrLen("key") + Expect(strLen.Err()).NotTo(HaveOccurred()) + Expect(strLen.Val()).To(Equal(int64(5))) + + strLen = client.StrLen("_") + Expect(strLen.Err()).NotTo(HaveOccurred()) + Expect(strLen.Val()).To(Equal(int64(0))) + }) + + }) + + Describe("hashes", func() { + + It("should HDel", func() { + hSet := client.HSet("hash", "key", "hello") + Expect(hSet.Err()).NotTo(HaveOccurred()) + + hDel := client.HDel("hash", "key") + Expect(hDel.Err()).NotTo(HaveOccurred()) + Expect(hDel.Val()).To(Equal(int64(1))) + + hDel = client.HDel("hash", "key") + Expect(hDel.Err()).NotTo(HaveOccurred()) + Expect(hDel.Val()).To(Equal(int64(0))) + }) + + It("should HExists", func() { + hSet := client.HSet("hash", "key", "hello") + Expect(hSet.Err()).NotTo(HaveOccurred()) + + hExists := client.HExists("hash", "key") + Expect(hExists.Err()).NotTo(HaveOccurred()) + Expect(hExists.Val()).To(Equal(true)) + + hExists = client.HExists("hash", "key1") + Expect(hExists.Err()).NotTo(HaveOccurred()) + Expect(hExists.Val()).To(Equal(false)) + }) + + It("should HGet", func() { + hSet := client.HSet("hash", "key", "hello") + Expect(hSet.Err()).NotTo(HaveOccurred()) + + hGet := client.HGet("hash", "key") + Expect(hGet.Err()).NotTo(HaveOccurred()) + Expect(hGet.Val()).To(Equal("hello")) + + hGet = client.HGet("hash", "key1") + Expect(hGet.Err()).To(Equal(redis.Nil)) + Expect(hGet.Val()).To(Equal("")) + }) + + It("should HGetAll", func() { + err := client.HSet("hash", "key1", "hello1").Err() + Expect(err).NotTo(HaveOccurred()) + err = client.HSet("hash", "key2", "hello2").Err() + Expect(err).NotTo(HaveOccurred()) + + m, err := client.HGetAll("hash").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(m).To(Equal(map[string]string{"key1": "hello1", "key2": "hello2"})) + }) + + It("should HIncrBy", func() { + hSet := client.HSet("hash", "key", "5") + Expect(hSet.Err()).NotTo(HaveOccurred()) + + hIncrBy := client.HIncrBy("hash", "key", 1) + Expect(hIncrBy.Err()).NotTo(HaveOccurred()) + Expect(hIncrBy.Val()).To(Equal(int64(6))) + + hIncrBy = client.HIncrBy("hash", "key", -1) + Expect(hIncrBy.Err()).NotTo(HaveOccurred()) + Expect(hIncrBy.Val()).To(Equal(int64(5))) + + hIncrBy = client.HIncrBy("hash", "key", -10) + Expect(hIncrBy.Err()).NotTo(HaveOccurred()) + Expect(hIncrBy.Val()).To(Equal(int64(-5))) + }) + + It("should HIncrByFloat", func() { + hSet := client.HSet("hash", "field", "10.50") + Expect(hSet.Err()).NotTo(HaveOccurred()) + Expect(hSet.Val()).To(Equal(true)) + + hIncrByFloat := client.HIncrByFloat("hash", "field", 0.1) + Expect(hIncrByFloat.Err()).NotTo(HaveOccurred()) + Expect(hIncrByFloat.Val()).To(Equal(10.6)) + + hSet = client.HSet("hash", "field", "5.0e3") + Expect(hSet.Err()).NotTo(HaveOccurred()) + Expect(hSet.Val()).To(Equal(false)) + + hIncrByFloat = client.HIncrByFloat("hash", "field", 2.0e2) + Expect(hIncrByFloat.Err()).NotTo(HaveOccurred()) + Expect(hIncrByFloat.Val()).To(Equal(float64(5200))) + }) + + It("should HKeys", func() { + hkeys := client.HKeys("hash") + Expect(hkeys.Err()).NotTo(HaveOccurred()) + Expect(hkeys.Val()).To(Equal([]string{})) + + hset := client.HSet("hash", "key1", "hello1") + Expect(hset.Err()).NotTo(HaveOccurred()) + hset = client.HSet("hash", "key2", "hello2") + Expect(hset.Err()).NotTo(HaveOccurred()) + + hkeys = client.HKeys("hash") + Expect(hkeys.Err()).NotTo(HaveOccurred()) + Expect(hkeys.Val()).To(Equal([]string{"key1", "key2"})) + }) + + It("should HLen", func() { + hSet := client.HSet("hash", "key1", "hello1") + Expect(hSet.Err()).NotTo(HaveOccurred()) + hSet = client.HSet("hash", "key2", "hello2") + Expect(hSet.Err()).NotTo(HaveOccurred()) + + hLen := client.HLen("hash") + Expect(hLen.Err()).NotTo(HaveOccurred()) + Expect(hLen.Val()).To(Equal(int64(2))) + }) + + It("should HMGet", func() { + err := client.HSet("hash", "key1", "hello1").Err() + Expect(err).NotTo(HaveOccurred()) + err = client.HSet("hash", "key2", "hello2").Err() + Expect(err).NotTo(HaveOccurred()) + + vals, err := client.HMGet("hash", "key1", "key2", "_").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]interface{}{"hello1", "hello2", nil})) + }) + + It("should HMSet", func() { + ok, err := client.HMSet("hash", map[string]interface{}{ + "key1": "hello1", + "key2": "hello2", + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(ok).To(Equal("OK")) + + v, err := client.HGet("hash", "key1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(v).To(Equal("hello1")) + + v, err = client.HGet("hash", "key2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(v).To(Equal("hello2")) + }) + + It("should HSet", func() { + hSet := client.HSet("hash", "key", "hello") + Expect(hSet.Err()).NotTo(HaveOccurred()) + Expect(hSet.Val()).To(Equal(true)) + + hGet := client.HGet("hash", "key") + Expect(hGet.Err()).NotTo(HaveOccurred()) + Expect(hGet.Val()).To(Equal("hello")) + }) + + It("should HSetNX", func() { + hSetNX := client.HSetNX("hash", "key", "hello") + Expect(hSetNX.Err()).NotTo(HaveOccurred()) + Expect(hSetNX.Val()).To(Equal(true)) + + hSetNX = client.HSetNX("hash", "key", "hello") + Expect(hSetNX.Err()).NotTo(HaveOccurred()) + Expect(hSetNX.Val()).To(Equal(false)) + + hGet := client.HGet("hash", "key") + Expect(hGet.Err()).NotTo(HaveOccurred()) + Expect(hGet.Val()).To(Equal("hello")) + }) + + It("should HVals", func() { + err := client.HSet("hash", "key1", "hello1").Err() + Expect(err).NotTo(HaveOccurred()) + err = client.HSet("hash", "key2", "hello2").Err() + Expect(err).NotTo(HaveOccurred()) + + v, err := client.HVals("hash").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(v).To(Equal([]string{"hello1", "hello2"})) + + var slice []string + err = client.HVals("hash").ScanSlice(&slice) + Expect(err).NotTo(HaveOccurred()) + Expect(slice).To(Equal([]string{"hello1", "hello2"})) + }) + + }) + + Describe("hyperloglog", func() { + It("should PFMerge", func() { + pfAdd := client.PFAdd("hll1", "1", "2", "3", "4", "5") + Expect(pfAdd.Err()).NotTo(HaveOccurred()) + + pfCount := client.PFCount("hll1") + Expect(pfCount.Err()).NotTo(HaveOccurred()) + Expect(pfCount.Val()).To(Equal(int64(5))) + + pfAdd = client.PFAdd("hll2", "a", "b", "c", "d", "e") + Expect(pfAdd.Err()).NotTo(HaveOccurred()) + + pfMerge := client.PFMerge("hllMerged", "hll1", "hll2") + Expect(pfMerge.Err()).NotTo(HaveOccurred()) + + pfCount = client.PFCount("hllMerged") + Expect(pfCount.Err()).NotTo(HaveOccurred()) + Expect(pfCount.Val()).To(Equal(int64(10))) + + pfCount = client.PFCount("hll1", "hll2") + Expect(pfCount.Err()).NotTo(HaveOccurred()) + Expect(pfCount.Val()).To(Equal(int64(10))) + }) + }) + + Describe("lists", func() { + + It("should BLPop", func() { + rPush := client.RPush("list1", "a", "b", "c") + Expect(rPush.Err()).NotTo(HaveOccurred()) + + bLPop := client.BLPop(0, "list1", "list2") + Expect(bLPop.Err()).NotTo(HaveOccurred()) + Expect(bLPop.Val()).To(Equal([]string{"list1", "a"})) + }) + + It("should BLPopBlocks", func() { + started := make(chan bool) + done := make(chan bool) + go func() { + defer GinkgoRecover() + + started <- true + bLPop := client.BLPop(0, "list") + Expect(bLPop.Err()).NotTo(HaveOccurred()) + Expect(bLPop.Val()).To(Equal([]string{"list", "a"})) + done <- true + }() + <-started + + select { + case <-done: + Fail("BLPop is not blocked") + case <-time.After(time.Second): + // ok + } + + rPush := client.RPush("list", "a") + Expect(rPush.Err()).NotTo(HaveOccurred()) + + select { + case <-done: + // ok + case <-time.After(time.Second): + Fail("BLPop is still blocked") + } + }) + + It("should BLPop timeout", func() { + val, err := client.BLPop(time.Second, "list1").Result() + Expect(err).To(Equal(redis.Nil)) + Expect(val).To(BeNil()) + + Expect(client.Ping().Err()).NotTo(HaveOccurred()) + + stats := client.PoolStats() + Expect(stats.Hits).To(Equal(uint32(1))) + Expect(stats.Misses).To(Equal(uint32(2))) + Expect(stats.Timeouts).To(Equal(uint32(0))) + }) + + It("should BRPop", func() { + rPush := client.RPush("list1", "a", "b", "c") + Expect(rPush.Err()).NotTo(HaveOccurred()) + + bRPop := client.BRPop(0, "list1", "list2") + Expect(bRPop.Err()).NotTo(HaveOccurred()) + Expect(bRPop.Val()).To(Equal([]string{"list1", "c"})) + }) + + It("should BRPop blocks", func() { + started := make(chan bool) + done := make(chan bool) + go func() { + defer GinkgoRecover() + + started <- true + brpop := client.BRPop(0, "list") + Expect(brpop.Err()).NotTo(HaveOccurred()) + Expect(brpop.Val()).To(Equal([]string{"list", "a"})) + done <- true + }() + <-started + + select { + case <-done: + Fail("BRPop is not blocked") + case <-time.After(time.Second): + // ok + } + + rPush := client.RPush("list", "a") + Expect(rPush.Err()).NotTo(HaveOccurred()) + + select { + case <-done: + // ok + case <-time.After(time.Second): + Fail("BRPop is still blocked") + // ok + } + }) + + It("should BRPopLPush", func() { + _, err := client.BRPopLPush("list1", "list2", time.Second).Result() + Expect(err).To(Equal(redis.Nil)) + + err = client.RPush("list1", "a", "b", "c").Err() + Expect(err).NotTo(HaveOccurred()) + + v, err := client.BRPopLPush("list1", "list2", 0).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(v).To(Equal("c")) + }) + + It("should LIndex", func() { + lPush := client.LPush("list", "World") + Expect(lPush.Err()).NotTo(HaveOccurred()) + lPush = client.LPush("list", "Hello") + Expect(lPush.Err()).NotTo(HaveOccurred()) + + lIndex := client.LIndex("list", 0) + Expect(lIndex.Err()).NotTo(HaveOccurred()) + Expect(lIndex.Val()).To(Equal("Hello")) + + lIndex = client.LIndex("list", -1) + Expect(lIndex.Err()).NotTo(HaveOccurred()) + Expect(lIndex.Val()).To(Equal("World")) + + lIndex = client.LIndex("list", 3) + Expect(lIndex.Err()).To(Equal(redis.Nil)) + Expect(lIndex.Val()).To(Equal("")) + }) + + It("should LInsert", func() { + rPush := client.RPush("list", "Hello") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "World") + Expect(rPush.Err()).NotTo(HaveOccurred()) + + lInsert := client.LInsert("list", "BEFORE", "World", "There") + Expect(lInsert.Err()).NotTo(HaveOccurred()) + Expect(lInsert.Val()).To(Equal(int64(3))) + + lRange := client.LRange("list", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"Hello", "There", "World"})) + }) + + It("should LLen", func() { + lPush := client.LPush("list", "World") + Expect(lPush.Err()).NotTo(HaveOccurred()) + lPush = client.LPush("list", "Hello") + Expect(lPush.Err()).NotTo(HaveOccurred()) + + lLen := client.LLen("list") + Expect(lLen.Err()).NotTo(HaveOccurred()) + Expect(lLen.Val()).To(Equal(int64(2))) + }) + + It("should LPop", func() { + rPush := client.RPush("list", "one") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "two") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "three") + Expect(rPush.Err()).NotTo(HaveOccurred()) + + lPop := client.LPop("list") + Expect(lPop.Err()).NotTo(HaveOccurred()) + Expect(lPop.Val()).To(Equal("one")) + + lRange := client.LRange("list", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"two", "three"})) + }) + + It("should LPush", func() { + lPush := client.LPush("list", "World") + Expect(lPush.Err()).NotTo(HaveOccurred()) + lPush = client.LPush("list", "Hello") + Expect(lPush.Err()).NotTo(HaveOccurred()) + + lRange := client.LRange("list", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"Hello", "World"})) + }) + + It("should LPushX", func() { + lPush := client.LPush("list", "World") + Expect(lPush.Err()).NotTo(HaveOccurred()) + + lPushX := client.LPushX("list", "Hello") + Expect(lPushX.Err()).NotTo(HaveOccurred()) + Expect(lPushX.Val()).To(Equal(int64(2))) + + lPushX = client.LPushX("list2", "Hello") + Expect(lPushX.Err()).NotTo(HaveOccurred()) + Expect(lPushX.Val()).To(Equal(int64(0))) + + lRange := client.LRange("list", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"Hello", "World"})) + + lRange = client.LRange("list2", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{})) + }) + + It("should LRange", func() { + rPush := client.RPush("list", "one") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "two") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "three") + Expect(rPush.Err()).NotTo(HaveOccurred()) + + lRange := client.LRange("list", 0, 0) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"one"})) + + lRange = client.LRange("list", -3, 2) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"one", "two", "three"})) + + lRange = client.LRange("list", -100, 100) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"one", "two", "three"})) + + lRange = client.LRange("list", 5, 10) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{})) + }) + + It("should LRem", func() { + rPush := client.RPush("list", "hello") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "hello") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "key") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "hello") + Expect(rPush.Err()).NotTo(HaveOccurred()) + + lRem := client.LRem("list", -2, "hello") + Expect(lRem.Err()).NotTo(HaveOccurred()) + Expect(lRem.Val()).To(Equal(int64(2))) + + lRange := client.LRange("list", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"hello", "key"})) + }) + + It("should LSet", func() { + rPush := client.RPush("list", "one") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "two") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "three") + Expect(rPush.Err()).NotTo(HaveOccurred()) + + lSet := client.LSet("list", 0, "four") + Expect(lSet.Err()).NotTo(HaveOccurred()) + Expect(lSet.Val()).To(Equal("OK")) + + lSet = client.LSet("list", -2, "five") + Expect(lSet.Err()).NotTo(HaveOccurred()) + Expect(lSet.Val()).To(Equal("OK")) + + lRange := client.LRange("list", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"four", "five", "three"})) + }) + + It("should LTrim", func() { + rPush := client.RPush("list", "one") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "two") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "three") + Expect(rPush.Err()).NotTo(HaveOccurred()) + + lTrim := client.LTrim("list", 1, -1) + Expect(lTrim.Err()).NotTo(HaveOccurred()) + Expect(lTrim.Val()).To(Equal("OK")) + + lRange := client.LRange("list", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"two", "three"})) + }) + + It("should RPop", func() { + rPush := client.RPush("list", "one") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "two") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "three") + Expect(rPush.Err()).NotTo(HaveOccurred()) + + rPop := client.RPop("list") + Expect(rPop.Err()).NotTo(HaveOccurred()) + Expect(rPop.Val()).To(Equal("three")) + + lRange := client.LRange("list", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"one", "two"})) + }) + + It("should RPopLPush", func() { + rPush := client.RPush("list", "one") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "two") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush("list", "three") + Expect(rPush.Err()).NotTo(HaveOccurred()) + + rPopLPush := client.RPopLPush("list", "list2") + Expect(rPopLPush.Err()).NotTo(HaveOccurred()) + Expect(rPopLPush.Val()).To(Equal("three")) + + lRange := client.LRange("list", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"one", "two"})) + + lRange = client.LRange("list2", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"three"})) + }) + + It("should RPush", func() { + rPush := client.RPush("list", "Hello") + Expect(rPush.Err()).NotTo(HaveOccurred()) + Expect(rPush.Val()).To(Equal(int64(1))) + + rPush = client.RPush("list", "World") + Expect(rPush.Err()).NotTo(HaveOccurred()) + Expect(rPush.Val()).To(Equal(int64(2))) + + lRange := client.LRange("list", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"Hello", "World"})) + }) + + It("should RPushX", func() { + rPush := client.RPush("list", "Hello") + Expect(rPush.Err()).NotTo(HaveOccurred()) + Expect(rPush.Val()).To(Equal(int64(1))) + + rPushX := client.RPushX("list", "World") + Expect(rPushX.Err()).NotTo(HaveOccurred()) + Expect(rPushX.Val()).To(Equal(int64(2))) + + rPushX = client.RPushX("list2", "World") + Expect(rPushX.Err()).NotTo(HaveOccurred()) + Expect(rPushX.Val()).To(Equal(int64(0))) + + lRange := client.LRange("list", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"Hello", "World"})) + + lRange = client.LRange("list2", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{})) + }) + + }) + + Describe("sets", func() { + + It("should SAdd", func() { + sAdd := client.SAdd("set", "Hello") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + Expect(sAdd.Val()).To(Equal(int64(1))) + + sAdd = client.SAdd("set", "World") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + Expect(sAdd.Val()).To(Equal(int64(1))) + + sAdd = client.SAdd("set", "World") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + Expect(sAdd.Val()).To(Equal(int64(0))) + + sMembers := client.SMembers("set") + Expect(sMembers.Err()).NotTo(HaveOccurred()) + Expect(sMembers.Val()).To(ConsistOf([]string{"Hello", "World"})) + }) + + It("should SCard", func() { + sAdd := client.SAdd("set", "Hello") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + Expect(sAdd.Val()).To(Equal(int64(1))) + + sAdd = client.SAdd("set", "World") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + Expect(sAdd.Val()).To(Equal(int64(1))) + + sCard := client.SCard("set") + Expect(sCard.Err()).NotTo(HaveOccurred()) + Expect(sCard.Val()).To(Equal(int64(2))) + }) + + It("should SDiff", func() { + sAdd := client.SAdd("set1", "a") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set1", "b") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set1", "c") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sAdd = client.SAdd("set2", "c") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set2", "d") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set2", "e") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sDiff := client.SDiff("set1", "set2") + Expect(sDiff.Err()).NotTo(HaveOccurred()) + Expect(sDiff.Val()).To(ConsistOf([]string{"a", "b"})) + }) + + It("should SDiffStore", func() { + sAdd := client.SAdd("set1", "a") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set1", "b") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set1", "c") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sAdd = client.SAdd("set2", "c") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set2", "d") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set2", "e") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sDiffStore := client.SDiffStore("set", "set1", "set2") + Expect(sDiffStore.Err()).NotTo(HaveOccurred()) + Expect(sDiffStore.Val()).To(Equal(int64(2))) + + sMembers := client.SMembers("set") + Expect(sMembers.Err()).NotTo(HaveOccurred()) + Expect(sMembers.Val()).To(ConsistOf([]string{"a", "b"})) + }) + + It("should SInter", func() { + sAdd := client.SAdd("set1", "a") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set1", "b") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set1", "c") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sAdd = client.SAdd("set2", "c") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set2", "d") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set2", "e") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sInter := client.SInter("set1", "set2") + Expect(sInter.Err()).NotTo(HaveOccurred()) + Expect(sInter.Val()).To(Equal([]string{"c"})) + }) + + It("should SInterStore", func() { + sAdd := client.SAdd("set1", "a") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set1", "b") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set1", "c") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sAdd = client.SAdd("set2", "c") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set2", "d") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set2", "e") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sInterStore := client.SInterStore("set", "set1", "set2") + Expect(sInterStore.Err()).NotTo(HaveOccurred()) + Expect(sInterStore.Val()).To(Equal(int64(1))) + + sMembers := client.SMembers("set") + Expect(sMembers.Err()).NotTo(HaveOccurred()) + Expect(sMembers.Val()).To(Equal([]string{"c"})) + }) + + It("should IsMember", func() { + sAdd := client.SAdd("set", "one") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sIsMember := client.SIsMember("set", "one") + Expect(sIsMember.Err()).NotTo(HaveOccurred()) + Expect(sIsMember.Val()).To(Equal(true)) + + sIsMember = client.SIsMember("set", "two") + Expect(sIsMember.Err()).NotTo(HaveOccurred()) + Expect(sIsMember.Val()).To(Equal(false)) + }) + + It("should SMembers", func() { + sAdd := client.SAdd("set", "Hello") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set", "World") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sMembers := client.SMembers("set") + Expect(sMembers.Err()).NotTo(HaveOccurred()) + Expect(sMembers.Val()).To(ConsistOf([]string{"Hello", "World"})) + }) + + It("should SMembersMap", func() { + sAdd := client.SAdd("set", "Hello") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set", "World") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sMembersMap := client.SMembersMap("set") + Expect(sMembersMap.Err()).NotTo(HaveOccurred()) + Expect(sMembersMap.Val()).To(Equal(map[string]struct{}{"Hello": struct{}{}, "World": struct{}{}})) + }) + + It("should SMove", func() { + sAdd := client.SAdd("set1", "one") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set1", "two") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sAdd = client.SAdd("set2", "three") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sMove := client.SMove("set1", "set2", "two") + Expect(sMove.Err()).NotTo(HaveOccurred()) + Expect(sMove.Val()).To(Equal(true)) + + sMembers := client.SMembers("set1") + Expect(sMembers.Err()).NotTo(HaveOccurred()) + Expect(sMembers.Val()).To(Equal([]string{"one"})) + + sMembers = client.SMembers("set2") + Expect(sMembers.Err()).NotTo(HaveOccurred()) + Expect(sMembers.Val()).To(ConsistOf([]string{"three", "two"})) + }) + + It("should SPop", func() { + sAdd := client.SAdd("set", "one") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set", "two") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set", "three") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sPop := client.SPop("set") + Expect(sPop.Err()).NotTo(HaveOccurred()) + Expect(sPop.Val()).NotTo(Equal("")) + + sMembers := client.SMembers("set") + Expect(sMembers.Err()).NotTo(HaveOccurred()) + Expect(sMembers.Val()).To(HaveLen(2)) + + }) + + It("should SPopN", func() { + sAdd := client.SAdd("set", "one") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set", "two") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set", "three") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set", "four") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sPopN := client.SPopN("set", 1) + Expect(sPopN.Err()).NotTo(HaveOccurred()) + Expect(sPopN.Val()).NotTo(Equal([]string{""})) + + sMembers := client.SMembers("set") + Expect(sMembers.Err()).NotTo(HaveOccurred()) + Expect(sMembers.Val()).To(HaveLen(3)) + + sPopN = client.SPopN("set", 4) + Expect(sPopN.Err()).NotTo(HaveOccurred()) + Expect(sPopN.Val()).To(HaveLen(3)) + + sMembers = client.SMembers("set") + Expect(sMembers.Err()).NotTo(HaveOccurred()) + Expect(sMembers.Val()).To(HaveLen(0)) + }) + + It("should SRandMember and SRandMemberN", func() { + err := client.SAdd("set", "one").Err() + Expect(err).NotTo(HaveOccurred()) + err = client.SAdd("set", "two").Err() + Expect(err).NotTo(HaveOccurred()) + err = client.SAdd("set", "three").Err() + Expect(err).NotTo(HaveOccurred()) + + members, err := client.SMembers("set").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(members).To(HaveLen(3)) + + member, err := client.SRandMember("set").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(member).NotTo(Equal("")) + + members, err = client.SRandMemberN("set", 2).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(members).To(HaveLen(2)) + }) + + It("should SRem", func() { + sAdd := client.SAdd("set", "one") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set", "two") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set", "three") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sRem := client.SRem("set", "one") + Expect(sRem.Err()).NotTo(HaveOccurred()) + Expect(sRem.Val()).To(Equal(int64(1))) + + sRem = client.SRem("set", "four") + Expect(sRem.Err()).NotTo(HaveOccurred()) + Expect(sRem.Val()).To(Equal(int64(0))) + + sMembers := client.SMembers("set") + Expect(sMembers.Err()).NotTo(HaveOccurred()) + Expect(sMembers.Val()).To(ConsistOf([]string{"three", "two"})) + }) + + It("should SUnion", func() { + sAdd := client.SAdd("set1", "a") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set1", "b") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set1", "c") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sAdd = client.SAdd("set2", "c") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set2", "d") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set2", "e") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sUnion := client.SUnion("set1", "set2") + Expect(sUnion.Err()).NotTo(HaveOccurred()) + Expect(sUnion.Val()).To(HaveLen(5)) + }) + + It("should SUnionStore", func() { + sAdd := client.SAdd("set1", "a") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set1", "b") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set1", "c") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sAdd = client.SAdd("set2", "c") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set2", "d") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + sAdd = client.SAdd("set2", "e") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sUnionStore := client.SUnionStore("set", "set1", "set2") + Expect(sUnionStore.Err()).NotTo(HaveOccurred()) + Expect(sUnionStore.Val()).To(Equal(int64(5))) + + sMembers := client.SMembers("set") + Expect(sMembers.Err()).NotTo(HaveOccurred()) + Expect(sMembers.Val()).To(HaveLen(5)) + }) + + }) + + Describe("sorted sets", func() { + + It("should ZAdd", func() { + added, err := client.ZAdd("zset", redis.Z{1, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(1))) + + added, err = client.ZAdd("zset", redis.Z{1, "uno"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(1))) + + added, err = client.ZAdd("zset", redis.Z{2, "two"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(1))) + + added, err = client.ZAdd("zset", redis.Z{3, "two"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(0))) + + vals, err := client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{1, "one"}, {1, "uno"}, {3, "two"}})) + }) + + It("should ZAdd bytes", func() { + added, err := client.ZAdd("zset", redis.Z{1, []byte("one")}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(1))) + + added, err = client.ZAdd("zset", redis.Z{1, []byte("uno")}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(1))) + + added, err = client.ZAdd("zset", redis.Z{2, []byte("two")}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(1))) + + added, err = client.ZAdd("zset", redis.Z{3, []byte("two")}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(0))) + + val, err := client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{1, "one"}, {1, "uno"}, {3, "two"}})) + }) + + It("should ZAddNX", func() { + added, err := client.ZAddNX("zset", redis.Z{1, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(1))) + + vals, err := client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{1, "one"}})) + + added, err = client.ZAddNX("zset", redis.Z{2, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(0))) + + vals, err = client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{1, "one"}})) + }) + + It("should ZAddXX", func() { + added, err := client.ZAddXX("zset", redis.Z{1, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(0))) + + vals, err := client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(BeEmpty()) + + added, err = client.ZAdd("zset", redis.Z{1, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(1))) + + added, err = client.ZAddXX("zset", redis.Z{2, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(0))) + + vals, err = client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{2, "one"}})) + }) + + It("should ZAddCh", func() { + changed, err := client.ZAddCh("zset", redis.Z{1, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(changed).To(Equal(int64(1))) + + changed, err = client.ZAddCh("zset", redis.Z{1, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(changed).To(Equal(int64(0))) + }) + + It("should ZAddNXCh", func() { + changed, err := client.ZAddNXCh("zset", redis.Z{1, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(changed).To(Equal(int64(1))) + + vals, err := client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{1, "one"}})) + + changed, err = client.ZAddNXCh("zset", redis.Z{2, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(changed).To(Equal(int64(0))) + + vals, err = client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{1, "one"}})) + }) + + It("should ZAddXXCh", func() { + changed, err := client.ZAddXXCh("zset", redis.Z{1, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(changed).To(Equal(int64(0))) + + vals, err := client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(BeEmpty()) + + added, err := client.ZAdd("zset", redis.Z{1, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(1))) + + changed, err = client.ZAddXXCh("zset", redis.Z{2, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(changed).To(Equal(int64(1))) + + vals, err = client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{2, "one"}})) + }) + + It("should ZIncr", func() { + score, err := client.ZIncr("zset", redis.Z{1, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(score).To(Equal(float64(1))) + + vals, err := client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{1, "one"}})) + + score, err = client.ZIncr("zset", redis.Z{1, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(score).To(Equal(float64(2))) + + vals, err = client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{2, "one"}})) + }) + + It("should ZIncrNX", func() { + score, err := client.ZIncrNX("zset", redis.Z{1, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(score).To(Equal(float64(1))) + + vals, err := client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{1, "one"}})) + + score, err = client.ZIncrNX("zset", redis.Z{1, "one"}).Result() + Expect(err).To(Equal(redis.Nil)) + Expect(score).To(Equal(float64(0))) + + vals, err = client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{1, "one"}})) + }) + + It("should ZIncrXX", func() { + score, err := client.ZIncrXX("zset", redis.Z{1, "one"}).Result() + Expect(err).To(Equal(redis.Nil)) + Expect(score).To(Equal(float64(0))) + + vals, err := client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(BeEmpty()) + + added, err := client.ZAdd("zset", redis.Z{1, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(1))) + + score, err = client.ZIncrXX("zset", redis.Z{1, "one"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(score).To(Equal(float64(2))) + + vals, err = client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{2, "one"}})) + }) + + It("should ZCard", func() { + zAdd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zCard := client.ZCard("zset") + Expect(zCard.Err()).NotTo(HaveOccurred()) + Expect(zCard.Val()).To(Equal(int64(2))) + }) + + It("should ZCount", func() { + err := client.ZAdd("zset", redis.Z{1, "one"}).Err() + Expect(err).NotTo(HaveOccurred()) + err = client.ZAdd("zset", redis.Z{2, "two"}).Err() + Expect(err).NotTo(HaveOccurred()) + err = client.ZAdd("zset", redis.Z{3, "three"}).Err() + Expect(err).NotTo(HaveOccurred()) + + count, err := client.ZCount("zset", "-inf", "+inf").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(count).To(Equal(int64(3))) + + count, err = client.ZCount("zset", "(1", "3").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(count).To(Equal(int64(2))) + + count, err = client.ZLexCount("zset", "-", "+").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(count).To(Equal(int64(3))) + }) + + It("should ZIncrBy", func() { + zAdd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zIncrBy := client.ZIncrBy("zset", 2, "one") + Expect(zIncrBy.Err()).NotTo(HaveOccurred()) + Expect(zIncrBy.Val()).To(Equal(float64(3))) + + val, err := client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{2, "two"}, {3, "one"}})) + }) + + It("should ZInterStore", func() { + zAdd := client.ZAdd("zset1", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset1", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zAdd = client.ZAdd("zset2", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset2", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset3", redis.Z{3, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zInterStore := client.ZInterStore( + "out", redis.ZStore{Weights: []float64{2, 3}}, "zset1", "zset2") + Expect(zInterStore.Err()).NotTo(HaveOccurred()) + Expect(zInterStore.Val()).To(Equal(int64(2))) + + val, err := client.ZRangeWithScores("out", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{5, "one"}, {10, "two"}})) + }) + + It("should ZRange", func() { + zAdd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{3, "three"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zRange := client.ZRange("zset", 0, -1) + Expect(zRange.Err()).NotTo(HaveOccurred()) + Expect(zRange.Val()).To(Equal([]string{"one", "two", "three"})) + + zRange = client.ZRange("zset", 2, 3) + Expect(zRange.Err()).NotTo(HaveOccurred()) + Expect(zRange.Val()).To(Equal([]string{"three"})) + + zRange = client.ZRange("zset", -2, -1) + Expect(zRange.Err()).NotTo(HaveOccurred()) + Expect(zRange.Val()).To(Equal([]string{"two", "three"})) + }) + + It("should ZRangeWithScores", func() { + zAdd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{3, "three"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + val, err := client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{1, "one"}, {2, "two"}, {3, "three"}})) + + val, err = client.ZRangeWithScores("zset", 2, 3).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{3, "three"}})) + + val, err = client.ZRangeWithScores("zset", -2, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{2, "two"}, {3, "three"}})) + }) + + It("should ZRangeByScore", func() { + zAdd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{3, "three"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zRangeByScore := client.ZRangeByScore("zset", redis.ZRangeBy{ + Min: "-inf", + Max: "+inf", + }) + Expect(zRangeByScore.Err()).NotTo(HaveOccurred()) + Expect(zRangeByScore.Val()).To(Equal([]string{"one", "two", "three"})) + + zRangeByScore = client.ZRangeByScore("zset", redis.ZRangeBy{ + Min: "1", + Max: "2", + }) + Expect(zRangeByScore.Err()).NotTo(HaveOccurred()) + Expect(zRangeByScore.Val()).To(Equal([]string{"one", "two"})) + + zRangeByScore = client.ZRangeByScore("zset", redis.ZRangeBy{ + Min: "(1", + Max: "2", + }) + Expect(zRangeByScore.Err()).NotTo(HaveOccurred()) + Expect(zRangeByScore.Val()).To(Equal([]string{"two"})) + + zRangeByScore = client.ZRangeByScore("zset", redis.ZRangeBy{ + Min: "(1", + Max: "(2", + }) + Expect(zRangeByScore.Err()).NotTo(HaveOccurred()) + Expect(zRangeByScore.Val()).To(Equal([]string{})) + }) + + It("should ZRangeByLex", func() { + zAdd := client.ZAdd("zset", redis.Z{0, "a"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{0, "b"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{0, "c"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zRangeByLex := client.ZRangeByLex("zset", redis.ZRangeBy{ + Min: "-", + Max: "+", + }) + Expect(zRangeByLex.Err()).NotTo(HaveOccurred()) + Expect(zRangeByLex.Val()).To(Equal([]string{"a", "b", "c"})) + + zRangeByLex = client.ZRangeByLex("zset", redis.ZRangeBy{ + Min: "[a", + Max: "[b", + }) + Expect(zRangeByLex.Err()).NotTo(HaveOccurred()) + Expect(zRangeByLex.Val()).To(Equal([]string{"a", "b"})) + + zRangeByLex = client.ZRangeByLex("zset", redis.ZRangeBy{ + Min: "(a", + Max: "[b", + }) + Expect(zRangeByLex.Err()).NotTo(HaveOccurred()) + Expect(zRangeByLex.Val()).To(Equal([]string{"b"})) + + zRangeByLex = client.ZRangeByLex("zset", redis.ZRangeBy{ + Min: "(a", + Max: "(b", + }) + Expect(zRangeByLex.Err()).NotTo(HaveOccurred()) + Expect(zRangeByLex.Val()).To(Equal([]string{})) + }) + + It("should ZRangeByScoreWithScoresMap", func() { + zAdd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{3, "three"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + val, err := client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{ + Min: "-inf", + Max: "+inf", + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{1, "one"}, {2, "two"}, {3, "three"}})) + + val, err = client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{ + Min: "1", + Max: "2", + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{1, "one"}, {2, "two"}})) + + val, err = client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{ + Min: "(1", + Max: "2", + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{2, "two"}})) + + val, err = client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{ + Min: "(1", + Max: "(2", + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{})) + }) + + It("should ZRank", func() { + zAdd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{3, "three"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zRank := client.ZRank("zset", "three") + Expect(zRank.Err()).NotTo(HaveOccurred()) + Expect(zRank.Val()).To(Equal(int64(2))) + + zRank = client.ZRank("zset", "four") + Expect(zRank.Err()).To(Equal(redis.Nil)) + Expect(zRank.Val()).To(Equal(int64(0))) + }) + + It("should ZRem", func() { + zAdd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{3, "three"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zRem := client.ZRem("zset", "two") + Expect(zRem.Err()).NotTo(HaveOccurred()) + Expect(zRem.Val()).To(Equal(int64(1))) + + val, err := client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{1, "one"}, {3, "three"}})) + }) + + It("should ZRemRangeByRank", func() { + zAdd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{3, "three"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zRemRangeByRank := client.ZRemRangeByRank("zset", 0, 1) + Expect(zRemRangeByRank.Err()).NotTo(HaveOccurred()) + Expect(zRemRangeByRank.Val()).To(Equal(int64(2))) + + val, err := client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{3, "three"}})) + }) + + It("should ZRemRangeByScore", func() { + zAdd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{3, "three"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zRemRangeByScore := client.ZRemRangeByScore("zset", "-inf", "(2") + Expect(zRemRangeByScore.Err()).NotTo(HaveOccurred()) + Expect(zRemRangeByScore.Val()).To(Equal(int64(1))) + + val, err := client.ZRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{2, "two"}, {3, "three"}})) + }) + + It("should ZRemRangeByLex", func() { + zz := []redis.Z{ + {0, "aaaa"}, + {0, "b"}, + {0, "c"}, + {0, "d"}, + {0, "e"}, + {0, "foo"}, + {0, "zap"}, + {0, "zip"}, + {0, "ALPHA"}, + {0, "alpha"}, + } + for _, z := range zz { + err := client.ZAdd("zset", z).Err() + Expect(err).NotTo(HaveOccurred()) + } + + n, err := client.ZRemRangeByLex("zset", "[alpha", "[omega").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(6))) + + vals, err := client.ZRange("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]string{"ALPHA", "aaaa", "zap", "zip"})) + }) + + It("should ZRevRange", func() { + zAdd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{3, "three"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zRevRange := client.ZRevRange("zset", 0, -1) + Expect(zRevRange.Err()).NotTo(HaveOccurred()) + Expect(zRevRange.Val()).To(Equal([]string{"three", "two", "one"})) + + zRevRange = client.ZRevRange("zset", 2, 3) + Expect(zRevRange.Err()).NotTo(HaveOccurred()) + Expect(zRevRange.Val()).To(Equal([]string{"one"})) + + zRevRange = client.ZRevRange("zset", -2, -1) + Expect(zRevRange.Err()).NotTo(HaveOccurred()) + Expect(zRevRange.Val()).To(Equal([]string{"two", "one"})) + }) + + It("should ZRevRangeWithScoresMap", func() { + zAdd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{3, "three"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + val, err := client.ZRevRangeWithScores("zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{3, "three"}, {2, "two"}, {1, "one"}})) + + val, err = client.ZRevRangeWithScores("zset", 2, 3).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{1, "one"}})) + + val, err = client.ZRevRangeWithScores("zset", -2, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{2, "two"}, {1, "one"}})) + }) + + It("should ZRevRangeByScore", func() { + zadd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zadd.Err()).NotTo(HaveOccurred()) + zadd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zadd.Err()).NotTo(HaveOccurred()) + zadd = client.ZAdd("zset", redis.Z{3, "three"}) + Expect(zadd.Err()).NotTo(HaveOccurred()) + + vals, err := client.ZRevRangeByScore( + "zset", redis.ZRangeBy{Max: "+inf", Min: "-inf"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]string{"three", "two", "one"})) + + vals, err = client.ZRevRangeByScore( + "zset", redis.ZRangeBy{Max: "2", Min: "(1"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]string{"two"})) + + vals, err = client.ZRevRangeByScore( + "zset", redis.ZRangeBy{Max: "(2", Min: "(1"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]string{})) + }) + + It("should ZRevRangeByLex", func() { + zadd := client.ZAdd("zset", redis.Z{0, "a"}) + Expect(zadd.Err()).NotTo(HaveOccurred()) + zadd = client.ZAdd("zset", redis.Z{0, "b"}) + Expect(zadd.Err()).NotTo(HaveOccurred()) + zadd = client.ZAdd("zset", redis.Z{0, "c"}) + Expect(zadd.Err()).NotTo(HaveOccurred()) + + vals, err := client.ZRevRangeByLex( + "zset", redis.ZRangeBy{Max: "+", Min: "-"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]string{"c", "b", "a"})) + + vals, err = client.ZRevRangeByLex( + "zset", redis.ZRangeBy{Max: "[b", Min: "(a"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]string{"b"})) + + vals, err = client.ZRevRangeByLex( + "zset", redis.ZRangeBy{Max: "(b", Min: "(a"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]string{})) + }) + + It("should ZRevRangeByScoreWithScores", func() { + zadd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zadd.Err()).NotTo(HaveOccurred()) + zadd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zadd.Err()).NotTo(HaveOccurred()) + zadd = client.ZAdd("zset", redis.Z{3, "three"}) + Expect(zadd.Err()).NotTo(HaveOccurred()) + + vals, err := client.ZRevRangeByScoreWithScores( + "zset", redis.ZRangeBy{Max: "+inf", Min: "-inf"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{3, "three"}, {2, "two"}, {1, "one"}})) + }) + + It("should ZRevRangeByScoreWithScoresMap", func() { + zAdd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{3, "three"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + val, err := client.ZRevRangeByScoreWithScores( + "zset", redis.ZRangeBy{Max: "+inf", Min: "-inf"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{3, "three"}, {2, "two"}, {1, "one"}})) + + val, err = client.ZRevRangeByScoreWithScores( + "zset", redis.ZRangeBy{Max: "2", Min: "(1"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{2, "two"}})) + + val, err = client.ZRevRangeByScoreWithScores( + "zset", redis.ZRangeBy{Max: "(2", Min: "(1"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{})) + }) + + It("should ZRevRank", func() { + zAdd := client.ZAdd("zset", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset", redis.Z{3, "three"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zRevRank := client.ZRevRank("zset", "one") + Expect(zRevRank.Err()).NotTo(HaveOccurred()) + Expect(zRevRank.Val()).To(Equal(int64(2))) + + zRevRank = client.ZRevRank("zset", "four") + Expect(zRevRank.Err()).To(Equal(redis.Nil)) + Expect(zRevRank.Val()).To(Equal(int64(0))) + }) + + It("should ZScore", func() { + zAdd := client.ZAdd("zset", redis.Z{1.001, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zScore := client.ZScore("zset", "one") + Expect(zScore.Err()).NotTo(HaveOccurred()) + Expect(zScore.Val()).To(Equal(float64(1.001))) + }) + + It("should ZUnionStore", func() { + zAdd := client.ZAdd("zset1", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset1", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zAdd = client.ZAdd("zset2", redis.Z{1, "one"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset2", redis.Z{2, "two"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + zAdd = client.ZAdd("zset2", redis.Z{3, "three"}) + Expect(zAdd.Err()).NotTo(HaveOccurred()) + + zUnionStore := client.ZUnionStore( + "out", redis.ZStore{Weights: []float64{2, 3}}, "zset1", "zset2") + Expect(zUnionStore.Err()).NotTo(HaveOccurred()) + Expect(zUnionStore.Val()).To(Equal(int64(3))) + + val, err := client.ZRangeWithScores("out", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.Z{{5, "one"}, {9, "three"}, {10, "two"}})) + }) + + }) + + Describe("Geo add and radius search", func() { + BeforeEach(func() { + geoAdd := client.GeoAdd( + "Sicily", + &redis.GeoLocation{Longitude: 13.361389, Latitude: 38.115556, Name: "Palermo"}, + &redis.GeoLocation{Longitude: 15.087269, Latitude: 37.502669, Name: "Catania"}, + ) + Expect(geoAdd.Err()).NotTo(HaveOccurred()) + Expect(geoAdd.Val()).To(Equal(int64(2))) + }) + + It("should not add same geo location", func() { + geoAdd := client.GeoAdd( + "Sicily", + &redis.GeoLocation{Longitude: 13.361389, Latitude: 38.115556, Name: "Palermo"}, + ) + Expect(geoAdd.Err()).NotTo(HaveOccurred()) + Expect(geoAdd.Val()).To(Equal(int64(0))) + }) + + It("should search geo radius", func() { + res, err := client.GeoRadius("Sicily", 15, 37, &redis.GeoRadiusQuery{ + Radius: 200, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(HaveLen(2)) + Expect(res[0].Name).To(Equal("Palermo")) + Expect(res[1].Name).To(Equal("Catania")) + }) + + It("should search geo radius with options", func() { + res, err := client.GeoRadius("Sicily", 15, 37, &redis.GeoRadiusQuery{ + Radius: 200, + Unit: "km", + WithGeoHash: true, + WithCoord: true, + WithDist: true, + Count: 2, + Sort: "ASC", + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(HaveLen(2)) + Expect(res[1].Name).To(Equal("Palermo")) + Expect(res[1].Dist).To(Equal(190.4424)) + Expect(res[1].GeoHash).To(Equal(int64(3479099956230698))) + Expect(res[1].Longitude).To(Equal(13.361389338970184)) + Expect(res[1].Latitude).To(Equal(38.115556395496299)) + Expect(res[0].Name).To(Equal("Catania")) + Expect(res[0].Dist).To(Equal(56.4413)) + Expect(res[0].GeoHash).To(Equal(int64(3479447370796909))) + Expect(res[0].Longitude).To(Equal(15.087267458438873)) + Expect(res[0].Latitude).To(Equal(37.50266842333162)) + }) + + It("should search geo radius with WithDist=false", func() { + res, err := client.GeoRadius("Sicily", 15, 37, &redis.GeoRadiusQuery{ + Radius: 200, + Unit: "km", + WithGeoHash: true, + WithCoord: true, + Count: 2, + Sort: "ASC", + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(HaveLen(2)) + Expect(res[1].Name).To(Equal("Palermo")) + Expect(res[1].Dist).To(Equal(float64(0))) + Expect(res[1].GeoHash).To(Equal(int64(3479099956230698))) + Expect(res[1].Longitude).To(Equal(13.361389338970184)) + Expect(res[1].Latitude).To(Equal(38.115556395496299)) + Expect(res[0].Name).To(Equal("Catania")) + Expect(res[0].Dist).To(Equal(float64(0))) + Expect(res[0].GeoHash).To(Equal(int64(3479447370796909))) + Expect(res[0].Longitude).To(Equal(15.087267458438873)) + Expect(res[0].Latitude).To(Equal(37.50266842333162)) + }) + + It("should search geo radius by member with options", func() { + res, err := client.GeoRadiusByMember("Sicily", "Catania", &redis.GeoRadiusQuery{ + Radius: 200, + Unit: "km", + WithGeoHash: true, + WithCoord: true, + WithDist: true, + Count: 2, + Sort: "ASC", + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(HaveLen(2)) + Expect(res[0].Name).To(Equal("Catania")) + Expect(res[0].Dist).To(Equal(0.0)) + Expect(res[0].GeoHash).To(Equal(int64(3479447370796909))) + Expect(res[0].Longitude).To(Equal(15.087267458438873)) + Expect(res[0].Latitude).To(Equal(37.50266842333162)) + Expect(res[1].Name).To(Equal("Palermo")) + Expect(res[1].Dist).To(Equal(166.2742)) + Expect(res[1].GeoHash).To(Equal(int64(3479099956230698))) + Expect(res[1].Longitude).To(Equal(13.361389338970184)) + Expect(res[1].Latitude).To(Equal(38.115556395496299)) + }) + + It("should search geo radius with no results", func() { + res, err := client.GeoRadius("Sicily", 99, 37, &redis.GeoRadiusQuery{ + Radius: 200, + Unit: "km", + WithGeoHash: true, + WithCoord: true, + WithDist: true, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(HaveLen(0)) + }) + + It("should get geo distance with unit options", func() { + // From Redis CLI, note the difference in rounding in m vs + // km on Redis itself. + // + // GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" + // GEODIST Sicily Palermo Catania m + // "166274.15156960033" + // GEODIST Sicily Palermo Catania km + // "166.27415156960032" + dist, err := client.GeoDist("Sicily", "Palermo", "Catania", "km").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(dist).To(BeNumerically("~", 166.27, 0.01)) + + dist, err = client.GeoDist("Sicily", "Palermo", "Catania", "m").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(dist).To(BeNumerically("~", 166274.15, 0.01)) + }) + + It("should get geo hash in string representation", func() { + hashes, err := client.GeoHash("Sicily", "Palermo", "Catania").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(hashes).To(ConsistOf([]string{"sqc8b49rny0", "sqdtr74hyu0"})) + }) + + It("should return geo position", func() { + pos, err := client.GeoPos("Sicily", "Palermo", "Catania", "NonExisting").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(pos).To(ConsistOf([]*redis.GeoPos{ + { + Longitude: 13.361389338970184, + Latitude: 38.1155563954963, + }, + { + Longitude: 15.087267458438873, + Latitude: 37.50266842333162, + }, + nil, + })) + }) + }) + + Describe("marshaling/unmarshaling", func() { + + type convTest struct { + value interface{} + wanted string + dest interface{} + } + + convTests := []convTest{ + {nil, "", nil}, + {"hello", "hello", new(string)}, + {[]byte("hello"), "hello", new([]byte)}, + {int(1), "1", new(int)}, + {int8(1), "1", new(int8)}, + {int16(1), "1", new(int16)}, + {int32(1), "1", new(int32)}, + {int64(1), "1", new(int64)}, + {uint(1), "1", new(uint)}, + {uint8(1), "1", new(uint8)}, + {uint16(1), "1", new(uint16)}, + {uint32(1), "1", new(uint32)}, + {uint64(1), "1", new(uint64)}, + {float32(1.0), "1", new(float32)}, + {float64(1.0), "1", new(float64)}, + {true, "1", new(bool)}, + {false, "0", new(bool)}, + } + + It("should convert to string", func() { + for _, test := range convTests { + err := client.Set("key", test.value, 0).Err() + Expect(err).NotTo(HaveOccurred()) + + s, err := client.Get("key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(s).To(Equal(test.wanted)) + + if test.dest == nil { + continue + } + + err = client.Get("key").Scan(test.dest) + Expect(err).NotTo(HaveOccurred()) + Expect(deref(test.dest)).To(Equal(test.value)) + } + }) + + }) + + Describe("json marshaling/unmarshaling", func() { + + BeforeEach(func() { + value := &numberStruct{Number: 42} + err := client.Set("key", value, 0).Err() + Expect(err).NotTo(HaveOccurred()) + }) + + It("should marshal custom values using json", func() { + s, err := client.Get("key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(s).To(Equal(`{"Number":42}`)) + }) + + It("should scan custom values using json", func() { + value := &numberStruct{} + err := client.Get("key").Scan(value) + Expect(err).NotTo(HaveOccurred()) + Expect(value.Number).To(Equal(42)) + }) + + }) + + Describe("Eval", func() { + + It("returns keys and values", func() { + vals, err := client.Eval( + "return {KEYS[1],ARGV[1]}", + []string{"key"}, + "hello", + ).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]interface{}{"key", "hello"})) + }) + + }) + +}) + +type numberStruct struct { + Number int +} + +func (s *numberStruct) MarshalBinary() ([]byte, error) { + return json.Marshal(s) +} + +func (s *numberStruct) UnmarshalBinary(b []byte) error { + return json.Unmarshal(b, s) +} + +func deref(viface interface{}) interface{} { + v := reflect.ValueOf(viface) + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + return v.Interface() +} diff --git a/vendor/github.com/go-redis/redis/doc.go b/vendor/github.com/go-redis/redis/doc.go new file mode 100644 index 0000000..5526253 --- /dev/null +++ b/vendor/github.com/go-redis/redis/doc.go @@ -0,0 +1,4 @@ +/* +Package redis implements a Redis client. +*/ +package redis diff --git a/vendor/github.com/go-redis/redis/example_instrumentation_test.go b/vendor/github.com/go-redis/redis/example_instrumentation_test.go new file mode 100644 index 0000000..02051f9 --- /dev/null +++ b/vendor/github.com/go-redis/redis/example_instrumentation_test.go @@ -0,0 +1,59 @@ +package redis_test + +import ( + "fmt" + "sync/atomic" + "time" + + "github.com/go-redis/redis" +) + +func Example_instrumentation() { + ring := redis.NewRing(&redis.RingOptions{ + Addrs: map[string]string{ + "shard1": ":6379", + }, + }) + ring.ForEachShard(func(client *redis.Client) error { + wrapRedisProcess(client) + return nil + }) + + for { + ring.Ping() + } +} + +func wrapRedisProcess(client *redis.Client) { + const precision = time.Microsecond + var count, avgDur uint32 + + go func() { + for range time.Tick(3 * time.Second) { + n := atomic.LoadUint32(&count) + dur := time.Duration(atomic.LoadUint32(&avgDur)) * precision + fmt.Printf("%s: processed=%d avg_dur=%s\n", client, n, dur) + } + }() + + client.WrapProcess(func(oldProcess func(redis.Cmder) error) func(redis.Cmder) error { + return func(cmd redis.Cmder) error { + start := time.Now() + err := oldProcess(cmd) + dur := time.Since(start) + + const decay = float64(1) / 100 + ms := float64(dur / precision) + for { + avg := atomic.LoadUint32(&avgDur) + newAvg := uint32((1-decay)*float64(avg) + decay*ms) + if atomic.CompareAndSwapUint32(&avgDur, avg, newAvg) { + break + } + } + atomic.AddUint32(&count, 1) + + return err + } + }) +} diff --git a/vendor/github.com/go-redis/redis/example_test.go b/vendor/github.com/go-redis/redis/example_test.go new file mode 100644 index 0000000..4d18ddb --- /dev/null +++ b/vendor/github.com/go-redis/redis/example_test.go @@ -0,0 +1,414 @@ +package redis_test + +import ( + "fmt" + "strconv" + "sync" + "time" + + "github.com/go-redis/redis" +) + +var client *redis.Client + +func init() { + client = redis.NewClient(&redis.Options{ + Addr: ":6379", + DialTimeout: 10 * time.Second, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + PoolSize: 10, + PoolTimeout: 30 * time.Second, + }) + client.FlushDB() +} + +func ExampleNewClient() { + client := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 0, // use default DB + }) + + pong, err := client.Ping().Result() + fmt.Println(pong, err) + // Output: PONG +} + +func ExampleParseURL() { + opt, err := redis.ParseURL("redis://:qwerty@localhost:6379/1") + if err != nil { + panic(err) + } + fmt.Println("addr is", opt.Addr) + fmt.Println("db is", opt.DB) + fmt.Println("password is", opt.Password) + + // Create client as usually. + _ = redis.NewClient(opt) + + // Output: addr is localhost:6379 + // db is 1 + // password is qwerty +} + +func ExampleNewFailoverClient() { + // See http://redis.io/topics/sentinel for instructions how to + // setup Redis Sentinel. + client := redis.NewFailoverClient(&redis.FailoverOptions{ + MasterName: "master", + SentinelAddrs: []string{":26379"}, + }) + client.Ping() +} + +func ExampleNewClusterClient() { + // See http://redis.io/topics/cluster-tutorial for instructions + // how to setup Redis Cluster. + client := redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"}, + }) + client.Ping() +} + +func ExampleNewRing() { + client := redis.NewRing(&redis.RingOptions{ + Addrs: map[string]string{ + "shard1": ":7000", + "shard2": ":7001", + "shard3": ":7002", + }, + }) + client.Ping() +} + +func ExampleClient() { + err := client.Set("key", "value", 0).Err() + if err != nil { + panic(err) + } + + val, err := client.Get("key").Result() + if err != nil { + panic(err) + } + fmt.Println("key", val) + + val2, err := client.Get("key2").Result() + if err == redis.Nil { + fmt.Println("key2 does not exist") + } else if err != nil { + panic(err) + } else { + fmt.Println("key2", val2) + } + // Output: key value + // key2 does not exist +} + +func ExampleClient_Set() { + // Last argument is expiration. Zero means the key has no + // expiration time. + err := client.Set("key", "value", 0).Err() + if err != nil { + panic(err) + } + + // key2 will expire in an hour. + err = client.Set("key2", "value", time.Hour).Err() + if err != nil { + panic(err) + } +} + +func ExampleClient_Incr() { + result, err := client.Incr("counter").Result() + if err != nil { + panic(err) + } + + fmt.Println(result) + // Output: 1 +} + +func ExampleClient_BLPop() { + if err := client.RPush("queue", "message").Err(); err != nil { + panic(err) + } + + // use `client.BLPop(0, "queue")` for infinite waiting time + result, err := client.BLPop(1*time.Second, "queue").Result() + if err != nil { + panic(err) + } + + fmt.Println(result[0], result[1]) + // Output: queue message +} + +func ExampleClient_Scan() { + client.FlushDB() + for i := 0; i < 33; i++ { + err := client.Set(fmt.Sprintf("key%d", i), "value", 0).Err() + if err != nil { + panic(err) + } + } + + var cursor uint64 + var n int + for { + var keys []string + var err error + keys, cursor, err = client.Scan(cursor, "", 10).Result() + if err != nil { + panic(err) + } + n += len(keys) + if cursor == 0 { + break + } + } + + fmt.Printf("found %d keys\n", n) + // Output: found 33 keys +} + +func ExampleClient_Pipelined() { + var incr *redis.IntCmd + _, err := client.Pipelined(func(pipe redis.Pipeliner) error { + incr = pipe.Incr("pipelined_counter") + pipe.Expire("pipelined_counter", time.Hour) + return nil + }) + fmt.Println(incr.Val(), err) + // Output: 1 +} + +func ExampleClient_Pipeline() { + pipe := client.Pipeline() + + incr := pipe.Incr("pipeline_counter") + pipe.Expire("pipeline_counter", time.Hour) + + // Execute + // + // INCR pipeline_counter + // EXPIRE pipeline_counts 3600 + // + // using one client-server roundtrip. + _, err := pipe.Exec() + fmt.Println(incr.Val(), err) + // Output: 1 +} + +func ExampleClient_TxPipelined() { + var incr *redis.IntCmd + _, err := client.TxPipelined(func(pipe redis.Pipeliner) error { + incr = pipe.Incr("tx_pipelined_counter") + pipe.Expire("tx_pipelined_counter", time.Hour) + return nil + }) + fmt.Println(incr.Val(), err) + // Output: 1 +} + +func ExampleClient_TxPipeline() { + pipe := client.TxPipeline() + + incr := pipe.Incr("tx_pipeline_counter") + pipe.Expire("tx_pipeline_counter", time.Hour) + + // Execute + // + // MULTI + // INCR pipeline_counter + // EXPIRE pipeline_counts 3600 + // EXEC + // + // using one client-server roundtrip. + _, err := pipe.Exec() + fmt.Println(incr.Val(), err) + // Output: 1 +} + +func ExampleClient_Watch() { + var incr func(string) error + + // Transactionally increments key using GET and SET commands. + incr = func(key string) error { + err := client.Watch(func(tx *redis.Tx) error { + n, err := tx.Get(key).Int64() + if err != nil && err != redis.Nil { + return err + } + + _, err = tx.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Set(key, strconv.FormatInt(n+1, 10), 0) + return nil + }) + return err + }, key) + if err == redis.TxFailedErr { + return incr(key) + } + return err + } + + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + + err := incr("counter3") + if err != nil { + panic(err) + } + }() + } + wg.Wait() + + n, err := client.Get("counter3").Int64() + fmt.Println(n, err) + // Output: 100 +} + +func ExamplePubSub() { + pubsub := client.Subscribe("mychannel1") + defer pubsub.Close() + + // Wait for subscription to be created before publishing message. + subscr, err := pubsub.ReceiveTimeout(time.Second) + if err != nil { + panic(err) + } + fmt.Println(subscr) + + err = client.Publish("mychannel1", "hello").Err() + if err != nil { + panic(err) + } + + msg, err := pubsub.ReceiveMessage() + if err != nil { + panic(err) + } + + fmt.Println(msg.Channel, msg.Payload) + // Output: subscribe: mychannel1 + // mychannel1 hello +} + +func ExamplePubSub_Receive() { + pubsub := client.Subscribe("mychannel2") + defer pubsub.Close() + + for i := 0; i < 2; i++ { + // ReceiveTimeout is a low level API. Use ReceiveMessage instead. + msgi, err := pubsub.ReceiveTimeout(time.Second) + if err != nil { + break + } + + switch msg := msgi.(type) { + case *redis.Subscription: + fmt.Println("subscribed to", msg.Channel) + + _, err := client.Publish("mychannel2", "hello").Result() + if err != nil { + panic(err) + } + case *redis.Message: + fmt.Println("received", msg.Payload, "from", msg.Channel) + default: + panic("unreached") + } + } + + // sent message to 1 client + // received hello from mychannel2 +} + +func ExampleScript() { + IncrByXX := redis.NewScript(` + if redis.call("GET", KEYS[1]) ~= false then + return redis.call("INCRBY", KEYS[1], ARGV[1]) + end + return false + `) + + n, err := IncrByXX.Run(client, []string{"xx_counter"}, 2).Result() + fmt.Println(n, err) + + err = client.Set("xx_counter", "40", 0).Err() + if err != nil { + panic(err) + } + + n, err = IncrByXX.Run(client, []string{"xx_counter"}, 2).Result() + fmt.Println(n, err) + + // Output: redis: nil + // 42 +} + +func Example_customCommand() { + Get := func(client *redis.Client, key string) *redis.StringCmd { + cmd := redis.NewStringCmd("get", key) + client.Process(cmd) + return cmd + } + + v, err := Get(client, "key_does_not_exist").Result() + fmt.Printf("%q %s", v, err) + // Output: "" redis: nil +} + +func ExampleScanIterator() { + iter := client.Scan(0, "", 0).Iterator() + for iter.Next() { + fmt.Println(iter.Val()) + } + if err := iter.Err(); err != nil { + panic(err) + } +} + +func ExampleScanCmd_Iterator() { + iter := client.Scan(0, "", 0).Iterator() + for iter.Next() { + fmt.Println(iter.Val()) + } + if err := iter.Err(); err != nil { + panic(err) + } +} + +func ExampleNewUniversalClient_simple() { + client := redis.NewUniversalClient(&redis.UniversalOptions{ + Addrs: []string{":6379"}, + }) + defer client.Close() + + client.Ping() +} + +func ExampleNewUniversalClient_failover() { + client := redis.NewUniversalClient(&redis.UniversalOptions{ + MasterName: "master", + Addrs: []string{":26379"}, + }) + defer client.Close() + + client.Ping() +} + +func ExampleNewUniversalClient_cluster() { + client := redis.NewUniversalClient(&redis.UniversalOptions{ + Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"}, + }) + defer client.Close() + + client.Ping() +} diff --git a/vendor/github.com/go-redis/redis/export_test.go b/vendor/github.com/go-redis/redis/export_test.go new file mode 100644 index 0000000..bcc18c4 --- /dev/null +++ b/vendor/github.com/go-redis/redis/export_test.go @@ -0,0 +1,46 @@ +package redis + +import ( + "net" + "time" + + "github.com/go-redis/redis/internal/pool" +) + +func (c *baseClient) Pool() pool.Pooler { + return c.connPool +} + +func (c *PubSub) SetNetConn(netConn net.Conn) { + c.cn = pool.NewConn(netConn) +} + +func (c *PubSub) ReceiveMessageTimeout(timeout time.Duration) (*Message, error) { + return c.receiveMessage(timeout) +} + +func (c *ClusterClient) SlotAddrs(slot int) []string { + state, err := c.state() + if err != nil { + panic(err) + } + + var addrs []string + for _, n := range state.slotNodes(slot) { + addrs = append(addrs, n.Client.getAddr()) + } + return addrs +} + +// SwapSlot swaps a slot's master/slave address for testing MOVED redirects. +func (c *ClusterClient) SwapSlotNodes(slot int) { + state, err := c.state() + if err != nil { + panic(err) + } + + nodes := state.slots[slot] + if len(nodes) == 2 { + nodes[0], nodes[1] = nodes[1], nodes[0] + } +} diff --git a/vendor/github.com/go-redis/redis/internal/consistenthash/consistenthash.go b/vendor/github.com/go-redis/redis/internal/consistenthash/consistenthash.go new file mode 100644 index 0000000..a9c56f0 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/consistenthash/consistenthash.go @@ -0,0 +1,81 @@ +/* +Copyright 2013 Google Inc. + +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 consistenthash provides an implementation of a ring hash. +package consistenthash + +import ( + "hash/crc32" + "sort" + "strconv" +) + +type Hash func(data []byte) uint32 + +type Map struct { + hash Hash + replicas int + keys []int // Sorted + hashMap map[int]string +} + +func New(replicas int, fn Hash) *Map { + m := &Map{ + replicas: replicas, + hash: fn, + hashMap: make(map[int]string), + } + if m.hash == nil { + m.hash = crc32.ChecksumIEEE + } + return m +} + +// Returns true if there are no items available. +func (m *Map) IsEmpty() bool { + return len(m.keys) == 0 +} + +// Adds some keys to the hash. +func (m *Map) Add(keys ...string) { + for _, key := range keys { + for i := 0; i < m.replicas; i++ { + hash := int(m.hash([]byte(strconv.Itoa(i) + key))) + m.keys = append(m.keys, hash) + m.hashMap[hash] = key + } + } + sort.Ints(m.keys) +} + +// Gets the closest item in the hash to the provided key. +func (m *Map) Get(key string) string { + if m.IsEmpty() { + return "" + } + + hash := int(m.hash([]byte(key))) + + // Binary search for appropriate replica. + idx := sort.Search(len(m.keys), func(i int) bool { return m.keys[i] >= hash }) + + // Means we have cycled back to the first replica. + if idx == len(m.keys) { + idx = 0 + } + + return m.hashMap[m.keys[idx]] +} diff --git a/vendor/github.com/go-redis/redis/internal/consistenthash/consistenthash_test.go b/vendor/github.com/go-redis/redis/internal/consistenthash/consistenthash_test.go new file mode 100644 index 0000000..1a37fd7 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/consistenthash/consistenthash_test.go @@ -0,0 +1,110 @@ +/* +Copyright 2013 Google Inc. + +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 consistenthash + +import ( + "fmt" + "strconv" + "testing" +) + +func TestHashing(t *testing.T) { + + // Override the hash function to return easier to reason about values. Assumes + // the keys can be converted to an integer. + hash := New(3, func(key []byte) uint32 { + i, err := strconv.Atoi(string(key)) + if err != nil { + panic(err) + } + return uint32(i) + }) + + // Given the above hash function, this will give replicas with "hashes": + // 2, 4, 6, 12, 14, 16, 22, 24, 26 + hash.Add("6", "4", "2") + + testCases := map[string]string{ + "2": "2", + "11": "2", + "23": "4", + "27": "2", + } + + for k, v := range testCases { + if hash.Get(k) != v { + t.Errorf("Asking for %s, should have yielded %s", k, v) + } + } + + // Adds 8, 18, 28 + hash.Add("8") + + // 27 should now map to 8. + testCases["27"] = "8" + + for k, v := range testCases { + if hash.Get(k) != v { + t.Errorf("Asking for %s, should have yielded %s", k, v) + } + } + +} + +func TestConsistency(t *testing.T) { + hash1 := New(1, nil) + hash2 := New(1, nil) + + hash1.Add("Bill", "Bob", "Bonny") + hash2.Add("Bob", "Bonny", "Bill") + + if hash1.Get("Ben") != hash2.Get("Ben") { + t.Errorf("Fetching 'Ben' from both hashes should be the same") + } + + hash2.Add("Becky", "Ben", "Bobby") + + if hash1.Get("Ben") != hash2.Get("Ben") || + hash1.Get("Bob") != hash2.Get("Bob") || + hash1.Get("Bonny") != hash2.Get("Bonny") { + t.Errorf("Direct matches should always return the same entry") + } + +} + +func BenchmarkGet8(b *testing.B) { benchmarkGet(b, 8) } +func BenchmarkGet32(b *testing.B) { benchmarkGet(b, 32) } +func BenchmarkGet128(b *testing.B) { benchmarkGet(b, 128) } +func BenchmarkGet512(b *testing.B) { benchmarkGet(b, 512) } + +func benchmarkGet(b *testing.B, shards int) { + + hash := New(50, nil) + + var buckets []string + for i := 0; i < shards; i++ { + buckets = append(buckets, fmt.Sprintf("shard-%d", i)) + } + + hash.Add(buckets...) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + hash.Get(buckets[i&(shards-1)]) + } +} diff --git a/vendor/github.com/go-redis/redis/internal/error.go b/vendor/github.com/go-redis/redis/internal/error.go new file mode 100644 index 0000000..0898eeb --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/error.go @@ -0,0 +1,84 @@ +package internal + +import ( + "io" + "net" + "strings" +) + +const Nil = RedisError("redis: nil") + +type RedisError string + +func (e RedisError) Error() string { return string(e) } + +func IsRetryableError(err error, retryNetError bool) bool { + if IsNetworkError(err) { + return retryNetError + } + s := err.Error() + if s == "ERR max number of clients reached" { + return true + } + if strings.HasPrefix(s, "LOADING ") { + return true + } + if strings.HasPrefix(s, "CLUSTERDOWN ") { + return true + } + return false +} + +func IsRedisError(err error) bool { + _, ok := err.(RedisError) + return ok +} + +func IsNetworkError(err error) bool { + if err == io.EOF { + return true + } + _, ok := err.(net.Error) + return ok +} + +func IsBadConn(err error, allowTimeout bool) bool { + if err == nil { + return false + } + if IsRedisError(err) { + return false + } + if allowTimeout { + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + return false + } + } + return true +} + +func IsMovedError(err error) (moved bool, ask bool, addr string) { + if !IsRedisError(err) { + return + } + + s := err.Error() + if strings.HasPrefix(s, "MOVED ") { + moved = true + } else if strings.HasPrefix(s, "ASK ") { + ask = true + } else { + return + } + + ind := strings.LastIndex(s, " ") + if ind == -1 { + return false, false, "" + } + addr = s[ind+1:] + return +} + +func IsLoadingError(err error) bool { + return strings.HasPrefix(err.Error(), "LOADING ") +} diff --git a/vendor/github.com/go-redis/redis/internal/hashtag/hashtag.go b/vendor/github.com/go-redis/redis/internal/hashtag/hashtag.go new file mode 100644 index 0000000..2866488 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/hashtag/hashtag.go @@ -0,0 +1,73 @@ +package hashtag + +import ( + "math/rand" + "strings" +) + +const SlotNumber = 16384 + +// CRC16 implementation according to CCITT standards. +// Copyright 2001-2010 Georges Menie (www.menie.org) +// Copyright 2013 The Go Authors. All rights reserved. +// http://redis.io/topics/cluster-spec#appendix-a-crc16-reference-implementation-in-ansi-c +var crc16tab = [256]uint16{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, +} + +func Key(key string) string { + if s := strings.IndexByte(key, '{'); s > -1 { + if e := strings.IndexByte(key[s+1:], '}'); e > 0 { + return key[s+1 : s+e+1] + } + } + return key +} + +// hashSlot returns a consistent slot number between 0 and 16383 +// for any given string key. +func Slot(key string) int { + key = Key(key) + if key == "" { + return rand.Intn(SlotNumber) + } + return int(crc16sum(key)) % SlotNumber +} + +func crc16sum(key string) (crc uint16) { + for i := 0; i < len(key); i++ { + crc = (crc << 8) ^ crc16tab[(byte(crc>>8)^key[i])&0x00ff] + } + return +} diff --git a/vendor/github.com/go-redis/redis/internal/hashtag/hashtag_test.go b/vendor/github.com/go-redis/redis/internal/hashtag/hashtag_test.go new file mode 100644 index 0000000..7f0fedf --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/hashtag/hashtag_test.go @@ -0,0 +1,74 @@ +package hashtag + +import ( + "math/rand" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestGinkgoSuite(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "hashtag") +} + +var _ = Describe("CRC16", func() { + + // http://redis.io/topics/cluster-spec#keys-distribution-model + It("should calculate CRC16", func() { + tests := []struct { + s string + n uint16 + }{ + {"123456789", 0x31C3}, + {string([]byte{83, 153, 134, 118, 229, 214, 244, 75, 140, 37, 215, 215}), 21847}, + } + + for _, test := range tests { + Expect(crc16sum(test.s)).To(Equal(test.n), "for %s", test.s) + } + }) + +}) + +var _ = Describe("HashSlot", func() { + + It("should calculate hash slots", func() { + tests := []struct { + key string + slot int + }{ + {"123456789", 12739}, + {"{}foo", 9500}, + {"foo{}", 5542}, + {"foo{}{bar}", 8363}, + {"", 10503}, + {"", 5176}, + {string([]byte{83, 153, 134, 118, 229, 214, 244, 75, 140, 37, 215, 215}), 5463}, + } + // Empty keys receive random slot. + rand.Seed(100) + + for _, test := range tests { + Expect(Slot(test.key)).To(Equal(test.slot), "for %s", test.key) + } + }) + + It("should extract keys from tags", func() { + tests := []struct { + one, two string + }{ + {"foo{bar}", "bar"}, + {"{foo}bar", "foo"}, + {"{user1000}.following", "{user1000}.followers"}, + {"foo{{bar}}zap", "{bar"}, + {"foo{bar}{zap}", "bar"}, + } + + for _, test := range tests { + Expect(Slot(test.one)).To(Equal(Slot(test.two)), "for %s <-> %s", test.one, test.two) + } + }) + +}) diff --git a/vendor/github.com/go-redis/redis/internal/internal.go b/vendor/github.com/go-redis/redis/internal/internal.go new file mode 100644 index 0000000..ad3fc3c --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/internal.go @@ -0,0 +1,24 @@ +package internal + +import ( + "math/rand" + "time" +) + +// Retry backoff with jitter sleep to prevent overloaded conditions during intervals +// https://www.awsarchitectureblog.com/2015/03/backoff.html +func RetryBackoff(retry int, minBackoff, maxBackoff time.Duration) time.Duration { + if retry < 0 { + retry = 0 + } + + backoff := minBackoff << uint(retry) + if backoff > maxBackoff || backoff < minBackoff { + backoff = maxBackoff + } + + if backoff == 0 { + return 0 + } + return time.Duration(rand.Int63n(int64(backoff))) +} diff --git a/vendor/github.com/go-redis/redis/internal/internal_test.go b/vendor/github.com/go-redis/redis/internal/internal_test.go new file mode 100644 index 0000000..56ff611 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/internal_test.go @@ -0,0 +1,18 @@ +package internal + +import ( + "testing" + "time" + + . "github.com/onsi/gomega" +) + +func TestRetryBackoff(t *testing.T) { + RegisterTestingT(t) + + for i := -1; i <= 16; i++ { + backoff := RetryBackoff(i, time.Millisecond, 512*time.Millisecond) + Expect(backoff >= 0).To(BeTrue()) + Expect(backoff <= 512*time.Millisecond).To(BeTrue()) + } +} diff --git a/vendor/github.com/go-redis/redis/internal/log.go b/vendor/github.com/go-redis/redis/internal/log.go new file mode 100644 index 0000000..fd14222 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/log.go @@ -0,0 +1,15 @@ +package internal + +import ( + "fmt" + "log" +) + +var Logger *log.Logger + +func Logf(s string, args ...interface{}) { + if Logger == nil { + return + } + Logger.Output(2, fmt.Sprintf(s, args...)) +} diff --git a/vendor/github.com/go-redis/redis/internal/once.go b/vendor/github.com/go-redis/redis/internal/once.go new file mode 100644 index 0000000..64f4627 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/once.go @@ -0,0 +1,60 @@ +/* +Copyright 2014 The Camlistore Authors + +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 internal + +import ( + "sync" + "sync/atomic" +) + +// A Once will perform a successful action exactly once. +// +// Unlike a sync.Once, this Once's func returns an error +// and is re-armed on failure. +type Once struct { + m sync.Mutex + done uint32 +} + +// Do calls the function f if and only if Do has not been invoked +// without error for this instance of Once. In other words, given +// var once Once +// if once.Do(f) is called multiple times, only the first call will +// invoke f, even if f has a different value in each invocation unless +// f returns an error. A new instance of Once is required for each +// function to execute. +// +// Do is intended for initialization that must be run exactly once. Since f +// is niladic, it may be necessary to use a function literal to capture the +// arguments to a function to be invoked by Do: +// err := config.once.Do(func() error { return config.init(filename) }) +func (o *Once) Do(f func() error) error { + if atomic.LoadUint32(&o.done) == 1 { + return nil + } + // Slow-path. + o.m.Lock() + defer o.m.Unlock() + var err error + if o.done == 0 { + err = f() + if err == nil { + atomic.StoreUint32(&o.done, 1) + } + } + return err +} diff --git a/vendor/github.com/go-redis/redis/internal/pool/bench_test.go b/vendor/github.com/go-redis/redis/internal/pool/bench_test.go new file mode 100644 index 0000000..e0bb524 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/pool/bench_test.go @@ -0,0 +1,80 @@ +package pool_test + +import ( + "testing" + "time" + + "github.com/go-redis/redis/internal/pool" +) + +func benchmarkPoolGetPut(b *testing.B, poolSize int) { + connPool := pool.NewConnPool(&pool.Options{ + Dialer: dummyDialer, + PoolSize: poolSize, + PoolTimeout: time.Second, + IdleTimeout: time.Hour, + IdleCheckFrequency: time.Hour, + }) + + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + cn, _, err := connPool.Get() + if err != nil { + b.Fatal(err) + } + if err = connPool.Put(cn); err != nil { + b.Fatal(err) + } + } + }) +} + +func BenchmarkPoolGetPut10Conns(b *testing.B) { + benchmarkPoolGetPut(b, 10) +} + +func BenchmarkPoolGetPut100Conns(b *testing.B) { + benchmarkPoolGetPut(b, 100) +} + +func BenchmarkPoolGetPut1000Conns(b *testing.B) { + benchmarkPoolGetPut(b, 1000) +} + +func benchmarkPoolGetRemove(b *testing.B, poolSize int) { + connPool := pool.NewConnPool(&pool.Options{ + Dialer: dummyDialer, + PoolSize: poolSize, + PoolTimeout: time.Second, + IdleTimeout: time.Hour, + IdleCheckFrequency: time.Hour, + }) + + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + cn, _, err := connPool.Get() + if err != nil { + b.Fatal(err) + } + if err := connPool.Remove(cn); err != nil { + b.Fatal(err) + } + } + }) +} + +func BenchmarkPoolGetRemove10Conns(b *testing.B) { + benchmarkPoolGetRemove(b, 10) +} + +func BenchmarkPoolGetRemove100Conns(b *testing.B) { + benchmarkPoolGetRemove(b, 100) +} + +func BenchmarkPoolGetRemove1000Conns(b *testing.B) { + benchmarkPoolGetRemove(b, 1000) +} diff --git a/vendor/github.com/go-redis/redis/internal/pool/conn.go b/vendor/github.com/go-redis/redis/internal/pool/conn.go new file mode 100644 index 0000000..8af51d9 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/pool/conn.go @@ -0,0 +1,78 @@ +package pool + +import ( + "net" + "sync/atomic" + "time" + + "github.com/go-redis/redis/internal/proto" +) + +var noDeadline = time.Time{} + +type Conn struct { + netConn net.Conn + + Rd *proto.Reader + Wb *proto.WriteBuffer + + Inited bool + usedAt atomic.Value +} + +func NewConn(netConn net.Conn) *Conn { + cn := &Conn{ + netConn: netConn, + Wb: proto.NewWriteBuffer(), + } + cn.Rd = proto.NewReader(cn.netConn) + cn.SetUsedAt(time.Now()) + return cn +} + +func (cn *Conn) UsedAt() time.Time { + return cn.usedAt.Load().(time.Time) +} + +func (cn *Conn) SetUsedAt(tm time.Time) { + cn.usedAt.Store(tm) +} + +func (cn *Conn) SetNetConn(netConn net.Conn) { + cn.netConn = netConn + cn.Rd.Reset(netConn) +} + +func (cn *Conn) IsStale(timeout time.Duration) bool { + return timeout > 0 && time.Since(cn.UsedAt()) > timeout +} + +func (cn *Conn) SetReadTimeout(timeout time.Duration) error { + now := time.Now() + cn.SetUsedAt(now) + if timeout > 0 { + return cn.netConn.SetReadDeadline(now.Add(timeout)) + } + return cn.netConn.SetReadDeadline(noDeadline) +} + +func (cn *Conn) SetWriteTimeout(timeout time.Duration) error { + now := time.Now() + cn.SetUsedAt(now) + if timeout > 0 { + return cn.netConn.SetWriteDeadline(now.Add(timeout)) + } + return cn.netConn.SetWriteDeadline(noDeadline) +} + +func (cn *Conn) Write(b []byte) (int, error) { + return cn.netConn.Write(b) +} + +func (cn *Conn) RemoteAddr() net.Addr { + return cn.netConn.RemoteAddr() +} + +func (cn *Conn) Close() error { + return cn.netConn.Close() +} diff --git a/vendor/github.com/go-redis/redis/internal/pool/main_test.go b/vendor/github.com/go-redis/redis/internal/pool/main_test.go new file mode 100644 index 0000000..43afe3f --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/pool/main_test.go @@ -0,0 +1,35 @@ +package pool_test + +import ( + "net" + "sync" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestGinkgoSuite(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "pool") +} + +func perform(n int, cbs ...func(int)) { + var wg sync.WaitGroup + for _, cb := range cbs { + for i := 0; i < n; i++ { + wg.Add(1) + go func(cb func(int), i int) { + defer GinkgoRecover() + defer wg.Done() + + cb(i) + }(cb, i) + } + } + wg.Wait() +} + +func dummyDialer() (net.Conn, error) { + return &net.TCPConn{}, nil +} diff --git a/vendor/github.com/go-redis/redis/internal/pool/pool.go b/vendor/github.com/go-redis/redis/internal/pool/pool.go new file mode 100644 index 0000000..ae81905 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/pool/pool.go @@ -0,0 +1,377 @@ +package pool + +import ( + "errors" + "net" + "sync" + "sync/atomic" + "time" + + "github.com/go-redis/redis/internal" +) + +var ErrClosed = errors.New("redis: client is closed") +var ErrPoolTimeout = errors.New("redis: connection pool timeout") + +var timers = sync.Pool{ + New: func() interface{} { + t := time.NewTimer(time.Hour) + t.Stop() + return t + }, +} + +// Stats contains pool state information and accumulated stats. +type Stats struct { + Hits uint32 // number of times free connection was found in the pool + Misses uint32 // number of times free connection was NOT found in the pool + Timeouts uint32 // number of times a wait timeout occurred + + TotalConns uint32 // number of total connections in the pool + FreeConns uint32 // number of free connections in the pool + StaleConns uint32 // number of stale connections removed from the pool +} + +type Pooler interface { + NewConn() (*Conn, error) + CloseConn(*Conn) error + + Get() (*Conn, bool, error) + Put(*Conn) error + Remove(*Conn) error + + Len() int + FreeLen() int + Stats() *Stats + + Close() error +} + +type Options struct { + Dialer func() (net.Conn, error) + OnClose func(*Conn) error + + PoolSize int + PoolTimeout time.Duration + IdleTimeout time.Duration + IdleCheckFrequency time.Duration +} + +type ConnPool struct { + opt *Options + + dialErrorsNum uint32 // atomic + + lastDialError error + lastDialErrorMu sync.RWMutex + + queue chan struct{} + + connsMu sync.Mutex + conns []*Conn + + freeConnsMu sync.Mutex + freeConns []*Conn + + stats Stats + + _closed uint32 // atomic +} + +var _ Pooler = (*ConnPool)(nil) + +func NewConnPool(opt *Options) *ConnPool { + p := &ConnPool{ + opt: opt, + + queue: make(chan struct{}, opt.PoolSize), + conns: make([]*Conn, 0, opt.PoolSize), + freeConns: make([]*Conn, 0, opt.PoolSize), + } + if opt.IdleTimeout > 0 && opt.IdleCheckFrequency > 0 { + go p.reaper(opt.IdleCheckFrequency) + } + return p +} + +func (p *ConnPool) NewConn() (*Conn, error) { + if p.closed() { + return nil, ErrClosed + } + + if atomic.LoadUint32(&p.dialErrorsNum) >= uint32(p.opt.PoolSize) { + return nil, p.getLastDialError() + } + + netConn, err := p.opt.Dialer() + if err != nil { + p.setLastDialError(err) + if atomic.AddUint32(&p.dialErrorsNum, 1) == uint32(p.opt.PoolSize) { + go p.tryDial() + } + return nil, err + } + + cn := NewConn(netConn) + p.connsMu.Lock() + p.conns = append(p.conns, cn) + p.connsMu.Unlock() + + return cn, nil +} + +func (p *ConnPool) tryDial() { + for { + if p.closed() { + return + } + + conn, err := p.opt.Dialer() + if err != nil { + p.setLastDialError(err) + time.Sleep(time.Second) + continue + } + + atomic.StoreUint32(&p.dialErrorsNum, 0) + _ = conn.Close() + return + } +} + +func (p *ConnPool) setLastDialError(err error) { + p.lastDialErrorMu.Lock() + p.lastDialError = err + p.lastDialErrorMu.Unlock() +} + +func (p *ConnPool) getLastDialError() error { + p.lastDialErrorMu.RLock() + err := p.lastDialError + p.lastDialErrorMu.RUnlock() + return err +} + +// Get returns existed connection from the pool or creates a new one. +func (p *ConnPool) Get() (*Conn, bool, error) { + if p.closed() { + return nil, false, ErrClosed + } + + select { + case p.queue <- struct{}{}: + default: + timer := timers.Get().(*time.Timer) + timer.Reset(p.opt.PoolTimeout) + + select { + case p.queue <- struct{}{}: + if !timer.Stop() { + <-timer.C + } + timers.Put(timer) + case <-timer.C: + timers.Put(timer) + atomic.AddUint32(&p.stats.Timeouts, 1) + return nil, false, ErrPoolTimeout + } + } + + for { + p.freeConnsMu.Lock() + cn := p.popFree() + p.freeConnsMu.Unlock() + + if cn == nil { + break + } + + if cn.IsStale(p.opt.IdleTimeout) { + p.CloseConn(cn) + continue + } + + atomic.AddUint32(&p.stats.Hits, 1) + return cn, false, nil + } + + atomic.AddUint32(&p.stats.Misses, 1) + + newcn, err := p.NewConn() + if err != nil { + <-p.queue + return nil, false, err + } + + return newcn, true, nil +} + +func (p *ConnPool) popFree() *Conn { + if len(p.freeConns) == 0 { + return nil + } + + idx := len(p.freeConns) - 1 + cn := p.freeConns[idx] + p.freeConns = p.freeConns[:idx] + return cn +} + +func (p *ConnPool) Put(cn *Conn) error { + if data := cn.Rd.PeekBuffered(); data != nil { + internal.Logf("connection has unread data: %q", data) + return p.Remove(cn) + } + p.freeConnsMu.Lock() + p.freeConns = append(p.freeConns, cn) + p.freeConnsMu.Unlock() + <-p.queue + return nil +} + +func (p *ConnPool) Remove(cn *Conn) error { + _ = p.CloseConn(cn) + <-p.queue + return nil +} + +func (p *ConnPool) CloseConn(cn *Conn) error { + p.connsMu.Lock() + for i, c := range p.conns { + if c == cn { + p.conns = append(p.conns[:i], p.conns[i+1:]...) + break + } + } + p.connsMu.Unlock() + + return p.closeConn(cn) +} + +func (p *ConnPool) closeConn(cn *Conn) error { + if p.opt.OnClose != nil { + _ = p.opt.OnClose(cn) + } + return cn.Close() +} + +// Len returns total number of connections. +func (p *ConnPool) Len() int { + p.connsMu.Lock() + l := len(p.conns) + p.connsMu.Unlock() + return l +} + +// FreeLen returns number of free connections. +func (p *ConnPool) FreeLen() int { + p.freeConnsMu.Lock() + l := len(p.freeConns) + p.freeConnsMu.Unlock() + return l +} + +func (p *ConnPool) Stats() *Stats { + return &Stats{ + Hits: atomic.LoadUint32(&p.stats.Hits), + Misses: atomic.LoadUint32(&p.stats.Misses), + Timeouts: atomic.LoadUint32(&p.stats.Timeouts), + + TotalConns: uint32(p.Len()), + FreeConns: uint32(p.FreeLen()), + StaleConns: atomic.LoadUint32(&p.stats.StaleConns), + } +} + +func (p *ConnPool) closed() bool { + return atomic.LoadUint32(&p._closed) == 1 +} + +func (p *ConnPool) Filter(fn func(*Conn) bool) error { + var firstErr error + p.connsMu.Lock() + for _, cn := range p.conns { + if fn(cn) { + if err := p.closeConn(cn); err != nil && firstErr == nil { + firstErr = err + } + } + } + p.connsMu.Unlock() + return firstErr +} + +func (p *ConnPool) Close() error { + if !atomic.CompareAndSwapUint32(&p._closed, 0, 1) { + return ErrClosed + } + + var firstErr error + p.connsMu.Lock() + for _, cn := range p.conns { + if err := p.closeConn(cn); err != nil && firstErr == nil { + firstErr = err + } + } + p.conns = nil + p.connsMu.Unlock() + + p.freeConnsMu.Lock() + p.freeConns = nil + p.freeConnsMu.Unlock() + + return firstErr +} + +func (p *ConnPool) reapStaleConn() bool { + if len(p.freeConns) == 0 { + return false + } + + cn := p.freeConns[0] + if !cn.IsStale(p.opt.IdleTimeout) { + return false + } + + p.CloseConn(cn) + p.freeConns = append(p.freeConns[:0], p.freeConns[1:]...) + + return true +} + +func (p *ConnPool) ReapStaleConns() (int, error) { + var n int + for { + p.queue <- struct{}{} + p.freeConnsMu.Lock() + + reaped := p.reapStaleConn() + + p.freeConnsMu.Unlock() + <-p.queue + + if reaped { + n++ + } else { + break + } + } + return n, nil +} + +func (p *ConnPool) reaper(frequency time.Duration) { + ticker := time.NewTicker(frequency) + defer ticker.Stop() + + for range ticker.C { + if p.closed() { + break + } + n, err := p.ReapStaleConns() + if err != nil { + internal.Logf("ReapStaleConns failed: %s", err) + continue + } + atomic.AddUint32(&p.stats.StaleConns, uint32(n)) + } +} diff --git a/vendor/github.com/go-redis/redis/internal/pool/pool_single.go b/vendor/github.com/go-redis/redis/internal/pool/pool_single.go new file mode 100644 index 0000000..ff91279 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/pool/pool_single.go @@ -0,0 +1,55 @@ +package pool + +type SingleConnPool struct { + cn *Conn +} + +var _ Pooler = (*SingleConnPool)(nil) + +func NewSingleConnPool(cn *Conn) *SingleConnPool { + return &SingleConnPool{ + cn: cn, + } +} + +func (p *SingleConnPool) NewConn() (*Conn, error) { + panic("not implemented") +} + +func (p *SingleConnPool) CloseConn(*Conn) error { + panic("not implemented") +} + +func (p *SingleConnPool) Get() (*Conn, bool, error) { + return p.cn, false, nil +} + +func (p *SingleConnPool) Put(cn *Conn) error { + if p.cn != cn { + panic("p.cn != cn") + } + return nil +} + +func (p *SingleConnPool) Remove(cn *Conn) error { + if p.cn != cn { + panic("p.cn != cn") + } + return nil +} + +func (p *SingleConnPool) Len() int { + return 1 +} + +func (p *SingleConnPool) FreeLen() int { + return 0 +} + +func (p *SingleConnPool) Stats() *Stats { + return nil +} + +func (p *SingleConnPool) Close() error { + return nil +} diff --git a/vendor/github.com/go-redis/redis/internal/pool/pool_sticky.go b/vendor/github.com/go-redis/redis/internal/pool/pool_sticky.go new file mode 100644 index 0000000..17f1638 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/pool/pool_sticky.go @@ -0,0 +1,123 @@ +package pool + +import "sync" + +type StickyConnPool struct { + pool *ConnPool + reusable bool + + cn *Conn + closed bool + mu sync.Mutex +} + +var _ Pooler = (*StickyConnPool)(nil) + +func NewStickyConnPool(pool *ConnPool, reusable bool) *StickyConnPool { + return &StickyConnPool{ + pool: pool, + reusable: reusable, + } +} + +func (p *StickyConnPool) NewConn() (*Conn, error) { + panic("not implemented") +} + +func (p *StickyConnPool) CloseConn(*Conn) error { + panic("not implemented") +} + +func (p *StickyConnPool) Get() (*Conn, bool, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if p.closed { + return nil, false, ErrClosed + } + if p.cn != nil { + return p.cn, false, nil + } + + cn, _, err := p.pool.Get() + if err != nil { + return nil, false, err + } + p.cn = cn + return cn, true, nil +} + +func (p *StickyConnPool) putUpstream() (err error) { + err = p.pool.Put(p.cn) + p.cn = nil + return err +} + +func (p *StickyConnPool) Put(cn *Conn) error { + p.mu.Lock() + defer p.mu.Unlock() + + if p.closed { + return ErrClosed + } + return nil +} + +func (p *StickyConnPool) removeUpstream() error { + err := p.pool.Remove(p.cn) + p.cn = nil + return err +} + +func (p *StickyConnPool) Remove(cn *Conn) error { + p.mu.Lock() + defer p.mu.Unlock() + + if p.closed { + return nil + } + return p.removeUpstream() +} + +func (p *StickyConnPool) Len() int { + p.mu.Lock() + defer p.mu.Unlock() + + if p.cn == nil { + return 0 + } + return 1 +} + +func (p *StickyConnPool) FreeLen() int { + p.mu.Lock() + defer p.mu.Unlock() + + if p.cn == nil { + return 1 + } + return 0 +} + +func (p *StickyConnPool) Stats() *Stats { + return nil +} + +func (p *StickyConnPool) Close() error { + p.mu.Lock() + defer p.mu.Unlock() + + if p.closed { + return ErrClosed + } + p.closed = true + var err error + if p.cn != nil { + if p.reusable { + err = p.putUpstream() + } else { + err = p.removeUpstream() + } + } + return err +} diff --git a/vendor/github.com/go-redis/redis/internal/pool/pool_test.go b/vendor/github.com/go-redis/redis/internal/pool/pool_test.go new file mode 100644 index 0000000..68c9a1b --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/pool/pool_test.go @@ -0,0 +1,241 @@ +package pool_test + +import ( + "testing" + "time" + + "github.com/go-redis/redis/internal/pool" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("ConnPool", func() { + var connPool *pool.ConnPool + + BeforeEach(func() { + connPool = pool.NewConnPool(&pool.Options{ + Dialer: dummyDialer, + PoolSize: 10, + PoolTimeout: time.Hour, + IdleTimeout: time.Millisecond, + IdleCheckFrequency: time.Millisecond, + }) + }) + + AfterEach(func() { + connPool.Close() + }) + + It("should unblock client when conn is removed", func() { + // Reserve one connection. + cn, _, err := connPool.Get() + Expect(err).NotTo(HaveOccurred()) + + // Reserve all other connections. + var cns []*pool.Conn + for i := 0; i < 9; i++ { + cn, _, err := connPool.Get() + Expect(err).NotTo(HaveOccurred()) + cns = append(cns, cn) + } + + started := make(chan bool, 1) + done := make(chan bool, 1) + go func() { + defer GinkgoRecover() + + started <- true + _, _, err := connPool.Get() + Expect(err).NotTo(HaveOccurred()) + done <- true + + err = connPool.Put(cn) + Expect(err).NotTo(HaveOccurred()) + }() + <-started + + // Check that Get is blocked. + select { + case <-done: + Fail("Get is not blocked") + default: + // ok + } + + err = connPool.Remove(cn) + Expect(err).NotTo(HaveOccurred()) + + // Check that Ping is unblocked. + select { + case <-done: + // ok + case <-time.After(time.Second): + Fail("Get is not unblocked") + } + + for _, cn := range cns { + err = connPool.Put(cn) + Expect(err).NotTo(HaveOccurred()) + } + }) +}) + +var _ = Describe("conns reaper", func() { + const idleTimeout = time.Minute + + var connPool *pool.ConnPool + var conns, idleConns, closedConns []*pool.Conn + + BeforeEach(func() { + conns = nil + closedConns = nil + + connPool = pool.NewConnPool(&pool.Options{ + Dialer: dummyDialer, + PoolSize: 10, + PoolTimeout: time.Second, + IdleTimeout: idleTimeout, + IdleCheckFrequency: time.Hour, + + OnClose: func(cn *pool.Conn) error { + closedConns = append(closedConns, cn) + return nil + }, + }) + + // add stale connections + idleConns = nil + for i := 0; i < 3; i++ { + cn, _, err := connPool.Get() + Expect(err).NotTo(HaveOccurred()) + cn.SetUsedAt(time.Now().Add(-2 * idleTimeout)) + conns = append(conns, cn) + idleConns = append(idleConns, cn) + } + + // add fresh connections + for i := 0; i < 3; i++ { + cn, _, err := connPool.Get() + Expect(err).NotTo(HaveOccurred()) + conns = append(conns, cn) + } + + for _, cn := range conns { + Expect(connPool.Put(cn)).NotTo(HaveOccurred()) + } + + Expect(connPool.Len()).To(Equal(6)) + Expect(connPool.FreeLen()).To(Equal(6)) + + n, err := connPool.ReapStaleConns() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(3)) + }) + + AfterEach(func() { + _ = connPool.Close() + Expect(connPool.Len()).To(Equal(0)) + Expect(connPool.FreeLen()).To(Equal(0)) + Expect(len(closedConns)).To(Equal(len(conns))) + Expect(closedConns).To(ConsistOf(conns)) + }) + + It("reaps stale connections", func() { + Expect(connPool.Len()).To(Equal(3)) + Expect(connPool.FreeLen()).To(Equal(3)) + }) + + It("does not reap fresh connections", func() { + n, err := connPool.ReapStaleConns() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(0)) + }) + + It("stale connections are closed", func() { + Expect(len(closedConns)).To(Equal(len(idleConns))) + Expect(closedConns).To(ConsistOf(idleConns)) + }) + + It("pool is functional", func() { + for j := 0; j < 3; j++ { + var freeCns []*pool.Conn + for i := 0; i < 3; i++ { + cn, _, err := connPool.Get() + Expect(err).NotTo(HaveOccurred()) + Expect(cn).NotTo(BeNil()) + freeCns = append(freeCns, cn) + } + + Expect(connPool.Len()).To(Equal(3)) + Expect(connPool.FreeLen()).To(Equal(0)) + + cn, _, err := connPool.Get() + Expect(err).NotTo(HaveOccurred()) + Expect(cn).NotTo(BeNil()) + conns = append(conns, cn) + + Expect(connPool.Len()).To(Equal(4)) + Expect(connPool.FreeLen()).To(Equal(0)) + + err = connPool.Remove(cn) + Expect(err).NotTo(HaveOccurred()) + + Expect(connPool.Len()).To(Equal(3)) + Expect(connPool.FreeLen()).To(Equal(0)) + + for _, cn := range freeCns { + err := connPool.Put(cn) + Expect(err).NotTo(HaveOccurred()) + } + + Expect(connPool.Len()).To(Equal(3)) + Expect(connPool.FreeLen()).To(Equal(3)) + } + }) +}) + +var _ = Describe("race", func() { + var connPool *pool.ConnPool + var C, N int + + BeforeEach(func() { + C, N = 10, 1000 + if testing.Short() { + C = 4 + N = 100 + } + }) + + AfterEach(func() { + connPool.Close() + }) + + It("does not happen on Get, Put, and Remove", func() { + connPool = pool.NewConnPool(&pool.Options{ + Dialer: dummyDialer, + PoolSize: 10, + PoolTimeout: time.Minute, + IdleTimeout: time.Millisecond, + IdleCheckFrequency: time.Millisecond, + }) + + perform(C, func(id int) { + for i := 0; i < N; i++ { + cn, _, err := connPool.Get() + Expect(err).NotTo(HaveOccurred()) + if err == nil { + Expect(connPool.Put(cn)).NotTo(HaveOccurred()) + } + } + }, func(id int) { + for i := 0; i < N; i++ { + cn, _, err := connPool.Get() + Expect(err).NotTo(HaveOccurred()) + if err == nil { + Expect(connPool.Remove(cn)).NotTo(HaveOccurred()) + } + } + }) + }) +}) diff --git a/vendor/github.com/go-redis/redis/internal/proto/proto_test.go b/vendor/github.com/go-redis/redis/internal/proto/proto_test.go new file mode 100644 index 0000000..c9a820e --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/proto/proto_test.go @@ -0,0 +1,13 @@ +package proto_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestGinkgoSuite(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "proto") +} diff --git a/vendor/github.com/go-redis/redis/internal/proto/reader.go b/vendor/github.com/go-redis/redis/internal/proto/reader.go new file mode 100644 index 0000000..cd94329 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/proto/reader.go @@ -0,0 +1,334 @@ +package proto + +import ( + "bufio" + "fmt" + "io" + "strconv" + + "github.com/go-redis/redis/internal" +) + +const bytesAllocLimit = 1024 * 1024 // 1mb + +const ( + ErrorReply = '-' + StatusReply = '+' + IntReply = ':' + StringReply = '$' + ArrayReply = '*' +) + +type MultiBulkParse func(*Reader, int64) (interface{}, error) + +type Reader struct { + src *bufio.Reader + buf []byte +} + +func NewReader(rd io.Reader) *Reader { + return &Reader{ + src: bufio.NewReader(rd), + buf: make([]byte, 4096), + } +} + +func (r *Reader) Reset(rd io.Reader) { + r.src.Reset(rd) +} + +func (p *Reader) PeekBuffered() []byte { + if n := p.src.Buffered(); n != 0 { + b, _ := p.src.Peek(n) + return b + } + return nil +} + +func (p *Reader) ReadN(n int) ([]byte, error) { + b, err := readN(p.src, p.buf, n) + if err != nil { + return nil, err + } + p.buf = b + return b, nil +} + +func (p *Reader) ReadLine() ([]byte, error) { + line, isPrefix, err := p.src.ReadLine() + if err != nil { + return nil, err + } + if isPrefix { + return nil, bufio.ErrBufferFull + } + if len(line) == 0 { + return nil, fmt.Errorf("redis: reply is empty") + } + if isNilReply(line) { + return nil, internal.Nil + } + return line, nil +} + +func (p *Reader) ReadReply(m MultiBulkParse) (interface{}, error) { + line, err := p.ReadLine() + if err != nil { + return nil, err + } + + switch line[0] { + case ErrorReply: + return nil, ParseErrorReply(line) + case StatusReply: + return parseStatusValue(line), nil + case IntReply: + return parseInt(line[1:], 10, 64) + case StringReply: + return p.readTmpBytesValue(line) + case ArrayReply: + n, err := parseArrayLen(line) + if err != nil { + return nil, err + } + return m(p, n) + } + return nil, fmt.Errorf("redis: can't parse %.100q", line) +} + +func (p *Reader) ReadIntReply() (int64, error) { + line, err := p.ReadLine() + if err != nil { + return 0, err + } + switch line[0] { + case ErrorReply: + return 0, ParseErrorReply(line) + case IntReply: + return parseInt(line[1:], 10, 64) + default: + return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line) + } +} + +func (p *Reader) ReadTmpBytesReply() ([]byte, error) { + line, err := p.ReadLine() + if err != nil { + return nil, err + } + switch line[0] { + case ErrorReply: + return nil, ParseErrorReply(line) + case StringReply: + return p.readTmpBytesValue(line) + case StatusReply: + return parseStatusValue(line), nil + default: + return nil, fmt.Errorf("redis: can't parse string reply: %.100q", line) + } +} + +func (r *Reader) ReadBytesReply() ([]byte, error) { + b, err := r.ReadTmpBytesReply() + if err != nil { + return nil, err + } + cp := make([]byte, len(b)) + copy(cp, b) + return cp, nil +} + +func (p *Reader) ReadStringReply() (string, error) { + b, err := p.ReadTmpBytesReply() + if err != nil { + return "", err + } + return string(b), nil +} + +func (p *Reader) ReadFloatReply() (float64, error) { + b, err := p.ReadTmpBytesReply() + if err != nil { + return 0, err + } + return parseFloat(b, 64) +} + +func (p *Reader) ReadArrayReply(m MultiBulkParse) (interface{}, error) { + line, err := p.ReadLine() + if err != nil { + return nil, err + } + switch line[0] { + case ErrorReply: + return nil, ParseErrorReply(line) + case ArrayReply: + n, err := parseArrayLen(line) + if err != nil { + return nil, err + } + return m(p, n) + default: + return nil, fmt.Errorf("redis: can't parse array reply: %.100q", line) + } +} + +func (p *Reader) ReadArrayLen() (int64, error) { + line, err := p.ReadLine() + if err != nil { + return 0, err + } + switch line[0] { + case ErrorReply: + return 0, ParseErrorReply(line) + case ArrayReply: + return parseArrayLen(line) + default: + return 0, fmt.Errorf("redis: can't parse array reply: %.100q", line) + } +} + +func (p *Reader) ReadScanReply() ([]string, uint64, error) { + n, err := p.ReadArrayLen() + if err != nil { + return nil, 0, err + } + if n != 2 { + return nil, 0, fmt.Errorf("redis: got %d elements in scan reply, expected 2", n) + } + + cursor, err := p.ReadUint() + if err != nil { + return nil, 0, err + } + + n, err = p.ReadArrayLen() + if err != nil { + return nil, 0, err + } + + keys := make([]string, n) + for i := int64(0); i < n; i++ { + key, err := p.ReadStringReply() + if err != nil { + return nil, 0, err + } + keys[i] = key + } + + return keys, cursor, err +} + +func (p *Reader) readTmpBytesValue(line []byte) ([]byte, error) { + if isNilReply(line) { + return nil, internal.Nil + } + + replyLen, err := strconv.Atoi(string(line[1:])) + if err != nil { + return nil, err + } + + b, err := p.ReadN(replyLen + 2) + if err != nil { + return nil, err + } + return b[:replyLen], nil +} + +func (r *Reader) ReadInt() (int64, error) { + b, err := r.ReadTmpBytesReply() + if err != nil { + return 0, err + } + return parseInt(b, 10, 64) +} + +func (r *Reader) ReadUint() (uint64, error) { + b, err := r.ReadTmpBytesReply() + if err != nil { + return 0, err + } + return parseUint(b, 10, 64) +} + +// -------------------------------------------------------------------- + +func readN(r io.Reader, b []byte, n int) ([]byte, error) { + if n == 0 && b == nil { + return make([]byte, 0), nil + } + + if cap(b) >= n { + b = b[:n] + _, err := io.ReadFull(r, b) + return b, err + } + b = b[:cap(b)] + + pos := 0 + for pos < n { + diff := n - len(b) + if diff > bytesAllocLimit { + diff = bytesAllocLimit + } + b = append(b, make([]byte, diff)...) + + nn, err := io.ReadFull(r, b[pos:]) + if err != nil { + return nil, err + } + pos += nn + } + + return b, nil +} + +func formatInt(n int64) string { + return strconv.FormatInt(n, 10) +} + +func formatUint(u uint64) string { + return strconv.FormatUint(u, 10) +} + +func formatFloat(f float64) string { + return strconv.FormatFloat(f, 'f', -1, 64) +} + +func isNilReply(b []byte) bool { + return len(b) == 3 && + (b[0] == StringReply || b[0] == ArrayReply) && + b[1] == '-' && b[2] == '1' +} + +func ParseErrorReply(line []byte) error { + return internal.RedisError(string(line[1:])) +} + +func parseStatusValue(line []byte) []byte { + return line[1:] +} + +func parseArrayLen(line []byte) (int64, error) { + if isNilReply(line) { + return 0, internal.Nil + } + return parseInt(line[1:], 10, 64) +} + +func atoi(b []byte) (int, error) { + return strconv.Atoi(internal.BytesToString(b)) +} + +func parseInt(b []byte, base int, bitSize int) (int64, error) { + return strconv.ParseInt(internal.BytesToString(b), base, bitSize) +} + +func parseUint(b []byte, base int, bitSize int) (uint64, error) { + return strconv.ParseUint(internal.BytesToString(b), base, bitSize) +} + +func parseFloat(b []byte, bitSize int) (float64, error) { + return strconv.ParseFloat(internal.BytesToString(b), bitSize) +} diff --git a/vendor/github.com/go-redis/redis/internal/proto/reader_test.go b/vendor/github.com/go-redis/redis/internal/proto/reader_test.go new file mode 100644 index 0000000..8d2d71b --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/proto/reader_test.go @@ -0,0 +1,87 @@ +package proto_test + +import ( + "bytes" + "strings" + "testing" + + "github.com/go-redis/redis/internal/proto" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Reader", func() { + + It("should read n bytes", func() { + data, err := proto.NewReader(strings.NewReader("ABCDEFGHIJKLMNO")).ReadN(10) + Expect(err).NotTo(HaveOccurred()) + Expect(len(data)).To(Equal(10)) + Expect(string(data)).To(Equal("ABCDEFGHIJ")) + + data, err = proto.NewReader(strings.NewReader(strings.Repeat("x", 8192))).ReadN(6000) + Expect(err).NotTo(HaveOccurred()) + Expect(len(data)).To(Equal(6000)) + }) + + It("should read lines", func() { + p := proto.NewReader(strings.NewReader("$5\r\nhello\r\n")) + + data, err := p.ReadLine() + Expect(err).NotTo(HaveOccurred()) + Expect(string(data)).To(Equal("$5")) + + data, err = p.ReadLine() + Expect(err).NotTo(HaveOccurred()) + Expect(string(data)).To(Equal("hello")) + }) + +}) + +func BenchmarkReader_ParseReply_Status(b *testing.B) { + benchmarkParseReply(b, "+OK\r\n", nil, false) +} + +func BenchmarkReader_ParseReply_Int(b *testing.B) { + benchmarkParseReply(b, ":1\r\n", nil, false) +} + +func BenchmarkReader_ParseReply_Error(b *testing.B) { + benchmarkParseReply(b, "-Error message\r\n", nil, true) +} + +func BenchmarkReader_ParseReply_String(b *testing.B) { + benchmarkParseReply(b, "$5\r\nhello\r\n", nil, false) +} + +func BenchmarkReader_ParseReply_Slice(b *testing.B) { + benchmarkParseReply(b, "*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n", multiBulkParse, false) +} + +func benchmarkParseReply(b *testing.B, reply string, m proto.MultiBulkParse, wanterr bool) { + buf := new(bytes.Buffer) + for i := 0; i < b.N; i++ { + buf.WriteString(reply) + } + p := proto.NewReader(buf) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, err := p.ReadReply(m) + if !wanterr && err != nil { + b.Fatal(err) + } + } +} + +func multiBulkParse(p *proto.Reader, n int64) (interface{}, error) { + vv := make([]interface{}, 0, n) + for i := int64(0); i < n; i++ { + v, err := p.ReadReply(multiBulkParse) + if err != nil { + return nil, err + } + vv = append(vv, v) + } + return vv, nil +} diff --git a/vendor/github.com/go-redis/redis/internal/proto/scan.go b/vendor/github.com/go-redis/redis/internal/proto/scan.go new file mode 100644 index 0000000..03c8b59 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/proto/scan.go @@ -0,0 +1,132 @@ +package proto + +import ( + "encoding" + "fmt" + "reflect" + + "github.com/go-redis/redis/internal" +) + +func Scan(b []byte, v interface{}) error { + switch v := v.(type) { + case nil: + return fmt.Errorf("redis: Scan(nil)") + case *string: + *v = internal.BytesToString(b) + return nil + case *[]byte: + *v = b + return nil + case *int: + var err error + *v, err = atoi(b) + return err + case *int8: + n, err := parseInt(b, 10, 8) + if err != nil { + return err + } + *v = int8(n) + return nil + case *int16: + n, err := parseInt(b, 10, 16) + if err != nil { + return err + } + *v = int16(n) + return nil + case *int32: + n, err := parseInt(b, 10, 32) + if err != nil { + return err + } + *v = int32(n) + return nil + case *int64: + n, err := parseInt(b, 10, 64) + if err != nil { + return err + } + *v = n + return nil + case *uint: + n, err := parseUint(b, 10, 64) + if err != nil { + return err + } + *v = uint(n) + return nil + case *uint8: + n, err := parseUint(b, 10, 8) + if err != nil { + return err + } + *v = uint8(n) + return nil + case *uint16: + n, err := parseUint(b, 10, 16) + if err != nil { + return err + } + *v = uint16(n) + return nil + case *uint32: + n, err := parseUint(b, 10, 32) + if err != nil { + return err + } + *v = uint32(n) + return nil + case *uint64: + n, err := parseUint(b, 10, 64) + if err != nil { + return err + } + *v = n + return nil + case *float32: + n, err := parseFloat(b, 32) + if err != nil { + return err + } + *v = float32(n) + return err + case *float64: + var err error + *v, err = parseFloat(b, 64) + return err + case *bool: + *v = len(b) == 1 && b[0] == '1' + return nil + case encoding.BinaryUnmarshaler: + return v.UnmarshalBinary(b) + default: + return fmt.Errorf( + "redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v) + } +} + +func ScanSlice(data []string, slice interface{}) error { + v := reflect.ValueOf(slice) + if !v.IsValid() { + return fmt.Errorf("redis: ScanSlice(nil)") + } + if v.Kind() != reflect.Ptr { + return fmt.Errorf("redis: ScanSlice(non-pointer %T)", slice) + } + v = v.Elem() + if v.Kind() != reflect.Slice { + return fmt.Errorf("redis: ScanSlice(non-slice %T)", slice) + } + + next := internal.MakeSliceNextElemFunc(v) + for i, s := range data { + elem := next() + if err := Scan(internal.StringToBytes(s), elem.Addr().Interface()); err != nil { + return fmt.Errorf("redis: ScanSlice(index=%d value=%q) failed: %s", i, s, err) + } + } + + return nil +} diff --git a/vendor/github.com/go-redis/redis/internal/proto/scan_test.go b/vendor/github.com/go-redis/redis/internal/proto/scan_test.go new file mode 100644 index 0000000..fadcd05 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/proto/scan_test.go @@ -0,0 +1,48 @@ +package proto + +import ( + "encoding/json" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +type testScanSliceStruct struct { + ID int + Name string +} + +func (s *testScanSliceStruct) MarshalBinary() ([]byte, error) { + return json.Marshal(s) +} + +func (s *testScanSliceStruct) UnmarshalBinary(b []byte) error { + return json.Unmarshal(b, s) +} + +var _ = Describe("ScanSlice", func() { + data := []string{ + `{"ID":-1,"Name":"Back Yu"}`, + `{"ID":1,"Name":"szyhf"}`, + } + + It("[]testScanSliceStruct", func() { + var slice []testScanSliceStruct + err := ScanSlice(data, &slice) + Expect(err).NotTo(HaveOccurred()) + Expect(slice).To(Equal([]testScanSliceStruct{ + {-1, "Back Yu"}, + {1, "szyhf"}, + })) + }) + + It("var testContainer []*testScanSliceStruct", func() { + var slice []*testScanSliceStruct + err := ScanSlice(data, &slice) + Expect(err).NotTo(HaveOccurred()) + Expect(slice).To(Equal([]*testScanSliceStruct{ + {-1, "Back Yu"}, + {1, "szyhf"}, + })) + }) +}) diff --git a/vendor/github.com/go-redis/redis/internal/proto/write_buffer.go b/vendor/github.com/go-redis/redis/internal/proto/write_buffer.go new file mode 100644 index 0000000..096b6d7 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/proto/write_buffer.go @@ -0,0 +1,103 @@ +package proto + +import ( + "encoding" + "fmt" + "strconv" +) + +type WriteBuffer struct { + b []byte +} + +func NewWriteBuffer() *WriteBuffer { + return &WriteBuffer{ + b: make([]byte, 0, 4096), + } +} + +func (w *WriteBuffer) Len() int { return len(w.b) } +func (w *WriteBuffer) Bytes() []byte { return w.b } +func (w *WriteBuffer) Reset() { w.b = w.b[:0] } + +func (w *WriteBuffer) Append(args []interface{}) error { + w.b = append(w.b, ArrayReply) + w.b = strconv.AppendUint(w.b, uint64(len(args)), 10) + w.b = append(w.b, '\r', '\n') + + for _, arg := range args { + if err := w.append(arg); err != nil { + return err + } + } + return nil +} + +func (w *WriteBuffer) append(val interface{}) error { + switch v := val.(type) { + case nil: + w.AppendString("") + case string: + w.AppendString(v) + case []byte: + w.AppendBytes(v) + case int: + w.AppendString(formatInt(int64(v))) + case int8: + w.AppendString(formatInt(int64(v))) + case int16: + w.AppendString(formatInt(int64(v))) + case int32: + w.AppendString(formatInt(int64(v))) + case int64: + w.AppendString(formatInt(v)) + case uint: + w.AppendString(formatUint(uint64(v))) + case uint8: + w.AppendString(formatUint(uint64(v))) + case uint16: + w.AppendString(formatUint(uint64(v))) + case uint32: + w.AppendString(formatUint(uint64(v))) + case uint64: + w.AppendString(formatUint(v)) + case float32: + w.AppendString(formatFloat(float64(v))) + case float64: + w.AppendString(formatFloat(v)) + case bool: + if v { + w.AppendString("1") + } else { + w.AppendString("0") + } + default: + if bm, ok := val.(encoding.BinaryMarshaler); ok { + bb, err := bm.MarshalBinary() + if err != nil { + return err + } + w.AppendBytes(bb) + } else { + return fmt.Errorf( + "redis: can't marshal %T (consider implementing encoding.BinaryMarshaler)", val) + } + } + return nil +} + +func (w *WriteBuffer) AppendString(s string) { + w.b = append(w.b, StringReply) + w.b = strconv.AppendUint(w.b, uint64(len(s)), 10) + w.b = append(w.b, '\r', '\n') + w.b = append(w.b, s...) + w.b = append(w.b, '\r', '\n') +} + +func (w *WriteBuffer) AppendBytes(p []byte) { + w.b = append(w.b, StringReply) + w.b = strconv.AppendUint(w.b, uint64(len(p)), 10) + w.b = append(w.b, '\r', '\n') + w.b = append(w.b, p...) + w.b = append(w.b, '\r', '\n') +} diff --git a/vendor/github.com/go-redis/redis/internal/proto/write_buffer_test.go b/vendor/github.com/go-redis/redis/internal/proto/write_buffer_test.go new file mode 100644 index 0000000..84799ff --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/proto/write_buffer_test.go @@ -0,0 +1,63 @@ +package proto_test + +import ( + "testing" + "time" + + "github.com/go-redis/redis/internal/proto" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("WriteBuffer", func() { + var buf *proto.WriteBuffer + + BeforeEach(func() { + buf = proto.NewWriteBuffer() + }) + + It("should reset", func() { + buf.AppendString("string") + Expect(buf.Len()).To(Equal(12)) + buf.Reset() + Expect(buf.Len()).To(Equal(0)) + }) + + It("should append args", func() { + err := buf.Append([]interface{}{ + "string", + 12, + 34.56, + []byte{'b', 'y', 't', 'e', 's'}, + true, + nil, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(buf.Bytes()).To(Equal([]byte("*6\r\n" + + "$6\r\nstring\r\n" + + "$2\r\n12\r\n" + + "$5\r\n34.56\r\n" + + "$5\r\nbytes\r\n" + + "$1\r\n1\r\n" + + "$0\r\n" + + "\r\n"))) + }) + + It("should append marshalable args", func() { + err := buf.Append([]interface{}{time.Unix(1414141414, 0)}) + Expect(err).NotTo(HaveOccurred()) + Expect(buf.Len()).To(Equal(26)) + }) + +}) + +func BenchmarkWriteBuffer_Append(b *testing.B) { + buf := proto.NewWriteBuffer() + args := []interface{}{"hello", "world", "foo", "bar"} + + for i := 0; i < b.N; i++ { + buf.Append(args) + buf.Reset() + } +} diff --git a/vendor/github.com/go-redis/redis/internal/safe.go b/vendor/github.com/go-redis/redis/internal/safe.go new file mode 100644 index 0000000..870fe54 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/safe.go @@ -0,0 +1,11 @@ +// +build appengine + +package internal + +func BytesToString(b []byte) string { + return string(b) +} + +func StringToBytes(s string) []byte { + return []byte(s) +} diff --git a/vendor/github.com/go-redis/redis/internal/unsafe.go b/vendor/github.com/go-redis/redis/internal/unsafe.go new file mode 100644 index 0000000..c18b25c --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/unsafe.go @@ -0,0 +1,27 @@ +// +build !appengine + +package internal + +import ( + "reflect" + "unsafe" +) + +func BytesToString(b []byte) string { + bytesHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + strHeader := reflect.StringHeader{ + Data: bytesHeader.Data, + Len: bytesHeader.Len, + } + return *(*string)(unsafe.Pointer(&strHeader)) +} + +func StringToBytes(s string) []byte { + sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) + bh := reflect.SliceHeader{ + Data: sh.Data, + Len: sh.Len, + Cap: sh.Len, + } + return *(*[]byte)(unsafe.Pointer(&bh)) +} diff --git a/vendor/github.com/go-redis/redis/internal/util.go b/vendor/github.com/go-redis/redis/internal/util.go new file mode 100644 index 0000000..1ba9805 --- /dev/null +++ b/vendor/github.com/go-redis/redis/internal/util.go @@ -0,0 +1,62 @@ +package internal + +import "reflect" + +func ToLower(s string) string { + if isLower(s) { + return s + } + + b := make([]byte, len(s)) + for i := range b { + c := s[i] + if c >= 'A' && c <= 'Z' { + c += 'a' - 'A' + } + b[i] = c + } + return BytesToString(b) +} + +func isLower(s string) bool { + for i := 0; i < len(s); i++ { + c := s[i] + if c >= 'A' && c <= 'Z' { + return false + } + } + return true +} + +func MakeSliceNextElemFunc(v reflect.Value) func() reflect.Value { + elemType := v.Type().Elem() + + if elemType.Kind() == reflect.Ptr { + elemType = elemType.Elem() + return func() reflect.Value { + if v.Len() < v.Cap() { + v.Set(v.Slice(0, v.Len()+1)) + elem := v.Index(v.Len() - 1) + if elem.IsNil() { + elem.Set(reflect.New(elemType)) + } + return elem.Elem() + } + + elem := reflect.New(elemType) + v.Set(reflect.Append(v, elem)) + return elem.Elem() + } + } + + zero := reflect.Zero(elemType) + return func() reflect.Value { + if v.Len() < v.Cap() { + v.Set(v.Slice(0, v.Len()+1)) + return v.Index(v.Len() - 1) + } + + v.Set(reflect.Append(v, zero)) + return v.Index(v.Len() - 1) + } +} diff --git a/vendor/github.com/go-redis/redis/iterator.go b/vendor/github.com/go-redis/redis/iterator.go new file mode 100644 index 0000000..5d4bedf --- /dev/null +++ b/vendor/github.com/go-redis/redis/iterator.go @@ -0,0 +1,73 @@ +package redis + +import "sync" + +// ScanIterator is used to incrementally iterate over a collection of elements. +// It's safe for concurrent use by multiple goroutines. +type ScanIterator struct { + mu sync.Mutex // protects Scanner and pos + cmd *ScanCmd + pos int +} + +// Err returns the last iterator error, if any. +func (it *ScanIterator) Err() error { + it.mu.Lock() + err := it.cmd.Err() + it.mu.Unlock() + return err +} + +// Next advances the cursor and returns true if more values can be read. +func (it *ScanIterator) Next() bool { + it.mu.Lock() + defer it.mu.Unlock() + + // Instantly return on errors. + if it.cmd.Err() != nil { + return false + } + + // Advance cursor, check if we are still within range. + if it.pos < len(it.cmd.page) { + it.pos++ + return true + } + + for { + // Return if there is no more data to fetch. + if it.cmd.cursor == 0 { + return false + } + + // Fetch next page. + if it.cmd._args[0] == "scan" { + it.cmd._args[1] = it.cmd.cursor + } else { + it.cmd._args[2] = it.cmd.cursor + } + + err := it.cmd.process(it.cmd) + if err != nil { + return false + } + + it.pos = 1 + + // Redis can occasionally return empty page. + if len(it.cmd.page) > 0 { + return true + } + } +} + +// Val returns the key/field at the current cursor position. +func (it *ScanIterator) Val() string { + var v string + it.mu.Lock() + if it.cmd.Err() == nil && it.pos > 0 && it.pos <= len(it.cmd.page) { + v = it.cmd.page[it.pos-1] + } + it.mu.Unlock() + return v +} diff --git a/vendor/github.com/go-redis/redis/iterator_test.go b/vendor/github.com/go-redis/redis/iterator_test.go new file mode 100644 index 0000000..a2e6238 --- /dev/null +++ b/vendor/github.com/go-redis/redis/iterator_test.go @@ -0,0 +1,136 @@ +package redis_test + +import ( + "fmt" + + "github.com/go-redis/redis" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("ScanIterator", func() { + var client *redis.Client + + var seed = func(n int) error { + pipe := client.Pipeline() + for i := 1; i <= n; i++ { + pipe.Set(fmt.Sprintf("K%02d", i), "x", 0).Err() + } + _, err := pipe.Exec() + return err + } + + var extraSeed = func(n int, m int) error { + pipe := client.Pipeline() + for i := 1; i <= m; i++ { + pipe.Set(fmt.Sprintf("A%02d", i), "x", 0).Err() + } + for i := 1; i <= n; i++ { + pipe.Set(fmt.Sprintf("K%02d", i), "x", 0).Err() + } + _, err := pipe.Exec() + return err + } + + var hashKey = "K_HASHTEST" + var hashSeed = func(n int) error { + pipe := client.Pipeline() + for i := 1; i <= n; i++ { + pipe.HSet(hashKey, fmt.Sprintf("K%02d", i), "x").Err() + } + _, err := pipe.Exec() + return err + } + + BeforeEach(func() { + client = redis.NewClient(redisOptions()) + Expect(client.FlushDB().Err()).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + It("should scan across empty DBs", func() { + iter := client.Scan(0, "", 10).Iterator() + Expect(iter.Next()).To(BeFalse()) + Expect(iter.Err()).NotTo(HaveOccurred()) + }) + + It("should scan across one page", func() { + Expect(seed(7)).NotTo(HaveOccurred()) + + var vals []string + iter := client.Scan(0, "", 0).Iterator() + for iter.Next() { + vals = append(vals, iter.Val()) + } + Expect(iter.Err()).NotTo(HaveOccurred()) + Expect(vals).To(ConsistOf([]string{"K01", "K02", "K03", "K04", "K05", "K06", "K07"})) + }) + + It("should scan across multiple pages", func() { + Expect(seed(71)).NotTo(HaveOccurred()) + + var vals []string + iter := client.Scan(0, "", 10).Iterator() + for iter.Next() { + vals = append(vals, iter.Val()) + } + Expect(iter.Err()).NotTo(HaveOccurred()) + Expect(vals).To(HaveLen(71)) + Expect(vals).To(ContainElement("K01")) + Expect(vals).To(ContainElement("K71")) + }) + + It("should hscan across multiple pages", func() { + Expect(hashSeed(71)).NotTo(HaveOccurred()) + + var vals []string + iter := client.HScan(hashKey, 0, "", 10).Iterator() + for iter.Next() { + vals = append(vals, iter.Val()) + } + Expect(iter.Err()).NotTo(HaveOccurred()) + Expect(vals).To(HaveLen(71 * 2)) + Expect(vals).To(ContainElement("K01")) + Expect(vals).To(ContainElement("K71")) + }) + + It("should scan to page borders", func() { + Expect(seed(20)).NotTo(HaveOccurred()) + + var vals []string + iter := client.Scan(0, "", 10).Iterator() + for iter.Next() { + vals = append(vals, iter.Val()) + } + Expect(iter.Err()).NotTo(HaveOccurred()) + Expect(vals).To(HaveLen(20)) + }) + + It("should scan with match", func() { + Expect(seed(33)).NotTo(HaveOccurred()) + + var vals []string + iter := client.Scan(0, "K*2*", 10).Iterator() + for iter.Next() { + vals = append(vals, iter.Val()) + } + Expect(iter.Err()).NotTo(HaveOccurred()) + Expect(vals).To(HaveLen(13)) + }) + + It("should scan with match across empty pages", func() { + Expect(extraSeed(2, 10)).NotTo(HaveOccurred()) + + var vals []string + iter := client.Scan(0, "K*", 1).Iterator() + for iter.Next() { + vals = append(vals, iter.Val()) + } + Expect(iter.Err()).NotTo(HaveOccurred()) + Expect(vals).To(HaveLen(2)) + }) +}) diff --git a/vendor/github.com/go-redis/redis/main_test.go b/vendor/github.com/go-redis/redis/main_test.go new file mode 100644 index 0000000..7c5a6a9 --- /dev/null +++ b/vendor/github.com/go-redis/redis/main_test.go @@ -0,0 +1,351 @@ +package redis_test + +import ( + "errors" + "fmt" + "net" + "os" + "os/exec" + "path/filepath" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/go-redis/redis" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +const ( + redisPort = "6380" + redisAddr = ":" + redisPort + redisSecondaryPort = "6381" +) + +const ( + ringShard1Port = "6390" + ringShard2Port = "6391" +) + +const ( + sentinelName = "mymaster" + sentinelMasterPort = "8123" + sentinelSlave1Port = "8124" + sentinelSlave2Port = "8125" + sentinelPort = "8126" +) + +var ( + redisMain *redisProcess + ringShard1, ringShard2 *redisProcess + sentinelMaster, sentinelSlave1, sentinelSlave2, sentinel *redisProcess +) + +var cluster = &clusterScenario{ + ports: []string{"8220", "8221", "8222", "8223", "8224", "8225"}, + nodeIds: make([]string, 6), + processes: make(map[string]*redisProcess, 6), + clients: make(map[string]*redis.Client, 6), +} + +var _ = BeforeSuite(func() { + var err error + + redisMain, err = startRedis(redisPort) + Expect(err).NotTo(HaveOccurred()) + + ringShard1, err = startRedis(ringShard1Port) + Expect(err).NotTo(HaveOccurred()) + + ringShard2, err = startRedis(ringShard2Port) + Expect(err).NotTo(HaveOccurred()) + + sentinelMaster, err = startRedis(sentinelMasterPort) + Expect(err).NotTo(HaveOccurred()) + + sentinel, err = startSentinel(sentinelPort, sentinelName, sentinelMasterPort) + Expect(err).NotTo(HaveOccurred()) + + sentinelSlave1, err = startRedis( + sentinelSlave1Port, "--slaveof", "127.0.0.1", sentinelMasterPort) + Expect(err).NotTo(HaveOccurred()) + + sentinelSlave2, err = startRedis( + sentinelSlave2Port, "--slaveof", "127.0.0.1", sentinelMasterPort) + Expect(err).NotTo(HaveOccurred()) + + Expect(startCluster(cluster)).NotTo(HaveOccurred()) +}) + +var _ = AfterSuite(func() { + Expect(redisMain.Close()).NotTo(HaveOccurred()) + + Expect(ringShard1.Close()).NotTo(HaveOccurred()) + Expect(ringShard2.Close()).NotTo(HaveOccurred()) + + Expect(sentinel.Close()).NotTo(HaveOccurred()) + Expect(sentinelSlave1.Close()).NotTo(HaveOccurred()) + Expect(sentinelSlave2.Close()).NotTo(HaveOccurred()) + Expect(sentinelMaster.Close()).NotTo(HaveOccurred()) + + Expect(stopCluster(cluster)).NotTo(HaveOccurred()) +}) + +func TestGinkgoSuite(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "go-redis") +} + +//------------------------------------------------------------------------------ + +func redisOptions() *redis.Options { + return &redis.Options{ + Addr: redisAddr, + DB: 15, + DialTimeout: 10 * time.Second, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + PoolSize: 10, + PoolTimeout: 30 * time.Second, + IdleTimeout: 500 * time.Millisecond, + IdleCheckFrequency: 500 * time.Millisecond, + } +} + +func redisClusterOptions() *redis.ClusterOptions { + return &redis.ClusterOptions{ + DialTimeout: 10 * time.Second, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + PoolSize: 10, + PoolTimeout: 30 * time.Second, + IdleTimeout: 500 * time.Millisecond, + IdleCheckFrequency: 500 * time.Millisecond, + } +} + +func redisRingOptions() *redis.RingOptions { + return &redis.RingOptions{ + Addrs: map[string]string{ + "ringShardOne": ":" + ringShard1Port, + "ringShardTwo": ":" + ringShard2Port, + }, + DialTimeout: 10 * time.Second, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + PoolSize: 10, + PoolTimeout: 30 * time.Second, + IdleTimeout: 500 * time.Millisecond, + IdleCheckFrequency: 500 * time.Millisecond, + } +} + +func perform(n int, cbs ...func(int)) { + var wg sync.WaitGroup + for _, cb := range cbs { + for i := 0; i < n; i++ { + wg.Add(1) + go func(cb func(int), i int) { + defer GinkgoRecover() + defer wg.Done() + + cb(i) + }(cb, i) + } + } + wg.Wait() +} + +func eventually(fn func() error, timeout time.Duration) error { + var exit int32 + errCh := make(chan error) + done := make(chan struct{}) + + go func() { + defer GinkgoRecover() + + for atomic.LoadInt32(&exit) == 0 { + err := fn() + if err == nil { + close(done) + return + } + select { + case errCh <- err: + default: + } + time.Sleep(timeout / 100) + } + }() + + select { + case <-done: + return nil + case <-time.After(timeout): + atomic.StoreInt32(&exit, 1) + select { + case err := <-errCh: + return err + default: + return fmt.Errorf("timeout after %s", timeout) + } + } +} + +func execCmd(name string, args ...string) (*os.Process, error) { + cmd := exec.Command(name, args...) + if testing.Verbose() { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } + return cmd.Process, cmd.Start() +} + +func connectTo(port string) (*redis.Client, error) { + client := redis.NewClient(&redis.Options{ + Addr: ":" + port, + }) + + err := eventually(func() error { + return client.Ping().Err() + }, 30*time.Second) + if err != nil { + return nil, err + } + + return client, nil +} + +type redisProcess struct { + *os.Process + *redis.Client +} + +func (p *redisProcess) Close() error { + if err := p.Kill(); err != nil { + return err + } + + err := eventually(func() error { + if err := p.Client.Ping().Err(); err != nil { + return nil + } + return errors.New("client is not shutdown") + }, 10*time.Second) + if err != nil { + return err + } + + p.Client.Close() + return nil +} + +var ( + redisServerBin, _ = filepath.Abs(filepath.Join("testdata", "redis", "src", "redis-server")) + redisServerConf, _ = filepath.Abs(filepath.Join("testdata", "redis.conf")) +) + +func redisDir(port string) (string, error) { + dir, err := filepath.Abs(filepath.Join("testdata", "instances", port)) + if err != nil { + return "", err + } + if err := os.RemoveAll(dir); err != nil { + return "", err + } + if err := os.MkdirAll(dir, 0775); err != nil { + return "", err + } + return dir, nil +} + +func startRedis(port string, args ...string) (*redisProcess, error) { + dir, err := redisDir(port) + if err != nil { + return nil, err + } + if err = exec.Command("cp", "-f", redisServerConf, dir).Run(); err != nil { + return nil, err + } + + baseArgs := []string{filepath.Join(dir, "redis.conf"), "--port", port, "--dir", dir} + process, err := execCmd(redisServerBin, append(baseArgs, args...)...) + if err != nil { + return nil, err + } + + client, err := connectTo(port) + if err != nil { + process.Kill() + return nil, err + } + return &redisProcess{process, client}, err +} + +func startSentinel(port, masterName, masterPort string) (*redisProcess, error) { + dir, err := redisDir(port) + if err != nil { + return nil, err + } + process, err := execCmd(redisServerBin, os.DevNull, "--sentinel", "--port", port, "--dir", dir) + if err != nil { + return nil, err + } + client, err := connectTo(port) + if err != nil { + process.Kill() + return nil, err + } + for _, cmd := range []*redis.StatusCmd{ + redis.NewStatusCmd("SENTINEL", "MONITOR", masterName, "127.0.0.1", masterPort, "1"), + redis.NewStatusCmd("SENTINEL", "SET", masterName, "down-after-milliseconds", "500"), + redis.NewStatusCmd("SENTINEL", "SET", masterName, "failover-timeout", "1000"), + redis.NewStatusCmd("SENTINEL", "SET", masterName, "parallel-syncs", "1"), + } { + client.Process(cmd) + if err := cmd.Err(); err != nil { + process.Kill() + return nil, err + } + } + return &redisProcess{process, client}, nil +} + +//------------------------------------------------------------------------------ + +type badConnError string + +func (e badConnError) Error() string { return string(e) } +func (e badConnError) Timeout() bool { return false } +func (e badConnError) Temporary() bool { return false } + +type badConn struct { + net.TCPConn + + readDelay, writeDelay time.Duration + readErr, writeErr error +} + +var _ net.Conn = &badConn{} + +func (cn *badConn) Read([]byte) (int, error) { + if cn.readDelay != 0 { + time.Sleep(cn.readDelay) + } + if cn.readErr != nil { + return 0, cn.readErr + } + return 0, badConnError("bad connection") +} + +func (cn *badConn) Write([]byte) (int, error) { + if cn.writeDelay != 0 { + time.Sleep(cn.writeDelay) + } + if cn.writeErr != nil { + return 0, cn.writeErr + } + return 0, badConnError("bad connection") +} diff --git a/vendor/github.com/go-redis/redis/options.go b/vendor/github.com/go-redis/redis/options.go new file mode 100644 index 0000000..7564805 --- /dev/null +++ b/vendor/github.com/go-redis/redis/options.go @@ -0,0 +1,200 @@ +package redis + +import ( + "crypto/tls" + "errors" + "fmt" + "net" + "net/url" + "runtime" + "strconv" + "strings" + "time" + + "github.com/go-redis/redis/internal/pool" +) + +type Options struct { + // The network type, either tcp or unix. + // Default is tcp. + Network string + // host:port address. + Addr string + + // Dialer creates new network connection and has priority over + // Network and Addr options. + Dialer func() (net.Conn, error) + + // Hook that is called when new connection is established. + OnConnect func(*Conn) error + + // Optional password. Must match the password specified in the + // requirepass server configuration option. + Password string + // Database to be selected after connecting to the server. + DB int + + // Maximum number of retries before giving up. + // Default is to not retry failed commands. + MaxRetries int + // Minimum backoff between each retry. + // Default is 8 milliseconds; -1 disables backoff. + MinRetryBackoff time.Duration + // Maximum backoff between each retry. + // Default is 512 milliseconds; -1 disables backoff. + MaxRetryBackoff time.Duration + + // Dial timeout for establishing new connections. + // Default is 5 seconds. + DialTimeout time.Duration + // Timeout for socket reads. If reached, commands will fail + // with a timeout instead of blocking. + // Default is 3 seconds. + ReadTimeout time.Duration + // Timeout for socket writes. If reached, commands will fail + // with a timeout instead of blocking. + // Default is ReadTimeout. + WriteTimeout time.Duration + + // Maximum number of socket connections. + // Default is 10 connections per every CPU as reported by runtime.NumCPU. + PoolSize int + // Amount of time client waits for connection if all connections + // are busy before returning an error. + // Default is ReadTimeout + 1 second. + PoolTimeout time.Duration + // Amount of time after which client closes idle connections. + // Should be less than server's timeout. + // Default is 5 minutes. + IdleTimeout time.Duration + // Frequency of idle checks. + // Default is 1 minute. + // When minus value is set, then idle check is disabled. + IdleCheckFrequency time.Duration + + // Enables read only queries on slave nodes. + readOnly bool + + // TLS Config to use. When set TLS will be negotiated. + TLSConfig *tls.Config +} + +func (opt *Options) init() { + if opt.Network == "" { + opt.Network = "tcp" + } + if opt.Dialer == nil { + opt.Dialer = func() (net.Conn, error) { + conn, err := net.DialTimeout(opt.Network, opt.Addr, opt.DialTimeout) + if opt.TLSConfig == nil || err != nil { + return conn, err + } + t := tls.Client(conn, opt.TLSConfig) + return t, t.Handshake() + } + } + if opt.PoolSize == 0 { + opt.PoolSize = 10 * runtime.NumCPU() + } + if opt.DialTimeout == 0 { + opt.DialTimeout = 5 * time.Second + } + switch opt.ReadTimeout { + case -1: + opt.ReadTimeout = 0 + case 0: + opt.ReadTimeout = 3 * time.Second + } + switch opt.WriteTimeout { + case -1: + opt.WriteTimeout = 0 + case 0: + opt.WriteTimeout = opt.ReadTimeout + } + if opt.PoolTimeout == 0 { + opt.PoolTimeout = opt.ReadTimeout + time.Second + } + if opt.IdleTimeout == 0 { + opt.IdleTimeout = 5 * time.Minute + } + if opt.IdleCheckFrequency == 0 { + opt.IdleCheckFrequency = time.Minute + } + + switch opt.MinRetryBackoff { + case -1: + opt.MinRetryBackoff = 0 + case 0: + opt.MinRetryBackoff = 8 * time.Millisecond + } + switch opt.MaxRetryBackoff { + case -1: + opt.MaxRetryBackoff = 0 + case 0: + opt.MaxRetryBackoff = 512 * time.Millisecond + } +} + +// ParseURL parses an URL into Options that can be used to connect to Redis. +func ParseURL(redisURL string) (*Options, error) { + o := &Options{Network: "tcp"} + u, err := url.Parse(redisURL) + if err != nil { + return nil, err + } + + if u.Scheme != "redis" && u.Scheme != "rediss" { + return nil, errors.New("invalid redis URL scheme: " + u.Scheme) + } + + if u.User != nil { + if p, ok := u.User.Password(); ok { + o.Password = p + } + } + + if len(u.Query()) > 0 { + return nil, errors.New("no options supported") + } + + h, p, err := net.SplitHostPort(u.Host) + if err != nil { + h = u.Host + } + if h == "" { + h = "localhost" + } + if p == "" { + p = "6379" + } + o.Addr = net.JoinHostPort(h, p) + + f := strings.FieldsFunc(u.Path, func(r rune) bool { + return r == '/' + }) + switch len(f) { + case 0: + o.DB = 0 + case 1: + if o.DB, err = strconv.Atoi(f[0]); err != nil { + return nil, fmt.Errorf("invalid redis database number: %q", f[0]) + } + default: + return nil, errors.New("invalid redis URL path: " + u.Path) + } + + if u.Scheme == "rediss" { + o.TLSConfig = &tls.Config{ServerName: h} + } + return o, nil +} + +func newConnPool(opt *Options) *pool.ConnPool { + return pool.NewConnPool(&pool.Options{ + Dialer: opt.Dialer, + PoolSize: opt.PoolSize, + PoolTimeout: opt.PoolTimeout, + IdleTimeout: opt.IdleTimeout, + IdleCheckFrequency: opt.IdleCheckFrequency, + }) +} diff --git a/vendor/github.com/go-redis/redis/options_test.go b/vendor/github.com/go-redis/redis/options_test.go new file mode 100644 index 0000000..6a4af71 --- /dev/null +++ b/vendor/github.com/go-redis/redis/options_test.go @@ -0,0 +1,94 @@ +// +build go1.7 + +package redis + +import ( + "errors" + "testing" +) + +func TestParseURL(t *testing.T) { + cases := []struct { + u string + addr string + db int + tls bool + err error + }{ + { + "redis://localhost:123/1", + "localhost:123", + 1, false, nil, + }, + { + "redis://localhost:123", + "localhost:123", + 0, false, nil, + }, + { + "redis://localhost/1", + "localhost:6379", + 1, false, nil, + }, + { + "redis://12345", + "12345:6379", + 0, false, nil, + }, + { + "rediss://localhost:123", + "localhost:123", + 0, true, nil, + }, + { + "redis://localhost/?abc=123", + "", + 0, false, errors.New("no options supported"), + }, + { + "http://google.com", + "", + 0, false, errors.New("invalid redis URL scheme: http"), + }, + { + "redis://localhost/1/2/3/4", + "", + 0, false, errors.New("invalid redis URL path: /1/2/3/4"), + }, + { + "12345", + "", + 0, false, errors.New("invalid redis URL scheme: "), + }, + { + "redis://localhost/iamadatabase", + "", + 0, false, errors.New(`invalid redis database number: "iamadatabase"`), + }, + } + + for _, c := range cases { + t.Run(c.u, func(t *testing.T) { + o, err := ParseURL(c.u) + if c.err == nil && err != nil { + t.Fatalf("unexpected error: '%q'", err) + return + } + if c.err != nil && err != nil { + if c.err.Error() != err.Error() { + t.Fatalf("got %q, expected %q", err, c.err) + } + return + } + if o.Addr != c.addr { + t.Errorf("got %q, want %q", o.Addr, c.addr) + } + if o.DB != c.db { + t.Errorf("got %q, expected %q", o.DB, c.db) + } + if c.tls && o.TLSConfig == nil { + t.Errorf("got nil TLSConfig, expected a TLSConfig") + } + }) + } +} diff --git a/vendor/github.com/go-redis/redis/parser.go b/vendor/github.com/go-redis/redis/parser.go new file mode 100644 index 0000000..b378abc --- /dev/null +++ b/vendor/github.com/go-redis/redis/parser.go @@ -0,0 +1,388 @@ +package redis + +import ( + "fmt" + "net" + "strconv" + "time" + + "github.com/go-redis/redis/internal/proto" +) + +// Implements proto.MultiBulkParse +func sliceParser(rd *proto.Reader, n int64) (interface{}, error) { + vals := make([]interface{}, 0, n) + for i := int64(0); i < n; i++ { + v, err := rd.ReadReply(sliceParser) + if err == Nil { + vals = append(vals, nil) + } else if err != nil { + return nil, err + } else { + switch vv := v.(type) { + case []byte: + vals = append(vals, string(vv)) + default: + vals = append(vals, v) + } + } + } + return vals, nil +} + +// Implements proto.MultiBulkParse +func boolSliceParser(rd *proto.Reader, n int64) (interface{}, error) { + bools := make([]bool, 0, n) + for i := int64(0); i < n; i++ { + n, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + bools = append(bools, n == 1) + } + return bools, nil +} + +// Implements proto.MultiBulkParse +func stringSliceParser(rd *proto.Reader, n int64) (interface{}, error) { + ss := make([]string, 0, n) + for i := int64(0); i < n; i++ { + s, err := rd.ReadStringReply() + if err == Nil { + ss = append(ss, "") + } else if err != nil { + return nil, err + } else { + ss = append(ss, s) + } + } + return ss, nil +} + +// Implements proto.MultiBulkParse +func stringStringMapParser(rd *proto.Reader, n int64) (interface{}, error) { + m := make(map[string]string, n/2) + for i := int64(0); i < n; i += 2 { + key, err := rd.ReadStringReply() + if err != nil { + return nil, err + } + + value, err := rd.ReadStringReply() + if err != nil { + return nil, err + } + + m[key] = value + } + return m, nil +} + +// Implements proto.MultiBulkParse +func stringIntMapParser(rd *proto.Reader, n int64) (interface{}, error) { + m := make(map[string]int64, n/2) + for i := int64(0); i < n; i += 2 { + key, err := rd.ReadStringReply() + if err != nil { + return nil, err + } + + n, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + + m[key] = n + } + return m, nil +} + +// Implements proto.MultiBulkParse +func stringStructMapParser(rd *proto.Reader, n int64) (interface{}, error) { + m := make(map[string]struct{}, n) + for i := int64(0); i < n; i++ { + key, err := rd.ReadStringReply() + if err != nil { + return nil, err + } + + m[key] = struct{}{} + } + return m, nil +} + +// Implements proto.MultiBulkParse +func zSliceParser(rd *proto.Reader, n int64) (interface{}, error) { + zz := make([]Z, n/2) + for i := int64(0); i < n; i += 2 { + var err error + + z := &zz[i/2] + + z.Member, err = rd.ReadStringReply() + if err != nil { + return nil, err + } + + z.Score, err = rd.ReadFloatReply() + if err != nil { + return nil, err + } + } + return zz, nil +} + +// Implements proto.MultiBulkParse +func clusterSlotsParser(rd *proto.Reader, n int64) (interface{}, error) { + slots := make([]ClusterSlot, n) + for i := 0; i < len(slots); i++ { + n, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + if n < 2 { + err := fmt.Errorf("redis: got %d elements in cluster info, expected at least 2", n) + return nil, err + } + + start, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + + end, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + + nodes := make([]ClusterNode, n-2) + for j := 0; j < len(nodes); j++ { + n, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + if n != 2 && n != 3 { + err := fmt.Errorf("got %d elements in cluster info address, expected 2 or 3", n) + return nil, err + } + + ip, err := rd.ReadStringReply() + if err != nil { + return nil, err + } + + port, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + nodes[j].Addr = net.JoinHostPort(ip, strconv.FormatInt(port, 10)) + + if n == 3 { + id, err := rd.ReadStringReply() + if err != nil { + return nil, err + } + nodes[j].Id = id + } + } + + slots[i] = ClusterSlot{ + Start: int(start), + End: int(end), + Nodes: nodes, + } + } + return slots, nil +} + +func newGeoLocationParser(q *GeoRadiusQuery) proto.MultiBulkParse { + return func(rd *proto.Reader, n int64) (interface{}, error) { + var loc GeoLocation + var err error + + loc.Name, err = rd.ReadStringReply() + if err != nil { + return nil, err + } + if q.WithDist { + loc.Dist, err = rd.ReadFloatReply() + if err != nil { + return nil, err + } + } + if q.WithGeoHash { + loc.GeoHash, err = rd.ReadIntReply() + if err != nil { + return nil, err + } + } + if q.WithCoord { + n, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + if n != 2 { + return nil, fmt.Errorf("got %d coordinates, expected 2", n) + } + + loc.Longitude, err = rd.ReadFloatReply() + if err != nil { + return nil, err + } + loc.Latitude, err = rd.ReadFloatReply() + if err != nil { + return nil, err + } + } + + return &loc, nil + } +} + +func newGeoLocationSliceParser(q *GeoRadiusQuery) proto.MultiBulkParse { + return func(rd *proto.Reader, n int64) (interface{}, error) { + locs := make([]GeoLocation, 0, n) + for i := int64(0); i < n; i++ { + v, err := rd.ReadReply(newGeoLocationParser(q)) + if err != nil { + return nil, err + } + switch vv := v.(type) { + case []byte: + locs = append(locs, GeoLocation{ + Name: string(vv), + }) + case *GeoLocation: + locs = append(locs, *vv) + default: + return nil, fmt.Errorf("got %T, expected string or *GeoLocation", v) + } + } + return locs, nil + } +} + +func geoPosParser(rd *proto.Reader, n int64) (interface{}, error) { + var pos GeoPos + var err error + + pos.Longitude, err = rd.ReadFloatReply() + if err != nil { + return nil, err + } + + pos.Latitude, err = rd.ReadFloatReply() + if err != nil { + return nil, err + } + + return &pos, nil +} + +func geoPosSliceParser(rd *proto.Reader, n int64) (interface{}, error) { + positions := make([]*GeoPos, 0, n) + for i := int64(0); i < n; i++ { + v, err := rd.ReadReply(geoPosParser) + if err != nil { + if err == Nil { + positions = append(positions, nil) + continue + } + return nil, err + } + switch v := v.(type) { + case *GeoPos: + positions = append(positions, v) + default: + return nil, fmt.Errorf("got %T, expected *GeoPos", v) + } + } + return positions, nil +} + +func commandInfoParser(rd *proto.Reader, n int64) (interface{}, error) { + var cmd CommandInfo + var err error + + if n != 6 { + return nil, fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 6", n) + } + + cmd.Name, err = rd.ReadStringReply() + if err != nil { + return nil, err + } + + arity, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + cmd.Arity = int8(arity) + + flags, err := rd.ReadReply(stringSliceParser) + if err != nil { + return nil, err + } + cmd.Flags = flags.([]string) + + firstKeyPos, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + cmd.FirstKeyPos = int8(firstKeyPos) + + lastKeyPos, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + cmd.LastKeyPos = int8(lastKeyPos) + + stepCount, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + cmd.StepCount = int8(stepCount) + + for _, flag := range cmd.Flags { + if flag == "readonly" { + cmd.ReadOnly = true + break + } + } + + return &cmd, nil +} + +// Implements proto.MultiBulkParse +func commandInfoSliceParser(rd *proto.Reader, n int64) (interface{}, error) { + m := make(map[string]*CommandInfo, n) + for i := int64(0); i < n; i++ { + v, err := rd.ReadReply(commandInfoParser) + if err != nil { + return nil, err + } + vv := v.(*CommandInfo) + m[vv.Name] = vv + + } + return m, nil +} + +// Implements proto.MultiBulkParse +func timeParser(rd *proto.Reader, n int64) (interface{}, error) { + if n != 2 { + return nil, fmt.Errorf("got %d elements, expected 2", n) + } + + sec, err := rd.ReadInt() + if err != nil { + return nil, err + } + + microsec, err := rd.ReadInt() + if err != nil { + return nil, err + } + + return time.Unix(sec, microsec*1000), nil +} diff --git a/vendor/github.com/go-redis/redis/pipeline.go b/vendor/github.com/go-redis/redis/pipeline.go new file mode 100644 index 0000000..9349ef5 --- /dev/null +++ b/vendor/github.com/go-redis/redis/pipeline.go @@ -0,0 +1,112 @@ +package redis + +import ( + "sync" + + "github.com/go-redis/redis/internal/pool" +) + +type pipelineExecer func([]Cmder) error + +type Pipeliner interface { + StatefulCmdable + Process(cmd Cmder) error + Close() error + Discard() error + Exec() ([]Cmder, error) +} + +var _ Pipeliner = (*Pipeline)(nil) + +// Pipeline implements pipelining as described in +// http://redis.io/topics/pipelining. It's safe for concurrent use +// by multiple goroutines. +type Pipeline struct { + statefulCmdable + + exec pipelineExecer + + mu sync.Mutex + cmds []Cmder + closed bool +} + +func (c *Pipeline) Process(cmd Cmder) error { + c.mu.Lock() + c.cmds = append(c.cmds, cmd) + c.mu.Unlock() + return nil +} + +// Close closes the pipeline, releasing any open resources. +func (c *Pipeline) Close() error { + c.mu.Lock() + c.discard() + c.closed = true + c.mu.Unlock() + return nil +} + +// Discard resets the pipeline and discards queued commands. +func (c *Pipeline) Discard() error { + c.mu.Lock() + err := c.discard() + c.mu.Unlock() + return err +} + +func (c *Pipeline) discard() error { + if c.closed { + return pool.ErrClosed + } + c.cmds = c.cmds[:0] + return nil +} + +// Exec executes all previously queued commands using one +// client-server roundtrip. +// +// Exec always returns list of commands and error of the first failed +// command if any. +func (c *Pipeline) Exec() ([]Cmder, error) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return nil, pool.ErrClosed + } + + if len(c.cmds) == 0 { + return nil, nil + } + + cmds := c.cmds + c.cmds = nil + + return cmds, c.exec(cmds) +} + +func (c *Pipeline) pipelined(fn func(Pipeliner) error) ([]Cmder, error) { + if err := fn(c); err != nil { + return nil, err + } + cmds, err := c.Exec() + _ = c.Close() + return cmds, err +} + +func (c *Pipeline) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { + return c.pipelined(fn) +} + +func (c *Pipeline) Pipeline() Pipeliner { + return c +} + +func (c *Pipeline) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { + return c.pipelined(fn) +} + +func (c *Pipeline) TxPipeline() Pipeliner { + return c +} diff --git a/vendor/github.com/go-redis/redis/pipeline_test.go b/vendor/github.com/go-redis/redis/pipeline_test.go new file mode 100644 index 0000000..11896c6 --- /dev/null +++ b/vendor/github.com/go-redis/redis/pipeline_test.go @@ -0,0 +1,80 @@ +package redis_test + +import ( + "github.com/go-redis/redis" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("pipelining", func() { + var client *redis.Client + var pipe *redis.Pipeline + + BeforeEach(func() { + client = redis.NewClient(redisOptions()) + Expect(client.FlushDB().Err()).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + It("supports block style", func() { + var get *redis.StringCmd + cmds, err := client.Pipelined(func(pipe redis.Pipeliner) error { + get = pipe.Get("foo") + return nil + }) + Expect(err).To(Equal(redis.Nil)) + Expect(cmds).To(HaveLen(1)) + Expect(cmds[0]).To(Equal(get)) + Expect(get.Err()).To(Equal(redis.Nil)) + Expect(get.Val()).To(Equal("")) + }) + + assertPipeline := func() { + It("returns no errors when there are no commands", func() { + _, err := pipe.Exec() + Expect(err).NotTo(HaveOccurred()) + }) + + It("discards queued commands", func() { + pipe.Get("key") + pipe.Discard() + cmds, err := pipe.Exec() + Expect(err).NotTo(HaveOccurred()) + Expect(cmds).To(BeNil()) + }) + + It("handles val/err", func() { + err := client.Set("key", "value", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + get := pipe.Get("key") + cmds, err := pipe.Exec() + Expect(err).NotTo(HaveOccurred()) + Expect(cmds).To(HaveLen(1)) + + val, err := get.Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("value")) + }) + } + + Describe("Pipeline", func() { + BeforeEach(func() { + pipe = client.Pipeline().(*redis.Pipeline) + }) + + assertPipeline() + }) + + Describe("TxPipeline", func() { + BeforeEach(func() { + pipe = client.TxPipeline().(*redis.Pipeline) + }) + + assertPipeline() + }) +}) diff --git a/vendor/github.com/go-redis/redis/pool_test.go b/vendor/github.com/go-redis/redis/pool_test.go new file mode 100644 index 0000000..0ca09ad --- /dev/null +++ b/vendor/github.com/go-redis/redis/pool_test.go @@ -0,0 +1,143 @@ +package redis_test + +import ( + "time" + + "github.com/go-redis/redis" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("pool", func() { + var client *redis.Client + + BeforeEach(func() { + client = redis.NewClient(redisOptions()) + Expect(client.FlushDB().Err()).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + It("respects max size", func() { + perform(1000, func(id int) { + val, err := client.Ping().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("PONG")) + }) + + pool := client.Pool() + Expect(pool.Len()).To(BeNumerically("<=", 10)) + Expect(pool.FreeLen()).To(BeNumerically("<=", 10)) + Expect(pool.Len()).To(Equal(pool.FreeLen())) + }) + + It("respects max size on multi", func() { + perform(1000, func(id int) { + var ping *redis.StatusCmd + + err := client.Watch(func(tx *redis.Tx) error { + cmds, err := tx.Pipelined(func(pipe redis.Pipeliner) error { + ping = pipe.Ping() + return nil + }) + Expect(err).NotTo(HaveOccurred()) + Expect(cmds).To(HaveLen(1)) + return err + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(ping.Err()).NotTo(HaveOccurred()) + Expect(ping.Val()).To(Equal("PONG")) + }) + + pool := client.Pool() + Expect(pool.Len()).To(BeNumerically("<=", 10)) + Expect(pool.FreeLen()).To(BeNumerically("<=", 10)) + Expect(pool.Len()).To(Equal(pool.FreeLen())) + }) + + It("respects max size on pipelines", func() { + perform(1000, func(id int) { + pipe := client.Pipeline() + ping := pipe.Ping() + cmds, err := pipe.Exec() + Expect(err).NotTo(HaveOccurred()) + Expect(cmds).To(HaveLen(1)) + Expect(ping.Err()).NotTo(HaveOccurred()) + Expect(ping.Val()).To(Equal("PONG")) + Expect(pipe.Close()).NotTo(HaveOccurred()) + }) + + pool := client.Pool() + Expect(pool.Len()).To(BeNumerically("<=", 10)) + Expect(pool.FreeLen()).To(BeNumerically("<=", 10)) + Expect(pool.Len()).To(Equal(pool.FreeLen())) + }) + + It("removes broken connections", func() { + cn, _, err := client.Pool().Get() + Expect(err).NotTo(HaveOccurred()) + cn.SetNetConn(&badConn{}) + Expect(client.Pool().Put(cn)).NotTo(HaveOccurred()) + + err = client.Ping().Err() + Expect(err).To(MatchError("bad connection")) + + val, err := client.Ping().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("PONG")) + + pool := client.Pool() + Expect(pool.Len()).To(Equal(1)) + Expect(pool.FreeLen()).To(Equal(1)) + + stats := pool.Stats() + Expect(stats.Hits).To(Equal(uint32(2))) + Expect(stats.Misses).To(Equal(uint32(2))) + Expect(stats.Timeouts).To(Equal(uint32(0))) + }) + + It("reuses connections", func() { + for i := 0; i < 100; i++ { + val, err := client.Ping().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("PONG")) + } + + pool := client.Pool() + Expect(pool.Len()).To(Equal(1)) + Expect(pool.FreeLen()).To(Equal(1)) + + stats := pool.Stats() + Expect(stats.Hits).To(Equal(uint32(100))) + Expect(stats.Misses).To(Equal(uint32(1))) + Expect(stats.Timeouts).To(Equal(uint32(0))) + }) + + It("removes idle connections", func() { + stats := client.PoolStats() + Expect(stats).To(Equal(&redis.PoolStats{ + Hits: 0, + Misses: 1, + Timeouts: 0, + TotalConns: 1, + FreeConns: 1, + StaleConns: 0, + })) + + time.Sleep(2 * time.Second) + + stats = client.PoolStats() + Expect(stats).To(Equal(&redis.PoolStats{ + Hits: 0, + Misses: 1, + Timeouts: 0, + TotalConns: 0, + FreeConns: 0, + StaleConns: 1, + })) + }) +}) diff --git a/vendor/github.com/go-redis/redis/pubsub.go b/vendor/github.com/go-redis/redis/pubsub.go new file mode 100644 index 0000000..01f8a61 --- /dev/null +++ b/vendor/github.com/go-redis/redis/pubsub.go @@ -0,0 +1,401 @@ +package redis + +import ( + "fmt" + "net" + "sync" + "time" + + "github.com/go-redis/redis/internal" + "github.com/go-redis/redis/internal/pool" +) + +// PubSub implements Pub/Sub commands as described in +// http://redis.io/topics/pubsub. It's NOT safe for concurrent use by +// multiple goroutines. +// +// PubSub automatically resubscribes to the channels and patterns +// when Redis becomes unavailable. +type PubSub struct { + opt *Options + + newConn func([]string) (*pool.Conn, error) + closeConn func(*pool.Conn) error + + mu sync.Mutex + cn *pool.Conn + channels []string + patterns []string + closed bool + + cmd *Cmd + + chOnce sync.Once + ch chan *Message +} + +func (c *PubSub) conn() (*pool.Conn, error) { + c.mu.Lock() + cn, err := c._conn(nil) + c.mu.Unlock() + return cn, err +} + +func (c *PubSub) _conn(channels []string) (*pool.Conn, error) { + if c.closed { + return nil, pool.ErrClosed + } + + if c.cn != nil { + return c.cn, nil + } + + cn, err := c.newConn(channels) + if err != nil { + return nil, err + } + + if err := c.resubscribe(cn); err != nil { + _ = c.closeConn(cn) + return nil, err + } + + c.cn = cn + return cn, nil +} + +func (c *PubSub) resubscribe(cn *pool.Conn) error { + var firstErr error + if len(c.channels) > 0 { + if err := c._subscribe(cn, "subscribe", c.channels...); err != nil && firstErr == nil { + firstErr = err + } + } + if len(c.patterns) > 0 { + if err := c._subscribe(cn, "psubscribe", c.patterns...); err != nil && firstErr == nil { + firstErr = err + } + } + return firstErr +} + +func (c *PubSub) _subscribe(cn *pool.Conn, redisCmd string, channels ...string) error { + args := make([]interface{}, 1+len(channels)) + args[0] = redisCmd + for i, channel := range channels { + args[1+i] = channel + } + cmd := NewSliceCmd(args...) + + cn.SetWriteTimeout(c.opt.WriteTimeout) + return writeCmd(cn, cmd) +} + +func (c *PubSub) releaseConn(cn *pool.Conn, err error) { + c.mu.Lock() + c._releaseConn(cn, err) + c.mu.Unlock() +} + +func (c *PubSub) _releaseConn(cn *pool.Conn, err error) { + if c.cn != cn { + return + } + if internal.IsBadConn(err, true) { + _ = c.closeTheCn() + } +} + +func (c *PubSub) closeTheCn() error { + err := c.closeConn(c.cn) + c.cn = nil + return err +} + +func (c *PubSub) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return pool.ErrClosed + } + c.closed = true + + if c.cn != nil { + return c.closeTheCn() + } + return nil +} + +// Subscribes the client to the specified channels. It returns +// empty subscription if there are no channels. +func (c *PubSub) Subscribe(channels ...string) error { + c.mu.Lock() + err := c.subscribe("subscribe", channels...) + c.channels = appendIfNotExists(c.channels, channels...) + c.mu.Unlock() + return err +} + +// Subscribes the client to the given patterns. It returns +// empty subscription if there are no patterns. +func (c *PubSub) PSubscribe(patterns ...string) error { + c.mu.Lock() + err := c.subscribe("psubscribe", patterns...) + c.patterns = appendIfNotExists(c.patterns, patterns...) + c.mu.Unlock() + return err +} + +// Unsubscribes the client from the given channels, or from all of +// them if none is given. +func (c *PubSub) Unsubscribe(channels ...string) error { + c.mu.Lock() + err := c.subscribe("unsubscribe", channels...) + c.channels = remove(c.channels, channels...) + c.mu.Unlock() + return err +} + +// Unsubscribes the client from the given patterns, or from all of +// them if none is given. +func (c *PubSub) PUnsubscribe(patterns ...string) error { + c.mu.Lock() + err := c.subscribe("punsubscribe", patterns...) + c.patterns = remove(c.patterns, patterns...) + c.mu.Unlock() + return err +} + +func (c *PubSub) subscribe(redisCmd string, channels ...string) error { + cn, err := c._conn(channels) + if err != nil { + return err + } + + err = c._subscribe(cn, redisCmd, channels...) + c._releaseConn(cn, err) + return err +} + +func (c *PubSub) Ping(payload ...string) error { + args := []interface{}{"ping"} + if len(payload) == 1 { + args = append(args, payload[0]) + } + cmd := NewCmd(args...) + + cn, err := c.conn() + if err != nil { + return err + } + + cn.SetWriteTimeout(c.opt.WriteTimeout) + err = writeCmd(cn, cmd) + c.releaseConn(cn, err) + return err +} + +// Message received after a successful subscription to channel. +type Subscription struct { + // Can be "subscribe", "unsubscribe", "psubscribe" or "punsubscribe". + Kind string + // Channel name we have subscribed to. + Channel string + // Number of channels we are currently subscribed to. + Count int +} + +func (m *Subscription) String() string { + return fmt.Sprintf("%s: %s", m.Kind, m.Channel) +} + +// Message received as result of a PUBLISH command issued by another client. +type Message struct { + Channel string + Pattern string + Payload string +} + +func (m *Message) String() string { + return fmt.Sprintf("Message<%s: %s>", m.Channel, m.Payload) +} + +// Pong received as result of a PING command issued by another client. +type Pong struct { + Payload string +} + +func (p *Pong) String() string { + if p.Payload != "" { + return fmt.Sprintf("Pong<%s>", p.Payload) + } + return "Pong" +} + +func (c *PubSub) newMessage(reply interface{}) (interface{}, error) { + switch reply := reply.(type) { + case string: + return &Pong{ + Payload: reply, + }, nil + case []interface{}: + switch kind := reply[0].(string); kind { + case "subscribe", "unsubscribe", "psubscribe", "punsubscribe": + return &Subscription{ + Kind: kind, + Channel: reply[1].(string), + Count: int(reply[2].(int64)), + }, nil + case "message": + return &Message{ + Channel: reply[1].(string), + Payload: reply[2].(string), + }, nil + case "pmessage": + return &Message{ + Pattern: reply[1].(string), + Channel: reply[2].(string), + Payload: reply[3].(string), + }, nil + case "pong": + return &Pong{ + Payload: reply[1].(string), + }, nil + default: + return nil, fmt.Errorf("redis: unsupported pubsub message: %q", kind) + } + default: + return nil, fmt.Errorf("redis: unsupported pubsub message: %#v", reply) + } +} + +// ReceiveTimeout acts like Receive but returns an error if message +// is not received in time. This is low-level API and most clients +// should use ReceiveMessage. +func (c *PubSub) ReceiveTimeout(timeout time.Duration) (interface{}, error) { + if c.cmd == nil { + c.cmd = NewCmd() + } + + cn, err := c.conn() + if err != nil { + return nil, err + } + + cn.SetReadTimeout(timeout) + err = c.cmd.readReply(cn) + c.releaseConn(cn, err) + if err != nil { + return nil, err + } + + return c.newMessage(c.cmd.Val()) +} + +// Receive returns a message as a Subscription, Message, Pong or error. +// See PubSub example for details. This is low-level API and most clients +// should use ReceiveMessage. +func (c *PubSub) Receive() (interface{}, error) { + return c.ReceiveTimeout(0) +} + +// ReceiveMessage returns a Message or error ignoring Subscription or Pong +// messages. It automatically reconnects to Redis Server and resubscribes +// to channels in case of network errors. +func (c *PubSub) ReceiveMessage() (*Message, error) { + return c.receiveMessage(5 * time.Second) +} + +func (c *PubSub) receiveMessage(timeout time.Duration) (*Message, error) { + var errNum uint + for { + msgi, err := c.ReceiveTimeout(timeout) + if err != nil { + if !internal.IsNetworkError(err) { + return nil, err + } + + errNum++ + if errNum < 3 { + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + err := c.Ping() + if err != nil { + internal.Logf("PubSub.Ping failed: %s", err) + } + } + } else { + // 3 consequent errors - connection is broken or + // Redis Server is down. + // Sleep to not exceed max number of open connections. + time.Sleep(time.Second) + } + continue + } + + // Reset error number, because we received a message. + errNum = 0 + + switch msg := msgi.(type) { + case *Subscription: + // Ignore. + case *Pong: + // Ignore. + case *Message: + return msg, nil + default: + return nil, fmt.Errorf("redis: unknown message: %T", msgi) + } + } +} + +// Channel returns a Go channel for concurrently receiving messages. +// The channel is closed with PubSub. Receive or ReceiveMessage APIs +// can not be used after channel is created. +func (c *PubSub) Channel() <-chan *Message { + c.chOnce.Do(func() { + c.ch = make(chan *Message, 100) + go func() { + for { + msg, err := c.ReceiveMessage() + if err != nil { + if err == pool.ErrClosed { + break + } + continue + } + c.ch <- msg + } + close(c.ch) + }() + }) + return c.ch +} + +func appendIfNotExists(ss []string, es ...string) []string { +loop: + for _, e := range es { + for _, s := range ss { + if s == e { + continue loop + } + } + ss = append(ss, e) + } + return ss +} + +func remove(ss []string, es ...string) []string { + if len(es) == 0 { + return ss[:0] + } + for _, e := range es { + for i, s := range ss { + if s == e { + ss = append(ss[:i], ss[i+1:]...) + break + } + } + } + return ss +} diff --git a/vendor/github.com/go-redis/redis/pubsub_test.go b/vendor/github.com/go-redis/redis/pubsub_test.go new file mode 100644 index 0000000..6a85bd0 --- /dev/null +++ b/vendor/github.com/go-redis/redis/pubsub_test.go @@ -0,0 +1,443 @@ +package redis_test + +import ( + "io" + "net" + "sync" + "time" + + "github.com/go-redis/redis" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("PubSub", func() { + var client *redis.Client + + BeforeEach(func() { + client = redis.NewClient(redisOptions()) + Expect(client.FlushDB().Err()).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + It("should support pattern matching", func() { + pubsub := client.PSubscribe("mychannel*") + defer pubsub.Close() + + { + msgi, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + subscr := msgi.(*redis.Subscription) + Expect(subscr.Kind).To(Equal("psubscribe")) + Expect(subscr.Channel).To(Equal("mychannel*")) + Expect(subscr.Count).To(Equal(1)) + } + + { + msgi, err := pubsub.ReceiveTimeout(time.Second) + Expect(err.(net.Error).Timeout()).To(Equal(true)) + Expect(msgi).To(BeNil()) + } + + n, err := client.Publish("mychannel1", "hello").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(1))) + + Expect(pubsub.PUnsubscribe("mychannel*")).NotTo(HaveOccurred()) + + { + msgi, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + subscr := msgi.(*redis.Message) + Expect(subscr.Channel).To(Equal("mychannel1")) + Expect(subscr.Pattern).To(Equal("mychannel*")) + Expect(subscr.Payload).To(Equal("hello")) + } + + { + msgi, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + subscr := msgi.(*redis.Subscription) + Expect(subscr.Kind).To(Equal("punsubscribe")) + Expect(subscr.Channel).To(Equal("mychannel*")) + Expect(subscr.Count).To(Equal(0)) + } + + stats := client.PoolStats() + Expect(stats.Misses).To(Equal(uint32(2))) + }) + + It("should pub/sub channels", func() { + channels, err := client.PubSubChannels("mychannel*").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(channels).To(BeEmpty()) + + pubsub := client.Subscribe("mychannel", "mychannel2") + defer pubsub.Close() + + channels, err = client.PubSubChannels("mychannel*").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(channels).To(ConsistOf([]string{"mychannel", "mychannel2"})) + + channels, err = client.PubSubChannels("").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(channels).To(BeEmpty()) + + channels, err = client.PubSubChannels("*").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(channels)).To(BeNumerically(">=", 2)) + }) + + It("should return the numbers of subscribers", func() { + pubsub := client.Subscribe("mychannel", "mychannel2") + defer pubsub.Close() + + channels, err := client.PubSubNumSub("mychannel", "mychannel2", "mychannel3").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(channels).To(Equal(map[string]int64{ + "mychannel": 1, + "mychannel2": 1, + "mychannel3": 0, + })) + }) + + It("should return the numbers of subscribers by pattern", func() { + num, err := client.PubSubNumPat().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(num).To(Equal(int64(0))) + + pubsub := client.PSubscribe("*") + defer pubsub.Close() + + num, err = client.PubSubNumPat().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(num).To(Equal(int64(1))) + }) + + It("should pub/sub", func() { + pubsub := client.Subscribe("mychannel", "mychannel2") + defer pubsub.Close() + + { + msgi, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + subscr := msgi.(*redis.Subscription) + Expect(subscr.Kind).To(Equal("subscribe")) + Expect(subscr.Channel).To(Equal("mychannel")) + Expect(subscr.Count).To(Equal(1)) + } + + { + msgi, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + subscr := msgi.(*redis.Subscription) + Expect(subscr.Kind).To(Equal("subscribe")) + Expect(subscr.Channel).To(Equal("mychannel2")) + Expect(subscr.Count).To(Equal(2)) + } + + { + msgi, err := pubsub.ReceiveTimeout(time.Second) + Expect(err.(net.Error).Timeout()).To(Equal(true)) + Expect(msgi).NotTo(HaveOccurred()) + } + + n, err := client.Publish("mychannel", "hello").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(1))) + + n, err = client.Publish("mychannel2", "hello2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(1))) + + Expect(pubsub.Unsubscribe("mychannel", "mychannel2")).NotTo(HaveOccurred()) + + { + msgi, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + msg := msgi.(*redis.Message) + Expect(msg.Channel).To(Equal("mychannel")) + Expect(msg.Payload).To(Equal("hello")) + } + + { + msgi, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + msg := msgi.(*redis.Message) + Expect(msg.Channel).To(Equal("mychannel2")) + Expect(msg.Payload).To(Equal("hello2")) + } + + { + msgi, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + subscr := msgi.(*redis.Subscription) + Expect(subscr.Kind).To(Equal("unsubscribe")) + Expect(subscr.Channel).To(Equal("mychannel")) + Expect(subscr.Count).To(Equal(1)) + } + + { + msgi, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + subscr := msgi.(*redis.Subscription) + Expect(subscr.Kind).To(Equal("unsubscribe")) + Expect(subscr.Channel).To(Equal("mychannel2")) + Expect(subscr.Count).To(Equal(0)) + } + + stats := client.PoolStats() + Expect(stats.Misses).To(Equal(uint32(2))) + }) + + It("should ping/pong", func() { + pubsub := client.Subscribe("mychannel") + defer pubsub.Close() + + _, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + + err = pubsub.Ping("") + Expect(err).NotTo(HaveOccurred()) + + msgi, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + pong := msgi.(*redis.Pong) + Expect(pong.Payload).To(Equal("")) + }) + + It("should ping/pong with payload", func() { + pubsub := client.Subscribe("mychannel") + defer pubsub.Close() + + _, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + + err = pubsub.Ping("hello") + Expect(err).NotTo(HaveOccurred()) + + msgi, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + pong := msgi.(*redis.Pong) + Expect(pong.Payload).To(Equal("hello")) + }) + + It("should multi-ReceiveMessage", func() { + pubsub := client.Subscribe("mychannel") + defer pubsub.Close() + + subscr, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + Expect(subscr).To(Equal(&redis.Subscription{ + Kind: "subscribe", + Channel: "mychannel", + Count: 1, + })) + + err = client.Publish("mychannel", "hello").Err() + Expect(err).NotTo(HaveOccurred()) + + err = client.Publish("mychannel", "world").Err() + Expect(err).NotTo(HaveOccurred()) + + msg, err := pubsub.ReceiveMessage() + Expect(err).NotTo(HaveOccurred()) + Expect(msg.Channel).To(Equal("mychannel")) + Expect(msg.Payload).To(Equal("hello")) + + msg, err = pubsub.ReceiveMessage() + Expect(err).NotTo(HaveOccurred()) + Expect(msg.Channel).To(Equal("mychannel")) + Expect(msg.Payload).To(Equal("world")) + }) + + It("should ReceiveMessage after timeout", func() { + timeout := 100 * time.Millisecond + + pubsub := client.Subscribe("mychannel") + defer pubsub.Close() + + subscr, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + Expect(subscr).To(Equal(&redis.Subscription{ + Kind: "subscribe", + Channel: "mychannel", + Count: 1, + })) + + done := make(chan bool, 1) + go func() { + defer GinkgoRecover() + defer func() { + done <- true + }() + + time.Sleep(timeout + 100*time.Millisecond) + n, err := client.Publish("mychannel", "hello").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(1))) + }() + + msg, err := pubsub.ReceiveMessageTimeout(timeout) + Expect(err).NotTo(HaveOccurred()) + Expect(msg.Channel).To(Equal("mychannel")) + Expect(msg.Payload).To(Equal("hello")) + + Eventually(done).Should(Receive()) + + stats := client.PoolStats() + Expect(stats.Hits).To(Equal(uint32(1))) + Expect(stats.Misses).To(Equal(uint32(1))) + }) + + It("returns an error when subscribe fails", func() { + pubsub := client.Subscribe() + defer pubsub.Close() + + pubsub.SetNetConn(&badConn{ + readErr: io.EOF, + writeErr: io.EOF, + }) + + err := pubsub.Subscribe("mychannel") + Expect(err).To(MatchError("EOF")) + + err = pubsub.Subscribe("mychannel") + Expect(err).NotTo(HaveOccurred()) + }) + + expectReceiveMessageOnError := func(pubsub *redis.PubSub) { + pubsub.SetNetConn(&badConn{ + readErr: io.EOF, + writeErr: io.EOF, + }) + + done := make(chan bool, 1) + go func() { + defer GinkgoRecover() + defer func() { + done <- true + }() + + time.Sleep(100 * time.Millisecond) + err := client.Publish("mychannel", "hello").Err() + Expect(err).NotTo(HaveOccurred()) + }() + + msg, err := pubsub.ReceiveMessage() + Expect(err).NotTo(HaveOccurred()) + Expect(msg.Channel).To(Equal("mychannel")) + Expect(msg.Payload).To(Equal("hello")) + + Eventually(done).Should(Receive()) + } + + It("Subscribe should reconnect on ReceiveMessage error", func() { + pubsub := client.Subscribe("mychannel") + defer pubsub.Close() + + subscr, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + Expect(subscr).To(Equal(&redis.Subscription{ + Kind: "subscribe", + Channel: "mychannel", + Count: 1, + })) + + expectReceiveMessageOnError(pubsub) + }) + + It("PSubscribe should reconnect on ReceiveMessage error", func() { + pubsub := client.PSubscribe("mychannel") + defer pubsub.Close() + + subscr, err := pubsub.ReceiveTimeout(time.Second) + Expect(err).NotTo(HaveOccurred()) + Expect(subscr).To(Equal(&redis.Subscription{ + Kind: "psubscribe", + Channel: "mychannel", + Count: 1, + })) + + expectReceiveMessageOnError(pubsub) + }) + + It("should return on Close", func() { + pubsub := client.Subscribe("mychannel") + defer pubsub.Close() + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer GinkgoRecover() + + wg.Done() + defer wg.Done() + + _, err := pubsub.ReceiveMessage() + Expect(err).To(HaveOccurred()) + Expect(err).To(SatisfyAny( + MatchError("redis: client is closed"), + MatchError("use of closed network connection"), // Go 1.4 + )) + }() + + wg.Wait() + wg.Add(1) + + Expect(pubsub.Close()).NotTo(HaveOccurred()) + + wg.Wait() + }) + + It("should ReceiveMessage without a subscription", func() { + timeout := 100 * time.Millisecond + + pubsub := client.Subscribe() + defer pubsub.Close() + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer GinkgoRecover() + defer wg.Done() + + time.Sleep(2 * timeout) + + err := pubsub.Subscribe("mychannel") + Expect(err).NotTo(HaveOccurred()) + + time.Sleep(timeout) + + err = client.Publish("mychannel", "hello").Err() + Expect(err).NotTo(HaveOccurred()) + }() + + msg, err := pubsub.ReceiveMessageTimeout(timeout) + Expect(err).NotTo(HaveOccurred()) + Expect(msg.Channel).To(Equal("mychannel")) + Expect(msg.Payload).To(Equal("hello")) + + wg.Wait() + }) + + It("handles big message payload", func() { + pubsub := client.Subscribe("mychannel") + defer pubsub.Close() + + ch := pubsub.Channel() + + bigVal := bigVal() + err := client.Publish("mychannel", bigVal).Err() + Expect(err).NotTo(HaveOccurred()) + + var msg *redis.Message + Eventually(ch).Should(Receive(&msg)) + Expect(msg.Channel).To(Equal("mychannel")) + Expect(msg.Payload).To(Equal(string(bigVal))) + }) +}) diff --git a/vendor/github.com/go-redis/redis/race_test.go b/vendor/github.com/go-redis/redis/race_test.go new file mode 100644 index 0000000..1426408 --- /dev/null +++ b/vendor/github.com/go-redis/redis/race_test.go @@ -0,0 +1,250 @@ +package redis_test + +import ( + "bytes" + "fmt" + "net" + "strconv" + "testing" + "time" + + "github.com/go-redis/redis" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("races", func() { + var client *redis.Client + var C, N int + + BeforeEach(func() { + client = redis.NewClient(redisOptions()) + Expect(client.FlushDB().Err()).To(BeNil()) + + C, N = 10, 1000 + if testing.Short() { + C = 4 + N = 100 + } + }) + + AfterEach(func() { + err := client.Close() + Expect(err).NotTo(HaveOccurred()) + }) + + It("should echo", func() { + perform(C, func(id int) { + for i := 0; i < N; i++ { + msg := fmt.Sprintf("echo %d %d", id, i) + echo, err := client.Echo(msg).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(echo).To(Equal(msg)) + } + }) + }) + + It("should incr", func() { + key := "TestIncrFromGoroutines" + + perform(C, func(id int) { + for i := 0; i < N; i++ { + err := client.Incr(key).Err() + Expect(err).NotTo(HaveOccurred()) + } + }) + + val, err := client.Get(key).Int64() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal(int64(C * N))) + }) + + It("should handle many keys", func() { + perform(C, func(id int) { + for i := 0; i < N; i++ { + err := client.Set( + fmt.Sprintf("keys.key-%d-%d", id, i), + fmt.Sprintf("hello-%d-%d", id, i), + 0, + ).Err() + Expect(err).NotTo(HaveOccurred()) + } + }) + + keys := client.Keys("keys.*") + Expect(keys.Err()).NotTo(HaveOccurred()) + Expect(len(keys.Val())).To(Equal(C * N)) + }) + + It("should handle many keys 2", func() { + perform(C, func(id int) { + keys := []string{"non-existent-key"} + for i := 0; i < N; i++ { + key := fmt.Sprintf("keys.key-%d", i) + keys = append(keys, key) + + err := client.Set(key, fmt.Sprintf("hello-%d", i), 0).Err() + Expect(err).NotTo(HaveOccurred()) + } + keys = append(keys, "non-existent-key") + + vals, err := client.MGet(keys...).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(vals)).To(Equal(N + 2)) + + for i := 0; i < N; i++ { + Expect(vals[i+1]).To(Equal(fmt.Sprintf("hello-%d", i))) + } + + Expect(vals[0]).To(BeNil()) + Expect(vals[N+1]).To(BeNil()) + }) + }) + + It("should handle big vals in Get", func() { + C, N = 4, 100 + + bigVal := bigVal() + + err := client.Set("key", bigVal, 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Reconnect to get new connection. + Expect(client.Close()).To(BeNil()) + client = redis.NewClient(redisOptions()) + + perform(C, func(id int) { + for i := 0; i < N; i++ { + got, err := client.Get("key").Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(got).To(Equal(bigVal)) + } + }) + }) + + It("should handle big vals in Set", func() { + C, N = 4, 100 + + bigVal := bigVal() + perform(C, func(id int) { + for i := 0; i < N; i++ { + err := client.Set("key", bigVal, 0).Err() + Expect(err).NotTo(HaveOccurred()) + } + }) + }) + + It("should select db", func() { + err := client.Set("db", 1, 0).Err() + Expect(err).NotTo(HaveOccurred()) + + perform(C, func(id int) { + opt := redisOptions() + opt.DB = id + client := redis.NewClient(opt) + for i := 0; i < N; i++ { + err := client.Set("db", id, 0).Err() + Expect(err).NotTo(HaveOccurred()) + + n, err := client.Get("db").Int64() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(id))) + } + err := client.Close() + Expect(err).NotTo(HaveOccurred()) + }) + + n, err := client.Get("db").Int64() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(1))) + }) + + It("should select DB with read timeout", func() { + perform(C, func(id int) { + opt := redisOptions() + opt.DB = id + opt.ReadTimeout = time.Nanosecond + client := redis.NewClient(opt) + + perform(C, func(id int) { + err := client.Ping().Err() + Expect(err).To(HaveOccurred()) + Expect(err.(net.Error).Timeout()).To(BeTrue()) + }) + + err := client.Close() + Expect(err).NotTo(HaveOccurred()) + }) + }) + + It("should Watch/Unwatch", func() { + err := client.Set("key", "0", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + perform(C, func(id int) { + for i := 0; i < N; i++ { + err := client.Watch(func(tx *redis.Tx) error { + val, err := tx.Get("key").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).NotTo(Equal(redis.Nil)) + + num, err := strconv.ParseInt(val, 10, 64) + Expect(err).NotTo(HaveOccurred()) + + cmds, err := tx.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Set("key", strconv.FormatInt(num+1, 10), 0) + return nil + }) + Expect(cmds).To(HaveLen(1)) + return err + }, "key") + if err == redis.TxFailedErr { + i-- + continue + } + Expect(err).NotTo(HaveOccurred()) + } + }) + + val, err := client.Get("key").Int64() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal(int64(C * N))) + }) + + It("should Pipeline", func() { + perform(C, func(id int) { + pipe := client.Pipeline() + for i := 0; i < N; i++ { + pipe.Echo(fmt.Sprint(i)) + } + + cmds, err := pipe.Exec() + Expect(err).NotTo(HaveOccurred()) + Expect(cmds).To(HaveLen(N)) + + for i := 0; i < N; i++ { + Expect(cmds[i].(*redis.StringCmd).Val()).To(Equal(fmt.Sprint(i))) + } + }) + }) + + It("should Pipeline", func() { + pipe := client.Pipeline() + perform(N, func(id int) { + pipe.Incr("key") + }) + + cmds, err := pipe.Exec() + Expect(err).NotTo(HaveOccurred()) + Expect(cmds).To(HaveLen(N)) + + n, err := client.Get("key").Int64() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(N))) + }) +}) + +func bigVal() []byte { + return bytes.Repeat([]byte{'*'}, 1<<17) // 128kb +} diff --git a/vendor/github.com/go-redis/redis/redis.go b/vendor/github.com/go-redis/redis/redis.go new file mode 100644 index 0000000..230091b --- /dev/null +++ b/vendor/github.com/go-redis/redis/redis.go @@ -0,0 +1,450 @@ +package redis + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/go-redis/redis/internal" + "github.com/go-redis/redis/internal/pool" + "github.com/go-redis/redis/internal/proto" +) + +// Redis nil reply, .e.g. when key does not exist. +const Nil = internal.Nil + +func init() { + SetLogger(log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile)) +} + +func SetLogger(logger *log.Logger) { + internal.Logger = logger +} + +func (c *baseClient) String() string { + return fmt.Sprintf("Redis<%s db:%d>", c.getAddr(), c.opt.DB) +} + +func (c *baseClient) newConn() (*pool.Conn, error) { + cn, err := c.connPool.NewConn() + if err != nil { + return nil, err + } + + if !cn.Inited { + if err := c.initConn(cn); err != nil { + _ = c.connPool.CloseConn(cn) + return nil, err + } + } + + return cn, nil +} + +func (c *baseClient) getConn() (*pool.Conn, bool, error) { + cn, isNew, err := c.connPool.Get() + if err != nil { + return nil, false, err + } + + if !cn.Inited { + if err := c.initConn(cn); err != nil { + _ = c.connPool.Remove(cn) + return nil, false, err + } + } + + return cn, isNew, nil +} + +func (c *baseClient) releaseConn(cn *pool.Conn, err error) bool { + if internal.IsBadConn(err, false) { + _ = c.connPool.Remove(cn) + return false + } + + _ = c.connPool.Put(cn) + return true +} + +func (c *baseClient) initConn(cn *pool.Conn) error { + cn.Inited = true + + if c.opt.Password == "" && + c.opt.DB == 0 && + !c.opt.readOnly && + c.opt.OnConnect == nil { + return nil + } + + // Temp client to initialize connection. + conn := &Conn{ + baseClient: baseClient{ + opt: c.opt, + connPool: pool.NewSingleConnPool(cn), + }, + } + conn.setProcessor(conn.Process) + + _, err := conn.Pipelined(func(pipe Pipeliner) error { + if c.opt.Password != "" { + pipe.Auth(c.opt.Password) + } + + if c.opt.DB > 0 { + pipe.Select(c.opt.DB) + } + + if c.opt.readOnly { + pipe.ReadOnly() + } + + return nil + }) + if err != nil { + return err + } + + if c.opt.OnConnect != nil { + return c.opt.OnConnect(conn) + } + return nil +} + +// WrapProcess replaces the process func. It takes a function createWrapper +// which is supplied by the user. createWrapper takes the old process func as +// an input and returns the new wrapper process func. createWrapper should +// use call the old process func within the new process func. +func (c *baseClient) WrapProcess(fn func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error) { + c.process = fn(c.defaultProcess) +} + +func (c *baseClient) Process(cmd Cmder) error { + if c.process != nil { + return c.process(cmd) + } + return c.defaultProcess(cmd) +} + +func (c *baseClient) defaultProcess(cmd Cmder) error { + for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ { + if attempt > 0 { + time.Sleep(c.retryBackoff(attempt)) + } + + cn, _, err := c.getConn() + if err != nil { + cmd.setErr(err) + if internal.IsRetryableError(err, true) { + continue + } + return err + } + + cn.SetWriteTimeout(c.opt.WriteTimeout) + if err := writeCmd(cn, cmd); err != nil { + c.releaseConn(cn, err) + cmd.setErr(err) + if internal.IsRetryableError(err, true) { + continue + } + return err + } + + cn.SetReadTimeout(c.cmdTimeout(cmd)) + err = cmd.readReply(cn) + c.releaseConn(cn, err) + if err != nil && internal.IsRetryableError(err, cmd.readTimeout() == nil) { + continue + } + + return err + } + + return cmd.Err() +} + +func (c *baseClient) retryBackoff(attempt int) time.Duration { + return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff) +} + +func (c *baseClient) cmdTimeout(cmd Cmder) time.Duration { + if timeout := cmd.readTimeout(); timeout != nil { + return *timeout + } else { + return c.opt.ReadTimeout + } +} + +// Close closes the client, releasing any open resources. +// +// It is rare to Close a Client, as the Client is meant to be +// long-lived and shared between many goroutines. +func (c *baseClient) Close() error { + var firstErr error + if c.onClose != nil { + if err := c.onClose(); err != nil && firstErr == nil { + firstErr = err + } + } + if err := c.connPool.Close(); err != nil && firstErr == nil { + firstErr = err + } + return firstErr +} + +func (c *baseClient) getAddr() string { + return c.opt.Addr +} + +type pipelineProcessor func(*pool.Conn, []Cmder) (bool, error) + +func (c *baseClient) pipelineExecer(p pipelineProcessor) pipelineExecer { + return func(cmds []Cmder) error { + for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ { + if attempt > 0 { + time.Sleep(c.retryBackoff(attempt)) + } + + cn, _, err := c.getConn() + if err != nil { + setCmdsErr(cmds, err) + return err + } + + canRetry, err := p(cn, cmds) + + if err == nil || internal.IsRedisError(err) { + _ = c.connPool.Put(cn) + break + } + _ = c.connPool.Remove(cn) + + if !canRetry || !internal.IsRetryableError(err, true) { + break + } + } + return firstCmdsErr(cmds) + } +} + +func (c *baseClient) pipelineProcessCmds(cn *pool.Conn, cmds []Cmder) (bool, error) { + cn.SetWriteTimeout(c.opt.WriteTimeout) + if err := writeCmd(cn, cmds...); err != nil { + setCmdsErr(cmds, err) + return true, err + } + + // Set read timeout for all commands. + cn.SetReadTimeout(c.opt.ReadTimeout) + return true, pipelineReadCmds(cn, cmds) +} + +func pipelineReadCmds(cn *pool.Conn, cmds []Cmder) error { + for _, cmd := range cmds { + err := cmd.readReply(cn) + if err != nil && !internal.IsRedisError(err) { + return err + } + } + return nil +} + +func (c *baseClient) txPipelineProcessCmds(cn *pool.Conn, cmds []Cmder) (bool, error) { + cn.SetWriteTimeout(c.opt.WriteTimeout) + if err := txPipelineWriteMulti(cn, cmds); err != nil { + setCmdsErr(cmds, err) + return true, err + } + + // Set read timeout for all commands. + cn.SetReadTimeout(c.opt.ReadTimeout) + + if err := c.txPipelineReadQueued(cn, cmds); err != nil { + setCmdsErr(cmds, err) + return false, err + } + + return false, pipelineReadCmds(cn, cmds) +} + +func txPipelineWriteMulti(cn *pool.Conn, cmds []Cmder) error { + multiExec := make([]Cmder, 0, len(cmds)+2) + multiExec = append(multiExec, NewStatusCmd("MULTI")) + multiExec = append(multiExec, cmds...) + multiExec = append(multiExec, NewSliceCmd("EXEC")) + return writeCmd(cn, multiExec...) +} + +func (c *baseClient) txPipelineReadQueued(cn *pool.Conn, cmds []Cmder) error { + // Parse queued replies. + var statusCmd StatusCmd + if err := statusCmd.readReply(cn); err != nil { + return err + } + + for _ = range cmds { + err := statusCmd.readReply(cn) + if err != nil && !internal.IsRedisError(err) { + return err + } + } + + // Parse number of replies. + line, err := cn.Rd.ReadLine() + if err != nil { + if err == Nil { + err = TxFailedErr + } + return err + } + + switch line[0] { + case proto.ErrorReply: + return proto.ParseErrorReply(line) + case proto.ArrayReply: + // ok + default: + err := fmt.Errorf("redis: expected '*', but got line %q", line) + return err + } + + return nil +} + +//------------------------------------------------------------------------------ + +// Client is a Redis client representing a pool of zero or more +// underlying connections. It's safe for concurrent use by multiple +// goroutines. +type Client struct { + baseClient + cmdable +} + +func newClient(opt *Options, pool pool.Pooler) *Client { + client := Client{ + baseClient: baseClient{ + opt: opt, + connPool: pool, + }, + } + client.setProcessor(client.Process) + return &client +} + +// NewClient returns a client to the Redis Server specified by Options. +func NewClient(opt *Options) *Client { + opt.init() + return newClient(opt, newConnPool(opt)) +} + +func (c *Client) copy() *Client { + c2 := new(Client) + *c2 = *c + c2.setProcessor(c2.Process) + return c2 +} + +// Options returns read-only Options that were used to create the client. +func (c *Client) Options() *Options { + return c.opt +} + +type PoolStats pool.Stats + +// PoolStats returns connection pool stats. +func (c *Client) PoolStats() *PoolStats { + stats := c.connPool.Stats() + return (*PoolStats)(stats) +} + +func (c *Client) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { + return c.Pipeline().Pipelined(fn) +} + +func (c *Client) Pipeline() Pipeliner { + pipe := Pipeline{ + exec: c.pipelineExecer(c.pipelineProcessCmds), + } + pipe.setProcessor(pipe.Process) + return &pipe +} + +func (c *Client) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { + return c.TxPipeline().Pipelined(fn) +} + +// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC. +func (c *Client) TxPipeline() Pipeliner { + pipe := Pipeline{ + exec: c.pipelineExecer(c.txPipelineProcessCmds), + } + pipe.setProcessor(pipe.Process) + return &pipe +} + +func (c *Client) pubSub() *PubSub { + return &PubSub{ + opt: c.opt, + + newConn: func(channels []string) (*pool.Conn, error) { + return c.newConn() + }, + closeConn: c.connPool.CloseConn, + } +} + +// Subscribe subscribes the client to the specified channels. +// Channels can be omitted to create empty subscription. +func (c *Client) Subscribe(channels ...string) *PubSub { + pubsub := c.pubSub() + if len(channels) > 0 { + _ = pubsub.Subscribe(channels...) + } + return pubsub +} + +// PSubscribe subscribes the client to the given patterns. +// Patterns can be omitted to create empty subscription. +func (c *Client) PSubscribe(channels ...string) *PubSub { + pubsub := c.pubSub() + if len(channels) > 0 { + _ = pubsub.PSubscribe(channels...) + } + return pubsub +} + +//------------------------------------------------------------------------------ + +// Conn is like Client, but its pool contains single connection. +type Conn struct { + baseClient + statefulCmdable +} + +func (c *Conn) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { + return c.Pipeline().Pipelined(fn) +} + +func (c *Conn) Pipeline() Pipeliner { + pipe := Pipeline{ + exec: c.pipelineExecer(c.pipelineProcessCmds), + } + pipe.setProcessor(pipe.Process) + return &pipe +} + +func (c *Conn) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { + return c.TxPipeline().Pipelined(fn) +} + +// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC. +func (c *Conn) TxPipeline() Pipeliner { + pipe := Pipeline{ + exec: c.pipelineExecer(c.txPipelineProcessCmds), + } + pipe.setProcessor(pipe.Process) + return &pipe +} diff --git a/vendor/github.com/go-redis/redis/redis_context.go b/vendor/github.com/go-redis/redis/redis_context.go new file mode 100644 index 0000000..6ec811c --- /dev/null +++ b/vendor/github.com/go-redis/redis/redis_context.go @@ -0,0 +1,35 @@ +// +build go1.7 + +package redis + +import ( + "context" + + "github.com/go-redis/redis/internal/pool" +) + +type baseClient struct { + connPool pool.Pooler + opt *Options + + process func(Cmder) error + onClose func() error // hook called when client is closed + + ctx context.Context +} + +func (c *Client) Context() context.Context { + if c.ctx != nil { + return c.ctx + } + return context.Background() +} + +func (c *Client) WithContext(ctx context.Context) *Client { + if ctx == nil { + panic("nil context") + } + c2 := c.copy() + c2.ctx = ctx + return c2 +} diff --git a/vendor/github.com/go-redis/redis/redis_no_context.go b/vendor/github.com/go-redis/redis/redis_no_context.go new file mode 100644 index 0000000..0752192 --- /dev/null +++ b/vendor/github.com/go-redis/redis/redis_no_context.go @@ -0,0 +1,15 @@ +// +build !go1.7 + +package redis + +import ( + "github.com/go-redis/redis/internal/pool" +) + +type baseClient struct { + connPool pool.Pooler + opt *Options + + process func(Cmder) error + onClose func() error // hook called when client is closed +} diff --git a/vendor/github.com/go-redis/redis/redis_test.go b/vendor/github.com/go-redis/redis/redis_test.go new file mode 100644 index 0000000..49d3fb3 --- /dev/null +++ b/vendor/github.com/go-redis/redis/redis_test.go @@ -0,0 +1,364 @@ +package redis_test + +import ( + "bytes" + "net" + "time" + + "github.com/go-redis/redis" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Client", func() { + var client *redis.Client + + BeforeEach(func() { + client = redis.NewClient(redisOptions()) + Expect(client.FlushDB().Err()).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + client.Close() + }) + + It("should Stringer", func() { + Expect(client.String()).To(Equal("Redis<:6380 db:15>")) + }) + + It("should ping", func() { + val, err := client.Ping().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("PONG")) + }) + + It("should return pool stats", func() { + Expect(client.PoolStats()).To(BeAssignableToTypeOf(&redis.PoolStats{})) + }) + + It("should support custom dialers", func() { + custom := redis.NewClient(&redis.Options{ + Addr: ":1234", + Dialer: func() (net.Conn, error) { + return net.Dial("tcp", redisAddr) + }, + }) + + val, err := custom.Ping().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("PONG")) + Expect(custom.Close()).NotTo(HaveOccurred()) + }) + + It("should close", func() { + Expect(client.Close()).NotTo(HaveOccurred()) + err := client.Ping().Err() + Expect(err).To(MatchError("redis: client is closed")) + }) + + It("should close pubsub without closing the client", func() { + pubsub := client.Subscribe() + Expect(pubsub.Close()).NotTo(HaveOccurred()) + + _, err := pubsub.Receive() + Expect(err).To(MatchError("redis: client is closed")) + Expect(client.Ping().Err()).NotTo(HaveOccurred()) + }) + + It("should close Tx without closing the client", func() { + err := client.Watch(func(tx *redis.Tx) error { + _, err := tx.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Ping() + return nil + }) + return err + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(client.Ping().Err()).NotTo(HaveOccurred()) + }) + + It("should close pipeline without closing the client", func() { + pipeline := client.Pipeline() + Expect(pipeline.Close()).NotTo(HaveOccurred()) + + pipeline.Ping() + _, err := pipeline.Exec() + Expect(err).To(MatchError("redis: client is closed")) + + Expect(client.Ping().Err()).NotTo(HaveOccurred()) + }) + + It("should close pubsub when client is closed", func() { + pubsub := client.Subscribe() + Expect(client.Close()).NotTo(HaveOccurred()) + + _, err := pubsub.Receive() + Expect(err).To(MatchError("redis: client is closed")) + + Expect(pubsub.Close()).NotTo(HaveOccurred()) + }) + + It("should close pipeline when client is closed", func() { + pipeline := client.Pipeline() + Expect(client.Close()).NotTo(HaveOccurred()) + Expect(pipeline.Close()).NotTo(HaveOccurred()) + }) + + It("should select DB", func() { + db2 := redis.NewClient(&redis.Options{ + Addr: redisAddr, + DB: 2, + }) + Expect(db2.FlushDB().Err()).NotTo(HaveOccurred()) + Expect(db2.Get("db").Err()).To(Equal(redis.Nil)) + Expect(db2.Set("db", 2, 0).Err()).NotTo(HaveOccurred()) + + n, err := db2.Get("db").Int64() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(2))) + + Expect(client.Get("db").Err()).To(Equal(redis.Nil)) + + Expect(db2.FlushDB().Err()).NotTo(HaveOccurred()) + Expect(db2.Close()).NotTo(HaveOccurred()) + }) + + It("processes custom commands", func() { + cmd := redis.NewCmd("PING") + client.Process(cmd) + + // Flush buffers. + Expect(client.Echo("hello").Err()).NotTo(HaveOccurred()) + + Expect(cmd.Err()).NotTo(HaveOccurred()) + Expect(cmd.Val()).To(Equal("PONG")) + }) + + It("should retry command on network error", func() { + Expect(client.Close()).NotTo(HaveOccurred()) + + client = redis.NewClient(&redis.Options{ + Addr: redisAddr, + MaxRetries: 1, + }) + + // Put bad connection in the pool. + cn, _, err := client.Pool().Get() + Expect(err).NotTo(HaveOccurred()) + + cn.SetNetConn(&badConn{}) + err = client.Pool().Put(cn) + Expect(err).NotTo(HaveOccurred()) + + err = client.Ping().Err() + Expect(err).NotTo(HaveOccurred()) + }) + + It("should retry with backoff", func() { + Expect(client.Close()).NotTo(HaveOccurred()) + + // use up all the available connections to force a fail + connectionHogClient := redis.NewClient(&redis.Options{ + Addr: redisAddr, + MaxRetries: 1, + }) + defer connectionHogClient.Close() + + for i := 0; i <= 1002; i++ { + connectionHogClient.Pool().NewConn() + } + + clientNoRetry := redis.NewClient(&redis.Options{ + Addr: redisAddr, + PoolSize: 1, + MaxRetryBackoff: -1, + }) + defer clientNoRetry.Close() + + clientRetry := redis.NewClient(&redis.Options{ + Addr: redisAddr, + MaxRetries: 5, + PoolSize: 1, + MaxRetryBackoff: 128 * time.Millisecond, + }) + defer clientRetry.Close() + + startNoRetry := time.Now() + err := clientNoRetry.Ping().Err() + Expect(err).To(HaveOccurred()) + elapseNoRetry := time.Since(startNoRetry) + + startRetry := time.Now() + err = clientRetry.Ping().Err() + Expect(err).To(HaveOccurred()) + elapseRetry := time.Since(startRetry) + + Expect(elapseRetry > elapseNoRetry).To(BeTrue()) + }) + + It("should update conn.UsedAt on read/write", func() { + cn, _, err := client.Pool().Get() + Expect(err).NotTo(HaveOccurred()) + Expect(cn.UsedAt).NotTo(BeZero()) + createdAt := cn.UsedAt() + + err = client.Pool().Put(cn) + Expect(err).NotTo(HaveOccurred()) + Expect(cn.UsedAt().Equal(createdAt)).To(BeTrue()) + + err = client.Ping().Err() + Expect(err).NotTo(HaveOccurred()) + + cn, _, err = client.Pool().Get() + Expect(err).NotTo(HaveOccurred()) + Expect(cn).NotTo(BeNil()) + Expect(cn.UsedAt().After(createdAt)).To(BeTrue()) + }) + + It("should process command with special chars", func() { + set := client.Set("key", "hello1\r\nhello2\r\n", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + get := client.Get("key") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("hello1\r\nhello2\r\n")) + }) + + It("should handle big vals", func() { + bigVal := bytes.Repeat([]byte{'*'}, 2e6) + + err := client.Set("key", bigVal, 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Reconnect to get new connection. + Expect(client.Close()).NotTo(HaveOccurred()) + client = redis.NewClient(redisOptions()) + + got, err := client.Get("key").Bytes() + Expect(err).NotTo(HaveOccurred()) + Expect(got).To(Equal(bigVal)) + }) + + It("should call WrapProcess", func() { + var wrapperFnCalled bool + + client.WrapProcess(func(oldProcess func(redis.Cmder) error) func(redis.Cmder) error { + return func(cmd redis.Cmder) error { + wrapperFnCalled = true + return oldProcess(cmd) + } + }) + + Expect(client.Ping().Err()).NotTo(HaveOccurred()) + + Expect(wrapperFnCalled).To(BeTrue()) + }) +}) + +var _ = Describe("Client timeout", func() { + var opt *redis.Options + var client *redis.Client + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + testTimeout := func() { + It("Ping timeouts", func() { + err := client.Ping().Err() + Expect(err).To(HaveOccurred()) + Expect(err.(net.Error).Timeout()).To(BeTrue()) + }) + + It("Pipeline timeouts", func() { + _, err := client.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Ping() + return nil + }) + Expect(err).To(HaveOccurred()) + Expect(err.(net.Error).Timeout()).To(BeTrue()) + }) + + It("Subscribe timeouts", func() { + if opt.WriteTimeout == 0 { + return + } + + pubsub := client.Subscribe() + defer pubsub.Close() + + err := pubsub.Subscribe("_") + Expect(err).To(HaveOccurred()) + Expect(err.(net.Error).Timeout()).To(BeTrue()) + }) + + It("Tx timeouts", func() { + err := client.Watch(func(tx *redis.Tx) error { + return tx.Ping().Err() + }) + Expect(err).To(HaveOccurred()) + Expect(err.(net.Error).Timeout()).To(BeTrue()) + }) + + It("Tx Pipeline timeouts", func() { + err := client.Watch(func(tx *redis.Tx) error { + _, err := tx.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Ping() + return nil + }) + return err + }) + Expect(err).To(HaveOccurred()) + Expect(err.(net.Error).Timeout()).To(BeTrue()) + }) + } + + Context("read timeout", func() { + BeforeEach(func() { + opt = redisOptions() + opt.ReadTimeout = time.Nanosecond + opt.WriteTimeout = -1 + client = redis.NewClient(opt) + }) + + testTimeout() + }) + + Context("write timeout", func() { + BeforeEach(func() { + opt = redisOptions() + opt.ReadTimeout = -1 + opt.WriteTimeout = time.Nanosecond + client = redis.NewClient(opt) + }) + + testTimeout() + }) +}) + +var _ = Describe("Client OnConnect", func() { + var client *redis.Client + + BeforeEach(func() { + opt := redisOptions() + opt.DB = 0 + opt.OnConnect = func(cn *redis.Conn) error { + return cn.ClientSetName("on_connect").Err() + } + + client = redis.NewClient(opt) + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + It("calls OnConnect", func() { + name, err := client.ClientGetName().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(name).To(Equal("on_connect")) + }) +}) diff --git a/vendor/github.com/go-redis/redis/result.go b/vendor/github.com/go-redis/redis/result.go new file mode 100644 index 0000000..28cea5c --- /dev/null +++ b/vendor/github.com/go-redis/redis/result.go @@ -0,0 +1,140 @@ +package redis + +import "time" + +// NewCmdResult returns a Cmd initalised with val and err for testing +func NewCmdResult(val interface{}, err error) *Cmd { + var cmd Cmd + cmd.val = val + cmd.setErr(err) + return &cmd +} + +// NewSliceResult returns a SliceCmd initalised with val and err for testing +func NewSliceResult(val []interface{}, err error) *SliceCmd { + var cmd SliceCmd + cmd.val = val + cmd.setErr(err) + return &cmd +} + +// NewStatusResult returns a StatusCmd initalised with val and err for testing +func NewStatusResult(val string, err error) *StatusCmd { + var cmd StatusCmd + cmd.val = val + cmd.setErr(err) + return &cmd +} + +// NewIntResult returns an IntCmd initalised with val and err for testing +func NewIntResult(val int64, err error) *IntCmd { + var cmd IntCmd + cmd.val = val + cmd.setErr(err) + return &cmd +} + +// NewDurationResult returns a DurationCmd initalised with val and err for testing +func NewDurationResult(val time.Duration, err error) *DurationCmd { + var cmd DurationCmd + cmd.val = val + cmd.setErr(err) + return &cmd +} + +// NewBoolResult returns a BoolCmd initalised with val and err for testing +func NewBoolResult(val bool, err error) *BoolCmd { + var cmd BoolCmd + cmd.val = val + cmd.setErr(err) + return &cmd +} + +// NewStringResult returns a StringCmd initalised with val and err for testing +func NewStringResult(val string, err error) *StringCmd { + var cmd StringCmd + cmd.val = []byte(val) + cmd.setErr(err) + return &cmd +} + +// NewFloatResult returns a FloatCmd initalised with val and err for testing +func NewFloatResult(val float64, err error) *FloatCmd { + var cmd FloatCmd + cmd.val = val + cmd.setErr(err) + return &cmd +} + +// NewStringSliceResult returns a StringSliceCmd initalised with val and err for testing +func NewStringSliceResult(val []string, err error) *StringSliceCmd { + var cmd StringSliceCmd + cmd.val = val + cmd.setErr(err) + return &cmd +} + +// NewBoolSliceResult returns a BoolSliceCmd initalised with val and err for testing +func NewBoolSliceResult(val []bool, err error) *BoolSliceCmd { + var cmd BoolSliceCmd + cmd.val = val + cmd.setErr(err) + return &cmd +} + +// NewStringStringMapResult returns a StringStringMapCmd initalised with val and err for testing +func NewStringStringMapResult(val map[string]string, err error) *StringStringMapCmd { + var cmd StringStringMapCmd + cmd.val = val + cmd.setErr(err) + return &cmd +} + +// NewStringIntMapCmdResult returns a StringIntMapCmd initalised with val and err for testing +func NewStringIntMapCmdResult(val map[string]int64, err error) *StringIntMapCmd { + var cmd StringIntMapCmd + cmd.val = val + cmd.setErr(err) + return &cmd +} + +// NewZSliceCmdResult returns a ZSliceCmd initalised with val and err for testing +func NewZSliceCmdResult(val []Z, err error) *ZSliceCmd { + var cmd ZSliceCmd + cmd.val = val + cmd.setErr(err) + return &cmd +} + +// NewScanCmdResult returns a ScanCmd initalised with val and err for testing +func NewScanCmdResult(keys []string, cursor uint64, err error) *ScanCmd { + var cmd ScanCmd + cmd.page = keys + cmd.cursor = cursor + cmd.setErr(err) + return &cmd +} + +// NewClusterSlotsCmdResult returns a ClusterSlotsCmd initalised with val and err for testing +func NewClusterSlotsCmdResult(val []ClusterSlot, err error) *ClusterSlotsCmd { + var cmd ClusterSlotsCmd + cmd.val = val + cmd.setErr(err) + return &cmd +} + +// NewGeoLocationCmdResult returns a GeoLocationCmd initalised with val and err for testing +func NewGeoLocationCmdResult(val []GeoLocation, err error) *GeoLocationCmd { + var cmd GeoLocationCmd + cmd.locations = val + cmd.setErr(err) + return &cmd +} + +// NewCommandsInfoCmdResult returns a CommandsInfoCmd initalised with val and err for testing +func NewCommandsInfoCmdResult(val map[string]*CommandInfo, err error) *CommandsInfoCmd { + var cmd CommandsInfoCmd + cmd.val = val + cmd.setErr(err) + return &cmd +} diff --git a/vendor/github.com/go-redis/redis/ring.go b/vendor/github.com/go-redis/redis/ring.go new file mode 100644 index 0000000..a30c321 --- /dev/null +++ b/vendor/github.com/go-redis/redis/ring.go @@ -0,0 +1,503 @@ +package redis + +import ( + "errors" + "fmt" + "math/rand" + "strconv" + "sync" + "sync/atomic" + "time" + + "github.com/go-redis/redis/internal" + "github.com/go-redis/redis/internal/consistenthash" + "github.com/go-redis/redis/internal/hashtag" + "github.com/go-redis/redis/internal/pool" +) + +var errRingShardsDown = errors.New("redis: all ring shards are down") + +// RingOptions are used to configure a ring client and should be +// passed to NewRing. +type RingOptions struct { + // Map of name => host:port addresses of ring shards. + Addrs map[string]string + + // Frequency of PING commands sent to check shards availability. + // Shard is considered down after 3 subsequent failed checks. + HeartbeatFrequency time.Duration + + // Following options are copied from Options struct. + + OnConnect func(*Conn) error + + DB int + Password string + + MaxRetries int + MinRetryBackoff time.Duration + MaxRetryBackoff time.Duration + + DialTimeout time.Duration + ReadTimeout time.Duration + WriteTimeout time.Duration + + PoolSize int + PoolTimeout time.Duration + IdleTimeout time.Duration + IdleCheckFrequency time.Duration +} + +func (opt *RingOptions) init() { + if opt.HeartbeatFrequency == 0 { + opt.HeartbeatFrequency = 500 * time.Millisecond + } + + switch opt.MinRetryBackoff { + case -1: + opt.MinRetryBackoff = 0 + case 0: + opt.MinRetryBackoff = 8 * time.Millisecond + } + switch opt.MaxRetryBackoff { + case -1: + opt.MaxRetryBackoff = 0 + case 0: + opt.MaxRetryBackoff = 512 * time.Millisecond + } +} + +func (opt *RingOptions) clientOptions() *Options { + return &Options{ + OnConnect: opt.OnConnect, + + DB: opt.DB, + Password: opt.Password, + + DialTimeout: opt.DialTimeout, + ReadTimeout: opt.ReadTimeout, + WriteTimeout: opt.WriteTimeout, + + PoolSize: opt.PoolSize, + PoolTimeout: opt.PoolTimeout, + IdleTimeout: opt.IdleTimeout, + IdleCheckFrequency: opt.IdleCheckFrequency, + } +} + +type ringShard struct { + Client *Client + down int32 +} + +func (shard *ringShard) String() string { + var state string + if shard.IsUp() { + state = "up" + } else { + state = "down" + } + return fmt.Sprintf("%s is %s", shard.Client, state) +} + +func (shard *ringShard) IsDown() bool { + const threshold = 3 + return atomic.LoadInt32(&shard.down) >= threshold +} + +func (shard *ringShard) IsUp() bool { + return !shard.IsDown() +} + +// Vote votes to set shard state and returns true if state was changed. +func (shard *ringShard) Vote(up bool) bool { + if up { + changed := shard.IsDown() + atomic.StoreInt32(&shard.down, 0) + return changed + } + + if shard.IsDown() { + return false + } + + atomic.AddInt32(&shard.down, 1) + return shard.IsDown() +} + +// Ring is a Redis client that uses constistent hashing to distribute +// keys across multiple Redis servers (shards). It's safe for +// concurrent use by multiple goroutines. +// +// Ring monitors the state of each shard and removes dead shards from +// the ring. When shard comes online it is added back to the ring. This +// gives you maximum availability and partition tolerance, but no +// consistency between different shards or even clients. Each client +// uses shards that are available to the client and does not do any +// coordination when shard state is changed. +// +// Ring should be used when you need multiple Redis servers for caching +// and can tolerate losing data when one of the servers dies. +// Otherwise you should use Redis Cluster. +type Ring struct { + cmdable + + opt *RingOptions + nreplicas int + + mu sync.RWMutex + hash *consistenthash.Map + shards map[string]*ringShard + shardsList []*ringShard + + cmdsInfoOnce internal.Once + cmdsInfo map[string]*CommandInfo + + closed bool +} + +func NewRing(opt *RingOptions) *Ring { + const nreplicas = 100 + opt.init() + ring := &Ring{ + opt: opt, + nreplicas: nreplicas, + + hash: consistenthash.New(nreplicas, nil), + shards: make(map[string]*ringShard), + } + ring.setProcessor(ring.Process) + for name, addr := range opt.Addrs { + clopt := opt.clientOptions() + clopt.Addr = addr + ring.addShard(name, NewClient(clopt)) + } + go ring.heartbeat() + return ring +} + +func (c *Ring) addShard(name string, cl *Client) { + shard := &ringShard{Client: cl} + c.mu.Lock() + c.hash.Add(name) + c.shards[name] = shard + c.shardsList = append(c.shardsList, shard) + c.mu.Unlock() +} + +// Options returns read-only Options that were used to create the client. +func (c *Ring) Options() *RingOptions { + return c.opt +} + +func (c *Ring) retryBackoff(attempt int) time.Duration { + return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff) +} + +// PoolStats returns accumulated connection pool stats. +func (c *Ring) PoolStats() *PoolStats { + c.mu.RLock() + shards := c.shardsList + c.mu.RUnlock() + + var acc PoolStats + for _, shard := range shards { + s := shard.Client.connPool.Stats() + acc.Hits += s.Hits + acc.Misses += s.Misses + acc.Timeouts += s.Timeouts + acc.TotalConns += s.TotalConns + acc.FreeConns += s.FreeConns + } + return &acc +} + +// Subscribe subscribes the client to the specified channels. +func (c *Ring) Subscribe(channels ...string) *PubSub { + if len(channels) == 0 { + panic("at least one channel is required") + } + + shard, err := c.shardByKey(channels[0]) + if err != nil { + // TODO: return PubSub with sticky error + panic(err) + } + return shard.Client.Subscribe(channels...) +} + +// PSubscribe subscribes the client to the given patterns. +func (c *Ring) PSubscribe(channels ...string) *PubSub { + if len(channels) == 0 { + panic("at least one channel is required") + } + + shard, err := c.shardByKey(channels[0]) + if err != nil { + // TODO: return PubSub with sticky error + panic(err) + } + return shard.Client.PSubscribe(channels...) +} + +// ForEachShard concurrently calls the fn on each live shard in the ring. +// It returns the first error if any. +func (c *Ring) ForEachShard(fn func(client *Client) error) error { + c.mu.RLock() + shards := c.shardsList + c.mu.RUnlock() + + var wg sync.WaitGroup + errCh := make(chan error, 1) + for _, shard := range shards { + if shard.IsDown() { + continue + } + + wg.Add(1) + go func(shard *ringShard) { + defer wg.Done() + err := fn(shard.Client) + if err != nil { + select { + case errCh <- err: + default: + } + } + }(shard) + } + wg.Wait() + + select { + case err := <-errCh: + return err + default: + return nil + } +} + +func (c *Ring) cmdInfo(name string) *CommandInfo { + err := c.cmdsInfoOnce.Do(func() error { + c.mu.RLock() + shards := c.shardsList + c.mu.RUnlock() + + var firstErr error + for _, shard := range shards { + cmdsInfo, err := shard.Client.Command().Result() + if err == nil { + c.cmdsInfo = cmdsInfo + return nil + } + if firstErr == nil { + firstErr = err + } + } + return firstErr + }) + if err != nil { + return nil + } + info := c.cmdsInfo[name] + if info == nil { + internal.Logf("info for cmd=%s not found", name) + } + return info +} + +func (c *Ring) shardByKey(key string) (*ringShard, error) { + key = hashtag.Key(key) + + c.mu.RLock() + + if c.closed { + c.mu.RUnlock() + return nil, pool.ErrClosed + } + + name := c.hash.Get(key) + if name == "" { + c.mu.RUnlock() + return nil, errRingShardsDown + } + + shard := c.shards[name] + c.mu.RUnlock() + return shard, nil +} + +func (c *Ring) randomShard() (*ringShard, error) { + return c.shardByKey(strconv.Itoa(rand.Int())) +} + +func (c *Ring) shardByName(name string) (*ringShard, error) { + if name == "" { + return c.randomShard() + } + + c.mu.RLock() + shard := c.shards[name] + c.mu.RUnlock() + return shard, nil +} + +func (c *Ring) cmdShard(cmd Cmder) (*ringShard, error) { + cmdInfo := c.cmdInfo(cmd.Name()) + firstKey := cmd.stringArg(cmdFirstKeyPos(cmd, cmdInfo)) + return c.shardByKey(firstKey) +} + +func (c *Ring) Process(cmd Cmder) error { + shard, err := c.cmdShard(cmd) + if err != nil { + cmd.setErr(err) + return err + } + return shard.Client.Process(cmd) +} + +// rebalance removes dead shards from the Ring. +func (c *Ring) rebalance() { + hash := consistenthash.New(c.nreplicas, nil) + for name, shard := range c.shards { + if shard.IsUp() { + hash.Add(name) + } + } + + c.mu.Lock() + c.hash = hash + c.mu.Unlock() +} + +// heartbeat monitors state of each shard in the ring. +func (c *Ring) heartbeat() { + ticker := time.NewTicker(c.opt.HeartbeatFrequency) + defer ticker.Stop() + for range ticker.C { + var rebalance bool + + c.mu.RLock() + + if c.closed { + c.mu.RUnlock() + break + } + + shards := c.shardsList + c.mu.RUnlock() + + for _, shard := range shards { + err := shard.Client.Ping().Err() + if shard.Vote(err == nil || err == pool.ErrPoolTimeout) { + internal.Logf("ring shard state changed: %s", shard) + rebalance = true + } + } + + if rebalance { + c.rebalance() + } + } +} + +// Close closes the ring client, releasing any open resources. +// +// It is rare to Close a Ring, as the Ring is meant to be long-lived +// and shared between many goroutines. +func (c *Ring) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return nil + } + c.closed = true + + var firstErr error + for _, shard := range c.shards { + if err := shard.Client.Close(); err != nil && firstErr == nil { + firstErr = err + } + } + c.hash = nil + c.shards = nil + c.shardsList = nil + + return firstErr +} + +func (c *Ring) Pipeline() Pipeliner { + pipe := Pipeline{ + exec: c.pipelineExec, + } + pipe.setProcessor(pipe.Process) + return &pipe +} + +func (c *Ring) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { + return c.Pipeline().Pipelined(fn) +} + +func (c *Ring) pipelineExec(cmds []Cmder) error { + cmdsMap := make(map[string][]Cmder) + for _, cmd := range cmds { + cmdInfo := c.cmdInfo(cmd.Name()) + name := cmd.stringArg(cmdFirstKeyPos(cmd, cmdInfo)) + if name != "" { + name = c.hash.Get(hashtag.Key(name)) + } + cmdsMap[name] = append(cmdsMap[name], cmd) + } + + for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ { + if attempt > 0 { + time.Sleep(c.retryBackoff(attempt)) + } + + var failedCmdsMap map[string][]Cmder + + for name, cmds := range cmdsMap { + shard, err := c.shardByName(name) + if err != nil { + setCmdsErr(cmds, err) + continue + } + + cn, _, err := shard.Client.getConn() + if err != nil { + setCmdsErr(cmds, err) + continue + } + + canRetry, err := shard.Client.pipelineProcessCmds(cn, cmds) + if err == nil || internal.IsRedisError(err) { + _ = shard.Client.connPool.Put(cn) + continue + } + _ = shard.Client.connPool.Remove(cn) + + if canRetry && internal.IsRetryableError(err, true) { + if failedCmdsMap == nil { + failedCmdsMap = make(map[string][]Cmder) + } + failedCmdsMap[name] = cmds + } + } + + if len(failedCmdsMap) == 0 { + break + } + cmdsMap = failedCmdsMap + } + + return firstCmdsErr(cmds) +} + +func (c *Ring) TxPipeline() Pipeliner { + panic("not implemented") +} + +func (c *Ring) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { + panic("not implemented") +} diff --git a/vendor/github.com/go-redis/redis/ring_test.go b/vendor/github.com/go-redis/redis/ring_test.go new file mode 100644 index 0000000..0cad429 --- /dev/null +++ b/vendor/github.com/go-redis/redis/ring_test.go @@ -0,0 +1,193 @@ +package redis_test + +import ( + "crypto/rand" + "fmt" + "time" + + "github.com/go-redis/redis" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Redis Ring", func() { + const heartbeat = 100 * time.Millisecond + + var ring *redis.Ring + + setRingKeys := func() { + for i := 0; i < 100; i++ { + err := ring.Set(fmt.Sprintf("key%d", i), "value", 0).Err() + Expect(err).NotTo(HaveOccurred()) + } + } + + BeforeEach(func() { + opt := redisRingOptions() + opt.HeartbeatFrequency = heartbeat + ring = redis.NewRing(opt) + + err := ring.ForEachShard(func(cl *redis.Client) error { + return cl.FlushDB().Err() + }) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(ring.Close()).NotTo(HaveOccurred()) + }) + + It("distributes keys", func() { + setRingKeys() + + // Both shards should have some keys now. + Expect(ringShard1.Info().Val()).To(ContainSubstring("keys=57")) + Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=43")) + }) + + It("distributes keys when using EVAL", func() { + script := redis.NewScript(` + local r = redis.call('SET', KEYS[1], ARGV[1]) + return r + `) + + var key string + for i := 0; i < 100; i++ { + key = fmt.Sprintf("key%d", i) + err := script.Run(ring, []string{key}, "value").Err() + Expect(err).NotTo(HaveOccurred()) + } + + Expect(ringShard1.Info().Val()).To(ContainSubstring("keys=57")) + Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=43")) + }) + + It("uses single shard when one of the shards is down", func() { + // Stop ringShard2. + Expect(ringShard2.Close()).NotTo(HaveOccurred()) + + // Ring needs 3 * heartbeat time to detect that node is down. + // Give it more to be sure. + time.Sleep(2 * 3 * heartbeat) + + setRingKeys() + + // RingShard1 should have all keys. + Expect(ringShard1.Info().Val()).To(ContainSubstring("keys=100")) + + // Start ringShard2. + var err error + ringShard2, err = startRedis(ringShard2Port) + Expect(err).NotTo(HaveOccurred()) + + // Wait for ringShard2 to come up. + Eventually(func() error { + return ringShard2.Ping().Err() + }, "1s").ShouldNot(HaveOccurred()) + + // Ring needs heartbeat time to detect that node is up. + // Give it more to be sure. + time.Sleep(heartbeat + heartbeat) + + setRingKeys() + + // RingShard2 should have its keys. + Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=43")) + }) + + It("supports hash tags", func() { + for i := 0; i < 100; i++ { + err := ring.Set(fmt.Sprintf("key%d{tag}", i), "value", 0).Err() + Expect(err).NotTo(HaveOccurred()) + } + + Expect(ringShard1.Info().Val()).ToNot(ContainSubstring("keys=")) + Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=100")) + }) + + Describe("pipeline", func() { + It("distributes keys", func() { + pipe := ring.Pipeline() + for i := 0; i < 100; i++ { + err := pipe.Set(fmt.Sprintf("key%d", i), "value", 0).Err() + Expect(err).NotTo(HaveOccurred()) + } + cmds, err := pipe.Exec() + Expect(err).NotTo(HaveOccurred()) + Expect(cmds).To(HaveLen(100)) + Expect(pipe.Close()).NotTo(HaveOccurred()) + + for _, cmd := range cmds { + Expect(cmd.Err()).NotTo(HaveOccurred()) + Expect(cmd.(*redis.StatusCmd).Val()).To(Equal("OK")) + } + + // Both shards should have some keys now. + Expect(ringShard1.Info().Val()).To(ContainSubstring("keys=57")) + Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=43")) + }) + + It("is consistent with ring", func() { + var keys []string + for i := 0; i < 100; i++ { + key := make([]byte, 64) + _, err := rand.Read(key) + Expect(err).NotTo(HaveOccurred()) + keys = append(keys, string(key)) + } + + _, err := ring.Pipelined(func(pipe redis.Pipeliner) error { + for _, key := range keys { + pipe.Set(key, "value", 0).Err() + } + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + for _, key := range keys { + val, err := ring.Get(key).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("value")) + } + }) + + It("supports hash tags", func() { + _, err := ring.Pipelined(func(pipe redis.Pipeliner) error { + for i := 0; i < 100; i++ { + pipe.Set(fmt.Sprintf("key%d{tag}", i), "value", 0).Err() + } + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(ringShard1.Info().Val()).ToNot(ContainSubstring("keys=")) + Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=100")) + }) + }) +}) + +var _ = Describe("empty Redis Ring", func() { + var ring *redis.Ring + + BeforeEach(func() { + ring = redis.NewRing(&redis.RingOptions{}) + }) + + AfterEach(func() { + Expect(ring.Close()).NotTo(HaveOccurred()) + }) + + It("returns an error", func() { + err := ring.Ping().Err() + Expect(err).To(MatchError("redis: all ring shards are down")) + }) + + It("pipeline returns an error", func() { + _, err := ring.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Ping() + return nil + }) + Expect(err).To(MatchError("redis: all ring shards are down")) + }) +}) diff --git a/vendor/github.com/go-redis/redis/script.go b/vendor/github.com/go-redis/redis/script.go new file mode 100644 index 0000000..74135f5 --- /dev/null +++ b/vendor/github.com/go-redis/redis/script.go @@ -0,0 +1,62 @@ +package redis + +import ( + "crypto/sha1" + "encoding/hex" + "io" + "strings" +) + +type scripter interface { + Eval(script string, keys []string, args ...interface{}) *Cmd + EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd + ScriptExists(scripts ...string) *BoolSliceCmd + ScriptLoad(script string) *StringCmd +} + +var _ scripter = (*Client)(nil) +var _ scripter = (*Ring)(nil) +var _ scripter = (*ClusterClient)(nil) + +type Script struct { + src, hash string +} + +func NewScript(src string) *Script { + h := sha1.New() + io.WriteString(h, src) + return &Script{ + src: src, + hash: hex.EncodeToString(h.Sum(nil)), + } +} + +func (s *Script) Hash() string { + return s.hash +} + +func (s *Script) Load(c scripter) *StringCmd { + return c.ScriptLoad(s.src) +} + +func (s *Script) Exists(c scripter) *BoolSliceCmd { + return c.ScriptExists(s.src) +} + +func (s *Script) Eval(c scripter, keys []string, args ...interface{}) *Cmd { + return c.Eval(s.src, keys, args...) +} + +func (s *Script) EvalSha(c scripter, keys []string, args ...interface{}) *Cmd { + return c.EvalSha(s.hash, keys, args...) +} + +// Run optimistically uses EVALSHA to run the script. If script does not exist +// it is retried using EVAL. +func (s *Script) Run(c scripter, keys []string, args ...interface{}) *Cmd { + r := s.EvalSha(c, keys, args...) + if err := r.Err(); err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") { + return s.Eval(c, keys, args...) + } + return r +} diff --git a/vendor/github.com/go-redis/redis/sentinel.go b/vendor/github.com/go-redis/redis/sentinel.go new file mode 100644 index 0000000..37d06b4 --- /dev/null +++ b/vendor/github.com/go-redis/redis/sentinel.go @@ -0,0 +1,337 @@ +package redis + +import ( + "errors" + "net" + "strings" + "sync" + "time" + + "github.com/go-redis/redis/internal" + "github.com/go-redis/redis/internal/pool" +) + +//------------------------------------------------------------------------------ + +// FailoverOptions are used to configure a failover client and should +// be passed to NewFailoverClient. +type FailoverOptions struct { + // The master name. + MasterName string + // A seed list of host:port addresses of sentinel nodes. + SentinelAddrs []string + + // Following options are copied from Options struct. + + OnConnect func(*Conn) error + + Password string + DB int + + MaxRetries int + + DialTimeout time.Duration + ReadTimeout time.Duration + WriteTimeout time.Duration + + PoolSize int + PoolTimeout time.Duration + IdleTimeout time.Duration + IdleCheckFrequency time.Duration +} + +func (opt *FailoverOptions) options() *Options { + return &Options{ + Addr: "FailoverClient", + + OnConnect: opt.OnConnect, + + DB: opt.DB, + Password: opt.Password, + + MaxRetries: opt.MaxRetries, + + DialTimeout: opt.DialTimeout, + ReadTimeout: opt.ReadTimeout, + WriteTimeout: opt.WriteTimeout, + + PoolSize: opt.PoolSize, + PoolTimeout: opt.PoolTimeout, + IdleTimeout: opt.IdleTimeout, + IdleCheckFrequency: opt.IdleCheckFrequency, + } +} + +// NewFailoverClient returns a Redis client that uses Redis Sentinel +// for automatic failover. It's safe for concurrent use by multiple +// goroutines. +func NewFailoverClient(failoverOpt *FailoverOptions) *Client { + opt := failoverOpt.options() + opt.init() + + failover := &sentinelFailover{ + masterName: failoverOpt.MasterName, + sentinelAddrs: failoverOpt.SentinelAddrs, + + opt: opt, + } + + client := Client{ + baseClient: baseClient{ + opt: opt, + connPool: failover.Pool(), + + onClose: func() error { + return failover.Close() + }, + }, + } + client.setProcessor(client.Process) + + return &client +} + +//------------------------------------------------------------------------------ + +type sentinelClient struct { + cmdable + baseClient +} + +func newSentinel(opt *Options) *sentinelClient { + opt.init() + client := sentinelClient{ + baseClient: baseClient{ + opt: opt, + connPool: newConnPool(opt), + }, + } + client.cmdable = cmdable{client.Process} + return &client +} + +func (c *sentinelClient) PubSub() *PubSub { + return &PubSub{ + opt: c.opt, + + newConn: func(channels []string) (*pool.Conn, error) { + return c.newConn() + }, + closeConn: c.connPool.CloseConn, + } +} + +func (c *sentinelClient) GetMasterAddrByName(name string) *StringSliceCmd { + cmd := NewStringSliceCmd("SENTINEL", "get-master-addr-by-name", name) + c.Process(cmd) + return cmd +} + +func (c *sentinelClient) Sentinels(name string) *SliceCmd { + cmd := NewSliceCmd("SENTINEL", "sentinels", name) + c.Process(cmd) + return cmd +} + +type sentinelFailover struct { + sentinelAddrs []string + + opt *Options + + pool *pool.ConnPool + poolOnce sync.Once + + mu sync.RWMutex + masterName string + _masterAddr string + sentinel *sentinelClient +} + +func (d *sentinelFailover) Close() error { + return d.resetSentinel() +} + +func (d *sentinelFailover) Pool() *pool.ConnPool { + d.poolOnce.Do(func() { + d.opt.Dialer = d.dial + d.pool = newConnPool(d.opt) + }) + return d.pool +} + +func (d *sentinelFailover) dial() (net.Conn, error) { + addr, err := d.MasterAddr() + if err != nil { + return nil, err + } + return net.DialTimeout("tcp", addr, d.opt.DialTimeout) +} + +func (d *sentinelFailover) MasterAddr() (string, error) { + d.mu.Lock() + defer d.mu.Unlock() + + addr, err := d.masterAddr() + if err != nil { + return "", err + } + + if d._masterAddr != addr { + d.switchMaster(addr) + } + + return addr, nil +} + +func (d *sentinelFailover) masterAddr() (string, error) { + // Try last working sentinel. + if d.sentinel != nil { + addr, err := d.sentinel.GetMasterAddrByName(d.masterName).Result() + if err == nil { + addr := net.JoinHostPort(addr[0], addr[1]) + internal.Logf("sentinel: master=%q addr=%q", d.masterName, addr) + return addr, nil + } + + internal.Logf("sentinel: GetMasterAddrByName name=%q failed: %s", d.masterName, err) + d._resetSentinel() + } + + for i, sentinelAddr := range d.sentinelAddrs { + sentinel := newSentinel(&Options{ + Addr: sentinelAddr, + + DialTimeout: d.opt.DialTimeout, + ReadTimeout: d.opt.ReadTimeout, + WriteTimeout: d.opt.WriteTimeout, + + PoolSize: d.opt.PoolSize, + PoolTimeout: d.opt.PoolTimeout, + IdleTimeout: d.opt.IdleTimeout, + }) + + masterAddr, err := sentinel.GetMasterAddrByName(d.masterName).Result() + if err != nil { + internal.Logf("sentinel: GetMasterAddrByName master=%q failed: %s", d.masterName, err) + sentinel.Close() + continue + } + + // Push working sentinel to the top. + d.sentinelAddrs[0], d.sentinelAddrs[i] = d.sentinelAddrs[i], d.sentinelAddrs[0] + d.setSentinel(sentinel) + + addr := net.JoinHostPort(masterAddr[0], masterAddr[1]) + return addr, nil + } + + return "", errors.New("redis: all sentinels are unreachable") +} + +func (d *sentinelFailover) switchMaster(masterAddr string) { + internal.Logf( + "sentinel: new master=%q addr=%q", + d.masterName, masterAddr, + ) + _ = d.Pool().Filter(func(cn *pool.Conn) bool { + return cn.RemoteAddr().String() != masterAddr + }) + d._masterAddr = masterAddr +} + +func (d *sentinelFailover) setSentinel(sentinel *sentinelClient) { + d.discoverSentinels(sentinel) + d.sentinel = sentinel + go d.listen(sentinel) +} + +func (d *sentinelFailover) resetSentinel() error { + var err error + d.mu.Lock() + if d.sentinel != nil { + err = d._resetSentinel() + } + d.mu.Unlock() + return err +} + +func (d *sentinelFailover) _resetSentinel() error { + err := d.sentinel.Close() + d.sentinel = nil + return err +} + +func (d *sentinelFailover) discoverSentinels(sentinel *sentinelClient) { + sentinels, err := sentinel.Sentinels(d.masterName).Result() + if err != nil { + internal.Logf("sentinel: Sentinels master=%q failed: %s", d.masterName, err) + return + } + for _, sentinel := range sentinels { + vals := sentinel.([]interface{}) + for i := 0; i < len(vals); i += 2 { + key := vals[i].(string) + if key == "name" { + sentinelAddr := vals[i+1].(string) + if !contains(d.sentinelAddrs, sentinelAddr) { + internal.Logf( + "sentinel: discovered new sentinel=%q for master=%q", + sentinelAddr, d.masterName, + ) + d.sentinelAddrs = append(d.sentinelAddrs, sentinelAddr) + } + } + } + } +} + +func (d *sentinelFailover) listen(sentinel *sentinelClient) { + var pubsub *PubSub + for { + if pubsub == nil { + pubsub = sentinel.PubSub() + + if err := pubsub.Subscribe("+switch-master"); err != nil { + internal.Logf("sentinel: Subscribe failed: %s", err) + pubsub.Close() + d.resetSentinel() + return + } + } + + msg, err := pubsub.ReceiveMessage() + if err != nil { + if err != pool.ErrClosed { + internal.Logf("sentinel: ReceiveMessage failed: %s", err) + pubsub.Close() + } + d.resetSentinel() + return + } + + switch msg.Channel { + case "+switch-master": + parts := strings.Split(msg.Payload, " ") + if parts[0] != d.masterName { + internal.Logf("sentinel: ignore addr for master=%q", parts[0]) + continue + } + addr := net.JoinHostPort(parts[3], parts[4]) + + d.mu.Lock() + if d._masterAddr != addr { + d.switchMaster(addr) + } + d.mu.Unlock() + } + } +} + +func contains(slice []string, str string) bool { + for _, s := range slice { + if s == str { + return true + } + } + return false +} diff --git a/vendor/github.com/go-redis/redis/sentinel_test.go b/vendor/github.com/go-redis/redis/sentinel_test.go new file mode 100644 index 0000000..c67713c --- /dev/null +++ b/vendor/github.com/go-redis/redis/sentinel_test.go @@ -0,0 +1,88 @@ +package redis_test + +import ( + "github.com/go-redis/redis" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Sentinel", func() { + var client *redis.Client + + BeforeEach(func() { + client = redis.NewFailoverClient(&redis.FailoverOptions{ + MasterName: sentinelName, + SentinelAddrs: []string{":" + sentinelPort}, + }) + Expect(client.FlushDB().Err()).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + It("should facilitate failover", func() { + // Set value on master. + err := client.Set("foo", "master", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + // Verify. + val, err := sentinelMaster.Get("foo").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("master")) + + // Create subscription. + ch := client.Subscribe("foo").Channel() + + // Wait until replicated. + Eventually(func() string { + return sentinelSlave1.Get("foo").Val() + }, "1s", "100ms").Should(Equal("master")) + Eventually(func() string { + return sentinelSlave2.Get("foo").Val() + }, "1s", "100ms").Should(Equal("master")) + + // Wait until slaves are picked up by sentinel. + Eventually(func() string { + return sentinel.Info().Val() + }, "10s", "100ms").Should(ContainSubstring("slaves=2")) + + // Kill master. + sentinelMaster.Shutdown() + Eventually(func() error { + return sentinelMaster.Ping().Err() + }, "5s", "100ms").Should(HaveOccurred()) + + // Wait for Redis sentinel to elect new master. + Eventually(func() string { + return sentinelSlave1.Info().Val() + sentinelSlave2.Info().Val() + }, "30s", "1s").Should(ContainSubstring("role:master")) + + // Check that client picked up new master. + Eventually(func() error { + return client.Get("foo").Err() + }, "5s", "100ms").ShouldNot(HaveOccurred()) + + // Publish message to check if subscription is renewed. + err = client.Publish("foo", "hello").Err() + Expect(err).NotTo(HaveOccurred()) + + var msg *redis.Message + Eventually(ch).Should(Receive(&msg)) + Expect(msg.Channel).To(Equal("foo")) + Expect(msg.Payload).To(Equal("hello")) + }) + + It("supports DB selection", func() { + Expect(client.Close()).NotTo(HaveOccurred()) + + client = redis.NewFailoverClient(&redis.FailoverOptions{ + MasterName: sentinelName, + SentinelAddrs: []string{":" + sentinelPort}, + DB: 1, + }) + err := client.Ping().Err() + Expect(err).NotTo(HaveOccurred()) + }) +}) diff --git a/vendor/github.com/go-redis/redis/testdata/redis.conf b/vendor/github.com/go-redis/redis/testdata/redis.conf new file mode 100644 index 0000000..235b295 --- /dev/null +++ b/vendor/github.com/go-redis/redis/testdata/redis.conf @@ -0,0 +1,10 @@ +# Minimal redis.conf + +port 6379 +daemonize no +dir . +save "" +appendonly yes +cluster-config-file nodes.conf +cluster-node-timeout 30000 +maxclients 1001 \ No newline at end of file diff --git a/vendor/github.com/go-redis/redis/tx.go b/vendor/github.com/go-redis/redis/tx.go new file mode 100644 index 0000000..11d5d5c --- /dev/null +++ b/vendor/github.com/go-redis/redis/tx.go @@ -0,0 +1,103 @@ +package redis + +import ( + "github.com/go-redis/redis/internal" + "github.com/go-redis/redis/internal/pool" +) + +// Redis transaction failed. +const TxFailedErr = internal.RedisError("redis: transaction failed") + +// Tx implements Redis transactions as described in +// http://redis.io/topics/transactions. It's NOT safe for concurrent use +// by multiple goroutines, because Exec resets list of watched keys. +// If you don't need WATCH it is better to use Pipeline. +type Tx struct { + statefulCmdable + baseClient +} + +func (c *Client) newTx() *Tx { + tx := Tx{ + baseClient: baseClient{ + opt: c.opt, + connPool: pool.NewStickyConnPool(c.connPool.(*pool.ConnPool), true), + }, + } + tx.setProcessor(tx.Process) + return &tx +} + +func (c *Client) Watch(fn func(*Tx) error, keys ...string) error { + tx := c.newTx() + if len(keys) > 0 { + if err := tx.Watch(keys...).Err(); err != nil { + _ = tx.Close() + return err + } + } + + err := fn(tx) + _ = tx.Close() + return err +} + +// close closes the transaction, releasing any open resources. +func (c *Tx) Close() error { + _ = c.Unwatch().Err() + return c.baseClient.Close() +} + +// Watch marks the keys to be watched for conditional execution +// of a transaction. +func (c *Tx) Watch(keys ...string) *StatusCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "watch" + for i, key := range keys { + args[1+i] = key + } + cmd := NewStatusCmd(args...) + c.Process(cmd) + return cmd +} + +// Unwatch flushes all the previously watched keys for a transaction. +func (c *Tx) Unwatch(keys ...string) *StatusCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "unwatch" + for i, key := range keys { + args[1+i] = key + } + cmd := NewStatusCmd(args...) + c.Process(cmd) + return cmd +} + +func (c *Tx) Pipeline() Pipeliner { + pipe := Pipeline{ + exec: c.pipelineExecer(c.txPipelineProcessCmds), + } + pipe.setProcessor(pipe.Process) + return &pipe +} + +// Pipelined executes commands queued in the fn in a transaction +// and restores the connection state to normal. +// +// When using WATCH, EXEC will execute commands only if the watched keys +// were not modified, allowing for a check-and-set mechanism. +// +// Exec always returns list of commands. If transaction fails +// TxFailedErr is returned. Otherwise Exec returns error of the first +// failed command or nil. +func (c *Tx) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { + return c.Pipeline().Pipelined(fn) +} + +func (c *Tx) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { + return c.Pipelined(fn) +} + +func (c *Tx) TxPipeline() Pipeliner { + return c.Pipeline() +} diff --git a/vendor/github.com/go-redis/redis/tx_test.go b/vendor/github.com/go-redis/redis/tx_test.go new file mode 100644 index 0000000..de597ff --- /dev/null +++ b/vendor/github.com/go-redis/redis/tx_test.go @@ -0,0 +1,151 @@ +package redis_test + +import ( + "strconv" + "sync" + + "github.com/go-redis/redis" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Tx", func() { + var client *redis.Client + + BeforeEach(func() { + client = redis.NewClient(redisOptions()) + Expect(client.FlushDB().Err()).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + It("should Watch", func() { + var incr func(string) error + + // Transactionally increments key using GET and SET commands. + incr = func(key string) error { + err := client.Watch(func(tx *redis.Tx) error { + n, err := tx.Get(key).Int64() + if err != nil && err != redis.Nil { + return err + } + + _, err = tx.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Set(key, strconv.FormatInt(n+1, 10), 0) + return nil + }) + return err + }, key) + if err == redis.TxFailedErr { + return incr(key) + } + return err + } + + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer GinkgoRecover() + defer wg.Done() + + err := incr("key") + Expect(err).NotTo(HaveOccurred()) + }() + } + wg.Wait() + + n, err := client.Get("key").Int64() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(100))) + }) + + It("should discard", func() { + err := client.Watch(func(tx *redis.Tx) error { + cmds, err := tx.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Set("key1", "hello1", 0) + pipe.Discard() + pipe.Set("key2", "hello2", 0) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + Expect(cmds).To(HaveLen(1)) + return err + }, "key1", "key2") + Expect(err).NotTo(HaveOccurred()) + + get := client.Get("key1") + Expect(get.Err()).To(Equal(redis.Nil)) + Expect(get.Val()).To(Equal("")) + + get = client.Get("key2") + Expect(get.Err()).NotTo(HaveOccurred()) + Expect(get.Val()).To(Equal("hello2")) + }) + + It("returns no error when there are no commands", func() { + err := client.Watch(func(tx *redis.Tx) error { + _, err := tx.Pipelined(func(redis.Pipeliner) error { return nil }) + return err + }) + Expect(err).NotTo(HaveOccurred()) + + v, err := client.Ping().Result() + Expect(err).NotTo(HaveOccurred()) + Expect(v).To(Equal("PONG")) + }) + + It("should exec bulks", func() { + const N = 20000 + + err := client.Watch(func(tx *redis.Tx) error { + cmds, err := tx.Pipelined(func(pipe redis.Pipeliner) error { + for i := 0; i < N; i++ { + pipe.Incr("key") + } + return nil + }) + Expect(err).NotTo(HaveOccurred()) + Expect(len(cmds)).To(Equal(N)) + for _, cmd := range cmds { + Expect(cmd.Err()).NotTo(HaveOccurred()) + } + return err + }) + Expect(err).NotTo(HaveOccurred()) + + num, err := client.Get("key").Int64() + Expect(err).NotTo(HaveOccurred()) + Expect(num).To(Equal(int64(N))) + }) + + It("should recover from bad connection", func() { + // Put bad connection in the pool. + cn, _, err := client.Pool().Get() + Expect(err).NotTo(HaveOccurred()) + + cn.SetNetConn(&badConn{}) + err = client.Pool().Put(cn) + Expect(err).NotTo(HaveOccurred()) + + do := func() error { + err := client.Watch(func(tx *redis.Tx) error { + _, err := tx.Pipelined(func(pipe redis.Pipeliner) error { + pipe.Ping() + return nil + }) + return err + }) + return err + } + + err = do() + Expect(err).To(MatchError("bad connection")) + + err = do() + Expect(err).NotTo(HaveOccurred()) + }) +}) diff --git a/vendor/github.com/go-redis/redis/universal.go b/vendor/github.com/go-redis/redis/universal.go new file mode 100644 index 0000000..29eb12b --- /dev/null +++ b/vendor/github.com/go-redis/redis/universal.go @@ -0,0 +1,136 @@ +package redis + +import "time" + +// UniversalOptions information is required by UniversalClient to establish +// connections. +type UniversalOptions struct { + // Either a single address or a seed list of host:port addresses + // of cluster/sentinel nodes. + Addrs []string + + // The sentinel master name. + // Only failover clients. + MasterName string + + // Database to be selected after connecting to the server. + // Only single-node and failover clients. + DB int + + // Only cluster clients. + + // Enables read only queries on slave nodes. + ReadOnly bool + + MaxRedirects int + RouteByLatency bool + + // Common options + + MaxRetries int + Password string + DialTimeout time.Duration + ReadTimeout time.Duration + WriteTimeout time.Duration + PoolSize int + PoolTimeout time.Duration + IdleTimeout time.Duration + IdleCheckFrequency time.Duration +} + +func (o *UniversalOptions) cluster() *ClusterOptions { + if len(o.Addrs) == 0 { + o.Addrs = []string{"127.0.0.1:6379"} + } + + return &ClusterOptions{ + Addrs: o.Addrs, + MaxRedirects: o.MaxRedirects, + RouteByLatency: o.RouteByLatency, + ReadOnly: o.ReadOnly, + + MaxRetries: o.MaxRetries, + Password: o.Password, + DialTimeout: o.DialTimeout, + ReadTimeout: o.ReadTimeout, + WriteTimeout: o.WriteTimeout, + PoolSize: o.PoolSize, + PoolTimeout: o.PoolTimeout, + IdleTimeout: o.IdleTimeout, + IdleCheckFrequency: o.IdleCheckFrequency, + } +} + +func (o *UniversalOptions) failover() *FailoverOptions { + if len(o.Addrs) == 0 { + o.Addrs = []string{"127.0.0.1:26379"} + } + + return &FailoverOptions{ + SentinelAddrs: o.Addrs, + MasterName: o.MasterName, + DB: o.DB, + + MaxRetries: o.MaxRetries, + Password: o.Password, + DialTimeout: o.DialTimeout, + ReadTimeout: o.ReadTimeout, + WriteTimeout: o.WriteTimeout, + PoolSize: o.PoolSize, + PoolTimeout: o.PoolTimeout, + IdleTimeout: o.IdleTimeout, + IdleCheckFrequency: o.IdleCheckFrequency, + } +} + +func (o *UniversalOptions) simple() *Options { + addr := "127.0.0.1:6379" + if len(o.Addrs) > 0 { + addr = o.Addrs[0] + } + + return &Options{ + Addr: addr, + DB: o.DB, + + MaxRetries: o.MaxRetries, + Password: o.Password, + DialTimeout: o.DialTimeout, + ReadTimeout: o.ReadTimeout, + WriteTimeout: o.WriteTimeout, + PoolSize: o.PoolSize, + PoolTimeout: o.PoolTimeout, + IdleTimeout: o.IdleTimeout, + IdleCheckFrequency: o.IdleCheckFrequency, + } +} + +// -------------------------------------------------------------------- + +// UniversalClient is an abstract client which - based on the provided options - +// can connect to either clusters, or sentinel-backed failover instances or simple +// single-instance servers. This can be useful for testing cluster-specific +// applications locally. +type UniversalClient interface { + Cmdable + Process(cmd Cmder) error + Close() error +} + +var _ UniversalClient = (*Client)(nil) +var _ UniversalClient = (*ClusterClient)(nil) + +// NewUniversalClient returns a new multi client. The type of client returned depends +// on the following three conditions: +// +// 1. if a MasterName is passed a sentinel-backed FailoverClient will be returned +// 2. if the number of Addrs is two or more, a ClusterClient will be returned +// 3. otherwise, a single-node redis Client will be returned. +func NewUniversalClient(opts *UniversalOptions) UniversalClient { + if opts.MasterName != "" { + return NewFailoverClient(opts.failover()) + } else if len(opts.Addrs) > 1 { + return NewClusterClient(opts.cluster()) + } + return NewClient(opts.simple()) +} diff --git a/vendor/github.com/go-redis/redis/universal_test.go b/vendor/github.com/go-redis/redis/universal_test.go new file mode 100644 index 0000000..2a0850d --- /dev/null +++ b/vendor/github.com/go-redis/redis/universal_test.go @@ -0,0 +1,41 @@ +package redis_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/go-redis/redis" +) + +var _ = Describe("UniversalClient", func() { + var client redis.UniversalClient + + AfterEach(func() { + if client != nil { + Expect(client.Close()).To(Succeed()) + } + }) + + It("should connect to failover servers", func() { + client = redis.NewUniversalClient(&redis.UniversalOptions{ + MasterName: sentinelName, + Addrs: []string{":" + sentinelPort}, + }) + Expect(client.Ping().Err()).NotTo(HaveOccurred()) + }) + + It("should connect to simple servers", func() { + client = redis.NewUniversalClient(&redis.UniversalOptions{ + Addrs: []string{redisAddr}, + }) + Expect(client.Ping().Err()).NotTo(HaveOccurred()) + }) + + It("should connect to clusters", func() { + client = redis.NewUniversalClient(&redis.UniversalOptions{ + Addrs: cluster.addrs(), + }) + Expect(client.Ping().Err()).NotTo(HaveOccurred()) + }) + +}) diff --git a/vendor/github.com/google/go-github/.gitignore b/vendor/github.com/google/go-github/.gitignore new file mode 100644 index 0000000..3515c4b --- /dev/null +++ b/vendor/github.com/google/go-github/.gitignore @@ -0,0 +1,2 @@ +*.test +coverage.out diff --git a/vendor/github.com/google/go-github/.travis.yml b/vendor/github.com/google/go-github/.travis.yml new file mode 100644 index 0000000..3a1f51f --- /dev/null +++ b/vendor/github.com/google/go-github/.travis.yml @@ -0,0 +1,32 @@ +sudo: false +language: go +go: + - "1.11.x" + - "1.10.x" + - "1.9.x" + - master +env: + global: + - GO111MODULE=on +matrix: + allow_failures: + - go: master + fast_finish: true +env: + secure: "IrnPmy/rkIP6Nrbqji+u7MCAibQlA6WvPLEllmDQ2yZP/uIe3wLwwYbTu9BOkgzoLA+f8PA6u3pp/RhY/rtaM4NzHAO2nVfGIv9UHUQ3NGq0DYS6rODjVKhq7vkhELoagRewyqFVN4rE0LnExkknRMgjQfRke6/DA7u7Xm8JyhY=" # COVERALLS_TOKEN +install: + - go get golang.org/x/tools/cmd/cover + - go get github.com/mattn/goveralls + - # Do not install go-github yet, since we want it to happen inside the script step. +script: + - go get -t -v ./... + - diff -u <(echo -n) <(gofmt -d -s .) + - go generate -x ./... && git diff --exit-code; code=$?; git checkout -- .; (exit $code) # Check that go generate ./... produces a zero diff; clean up any changes afterwards. + - go tool vet . + - go test -v -race ./... + - go test -v -tags=integration -run=^$ ./test/integration # Check that integration test builds successfully, but don't run any of the tests (they hit live GitHub API). + + # Generate test coverage report. This must be after all other tests. + #- rm github/github-accessors.go # exclude generated code + #- go test -v -covermode=count -coverprofile=coverage.out ./github + #- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN diff --git a/vendor/github.com/google/go-github/AUTHORS b/vendor/github.com/google/go-github/AUTHORS new file mode 100644 index 0000000..ce1a0b0 --- /dev/null +++ b/vendor/github.com/google/go-github/AUTHORS @@ -0,0 +1,199 @@ +# This is the official list of go-github authors for copyright purposes. +# +# This does not necessarily list everyone who has contributed code, since in +# some cases, their employer may be the copyright holder. To see the full list +# of contributors, see the revision history in source control or +# https://github.com/google/go-github/graphs/contributors. +# +# Authors who wish to be recognized in this file should add themselves (or +# their employer, as appropriate). + +178inaba +Abhinav Gupta +Ahmed Hagy +Ainsley Chong +Akeda Bagus +Alec Thomas +Aleks Clark +Alex Bramley +Alexander Harkness +Allen Sun +Amey Sakhadeo +Andreas Garnæs +Andrew Ryabchun +Andy Hume +Andy Lindeman +Anshuman Bhartiya +Antoine Pelisse +Anubha Kushwaha +appilon +Aravind +Arıl Bozoluk +Austin Dizzy +Ben Batha +Beshr Kayali +Beyang Liu +Billy Lynch +Björn Häuser +Brad Harris +Brad Moylan +Bradley Falzon +Brandon Cook +Brian Egizi +Bryan Boreham +Cami Diez +Carlos Alexandro Becker +chandresh-pancholi +Charles Fenwick Elliott +Charlie Yan +Chris King +Chris Roche +Chris Schaefer +Christoph Sassenberg +Colin Misare +Craig Peterson +Cristian Maglie +Daehyeok Mun +Daniel Leavitt +Dave Du Cros +Dave Henderson +David Deng +David Jannotta +Davide Zipeto +Dennis Webb +Dhi Aurrahman +Diego Lapiduz +Dmitri Shuralyov +dmnlk +Don Petersen +Doug Turner +Drew Fradette +Eli Uriegas +Elliott Beach +Emerson Wood +erwinvaneyk +Fabrice +Filippo Valsorda +Florian Forster +Francesc Gil +Francis +Fredrik Jönsson +Garrett Squire +Georgy Buranov +Gnahz +Google Inc. +griffin_stewie +Guillaume Jacquet +Guz Alexander +Guðmundur Bjarni Ólafsson +Hanno Hecker +Hari haran +haya14busa +Huy Tr +huydx +i2bskn +Isao Jonas +isqua +Jameel Haffejee +Jan Kosecki +Jeremy Morris +Jesse Newland +Jihoon Chung +Jimmi Dyson +Joan Saum +Joe Tsai +John Barton +John Engelman +jpbelanger-mtl +Juan Basso +Julien Garcia Gonzalez +Julien Rostand +Justin Abrahms +Jusung Lee +jzhoucliqr +Katrina Owen +Keita Urashima +Kevin Burke +Konrad Malawski +Kookheon Kwon +Krzysztof Kowalczyk +Kshitij Saraogi +kyokomi +Lucas Alcantara +Luke Evers +Luke Kysow +Luke Roberts +Luke Young +Maksim Zhylinski +Martin-Louis Bright +Marwan Sulaiman +Mat Geist +Matt +Matt Brender +Matt Landis +Maxime Bury +Michael Spiegel +Michael Tiller +Michał Glapa +Nathan VanBenschoten +Neil O'Toole +Nick Miyake +Nick Spragg +Nikhita Raghunath +Noah Zoschke +ns-cweber +Oleg Kovalov +Ondřej Kupka +Panagiotis Moustafellos +Parham Alvani +Parker Moore +parkhyukjun89 +Pavel Shtanko +Pete Wagner +Petr Shevtsov +Pierre Carrier +Piotr Zurek +Quinn Slack +Rackspace US, Inc. +Radek Simko +Rajendra arora +RaviTeja Pothana +rc1140 +Red Hat, Inc. +Rob Figueiredo +Rohit Upadhyay +Ronak Jain +Ruben Vereecken +Ryan Lower +Sahil Dua +saisi +Sam Minnée +Sandeep Sukhani +Sander van Harmelen +Sanket Payghan +Sarasa Kisaragi +Sean Wang +Sebastian Mandrean +Sebastian Mæland Pedersen +Sevki +Shagun Khemka +Shawn Catanzarite +Shawn Smith +sona-tar +SoundCloud, Ltd. +Stian Eikeland +Thomas Bruyelle +Timothée Peignier +Trey Tacon +ttacon +Varadarajan Aravamudhan +Victor Castell +Victor Vrantchan +Vlad Ungureanu +Will Maier +William Bailey +Yann Malet +Yannick Utard +Yicheng Qin +Yumikiyo Osanai +Zach Latta diff --git a/vendor/github.com/google/go-github/CONTRIBUTING.md b/vendor/github.com/google/go-github/CONTRIBUTING.md new file mode 100644 index 0000000..8da635a --- /dev/null +++ b/vendor/github.com/google/go-github/CONTRIBUTING.md @@ -0,0 +1,114 @@ +# How to contribute # + +We'd love to accept your patches and contributions to this project. There are +a just a few small guidelines you need to follow. + + +## Contributor License Agreement ## + +Contributions to any Google project must be accompanied by a Contributor +License Agreement. This is not a copyright **assignment**, it simply gives +Google permission to use and redistribute your contributions as part of the +project. Head over to to see your current +agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + + +## Submitting a patch ## + + 1. It's generally best to start by opening a new issue describing the bug or + feature you're intending to fix. Even if you think it's relatively minor, + it's helpful to know what people are working on. Mention in the initial + issue that you are planning to work on that bug or feature so that it can + be assigned to you. + + 1. Follow the normal process of [forking][] the project, and setup a new + branch to work in. It's important that each group of changes be done in + separate branches in order to ensure that a pull request only includes the + commits related to that bug or feature. + + 1. Go makes it very simple to ensure properly formatted code, so always run + `go fmt` on your code before committing it. You should also run + [golint][] over your code. As noted in the [golint readme][], it's not + strictly necessary that your code be completely "lint-free", but this will + help you find common style issues. + + 1. Any significant changes should almost always be accompanied by tests. The + project already has good test coverage, so look at some of the existing + tests if you're unsure how to go about it. [gocov][] and [gocov-html][] + are invaluable tools for seeing which parts of your code aren't being + exercised by your tests. + + 1. Please run: + * `go generate github.com/google/go-github/...` + * `go test github.com/google/go-github/...` + * `go vet github.com/google/go-github/...` + + 1. Do your best to have [well-formed commit messages][] for each change. + This provides consistency throughout the project, and ensures that commit + messages are able to be formatted properly by various git tools. + + 1. Finally, push the commits to your fork and submit a [pull request][]. + +[forking]: https://help.github.com/articles/fork-a-repo +[golint]: https://github.com/golang/lint +[golint readme]: https://github.com/golang/lint/blob/master/README.md +[gocov]: https://github.com/axw/gocov +[gocov-html]: https://github.com/matm/gocov-html +[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html +[squash]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits +[pull request]: https://help.github.com/articles/creating-a-pull-request + + +## Other notes on code organization ## + +Currently, everything is defined in the main `github` package, with API methods +broken into separate service objects. These services map directly to how +the [GitHub API documentation][] is organized, so use that as your guide for +where to put new methods. + +Code is organized in files also based pretty closely on the GitHub API +documentation, following the format `{service}_{api}.go`. For example, methods +defined at live in +[repos_hooks.go][]. + +[GitHub API documentation]: https://developer.github.com/v3/ +[repos_hooks.go]: https://github.com/google/go-github/blob/master/github/repos_hooks.go + + +## Maintainer's Guide ## + +(These notes are mostly only for people merging in pull requests.) + +**Verify CLAs.** CLAs must be on file for the pull request submitter and commit +author(s). Google's CLA verification system should handle this automatically +and will set commit statuses as appropriate. If there's ever any question about +a pull request, ask [willnorris](https://github.com/willnorris). + +**Always try to maintain a clean, linear git history.** With very few +exceptions, running `git log` should not show a bunch of branching and merging. + +Never use the GitHub "merge" button, since it always creates a merge commit. +Instead, check out the pull request locally ([these git aliases +help][git-aliases]), then cherry-pick or rebase them onto master. If there are +small cleanup commits, especially as a result of addressing code review +comments, these should almost always be squashed down to a single commit. Don't +bother squashing commits that really deserve to be separate though. If needed, +feel free to amend additional small changes to the code or commit message that +aren't worth going through code review for. + +If you made any changes like squashing commits, rebasing onto master, etc, then +GitHub won't recognize that this is the same commit in order to mark the pull +request as "merged". So instead, amend the commit message to include a line +"Fixes #0", referencing the pull request number. This would be in addition to +any other "Fixes" lines for closing related issues. If you forget to do this, +you can also leave a comment on the pull request [like this][rebase-comment]. +If you made any other changes, it's worth noting that as well, [like +this][modified-comment]. + +[git-aliases]: https://github.com/willnorris/dotfiles/blob/d640d010c23b1116bdb3d4dc12088ed26120d87d/git/.gitconfig#L13-L15 +[rebase-comment]: https://github.com/google/go-github/pull/277#issuecomment-183035491 +[modified-comment]: https://github.com/google/go-github/pull/280#issuecomment-184859046 diff --git a/vendor/github.com/google/go-github/LICENSE b/vendor/github.com/google/go-github/LICENSE new file mode 100644 index 0000000..28b6486 --- /dev/null +++ b/vendor/github.com/google/go-github/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013 The go-github AUTHORS. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/google/go-github/README.md b/vendor/github.com/google/go-github/README.md new file mode 100644 index 0000000..3e2483d --- /dev/null +++ b/vendor/github.com/google/go-github/README.md @@ -0,0 +1,262 @@ +# go-github # + +[![GoDoc](https://godoc.org/github.com/google/go-github/github?status.svg)](https://godoc.org/github.com/google/go-github/github) [![Build Status](https://travis-ci.org/google/go-github.svg?branch=master)](https://travis-ci.org/google/go-github) [![Test Coverage](https://coveralls.io/repos/google/go-github/badge.svg?branch=master)](https://coveralls.io/r/google/go-github?branch=master) [![Discuss at go-github@googlegroups.com](https://img.shields.io/badge/discuss-go--github%40googlegroups.com-blue.svg)](https://groups.google.com/group/go-github) + +go-github is a Go client library for accessing the [GitHub API v3][]. + +go-github requires Go version 1.9 or greater. + +If you're interested in using the [GraphQL API v4][], the recommended library is +[shurcooL/githubv4][]. + +## Usage ## + +```go +import "github.com/google/go-github/v18/github" +``` + +Construct a new GitHub client, then use the various services on the client to +access different parts of the GitHub API. For example: + +```go +client := github.NewClient(nil) + +// list all organizations for user "willnorris" +orgs, _, err := client.Organizations.List(context.Background(), "willnorris", nil) +``` + +Some API methods have optional parameters that can be passed. For example: + +```go +client := github.NewClient(nil) + +// list public repositories for org "github" +opt := &github.RepositoryListByOrgOptions{Type: "public"} +repos, _, err := client.Repositories.ListByOrg(context.Background(), "github", opt) +``` + +The services of a client divide the API into logical chunks and correspond to +the structure of the GitHub API documentation at +https://developer.github.com/v3/. + +NOTE: Using the [context](https://godoc.org/context) package, one can easily +pass cancelation signals and deadlines to various services of the client for +handling a request. In case there is no context available, then `context.Background()` +can be used as a starting point. + +For more sample code snippets, head over to the +[example](https://github.com/google/go-github/tree/master/example) directory. + +### Authentication ### + +The go-github library does not directly handle authentication. Instead, when +creating a new client, pass an `http.Client` that can handle authentication for +you. The easiest and recommended way to do this is using the [oauth2][] +library, but you can always use any other library that provides an +`http.Client`. If you have an OAuth2 access token (for example, a [personal +API token][]), you can use it with the oauth2 library using: + +```go +import "golang.org/x/oauth2" + +func main() { + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: "... your access token ..."}, + ) + tc := oauth2.NewClient(ctx, ts) + + client := github.NewClient(tc) + + // list all repositories for the authenticated user + repos, _, err := client.Repositories.List(ctx, "", nil) +} +``` + +Note that when using an authenticated Client, all calls made by the client will +include the specified OAuth token. Therefore, authenticated clients should +almost never be shared between different users. + +See the [oauth2 docs][] for complete instructions on using that library. + +For API methods that require HTTP Basic Authentication, use the +[`BasicAuthTransport`](https://godoc.org/github.com/google/go-github/github#BasicAuthTransport). + +GitHub Apps authentication can be provided by the [ghinstallation](https://github.com/bradleyfalzon/ghinstallation) +package. + +```go +import "github.com/bradleyfalzon/ghinstallation" + +func main() { + // Wrap the shared transport for use with the integration ID 1 authenticating with installation ID 99. + itr, err := ghinstallation.NewKeyFromFile(http.DefaultTransport, 1, 99, "2016-10-19.private-key.pem") + if err != nil { + // Handle error. + } + + // Use installation transport with client. + client := github.NewClient(&http.Client{Transport: itr}) + + // Use client... +} +``` + +### Rate Limiting ### + +GitHub imposes a rate limit on all API clients. Unauthenticated clients are +limited to 60 requests per hour, while authenticated clients can make up to +5,000 requests per hour. The Search API has a custom rate limit. Unauthenticated +clients are limited to 10 requests per minute, while authenticated clients +can make up to 30 requests per minute. To receive the higher rate limit when +making calls that are not issued on behalf of a user, +use `UnauthenticatedRateLimitedTransport`. + +The returned `Response.Rate` value contains the rate limit information +from the most recent API call. If a recent enough response isn't +available, you can use `RateLimits` to fetch the most up-to-date rate +limit data for the client. + +To detect an API rate limit error, you can check if its type is `*github.RateLimitError`: + +```go +repos, _, err := client.Repositories.List(ctx, "", nil) +if _, ok := err.(*github.RateLimitError); ok { + log.Println("hit rate limit") +} +``` + +Learn more about GitHub rate limiting at +https://developer.github.com/v3/#rate-limiting. + +### Accepted Status ### + +Some endpoints may return a 202 Accepted status code, meaning that the +information required is not yet ready and was scheduled to be gathered on +the GitHub side. Methods known to behave like this are documented specifying +this behavior. + +To detect this condition of error, you can check if its type is +`*github.AcceptedError`: + +```go +stats, _, err := client.Repositories.ListContributorsStats(ctx, org, repo) +if _, ok := err.(*github.AcceptedError); ok { + log.Println("scheduled on GitHub side") +} +``` + +### Conditional Requests ### + +The GitHub API has good support for conditional requests which will help +prevent you from burning through your rate limit, as well as help speed up your +application. `go-github` does not handle conditional requests directly, but is +instead designed to work with a caching `http.Transport`. We recommend using +https://github.com/gregjones/httpcache for that. + +Learn more about GitHub conditional requests at +https://developer.github.com/v3/#conditional-requests. + +### Creating and Updating Resources ### + +All structs for GitHub resources use pointer values for all non-repeated fields. +This allows distinguishing between unset fields and those set to a zero-value. +Helper functions have been provided to easily create these pointers for string, +bool, and int values. For example: + +```go +// create a new private repository named "foo" +repo := &github.Repository{ + Name: github.String("foo"), + Private: github.Bool(true), +} +client.Repositories.Create(ctx, "", repo) +``` + +Users who have worked with protocol buffers should find this pattern familiar. + +### Pagination ### + +All requests for resource collections (repos, pull requests, issues, etc.) +support pagination. Pagination options are described in the +`github.ListOptions` struct and passed to the list methods directly or as an +embedded type of a more specific list options struct (for example +`github.PullRequestListOptions`). Pages information is available via the +`github.Response` struct. + +```go +client := github.NewClient(nil) + +opt := &github.RepositoryListByOrgOptions{ + ListOptions: github.ListOptions{PerPage: 10}, +} +// get all pages of results +var allRepos []*github.Repository +for { + repos, resp, err := client.Repositories.ListByOrg(ctx, "github", opt) + if err != nil { + return err + } + allRepos = append(allRepos, repos...) + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage +} +``` + +For complete usage of go-github, see the full [package docs][]. + +[GitHub API v3]: https://developer.github.com/v3/ +[oauth2]: https://github.com/golang/oauth2 +[oauth2 docs]: https://godoc.org/golang.org/x/oauth2 +[personal API token]: https://github.com/blog/1509-personal-api-tokens +[package docs]: https://godoc.org/github.com/google/go-github/github +[GraphQL API v4]: https://developer.github.com/v4/ +[shurcooL/githubv4]: https://github.com/shurcooL/githubv4 + +### Integration Tests ### + +You can run integration tests from the `test` directory. See the integration tests [README](test/README.md). + +## Roadmap ## + +This library is being initially developed for an internal application at +Google, so API methods will likely be implemented in the order that they are +needed by that application. You can track the status of implementation in +[this Google spreadsheet][roadmap]. + +[roadmap]: https://docs.google.com/spreadsheet/ccc?key=0ApoVX4GOiXr-dGNKN1pObFh6ek1DR2FKUjBNZ1FmaEE&usp=sharing + +## Contributing ## +I would like to cover the entire GitHub API and contributions are of course always welcome. The +calling pattern is pretty well established, so adding new methods is relatively +straightforward. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for details. + +## Versioning ## + +In general, go-github follows [semver](https://semver.org/) as closely as we +can for tagging releases of the package. For self-contained libraries, the +application of semantic versioning is relatively straightforward and generally +understood. But because go-github is a client library for the GitHub API, which +itself changes behavior, and because we are typically pretty aggressive about +implementing preview features of the GitHub API, we've adopted the following +versioning policy: + +* We increment the **major version** with any incompatible change to + non-preview functionality, including changes to the exported Go API surface + or behavior of the API. +* We increment the **minor version** with any backwards-compatible changes to + functionality, as well as any changes to preview functionality in the GitHub + API. GitHub makes no guarantee about the stability of preview functionality, + so neither do we consider it a stable part of the go-github API. +* We increment the **patch version** with any backwards-compatible bug fixes. + +Preview functionality may take the form of entire methods or simply additional +data returned from an otherwise non-preview method. Refer to the GitHub API +documentation for details on preview functionality. + +## License ## + +This library is distributed under the BSD-style license found in the [LICENSE](./LICENSE) +file. diff --git a/vendor/github.com/google/go-github/example/appengine/app.go b/vendor/github.com/google/go-github/example/appengine/app.go new file mode 100644 index 0000000..38f24e4 --- /dev/null +++ b/vendor/github.com/google/go-github/example/appengine/app.go @@ -0,0 +1,49 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package demo provides an app that shows how to use the github package on +// Google App Engine. +package demo + +import ( + "fmt" + "net/http" + "os" + + "github.com/google/go-github/v18/github" + "golang.org/x/oauth2" + "google.golang.org/appengine" + "google.golang.org/appengine/log" +) + +func init() { + http.HandleFunc("/", handler) +} + +func handler(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + + ctx := appengine.NewContext(r) + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: os.Getenv("GITHUB_AUTH_TOKEN")}, + ) + tc := oauth2.NewClient(ctx, ts) + client := github.NewClient(tc) + + commits, _, err := client.Repositories.ListCommits(ctx, "google", "go-github", nil) + if err != nil { + log.Errorf(ctx, "ListCommits: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + for _, commit := range commits { + fmt.Fprintln(w, commit.GetHTMLURL()) + } +} diff --git a/vendor/github.com/google/go-github/example/appengine/app.yaml b/vendor/github.com/google/go-github/example/appengine/app.yaml new file mode 100644 index 0000000..dca235f --- /dev/null +++ b/vendor/github.com/google/go-github/example/appengine/app.yaml @@ -0,0 +1,14 @@ +# Copyright 2017 The go-github AUTHORS. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +runtime: go +api_version: go1 + +handlers: +- url: /.* + script: _go_app + +env_variables: + GITHUB_AUTH_TOKEN: "-your-auth-token-here-" diff --git a/vendor/github.com/google/go-github/example/basicauth/main.go b/vendor/github.com/google/go-github/example/basicauth/main.go new file mode 100644 index 0000000..8dfd68a --- /dev/null +++ b/vendor/github.com/google/go-github/example/basicauth/main.go @@ -0,0 +1,55 @@ +// Copyright 2015 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The basicauth command demonstrates using the github.BasicAuthTransport, +// including handling two-factor authentication. This won't currently work for +// accounts that use SMS to receive one-time passwords. +package main + +import ( + "bufio" + "context" + "fmt" + "os" + "strings" + "syscall" + + "github.com/google/go-github/v18/github" + "golang.org/x/crypto/ssh/terminal" +) + +func main() { + r := bufio.NewReader(os.Stdin) + fmt.Print("GitHub Username: ") + username, _ := r.ReadString('\n') + + fmt.Print("GitHub Password: ") + bytePassword, _ := terminal.ReadPassword(int(syscall.Stdin)) + password := string(bytePassword) + + tp := github.BasicAuthTransport{ + Username: strings.TrimSpace(username), + Password: strings.TrimSpace(password), + } + + client := github.NewClient(tp.Client()) + ctx := context.Background() + user, _, err := client.Users.Get(ctx, "") + + // Is this a two-factor auth error? If so, prompt for OTP and try again. + if _, ok := err.(*github.TwoFactorAuthError); ok { + fmt.Print("\nGitHub OTP: ") + otp, _ := r.ReadString('\n') + tp.OTP = strings.TrimSpace(otp) + user, _, err = client.Users.Get(ctx, "") + } + + if err != nil { + fmt.Printf("\nerror: %v\n", err) + return + } + + fmt.Printf("\n%v\n", github.Stringify(user)) +} diff --git a/vendor/github.com/google/go-github/example/commitpr/main.go b/vendor/github.com/google/go-github/example/commitpr/main.go new file mode 100644 index 0000000..80ac88e --- /dev/null +++ b/vendor/github.com/google/go-github/example/commitpr/main.go @@ -0,0 +1,216 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The commitpr command utilizes go-github as a CLI tool for +// pushing files to a branch and creating a pull request from it. +// It takes an auth token as an environment variable and creates +// the commit and the PR under the account affiliated with that token. +// +// The purpose of this example is to show how to use refs, trees and commits to +// create commits and pull requests. +// +// Note, if you want to push a single file, you probably prefer to use the +// content API. An example is available here: +// https://godoc.org/github.com/google/go-github/github#example-RepositoriesService-CreateFile +// +// Note, for this to work at least 1 commit is needed, so you if you use this +// after creating a repository you might want to make sure you set `AutoInit` to +// `true`. +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "strings" + "time" + + "github.com/google/go-github/v18/github" + "golang.org/x/oauth2" +) + +var ( + sourceOwner = flag.String("source-owner", "", "Name of the owner (user or org) of the repo to create the commit in.") + sourceRepo = flag.String("source-repo", "", "Name of repo to create the commit in.") + commitMessage = flag.String("commit-message", "", "Content of the commit message.") + commitBranch = flag.String("commit-branch", "", "Name of branch to create the commit in. If it does not already exists, it will be created using the `base-branch` parameter") + baseBranch = flag.String("base-branch", "master", "Name of branch to create the `commit-branch` from.") + prRepoOwner = flag.String("merge-repo-owner", "", "Name of the owner (user or org) of the repo to create the PR against. If not specified, the value of the `-source-owner` flag will be used.") + prRepo = flag.String("merge-repo", "", "Name of repo to create the PR against. If not specified, the value of the `-source-repo` flag will be used.") + prBranch = flag.String("merge-branch", "master", "Name of branch to create the PR against (the one you want to merge your branch in via the PR).") + prSubject = flag.String("pr-title", "", "Title of the pull request. If not specified, no pull request will be created.") + prDescription = flag.String("pr-text", "", "Text to put in the description of the pull request.") + sourceFiles = flag.String("files", "", `Comma-separated list of files to commit and their location. +The local file is separated by its target location by a semi-colon. +If the file should be in the same location with the same name, you can just put the file name and omit the repetition. +Example: README.md,main.go:github/examples/commitpr/main.go`) + authorName = flag.String("author-name", "", "Name of the author of the commit.") + authorEmail = flag.String("author-email", "", "Email of the author of the commit.") +) + +var client *github.Client +var ctx = context.Background() + +// getRef returns the commit branch reference object if it exists or creates it +// from the base branch before returning it. +func getRef() (ref *github.Reference, err error) { + if ref, _, err = client.Git.GetRef(ctx, *sourceOwner, *sourceRepo, "refs/heads/"+*commitBranch); err == nil { + return ref, nil + } + + // We consider that an error means the branch has not been found and needs to + // be created. + if *commitBranch == *baseBranch { + return nil, errors.New("The commit branch does not exist but `-base-branch` is the same as `-commit-branch`") + } + + if *baseBranch == "" { + return nil, errors.New("The `-base-branch` should not be set to an empty string when the branch specified by `-commit-branch` does not exists") + } + + var baseRef *github.Reference + if baseRef, _, err = client.Git.GetRef(ctx, *sourceOwner, *sourceRepo, "refs/heads/"+*baseBranch); err != nil { + return nil, err + } + newRef := &github.Reference{Ref: github.String("refs/heads/" + *commitBranch), Object: &github.GitObject{SHA: baseRef.Object.SHA}} + ref, _, err = client.Git.CreateRef(ctx, *sourceOwner, *sourceRepo, newRef) + return ref, err +} + +// getTree generates the tree to commit based on the given files and the commit +// of the ref you got in getRef. +func getTree(ref *github.Reference) (tree *github.Tree, err error) { + // Create a tree with what to commit. + entries := []github.TreeEntry{} + + // Load each file into the tree. + for _, fileArg := range strings.Split(*sourceFiles, ",") { + file, content, err := getFileContent(fileArg) + if err != nil { + return nil, err + } + entries = append(entries, github.TreeEntry{Path: github.String(file), Type: github.String("blob"), Content: github.String(string(content)), Mode: github.String("100644")}) + } + + tree, _, err = client.Git.CreateTree(ctx, *sourceOwner, *sourceRepo, *ref.Object.SHA, entries) + return tree, err +} + +// getFileContent loads the local content of a file and return the target name +// of the file in the target repository and its contents. +func getFileContent(fileArg string) (targetName string, b []byte, err error) { + var localFile string + files := strings.Split(fileArg, ":") + switch { + case len(files) < 1: + return "", nil, errors.New("empty `-files` parameter") + case len(files) == 1: + localFile = files[0] + targetName = files[0] + default: + localFile = files[0] + targetName = files[1] + } + + b, err = ioutil.ReadFile(localFile) + return targetName, b, err +} + +// createCommit creates the commit in the given reference using the given tree. +func pushCommit(ref *github.Reference, tree *github.Tree) (err error) { + // Get the parent commit to attach the commit to. + parent, _, err := client.Repositories.GetCommit(ctx, *sourceOwner, *sourceRepo, *ref.Object.SHA) + if err != nil { + return err + } + // This is not always populated, but is needed. + parent.Commit.SHA = parent.SHA + + // Create the commit using the tree. + date := time.Now() + author := &github.CommitAuthor{Date: &date, Name: authorName, Email: authorEmail} + commit := &github.Commit{Author: author, Message: commitMessage, Tree: tree, Parents: []github.Commit{*parent.Commit}} + newCommit, _, err := client.Git.CreateCommit(ctx, *sourceOwner, *sourceRepo, commit) + if err != nil { + return err + } + + // Attach the commit to the master branch. + ref.Object.SHA = newCommit.SHA + _, _, err = client.Git.UpdateRef(ctx, *sourceOwner, *sourceRepo, ref, false) + return err +} + +// createPR creates a pull requesit. Based on: https://godoc.org/github.com/google/go-github/github#example-PullRequestsService-Create +func createPR() (err error) { + if *prSubject == "" { + return errors.New("missing `-pr-title` flag; skipping PR creation") + } + + if *prRepoOwner != "" && *prRepoOwner != *sourceOwner { + *commitBranch = fmt.Sprintf("%s:%s", *sourceOwner, *commitBranch) + } else { + prRepoOwner = sourceOwner + } + + if *prRepo == "" { + prRepo = sourceRepo + } + + newPR := &github.NewPullRequest{ + Title: prSubject, + Head: commitBranch, + Base: prBranch, + Body: prDescription, + MaintainerCanModify: github.Bool(true), + } + + pr, _, err := client.PullRequests.Create(ctx, *prRepoOwner, *prRepo, newPR) + if err != nil { + return err + } + + fmt.Printf("PR created: %s\n", pr.GetHTMLURL()) + return nil +} + +func main() { + flag.Parse() + token := os.Getenv("GITHUB_AUTH_TOKEN") + if token == "" { + log.Fatal("Unauthorized: No token present") + } + if *sourceOwner == "" || *sourceRepo == "" || *commitBranch == "" || *sourceFiles == "" || *authorName == "" || *authorEmail == "" { + log.Fatal("You need to specify a non-empty value for the flags `-source-owner`, `-source-repo`, `-commit-branch`, `-files`, `-author-name` and `-author-email`") + } + ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}) + tc := oauth2.NewClient(ctx, ts) + client = github.NewClient(tc) + + ref, err := getRef() + if err != nil { + log.Fatalf("Unable to get/create the commit reference: %s\n", err) + } + if ref == nil { + log.Fatalf("No error where returned but the reference is nil") + } + + tree, err := getTree(ref) + if err != nil { + log.Fatalf("Unable to create the tree based on the provided files: %s\n", err) + } + + if err := pushCommit(ref, tree); err != nil { + log.Fatalf("Unable to create the commit: %s\n", err) + } + + if err := createPR(); err != nil { + log.Fatalf("Error while creating the pull request: %s", err) + } +} diff --git a/vendor/github.com/google/go-github/example/migrations/main.go b/vendor/github.com/google/go-github/example/migrations/main.go new file mode 100644 index 0000000..d826606 --- /dev/null +++ b/vendor/github.com/google/go-github/example/migrations/main.go @@ -0,0 +1,40 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// migrations demonstrates the functionality of +// the user data migration API for the authenticated GitHub +// user and lists all the user migrations. +package main + +import ( + "context" + "fmt" + "github.com/google/go-github/v18/github" + "golang.org/x/oauth2" +) + +func fetchAllUserMigrations() ([]*github.UserMigration, error) { + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: ""}, + ) + tc := oauth2.NewClient(ctx, ts) + client := github.NewClient(tc) + + migrations, _, err := client.Migrations.ListUserMigrations(ctx) + return migrations, err +} + +func main() { + migrations, err := fetchAllUserMigrations() + if err != nil { + fmt.Printf("Error %v\n", err) + return + } + + for i, m := range migrations { + fmt.Printf("%v. %v", i+1, m.GetID()) + } +} diff --git a/vendor/github.com/google/go-github/example/newrepo/main.go b/vendor/github.com/google/go-github/example/newrepo/main.go new file mode 100644 index 0000000..49fba77 --- /dev/null +++ b/vendor/github.com/google/go-github/example/newrepo/main.go @@ -0,0 +1,49 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The newrepo command utilizes go-github as a cli tool for +// creating new repositories. It takes an auth token as +// an enviroment variable and creates the new repo under +// the account affiliated with that token. +package main + +import ( + "context" + "flag" + "fmt" + "log" + "os" + + "github.com/google/go-github/v18/github" + "golang.org/x/oauth2" +) + +var ( + name = flag.String("name", "", "Name of repo to create in authenticated user's GitHub account.") + description = flag.String("description", "", "Description of created repo.") + private = flag.Bool("private", false, "Will created repo be private.") +) + +func main() { + flag.Parse() + token := os.Getenv("GITHUB_AUTH_TOKEN") + if token == "" { + log.Fatal("Unauthorized: No token present") + } + if *name == "" { + log.Fatal("No name: New repos must be given a name") + } + ctx := context.Background() + ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}) + tc := oauth2.NewClient(ctx, ts) + client := github.NewClient(tc) + + r := &github.Repository{Name: name, Private: private, Description: description} + repo, _, err := client.Repositories.Create(ctx, "", r) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Successfully created new repo: %v\n", repo.GetName()) +} diff --git a/vendor/github.com/google/go-github/example/simple/main.go b/vendor/github.com/google/go-github/example/simple/main.go new file mode 100644 index 0000000..5f2ce20 --- /dev/null +++ b/vendor/github.com/google/go-github/example/simple/main.go @@ -0,0 +1,40 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The simple command demonstrates a simple functionality which +// prompts the user for a GitHub username and lists all the public +// organization memberships of the specified username. +package main + +import ( + "context" + "fmt" + + "github.com/google/go-github/v18/github" +) + +// Fetch all the public organizations' membership of a user. +// +func FetchOrganizations(username string) ([]*github.Organization, error) { + client := github.NewClient(nil) + orgs, _, err := client.Organizations.List(context.Background(), username, nil) + return orgs, err +} + +func main() { + var username string + fmt.Print("Enter GitHub username: ") + fmt.Scanf("%s", &username) + + organizations, err := FetchOrganizations(username) + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + for i, organization := range organizations { + fmt.Printf("%v. %v\n", i+1, organization.GetLogin()) + } +} diff --git a/vendor/github.com/google/go-github/github/activity.go b/vendor/github.com/google/go-github/github/activity.go new file mode 100644 index 0000000..d6c992c --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity.go @@ -0,0 +1,69 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "context" + +// ActivityService handles communication with the activity related +// methods of the GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/activity/ +type ActivityService service + +// FeedLink represents a link to a related resource. +type FeedLink struct { + HRef *string `json:"href,omitempty"` + Type *string `json:"type,omitempty"` +} + +// Feeds represents timeline resources in Atom format. +type Feeds struct { + TimelineURL *string `json:"timeline_url,omitempty"` + UserURL *string `json:"user_url,omitempty"` + CurrentUserPublicURL *string `json:"current_user_public_url,omitempty"` + CurrentUserURL *string `json:"current_user_url,omitempty"` + CurrentUserActorURL *string `json:"current_user_actor_url,omitempty"` + CurrentUserOrganizationURL *string `json:"current_user_organization_url,omitempty"` + CurrentUserOrganizationURLs []string `json:"current_user_organization_urls,omitempty"` + Links *struct { + Timeline *FeedLink `json:"timeline,omitempty"` + User *FeedLink `json:"user,omitempty"` + CurrentUserPublic *FeedLink `json:"current_user_public,omitempty"` + CurrentUser *FeedLink `json:"current_user,omitempty"` + CurrentUserActor *FeedLink `json:"current_user_actor,omitempty"` + CurrentUserOrganization *FeedLink `json:"current_user_organization,omitempty"` + CurrentUserOrganizations []FeedLink `json:"current_user_organizations,omitempty"` + } `json:"_links,omitempty"` +} + +// ListFeeds lists all the feeds available to the authenticated user. +// +// GitHub provides several timeline resources in Atom format: +// Timeline: The GitHub global public timeline +// User: The public timeline for any user, using URI template +// Current user public: The public timeline for the authenticated user +// Current user: The private timeline for the authenticated user +// Current user actor: The private timeline for activity created by the +// authenticated user +// Current user organizations: The private timeline for the organizations +// the authenticated user is a member of. +// +// Note: Private feeds are only returned when authenticating via Basic Auth +// since current feed URIs use the older, non revocable auth tokens. +func (s *ActivityService) ListFeeds(ctx context.Context) (*Feeds, *Response, error) { + req, err := s.client.NewRequest("GET", "feeds", nil) + if err != nil { + return nil, nil, err + } + + f := &Feeds{} + resp, err := s.client.Do(ctx, req, f) + if err != nil { + return nil, resp, err + } + + return f, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/activity_events.go b/vendor/github.com/google/go-github/github/activity_events.go new file mode 100644 index 0000000..a919b11 --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_events.go @@ -0,0 +1,328 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "time" +) + +// Event represents a GitHub event. +type Event struct { + Type *string `json:"type,omitempty"` + Public *bool `json:"public,omitempty"` + RawPayload *json.RawMessage `json:"payload,omitempty"` + Repo *Repository `json:"repo,omitempty"` + Actor *User `json:"actor,omitempty"` + Org *Organization `json:"org,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + ID *string `json:"id,omitempty"` +} + +func (e Event) String() string { + return Stringify(e) +} + +// ParsePayload parses the event payload. For recognized event types, +// a value of the corresponding struct type will be returned. +func (e *Event) ParsePayload() (payload interface{}, err error) { + switch *e.Type { + case "CheckRunEvent": + payload = &CheckRunEvent{} + case "CheckSuiteEvent": + payload = &CheckSuiteEvent{} + case "CommitCommentEvent": + payload = &CommitCommentEvent{} + case "CreateEvent": + payload = &CreateEvent{} + case "DeleteEvent": + payload = &DeleteEvent{} + case "DeploymentEvent": + payload = &DeploymentEvent{} + case "DeploymentStatusEvent": + payload = &DeploymentStatusEvent{} + case "ForkEvent": + payload = &ForkEvent{} + case "GollumEvent": + payload = &GollumEvent{} + case "InstallationEvent": + payload = &InstallationEvent{} + case "InstallationRepositoriesEvent": + payload = &InstallationRepositoriesEvent{} + case "IssueCommentEvent": + payload = &IssueCommentEvent{} + case "IssuesEvent": + payload = &IssuesEvent{} + case "LabelEvent": + payload = &LabelEvent{} + case "MarketplacePurchaseEvent": + payload = &MarketplacePurchaseEvent{} + case "MemberEvent": + payload = &MemberEvent{} + case "MembershipEvent": + payload = &MembershipEvent{} + case "MilestoneEvent": + payload = &MilestoneEvent{} + case "OrganizationEvent": + payload = &OrganizationEvent{} + case "OrgBlockEvent": + payload = &OrgBlockEvent{} + case "PageBuildEvent": + payload = &PageBuildEvent{} + case "PingEvent": + payload = &PingEvent{} + case "ProjectEvent": + payload = &ProjectEvent{} + case "ProjectCardEvent": + payload = &ProjectCardEvent{} + case "ProjectColumnEvent": + payload = &ProjectColumnEvent{} + case "PublicEvent": + payload = &PublicEvent{} + case "PullRequestEvent": + payload = &PullRequestEvent{} + case "PullRequestReviewEvent": + payload = &PullRequestReviewEvent{} + case "PullRequestReviewCommentEvent": + payload = &PullRequestReviewCommentEvent{} + case "PushEvent": + payload = &PushEvent{} + case "ReleaseEvent": + payload = &ReleaseEvent{} + case "RepositoryEvent": + payload = &RepositoryEvent{} + case "StatusEvent": + payload = &StatusEvent{} + case "TeamEvent": + payload = &TeamEvent{} + case "TeamAddEvent": + payload = &TeamAddEvent{} + case "WatchEvent": + payload = &WatchEvent{} + } + err = json.Unmarshal(*e.RawPayload, &payload) + return payload, err +} + +// Payload returns the parsed event payload. For recognized event types, +// a value of the corresponding struct type will be returned. +// +// Deprecated: Use ParsePayload instead, which returns an error +// rather than panics if JSON unmarshaling raw payload fails. +func (e *Event) Payload() (payload interface{}) { + var err error + payload, err = e.ParsePayload() + if err != nil { + panic(err) + } + return payload +} + +// ListEvents drinks from the firehose of all public events across GitHub. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-events +func (s *ActivityService) ListEvents(ctx context.Context, opt *ListOptions) ([]*Event, *Response, error) { + u, err := addOptions("events", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListRepositoryEvents lists events for a repository. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-repository-events +func (s *ActivityService) ListRepositoryEvents(ctx context.Context, owner, repo string, opt *ListOptions) ([]*Event, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/events", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListIssueEventsForRepository lists issue events for a repository. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-issue-events-for-a-repository +func (s *ActivityService) ListIssueEventsForRepository(ctx context.Context, owner, repo string, opt *ListOptions) ([]*IssueEvent, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/events", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var events []*IssueEvent + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListEventsForRepoNetwork lists public events for a network of repositories. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-events-for-a-network-of-repositories +func (s *ActivityService) ListEventsForRepoNetwork(ctx context.Context, owner, repo string, opt *ListOptions) ([]*Event, *Response, error) { + u := fmt.Sprintf("networks/%v/%v/events", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListEventsForOrganization lists public events for an organization. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-events-for-an-organization +func (s *ActivityService) ListEventsForOrganization(ctx context.Context, org string, opt *ListOptions) ([]*Event, *Response, error) { + u := fmt.Sprintf("orgs/%v/events", org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListEventsPerformedByUser lists the events performed by a user. If publicOnly is +// true, only public events will be returned. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-events-performed-by-a-user +func (s *ActivityService) ListEventsPerformedByUser(ctx context.Context, user string, publicOnly bool, opt *ListOptions) ([]*Event, *Response, error) { + var u string + if publicOnly { + u = fmt.Sprintf("users/%v/events/public", user) + } else { + u = fmt.Sprintf("users/%v/events", user) + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListEventsReceivedByUser lists the events received by a user. If publicOnly is +// true, only public events will be returned. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-events-that-a-user-has-received +func (s *ActivityService) ListEventsReceivedByUser(ctx context.Context, user string, publicOnly bool, opt *ListOptions) ([]*Event, *Response, error) { + var u string + if publicOnly { + u = fmt.Sprintf("users/%v/received_events/public", user) + } else { + u = fmt.Sprintf("users/%v/received_events", user) + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListUserEventsForOrganization provides the user’s organization dashboard. You +// must be authenticated as the user to view this. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-events-for-an-organization +func (s *ActivityService) ListUserEventsForOrganization(ctx context.Context, org, user string, opt *ListOptions) ([]*Event, *Response, error) { + u := fmt.Sprintf("users/%v/events/orgs/%v", user, org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/activity_events_test.go b/vendor/github.com/google/go-github/github/activity_events_test.go new file mode 100644 index 0000000..0b01c4a --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_events_test.go @@ -0,0 +1,349 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestActivityService_ListEvents(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListEvents(context.Background(), opt) + if err != nil { + t.Errorf("Activities.ListEvents returned error: %v", err) + } + + want := []*Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListEvents returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListRepositoryEvents(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListRepositoryEvents(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Activities.ListRepositoryEvents returned error: %v", err) + } + + want := []*Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListRepositoryEvents returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListRepositoryEvents_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Activity.ListRepositoryEvents(context.Background(), "%", "%", nil) + testURLParseError(t, err) +} + +func TestActivityService_ListIssueEventsForRepository(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListIssueEventsForRepository(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Activities.ListIssueEventsForRepository returned error: %v", err) + } + + want := []*IssueEvent{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListIssueEventsForRepository returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListIssueEventsForRepository_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Activity.ListIssueEventsForRepository(context.Background(), "%", "%", nil) + testURLParseError(t, err) +} + +func TestActivityService_ListEventsForRepoNetwork(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/networks/o/r/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListEventsForRepoNetwork(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Activities.ListEventsForRepoNetwork returned error: %v", err) + } + + want := []*Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListEventsForRepoNetwork returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsForRepoNetwork_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Activity.ListEventsForRepoNetwork(context.Background(), "%", "%", nil) + testURLParseError(t, err) +} + +func TestActivityService_ListEventsForOrganization(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListEventsForOrganization(context.Background(), "o", opt) + if err != nil { + t.Errorf("Activities.ListEventsForOrganization returned error: %v", err) + } + + want := []*Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListEventsForOrganization returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsForOrganization_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Activity.ListEventsForOrganization(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestActivityService_ListEventsPerformedByUser_all(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListEventsPerformedByUser(context.Background(), "u", false, opt) + if err != nil { + t.Errorf("Events.ListPerformedByUser returned error: %v", err) + } + + want := []*Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Events.ListPerformedByUser returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsPerformedByUser_publicOnly(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/events/public", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + events, _, err := client.Activity.ListEventsPerformedByUser(context.Background(), "u", true, nil) + if err != nil { + t.Errorf("Events.ListPerformedByUser returned error: %v", err) + } + + want := []*Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Events.ListPerformedByUser returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsPerformedByUser_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Activity.ListEventsPerformedByUser(context.Background(), "%", false, nil) + testURLParseError(t, err) +} + +func TestActivityService_ListEventsReceivedByUser_all(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/received_events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListEventsReceivedByUser(context.Background(), "u", false, opt) + if err != nil { + t.Errorf("Events.ListReceivedByUser returned error: %v", err) + } + + want := []*Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Events.ListReceivedUser returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsReceivedByUser_publicOnly(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/received_events/public", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + events, _, err := client.Activity.ListEventsReceivedByUser(context.Background(), "u", true, nil) + if err != nil { + t.Errorf("Events.ListReceivedByUser returned error: %v", err) + } + + want := []*Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Events.ListReceivedByUser returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsReceivedByUser_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Activity.ListEventsReceivedByUser(context.Background(), "%", false, nil) + testURLParseError(t, err) +} + +func TestActivityService_ListUserEventsForOrganization(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/events/orgs/o", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListUserEventsForOrganization(context.Background(), "o", "u", opt) + if err != nil { + t.Errorf("Activities.ListUserEventsForOrganization returned error: %v", err) + } + + want := []*Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListUserEventsForOrganization returned %+v, want %+v", events, want) + } +} + +func TestActivityService_EventParsePayload_typed(t *testing.T) { + raw := []byte(`{"type": "PushEvent","payload":{"push_id": 1}}`) + var event *Event + if err := json.Unmarshal(raw, &event); err != nil { + t.Fatalf("Unmarshal Event returned error: %v", err) + } + + want := &PushEvent{PushID: Int64(1)} + got, err := event.ParsePayload() + if err != nil { + t.Fatalf("ParsePayload returned unexpected error: %v", err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Event.ParsePayload returned %+v, want %+v", got, want) + } +} + +// TestEvent_Payload_untyped checks that unrecognized events are parsed to an +// interface{} value (instead of being discarded or throwing an error), for +// forward compatibility with new event types. +func TestActivityService_EventParsePayload_untyped(t *testing.T) { + raw := []byte(`{"type": "UnrecognizedEvent","payload":{"field": "val"}}`) + var event *Event + if err := json.Unmarshal(raw, &event); err != nil { + t.Fatalf("Unmarshal Event returned error: %v", err) + } + + want := map[string]interface{}{"field": "val"} + got, err := event.ParsePayload() + if err != nil { + t.Fatalf("ParsePayload returned unexpected error: %v", err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Event.ParsePayload returned %+v, want %+v", got, want) + } +} + +func TestActivityService_EventParsePayload_installation(t *testing.T) { + raw := []byte(`{"type": "PullRequestEvent","payload":{"installation":{"id":1}}}`) + var event *Event + if err := json.Unmarshal(raw, &event); err != nil { + t.Fatalf("Unmarshal Event returned error: %v", err) + } + + want := &PullRequestEvent{Installation: &Installation{ID: Int64(1)}} + got, err := event.ParsePayload() + if err != nil { + t.Fatalf("ParsePayload returned unexpected error: %v", err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Event.ParsePayload returned %+v, want %+v", got, want) + } +} diff --git a/vendor/github.com/google/go-github/github/activity_notifications.go b/vendor/github.com/google/go-github/github/activity_notifications.go new file mode 100644 index 0000000..45c8b2a --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_notifications.go @@ -0,0 +1,223 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// Notification identifies a GitHub notification for a user. +type Notification struct { + ID *string `json:"id,omitempty"` + Repository *Repository `json:"repository,omitempty"` + Subject *NotificationSubject `json:"subject,omitempty"` + + // Reason identifies the event that triggered the notification. + // + // GitHub API docs: https://developer.github.com/v3/activity/notifications/#notification-reasons + Reason *string `json:"reason,omitempty"` + + Unread *bool `json:"unread,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + LastReadAt *time.Time `json:"last_read_at,omitempty"` + URL *string `json:"url,omitempty"` +} + +// NotificationSubject identifies the subject of a notification. +type NotificationSubject struct { + Title *string `json:"title,omitempty"` + URL *string `json:"url,omitempty"` + LatestCommentURL *string `json:"latest_comment_url,omitempty"` + Type *string `json:"type,omitempty"` +} + +// NotificationListOptions specifies the optional parameters to the +// ActivityService.ListNotifications method. +type NotificationListOptions struct { + All bool `url:"all,omitempty"` + Participating bool `url:"participating,omitempty"` + Since time.Time `url:"since,omitempty"` + Before time.Time `url:"before,omitempty"` + + ListOptions +} + +// ListNotifications lists all notifications for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/activity/notifications/#list-your-notifications +func (s *ActivityService) ListNotifications(ctx context.Context, opt *NotificationListOptions) ([]*Notification, *Response, error) { + u := fmt.Sprintf("notifications") + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var notifications []*Notification + resp, err := s.client.Do(ctx, req, ¬ifications) + if err != nil { + return nil, resp, err + } + + return notifications, resp, nil +} + +// ListRepositoryNotifications lists all notifications in a given repository +// for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/activity/notifications/#list-your-notifications-in-a-repository +func (s *ActivityService) ListRepositoryNotifications(ctx context.Context, owner, repo string, opt *NotificationListOptions) ([]*Notification, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/notifications", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var notifications []*Notification + resp, err := s.client.Do(ctx, req, ¬ifications) + if err != nil { + return nil, resp, err + } + + return notifications, resp, nil +} + +type markReadOptions struct { + LastReadAt time.Time `json:"last_read_at,omitempty"` +} + +// MarkNotificationsRead marks all notifications up to lastRead as read. +// +// GitHub API docs: https://developer.github.com/v3/activity/notifications/#mark-as-read +func (s *ActivityService) MarkNotificationsRead(ctx context.Context, lastRead time.Time) (*Response, error) { + opts := &markReadOptions{ + LastReadAt: lastRead, + } + req, err := s.client.NewRequest("PUT", "notifications", opts) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// MarkRepositoryNotificationsRead marks all notifications up to lastRead in +// the specified repository as read. +// +// GitHub API docs: https://developer.github.com/v3/activity/notifications/#mark-notifications-as-read-in-a-repository +func (s *ActivityService) MarkRepositoryNotificationsRead(ctx context.Context, owner, repo string, lastRead time.Time) (*Response, error) { + opts := &markReadOptions{ + LastReadAt: lastRead, + } + u := fmt.Sprintf("repos/%v/%v/notifications", owner, repo) + req, err := s.client.NewRequest("PUT", u, opts) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// GetThread gets the specified notification thread. +// +// GitHub API docs: https://developer.github.com/v3/activity/notifications/#view-a-single-thread +func (s *ActivityService) GetThread(ctx context.Context, id string) (*Notification, *Response, error) { + u := fmt.Sprintf("notifications/threads/%v", id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + notification := new(Notification) + resp, err := s.client.Do(ctx, req, notification) + if err != nil { + return nil, resp, err + } + + return notification, resp, nil +} + +// MarkThreadRead marks the specified thread as read. +// +// GitHub API docs: https://developer.github.com/v3/activity/notifications/#mark-a-thread-as-read +func (s *ActivityService) MarkThreadRead(ctx context.Context, id string) (*Response, error) { + u := fmt.Sprintf("notifications/threads/%v", id) + + req, err := s.client.NewRequest("PATCH", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// GetThreadSubscription checks to see if the authenticated user is subscribed +// to a thread. +// +// GitHub API docs: https://developer.github.com/v3/activity/notifications/#get-a-thread-subscription +func (s *ActivityService) GetThreadSubscription(ctx context.Context, id string) (*Subscription, *Response, error) { + u := fmt.Sprintf("notifications/threads/%v/subscription", id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + sub := new(Subscription) + resp, err := s.client.Do(ctx, req, sub) + if err != nil { + return nil, resp, err + } + + return sub, resp, nil +} + +// SetThreadSubscription sets the subscription for the specified thread for the +// authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/activity/notifications/#set-a-thread-subscription +func (s *ActivityService) SetThreadSubscription(ctx context.Context, id string, subscription *Subscription) (*Subscription, *Response, error) { + u := fmt.Sprintf("notifications/threads/%v/subscription", id) + + req, err := s.client.NewRequest("PUT", u, subscription) + if err != nil { + return nil, nil, err + } + + sub := new(Subscription) + resp, err := s.client.Do(ctx, req, sub) + if err != nil { + return nil, resp, err + } + + return sub, resp, nil +} + +// DeleteThreadSubscription deletes the subscription for the specified thread +// for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/activity/notifications/#delete-a-thread-subscription +func (s *ActivityService) DeleteThreadSubscription(ctx context.Context, id string) (*Response, error) { + u := fmt.Sprintf("notifications/threads/%v/subscription", id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/activity_notifications_test.go b/vendor/github.com/google/go-github/github/activity_notifications_test.go new file mode 100644 index 0000000..a36b253 --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_notifications_test.go @@ -0,0 +1,204 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestActivityService_ListNotification(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/notifications", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "all": "true", + "participating": "true", + "since": "2006-01-02T15:04:05Z", + "before": "2007-03-04T15:04:05Z", + }) + + fmt.Fprint(w, `[{"id":"1", "subject":{"title":"t"}}]`) + }) + + opt := &NotificationListOptions{ + All: true, + Participating: true, + Since: time.Date(2006, time.January, 02, 15, 04, 05, 0, time.UTC), + Before: time.Date(2007, time.March, 04, 15, 04, 05, 0, time.UTC), + } + notifications, _, err := client.Activity.ListNotifications(context.Background(), opt) + if err != nil { + t.Errorf("Activity.ListNotifications returned error: %v", err) + } + + want := []*Notification{{ID: String("1"), Subject: &NotificationSubject{Title: String("t")}}} + if !reflect.DeepEqual(notifications, want) { + t.Errorf("Activity.ListNotifications returned %+v, want %+v", notifications, want) + } +} + +func TestActivityService_ListRepositoryNotification(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/notifications", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":"1"}]`) + }) + + notifications, _, err := client.Activity.ListRepositoryNotifications(context.Background(), "o", "r", nil) + if err != nil { + t.Errorf("Activity.ListRepositoryNotifications returned error: %v", err) + } + + want := []*Notification{{ID: String("1")}} + if !reflect.DeepEqual(notifications, want) { + t.Errorf("Activity.ListRepositoryNotifications returned %+v, want %+v", notifications, want) + } +} + +func TestActivityService_MarkNotificationsRead(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/notifications", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testHeader(t, r, "Content-Type", "application/json") + testBody(t, r, `{"last_read_at":"2006-01-02T15:04:05Z"}`+"\n") + + w.WriteHeader(http.StatusResetContent) + }) + + _, err := client.Activity.MarkNotificationsRead(context.Background(), time.Date(2006, time.January, 02, 15, 04, 05, 0, time.UTC)) + if err != nil { + t.Errorf("Activity.MarkNotificationsRead returned error: %v", err) + } +} + +func TestActivityService_MarkRepositoryNotificationsRead(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/notifications", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testHeader(t, r, "Content-Type", "application/json") + testBody(t, r, `{"last_read_at":"2006-01-02T15:04:05Z"}`+"\n") + + w.WriteHeader(http.StatusResetContent) + }) + + _, err := client.Activity.MarkRepositoryNotificationsRead(context.Background(), "o", "r", time.Date(2006, time.January, 02, 15, 04, 05, 0, time.UTC)) + if err != nil { + t.Errorf("Activity.MarkRepositoryNotificationsRead returned error: %v", err) + } +} + +func TestActivityService_GetThread(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/notifications/threads/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":"1"}`) + }) + + notification, _, err := client.Activity.GetThread(context.Background(), "1") + if err != nil { + t.Errorf("Activity.GetThread returned error: %v", err) + } + + want := &Notification{ID: String("1")} + if !reflect.DeepEqual(notification, want) { + t.Errorf("Activity.GetThread returned %+v, want %+v", notification, want) + } +} + +func TestActivityService_MarkThreadRead(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/notifications/threads/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + w.WriteHeader(http.StatusResetContent) + }) + + _, err := client.Activity.MarkThreadRead(context.Background(), "1") + if err != nil { + t.Errorf("Activity.MarkThreadRead returned error: %v", err) + } +} + +func TestActivityService_GetThreadSubscription(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/notifications/threads/1/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"subscribed":true}`) + }) + + sub, _, err := client.Activity.GetThreadSubscription(context.Background(), "1") + if err != nil { + t.Errorf("Activity.GetThreadSubscription returned error: %v", err) + } + + want := &Subscription{Subscribed: Bool(true)} + if !reflect.DeepEqual(sub, want) { + t.Errorf("Activity.GetThreadSubscription returned %+v, want %+v", sub, want) + } +} + +func TestActivityService_SetThreadSubscription(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Subscription{Subscribed: Bool(true)} + + mux.HandleFunc("/notifications/threads/1/subscription", func(w http.ResponseWriter, r *http.Request) { + v := new(Subscription) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"ignored":true}`) + }) + + sub, _, err := client.Activity.SetThreadSubscription(context.Background(), "1", input) + if err != nil { + t.Errorf("Activity.SetThreadSubscription returned error: %v", err) + } + + want := &Subscription{Ignored: Bool(true)} + if !reflect.DeepEqual(sub, want) { + t.Errorf("Activity.SetThreadSubscription returned %+v, want %+v", sub, want) + } +} + +func TestActivityService_DeleteThreadSubscription(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/notifications/threads/1/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Activity.DeleteThreadSubscription(context.Background(), "1") + if err != nil { + t.Errorf("Activity.DeleteThreadSubscription returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/activity_star.go b/vendor/github.com/google/go-github/github/activity_star.go new file mode 100644 index 0000000..5ae5c10 --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_star.go @@ -0,0 +1,137 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "strings" +) + +// StarredRepository is returned by ListStarred. +type StarredRepository struct { + StarredAt *Timestamp `json:"starred_at,omitempty"` + Repository *Repository `json:"repo,omitempty"` +} + +// Stargazer represents a user that has starred a repository. +type Stargazer struct { + StarredAt *Timestamp `json:"starred_at,omitempty"` + User *User `json:"user,omitempty"` +} + +// ListStargazers lists people who have starred the specified repo. +// +// GitHub API docs: https://developer.github.com/v3/activity/starring/#list-stargazers +func (s *ActivityService) ListStargazers(ctx context.Context, owner, repo string, opt *ListOptions) ([]*Stargazer, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/stargazers", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeStarringPreview) + + var stargazers []*Stargazer + resp, err := s.client.Do(ctx, req, &stargazers) + if err != nil { + return nil, resp, err + } + + return stargazers, resp, nil +} + +// ActivityListStarredOptions specifies the optional parameters to the +// ActivityService.ListStarred method. +type ActivityListStarredOptions struct { + // How to sort the repository list. Possible values are: created, updated, + // pushed, full_name. Default is "full_name". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort repositories. Possible values are: asc, desc. + // Default is "asc" when sort is "full_name", otherwise default is "desc". + Direction string `url:"direction,omitempty"` + + ListOptions +} + +// ListStarred lists all the repos starred by a user. Passing the empty string +// will list the starred repositories for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/activity/starring/#list-repositories-being-starred +func (s *ActivityService) ListStarred(ctx context.Context, user string, opt *ActivityListStarredOptions) ([]*StarredRepository, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/starred", user) + } else { + u = "user/starred" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when APIs fully launch + acceptHeaders := []string{mediaTypeStarringPreview, mediaTypeTopicsPreview} + req.Header.Set("Accept", strings.Join(acceptHeaders, ", ")) + + var repos []*StarredRepository + resp, err := s.client.Do(ctx, req, &repos) + if err != nil { + return nil, resp, err + } + + return repos, resp, nil +} + +// IsStarred checks if a repository is starred by authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/activity/starring/#check-if-you-are-starring-a-repository +func (s *ActivityService) IsStarred(ctx context.Context, owner, repo string) (bool, *Response, error) { + u := fmt.Sprintf("user/starred/%v/%v", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + resp, err := s.client.Do(ctx, req, nil) + starred, err := parseBoolResponse(err) + return starred, resp, err +} + +// Star a repository as the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/activity/starring/#star-a-repository +func (s *ActivityService) Star(ctx context.Context, owner, repo string) (*Response, error) { + u := fmt.Sprintf("user/starred/%v/%v", owner, repo) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// Unstar a repository as the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/activity/starring/#unstar-a-repository +func (s *ActivityService) Unstar(ctx context.Context, owner, repo string) (*Response, error) { + u := fmt.Sprintf("user/starred/%v/%v", owner, repo) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/activity_star_test.go b/vendor/github.com/google/go-github/github/activity_star_test.go new file mode 100644 index 0000000..8a12247 --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_star_test.go @@ -0,0 +1,185 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "strings" + "testing" + "time" +) + +func TestActivityService_ListStargazers(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stargazers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeStarringPreview) + testFormValues(t, r, values{ + "page": "2", + }) + + fmt.Fprint(w, `[{"starred_at":"2002-02-10T15:30:00Z","user":{"id":1}}]`) + }) + + stargazers, _, err := client.Activity.ListStargazers(context.Background(), "o", "r", &ListOptions{Page: 2}) + if err != nil { + t.Errorf("Activity.ListStargazers returned error: %v", err) + } + + want := []*Stargazer{{StarredAt: &Timestamp{time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC)}, User: &User{ID: Int64(1)}}} + if !reflect.DeepEqual(stargazers, want) { + t.Errorf("Activity.ListStargazers returned %+v, want %+v", stargazers, want) + } +} + +func TestActivityService_ListStarred_authenticatedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/starred", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", strings.Join([]string{mediaTypeStarringPreview, mediaTypeTopicsPreview}, ", ")) + fmt.Fprint(w, `[{"starred_at":"2002-02-10T15:30:00Z","repo":{"id":1}}]`) + }) + + repos, _, err := client.Activity.ListStarred(context.Background(), "", nil) + if err != nil { + t.Errorf("Activity.ListStarred returned error: %v", err) + } + + want := []*StarredRepository{{StarredAt: &Timestamp{time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC)}, Repository: &Repository{ID: Int64(1)}}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Activity.ListStarred returned %+v, want %+v", repos, want) + } +} + +func TestActivityService_ListStarred_specifiedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/starred", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", strings.Join([]string{mediaTypeStarringPreview, mediaTypeTopicsPreview}, ", ")) + testFormValues(t, r, values{ + "sort": "created", + "direction": "asc", + "page": "2", + }) + fmt.Fprint(w, `[{"starred_at":"2002-02-10T15:30:00Z","repo":{"id":2}}]`) + }) + + opt := &ActivityListStarredOptions{"created", "asc", ListOptions{Page: 2}} + repos, _, err := client.Activity.ListStarred(context.Background(), "u", opt) + if err != nil { + t.Errorf("Activity.ListStarred returned error: %v", err) + } + + want := []*StarredRepository{{StarredAt: &Timestamp{time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC)}, Repository: &Repository{ID: Int64(2)}}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Activity.ListStarred returned %+v, want %+v", repos, want) + } +} + +func TestActivityService_ListStarred_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Activity.ListStarred(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestActivityService_IsStarred_hasStar(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + star, _, err := client.Activity.IsStarred(context.Background(), "o", "r") + if err != nil { + t.Errorf("Activity.IsStarred returned error: %v", err) + } + if want := true; star != want { + t.Errorf("Activity.IsStarred returned %+v, want %+v", star, want) + } +} + +func TestActivityService_IsStarred_noStar(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + star, _, err := client.Activity.IsStarred(context.Background(), "o", "r") + if err != nil { + t.Errorf("Activity.IsStarred returned error: %v", err) + } + if want := false; star != want { + t.Errorf("Activity.IsStarred returned %+v, want %+v", star, want) + } +} + +func TestActivityService_IsStarred_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Activity.IsStarred(context.Background(), "%", "%") + testURLParseError(t, err) +} + +func TestActivityService_Star(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + }) + + _, err := client.Activity.Star(context.Background(), "o", "r") + if err != nil { + t.Errorf("Activity.Star returned error: %v", err) + } +} + +func TestActivityService_Star_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Activity.Star(context.Background(), "%", "%") + testURLParseError(t, err) +} + +func TestActivityService_Unstar(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Activity.Unstar(context.Background(), "o", "r") + if err != nil { + t.Errorf("Activity.Unstar returned error: %v", err) + } +} + +func TestActivityService_Unstar_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Activity.Unstar(context.Background(), "%", "%") + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/activity_test.go b/vendor/github.com/google/go-github/github/activity_test.go new file mode 100644 index 0000000..92b42d3 --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_test.go @@ -0,0 +1,129 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "net/http" + "reflect" + "testing" +) + +func TestActivityService_List(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/feeds", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + w.WriteHeader(http.StatusOK) + w.Write(feedsJSON) + }) + + got, _, err := client.Activity.ListFeeds(context.Background()) + if err != nil { + t.Errorf("Activity.ListFeeds returned error: %v", err) + } + if want := wantFeeds; !reflect.DeepEqual(got, want) { + t.Errorf("Activity.ListFeeds = %+v, want %+v", got, want) + } +} + +var feedsJSON = []byte(`{ + "timeline_url": "https://github.com/timeline", + "user_url": "https://github.com/{user}", + "current_user_public_url": "https://github.com/defunkt", + "current_user_url": "https://github.com/defunkt.private?token=abc123", + "current_user_actor_url": "https://github.com/defunkt.private.actor?token=abc123", + "current_user_organization_url": "", + "current_user_organization_urls": [ + "https://github.com/organizations/github/defunkt.private.atom?token=abc123" + ], + "_links": { + "timeline": { + "href": "https://github.com/timeline", + "type": "application/atom+xml" + }, + "user": { + "href": "https://github.com/{user}", + "type": "application/atom+xml" + }, + "current_user_public": { + "href": "https://github.com/defunkt", + "type": "application/atom+xml" + }, + "current_user": { + "href": "https://github.com/defunkt.private?token=abc123", + "type": "application/atom+xml" + }, + "current_user_actor": { + "href": "https://github.com/defunkt.private.actor?token=abc123", + "type": "application/atom+xml" + }, + "current_user_organization": { + "href": "", + "type": "" + }, + "current_user_organizations": [ + { + "href": "https://github.com/organizations/github/defunkt.private.atom?token=abc123", + "type": "application/atom+xml" + } + ] + } +}`) + +var wantFeeds = &Feeds{ + TimelineURL: String("https://github.com/timeline"), + UserURL: String("https://github.com/{user}"), + CurrentUserPublicURL: String("https://github.com/defunkt"), + CurrentUserURL: String("https://github.com/defunkt.private?token=abc123"), + CurrentUserActorURL: String("https://github.com/defunkt.private.actor?token=abc123"), + CurrentUserOrganizationURL: String(""), + CurrentUserOrganizationURLs: []string{ + "https://github.com/organizations/github/defunkt.private.atom?token=abc123", + }, + Links: &struct { + Timeline *FeedLink `json:"timeline,omitempty"` + User *FeedLink `json:"user,omitempty"` + CurrentUserPublic *FeedLink `json:"current_user_public,omitempty"` + CurrentUser *FeedLink `json:"current_user,omitempty"` + CurrentUserActor *FeedLink `json:"current_user_actor,omitempty"` + CurrentUserOrganization *FeedLink `json:"current_user_organization,omitempty"` + CurrentUserOrganizations []FeedLink `json:"current_user_organizations,omitempty"` + }{ + Timeline: &FeedLink{ + HRef: String("https://github.com/timeline"), + Type: String("application/atom+xml"), + }, + User: &FeedLink{ + HRef: String("https://github.com/{user}"), + Type: String("application/atom+xml"), + }, + CurrentUserPublic: &FeedLink{ + HRef: String("https://github.com/defunkt"), + Type: String("application/atom+xml"), + }, + CurrentUser: &FeedLink{ + HRef: String("https://github.com/defunkt.private?token=abc123"), + Type: String("application/atom+xml"), + }, + CurrentUserActor: &FeedLink{ + HRef: String("https://github.com/defunkt.private.actor?token=abc123"), + Type: String("application/atom+xml"), + }, + CurrentUserOrganization: &FeedLink{ + HRef: String(""), + Type: String(""), + }, + CurrentUserOrganizations: []FeedLink{ + { + HRef: String("https://github.com/organizations/github/defunkt.private.atom?token=abc123"), + Type: String("application/atom+xml"), + }, + }, + }, +} diff --git a/vendor/github.com/google/go-github/github/activity_watching.go b/vendor/github.com/google/go-github/github/activity_watching.go new file mode 100644 index 0000000..c749ca8 --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_watching.go @@ -0,0 +1,146 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// Subscription identifies a repository or thread subscription. +type Subscription struct { + Subscribed *bool `json:"subscribed,omitempty"` + Ignored *bool `json:"ignored,omitempty"` + Reason *string `json:"reason,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + URL *string `json:"url,omitempty"` + + // only populated for repository subscriptions + RepositoryURL *string `json:"repository_url,omitempty"` + + // only populated for thread subscriptions + ThreadURL *string `json:"thread_url,omitempty"` +} + +// ListWatchers lists watchers of a particular repo. +// +// GitHub API docs: https://developer.github.com/v3/activity/watching/#list-watchers +func (s *ActivityService) ListWatchers(ctx context.Context, owner, repo string, opt *ListOptions) ([]*User, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/subscribers", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var watchers []*User + resp, err := s.client.Do(ctx, req, &watchers) + if err != nil { + return nil, resp, err + } + + return watchers, resp, nil +} + +// ListWatched lists the repositories the specified user is watching. Passing +// the empty string will fetch watched repos for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/activity/watching/#list-repositories-being-watched +func (s *ActivityService) ListWatched(ctx context.Context, user string, opt *ListOptions) ([]*Repository, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/subscriptions", user) + } else { + u = "user/subscriptions" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var watched []*Repository + resp, err := s.client.Do(ctx, req, &watched) + if err != nil { + return nil, resp, err + } + + return watched, resp, nil +} + +// GetRepositorySubscription returns the subscription for the specified +// repository for the authenticated user. If the authenticated user is not +// watching the repository, a nil Subscription is returned. +// +// GitHub API docs: https://developer.github.com/v3/activity/watching/#get-a-repository-subscription +func (s *ActivityService) GetRepositorySubscription(ctx context.Context, owner, repo string) (*Subscription, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/subscription", owner, repo) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + sub := new(Subscription) + resp, err := s.client.Do(ctx, req, sub) + if err != nil { + // if it's just a 404, don't return that as an error + _, err = parseBoolResponse(err) + return nil, resp, err + } + + return sub, resp, nil +} + +// SetRepositorySubscription sets the subscription for the specified repository +// for the authenticated user. +// +// To watch a repository, set subscription.Subscribed to true. +// To ignore notifications made within a repository, set subscription.Ignored to true. +// To stop watching a repository, use DeleteRepositorySubscription. +// +// GitHub API docs: https://developer.github.com/v3/activity/watching/#set-a-repository-subscription +func (s *ActivityService) SetRepositorySubscription(ctx context.Context, owner, repo string, subscription *Subscription) (*Subscription, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/subscription", owner, repo) + + req, err := s.client.NewRequest("PUT", u, subscription) + if err != nil { + return nil, nil, err + } + + sub := new(Subscription) + resp, err := s.client.Do(ctx, req, sub) + if err != nil { + return nil, resp, err + } + + return sub, resp, nil +} + +// DeleteRepositorySubscription deletes the subscription for the specified +// repository for the authenticated user. +// +// This is used to stop watching a repository. To control whether or not to +// receive notifications from a repository, use SetRepositorySubscription. +// +// GitHub API docs: https://developer.github.com/v3/activity/watching/#delete-a-repository-subscription +func (s *ActivityService) DeleteRepositorySubscription(ctx context.Context, owner, repo string) (*Response, error) { + u := fmt.Sprintf("repos/%s/%s/subscription", owner, repo) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/activity_watching_test.go b/vendor/github.com/google/go-github/github/activity_watching_test.go new file mode 100644 index 0000000..b017d8d --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_watching_test.go @@ -0,0 +1,184 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestActivityService_ListWatchers(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/subscribers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + + fmt.Fprint(w, `[{"id":1}]`) + }) + + watchers, _, err := client.Activity.ListWatchers(context.Background(), "o", "r", &ListOptions{Page: 2}) + if err != nil { + t.Errorf("Activity.ListWatchers returned error: %v", err) + } + + want := []*User{{ID: Int64(1)}} + if !reflect.DeepEqual(watchers, want) { + t.Errorf("Activity.ListWatchers returned %+v, want %+v", watchers, want) + } +} + +func TestActivityService_ListWatched_authenticatedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/subscriptions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + watched, _, err := client.Activity.ListWatched(context.Background(), "", &ListOptions{Page: 2}) + if err != nil { + t.Errorf("Activity.ListWatched returned error: %v", err) + } + + want := []*Repository{{ID: Int64(1)}} + if !reflect.DeepEqual(watched, want) { + t.Errorf("Activity.ListWatched returned %+v, want %+v", watched, want) + } +} + +func TestActivityService_ListWatched_specifiedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/subscriptions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + watched, _, err := client.Activity.ListWatched(context.Background(), "u", &ListOptions{Page: 2}) + if err != nil { + t.Errorf("Activity.ListWatched returned error: %v", err) + } + + want := []*Repository{{ID: Int64(1)}} + if !reflect.DeepEqual(watched, want) { + t.Errorf("Activity.ListWatched returned %+v, want %+v", watched, want) + } +} + +func TestActivityService_GetRepositorySubscription_true(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"subscribed":true}`) + }) + + sub, _, err := client.Activity.GetRepositorySubscription(context.Background(), "o", "r") + if err != nil { + t.Errorf("Activity.GetRepositorySubscription returned error: %v", err) + } + + want := &Subscription{Subscribed: Bool(true)} + if !reflect.DeepEqual(sub, want) { + t.Errorf("Activity.GetRepositorySubscription returned %+v, want %+v", sub, want) + } +} + +func TestActivityService_GetRepositorySubscription_false(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + sub, _, err := client.Activity.GetRepositorySubscription(context.Background(), "o", "r") + if err != nil { + t.Errorf("Activity.GetRepositorySubscription returned error: %v", err) + } + + var want *Subscription + if !reflect.DeepEqual(sub, want) { + t.Errorf("Activity.GetRepositorySubscription returned %+v, want %+v", sub, want) + } +} + +func TestActivityService_GetRepositorySubscription_error(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusBadRequest) + }) + + _, _, err := client.Activity.GetRepositorySubscription(context.Background(), "o", "r") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } +} + +func TestActivityService_SetRepositorySubscription(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Subscription{Subscribed: Bool(true)} + + mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) { + v := new(Subscription) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"ignored":true}`) + }) + + sub, _, err := client.Activity.SetRepositorySubscription(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("Activity.SetRepositorySubscription returned error: %v", err) + } + + want := &Subscription{Ignored: Bool(true)} + if !reflect.DeepEqual(sub, want) { + t.Errorf("Activity.SetRepositorySubscription returned %+v, want %+v", sub, want) + } +} + +func TestActivityService_DeleteRepositorySubscription(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Activity.DeleteRepositorySubscription(context.Background(), "o", "r") + if err != nil { + t.Errorf("Activity.DeleteRepositorySubscription returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/admin.go b/vendor/github.com/google/go-github/github/admin.go new file mode 100644 index 0000000..2d96733 --- /dev/null +++ b/vendor/github.com/google/go-github/github/admin.go @@ -0,0 +1,101 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// AdminService handles communication with the admin related methods of the +// GitHub API. These API routes are normally only accessible for GitHub +// Enterprise installations. +// +// GitHub API docs: https://developer.github.com/v3/enterprise/ +type AdminService service + +// TeamLDAPMapping represents the mapping between a GitHub team and an LDAP group. +type TeamLDAPMapping struct { + ID *int64 `json:"id,omitempty"` + LDAPDN *string `json:"ldap_dn,omitempty"` + URL *string `json:"url,omitempty"` + Name *string `json:"name,omitempty"` + Slug *string `json:"slug,omitempty"` + Description *string `json:"description,omitempty"` + Privacy *string `json:"privacy,omitempty"` + Permission *string `json:"permission,omitempty"` + + MembersURL *string `json:"members_url,omitempty"` + RepositoriesURL *string `json:"repositories_url,omitempty"` +} + +func (m TeamLDAPMapping) String() string { + return Stringify(m) +} + +// UserLDAPMapping represents the mapping between a GitHub user and an LDAP user. +type UserLDAPMapping struct { + ID *int64 `json:"id,omitempty"` + LDAPDN *string `json:"ldap_dn,omitempty"` + Login *string `json:"login,omitempty"` + AvatarURL *string `json:"avatar_url,omitempty"` + GravatarID *string `json:"gravatar_id,omitempty"` + Type *string `json:"type,omitempty"` + SiteAdmin *bool `json:"site_admin,omitempty"` + + URL *string `json:"url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + FollowingURL *string `json:"following_url,omitempty"` + FollowersURL *string `json:"followers_url,omitempty"` + GistsURL *string `json:"gists_url,omitempty"` + OrganizationsURL *string `json:"organizations_url,omitempty"` + ReceivedEventsURL *string `json:"received_events_url,omitempty"` + ReposURL *string `json:"repos_url,omitempty"` + StarredURL *string `json:"starred_url,omitempty"` + SubscriptionsURL *string `json:"subscriptions_url,omitempty"` +} + +func (m UserLDAPMapping) String() string { + return Stringify(m) +} + +// UpdateUserLDAPMapping updates the mapping between a GitHub user and an LDAP user. +// +// GitHub API docs: https://developer.github.com/v3/enterprise/ldap/#update-ldap-mapping-for-a-user +func (s *AdminService) UpdateUserLDAPMapping(ctx context.Context, user string, mapping *UserLDAPMapping) (*UserLDAPMapping, *Response, error) { + u := fmt.Sprintf("admin/ldap/users/%v/mapping", user) + req, err := s.client.NewRequest("PATCH", u, mapping) + if err != nil { + return nil, nil, err + } + + m := new(UserLDAPMapping) + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// UpdateTeamLDAPMapping updates the mapping between a GitHub team and an LDAP group. +// +// GitHub API docs: https://developer.github.com/v3/enterprise/ldap/#update-ldap-mapping-for-a-team +func (s *AdminService) UpdateTeamLDAPMapping(ctx context.Context, team int64, mapping *TeamLDAPMapping) (*TeamLDAPMapping, *Response, error) { + u := fmt.Sprintf("admin/ldap/teams/%v/mapping", team) + req, err := s.client.NewRequest("PATCH", u, mapping) + if err != nil { + return nil, nil, err + } + + m := new(TeamLDAPMapping) + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/admin_stats.go b/vendor/github.com/google/go-github/github/admin_stats.go new file mode 100644 index 0000000..b5645f8 --- /dev/null +++ b/vendor/github.com/google/go-github/github/admin_stats.go @@ -0,0 +1,171 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// AdminStats represents a variety of stats of a Github Enterprise +// installation. +type AdminStats struct { + Issues *IssueStats `json:"issues,omitempty"` + Hooks *HookStats `json:"hooks,omitempty"` + Milestones *MilestoneStats `json:"milestones,omitempty"` + Orgs *OrgStats `json:"orgs,omitempty"` + Comments *CommentStats `json:"comments,omitempty"` + Pages *PageStats `json:"pages,omitempty"` + Users *UserStats `json:"users,omitempty"` + Gists *GistStats `json:"gists,omitempty"` + Pulls *PullStats `json:"pulls,omitempty"` + Repos *RepoStats `json:"repos,omitempty"` +} + +func (s AdminStats) String() string { + return Stringify(s) +} + +// IssueStats represents the number of total, open and closed issues. +type IssueStats struct { + TotalIssues *int `json:"total_issues,omitempty"` + OpenIssues *int `json:"open_issues,omitempty"` + ClosedIssues *int `json:"closed_issues,omitempty"` +} + +func (s IssueStats) String() string { + return Stringify(s) +} + +// HookStats represents the number of total, active and inactive hooks. +type HookStats struct { + TotalHooks *int `json:"total_hooks,omitempty"` + ActiveHooks *int `json:"active_hooks,omitempty"` + InactiveHooks *int `json:"inactive_hooks,omitempty"` +} + +func (s HookStats) String() string { + return Stringify(s) +} + +// MilestoneStats represents the number of total, open and close milestones. +type MilestoneStats struct { + TotalMilestones *int `json:"total_milestones,omitempty"` + OpenMilestones *int `json:"open_milestones,omitempty"` + ClosedMilestones *int `json:"closed_milestones,omitempty"` +} + +func (s MilestoneStats) String() string { + return Stringify(s) +} + +// OrgStats represents the number of total, disabled organizations and the team +// and team member count. +type OrgStats struct { + TotalOrgs *int `json:"total_orgs,omitempty"` + DisabledOrgs *int `json:"disabled_orgs,omitempty"` + TotalTeams *int `json:"total_teams,omitempty"` + TotalTeamMembers *int `json:"total_team_members,omitempty"` +} + +func (s OrgStats) String() string { + return Stringify(s) +} + +// CommentStats represents the number of total comments on commits, gists, issues +// and pull requests. +type CommentStats struct { + TotalCommitComments *int `json:"total_commit_comments,omitempty"` + TotalGistComments *int `json:"total_gist_comments,omitempty"` + TotalIssueComments *int `json:"total_issue_comments,omitempty"` + TotalPullRequestComments *int `json:"total_pull_request_comments,omitempty"` +} + +func (s CommentStats) String() string { + return Stringify(s) +} + +// PageStats represents the total number of github pages. +type PageStats struct { + TotalPages *int `json:"total_pages,omitempty"` +} + +func (s PageStats) String() string { + return Stringify(s) +} + +// UserStats represents the number of total, admin and suspended users. +type UserStats struct { + TotalUsers *int `json:"total_users,omitempty"` + AdminUsers *int `json:"admin_users,omitempty"` + SuspendedUsers *int `json:"suspended_users,omitempty"` +} + +func (s UserStats) String() string { + return Stringify(s) +} + +// GistStats represents the number of total, private and public gists. +type GistStats struct { + TotalGists *int `json:"total_gists,omitempty"` + PrivateGists *int `json:"private_gists,omitempty"` + PublicGists *int `json:"public_gists,omitempty"` +} + +func (s GistStats) String() string { + return Stringify(s) +} + +// PullStats represents the number of total, merged, mergable and unmergeable +// pull-requests. +type PullStats struct { + TotalPulls *int `json:"total_pulls,omitempty"` + MergedPulls *int `json:"merged_pulls,omitempty"` + MergablePulls *int `json:"mergeable_pulls,omitempty"` + UnmergablePulls *int `json:"unmergeable_pulls,omitempty"` +} + +func (s PullStats) String() string { + return Stringify(s) +} + +// RepoStats represents the number of total, root, fork, organization repositories +// together with the total number of pushes and wikis. +type RepoStats struct { + TotalRepos *int `json:"total_repos,omitempty"` + RootRepos *int `json:"root_repos,omitempty"` + ForkRepos *int `json:"fork_repos,omitempty"` + OrgRepos *int `json:"org_repos,omitempty"` + TotalPushes *int `json:"total_pushes,omitempty"` + TotalWikis *int `json:"total_wikis,omitempty"` +} + +func (s RepoStats) String() string { + return Stringify(s) +} + +// GetAdminStats returns a variety of metrics about a Github Enterprise +// installation. +// +// Please note that this is only available to site administrators, +// otherwise it will error with a 404 not found (instead of 401 or 403). +// +// GitHub API docs: https://developer.github.com/v3/enterprise-admin/admin_stats/ +func (s *AdminService) GetAdminStats(ctx context.Context) (*AdminStats, *Response, error) { + u := fmt.Sprintf("enterprise/stats/all") + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + m := new(AdminStats) + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/admin_stats_test.go b/vendor/github.com/google/go-github/github/admin_stats_test.go new file mode 100644 index 0000000..9433adc --- /dev/null +++ b/vendor/github.com/google/go-github/github/admin_stats_test.go @@ -0,0 +1,142 @@ +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestAdminService_GetAdminStats(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/enterprise/stats/all", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, ` +{ + "repos": { + "total_repos": 212, + "root_repos": 194, + "fork_repos": 18, + "org_repos": 51, + "total_pushes": 3082, + "total_wikis": 15 + }, + "hooks": { + "total_hooks": 27, + "active_hooks": 23, + "inactive_hooks": 4 + }, + "pages": { + "total_pages": 36 + }, + "orgs": { + "total_orgs": 33, + "disabled_orgs": 0, + "total_teams": 60, + "total_team_members": 314 + }, + "users": { + "total_users": 254, + "admin_users": 45, + "suspended_users": 21 + }, + "pulls": { + "total_pulls": 86, + "merged_pulls": 60, + "mergeable_pulls": 21, + "unmergeable_pulls": 3 + }, + "issues": { + "total_issues": 179, + "open_issues": 83, + "closed_issues": 96 + }, + "milestones": { + "total_milestones": 7, + "open_milestones": 6, + "closed_milestones": 1 + }, + "gists": { + "total_gists": 178, + "private_gists": 151, + "public_gists": 25 + }, + "comments": { + "total_commit_comments": 6, + "total_gist_comments": 28, + "total_issue_comments": 366, + "total_pull_request_comments": 30 + } +} +`) + }) + + stats, _, err := client.Admin.GetAdminStats(context.Background()) + if err != nil { + t.Errorf("AdminService.GetAdminStats returned error: %v", err) + } + + want := &AdminStats{ + Repos: &RepoStats{ + TotalRepos: Int(212), + RootRepos: Int(194), + ForkRepos: Int(18), + OrgRepos: Int(51), + TotalPushes: Int(3082), + TotalWikis: Int(15), + }, + Hooks: &HookStats{ + TotalHooks: Int(27), + ActiveHooks: Int(23), + InactiveHooks: Int(4), + }, + Pages: &PageStats{ + TotalPages: Int(36), + }, + Orgs: &OrgStats{ + TotalOrgs: Int(33), + DisabledOrgs: Int(0), + TotalTeams: Int(60), + TotalTeamMembers: Int(314), + }, + Users: &UserStats{ + TotalUsers: Int(254), + AdminUsers: Int(45), + SuspendedUsers: Int(21), + }, + Pulls: &PullStats{ + TotalPulls: Int(86), + MergedPulls: Int(60), + MergablePulls: Int(21), + UnmergablePulls: Int(3), + }, + Issues: &IssueStats{ + TotalIssues: Int(179), + OpenIssues: Int(83), + ClosedIssues: Int(96), + }, + Milestones: &MilestoneStats{ + TotalMilestones: Int(7), + OpenMilestones: Int(6), + ClosedMilestones: Int(1), + }, + Gists: &GistStats{ + TotalGists: Int(178), + PrivateGists: Int(151), + PublicGists: Int(25), + }, + Comments: &CommentStats{ + TotalCommitComments: Int(6), + TotalGistComments: Int(28), + TotalIssueComments: Int(366), + TotalPullRequestComments: Int(30), + }, + } + if !reflect.DeepEqual(stats, want) { + t.Errorf("AdminService.GetAdminStats returned %+v, want %+v", stats, want) + } +} diff --git a/vendor/github.com/google/go-github/github/admin_test.go b/vendor/github.com/google/go-github/github/admin_test.go new file mode 100644 index 0000000..8979686 --- /dev/null +++ b/vendor/github.com/google/go-github/github/admin_test.go @@ -0,0 +1,81 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestAdminService_UpdateUserLDAPMapping(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &UserLDAPMapping{ + LDAPDN: String("uid=asdf,ou=users,dc=github,dc=com"), + } + + mux.HandleFunc("/admin/ldap/users/u/mapping", func(w http.ResponseWriter, r *http.Request) { + v := new(UserLDAPMapping) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + fmt.Fprint(w, `{"id":1,"ldap_dn":"uid=asdf,ou=users,dc=github,dc=com"}`) + }) + + mapping, _, err := client.Admin.UpdateUserLDAPMapping(context.Background(), "u", input) + if err != nil { + t.Errorf("Admin.UpdateUserLDAPMapping returned error: %v", err) + } + + want := &UserLDAPMapping{ + ID: Int64(1), + LDAPDN: String("uid=asdf,ou=users,dc=github,dc=com"), + } + if !reflect.DeepEqual(mapping, want) { + t.Errorf("Admin.UpdateUserLDAPMapping returned %+v, want %+v", mapping, want) + } +} + +func TestAdminService_UpdateTeamLDAPMapping(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &TeamLDAPMapping{ + LDAPDN: String("cn=Enterprise Ops,ou=teams,dc=github,dc=com"), + } + + mux.HandleFunc("/admin/ldap/teams/1/mapping", func(w http.ResponseWriter, r *http.Request) { + v := new(TeamLDAPMapping) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + fmt.Fprint(w, `{"id":1,"ldap_dn":"cn=Enterprise Ops,ou=teams,dc=github,dc=com"}`) + }) + + mapping, _, err := client.Admin.UpdateTeamLDAPMapping(context.Background(), 1, input) + if err != nil { + t.Errorf("Admin.UpdateTeamLDAPMapping returned error: %v", err) + } + + want := &TeamLDAPMapping{ + ID: Int64(1), + LDAPDN: String("cn=Enterprise Ops,ou=teams,dc=github,dc=com"), + } + if !reflect.DeepEqual(mapping, want) { + t.Errorf("Admin.UpdateTeamLDAPMapping returned %+v, want %+v", mapping, want) + } +} diff --git a/vendor/github.com/google/go-github/github/apps.go b/vendor/github.com/google/go-github/github/apps.go new file mode 100644 index 0000000..32d4f2f --- /dev/null +++ b/vendor/github.com/google/go-github/github/apps.go @@ -0,0 +1,230 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// AppsService provides access to the installation related functions +// in the GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/apps/ +type AppsService service + +// App represents a GitHub App. +type App struct { + ID *int64 `json:"id,omitempty"` + NodeID *string `json:"node_id,omitempty"` + Owner *User `json:"owner,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + ExternalURL *string `json:"external_url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +// InstallationToken represents an installation token. +type InstallationToken struct { + Token *string `json:"token,omitempty"` + ExpiresAt *time.Time `json:"expires_at,omitempty"` +} + +// InstallationPermissions lists the permissions for metadata, contents, issues and single file for an installation. +type InstallationPermissions struct { + Metadata *string `json:"metadata,omitempty"` + Contents *string `json:"contents,omitempty"` + Issues *string `json:"issues,omitempty"` + SingleFile *string `json:"single_file,omitempty"` +} + +// Installation represents a GitHub Apps installation. +type Installation struct { + ID *int64 `json:"id,omitempty"` + AppID *int64 `json:"app_id,omitempty"` + TargetID *int64 `json:"target_id,omitempty"` + Account *User `json:"account,omitempty"` + AccessTokensURL *string `json:"access_tokens_url,omitempty"` + RepositoriesURL *string `json:"repositories_url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + TargetType *string `json:"target_type,omitempty"` + SingleFileName *string `json:"single_file_name,omitempty"` + RepositorySelection *string `json:"repository_selection,omitempty"` + Events []string `json:"events,omitempty"` + Permissions *InstallationPermissions `json:"permissions,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` +} + +func (i Installation) String() string { + return Stringify(i) +} + +// Get a single GitHub App. Passing the empty string will get +// the authenticated GitHub App. +// +// Note: appSlug is just the URL-friendly name of your GitHub App. +// You can find this on the settings page for your GitHub App +// (e.g., https://github.com/settings/apps/:app_slug). +// +// GitHub API docs: https://developer.github.com/v3/apps/#get-a-single-github-app +func (s *AppsService) Get(ctx context.Context, appSlug string) (*App, *Response, error) { + var u string + if appSlug != "" { + u = fmt.Sprintf("apps/%v", appSlug) + } else { + u = "app" + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeIntegrationPreview) + + app := new(App) + resp, err := s.client.Do(ctx, req, app) + if err != nil { + return nil, resp, err + } + + return app, resp, nil +} + +// ListInstallations lists the installations that the current GitHub App has. +// +// GitHub API docs: https://developer.github.com/v3/apps/#find-installations +func (s *AppsService) ListInstallations(ctx context.Context, opt *ListOptions) ([]*Installation, *Response, error) { + u, err := addOptions("app/installations", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeIntegrationPreview) + + var i []*Installation + resp, err := s.client.Do(ctx, req, &i) + if err != nil { + return nil, resp, err + } + + return i, resp, nil +} + +// GetInstallation returns the specified installation. +// +// GitHub API docs: https://developer.github.com/v3/apps/#get-a-single-installation +func (s *AppsService) GetInstallation(ctx context.Context, id int64) (*Installation, *Response, error) { + return s.getInstallation(ctx, fmt.Sprintf("app/installations/%v", id)) +} + +// ListUserInstallations lists installations that are accessible to the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/apps/#list-installations-for-user +func (s *AppsService) ListUserInstallations(ctx context.Context, opt *ListOptions) ([]*Installation, *Response, error) { + u, err := addOptions("user/installations", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeIntegrationPreview) + + var i struct { + Installations []*Installation `json:"installations"` + } + resp, err := s.client.Do(ctx, req, &i) + if err != nil { + return nil, resp, err + } + + return i.Installations, resp, nil +} + +// CreateInstallationToken creates a new installation token. +// +// GitHub API docs: https://developer.github.com/v3/apps/#create-a-new-installation-token +func (s *AppsService) CreateInstallationToken(ctx context.Context, id int64) (*InstallationToken, *Response, error) { + u := fmt.Sprintf("installations/%v/access_tokens", id) + + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeIntegrationPreview) + + t := new(InstallationToken) + resp, err := s.client.Do(ctx, req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, nil +} + +// FindOrganizationInstallation finds the organization's installation information. +// +// GitHub API docs: https://developer.github.com/v3/apps/#find-organization-installation +func (s *AppsService) FindOrganizationInstallation(ctx context.Context, org string) (*Installation, *Response, error) { + return s.getInstallation(ctx, fmt.Sprintf("orgs/%v/installation", org)) +} + +// FindRepositoryInstallation finds the repository's installation information. +// +// GitHub API docs: https://developer.github.com/v3/apps/#find-repository-installation +func (s *AppsService) FindRepositoryInstallation(ctx context.Context, owner, repo string) (*Installation, *Response, error) { + return s.getInstallation(ctx, fmt.Sprintf("repos/%v/%v/installation", owner, repo)) +} + +// FindRepositoryInstallationByID finds the repository's installation information. +// +// Note: FindRepositoryInstallationByID uses the undocumented GitHub API endpoint /repositories/:id/installation. +func (s *AppsService) FindRepositoryInstallationByID(ctx context.Context, id int64) (*Installation, *Response, error) { + return s.getInstallation(ctx, fmt.Sprintf("repositories/%d/installation", id)) +} + +// FindUserInstallation finds the user's installation information. +// +// GitHub API docs: https://developer.github.com/v3/apps/#find-repository-installation +func (s *AppsService) FindUserInstallation(ctx context.Context, user string) (*Installation, *Response, error) { + return s.getInstallation(ctx, fmt.Sprintf("users/%v/installation", user)) +} + +func (s *AppsService) getInstallation(ctx context.Context, url string) (*Installation, *Response, error) { + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeIntegrationPreview) + + i := new(Installation) + resp, err := s.client.Do(ctx, req, i) + if err != nil { + return nil, resp, err + } + + return i, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/apps_installation.go b/vendor/github.com/google/go-github/github/apps_installation.go new file mode 100644 index 0000000..ccfecb8 --- /dev/null +++ b/vendor/github.com/google/go-github/github/apps_installation.go @@ -0,0 +1,101 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ListRepos lists the repositories that are accessible to the authenticated installation. +// +// GitHub API docs: https://developer.github.com/v3/apps/installations/#list-repositories +func (s *AppsService) ListRepos(ctx context.Context, opt *ListOptions) ([]*Repository, *Response, error) { + u, err := addOptions("installation/repositories", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeIntegrationPreview) + + var r struct { + Repositories []*Repository `json:"repositories"` + } + resp, err := s.client.Do(ctx, req, &r) + if err != nil { + return nil, resp, err + } + + return r.Repositories, resp, nil +} + +// ListUserRepos lists repositories that are accessible +// to the authenticated user for an installation. +// +// GitHub API docs: https://developer.github.com/v3/apps/installations/#list-repositories-accessible-to-the-user-for-an-installation +func (s *AppsService) ListUserRepos(ctx context.Context, id int64, opt *ListOptions) ([]*Repository, *Response, error) { + u := fmt.Sprintf("user/installations/%v/repositories", id) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeIntegrationPreview) + + var r struct { + Repositories []*Repository `json:"repositories"` + } + resp, err := s.client.Do(ctx, req, &r) + if err != nil { + return nil, resp, err + } + + return r.Repositories, resp, nil +} + +// AddRepository adds a single repository to an installation. +// +// GitHub API docs: https://developer.github.com/v3/apps/installations/#add-repository-to-installation +func (s *AppsService) AddRepository(ctx context.Context, instID, repoID int64) (*Repository, *Response, error) { + u := fmt.Sprintf("apps/installations/%v/repositories/%v", instID, repoID) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, nil, err + } + + r := new(Repository) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// RemoveRepository removes a single repository from an installation. +// +// GitHub docs: https://developer.github.com/v3/apps/installations/#remove-repository-from-installation +func (s *AppsService) RemoveRepository(ctx context.Context, instID, repoID int64) (*Response, error) { + u := fmt.Sprintf("apps/installations/%v/repositories/%v", instID, repoID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/apps_installation_test.go b/vendor/github.com/google/go-github/github/apps_installation_test.go new file mode 100644 index 0000000..01358c0 --- /dev/null +++ b/vendor/github.com/google/go-github/github/apps_installation_test.go @@ -0,0 +1,101 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestAppsService_ListRepos(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/installation/repositories", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `{"repositories": [{"id":1}]}`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + repositories, _, err := client.Apps.ListRepos(context.Background(), opt) + if err != nil { + t.Errorf("Apps.ListRepos returned error: %v", err) + } + + want := []*Repository{{ID: Int64(1)}} + if !reflect.DeepEqual(repositories, want) { + t.Errorf("Apps.ListRepos returned %+v, want %+v", repositories, want) + } +} + +func TestAppsService_ListUserRepos(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/installations/1/repositories", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `{"repositories": [{"id":1}]}`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + repositories, _, err := client.Apps.ListUserRepos(context.Background(), 1, opt) + if err != nil { + t.Errorf("Apps.ListUserRepos returned error: %v", err) + } + + want := []*Repository{{ID: Int64(1)}} + if !reflect.DeepEqual(repositories, want) { + t.Errorf("Apps.ListUserRepos returned %+v, want %+v", repositories, want) + } +} + +func TestAppsService_AddRepository(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/apps/installations/1/repositories/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + fmt.Fprint(w, `{"id":1,"name":"n","description":"d","owner":{"login":"l"},"license":{"key":"mit"}}`) + }) + + repo, _, err := client.Apps.AddRepository(context.Background(), 1, 1) + if err != nil { + t.Errorf("Apps.AddRepository returned error: %v", err) + } + + want := &Repository{ID: Int64(1), Name: String("n"), Description: String("d"), Owner: &User{Login: String("l")}, License: &License{Key: String("mit")}} + if !reflect.DeepEqual(repo, want) { + t.Errorf("AddRepository returned %+v, want %+v", repo, want) + } +} + +func TestAppsService_RemoveRepository(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/apps/installations/1/repositories/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Apps.RemoveRepository(context.Background(), 1, 1) + if err != nil { + t.Errorf("Apps.RemoveRepository returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/apps_marketplace.go b/vendor/github.com/google/go-github/github/apps_marketplace.go new file mode 100644 index 0000000..3f35b91 --- /dev/null +++ b/vendor/github.com/google/go-github/github/apps_marketplace.go @@ -0,0 +1,168 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// MarketplaceService handles communication with the marketplace related +// methods of the GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/apps/marketplace/ +type MarketplaceService struct { + client *Client + // Stubbed controls whether endpoints that return stubbed data are used + // instead of production endpoints. Stubbed data is fake data that's useful + // for testing your GitHub Apps. Stubbed data is hard-coded and will not + // change based on actual subscriptions. + // + // GitHub API docs: https://developer.github.com/v3/apps/marketplace/ + Stubbed bool +} + +// MarketplacePlan represents a GitHub Apps Marketplace Listing Plan. +type MarketplacePlan struct { + URL *string `json:"url,omitempty"` + AccountsURL *string `json:"accounts_url,omitempty"` + ID *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + MonthlyPriceInCents *int `json:"monthly_price_in_cents,omitempty"` + YearlyPriceInCents *int `json:"yearly_price_in_cents,omitempty"` + PriceModel *string `json:"price_model,omitempty"` + UnitName *string `json:"unit_name,omitempty"` + Bullets *[]string `json:"bullets,omitempty"` +} + +// MarketplacePurchase represents a GitHub Apps Marketplace Purchase. +type MarketplacePurchase struct { + BillingCycle *string `json:"billing_cycle,omitempty"` + NextBillingDate *string `json:"next_billing_date,omitempty"` + UnitCount *int `json:"unit_count,omitempty"` + Plan *MarketplacePlan `json:"plan,omitempty"` + Account *MarketplacePlanAccount `json:"account,omitempty"` +} + +// MarketplacePlanAccount represents a GitHub Account (user or organization) on a specific plan. +type MarketplacePlanAccount struct { + URL *string `json:"url,omitempty"` + Type *string `json:"type,omitempty"` + ID *int64 `json:"id,omitempty"` + Login *string `json:"login,omitempty"` + Email *string `json:"email,omitempty"` + OrganizationBillingEmail *string `json:"organization_billing_email,omitempty"` + MarketplacePurchase *MarketplacePurchase `json:"marketplace_purchase,omitempty"` +} + +// ListPlans lists all plans for your Marketplace listing. +// +// GitHub API docs: https://developer.github.com/v3/apps/marketplace/#list-all-plans-for-your-marketplace-listing +func (s *MarketplaceService) ListPlans(ctx context.Context, opt *ListOptions) ([]*MarketplacePlan, *Response, error) { + uri := s.marketplaceURI("plans") + u, err := addOptions(uri, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var plans []*MarketplacePlan + resp, err := s.client.Do(ctx, req, &plans) + if err != nil { + return nil, resp, err + } + + return plans, resp, nil +} + +// ListPlanAccountsForPlan lists all GitHub accounts (user or organization) on a specific plan. +// +// GitHub API docs: https://developer.github.com/v3/apps/marketplace/#list-all-github-accounts-user-or-organization-on-a-specific-plan +func (s *MarketplaceService) ListPlanAccountsForPlan(ctx context.Context, planID int64, opt *ListOptions) ([]*MarketplacePlanAccount, *Response, error) { + uri := s.marketplaceURI(fmt.Sprintf("plans/%v/accounts", planID)) + u, err := addOptions(uri, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var accounts []*MarketplacePlanAccount + resp, err := s.client.Do(ctx, req, &accounts) + if err != nil { + return nil, resp, err + } + + return accounts, resp, nil +} + +// ListPlanAccountsForAccount lists all GitHub accounts (user or organization) associated with an account. +// +// GitHub API docs: https://developer.github.com/v3/apps/marketplace/#check-if-a-github-account-is-associated-with-any-marketplace-listing +func (s *MarketplaceService) ListPlanAccountsForAccount(ctx context.Context, accountID int64, opt *ListOptions) ([]*MarketplacePlanAccount, *Response, error) { + uri := s.marketplaceURI(fmt.Sprintf("accounts/%v", accountID)) + u, err := addOptions(uri, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var accounts []*MarketplacePlanAccount + resp, err := s.client.Do(ctx, req, &accounts) + if err != nil { + return nil, resp, err + } + + return accounts, resp, nil +} + +// ListMarketplacePurchasesForUser lists all GitHub marketplace purchases made by a user. +// +// GitHub API docs: https://developer.github.com/v3/apps/marketplace/#get-a-users-marketplace-purchases +func (s *MarketplaceService) ListMarketplacePurchasesForUser(ctx context.Context, opt *ListOptions) ([]*MarketplacePurchase, *Response, error) { + uri := "user/marketplace_purchases" + if s.Stubbed { + uri = "user/marketplace_purchases/stubbed" + } + + u, err := addOptions(uri, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var purchases []*MarketplacePurchase + resp, err := s.client.Do(ctx, req, &purchases) + if err != nil { + return nil, resp, err + } + + return purchases, resp, nil +} + +func (s *MarketplaceService) marketplaceURI(endpoint string) string { + url := "marketplace_listing" + if s.Stubbed { + url = "marketplace_listing/stubbed" + } + return url + "/" + endpoint +} diff --git a/vendor/github.com/google/go-github/github/apps_marketplace_test.go b/vendor/github.com/google/go-github/github/apps_marketplace_test.go new file mode 100644 index 0000000..0c1f86c --- /dev/null +++ b/vendor/github.com/google/go-github/github/apps_marketplace_test.go @@ -0,0 +1,194 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestMarketplaceService_ListPlans(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/marketplace_listing/plans", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + client.Marketplace.Stubbed = false + plans, _, err := client.Marketplace.ListPlans(context.Background(), opt) + if err != nil { + t.Errorf("Marketplace.ListPlans returned error: %v", err) + } + + want := []*MarketplacePlan{{ID: Int64(1)}} + if !reflect.DeepEqual(plans, want) { + t.Errorf("Marketplace.ListPlans returned %+v, want %+v", plans, want) + } +} + +func TestMarketplaceService_Stubbed_ListPlans(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/marketplace_listing/stubbed/plans", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + client.Marketplace.Stubbed = true + plans, _, err := client.Marketplace.ListPlans(context.Background(), opt) + if err != nil { + t.Errorf("Marketplace.ListPlans (Stubbed) returned error: %v", err) + } + + want := []*MarketplacePlan{{ID: Int64(1)}} + if !reflect.DeepEqual(plans, want) { + t.Errorf("Marketplace.ListPlans (Stubbed) returned %+v, want %+v", plans, want) + } +} + +func TestMarketplaceService_ListPlanAccountsForPlan(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/marketplace_listing/plans/1/accounts", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + client.Marketplace.Stubbed = false + accounts, _, err := client.Marketplace.ListPlanAccountsForPlan(context.Background(), 1, opt) + if err != nil { + t.Errorf("Marketplace.ListPlanAccountsForPlan returned error: %v", err) + } + + want := []*MarketplacePlanAccount{{ID: Int64(1)}} + if !reflect.DeepEqual(accounts, want) { + t.Errorf("Marketplace.ListPlanAccountsForPlan returned %+v, want %+v", accounts, want) + } +} + +func TestMarketplaceService_Stubbed_ListPlanAccountsForPlan(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/marketplace_listing/stubbed/plans/1/accounts", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + client.Marketplace.Stubbed = true + accounts, _, err := client.Marketplace.ListPlanAccountsForPlan(context.Background(), 1, opt) + if err != nil { + t.Errorf("Marketplace.ListPlanAccountsForPlan (Stubbed) returned error: %v", err) + } + + want := []*MarketplacePlanAccount{{ID: Int64(1)}} + if !reflect.DeepEqual(accounts, want) { + t.Errorf("Marketplace.ListPlanAccountsForPlan (Stubbed) returned %+v, want %+v", accounts, want) + } +} + +func TestMarketplaceService_ListPlanAccountsForAccount(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/marketplace_listing/accounts/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + client.Marketplace.Stubbed = false + accounts, _, err := client.Marketplace.ListPlanAccountsForAccount(context.Background(), 1, opt) + if err != nil { + t.Errorf("Marketplace.ListPlanAccountsForAccount returned error: %v", err) + } + + want := []*MarketplacePlanAccount{{ID: Int64(1)}} + if !reflect.DeepEqual(accounts, want) { + t.Errorf("Marketplace.ListPlanAccountsForAccount returned %+v, want %+v", accounts, want) + } +} + +func TestMarketplaceService_Stubbed_ListPlanAccountsForAccount(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/marketplace_listing/stubbed/accounts/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + client.Marketplace.Stubbed = true + accounts, _, err := client.Marketplace.ListPlanAccountsForAccount(context.Background(), 1, opt) + if err != nil { + t.Errorf("Marketplace.ListPlanAccountsForAccount (Stubbed) returned error: %v", err) + } + + want := []*MarketplacePlanAccount{{ID: Int64(1)}} + if !reflect.DeepEqual(accounts, want) { + t.Errorf("Marketplace.ListPlanAccountsForAccount (Stubbed) returned %+v, want %+v", accounts, want) + } +} + +func TestMarketplaceService_ListMarketplacePurchasesForUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/marketplace_purchases", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"billing_cycle":"monthly"}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + client.Marketplace.Stubbed = false + purchases, _, err := client.Marketplace.ListMarketplacePurchasesForUser(context.Background(), opt) + if err != nil { + t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned error: %v", err) + } + + want := []*MarketplacePurchase{{BillingCycle: String("monthly")}} + if !reflect.DeepEqual(purchases, want) { + t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned %+v, want %+v", purchases, want) + } +} + +func TestMarketplaceService_Stubbed_ListMarketplacePurchasesForUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/marketplace_purchases/stubbed", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"billing_cycle":"monthly"}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + client.Marketplace.Stubbed = true + purchases, _, err := client.Marketplace.ListMarketplacePurchasesForUser(context.Background(), opt) + if err != nil { + t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned error: %v", err) + } + + want := []*MarketplacePurchase{{BillingCycle: String("monthly")}} + if !reflect.DeepEqual(purchases, want) { + t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned %+v, want %+v", purchases, want) + } +} diff --git a/vendor/github.com/google/go-github/github/apps_test.go b/vendor/github.com/google/go-github/github/apps_test.go new file mode 100644 index 0000000..f56c1c4 --- /dev/null +++ b/vendor/github.com/google/go-github/github/apps_test.go @@ -0,0 +1,270 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestAppsService_Get_authenticatedApp(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/app", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + fmt.Fprint(w, `{"id":1}`) + }) + + app, _, err := client.Apps.Get(context.Background(), "") + if err != nil { + t.Errorf("Apps.Get returned error: %v", err) + } + + want := &App{ID: Int64(1)} + if !reflect.DeepEqual(app, want) { + t.Errorf("Apps.Get returned %+v, want %+v", app, want) + } +} + +func TestAppsService_Get_specifiedApp(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/apps/a", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + fmt.Fprint(w, `{"html_url":"https://github.com/apps/a"}`) + }) + + app, _, err := client.Apps.Get(context.Background(), "a") + if err != nil { + t.Errorf("Apps.Get returned error: %v", err) + } + + want := &App{HTMLURL: String("https://github.com/apps/a")} + if !reflect.DeepEqual(app, want) { + t.Errorf("Apps.Get returned %+v, want %+v", *app.HTMLURL, *want.HTMLURL) + } +} + +func TestAppsService_ListInstallations(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/app/installations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `[{ + "id":1, + "app_id":1, + "target_id":1, + "target_type": "Organization", + "permissions": { + "metadata": "read", + "contents": "read", + "issues": "write", + "single_file": "write" + }, + "events": [ + "push", + "pull_request" + ], + "single_file_name": "config.yml", + "repository_selection": "selected", + "created_at": "2018-01-01T00:00:00Z", + "updated_at": "2018-01-01T00:00:00Z"}]`, + ) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + installations, _, err := client.Apps.ListInstallations(context.Background(), opt) + if err != nil { + t.Errorf("Apps.ListInstallations returned error: %v", err) + } + + date := Timestamp{Time: time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC)} + want := []*Installation{{ + ID: Int64(1), + AppID: Int64(1), + TargetID: Int64(1), + TargetType: String("Organization"), + SingleFileName: String("config.yml"), + RepositorySelection: String("selected"), + Permissions: &InstallationPermissions{ + Metadata: String("read"), + Contents: String("read"), + Issues: String("write"), + SingleFile: String("write")}, + Events: []string{"push", "pull_request"}, + CreatedAt: &date, + UpdatedAt: &date, + }} + if !reflect.DeepEqual(installations, want) { + t.Errorf("Apps.ListInstallations returned %+v, want %+v", installations, want) + } +} + +func TestAppsService_GetInstallation(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/app/installations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + fmt.Fprint(w, `{"id":1, "app_id":1, "target_id":1, "target_type": "Organization"}`) + }) + + installation, _, err := client.Apps.GetInstallation(context.Background(), 1) + if err != nil { + t.Errorf("Apps.GetInstallation returned error: %v", err) + } + + want := &Installation{ID: Int64(1), AppID: Int64(1), TargetID: Int64(1), TargetType: String("Organization")} + if !reflect.DeepEqual(installation, want) { + t.Errorf("Apps.GetInstallation returned %+v, want %+v", installation, want) + } +} + +func TestAppsService_ListUserInstallations(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/installations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `{"installations":[{"id":1, "app_id":1, "target_id":1, "target_type": "Organization"}]}`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + installations, _, err := client.Apps.ListUserInstallations(context.Background(), opt) + if err != nil { + t.Errorf("Apps.ListUserInstallations returned error: %v", err) + } + + want := []*Installation{{ID: Int64(1), AppID: Int64(1), TargetID: Int64(1), TargetType: String("Organization")}} + if !reflect.DeepEqual(installations, want) { + t.Errorf("Apps.ListUserInstallations returned %+v, want %+v", installations, want) + } +} + +func TestAppsService_CreateInstallationToken(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/installations/1/access_tokens", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + fmt.Fprint(w, `{"token":"t"}`) + }) + + token, _, err := client.Apps.CreateInstallationToken(context.Background(), 1) + if err != nil { + t.Errorf("Apps.CreateInstallationToken returned error: %v", err) + } + + want := &InstallationToken{Token: String("t")} + if !reflect.DeepEqual(token, want) { + t.Errorf("Apps.CreateInstallationToken returned %+v, want %+v", token, want) + } +} + +func TestAppsService_FindOrganizationInstallation(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/installation", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + fmt.Fprint(w, `{"id":1, "app_id":1, "target_id":1, "target_type": "Organization"}`) + }) + + installation, _, err := client.Apps.FindOrganizationInstallation(context.Background(), "o") + if err != nil { + t.Errorf("Apps.FindOrganizationInstallation returned error: %v", err) + } + + want := &Installation{ID: Int64(1), AppID: Int64(1), TargetID: Int64(1), TargetType: String("Organization")} + if !reflect.DeepEqual(installation, want) { + t.Errorf("Apps.FindOrganizationInstallation returned %+v, want %+v", installation, want) + } +} + +func TestAppsService_FindRepositoryInstallation(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/installation", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + fmt.Fprint(w, `{"id":1, "app_id":1, "target_id":1, "target_type": "Organization"}`) + }) + + installation, _, err := client.Apps.FindRepositoryInstallation(context.Background(), "o", "r") + if err != nil { + t.Errorf("Apps.FindRepositoryInstallation returned error: %v", err) + } + + want := &Installation{ID: Int64(1), AppID: Int64(1), TargetID: Int64(1), TargetType: String("Organization")} + if !reflect.DeepEqual(installation, want) { + t.Errorf("Apps.FindRepositoryInstallation returned %+v, want %+v", installation, want) + } +} + +func TestAppsService_FindRepositoryInstallationByID(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repositories/1/installation", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + fmt.Fprint(w, `{"id":1, "app_id":1, "target_id":1, "target_type": "Organization"}`) + }) + + installation, _, err := client.Apps.FindRepositoryInstallationByID(context.Background(), 1) + if err != nil { + t.Errorf("Apps.FindRepositoryInstallationByID returned error: %v", err) + } + + want := &Installation{ID: Int64(1), AppID: Int64(1), TargetID: Int64(1), TargetType: String("Organization")} + if !reflect.DeepEqual(installation, want) { + t.Errorf("Apps.FindRepositoryInstallationByID returned %+v, want %+v", installation, want) + } +} + +func TestAppsService_FindUserInstallation(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/installation", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + fmt.Fprint(w, `{"id":1, "app_id":1, "target_id":1, "target_type": "User"}`) + }) + + installation, _, err := client.Apps.FindUserInstallation(context.Background(), "u") + if err != nil { + t.Errorf("Apps.FindUserInstallation returned error: %v", err) + } + + want := &Installation{ID: Int64(1), AppID: Int64(1), TargetID: Int64(1), TargetType: String("User")} + if !reflect.DeepEqual(installation, want) { + t.Errorf("Apps.FindUserInstallation returned %+v, want %+v", installation, want) + } +} diff --git a/vendor/github.com/google/go-github/github/authorizations.go b/vendor/github.com/google/go-github/github/authorizations.go new file mode 100644 index 0000000..190205b --- /dev/null +++ b/vendor/github.com/google/go-github/github/authorizations.go @@ -0,0 +1,435 @@ +// Copyright 2015 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// Scope models a GitHub authorization scope. +// +// GitHub API docs: https://developer.github.com/v3/oauth/#scopes +type Scope string + +// This is the set of scopes for GitHub API V3 +const ( + ScopeNone Scope = "(no scope)" // REVISIT: is this actually returned, or just a documentation artifact? + ScopeUser Scope = "user" + ScopeUserEmail Scope = "user:email" + ScopeUserFollow Scope = "user:follow" + ScopePublicRepo Scope = "public_repo" + ScopeRepo Scope = "repo" + ScopeRepoDeployment Scope = "repo_deployment" + ScopeRepoStatus Scope = "repo:status" + ScopeDeleteRepo Scope = "delete_repo" + ScopeNotifications Scope = "notifications" + ScopeGist Scope = "gist" + ScopeReadRepoHook Scope = "read:repo_hook" + ScopeWriteRepoHook Scope = "write:repo_hook" + ScopeAdminRepoHook Scope = "admin:repo_hook" + ScopeAdminOrgHook Scope = "admin:org_hook" + ScopeReadOrg Scope = "read:org" + ScopeWriteOrg Scope = "write:org" + ScopeAdminOrg Scope = "admin:org" + ScopeReadPublicKey Scope = "read:public_key" + ScopeWritePublicKey Scope = "write:public_key" + ScopeAdminPublicKey Scope = "admin:public_key" + ScopeReadGPGKey Scope = "read:gpg_key" + ScopeWriteGPGKey Scope = "write:gpg_key" + ScopeAdminGPGKey Scope = "admin:gpg_key" +) + +// AuthorizationsService handles communication with the authorization related +// methods of the GitHub API. +// +// This service requires HTTP Basic Authentication; it cannot be accessed using +// an OAuth token. +// +// GitHub API docs: https://developer.github.com/v3/oauth_authorizations/ +type AuthorizationsService service + +// Authorization represents an individual GitHub authorization. +type Authorization struct { + ID *int64 `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + Scopes []Scope `json:"scopes,omitempty"` + Token *string `json:"token,omitempty"` + TokenLastEight *string `json:"token_last_eight,omitempty"` + HashedToken *string `json:"hashed_token,omitempty"` + App *AuthorizationApp `json:"app,omitempty"` + Note *string `json:"note,omitempty"` + NoteURL *string `json:"note_url,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + Fingerprint *string `json:"fingerprint,omitempty"` + + // User is only populated by the Check and Reset methods. + User *User `json:"user,omitempty"` +} + +func (a Authorization) String() string { + return Stringify(a) +} + +// AuthorizationApp represents an individual GitHub app (in the context of authorization). +type AuthorizationApp struct { + URL *string `json:"url,omitempty"` + Name *string `json:"name,omitempty"` + ClientID *string `json:"client_id,omitempty"` +} + +func (a AuthorizationApp) String() string { + return Stringify(a) +} + +// Grant represents an OAuth application that has been granted access to an account. +type Grant struct { + ID *int64 `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + App *AuthorizationApp `json:"app,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +func (g Grant) String() string { + return Stringify(g) +} + +// AuthorizationRequest represents a request to create an authorization. +type AuthorizationRequest struct { + Scopes []Scope `json:"scopes,omitempty"` + Note *string `json:"note,omitempty"` + NoteURL *string `json:"note_url,omitempty"` + ClientID *string `json:"client_id,omitempty"` + ClientSecret *string `json:"client_secret,omitempty"` + Fingerprint *string `json:"fingerprint,omitempty"` +} + +func (a AuthorizationRequest) String() string { + return Stringify(a) +} + +// AuthorizationUpdateRequest represents a request to update an authorization. +// +// Note that for any one update, you must only provide one of the "scopes" +// fields. That is, you may provide only one of "Scopes", or "AddScopes", or +// "RemoveScopes". +// +// GitHub API docs: https://developer.github.com/v3/oauth_authorizations/#update-an-existing-authorization +type AuthorizationUpdateRequest struct { + Scopes []string `json:"scopes,omitempty"` + AddScopes []string `json:"add_scopes,omitempty"` + RemoveScopes []string `json:"remove_scopes,omitempty"` + Note *string `json:"note,omitempty"` + NoteURL *string `json:"note_url,omitempty"` + Fingerprint *string `json:"fingerprint,omitempty"` +} + +func (a AuthorizationUpdateRequest) String() string { + return Stringify(a) +} + +// List the authorizations for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/oauth_authorizations/#list-your-authorizations +func (s *AuthorizationsService) List(ctx context.Context, opt *ListOptions) ([]*Authorization, *Response, error) { + u := "authorizations" + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var auths []*Authorization + resp, err := s.client.Do(ctx, req, &auths) + if err != nil { + return nil, resp, err + } + return auths, resp, nil +} + +// Get a single authorization. +// +// GitHub API docs: https://developer.github.com/v3/oauth_authorizations/#get-a-single-authorization +func (s *AuthorizationsService) Get(ctx context.Context, id int64) (*Authorization, *Response, error) { + u := fmt.Sprintf("authorizations/%d", id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + a := new(Authorization) + resp, err := s.client.Do(ctx, req, a) + if err != nil { + return nil, resp, err + } + return a, resp, nil +} + +// Create a new authorization for the specified OAuth application. +// +// GitHub API docs: https://developer.github.com/v3/oauth_authorizations/#create-a-new-authorization +func (s *AuthorizationsService) Create(ctx context.Context, auth *AuthorizationRequest) (*Authorization, *Response, error) { + u := "authorizations" + + req, err := s.client.NewRequest("POST", u, auth) + if err != nil { + return nil, nil, err + } + + a := new(Authorization) + resp, err := s.client.Do(ctx, req, a) + if err != nil { + return nil, resp, err + } + return a, resp, nil +} + +// GetOrCreateForApp creates a new authorization for the specified OAuth +// application, only if an authorization for that application doesn’t already +// exist for the user. +// +// If a new token is created, the HTTP status code will be "201 Created", and +// the returned Authorization.Token field will be populated. If an existing +// token is returned, the status code will be "200 OK" and the +// Authorization.Token field will be empty. +// +// clientID is the OAuth Client ID with which to create the token. +// +// GitHub API docs: +// https://developer.github.com/v3/oauth_authorizations/#get-or-create-an-authorization-for-a-specific-app +// https://developer.github.com/v3/oauth_authorizations/#get-or-create-an-authorization-for-a-specific-app-and-fingerprint +func (s *AuthorizationsService) GetOrCreateForApp(ctx context.Context, clientID string, auth *AuthorizationRequest) (*Authorization, *Response, error) { + var u string + if auth.Fingerprint == nil || *auth.Fingerprint == "" { + u = fmt.Sprintf("authorizations/clients/%v", clientID) + } else { + u = fmt.Sprintf("authorizations/clients/%v/%v", clientID, *auth.Fingerprint) + } + + req, err := s.client.NewRequest("PUT", u, auth) + if err != nil { + return nil, nil, err + } + + a := new(Authorization) + resp, err := s.client.Do(ctx, req, a) + if err != nil { + return nil, resp, err + } + + return a, resp, nil +} + +// Edit a single authorization. +// +// GitHub API docs: https://developer.github.com/v3/oauth_authorizations/#update-an-existing-authorization +func (s *AuthorizationsService) Edit(ctx context.Context, id int64, auth *AuthorizationUpdateRequest) (*Authorization, *Response, error) { + u := fmt.Sprintf("authorizations/%d", id) + + req, err := s.client.NewRequest("PATCH", u, auth) + if err != nil { + return nil, nil, err + } + + a := new(Authorization) + resp, err := s.client.Do(ctx, req, a) + if err != nil { + return nil, resp, err + } + + return a, resp, nil +} + +// Delete a single authorization. +// +// GitHub API docs: https://developer.github.com/v3/oauth_authorizations/#delete-an-authorization +func (s *AuthorizationsService) Delete(ctx context.Context, id int64) (*Response, error) { + u := fmt.Sprintf("authorizations/%d", id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// Check if an OAuth token is valid for a specific app. +// +// Note that this operation requires the use of BasicAuth, but where the +// username is the OAuth application clientID, and the password is its +// clientSecret. Invalid tokens will return a 404 Not Found. +// +// The returned Authorization.User field will be populated. +// +// GitHub API docs: https://developer.github.com/v3/oauth_authorizations/#check-an-authorization +func (s *AuthorizationsService) Check(ctx context.Context, clientID string, token string) (*Authorization, *Response, error) { + u := fmt.Sprintf("applications/%v/tokens/%v", clientID, token) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + a := new(Authorization) + resp, err := s.client.Do(ctx, req, a) + if err != nil { + return nil, resp, err + } + + return a, resp, nil +} + +// Reset is used to reset a valid OAuth token without end user involvement. +// Applications must save the "token" property in the response, because changes +// take effect immediately. +// +// Note that this operation requires the use of BasicAuth, but where the +// username is the OAuth application clientID, and the password is its +// clientSecret. Invalid tokens will return a 404 Not Found. +// +// The returned Authorization.User field will be populated. +// +// GitHub API docs: https://developer.github.com/v3/oauth_authorizations/#reset-an-authorization +func (s *AuthorizationsService) Reset(ctx context.Context, clientID string, token string) (*Authorization, *Response, error) { + u := fmt.Sprintf("applications/%v/tokens/%v", clientID, token) + + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + a := new(Authorization) + resp, err := s.client.Do(ctx, req, a) + if err != nil { + return nil, resp, err + } + + return a, resp, nil +} + +// Revoke an authorization for an application. +// +// Note that this operation requires the use of BasicAuth, but where the +// username is the OAuth application clientID, and the password is its +// clientSecret. Invalid tokens will return a 404 Not Found. +// +// GitHub API docs: https://developer.github.com/v3/oauth_authorizations/#revoke-an-authorization-for-an-application +func (s *AuthorizationsService) Revoke(ctx context.Context, clientID string, token string) (*Response, error) { + u := fmt.Sprintf("applications/%v/tokens/%v", clientID, token) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// ListGrants lists the set of OAuth applications that have been granted +// access to a user's account. This will return one entry for each application +// that has been granted access to the account, regardless of the number of +// tokens an application has generated for the user. +// +// GitHub API docs: https://developer.github.com/v3/oauth_authorizations/#list-your-grants +func (s *AuthorizationsService) ListGrants(ctx context.Context, opt *ListOptions) ([]*Grant, *Response, error) { + u, err := addOptions("applications/grants", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + grants := []*Grant{} + resp, err := s.client.Do(ctx, req, &grants) + if err != nil { + return nil, resp, err + } + + return grants, resp, nil +} + +// GetGrant gets a single OAuth application grant. +// +// GitHub API docs: https://developer.github.com/v3/oauth_authorizations/#get-a-single-grant +func (s *AuthorizationsService) GetGrant(ctx context.Context, id int64) (*Grant, *Response, error) { + u := fmt.Sprintf("applications/grants/%d", id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + grant := new(Grant) + resp, err := s.client.Do(ctx, req, grant) + if err != nil { + return nil, resp, err + } + + return grant, resp, nil +} + +// DeleteGrant deletes an OAuth application grant. Deleting an application's +// grant will also delete all OAuth tokens associated with the application for +// the user. +// +// GitHub API docs: https://developer.github.com/v3/oauth_authorizations/#delete-a-grant +func (s *AuthorizationsService) DeleteGrant(ctx context.Context, id int64) (*Response, error) { + u := fmt.Sprintf("applications/grants/%d", id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// CreateImpersonation creates an impersonation OAuth token. +// +// This requires admin permissions. With the returned Authorization.Token +// you can e.g. create or delete a user's public SSH key. NOTE: creating a +// new token automatically revokes an existing one. +// +// GitHub API docs: https://developer.github.com/enterprise/2.5/v3/users/administration/#create-an-impersonation-oauth-token +func (s *AuthorizationsService) CreateImpersonation(ctx context.Context, username string, authReq *AuthorizationRequest) (*Authorization, *Response, error) { + u := fmt.Sprintf("admin/users/%v/authorizations", username) + req, err := s.client.NewRequest("POST", u, authReq) + if err != nil { + return nil, nil, err + } + + a := new(Authorization) + resp, err := s.client.Do(ctx, req, a) + if err != nil { + return nil, resp, err + } + return a, resp, nil +} + +// DeleteImpersonation deletes an impersonation OAuth token. +// +// NOTE: there can be only one at a time. +// +// GitHub API docs: https://developer.github.com/enterprise/2.5/v3/users/administration/#delete-an-impersonation-oauth-token +func (s *AuthorizationsService) DeleteImpersonation(ctx context.Context, username string) (*Response, error) { + u := fmt.Sprintf("admin/users/%v/authorizations", username) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/authorizations_test.go b/vendor/github.com/google/go-github/github/authorizations_test.go new file mode 100644 index 0000000..faf2574 --- /dev/null +++ b/vendor/github.com/google/go-github/github/authorizations_test.go @@ -0,0 +1,359 @@ +// Copyright 2015 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestAuthorizationsService_List(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/authorizations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "1", "per_page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + got, _, err := client.Authorizations.List(context.Background(), opt) + if err != nil { + t.Errorf("Authorizations.List returned error: %v", err) + } + + want := []*Authorization{{ID: Int64(1)}} + if !reflect.DeepEqual(got, want) { + t.Errorf("Authorizations.List returned %+v, want %+v", *got[0].ID, *want[0].ID) + } +} + +func TestAuthorizationsService_Get(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/authorizations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + got, _, err := client.Authorizations.Get(context.Background(), 1) + if err != nil { + t.Errorf("Authorizations.Get returned error: %v", err) + } + + want := &Authorization{ID: Int64(1)} + if !reflect.DeepEqual(got, want) { + t.Errorf("Authorizations.Get returned auth %+v, want %+v", got, want) + } +} + +func TestAuthorizationsService_Create(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &AuthorizationRequest{ + Note: String("test"), + } + + mux.HandleFunc("/authorizations", func(w http.ResponseWriter, r *http.Request) { + v := new(AuthorizationRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"ID":1}`) + }) + + got, _, err := client.Authorizations.Create(context.Background(), input) + if err != nil { + t.Errorf("Authorizations.Create returned error: %v", err) + } + + want := &Authorization{ID: Int64(1)} + if !reflect.DeepEqual(got, want) { + t.Errorf("Authorization.Create returned %+v, want %+v", got, want) + } +} + +func TestAuthorizationsService_GetOrCreateForApp(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &AuthorizationRequest{ + Note: String("test"), + } + + mux.HandleFunc("/authorizations/clients/id", func(w http.ResponseWriter, r *http.Request) { + v := new(AuthorizationRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"ID":1}`) + }) + + got, _, err := client.Authorizations.GetOrCreateForApp(context.Background(), "id", input) + if err != nil { + t.Errorf("Authorizations.GetOrCreateForApp returned error: %v", err) + } + + want := &Authorization{ID: Int64(1)} + if !reflect.DeepEqual(got, want) { + t.Errorf("Authorization.GetOrCreateForApp returned %+v, want %+v", got, want) + } +} + +func TestAuthorizationsService_GetOrCreateForApp_Fingerprint(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &AuthorizationRequest{ + Note: String("test"), + Fingerprint: String("fp"), + } + + mux.HandleFunc("/authorizations/clients/id/fp", func(w http.ResponseWriter, r *http.Request) { + v := new(AuthorizationRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"ID":1}`) + }) + + got, _, err := client.Authorizations.GetOrCreateForApp(context.Background(), "id", input) + if err != nil { + t.Errorf("Authorizations.GetOrCreateForApp returned error: %v", err) + } + + want := &Authorization{ID: Int64(1)} + if !reflect.DeepEqual(got, want) { + t.Errorf("Authorization.GetOrCreateForApp returned %+v, want %+v", got, want) + } +} + +func TestAuthorizationsService_Edit(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &AuthorizationUpdateRequest{ + Note: String("test"), + } + + mux.HandleFunc("/authorizations/1", func(w http.ResponseWriter, r *http.Request) { + v := new(AuthorizationUpdateRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"ID":1}`) + }) + + got, _, err := client.Authorizations.Edit(context.Background(), 1, input) + if err != nil { + t.Errorf("Authorizations.Edit returned error: %v", err) + } + + want := &Authorization{ID: Int64(1)} + if !reflect.DeepEqual(got, want) { + t.Errorf("Authorization.Update returned %+v, want %+v", got, want) + } +} + +func TestAuthorizationsService_Delete(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/authorizations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Authorizations.Delete(context.Background(), 1) + if err != nil { + t.Errorf("Authorizations.Delete returned error: %v", err) + } +} + +func TestAuthorizationsService_Check(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/applications/id/tokens/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + got, _, err := client.Authorizations.Check(context.Background(), "id", "t") + if err != nil { + t.Errorf("Authorizations.Check returned error: %v", err) + } + + want := &Authorization{ID: Int64(1)} + if !reflect.DeepEqual(got, want) { + t.Errorf("Authorizations.Check returned auth %+v, want %+v", got, want) + } +} + +func TestAuthorizationsService_Reset(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/applications/id/tokens/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, `{"ID":1}`) + }) + + got, _, err := client.Authorizations.Reset(context.Background(), "id", "t") + if err != nil { + t.Errorf("Authorizations.Reset returned error: %v", err) + } + + want := &Authorization{ID: Int64(1)} + if !reflect.DeepEqual(got, want) { + t.Errorf("Authorizations.Reset returned auth %+v, want %+v", got, want) + } +} + +func TestAuthorizationsService_Revoke(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/applications/id/tokens/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Authorizations.Revoke(context.Background(), "id", "t") + if err != nil { + t.Errorf("Authorizations.Revoke returned error: %v", err) + } +} + +func TestListGrants(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/applications/grants", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id": 1}]`) + }) + + got, _, err := client.Authorizations.ListGrants(context.Background(), nil) + if err != nil { + t.Errorf("OAuthAuthorizations.ListGrants returned error: %v", err) + } + + want := []*Grant{{ID: Int64(1)}} + if !reflect.DeepEqual(got, want) { + t.Errorf("OAuthAuthorizations.ListGrants = %+v, want %+v", got, want) + } +} + +func TestListGrants_withOptions(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/applications/grants", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id": 1}]`) + }) + + _, _, err := client.Authorizations.ListGrants(context.Background(), &ListOptions{Page: 2}) + if err != nil { + t.Errorf("OAuthAuthorizations.ListGrants returned error: %v", err) + } +} + +func TestGetGrant(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/applications/grants/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id": 1}`) + }) + + got, _, err := client.Authorizations.GetGrant(context.Background(), 1) + if err != nil { + t.Errorf("OAuthAuthorizations.GetGrant returned error: %v", err) + } + + want := &Grant{ID: Int64(1)} + if !reflect.DeepEqual(got, want) { + t.Errorf("OAuthAuthorizations.GetGrant = %+v, want %+v", got, want) + } +} + +func TestDeleteGrant(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/applications/grants/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Authorizations.DeleteGrant(context.Background(), 1) + if err != nil { + t.Errorf("OAuthAuthorizations.DeleteGrant returned error: %v", err) + } +} + +func TestAuthorizationsService_CreateImpersonation(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/admin/users/u/authorizations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, `{"id":1}`) + }) + + req := &AuthorizationRequest{Scopes: []Scope{ScopePublicRepo}} + got, _, err := client.Authorizations.CreateImpersonation(context.Background(), "u", req) + if err != nil { + t.Errorf("Authorizations.CreateImpersonation returned error: %+v", err) + } + + want := &Authorization{ID: Int64(1)} + if !reflect.DeepEqual(got, want) { + t.Errorf("Authorizations.CreateImpersonation returned %+v, want %+v", *got.ID, *want.ID) + } +} + +func TestAuthorizationsService_DeleteImpersonation(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/admin/users/u/authorizations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Authorizations.DeleteImpersonation(context.Background(), "u") + if err != nil { + t.Errorf("Authorizations.DeleteImpersonation returned error: %+v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/checks.go b/vendor/github.com/google/go-github/github/checks.go new file mode 100644 index 0000000..2a6ce41 --- /dev/null +++ b/vendor/github.com/google/go-github/github/checks.go @@ -0,0 +1,421 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ChecksService provides access to the Checks API in the +// GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/checks/ +type ChecksService service + +// CheckRun represents a GitHub check run on a repository associated with a GitHub app. +type CheckRun struct { + ID *int64 `json:"id,omitempty"` + NodeID *string `json:"node_id,omitempty"` + HeadSHA *string `json:"head_sha,omitempty"` + ExternalID *string `json:"external_id,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + Status *string `json:"status,omitempty"` + Conclusion *string `json:"conclusion,omitempty"` + StartedAt *Timestamp `json:"started_at,omitempty"` + CompletedAt *Timestamp `json:"completed_at,omitempty"` + Output *CheckRunOutput `json:"output,omitempty"` + Name *string `json:"name,omitempty"` + CheckSuite *CheckSuite `json:"check_suite,omitempty"` + App *App `json:"app,omitempty"` + PullRequests []*PullRequest `json:"pull_requests,omitempty"` +} + +// CheckRunOutput represents the output of a CheckRun. +type CheckRunOutput struct { + Title *string `json:"title,omitempty"` + Summary *string `json:"summary,omitempty"` + Text *string `json:"text,omitempty"` + AnnotationsCount *int `json:"annotations_count,omitempty"` + AnnotationsURL *string `json:"annotations_url,omitempty"` + Annotations []*CheckRunAnnotation `json:"annotations,omitempty"` + Images []*CheckRunImage `json:"images,omitempty"` +} + +// CheckRunAnnotation represents an annotation object for a CheckRun output. +type CheckRunAnnotation struct { + Path *string `json:"path,omitempty"` + BlobHRef *string `json:"blob_href,omitempty"` + StartLine *int `json:"start_line,omitempty"` + EndLine *int `json:"end_line,omitempty"` + AnnotationLevel *string `json:"annotation_level,omitempty"` + Message *string `json:"message,omitempty"` + Title *string `json:"title,omitempty"` + RawDetails *string `json:"raw_details,omitempty"` +} + +// CheckRunImage represents an image object for a CheckRun output. +type CheckRunImage struct { + Alt *string `json:"alt,omitempty"` + ImageURL *string `json:"image_url,omitempty"` + Caption *string `json:"caption,omitempty"` +} + +// CheckSuite represents a suite of check runs. +type CheckSuite struct { + ID *int64 `json:"id,omitempty"` + NodeID *string `json:"node_id,omitempty"` + HeadBranch *string `json:"head_branch,omitempty"` + HeadSHA *string `json:"head_sha,omitempty"` + URL *string `json:"url,omitempty"` + BeforeSHA *string `json:"before,omitempty"` + AfterSHA *string `json:"after,omitempty"` + Status *string `json:"status,omitempty"` + Conclusion *string `json:"conclusion,omitempty"` + App *App `json:"app,omitempty"` + Repository *Repository `json:"repository,omitempty"` + PullRequests []*PullRequest `json:"pull_requests,omitempty"` +} + +func (c CheckRun) String() string { + return Stringify(c) +} + +func (c CheckSuite) String() string { + return Stringify(c) +} + +// GetCheckRun gets a check-run for a repository. +// +// GitHub API docs: https://developer.github.com/v3/checks/runs/#get-a-single-check-run +func (s *ChecksService) GetCheckRun(ctx context.Context, owner, repo string, checkRunID int64) (*CheckRun, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/check-runs/%v", owner, repo, checkRunID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeCheckRunsPreview) + + checkRun := new(CheckRun) + resp, err := s.client.Do(ctx, req, checkRun) + if err != nil { + return nil, resp, err + } + + return checkRun, resp, nil +} + +// GetCheckSuite gets a single check suite. +// +// GitHub API docs: https://developer.github.com/v3/checks/suites/#get-a-single-check-suite +func (s *ChecksService) GetCheckSuite(ctx context.Context, owner, repo string, checkSuiteID int64) (*CheckSuite, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/check-suites/%v", owner, repo, checkSuiteID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeCheckRunsPreview) + + checkSuite := new(CheckSuite) + resp, err := s.client.Do(ctx, req, checkSuite) + if err != nil { + return nil, resp, err + } + + return checkSuite, resp, nil +} + +// CreateCheckRunOptions sets up parameters needed to create a CheckRun. +type CreateCheckRunOptions struct { + Name string `json:"name"` // The name of the check (e.g., "code-coverage"). (Required.) + HeadBranch string `json:"head_branch"` // The name of the branch to perform a check against. (Required.) + HeadSHA string `json:"head_sha"` // The SHA of the commit. (Required.) + DetailsURL *string `json:"details_url,omitempty"` // The URL of the integrator's site that has the full details of the check. (Optional.) + ExternalID *string `json:"external_id,omitempty"` // A reference for the run on the integrator's system. (Optional.) + Status *string `json:"status,omitempty"` // The current status. Can be one of "queued", "in_progress", or "completed". Default: "queued". (Optional.) + Conclusion *string `json:"conclusion,omitempty"` // Can be one of "success", "failure", "neutral", "cancelled", "timed_out", or "action_required". (Optional. Required if you provide a status of "completed".) + StartedAt *Timestamp `json:"started_at,omitempty"` // The time that the check run began. (Optional.) + CompletedAt *Timestamp `json:"completed_at,omitempty"` // The time the check completed. (Optional. Required if you provide conclusion.) + Output *CheckRunOutput `json:"output,omitempty"` // Provide descriptive details about the run. (Optional) +} + +// CreateCheckRun creates a check run for repository. +// +// GitHub API docs: https://developer.github.com/v3/checks/runs/#create-a-check-run +func (s *ChecksService) CreateCheckRun(ctx context.Context, owner, repo string, opt CreateCheckRunOptions) (*CheckRun, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/check-runs", owner, repo) + req, err := s.client.NewRequest("POST", u, opt) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeCheckRunsPreview) + + checkRun := new(CheckRun) + resp, err := s.client.Do(ctx, req, checkRun) + if err != nil { + return nil, resp, err + } + + return checkRun, resp, nil +} + +// UpdateCheckRunOptions sets up parameters needed to update a CheckRun. +type UpdateCheckRunOptions struct { + Name string `json:"name"` // The name of the check (e.g., "code-coverage"). (Required.) + HeadBranch *string `json:"head_branch,omitempty"` // The name of the branch to perform a check against. (Optional.) + HeadSHA *string `json:"head_sha,omitempty"` // The SHA of the commit. (Optional.) + DetailsURL *string `json:"details_url,omitempty"` // The URL of the integrator's site that has the full details of the check. (Optional.) + ExternalID *string `json:"external_id,omitempty"` // A reference for the run on the integrator's system. (Optional.) + Status *string `json:"status,omitempty"` // The current status. Can be one of "queued", "in_progress", or "completed". Default: "queued". (Optional.) + Conclusion *string `json:"conclusion,omitempty"` // Can be one of "success", "failure", "neutral", "cancelled", "timed_out", or "action_required". (Optional. Required if you provide a status of "completed".) + CompletedAt *Timestamp `json:"completed_at,omitempty"` // The time the check completed. (Optional. Required if you provide conclusion.) + Output *CheckRunOutput `json:"output,omitempty"` // Provide descriptive details about the run. (Optional) +} + +// UpdateCheckRun updates a check run for a specific commit in a repository. +// +// GitHub API docs: https://developer.github.com/v3/checks/runs/#update-a-check-run +func (s *ChecksService) UpdateCheckRun(ctx context.Context, owner, repo string, checkRunID int64, opt UpdateCheckRunOptions) (*CheckRun, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/check-runs/%v", owner, repo, checkRunID) + req, err := s.client.NewRequest("PATCH", u, opt) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeCheckRunsPreview) + + checkRun := new(CheckRun) + resp, err := s.client.Do(ctx, req, checkRun) + if err != nil { + return nil, resp, err + } + + return checkRun, resp, nil +} + +// ListCheckRunAnnotations lists the annotations for a check run. +// +// GitHub API docs: https://developer.github.com/v3/checks/runs/#list-annotations-for-a-check-run +func (s *ChecksService) ListCheckRunAnnotations(ctx context.Context, owner, repo string, checkRunID int64, opt *ListOptions) ([]*CheckRunAnnotation, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/check-runs/%v/annotations", owner, repo, checkRunID) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeCheckRunsPreview) + + var checkRunAnnotations []*CheckRunAnnotation + resp, err := s.client.Do(ctx, req, &checkRunAnnotations) + if err != nil { + return nil, resp, err + } + + return checkRunAnnotations, resp, nil +} + +// ListCheckRunsOptions represents parameters to list check runs. +type ListCheckRunsOptions struct { + CheckName *string `url:"check_name,omitempty"` // Returns check runs with the specified name. + Status *string `url:"status,omitempty"` // Returns check runs with the specified status. Can be one of "queued", "in_progress", or "completed". + Filter *string `url:"filter,omitempty"` // Filters check runs by their completed_at timestamp. Can be one of "latest" (returning the most recent check runs) or "all". Default: "latest" + + ListOptions +} + +// ListCheckRunsResults represents the result of a check run list. +type ListCheckRunsResults struct { + Total *int `json:"total_count,omitempty"` + CheckRuns []*CheckRun `json:"check_runs,omitempty"` +} + +// ListCheckRunsForRef lists check runs for a specific ref. +// +// GitHub API docs: https://developer.github.com/v3/checks/runs/#list-check-runs-for-a-specific-ref +func (s *ChecksService) ListCheckRunsForRef(ctx context.Context, owner, repo, ref string, opt *ListCheckRunsOptions) (*ListCheckRunsResults, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v/check-runs", owner, repo, ref) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeCheckRunsPreview) + + var checkRunResults *ListCheckRunsResults + resp, err := s.client.Do(ctx, req, &checkRunResults) + if err != nil { + return nil, resp, err + } + + return checkRunResults, resp, nil +} + +// ListCheckRunsCheckSuite lists check runs for a check suite. +// +// GitHub API docs: https://developer.github.com/v3/checks/runs/#list-check-runs-in-a-check-suite +func (s *ChecksService) ListCheckRunsCheckSuite(ctx context.Context, owner, repo string, checkSuiteID int64, opt *ListCheckRunsOptions) (*ListCheckRunsResults, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/check-suites/%v/check-runs", owner, repo, checkSuiteID) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeCheckRunsPreview) + + var checkRunResults *ListCheckRunsResults + resp, err := s.client.Do(ctx, req, &checkRunResults) + if err != nil { + return nil, resp, err + } + + return checkRunResults, resp, nil +} + +// ListCheckSuiteOptions represents parameters to list check suites. +type ListCheckSuiteOptions struct { + CheckName *string `url:"check_name,omitempty"` // Filters checks suites by the name of the check run. + AppID *int `url:"app_id,omitempty"` // Filters check suites by GitHub App id. + + ListOptions +} + +// ListCheckSuiteResults represents the result of a check run list. +type ListCheckSuiteResults struct { + Total *int `json:"total_count,omitempty"` + CheckSuites []*CheckSuite `json:"check_suites,omitempty"` +} + +// ListCheckSuitesForRef lists check suite for a specific ref. +// +// GitHub API docs: https://developer.github.com/v3/checks/suites/#list-check-suites-for-a-specific-ref +func (s *ChecksService) ListCheckSuitesForRef(ctx context.Context, owner, repo, ref string, opt *ListCheckSuiteOptions) (*ListCheckSuiteResults, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v/check-suites", owner, repo, ref) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeCheckRunsPreview) + + var checkSuiteResults *ListCheckSuiteResults + resp, err := s.client.Do(ctx, req, &checkSuiteResults) + if err != nil { + return nil, resp, err + } + + return checkSuiteResults, resp, nil +} + +// AutoTriggerCheck enables or disables automatic creation of CheckSuite events upon pushes to the repository. +type AutoTriggerCheck struct { + AppID *int64 `json:"app_id,omitempty"` // The id of the GitHub App. (Required.) + Setting *bool `json:"setting,omitempty"` // Set to "true" to enable automatic creation of CheckSuite events upon pushes to the repository, or "false" to disable them. Default: "true" (Required.) +} + +// CheckSuitePreferenceOptions set options for check suite preferences for a repository. +type CheckSuitePreferenceOptions struct { + PreferenceList *PreferenceList `json:"auto_trigger_checks,omitempty"` // A list of auto trigger checks that can be set for a check suite in a repository. +} + +// CheckSuitePreferenceResults represents the results of the preference set operation. +type CheckSuitePreferenceResults struct { + Preferences *PreferenceList `json:"preferences,omitempty"` + Repository *Repository `json:"repository,omitempty"` +} + +// PreferenceList represents a list of auto trigger checks for repository +type PreferenceList struct { + AutoTriggerChecks []*AutoTriggerCheck `json:"auto_trigger_checks,omitempty"` // A slice of auto trigger checks that can be set for a check suite in a repository. +} + +// SetCheckSuitePreferences changes the default automatic flow when creating check suites. +// +// GitHub API docs: https://developer.github.com/v3/checks/suites/#set-preferences-for-check-suites-on-a-repository +func (s *ChecksService) SetCheckSuitePreferences(ctx context.Context, owner, repo string, opt CheckSuitePreferenceOptions) (*CheckSuitePreferenceResults, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/check-suites/preferences", owner, repo) + req, err := s.client.NewRequest("PATCH", u, opt) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeCheckRunsPreview) + + var checkSuitePrefResults *CheckSuitePreferenceResults + resp, err := s.client.Do(ctx, req, &checkSuitePrefResults) + if err != nil { + return nil, resp, err + } + + return checkSuitePrefResults, resp, nil +} + +// CreateCheckSuiteOptions sets up parameters to manually create a check suites +type CreateCheckSuiteOptions struct { + HeadSHA string `json:"head_sha"` // The sha of the head commit. (Required.) + HeadBranch *string `json:"head_branch,omitempty"` // The name of the head branch where the code changes are implemented. +} + +// CreateCheckSuite manually creates a check suite for a repository. +// +// GitHub API docs: https://developer.github.com/v3/checks/suites/#create-a-check-suite +func (s *ChecksService) CreateCheckSuite(ctx context.Context, owner, repo string, opt CreateCheckSuiteOptions) (*CheckSuite, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/check-suites", owner, repo) + req, err := s.client.NewRequest("POST", u, opt) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeCheckRunsPreview) + + checkSuite := new(CheckSuite) + resp, err := s.client.Do(ctx, req, checkSuite) + if err != nil { + return nil, resp, err + } + + return checkSuite, resp, nil +} + +// ReRequestCheckSuite triggers GitHub to rerequest an existing check suite, without pushing new code to a repository. +// +// GitHub API docs: https://developer.github.com/v3/checks/suites/#rerequest-check-suite +func (s *ChecksService) ReRequestCheckSuite(ctx context.Context, owner, repo string, checkSuiteID int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/check-suites/%v/rerequest", owner, repo, checkSuiteID) + + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, err + } + + req.Header.Set("Accept", mediaTypeCheckRunsPreview) + + resp, err := s.client.Do(ctx, req, nil) + return resp, err +} diff --git a/vendor/github.com/google/go-github/github/checks_test.go b/vendor/github.com/google/go-github/github/checks_test.go new file mode 100644 index 0000000..3170f70 --- /dev/null +++ b/vendor/github.com/google/go-github/github/checks_test.go @@ -0,0 +1,479 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestChecksService_GetCheckRun(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/check-runs/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeCheckRunsPreview) + fmt.Fprint(w, `{ + "id": 1, + "name":"testCheckRun", + "status": "completed", + "conclusion": "neutral", + "started_at": "2018-05-04T01:14:52Z", + "completed_at": "2018-05-04T01:14:52Z"}`) + }) + checkRun, _, err := client.Checks.GetCheckRun(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Checks.GetCheckRun return error: %v", err) + } + startedAt, _ := time.Parse(time.RFC3339, "2018-05-04T01:14:52Z") + completeAt, _ := time.Parse(time.RFC3339, "2018-05-04T01:14:52Z") + + want := &CheckRun{ + ID: Int64(1), + Status: String("completed"), + Conclusion: String("neutral"), + StartedAt: &Timestamp{startedAt}, + CompletedAt: &Timestamp{completeAt}, + Name: String("testCheckRun"), + } + if !reflect.DeepEqual(checkRun, want) { + t.Errorf("Checks.GetCheckRun return %+v, want %+v", checkRun, want) + } +} + +func TestChecksService_GetCheckSuite(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/check-suites/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeCheckRunsPreview) + fmt.Fprint(w, `{ + "id": 1, + "head_branch":"master", + "head_sha": "deadbeef", + "conclusion": "neutral", + "before": "deadbeefb", + "after": "deadbeefa", + "status": "completed"}`) + }) + checkSuite, _, err := client.Checks.GetCheckSuite(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Checks.GetCheckSuite return error: %v", err) + } + want := &CheckSuite{ + ID: Int64(1), + HeadBranch: String("master"), + HeadSHA: String("deadbeef"), + AfterSHA: String("deadbeefa"), + BeforeSHA: String("deadbeefb"), + Status: String("completed"), + Conclusion: String("neutral"), + } + if !reflect.DeepEqual(checkSuite, want) { + t.Errorf("Checks.GetCheckSuite return %+v, want %+v", checkSuite, want) + } +} + +func TestChecksService_CreateCheckRun(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/check-runs", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeCheckRunsPreview) + fmt.Fprint(w, `{ + "id": 1, + "name":"testCreateCheckRun", + "head_sha":"deadbeef", + "status": "in_progress", + "conclusion": null, + "started_at": "2018-05-04T01:14:52Z", + "completed_at": null, + "output":{"title": "Mighty test report", "summary":"", "text":""}}`) + }) + startedAt, _ := time.Parse(time.RFC3339, "2018-05-04T01:14:52Z") + checkRunOpt := CreateCheckRunOptions{ + HeadBranch: "master", + Name: "testCreateCheckRun", + HeadSHA: "deadbeef", + Status: String("in_progress"), + StartedAt: &Timestamp{startedAt}, + Output: &CheckRunOutput{ + Title: String("Mighty test report"), + Summary: String(""), + Text: String(""), + }, + } + + checkRun, _, err := client.Checks.CreateCheckRun(context.Background(), "o", "r", checkRunOpt) + if err != nil { + t.Errorf("Checks.CreateCheckRun return error: %v", err) + } + + want := &CheckRun{ + ID: Int64(1), + Status: String("in_progress"), + StartedAt: &Timestamp{startedAt}, + HeadSHA: String("deadbeef"), + Name: String("testCreateCheckRun"), + Output: &CheckRunOutput{ + Title: String("Mighty test report"), + Summary: String(""), + Text: String(""), + }, + } + if !reflect.DeepEqual(checkRun, want) { + t.Errorf("Checks.CreateCheckRun return %+v, want %+v", checkRun, want) + } +} + +func TestChecksService_ListCheckRunAnnotations(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/check-runs/1/annotations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeCheckRunsPreview) + testFormValues(t, r, values{ + "page": "1", + }) + fmt.Fprint(w, `[{ + "path": "README.md", + "blob_href": "https://github.com/octocat/Hello-World/blob/837db83be4137ca555d9a5598d0a1ea2987ecfee/README.md", + "start_line": 2, + "end_line": 2, + "annotation_level": "warning", + "message": "Check your spelling for 'banaas'.", + "title": "Spell check", + "raw_details": "Do you mean 'bananas' or 'banana'?"}]`, + ) + }) + + checkRunAnnotations, _, err := client.Checks.ListCheckRunAnnotations(context.Background(), "o", "r", 1, &ListOptions{Page: 1}) + if err != nil { + t.Errorf("Checks.ListCheckRunAnnotations return error: %v", err) + } + + want := []*CheckRunAnnotation{{ + Path: String("README.md"), + BlobHRef: String("https://github.com/octocat/Hello-World/blob/837db83be4137ca555d9a5598d0a1ea2987ecfee/README.md"), + StartLine: Int(2), + EndLine: Int(2), + AnnotationLevel: String("warning"), + Message: String("Check your spelling for 'banaas'."), + RawDetails: String("Do you mean 'bananas' or 'banana'?"), + Title: String("Spell check"), + }} + + if !reflect.DeepEqual(checkRunAnnotations, want) { + t.Errorf("Checks.ListCheckRunAnnotations returned %+v, want %+v", checkRunAnnotations, want) + } +} + +func TestChecksService_UpdateCheckRun(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/check-runs/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeCheckRunsPreview) + fmt.Fprint(w, `{ + "id": 1, + "name":"testUpdateCheckRun", + "head_sha":"deadbeef", + "status": "completed", + "conclusion": "neutral", + "started_at": "2018-05-04T01:14:52Z", + "completed_at": "2018-05-04T01:14:52Z", + "output":{"title": "Mighty test report", "summary":"There are 0 failures, 2 warnings and 1 notice", "text":"You may have misspelled some words."}}`) + }) + startedAt, _ := time.Parse(time.RFC3339, "2018-05-04T01:14:52Z") + updateCheckRunOpt := UpdateCheckRunOptions{ + HeadBranch: String("master"), + Name: "testUpdateCheckRun", + HeadSHA: String("deadbeef"), + Status: String("completed"), + CompletedAt: &Timestamp{startedAt}, + Output: &CheckRunOutput{ + Title: String("Mighty test report"), + Summary: String("There are 0 failures, 2 warnings and 1 notice"), + Text: String("You may have misspelled some words."), + }, + } + + checkRun, _, err := client.Checks.UpdateCheckRun(context.Background(), "o", "r", 1, updateCheckRunOpt) + if err != nil { + t.Errorf("Checks.UpdateCheckRun return error: %v", err) + } + + want := &CheckRun{ + ID: Int64(1), + Status: String("completed"), + StartedAt: &Timestamp{startedAt}, + CompletedAt: &Timestamp{startedAt}, + Conclusion: String("neutral"), + HeadSHA: String("deadbeef"), + Name: String("testUpdateCheckRun"), + Output: &CheckRunOutput{ + Title: String("Mighty test report"), + Summary: String("There are 0 failures, 2 warnings and 1 notice"), + Text: String("You may have misspelled some words."), + }, + } + if !reflect.DeepEqual(checkRun, want) { + t.Errorf("Checks.UpdateCheckRun return %+v, want %+v", checkRun, want) + } +} + +func TestChecksService_ListCheckRunsForRef(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/commits/master/check-runs", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeCheckRunsPreview) + testFormValues(t, r, values{ + "check_name": "testing", + "page": "1", + "status": "completed", + "filter": "all", + }) + fmt.Fprint(w, `{"total_count":1, + "check_runs": [{ + "id": 1, + "head_sha": "deadbeef", + "status": "completed", + "conclusion": "neutral", + "started_at": "2018-05-04T01:14:52Z", + "completed_at": "2018-05-04T01:14:52Z"}]}`, + ) + }) + + opt := &ListCheckRunsOptions{ + CheckName: String("testing"), + Status: String("completed"), + Filter: String("all"), + ListOptions: ListOptions{Page: 1}, + } + checkRuns, _, err := client.Checks.ListCheckRunsForRef(context.Background(), "o", "r", "master", opt) + if err != nil { + t.Errorf("Checks.ListCheckRunsForRef return error: %v", err) + } + startedAt, _ := time.Parse(time.RFC3339, "2018-05-04T01:14:52Z") + want := &ListCheckRunsResults{ + Total: Int(1), + CheckRuns: []*CheckRun{{ + ID: Int64(1), + Status: String("completed"), + StartedAt: &Timestamp{startedAt}, + CompletedAt: &Timestamp{startedAt}, + Conclusion: String("neutral"), + HeadSHA: String("deadbeef"), + }}, + } + + if !reflect.DeepEqual(checkRuns, want) { + t.Errorf("Checks.ListCheckRunsForRef returned %+v, want %+v", checkRuns, want) + } +} + +func TestChecksService_ListCheckRunsCheckSuite(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/check-suites/1/check-runs", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeCheckRunsPreview) + testFormValues(t, r, values{ + "check_name": "testing", + "page": "1", + "status": "completed", + "filter": "all", + }) + fmt.Fprint(w, `{"total_count":1, + "check_runs": [{ + "id": 1, + "head_sha": "deadbeef", + "status": "completed", + "conclusion": "neutral", + "started_at": "2018-05-04T01:14:52Z", + "completed_at": "2018-05-04T01:14:52Z"}]}`, + ) + }) + + opt := &ListCheckRunsOptions{ + CheckName: String("testing"), + Status: String("completed"), + Filter: String("all"), + ListOptions: ListOptions{Page: 1}, + } + checkRuns, _, err := client.Checks.ListCheckRunsCheckSuite(context.Background(), "o", "r", 1, opt) + if err != nil { + t.Errorf("Checks.ListCheckRunsCheckSuite return error: %v", err) + } + startedAt, _ := time.Parse(time.RFC3339, "2018-05-04T01:14:52Z") + want := &ListCheckRunsResults{ + Total: Int(1), + CheckRuns: []*CheckRun{{ + ID: Int64(1), + Status: String("completed"), + StartedAt: &Timestamp{startedAt}, + CompletedAt: &Timestamp{startedAt}, + Conclusion: String("neutral"), + HeadSHA: String("deadbeef"), + }}, + } + + if !reflect.DeepEqual(checkRuns, want) { + t.Errorf("Checks.ListCheckRunsCheckSuite returned %+v, want %+v", checkRuns, want) + } +} + +func TestChecksService_ListCheckSuiteForRef(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/commits/master/check-suites", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeCheckRunsPreview) + testFormValues(t, r, values{ + "check_name": "testing", + "page": "1", + "app_id": "2", + }) + fmt.Fprint(w, `{"total_count":1, + "check_suites": [{ + "id": 1, + "head_sha": "deadbeef", + "head_branch": "master", + "status": "completed", + "conclusion": "neutral", + "before": "deadbeefb", + "after": "deadbeefa"}]}`, + ) + }) + + opt := &ListCheckSuiteOptions{ + CheckName: String("testing"), + AppID: Int(2), + ListOptions: ListOptions{Page: 1}, + } + checkSuites, _, err := client.Checks.ListCheckSuitesForRef(context.Background(), "o", "r", "master", opt) + if err != nil { + t.Errorf("Checks.ListCheckSuitesForRef return error: %v", err) + } + want := &ListCheckSuiteResults{ + Total: Int(1), + CheckSuites: []*CheckSuite{{ + ID: Int64(1), + Status: String("completed"), + Conclusion: String("neutral"), + HeadSHA: String("deadbeef"), + HeadBranch: String("master"), + BeforeSHA: String("deadbeefb"), + AfterSHA: String("deadbeefa"), + }}, + } + + if !reflect.DeepEqual(checkSuites, want) { + t.Errorf("Checks.ListCheckSuitesForRef returned %+v, want %+v", checkSuites, want) + } +} + +func TestChecksService_SetCheckSuitePreferences(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/check-suites/preferences", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeCheckRunsPreview) + fmt.Fprint(w, `{"preferences":{"auto_trigger_checks":[{"app_id": 2,"setting": false}]}}`) + }) + p := &PreferenceList{ + AutoTriggerChecks: []*AutoTriggerCheck{{ + AppID: Int64(2), + Setting: Bool(false), + }}, + } + opt := CheckSuitePreferenceOptions{PreferenceList: p} + prefResults, _, err := client.Checks.SetCheckSuitePreferences(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Checks.SetCheckSuitePreferences return error: %v", err) + } + + want := &CheckSuitePreferenceResults{ + Preferences: p, + } + + if !reflect.DeepEqual(prefResults, want) { + t.Errorf("Checks.SetCheckSuitePreferences return %+v, want %+v", prefResults, want) + } +} + +func TestChecksService_CreateCheckSuite(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/check-suites", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeCheckRunsPreview) + fmt.Fprint(w, `{ + "id": 2, + "head_branch":"master", + "head_sha":"deadbeef", + "status": "completed", + "conclusion": "neutral", + "before": "deadbeefb", + "after": "deadbeefa"}`) + }) + + checkSuiteOpt := CreateCheckSuiteOptions{ + HeadSHA: "deadbeef", + HeadBranch: String("master"), + } + + checkSuite, _, err := client.Checks.CreateCheckSuite(context.Background(), "o", "r", checkSuiteOpt) + if err != nil { + t.Errorf("Checks.CreateCheckSuite return error: %v", err) + } + + want := &CheckSuite{ + ID: Int64(2), + Status: String("completed"), + HeadSHA: String("deadbeef"), + HeadBranch: String("master"), + Conclusion: String("neutral"), + BeforeSHA: String("deadbeefb"), + AfterSHA: String("deadbeefa"), + } + if !reflect.DeepEqual(checkSuite, want) { + t.Errorf("Checks.CreateCheckSuite return %+v, want %+v", checkSuite, want) + } +} + +func TestChecksService_ReRequestCheckSuite(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/check-suites/1/rerequest", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeCheckRunsPreview) + w.WriteHeader(http.StatusCreated) + }) + resp, err := client.Checks.ReRequestCheckSuite(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Checks.ReRequestCheckSuite return error: %v", err) + } + if got, want := resp.StatusCode, http.StatusCreated; got != want { + t.Errorf("Checks.ReRequestCheckSuite = %v, want %v", got, want) + } +} diff --git a/vendor/github.com/google/go-github/github/doc.go b/vendor/github.com/google/go-github/github/doc.go new file mode 100644 index 0000000..96445d2 --- /dev/null +++ b/vendor/github.com/google/go-github/github/doc.go @@ -0,0 +1,187 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package github provides a client for using the GitHub API. + +Usage: + + import "github.com/google/go-github/github" + +Construct a new GitHub client, then use the various services on the client to +access different parts of the GitHub API. For example: + + client := github.NewClient(nil) + + // list all organizations for user "willnorris" + orgs, _, err := client.Organizations.List(ctx, "willnorris", nil) + +Some API methods have optional parameters that can be passed. For example: + + client := github.NewClient(nil) + + // list public repositories for org "github" + opt := &github.RepositoryListByOrgOptions{Type: "public"} + repos, _, err := client.Repositories.ListByOrg(ctx, "github", opt) + +The services of a client divide the API into logical chunks and correspond to +the structure of the GitHub API documentation at +https://developer.github.com/v3/. + +NOTE: Using the https://godoc.org/context package, one can easily +pass cancelation signals and deadlines to various services of the client for +handling a request. In case there is no context available, then context.Background() +can be used as a starting point. + +For more sample code snippets, head over to the https://github.com/google/go-github/tree/master/example directory. + +Authentication + +The go-github library does not directly handle authentication. Instead, when +creating a new client, pass an http.Client that can handle authentication for +you. The easiest and recommended way to do this is using the golang.org/x/oauth2 +library, but you can always use any other library that provides an http.Client. +If you have an OAuth2 access token (for example, a personal API token), you can +use it with the oauth2 library using: + + import "golang.org/x/oauth2" + + func main() { + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: "... your access token ..."}, + ) + tc := oauth2.NewClient(ctx, ts) + + client := github.NewClient(tc) + + // list all repositories for the authenticated user + repos, _, err := client.Repositories.List(ctx, "", nil) + } + +Note that when using an authenticated Client, all calls made by the client will +include the specified OAuth token. Therefore, authenticated clients should +almost never be shared between different users. + +See the oauth2 docs for complete instructions on using that library. + +For API methods that require HTTP Basic Authentication, use the +BasicAuthTransport. + +GitHub Apps authentication can be provided by the +https://github.com/bradleyfalzon/ghinstallation package. + + import "github.com/bradleyfalzon/ghinstallation" + + func main() { + // Wrap the shared transport for use with the integration ID 1 authenticating with installation ID 99. + itr, err := ghinstallation.NewKeyFromFile(http.DefaultTransport, 1, 99, "2016-10-19.private-key.pem") + if err != nil { + // Handle error. + } + + // Use installation transport with client + client := github.NewClient(&http.Client{Transport: itr}) + + // Use client... + } + +Rate Limiting + +GitHub imposes a rate limit on all API clients. Unauthenticated clients are +limited to 60 requests per hour, while authenticated clients can make up to +5,000 requests per hour. The Search API has a custom rate limit. Unauthenticated +clients are limited to 10 requests per minute, while authenticated clients +can make up to 30 requests per minute. To receive the higher rate limit when +making calls that are not issued on behalf of a user, +use UnauthenticatedRateLimitedTransport. + +The returned Response.Rate value contains the rate limit information +from the most recent API call. If a recent enough response isn't +available, you can use RateLimits to fetch the most up-to-date rate +limit data for the client. + +To detect an API rate limit error, you can check if its type is *github.RateLimitError: + + repos, _, err := client.Repositories.List(ctx, "", nil) + if _, ok := err.(*github.RateLimitError); ok { + log.Println("hit rate limit") + } + +Learn more about GitHub rate limiting at +https://developer.github.com/v3/#rate-limiting. + +Accepted Status + +Some endpoints may return a 202 Accepted status code, meaning that the +information required is not yet ready and was scheduled to be gathered on +the GitHub side. Methods known to behave like this are documented specifying +this behavior. + +To detect this condition of error, you can check if its type is +*github.AcceptedError: + + stats, _, err := client.Repositories.ListContributorsStats(ctx, org, repo) + if _, ok := err.(*github.AcceptedError); ok { + log.Println("scheduled on GitHub side") + } + +Conditional Requests + +The GitHub API has good support for conditional requests which will help +prevent you from burning through your rate limit, as well as help speed up your +application. go-github does not handle conditional requests directly, but is +instead designed to work with a caching http.Transport. We recommend using +https://github.com/gregjones/httpcache for that. + +Learn more about GitHub conditional requests at +https://developer.github.com/v3/#conditional-requests. + +Creating and Updating Resources + +All structs for GitHub resources use pointer values for all non-repeated fields. +This allows distinguishing between unset fields and those set to a zero-value. +Helper functions have been provided to easily create these pointers for string, +bool, and int values. For example: + + // create a new private repository named "foo" + repo := &github.Repository{ + Name: github.String("foo"), + Private: github.Bool(true), + } + client.Repositories.Create(ctx, "", repo) + +Users who have worked with protocol buffers should find this pattern familiar. + +Pagination + +All requests for resource collections (repos, pull requests, issues, etc.) +support pagination. Pagination options are described in the +github.ListOptions struct and passed to the list methods directly or as an +embedded type of a more specific list options struct (for example +github.PullRequestListOptions). Pages information is available via the +github.Response struct. + + client := github.NewClient(nil) + + opt := &github.RepositoryListByOrgOptions{ + ListOptions: github.ListOptions{PerPage: 10}, + } + // get all pages of results + var allRepos []*github.Repository + for { + repos, resp, err := client.Repositories.ListByOrg(ctx, "github", opt) + if err != nil { + return err + } + allRepos = append(allRepos, repos...) + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + +*/ +package github diff --git a/vendor/github.com/google/go-github/github/event_types.go b/vendor/github.com/google/go-github/github/event_types.go new file mode 100644 index 0000000..9f49b34 --- /dev/null +++ b/vendor/github.com/google/go-github/github/event_types.go @@ -0,0 +1,791 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// These event types are shared between the Events API and used as Webhook payloads. + +package github + +// CheckRunEvent is triggered when a check run is "created", "updated", or "re-requested". +// The Webhook event name is "check_run". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#checkrunevent +type CheckRunEvent struct { + CheckRun *CheckRun `json:"check_run,omitempty"` + // The action performed. Can be "created", "updated" or "re-requested". + Action *string `json:"action,omitempty"` + + // The following fields are only populated by Webhook events. + Repo *Repository `json:"repository,omitempty"` + Org *Organization `json:"organization,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// CheckSuiteEvent is triggered when a check suite is "completed", "requested", or "re-requested". +// The Webhook event name is "check_suite". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#checksuiteevent +type CheckSuiteEvent struct { + CheckSuite *CheckSuite `json:"check_suite,omitempty"` + // The action performed. Can be "completed", "requested" or "re-requested". + Action *string `json:"action,omitempty"` + + // The following fields are only populated by Webhook events. + Repo *Repository `json:"repository,omitempty"` + Org *Organization `json:"organization,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// CommitCommentEvent is triggered when a commit comment is created. +// The Webhook event name is "commit_comment". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#commitcommentevent +type CommitCommentEvent struct { + Comment *RepositoryComment `json:"comment,omitempty"` + + // The following fields are only populated by Webhook events. + Action *string `json:"action,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// CreateEvent represents a created repository, branch, or tag. +// The Webhook event name is "create". +// +// Note: webhooks will not receive this event for created repositories. +// Additionally, webhooks will not receive this event for tags if more +// than three tags are pushed at once. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#createevent +type CreateEvent struct { + Ref *string `json:"ref,omitempty"` + // RefType is the object that was created. Possible values are: "repository", "branch", "tag". + RefType *string `json:"ref_type,omitempty"` + MasterBranch *string `json:"master_branch,omitempty"` + Description *string `json:"description,omitempty"` + + // The following fields are only populated by Webhook events. + PusherType *string `json:"pusher_type,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// DeleteEvent represents a deleted branch or tag. +// The Webhook event name is "delete". +// +// Note: webhooks will not receive this event for tags if more than three tags +// are deleted at once. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#deleteevent +type DeleteEvent struct { + Ref *string `json:"ref,omitempty"` + // RefType is the object that was deleted. Possible values are: "branch", "tag". + RefType *string `json:"ref_type,omitempty"` + + // The following fields are only populated by Webhook events. + PusherType *string `json:"pusher_type,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// DeploymentEvent represents a deployment. +// The Webhook event name is "deployment". +// +// Events of this type are not visible in timelines, they are only used to trigger hooks. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#deploymentevent +type DeploymentEvent struct { + Deployment *Deployment `json:"deployment,omitempty"` + Repo *Repository `json:"repository,omitempty"` + + // The following fields are only populated by Webhook events. + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// DeploymentStatusEvent represents a deployment status. +// The Webhook event name is "deployment_status". +// +// Events of this type are not visible in timelines, they are only used to trigger hooks. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#deploymentstatusevent +type DeploymentStatusEvent struct { + Deployment *Deployment `json:"deployment,omitempty"` + DeploymentStatus *DeploymentStatus `json:"deployment_status,omitempty"` + Repo *Repository `json:"repository,omitempty"` + + // The following fields are only populated by Webhook events. + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// ForkEvent is triggered when a user forks a repository. +// The Webhook event name is "fork". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#forkevent +type ForkEvent struct { + // Forkee is the created repository. + Forkee *Repository `json:"forkee,omitempty"` + + // The following fields are only populated by Webhook events. + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// Page represents a single Wiki page. +type Page struct { + PageName *string `json:"page_name,omitempty"` + Title *string `json:"title,omitempty"` + Summary *string `json:"summary,omitempty"` + Action *string `json:"action,omitempty"` + SHA *string `json:"sha,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` +} + +// GollumEvent is triggered when a Wiki page is created or updated. +// The Webhook event name is "gollum". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#gollumevent +type GollumEvent struct { + Pages []*Page `json:"pages,omitempty"` + + // The following fields are only populated by Webhook events. + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// EditChange represents the changes when an issue, pull request, or comment has +// been edited. +type EditChange struct { + Title *struct { + From *string `json:"from,omitempty"` + } `json:"title,omitempty"` + Body *struct { + From *string `json:"from,omitempty"` + } `json:"body,omitempty"` +} + +// ProjectChange represents the changes when a project has been edited. +type ProjectChange struct { + Name *struct { + From *string `json:"from,omitempty"` + } `json:"name,omitempty"` + Body *struct { + From *string `json:"from,omitempty"` + } `json:"body,omitempty"` +} + +// ProjectCardChange represents the changes when a project card has been edited. +type ProjectCardChange struct { + Note *struct { + From *string `json:"from,omitempty"` + } `json:"note,omitempty"` +} + +// ProjectColumnChange represents the changes when a project column has been edited. +type ProjectColumnChange struct { + Name *struct { + From *string `json:"from,omitempty"` + } `json:"name,omitempty"` +} + +// TeamChange represents the changes when a team has been edited. +type TeamChange struct { + Description *struct { + From *string `json:"from,omitempty"` + } `json:"description,omitempty"` + Name *struct { + From *string `json:"from,omitempty"` + } `json:"name,omitempty"` + Privacy *struct { + From *string `json:"from,omitempty"` + } `json:"privacy,omitempty"` + Repository *struct { + Permissions *struct { + From *struct { + Admin *bool `json:"admin,omitempty"` + Pull *bool `json:"pull,omitempty"` + Push *bool `json:"push,omitempty"` + } `json:"from,omitempty"` + } `json:"permissions,omitempty"` + } `json:"repository,omitempty"` +} + +// InstallationEvent is triggered when a GitHub App has been installed or uninstalled. +// The Webhook event name is "installation". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#installationevent +type InstallationEvent struct { + // The action that was performed. Can be either "created" or "deleted". + Action *string `json:"action,omitempty"` + Repositories []*Repository `json:"repositories,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// InstallationRepositoriesEvent is triggered when a repository is added or +// removed from an installation. The Webhook event name is "installation_repositories". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#installationrepositoriesevent +type InstallationRepositoriesEvent struct { + // The action that was performed. Can be either "added" or "removed". + Action *string `json:"action,omitempty"` + RepositoriesAdded []*Repository `json:"repositories_added,omitempty"` + RepositoriesRemoved []*Repository `json:"repositories_removed,omitempty"` + RepositorySelection *string `json:"repository_selection,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// IssueCommentEvent is triggered when an issue comment is created on an issue +// or pull request. +// The Webhook event name is "issue_comment". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#issuecommentevent +type IssueCommentEvent struct { + // Action is the action that was performed on the comment. + // Possible values are: "created", "edited", "deleted". + Action *string `json:"action,omitempty"` + Issue *Issue `json:"issue,omitempty"` + Comment *IssueComment `json:"comment,omitempty"` + + // The following fields are only populated by Webhook events. + Changes *EditChange `json:"changes,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// IssuesEvent is triggered when an issue is assigned, unassigned, labeled, +// unlabeled, opened, closed, or reopened. +// The Webhook event name is "issues". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#issuesevent +type IssuesEvent struct { + // Action is the action that was performed. Possible values are: "assigned", + // "unassigned", "labeled", "unlabeled", "opened", "closed", "reopened", "edited". + Action *string `json:"action,omitempty"` + Issue *Issue `json:"issue,omitempty"` + Assignee *User `json:"assignee,omitempty"` + Label *Label `json:"label,omitempty"` + + // The following fields are only populated by Webhook events. + Changes *EditChange `json:"changes,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// LabelEvent is triggered when a repository's label is created, edited, or deleted. +// The Webhook event name is "label" +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#labelevent +type LabelEvent struct { + // Action is the action that was performed. Possible values are: + // "created", "edited", "deleted" + Action *string `json:"action,omitempty"` + Label *Label `json:"label,omitempty"` + + // The following fields are only populated by Webhook events. + Changes *EditChange `json:"changes,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Org *Organization `json:"organization,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// MarketplacePurchaseEvent is triggered when a user purchases, cancels, or changes +// their GitHub Marketplace plan. +// Webhook event name "marketplace_purchase". +// +// Github API docs: https://developer.github.com/v3/activity/events/types/#marketplacepurchaseevent +type MarketplacePurchaseEvent struct { + // Action is the action that was performed. Possible values are: + // "purchased", "cancelled", "changed". + Action *string `json:"action,omitempty"` + + // The following fields are only populated by Webhook events. + EffectiveDate *Timestamp `json:"effective_date,omitempty"` + MarketplacePurchase *MarketplacePurchase `json:"marketplace_purchase,omitempty"` + PreviousMarketplacePurchase *MarketplacePurchase `json:"previous_marketplace_purchase,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// MemberEvent is triggered when a user is added as a collaborator to a repository. +// The Webhook event name is "member". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#memberevent +type MemberEvent struct { + // Action is the action that was performed. Possible value is: "added". + Action *string `json:"action,omitempty"` + Member *User `json:"member,omitempty"` + + // The following fields are only populated by Webhook events. + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// MembershipEvent is triggered when a user is added or removed from a team. +// The Webhook event name is "membership". +// +// Events of this type are not visible in timelines, they are only used to +// trigger organization webhooks. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#membershipevent +type MembershipEvent struct { + // Action is the action that was performed. Possible values are: "added", "removed". + Action *string `json:"action,omitempty"` + // Scope is the scope of the membership. Possible value is: "team". + Scope *string `json:"scope,omitempty"` + Member *User `json:"member,omitempty"` + Team *Team `json:"team,omitempty"` + + // The following fields are only populated by Webhook events. + Org *Organization `json:"organization,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// MilestoneEvent is triggered when a milestone is created, closed, opened, edited, or deleted. +// The Webhook event name is "milestone". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#milestoneevent +type MilestoneEvent struct { + // Action is the action that was performed. Possible values are: + // "created", "closed", "opened", "edited", "deleted" + Action *string `json:"action,omitempty"` + Milestone *Milestone `json:"milestone,omitempty"` + + // The following fields are only populated by Webhook events. + Changes *EditChange `json:"changes,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Org *Organization `json:"organization,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// OrganizationEvent is triggered when a user is added, removed, or invited to an organization. +// Events of this type are not visible in timelines. These events are only used to trigger organization hooks. +// Webhook event name is "organization". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#organizationevent +type OrganizationEvent struct { + // Action is the action that was performed. + // Can be one of "member_added", "member_removed", or "member_invited". + Action *string `json:"action,omitempty"` + + // Invitaion is the invitation for the user or email if the action is "member_invited". + Invitation *Invitation `json:"invitation,omitempty"` + + // Membership is the membership between the user and the organization. + // Not present when the action is "member_invited". + Membership *Membership `json:"membership,omitempty"` + + Organization *Organization `json:"organization,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// OrgBlockEvent is triggered when an organization blocks or unblocks a user. +// The Webhook event name is "org_block". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#orgblockevent +type OrgBlockEvent struct { + // Action is the action that was performed. + // Can be "blocked" or "unblocked". + Action *string `json:"action,omitempty"` + BlockedUser *User `json:"blocked_user,omitempty"` + Organization *Organization `json:"organization,omitempty"` + Sender *User `json:"sender,omitempty"` + + // The following fields are only populated by Webhook events. + Installation *Installation `json:"installation,omitempty"` +} + +// PageBuildEvent represents an attempted build of a GitHub Pages site, whether +// successful or not. +// The Webhook event name is "page_build". +// +// This event is triggered on push to a GitHub Pages enabled branch (gh-pages +// for project pages, master for user and organization pages). +// +// Events of this type are not visible in timelines, they are only used to trigger hooks. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#pagebuildevent +type PageBuildEvent struct { + Build *PagesBuild `json:"build,omitempty"` + + // The following fields are only populated by Webhook events. + ID *int64 `json:"id,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// PingEvent is triggered when a Webhook is added to GitHub. +// +// GitHub API docs: https://developer.github.com/webhooks/#ping-event +type PingEvent struct { + // Random string of GitHub zen. + Zen *string `json:"zen,omitempty"` + // The ID of the webhook that triggered the ping. + HookID *int64 `json:"hook_id,omitempty"` + // The webhook configuration. + Hook *Hook `json:"hook,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// ProjectEvent is triggered when project is created, modified or deleted. +// The webhook event name is "project". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#projectevent +type ProjectEvent struct { + Action *string `json:"action,omitempty"` + Changes *ProjectChange `json:"changes,omitempty"` + Project *Project `json:"project,omitempty"` + + // The following fields are only populated by Webhook events. + Repo *Repository `json:"repository,omitempty"` + Org *Organization `json:"organization,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// ProjectCardEvent is triggered when a project card is created, updated, moved, converted to an issue, or deleted. +// The webhook event name is "project_card". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#projectcardevent +type ProjectCardEvent struct { + Action *string `json:"action,omitempty"` + Changes *ProjectCardChange `json:"changes,omitempty"` + AfterID *int64 `json:"after_id,omitempty"` + ProjectCard *ProjectCard `json:"project_card,omitempty"` + + // The following fields are only populated by Webhook events. + Repo *Repository `json:"repository,omitempty"` + Org *Organization `json:"organization,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// ProjectColumnEvent is triggered when a project column is created, updated, moved, or deleted. +// The webhook event name is "project_column". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#projectcolumnevent +type ProjectColumnEvent struct { + Action *string `json:"action,omitempty"` + Changes *ProjectColumnChange `json:"changes,omitempty"` + AfterID *int64 `json:"after_id,omitempty"` + ProjectColumn *ProjectColumn `json:"project_column,omitempty"` + + // The following fields are only populated by Webhook events. + Repo *Repository `json:"repository,omitempty"` + Org *Organization `json:"organization,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// PublicEvent is triggered when a private repository is open sourced. +// According to GitHub: "Without a doubt: the best GitHub event." +// The Webhook event name is "public". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#publicevent +type PublicEvent struct { + // The following fields are only populated by Webhook events. + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// PullRequestEvent is triggered when a pull request is assigned, unassigned, +// labeled, unlabeled, opened, closed, reopened, or synchronized. +// The Webhook event name is "pull_request". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#pullrequestevent +type PullRequestEvent struct { + // Action is the action that was performed. Possible values are: + // "assigned", "unassigned", "review_requested", "review_request_removed", "labeled", "unlabeled", + // "opened", "closed", "reopened", "synchronize", "edited". + // If the action is "closed" and the merged key is false, + // the pull request was closed with unmerged commits. If the action is "closed" + // and the merged key is true, the pull request was merged. + Action *string `json:"action,omitempty"` + Assignee *User `json:"assignee,omitempty"` + Number *int `json:"number,omitempty"` + PullRequest *PullRequest `json:"pull_request,omitempty"` + + // The following fields are only populated by Webhook events. + Changes *EditChange `json:"changes,omitempty"` + // RequestedReviewer is populated in "review_requested", "review_request_removed" event deliveries. + // A request affecting multiple reviewers at once is split into multiple + // such event deliveries, each with a single, different RequestedReviewer. + RequestedReviewer *User `json:"requested_reviewer,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` + Label *Label `json:"label,omitempty"` // Populated in "labeled" event deliveries. + + // The following field is only present when the webhook is triggered on + // a repository belonging to an organization. + Organization *Organization `json:"organization,omitempty"` +} + +// PullRequestReviewEvent is triggered when a review is submitted on a pull +// request. +// The Webhook event name is "pull_request_review". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#pullrequestreviewevent +type PullRequestReviewEvent struct { + // Action is always "submitted". + Action *string `json:"action,omitempty"` + Review *PullRequestReview `json:"review,omitempty"` + PullRequest *PullRequest `json:"pull_request,omitempty"` + + // The following fields are only populated by Webhook events. + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` + + // The following field is only present when the webhook is triggered on + // a repository belonging to an organization. + Organization *Organization `json:"organization,omitempty"` +} + +// PullRequestReviewCommentEvent is triggered when a comment is created on a +// portion of the unified diff of a pull request. +// The Webhook event name is "pull_request_review_comment". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#pullrequestreviewcommentevent +type PullRequestReviewCommentEvent struct { + // Action is the action that was performed on the comment. + // Possible values are: "created", "edited", "deleted". + Action *string `json:"action,omitempty"` + PullRequest *PullRequest `json:"pull_request,omitempty"` + Comment *PullRequestComment `json:"comment,omitempty"` + + // The following fields are only populated by Webhook events. + Changes *EditChange `json:"changes,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// PushEvent represents a git push to a GitHub repository. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#pushevent +type PushEvent struct { + PushID *int64 `json:"push_id,omitempty"` + Head *string `json:"head,omitempty"` + Ref *string `json:"ref,omitempty"` + Size *int `json:"size,omitempty"` + Commits []PushEventCommit `json:"commits,omitempty"` + Before *string `json:"before,omitempty"` + DistinctSize *int `json:"distinct_size,omitempty"` + + // The following fields are only populated by Webhook events. + After *string `json:"after,omitempty"` + Created *bool `json:"created,omitempty"` + Deleted *bool `json:"deleted,omitempty"` + Forced *bool `json:"forced,omitempty"` + BaseRef *string `json:"base_ref,omitempty"` + Compare *string `json:"compare,omitempty"` + Repo *PushEventRepository `json:"repository,omitempty"` + HeadCommit *PushEventCommit `json:"head_commit,omitempty"` + Pusher *User `json:"pusher,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +func (p PushEvent) String() string { + return Stringify(p) +} + +// PushEventCommit represents a git commit in a GitHub PushEvent. +type PushEventCommit struct { + Message *string `json:"message,omitempty"` + Author *CommitAuthor `json:"author,omitempty"` + URL *string `json:"url,omitempty"` + Distinct *bool `json:"distinct,omitempty"` + + // The following fields are only populated by Events API. + SHA *string `json:"sha,omitempty"` + + // The following fields are only populated by Webhook events. + ID *string `json:"id,omitempty"` + TreeID *string `json:"tree_id,omitempty"` + Timestamp *Timestamp `json:"timestamp,omitempty"` + Committer *CommitAuthor `json:"committer,omitempty"` + Added []string `json:"added,omitempty"` + Removed []string `json:"removed,omitempty"` + Modified []string `json:"modified,omitempty"` +} + +func (p PushEventCommit) String() string { + return Stringify(p) +} + +// PushEventRepository represents the repo object in a PushEvent payload. +type PushEventRepository struct { + ID *int64 `json:"id,omitempty"` + NodeID *string `json:"node_id,omitempty"` + Name *string `json:"name,omitempty"` + FullName *string `json:"full_name,omitempty"` + Owner *User `json:"owner,omitempty"` + Private *bool `json:"private,omitempty"` + Description *string `json:"description,omitempty"` + Fork *bool `json:"fork,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + PushedAt *Timestamp `json:"pushed_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + Homepage *string `json:"homepage,omitempty"` + Size *int `json:"size,omitempty"` + StargazersCount *int `json:"stargazers_count,omitempty"` + WatchersCount *int `json:"watchers_count,omitempty"` + Language *string `json:"language,omitempty"` + HasIssues *bool `json:"has_issues,omitempty"` + HasDownloads *bool `json:"has_downloads,omitempty"` + HasWiki *bool `json:"has_wiki,omitempty"` + HasPages *bool `json:"has_pages,omitempty"` + ForksCount *int `json:"forks_count,omitempty"` + OpenIssuesCount *int `json:"open_issues_count,omitempty"` + DefaultBranch *string `json:"default_branch,omitempty"` + MasterBranch *string `json:"master_branch,omitempty"` + Organization *string `json:"organization,omitempty"` + URL *string `json:"url,omitempty"` + ArchiveURL *string `json:"archive_url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + StatusesURL *string `json:"statuses_url,omitempty"` + GitURL *string `json:"git_url,omitempty"` + SSHURL *string `json:"ssh_url,omitempty"` + CloneURL *string `json:"clone_url,omitempty"` + SVNURL *string `json:"svn_url,omitempty"` +} + +// PushEventRepoOwner is a basic representation of user/org in a PushEvent payload. +type PushEventRepoOwner struct { + Name *string `json:"name,omitempty"` + Email *string `json:"email,omitempty"` +} + +// ReleaseEvent is triggered when a release is published. +// The Webhook event name is "release". +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#releaseevent +type ReleaseEvent struct { + // Action is the action that was performed. Possible value is: "published". + Action *string `json:"action,omitempty"` + Release *RepositoryRelease `json:"release,omitempty"` + + // The following fields are only populated by Webhook events. + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// RepositoryEvent is triggered when a repository is created. +// The Webhook event name is "repository". +// +// Events of this type are not visible in timelines, they are only used to +// trigger organization webhooks. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#repositoryevent +type RepositoryEvent struct { + // Action is the action that was performed. Possible values are: "created", "deleted", + // "publicized", "privatized". + Action *string `json:"action,omitempty"` + Repo *Repository `json:"repository,omitempty"` + + // The following fields are only populated by Webhook events. + Org *Organization `json:"organization,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// StatusEvent is triggered when the status of a Git commit changes. +// The Webhook event name is "status". +// +// Events of this type are not visible in timelines, they are only used to +// trigger hooks. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#statusevent +type StatusEvent struct { + SHA *string `json:"sha,omitempty"` + // State is the new state. Possible values are: "pending", "success", "failure", "error". + State *string `json:"state,omitempty"` + Description *string `json:"description,omitempty"` + TargetURL *string `json:"target_url,omitempty"` + Branches []*Branch `json:"branches,omitempty"` + + // The following fields are only populated by Webhook events. + ID *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Context *string `json:"context,omitempty"` + Commit *RepositoryCommit `json:"commit,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// TeamEvent is triggered when an organization's team is created, modified or deleted. +// The Webhook event name is "team". +// +// Events of this type are not visible in timelines. These events are only used +// to trigger hooks. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#teamevent +type TeamEvent struct { + Action *string `json:"action,omitempty"` + Team *Team `json:"team,omitempty"` + Changes *TeamChange `json:"changes,omitempty"` + Repo *Repository `json:"repository,omitempty"` + + // The following fields are only populated by Webhook events. + Org *Organization `json:"organization,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// TeamAddEvent is triggered when a repository is added to a team. +// The Webhook event name is "team_add". +// +// Events of this type are not visible in timelines. These events are only used +// to trigger hooks. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#teamaddevent +type TeamAddEvent struct { + Team *Team `json:"team,omitempty"` + Repo *Repository `json:"repository,omitempty"` + + // The following fields are only populated by Webhook events. + Org *Organization `json:"organization,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// WatchEvent is related to starring a repository, not watching. See this API +// blog post for an explanation: https://developer.github.com/changes/2012-09-05-watcher-api/ +// +// The event’s actor is the user who starred a repository, and the event’s +// repository is the repository that was starred. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/types/#watchevent +type WatchEvent struct { + // Action is the action that was performed. Possible value is: "started". + Action *string `json:"action,omitempty"` + + // The following fields are only populated by Webhook events. + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} diff --git a/vendor/github.com/google/go-github/github/examples_test.go b/vendor/github.com/google/go-github/github/examples_test.go new file mode 100644 index 0000000..942b0b7 --- /dev/null +++ b/vendor/github.com/google/go-github/github/examples_test.go @@ -0,0 +1,168 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github_test + +import ( + "context" + "fmt" + "log" + + "github.com/google/go-github/v18/github" +) + +func ExampleClient_Markdown() { + client := github.NewClient(nil) + + input := "# heading #\n\nLink to issue #1" + opt := &github.MarkdownOptions{Mode: "gfm", Context: "google/go-github"} + + output, _, err := client.Markdown(context.Background(), input, opt) + if err != nil { + fmt.Println(err) + } + + fmt.Println(output) +} + +func ExampleRepositoriesService_GetReadme() { + client := github.NewClient(nil) + + readme, _, err := client.Repositories.GetReadme(context.Background(), "google", "go-github", nil) + if err != nil { + fmt.Println(err) + return + } + + content, err := readme.GetContent() + if err != nil { + fmt.Println(err) + return + } + + fmt.Printf("google/go-github README:\n%v\n", content) +} + +func ExampleRepositoriesService_List() { + client := github.NewClient(nil) + + user := "willnorris" + opt := &github.RepositoryListOptions{Type: "owner", Sort: "updated", Direction: "desc"} + + repos, _, err := client.Repositories.List(context.Background(), user, opt) + if err != nil { + fmt.Println(err) + } + + fmt.Printf("Recently updated repositories by %q: %v", user, github.Stringify(repos)) +} + +func ExampleRepositoriesService_CreateFile() { + // In this example we're creating a new file in a repository using the + // Contents API. Only 1 file per commit can be managed through that API. + + // Note that authentication is needed here as you are performing a modification + // so you will need to modify the example to provide an oauth client to + // github.NewClient() instead of nil. See the following documentation for more + // information on how to authenticate with the client: + // https://godoc.org/github.com/google/go-github/github#hdr-Authentication + client := github.NewClient(nil) + + ctx := context.Background() + fileContent := []byte("This is the content of my file\nand the 2nd line of it") + + // Note: the file needs to be absent from the repository as you are not + // specifying a SHA reference here. + opts := &github.RepositoryContentFileOptions{ + Message: github.String("This is my commit message"), + Content: fileContent, + Branch: github.String("master"), + Committer: &github.CommitAuthor{Name: github.String("FirstName LastName"), Email: github.String("user@example.com")}, + } + _, _, err := client.Repositories.CreateFile(ctx, "myOrganization", "myRepository", "myNewFile.md", opts) + if err != nil { + fmt.Println(err) + return + } +} + +func ExampleUsersService_ListAll() { + client := github.NewClient(nil) + opts := &github.UserListOptions{} + for { + users, _, err := client.Users.ListAll(context.Background(), opts) + if err != nil { + log.Fatalf("error listing users: %v", err) + } + if len(users) == 0 { + break + } + opts.Since = *users[len(users)-1].ID + // Process users... + } +} + +func ExamplePullRequestsService_Create() { + // In this example we're creating a PR and displaying the HTML url at the end. + + // Note that authentication is needed here as you are performing a modification + // so you will need to modify the example to provide an oauth client to + // github.NewClient() instead of nil. See the following documentation for more + // information on how to authenticate with the client: + // https://godoc.org/github.com/google/go-github/github#hdr-Authentication + client := github.NewClient(nil) + + newPR := &github.NewPullRequest{ + Title: github.String("My awesome pull request"), + Head: github.String("branch_to_merge"), + Base: github.String("master"), + Body: github.String("This is the description of the PR created with the package `github.com/google/go-github/github`"), + MaintainerCanModify: github.Bool(true), + } + + pr, _, err := client.PullRequests.Create(context.Background(), "myOrganization", "myRepository", newPR) + if err != nil { + fmt.Println(err) + return + } + + fmt.Printf("PR created: %s\n", pr.GetHTMLURL()) +} + +func ExampleTeamsService_ListTeams() { + // This example shows how to get a team ID corresponding to a given team name. + + // Note that authentication is needed here as you are performing a lookup on + // an organization's administrative configuration, so you will need to modify + // the example to provide an oauth client to github.NewClient() instead of nil. + // See the following documentation for more information on how to authenticate + // with the client: + // https://godoc.org/github.com/google/go-github/github#hdr-Authentication + client := github.NewClient(nil) + + teamName := "Developers team" + ctx := context.Background() + opts := &github.ListOptions{} + + for { + teams, resp, err := client.Teams.ListTeams(ctx, "myOrganization", opts) + if err != nil { + fmt.Println(err) + return + } + for _, t := range teams { + if t.GetName() == teamName { + fmt.Printf("Team %q has ID %d\n", teamName, t.GetID()) + return + } + } + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + + fmt.Printf("Team %q was not found\n", teamName) +} diff --git a/vendor/github.com/google/go-github/github/gen-accessors.go b/vendor/github.com/google/go-github/github/gen-accessors.go new file mode 100644 index 0000000..fe92206 --- /dev/null +++ b/vendor/github.com/google/go-github/github/gen-accessors.go @@ -0,0 +1,332 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ignore + +// gen-accessors generates accessor methods for structs with pointer fields. +// +// It is meant to be used by the go-github authors in conjunction with the +// go generate tool before sending a commit to GitHub. +package main + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "io/ioutil" + "log" + "os" + "sort" + "strings" + "text/template" +) + +const ( + fileSuffix = "-accessors.go" +) + +var ( + verbose = flag.Bool("v", false, "Print verbose log messages") + + sourceTmpl = template.Must(template.New("source").Parse(source)) + + // blacklistStructMethod lists "struct.method" combos to skip. + blacklistStructMethod = map[string]bool{ + "RepositoryContent.GetContent": true, + "Client.GetBaseURL": true, + "Client.GetUploadURL": true, + "ErrorResponse.GetResponse": true, + "RateLimitError.GetResponse": true, + "AbuseRateLimitError.GetResponse": true, + } + // blacklistStruct lists structs to skip. + blacklistStruct = map[string]bool{ + "Client": true, + } +) + +func logf(fmt string, args ...interface{}) { + if *verbose { + log.Printf(fmt, args...) + } +} + +func main() { + flag.Parse() + fset := token.NewFileSet() + + pkgs, err := parser.ParseDir(fset, ".", sourceFilter, 0) + if err != nil { + log.Fatal(err) + return + } + + for pkgName, pkg := range pkgs { + t := &templateData{ + filename: pkgName + fileSuffix, + Year: 2017, + Package: pkgName, + Imports: map[string]string{}, + } + for filename, f := range pkg.Files { + logf("Processing %v...", filename) + if err := t.processAST(f); err != nil { + log.Fatal(err) + } + } + if err := t.dump(); err != nil { + log.Fatal(err) + } + } + logf("Done.") +} + +func (t *templateData) processAST(f *ast.File) error { + for _, decl := range f.Decls { + gd, ok := decl.(*ast.GenDecl) + if !ok { + continue + } + for _, spec := range gd.Specs { + ts, ok := spec.(*ast.TypeSpec) + if !ok { + continue + } + // Skip unexported identifiers. + if !ts.Name.IsExported() { + logf("Struct %v is unexported; skipping.", ts.Name) + continue + } + // Check if the struct is blacklisted. + if blacklistStruct[ts.Name.Name] { + logf("Struct %v is blacklisted; skipping.", ts.Name) + continue + } + st, ok := ts.Type.(*ast.StructType) + if !ok { + continue + } + for _, field := range st.Fields.List { + se, ok := field.Type.(*ast.StarExpr) + if len(field.Names) == 0 || !ok { + continue + } + + fieldName := field.Names[0] + // Skip unexported identifiers. + if !fieldName.IsExported() { + logf("Field %v is unexported; skipping.", fieldName) + continue + } + // Check if "struct.method" is blacklisted. + if key := fmt.Sprintf("%v.Get%v", ts.Name, fieldName); blacklistStructMethod[key] { + logf("Method %v is blacklisted; skipping.", key) + continue + } + + switch x := se.X.(type) { + case *ast.ArrayType: + t.addArrayType(x, ts.Name.String(), fieldName.String()) + case *ast.Ident: + t.addIdent(x, ts.Name.String(), fieldName.String()) + case *ast.MapType: + t.addMapType(x, ts.Name.String(), fieldName.String()) + case *ast.SelectorExpr: + t.addSelectorExpr(x, ts.Name.String(), fieldName.String()) + default: + logf("processAST: type %q, field %q, unknown %T: %+v", ts.Name, fieldName, x, x) + } + } + } + } + return nil +} + +func sourceFilter(fi os.FileInfo) bool { + return !strings.HasSuffix(fi.Name(), "_test.go") && !strings.HasSuffix(fi.Name(), fileSuffix) +} + +func (t *templateData) dump() error { + if len(t.Getters) == 0 { + logf("No getters for %v; skipping.", t.filename) + return nil + } + + // Sort getters by ReceiverType.FieldName. + sort.Sort(byName(t.Getters)) + + var buf bytes.Buffer + if err := sourceTmpl.Execute(&buf, t); err != nil { + return err + } + clean, err := format.Source(buf.Bytes()) + if err != nil { + return err + } + + logf("Writing %v...", t.filename) + return ioutil.WriteFile(t.filename, clean, 0644) +} + +func newGetter(receiverType, fieldName, fieldType, zeroValue string, namedStruct bool) *getter { + return &getter{ + sortVal: strings.ToLower(receiverType) + "." + strings.ToLower(fieldName), + ReceiverVar: strings.ToLower(receiverType[:1]), + ReceiverType: receiverType, + FieldName: fieldName, + FieldType: fieldType, + ZeroValue: zeroValue, + NamedStruct: namedStruct, + } +} + +func (t *templateData) addArrayType(x *ast.ArrayType, receiverType, fieldName string) { + var eltType string + switch elt := x.Elt.(type) { + case *ast.Ident: + eltType = elt.String() + default: + logf("addArrayType: type %q, field %q: unknown elt type: %T %+v; skipping.", receiverType, fieldName, elt, elt) + return + } + + t.Getters = append(t.Getters, newGetter(receiverType, fieldName, "[]"+eltType, "nil", false)) +} + +func (t *templateData) addIdent(x *ast.Ident, receiverType, fieldName string) { + var zeroValue string + var namedStruct = false + switch x.String() { + case "int", "int64": + zeroValue = "0" + case "string": + zeroValue = `""` + case "bool": + zeroValue = "false" + case "Timestamp": + zeroValue = "Timestamp{}" + default: + zeroValue = "nil" + namedStruct = true + } + + t.Getters = append(t.Getters, newGetter(receiverType, fieldName, x.String(), zeroValue, namedStruct)) +} + +func (t *templateData) addMapType(x *ast.MapType, receiverType, fieldName string) { + var keyType string + switch key := x.Key.(type) { + case *ast.Ident: + keyType = key.String() + default: + logf("addMapType: type %q, field %q: unknown key type: %T %+v; skipping.", receiverType, fieldName, key, key) + return + } + + var valueType string + switch value := x.Value.(type) { + case *ast.Ident: + valueType = value.String() + default: + logf("addMapType: type %q, field %q: unknown value type: %T %+v; skipping.", receiverType, fieldName, value, value) + return + } + + fieldType := fmt.Sprintf("map[%v]%v", keyType, valueType) + zeroValue := fmt.Sprintf("map[%v]%v{}", keyType, valueType) + t.Getters = append(t.Getters, newGetter(receiverType, fieldName, fieldType, zeroValue, false)) +} + +func (t *templateData) addSelectorExpr(x *ast.SelectorExpr, receiverType, fieldName string) { + if strings.ToLower(fieldName[:1]) == fieldName[:1] { // Non-exported field. + return + } + + var xX string + if xx, ok := x.X.(*ast.Ident); ok { + xX = xx.String() + } + + switch xX { + case "time", "json": + if xX == "json" { + t.Imports["encoding/json"] = "encoding/json" + } else { + t.Imports[xX] = xX + } + fieldType := fmt.Sprintf("%v.%v", xX, x.Sel.Name) + zeroValue := fmt.Sprintf("%v.%v{}", xX, x.Sel.Name) + if xX == "time" && x.Sel.Name == "Duration" { + zeroValue = "0" + } + t.Getters = append(t.Getters, newGetter(receiverType, fieldName, fieldType, zeroValue, false)) + default: + logf("addSelectorExpr: xX %q, type %q, field %q: unknown x=%+v; skipping.", xX, receiverType, fieldName, x) + } +} + +type templateData struct { + filename string + Year int + Package string + Imports map[string]string + Getters []*getter +} + +type getter struct { + sortVal string // Lower-case version of "ReceiverType.FieldName". + ReceiverVar string // The one-letter variable name to match the ReceiverType. + ReceiverType string + FieldName string + FieldType string + ZeroValue string + NamedStruct bool // Getter for named struct. +} + +type byName []*getter + +func (b byName) Len() int { return len(b) } +func (b byName) Less(i, j int) bool { return b[i].sortVal < b[j].sortVal } +func (b byName) Swap(i, j int) { b[i], b[j] = b[j], b[i] } + +const source = `// Copyright {{.Year}} The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Code generated by gen-accessors; DO NOT EDIT. + +package {{.Package}} +{{with .Imports}} +import ( + {{- range . -}} + "{{.}}" + {{end -}} +) +{{end}} +{{range .Getters}} +{{if .NamedStruct}} +// Get{{.FieldName}} returns the {{.FieldName}} field. +func ({{.ReceiverVar}} *{{.ReceiverType}}) Get{{.FieldName}}() *{{.FieldType}} { + if {{.ReceiverVar}} == nil { + return {{.ZeroValue}} + } + return {{.ReceiverVar}}.{{.FieldName}} +} +{{else}} +// Get{{.FieldName}} returns the {{.FieldName}} field if it's non-nil, zero value otherwise. +func ({{.ReceiverVar}} *{{.ReceiverType}}) Get{{.FieldName}}() {{.FieldType}} { + if {{.ReceiverVar}} == nil || {{.ReceiverVar}}.{{.FieldName}} == nil { + return {{.ZeroValue}} + } + return *{{.ReceiverVar}}.{{.FieldName}} +} +{{end}} +{{end}} +` diff --git a/vendor/github.com/google/go-github/github/gists.go b/vendor/github.com/google/go-github/github/gists.go new file mode 100644 index 0000000..15e0bc2 --- /dev/null +++ b/vendor/github.com/google/go-github/github/gists.go @@ -0,0 +1,358 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// GistsService handles communication with the Gist related +// methods of the GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/gists/ +type GistsService service + +// Gist represents a GitHub's gist. +type Gist struct { + ID *string `json:"id,omitempty"` + Description *string `json:"description,omitempty"` + Public *bool `json:"public,omitempty"` + Owner *User `json:"owner,omitempty"` + Files map[GistFilename]GistFile `json:"files,omitempty"` + Comments *int `json:"comments,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + GitPullURL *string `json:"git_pull_url,omitempty"` + GitPushURL *string `json:"git_push_url,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + NodeID *string `json:"node_id,omitempty"` +} + +func (g Gist) String() string { + return Stringify(g) +} + +// GistFilename represents filename on a gist. +type GistFilename string + +// GistFile represents a file on a gist. +type GistFile struct { + Size *int `json:"size,omitempty"` + Filename *string `json:"filename,omitempty"` + Language *string `json:"language,omitempty"` + Type *string `json:"type,omitempty"` + RawURL *string `json:"raw_url,omitempty"` + Content *string `json:"content,omitempty"` +} + +func (g GistFile) String() string { + return Stringify(g) +} + +// GistCommit represents a commit on a gist. +type GistCommit struct { + URL *string `json:"url,omitempty"` + Version *string `json:"version,omitempty"` + User *User `json:"user,omitempty"` + ChangeStatus *CommitStats `json:"change_status,omitempty"` + CommittedAt *Timestamp `json:"committed_at,omitempty"` + NodeID *string `json:"node_id,omitempty"` +} + +func (gc GistCommit) String() string { + return Stringify(gc) +} + +// GistFork represents a fork of a gist. +type GistFork struct { + URL *string `json:"url,omitempty"` + User *User `json:"user,omitempty"` + ID *string `json:"id,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + NodeID *string `json:"node_id,omitempty"` +} + +func (gf GistFork) String() string { + return Stringify(gf) +} + +// GistListOptions specifies the optional parameters to the +// GistsService.List, GistsService.ListAll, and GistsService.ListStarred methods. +type GistListOptions struct { + // Since filters Gists by time. + Since time.Time `url:"since,omitempty"` + + ListOptions +} + +// List gists for a user. Passing the empty string will list +// all public gists if called anonymously. However, if the call +// is authenticated, it will returns all gists for the authenticated +// user. +// +// GitHub API docs: https://developer.github.com/v3/gists/#list-gists +func (s *GistsService) List(ctx context.Context, user string, opt *GistListOptions) ([]*Gist, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/gists", user) + } else { + u = "gists" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var gists []*Gist + resp, err := s.client.Do(ctx, req, &gists) + if err != nil { + return nil, resp, err + } + + return gists, resp, nil +} + +// ListAll lists all public gists. +// +// GitHub API docs: https://developer.github.com/v3/gists/#list-gists +func (s *GistsService) ListAll(ctx context.Context, opt *GistListOptions) ([]*Gist, *Response, error) { + u, err := addOptions("gists/public", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var gists []*Gist + resp, err := s.client.Do(ctx, req, &gists) + if err != nil { + return nil, resp, err + } + + return gists, resp, nil +} + +// ListStarred lists starred gists of authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/gists/#list-gists +func (s *GistsService) ListStarred(ctx context.Context, opt *GistListOptions) ([]*Gist, *Response, error) { + u, err := addOptions("gists/starred", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var gists []*Gist + resp, err := s.client.Do(ctx, req, &gists) + if err != nil { + return nil, resp, err + } + + return gists, resp, nil +} + +// Get a single gist. +// +// GitHub API docs: https://developer.github.com/v3/gists/#get-a-single-gist +func (s *GistsService) Get(ctx context.Context, id string) (*Gist, *Response, error) { + u := fmt.Sprintf("gists/%v", id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + gist := new(Gist) + resp, err := s.client.Do(ctx, req, gist) + if err != nil { + return nil, resp, err + } + + return gist, resp, nil +} + +// GetRevision gets a specific revision of a gist. +// +// GitHub API docs: https://developer.github.com/v3/gists/#get-a-specific-revision-of-a-gist +func (s *GistsService) GetRevision(ctx context.Context, id, sha string) (*Gist, *Response, error) { + u := fmt.Sprintf("gists/%v/%v", id, sha) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + gist := new(Gist) + resp, err := s.client.Do(ctx, req, gist) + if err != nil { + return nil, resp, err + } + + return gist, resp, nil +} + +// Create a gist for authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/gists/#create-a-gist +func (s *GistsService) Create(ctx context.Context, gist *Gist) (*Gist, *Response, error) { + u := "gists" + req, err := s.client.NewRequest("POST", u, gist) + if err != nil { + return nil, nil, err + } + + g := new(Gist) + resp, err := s.client.Do(ctx, req, g) + if err != nil { + return nil, resp, err + } + + return g, resp, nil +} + +// Edit a gist. +// +// GitHub API docs: https://developer.github.com/v3/gists/#edit-a-gist +func (s *GistsService) Edit(ctx context.Context, id string, gist *Gist) (*Gist, *Response, error) { + u := fmt.Sprintf("gists/%v", id) + req, err := s.client.NewRequest("PATCH", u, gist) + if err != nil { + return nil, nil, err + } + + g := new(Gist) + resp, err := s.client.Do(ctx, req, g) + if err != nil { + return nil, resp, err + } + + return g, resp, nil +} + +// ListCommits lists commits of a gist. +// +// GitHub API docs: https://developer.github.com/v3/gists/#list-gist-commits +func (s *GistsService) ListCommits(ctx context.Context, id string, opt *ListOptions) ([]*GistCommit, *Response, error) { + u := fmt.Sprintf("gists/%v/commits", id) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var gistCommits []*GistCommit + resp, err := s.client.Do(ctx, req, &gistCommits) + if err != nil { + return nil, resp, err + } + + return gistCommits, resp, nil +} + +// Delete a gist. +// +// GitHub API docs: https://developer.github.com/v3/gists/#delete-a-gist +func (s *GistsService) Delete(ctx context.Context, id string) (*Response, error) { + u := fmt.Sprintf("gists/%v", id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// Star a gist on behalf of authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/gists/#star-a-gist +func (s *GistsService) Star(ctx context.Context, id string) (*Response, error) { + u := fmt.Sprintf("gists/%v/star", id) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// Unstar a gist on a behalf of authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/gists/#unstar-a-gist +func (s *GistsService) Unstar(ctx context.Context, id string) (*Response, error) { + u := fmt.Sprintf("gists/%v/star", id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// IsStarred checks if a gist is starred by authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/gists/#check-if-a-gist-is-starred +func (s *GistsService) IsStarred(ctx context.Context, id string) (bool, *Response, error) { + u := fmt.Sprintf("gists/%v/star", id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + resp, err := s.client.Do(ctx, req, nil) + starred, err := parseBoolResponse(err) + return starred, resp, err +} + +// Fork a gist. +// +// GitHub API docs: https://developer.github.com/v3/gists/#fork-a-gist +func (s *GistsService) Fork(ctx context.Context, id string) (*Gist, *Response, error) { + u := fmt.Sprintf("gists/%v/forks", id) + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + g := new(Gist) + resp, err := s.client.Do(ctx, req, g) + if err != nil { + return nil, resp, err + } + + return g, resp, nil +} + +// ListForks lists forks of a gist. +// +// GitHub API docs: https://developer.github.com/v3/gists/#list-gist-forks +func (s *GistsService) ListForks(ctx context.Context, id string) ([]*GistFork, *Response, error) { + u := fmt.Sprintf("gists/%v/forks", id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var gistForks []*GistFork + resp, err := s.client.Do(ctx, req, &gistForks) + if err != nil { + return nil, resp, err + } + + return gistForks, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/gists_comments.go b/vendor/github.com/google/go-github/github/gists_comments.go new file mode 100644 index 0000000..d5322e3 --- /dev/null +++ b/vendor/github.com/google/go-github/github/gists_comments.go @@ -0,0 +1,119 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// GistComment represents a Gist comment. +type GistComment struct { + ID *int64 `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + Body *string `json:"body,omitempty"` + User *User `json:"user,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` +} + +func (g GistComment) String() string { + return Stringify(g) +} + +// ListComments lists all comments for a gist. +// +// GitHub API docs: https://developer.github.com/v3/gists/comments/#list-comments-on-a-gist +func (s *GistsService) ListComments(ctx context.Context, gistID string, opt *ListOptions) ([]*GistComment, *Response, error) { + u := fmt.Sprintf("gists/%v/comments", gistID) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var comments []*GistComment + resp, err := s.client.Do(ctx, req, &comments) + if err != nil { + return nil, resp, err + } + + return comments, resp, nil +} + +// GetComment retrieves a single comment from a gist. +// +// GitHub API docs: https://developer.github.com/v3/gists/comments/#get-a-single-comment +func (s *GistsService) GetComment(ctx context.Context, gistID string, commentID int64) (*GistComment, *Response, error) { + u := fmt.Sprintf("gists/%v/comments/%v", gistID, commentID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + c := new(GistComment) + resp, err := s.client.Do(ctx, req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, nil +} + +// CreateComment creates a comment for a gist. +// +// GitHub API docs: https://developer.github.com/v3/gists/comments/#create-a-comment +func (s *GistsService) CreateComment(ctx context.Context, gistID string, comment *GistComment) (*GistComment, *Response, error) { + u := fmt.Sprintf("gists/%v/comments", gistID) + req, err := s.client.NewRequest("POST", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(GistComment) + resp, err := s.client.Do(ctx, req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, nil +} + +// EditComment edits an existing gist comment. +// +// GitHub API docs: https://developer.github.com/v3/gists/comments/#edit-a-comment +func (s *GistsService) EditComment(ctx context.Context, gistID string, commentID int64, comment *GistComment) (*GistComment, *Response, error) { + u := fmt.Sprintf("gists/%v/comments/%v", gistID, commentID) + req, err := s.client.NewRequest("PATCH", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(GistComment) + resp, err := s.client.Do(ctx, req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, nil +} + +// DeleteComment deletes a gist comment. +// +// GitHub API docs: https://developer.github.com/v3/gists/comments/#delete-a-comment +func (s *GistsService) DeleteComment(ctx context.Context, gistID string, commentID int64) (*Response, error) { + u := fmt.Sprintf("gists/%v/comments/%v", gistID, commentID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/gists_comments_test.go b/vendor/github.com/google/go-github/github/gists_comments_test.go new file mode 100644 index 0000000..d2f5a95 --- /dev/null +++ b/vendor/github.com/google/go-github/github/gists_comments_test.go @@ -0,0 +1,169 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestGistsService_ListComments(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gists/1/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id": 1}]`) + }) + + opt := &ListOptions{Page: 2} + comments, _, err := client.Gists.ListComments(context.Background(), "1", opt) + if err != nil { + t.Errorf("Gists.Comments returned error: %v", err) + } + + want := []*GistComment{{ID: Int64(1)}} + if !reflect.DeepEqual(comments, want) { + t.Errorf("Gists.ListComments returned %+v, want %+v", comments, want) + } +} + +func TestGistsService_ListComments_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Gists.ListComments(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestGistsService_GetComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gists/1/comments/2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id": 1}`) + }) + + comment, _, err := client.Gists.GetComment(context.Background(), "1", 2) + if err != nil { + t.Errorf("Gists.GetComment returned error: %v", err) + } + + want := &GistComment{ID: Int64(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Gists.GetComment returned %+v, want %+v", comment, want) + } +} + +func TestGistsService_GetComment_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Gists.GetComment(context.Background(), "%", 1) + testURLParseError(t, err) +} + +func TestGistsService_CreateComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &GistComment{ID: Int64(1), Body: String("b")} + + mux.HandleFunc("/gists/1/comments", func(w http.ResponseWriter, r *http.Request) { + v := new(GistComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Gists.CreateComment(context.Background(), "1", input) + if err != nil { + t.Errorf("Gists.CreateComment returned error: %v", err) + } + + want := &GistComment{ID: Int64(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Gists.CreateComment returned %+v, want %+v", comment, want) + } +} + +func TestGistsService_CreateComment_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Gists.CreateComment(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestGistsService_EditComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &GistComment{ID: Int64(1), Body: String("b")} + + mux.HandleFunc("/gists/1/comments/2", func(w http.ResponseWriter, r *http.Request) { + v := new(GistComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Gists.EditComment(context.Background(), "1", 2, input) + if err != nil { + t.Errorf("Gists.EditComment returned error: %v", err) + } + + want := &GistComment{ID: Int64(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Gists.EditComment returned %+v, want %+v", comment, want) + } +} + +func TestGistsService_EditComment_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Gists.EditComment(context.Background(), "%", 1, nil) + testURLParseError(t, err) +} + +func TestGistsService_DeleteComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gists/1/comments/2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Gists.DeleteComment(context.Background(), "1", 2) + if err != nil { + t.Errorf("Gists.Delete returned error: %v", err) + } +} + +func TestGistsService_DeleteComment_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Gists.DeleteComment(context.Background(), "%", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/gists_test.go b/vendor/github.com/google/go-github/github/gists_test.go new file mode 100644 index 0000000..3d31c9a --- /dev/null +++ b/vendor/github.com/google/go-github/github/gists_test.go @@ -0,0 +1,534 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestGistsService_List_specifiedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + since := "2013-01-01T00:00:00Z" + + mux.HandleFunc("/users/u/gists", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "since": since, + }) + fmt.Fprint(w, `[{"id": "1"}]`) + }) + + opt := &GistListOptions{Since: time.Date(2013, time.January, 1, 0, 0, 0, 0, time.UTC)} + gists, _, err := client.Gists.List(context.Background(), "u", opt) + if err != nil { + t.Errorf("Gists.List returned error: %v", err) + } + + want := []*Gist{{ID: String("1")}} + if !reflect.DeepEqual(gists, want) { + t.Errorf("Gists.List returned %+v, want %+v", gists, want) + } +} + +func TestGistsService_List_authenticatedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gists", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id": "1"}]`) + }) + + gists, _, err := client.Gists.List(context.Background(), "", nil) + if err != nil { + t.Errorf("Gists.List returned error: %v", err) + } + + want := []*Gist{{ID: String("1")}} + if !reflect.DeepEqual(gists, want) { + t.Errorf("Gists.List returned %+v, want %+v", gists, want) + } +} + +func TestGistsService_List_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Gists.List(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestGistsService_ListAll(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + since := "2013-01-01T00:00:00Z" + + mux.HandleFunc("/gists/public", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "since": since, + }) + fmt.Fprint(w, `[{"id": "1"}]`) + }) + + opt := &GistListOptions{Since: time.Date(2013, time.January, 1, 0, 0, 0, 0, time.UTC)} + gists, _, err := client.Gists.ListAll(context.Background(), opt) + if err != nil { + t.Errorf("Gists.ListAll returned error: %v", err) + } + + want := []*Gist{{ID: String("1")}} + if !reflect.DeepEqual(gists, want) { + t.Errorf("Gists.ListAll returned %+v, want %+v", gists, want) + } +} + +func TestGistsService_ListStarred(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + since := "2013-01-01T00:00:00Z" + + mux.HandleFunc("/gists/starred", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "since": since, + }) + fmt.Fprint(w, `[{"id": "1"}]`) + }) + + opt := &GistListOptions{Since: time.Date(2013, time.January, 1, 0, 0, 0, 0, time.UTC)} + gists, _, err := client.Gists.ListStarred(context.Background(), opt) + if err != nil { + t.Errorf("Gists.ListStarred returned error: %v", err) + } + + want := []*Gist{{ID: String("1")}} + if !reflect.DeepEqual(gists, want) { + t.Errorf("Gists.ListStarred returned %+v, want %+v", gists, want) + } +} + +func TestGistsService_Get(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gists/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id": "1"}`) + }) + + gist, _, err := client.Gists.Get(context.Background(), "1") + if err != nil { + t.Errorf("Gists.Get returned error: %v", err) + } + + want := &Gist{ID: String("1")} + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Get returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_Get_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Gists.Get(context.Background(), "%") + testURLParseError(t, err) +} + +func TestGistsService_GetRevision(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gists/1/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id": "1"}`) + }) + + gist, _, err := client.Gists.GetRevision(context.Background(), "1", "s") + if err != nil { + t.Errorf("Gists.Get returned error: %v", err) + } + + want := &Gist{ID: String("1")} + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Get returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_GetRevision_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Gists.GetRevision(context.Background(), "%", "%") + testURLParseError(t, err) +} + +func TestGistsService_Create(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Gist{ + Description: String("Gist description"), + Public: Bool(false), + Files: map[GistFilename]GistFile{ + "test.txt": {Content: String("Gist file content")}, + }, + } + + mux.HandleFunc("/gists", func(w http.ResponseWriter, r *http.Request) { + v := new(Gist) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, + ` + { + "id": "1", + "description": "Gist description", + "public": false, + "files": { + "test.txt": { + "filename": "test.txt" + } + } + }`) + }) + + gist, _, err := client.Gists.Create(context.Background(), input) + if err != nil { + t.Errorf("Gists.Create returned error: %v", err) + } + + want := &Gist{ + ID: String("1"), + Description: String("Gist description"), + Public: Bool(false), + Files: map[GistFilename]GistFile{ + "test.txt": {Filename: String("test.txt")}, + }, + } + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Create returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_Edit(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Gist{ + Description: String("New description"), + Files: map[GistFilename]GistFile{ + "new.txt": {Content: String("new file content")}, + }, + } + + mux.HandleFunc("/gists/1", func(w http.ResponseWriter, r *http.Request) { + v := new(Gist) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, + ` + { + "id": "1", + "description": "new description", + "public": false, + "files": { + "test.txt": { + "filename": "test.txt" + }, + "new.txt": { + "filename": "new.txt" + } + } + }`) + }) + + gist, _, err := client.Gists.Edit(context.Background(), "1", input) + if err != nil { + t.Errorf("Gists.Edit returned error: %v", err) + } + + want := &Gist{ + ID: String("1"), + Description: String("new description"), + Public: Bool(false), + Files: map[GistFilename]GistFile{ + "test.txt": {Filename: String("test.txt")}, + "new.txt": {Filename: String("new.txt")}, + }, + } + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Edit returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_Edit_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Gists.Edit(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestGistsService_ListCommits(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gists/1/commits", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, nil) + fmt.Fprint(w, ` + [ + { + "url": "https://api.github.com/gists/1/1", + "version": "1", + "user": { + "id": 1 + }, + "change_status": { + "deletions": 0, + "additions": 180, + "total": 180 + }, + "committed_at": "2010-01-01T00:00:00Z" + } + ] + `) + }) + + gistCommits, _, err := client.Gists.ListCommits(context.Background(), "1", nil) + if err != nil { + t.Errorf("Gists.ListCommits returned error: %v", err) + } + + want := []*GistCommit{{ + URL: String("https://api.github.com/gists/1/1"), + Version: String("1"), + User: &User{ID: Int64(1)}, + CommittedAt: &Timestamp{time.Date(2010, time.January, 1, 00, 00, 00, 0, time.UTC)}, + ChangeStatus: &CommitStats{ + Additions: Int(180), + Deletions: Int(0), + Total: Int(180), + }}} + + if !reflect.DeepEqual(gistCommits, want) { + t.Errorf("Gists.ListCommits returned %+v, want %+v", gistCommits, want) + } +} + +func TestGistsService_ListCommits_withOptions(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gists/1/commits", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[]`) + }) + + _, _, err := client.Gists.ListCommits(context.Background(), "1", &ListOptions{Page: 2}) + if err != nil { + t.Errorf("Gists.ListCommits returned error: %v", err) + } +} + +func TestGistsService_Delete(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gists/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Gists.Delete(context.Background(), "1") + if err != nil { + t.Errorf("Gists.Delete returned error: %v", err) + } +} + +func TestGistsService_Delete_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Gists.Delete(context.Background(), "%") + testURLParseError(t, err) +} + +func TestGistsService_Star(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + }) + + _, err := client.Gists.Star(context.Background(), "1") + if err != nil { + t.Errorf("Gists.Star returned error: %v", err) + } +} + +func TestGistsService_Star_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Gists.Star(context.Background(), "%") + testURLParseError(t, err) +} + +func TestGistsService_Unstar(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Gists.Unstar(context.Background(), "1") + if err != nil { + t.Errorf("Gists.Unstar returned error: %v", err) + } +} + +func TestGistsService_Unstar_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Gists.Unstar(context.Background(), "%") + testURLParseError(t, err) +} + +func TestGistsService_IsStarred_hasStar(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + star, _, err := client.Gists.IsStarred(context.Background(), "1") + if err != nil { + t.Errorf("Gists.Starred returned error: %v", err) + } + if want := true; star != want { + t.Errorf("Gists.Starred returned %+v, want %+v", star, want) + } +} + +func TestGistsService_IsStarred_noStar(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + star, _, err := client.Gists.IsStarred(context.Background(), "1") + if err != nil { + t.Errorf("Gists.Starred returned error: %v", err) + } + if want := false; star != want { + t.Errorf("Gists.Starred returned %+v, want %+v", star, want) + } +} + +func TestGistsService_IsStarred_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Gists.IsStarred(context.Background(), "%") + testURLParseError(t, err) +} + +func TestGistsService_Fork(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gists/1/forks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, `{"id": "2"}`) + }) + + gist, _, err := client.Gists.Fork(context.Background(), "1") + if err != nil { + t.Errorf("Gists.Fork returned error: %v", err) + } + + want := &Gist{ID: String("2")} + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Fork returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_ListForks(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gists/1/forks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, nil) + fmt.Fprint(w, ` + [ + {"url": "https://api.github.com/gists/1", + "user": {"id": 1}, + "id": "1", + "created_at": "2010-01-01T00:00:00Z", + "updated_at": "2013-01-01T00:00:00Z" + } + ] + `) + }) + + gistForks, _, err := client.Gists.ListForks(context.Background(), "1") + if err != nil { + t.Errorf("Gists.ListForks returned error: %v", err) + } + + want := []*GistFork{{ + URL: String("https://api.github.com/gists/1"), + ID: String("1"), + User: &User{ID: Int64(1)}, + CreatedAt: &Timestamp{time.Date(2010, time.January, 1, 00, 00, 00, 0, time.UTC)}, + UpdatedAt: &Timestamp{time.Date(2013, time.January, 1, 00, 00, 00, 0, time.UTC)}}} + + if !reflect.DeepEqual(gistForks, want) { + t.Errorf("Gists.ListForks returned %+v, want %+v", gistForks, want) + } +} + +func TestGistsService_Fork_invalidID(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Gists.Fork(context.Background(), "%") + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/git.go b/vendor/github.com/google/go-github/github/git.go new file mode 100644 index 0000000..1ce4743 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git.go @@ -0,0 +1,12 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +// GitService handles communication with the git data related +// methods of the GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/git/ +type GitService service diff --git a/vendor/github.com/google/go-github/github/git_blobs.go b/vendor/github.com/google/go-github/github/git_blobs.go new file mode 100644 index 0000000..70aee14 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_blobs.go @@ -0,0 +1,69 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "bytes" + "context" + "fmt" +) + +// Blob represents a blob object. +type Blob struct { + Content *string `json:"content,omitempty"` + Encoding *string `json:"encoding,omitempty"` + SHA *string `json:"sha,omitempty"` + Size *int `json:"size,omitempty"` + URL *string `json:"url,omitempty"` + NodeID *string `json:"node_id,omitempty"` +} + +// GetBlob fetches a blob from a repo given a SHA. +// +// GitHub API docs: https://developer.github.com/v3/git/blobs/#get-a-blob +func (s *GitService) GetBlob(ctx context.Context, owner string, repo string, sha string) (*Blob, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/blobs/%v", owner, repo, sha) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + blob := new(Blob) + resp, err := s.client.Do(ctx, req, blob) + return blob, resp, err +} + +// GetBlobRaw fetches a blob's contents from a repo. +// Unlike GetBlob, it returns the raw bytes rather than the base64-encoded data. +// +// GitHub API docs: https://developer.github.com/v3/git/blobs/#get-a-blob +func (s *GitService) GetBlobRaw(ctx context.Context, owner, repo, sha string) ([]byte, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/blobs/%v", owner, repo, sha) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + req.Header.Set("Accept", "application/vnd.github.v3.raw") + + var buf bytes.Buffer + resp, err := s.client.Do(ctx, req, &buf) + return buf.Bytes(), resp, err +} + +// CreateBlob creates a blob object. +// +// GitHub API docs: https://developer.github.com/v3/git/blobs/#create-a-blob +func (s *GitService) CreateBlob(ctx context.Context, owner string, repo string, blob *Blob) (*Blob, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/blobs", owner, repo) + req, err := s.client.NewRequest("POST", u, blob) + if err != nil { + return nil, nil, err + } + + t := new(Blob) + resp, err := s.client.Do(ctx, req, t) + return t, resp, err +} diff --git a/vendor/github.com/google/go-github/github/git_blobs_test.go b/vendor/github.com/google/go-github/github/git_blobs_test.go new file mode 100644 index 0000000..9945d35 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_blobs_test.go @@ -0,0 +1,124 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestGitService_GetBlob(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/blobs/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, `{ + "sha": "s", + "content": "blob content" + }`) + }) + + blob, _, err := client.Git.GetBlob(context.Background(), "o", "r", "s") + if err != nil { + t.Errorf("Git.GetBlob returned error: %v", err) + } + + want := Blob{ + SHA: String("s"), + Content: String("blob content"), + } + + if !reflect.DeepEqual(*blob, want) { + t.Errorf("Blob.Get returned %+v, want %+v", *blob, want) + } +} + +func TestGitService_GetBlob_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Git.GetBlob(context.Background(), "%", "%", "%") + testURLParseError(t, err) +} + +func TestGitService_GetBlobRaw(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/blobs/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", "application/vnd.github.v3.raw") + + fmt.Fprint(w, `raw contents here`) + }) + + blob, _, err := client.Git.GetBlobRaw(context.Background(), "o", "r", "s") + if err != nil { + t.Errorf("Git.GetBlobRaw returned error: %v", err) + } + + want := []byte("raw contents here") + if !bytes.Equal(blob, want) { + t.Errorf("GetBlobRaw returned %q, want %q", blob, want) + } +} + +func TestGitService_CreateBlob(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Blob{ + SHA: String("s"), + Content: String("blob content"), + Encoding: String("utf-8"), + Size: Int(12), + } + + mux.HandleFunc("/repos/o/r/git/blobs", func(w http.ResponseWriter, r *http.Request) { + v := new(Blob) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + + want := input + if !reflect.DeepEqual(v, want) { + t.Errorf("Git.CreateBlob request body: %+v, want %+v", v, want) + } + + fmt.Fprint(w, `{ + "sha": "s", + "content": "blob content", + "encoding": "utf-8", + "size": 12 + }`) + }) + + blob, _, err := client.Git.CreateBlob(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("Git.CreateBlob returned error: %v", err) + } + + want := input + + if !reflect.DeepEqual(*blob, *want) { + t.Errorf("Git.CreateBlob returned %+v, want %+v", *blob, *want) + } +} + +func TestGitService_CreateBlob_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Git.CreateBlob(context.Background(), "%", "%", &Blob{}) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/git_commits.go b/vendor/github.com/google/go-github/github/git_commits.go new file mode 100644 index 0000000..1eb48a8 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_commits.go @@ -0,0 +1,134 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// SignatureVerification represents GPG signature verification. +type SignatureVerification struct { + Verified *bool `json:"verified,omitempty"` + Reason *string `json:"reason,omitempty"` + Signature *string `json:"signature,omitempty"` + Payload *string `json:"payload,omitempty"` +} + +// Commit represents a GitHub commit. +type Commit struct { + SHA *string `json:"sha,omitempty"` + Author *CommitAuthor `json:"author,omitempty"` + Committer *CommitAuthor `json:"committer,omitempty"` + Message *string `json:"message,omitempty"` + Tree *Tree `json:"tree,omitempty"` + Parents []Commit `json:"parents,omitempty"` + Stats *CommitStats `json:"stats,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + URL *string `json:"url,omitempty"` + Verification *SignatureVerification `json:"verification,omitempty"` + NodeID *string `json:"node_id,omitempty"` + + // CommentCount is the number of GitHub comments on the commit. This + // is only populated for requests that fetch GitHub data like + // Pulls.ListCommits, Repositories.ListCommits, etc. + CommentCount *int `json:"comment_count,omitempty"` +} + +func (c Commit) String() string { + return Stringify(c) +} + +// CommitAuthor represents the author or committer of a commit. The commit +// author may not correspond to a GitHub User. +type CommitAuthor struct { + Date *time.Time `json:"date,omitempty"` + Name *string `json:"name,omitempty"` + Email *string `json:"email,omitempty"` + + // The following fields are only populated by Webhook events. + Login *string `json:"username,omitempty"` // Renamed for go-github consistency. +} + +func (c CommitAuthor) String() string { + return Stringify(c) +} + +// GetCommit fetchs the Commit object for a given SHA. +// +// GitHub API docs: https://developer.github.com/v3/git/commits/#get-a-commit +func (s *GitService) GetCommit(ctx context.Context, owner string, repo string, sha string) (*Commit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/commits/%v", owner, repo, sha) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeGitSigningPreview) + + c := new(Commit) + resp, err := s.client.Do(ctx, req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, nil +} + +// createCommit represents the body of a CreateCommit request. +type createCommit struct { + Author *CommitAuthor `json:"author,omitempty"` + Committer *CommitAuthor `json:"committer,omitempty"` + Message *string `json:"message,omitempty"` + Tree *string `json:"tree,omitempty"` + Parents []string `json:"parents,omitempty"` +} + +// CreateCommit creates a new commit in a repository. +// commit must not be nil. +// +// The commit.Committer is optional and will be filled with the commit.Author +// data if omitted. If the commit.Author is omitted, it will be filled in with +// the authenticated user’s information and the current date. +// +// GitHub API docs: https://developer.github.com/v3/git/commits/#create-a-commit +func (s *GitService) CreateCommit(ctx context.Context, owner string, repo string, commit *Commit) (*Commit, *Response, error) { + if commit == nil { + return nil, nil, fmt.Errorf("commit must be provided") + } + + u := fmt.Sprintf("repos/%v/%v/git/commits", owner, repo) + + parents := make([]string, len(commit.Parents)) + for i, parent := range commit.Parents { + parents[i] = *parent.SHA + } + + body := &createCommit{ + Author: commit.Author, + Committer: commit.Committer, + Message: commit.Message, + Parents: parents, + } + if commit.Tree != nil { + body.Tree = commit.Tree.SHA + } + + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + c := new(Commit) + resp, err := s.client.Do(ctx, req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/git_commits_test.go b/vendor/github.com/google/go-github/github/git_commits_test.go new file mode 100644 index 0000000..ff8827f --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_commits_test.go @@ -0,0 +1,90 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestGitService_GetCommit(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/commits/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeGitSigningPreview) + fmt.Fprint(w, `{"sha":"s","message":"m","author":{"name":"n"}}`) + }) + + commit, _, err := client.Git.GetCommit(context.Background(), "o", "r", "s") + if err != nil { + t.Errorf("Git.GetCommit returned error: %v", err) + } + + want := &Commit{SHA: String("s"), Message: String("m"), Author: &CommitAuthor{Name: String("n")}} + if !reflect.DeepEqual(commit, want) { + t.Errorf("Git.GetCommit returned %+v, want %+v", commit, want) + } +} + +func TestGitService_GetCommit_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Git.GetCommit(context.Background(), "%", "%", "%") + testURLParseError(t, err) +} + +func TestGitService_CreateCommit(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Commit{ + Message: String("m"), + Tree: &Tree{SHA: String("t")}, + Parents: []Commit{{SHA: String("p")}}, + } + + mux.HandleFunc("/repos/o/r/git/commits", func(w http.ResponseWriter, r *http.Request) { + v := new(createCommit) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + + want := &createCommit{ + Message: input.Message, + Tree: String("t"), + Parents: []string{"p"}, + } + if !reflect.DeepEqual(v, want) { + t.Errorf("Request body = %+v, want %+v", v, want) + } + fmt.Fprint(w, `{"sha":"s"}`) + }) + + commit, _, err := client.Git.CreateCommit(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("Git.CreateCommit returned error: %v", err) + } + + want := &Commit{SHA: String("s")} + if !reflect.DeepEqual(commit, want) { + t.Errorf("Git.CreateCommit returned %+v, want %+v", commit, want) + } +} + +func TestGitService_CreateCommit_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Git.CreateCommit(context.Background(), "%", "%", &Commit{}) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/git_refs.go b/vendor/github.com/google/go-github/github/git_refs.go new file mode 100644 index 0000000..3b2ced2 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_refs.go @@ -0,0 +1,218 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" +) + +// Reference represents a GitHub reference. +type Reference struct { + Ref *string `json:"ref"` + URL *string `json:"url"` + Object *GitObject `json:"object"` + NodeID *string `json:"node_id,omitempty"` +} + +func (r Reference) String() string { + return Stringify(r) +} + +// GitObject represents a Git object. +type GitObject struct { + Type *string `json:"type"` + SHA *string `json:"sha"` + URL *string `json:"url"` +} + +func (o GitObject) String() string { + return Stringify(o) +} + +// createRefRequest represents the payload for creating a reference. +type createRefRequest struct { + Ref *string `json:"ref"` + SHA *string `json:"sha"` +} + +// updateRefRequest represents the payload for updating a reference. +type updateRefRequest struct { + SHA *string `json:"sha"` + Force *bool `json:"force"` +} + +// GetRef fetches a single Reference object for a given Git ref. +// If there is no exact match, GetRef will return an error. +// +// Note: The GitHub API can return multiple matches. +// If you wish to use this functionality please use the GetRefs() method. +// +// GitHub API docs: https://developer.github.com/v3/git/refs/#get-a-reference +func (s *GitService) GetRef(ctx context.Context, owner string, repo string, ref string) (*Reference, *Response, error) { + ref = strings.TrimPrefix(ref, "refs/") + u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, ref) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + r := new(Reference) + resp, err := s.client.Do(ctx, req, r) + if _, ok := err.(*json.UnmarshalTypeError); ok { + // Multiple refs, means there wasn't an exact match. + return nil, resp, errors.New("no exact match found for this ref") + } else if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// GetRefs fetches a slice of Reference objects for a given Git ref. +// If there is an exact match, only that ref is returned. +// If there is no exact match, GitHub returns all refs that start with ref. +// If returned error is nil, there will be at least 1 ref returned. +// For example: +// +// "heads/featureA" -> ["refs/heads/featureA"] // Exact match, single ref is returned. +// "heads/feature" -> ["refs/heads/featureA", "refs/heads/featureB"] // All refs that start with ref. +// "heads/notexist" -> [] // Returns an error. +// +// GitHub API docs: https://developer.github.com/v3/git/refs/#get-a-reference +func (s *GitService) GetRefs(ctx context.Context, owner string, repo string, ref string) ([]*Reference, *Response, error) { + ref = strings.TrimPrefix(ref, "refs/") + u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, ref) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var rawJSON json.RawMessage + resp, err := s.client.Do(ctx, req, &rawJSON) + if err != nil { + return nil, resp, err + } + + // Prioritize the most common case: a single returned ref. + r := new(Reference) + singleUnmarshalError := json.Unmarshal(rawJSON, r) + if singleUnmarshalError == nil { + return []*Reference{r}, resp, nil + } + + // Attempt to unmarshal multiple refs. + var rs []*Reference + multipleUnmarshalError := json.Unmarshal(rawJSON, &rs) + if multipleUnmarshalError == nil { + if len(rs) == 0 { + return nil, resp, fmt.Errorf("unexpected response from GitHub API: an array of refs with length 0") + } + return rs, resp, nil + } + + return nil, resp, fmt.Errorf("unmarshalling failed for both single and multiple refs: %s and %s", singleUnmarshalError, multipleUnmarshalError) +} + +// ReferenceListOptions specifies optional parameters to the +// GitService.ListRefs method. +type ReferenceListOptions struct { + Type string `url:"-"` + + ListOptions +} + +// ListRefs lists all refs in a repository. +// +// GitHub API docs: https://developer.github.com/v3/git/refs/#get-all-references +func (s *GitService) ListRefs(ctx context.Context, owner, repo string, opt *ReferenceListOptions) ([]*Reference, *Response, error) { + var u string + if opt != nil && opt.Type != "" { + u = fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, opt.Type) + } else { + u = fmt.Sprintf("repos/%v/%v/git/refs", owner, repo) + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var rs []*Reference + resp, err := s.client.Do(ctx, req, &rs) + if err != nil { + return nil, resp, err + } + + return rs, resp, nil +} + +// CreateRef creates a new ref in a repository. +// +// GitHub API docs: https://developer.github.com/v3/git/refs/#create-a-reference +func (s *GitService) CreateRef(ctx context.Context, owner string, repo string, ref *Reference) (*Reference, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/refs", owner, repo) + req, err := s.client.NewRequest("POST", u, &createRefRequest{ + // back-compat with previous behavior that didn't require 'refs/' prefix + Ref: String("refs/" + strings.TrimPrefix(*ref.Ref, "refs/")), + SHA: ref.Object.SHA, + }) + if err != nil { + return nil, nil, err + } + + r := new(Reference) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// UpdateRef updates an existing ref in a repository. +// +// GitHub API docs: https://developer.github.com/v3/git/refs/#update-a-reference +func (s *GitService) UpdateRef(ctx context.Context, owner string, repo string, ref *Reference, force bool) (*Reference, *Response, error) { + refPath := strings.TrimPrefix(*ref.Ref, "refs/") + u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, refPath) + req, err := s.client.NewRequest("PATCH", u, &updateRefRequest{ + SHA: ref.Object.SHA, + Force: &force, + }) + if err != nil { + return nil, nil, err + } + + r := new(Reference) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// DeleteRef deletes a ref from a repository. +// +// GitHub API docs: https://developer.github.com/v3/git/refs/#delete-a-reference +func (s *GitService) DeleteRef(ctx context.Context, owner string, repo string, ref string) (*Response, error) { + ref = strings.TrimPrefix(ref, "refs/") + u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, ref) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/git_refs_test.go b/vendor/github.com/google/go-github/github/git_refs_test.go new file mode 100644 index 0000000..3cae3c0 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_refs_test.go @@ -0,0 +1,429 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestGitService_GetRef_singleRef(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, ` + { + "ref": "refs/heads/b", + "url": "https://api.github.com/repos/o/r/git/refs/heads/b", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + }`) + }) + + ref, _, err := client.Git.GetRef(context.Background(), "o", "r", "refs/heads/b") + if err != nil { + t.Fatalf("Git.GetRef returned error: %v", err) + } + + want := &Reference{ + Ref: String("refs/heads/b"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/b"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + } + if !reflect.DeepEqual(ref, want) { + t.Errorf("Git.GetRef returned %+v, want %+v", ref, want) + } + + // without 'refs/' prefix + if _, _, err := client.Git.GetRef(context.Background(), "o", "r", "heads/b"); err != nil { + t.Errorf("Git.GetRef returned error: %v", err) + } +} + +func TestGitService_GetRef_multipleRefs(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, ` + [ + { + "ref": "refs/heads/booger", + "url": "https://api.github.com/repos/o/r/git/refs/heads/booger", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + }, + { + "ref": "refs/heads/bandsaw", + "url": "https://api.github.com/repos/o/r/git/refs/heads/bandsaw", + "object": { + "type": "commit", + "sha": "612077ae6dffb4d2fbd8ce0cccaa58893b07b5ac", + "url": "https://api.github.com/repos/o/r/git/commits/612077ae6dffb4d2fbd8ce0cccaa58893b07b5ac" + } + } + ] + `) + }) + + _, _, err := client.Git.GetRef(context.Background(), "o", "r", "refs/heads/b") + want := "no exact match found for this ref" + if err.Error() != want { + t.Errorf("Git.GetRef returned %+v, want %+v", err, want) + } + +} + +func TestGitService_GetRefs_singleRef(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, ` + { + "ref": "refs/heads/b", + "url": "https://api.github.com/repos/o/r/git/refs/heads/b", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + }`) + }) + + refs, _, err := client.Git.GetRefs(context.Background(), "o", "r", "refs/heads/b") + if err != nil { + t.Fatalf("Git.GetRefs returned error: %v", err) + } + + ref := refs[0] + want := &Reference{ + Ref: String("refs/heads/b"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/b"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + } + if !reflect.DeepEqual(ref, want) { + t.Errorf("Git.GetRefs returned %+v, want %+v", ref, want) + } + + // without 'refs/' prefix + if _, _, err := client.Git.GetRefs(context.Background(), "o", "r", "heads/b"); err != nil { + t.Errorf("Git.GetRefs returned error: %v", err) + } +} + +func TestGitService_GetRefs_multipleRefs(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, ` + [ + { + "ref": "refs/heads/booger", + "url": "https://api.github.com/repos/o/r/git/refs/heads/booger", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + }, + { + "ref": "refs/heads/bandsaw", + "url": "https://api.github.com/repos/o/r/git/refs/heads/bandsaw", + "object": { + "type": "commit", + "sha": "612077ae6dffb4d2fbd8ce0cccaa58893b07b5ac", + "url": "https://api.github.com/repos/o/r/git/commits/612077ae6dffb4d2fbd8ce0cccaa58893b07b5ac" + } + } + ] + `) + }) + + refs, _, err := client.Git.GetRefs(context.Background(), "o", "r", "refs/heads/b") + if err != nil { + t.Errorf("Git.GetRefs returned error: %v", err) + } + + want := &Reference{ + Ref: String("refs/heads/booger"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/booger"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + } + if !reflect.DeepEqual(refs[0], want) { + t.Errorf("Git.GetRefs returned %+v, want %+v", refs[0], want) + } +} + +// TestGitService_GetRefs_noRefs tests for behaviour resulting from an unexpected GH response. This should never actually happen. +func TestGitService_GetRefs_noRefs(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, "[]") + }) + + _, _, err := client.Git.GetRefs(context.Background(), "o", "r", "refs/heads/b") + want := "unexpected response from GitHub API: an array of refs with length 0" + if err.Error() != want { + t.Errorf("Git.GetRefs returned %+v, want %+v", err, want) + } + +} + +func TestGitService_ListRefs(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, ` + [ + { + "ref": "refs/heads/branchA", + "url": "https://api.github.com/repos/o/r/git/refs/heads/branchA", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + }, + { + "ref": "refs/heads/branchB", + "url": "https://api.github.com/repos/o/r/git/refs/heads/branchB", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + } + ]`) + }) + + refs, _, err := client.Git.ListRefs(context.Background(), "o", "r", nil) + if err != nil { + t.Errorf("Git.ListRefs returned error: %v", err) + } + + want := []*Reference{ + { + Ref: String("refs/heads/branchA"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/branchA"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + }, + { + Ref: String("refs/heads/branchB"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/branchB"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + }, + } + if !reflect.DeepEqual(refs, want) { + t.Errorf("Git.ListRefs returned %+v, want %+v", refs, want) + } +} + +func TestGitService_ListRefs_options(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"ref": "r"}]`) + }) + + opt := &ReferenceListOptions{Type: "t", ListOptions: ListOptions{Page: 2}} + refs, _, err := client.Git.ListRefs(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Git.ListRefs returned error: %v", err) + } + + want := []*Reference{{Ref: String("r")}} + if !reflect.DeepEqual(refs, want) { + t.Errorf("Git.ListRefs returned %+v, want %+v", refs, want) + } +} + +func TestGitService_CreateRef(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + args := &createRefRequest{ + Ref: String("refs/heads/b"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + } + + mux.HandleFunc("/repos/o/r/git/refs", func(w http.ResponseWriter, r *http.Request) { + v := new(createRefRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, args) { + t.Errorf("Request body = %+v, want %+v", v, args) + } + fmt.Fprint(w, ` + { + "ref": "refs/heads/b", + "url": "https://api.github.com/repos/o/r/git/refs/heads/b", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + }`) + }) + + ref, _, err := client.Git.CreateRef(context.Background(), "o", "r", &Reference{ + Ref: String("refs/heads/b"), + Object: &GitObject{ + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + }) + if err != nil { + t.Errorf("Git.CreateRef returned error: %v", err) + } + + want := &Reference{ + Ref: String("refs/heads/b"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/b"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + } + if !reflect.DeepEqual(ref, want) { + t.Errorf("Git.CreateRef returned %+v, want %+v", ref, want) + } + + // without 'refs/' prefix + _, _, err = client.Git.CreateRef(context.Background(), "o", "r", &Reference{ + Ref: String("heads/b"), + Object: &GitObject{ + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + }) + if err != nil { + t.Errorf("Git.CreateRef returned error: %v", err) + } +} + +func TestGitService_UpdateRef(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + args := &updateRefRequest{ + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + Force: Bool(true), + } + + mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) { + v := new(updateRefRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, args) { + t.Errorf("Request body = %+v, want %+v", v, args) + } + fmt.Fprint(w, ` + { + "ref": "refs/heads/b", + "url": "https://api.github.com/repos/o/r/git/refs/heads/b", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + }`) + }) + + ref, _, err := client.Git.UpdateRef(context.Background(), "o", "r", &Reference{ + Ref: String("refs/heads/b"), + Object: &GitObject{SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd")}, + }, true) + if err != nil { + t.Errorf("Git.UpdateRef returned error: %v", err) + } + + want := &Reference{ + Ref: String("refs/heads/b"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/b"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + } + if !reflect.DeepEqual(ref, want) { + t.Errorf("Git.UpdateRef returned %+v, want %+v", ref, want) + } + + // without 'refs/' prefix + _, _, err = client.Git.UpdateRef(context.Background(), "o", "r", &Reference{ + Ref: String("heads/b"), + Object: &GitObject{SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd")}, + }, true) + if err != nil { + t.Errorf("Git.UpdateRef returned error: %v", err) + } +} + +func TestGitService_DeleteRef(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Git.DeleteRef(context.Background(), "o", "r", "refs/heads/b") + if err != nil { + t.Errorf("Git.DeleteRef returned error: %v", err) + } + + // without 'refs/' prefix + if _, err := client.Git.DeleteRef(context.Background(), "o", "r", "heads/b"); err != nil { + t.Errorf("Git.DeleteRef returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/git_tags.go b/vendor/github.com/google/go-github/github/git_tags.go new file mode 100644 index 0000000..90398b3 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_tags.go @@ -0,0 +1,79 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// Tag represents a tag object. +type Tag struct { + Tag *string `json:"tag,omitempty"` + SHA *string `json:"sha,omitempty"` + URL *string `json:"url,omitempty"` + Message *string `json:"message,omitempty"` + Tagger *CommitAuthor `json:"tagger,omitempty"` + Object *GitObject `json:"object,omitempty"` + Verification *SignatureVerification `json:"verification,omitempty"` + NodeID *string `json:"node_id,omitempty"` +} + +// createTagRequest represents the body of a CreateTag request. This is mostly +// identical to Tag with the exception that the object SHA and Type are +// top-level fields, rather than being nested inside a JSON object. +type createTagRequest struct { + Tag *string `json:"tag,omitempty"` + Message *string `json:"message,omitempty"` + Object *string `json:"object,omitempty"` + Type *string `json:"type,omitempty"` + Tagger *CommitAuthor `json:"tagger,omitempty"` +} + +// GetTag fetchs a tag from a repo given a SHA. +// +// GitHub API docs: https://developer.github.com/v3/git/tags/#get-a-tag +func (s *GitService) GetTag(ctx context.Context, owner string, repo string, sha string) (*Tag, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/tags/%v", owner, repo, sha) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeGitSigningPreview) + + tag := new(Tag) + resp, err := s.client.Do(ctx, req, tag) + return tag, resp, err +} + +// CreateTag creates a tag object. +// +// GitHub API docs: https://developer.github.com/v3/git/tags/#create-a-tag-object +func (s *GitService) CreateTag(ctx context.Context, owner string, repo string, tag *Tag) (*Tag, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/tags", owner, repo) + + // convert Tag into a createTagRequest + tagRequest := &createTagRequest{ + Tag: tag.Tag, + Message: tag.Message, + Tagger: tag.Tagger, + } + if tag.Object != nil { + tagRequest.Object = tag.Object.SHA + tagRequest.Type = tag.Object.Type + } + + req, err := s.client.NewRequest("POST", u, tagRequest) + if err != nil { + return nil, nil, err + } + + t := new(Tag) + resp, err := s.client.Do(ctx, req, t) + return t, resp, err +} diff --git a/vendor/github.com/google/go-github/github/git_tags_test.go b/vendor/github.com/google/go-github/github/git_tags_test.go new file mode 100644 index 0000000..0b11c64 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_tags_test.go @@ -0,0 +1,68 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestGitService_GetTag(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/tags/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeGitSigningPreview) + fmt.Fprint(w, `{"tag": "t"}`) + }) + + tag, _, err := client.Git.GetTag(context.Background(), "o", "r", "s") + if err != nil { + t.Errorf("Git.GetTag returned error: %v", err) + } + + want := &Tag{Tag: String("t")} + if !reflect.DeepEqual(tag, want) { + t.Errorf("Git.GetTag returned %+v, want %+v", tag, want) + } +} + +func TestGitService_CreateTag(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &createTagRequest{Tag: String("t"), Object: String("s")} + + mux.HandleFunc("/repos/o/r/git/tags", func(w http.ResponseWriter, r *http.Request) { + v := new(createTagRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"tag": "t"}`) + }) + + tag, _, err := client.Git.CreateTag(context.Background(), "o", "r", &Tag{ + Tag: input.Tag, + Object: &GitObject{SHA: input.Object}, + }) + if err != nil { + t.Errorf("Git.CreateTag returned error: %v", err) + } + + want := &Tag{Tag: String("t")} + if !reflect.DeepEqual(tag, want) { + t.Errorf("Git.GetTag returned %+v, want %+v", tag, want) + } +} diff --git a/vendor/github.com/google/go-github/github/git_trees.go b/vendor/github.com/google/go-github/github/git_trees.go new file mode 100644 index 0000000..4bc2913 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_trees.go @@ -0,0 +1,99 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// Tree represents a GitHub tree. +type Tree struct { + SHA *string `json:"sha,omitempty"` + Entries []TreeEntry `json:"tree,omitempty"` + + // Truncated is true if the number of items in the tree + // exceeded GitHub's maximum limit and the Entries were truncated + // in the response. Only populated for requests that fetch + // trees like Git.GetTree. + Truncated *bool `json:"truncated,omitempty"` +} + +func (t Tree) String() string { + return Stringify(t) +} + +// TreeEntry represents the contents of a tree structure. TreeEntry can +// represent either a blob, a commit (in the case of a submodule), or another +// tree. +type TreeEntry struct { + SHA *string `json:"sha,omitempty"` + Path *string `json:"path,omitempty"` + Mode *string `json:"mode,omitempty"` + Type *string `json:"type,omitempty"` + Size *int `json:"size,omitempty"` + Content *string `json:"content,omitempty"` + URL *string `json:"url,omitempty"` +} + +func (t TreeEntry) String() string { + return Stringify(t) +} + +// GetTree fetches the Tree object for a given sha hash from a repository. +// +// GitHub API docs: https://developer.github.com/v3/git/trees/#get-a-tree +func (s *GitService) GetTree(ctx context.Context, owner string, repo string, sha string, recursive bool) (*Tree, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/trees/%v", owner, repo, sha) + if recursive { + u += "?recursive=1" + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + t := new(Tree) + resp, err := s.client.Do(ctx, req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, nil +} + +// createTree represents the body of a CreateTree request. +type createTree struct { + BaseTree string `json:"base_tree,omitempty"` + Entries []TreeEntry `json:"tree"` +} + +// CreateTree creates a new tree in a repository. If both a tree and a nested +// path modifying that tree are specified, it will overwrite the contents of +// that tree with the new path contents and write a new tree out. +// +// GitHub API docs: https://developer.github.com/v3/git/trees/#create-a-tree +func (s *GitService) CreateTree(ctx context.Context, owner string, repo string, baseTree string, entries []TreeEntry) (*Tree, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/trees", owner, repo) + + body := &createTree{ + BaseTree: baseTree, + Entries: entries, + } + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + t := new(Tree) + resp, err := s.client.Do(ctx, req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/git_trees_test.go b/vendor/github.com/google/go-github/github/git_trees_test.go new file mode 100644 index 0000000..1f7024f --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_trees_test.go @@ -0,0 +1,195 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestGitService_GetTree(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/trees/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{ + "sha": "s", + "tree": [ { "type": "blob" } ], + "truncated": true + }`) + }) + + tree, _, err := client.Git.GetTree(context.Background(), "o", "r", "s", true) + if err != nil { + t.Errorf("Git.GetTree returned error: %v", err) + } + + want := Tree{ + SHA: String("s"), + Entries: []TreeEntry{ + { + Type: String("blob"), + }, + }, + Truncated: Bool(true), + } + if !reflect.DeepEqual(*tree, want) { + t.Errorf("Tree.Get returned %+v, want %+v", *tree, want) + } +} + +func TestGitService_GetTree_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Git.GetTree(context.Background(), "%", "%", "%", false) + testURLParseError(t, err) +} + +func TestGitService_CreateTree(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := []TreeEntry{ + { + Path: String("file.rb"), + Mode: String("100644"), + Type: String("blob"), + SHA: String("7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b"), + }, + } + + mux.HandleFunc("/repos/o/r/git/trees", func(w http.ResponseWriter, r *http.Request) { + v := new(createTree) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + + want := &createTree{ + BaseTree: "b", + Entries: input, + } + if !reflect.DeepEqual(v, want) { + t.Errorf("Git.CreateTree request body: %+v, want %+v", v, want) + } + + fmt.Fprint(w, `{ + "sha": "cd8274d15fa3ae2ab983129fb037999f264ba9a7", + "tree": [ + { + "path": "file.rb", + "mode": "100644", + "type": "blob", + "size": 132, + "sha": "7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b" + } + ] + }`) + }) + + tree, _, err := client.Git.CreateTree(context.Background(), "o", "r", "b", input) + if err != nil { + t.Errorf("Git.CreateTree returned error: %v", err) + } + + want := Tree{ + String("cd8274d15fa3ae2ab983129fb037999f264ba9a7"), + []TreeEntry{ + { + Path: String("file.rb"), + Mode: String("100644"), + Type: String("blob"), + Size: Int(132), + SHA: String("7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b"), + }, + }, + nil, + } + + if !reflect.DeepEqual(*tree, want) { + t.Errorf("Git.CreateTree returned %+v, want %+v", *tree, want) + } +} + +func TestGitService_CreateTree_Content(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := []TreeEntry{ + { + Path: String("content.md"), + Mode: String("100644"), + Content: String("file content"), + }, + } + + mux.HandleFunc("/repos/o/r/git/trees", func(w http.ResponseWriter, r *http.Request) { + v := new(createTree) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + + want := &createTree{ + BaseTree: "b", + Entries: input, + } + if !reflect.DeepEqual(v, want) { + t.Errorf("Git.CreateTree request body: %+v, want %+v", v, want) + } + + fmt.Fprint(w, `{ + "sha": "5c6780ad2c68743383b740fd1dab6f6a33202b11", + "url": "https://api.github.com/repos/o/r/git/trees/5c6780ad2c68743383b740fd1dab6f6a33202b11", + "tree": [ + { + "mode": "100644", + "type": "blob", + "sha": "aad8feacf6f8063150476a7b2bd9770f2794c08b", + "path": "content.md", + "size": 12, + "url": "https://api.github.com/repos/o/r/git/blobs/aad8feacf6f8063150476a7b2bd9770f2794c08b" + } + ] + }`) + }) + + tree, _, err := client.Git.CreateTree(context.Background(), "o", "r", "b", input) + if err != nil { + t.Errorf("Git.CreateTree returned error: %v", err) + } + + want := Tree{ + String("5c6780ad2c68743383b740fd1dab6f6a33202b11"), + []TreeEntry{ + { + Path: String("content.md"), + Mode: String("100644"), + Type: String("blob"), + Size: Int(12), + SHA: String("aad8feacf6f8063150476a7b2bd9770f2794c08b"), + URL: String("https://api.github.com/repos/o/r/git/blobs/aad8feacf6f8063150476a7b2bd9770f2794c08b"), + }, + }, + nil, + } + + if !reflect.DeepEqual(*tree, want) { + t.Errorf("Git.CreateTree returned %+v, want %+v", *tree, want) + } +} + +func TestGitService_CreateTree_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Git.CreateTree(context.Background(), "%", "%", "", nil) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/github-accessors.go b/vendor/github.com/google/go-github/github/github-accessors.go new file mode 100644 index 0000000..adc94b5 --- /dev/null +++ b/vendor/github.com/google/go-github/github/github-accessors.go @@ -0,0 +1,11997 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Code generated by gen-accessors; DO NOT EDIT. + +package github + +import ( + "encoding/json" + "time" +) + +// GetRetryAfter returns the RetryAfter field if it's non-nil, zero value otherwise. +func (a *AbuseRateLimitError) GetRetryAfter() time.Duration { + if a == nil || a.RetryAfter == nil { + return 0 + } + return *a.RetryAfter +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (a *AdminEnforcement) GetURL() string { + if a == nil || a.URL == nil { + return "" + } + return *a.URL +} + +// GetComments returns the Comments field. +func (a *AdminStats) GetComments() *CommentStats { + if a == nil { + return nil + } + return a.Comments +} + +// GetGists returns the Gists field. +func (a *AdminStats) GetGists() *GistStats { + if a == nil { + return nil + } + return a.Gists +} + +// GetHooks returns the Hooks field. +func (a *AdminStats) GetHooks() *HookStats { + if a == nil { + return nil + } + return a.Hooks +} + +// GetIssues returns the Issues field. +func (a *AdminStats) GetIssues() *IssueStats { + if a == nil { + return nil + } + return a.Issues +} + +// GetMilestones returns the Milestones field. +func (a *AdminStats) GetMilestones() *MilestoneStats { + if a == nil { + return nil + } + return a.Milestones +} + +// GetOrgs returns the Orgs field. +func (a *AdminStats) GetOrgs() *OrgStats { + if a == nil { + return nil + } + return a.Orgs +} + +// GetPages returns the Pages field. +func (a *AdminStats) GetPages() *PageStats { + if a == nil { + return nil + } + return a.Pages +} + +// GetPulls returns the Pulls field. +func (a *AdminStats) GetPulls() *PullStats { + if a == nil { + return nil + } + return a.Pulls +} + +// GetRepos returns the Repos field. +func (a *AdminStats) GetRepos() *RepoStats { + if a == nil { + return nil + } + return a.Repos +} + +// GetUsers returns the Users field. +func (a *AdminStats) GetUsers() *UserStats { + if a == nil { + return nil + } + return a.Users +} + +// GetVerifiablePasswordAuthentication returns the VerifiablePasswordAuthentication field if it's non-nil, zero value otherwise. +func (a *APIMeta) GetVerifiablePasswordAuthentication() bool { + if a == nil || a.VerifiablePasswordAuthentication == nil { + return false + } + return *a.VerifiablePasswordAuthentication +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (a *App) GetCreatedAt() time.Time { + if a == nil || a.CreatedAt == nil { + return time.Time{} + } + return *a.CreatedAt +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (a *App) GetDescription() string { + if a == nil || a.Description == nil { + return "" + } + return *a.Description +} + +// GetExternalURL returns the ExternalURL field if it's non-nil, zero value otherwise. +func (a *App) GetExternalURL() string { + if a == nil || a.ExternalURL == nil { + return "" + } + return *a.ExternalURL +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (a *App) GetHTMLURL() string { + if a == nil || a.HTMLURL == nil { + return "" + } + return *a.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (a *App) GetID() int64 { + if a == nil || a.ID == nil { + return 0 + } + return *a.ID +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (a *App) GetName() string { + if a == nil || a.Name == nil { + return "" + } + return *a.Name +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (a *App) GetNodeID() string { + if a == nil || a.NodeID == nil { + return "" + } + return *a.NodeID +} + +// GetOwner returns the Owner field. +func (a *App) GetOwner() *User { + if a == nil { + return nil + } + return a.Owner +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (a *App) GetUpdatedAt() time.Time { + if a == nil || a.UpdatedAt == nil { + return time.Time{} + } + return *a.UpdatedAt +} + +// GetApp returns the App field. +func (a *Authorization) GetApp() *AuthorizationApp { + if a == nil { + return nil + } + return a.App +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (a *Authorization) GetCreatedAt() Timestamp { + if a == nil || a.CreatedAt == nil { + return Timestamp{} + } + return *a.CreatedAt +} + +// GetFingerprint returns the Fingerprint field if it's non-nil, zero value otherwise. +func (a *Authorization) GetFingerprint() string { + if a == nil || a.Fingerprint == nil { + return "" + } + return *a.Fingerprint +} + +// GetHashedToken returns the HashedToken field if it's non-nil, zero value otherwise. +func (a *Authorization) GetHashedToken() string { + if a == nil || a.HashedToken == nil { + return "" + } + return *a.HashedToken +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (a *Authorization) GetID() int64 { + if a == nil || a.ID == nil { + return 0 + } + return *a.ID +} + +// GetNote returns the Note field if it's non-nil, zero value otherwise. +func (a *Authorization) GetNote() string { + if a == nil || a.Note == nil { + return "" + } + return *a.Note +} + +// GetNoteURL returns the NoteURL field if it's non-nil, zero value otherwise. +func (a *Authorization) GetNoteURL() string { + if a == nil || a.NoteURL == nil { + return "" + } + return *a.NoteURL +} + +// GetToken returns the Token field if it's non-nil, zero value otherwise. +func (a *Authorization) GetToken() string { + if a == nil || a.Token == nil { + return "" + } + return *a.Token +} + +// GetTokenLastEight returns the TokenLastEight field if it's non-nil, zero value otherwise. +func (a *Authorization) GetTokenLastEight() string { + if a == nil || a.TokenLastEight == nil { + return "" + } + return *a.TokenLastEight +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (a *Authorization) GetUpdatedAt() Timestamp { + if a == nil || a.UpdatedAt == nil { + return Timestamp{} + } + return *a.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (a *Authorization) GetURL() string { + if a == nil || a.URL == nil { + return "" + } + return *a.URL +} + +// GetUser returns the User field. +func (a *Authorization) GetUser() *User { + if a == nil { + return nil + } + return a.User +} + +// GetClientID returns the ClientID field if it's non-nil, zero value otherwise. +func (a *AuthorizationApp) GetClientID() string { + if a == nil || a.ClientID == nil { + return "" + } + return *a.ClientID +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (a *AuthorizationApp) GetName() string { + if a == nil || a.Name == nil { + return "" + } + return *a.Name +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (a *AuthorizationApp) GetURL() string { + if a == nil || a.URL == nil { + return "" + } + return *a.URL +} + +// GetClientID returns the ClientID field if it's non-nil, zero value otherwise. +func (a *AuthorizationRequest) GetClientID() string { + if a == nil || a.ClientID == nil { + return "" + } + return *a.ClientID +} + +// GetClientSecret returns the ClientSecret field if it's non-nil, zero value otherwise. +func (a *AuthorizationRequest) GetClientSecret() string { + if a == nil || a.ClientSecret == nil { + return "" + } + return *a.ClientSecret +} + +// GetFingerprint returns the Fingerprint field if it's non-nil, zero value otherwise. +func (a *AuthorizationRequest) GetFingerprint() string { + if a == nil || a.Fingerprint == nil { + return "" + } + return *a.Fingerprint +} + +// GetNote returns the Note field if it's non-nil, zero value otherwise. +func (a *AuthorizationRequest) GetNote() string { + if a == nil || a.Note == nil { + return "" + } + return *a.Note +} + +// GetNoteURL returns the NoteURL field if it's non-nil, zero value otherwise. +func (a *AuthorizationRequest) GetNoteURL() string { + if a == nil || a.NoteURL == nil { + return "" + } + return *a.NoteURL +} + +// GetFingerprint returns the Fingerprint field if it's non-nil, zero value otherwise. +func (a *AuthorizationUpdateRequest) GetFingerprint() string { + if a == nil || a.Fingerprint == nil { + return "" + } + return *a.Fingerprint +} + +// GetNote returns the Note field if it's non-nil, zero value otherwise. +func (a *AuthorizationUpdateRequest) GetNote() string { + if a == nil || a.Note == nil { + return "" + } + return *a.Note +} + +// GetNoteURL returns the NoteURL field if it's non-nil, zero value otherwise. +func (a *AuthorizationUpdateRequest) GetNoteURL() string { + if a == nil || a.NoteURL == nil { + return "" + } + return *a.NoteURL +} + +// GetAppID returns the AppID field if it's non-nil, zero value otherwise. +func (a *AutoTriggerCheck) GetAppID() int64 { + if a == nil || a.AppID == nil { + return 0 + } + return *a.AppID +} + +// GetSetting returns the Setting field if it's non-nil, zero value otherwise. +func (a *AutoTriggerCheck) GetSetting() bool { + if a == nil || a.Setting == nil { + return false + } + return *a.Setting +} + +// GetContent returns the Content field if it's non-nil, zero value otherwise. +func (b *Blob) GetContent() string { + if b == nil || b.Content == nil { + return "" + } + return *b.Content +} + +// GetEncoding returns the Encoding field if it's non-nil, zero value otherwise. +func (b *Blob) GetEncoding() string { + if b == nil || b.Encoding == nil { + return "" + } + return *b.Encoding +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (b *Blob) GetNodeID() string { + if b == nil || b.NodeID == nil { + return "" + } + return *b.NodeID +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (b *Blob) GetSHA() string { + if b == nil || b.SHA == nil { + return "" + } + return *b.SHA +} + +// GetSize returns the Size field if it's non-nil, zero value otherwise. +func (b *Blob) GetSize() int { + if b == nil || b.Size == nil { + return 0 + } + return *b.Size +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (b *Blob) GetURL() string { + if b == nil || b.URL == nil { + return "" + } + return *b.URL +} + +// GetCommit returns the Commit field. +func (b *Branch) GetCommit() *RepositoryCommit { + if b == nil { + return nil + } + return b.Commit +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (b *Branch) GetName() string { + if b == nil || b.Name == nil { + return "" + } + return *b.Name +} + +// GetProtected returns the Protected field if it's non-nil, zero value otherwise. +func (b *Branch) GetProtected() bool { + if b == nil || b.Protected == nil { + return false + } + return *b.Protected +} + +// GetApp returns the App field. +func (c *CheckRun) GetApp() *App { + if c == nil { + return nil + } + return c.App +} + +// GetCheckSuite returns the CheckSuite field. +func (c *CheckRun) GetCheckSuite() *CheckSuite { + if c == nil { + return nil + } + return c.CheckSuite +} + +// GetCompletedAt returns the CompletedAt field if it's non-nil, zero value otherwise. +func (c *CheckRun) GetCompletedAt() Timestamp { + if c == nil || c.CompletedAt == nil { + return Timestamp{} + } + return *c.CompletedAt +} + +// GetConclusion returns the Conclusion field if it's non-nil, zero value otherwise. +func (c *CheckRun) GetConclusion() string { + if c == nil || c.Conclusion == nil { + return "" + } + return *c.Conclusion +} + +// GetExternalID returns the ExternalID field if it's non-nil, zero value otherwise. +func (c *CheckRun) GetExternalID() string { + if c == nil || c.ExternalID == nil { + return "" + } + return *c.ExternalID +} + +// GetHeadSHA returns the HeadSHA field if it's non-nil, zero value otherwise. +func (c *CheckRun) GetHeadSHA() string { + if c == nil || c.HeadSHA == nil { + return "" + } + return *c.HeadSHA +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (c *CheckRun) GetHTMLURL() string { + if c == nil || c.HTMLURL == nil { + return "" + } + return *c.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (c *CheckRun) GetID() int64 { + if c == nil || c.ID == nil { + return 0 + } + return *c.ID +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (c *CheckRun) GetName() string { + if c == nil || c.Name == nil { + return "" + } + return *c.Name +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (c *CheckRun) GetNodeID() string { + if c == nil || c.NodeID == nil { + return "" + } + return *c.NodeID +} + +// GetOutput returns the Output field. +func (c *CheckRun) GetOutput() *CheckRunOutput { + if c == nil { + return nil + } + return c.Output +} + +// GetStartedAt returns the StartedAt field if it's non-nil, zero value otherwise. +func (c *CheckRun) GetStartedAt() Timestamp { + if c == nil || c.StartedAt == nil { + return Timestamp{} + } + return *c.StartedAt +} + +// GetStatus returns the Status field if it's non-nil, zero value otherwise. +func (c *CheckRun) GetStatus() string { + if c == nil || c.Status == nil { + return "" + } + return *c.Status +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (c *CheckRun) GetURL() string { + if c == nil || c.URL == nil { + return "" + } + return *c.URL +} + +// GetAnnotationLevel returns the AnnotationLevel field if it's non-nil, zero value otherwise. +func (c *CheckRunAnnotation) GetAnnotationLevel() string { + if c == nil || c.AnnotationLevel == nil { + return "" + } + return *c.AnnotationLevel +} + +// GetBlobHRef returns the BlobHRef field if it's non-nil, zero value otherwise. +func (c *CheckRunAnnotation) GetBlobHRef() string { + if c == nil || c.BlobHRef == nil { + return "" + } + return *c.BlobHRef +} + +// GetEndLine returns the EndLine field if it's non-nil, zero value otherwise. +func (c *CheckRunAnnotation) GetEndLine() int { + if c == nil || c.EndLine == nil { + return 0 + } + return *c.EndLine +} + +// GetMessage returns the Message field if it's non-nil, zero value otherwise. +func (c *CheckRunAnnotation) GetMessage() string { + if c == nil || c.Message == nil { + return "" + } + return *c.Message +} + +// GetPath returns the Path field if it's non-nil, zero value otherwise. +func (c *CheckRunAnnotation) GetPath() string { + if c == nil || c.Path == nil { + return "" + } + return *c.Path +} + +// GetRawDetails returns the RawDetails field if it's non-nil, zero value otherwise. +func (c *CheckRunAnnotation) GetRawDetails() string { + if c == nil || c.RawDetails == nil { + return "" + } + return *c.RawDetails +} + +// GetStartLine returns the StartLine field if it's non-nil, zero value otherwise. +func (c *CheckRunAnnotation) GetStartLine() int { + if c == nil || c.StartLine == nil { + return 0 + } + return *c.StartLine +} + +// GetTitle returns the Title field if it's non-nil, zero value otherwise. +func (c *CheckRunAnnotation) GetTitle() string { + if c == nil || c.Title == nil { + return "" + } + return *c.Title +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (c *CheckRunEvent) GetAction() string { + if c == nil || c.Action == nil { + return "" + } + return *c.Action +} + +// GetCheckRun returns the CheckRun field. +func (c *CheckRunEvent) GetCheckRun() *CheckRun { + if c == nil { + return nil + } + return c.CheckRun +} + +// GetInstallation returns the Installation field. +func (c *CheckRunEvent) GetInstallation() *Installation { + if c == nil { + return nil + } + return c.Installation +} + +// GetOrg returns the Org field. +func (c *CheckRunEvent) GetOrg() *Organization { + if c == nil { + return nil + } + return c.Org +} + +// GetRepo returns the Repo field. +func (c *CheckRunEvent) GetRepo() *Repository { + if c == nil { + return nil + } + return c.Repo +} + +// GetSender returns the Sender field. +func (c *CheckRunEvent) GetSender() *User { + if c == nil { + return nil + } + return c.Sender +} + +// GetAlt returns the Alt field if it's non-nil, zero value otherwise. +func (c *CheckRunImage) GetAlt() string { + if c == nil || c.Alt == nil { + return "" + } + return *c.Alt +} + +// GetCaption returns the Caption field if it's non-nil, zero value otherwise. +func (c *CheckRunImage) GetCaption() string { + if c == nil || c.Caption == nil { + return "" + } + return *c.Caption +} + +// GetImageURL returns the ImageURL field if it's non-nil, zero value otherwise. +func (c *CheckRunImage) GetImageURL() string { + if c == nil || c.ImageURL == nil { + return "" + } + return *c.ImageURL +} + +// GetAnnotationsCount returns the AnnotationsCount field if it's non-nil, zero value otherwise. +func (c *CheckRunOutput) GetAnnotationsCount() int { + if c == nil || c.AnnotationsCount == nil { + return 0 + } + return *c.AnnotationsCount +} + +// GetAnnotationsURL returns the AnnotationsURL field if it's non-nil, zero value otherwise. +func (c *CheckRunOutput) GetAnnotationsURL() string { + if c == nil || c.AnnotationsURL == nil { + return "" + } + return *c.AnnotationsURL +} + +// GetSummary returns the Summary field if it's non-nil, zero value otherwise. +func (c *CheckRunOutput) GetSummary() string { + if c == nil || c.Summary == nil { + return "" + } + return *c.Summary +} + +// GetText returns the Text field if it's non-nil, zero value otherwise. +func (c *CheckRunOutput) GetText() string { + if c == nil || c.Text == nil { + return "" + } + return *c.Text +} + +// GetTitle returns the Title field if it's non-nil, zero value otherwise. +func (c *CheckRunOutput) GetTitle() string { + if c == nil || c.Title == nil { + return "" + } + return *c.Title +} + +// GetAfterSHA returns the AfterSHA field if it's non-nil, zero value otherwise. +func (c *CheckSuite) GetAfterSHA() string { + if c == nil || c.AfterSHA == nil { + return "" + } + return *c.AfterSHA +} + +// GetApp returns the App field. +func (c *CheckSuite) GetApp() *App { + if c == nil { + return nil + } + return c.App +} + +// GetBeforeSHA returns the BeforeSHA field if it's non-nil, zero value otherwise. +func (c *CheckSuite) GetBeforeSHA() string { + if c == nil || c.BeforeSHA == nil { + return "" + } + return *c.BeforeSHA +} + +// GetConclusion returns the Conclusion field if it's non-nil, zero value otherwise. +func (c *CheckSuite) GetConclusion() string { + if c == nil || c.Conclusion == nil { + return "" + } + return *c.Conclusion +} + +// GetHeadBranch returns the HeadBranch field if it's non-nil, zero value otherwise. +func (c *CheckSuite) GetHeadBranch() string { + if c == nil || c.HeadBranch == nil { + return "" + } + return *c.HeadBranch +} + +// GetHeadSHA returns the HeadSHA field if it's non-nil, zero value otherwise. +func (c *CheckSuite) GetHeadSHA() string { + if c == nil || c.HeadSHA == nil { + return "" + } + return *c.HeadSHA +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (c *CheckSuite) GetID() int64 { + if c == nil || c.ID == nil { + return 0 + } + return *c.ID +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (c *CheckSuite) GetNodeID() string { + if c == nil || c.NodeID == nil { + return "" + } + return *c.NodeID +} + +// GetRepository returns the Repository field. +func (c *CheckSuite) GetRepository() *Repository { + if c == nil { + return nil + } + return c.Repository +} + +// GetStatus returns the Status field if it's non-nil, zero value otherwise. +func (c *CheckSuite) GetStatus() string { + if c == nil || c.Status == nil { + return "" + } + return *c.Status +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (c *CheckSuite) GetURL() string { + if c == nil || c.URL == nil { + return "" + } + return *c.URL +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (c *CheckSuiteEvent) GetAction() string { + if c == nil || c.Action == nil { + return "" + } + return *c.Action +} + +// GetCheckSuite returns the CheckSuite field. +func (c *CheckSuiteEvent) GetCheckSuite() *CheckSuite { + if c == nil { + return nil + } + return c.CheckSuite +} + +// GetInstallation returns the Installation field. +func (c *CheckSuiteEvent) GetInstallation() *Installation { + if c == nil { + return nil + } + return c.Installation +} + +// GetOrg returns the Org field. +func (c *CheckSuiteEvent) GetOrg() *Organization { + if c == nil { + return nil + } + return c.Org +} + +// GetRepo returns the Repo field. +func (c *CheckSuiteEvent) GetRepo() *Repository { + if c == nil { + return nil + } + return c.Repo +} + +// GetSender returns the Sender field. +func (c *CheckSuiteEvent) GetSender() *User { + if c == nil { + return nil + } + return c.Sender +} + +// GetPreferenceList returns the PreferenceList field. +func (c *CheckSuitePreferenceOptions) GetPreferenceList() *PreferenceList { + if c == nil { + return nil + } + return c.PreferenceList +} + +// GetPreferences returns the Preferences field. +func (c *CheckSuitePreferenceResults) GetPreferences() *PreferenceList { + if c == nil { + return nil + } + return c.Preferences +} + +// GetRepository returns the Repository field. +func (c *CheckSuitePreferenceResults) GetRepository() *Repository { + if c == nil { + return nil + } + return c.Repository +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (c *CodeOfConduct) GetBody() string { + if c == nil || c.Body == nil { + return "" + } + return *c.Body +} + +// GetKey returns the Key field if it's non-nil, zero value otherwise. +func (c *CodeOfConduct) GetKey() string { + if c == nil || c.Key == nil { + return "" + } + return *c.Key +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (c *CodeOfConduct) GetName() string { + if c == nil || c.Name == nil { + return "" + } + return *c.Name +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (c *CodeOfConduct) GetURL() string { + if c == nil || c.URL == nil { + return "" + } + return *c.URL +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (c *CodeResult) GetHTMLURL() string { + if c == nil || c.HTMLURL == nil { + return "" + } + return *c.HTMLURL +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (c *CodeResult) GetName() string { + if c == nil || c.Name == nil { + return "" + } + return *c.Name +} + +// GetPath returns the Path field if it's non-nil, zero value otherwise. +func (c *CodeResult) GetPath() string { + if c == nil || c.Path == nil { + return "" + } + return *c.Path +} + +// GetRepository returns the Repository field. +func (c *CodeResult) GetRepository() *Repository { + if c == nil { + return nil + } + return c.Repository +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (c *CodeResult) GetSHA() string { + if c == nil || c.SHA == nil { + return "" + } + return *c.SHA +} + +// GetIncompleteResults returns the IncompleteResults field if it's non-nil, zero value otherwise. +func (c *CodeSearchResult) GetIncompleteResults() bool { + if c == nil || c.IncompleteResults == nil { + return false + } + return *c.IncompleteResults +} + +// GetTotal returns the Total field if it's non-nil, zero value otherwise. +func (c *CodeSearchResult) GetTotal() int { + if c == nil || c.Total == nil { + return 0 + } + return *c.Total +} + +// GetCommitURL returns the CommitURL field if it's non-nil, zero value otherwise. +func (c *CombinedStatus) GetCommitURL() string { + if c == nil || c.CommitURL == nil { + return "" + } + return *c.CommitURL +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (c *CombinedStatus) GetName() string { + if c == nil || c.Name == nil { + return "" + } + return *c.Name +} + +// GetRepositoryURL returns the RepositoryURL field if it's non-nil, zero value otherwise. +func (c *CombinedStatus) GetRepositoryURL() string { + if c == nil || c.RepositoryURL == nil { + return "" + } + return *c.RepositoryURL +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (c *CombinedStatus) GetSHA() string { + if c == nil || c.SHA == nil { + return "" + } + return *c.SHA +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (c *CombinedStatus) GetState() string { + if c == nil || c.State == nil { + return "" + } + return *c.State +} + +// GetTotalCount returns the TotalCount field if it's non-nil, zero value otherwise. +func (c *CombinedStatus) GetTotalCount() int { + if c == nil || c.TotalCount == nil { + return 0 + } + return *c.TotalCount +} + +// GetTotalCommitComments returns the TotalCommitComments field if it's non-nil, zero value otherwise. +func (c *CommentStats) GetTotalCommitComments() int { + if c == nil || c.TotalCommitComments == nil { + return 0 + } + return *c.TotalCommitComments +} + +// GetTotalGistComments returns the TotalGistComments field if it's non-nil, zero value otherwise. +func (c *CommentStats) GetTotalGistComments() int { + if c == nil || c.TotalGistComments == nil { + return 0 + } + return *c.TotalGistComments +} + +// GetTotalIssueComments returns the TotalIssueComments field if it's non-nil, zero value otherwise. +func (c *CommentStats) GetTotalIssueComments() int { + if c == nil || c.TotalIssueComments == nil { + return 0 + } + return *c.TotalIssueComments +} + +// GetTotalPullRequestComments returns the TotalPullRequestComments field if it's non-nil, zero value otherwise. +func (c *CommentStats) GetTotalPullRequestComments() int { + if c == nil || c.TotalPullRequestComments == nil { + return 0 + } + return *c.TotalPullRequestComments +} + +// GetAuthor returns the Author field. +func (c *Commit) GetAuthor() *CommitAuthor { + if c == nil { + return nil + } + return c.Author +} + +// GetCommentCount returns the CommentCount field if it's non-nil, zero value otherwise. +func (c *Commit) GetCommentCount() int { + if c == nil || c.CommentCount == nil { + return 0 + } + return *c.CommentCount +} + +// GetCommitter returns the Committer field. +func (c *Commit) GetCommitter() *CommitAuthor { + if c == nil { + return nil + } + return c.Committer +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (c *Commit) GetHTMLURL() string { + if c == nil || c.HTMLURL == nil { + return "" + } + return *c.HTMLURL +} + +// GetMessage returns the Message field if it's non-nil, zero value otherwise. +func (c *Commit) GetMessage() string { + if c == nil || c.Message == nil { + return "" + } + return *c.Message +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (c *Commit) GetNodeID() string { + if c == nil || c.NodeID == nil { + return "" + } + return *c.NodeID +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (c *Commit) GetSHA() string { + if c == nil || c.SHA == nil { + return "" + } + return *c.SHA +} + +// GetStats returns the Stats field. +func (c *Commit) GetStats() *CommitStats { + if c == nil { + return nil + } + return c.Stats +} + +// GetTree returns the Tree field. +func (c *Commit) GetTree() *Tree { + if c == nil { + return nil + } + return c.Tree +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (c *Commit) GetURL() string { + if c == nil || c.URL == nil { + return "" + } + return *c.URL +} + +// GetVerification returns the Verification field. +func (c *Commit) GetVerification() *SignatureVerification { + if c == nil { + return nil + } + return c.Verification +} + +// GetDate returns the Date field if it's non-nil, zero value otherwise. +func (c *CommitAuthor) GetDate() time.Time { + if c == nil || c.Date == nil { + return time.Time{} + } + return *c.Date +} + +// GetEmail returns the Email field if it's non-nil, zero value otherwise. +func (c *CommitAuthor) GetEmail() string { + if c == nil || c.Email == nil { + return "" + } + return *c.Email +} + +// GetLogin returns the Login field if it's non-nil, zero value otherwise. +func (c *CommitAuthor) GetLogin() string { + if c == nil || c.Login == nil { + return "" + } + return *c.Login +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (c *CommitAuthor) GetName() string { + if c == nil || c.Name == nil { + return "" + } + return *c.Name +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (c *CommitCommentEvent) GetAction() string { + if c == nil || c.Action == nil { + return "" + } + return *c.Action +} + +// GetComment returns the Comment field. +func (c *CommitCommentEvent) GetComment() *RepositoryComment { + if c == nil { + return nil + } + return c.Comment +} + +// GetInstallation returns the Installation field. +func (c *CommitCommentEvent) GetInstallation() *Installation { + if c == nil { + return nil + } + return c.Installation +} + +// GetRepo returns the Repo field. +func (c *CommitCommentEvent) GetRepo() *Repository { + if c == nil { + return nil + } + return c.Repo +} + +// GetSender returns the Sender field. +func (c *CommitCommentEvent) GetSender() *User { + if c == nil { + return nil + } + return c.Sender +} + +// GetAdditions returns the Additions field if it's non-nil, zero value otherwise. +func (c *CommitFile) GetAdditions() int { + if c == nil || c.Additions == nil { + return 0 + } + return *c.Additions +} + +// GetBlobURL returns the BlobURL field if it's non-nil, zero value otherwise. +func (c *CommitFile) GetBlobURL() string { + if c == nil || c.BlobURL == nil { + return "" + } + return *c.BlobURL +} + +// GetChanges returns the Changes field if it's non-nil, zero value otherwise. +func (c *CommitFile) GetChanges() int { + if c == nil || c.Changes == nil { + return 0 + } + return *c.Changes +} + +// GetContentsURL returns the ContentsURL field if it's non-nil, zero value otherwise. +func (c *CommitFile) GetContentsURL() string { + if c == nil || c.ContentsURL == nil { + return "" + } + return *c.ContentsURL +} + +// GetDeletions returns the Deletions field if it's non-nil, zero value otherwise. +func (c *CommitFile) GetDeletions() int { + if c == nil || c.Deletions == nil { + return 0 + } + return *c.Deletions +} + +// GetFilename returns the Filename field if it's non-nil, zero value otherwise. +func (c *CommitFile) GetFilename() string { + if c == nil || c.Filename == nil { + return "" + } + return *c.Filename +} + +// GetPatch returns the Patch field if it's non-nil, zero value otherwise. +func (c *CommitFile) GetPatch() string { + if c == nil || c.Patch == nil { + return "" + } + return *c.Patch +} + +// GetRawURL returns the RawURL field if it's non-nil, zero value otherwise. +func (c *CommitFile) GetRawURL() string { + if c == nil || c.RawURL == nil { + return "" + } + return *c.RawURL +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (c *CommitFile) GetSHA() string { + if c == nil || c.SHA == nil { + return "" + } + return *c.SHA +} + +// GetStatus returns the Status field if it's non-nil, zero value otherwise. +func (c *CommitFile) GetStatus() string { + if c == nil || c.Status == nil { + return "" + } + return *c.Status +} + +// GetAuthor returns the Author field. +func (c *CommitResult) GetAuthor() *User { + if c == nil { + return nil + } + return c.Author +} + +// GetCommentsURL returns the CommentsURL field if it's non-nil, zero value otherwise. +func (c *CommitResult) GetCommentsURL() string { + if c == nil || c.CommentsURL == nil { + return "" + } + return *c.CommentsURL +} + +// GetCommit returns the Commit field. +func (c *CommitResult) GetCommit() *Commit { + if c == nil { + return nil + } + return c.Commit +} + +// GetCommitter returns the Committer field. +func (c *CommitResult) GetCommitter() *User { + if c == nil { + return nil + } + return c.Committer +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (c *CommitResult) GetHTMLURL() string { + if c == nil || c.HTMLURL == nil { + return "" + } + return *c.HTMLURL +} + +// GetRepository returns the Repository field. +func (c *CommitResult) GetRepository() *Repository { + if c == nil { + return nil + } + return c.Repository +} + +// GetScore returns the Score field. +func (c *CommitResult) GetScore() *float64 { + if c == nil { + return nil + } + return c.Score +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (c *CommitResult) GetSHA() string { + if c == nil || c.SHA == nil { + return "" + } + return *c.SHA +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (c *CommitResult) GetURL() string { + if c == nil || c.URL == nil { + return "" + } + return *c.URL +} + +// GetAheadBy returns the AheadBy field if it's non-nil, zero value otherwise. +func (c *CommitsComparison) GetAheadBy() int { + if c == nil || c.AheadBy == nil { + return 0 + } + return *c.AheadBy +} + +// GetBaseCommit returns the BaseCommit field. +func (c *CommitsComparison) GetBaseCommit() *RepositoryCommit { + if c == nil { + return nil + } + return c.BaseCommit +} + +// GetBehindBy returns the BehindBy field if it's non-nil, zero value otherwise. +func (c *CommitsComparison) GetBehindBy() int { + if c == nil || c.BehindBy == nil { + return 0 + } + return *c.BehindBy +} + +// GetDiffURL returns the DiffURL field if it's non-nil, zero value otherwise. +func (c *CommitsComparison) GetDiffURL() string { + if c == nil || c.DiffURL == nil { + return "" + } + return *c.DiffURL +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (c *CommitsComparison) GetHTMLURL() string { + if c == nil || c.HTMLURL == nil { + return "" + } + return *c.HTMLURL +} + +// GetMergeBaseCommit returns the MergeBaseCommit field. +func (c *CommitsComparison) GetMergeBaseCommit() *RepositoryCommit { + if c == nil { + return nil + } + return c.MergeBaseCommit +} + +// GetPatchURL returns the PatchURL field if it's non-nil, zero value otherwise. +func (c *CommitsComparison) GetPatchURL() string { + if c == nil || c.PatchURL == nil { + return "" + } + return *c.PatchURL +} + +// GetPermalinkURL returns the PermalinkURL field if it's non-nil, zero value otherwise. +func (c *CommitsComparison) GetPermalinkURL() string { + if c == nil || c.PermalinkURL == nil { + return "" + } + return *c.PermalinkURL +} + +// GetStatus returns the Status field if it's non-nil, zero value otherwise. +func (c *CommitsComparison) GetStatus() string { + if c == nil || c.Status == nil { + return "" + } + return *c.Status +} + +// GetTotalCommits returns the TotalCommits field if it's non-nil, zero value otherwise. +func (c *CommitsComparison) GetTotalCommits() int { + if c == nil || c.TotalCommits == nil { + return 0 + } + return *c.TotalCommits +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (c *CommitsComparison) GetURL() string { + if c == nil || c.URL == nil { + return "" + } + return *c.URL +} + +// GetIncompleteResults returns the IncompleteResults field if it's non-nil, zero value otherwise. +func (c *CommitsSearchResult) GetIncompleteResults() bool { + if c == nil || c.IncompleteResults == nil { + return false + } + return *c.IncompleteResults +} + +// GetTotal returns the Total field if it's non-nil, zero value otherwise. +func (c *CommitsSearchResult) GetTotal() int { + if c == nil || c.Total == nil { + return 0 + } + return *c.Total +} + +// GetAdditions returns the Additions field if it's non-nil, zero value otherwise. +func (c *CommitStats) GetAdditions() int { + if c == nil || c.Additions == nil { + return 0 + } + return *c.Additions +} + +// GetDeletions returns the Deletions field if it's non-nil, zero value otherwise. +func (c *CommitStats) GetDeletions() int { + if c == nil || c.Deletions == nil { + return 0 + } + return *c.Deletions +} + +// GetTotal returns the Total field if it's non-nil, zero value otherwise. +func (c *CommitStats) GetTotal() int { + if c == nil || c.Total == nil { + return 0 + } + return *c.Total +} + +// GetCodeOfConduct returns the CodeOfConduct field. +func (c *CommunityHealthFiles) GetCodeOfConduct() *Metric { + if c == nil { + return nil + } + return c.CodeOfConduct +} + +// GetContributing returns the Contributing field. +func (c *CommunityHealthFiles) GetContributing() *Metric { + if c == nil { + return nil + } + return c.Contributing +} + +// GetIssueTemplate returns the IssueTemplate field. +func (c *CommunityHealthFiles) GetIssueTemplate() *Metric { + if c == nil { + return nil + } + return c.IssueTemplate +} + +// GetLicense returns the License field. +func (c *CommunityHealthFiles) GetLicense() *Metric { + if c == nil { + return nil + } + return c.License +} + +// GetPullRequestTemplate returns the PullRequestTemplate field. +func (c *CommunityHealthFiles) GetPullRequestTemplate() *Metric { + if c == nil { + return nil + } + return c.PullRequestTemplate +} + +// GetReadme returns the Readme field. +func (c *CommunityHealthFiles) GetReadme() *Metric { + if c == nil { + return nil + } + return c.Readme +} + +// GetFiles returns the Files field. +func (c *CommunityHealthMetrics) GetFiles() *CommunityHealthFiles { + if c == nil { + return nil + } + return c.Files +} + +// GetHealthPercentage returns the HealthPercentage field if it's non-nil, zero value otherwise. +func (c *CommunityHealthMetrics) GetHealthPercentage() int { + if c == nil || c.HealthPercentage == nil { + return 0 + } + return *c.HealthPercentage +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (c *CommunityHealthMetrics) GetUpdatedAt() time.Time { + if c == nil || c.UpdatedAt == nil { + return time.Time{} + } + return *c.UpdatedAt +} + +// GetAvatarURL returns the AvatarURL field if it's non-nil, zero value otherwise. +func (c *Contributor) GetAvatarURL() string { + if c == nil || c.AvatarURL == nil { + return "" + } + return *c.AvatarURL +} + +// GetContributions returns the Contributions field if it's non-nil, zero value otherwise. +func (c *Contributor) GetContributions() int { + if c == nil || c.Contributions == nil { + return 0 + } + return *c.Contributions +} + +// GetEventsURL returns the EventsURL field if it's non-nil, zero value otherwise. +func (c *Contributor) GetEventsURL() string { + if c == nil || c.EventsURL == nil { + return "" + } + return *c.EventsURL +} + +// GetFollowersURL returns the FollowersURL field if it's non-nil, zero value otherwise. +func (c *Contributor) GetFollowersURL() string { + if c == nil || c.FollowersURL == nil { + return "" + } + return *c.FollowersURL +} + +// GetFollowingURL returns the FollowingURL field if it's non-nil, zero value otherwise. +func (c *Contributor) GetFollowingURL() string { + if c == nil || c.FollowingURL == nil { + return "" + } + return *c.FollowingURL +} + +// GetGistsURL returns the GistsURL field if it's non-nil, zero value otherwise. +func (c *Contributor) GetGistsURL() string { + if c == nil || c.GistsURL == nil { + return "" + } + return *c.GistsURL +} + +// GetGravatarID returns the GravatarID field if it's non-nil, zero value otherwise. +func (c *Contributor) GetGravatarID() string { + if c == nil || c.GravatarID == nil { + return "" + } + return *c.GravatarID +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (c *Contributor) GetHTMLURL() string { + if c == nil || c.HTMLURL == nil { + return "" + } + return *c.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (c *Contributor) GetID() int64 { + if c == nil || c.ID == nil { + return 0 + } + return *c.ID +} + +// GetLogin returns the Login field if it's non-nil, zero value otherwise. +func (c *Contributor) GetLogin() string { + if c == nil || c.Login == nil { + return "" + } + return *c.Login +} + +// GetOrganizationsURL returns the OrganizationsURL field if it's non-nil, zero value otherwise. +func (c *Contributor) GetOrganizationsURL() string { + if c == nil || c.OrganizationsURL == nil { + return "" + } + return *c.OrganizationsURL +} + +// GetReceivedEventsURL returns the ReceivedEventsURL field if it's non-nil, zero value otherwise. +func (c *Contributor) GetReceivedEventsURL() string { + if c == nil || c.ReceivedEventsURL == nil { + return "" + } + return *c.ReceivedEventsURL +} + +// GetReposURL returns the ReposURL field if it's non-nil, zero value otherwise. +func (c *Contributor) GetReposURL() string { + if c == nil || c.ReposURL == nil { + return "" + } + return *c.ReposURL +} + +// GetSiteAdmin returns the SiteAdmin field if it's non-nil, zero value otherwise. +func (c *Contributor) GetSiteAdmin() bool { + if c == nil || c.SiteAdmin == nil { + return false + } + return *c.SiteAdmin +} + +// GetStarredURL returns the StarredURL field if it's non-nil, zero value otherwise. +func (c *Contributor) GetStarredURL() string { + if c == nil || c.StarredURL == nil { + return "" + } + return *c.StarredURL +} + +// GetSubscriptionsURL returns the SubscriptionsURL field if it's non-nil, zero value otherwise. +func (c *Contributor) GetSubscriptionsURL() string { + if c == nil || c.SubscriptionsURL == nil { + return "" + } + return *c.SubscriptionsURL +} + +// GetType returns the Type field if it's non-nil, zero value otherwise. +func (c *Contributor) GetType() string { + if c == nil || c.Type == nil { + return "" + } + return *c.Type +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (c *Contributor) GetURL() string { + if c == nil || c.URL == nil { + return "" + } + return *c.URL +} + +// GetAuthor returns the Author field. +func (c *ContributorStats) GetAuthor() *Contributor { + if c == nil { + return nil + } + return c.Author +} + +// GetTotal returns the Total field if it's non-nil, zero value otherwise. +func (c *ContributorStats) GetTotal() int { + if c == nil || c.Total == nil { + return 0 + } + return *c.Total +} + +// GetCompletedAt returns the CompletedAt field if it's non-nil, zero value otherwise. +func (c *CreateCheckRunOptions) GetCompletedAt() Timestamp { + if c == nil || c.CompletedAt == nil { + return Timestamp{} + } + return *c.CompletedAt +} + +// GetConclusion returns the Conclusion field if it's non-nil, zero value otherwise. +func (c *CreateCheckRunOptions) GetConclusion() string { + if c == nil || c.Conclusion == nil { + return "" + } + return *c.Conclusion +} + +// GetDetailsURL returns the DetailsURL field if it's non-nil, zero value otherwise. +func (c *CreateCheckRunOptions) GetDetailsURL() string { + if c == nil || c.DetailsURL == nil { + return "" + } + return *c.DetailsURL +} + +// GetExternalID returns the ExternalID field if it's non-nil, zero value otherwise. +func (c *CreateCheckRunOptions) GetExternalID() string { + if c == nil || c.ExternalID == nil { + return "" + } + return *c.ExternalID +} + +// GetOutput returns the Output field. +func (c *CreateCheckRunOptions) GetOutput() *CheckRunOutput { + if c == nil { + return nil + } + return c.Output +} + +// GetStartedAt returns the StartedAt field if it's non-nil, zero value otherwise. +func (c *CreateCheckRunOptions) GetStartedAt() Timestamp { + if c == nil || c.StartedAt == nil { + return Timestamp{} + } + return *c.StartedAt +} + +// GetStatus returns the Status field if it's non-nil, zero value otherwise. +func (c *CreateCheckRunOptions) GetStatus() string { + if c == nil || c.Status == nil { + return "" + } + return *c.Status +} + +// GetHeadBranch returns the HeadBranch field if it's non-nil, zero value otherwise. +func (c *CreateCheckSuiteOptions) GetHeadBranch() string { + if c == nil || c.HeadBranch == nil { + return "" + } + return *c.HeadBranch +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (c *CreateEvent) GetDescription() string { + if c == nil || c.Description == nil { + return "" + } + return *c.Description +} + +// GetInstallation returns the Installation field. +func (c *CreateEvent) GetInstallation() *Installation { + if c == nil { + return nil + } + return c.Installation +} + +// GetMasterBranch returns the MasterBranch field if it's non-nil, zero value otherwise. +func (c *CreateEvent) GetMasterBranch() string { + if c == nil || c.MasterBranch == nil { + return "" + } + return *c.MasterBranch +} + +// GetPusherType returns the PusherType field if it's non-nil, zero value otherwise. +func (c *CreateEvent) GetPusherType() string { + if c == nil || c.PusherType == nil { + return "" + } + return *c.PusherType +} + +// GetRef returns the Ref field if it's non-nil, zero value otherwise. +func (c *CreateEvent) GetRef() string { + if c == nil || c.Ref == nil { + return "" + } + return *c.Ref +} + +// GetRefType returns the RefType field if it's non-nil, zero value otherwise. +func (c *CreateEvent) GetRefType() string { + if c == nil || c.RefType == nil { + return "" + } + return *c.RefType +} + +// GetRepo returns the Repo field. +func (c *CreateEvent) GetRepo() *Repository { + if c == nil { + return nil + } + return c.Repo +} + +// GetSender returns the Sender field. +func (c *CreateEvent) GetSender() *User { + if c == nil { + return nil + } + return c.Sender +} + +// GetEmail returns the Email field if it's non-nil, zero value otherwise. +func (c *CreateOrgInvitationOptions) GetEmail() string { + if c == nil || c.Email == nil { + return "" + } + return *c.Email +} + +// GetInviteeID returns the InviteeID field if it's non-nil, zero value otherwise. +func (c *CreateOrgInvitationOptions) GetInviteeID() int64 { + if c == nil || c.InviteeID == nil { + return 0 + } + return *c.InviteeID +} + +// GetRole returns the Role field if it's non-nil, zero value otherwise. +func (c *CreateOrgInvitationOptions) GetRole() string { + if c == nil || c.Role == nil { + return "" + } + return *c.Role +} + +// GetInstallation returns the Installation field. +func (d *DeleteEvent) GetInstallation() *Installation { + if d == nil { + return nil + } + return d.Installation +} + +// GetPusherType returns the PusherType field if it's non-nil, zero value otherwise. +func (d *DeleteEvent) GetPusherType() string { + if d == nil || d.PusherType == nil { + return "" + } + return *d.PusherType +} + +// GetRef returns the Ref field if it's non-nil, zero value otherwise. +func (d *DeleteEvent) GetRef() string { + if d == nil || d.Ref == nil { + return "" + } + return *d.Ref +} + +// GetRefType returns the RefType field if it's non-nil, zero value otherwise. +func (d *DeleteEvent) GetRefType() string { + if d == nil || d.RefType == nil { + return "" + } + return *d.RefType +} + +// GetRepo returns the Repo field. +func (d *DeleteEvent) GetRepo() *Repository { + if d == nil { + return nil + } + return d.Repo +} + +// GetSender returns the Sender field. +func (d *DeleteEvent) GetSender() *User { + if d == nil { + return nil + } + return d.Sender +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (d *Deployment) GetCreatedAt() Timestamp { + if d == nil || d.CreatedAt == nil { + return Timestamp{} + } + return *d.CreatedAt +} + +// GetCreator returns the Creator field. +func (d *Deployment) GetCreator() *User { + if d == nil { + return nil + } + return d.Creator +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (d *Deployment) GetDescription() string { + if d == nil || d.Description == nil { + return "" + } + return *d.Description +} + +// GetEnvironment returns the Environment field if it's non-nil, zero value otherwise. +func (d *Deployment) GetEnvironment() string { + if d == nil || d.Environment == nil { + return "" + } + return *d.Environment +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (d *Deployment) GetID() int64 { + if d == nil || d.ID == nil { + return 0 + } + return *d.ID +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (d *Deployment) GetNodeID() string { + if d == nil || d.NodeID == nil { + return "" + } + return *d.NodeID +} + +// GetRef returns the Ref field if it's non-nil, zero value otherwise. +func (d *Deployment) GetRef() string { + if d == nil || d.Ref == nil { + return "" + } + return *d.Ref +} + +// GetRepositoryURL returns the RepositoryURL field if it's non-nil, zero value otherwise. +func (d *Deployment) GetRepositoryURL() string { + if d == nil || d.RepositoryURL == nil { + return "" + } + return *d.RepositoryURL +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (d *Deployment) GetSHA() string { + if d == nil || d.SHA == nil { + return "" + } + return *d.SHA +} + +// GetStatusesURL returns the StatusesURL field if it's non-nil, zero value otherwise. +func (d *Deployment) GetStatusesURL() string { + if d == nil || d.StatusesURL == nil { + return "" + } + return *d.StatusesURL +} + +// GetTask returns the Task field if it's non-nil, zero value otherwise. +func (d *Deployment) GetTask() string { + if d == nil || d.Task == nil { + return "" + } + return *d.Task +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (d *Deployment) GetUpdatedAt() Timestamp { + if d == nil || d.UpdatedAt == nil { + return Timestamp{} + } + return *d.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (d *Deployment) GetURL() string { + if d == nil || d.URL == nil { + return "" + } + return *d.URL +} + +// GetDeployment returns the Deployment field. +func (d *DeploymentEvent) GetDeployment() *Deployment { + if d == nil { + return nil + } + return d.Deployment +} + +// GetInstallation returns the Installation field. +func (d *DeploymentEvent) GetInstallation() *Installation { + if d == nil { + return nil + } + return d.Installation +} + +// GetRepo returns the Repo field. +func (d *DeploymentEvent) GetRepo() *Repository { + if d == nil { + return nil + } + return d.Repo +} + +// GetSender returns the Sender field. +func (d *DeploymentEvent) GetSender() *User { + if d == nil { + return nil + } + return d.Sender +} + +// GetAutoMerge returns the AutoMerge field if it's non-nil, zero value otherwise. +func (d *DeploymentRequest) GetAutoMerge() bool { + if d == nil || d.AutoMerge == nil { + return false + } + return *d.AutoMerge +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (d *DeploymentRequest) GetDescription() string { + if d == nil || d.Description == nil { + return "" + } + return *d.Description +} + +// GetEnvironment returns the Environment field if it's non-nil, zero value otherwise. +func (d *DeploymentRequest) GetEnvironment() string { + if d == nil || d.Environment == nil { + return "" + } + return *d.Environment +} + +// GetPayload returns the Payload field if it's non-nil, zero value otherwise. +func (d *DeploymentRequest) GetPayload() string { + if d == nil || d.Payload == nil { + return "" + } + return *d.Payload +} + +// GetProductionEnvironment returns the ProductionEnvironment field if it's non-nil, zero value otherwise. +func (d *DeploymentRequest) GetProductionEnvironment() bool { + if d == nil || d.ProductionEnvironment == nil { + return false + } + return *d.ProductionEnvironment +} + +// GetRef returns the Ref field if it's non-nil, zero value otherwise. +func (d *DeploymentRequest) GetRef() string { + if d == nil || d.Ref == nil { + return "" + } + return *d.Ref +} + +// GetRequiredContexts returns the RequiredContexts field if it's non-nil, zero value otherwise. +func (d *DeploymentRequest) GetRequiredContexts() []string { + if d == nil || d.RequiredContexts == nil { + return nil + } + return *d.RequiredContexts +} + +// GetTask returns the Task field if it's non-nil, zero value otherwise. +func (d *DeploymentRequest) GetTask() string { + if d == nil || d.Task == nil { + return "" + } + return *d.Task +} + +// GetTransientEnvironment returns the TransientEnvironment field if it's non-nil, zero value otherwise. +func (d *DeploymentRequest) GetTransientEnvironment() bool { + if d == nil || d.TransientEnvironment == nil { + return false + } + return *d.TransientEnvironment +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (d *DeploymentStatus) GetCreatedAt() Timestamp { + if d == nil || d.CreatedAt == nil { + return Timestamp{} + } + return *d.CreatedAt +} + +// GetCreator returns the Creator field. +func (d *DeploymentStatus) GetCreator() *User { + if d == nil { + return nil + } + return d.Creator +} + +// GetDeploymentURL returns the DeploymentURL field if it's non-nil, zero value otherwise. +func (d *DeploymentStatus) GetDeploymentURL() string { + if d == nil || d.DeploymentURL == nil { + return "" + } + return *d.DeploymentURL +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (d *DeploymentStatus) GetDescription() string { + if d == nil || d.Description == nil { + return "" + } + return *d.Description +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (d *DeploymentStatus) GetID() int64 { + if d == nil || d.ID == nil { + return 0 + } + return *d.ID +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (d *DeploymentStatus) GetNodeID() string { + if d == nil || d.NodeID == nil { + return "" + } + return *d.NodeID +} + +// GetRepositoryURL returns the RepositoryURL field if it's non-nil, zero value otherwise. +func (d *DeploymentStatus) GetRepositoryURL() string { + if d == nil || d.RepositoryURL == nil { + return "" + } + return *d.RepositoryURL +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (d *DeploymentStatus) GetState() string { + if d == nil || d.State == nil { + return "" + } + return *d.State +} + +// GetTargetURL returns the TargetURL field if it's non-nil, zero value otherwise. +func (d *DeploymentStatus) GetTargetURL() string { + if d == nil || d.TargetURL == nil { + return "" + } + return *d.TargetURL +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (d *DeploymentStatus) GetUpdatedAt() Timestamp { + if d == nil || d.UpdatedAt == nil { + return Timestamp{} + } + return *d.UpdatedAt +} + +// GetDeployment returns the Deployment field. +func (d *DeploymentStatusEvent) GetDeployment() *Deployment { + if d == nil { + return nil + } + return d.Deployment +} + +// GetDeploymentStatus returns the DeploymentStatus field. +func (d *DeploymentStatusEvent) GetDeploymentStatus() *DeploymentStatus { + if d == nil { + return nil + } + return d.DeploymentStatus +} + +// GetInstallation returns the Installation field. +func (d *DeploymentStatusEvent) GetInstallation() *Installation { + if d == nil { + return nil + } + return d.Installation +} + +// GetRepo returns the Repo field. +func (d *DeploymentStatusEvent) GetRepo() *Repository { + if d == nil { + return nil + } + return d.Repo +} + +// GetSender returns the Sender field. +func (d *DeploymentStatusEvent) GetSender() *User { + if d == nil { + return nil + } + return d.Sender +} + +// GetAutoInactive returns the AutoInactive field if it's non-nil, zero value otherwise. +func (d *DeploymentStatusRequest) GetAutoInactive() bool { + if d == nil || d.AutoInactive == nil { + return false + } + return *d.AutoInactive +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (d *DeploymentStatusRequest) GetDescription() string { + if d == nil || d.Description == nil { + return "" + } + return *d.Description +} + +// GetEnvironmentURL returns the EnvironmentURL field if it's non-nil, zero value otherwise. +func (d *DeploymentStatusRequest) GetEnvironmentURL() string { + if d == nil || d.EnvironmentURL == nil { + return "" + } + return *d.EnvironmentURL +} + +// GetLogURL returns the LogURL field if it's non-nil, zero value otherwise. +func (d *DeploymentStatusRequest) GetLogURL() string { + if d == nil || d.LogURL == nil { + return "" + } + return *d.LogURL +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (d *DeploymentStatusRequest) GetState() string { + if d == nil || d.State == nil { + return "" + } + return *d.State +} + +// GetAuthor returns the Author field. +func (d *DiscussionComment) GetAuthor() *User { + if d == nil { + return nil + } + return d.Author +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (d *DiscussionComment) GetBody() string { + if d == nil || d.Body == nil { + return "" + } + return *d.Body +} + +// GetBodyHTML returns the BodyHTML field if it's non-nil, zero value otherwise. +func (d *DiscussionComment) GetBodyHTML() string { + if d == nil || d.BodyHTML == nil { + return "" + } + return *d.BodyHTML +} + +// GetBodyVersion returns the BodyVersion field if it's non-nil, zero value otherwise. +func (d *DiscussionComment) GetBodyVersion() string { + if d == nil || d.BodyVersion == nil { + return "" + } + return *d.BodyVersion +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (d *DiscussionComment) GetCreatedAt() Timestamp { + if d == nil || d.CreatedAt == nil { + return Timestamp{} + } + return *d.CreatedAt +} + +// GetDiscussionURL returns the DiscussionURL field if it's non-nil, zero value otherwise. +func (d *DiscussionComment) GetDiscussionURL() string { + if d == nil || d.DiscussionURL == nil { + return "" + } + return *d.DiscussionURL +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (d *DiscussionComment) GetHTMLURL() string { + if d == nil || d.HTMLURL == nil { + return "" + } + return *d.HTMLURL +} + +// GetLastEditedAt returns the LastEditedAt field if it's non-nil, zero value otherwise. +func (d *DiscussionComment) GetLastEditedAt() Timestamp { + if d == nil || d.LastEditedAt == nil { + return Timestamp{} + } + return *d.LastEditedAt +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (d *DiscussionComment) GetNodeID() string { + if d == nil || d.NodeID == nil { + return "" + } + return *d.NodeID +} + +// GetNumber returns the Number field if it's non-nil, zero value otherwise. +func (d *DiscussionComment) GetNumber() int { + if d == nil || d.Number == nil { + return 0 + } + return *d.Number +} + +// GetReactions returns the Reactions field. +func (d *DiscussionComment) GetReactions() *Reactions { + if d == nil { + return nil + } + return d.Reactions +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (d *DiscussionComment) GetUpdatedAt() Timestamp { + if d == nil || d.UpdatedAt == nil { + return Timestamp{} + } + return *d.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (d *DiscussionComment) GetURL() string { + if d == nil || d.URL == nil { + return "" + } + return *d.URL +} + +// GetTeams returns the Teams field if it's non-nil, zero value otherwise. +func (d *DismissalRestrictionsRequest) GetTeams() []string { + if d == nil || d.Teams == nil { + return nil + } + return *d.Teams +} + +// GetUsers returns the Users field if it's non-nil, zero value otherwise. +func (d *DismissalRestrictionsRequest) GetUsers() []string { + if d == nil || d.Users == nil { + return nil + } + return *d.Users +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (d *DraftReviewComment) GetBody() string { + if d == nil || d.Body == nil { + return "" + } + return *d.Body +} + +// GetPath returns the Path field if it's non-nil, zero value otherwise. +func (d *DraftReviewComment) GetPath() string { + if d == nil || d.Path == nil { + return "" + } + return *d.Path +} + +// GetPosition returns the Position field if it's non-nil, zero value otherwise. +func (d *DraftReviewComment) GetPosition() int { + if d == nil || d.Position == nil { + return 0 + } + return *d.Position +} + +// GetActor returns the Actor field. +func (e *Event) GetActor() *User { + if e == nil { + return nil + } + return e.Actor +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (e *Event) GetCreatedAt() time.Time { + if e == nil || e.CreatedAt == nil { + return time.Time{} + } + return *e.CreatedAt +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (e *Event) GetID() string { + if e == nil || e.ID == nil { + return "" + } + return *e.ID +} + +// GetOrg returns the Org field. +func (e *Event) GetOrg() *Organization { + if e == nil { + return nil + } + return e.Org +} + +// GetPublic returns the Public field if it's non-nil, zero value otherwise. +func (e *Event) GetPublic() bool { + if e == nil || e.Public == nil { + return false + } + return *e.Public +} + +// GetRawPayload returns the RawPayload field if it's non-nil, zero value otherwise. +func (e *Event) GetRawPayload() json.RawMessage { + if e == nil || e.RawPayload == nil { + return json.RawMessage{} + } + return *e.RawPayload +} + +// GetRepo returns the Repo field. +func (e *Event) GetRepo() *Repository { + if e == nil { + return nil + } + return e.Repo +} + +// GetType returns the Type field if it's non-nil, zero value otherwise. +func (e *Event) GetType() string { + if e == nil || e.Type == nil { + return "" + } + return *e.Type +} + +// GetHRef returns the HRef field if it's non-nil, zero value otherwise. +func (f *FeedLink) GetHRef() string { + if f == nil || f.HRef == nil { + return "" + } + return *f.HRef +} + +// GetType returns the Type field if it's non-nil, zero value otherwise. +func (f *FeedLink) GetType() string { + if f == nil || f.Type == nil { + return "" + } + return *f.Type +} + +// GetCurrentUserActorURL returns the CurrentUserActorURL field if it's non-nil, zero value otherwise. +func (f *Feeds) GetCurrentUserActorURL() string { + if f == nil || f.CurrentUserActorURL == nil { + return "" + } + return *f.CurrentUserActorURL +} + +// GetCurrentUserOrganizationURL returns the CurrentUserOrganizationURL field if it's non-nil, zero value otherwise. +func (f *Feeds) GetCurrentUserOrganizationURL() string { + if f == nil || f.CurrentUserOrganizationURL == nil { + return "" + } + return *f.CurrentUserOrganizationURL +} + +// GetCurrentUserPublicURL returns the CurrentUserPublicURL field if it's non-nil, zero value otherwise. +func (f *Feeds) GetCurrentUserPublicURL() string { + if f == nil || f.CurrentUserPublicURL == nil { + return "" + } + return *f.CurrentUserPublicURL +} + +// GetCurrentUserURL returns the CurrentUserURL field if it's non-nil, zero value otherwise. +func (f *Feeds) GetCurrentUserURL() string { + if f == nil || f.CurrentUserURL == nil { + return "" + } + return *f.CurrentUserURL +} + +// GetTimelineURL returns the TimelineURL field if it's non-nil, zero value otherwise. +func (f *Feeds) GetTimelineURL() string { + if f == nil || f.TimelineURL == nil { + return "" + } + return *f.TimelineURL +} + +// GetUserURL returns the UserURL field if it's non-nil, zero value otherwise. +func (f *Feeds) GetUserURL() string { + if f == nil || f.UserURL == nil { + return "" + } + return *f.UserURL +} + +// GetForkee returns the Forkee field. +func (f *ForkEvent) GetForkee() *Repository { + if f == nil { + return nil + } + return f.Forkee +} + +// GetInstallation returns the Installation field. +func (f *ForkEvent) GetInstallation() *Installation { + if f == nil { + return nil + } + return f.Installation +} + +// GetRepo returns the Repo field. +func (f *ForkEvent) GetRepo() *Repository { + if f == nil { + return nil + } + return f.Repo +} + +// GetSender returns the Sender field. +func (f *ForkEvent) GetSender() *User { + if f == nil { + return nil + } + return f.Sender +} + +// GetComments returns the Comments field if it's non-nil, zero value otherwise. +func (g *Gist) GetComments() int { + if g == nil || g.Comments == nil { + return 0 + } + return *g.Comments +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (g *Gist) GetCreatedAt() time.Time { + if g == nil || g.CreatedAt == nil { + return time.Time{} + } + return *g.CreatedAt +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (g *Gist) GetDescription() string { + if g == nil || g.Description == nil { + return "" + } + return *g.Description +} + +// GetGitPullURL returns the GitPullURL field if it's non-nil, zero value otherwise. +func (g *Gist) GetGitPullURL() string { + if g == nil || g.GitPullURL == nil { + return "" + } + return *g.GitPullURL +} + +// GetGitPushURL returns the GitPushURL field if it's non-nil, zero value otherwise. +func (g *Gist) GetGitPushURL() string { + if g == nil || g.GitPushURL == nil { + return "" + } + return *g.GitPushURL +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (g *Gist) GetHTMLURL() string { + if g == nil || g.HTMLURL == nil { + return "" + } + return *g.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (g *Gist) GetID() string { + if g == nil || g.ID == nil { + return "" + } + return *g.ID +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (g *Gist) GetNodeID() string { + if g == nil || g.NodeID == nil { + return "" + } + return *g.NodeID +} + +// GetOwner returns the Owner field. +func (g *Gist) GetOwner() *User { + if g == nil { + return nil + } + return g.Owner +} + +// GetPublic returns the Public field if it's non-nil, zero value otherwise. +func (g *Gist) GetPublic() bool { + if g == nil || g.Public == nil { + return false + } + return *g.Public +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (g *Gist) GetUpdatedAt() time.Time { + if g == nil || g.UpdatedAt == nil { + return time.Time{} + } + return *g.UpdatedAt +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (g *GistComment) GetBody() string { + if g == nil || g.Body == nil { + return "" + } + return *g.Body +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (g *GistComment) GetCreatedAt() time.Time { + if g == nil || g.CreatedAt == nil { + return time.Time{} + } + return *g.CreatedAt +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (g *GistComment) GetID() int64 { + if g == nil || g.ID == nil { + return 0 + } + return *g.ID +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (g *GistComment) GetURL() string { + if g == nil || g.URL == nil { + return "" + } + return *g.URL +} + +// GetUser returns the User field. +func (g *GistComment) GetUser() *User { + if g == nil { + return nil + } + return g.User +} + +// GetChangeStatus returns the ChangeStatus field. +func (g *GistCommit) GetChangeStatus() *CommitStats { + if g == nil { + return nil + } + return g.ChangeStatus +} + +// GetCommittedAt returns the CommittedAt field if it's non-nil, zero value otherwise. +func (g *GistCommit) GetCommittedAt() Timestamp { + if g == nil || g.CommittedAt == nil { + return Timestamp{} + } + return *g.CommittedAt +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (g *GistCommit) GetNodeID() string { + if g == nil || g.NodeID == nil { + return "" + } + return *g.NodeID +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (g *GistCommit) GetURL() string { + if g == nil || g.URL == nil { + return "" + } + return *g.URL +} + +// GetUser returns the User field. +func (g *GistCommit) GetUser() *User { + if g == nil { + return nil + } + return g.User +} + +// GetVersion returns the Version field if it's non-nil, zero value otherwise. +func (g *GistCommit) GetVersion() string { + if g == nil || g.Version == nil { + return "" + } + return *g.Version +} + +// GetContent returns the Content field if it's non-nil, zero value otherwise. +func (g *GistFile) GetContent() string { + if g == nil || g.Content == nil { + return "" + } + return *g.Content +} + +// GetFilename returns the Filename field if it's non-nil, zero value otherwise. +func (g *GistFile) GetFilename() string { + if g == nil || g.Filename == nil { + return "" + } + return *g.Filename +} + +// GetLanguage returns the Language field if it's non-nil, zero value otherwise. +func (g *GistFile) GetLanguage() string { + if g == nil || g.Language == nil { + return "" + } + return *g.Language +} + +// GetRawURL returns the RawURL field if it's non-nil, zero value otherwise. +func (g *GistFile) GetRawURL() string { + if g == nil || g.RawURL == nil { + return "" + } + return *g.RawURL +} + +// GetSize returns the Size field if it's non-nil, zero value otherwise. +func (g *GistFile) GetSize() int { + if g == nil || g.Size == nil { + return 0 + } + return *g.Size +} + +// GetType returns the Type field if it's non-nil, zero value otherwise. +func (g *GistFile) GetType() string { + if g == nil || g.Type == nil { + return "" + } + return *g.Type +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (g *GistFork) GetCreatedAt() Timestamp { + if g == nil || g.CreatedAt == nil { + return Timestamp{} + } + return *g.CreatedAt +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (g *GistFork) GetID() string { + if g == nil || g.ID == nil { + return "" + } + return *g.ID +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (g *GistFork) GetNodeID() string { + if g == nil || g.NodeID == nil { + return "" + } + return *g.NodeID +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (g *GistFork) GetUpdatedAt() Timestamp { + if g == nil || g.UpdatedAt == nil { + return Timestamp{} + } + return *g.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (g *GistFork) GetURL() string { + if g == nil || g.URL == nil { + return "" + } + return *g.URL +} + +// GetUser returns the User field. +func (g *GistFork) GetUser() *User { + if g == nil { + return nil + } + return g.User +} + +// GetPrivateGists returns the PrivateGists field if it's non-nil, zero value otherwise. +func (g *GistStats) GetPrivateGists() int { + if g == nil || g.PrivateGists == nil { + return 0 + } + return *g.PrivateGists +} + +// GetPublicGists returns the PublicGists field if it's non-nil, zero value otherwise. +func (g *GistStats) GetPublicGists() int { + if g == nil || g.PublicGists == nil { + return 0 + } + return *g.PublicGists +} + +// GetTotalGists returns the TotalGists field if it's non-nil, zero value otherwise. +func (g *GistStats) GetTotalGists() int { + if g == nil || g.TotalGists == nil { + return 0 + } + return *g.TotalGists +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (g *Gitignore) GetName() string { + if g == nil || g.Name == nil { + return "" + } + return *g.Name +} + +// GetSource returns the Source field if it's non-nil, zero value otherwise. +func (g *Gitignore) GetSource() string { + if g == nil || g.Source == nil { + return "" + } + return *g.Source +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (g *GitObject) GetSHA() string { + if g == nil || g.SHA == nil { + return "" + } + return *g.SHA +} + +// GetType returns the Type field if it's non-nil, zero value otherwise. +func (g *GitObject) GetType() string { + if g == nil || g.Type == nil { + return "" + } + return *g.Type +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (g *GitObject) GetURL() string { + if g == nil || g.URL == nil { + return "" + } + return *g.URL +} + +// GetInstallation returns the Installation field. +func (g *GollumEvent) GetInstallation() *Installation { + if g == nil { + return nil + } + return g.Installation +} + +// GetRepo returns the Repo field. +func (g *GollumEvent) GetRepo() *Repository { + if g == nil { + return nil + } + return g.Repo +} + +// GetSender returns the Sender field. +func (g *GollumEvent) GetSender() *User { + if g == nil { + return nil + } + return g.Sender +} + +// GetEmail returns the Email field if it's non-nil, zero value otherwise. +func (g *GPGEmail) GetEmail() string { + if g == nil || g.Email == nil { + return "" + } + return *g.Email +} + +// GetVerified returns the Verified field if it's non-nil, zero value otherwise. +func (g *GPGEmail) GetVerified() bool { + if g == nil || g.Verified == nil { + return false + } + return *g.Verified +} + +// GetCanCertify returns the CanCertify field if it's non-nil, zero value otherwise. +func (g *GPGKey) GetCanCertify() bool { + if g == nil || g.CanCertify == nil { + return false + } + return *g.CanCertify +} + +// GetCanEncryptComms returns the CanEncryptComms field if it's non-nil, zero value otherwise. +func (g *GPGKey) GetCanEncryptComms() bool { + if g == nil || g.CanEncryptComms == nil { + return false + } + return *g.CanEncryptComms +} + +// GetCanEncryptStorage returns the CanEncryptStorage field if it's non-nil, zero value otherwise. +func (g *GPGKey) GetCanEncryptStorage() bool { + if g == nil || g.CanEncryptStorage == nil { + return false + } + return *g.CanEncryptStorage +} + +// GetCanSign returns the CanSign field if it's non-nil, zero value otherwise. +func (g *GPGKey) GetCanSign() bool { + if g == nil || g.CanSign == nil { + return false + } + return *g.CanSign +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (g *GPGKey) GetCreatedAt() time.Time { + if g == nil || g.CreatedAt == nil { + return time.Time{} + } + return *g.CreatedAt +} + +// GetExpiresAt returns the ExpiresAt field if it's non-nil, zero value otherwise. +func (g *GPGKey) GetExpiresAt() time.Time { + if g == nil || g.ExpiresAt == nil { + return time.Time{} + } + return *g.ExpiresAt +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (g *GPGKey) GetID() int64 { + if g == nil || g.ID == nil { + return 0 + } + return *g.ID +} + +// GetKeyID returns the KeyID field if it's non-nil, zero value otherwise. +func (g *GPGKey) GetKeyID() string { + if g == nil || g.KeyID == nil { + return "" + } + return *g.KeyID +} + +// GetPrimaryKeyID returns the PrimaryKeyID field if it's non-nil, zero value otherwise. +func (g *GPGKey) GetPrimaryKeyID() int64 { + if g == nil || g.PrimaryKeyID == nil { + return 0 + } + return *g.PrimaryKeyID +} + +// GetPublicKey returns the PublicKey field if it's non-nil, zero value otherwise. +func (g *GPGKey) GetPublicKey() string { + if g == nil || g.PublicKey == nil { + return "" + } + return *g.PublicKey +} + +// GetApp returns the App field. +func (g *Grant) GetApp() *AuthorizationApp { + if g == nil { + return nil + } + return g.App +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (g *Grant) GetCreatedAt() Timestamp { + if g == nil || g.CreatedAt == nil { + return Timestamp{} + } + return *g.CreatedAt +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (g *Grant) GetID() int64 { + if g == nil || g.ID == nil { + return 0 + } + return *g.ID +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (g *Grant) GetUpdatedAt() Timestamp { + if g == nil || g.UpdatedAt == nil { + return Timestamp{} + } + return *g.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (g *Grant) GetURL() string { + if g == nil || g.URL == nil { + return "" + } + return *g.URL +} + +// GetActive returns the Active field if it's non-nil, zero value otherwise. +func (h *Hook) GetActive() bool { + if h == nil || h.Active == nil { + return false + } + return *h.Active +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (h *Hook) GetCreatedAt() time.Time { + if h == nil || h.CreatedAt == nil { + return time.Time{} + } + return *h.CreatedAt +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (h *Hook) GetID() int64 { + if h == nil || h.ID == nil { + return 0 + } + return *h.ID +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (h *Hook) GetName() string { + if h == nil || h.Name == nil { + return "" + } + return *h.Name +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (h *Hook) GetUpdatedAt() time.Time { + if h == nil || h.UpdatedAt == nil { + return time.Time{} + } + return *h.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (h *Hook) GetURL() string { + if h == nil || h.URL == nil { + return "" + } + return *h.URL +} + +// GetActiveHooks returns the ActiveHooks field if it's non-nil, zero value otherwise. +func (h *HookStats) GetActiveHooks() int { + if h == nil || h.ActiveHooks == nil { + return 0 + } + return *h.ActiveHooks +} + +// GetInactiveHooks returns the InactiveHooks field if it's non-nil, zero value otherwise. +func (h *HookStats) GetInactiveHooks() int { + if h == nil || h.InactiveHooks == nil { + return 0 + } + return *h.InactiveHooks +} + +// GetTotalHooks returns the TotalHooks field if it's non-nil, zero value otherwise. +func (h *HookStats) GetTotalHooks() int { + if h == nil || h.TotalHooks == nil { + return 0 + } + return *h.TotalHooks +} + +// GetAuthorsCount returns the AuthorsCount field if it's non-nil, zero value otherwise. +func (i *Import) GetAuthorsCount() int { + if i == nil || i.AuthorsCount == nil { + return 0 + } + return *i.AuthorsCount +} + +// GetAuthorsURL returns the AuthorsURL field if it's non-nil, zero value otherwise. +func (i *Import) GetAuthorsURL() string { + if i == nil || i.AuthorsURL == nil { + return "" + } + return *i.AuthorsURL +} + +// GetCommitCount returns the CommitCount field if it's non-nil, zero value otherwise. +func (i *Import) GetCommitCount() int { + if i == nil || i.CommitCount == nil { + return 0 + } + return *i.CommitCount +} + +// GetFailedStep returns the FailedStep field if it's non-nil, zero value otherwise. +func (i *Import) GetFailedStep() string { + if i == nil || i.FailedStep == nil { + return "" + } + return *i.FailedStep +} + +// GetHasLargeFiles returns the HasLargeFiles field if it's non-nil, zero value otherwise. +func (i *Import) GetHasLargeFiles() bool { + if i == nil || i.HasLargeFiles == nil { + return false + } + return *i.HasLargeFiles +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (i *Import) GetHTMLURL() string { + if i == nil || i.HTMLURL == nil { + return "" + } + return *i.HTMLURL +} + +// GetHumanName returns the HumanName field if it's non-nil, zero value otherwise. +func (i *Import) GetHumanName() string { + if i == nil || i.HumanName == nil { + return "" + } + return *i.HumanName +} + +// GetLargeFilesCount returns the LargeFilesCount field if it's non-nil, zero value otherwise. +func (i *Import) GetLargeFilesCount() int { + if i == nil || i.LargeFilesCount == nil { + return 0 + } + return *i.LargeFilesCount +} + +// GetLargeFilesSize returns the LargeFilesSize field if it's non-nil, zero value otherwise. +func (i *Import) GetLargeFilesSize() int { + if i == nil || i.LargeFilesSize == nil { + return 0 + } + return *i.LargeFilesSize +} + +// GetMessage returns the Message field if it's non-nil, zero value otherwise. +func (i *Import) GetMessage() string { + if i == nil || i.Message == nil { + return "" + } + return *i.Message +} + +// GetPercent returns the Percent field if it's non-nil, zero value otherwise. +func (i *Import) GetPercent() int { + if i == nil || i.Percent == nil { + return 0 + } + return *i.Percent +} + +// GetPushPercent returns the PushPercent field if it's non-nil, zero value otherwise. +func (i *Import) GetPushPercent() int { + if i == nil || i.PushPercent == nil { + return 0 + } + return *i.PushPercent +} + +// GetRepositoryURL returns the RepositoryURL field if it's non-nil, zero value otherwise. +func (i *Import) GetRepositoryURL() string { + if i == nil || i.RepositoryURL == nil { + return "" + } + return *i.RepositoryURL +} + +// GetStatus returns the Status field if it's non-nil, zero value otherwise. +func (i *Import) GetStatus() string { + if i == nil || i.Status == nil { + return "" + } + return *i.Status +} + +// GetStatusText returns the StatusText field if it's non-nil, zero value otherwise. +func (i *Import) GetStatusText() string { + if i == nil || i.StatusText == nil { + return "" + } + return *i.StatusText +} + +// GetTFVCProject returns the TFVCProject field if it's non-nil, zero value otherwise. +func (i *Import) GetTFVCProject() string { + if i == nil || i.TFVCProject == nil { + return "" + } + return *i.TFVCProject +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (i *Import) GetURL() string { + if i == nil || i.URL == nil { + return "" + } + return *i.URL +} + +// GetUseLFS returns the UseLFS field if it's non-nil, zero value otherwise. +func (i *Import) GetUseLFS() string { + if i == nil || i.UseLFS == nil { + return "" + } + return *i.UseLFS +} + +// GetVCS returns the VCS field if it's non-nil, zero value otherwise. +func (i *Import) GetVCS() string { + if i == nil || i.VCS == nil { + return "" + } + return *i.VCS +} + +// GetVCSPassword returns the VCSPassword field if it's non-nil, zero value otherwise. +func (i *Import) GetVCSPassword() string { + if i == nil || i.VCSPassword == nil { + return "" + } + return *i.VCSPassword +} + +// GetVCSURL returns the VCSURL field if it's non-nil, zero value otherwise. +func (i *Import) GetVCSURL() string { + if i == nil || i.VCSURL == nil { + return "" + } + return *i.VCSURL +} + +// GetVCSUsername returns the VCSUsername field if it's non-nil, zero value otherwise. +func (i *Import) GetVCSUsername() string { + if i == nil || i.VCSUsername == nil { + return "" + } + return *i.VCSUsername +} + +// GetAccessTokensURL returns the AccessTokensURL field if it's non-nil, zero value otherwise. +func (i *Installation) GetAccessTokensURL() string { + if i == nil || i.AccessTokensURL == nil { + return "" + } + return *i.AccessTokensURL +} + +// GetAccount returns the Account field. +func (i *Installation) GetAccount() *User { + if i == nil { + return nil + } + return i.Account +} + +// GetAppID returns the AppID field if it's non-nil, zero value otherwise. +func (i *Installation) GetAppID() int64 { + if i == nil || i.AppID == nil { + return 0 + } + return *i.AppID +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (i *Installation) GetCreatedAt() Timestamp { + if i == nil || i.CreatedAt == nil { + return Timestamp{} + } + return *i.CreatedAt +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (i *Installation) GetHTMLURL() string { + if i == nil || i.HTMLURL == nil { + return "" + } + return *i.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (i *Installation) GetID() int64 { + if i == nil || i.ID == nil { + return 0 + } + return *i.ID +} + +// GetPermissions returns the Permissions field. +func (i *Installation) GetPermissions() *InstallationPermissions { + if i == nil { + return nil + } + return i.Permissions +} + +// GetRepositoriesURL returns the RepositoriesURL field if it's non-nil, zero value otherwise. +func (i *Installation) GetRepositoriesURL() string { + if i == nil || i.RepositoriesURL == nil { + return "" + } + return *i.RepositoriesURL +} + +// GetRepositorySelection returns the RepositorySelection field if it's non-nil, zero value otherwise. +func (i *Installation) GetRepositorySelection() string { + if i == nil || i.RepositorySelection == nil { + return "" + } + return *i.RepositorySelection +} + +// GetSingleFileName returns the SingleFileName field if it's non-nil, zero value otherwise. +func (i *Installation) GetSingleFileName() string { + if i == nil || i.SingleFileName == nil { + return "" + } + return *i.SingleFileName +} + +// GetTargetID returns the TargetID field if it's non-nil, zero value otherwise. +func (i *Installation) GetTargetID() int64 { + if i == nil || i.TargetID == nil { + return 0 + } + return *i.TargetID +} + +// GetTargetType returns the TargetType field if it's non-nil, zero value otherwise. +func (i *Installation) GetTargetType() string { + if i == nil || i.TargetType == nil { + return "" + } + return *i.TargetType +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (i *Installation) GetUpdatedAt() Timestamp { + if i == nil || i.UpdatedAt == nil { + return Timestamp{} + } + return *i.UpdatedAt +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (i *InstallationEvent) GetAction() string { + if i == nil || i.Action == nil { + return "" + } + return *i.Action +} + +// GetInstallation returns the Installation field. +func (i *InstallationEvent) GetInstallation() *Installation { + if i == nil { + return nil + } + return i.Installation +} + +// GetSender returns the Sender field. +func (i *InstallationEvent) GetSender() *User { + if i == nil { + return nil + } + return i.Sender +} + +// GetContents returns the Contents field if it's non-nil, zero value otherwise. +func (i *InstallationPermissions) GetContents() string { + if i == nil || i.Contents == nil { + return "" + } + return *i.Contents +} + +// GetIssues returns the Issues field if it's non-nil, zero value otherwise. +func (i *InstallationPermissions) GetIssues() string { + if i == nil || i.Issues == nil { + return "" + } + return *i.Issues +} + +// GetMetadata returns the Metadata field if it's non-nil, zero value otherwise. +func (i *InstallationPermissions) GetMetadata() string { + if i == nil || i.Metadata == nil { + return "" + } + return *i.Metadata +} + +// GetSingleFile returns the SingleFile field if it's non-nil, zero value otherwise. +func (i *InstallationPermissions) GetSingleFile() string { + if i == nil || i.SingleFile == nil { + return "" + } + return *i.SingleFile +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (i *InstallationRepositoriesEvent) GetAction() string { + if i == nil || i.Action == nil { + return "" + } + return *i.Action +} + +// GetInstallation returns the Installation field. +func (i *InstallationRepositoriesEvent) GetInstallation() *Installation { + if i == nil { + return nil + } + return i.Installation +} + +// GetRepositorySelection returns the RepositorySelection field if it's non-nil, zero value otherwise. +func (i *InstallationRepositoriesEvent) GetRepositorySelection() string { + if i == nil || i.RepositorySelection == nil { + return "" + } + return *i.RepositorySelection +} + +// GetSender returns the Sender field. +func (i *InstallationRepositoriesEvent) GetSender() *User { + if i == nil { + return nil + } + return i.Sender +} + +// GetExpiresAt returns the ExpiresAt field if it's non-nil, zero value otherwise. +func (i *InstallationToken) GetExpiresAt() time.Time { + if i == nil || i.ExpiresAt == nil { + return time.Time{} + } + return *i.ExpiresAt +} + +// GetToken returns the Token field if it's non-nil, zero value otherwise. +func (i *InstallationToken) GetToken() string { + if i == nil || i.Token == nil { + return "" + } + return *i.Token +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (i *Invitation) GetCreatedAt() time.Time { + if i == nil || i.CreatedAt == nil { + return time.Time{} + } + return *i.CreatedAt +} + +// GetEmail returns the Email field if it's non-nil, zero value otherwise. +func (i *Invitation) GetEmail() string { + if i == nil || i.Email == nil { + return "" + } + return *i.Email +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (i *Invitation) GetID() int64 { + if i == nil || i.ID == nil { + return 0 + } + return *i.ID +} + +// GetInvitationTeamURL returns the InvitationTeamURL field if it's non-nil, zero value otherwise. +func (i *Invitation) GetInvitationTeamURL() string { + if i == nil || i.InvitationTeamURL == nil { + return "" + } + return *i.InvitationTeamURL +} + +// GetInviter returns the Inviter field. +func (i *Invitation) GetInviter() *User { + if i == nil { + return nil + } + return i.Inviter +} + +// GetLogin returns the Login field if it's non-nil, zero value otherwise. +func (i *Invitation) GetLogin() string { + if i == nil || i.Login == nil { + return "" + } + return *i.Login +} + +// GetRole returns the Role field if it's non-nil, zero value otherwise. +func (i *Invitation) GetRole() string { + if i == nil || i.Role == nil { + return "" + } + return *i.Role +} + +// GetTeamCount returns the TeamCount field if it's non-nil, zero value otherwise. +func (i *Invitation) GetTeamCount() int { + if i == nil || i.TeamCount == nil { + return 0 + } + return *i.TeamCount +} + +// GetActiveLockReason returns the ActiveLockReason field if it's non-nil, zero value otherwise. +func (i *Issue) GetActiveLockReason() string { + if i == nil || i.ActiveLockReason == nil { + return "" + } + return *i.ActiveLockReason +} + +// GetAssignee returns the Assignee field. +func (i *Issue) GetAssignee() *User { + if i == nil { + return nil + } + return i.Assignee +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (i *Issue) GetBody() string { + if i == nil || i.Body == nil { + return "" + } + return *i.Body +} + +// GetClosedAt returns the ClosedAt field if it's non-nil, zero value otherwise. +func (i *Issue) GetClosedAt() time.Time { + if i == nil || i.ClosedAt == nil { + return time.Time{} + } + return *i.ClosedAt +} + +// GetClosedBy returns the ClosedBy field. +func (i *Issue) GetClosedBy() *User { + if i == nil { + return nil + } + return i.ClosedBy +} + +// GetComments returns the Comments field if it's non-nil, zero value otherwise. +func (i *Issue) GetComments() int { + if i == nil || i.Comments == nil { + return 0 + } + return *i.Comments +} + +// GetCommentsURL returns the CommentsURL field if it's non-nil, zero value otherwise. +func (i *Issue) GetCommentsURL() string { + if i == nil || i.CommentsURL == nil { + return "" + } + return *i.CommentsURL +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (i *Issue) GetCreatedAt() time.Time { + if i == nil || i.CreatedAt == nil { + return time.Time{} + } + return *i.CreatedAt +} + +// GetEventsURL returns the EventsURL field if it's non-nil, zero value otherwise. +func (i *Issue) GetEventsURL() string { + if i == nil || i.EventsURL == nil { + return "" + } + return *i.EventsURL +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (i *Issue) GetHTMLURL() string { + if i == nil || i.HTMLURL == nil { + return "" + } + return *i.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (i *Issue) GetID() int64 { + if i == nil || i.ID == nil { + return 0 + } + return *i.ID +} + +// GetLabelsURL returns the LabelsURL field if it's non-nil, zero value otherwise. +func (i *Issue) GetLabelsURL() string { + if i == nil || i.LabelsURL == nil { + return "" + } + return *i.LabelsURL +} + +// GetLocked returns the Locked field if it's non-nil, zero value otherwise. +func (i *Issue) GetLocked() bool { + if i == nil || i.Locked == nil { + return false + } + return *i.Locked +} + +// GetMilestone returns the Milestone field. +func (i *Issue) GetMilestone() *Milestone { + if i == nil { + return nil + } + return i.Milestone +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (i *Issue) GetNodeID() string { + if i == nil || i.NodeID == nil { + return "" + } + return *i.NodeID +} + +// GetNumber returns the Number field if it's non-nil, zero value otherwise. +func (i *Issue) GetNumber() int { + if i == nil || i.Number == nil { + return 0 + } + return *i.Number +} + +// GetPullRequestLinks returns the PullRequestLinks field. +func (i *Issue) GetPullRequestLinks() *PullRequestLinks { + if i == nil { + return nil + } + return i.PullRequestLinks +} + +// GetReactions returns the Reactions field. +func (i *Issue) GetReactions() *Reactions { + if i == nil { + return nil + } + return i.Reactions +} + +// GetRepository returns the Repository field. +func (i *Issue) GetRepository() *Repository { + if i == nil { + return nil + } + return i.Repository +} + +// GetRepositoryURL returns the RepositoryURL field if it's non-nil, zero value otherwise. +func (i *Issue) GetRepositoryURL() string { + if i == nil || i.RepositoryURL == nil { + return "" + } + return *i.RepositoryURL +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (i *Issue) GetState() string { + if i == nil || i.State == nil { + return "" + } + return *i.State +} + +// GetTitle returns the Title field if it's non-nil, zero value otherwise. +func (i *Issue) GetTitle() string { + if i == nil || i.Title == nil { + return "" + } + return *i.Title +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (i *Issue) GetUpdatedAt() time.Time { + if i == nil || i.UpdatedAt == nil { + return time.Time{} + } + return *i.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (i *Issue) GetURL() string { + if i == nil || i.URL == nil { + return "" + } + return *i.URL +} + +// GetUser returns the User field. +func (i *Issue) GetUser() *User { + if i == nil { + return nil + } + return i.User +} + +// GetAuthorAssociation returns the AuthorAssociation field if it's non-nil, zero value otherwise. +func (i *IssueComment) GetAuthorAssociation() string { + if i == nil || i.AuthorAssociation == nil { + return "" + } + return *i.AuthorAssociation +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (i *IssueComment) GetBody() string { + if i == nil || i.Body == nil { + return "" + } + return *i.Body +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (i *IssueComment) GetCreatedAt() time.Time { + if i == nil || i.CreatedAt == nil { + return time.Time{} + } + return *i.CreatedAt +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (i *IssueComment) GetHTMLURL() string { + if i == nil || i.HTMLURL == nil { + return "" + } + return *i.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (i *IssueComment) GetID() int64 { + if i == nil || i.ID == nil { + return 0 + } + return *i.ID +} + +// GetIssueURL returns the IssueURL field if it's non-nil, zero value otherwise. +func (i *IssueComment) GetIssueURL() string { + if i == nil || i.IssueURL == nil { + return "" + } + return *i.IssueURL +} + +// GetReactions returns the Reactions field. +func (i *IssueComment) GetReactions() *Reactions { + if i == nil { + return nil + } + return i.Reactions +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (i *IssueComment) GetUpdatedAt() time.Time { + if i == nil || i.UpdatedAt == nil { + return time.Time{} + } + return *i.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (i *IssueComment) GetURL() string { + if i == nil || i.URL == nil { + return "" + } + return *i.URL +} + +// GetUser returns the User field. +func (i *IssueComment) GetUser() *User { + if i == nil { + return nil + } + return i.User +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (i *IssueCommentEvent) GetAction() string { + if i == nil || i.Action == nil { + return "" + } + return *i.Action +} + +// GetChanges returns the Changes field. +func (i *IssueCommentEvent) GetChanges() *EditChange { + if i == nil { + return nil + } + return i.Changes +} + +// GetComment returns the Comment field. +func (i *IssueCommentEvent) GetComment() *IssueComment { + if i == nil { + return nil + } + return i.Comment +} + +// GetInstallation returns the Installation field. +func (i *IssueCommentEvent) GetInstallation() *Installation { + if i == nil { + return nil + } + return i.Installation +} + +// GetIssue returns the Issue field. +func (i *IssueCommentEvent) GetIssue() *Issue { + if i == nil { + return nil + } + return i.Issue +} + +// GetRepo returns the Repo field. +func (i *IssueCommentEvent) GetRepo() *Repository { + if i == nil { + return nil + } + return i.Repo +} + +// GetSender returns the Sender field. +func (i *IssueCommentEvent) GetSender() *User { + if i == nil { + return nil + } + return i.Sender +} + +// GetActor returns the Actor field. +func (i *IssueEvent) GetActor() *User { + if i == nil { + return nil + } + return i.Actor +} + +// GetAssignee returns the Assignee field. +func (i *IssueEvent) GetAssignee() *User { + if i == nil { + return nil + } + return i.Assignee +} + +// GetAssigner returns the Assigner field. +func (i *IssueEvent) GetAssigner() *User { + if i == nil { + return nil + } + return i.Assigner +} + +// GetCommitID returns the CommitID field if it's non-nil, zero value otherwise. +func (i *IssueEvent) GetCommitID() string { + if i == nil || i.CommitID == nil { + return "" + } + return *i.CommitID +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (i *IssueEvent) GetCreatedAt() time.Time { + if i == nil || i.CreatedAt == nil { + return time.Time{} + } + return *i.CreatedAt +} + +// GetEvent returns the Event field if it's non-nil, zero value otherwise. +func (i *IssueEvent) GetEvent() string { + if i == nil || i.Event == nil { + return "" + } + return *i.Event +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (i *IssueEvent) GetID() int64 { + if i == nil || i.ID == nil { + return 0 + } + return *i.ID +} + +// GetIssue returns the Issue field. +func (i *IssueEvent) GetIssue() *Issue { + if i == nil { + return nil + } + return i.Issue +} + +// GetLabel returns the Label field. +func (i *IssueEvent) GetLabel() *Label { + if i == nil { + return nil + } + return i.Label +} + +// GetLockReason returns the LockReason field if it's non-nil, zero value otherwise. +func (i *IssueEvent) GetLockReason() string { + if i == nil || i.LockReason == nil { + return "" + } + return *i.LockReason +} + +// GetMilestone returns the Milestone field. +func (i *IssueEvent) GetMilestone() *Milestone { + if i == nil { + return nil + } + return i.Milestone +} + +// GetRename returns the Rename field. +func (i *IssueEvent) GetRename() *Rename { + if i == nil { + return nil + } + return i.Rename +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (i *IssueEvent) GetURL() string { + if i == nil || i.URL == nil { + return "" + } + return *i.URL +} + +// GetAssignee returns the Assignee field if it's non-nil, zero value otherwise. +func (i *IssueRequest) GetAssignee() string { + if i == nil || i.Assignee == nil { + return "" + } + return *i.Assignee +} + +// GetAssignees returns the Assignees field if it's non-nil, zero value otherwise. +func (i *IssueRequest) GetAssignees() []string { + if i == nil || i.Assignees == nil { + return nil + } + return *i.Assignees +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (i *IssueRequest) GetBody() string { + if i == nil || i.Body == nil { + return "" + } + return *i.Body +} + +// GetLabels returns the Labels field if it's non-nil, zero value otherwise. +func (i *IssueRequest) GetLabels() []string { + if i == nil || i.Labels == nil { + return nil + } + return *i.Labels +} + +// GetMilestone returns the Milestone field if it's non-nil, zero value otherwise. +func (i *IssueRequest) GetMilestone() int { + if i == nil || i.Milestone == nil { + return 0 + } + return *i.Milestone +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (i *IssueRequest) GetState() string { + if i == nil || i.State == nil { + return "" + } + return *i.State +} + +// GetTitle returns the Title field if it's non-nil, zero value otherwise. +func (i *IssueRequest) GetTitle() string { + if i == nil || i.Title == nil { + return "" + } + return *i.Title +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (i *IssuesEvent) GetAction() string { + if i == nil || i.Action == nil { + return "" + } + return *i.Action +} + +// GetAssignee returns the Assignee field. +func (i *IssuesEvent) GetAssignee() *User { + if i == nil { + return nil + } + return i.Assignee +} + +// GetChanges returns the Changes field. +func (i *IssuesEvent) GetChanges() *EditChange { + if i == nil { + return nil + } + return i.Changes +} + +// GetInstallation returns the Installation field. +func (i *IssuesEvent) GetInstallation() *Installation { + if i == nil { + return nil + } + return i.Installation +} + +// GetIssue returns the Issue field. +func (i *IssuesEvent) GetIssue() *Issue { + if i == nil { + return nil + } + return i.Issue +} + +// GetLabel returns the Label field. +func (i *IssuesEvent) GetLabel() *Label { + if i == nil { + return nil + } + return i.Label +} + +// GetRepo returns the Repo field. +func (i *IssuesEvent) GetRepo() *Repository { + if i == nil { + return nil + } + return i.Repo +} + +// GetSender returns the Sender field. +func (i *IssuesEvent) GetSender() *User { + if i == nil { + return nil + } + return i.Sender +} + +// GetIncompleteResults returns the IncompleteResults field if it's non-nil, zero value otherwise. +func (i *IssuesSearchResult) GetIncompleteResults() bool { + if i == nil || i.IncompleteResults == nil { + return false + } + return *i.IncompleteResults +} + +// GetTotal returns the Total field if it's non-nil, zero value otherwise. +func (i *IssuesSearchResult) GetTotal() int { + if i == nil || i.Total == nil { + return 0 + } + return *i.Total +} + +// GetClosedIssues returns the ClosedIssues field if it's non-nil, zero value otherwise. +func (i *IssueStats) GetClosedIssues() int { + if i == nil || i.ClosedIssues == nil { + return 0 + } + return *i.ClosedIssues +} + +// GetOpenIssues returns the OpenIssues field if it's non-nil, zero value otherwise. +func (i *IssueStats) GetOpenIssues() int { + if i == nil || i.OpenIssues == nil { + return 0 + } + return *i.OpenIssues +} + +// GetTotalIssues returns the TotalIssues field if it's non-nil, zero value otherwise. +func (i *IssueStats) GetTotalIssues() int { + if i == nil || i.TotalIssues == nil { + return 0 + } + return *i.TotalIssues +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (k *Key) GetID() int64 { + if k == nil || k.ID == nil { + return 0 + } + return *k.ID +} + +// GetKey returns the Key field if it's non-nil, zero value otherwise. +func (k *Key) GetKey() string { + if k == nil || k.Key == nil { + return "" + } + return *k.Key +} + +// GetReadOnly returns the ReadOnly field if it's non-nil, zero value otherwise. +func (k *Key) GetReadOnly() bool { + if k == nil || k.ReadOnly == nil { + return false + } + return *k.ReadOnly +} + +// GetTitle returns the Title field if it's non-nil, zero value otherwise. +func (k *Key) GetTitle() string { + if k == nil || k.Title == nil { + return "" + } + return *k.Title +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (k *Key) GetURL() string { + if k == nil || k.URL == nil { + return "" + } + return *k.URL +} + +// GetColor returns the Color field if it's non-nil, zero value otherwise. +func (l *Label) GetColor() string { + if l == nil || l.Color == nil { + return "" + } + return *l.Color +} + +// GetDefault returns the Default field if it's non-nil, zero value otherwise. +func (l *Label) GetDefault() bool { + if l == nil || l.Default == nil { + return false + } + return *l.Default +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (l *Label) GetDescription() string { + if l == nil || l.Description == nil { + return "" + } + return *l.Description +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (l *Label) GetID() int64 { + if l == nil || l.ID == nil { + return 0 + } + return *l.ID +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (l *Label) GetName() string { + if l == nil || l.Name == nil { + return "" + } + return *l.Name +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (l *Label) GetNodeID() string { + if l == nil || l.NodeID == nil { + return "" + } + return *l.NodeID +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (l *Label) GetURL() string { + if l == nil || l.URL == nil { + return "" + } + return *l.URL +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (l *LabelEvent) GetAction() string { + if l == nil || l.Action == nil { + return "" + } + return *l.Action +} + +// GetChanges returns the Changes field. +func (l *LabelEvent) GetChanges() *EditChange { + if l == nil { + return nil + } + return l.Changes +} + +// GetInstallation returns the Installation field. +func (l *LabelEvent) GetInstallation() *Installation { + if l == nil { + return nil + } + return l.Installation +} + +// GetLabel returns the Label field. +func (l *LabelEvent) GetLabel() *Label { + if l == nil { + return nil + } + return l.Label +} + +// GetOrg returns the Org field. +func (l *LabelEvent) GetOrg() *Organization { + if l == nil { + return nil + } + return l.Org +} + +// GetRepo returns the Repo field. +func (l *LabelEvent) GetRepo() *Repository { + if l == nil { + return nil + } + return l.Repo +} + +// GetColor returns the Color field if it's non-nil, zero value otherwise. +func (l *LabelResult) GetColor() string { + if l == nil || l.Color == nil { + return "" + } + return *l.Color +} + +// GetDefault returns the Default field if it's non-nil, zero value otherwise. +func (l *LabelResult) GetDefault() bool { + if l == nil || l.Default == nil { + return false + } + return *l.Default +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (l *LabelResult) GetDescription() string { + if l == nil || l.Description == nil { + return "" + } + return *l.Description +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (l *LabelResult) GetID() int64 { + if l == nil || l.ID == nil { + return 0 + } + return *l.ID +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (l *LabelResult) GetName() string { + if l == nil || l.Name == nil { + return "" + } + return *l.Name +} + +// GetScore returns the Score field. +func (l *LabelResult) GetScore() *float64 { + if l == nil { + return nil + } + return l.Score +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (l *LabelResult) GetURL() string { + if l == nil || l.URL == nil { + return "" + } + return *l.URL +} + +// GetIncompleteResults returns the IncompleteResults field if it's non-nil, zero value otherwise. +func (l *LabelsSearchResult) GetIncompleteResults() bool { + if l == nil || l.IncompleteResults == nil { + return false + } + return *l.IncompleteResults +} + +// GetTotal returns the Total field if it's non-nil, zero value otherwise. +func (l *LabelsSearchResult) GetTotal() int { + if l == nil || l.Total == nil { + return 0 + } + return *l.Total +} + +// GetOID returns the OID field if it's non-nil, zero value otherwise. +func (l *LargeFile) GetOID() string { + if l == nil || l.OID == nil { + return "" + } + return *l.OID +} + +// GetPath returns the Path field if it's non-nil, zero value otherwise. +func (l *LargeFile) GetPath() string { + if l == nil || l.Path == nil { + return "" + } + return *l.Path +} + +// GetRefName returns the RefName field if it's non-nil, zero value otherwise. +func (l *LargeFile) GetRefName() string { + if l == nil || l.RefName == nil { + return "" + } + return *l.RefName +} + +// GetSize returns the Size field if it's non-nil, zero value otherwise. +func (l *LargeFile) GetSize() int { + if l == nil || l.Size == nil { + return 0 + } + return *l.Size +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (l *License) GetBody() string { + if l == nil || l.Body == nil { + return "" + } + return *l.Body +} + +// GetConditions returns the Conditions field if it's non-nil, zero value otherwise. +func (l *License) GetConditions() []string { + if l == nil || l.Conditions == nil { + return nil + } + return *l.Conditions +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (l *License) GetDescription() string { + if l == nil || l.Description == nil { + return "" + } + return *l.Description +} + +// GetFeatured returns the Featured field if it's non-nil, zero value otherwise. +func (l *License) GetFeatured() bool { + if l == nil || l.Featured == nil { + return false + } + return *l.Featured +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (l *License) GetHTMLURL() string { + if l == nil || l.HTMLURL == nil { + return "" + } + return *l.HTMLURL +} + +// GetImplementation returns the Implementation field if it's non-nil, zero value otherwise. +func (l *License) GetImplementation() string { + if l == nil || l.Implementation == nil { + return "" + } + return *l.Implementation +} + +// GetKey returns the Key field if it's non-nil, zero value otherwise. +func (l *License) GetKey() string { + if l == nil || l.Key == nil { + return "" + } + return *l.Key +} + +// GetLimitations returns the Limitations field if it's non-nil, zero value otherwise. +func (l *License) GetLimitations() []string { + if l == nil || l.Limitations == nil { + return nil + } + return *l.Limitations +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (l *License) GetName() string { + if l == nil || l.Name == nil { + return "" + } + return *l.Name +} + +// GetPermissions returns the Permissions field if it's non-nil, zero value otherwise. +func (l *License) GetPermissions() []string { + if l == nil || l.Permissions == nil { + return nil + } + return *l.Permissions +} + +// GetSPDXID returns the SPDXID field if it's non-nil, zero value otherwise. +func (l *License) GetSPDXID() string { + if l == nil || l.SPDXID == nil { + return "" + } + return *l.SPDXID +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (l *License) GetURL() string { + if l == nil || l.URL == nil { + return "" + } + return *l.URL +} + +// GetCheckName returns the CheckName field if it's non-nil, zero value otherwise. +func (l *ListCheckRunsOptions) GetCheckName() string { + if l == nil || l.CheckName == nil { + return "" + } + return *l.CheckName +} + +// GetFilter returns the Filter field if it's non-nil, zero value otherwise. +func (l *ListCheckRunsOptions) GetFilter() string { + if l == nil || l.Filter == nil { + return "" + } + return *l.Filter +} + +// GetStatus returns the Status field if it's non-nil, zero value otherwise. +func (l *ListCheckRunsOptions) GetStatus() string { + if l == nil || l.Status == nil { + return "" + } + return *l.Status +} + +// GetTotal returns the Total field if it's non-nil, zero value otherwise. +func (l *ListCheckRunsResults) GetTotal() int { + if l == nil || l.Total == nil { + return 0 + } + return *l.Total +} + +// GetAppID returns the AppID field if it's non-nil, zero value otherwise. +func (l *ListCheckSuiteOptions) GetAppID() int { + if l == nil || l.AppID == nil { + return 0 + } + return *l.AppID +} + +// GetCheckName returns the CheckName field if it's non-nil, zero value otherwise. +func (l *ListCheckSuiteOptions) GetCheckName() string { + if l == nil || l.CheckName == nil { + return "" + } + return *l.CheckName +} + +// GetTotal returns the Total field if it's non-nil, zero value otherwise. +func (l *ListCheckSuiteResults) GetTotal() int { + if l == nil || l.Total == nil { + return 0 + } + return *l.Total +} + +// GetAccountsURL returns the AccountsURL field if it's non-nil, zero value otherwise. +func (m *MarketplacePlan) GetAccountsURL() string { + if m == nil || m.AccountsURL == nil { + return "" + } + return *m.AccountsURL +} + +// GetBullets returns the Bullets field if it's non-nil, zero value otherwise. +func (m *MarketplacePlan) GetBullets() []string { + if m == nil || m.Bullets == nil { + return nil + } + return *m.Bullets +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (m *MarketplacePlan) GetDescription() string { + if m == nil || m.Description == nil { + return "" + } + return *m.Description +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (m *MarketplacePlan) GetID() int64 { + if m == nil || m.ID == nil { + return 0 + } + return *m.ID +} + +// GetMonthlyPriceInCents returns the MonthlyPriceInCents field if it's non-nil, zero value otherwise. +func (m *MarketplacePlan) GetMonthlyPriceInCents() int { + if m == nil || m.MonthlyPriceInCents == nil { + return 0 + } + return *m.MonthlyPriceInCents +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (m *MarketplacePlan) GetName() string { + if m == nil || m.Name == nil { + return "" + } + return *m.Name +} + +// GetPriceModel returns the PriceModel field if it's non-nil, zero value otherwise. +func (m *MarketplacePlan) GetPriceModel() string { + if m == nil || m.PriceModel == nil { + return "" + } + return *m.PriceModel +} + +// GetUnitName returns the UnitName field if it's non-nil, zero value otherwise. +func (m *MarketplacePlan) GetUnitName() string { + if m == nil || m.UnitName == nil { + return "" + } + return *m.UnitName +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (m *MarketplacePlan) GetURL() string { + if m == nil || m.URL == nil { + return "" + } + return *m.URL +} + +// GetYearlyPriceInCents returns the YearlyPriceInCents field if it's non-nil, zero value otherwise. +func (m *MarketplacePlan) GetYearlyPriceInCents() int { + if m == nil || m.YearlyPriceInCents == nil { + return 0 + } + return *m.YearlyPriceInCents +} + +// GetEmail returns the Email field if it's non-nil, zero value otherwise. +func (m *MarketplacePlanAccount) GetEmail() string { + if m == nil || m.Email == nil { + return "" + } + return *m.Email +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (m *MarketplacePlanAccount) GetID() int64 { + if m == nil || m.ID == nil { + return 0 + } + return *m.ID +} + +// GetLogin returns the Login field if it's non-nil, zero value otherwise. +func (m *MarketplacePlanAccount) GetLogin() string { + if m == nil || m.Login == nil { + return "" + } + return *m.Login +} + +// GetMarketplacePurchase returns the MarketplacePurchase field. +func (m *MarketplacePlanAccount) GetMarketplacePurchase() *MarketplacePurchase { + if m == nil { + return nil + } + return m.MarketplacePurchase +} + +// GetOrganizationBillingEmail returns the OrganizationBillingEmail field if it's non-nil, zero value otherwise. +func (m *MarketplacePlanAccount) GetOrganizationBillingEmail() string { + if m == nil || m.OrganizationBillingEmail == nil { + return "" + } + return *m.OrganizationBillingEmail +} + +// GetType returns the Type field if it's non-nil, zero value otherwise. +func (m *MarketplacePlanAccount) GetType() string { + if m == nil || m.Type == nil { + return "" + } + return *m.Type +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (m *MarketplacePlanAccount) GetURL() string { + if m == nil || m.URL == nil { + return "" + } + return *m.URL +} + +// GetAccount returns the Account field. +func (m *MarketplacePurchase) GetAccount() *MarketplacePlanAccount { + if m == nil { + return nil + } + return m.Account +} + +// GetBillingCycle returns the BillingCycle field if it's non-nil, zero value otherwise. +func (m *MarketplacePurchase) GetBillingCycle() string { + if m == nil || m.BillingCycle == nil { + return "" + } + return *m.BillingCycle +} + +// GetNextBillingDate returns the NextBillingDate field if it's non-nil, zero value otherwise. +func (m *MarketplacePurchase) GetNextBillingDate() string { + if m == nil || m.NextBillingDate == nil { + return "" + } + return *m.NextBillingDate +} + +// GetPlan returns the Plan field. +func (m *MarketplacePurchase) GetPlan() *MarketplacePlan { + if m == nil { + return nil + } + return m.Plan +} + +// GetUnitCount returns the UnitCount field if it's non-nil, zero value otherwise. +func (m *MarketplacePurchase) GetUnitCount() int { + if m == nil || m.UnitCount == nil { + return 0 + } + return *m.UnitCount +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (m *MarketplacePurchaseEvent) GetAction() string { + if m == nil || m.Action == nil { + return "" + } + return *m.Action +} + +// GetEffectiveDate returns the EffectiveDate field if it's non-nil, zero value otherwise. +func (m *MarketplacePurchaseEvent) GetEffectiveDate() Timestamp { + if m == nil || m.EffectiveDate == nil { + return Timestamp{} + } + return *m.EffectiveDate +} + +// GetInstallation returns the Installation field. +func (m *MarketplacePurchaseEvent) GetInstallation() *Installation { + if m == nil { + return nil + } + return m.Installation +} + +// GetMarketplacePurchase returns the MarketplacePurchase field. +func (m *MarketplacePurchaseEvent) GetMarketplacePurchase() *MarketplacePurchase { + if m == nil { + return nil + } + return m.MarketplacePurchase +} + +// GetPreviousMarketplacePurchase returns the PreviousMarketplacePurchase field. +func (m *MarketplacePurchaseEvent) GetPreviousMarketplacePurchase() *MarketplacePurchase { + if m == nil { + return nil + } + return m.PreviousMarketplacePurchase +} + +// GetSender returns the Sender field. +func (m *MarketplacePurchaseEvent) GetSender() *User { + if m == nil { + return nil + } + return m.Sender +} + +// GetText returns the Text field if it's non-nil, zero value otherwise. +func (m *Match) GetText() string { + if m == nil || m.Text == nil { + return "" + } + return *m.Text +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (m *MemberEvent) GetAction() string { + if m == nil || m.Action == nil { + return "" + } + return *m.Action +} + +// GetInstallation returns the Installation field. +func (m *MemberEvent) GetInstallation() *Installation { + if m == nil { + return nil + } + return m.Installation +} + +// GetMember returns the Member field. +func (m *MemberEvent) GetMember() *User { + if m == nil { + return nil + } + return m.Member +} + +// GetRepo returns the Repo field. +func (m *MemberEvent) GetRepo() *Repository { + if m == nil { + return nil + } + return m.Repo +} + +// GetSender returns the Sender field. +func (m *MemberEvent) GetSender() *User { + if m == nil { + return nil + } + return m.Sender +} + +// GetOrganization returns the Organization field. +func (m *Membership) GetOrganization() *Organization { + if m == nil { + return nil + } + return m.Organization +} + +// GetOrganizationURL returns the OrganizationURL field if it's non-nil, zero value otherwise. +func (m *Membership) GetOrganizationURL() string { + if m == nil || m.OrganizationURL == nil { + return "" + } + return *m.OrganizationURL +} + +// GetRole returns the Role field if it's non-nil, zero value otherwise. +func (m *Membership) GetRole() string { + if m == nil || m.Role == nil { + return "" + } + return *m.Role +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (m *Membership) GetState() string { + if m == nil || m.State == nil { + return "" + } + return *m.State +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (m *Membership) GetURL() string { + if m == nil || m.URL == nil { + return "" + } + return *m.URL +} + +// GetUser returns the User field. +func (m *Membership) GetUser() *User { + if m == nil { + return nil + } + return m.User +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (m *MembershipEvent) GetAction() string { + if m == nil || m.Action == nil { + return "" + } + return *m.Action +} + +// GetInstallation returns the Installation field. +func (m *MembershipEvent) GetInstallation() *Installation { + if m == nil { + return nil + } + return m.Installation +} + +// GetMember returns the Member field. +func (m *MembershipEvent) GetMember() *User { + if m == nil { + return nil + } + return m.Member +} + +// GetOrg returns the Org field. +func (m *MembershipEvent) GetOrg() *Organization { + if m == nil { + return nil + } + return m.Org +} + +// GetScope returns the Scope field if it's non-nil, zero value otherwise. +func (m *MembershipEvent) GetScope() string { + if m == nil || m.Scope == nil { + return "" + } + return *m.Scope +} + +// GetSender returns the Sender field. +func (m *MembershipEvent) GetSender() *User { + if m == nil { + return nil + } + return m.Sender +} + +// GetTeam returns the Team field. +func (m *MembershipEvent) GetTeam() *Team { + if m == nil { + return nil + } + return m.Team +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (m *Metric) GetHTMLURL() string { + if m == nil || m.HTMLURL == nil { + return "" + } + return *m.HTMLURL +} + +// GetKey returns the Key field if it's non-nil, zero value otherwise. +func (m *Metric) GetKey() string { + if m == nil || m.Key == nil { + return "" + } + return *m.Key +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (m *Metric) GetName() string { + if m == nil || m.Name == nil { + return "" + } + return *m.Name +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (m *Metric) GetURL() string { + if m == nil || m.URL == nil { + return "" + } + return *m.URL +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (m *Migration) GetCreatedAt() string { + if m == nil || m.CreatedAt == nil { + return "" + } + return *m.CreatedAt +} + +// GetExcludeAttachments returns the ExcludeAttachments field if it's non-nil, zero value otherwise. +func (m *Migration) GetExcludeAttachments() bool { + if m == nil || m.ExcludeAttachments == nil { + return false + } + return *m.ExcludeAttachments +} + +// GetGUID returns the GUID field if it's non-nil, zero value otherwise. +func (m *Migration) GetGUID() string { + if m == nil || m.GUID == nil { + return "" + } + return *m.GUID +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (m *Migration) GetID() int64 { + if m == nil || m.ID == nil { + return 0 + } + return *m.ID +} + +// GetLockRepositories returns the LockRepositories field if it's non-nil, zero value otherwise. +func (m *Migration) GetLockRepositories() bool { + if m == nil || m.LockRepositories == nil { + return false + } + return *m.LockRepositories +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (m *Migration) GetState() string { + if m == nil || m.State == nil { + return "" + } + return *m.State +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (m *Migration) GetUpdatedAt() string { + if m == nil || m.UpdatedAt == nil { + return "" + } + return *m.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (m *Migration) GetURL() string { + if m == nil || m.URL == nil { + return "" + } + return *m.URL +} + +// GetClosedAt returns the ClosedAt field if it's non-nil, zero value otherwise. +func (m *Milestone) GetClosedAt() time.Time { + if m == nil || m.ClosedAt == nil { + return time.Time{} + } + return *m.ClosedAt +} + +// GetClosedIssues returns the ClosedIssues field if it's non-nil, zero value otherwise. +func (m *Milestone) GetClosedIssues() int { + if m == nil || m.ClosedIssues == nil { + return 0 + } + return *m.ClosedIssues +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (m *Milestone) GetCreatedAt() time.Time { + if m == nil || m.CreatedAt == nil { + return time.Time{} + } + return *m.CreatedAt +} + +// GetCreator returns the Creator field. +func (m *Milestone) GetCreator() *User { + if m == nil { + return nil + } + return m.Creator +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (m *Milestone) GetDescription() string { + if m == nil || m.Description == nil { + return "" + } + return *m.Description +} + +// GetDueOn returns the DueOn field if it's non-nil, zero value otherwise. +func (m *Milestone) GetDueOn() time.Time { + if m == nil || m.DueOn == nil { + return time.Time{} + } + return *m.DueOn +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (m *Milestone) GetHTMLURL() string { + if m == nil || m.HTMLURL == nil { + return "" + } + return *m.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (m *Milestone) GetID() int64 { + if m == nil || m.ID == nil { + return 0 + } + return *m.ID +} + +// GetLabelsURL returns the LabelsURL field if it's non-nil, zero value otherwise. +func (m *Milestone) GetLabelsURL() string { + if m == nil || m.LabelsURL == nil { + return "" + } + return *m.LabelsURL +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (m *Milestone) GetNodeID() string { + if m == nil || m.NodeID == nil { + return "" + } + return *m.NodeID +} + +// GetNumber returns the Number field if it's non-nil, zero value otherwise. +func (m *Milestone) GetNumber() int { + if m == nil || m.Number == nil { + return 0 + } + return *m.Number +} + +// GetOpenIssues returns the OpenIssues field if it's non-nil, zero value otherwise. +func (m *Milestone) GetOpenIssues() int { + if m == nil || m.OpenIssues == nil { + return 0 + } + return *m.OpenIssues +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (m *Milestone) GetState() string { + if m == nil || m.State == nil { + return "" + } + return *m.State +} + +// GetTitle returns the Title field if it's non-nil, zero value otherwise. +func (m *Milestone) GetTitle() string { + if m == nil || m.Title == nil { + return "" + } + return *m.Title +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (m *Milestone) GetUpdatedAt() time.Time { + if m == nil || m.UpdatedAt == nil { + return time.Time{} + } + return *m.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (m *Milestone) GetURL() string { + if m == nil || m.URL == nil { + return "" + } + return *m.URL +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (m *MilestoneEvent) GetAction() string { + if m == nil || m.Action == nil { + return "" + } + return *m.Action +} + +// GetChanges returns the Changes field. +func (m *MilestoneEvent) GetChanges() *EditChange { + if m == nil { + return nil + } + return m.Changes +} + +// GetInstallation returns the Installation field. +func (m *MilestoneEvent) GetInstallation() *Installation { + if m == nil { + return nil + } + return m.Installation +} + +// GetMilestone returns the Milestone field. +func (m *MilestoneEvent) GetMilestone() *Milestone { + if m == nil { + return nil + } + return m.Milestone +} + +// GetOrg returns the Org field. +func (m *MilestoneEvent) GetOrg() *Organization { + if m == nil { + return nil + } + return m.Org +} + +// GetRepo returns the Repo field. +func (m *MilestoneEvent) GetRepo() *Repository { + if m == nil { + return nil + } + return m.Repo +} + +// GetSender returns the Sender field. +func (m *MilestoneEvent) GetSender() *User { + if m == nil { + return nil + } + return m.Sender +} + +// GetClosedMilestones returns the ClosedMilestones field if it's non-nil, zero value otherwise. +func (m *MilestoneStats) GetClosedMilestones() int { + if m == nil || m.ClosedMilestones == nil { + return 0 + } + return *m.ClosedMilestones +} + +// GetOpenMilestones returns the OpenMilestones field if it's non-nil, zero value otherwise. +func (m *MilestoneStats) GetOpenMilestones() int { + if m == nil || m.OpenMilestones == nil { + return 0 + } + return *m.OpenMilestones +} + +// GetTotalMilestones returns the TotalMilestones field if it's non-nil, zero value otherwise. +func (m *MilestoneStats) GetTotalMilestones() int { + if m == nil || m.TotalMilestones == nil { + return 0 + } + return *m.TotalMilestones +} + +// GetBase returns the Base field if it's non-nil, zero value otherwise. +func (n *NewPullRequest) GetBase() string { + if n == nil || n.Base == nil { + return "" + } + return *n.Base +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (n *NewPullRequest) GetBody() string { + if n == nil || n.Body == nil { + return "" + } + return *n.Body +} + +// GetHead returns the Head field if it's non-nil, zero value otherwise. +func (n *NewPullRequest) GetHead() string { + if n == nil || n.Head == nil { + return "" + } + return *n.Head +} + +// GetIssue returns the Issue field if it's non-nil, zero value otherwise. +func (n *NewPullRequest) GetIssue() int { + if n == nil || n.Issue == nil { + return 0 + } + return *n.Issue +} + +// GetMaintainerCanModify returns the MaintainerCanModify field if it's non-nil, zero value otherwise. +func (n *NewPullRequest) GetMaintainerCanModify() bool { + if n == nil || n.MaintainerCanModify == nil { + return false + } + return *n.MaintainerCanModify +} + +// GetTitle returns the Title field if it's non-nil, zero value otherwise. +func (n *NewPullRequest) GetTitle() string { + if n == nil || n.Title == nil { + return "" + } + return *n.Title +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (n *NewTeam) GetDescription() string { + if n == nil || n.Description == nil { + return "" + } + return *n.Description +} + +// GetLDAPDN returns the LDAPDN field if it's non-nil, zero value otherwise. +func (n *NewTeam) GetLDAPDN() string { + if n == nil || n.LDAPDN == nil { + return "" + } + return *n.LDAPDN +} + +// GetParentTeamID returns the ParentTeamID field if it's non-nil, zero value otherwise. +func (n *NewTeam) GetParentTeamID() int64 { + if n == nil || n.ParentTeamID == nil { + return 0 + } + return *n.ParentTeamID +} + +// GetPermission returns the Permission field if it's non-nil, zero value otherwise. +func (n *NewTeam) GetPermission() string { + if n == nil || n.Permission == nil { + return "" + } + return *n.Permission +} + +// GetPrivacy returns the Privacy field if it's non-nil, zero value otherwise. +func (n *NewTeam) GetPrivacy() string { + if n == nil || n.Privacy == nil { + return "" + } + return *n.Privacy +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (n *Notification) GetID() string { + if n == nil || n.ID == nil { + return "" + } + return *n.ID +} + +// GetLastReadAt returns the LastReadAt field if it's non-nil, zero value otherwise. +func (n *Notification) GetLastReadAt() time.Time { + if n == nil || n.LastReadAt == nil { + return time.Time{} + } + return *n.LastReadAt +} + +// GetReason returns the Reason field if it's non-nil, zero value otherwise. +func (n *Notification) GetReason() string { + if n == nil || n.Reason == nil { + return "" + } + return *n.Reason +} + +// GetRepository returns the Repository field. +func (n *Notification) GetRepository() *Repository { + if n == nil { + return nil + } + return n.Repository +} + +// GetSubject returns the Subject field. +func (n *Notification) GetSubject() *NotificationSubject { + if n == nil { + return nil + } + return n.Subject +} + +// GetUnread returns the Unread field if it's non-nil, zero value otherwise. +func (n *Notification) GetUnread() bool { + if n == nil || n.Unread == nil { + return false + } + return *n.Unread +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (n *Notification) GetUpdatedAt() time.Time { + if n == nil || n.UpdatedAt == nil { + return time.Time{} + } + return *n.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (n *Notification) GetURL() string { + if n == nil || n.URL == nil { + return "" + } + return *n.URL +} + +// GetLatestCommentURL returns the LatestCommentURL field if it's non-nil, zero value otherwise. +func (n *NotificationSubject) GetLatestCommentURL() string { + if n == nil || n.LatestCommentURL == nil { + return "" + } + return *n.LatestCommentURL +} + +// GetTitle returns the Title field if it's non-nil, zero value otherwise. +func (n *NotificationSubject) GetTitle() string { + if n == nil || n.Title == nil { + return "" + } + return *n.Title +} + +// GetType returns the Type field if it's non-nil, zero value otherwise. +func (n *NotificationSubject) GetType() string { + if n == nil || n.Type == nil { + return "" + } + return *n.Type +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (n *NotificationSubject) GetURL() string { + if n == nil || n.URL == nil { + return "" + } + return *n.URL +} + +// GetAvatarURL returns the AvatarURL field if it's non-nil, zero value otherwise. +func (o *Organization) GetAvatarURL() string { + if o == nil || o.AvatarURL == nil { + return "" + } + return *o.AvatarURL +} + +// GetBillingEmail returns the BillingEmail field if it's non-nil, zero value otherwise. +func (o *Organization) GetBillingEmail() string { + if o == nil || o.BillingEmail == nil { + return "" + } + return *o.BillingEmail +} + +// GetBlog returns the Blog field if it's non-nil, zero value otherwise. +func (o *Organization) GetBlog() string { + if o == nil || o.Blog == nil { + return "" + } + return *o.Blog +} + +// GetCollaborators returns the Collaborators field if it's non-nil, zero value otherwise. +func (o *Organization) GetCollaborators() int { + if o == nil || o.Collaborators == nil { + return 0 + } + return *o.Collaborators +} + +// GetCompany returns the Company field if it's non-nil, zero value otherwise. +func (o *Organization) GetCompany() string { + if o == nil || o.Company == nil { + return "" + } + return *o.Company +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (o *Organization) GetCreatedAt() time.Time { + if o == nil || o.CreatedAt == nil { + return time.Time{} + } + return *o.CreatedAt +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (o *Organization) GetDescription() string { + if o == nil || o.Description == nil { + return "" + } + return *o.Description +} + +// GetDiskUsage returns the DiskUsage field if it's non-nil, zero value otherwise. +func (o *Organization) GetDiskUsage() int { + if o == nil || o.DiskUsage == nil { + return 0 + } + return *o.DiskUsage +} + +// GetEmail returns the Email field if it's non-nil, zero value otherwise. +func (o *Organization) GetEmail() string { + if o == nil || o.Email == nil { + return "" + } + return *o.Email +} + +// GetEventsURL returns the EventsURL field if it's non-nil, zero value otherwise. +func (o *Organization) GetEventsURL() string { + if o == nil || o.EventsURL == nil { + return "" + } + return *o.EventsURL +} + +// GetFollowers returns the Followers field if it's non-nil, zero value otherwise. +func (o *Organization) GetFollowers() int { + if o == nil || o.Followers == nil { + return 0 + } + return *o.Followers +} + +// GetFollowing returns the Following field if it's non-nil, zero value otherwise. +func (o *Organization) GetFollowing() int { + if o == nil || o.Following == nil { + return 0 + } + return *o.Following +} + +// GetHooksURL returns the HooksURL field if it's non-nil, zero value otherwise. +func (o *Organization) GetHooksURL() string { + if o == nil || o.HooksURL == nil { + return "" + } + return *o.HooksURL +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (o *Organization) GetHTMLURL() string { + if o == nil || o.HTMLURL == nil { + return "" + } + return *o.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (o *Organization) GetID() int64 { + if o == nil || o.ID == nil { + return 0 + } + return *o.ID +} + +// GetIssuesURL returns the IssuesURL field if it's non-nil, zero value otherwise. +func (o *Organization) GetIssuesURL() string { + if o == nil || o.IssuesURL == nil { + return "" + } + return *o.IssuesURL +} + +// GetLocation returns the Location field if it's non-nil, zero value otherwise. +func (o *Organization) GetLocation() string { + if o == nil || o.Location == nil { + return "" + } + return *o.Location +} + +// GetLogin returns the Login field if it's non-nil, zero value otherwise. +func (o *Organization) GetLogin() string { + if o == nil || o.Login == nil { + return "" + } + return *o.Login +} + +// GetMembersURL returns the MembersURL field if it's non-nil, zero value otherwise. +func (o *Organization) GetMembersURL() string { + if o == nil || o.MembersURL == nil { + return "" + } + return *o.MembersURL +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (o *Organization) GetName() string { + if o == nil || o.Name == nil { + return "" + } + return *o.Name +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (o *Organization) GetNodeID() string { + if o == nil || o.NodeID == nil { + return "" + } + return *o.NodeID +} + +// GetOwnedPrivateRepos returns the OwnedPrivateRepos field if it's non-nil, zero value otherwise. +func (o *Organization) GetOwnedPrivateRepos() int { + if o == nil || o.OwnedPrivateRepos == nil { + return 0 + } + return *o.OwnedPrivateRepos +} + +// GetPlan returns the Plan field. +func (o *Organization) GetPlan() *Plan { + if o == nil { + return nil + } + return o.Plan +} + +// GetPrivateGists returns the PrivateGists field if it's non-nil, zero value otherwise. +func (o *Organization) GetPrivateGists() int { + if o == nil || o.PrivateGists == nil { + return 0 + } + return *o.PrivateGists +} + +// GetPublicGists returns the PublicGists field if it's non-nil, zero value otherwise. +func (o *Organization) GetPublicGists() int { + if o == nil || o.PublicGists == nil { + return 0 + } + return *o.PublicGists +} + +// GetPublicMembersURL returns the PublicMembersURL field if it's non-nil, zero value otherwise. +func (o *Organization) GetPublicMembersURL() string { + if o == nil || o.PublicMembersURL == nil { + return "" + } + return *o.PublicMembersURL +} + +// GetPublicRepos returns the PublicRepos field if it's non-nil, zero value otherwise. +func (o *Organization) GetPublicRepos() int { + if o == nil || o.PublicRepos == nil { + return 0 + } + return *o.PublicRepos +} + +// GetReposURL returns the ReposURL field if it's non-nil, zero value otherwise. +func (o *Organization) GetReposURL() string { + if o == nil || o.ReposURL == nil { + return "" + } + return *o.ReposURL +} + +// GetTotalPrivateRepos returns the TotalPrivateRepos field if it's non-nil, zero value otherwise. +func (o *Organization) GetTotalPrivateRepos() int { + if o == nil || o.TotalPrivateRepos == nil { + return 0 + } + return *o.TotalPrivateRepos +} + +// GetTwoFactorRequirementEnabled returns the TwoFactorRequirementEnabled field if it's non-nil, zero value otherwise. +func (o *Organization) GetTwoFactorRequirementEnabled() bool { + if o == nil || o.TwoFactorRequirementEnabled == nil { + return false + } + return *o.TwoFactorRequirementEnabled +} + +// GetType returns the Type field if it's non-nil, zero value otherwise. +func (o *Organization) GetType() string { + if o == nil || o.Type == nil { + return "" + } + return *o.Type +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (o *Organization) GetUpdatedAt() time.Time { + if o == nil || o.UpdatedAt == nil { + return time.Time{} + } + return *o.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (o *Organization) GetURL() string { + if o == nil || o.URL == nil { + return "" + } + return *o.URL +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (o *OrganizationEvent) GetAction() string { + if o == nil || o.Action == nil { + return "" + } + return *o.Action +} + +// GetInstallation returns the Installation field. +func (o *OrganizationEvent) GetInstallation() *Installation { + if o == nil { + return nil + } + return o.Installation +} + +// GetInvitation returns the Invitation field. +func (o *OrganizationEvent) GetInvitation() *Invitation { + if o == nil { + return nil + } + return o.Invitation +} + +// GetMembership returns the Membership field. +func (o *OrganizationEvent) GetMembership() *Membership { + if o == nil { + return nil + } + return o.Membership +} + +// GetOrganization returns the Organization field. +func (o *OrganizationEvent) GetOrganization() *Organization { + if o == nil { + return nil + } + return o.Organization +} + +// GetSender returns the Sender field. +func (o *OrganizationEvent) GetSender() *User { + if o == nil { + return nil + } + return o.Sender +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (o *OrgBlockEvent) GetAction() string { + if o == nil || o.Action == nil { + return "" + } + return *o.Action +} + +// GetBlockedUser returns the BlockedUser field. +func (o *OrgBlockEvent) GetBlockedUser() *User { + if o == nil { + return nil + } + return o.BlockedUser +} + +// GetInstallation returns the Installation field. +func (o *OrgBlockEvent) GetInstallation() *Installation { + if o == nil { + return nil + } + return o.Installation +} + +// GetOrganization returns the Organization field. +func (o *OrgBlockEvent) GetOrganization() *Organization { + if o == nil { + return nil + } + return o.Organization +} + +// GetSender returns the Sender field. +func (o *OrgBlockEvent) GetSender() *User { + if o == nil { + return nil + } + return o.Sender +} + +// GetDisabledOrgs returns the DisabledOrgs field if it's non-nil, zero value otherwise. +func (o *OrgStats) GetDisabledOrgs() int { + if o == nil || o.DisabledOrgs == nil { + return 0 + } + return *o.DisabledOrgs +} + +// GetTotalOrgs returns the TotalOrgs field if it's non-nil, zero value otherwise. +func (o *OrgStats) GetTotalOrgs() int { + if o == nil || o.TotalOrgs == nil { + return 0 + } + return *o.TotalOrgs +} + +// GetTotalTeamMembers returns the TotalTeamMembers field if it's non-nil, zero value otherwise. +func (o *OrgStats) GetTotalTeamMembers() int { + if o == nil || o.TotalTeamMembers == nil { + return 0 + } + return *o.TotalTeamMembers +} + +// GetTotalTeams returns the TotalTeams field if it's non-nil, zero value otherwise. +func (o *OrgStats) GetTotalTeams() int { + if o == nil || o.TotalTeams == nil { + return 0 + } + return *o.TotalTeams +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (p *Page) GetAction() string { + if p == nil || p.Action == nil { + return "" + } + return *p.Action +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (p *Page) GetHTMLURL() string { + if p == nil || p.HTMLURL == nil { + return "" + } + return *p.HTMLURL +} + +// GetPageName returns the PageName field if it's non-nil, zero value otherwise. +func (p *Page) GetPageName() string { + if p == nil || p.PageName == nil { + return "" + } + return *p.PageName +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (p *Page) GetSHA() string { + if p == nil || p.SHA == nil { + return "" + } + return *p.SHA +} + +// GetSummary returns the Summary field if it's non-nil, zero value otherwise. +func (p *Page) GetSummary() string { + if p == nil || p.Summary == nil { + return "" + } + return *p.Summary +} + +// GetTitle returns the Title field if it's non-nil, zero value otherwise. +func (p *Page) GetTitle() string { + if p == nil || p.Title == nil { + return "" + } + return *p.Title +} + +// GetBuild returns the Build field. +func (p *PageBuildEvent) GetBuild() *PagesBuild { + if p == nil { + return nil + } + return p.Build +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *PageBuildEvent) GetID() int64 { + if p == nil || p.ID == nil { + return 0 + } + return *p.ID +} + +// GetInstallation returns the Installation field. +func (p *PageBuildEvent) GetInstallation() *Installation { + if p == nil { + return nil + } + return p.Installation +} + +// GetRepo returns the Repo field. +func (p *PageBuildEvent) GetRepo() *Repository { + if p == nil { + return nil + } + return p.Repo +} + +// GetSender returns the Sender field. +func (p *PageBuildEvent) GetSender() *User { + if p == nil { + return nil + } + return p.Sender +} + +// GetCNAME returns the CNAME field if it's non-nil, zero value otherwise. +func (p *Pages) GetCNAME() string { + if p == nil || p.CNAME == nil { + return "" + } + return *p.CNAME +} + +// GetCustom404 returns the Custom404 field if it's non-nil, zero value otherwise. +func (p *Pages) GetCustom404() bool { + if p == nil || p.Custom404 == nil { + return false + } + return *p.Custom404 +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (p *Pages) GetHTMLURL() string { + if p == nil || p.HTMLURL == nil { + return "" + } + return *p.HTMLURL +} + +// GetStatus returns the Status field if it's non-nil, zero value otherwise. +func (p *Pages) GetStatus() string { + if p == nil || p.Status == nil { + return "" + } + return *p.Status +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (p *Pages) GetURL() string { + if p == nil || p.URL == nil { + return "" + } + return *p.URL +} + +// GetCommit returns the Commit field if it's non-nil, zero value otherwise. +func (p *PagesBuild) GetCommit() string { + if p == nil || p.Commit == nil { + return "" + } + return *p.Commit +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (p *PagesBuild) GetCreatedAt() Timestamp { + if p == nil || p.CreatedAt == nil { + return Timestamp{} + } + return *p.CreatedAt +} + +// GetDuration returns the Duration field if it's non-nil, zero value otherwise. +func (p *PagesBuild) GetDuration() int { + if p == nil || p.Duration == nil { + return 0 + } + return *p.Duration +} + +// GetError returns the Error field. +func (p *PagesBuild) GetError() *PagesError { + if p == nil { + return nil + } + return p.Error +} + +// GetPusher returns the Pusher field. +func (p *PagesBuild) GetPusher() *User { + if p == nil { + return nil + } + return p.Pusher +} + +// GetStatus returns the Status field if it's non-nil, zero value otherwise. +func (p *PagesBuild) GetStatus() string { + if p == nil || p.Status == nil { + return "" + } + return *p.Status +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (p *PagesBuild) GetUpdatedAt() Timestamp { + if p == nil || p.UpdatedAt == nil { + return Timestamp{} + } + return *p.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (p *PagesBuild) GetURL() string { + if p == nil || p.URL == nil { + return "" + } + return *p.URL +} + +// GetMessage returns the Message field if it's non-nil, zero value otherwise. +func (p *PagesError) GetMessage() string { + if p == nil || p.Message == nil { + return "" + } + return *p.Message +} + +// GetTotalPages returns the TotalPages field if it's non-nil, zero value otherwise. +func (p *PageStats) GetTotalPages() int { + if p == nil || p.TotalPages == nil { + return 0 + } + return *p.TotalPages +} + +// GetHook returns the Hook field. +func (p *PingEvent) GetHook() *Hook { + if p == nil { + return nil + } + return p.Hook +} + +// GetHookID returns the HookID field if it's non-nil, zero value otherwise. +func (p *PingEvent) GetHookID() int64 { + if p == nil || p.HookID == nil { + return 0 + } + return *p.HookID +} + +// GetInstallation returns the Installation field. +func (p *PingEvent) GetInstallation() *Installation { + if p == nil { + return nil + } + return p.Installation +} + +// GetZen returns the Zen field if it's non-nil, zero value otherwise. +func (p *PingEvent) GetZen() string { + if p == nil || p.Zen == nil { + return "" + } + return *p.Zen +} + +// GetCollaborators returns the Collaborators field if it's non-nil, zero value otherwise. +func (p *Plan) GetCollaborators() int { + if p == nil || p.Collaborators == nil { + return 0 + } + return *p.Collaborators +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (p *Plan) GetName() string { + if p == nil || p.Name == nil { + return "" + } + return *p.Name +} + +// GetPrivateRepos returns the PrivateRepos field if it's non-nil, zero value otherwise. +func (p *Plan) GetPrivateRepos() int { + if p == nil || p.PrivateRepos == nil { + return 0 + } + return *p.PrivateRepos +} + +// GetSpace returns the Space field if it's non-nil, zero value otherwise. +func (p *Plan) GetSpace() int { + if p == nil || p.Space == nil { + return 0 + } + return *p.Space +} + +// GetConfigURL returns the ConfigURL field if it's non-nil, zero value otherwise. +func (p *PreReceiveHook) GetConfigURL() string { + if p == nil || p.ConfigURL == nil { + return "" + } + return *p.ConfigURL +} + +// GetEnforcement returns the Enforcement field if it's non-nil, zero value otherwise. +func (p *PreReceiveHook) GetEnforcement() string { + if p == nil || p.Enforcement == nil { + return "" + } + return *p.Enforcement +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *PreReceiveHook) GetID() int64 { + if p == nil || p.ID == nil { + return 0 + } + return *p.ID +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (p *PreReceiveHook) GetName() string { + if p == nil || p.Name == nil { + return "" + } + return *p.Name +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (p *Project) GetBody() string { + if p == nil || p.Body == nil { + return "" + } + return *p.Body +} + +// GetColumnsURL returns the ColumnsURL field if it's non-nil, zero value otherwise. +func (p *Project) GetColumnsURL() string { + if p == nil || p.ColumnsURL == nil { + return "" + } + return *p.ColumnsURL +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (p *Project) GetCreatedAt() Timestamp { + if p == nil || p.CreatedAt == nil { + return Timestamp{} + } + return *p.CreatedAt +} + +// GetCreator returns the Creator field. +func (p *Project) GetCreator() *User { + if p == nil { + return nil + } + return p.Creator +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (p *Project) GetHTMLURL() string { + if p == nil || p.HTMLURL == nil { + return "" + } + return *p.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *Project) GetID() int64 { + if p == nil || p.ID == nil { + return 0 + } + return *p.ID +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (p *Project) GetName() string { + if p == nil || p.Name == nil { + return "" + } + return *p.Name +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (p *Project) GetNodeID() string { + if p == nil || p.NodeID == nil { + return "" + } + return *p.NodeID +} + +// GetNumber returns the Number field if it's non-nil, zero value otherwise. +func (p *Project) GetNumber() int { + if p == nil || p.Number == nil { + return 0 + } + return *p.Number +} + +// GetOwnerURL returns the OwnerURL field if it's non-nil, zero value otherwise. +func (p *Project) GetOwnerURL() string { + if p == nil || p.OwnerURL == nil { + return "" + } + return *p.OwnerURL +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (p *Project) GetState() string { + if p == nil || p.State == nil { + return "" + } + return *p.State +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (p *Project) GetUpdatedAt() Timestamp { + if p == nil || p.UpdatedAt == nil { + return Timestamp{} + } + return *p.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (p *Project) GetURL() string { + if p == nil || p.URL == nil { + return "" + } + return *p.URL +} + +// GetArchived returns the Archived field if it's non-nil, zero value otherwise. +func (p *ProjectCard) GetArchived() bool { + if p == nil || p.Archived == nil { + return false + } + return *p.Archived +} + +// GetColumnID returns the ColumnID field if it's non-nil, zero value otherwise. +func (p *ProjectCard) GetColumnID() int64 { + if p == nil || p.ColumnID == nil { + return 0 + } + return *p.ColumnID +} + +// GetColumnURL returns the ColumnURL field if it's non-nil, zero value otherwise. +func (p *ProjectCard) GetColumnURL() string { + if p == nil || p.ColumnURL == nil { + return "" + } + return *p.ColumnURL +} + +// GetContentURL returns the ContentURL field if it's non-nil, zero value otherwise. +func (p *ProjectCard) GetContentURL() string { + if p == nil || p.ContentURL == nil { + return "" + } + return *p.ContentURL +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (p *ProjectCard) GetCreatedAt() Timestamp { + if p == nil || p.CreatedAt == nil { + return Timestamp{} + } + return *p.CreatedAt +} + +// GetCreator returns the Creator field. +func (p *ProjectCard) GetCreator() *User { + if p == nil { + return nil + } + return p.Creator +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *ProjectCard) GetID() int64 { + if p == nil || p.ID == nil { + return 0 + } + return *p.ID +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (p *ProjectCard) GetNodeID() string { + if p == nil || p.NodeID == nil { + return "" + } + return *p.NodeID +} + +// GetNote returns the Note field if it's non-nil, zero value otherwise. +func (p *ProjectCard) GetNote() string { + if p == nil || p.Note == nil { + return "" + } + return *p.Note +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (p *ProjectCard) GetUpdatedAt() Timestamp { + if p == nil || p.UpdatedAt == nil { + return Timestamp{} + } + return *p.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (p *ProjectCard) GetURL() string { + if p == nil || p.URL == nil { + return "" + } + return *p.URL +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (p *ProjectCardEvent) GetAction() string { + if p == nil || p.Action == nil { + return "" + } + return *p.Action +} + +// GetAfterID returns the AfterID field if it's non-nil, zero value otherwise. +func (p *ProjectCardEvent) GetAfterID() int64 { + if p == nil || p.AfterID == nil { + return 0 + } + return *p.AfterID +} + +// GetChanges returns the Changes field. +func (p *ProjectCardEvent) GetChanges() *ProjectCardChange { + if p == nil { + return nil + } + return p.Changes +} + +// GetInstallation returns the Installation field. +func (p *ProjectCardEvent) GetInstallation() *Installation { + if p == nil { + return nil + } + return p.Installation +} + +// GetOrg returns the Org field. +func (p *ProjectCardEvent) GetOrg() *Organization { + if p == nil { + return nil + } + return p.Org +} + +// GetProjectCard returns the ProjectCard field. +func (p *ProjectCardEvent) GetProjectCard() *ProjectCard { + if p == nil { + return nil + } + return p.ProjectCard +} + +// GetRepo returns the Repo field. +func (p *ProjectCardEvent) GetRepo() *Repository { + if p == nil { + return nil + } + return p.Repo +} + +// GetSender returns the Sender field. +func (p *ProjectCardEvent) GetSender() *User { + if p == nil { + return nil + } + return p.Sender +} + +// GetArchivedState returns the ArchivedState field if it's non-nil, zero value otherwise. +func (p *ProjectCardListOptions) GetArchivedState() string { + if p == nil || p.ArchivedState == nil { + return "" + } + return *p.ArchivedState +} + +// GetArchived returns the Archived field if it's non-nil, zero value otherwise. +func (p *ProjectCardOptions) GetArchived() bool { + if p == nil || p.Archived == nil { + return false + } + return *p.Archived +} + +// GetCardsURL returns the CardsURL field if it's non-nil, zero value otherwise. +func (p *ProjectColumn) GetCardsURL() string { + if p == nil || p.CardsURL == nil { + return "" + } + return *p.CardsURL +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (p *ProjectColumn) GetCreatedAt() Timestamp { + if p == nil || p.CreatedAt == nil { + return Timestamp{} + } + return *p.CreatedAt +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *ProjectColumn) GetID() int64 { + if p == nil || p.ID == nil { + return 0 + } + return *p.ID +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (p *ProjectColumn) GetName() string { + if p == nil || p.Name == nil { + return "" + } + return *p.Name +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (p *ProjectColumn) GetNodeID() string { + if p == nil || p.NodeID == nil { + return "" + } + return *p.NodeID +} + +// GetProjectURL returns the ProjectURL field if it's non-nil, zero value otherwise. +func (p *ProjectColumn) GetProjectURL() string { + if p == nil || p.ProjectURL == nil { + return "" + } + return *p.ProjectURL +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (p *ProjectColumn) GetUpdatedAt() Timestamp { + if p == nil || p.UpdatedAt == nil { + return Timestamp{} + } + return *p.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (p *ProjectColumn) GetURL() string { + if p == nil || p.URL == nil { + return "" + } + return *p.URL +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (p *ProjectColumnEvent) GetAction() string { + if p == nil || p.Action == nil { + return "" + } + return *p.Action +} + +// GetAfterID returns the AfterID field if it's non-nil, zero value otherwise. +func (p *ProjectColumnEvent) GetAfterID() int64 { + if p == nil || p.AfterID == nil { + return 0 + } + return *p.AfterID +} + +// GetChanges returns the Changes field. +func (p *ProjectColumnEvent) GetChanges() *ProjectColumnChange { + if p == nil { + return nil + } + return p.Changes +} + +// GetInstallation returns the Installation field. +func (p *ProjectColumnEvent) GetInstallation() *Installation { + if p == nil { + return nil + } + return p.Installation +} + +// GetOrg returns the Org field. +func (p *ProjectColumnEvent) GetOrg() *Organization { + if p == nil { + return nil + } + return p.Org +} + +// GetProjectColumn returns the ProjectColumn field. +func (p *ProjectColumnEvent) GetProjectColumn() *ProjectColumn { + if p == nil { + return nil + } + return p.ProjectColumn +} + +// GetRepo returns the Repo field. +func (p *ProjectColumnEvent) GetRepo() *Repository { + if p == nil { + return nil + } + return p.Repo +} + +// GetSender returns the Sender field. +func (p *ProjectColumnEvent) GetSender() *User { + if p == nil { + return nil + } + return p.Sender +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (p *ProjectEvent) GetAction() string { + if p == nil || p.Action == nil { + return "" + } + return *p.Action +} + +// GetChanges returns the Changes field. +func (p *ProjectEvent) GetChanges() *ProjectChange { + if p == nil { + return nil + } + return p.Changes +} + +// GetInstallation returns the Installation field. +func (p *ProjectEvent) GetInstallation() *Installation { + if p == nil { + return nil + } + return p.Installation +} + +// GetOrg returns the Org field. +func (p *ProjectEvent) GetOrg() *Organization { + if p == nil { + return nil + } + return p.Org +} + +// GetProject returns the Project field. +func (p *ProjectEvent) GetProject() *Project { + if p == nil { + return nil + } + return p.Project +} + +// GetRepo returns the Repo field. +func (p *ProjectEvent) GetRepo() *Repository { + if p == nil { + return nil + } + return p.Repo +} + +// GetSender returns the Sender field. +func (p *ProjectEvent) GetSender() *User { + if p == nil { + return nil + } + return p.Sender +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (p *ProjectOptions) GetBody() string { + if p == nil || p.Body == nil { + return "" + } + return *p.Body +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (p *ProjectOptions) GetName() string { + if p == nil || p.Name == nil { + return "" + } + return *p.Name +} + +// GetOrganizationPermission returns the OrganizationPermission field if it's non-nil, zero value otherwise. +func (p *ProjectOptions) GetOrganizationPermission() string { + if p == nil || p.OrganizationPermission == nil { + return "" + } + return *p.OrganizationPermission +} + +// GetPublic returns the Public field if it's non-nil, zero value otherwise. +func (p *ProjectOptions) GetPublic() bool { + if p == nil || p.Public == nil { + return false + } + return *p.Public +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (p *ProjectOptions) GetState() string { + if p == nil || p.State == nil { + return "" + } + return *p.State +} + +// GetEnforceAdmins returns the EnforceAdmins field. +func (p *Protection) GetEnforceAdmins() *AdminEnforcement { + if p == nil { + return nil + } + return p.EnforceAdmins +} + +// GetRequiredPullRequestReviews returns the RequiredPullRequestReviews field. +func (p *Protection) GetRequiredPullRequestReviews() *PullRequestReviewsEnforcement { + if p == nil { + return nil + } + return p.RequiredPullRequestReviews +} + +// GetRequiredStatusChecks returns the RequiredStatusChecks field. +func (p *Protection) GetRequiredStatusChecks() *RequiredStatusChecks { + if p == nil { + return nil + } + return p.RequiredStatusChecks +} + +// GetRestrictions returns the Restrictions field. +func (p *Protection) GetRestrictions() *BranchRestrictions { + if p == nil { + return nil + } + return p.Restrictions +} + +// GetRequiredPullRequestReviews returns the RequiredPullRequestReviews field. +func (p *ProtectionRequest) GetRequiredPullRequestReviews() *PullRequestReviewsEnforcementRequest { + if p == nil { + return nil + } + return p.RequiredPullRequestReviews +} + +// GetRequiredStatusChecks returns the RequiredStatusChecks field. +func (p *ProtectionRequest) GetRequiredStatusChecks() *RequiredStatusChecks { + if p == nil { + return nil + } + return p.RequiredStatusChecks +} + +// GetRestrictions returns the Restrictions field. +func (p *ProtectionRequest) GetRestrictions() *BranchRestrictionsRequest { + if p == nil { + return nil + } + return p.Restrictions +} + +// GetInstallation returns the Installation field. +func (p *PublicEvent) GetInstallation() *Installation { + if p == nil { + return nil + } + return p.Installation +} + +// GetRepo returns the Repo field. +func (p *PublicEvent) GetRepo() *Repository { + if p == nil { + return nil + } + return p.Repo +} + +// GetSender returns the Sender field. +func (p *PublicEvent) GetSender() *User { + if p == nil { + return nil + } + return p.Sender +} + +// GetActiveLockReason returns the ActiveLockReason field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetActiveLockReason() string { + if p == nil || p.ActiveLockReason == nil { + return "" + } + return *p.ActiveLockReason +} + +// GetAdditions returns the Additions field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetAdditions() int { + if p == nil || p.Additions == nil { + return 0 + } + return *p.Additions +} + +// GetAssignee returns the Assignee field. +func (p *PullRequest) GetAssignee() *User { + if p == nil { + return nil + } + return p.Assignee +} + +// GetAuthorAssociation returns the AuthorAssociation field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetAuthorAssociation() string { + if p == nil || p.AuthorAssociation == nil { + return "" + } + return *p.AuthorAssociation +} + +// GetBase returns the Base field. +func (p *PullRequest) GetBase() *PullRequestBranch { + if p == nil { + return nil + } + return p.Base +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetBody() string { + if p == nil || p.Body == nil { + return "" + } + return *p.Body +} + +// GetChangedFiles returns the ChangedFiles field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetChangedFiles() int { + if p == nil || p.ChangedFiles == nil { + return 0 + } + return *p.ChangedFiles +} + +// GetClosedAt returns the ClosedAt field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetClosedAt() time.Time { + if p == nil || p.ClosedAt == nil { + return time.Time{} + } + return *p.ClosedAt +} + +// GetComments returns the Comments field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetComments() int { + if p == nil || p.Comments == nil { + return 0 + } + return *p.Comments +} + +// GetCommentsURL returns the CommentsURL field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetCommentsURL() string { + if p == nil || p.CommentsURL == nil { + return "" + } + return *p.CommentsURL +} + +// GetCommits returns the Commits field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetCommits() int { + if p == nil || p.Commits == nil { + return 0 + } + return *p.Commits +} + +// GetCommitsURL returns the CommitsURL field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetCommitsURL() string { + if p == nil || p.CommitsURL == nil { + return "" + } + return *p.CommitsURL +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetCreatedAt() time.Time { + if p == nil || p.CreatedAt == nil { + return time.Time{} + } + return *p.CreatedAt +} + +// GetDeletions returns the Deletions field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetDeletions() int { + if p == nil || p.Deletions == nil { + return 0 + } + return *p.Deletions +} + +// GetDiffURL returns the DiffURL field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetDiffURL() string { + if p == nil || p.DiffURL == nil { + return "" + } + return *p.DiffURL +} + +// GetHead returns the Head field. +func (p *PullRequest) GetHead() *PullRequestBranch { + if p == nil { + return nil + } + return p.Head +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetHTMLURL() string { + if p == nil || p.HTMLURL == nil { + return "" + } + return *p.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetID() int64 { + if p == nil || p.ID == nil { + return 0 + } + return *p.ID +} + +// GetIssueURL returns the IssueURL field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetIssueURL() string { + if p == nil || p.IssueURL == nil { + return "" + } + return *p.IssueURL +} + +// GetMaintainerCanModify returns the MaintainerCanModify field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetMaintainerCanModify() bool { + if p == nil || p.MaintainerCanModify == nil { + return false + } + return *p.MaintainerCanModify +} + +// GetMergeable returns the Mergeable field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetMergeable() bool { + if p == nil || p.Mergeable == nil { + return false + } + return *p.Mergeable +} + +// GetMergeableState returns the MergeableState field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetMergeableState() string { + if p == nil || p.MergeableState == nil { + return "" + } + return *p.MergeableState +} + +// GetMergeCommitSHA returns the MergeCommitSHA field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetMergeCommitSHA() string { + if p == nil || p.MergeCommitSHA == nil { + return "" + } + return *p.MergeCommitSHA +} + +// GetMerged returns the Merged field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetMerged() bool { + if p == nil || p.Merged == nil { + return false + } + return *p.Merged +} + +// GetMergedAt returns the MergedAt field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetMergedAt() time.Time { + if p == nil || p.MergedAt == nil { + return time.Time{} + } + return *p.MergedAt +} + +// GetMergedBy returns the MergedBy field. +func (p *PullRequest) GetMergedBy() *User { + if p == nil { + return nil + } + return p.MergedBy +} + +// GetMilestone returns the Milestone field. +func (p *PullRequest) GetMilestone() *Milestone { + if p == nil { + return nil + } + return p.Milestone +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetNodeID() string { + if p == nil || p.NodeID == nil { + return "" + } + return *p.NodeID +} + +// GetNumber returns the Number field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetNumber() int { + if p == nil || p.Number == nil { + return 0 + } + return *p.Number +} + +// GetPatchURL returns the PatchURL field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetPatchURL() string { + if p == nil || p.PatchURL == nil { + return "" + } + return *p.PatchURL +} + +// GetReviewCommentsURL returns the ReviewCommentsURL field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetReviewCommentsURL() string { + if p == nil || p.ReviewCommentsURL == nil { + return "" + } + return *p.ReviewCommentsURL +} + +// GetReviewCommentURL returns the ReviewCommentURL field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetReviewCommentURL() string { + if p == nil || p.ReviewCommentURL == nil { + return "" + } + return *p.ReviewCommentURL +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetState() string { + if p == nil || p.State == nil { + return "" + } + return *p.State +} + +// GetStatusesURL returns the StatusesURL field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetStatusesURL() string { + if p == nil || p.StatusesURL == nil { + return "" + } + return *p.StatusesURL +} + +// GetTitle returns the Title field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetTitle() string { + if p == nil || p.Title == nil { + return "" + } + return *p.Title +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetUpdatedAt() time.Time { + if p == nil || p.UpdatedAt == nil { + return time.Time{} + } + return *p.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (p *PullRequest) GetURL() string { + if p == nil || p.URL == nil { + return "" + } + return *p.URL +} + +// GetUser returns the User field. +func (p *PullRequest) GetUser() *User { + if p == nil { + return nil + } + return p.User +} + +// GetLabel returns the Label field if it's non-nil, zero value otherwise. +func (p *PullRequestBranch) GetLabel() string { + if p == nil || p.Label == nil { + return "" + } + return *p.Label +} + +// GetRef returns the Ref field if it's non-nil, zero value otherwise. +func (p *PullRequestBranch) GetRef() string { + if p == nil || p.Ref == nil { + return "" + } + return *p.Ref +} + +// GetRepo returns the Repo field. +func (p *PullRequestBranch) GetRepo() *Repository { + if p == nil { + return nil + } + return p.Repo +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (p *PullRequestBranch) GetSHA() string { + if p == nil || p.SHA == nil { + return "" + } + return *p.SHA +} + +// GetUser returns the User field. +func (p *PullRequestBranch) GetUser() *User { + if p == nil { + return nil + } + return p.User +} + +// GetAuthorAssociation returns the AuthorAssociation field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetAuthorAssociation() string { + if p == nil || p.AuthorAssociation == nil { + return "" + } + return *p.AuthorAssociation +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetBody() string { + if p == nil || p.Body == nil { + return "" + } + return *p.Body +} + +// GetCommitID returns the CommitID field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetCommitID() string { + if p == nil || p.CommitID == nil { + return "" + } + return *p.CommitID +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetCreatedAt() time.Time { + if p == nil || p.CreatedAt == nil { + return time.Time{} + } + return *p.CreatedAt +} + +// GetDiffHunk returns the DiffHunk field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetDiffHunk() string { + if p == nil || p.DiffHunk == nil { + return "" + } + return *p.DiffHunk +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetHTMLURL() string { + if p == nil || p.HTMLURL == nil { + return "" + } + return *p.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetID() int64 { + if p == nil || p.ID == nil { + return 0 + } + return *p.ID +} + +// GetInReplyTo returns the InReplyTo field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetInReplyTo() int64 { + if p == nil || p.InReplyTo == nil { + return 0 + } + return *p.InReplyTo +} + +// GetOriginalCommitID returns the OriginalCommitID field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetOriginalCommitID() string { + if p == nil || p.OriginalCommitID == nil { + return "" + } + return *p.OriginalCommitID +} + +// GetOriginalPosition returns the OriginalPosition field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetOriginalPosition() int { + if p == nil || p.OriginalPosition == nil { + return 0 + } + return *p.OriginalPosition +} + +// GetPath returns the Path field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetPath() string { + if p == nil || p.Path == nil { + return "" + } + return *p.Path +} + +// GetPosition returns the Position field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetPosition() int { + if p == nil || p.Position == nil { + return 0 + } + return *p.Position +} + +// GetPullRequestReviewID returns the PullRequestReviewID field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetPullRequestReviewID() int64 { + if p == nil || p.PullRequestReviewID == nil { + return 0 + } + return *p.PullRequestReviewID +} + +// GetPullRequestURL returns the PullRequestURL field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetPullRequestURL() string { + if p == nil || p.PullRequestURL == nil { + return "" + } + return *p.PullRequestURL +} + +// GetReactions returns the Reactions field. +func (p *PullRequestComment) GetReactions() *Reactions { + if p == nil { + return nil + } + return p.Reactions +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetUpdatedAt() time.Time { + if p == nil || p.UpdatedAt == nil { + return time.Time{} + } + return *p.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (p *PullRequestComment) GetURL() string { + if p == nil || p.URL == nil { + return "" + } + return *p.URL +} + +// GetUser returns the User field. +func (p *PullRequestComment) GetUser() *User { + if p == nil { + return nil + } + return p.User +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (p *PullRequestEvent) GetAction() string { + if p == nil || p.Action == nil { + return "" + } + return *p.Action +} + +// GetAssignee returns the Assignee field. +func (p *PullRequestEvent) GetAssignee() *User { + if p == nil { + return nil + } + return p.Assignee +} + +// GetChanges returns the Changes field. +func (p *PullRequestEvent) GetChanges() *EditChange { + if p == nil { + return nil + } + return p.Changes +} + +// GetInstallation returns the Installation field. +func (p *PullRequestEvent) GetInstallation() *Installation { + if p == nil { + return nil + } + return p.Installation +} + +// GetLabel returns the Label field. +func (p *PullRequestEvent) GetLabel() *Label { + if p == nil { + return nil + } + return p.Label +} + +// GetNumber returns the Number field if it's non-nil, zero value otherwise. +func (p *PullRequestEvent) GetNumber() int { + if p == nil || p.Number == nil { + return 0 + } + return *p.Number +} + +// GetOrganization returns the Organization field. +func (p *PullRequestEvent) GetOrganization() *Organization { + if p == nil { + return nil + } + return p.Organization +} + +// GetPullRequest returns the PullRequest field. +func (p *PullRequestEvent) GetPullRequest() *PullRequest { + if p == nil { + return nil + } + return p.PullRequest +} + +// GetRepo returns the Repo field. +func (p *PullRequestEvent) GetRepo() *Repository { + if p == nil { + return nil + } + return p.Repo +} + +// GetRequestedReviewer returns the RequestedReviewer field. +func (p *PullRequestEvent) GetRequestedReviewer() *User { + if p == nil { + return nil + } + return p.RequestedReviewer +} + +// GetSender returns the Sender field. +func (p *PullRequestEvent) GetSender() *User { + if p == nil { + return nil + } + return p.Sender +} + +// GetDiffURL returns the DiffURL field if it's non-nil, zero value otherwise. +func (p *PullRequestLinks) GetDiffURL() string { + if p == nil || p.DiffURL == nil { + return "" + } + return *p.DiffURL +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (p *PullRequestLinks) GetHTMLURL() string { + if p == nil || p.HTMLURL == nil { + return "" + } + return *p.HTMLURL +} + +// GetPatchURL returns the PatchURL field if it's non-nil, zero value otherwise. +func (p *PullRequestLinks) GetPatchURL() string { + if p == nil || p.PatchURL == nil { + return "" + } + return *p.PatchURL +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (p *PullRequestLinks) GetURL() string { + if p == nil || p.URL == nil { + return "" + } + return *p.URL +} + +// GetMerged returns the Merged field if it's non-nil, zero value otherwise. +func (p *PullRequestMergeResult) GetMerged() bool { + if p == nil || p.Merged == nil { + return false + } + return *p.Merged +} + +// GetMessage returns the Message field if it's non-nil, zero value otherwise. +func (p *PullRequestMergeResult) GetMessage() string { + if p == nil || p.Message == nil { + return "" + } + return *p.Message +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (p *PullRequestMergeResult) GetSHA() string { + if p == nil || p.SHA == nil { + return "" + } + return *p.SHA +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (p *PullRequestReview) GetBody() string { + if p == nil || p.Body == nil { + return "" + } + return *p.Body +} + +// GetCommitID returns the CommitID field if it's non-nil, zero value otherwise. +func (p *PullRequestReview) GetCommitID() string { + if p == nil || p.CommitID == nil { + return "" + } + return *p.CommitID +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (p *PullRequestReview) GetHTMLURL() string { + if p == nil || p.HTMLURL == nil { + return "" + } + return *p.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *PullRequestReview) GetID() int64 { + if p == nil || p.ID == nil { + return 0 + } + return *p.ID +} + +// GetPullRequestURL returns the PullRequestURL field if it's non-nil, zero value otherwise. +func (p *PullRequestReview) GetPullRequestURL() string { + if p == nil || p.PullRequestURL == nil { + return "" + } + return *p.PullRequestURL +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (p *PullRequestReview) GetState() string { + if p == nil || p.State == nil { + return "" + } + return *p.State +} + +// GetSubmittedAt returns the SubmittedAt field if it's non-nil, zero value otherwise. +func (p *PullRequestReview) GetSubmittedAt() time.Time { + if p == nil || p.SubmittedAt == nil { + return time.Time{} + } + return *p.SubmittedAt +} + +// GetUser returns the User field. +func (p *PullRequestReview) GetUser() *User { + if p == nil { + return nil + } + return p.User +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (p *PullRequestReviewCommentEvent) GetAction() string { + if p == nil || p.Action == nil { + return "" + } + return *p.Action +} + +// GetChanges returns the Changes field. +func (p *PullRequestReviewCommentEvent) GetChanges() *EditChange { + if p == nil { + return nil + } + return p.Changes +} + +// GetComment returns the Comment field. +func (p *PullRequestReviewCommentEvent) GetComment() *PullRequestComment { + if p == nil { + return nil + } + return p.Comment +} + +// GetInstallation returns the Installation field. +func (p *PullRequestReviewCommentEvent) GetInstallation() *Installation { + if p == nil { + return nil + } + return p.Installation +} + +// GetPullRequest returns the PullRequest field. +func (p *PullRequestReviewCommentEvent) GetPullRequest() *PullRequest { + if p == nil { + return nil + } + return p.PullRequest +} + +// GetRepo returns the Repo field. +func (p *PullRequestReviewCommentEvent) GetRepo() *Repository { + if p == nil { + return nil + } + return p.Repo +} + +// GetSender returns the Sender field. +func (p *PullRequestReviewCommentEvent) GetSender() *User { + if p == nil { + return nil + } + return p.Sender +} + +// GetMessage returns the Message field if it's non-nil, zero value otherwise. +func (p *PullRequestReviewDismissalRequest) GetMessage() string { + if p == nil || p.Message == nil { + return "" + } + return *p.Message +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (p *PullRequestReviewEvent) GetAction() string { + if p == nil || p.Action == nil { + return "" + } + return *p.Action +} + +// GetInstallation returns the Installation field. +func (p *PullRequestReviewEvent) GetInstallation() *Installation { + if p == nil { + return nil + } + return p.Installation +} + +// GetOrganization returns the Organization field. +func (p *PullRequestReviewEvent) GetOrganization() *Organization { + if p == nil { + return nil + } + return p.Organization +} + +// GetPullRequest returns the PullRequest field. +func (p *PullRequestReviewEvent) GetPullRequest() *PullRequest { + if p == nil { + return nil + } + return p.PullRequest +} + +// GetRepo returns the Repo field. +func (p *PullRequestReviewEvent) GetRepo() *Repository { + if p == nil { + return nil + } + return p.Repo +} + +// GetReview returns the Review field. +func (p *PullRequestReviewEvent) GetReview() *PullRequestReview { + if p == nil { + return nil + } + return p.Review +} + +// GetSender returns the Sender field. +func (p *PullRequestReviewEvent) GetSender() *User { + if p == nil { + return nil + } + return p.Sender +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (p *PullRequestReviewRequest) GetBody() string { + if p == nil || p.Body == nil { + return "" + } + return *p.Body +} + +// GetCommitID returns the CommitID field if it's non-nil, zero value otherwise. +func (p *PullRequestReviewRequest) GetCommitID() string { + if p == nil || p.CommitID == nil { + return "" + } + return *p.CommitID +} + +// GetEvent returns the Event field if it's non-nil, zero value otherwise. +func (p *PullRequestReviewRequest) GetEvent() string { + if p == nil || p.Event == nil { + return "" + } + return *p.Event +} + +// GetDismissalRestrictionsRequest returns the DismissalRestrictionsRequest field. +func (p *PullRequestReviewsEnforcementRequest) GetDismissalRestrictionsRequest() *DismissalRestrictionsRequest { + if p == nil { + return nil + } + return p.DismissalRestrictionsRequest +} + +// GetDismissalRestrictionsRequest returns the DismissalRestrictionsRequest field. +func (p *PullRequestReviewsEnforcementUpdate) GetDismissalRestrictionsRequest() *DismissalRestrictionsRequest { + if p == nil { + return nil + } + return p.DismissalRestrictionsRequest +} + +// GetDismissStaleReviews returns the DismissStaleReviews field if it's non-nil, zero value otherwise. +func (p *PullRequestReviewsEnforcementUpdate) GetDismissStaleReviews() bool { + if p == nil || p.DismissStaleReviews == nil { + return false + } + return *p.DismissStaleReviews +} + +// GetMergablePulls returns the MergablePulls field if it's non-nil, zero value otherwise. +func (p *PullStats) GetMergablePulls() int { + if p == nil || p.MergablePulls == nil { + return 0 + } + return *p.MergablePulls +} + +// GetMergedPulls returns the MergedPulls field if it's non-nil, zero value otherwise. +func (p *PullStats) GetMergedPulls() int { + if p == nil || p.MergedPulls == nil { + return 0 + } + return *p.MergedPulls +} + +// GetTotalPulls returns the TotalPulls field if it's non-nil, zero value otherwise. +func (p *PullStats) GetTotalPulls() int { + if p == nil || p.TotalPulls == nil { + return 0 + } + return *p.TotalPulls +} + +// GetUnmergablePulls returns the UnmergablePulls field if it's non-nil, zero value otherwise. +func (p *PullStats) GetUnmergablePulls() int { + if p == nil || p.UnmergablePulls == nil { + return 0 + } + return *p.UnmergablePulls +} + +// GetCommits returns the Commits field if it's non-nil, zero value otherwise. +func (p *PunchCard) GetCommits() int { + if p == nil || p.Commits == nil { + return 0 + } + return *p.Commits +} + +// GetDay returns the Day field if it's non-nil, zero value otherwise. +func (p *PunchCard) GetDay() int { + if p == nil || p.Day == nil { + return 0 + } + return *p.Day +} + +// GetHour returns the Hour field if it's non-nil, zero value otherwise. +func (p *PunchCard) GetHour() int { + if p == nil || p.Hour == nil { + return 0 + } + return *p.Hour +} + +// GetAfter returns the After field if it's non-nil, zero value otherwise. +func (p *PushEvent) GetAfter() string { + if p == nil || p.After == nil { + return "" + } + return *p.After +} + +// GetBaseRef returns the BaseRef field if it's non-nil, zero value otherwise. +func (p *PushEvent) GetBaseRef() string { + if p == nil || p.BaseRef == nil { + return "" + } + return *p.BaseRef +} + +// GetBefore returns the Before field if it's non-nil, zero value otherwise. +func (p *PushEvent) GetBefore() string { + if p == nil || p.Before == nil { + return "" + } + return *p.Before +} + +// GetCompare returns the Compare field if it's non-nil, zero value otherwise. +func (p *PushEvent) GetCompare() string { + if p == nil || p.Compare == nil { + return "" + } + return *p.Compare +} + +// GetCreated returns the Created field if it's non-nil, zero value otherwise. +func (p *PushEvent) GetCreated() bool { + if p == nil || p.Created == nil { + return false + } + return *p.Created +} + +// GetDeleted returns the Deleted field if it's non-nil, zero value otherwise. +func (p *PushEvent) GetDeleted() bool { + if p == nil || p.Deleted == nil { + return false + } + return *p.Deleted +} + +// GetDistinctSize returns the DistinctSize field if it's non-nil, zero value otherwise. +func (p *PushEvent) GetDistinctSize() int { + if p == nil || p.DistinctSize == nil { + return 0 + } + return *p.DistinctSize +} + +// GetForced returns the Forced field if it's non-nil, zero value otherwise. +func (p *PushEvent) GetForced() bool { + if p == nil || p.Forced == nil { + return false + } + return *p.Forced +} + +// GetHead returns the Head field if it's non-nil, zero value otherwise. +func (p *PushEvent) GetHead() string { + if p == nil || p.Head == nil { + return "" + } + return *p.Head +} + +// GetHeadCommit returns the HeadCommit field. +func (p *PushEvent) GetHeadCommit() *PushEventCommit { + if p == nil { + return nil + } + return p.HeadCommit +} + +// GetInstallation returns the Installation field. +func (p *PushEvent) GetInstallation() *Installation { + if p == nil { + return nil + } + return p.Installation +} + +// GetPusher returns the Pusher field. +func (p *PushEvent) GetPusher() *User { + if p == nil { + return nil + } + return p.Pusher +} + +// GetPushID returns the PushID field if it's non-nil, zero value otherwise. +func (p *PushEvent) GetPushID() int64 { + if p == nil || p.PushID == nil { + return 0 + } + return *p.PushID +} + +// GetRef returns the Ref field if it's non-nil, zero value otherwise. +func (p *PushEvent) GetRef() string { + if p == nil || p.Ref == nil { + return "" + } + return *p.Ref +} + +// GetRepo returns the Repo field. +func (p *PushEvent) GetRepo() *PushEventRepository { + if p == nil { + return nil + } + return p.Repo +} + +// GetSender returns the Sender field. +func (p *PushEvent) GetSender() *User { + if p == nil { + return nil + } + return p.Sender +} + +// GetSize returns the Size field if it's non-nil, zero value otherwise. +func (p *PushEvent) GetSize() int { + if p == nil || p.Size == nil { + return 0 + } + return *p.Size +} + +// GetAuthor returns the Author field. +func (p *PushEventCommit) GetAuthor() *CommitAuthor { + if p == nil { + return nil + } + return p.Author +} + +// GetCommitter returns the Committer field. +func (p *PushEventCommit) GetCommitter() *CommitAuthor { + if p == nil { + return nil + } + return p.Committer +} + +// GetDistinct returns the Distinct field if it's non-nil, zero value otherwise. +func (p *PushEventCommit) GetDistinct() bool { + if p == nil || p.Distinct == nil { + return false + } + return *p.Distinct +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *PushEventCommit) GetID() string { + if p == nil || p.ID == nil { + return "" + } + return *p.ID +} + +// GetMessage returns the Message field if it's non-nil, zero value otherwise. +func (p *PushEventCommit) GetMessage() string { + if p == nil || p.Message == nil { + return "" + } + return *p.Message +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (p *PushEventCommit) GetSHA() string { + if p == nil || p.SHA == nil { + return "" + } + return *p.SHA +} + +// GetTimestamp returns the Timestamp field if it's non-nil, zero value otherwise. +func (p *PushEventCommit) GetTimestamp() Timestamp { + if p == nil || p.Timestamp == nil { + return Timestamp{} + } + return *p.Timestamp +} + +// GetTreeID returns the TreeID field if it's non-nil, zero value otherwise. +func (p *PushEventCommit) GetTreeID() string { + if p == nil || p.TreeID == nil { + return "" + } + return *p.TreeID +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (p *PushEventCommit) GetURL() string { + if p == nil || p.URL == nil { + return "" + } + return *p.URL +} + +// GetEmail returns the Email field if it's non-nil, zero value otherwise. +func (p *PushEventRepoOwner) GetEmail() string { + if p == nil || p.Email == nil { + return "" + } + return *p.Email +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (p *PushEventRepoOwner) GetName() string { + if p == nil || p.Name == nil { + return "" + } + return *p.Name +} + +// GetArchiveURL returns the ArchiveURL field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetArchiveURL() string { + if p == nil || p.ArchiveURL == nil { + return "" + } + return *p.ArchiveURL +} + +// GetCloneURL returns the CloneURL field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetCloneURL() string { + if p == nil || p.CloneURL == nil { + return "" + } + return *p.CloneURL +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetCreatedAt() Timestamp { + if p == nil || p.CreatedAt == nil { + return Timestamp{} + } + return *p.CreatedAt +} + +// GetDefaultBranch returns the DefaultBranch field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetDefaultBranch() string { + if p == nil || p.DefaultBranch == nil { + return "" + } + return *p.DefaultBranch +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetDescription() string { + if p == nil || p.Description == nil { + return "" + } + return *p.Description +} + +// GetFork returns the Fork field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetFork() bool { + if p == nil || p.Fork == nil { + return false + } + return *p.Fork +} + +// GetForksCount returns the ForksCount field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetForksCount() int { + if p == nil || p.ForksCount == nil { + return 0 + } + return *p.ForksCount +} + +// GetFullName returns the FullName field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetFullName() string { + if p == nil || p.FullName == nil { + return "" + } + return *p.FullName +} + +// GetGitURL returns the GitURL field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetGitURL() string { + if p == nil || p.GitURL == nil { + return "" + } + return *p.GitURL +} + +// GetHasDownloads returns the HasDownloads field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetHasDownloads() bool { + if p == nil || p.HasDownloads == nil { + return false + } + return *p.HasDownloads +} + +// GetHasIssues returns the HasIssues field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetHasIssues() bool { + if p == nil || p.HasIssues == nil { + return false + } + return *p.HasIssues +} + +// GetHasPages returns the HasPages field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetHasPages() bool { + if p == nil || p.HasPages == nil { + return false + } + return *p.HasPages +} + +// GetHasWiki returns the HasWiki field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetHasWiki() bool { + if p == nil || p.HasWiki == nil { + return false + } + return *p.HasWiki +} + +// GetHomepage returns the Homepage field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetHomepage() string { + if p == nil || p.Homepage == nil { + return "" + } + return *p.Homepage +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetHTMLURL() string { + if p == nil || p.HTMLURL == nil { + return "" + } + return *p.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetID() int64 { + if p == nil || p.ID == nil { + return 0 + } + return *p.ID +} + +// GetLanguage returns the Language field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetLanguage() string { + if p == nil || p.Language == nil { + return "" + } + return *p.Language +} + +// GetMasterBranch returns the MasterBranch field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetMasterBranch() string { + if p == nil || p.MasterBranch == nil { + return "" + } + return *p.MasterBranch +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetName() string { + if p == nil || p.Name == nil { + return "" + } + return *p.Name +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetNodeID() string { + if p == nil || p.NodeID == nil { + return "" + } + return *p.NodeID +} + +// GetOpenIssuesCount returns the OpenIssuesCount field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetOpenIssuesCount() int { + if p == nil || p.OpenIssuesCount == nil { + return 0 + } + return *p.OpenIssuesCount +} + +// GetOrganization returns the Organization field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetOrganization() string { + if p == nil || p.Organization == nil { + return "" + } + return *p.Organization +} + +// GetOwner returns the Owner field. +func (p *PushEventRepository) GetOwner() *User { + if p == nil { + return nil + } + return p.Owner +} + +// GetPrivate returns the Private field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetPrivate() bool { + if p == nil || p.Private == nil { + return false + } + return *p.Private +} + +// GetPushedAt returns the PushedAt field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetPushedAt() Timestamp { + if p == nil || p.PushedAt == nil { + return Timestamp{} + } + return *p.PushedAt +} + +// GetSize returns the Size field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetSize() int { + if p == nil || p.Size == nil { + return 0 + } + return *p.Size +} + +// GetSSHURL returns the SSHURL field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetSSHURL() string { + if p == nil || p.SSHURL == nil { + return "" + } + return *p.SSHURL +} + +// GetStargazersCount returns the StargazersCount field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetStargazersCount() int { + if p == nil || p.StargazersCount == nil { + return 0 + } + return *p.StargazersCount +} + +// GetStatusesURL returns the StatusesURL field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetStatusesURL() string { + if p == nil || p.StatusesURL == nil { + return "" + } + return *p.StatusesURL +} + +// GetSVNURL returns the SVNURL field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetSVNURL() string { + if p == nil || p.SVNURL == nil { + return "" + } + return *p.SVNURL +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetUpdatedAt() Timestamp { + if p == nil || p.UpdatedAt == nil { + return Timestamp{} + } + return *p.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetURL() string { + if p == nil || p.URL == nil { + return "" + } + return *p.URL +} + +// GetWatchersCount returns the WatchersCount field if it's non-nil, zero value otherwise. +func (p *PushEventRepository) GetWatchersCount() int { + if p == nil || p.WatchersCount == nil { + return 0 + } + return *p.WatchersCount +} + +// GetCore returns the Core field. +func (r *RateLimits) GetCore() *Rate { + if r == nil { + return nil + } + return r.Core +} + +// GetSearch returns the Search field. +func (r *RateLimits) GetSearch() *Rate { + if r == nil { + return nil + } + return r.Search +} + +// GetContent returns the Content field if it's non-nil, zero value otherwise. +func (r *Reaction) GetContent() string { + if r == nil || r.Content == nil { + return "" + } + return *r.Content +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (r *Reaction) GetID() int64 { + if r == nil || r.ID == nil { + return 0 + } + return *r.ID +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (r *Reaction) GetNodeID() string { + if r == nil || r.NodeID == nil { + return "" + } + return *r.NodeID +} + +// GetUser returns the User field. +func (r *Reaction) GetUser() *User { + if r == nil { + return nil + } + return r.User +} + +// GetConfused returns the Confused field if it's non-nil, zero value otherwise. +func (r *Reactions) GetConfused() int { + if r == nil || r.Confused == nil { + return 0 + } + return *r.Confused +} + +// GetHeart returns the Heart field if it's non-nil, zero value otherwise. +func (r *Reactions) GetHeart() int { + if r == nil || r.Heart == nil { + return 0 + } + return *r.Heart +} + +// GetHooray returns the Hooray field if it's non-nil, zero value otherwise. +func (r *Reactions) GetHooray() int { + if r == nil || r.Hooray == nil { + return 0 + } + return *r.Hooray +} + +// GetLaugh returns the Laugh field if it's non-nil, zero value otherwise. +func (r *Reactions) GetLaugh() int { + if r == nil || r.Laugh == nil { + return 0 + } + return *r.Laugh +} + +// GetMinusOne returns the MinusOne field if it's non-nil, zero value otherwise. +func (r *Reactions) GetMinusOne() int { + if r == nil || r.MinusOne == nil { + return 0 + } + return *r.MinusOne +} + +// GetPlusOne returns the PlusOne field if it's non-nil, zero value otherwise. +func (r *Reactions) GetPlusOne() int { + if r == nil || r.PlusOne == nil { + return 0 + } + return *r.PlusOne +} + +// GetTotalCount returns the TotalCount field if it's non-nil, zero value otherwise. +func (r *Reactions) GetTotalCount() int { + if r == nil || r.TotalCount == nil { + return 0 + } + return *r.TotalCount +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (r *Reactions) GetURL() string { + if r == nil || r.URL == nil { + return "" + } + return *r.URL +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (r *Reference) GetNodeID() string { + if r == nil || r.NodeID == nil { + return "" + } + return *r.NodeID +} + +// GetObject returns the Object field. +func (r *Reference) GetObject() *GitObject { + if r == nil { + return nil + } + return r.Object +} + +// GetRef returns the Ref field if it's non-nil, zero value otherwise. +func (r *Reference) GetRef() string { + if r == nil || r.Ref == nil { + return "" + } + return *r.Ref +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (r *Reference) GetURL() string { + if r == nil || r.URL == nil { + return "" + } + return *r.URL +} + +// GetBrowserDownloadURL returns the BrowserDownloadURL field if it's non-nil, zero value otherwise. +func (r *ReleaseAsset) GetBrowserDownloadURL() string { + if r == nil || r.BrowserDownloadURL == nil { + return "" + } + return *r.BrowserDownloadURL +} + +// GetContentType returns the ContentType field if it's non-nil, zero value otherwise. +func (r *ReleaseAsset) GetContentType() string { + if r == nil || r.ContentType == nil { + return "" + } + return *r.ContentType +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (r *ReleaseAsset) GetCreatedAt() Timestamp { + if r == nil || r.CreatedAt == nil { + return Timestamp{} + } + return *r.CreatedAt +} + +// GetDownloadCount returns the DownloadCount field if it's non-nil, zero value otherwise. +func (r *ReleaseAsset) GetDownloadCount() int { + if r == nil || r.DownloadCount == nil { + return 0 + } + return *r.DownloadCount +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (r *ReleaseAsset) GetID() int64 { + if r == nil || r.ID == nil { + return 0 + } + return *r.ID +} + +// GetLabel returns the Label field if it's non-nil, zero value otherwise. +func (r *ReleaseAsset) GetLabel() string { + if r == nil || r.Label == nil { + return "" + } + return *r.Label +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (r *ReleaseAsset) GetName() string { + if r == nil || r.Name == nil { + return "" + } + return *r.Name +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (r *ReleaseAsset) GetNodeID() string { + if r == nil || r.NodeID == nil { + return "" + } + return *r.NodeID +} + +// GetSize returns the Size field if it's non-nil, zero value otherwise. +func (r *ReleaseAsset) GetSize() int { + if r == nil || r.Size == nil { + return 0 + } + return *r.Size +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (r *ReleaseAsset) GetState() string { + if r == nil || r.State == nil { + return "" + } + return *r.State +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (r *ReleaseAsset) GetUpdatedAt() Timestamp { + if r == nil || r.UpdatedAt == nil { + return Timestamp{} + } + return *r.UpdatedAt +} + +// GetUploader returns the Uploader field. +func (r *ReleaseAsset) GetUploader() *User { + if r == nil { + return nil + } + return r.Uploader +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (r *ReleaseAsset) GetURL() string { + if r == nil || r.URL == nil { + return "" + } + return *r.URL +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (r *ReleaseEvent) GetAction() string { + if r == nil || r.Action == nil { + return "" + } + return *r.Action +} + +// GetInstallation returns the Installation field. +func (r *ReleaseEvent) GetInstallation() *Installation { + if r == nil { + return nil + } + return r.Installation +} + +// GetRelease returns the Release field. +func (r *ReleaseEvent) GetRelease() *RepositoryRelease { + if r == nil { + return nil + } + return r.Release +} + +// GetRepo returns the Repo field. +func (r *ReleaseEvent) GetRepo() *Repository { + if r == nil { + return nil + } + return r.Repo +} + +// GetSender returns the Sender field. +func (r *ReleaseEvent) GetSender() *User { + if r == nil { + return nil + } + return r.Sender +} + +// GetFrom returns the From field if it's non-nil, zero value otherwise. +func (r *Rename) GetFrom() string { + if r == nil || r.From == nil { + return "" + } + return *r.From +} + +// GetTo returns the To field if it's non-nil, zero value otherwise. +func (r *Rename) GetTo() string { + if r == nil || r.To == nil { + return "" + } + return *r.To +} + +// GetIncompleteResults returns the IncompleteResults field if it's non-nil, zero value otherwise. +func (r *RepositoriesSearchResult) GetIncompleteResults() bool { + if r == nil || r.IncompleteResults == nil { + return false + } + return *r.IncompleteResults +} + +// GetTotal returns the Total field if it's non-nil, zero value otherwise. +func (r *RepositoriesSearchResult) GetTotal() int { + if r == nil || r.Total == nil { + return 0 + } + return *r.Total +} + +// GetAllowMergeCommit returns the AllowMergeCommit field if it's non-nil, zero value otherwise. +func (r *Repository) GetAllowMergeCommit() bool { + if r == nil || r.AllowMergeCommit == nil { + return false + } + return *r.AllowMergeCommit +} + +// GetAllowRebaseMerge returns the AllowRebaseMerge field if it's non-nil, zero value otherwise. +func (r *Repository) GetAllowRebaseMerge() bool { + if r == nil || r.AllowRebaseMerge == nil { + return false + } + return *r.AllowRebaseMerge +} + +// GetAllowSquashMerge returns the AllowSquashMerge field if it's non-nil, zero value otherwise. +func (r *Repository) GetAllowSquashMerge() bool { + if r == nil || r.AllowSquashMerge == nil { + return false + } + return *r.AllowSquashMerge +} + +// GetArchived returns the Archived field if it's non-nil, zero value otherwise. +func (r *Repository) GetArchived() bool { + if r == nil || r.Archived == nil { + return false + } + return *r.Archived +} + +// GetArchiveURL returns the ArchiveURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetArchiveURL() string { + if r == nil || r.ArchiveURL == nil { + return "" + } + return *r.ArchiveURL +} + +// GetAssigneesURL returns the AssigneesURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetAssigneesURL() string { + if r == nil || r.AssigneesURL == nil { + return "" + } + return *r.AssigneesURL +} + +// GetAutoInit returns the AutoInit field if it's non-nil, zero value otherwise. +func (r *Repository) GetAutoInit() bool { + if r == nil || r.AutoInit == nil { + return false + } + return *r.AutoInit +} + +// GetBlobsURL returns the BlobsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetBlobsURL() string { + if r == nil || r.BlobsURL == nil { + return "" + } + return *r.BlobsURL +} + +// GetBranchesURL returns the BranchesURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetBranchesURL() string { + if r == nil || r.BranchesURL == nil { + return "" + } + return *r.BranchesURL +} + +// GetCloneURL returns the CloneURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetCloneURL() string { + if r == nil || r.CloneURL == nil { + return "" + } + return *r.CloneURL +} + +// GetCodeOfConduct returns the CodeOfConduct field. +func (r *Repository) GetCodeOfConduct() *CodeOfConduct { + if r == nil { + return nil + } + return r.CodeOfConduct +} + +// GetCollaboratorsURL returns the CollaboratorsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetCollaboratorsURL() string { + if r == nil || r.CollaboratorsURL == nil { + return "" + } + return *r.CollaboratorsURL +} + +// GetCommentsURL returns the CommentsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetCommentsURL() string { + if r == nil || r.CommentsURL == nil { + return "" + } + return *r.CommentsURL +} + +// GetCommitsURL returns the CommitsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetCommitsURL() string { + if r == nil || r.CommitsURL == nil { + return "" + } + return *r.CommitsURL +} + +// GetCompareURL returns the CompareURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetCompareURL() string { + if r == nil || r.CompareURL == nil { + return "" + } + return *r.CompareURL +} + +// GetContentsURL returns the ContentsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetContentsURL() string { + if r == nil || r.ContentsURL == nil { + return "" + } + return *r.ContentsURL +} + +// GetContributorsURL returns the ContributorsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetContributorsURL() string { + if r == nil || r.ContributorsURL == nil { + return "" + } + return *r.ContributorsURL +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (r *Repository) GetCreatedAt() Timestamp { + if r == nil || r.CreatedAt == nil { + return Timestamp{} + } + return *r.CreatedAt +} + +// GetDefaultBranch returns the DefaultBranch field if it's non-nil, zero value otherwise. +func (r *Repository) GetDefaultBranch() string { + if r == nil || r.DefaultBranch == nil { + return "" + } + return *r.DefaultBranch +} + +// GetDeploymentsURL returns the DeploymentsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetDeploymentsURL() string { + if r == nil || r.DeploymentsURL == nil { + return "" + } + return *r.DeploymentsURL +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (r *Repository) GetDescription() string { + if r == nil || r.Description == nil { + return "" + } + return *r.Description +} + +// GetDownloadsURL returns the DownloadsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetDownloadsURL() string { + if r == nil || r.DownloadsURL == nil { + return "" + } + return *r.DownloadsURL +} + +// GetEventsURL returns the EventsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetEventsURL() string { + if r == nil || r.EventsURL == nil { + return "" + } + return *r.EventsURL +} + +// GetFork returns the Fork field if it's non-nil, zero value otherwise. +func (r *Repository) GetFork() bool { + if r == nil || r.Fork == nil { + return false + } + return *r.Fork +} + +// GetForksCount returns the ForksCount field if it's non-nil, zero value otherwise. +func (r *Repository) GetForksCount() int { + if r == nil || r.ForksCount == nil { + return 0 + } + return *r.ForksCount +} + +// GetForksURL returns the ForksURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetForksURL() string { + if r == nil || r.ForksURL == nil { + return "" + } + return *r.ForksURL +} + +// GetFullName returns the FullName field if it's non-nil, zero value otherwise. +func (r *Repository) GetFullName() string { + if r == nil || r.FullName == nil { + return "" + } + return *r.FullName +} + +// GetGitCommitsURL returns the GitCommitsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetGitCommitsURL() string { + if r == nil || r.GitCommitsURL == nil { + return "" + } + return *r.GitCommitsURL +} + +// GetGitignoreTemplate returns the GitignoreTemplate field if it's non-nil, zero value otherwise. +func (r *Repository) GetGitignoreTemplate() string { + if r == nil || r.GitignoreTemplate == nil { + return "" + } + return *r.GitignoreTemplate +} + +// GetGitRefsURL returns the GitRefsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetGitRefsURL() string { + if r == nil || r.GitRefsURL == nil { + return "" + } + return *r.GitRefsURL +} + +// GetGitTagsURL returns the GitTagsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetGitTagsURL() string { + if r == nil || r.GitTagsURL == nil { + return "" + } + return *r.GitTagsURL +} + +// GetGitURL returns the GitURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetGitURL() string { + if r == nil || r.GitURL == nil { + return "" + } + return *r.GitURL +} + +// GetHasDownloads returns the HasDownloads field if it's non-nil, zero value otherwise. +func (r *Repository) GetHasDownloads() bool { + if r == nil || r.HasDownloads == nil { + return false + } + return *r.HasDownloads +} + +// GetHasIssues returns the HasIssues field if it's non-nil, zero value otherwise. +func (r *Repository) GetHasIssues() bool { + if r == nil || r.HasIssues == nil { + return false + } + return *r.HasIssues +} + +// GetHasPages returns the HasPages field if it's non-nil, zero value otherwise. +func (r *Repository) GetHasPages() bool { + if r == nil || r.HasPages == nil { + return false + } + return *r.HasPages +} + +// GetHasProjects returns the HasProjects field if it's non-nil, zero value otherwise. +func (r *Repository) GetHasProjects() bool { + if r == nil || r.HasProjects == nil { + return false + } + return *r.HasProjects +} + +// GetHasWiki returns the HasWiki field if it's non-nil, zero value otherwise. +func (r *Repository) GetHasWiki() bool { + if r == nil || r.HasWiki == nil { + return false + } + return *r.HasWiki +} + +// GetHomepage returns the Homepage field if it's non-nil, zero value otherwise. +func (r *Repository) GetHomepage() string { + if r == nil || r.Homepage == nil { + return "" + } + return *r.Homepage +} + +// GetHooksURL returns the HooksURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetHooksURL() string { + if r == nil || r.HooksURL == nil { + return "" + } + return *r.HooksURL +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetHTMLURL() string { + if r == nil || r.HTMLURL == nil { + return "" + } + return *r.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (r *Repository) GetID() int64 { + if r == nil || r.ID == nil { + return 0 + } + return *r.ID +} + +// GetIssueCommentURL returns the IssueCommentURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetIssueCommentURL() string { + if r == nil || r.IssueCommentURL == nil { + return "" + } + return *r.IssueCommentURL +} + +// GetIssueEventsURL returns the IssueEventsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetIssueEventsURL() string { + if r == nil || r.IssueEventsURL == nil { + return "" + } + return *r.IssueEventsURL +} + +// GetIssuesURL returns the IssuesURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetIssuesURL() string { + if r == nil || r.IssuesURL == nil { + return "" + } + return *r.IssuesURL +} + +// GetKeysURL returns the KeysURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetKeysURL() string { + if r == nil || r.KeysURL == nil { + return "" + } + return *r.KeysURL +} + +// GetLabelsURL returns the LabelsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetLabelsURL() string { + if r == nil || r.LabelsURL == nil { + return "" + } + return *r.LabelsURL +} + +// GetLanguage returns the Language field if it's non-nil, zero value otherwise. +func (r *Repository) GetLanguage() string { + if r == nil || r.Language == nil { + return "" + } + return *r.Language +} + +// GetLanguagesURL returns the LanguagesURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetLanguagesURL() string { + if r == nil || r.LanguagesURL == nil { + return "" + } + return *r.LanguagesURL +} + +// GetLicense returns the License field. +func (r *Repository) GetLicense() *License { + if r == nil { + return nil + } + return r.License +} + +// GetLicenseTemplate returns the LicenseTemplate field if it's non-nil, zero value otherwise. +func (r *Repository) GetLicenseTemplate() string { + if r == nil || r.LicenseTemplate == nil { + return "" + } + return *r.LicenseTemplate +} + +// GetMasterBranch returns the MasterBranch field if it's non-nil, zero value otherwise. +func (r *Repository) GetMasterBranch() string { + if r == nil || r.MasterBranch == nil { + return "" + } + return *r.MasterBranch +} + +// GetMergesURL returns the MergesURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetMergesURL() string { + if r == nil || r.MergesURL == nil { + return "" + } + return *r.MergesURL +} + +// GetMilestonesURL returns the MilestonesURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetMilestonesURL() string { + if r == nil || r.MilestonesURL == nil { + return "" + } + return *r.MilestonesURL +} + +// GetMirrorURL returns the MirrorURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetMirrorURL() string { + if r == nil || r.MirrorURL == nil { + return "" + } + return *r.MirrorURL +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (r *Repository) GetName() string { + if r == nil || r.Name == nil { + return "" + } + return *r.Name +} + +// GetNetworkCount returns the NetworkCount field if it's non-nil, zero value otherwise. +func (r *Repository) GetNetworkCount() int { + if r == nil || r.NetworkCount == nil { + return 0 + } + return *r.NetworkCount +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (r *Repository) GetNodeID() string { + if r == nil || r.NodeID == nil { + return "" + } + return *r.NodeID +} + +// GetNotificationsURL returns the NotificationsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetNotificationsURL() string { + if r == nil || r.NotificationsURL == nil { + return "" + } + return *r.NotificationsURL +} + +// GetOpenIssuesCount returns the OpenIssuesCount field if it's non-nil, zero value otherwise. +func (r *Repository) GetOpenIssuesCount() int { + if r == nil || r.OpenIssuesCount == nil { + return 0 + } + return *r.OpenIssuesCount +} + +// GetOrganization returns the Organization field. +func (r *Repository) GetOrganization() *Organization { + if r == nil { + return nil + } + return r.Organization +} + +// GetOwner returns the Owner field. +func (r *Repository) GetOwner() *User { + if r == nil { + return nil + } + return r.Owner +} + +// GetParent returns the Parent field. +func (r *Repository) GetParent() *Repository { + if r == nil { + return nil + } + return r.Parent +} + +// GetPermissions returns the Permissions field if it's non-nil, zero value otherwise. +func (r *Repository) GetPermissions() map[string]bool { + if r == nil || r.Permissions == nil { + return map[string]bool{} + } + return *r.Permissions +} + +// GetPrivate returns the Private field if it's non-nil, zero value otherwise. +func (r *Repository) GetPrivate() bool { + if r == nil || r.Private == nil { + return false + } + return *r.Private +} + +// GetPullsURL returns the PullsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetPullsURL() string { + if r == nil || r.PullsURL == nil { + return "" + } + return *r.PullsURL +} + +// GetPushedAt returns the PushedAt field if it's non-nil, zero value otherwise. +func (r *Repository) GetPushedAt() Timestamp { + if r == nil || r.PushedAt == nil { + return Timestamp{} + } + return *r.PushedAt +} + +// GetReleasesURL returns the ReleasesURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetReleasesURL() string { + if r == nil || r.ReleasesURL == nil { + return "" + } + return *r.ReleasesURL +} + +// GetSize returns the Size field if it's non-nil, zero value otherwise. +func (r *Repository) GetSize() int { + if r == nil || r.Size == nil { + return 0 + } + return *r.Size +} + +// GetSource returns the Source field. +func (r *Repository) GetSource() *Repository { + if r == nil { + return nil + } + return r.Source +} + +// GetSSHURL returns the SSHURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetSSHURL() string { + if r == nil || r.SSHURL == nil { + return "" + } + return *r.SSHURL +} + +// GetStargazersCount returns the StargazersCount field if it's non-nil, zero value otherwise. +func (r *Repository) GetStargazersCount() int { + if r == nil || r.StargazersCount == nil { + return 0 + } + return *r.StargazersCount +} + +// GetStargazersURL returns the StargazersURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetStargazersURL() string { + if r == nil || r.StargazersURL == nil { + return "" + } + return *r.StargazersURL +} + +// GetStatusesURL returns the StatusesURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetStatusesURL() string { + if r == nil || r.StatusesURL == nil { + return "" + } + return *r.StatusesURL +} + +// GetSubscribersCount returns the SubscribersCount field if it's non-nil, zero value otherwise. +func (r *Repository) GetSubscribersCount() int { + if r == nil || r.SubscribersCount == nil { + return 0 + } + return *r.SubscribersCount +} + +// GetSubscribersURL returns the SubscribersURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetSubscribersURL() string { + if r == nil || r.SubscribersURL == nil { + return "" + } + return *r.SubscribersURL +} + +// GetSubscriptionURL returns the SubscriptionURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetSubscriptionURL() string { + if r == nil || r.SubscriptionURL == nil { + return "" + } + return *r.SubscriptionURL +} + +// GetSVNURL returns the SVNURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetSVNURL() string { + if r == nil || r.SVNURL == nil { + return "" + } + return *r.SVNURL +} + +// GetTagsURL returns the TagsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetTagsURL() string { + if r == nil || r.TagsURL == nil { + return "" + } + return *r.TagsURL +} + +// GetTeamID returns the TeamID field if it's non-nil, zero value otherwise. +func (r *Repository) GetTeamID() int64 { + if r == nil || r.TeamID == nil { + return 0 + } + return *r.TeamID +} + +// GetTeamsURL returns the TeamsURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetTeamsURL() string { + if r == nil || r.TeamsURL == nil { + return "" + } + return *r.TeamsURL +} + +// GetTreesURL returns the TreesURL field if it's non-nil, zero value otherwise. +func (r *Repository) GetTreesURL() string { + if r == nil || r.TreesURL == nil { + return "" + } + return *r.TreesURL +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (r *Repository) GetUpdatedAt() Timestamp { + if r == nil || r.UpdatedAt == nil { + return Timestamp{} + } + return *r.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (r *Repository) GetURL() string { + if r == nil || r.URL == nil { + return "" + } + return *r.URL +} + +// GetWatchersCount returns the WatchersCount field if it's non-nil, zero value otherwise. +func (r *Repository) GetWatchersCount() int { + if r == nil || r.WatchersCount == nil { + return 0 + } + return *r.WatchersCount +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (r *RepositoryComment) GetBody() string { + if r == nil || r.Body == nil { + return "" + } + return *r.Body +} + +// GetCommitID returns the CommitID field if it's non-nil, zero value otherwise. +func (r *RepositoryComment) GetCommitID() string { + if r == nil || r.CommitID == nil { + return "" + } + return *r.CommitID +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (r *RepositoryComment) GetCreatedAt() time.Time { + if r == nil || r.CreatedAt == nil { + return time.Time{} + } + return *r.CreatedAt +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (r *RepositoryComment) GetHTMLURL() string { + if r == nil || r.HTMLURL == nil { + return "" + } + return *r.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (r *RepositoryComment) GetID() int64 { + if r == nil || r.ID == nil { + return 0 + } + return *r.ID +} + +// GetPath returns the Path field if it's non-nil, zero value otherwise. +func (r *RepositoryComment) GetPath() string { + if r == nil || r.Path == nil { + return "" + } + return *r.Path +} + +// GetPosition returns the Position field if it's non-nil, zero value otherwise. +func (r *RepositoryComment) GetPosition() int { + if r == nil || r.Position == nil { + return 0 + } + return *r.Position +} + +// GetReactions returns the Reactions field. +func (r *RepositoryComment) GetReactions() *Reactions { + if r == nil { + return nil + } + return r.Reactions +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (r *RepositoryComment) GetUpdatedAt() time.Time { + if r == nil || r.UpdatedAt == nil { + return time.Time{} + } + return *r.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (r *RepositoryComment) GetURL() string { + if r == nil || r.URL == nil { + return "" + } + return *r.URL +} + +// GetUser returns the User field. +func (r *RepositoryComment) GetUser() *User { + if r == nil { + return nil + } + return r.User +} + +// GetAuthor returns the Author field. +func (r *RepositoryCommit) GetAuthor() *User { + if r == nil { + return nil + } + return r.Author +} + +// GetCommentsURL returns the CommentsURL field if it's non-nil, zero value otherwise. +func (r *RepositoryCommit) GetCommentsURL() string { + if r == nil || r.CommentsURL == nil { + return "" + } + return *r.CommentsURL +} + +// GetCommit returns the Commit field. +func (r *RepositoryCommit) GetCommit() *Commit { + if r == nil { + return nil + } + return r.Commit +} + +// GetCommitter returns the Committer field. +func (r *RepositoryCommit) GetCommitter() *User { + if r == nil { + return nil + } + return r.Committer +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (r *RepositoryCommit) GetHTMLURL() string { + if r == nil || r.HTMLURL == nil { + return "" + } + return *r.HTMLURL +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (r *RepositoryCommit) GetSHA() string { + if r == nil || r.SHA == nil { + return "" + } + return *r.SHA +} + +// GetStats returns the Stats field. +func (r *RepositoryCommit) GetStats() *CommitStats { + if r == nil { + return nil + } + return r.Stats +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (r *RepositoryCommit) GetURL() string { + if r == nil || r.URL == nil { + return "" + } + return *r.URL +} + +// GetDownloadURL returns the DownloadURL field if it's non-nil, zero value otherwise. +func (r *RepositoryContent) GetDownloadURL() string { + if r == nil || r.DownloadURL == nil { + return "" + } + return *r.DownloadURL +} + +// GetEncoding returns the Encoding field if it's non-nil, zero value otherwise. +func (r *RepositoryContent) GetEncoding() string { + if r == nil || r.Encoding == nil { + return "" + } + return *r.Encoding +} + +// GetGitURL returns the GitURL field if it's non-nil, zero value otherwise. +func (r *RepositoryContent) GetGitURL() string { + if r == nil || r.GitURL == nil { + return "" + } + return *r.GitURL +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (r *RepositoryContent) GetHTMLURL() string { + if r == nil || r.HTMLURL == nil { + return "" + } + return *r.HTMLURL +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (r *RepositoryContent) GetName() string { + if r == nil || r.Name == nil { + return "" + } + return *r.Name +} + +// GetPath returns the Path field if it's non-nil, zero value otherwise. +func (r *RepositoryContent) GetPath() string { + if r == nil || r.Path == nil { + return "" + } + return *r.Path +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (r *RepositoryContent) GetSHA() string { + if r == nil || r.SHA == nil { + return "" + } + return *r.SHA +} + +// GetSize returns the Size field if it's non-nil, zero value otherwise. +func (r *RepositoryContent) GetSize() int { + if r == nil || r.Size == nil { + return 0 + } + return *r.Size +} + +// GetType returns the Type field if it's non-nil, zero value otherwise. +func (r *RepositoryContent) GetType() string { + if r == nil || r.Type == nil { + return "" + } + return *r.Type +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (r *RepositoryContent) GetURL() string { + if r == nil || r.URL == nil { + return "" + } + return *r.URL +} + +// GetAuthor returns the Author field. +func (r *RepositoryContentFileOptions) GetAuthor() *CommitAuthor { + if r == nil { + return nil + } + return r.Author +} + +// GetBranch returns the Branch field if it's non-nil, zero value otherwise. +func (r *RepositoryContentFileOptions) GetBranch() string { + if r == nil || r.Branch == nil { + return "" + } + return *r.Branch +} + +// GetCommitter returns the Committer field. +func (r *RepositoryContentFileOptions) GetCommitter() *CommitAuthor { + if r == nil { + return nil + } + return r.Committer +} + +// GetMessage returns the Message field if it's non-nil, zero value otherwise. +func (r *RepositoryContentFileOptions) GetMessage() string { + if r == nil || r.Message == nil { + return "" + } + return *r.Message +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (r *RepositoryContentFileOptions) GetSHA() string { + if r == nil || r.SHA == nil { + return "" + } + return *r.SHA +} + +// GetContent returns the Content field. +func (r *RepositoryContentResponse) GetContent() *RepositoryContent { + if r == nil { + return nil + } + return r.Content +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (r *RepositoryEvent) GetAction() string { + if r == nil || r.Action == nil { + return "" + } + return *r.Action +} + +// GetInstallation returns the Installation field. +func (r *RepositoryEvent) GetInstallation() *Installation { + if r == nil { + return nil + } + return r.Installation +} + +// GetOrg returns the Org field. +func (r *RepositoryEvent) GetOrg() *Organization { + if r == nil { + return nil + } + return r.Org +} + +// GetRepo returns the Repo field. +func (r *RepositoryEvent) GetRepo() *Repository { + if r == nil { + return nil + } + return r.Repo +} + +// GetSender returns the Sender field. +func (r *RepositoryEvent) GetSender() *User { + if r == nil { + return nil + } + return r.Sender +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (r *RepositoryInvitation) GetCreatedAt() Timestamp { + if r == nil || r.CreatedAt == nil { + return Timestamp{} + } + return *r.CreatedAt +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (r *RepositoryInvitation) GetHTMLURL() string { + if r == nil || r.HTMLURL == nil { + return "" + } + return *r.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (r *RepositoryInvitation) GetID() int64 { + if r == nil || r.ID == nil { + return 0 + } + return *r.ID +} + +// GetInvitee returns the Invitee field. +func (r *RepositoryInvitation) GetInvitee() *User { + if r == nil { + return nil + } + return r.Invitee +} + +// GetInviter returns the Inviter field. +func (r *RepositoryInvitation) GetInviter() *User { + if r == nil { + return nil + } + return r.Inviter +} + +// GetPermissions returns the Permissions field if it's non-nil, zero value otherwise. +func (r *RepositoryInvitation) GetPermissions() string { + if r == nil || r.Permissions == nil { + return "" + } + return *r.Permissions +} + +// GetRepo returns the Repo field. +func (r *RepositoryInvitation) GetRepo() *Repository { + if r == nil { + return nil + } + return r.Repo +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (r *RepositoryInvitation) GetURL() string { + if r == nil || r.URL == nil { + return "" + } + return *r.URL +} + +// GetContent returns the Content field if it's non-nil, zero value otherwise. +func (r *RepositoryLicense) GetContent() string { + if r == nil || r.Content == nil { + return "" + } + return *r.Content +} + +// GetDownloadURL returns the DownloadURL field if it's non-nil, zero value otherwise. +func (r *RepositoryLicense) GetDownloadURL() string { + if r == nil || r.DownloadURL == nil { + return "" + } + return *r.DownloadURL +} + +// GetEncoding returns the Encoding field if it's non-nil, zero value otherwise. +func (r *RepositoryLicense) GetEncoding() string { + if r == nil || r.Encoding == nil { + return "" + } + return *r.Encoding +} + +// GetGitURL returns the GitURL field if it's non-nil, zero value otherwise. +func (r *RepositoryLicense) GetGitURL() string { + if r == nil || r.GitURL == nil { + return "" + } + return *r.GitURL +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (r *RepositoryLicense) GetHTMLURL() string { + if r == nil || r.HTMLURL == nil { + return "" + } + return *r.HTMLURL +} + +// GetLicense returns the License field. +func (r *RepositoryLicense) GetLicense() *License { + if r == nil { + return nil + } + return r.License +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (r *RepositoryLicense) GetName() string { + if r == nil || r.Name == nil { + return "" + } + return *r.Name +} + +// GetPath returns the Path field if it's non-nil, zero value otherwise. +func (r *RepositoryLicense) GetPath() string { + if r == nil || r.Path == nil { + return "" + } + return *r.Path +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (r *RepositoryLicense) GetSHA() string { + if r == nil || r.SHA == nil { + return "" + } + return *r.SHA +} + +// GetSize returns the Size field if it's non-nil, zero value otherwise. +func (r *RepositoryLicense) GetSize() int { + if r == nil || r.Size == nil { + return 0 + } + return *r.Size +} + +// GetType returns the Type field if it's non-nil, zero value otherwise. +func (r *RepositoryLicense) GetType() string { + if r == nil || r.Type == nil { + return "" + } + return *r.Type +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (r *RepositoryLicense) GetURL() string { + if r == nil || r.URL == nil { + return "" + } + return *r.URL +} + +// GetBase returns the Base field if it's non-nil, zero value otherwise. +func (r *RepositoryMergeRequest) GetBase() string { + if r == nil || r.Base == nil { + return "" + } + return *r.Base +} + +// GetCommitMessage returns the CommitMessage field if it's non-nil, zero value otherwise. +func (r *RepositoryMergeRequest) GetCommitMessage() string { + if r == nil || r.CommitMessage == nil { + return "" + } + return *r.CommitMessage +} + +// GetHead returns the Head field if it's non-nil, zero value otherwise. +func (r *RepositoryMergeRequest) GetHead() string { + if r == nil || r.Head == nil { + return "" + } + return *r.Head +} + +// GetPermission returns the Permission field if it's non-nil, zero value otherwise. +func (r *RepositoryPermissionLevel) GetPermission() string { + if r == nil || r.Permission == nil { + return "" + } + return *r.Permission +} + +// GetUser returns the User field. +func (r *RepositoryPermissionLevel) GetUser() *User { + if r == nil { + return nil + } + return r.User +} + +// GetAssetsURL returns the AssetsURL field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetAssetsURL() string { + if r == nil || r.AssetsURL == nil { + return "" + } + return *r.AssetsURL +} + +// GetAuthor returns the Author field. +func (r *RepositoryRelease) GetAuthor() *User { + if r == nil { + return nil + } + return r.Author +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetBody() string { + if r == nil || r.Body == nil { + return "" + } + return *r.Body +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetCreatedAt() Timestamp { + if r == nil || r.CreatedAt == nil { + return Timestamp{} + } + return *r.CreatedAt +} + +// GetDraft returns the Draft field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetDraft() bool { + if r == nil || r.Draft == nil { + return false + } + return *r.Draft +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetHTMLURL() string { + if r == nil || r.HTMLURL == nil { + return "" + } + return *r.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetID() int64 { + if r == nil || r.ID == nil { + return 0 + } + return *r.ID +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetName() string { + if r == nil || r.Name == nil { + return "" + } + return *r.Name +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetNodeID() string { + if r == nil || r.NodeID == nil { + return "" + } + return *r.NodeID +} + +// GetPrerelease returns the Prerelease field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetPrerelease() bool { + if r == nil || r.Prerelease == nil { + return false + } + return *r.Prerelease +} + +// GetPublishedAt returns the PublishedAt field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetPublishedAt() Timestamp { + if r == nil || r.PublishedAt == nil { + return Timestamp{} + } + return *r.PublishedAt +} + +// GetTagName returns the TagName field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetTagName() string { + if r == nil || r.TagName == nil { + return "" + } + return *r.TagName +} + +// GetTarballURL returns the TarballURL field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetTarballURL() string { + if r == nil || r.TarballURL == nil { + return "" + } + return *r.TarballURL +} + +// GetTargetCommitish returns the TargetCommitish field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetTargetCommitish() string { + if r == nil || r.TargetCommitish == nil { + return "" + } + return *r.TargetCommitish +} + +// GetUploadURL returns the UploadURL field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetUploadURL() string { + if r == nil || r.UploadURL == nil { + return "" + } + return *r.UploadURL +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetURL() string { + if r == nil || r.URL == nil { + return "" + } + return *r.URL +} + +// GetZipballURL returns the ZipballURL field if it's non-nil, zero value otherwise. +func (r *RepositoryRelease) GetZipballURL() string { + if r == nil || r.ZipballURL == nil { + return "" + } + return *r.ZipballURL +} + +// GetCommit returns the Commit field. +func (r *RepositoryTag) GetCommit() *Commit { + if r == nil { + return nil + } + return r.Commit +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (r *RepositoryTag) GetName() string { + if r == nil || r.Name == nil { + return "" + } + return *r.Name +} + +// GetTarballURL returns the TarballURL field if it's non-nil, zero value otherwise. +func (r *RepositoryTag) GetTarballURL() string { + if r == nil || r.TarballURL == nil { + return "" + } + return *r.TarballURL +} + +// GetZipballURL returns the ZipballURL field if it's non-nil, zero value otherwise. +func (r *RepositoryTag) GetZipballURL() string { + if r == nil || r.ZipballURL == nil { + return "" + } + return *r.ZipballURL +} + +// GetForkRepos returns the ForkRepos field if it's non-nil, zero value otherwise. +func (r *RepoStats) GetForkRepos() int { + if r == nil || r.ForkRepos == nil { + return 0 + } + return *r.ForkRepos +} + +// GetOrgRepos returns the OrgRepos field if it's non-nil, zero value otherwise. +func (r *RepoStats) GetOrgRepos() int { + if r == nil || r.OrgRepos == nil { + return 0 + } + return *r.OrgRepos +} + +// GetRootRepos returns the RootRepos field if it's non-nil, zero value otherwise. +func (r *RepoStats) GetRootRepos() int { + if r == nil || r.RootRepos == nil { + return 0 + } + return *r.RootRepos +} + +// GetTotalPushes returns the TotalPushes field if it's non-nil, zero value otherwise. +func (r *RepoStats) GetTotalPushes() int { + if r == nil || r.TotalPushes == nil { + return 0 + } + return *r.TotalPushes +} + +// GetTotalRepos returns the TotalRepos field if it's non-nil, zero value otherwise. +func (r *RepoStats) GetTotalRepos() int { + if r == nil || r.TotalRepos == nil { + return 0 + } + return *r.TotalRepos +} + +// GetTotalWikis returns the TotalWikis field if it's non-nil, zero value otherwise. +func (r *RepoStats) GetTotalWikis() int { + if r == nil || r.TotalWikis == nil { + return 0 + } + return *r.TotalWikis +} + +// GetContext returns the Context field if it's non-nil, zero value otherwise. +func (r *RepoStatus) GetContext() string { + if r == nil || r.Context == nil { + return "" + } + return *r.Context +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (r *RepoStatus) GetCreatedAt() time.Time { + if r == nil || r.CreatedAt == nil { + return time.Time{} + } + return *r.CreatedAt +} + +// GetCreator returns the Creator field. +func (r *RepoStatus) GetCreator() *User { + if r == nil { + return nil + } + return r.Creator +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (r *RepoStatus) GetDescription() string { + if r == nil || r.Description == nil { + return "" + } + return *r.Description +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (r *RepoStatus) GetID() int64 { + if r == nil || r.ID == nil { + return 0 + } + return *r.ID +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (r *RepoStatus) GetState() string { + if r == nil || r.State == nil { + return "" + } + return *r.State +} + +// GetTargetURL returns the TargetURL field if it's non-nil, zero value otherwise. +func (r *RepoStatus) GetTargetURL() string { + if r == nil || r.TargetURL == nil { + return "" + } + return *r.TargetURL +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (r *RepoStatus) GetUpdatedAt() time.Time { + if r == nil || r.UpdatedAt == nil { + return time.Time{} + } + return *r.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (r *RepoStatus) GetURL() string { + if r == nil || r.URL == nil { + return "" + } + return *r.URL +} + +// GetStrict returns the Strict field if it's non-nil, zero value otherwise. +func (r *RequiredStatusChecksRequest) GetStrict() bool { + if r == nil || r.Strict == nil { + return false + } + return *r.Strict +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (s *ServiceHook) GetName() string { + if s == nil || s.Name == nil { + return "" + } + return *s.Name +} + +// GetPayload returns the Payload field if it's non-nil, zero value otherwise. +func (s *SignatureVerification) GetPayload() string { + if s == nil || s.Payload == nil { + return "" + } + return *s.Payload +} + +// GetReason returns the Reason field if it's non-nil, zero value otherwise. +func (s *SignatureVerification) GetReason() string { + if s == nil || s.Reason == nil { + return "" + } + return *s.Reason +} + +// GetSignature returns the Signature field if it's non-nil, zero value otherwise. +func (s *SignatureVerification) GetSignature() string { + if s == nil || s.Signature == nil { + return "" + } + return *s.Signature +} + +// GetVerified returns the Verified field if it's non-nil, zero value otherwise. +func (s *SignatureVerification) GetVerified() bool { + if s == nil || s.Verified == nil { + return false + } + return *s.Verified +} + +// GetActor returns the Actor field. +func (s *Source) GetActor() *User { + if s == nil { + return nil + } + return s.Actor +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (s *Source) GetID() int64 { + if s == nil || s.ID == nil { + return 0 + } + return *s.ID +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (s *Source) GetURL() string { + if s == nil || s.URL == nil { + return "" + } + return *s.URL +} + +// GetEmail returns the Email field if it's non-nil, zero value otherwise. +func (s *SourceImportAuthor) GetEmail() string { + if s == nil || s.Email == nil { + return "" + } + return *s.Email +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (s *SourceImportAuthor) GetID() int64 { + if s == nil || s.ID == nil { + return 0 + } + return *s.ID +} + +// GetImportURL returns the ImportURL field if it's non-nil, zero value otherwise. +func (s *SourceImportAuthor) GetImportURL() string { + if s == nil || s.ImportURL == nil { + return "" + } + return *s.ImportURL +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (s *SourceImportAuthor) GetName() string { + if s == nil || s.Name == nil { + return "" + } + return *s.Name +} + +// GetRemoteID returns the RemoteID field if it's non-nil, zero value otherwise. +func (s *SourceImportAuthor) GetRemoteID() string { + if s == nil || s.RemoteID == nil { + return "" + } + return *s.RemoteID +} + +// GetRemoteName returns the RemoteName field if it's non-nil, zero value otherwise. +func (s *SourceImportAuthor) GetRemoteName() string { + if s == nil || s.RemoteName == nil { + return "" + } + return *s.RemoteName +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (s *SourceImportAuthor) GetURL() string { + if s == nil || s.URL == nil { + return "" + } + return *s.URL +} + +// GetStarredAt returns the StarredAt field if it's non-nil, zero value otherwise. +func (s *Stargazer) GetStarredAt() Timestamp { + if s == nil || s.StarredAt == nil { + return Timestamp{} + } + return *s.StarredAt +} + +// GetUser returns the User field. +func (s *Stargazer) GetUser() *User { + if s == nil { + return nil + } + return s.User +} + +// GetRepository returns the Repository field. +func (s *StarredRepository) GetRepository() *Repository { + if s == nil { + return nil + } + return s.Repository +} + +// GetStarredAt returns the StarredAt field if it's non-nil, zero value otherwise. +func (s *StarredRepository) GetStarredAt() Timestamp { + if s == nil || s.StarredAt == nil { + return Timestamp{} + } + return *s.StarredAt +} + +// GetCommit returns the Commit field. +func (s *StatusEvent) GetCommit() *RepositoryCommit { + if s == nil { + return nil + } + return s.Commit +} + +// GetContext returns the Context field if it's non-nil, zero value otherwise. +func (s *StatusEvent) GetContext() string { + if s == nil || s.Context == nil { + return "" + } + return *s.Context +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (s *StatusEvent) GetCreatedAt() Timestamp { + if s == nil || s.CreatedAt == nil { + return Timestamp{} + } + return *s.CreatedAt +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (s *StatusEvent) GetDescription() string { + if s == nil || s.Description == nil { + return "" + } + return *s.Description +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (s *StatusEvent) GetID() int64 { + if s == nil || s.ID == nil { + return 0 + } + return *s.ID +} + +// GetInstallation returns the Installation field. +func (s *StatusEvent) GetInstallation() *Installation { + if s == nil { + return nil + } + return s.Installation +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (s *StatusEvent) GetName() string { + if s == nil || s.Name == nil { + return "" + } + return *s.Name +} + +// GetRepo returns the Repo field. +func (s *StatusEvent) GetRepo() *Repository { + if s == nil { + return nil + } + return s.Repo +} + +// GetSender returns the Sender field. +func (s *StatusEvent) GetSender() *User { + if s == nil { + return nil + } + return s.Sender +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (s *StatusEvent) GetSHA() string { + if s == nil || s.SHA == nil { + return "" + } + return *s.SHA +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (s *StatusEvent) GetState() string { + if s == nil || s.State == nil { + return "" + } + return *s.State +} + +// GetTargetURL returns the TargetURL field if it's non-nil, zero value otherwise. +func (s *StatusEvent) GetTargetURL() string { + if s == nil || s.TargetURL == nil { + return "" + } + return *s.TargetURL +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (s *StatusEvent) GetUpdatedAt() Timestamp { + if s == nil || s.UpdatedAt == nil { + return Timestamp{} + } + return *s.UpdatedAt +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (s *Subscription) GetCreatedAt() Timestamp { + if s == nil || s.CreatedAt == nil { + return Timestamp{} + } + return *s.CreatedAt +} + +// GetIgnored returns the Ignored field if it's non-nil, zero value otherwise. +func (s *Subscription) GetIgnored() bool { + if s == nil || s.Ignored == nil { + return false + } + return *s.Ignored +} + +// GetReason returns the Reason field if it's non-nil, zero value otherwise. +func (s *Subscription) GetReason() string { + if s == nil || s.Reason == nil { + return "" + } + return *s.Reason +} + +// GetRepositoryURL returns the RepositoryURL field if it's non-nil, zero value otherwise. +func (s *Subscription) GetRepositoryURL() string { + if s == nil || s.RepositoryURL == nil { + return "" + } + return *s.RepositoryURL +} + +// GetSubscribed returns the Subscribed field if it's non-nil, zero value otherwise. +func (s *Subscription) GetSubscribed() bool { + if s == nil || s.Subscribed == nil { + return false + } + return *s.Subscribed +} + +// GetThreadURL returns the ThreadURL field if it's non-nil, zero value otherwise. +func (s *Subscription) GetThreadURL() string { + if s == nil || s.ThreadURL == nil { + return "" + } + return *s.ThreadURL +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (s *Subscription) GetURL() string { + if s == nil || s.URL == nil { + return "" + } + return *s.URL +} + +// GetMessage returns the Message field if it's non-nil, zero value otherwise. +func (t *Tag) GetMessage() string { + if t == nil || t.Message == nil { + return "" + } + return *t.Message +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (t *Tag) GetNodeID() string { + if t == nil || t.NodeID == nil { + return "" + } + return *t.NodeID +} + +// GetObject returns the Object field. +func (t *Tag) GetObject() *GitObject { + if t == nil { + return nil + } + return t.Object +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (t *Tag) GetSHA() string { + if t == nil || t.SHA == nil { + return "" + } + return *t.SHA +} + +// GetTag returns the Tag field if it's non-nil, zero value otherwise. +func (t *Tag) GetTag() string { + if t == nil || t.Tag == nil { + return "" + } + return *t.Tag +} + +// GetTagger returns the Tagger field. +func (t *Tag) GetTagger() *CommitAuthor { + if t == nil { + return nil + } + return t.Tagger +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (t *Tag) GetURL() string { + if t == nil || t.URL == nil { + return "" + } + return *t.URL +} + +// GetVerification returns the Verification field. +func (t *Tag) GetVerification() *SignatureVerification { + if t == nil { + return nil + } + return t.Verification +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (t *Team) GetDescription() string { + if t == nil || t.Description == nil { + return "" + } + return *t.Description +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (t *Team) GetID() int64 { + if t == nil || t.ID == nil { + return 0 + } + return *t.ID +} + +// GetLDAPDN returns the LDAPDN field if it's non-nil, zero value otherwise. +func (t *Team) GetLDAPDN() string { + if t == nil || t.LDAPDN == nil { + return "" + } + return *t.LDAPDN +} + +// GetMembersCount returns the MembersCount field if it's non-nil, zero value otherwise. +func (t *Team) GetMembersCount() int { + if t == nil || t.MembersCount == nil { + return 0 + } + return *t.MembersCount +} + +// GetMembersURL returns the MembersURL field if it's non-nil, zero value otherwise. +func (t *Team) GetMembersURL() string { + if t == nil || t.MembersURL == nil { + return "" + } + return *t.MembersURL +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (t *Team) GetName() string { + if t == nil || t.Name == nil { + return "" + } + return *t.Name +} + +// GetOrganization returns the Organization field. +func (t *Team) GetOrganization() *Organization { + if t == nil { + return nil + } + return t.Organization +} + +// GetParent returns the Parent field. +func (t *Team) GetParent() *Team { + if t == nil { + return nil + } + return t.Parent +} + +// GetPermission returns the Permission field if it's non-nil, zero value otherwise. +func (t *Team) GetPermission() string { + if t == nil || t.Permission == nil { + return "" + } + return *t.Permission +} + +// GetPrivacy returns the Privacy field if it's non-nil, zero value otherwise. +func (t *Team) GetPrivacy() string { + if t == nil || t.Privacy == nil { + return "" + } + return *t.Privacy +} + +// GetReposCount returns the ReposCount field if it's non-nil, zero value otherwise. +func (t *Team) GetReposCount() int { + if t == nil || t.ReposCount == nil { + return 0 + } + return *t.ReposCount +} + +// GetRepositoriesURL returns the RepositoriesURL field if it's non-nil, zero value otherwise. +func (t *Team) GetRepositoriesURL() string { + if t == nil || t.RepositoriesURL == nil { + return "" + } + return *t.RepositoriesURL +} + +// GetSlug returns the Slug field if it's non-nil, zero value otherwise. +func (t *Team) GetSlug() string { + if t == nil || t.Slug == nil { + return "" + } + return *t.Slug +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (t *Team) GetURL() string { + if t == nil || t.URL == nil { + return "" + } + return *t.URL +} + +// GetInstallation returns the Installation field. +func (t *TeamAddEvent) GetInstallation() *Installation { + if t == nil { + return nil + } + return t.Installation +} + +// GetOrg returns the Org field. +func (t *TeamAddEvent) GetOrg() *Organization { + if t == nil { + return nil + } + return t.Org +} + +// GetRepo returns the Repo field. +func (t *TeamAddEvent) GetRepo() *Repository { + if t == nil { + return nil + } + return t.Repo +} + +// GetSender returns the Sender field. +func (t *TeamAddEvent) GetSender() *User { + if t == nil { + return nil + } + return t.Sender +} + +// GetTeam returns the Team field. +func (t *TeamAddEvent) GetTeam() *Team { + if t == nil { + return nil + } + return t.Team +} + +// GetAuthor returns the Author field. +func (t *TeamDiscussion) GetAuthor() *User { + if t == nil { + return nil + } + return t.Author +} + +// GetBody returns the Body field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetBody() string { + if t == nil || t.Body == nil { + return "" + } + return *t.Body +} + +// GetBodyHTML returns the BodyHTML field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetBodyHTML() string { + if t == nil || t.BodyHTML == nil { + return "" + } + return *t.BodyHTML +} + +// GetBodyVersion returns the BodyVersion field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetBodyVersion() string { + if t == nil || t.BodyVersion == nil { + return "" + } + return *t.BodyVersion +} + +// GetCommentsCount returns the CommentsCount field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetCommentsCount() int { + if t == nil || t.CommentsCount == nil { + return 0 + } + return *t.CommentsCount +} + +// GetCommentsURL returns the CommentsURL field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetCommentsURL() string { + if t == nil || t.CommentsURL == nil { + return "" + } + return *t.CommentsURL +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetCreatedAt() Timestamp { + if t == nil || t.CreatedAt == nil { + return Timestamp{} + } + return *t.CreatedAt +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetHTMLURL() string { + if t == nil || t.HTMLURL == nil { + return "" + } + return *t.HTMLURL +} + +// GetLastEditedAt returns the LastEditedAt field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetLastEditedAt() Timestamp { + if t == nil || t.LastEditedAt == nil { + return Timestamp{} + } + return *t.LastEditedAt +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetNodeID() string { + if t == nil || t.NodeID == nil { + return "" + } + return *t.NodeID +} + +// GetNumber returns the Number field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetNumber() int { + if t == nil || t.Number == nil { + return 0 + } + return *t.Number +} + +// GetPinned returns the Pinned field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetPinned() bool { + if t == nil || t.Pinned == nil { + return false + } + return *t.Pinned +} + +// GetPrivate returns the Private field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetPrivate() bool { + if t == nil || t.Private == nil { + return false + } + return *t.Private +} + +// GetReactions returns the Reactions field. +func (t *TeamDiscussion) GetReactions() *Reactions { + if t == nil { + return nil + } + return t.Reactions +} + +// GetTeamURL returns the TeamURL field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetTeamURL() string { + if t == nil || t.TeamURL == nil { + return "" + } + return *t.TeamURL +} + +// GetTitle returns the Title field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetTitle() string { + if t == nil || t.Title == nil { + return "" + } + return *t.Title +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetUpdatedAt() Timestamp { + if t == nil || t.UpdatedAt == nil { + return Timestamp{} + } + return *t.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (t *TeamDiscussion) GetURL() string { + if t == nil || t.URL == nil { + return "" + } + return *t.URL +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (t *TeamEvent) GetAction() string { + if t == nil || t.Action == nil { + return "" + } + return *t.Action +} + +// GetChanges returns the Changes field. +func (t *TeamEvent) GetChanges() *TeamChange { + if t == nil { + return nil + } + return t.Changes +} + +// GetInstallation returns the Installation field. +func (t *TeamEvent) GetInstallation() *Installation { + if t == nil { + return nil + } + return t.Installation +} + +// GetOrg returns the Org field. +func (t *TeamEvent) GetOrg() *Organization { + if t == nil { + return nil + } + return t.Org +} + +// GetRepo returns the Repo field. +func (t *TeamEvent) GetRepo() *Repository { + if t == nil { + return nil + } + return t.Repo +} + +// GetSender returns the Sender field. +func (t *TeamEvent) GetSender() *User { + if t == nil { + return nil + } + return t.Sender +} + +// GetTeam returns the Team field. +func (t *TeamEvent) GetTeam() *Team { + if t == nil { + return nil + } + return t.Team +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (t *TeamLDAPMapping) GetDescription() string { + if t == nil || t.Description == nil { + return "" + } + return *t.Description +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (t *TeamLDAPMapping) GetID() int64 { + if t == nil || t.ID == nil { + return 0 + } + return *t.ID +} + +// GetLDAPDN returns the LDAPDN field if it's non-nil, zero value otherwise. +func (t *TeamLDAPMapping) GetLDAPDN() string { + if t == nil || t.LDAPDN == nil { + return "" + } + return *t.LDAPDN +} + +// GetMembersURL returns the MembersURL field if it's non-nil, zero value otherwise. +func (t *TeamLDAPMapping) GetMembersURL() string { + if t == nil || t.MembersURL == nil { + return "" + } + return *t.MembersURL +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (t *TeamLDAPMapping) GetName() string { + if t == nil || t.Name == nil { + return "" + } + return *t.Name +} + +// GetPermission returns the Permission field if it's non-nil, zero value otherwise. +func (t *TeamLDAPMapping) GetPermission() string { + if t == nil || t.Permission == nil { + return "" + } + return *t.Permission +} + +// GetPrivacy returns the Privacy field if it's non-nil, zero value otherwise. +func (t *TeamLDAPMapping) GetPrivacy() string { + if t == nil || t.Privacy == nil { + return "" + } + return *t.Privacy +} + +// GetRepositoriesURL returns the RepositoriesURL field if it's non-nil, zero value otherwise. +func (t *TeamLDAPMapping) GetRepositoriesURL() string { + if t == nil || t.RepositoriesURL == nil { + return "" + } + return *t.RepositoriesURL +} + +// GetSlug returns the Slug field if it's non-nil, zero value otherwise. +func (t *TeamLDAPMapping) GetSlug() string { + if t == nil || t.Slug == nil { + return "" + } + return *t.Slug +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (t *TeamLDAPMapping) GetURL() string { + if t == nil || t.URL == nil { + return "" + } + return *t.URL +} + +// GetFragment returns the Fragment field if it's non-nil, zero value otherwise. +func (t *TextMatch) GetFragment() string { + if t == nil || t.Fragment == nil { + return "" + } + return *t.Fragment +} + +// GetObjectType returns the ObjectType field if it's non-nil, zero value otherwise. +func (t *TextMatch) GetObjectType() string { + if t == nil || t.ObjectType == nil { + return "" + } + return *t.ObjectType +} + +// GetObjectURL returns the ObjectURL field if it's non-nil, zero value otherwise. +func (t *TextMatch) GetObjectURL() string { + if t == nil || t.ObjectURL == nil { + return "" + } + return *t.ObjectURL +} + +// GetProperty returns the Property field if it's non-nil, zero value otherwise. +func (t *TextMatch) GetProperty() string { + if t == nil || t.Property == nil { + return "" + } + return *t.Property +} + +// GetActor returns the Actor field. +func (t *Timeline) GetActor() *User { + if t == nil { + return nil + } + return t.Actor +} + +// GetAssignee returns the Assignee field. +func (t *Timeline) GetAssignee() *User { + if t == nil { + return nil + } + return t.Assignee +} + +// GetCommitID returns the CommitID field if it's non-nil, zero value otherwise. +func (t *Timeline) GetCommitID() string { + if t == nil || t.CommitID == nil { + return "" + } + return *t.CommitID +} + +// GetCommitURL returns the CommitURL field if it's non-nil, zero value otherwise. +func (t *Timeline) GetCommitURL() string { + if t == nil || t.CommitURL == nil { + return "" + } + return *t.CommitURL +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (t *Timeline) GetCreatedAt() time.Time { + if t == nil || t.CreatedAt == nil { + return time.Time{} + } + return *t.CreatedAt +} + +// GetEvent returns the Event field if it's non-nil, zero value otherwise. +func (t *Timeline) GetEvent() string { + if t == nil || t.Event == nil { + return "" + } + return *t.Event +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (t *Timeline) GetID() int64 { + if t == nil || t.ID == nil { + return 0 + } + return *t.ID +} + +// GetLabel returns the Label field. +func (t *Timeline) GetLabel() *Label { + if t == nil { + return nil + } + return t.Label +} + +// GetMilestone returns the Milestone field. +func (t *Timeline) GetMilestone() *Milestone { + if t == nil { + return nil + } + return t.Milestone +} + +// GetRename returns the Rename field. +func (t *Timeline) GetRename() *Rename { + if t == nil { + return nil + } + return t.Rename +} + +// GetSource returns the Source field. +func (t *Timeline) GetSource() *Source { + if t == nil { + return nil + } + return t.Source +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (t *Timeline) GetURL() string { + if t == nil || t.URL == nil { + return "" + } + return *t.URL +} + +// GetCount returns the Count field if it's non-nil, zero value otherwise. +func (t *TrafficClones) GetCount() int { + if t == nil || t.Count == nil { + return 0 + } + return *t.Count +} + +// GetUniques returns the Uniques field if it's non-nil, zero value otherwise. +func (t *TrafficClones) GetUniques() int { + if t == nil || t.Uniques == nil { + return 0 + } + return *t.Uniques +} + +// GetCount returns the Count field if it's non-nil, zero value otherwise. +func (t *TrafficData) GetCount() int { + if t == nil || t.Count == nil { + return 0 + } + return *t.Count +} + +// GetTimestamp returns the Timestamp field if it's non-nil, zero value otherwise. +func (t *TrafficData) GetTimestamp() Timestamp { + if t == nil || t.Timestamp == nil { + return Timestamp{} + } + return *t.Timestamp +} + +// GetUniques returns the Uniques field if it's non-nil, zero value otherwise. +func (t *TrafficData) GetUniques() int { + if t == nil || t.Uniques == nil { + return 0 + } + return *t.Uniques +} + +// GetCount returns the Count field if it's non-nil, zero value otherwise. +func (t *TrafficPath) GetCount() int { + if t == nil || t.Count == nil { + return 0 + } + return *t.Count +} + +// GetPath returns the Path field if it's non-nil, zero value otherwise. +func (t *TrafficPath) GetPath() string { + if t == nil || t.Path == nil { + return "" + } + return *t.Path +} + +// GetTitle returns the Title field if it's non-nil, zero value otherwise. +func (t *TrafficPath) GetTitle() string { + if t == nil || t.Title == nil { + return "" + } + return *t.Title +} + +// GetUniques returns the Uniques field if it's non-nil, zero value otherwise. +func (t *TrafficPath) GetUniques() int { + if t == nil || t.Uniques == nil { + return 0 + } + return *t.Uniques +} + +// GetCount returns the Count field if it's non-nil, zero value otherwise. +func (t *TrafficReferrer) GetCount() int { + if t == nil || t.Count == nil { + return 0 + } + return *t.Count +} + +// GetReferrer returns the Referrer field if it's non-nil, zero value otherwise. +func (t *TrafficReferrer) GetReferrer() string { + if t == nil || t.Referrer == nil { + return "" + } + return *t.Referrer +} + +// GetUniques returns the Uniques field if it's non-nil, zero value otherwise. +func (t *TrafficReferrer) GetUniques() int { + if t == nil || t.Uniques == nil { + return 0 + } + return *t.Uniques +} + +// GetCount returns the Count field if it's non-nil, zero value otherwise. +func (t *TrafficViews) GetCount() int { + if t == nil || t.Count == nil { + return 0 + } + return *t.Count +} + +// GetUniques returns the Uniques field if it's non-nil, zero value otherwise. +func (t *TrafficViews) GetUniques() int { + if t == nil || t.Uniques == nil { + return 0 + } + return *t.Uniques +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (t *Tree) GetSHA() string { + if t == nil || t.SHA == nil { + return "" + } + return *t.SHA +} + +// GetTruncated returns the Truncated field if it's non-nil, zero value otherwise. +func (t *Tree) GetTruncated() bool { + if t == nil || t.Truncated == nil { + return false + } + return *t.Truncated +} + +// GetContent returns the Content field if it's non-nil, zero value otherwise. +func (t *TreeEntry) GetContent() string { + if t == nil || t.Content == nil { + return "" + } + return *t.Content +} + +// GetMode returns the Mode field if it's non-nil, zero value otherwise. +func (t *TreeEntry) GetMode() string { + if t == nil || t.Mode == nil { + return "" + } + return *t.Mode +} + +// GetPath returns the Path field if it's non-nil, zero value otherwise. +func (t *TreeEntry) GetPath() string { + if t == nil || t.Path == nil { + return "" + } + return *t.Path +} + +// GetSHA returns the SHA field if it's non-nil, zero value otherwise. +func (t *TreeEntry) GetSHA() string { + if t == nil || t.SHA == nil { + return "" + } + return *t.SHA +} + +// GetSize returns the Size field if it's non-nil, zero value otherwise. +func (t *TreeEntry) GetSize() int { + if t == nil || t.Size == nil { + return 0 + } + return *t.Size +} + +// GetType returns the Type field if it's non-nil, zero value otherwise. +func (t *TreeEntry) GetType() string { + if t == nil || t.Type == nil { + return "" + } + return *t.Type +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (t *TreeEntry) GetURL() string { + if t == nil || t.URL == nil { + return "" + } + return *t.URL +} + +// GetCompletedAt returns the CompletedAt field if it's non-nil, zero value otherwise. +func (u *UpdateCheckRunOptions) GetCompletedAt() Timestamp { + if u == nil || u.CompletedAt == nil { + return Timestamp{} + } + return *u.CompletedAt +} + +// GetConclusion returns the Conclusion field if it's non-nil, zero value otherwise. +func (u *UpdateCheckRunOptions) GetConclusion() string { + if u == nil || u.Conclusion == nil { + return "" + } + return *u.Conclusion +} + +// GetDetailsURL returns the DetailsURL field if it's non-nil, zero value otherwise. +func (u *UpdateCheckRunOptions) GetDetailsURL() string { + if u == nil || u.DetailsURL == nil { + return "" + } + return *u.DetailsURL +} + +// GetExternalID returns the ExternalID field if it's non-nil, zero value otherwise. +func (u *UpdateCheckRunOptions) GetExternalID() string { + if u == nil || u.ExternalID == nil { + return "" + } + return *u.ExternalID +} + +// GetHeadBranch returns the HeadBranch field if it's non-nil, zero value otherwise. +func (u *UpdateCheckRunOptions) GetHeadBranch() string { + if u == nil || u.HeadBranch == nil { + return "" + } + return *u.HeadBranch +} + +// GetHeadSHA returns the HeadSHA field if it's non-nil, zero value otherwise. +func (u *UpdateCheckRunOptions) GetHeadSHA() string { + if u == nil || u.HeadSHA == nil { + return "" + } + return *u.HeadSHA +} + +// GetOutput returns the Output field. +func (u *UpdateCheckRunOptions) GetOutput() *CheckRunOutput { + if u == nil { + return nil + } + return u.Output +} + +// GetStatus returns the Status field if it's non-nil, zero value otherwise. +func (u *UpdateCheckRunOptions) GetStatus() string { + if u == nil || u.Status == nil { + return "" + } + return *u.Status +} + +// GetAvatarURL returns the AvatarURL field if it's non-nil, zero value otherwise. +func (u *User) GetAvatarURL() string { + if u == nil || u.AvatarURL == nil { + return "" + } + return *u.AvatarURL +} + +// GetBio returns the Bio field if it's non-nil, zero value otherwise. +func (u *User) GetBio() string { + if u == nil || u.Bio == nil { + return "" + } + return *u.Bio +} + +// GetBlog returns the Blog field if it's non-nil, zero value otherwise. +func (u *User) GetBlog() string { + if u == nil || u.Blog == nil { + return "" + } + return *u.Blog +} + +// GetCollaborators returns the Collaborators field if it's non-nil, zero value otherwise. +func (u *User) GetCollaborators() int { + if u == nil || u.Collaborators == nil { + return 0 + } + return *u.Collaborators +} + +// GetCompany returns the Company field if it's non-nil, zero value otherwise. +func (u *User) GetCompany() string { + if u == nil || u.Company == nil { + return "" + } + return *u.Company +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (u *User) GetCreatedAt() Timestamp { + if u == nil || u.CreatedAt == nil { + return Timestamp{} + } + return *u.CreatedAt +} + +// GetDiskUsage returns the DiskUsage field if it's non-nil, zero value otherwise. +func (u *User) GetDiskUsage() int { + if u == nil || u.DiskUsage == nil { + return 0 + } + return *u.DiskUsage +} + +// GetEmail returns the Email field if it's non-nil, zero value otherwise. +func (u *User) GetEmail() string { + if u == nil || u.Email == nil { + return "" + } + return *u.Email +} + +// GetEventsURL returns the EventsURL field if it's non-nil, zero value otherwise. +func (u *User) GetEventsURL() string { + if u == nil || u.EventsURL == nil { + return "" + } + return *u.EventsURL +} + +// GetFollowers returns the Followers field if it's non-nil, zero value otherwise. +func (u *User) GetFollowers() int { + if u == nil || u.Followers == nil { + return 0 + } + return *u.Followers +} + +// GetFollowersURL returns the FollowersURL field if it's non-nil, zero value otherwise. +func (u *User) GetFollowersURL() string { + if u == nil || u.FollowersURL == nil { + return "" + } + return *u.FollowersURL +} + +// GetFollowing returns the Following field if it's non-nil, zero value otherwise. +func (u *User) GetFollowing() int { + if u == nil || u.Following == nil { + return 0 + } + return *u.Following +} + +// GetFollowingURL returns the FollowingURL field if it's non-nil, zero value otherwise. +func (u *User) GetFollowingURL() string { + if u == nil || u.FollowingURL == nil { + return "" + } + return *u.FollowingURL +} + +// GetGistsURL returns the GistsURL field if it's non-nil, zero value otherwise. +func (u *User) GetGistsURL() string { + if u == nil || u.GistsURL == nil { + return "" + } + return *u.GistsURL +} + +// GetGravatarID returns the GravatarID field if it's non-nil, zero value otherwise. +func (u *User) GetGravatarID() string { + if u == nil || u.GravatarID == nil { + return "" + } + return *u.GravatarID +} + +// GetHireable returns the Hireable field if it's non-nil, zero value otherwise. +func (u *User) GetHireable() bool { + if u == nil || u.Hireable == nil { + return false + } + return *u.Hireable +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (u *User) GetHTMLURL() string { + if u == nil || u.HTMLURL == nil { + return "" + } + return *u.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (u *User) GetID() int64 { + if u == nil || u.ID == nil { + return 0 + } + return *u.ID +} + +// GetLocation returns the Location field if it's non-nil, zero value otherwise. +func (u *User) GetLocation() string { + if u == nil || u.Location == nil { + return "" + } + return *u.Location +} + +// GetLogin returns the Login field if it's non-nil, zero value otherwise. +func (u *User) GetLogin() string { + if u == nil || u.Login == nil { + return "" + } + return *u.Login +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (u *User) GetName() string { + if u == nil || u.Name == nil { + return "" + } + return *u.Name +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (u *User) GetNodeID() string { + if u == nil || u.NodeID == nil { + return "" + } + return *u.NodeID +} + +// GetOrganizationsURL returns the OrganizationsURL field if it's non-nil, zero value otherwise. +func (u *User) GetOrganizationsURL() string { + if u == nil || u.OrganizationsURL == nil { + return "" + } + return *u.OrganizationsURL +} + +// GetOwnedPrivateRepos returns the OwnedPrivateRepos field if it's non-nil, zero value otherwise. +func (u *User) GetOwnedPrivateRepos() int { + if u == nil || u.OwnedPrivateRepos == nil { + return 0 + } + return *u.OwnedPrivateRepos +} + +// GetPermissions returns the Permissions field if it's non-nil, zero value otherwise. +func (u *User) GetPermissions() map[string]bool { + if u == nil || u.Permissions == nil { + return map[string]bool{} + } + return *u.Permissions +} + +// GetPlan returns the Plan field. +func (u *User) GetPlan() *Plan { + if u == nil { + return nil + } + return u.Plan +} + +// GetPrivateGists returns the PrivateGists field if it's non-nil, zero value otherwise. +func (u *User) GetPrivateGists() int { + if u == nil || u.PrivateGists == nil { + return 0 + } + return *u.PrivateGists +} + +// GetPublicGists returns the PublicGists field if it's non-nil, zero value otherwise. +func (u *User) GetPublicGists() int { + if u == nil || u.PublicGists == nil { + return 0 + } + return *u.PublicGists +} + +// GetPublicRepos returns the PublicRepos field if it's non-nil, zero value otherwise. +func (u *User) GetPublicRepos() int { + if u == nil || u.PublicRepos == nil { + return 0 + } + return *u.PublicRepos +} + +// GetReceivedEventsURL returns the ReceivedEventsURL field if it's non-nil, zero value otherwise. +func (u *User) GetReceivedEventsURL() string { + if u == nil || u.ReceivedEventsURL == nil { + return "" + } + return *u.ReceivedEventsURL +} + +// GetReposURL returns the ReposURL field if it's non-nil, zero value otherwise. +func (u *User) GetReposURL() string { + if u == nil || u.ReposURL == nil { + return "" + } + return *u.ReposURL +} + +// GetSiteAdmin returns the SiteAdmin field if it's non-nil, zero value otherwise. +func (u *User) GetSiteAdmin() bool { + if u == nil || u.SiteAdmin == nil { + return false + } + return *u.SiteAdmin +} + +// GetStarredURL returns the StarredURL field if it's non-nil, zero value otherwise. +func (u *User) GetStarredURL() string { + if u == nil || u.StarredURL == nil { + return "" + } + return *u.StarredURL +} + +// GetSubscriptionsURL returns the SubscriptionsURL field if it's non-nil, zero value otherwise. +func (u *User) GetSubscriptionsURL() string { + if u == nil || u.SubscriptionsURL == nil { + return "" + } + return *u.SubscriptionsURL +} + +// GetSuspendedAt returns the SuspendedAt field if it's non-nil, zero value otherwise. +func (u *User) GetSuspendedAt() Timestamp { + if u == nil || u.SuspendedAt == nil { + return Timestamp{} + } + return *u.SuspendedAt +} + +// GetTotalPrivateRepos returns the TotalPrivateRepos field if it's non-nil, zero value otherwise. +func (u *User) GetTotalPrivateRepos() int { + if u == nil || u.TotalPrivateRepos == nil { + return 0 + } + return *u.TotalPrivateRepos +} + +// GetType returns the Type field if it's non-nil, zero value otherwise. +func (u *User) GetType() string { + if u == nil || u.Type == nil { + return "" + } + return *u.Type +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (u *User) GetUpdatedAt() Timestamp { + if u == nil || u.UpdatedAt == nil { + return Timestamp{} + } + return *u.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (u *User) GetURL() string { + if u == nil || u.URL == nil { + return "" + } + return *u.URL +} + +// GetMessage returns the Message field if it's non-nil, zero value otherwise. +func (u *UserContext) GetMessage() string { + if u == nil || u.Message == nil { + return "" + } + return *u.Message +} + +// GetOcticon returns the Octicon field if it's non-nil, zero value otherwise. +func (u *UserContext) GetOcticon() string { + if u == nil || u.Octicon == nil { + return "" + } + return *u.Octicon +} + +// GetEmail returns the Email field if it's non-nil, zero value otherwise. +func (u *UserEmail) GetEmail() string { + if u == nil || u.Email == nil { + return "" + } + return *u.Email +} + +// GetPrimary returns the Primary field if it's non-nil, zero value otherwise. +func (u *UserEmail) GetPrimary() bool { + if u == nil || u.Primary == nil { + return false + } + return *u.Primary +} + +// GetVerified returns the Verified field if it's non-nil, zero value otherwise. +func (u *UserEmail) GetVerified() bool { + if u == nil || u.Verified == nil { + return false + } + return *u.Verified +} + +// GetAvatarURL returns the AvatarURL field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetAvatarURL() string { + if u == nil || u.AvatarURL == nil { + return "" + } + return *u.AvatarURL +} + +// GetEventsURL returns the EventsURL field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetEventsURL() string { + if u == nil || u.EventsURL == nil { + return "" + } + return *u.EventsURL +} + +// GetFollowersURL returns the FollowersURL field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetFollowersURL() string { + if u == nil || u.FollowersURL == nil { + return "" + } + return *u.FollowersURL +} + +// GetFollowingURL returns the FollowingURL field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetFollowingURL() string { + if u == nil || u.FollowingURL == nil { + return "" + } + return *u.FollowingURL +} + +// GetGistsURL returns the GistsURL field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetGistsURL() string { + if u == nil || u.GistsURL == nil { + return "" + } + return *u.GistsURL +} + +// GetGravatarID returns the GravatarID field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetGravatarID() string { + if u == nil || u.GravatarID == nil { + return "" + } + return *u.GravatarID +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetID() int64 { + if u == nil || u.ID == nil { + return 0 + } + return *u.ID +} + +// GetLDAPDN returns the LDAPDN field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetLDAPDN() string { + if u == nil || u.LDAPDN == nil { + return "" + } + return *u.LDAPDN +} + +// GetLogin returns the Login field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetLogin() string { + if u == nil || u.Login == nil { + return "" + } + return *u.Login +} + +// GetOrganizationsURL returns the OrganizationsURL field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetOrganizationsURL() string { + if u == nil || u.OrganizationsURL == nil { + return "" + } + return *u.OrganizationsURL +} + +// GetReceivedEventsURL returns the ReceivedEventsURL field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetReceivedEventsURL() string { + if u == nil || u.ReceivedEventsURL == nil { + return "" + } + return *u.ReceivedEventsURL +} + +// GetReposURL returns the ReposURL field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetReposURL() string { + if u == nil || u.ReposURL == nil { + return "" + } + return *u.ReposURL +} + +// GetSiteAdmin returns the SiteAdmin field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetSiteAdmin() bool { + if u == nil || u.SiteAdmin == nil { + return false + } + return *u.SiteAdmin +} + +// GetStarredURL returns the StarredURL field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetStarredURL() string { + if u == nil || u.StarredURL == nil { + return "" + } + return *u.StarredURL +} + +// GetSubscriptionsURL returns the SubscriptionsURL field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetSubscriptionsURL() string { + if u == nil || u.SubscriptionsURL == nil { + return "" + } + return *u.SubscriptionsURL +} + +// GetType returns the Type field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetType() string { + if u == nil || u.Type == nil { + return "" + } + return *u.Type +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (u *UserLDAPMapping) GetURL() string { + if u == nil || u.URL == nil { + return "" + } + return *u.URL +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (u *UserMigration) GetCreatedAt() string { + if u == nil || u.CreatedAt == nil { + return "" + } + return *u.CreatedAt +} + +// GetExcludeAttachments returns the ExcludeAttachments field if it's non-nil, zero value otherwise. +func (u *UserMigration) GetExcludeAttachments() bool { + if u == nil || u.ExcludeAttachments == nil { + return false + } + return *u.ExcludeAttachments +} + +// GetGUID returns the GUID field if it's non-nil, zero value otherwise. +func (u *UserMigration) GetGUID() string { + if u == nil || u.GUID == nil { + return "" + } + return *u.GUID +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (u *UserMigration) GetID() int64 { + if u == nil || u.ID == nil { + return 0 + } + return *u.ID +} + +// GetLockRepositories returns the LockRepositories field if it's non-nil, zero value otherwise. +func (u *UserMigration) GetLockRepositories() bool { + if u == nil || u.LockRepositories == nil { + return false + } + return *u.LockRepositories +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (u *UserMigration) GetState() string { + if u == nil || u.State == nil { + return "" + } + return *u.State +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (u *UserMigration) GetUpdatedAt() string { + if u == nil || u.UpdatedAt == nil { + return "" + } + return *u.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (u *UserMigration) GetURL() string { + if u == nil || u.URL == nil { + return "" + } + return *u.URL +} + +// GetIncompleteResults returns the IncompleteResults field if it's non-nil, zero value otherwise. +func (u *UsersSearchResult) GetIncompleteResults() bool { + if u == nil || u.IncompleteResults == nil { + return false + } + return *u.IncompleteResults +} + +// GetTotal returns the Total field if it's non-nil, zero value otherwise. +func (u *UsersSearchResult) GetTotal() int { + if u == nil || u.Total == nil { + return 0 + } + return *u.Total +} + +// GetAdminUsers returns the AdminUsers field if it's non-nil, zero value otherwise. +func (u *UserStats) GetAdminUsers() int { + if u == nil || u.AdminUsers == nil { + return 0 + } + return *u.AdminUsers +} + +// GetSuspendedUsers returns the SuspendedUsers field if it's non-nil, zero value otherwise. +func (u *UserStats) GetSuspendedUsers() int { + if u == nil || u.SuspendedUsers == nil { + return 0 + } + return *u.SuspendedUsers +} + +// GetTotalUsers returns the TotalUsers field if it's non-nil, zero value otherwise. +func (u *UserStats) GetTotalUsers() int { + if u == nil || u.TotalUsers == nil { + return 0 + } + return *u.TotalUsers +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (w *WatchEvent) GetAction() string { + if w == nil || w.Action == nil { + return "" + } + return *w.Action +} + +// GetInstallation returns the Installation field. +func (w *WatchEvent) GetInstallation() *Installation { + if w == nil { + return nil + } + return w.Installation +} + +// GetRepo returns the Repo field. +func (w *WatchEvent) GetRepo() *Repository { + if w == nil { + return nil + } + return w.Repo +} + +// GetSender returns the Sender field. +func (w *WatchEvent) GetSender() *User { + if w == nil { + return nil + } + return w.Sender +} + +// GetEmail returns the Email field if it's non-nil, zero value otherwise. +func (w *WebHookAuthor) GetEmail() string { + if w == nil || w.Email == nil { + return "" + } + return *w.Email +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (w *WebHookAuthor) GetName() string { + if w == nil || w.Name == nil { + return "" + } + return *w.Name +} + +// GetUsername returns the Username field if it's non-nil, zero value otherwise. +func (w *WebHookAuthor) GetUsername() string { + if w == nil || w.Username == nil { + return "" + } + return *w.Username +} + +// GetAuthor returns the Author field. +func (w *WebHookCommit) GetAuthor() *WebHookAuthor { + if w == nil { + return nil + } + return w.Author +} + +// GetCommitter returns the Committer field. +func (w *WebHookCommit) GetCommitter() *WebHookAuthor { + if w == nil { + return nil + } + return w.Committer +} + +// GetDistinct returns the Distinct field if it's non-nil, zero value otherwise. +func (w *WebHookCommit) GetDistinct() bool { + if w == nil || w.Distinct == nil { + return false + } + return *w.Distinct +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (w *WebHookCommit) GetID() string { + if w == nil || w.ID == nil { + return "" + } + return *w.ID +} + +// GetMessage returns the Message field if it's non-nil, zero value otherwise. +func (w *WebHookCommit) GetMessage() string { + if w == nil || w.Message == nil { + return "" + } + return *w.Message +} + +// GetTimestamp returns the Timestamp field if it's non-nil, zero value otherwise. +func (w *WebHookCommit) GetTimestamp() time.Time { + if w == nil || w.Timestamp == nil { + return time.Time{} + } + return *w.Timestamp +} + +// GetAfter returns the After field if it's non-nil, zero value otherwise. +func (w *WebHookPayload) GetAfter() string { + if w == nil || w.After == nil { + return "" + } + return *w.After +} + +// GetBefore returns the Before field if it's non-nil, zero value otherwise. +func (w *WebHookPayload) GetBefore() string { + if w == nil || w.Before == nil { + return "" + } + return *w.Before +} + +// GetCompare returns the Compare field if it's non-nil, zero value otherwise. +func (w *WebHookPayload) GetCompare() string { + if w == nil || w.Compare == nil { + return "" + } + return *w.Compare +} + +// GetCreated returns the Created field if it's non-nil, zero value otherwise. +func (w *WebHookPayload) GetCreated() bool { + if w == nil || w.Created == nil { + return false + } + return *w.Created +} + +// GetDeleted returns the Deleted field if it's non-nil, zero value otherwise. +func (w *WebHookPayload) GetDeleted() bool { + if w == nil || w.Deleted == nil { + return false + } + return *w.Deleted +} + +// GetForced returns the Forced field if it's non-nil, zero value otherwise. +func (w *WebHookPayload) GetForced() bool { + if w == nil || w.Forced == nil { + return false + } + return *w.Forced +} + +// GetHeadCommit returns the HeadCommit field. +func (w *WebHookPayload) GetHeadCommit() *WebHookCommit { + if w == nil { + return nil + } + return w.HeadCommit +} + +// GetPusher returns the Pusher field. +func (w *WebHookPayload) GetPusher() *User { + if w == nil { + return nil + } + return w.Pusher +} + +// GetRef returns the Ref field if it's non-nil, zero value otherwise. +func (w *WebHookPayload) GetRef() string { + if w == nil || w.Ref == nil { + return "" + } + return *w.Ref +} + +// GetRepo returns the Repo field. +func (w *WebHookPayload) GetRepo() *Repository { + if w == nil { + return nil + } + return w.Repo +} + +// GetSender returns the Sender field. +func (w *WebHookPayload) GetSender() *User { + if w == nil { + return nil + } + return w.Sender +} + +// GetTotal returns the Total field if it's non-nil, zero value otherwise. +func (w *WeeklyCommitActivity) GetTotal() int { + if w == nil || w.Total == nil { + return 0 + } + return *w.Total +} + +// GetWeek returns the Week field if it's non-nil, zero value otherwise. +func (w *WeeklyCommitActivity) GetWeek() Timestamp { + if w == nil || w.Week == nil { + return Timestamp{} + } + return *w.Week +} + +// GetAdditions returns the Additions field if it's non-nil, zero value otherwise. +func (w *WeeklyStats) GetAdditions() int { + if w == nil || w.Additions == nil { + return 0 + } + return *w.Additions +} + +// GetCommits returns the Commits field if it's non-nil, zero value otherwise. +func (w *WeeklyStats) GetCommits() int { + if w == nil || w.Commits == nil { + return 0 + } + return *w.Commits +} + +// GetDeletions returns the Deletions field if it's non-nil, zero value otherwise. +func (w *WeeklyStats) GetDeletions() int { + if w == nil || w.Deletions == nil { + return 0 + } + return *w.Deletions +} + +// GetWeek returns the Week field if it's non-nil, zero value otherwise. +func (w *WeeklyStats) GetWeek() Timestamp { + if w == nil || w.Week == nil { + return Timestamp{} + } + return *w.Week +} diff --git a/vendor/github.com/google/go-github/github/github.go b/vendor/github.com/google/go-github/github/github.go new file mode 100644 index 0000000..af1f1b2 --- /dev/null +++ b/vendor/github.com/google/go-github/github/github.go @@ -0,0 +1,996 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:generate go run gen-accessors.go + +package github + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/google/go-querystring/query" +) + +const ( + defaultBaseURL = "https://api.github.com/" + uploadBaseURL = "https://uploads.github.com/" + userAgent = "go-github" + + headerRateLimit = "X-RateLimit-Limit" + headerRateRemaining = "X-RateLimit-Remaining" + headerRateReset = "X-RateLimit-Reset" + headerOTP = "X-GitHub-OTP" + + mediaTypeV3 = "application/vnd.github.v3+json" + defaultMediaType = "application/octet-stream" + mediaTypeV3SHA = "application/vnd.github.v3.sha" + mediaTypeV3Diff = "application/vnd.github.v3.diff" + mediaTypeV3Patch = "application/vnd.github.v3.patch" + mediaTypeOrgPermissionRepo = "application/vnd.github.v3.repository+json" + + // Media Type values to access preview APIs + + // https://developer.github.com/changes/2014-12-09-new-attributes-for-stars-api/ + mediaTypeStarringPreview = "application/vnd.github.v3.star+json" + + // https://help.github.com/enterprise/2.4/admin/guides/migrations/exporting-the-github-com-organization-s-repositories/ + mediaTypeMigrationsPreview = "application/vnd.github.wyandotte-preview+json" + + // https://developer.github.com/changes/2016-04-06-deployment-and-deployment-status-enhancements/ + mediaTypeDeploymentStatusPreview = "application/vnd.github.ant-man-preview+json" + + // https://developer.github.com/changes/2016-02-19-source-import-preview-api/ + mediaTypeImportPreview = "application/vnd.github.barred-rock-preview" + + // https://developer.github.com/changes/2016-05-12-reactions-api-preview/ + mediaTypeReactionsPreview = "application/vnd.github.squirrel-girl-preview" + + // https://developer.github.com/changes/2016-04-04-git-signing-api-preview/ + mediaTypeGitSigningPreview = "application/vnd.github.cryptographer-preview+json" + + // https://developer.github.com/changes/2016-05-23-timeline-preview-api/ + mediaTypeTimelinePreview = "application/vnd.github.mockingbird-preview+json" + + // https://developer.github.com/changes/2016-06-14-repository-invitations/ + mediaTypeRepositoryInvitationsPreview = "application/vnd.github.swamp-thing-preview+json" + + // https://developer.github.com/changes/2016-07-06-github-pages-preiew-api/ + mediaTypePagesPreview = "application/vnd.github.mister-fantastic-preview+json" + + // https://developer.github.com/changes/2016-09-14-projects-api/ + mediaTypeProjectsPreview = "application/vnd.github.inertia-preview+json" + + // https://developer.github.com/changes/2016-09-14-Integrations-Early-Access/ + mediaTypeIntegrationPreview = "application/vnd.github.machine-man-preview+json" + + // https://developer.github.com/changes/2017-01-05-commit-search-api/ + mediaTypeCommitSearchPreview = "application/vnd.github.cloak-preview+json" + + // https://developer.github.com/changes/2017-02-28-user-blocking-apis-and-webhook/ + mediaTypeBlockUsersPreview = "application/vnd.github.giant-sentry-fist-preview+json" + + // https://developer.github.com/changes/2017-02-09-community-health/ + mediaTypeRepositoryCommunityHealthMetricsPreview = "application/vnd.github.black-panther-preview+json" + + // https://developer.github.com/changes/2017-05-23-coc-api/ + mediaTypeCodesOfConductPreview = "application/vnd.github.scarlet-witch-preview+json" + + // https://developer.github.com/changes/2017-07-17-update-topics-on-repositories/ + mediaTypeTopicsPreview = "application/vnd.github.mercy-preview+json" + + // https://developer.github.com/changes/2017-08-30-preview-nested-teams/ + mediaTypeNestedTeamsPreview = "application/vnd.github.hellcat-preview+json" + + // https://developer.github.com/changes/2017-11-09-repository-transfer-api-preview/ + mediaTypeRepositoryTransferPreview = "application/vnd.github.nightshade-preview+json" + + // https://developer.github.com/changes/2018-01-25-organization-invitation-api-preview/ + mediaTypeOrganizationInvitationPreview = "application/vnd.github.dazzler-preview+json" + + // https://developer.github.com/changes/2018-03-16-protected-branches-required-approving-reviews/ + mediaTypeRequiredApprovingReviewsPreview = "application/vnd.github.luke-cage-preview+json" + + // https://developer.github.com/changes/2018-02-22-label-description-search-preview/ + mediaTypeLabelDescriptionSearchPreview = "application/vnd.github.symmetra-preview+json" + + // https://developer.github.com/changes/2018-02-07-team-discussions-api/ + mediaTypeTeamDiscussionsPreview = "application/vnd.github.echo-preview+json" + + // https://developer.github.com/changes/2018-03-21-hovercard-api-preview/ + mediaTypeHovercardPreview = "application/vnd.github.hagar-preview+json" + + // https://developer.github.com/changes/2018-01-10-lock-reason-api-preview/ + mediaTypeLockReasonPreview = "application/vnd.github.sailor-v-preview+json" + + // https://developer.github.com/changes/2018-05-07-new-checks-api-public-beta/ + mediaTypeCheckRunsPreview = "application/vnd.github.antiope-preview+json" + + // https://developer.github.com/enterprise/2.13/v3/repos/pre_receive_hooks/ + mediaTypePreReceiveHooksPreview = "application/vnd.github.eye-scream-preview" +) + +// A Client manages communication with the GitHub API. +type Client struct { + clientMu sync.Mutex // clientMu protects the client during calls that modify the CheckRedirect func. + client *http.Client // HTTP client used to communicate with the API. + + // Base URL for API requests. Defaults to the public GitHub API, but can be + // set to a domain endpoint to use with GitHub Enterprise. BaseURL should + // always be specified with a trailing slash. + BaseURL *url.URL + + // Base URL for uploading files. + UploadURL *url.URL + + // User agent used when communicating with the GitHub API. + UserAgent string + + rateMu sync.Mutex + rateLimits [categories]Rate // Rate limits for the client as determined by the most recent API calls. + + common service // Reuse a single struct instead of allocating one for each service on the heap. + + // Services used for talking to different parts of the GitHub API. + Activity *ActivityService + Admin *AdminService + Apps *AppsService + Authorizations *AuthorizationsService + Checks *ChecksService + Gists *GistsService + Git *GitService + Gitignores *GitignoresService + Issues *IssuesService + Licenses *LicensesService + Marketplace *MarketplaceService + Migrations *MigrationService + Organizations *OrganizationsService + Projects *ProjectsService + PullRequests *PullRequestsService + Reactions *ReactionsService + Repositories *RepositoriesService + Search *SearchService + Teams *TeamsService + Users *UsersService +} + +type service struct { + client *Client +} + +// ListOptions specifies the optional parameters to various List methods that +// support pagination. +type ListOptions struct { + // For paginated result sets, page of results to retrieve. + Page int `url:"page,omitempty"` + + // For paginated result sets, the number of results to include per page. + PerPage int `url:"per_page,omitempty"` +} + +// UploadOptions specifies the parameters to methods that support uploads. +type UploadOptions struct { + Name string `url:"name,omitempty"` +} + +// RawType represents type of raw format of a request instead of JSON. +type RawType uint8 + +const ( + // Diff format. + Diff RawType = 1 + iota + // Patch format. + Patch +) + +// RawOptions specifies parameters when user wants to get raw format of +// a response instead of JSON. +type RawOptions struct { + Type RawType +} + +// addOptions adds the parameters in opt as URL query parameters to s. opt +// must be a struct whose fields may contain "url" tags. +func addOptions(s string, opt interface{}) (string, error) { + v := reflect.ValueOf(opt) + if v.Kind() == reflect.Ptr && v.IsNil() { + return s, nil + } + + u, err := url.Parse(s) + if err != nil { + return s, err + } + + qs, err := query.Values(opt) + if err != nil { + return s, err + } + + u.RawQuery = qs.Encode() + return u.String(), nil +} + +// NewClient returns a new GitHub API client. If a nil httpClient is +// provided, http.DefaultClient will be used. To use API methods which require +// authentication, provide an http.Client that will perform the authentication +// for you (such as that provided by the golang.org/x/oauth2 library). +func NewClient(httpClient *http.Client) *Client { + if httpClient == nil { + httpClient = http.DefaultClient + } + baseURL, _ := url.Parse(defaultBaseURL) + uploadURL, _ := url.Parse(uploadBaseURL) + + c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent, UploadURL: uploadURL} + c.common.client = c + c.Activity = (*ActivityService)(&c.common) + c.Admin = (*AdminService)(&c.common) + c.Apps = (*AppsService)(&c.common) + c.Authorizations = (*AuthorizationsService)(&c.common) + c.Checks = (*ChecksService)(&c.common) + c.Gists = (*GistsService)(&c.common) + c.Git = (*GitService)(&c.common) + c.Gitignores = (*GitignoresService)(&c.common) + c.Issues = (*IssuesService)(&c.common) + c.Licenses = (*LicensesService)(&c.common) + c.Marketplace = &MarketplaceService{client: c} + c.Migrations = (*MigrationService)(&c.common) + c.Organizations = (*OrganizationsService)(&c.common) + c.Projects = (*ProjectsService)(&c.common) + c.PullRequests = (*PullRequestsService)(&c.common) + c.Reactions = (*ReactionsService)(&c.common) + c.Repositories = (*RepositoriesService)(&c.common) + c.Search = (*SearchService)(&c.common) + c.Teams = (*TeamsService)(&c.common) + c.Users = (*UsersService)(&c.common) + return c +} + +// NewEnterpriseClient returns a new GitHub API client with provided +// base URL and upload URL (often the same URL). +// If either URL does not have a trailing slash, one is added automatically. +// If a nil httpClient is provided, http.DefaultClient will be used. +// +// Note that NewEnterpriseClient is a convenience helper only; +// its behavior is equivalent to using NewClient, followed by setting +// the BaseURL and UploadURL fields. +func NewEnterpriseClient(baseURL, uploadURL string, httpClient *http.Client) (*Client, error) { + baseEndpoint, err := url.Parse(baseURL) + if err != nil { + return nil, err + } + if !strings.HasSuffix(baseEndpoint.Path, "/") { + baseEndpoint.Path += "/" + } + + uploadEndpoint, err := url.Parse(uploadURL) + if err != nil { + return nil, err + } + if !strings.HasSuffix(uploadEndpoint.Path, "/") { + uploadEndpoint.Path += "/" + } + + c := NewClient(httpClient) + c.BaseURL = baseEndpoint + c.UploadURL = uploadEndpoint + return c, nil +} + +// NewRequest creates an API request. A relative URL can be provided in urlStr, +// in which case it is resolved relative to the BaseURL of the Client. +// Relative URLs should always be specified without a preceding slash. If +// specified, the value pointed to by body is JSON encoded and included as the +// request body. +func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) { + if !strings.HasSuffix(c.BaseURL.Path, "/") { + return nil, fmt.Errorf("BaseURL must have a trailing slash, but %q does not", c.BaseURL) + } + u, err := c.BaseURL.Parse(urlStr) + if err != nil { + return nil, err + } + + var buf io.ReadWriter + if body != nil { + buf = new(bytes.Buffer) + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + err := enc.Encode(body) + if err != nil { + return nil, err + } + } + + req, err := http.NewRequest(method, u.String(), buf) + if err != nil { + return nil, err + } + + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + req.Header.Set("Accept", mediaTypeV3) + if c.UserAgent != "" { + req.Header.Set("User-Agent", c.UserAgent) + } + return req, nil +} + +// NewUploadRequest creates an upload request. A relative URL can be provided in +// urlStr, in which case it is resolved relative to the UploadURL of the Client. +// Relative URLs should always be specified without a preceding slash. +func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string) (*http.Request, error) { + if !strings.HasSuffix(c.UploadURL.Path, "/") { + return nil, fmt.Errorf("UploadURL must have a trailing slash, but %q does not", c.UploadURL) + } + u, err := c.UploadURL.Parse(urlStr) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", u.String(), reader) + if err != nil { + return nil, err + } + req.ContentLength = size + + if mediaType == "" { + mediaType = defaultMediaType + } + req.Header.Set("Content-Type", mediaType) + req.Header.Set("Accept", mediaTypeV3) + req.Header.Set("User-Agent", c.UserAgent) + return req, nil +} + +// Response is a GitHub API response. This wraps the standard http.Response +// returned from GitHub and provides convenient access to things like +// pagination links. +type Response struct { + *http.Response + + // These fields provide the page values for paginating through a set of + // results. Any or all of these may be set to the zero value for + // responses that are not part of a paginated set, or for which there + // are no additional pages. + + NextPage int + PrevPage int + FirstPage int + LastPage int + + // Explicitly specify the Rate type so Rate's String() receiver doesn't + // propagate to Response. + Rate Rate +} + +// newResponse creates a new Response for the provided http.Response. +// r must not be nil. +func newResponse(r *http.Response) *Response { + response := &Response{Response: r} + response.populatePageValues() + response.Rate = parseRate(r) + return response +} + +// populatePageValues parses the HTTP Link response headers and populates the +// various pagination link values in the Response. +func (r *Response) populatePageValues() { + if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 { + for _, link := range strings.Split(links[0], ",") { + segments := strings.Split(strings.TrimSpace(link), ";") + + // link must at least have href and rel + if len(segments) < 2 { + continue + } + + // ensure href is properly formatted + if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") { + continue + } + + // try to pull out page parameter + url, err := url.Parse(segments[0][1 : len(segments[0])-1]) + if err != nil { + continue + } + page := url.Query().Get("page") + if page == "" { + continue + } + + for _, segment := range segments[1:] { + switch strings.TrimSpace(segment) { + case `rel="next"`: + r.NextPage, _ = strconv.Atoi(page) + case `rel="prev"`: + r.PrevPage, _ = strconv.Atoi(page) + case `rel="first"`: + r.FirstPage, _ = strconv.Atoi(page) + case `rel="last"`: + r.LastPage, _ = strconv.Atoi(page) + } + + } + } + } +} + +// parseRate parses the rate related headers. +func parseRate(r *http.Response) Rate { + var rate Rate + if limit := r.Header.Get(headerRateLimit); limit != "" { + rate.Limit, _ = strconv.Atoi(limit) + } + if remaining := r.Header.Get(headerRateRemaining); remaining != "" { + rate.Remaining, _ = strconv.Atoi(remaining) + } + if reset := r.Header.Get(headerRateReset); reset != "" { + if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 { + rate.Reset = Timestamp{time.Unix(v, 0)} + } + } + return rate +} + +// Do sends an API request and returns the API response. The API response is +// JSON decoded and stored in the value pointed to by v, or returned as an +// error if an API error has occurred. If v implements the io.Writer +// interface, the raw response body will be written to v, without attempting to +// first decode it. If rate limit is exceeded and reset time is in the future, +// Do returns *RateLimitError immediately without making a network API call. +// +// The provided ctx must be non-nil. If it is canceled or times out, +// ctx.Err() will be returned. +func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) { + req = withContext(ctx, req) + + rateLimitCategory := category(req.URL.Path) + + // If we've hit rate limit, don't make further requests before Reset time. + if err := c.checkRateLimitBeforeDo(req, rateLimitCategory); err != nil { + return &Response{ + Response: err.Response, + Rate: err.Rate, + }, err + } + + resp, err := c.client.Do(req) + if err != nil { + // If we got an error, and the context has been canceled, + // the context's error is probably more useful. + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + // If the error type is *url.Error, sanitize its URL before returning. + if e, ok := err.(*url.Error); ok { + if url, err := url.Parse(e.URL); err == nil { + e.URL = sanitizeURL(url).String() + return nil, e + } + } + + return nil, err + } + defer resp.Body.Close() + + response := newResponse(resp) + + c.rateMu.Lock() + c.rateLimits[rateLimitCategory] = response.Rate + c.rateMu.Unlock() + + err = CheckResponse(resp) + if err != nil { + // Even though there was an error, we still return the response + // in case the caller wants to inspect it further. + // However, if the error is AcceptedError, decode it below before + // returning from this function and closing the response body. + if _, ok := err.(*AcceptedError); !ok { + return response, err + } + } + + if v != nil { + if w, ok := v.(io.Writer); ok { + io.Copy(w, resp.Body) + } else { + decErr := json.NewDecoder(resp.Body).Decode(v) + if decErr == io.EOF { + decErr = nil // ignore EOF errors caused by empty response body + } + if decErr != nil { + err = decErr + } + } + } + + return response, err +} + +// checkRateLimitBeforeDo does not make any network calls, but uses existing knowledge from +// current client state in order to quickly check if *RateLimitError can be immediately returned +// from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily. +// Otherwise it returns nil, and Client.Do should proceed normally. +func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory rateLimitCategory) *RateLimitError { + c.rateMu.Lock() + rate := c.rateLimits[rateLimitCategory] + c.rateMu.Unlock() + if !rate.Reset.Time.IsZero() && rate.Remaining == 0 && time.Now().Before(rate.Reset.Time) { + // Create a fake response. + resp := &http.Response{ + Status: http.StatusText(http.StatusForbidden), + StatusCode: http.StatusForbidden, + Request: req, + Header: make(http.Header), + Body: ioutil.NopCloser(strings.NewReader("")), + } + return &RateLimitError{ + Rate: rate, + Response: resp, + Message: fmt.Sprintf("API rate limit of %v still exceeded until %v, not making remote request.", rate.Limit, rate.Reset.Time), + } + } + + return nil +} + +/* +An ErrorResponse reports one or more errors caused by an API request. + +GitHub API docs: https://developer.github.com/v3/#client-errors +*/ +type ErrorResponse struct { + Response *http.Response // HTTP response that caused this error + Message string `json:"message"` // error message + Errors []Error `json:"errors"` // more detail on individual errors + // Block is only populated on certain types of errors such as code 451. + // See https://developer.github.com/changes/2016-03-17-the-451-status-code-is-now-supported/ + // for more information. + Block *struct { + Reason string `json:"reason,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + } `json:"block,omitempty"` + // Most errors will also include a documentation_url field pointing + // to some content that might help you resolve the error, see + // https://developer.github.com/v3/#client-errors + DocumentationURL string `json:"documentation_url,omitempty"` +} + +func (r *ErrorResponse) Error() string { + return fmt.Sprintf("%v %v: %d %v %+v", + r.Response.Request.Method, sanitizeURL(r.Response.Request.URL), + r.Response.StatusCode, r.Message, r.Errors) +} + +// TwoFactorAuthError occurs when using HTTP Basic Authentication for a user +// that has two-factor authentication enabled. The request can be reattempted +// by providing a one-time password in the request. +type TwoFactorAuthError ErrorResponse + +func (r *TwoFactorAuthError) Error() string { return (*ErrorResponse)(r).Error() } + +// RateLimitError occurs when GitHub returns 403 Forbidden response with a rate limit +// remaining value of 0, and error message starts with "API rate limit exceeded for ". +type RateLimitError struct { + Rate Rate // Rate specifies last known rate limit for the client + Response *http.Response // HTTP response that caused this error + Message string `json:"message"` // error message +} + +func (r *RateLimitError) Error() string { + return fmt.Sprintf("%v %v: %d %v %v", + r.Response.Request.Method, sanitizeURL(r.Response.Request.URL), + r.Response.StatusCode, r.Message, formatRateReset(r.Rate.Reset.Time.Sub(time.Now()))) +} + +// AcceptedError occurs when GitHub returns 202 Accepted response with an +// empty body, which means a job was scheduled on the GitHub side to process +// the information needed and cache it. +// Technically, 202 Accepted is not a real error, it's just used to +// indicate that results are not ready yet, but should be available soon. +// The request can be repeated after some time. +type AcceptedError struct{} + +func (*AcceptedError) Error() string { + return "job scheduled on GitHub side; try again later" +} + +// AbuseRateLimitError occurs when GitHub returns 403 Forbidden response with the +// "documentation_url" field value equal to "https://developer.github.com/v3/#abuse-rate-limits". +type AbuseRateLimitError struct { + Response *http.Response // HTTP response that caused this error + Message string `json:"message"` // error message + + // RetryAfter is provided with some abuse rate limit errors. If present, + // it is the amount of time that the client should wait before retrying. + // Otherwise, the client should try again later (after an unspecified amount of time). + RetryAfter *time.Duration +} + +func (r *AbuseRateLimitError) Error() string { + return fmt.Sprintf("%v %v: %d %v", + r.Response.Request.Method, sanitizeURL(r.Response.Request.URL), + r.Response.StatusCode, r.Message) +} + +// sanitizeURL redacts the client_secret parameter from the URL which may be +// exposed to the user. +func sanitizeURL(uri *url.URL) *url.URL { + if uri == nil { + return nil + } + params := uri.Query() + if len(params.Get("client_secret")) > 0 { + params.Set("client_secret", "REDACTED") + uri.RawQuery = params.Encode() + } + return uri +} + +/* +An Error reports more details on an individual error in an ErrorResponse. +These are the possible validation error codes: + + missing: + resource does not exist + missing_field: + a required field on a resource has not been set + invalid: + the formatting of a field is invalid + already_exists: + another resource has the same valid as this field + custom: + some resources return this (e.g. github.User.CreateKey()), additional + information is set in the Message field of the Error + +GitHub API docs: https://developer.github.com/v3/#client-errors +*/ +type Error struct { + Resource string `json:"resource"` // resource on which the error occurred + Field string `json:"field"` // field on which the error occurred + Code string `json:"code"` // validation error code + Message string `json:"message"` // Message describing the error. Errors with Code == "custom" will always have this set. +} + +func (e *Error) Error() string { + return fmt.Sprintf("%v error caused by %v field on %v resource", + e.Code, e.Field, e.Resource) +} + +// CheckResponse checks the API response for errors, and returns them if +// present. A response is considered an error if it has a status code outside +// the 200 range or equal to 202 Accepted. +// API error responses are expected to have either no response +// body, or a JSON response body that maps to ErrorResponse. Any other +// response body will be silently ignored. +// +// The error type will be *RateLimitError for rate limit exceeded errors, +// *AcceptedError for 202 Accepted status codes, +// and *TwoFactorAuthError for two-factor authentication errors. +func CheckResponse(r *http.Response) error { + if r.StatusCode == http.StatusAccepted { + return &AcceptedError{} + } + if c := r.StatusCode; 200 <= c && c <= 299 { + return nil + } + errorResponse := &ErrorResponse{Response: r} + data, err := ioutil.ReadAll(r.Body) + if err == nil && data != nil { + json.Unmarshal(data, errorResponse) + } + switch { + case r.StatusCode == http.StatusUnauthorized && strings.HasPrefix(r.Header.Get(headerOTP), "required"): + return (*TwoFactorAuthError)(errorResponse) + case r.StatusCode == http.StatusForbidden && r.Header.Get(headerRateRemaining) == "0" && strings.HasPrefix(errorResponse.Message, "API rate limit exceeded for "): + return &RateLimitError{ + Rate: parseRate(r), + Response: errorResponse.Response, + Message: errorResponse.Message, + } + case r.StatusCode == http.StatusForbidden && strings.HasSuffix(errorResponse.DocumentationURL, "/v3/#abuse-rate-limits"): + abuseRateLimitError := &AbuseRateLimitError{ + Response: errorResponse.Response, + Message: errorResponse.Message, + } + if v := r.Header["Retry-After"]; len(v) > 0 { + // According to GitHub support, the "Retry-After" header value will be + // an integer which represents the number of seconds that one should + // wait before resuming making requests. + retryAfterSeconds, _ := strconv.ParseInt(v[0], 10, 64) // Error handling is noop. + retryAfter := time.Duration(retryAfterSeconds) * time.Second + abuseRateLimitError.RetryAfter = &retryAfter + } + return abuseRateLimitError + default: + return errorResponse + } +} + +// parseBoolResponse determines the boolean result from a GitHub API response. +// Several GitHub API methods return boolean responses indicated by the HTTP +// status code in the response (true indicated by a 204, false indicated by a +// 404). This helper function will determine that result and hide the 404 +// error if present. Any other error will be returned through as-is. +func parseBoolResponse(err error) (bool, error) { + if err == nil { + return true, nil + } + + if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound { + // Simply false. In this one case, we do not pass the error through. + return false, nil + } + + // some other real error occurred + return false, err +} + +// Rate represents the rate limit for the current client. +type Rate struct { + // The number of requests per hour the client is currently limited to. + Limit int `json:"limit"` + + // The number of remaining requests the client can make this hour. + Remaining int `json:"remaining"` + + // The time at which the current rate limit will reset. + Reset Timestamp `json:"reset"` +} + +func (r Rate) String() string { + return Stringify(r) +} + +// RateLimits represents the rate limits for the current client. +type RateLimits struct { + // The rate limit for non-search API requests. Unauthenticated + // requests are limited to 60 per hour. Authenticated requests are + // limited to 5,000 per hour. + // + // GitHub API docs: https://developer.github.com/v3/#rate-limiting + Core *Rate `json:"core"` + + // The rate limit for search API requests. Unauthenticated requests + // are limited to 10 requests per minutes. Authenticated requests are + // limited to 30 per minute. + // + // GitHub API docs: https://developer.github.com/v3/search/#rate-limit + Search *Rate `json:"search"` +} + +func (r RateLimits) String() string { + return Stringify(r) +} + +type rateLimitCategory uint8 + +const ( + coreCategory rateLimitCategory = iota + searchCategory + + categories // An array of this length will be able to contain all rate limit categories. +) + +// category returns the rate limit category of the endpoint, determined by Request.URL.Path. +func category(path string) rateLimitCategory { + switch { + default: + return coreCategory + case strings.HasPrefix(path, "/search/"): + return searchCategory + } +} + +// RateLimits returns the rate limits for the current client. +func (c *Client) RateLimits(ctx context.Context) (*RateLimits, *Response, error) { + req, err := c.NewRequest("GET", "rate_limit", nil) + if err != nil { + return nil, nil, err + } + + response := new(struct { + Resources *RateLimits `json:"resources"` + }) + resp, err := c.Do(ctx, req, response) + if err != nil { + return nil, nil, err + } + + if response.Resources != nil { + c.rateMu.Lock() + if response.Resources.Core != nil { + c.rateLimits[coreCategory] = *response.Resources.Core + } + if response.Resources.Search != nil { + c.rateLimits[searchCategory] = *response.Resources.Search + } + c.rateMu.Unlock() + } + + return response.Resources, resp, nil +} + +/* +UnauthenticatedRateLimitedTransport allows you to make unauthenticated calls +that need to use a higher rate limit associated with your OAuth application. + + t := &github.UnauthenticatedRateLimitedTransport{ + ClientID: "your app's client ID", + ClientSecret: "your app's client secret", + } + client := github.NewClient(t.Client()) + +This will append the querystring params client_id=xxx&client_secret=yyy to all +requests. + +See https://developer.github.com/v3/#unauthenticated-rate-limited-requests for +more information. +*/ +type UnauthenticatedRateLimitedTransport struct { + // ClientID is the GitHub OAuth client ID of the current application, which + // can be found by selecting its entry in the list at + // https://github.com/settings/applications. + ClientID string + + // ClientSecret is the GitHub OAuth client secret of the current + // application. + ClientSecret string + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +// RoundTrip implements the RoundTripper interface. +func (t *UnauthenticatedRateLimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) { + if t.ClientID == "" { + return nil, errors.New("t.ClientID is empty") + } + if t.ClientSecret == "" { + return nil, errors.New("t.ClientSecret is empty") + } + + // To set extra querystring params, we must make a copy of the Request so + // that we don't modify the Request we were given. This is required by the + // specification of http.RoundTripper. + // + // Since we are going to modify only req.URL here, we only need a deep copy + // of req.URL. + req2 := new(http.Request) + *req2 = *req + req2.URL = new(url.URL) + *req2.URL = *req.URL + + q := req2.URL.Query() + q.Set("client_id", t.ClientID) + q.Set("client_secret", t.ClientSecret) + req2.URL.RawQuery = q.Encode() + + // Make the HTTP request. + return t.transport().RoundTrip(req2) +} + +// Client returns an *http.Client that makes requests which are subject to the +// rate limit of your OAuth application. +func (t *UnauthenticatedRateLimitedTransport) Client() *http.Client { + return &http.Client{Transport: t} +} + +func (t *UnauthenticatedRateLimitedTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} + +// BasicAuthTransport is an http.RoundTripper that authenticates all requests +// using HTTP Basic Authentication with the provided username and password. It +// additionally supports users who have two-factor authentication enabled on +// their GitHub account. +type BasicAuthTransport struct { + Username string // GitHub username + Password string // GitHub password + OTP string // one-time password for users with two-factor auth enabled + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +// RoundTrip implements the RoundTripper interface. +func (t *BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + // To set extra headers, we must make a copy of the Request so + // that we don't modify the Request we were given. This is required by the + // specification of http.RoundTripper. + // + // Since we are going to modify only req.Header here, we only need a deep copy + // of req.Header. + req2 := new(http.Request) + *req2 = *req + req2.Header = make(http.Header, len(req.Header)) + for k, s := range req.Header { + req2.Header[k] = append([]string(nil), s...) + } + + req2.SetBasicAuth(t.Username, t.Password) + if t.OTP != "" { + req2.Header.Set(headerOTP, t.OTP) + } + return t.transport().RoundTrip(req2) +} + +// Client returns an *http.Client that makes requests that are authenticated +// using HTTP Basic Authentication. +func (t *BasicAuthTransport) Client() *http.Client { + return &http.Client{Transport: t} +} + +func (t *BasicAuthTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} + +// formatRateReset formats d to look like "[rate reset in 2s]" or +// "[rate reset in 87m02s]" for the positive durations. And like "[rate limit was reset 87m02s ago]" +// for the negative cases. +func formatRateReset(d time.Duration) string { + isNegative := d < 0 + if isNegative { + d *= -1 + } + secondsTotal := int(0.5 + d.Seconds()) + minutes := secondsTotal / 60 + seconds := secondsTotal - minutes*60 + + var timeString string + if minutes > 0 { + timeString = fmt.Sprintf("%dm%02ds", minutes, seconds) + } else { + timeString = fmt.Sprintf("%ds", seconds) + } + + if isNegative { + return fmt.Sprintf("[rate limit was reset %v ago]", timeString) + } + return fmt.Sprintf("[rate reset in %v]", timeString) +} + +// Bool is a helper routine that allocates a new bool value +// to store v and returns a pointer to it. +func Bool(v bool) *bool { return &v } + +// Int is a helper routine that allocates a new int value +// to store v and returns a pointer to it. +func Int(v int) *int { return &v } + +// Int64 is a helper routine that allocates a new int64 value +// to store v and returns a pointer to it. +func Int64(v int64) *int64 { return &v } + +// String is a helper routine that allocates a new string value +// to store v and returns a pointer to it. +func String(v string) *string { return &v } diff --git a/vendor/github.com/google/go-github/github/github_test.go b/vendor/github.com/google/go-github/github/github_test.go new file mode 100644 index 0000000..da29e1e --- /dev/null +++ b/vendor/github.com/google/go-github/github/github_test.go @@ -0,0 +1,1083 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path" + "reflect" + "strings" + "testing" + "time" +) + +const ( + // baseURLPath is a non-empty Client.BaseURL path to use during tests, + // to ensure relative URLs are used for all endpoints. See issue #752. + baseURLPath = "/api-v3" +) + +// setup sets up a test HTTP server along with a github.Client that is +// configured to talk to that test server. Tests should register handlers on +// mux which provide mock responses for the API method being tested. +func setup() (client *Client, mux *http.ServeMux, serverURL string, teardown func()) { + // mux is the HTTP request multiplexer used with the test server. + mux = http.NewServeMux() + + // We want to ensure that tests catch mistakes where the endpoint URL is + // specified as absolute rather than relative. It only makes a difference + // when there's a non-empty base URL path. So, use that. See issue #752. + apiHandler := http.NewServeMux() + apiHandler.Handle(baseURLPath+"/", http.StripPrefix(baseURLPath, mux)) + apiHandler.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + fmt.Fprintln(os.Stderr, "FAIL: Client.BaseURL path prefix is not preserved in the request URL:") + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "\t"+req.URL.String()) + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "\tDid you accidentally use an absolute endpoint URL rather than relative?") + fmt.Fprintln(os.Stderr, "\tSee https://github.com/google/go-github/issues/752 for information.") + http.Error(w, "Client.BaseURL path prefix is not preserved in the request URL.", http.StatusInternalServerError) + }) + + // server is a test HTTP server used to provide mock API responses. + server := httptest.NewServer(apiHandler) + + // client is the GitHub client being tested and is + // configured to use test server. + client = NewClient(nil) + url, _ := url.Parse(server.URL + baseURLPath + "/") + client.BaseURL = url + client.UploadURL = url + + return client, mux, server.URL, server.Close +} + +// openTestFile creates a new file with the given name and content for testing. +// In order to ensure the exact file name, this function will create a new temp +// directory, and create the file in that directory. It is the caller's +// responsibility to remove the directory and its contents when no longer needed. +func openTestFile(name, content string) (file *os.File, dir string, err error) { + dir, err = ioutil.TempDir("", "go-github") + if err != nil { + return nil, dir, err + } + + file, err = os.OpenFile(path.Join(dir, name), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) + if err != nil { + return nil, dir, err + } + + fmt.Fprint(file, content) + + // close and re-open the file to keep file.Stat() happy + file.Close() + file, err = os.Open(file.Name()) + if err != nil { + return nil, dir, err + } + + return file, dir, err +} + +func testMethod(t *testing.T, r *http.Request, want string) { + if got := r.Method; got != want { + t.Errorf("Request method: %v, want %v", got, want) + } +} + +type values map[string]string + +func testFormValues(t *testing.T, r *http.Request, values values) { + want := url.Values{} + for k, v := range values { + want.Set(k, v) + } + + r.ParseForm() + if got := r.Form; !reflect.DeepEqual(got, want) { + t.Errorf("Request parameters: %v, want %v", got, want) + } +} + +func testHeader(t *testing.T, r *http.Request, header string, want string) { + if got := r.Header.Get(header); got != want { + t.Errorf("Header.Get(%q) returned %q, want %q", header, got, want) + } +} + +func testURLParseError(t *testing.T, err error) { + if err == nil { + t.Errorf("Expected error to be returned") + } + if err, ok := err.(*url.Error); !ok || err.Op != "parse" { + t.Errorf("Expected URL parse error, got %+v", err) + } +} + +func testBody(t *testing.T, r *http.Request, want string) { + b, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Error reading request body: %v", err) + } + if got := string(b); got != want { + t.Errorf("request Body is %s, want %s", got, want) + } +} + +// Helper function to test that a value is marshalled to JSON as expected. +func testJSONMarshal(t *testing.T, v interface{}, want string) { + j, err := json.Marshal(v) + if err != nil { + t.Errorf("Unable to marshal JSON for %v", v) + } + + w := new(bytes.Buffer) + err = json.Compact(w, []byte(want)) + if err != nil { + t.Errorf("String is not valid json: %s", want) + } + + if w.String() != string(j) { + t.Errorf("json.Marshal(%q) returned %s, want %s", v, j, w) + } + + // now go the other direction and make sure things unmarshal as expected + u := reflect.ValueOf(v).Interface() + if err := json.Unmarshal([]byte(want), u); err != nil { + t.Errorf("Unable to unmarshal JSON for %v", want) + } + + if !reflect.DeepEqual(v, u) { + t.Errorf("json.Unmarshal(%q) returned %s, want %s", want, u, v) + } +} + +func TestNewClient(t *testing.T) { + c := NewClient(nil) + + if got, want := c.BaseURL.String(), defaultBaseURL; got != want { + t.Errorf("NewClient BaseURL is %v, want %v", got, want) + } + if got, want := c.UserAgent, userAgent; got != want { + t.Errorf("NewClient UserAgent is %v, want %v", got, want) + } +} + +func TestNewEnterpriseClient(t *testing.T) { + baseURL := "https://custom-url/" + uploadURL := "https://custom-upload-url/" + c, err := NewEnterpriseClient(baseURL, uploadURL, nil) + if err != nil { + t.Fatalf("NewEnterpriseClient returned unexpected error: %v", err) + } + + if got, want := c.BaseURL.String(), baseURL; got != want { + t.Errorf("NewClient BaseURL is %v, want %v", got, want) + } + if got, want := c.UploadURL.String(), uploadURL; got != want { + t.Errorf("NewClient UploadURL is %v, want %v", got, want) + } +} + +func TestNewEnterpriseClient_addsTrailingSlashToURLs(t *testing.T) { + baseURL := "https://custom-url" + uploadURL := "https://custom-upload-url" + formattedBaseURL := baseURL + "/" + formattedUploadURL := uploadURL + "/" + + c, err := NewEnterpriseClient(baseURL, uploadURL, nil) + if err != nil { + t.Fatalf("NewEnterpriseClient returned unexpected error: %v", err) + } + + if got, want := c.BaseURL.String(), formattedBaseURL; got != want { + t.Errorf("NewClient BaseURL is %v, want %v", got, want) + } + if got, want := c.UploadURL.String(), formattedUploadURL; got != want { + t.Errorf("NewClient UploadURL is %v, want %v", got, want) + } +} + +// Ensure that length of Client.rateLimits is the same as number of fields in RateLimits struct. +func TestClient_rateLimits(t *testing.T) { + if got, want := len(Client{}.rateLimits), reflect.TypeOf(RateLimits{}).NumField(); got != want { + t.Errorf("len(Client{}.rateLimits) is %v, want %v", got, want) + } +} + +func TestNewRequest(t *testing.T) { + c := NewClient(nil) + + inURL, outURL := "/foo", defaultBaseURL+"foo" + inBody, outBody := &User{Login: String("l")}, `{"login":"l"}`+"\n" + req, _ := c.NewRequest("GET", inURL, inBody) + + // test that relative URL was expanded + if got, want := req.URL.String(), outURL; got != want { + t.Errorf("NewRequest(%q) URL is %v, want %v", inURL, got, want) + } + + // test that body was JSON encoded + body, _ := ioutil.ReadAll(req.Body) + if got, want := string(body), outBody; got != want { + t.Errorf("NewRequest(%q) Body is %v, want %v", inBody, got, want) + } + + // test that default user-agent is attached to the request + if got, want := req.Header.Get("User-Agent"), c.UserAgent; got != want { + t.Errorf("NewRequest() User-Agent is %v, want %v", got, want) + } +} + +func TestNewRequest_invalidJSON(t *testing.T) { + c := NewClient(nil) + + type T struct { + A map[interface{}]interface{} + } + _, err := c.NewRequest("GET", ".", &T{}) + + if err == nil { + t.Error("Expected error to be returned.") + } + if err, ok := err.(*json.UnsupportedTypeError); !ok { + t.Errorf("Expected a JSON error; got %#v.", err) + } +} + +func TestNewRequest_badURL(t *testing.T) { + c := NewClient(nil) + _, err := c.NewRequest("GET", ":", nil) + testURLParseError(t, err) +} + +// ensure that no User-Agent header is set if the client's UserAgent is empty. +// This caused a problem with Google's internal http client. +func TestNewRequest_emptyUserAgent(t *testing.T) { + c := NewClient(nil) + c.UserAgent = "" + req, err := c.NewRequest("GET", ".", nil) + if err != nil { + t.Fatalf("NewRequest returned unexpected error: %v", err) + } + if _, ok := req.Header["User-Agent"]; ok { + t.Fatal("constructed request contains unexpected User-Agent header") + } +} + +// If a nil body is passed to github.NewRequest, make sure that nil is also +// passed to http.NewRequest. In most cases, passing an io.Reader that returns +// no content is fine, since there is no difference between an HTTP request +// body that is an empty string versus one that is not set at all. However in +// certain cases, intermediate systems may treat these differently resulting in +// subtle errors. +func TestNewRequest_emptyBody(t *testing.T) { + c := NewClient(nil) + req, err := c.NewRequest("GET", ".", nil) + if err != nil { + t.Fatalf("NewRequest returned unexpected error: %v", err) + } + if req.Body != nil { + t.Fatalf("constructed request contains a non-nil Body") + } +} + +func TestNewRequest_errorForNoTrailingSlash(t *testing.T) { + tests := []struct { + rawurl string + wantError bool + }{ + {rawurl: "https://example.com/api/v3", wantError: true}, + {rawurl: "https://example.com/api/v3/", wantError: false}, + } + c := NewClient(nil) + for _, test := range tests { + u, err := url.Parse(test.rawurl) + if err != nil { + t.Fatalf("url.Parse returned unexpected error: %v.", err) + } + c.BaseURL = u + if _, err := c.NewRequest(http.MethodGet, "test", nil); test.wantError && err == nil { + t.Fatalf("Expected error to be returned.") + } else if !test.wantError && err != nil { + t.Fatalf("NewRequest returned unexpected error: %v.", err) + } + } +} + +func TestNewUploadRequest_errorForNoTrailingSlash(t *testing.T) { + tests := []struct { + rawurl string + wantError bool + }{ + {rawurl: "https://example.com/api/uploads", wantError: true}, + {rawurl: "https://example.com/api/uploads/", wantError: false}, + } + c := NewClient(nil) + for _, test := range tests { + u, err := url.Parse(test.rawurl) + if err != nil { + t.Fatalf("url.Parse returned unexpected error: %v.", err) + } + c.UploadURL = u + if _, err = c.NewUploadRequest("test", nil, 0, ""); test.wantError && err == nil { + t.Fatalf("Expected error to be returned.") + } else if !test.wantError && err != nil { + t.Fatalf("NewUploadRequest returned unexpected error: %v.", err) + } + } +} + +func TestResponse_populatePageValues(t *testing.T) { + r := http.Response{ + Header: http.Header{ + "Link": {`; rel="first",` + + ` ; rel="prev",` + + ` ; rel="next",` + + ` ; rel="last"`, + }, + }, + } + + response := newResponse(&r) + if got, want := response.FirstPage, 1; got != want { + t.Errorf("response.FirstPage: %v, want %v", got, want) + } + if got, want := response.PrevPage, 2; want != got { + t.Errorf("response.PrevPage: %v, want %v", got, want) + } + if got, want := response.NextPage, 4; want != got { + t.Errorf("response.NextPage: %v, want %v", got, want) + } + if got, want := response.LastPage, 5; want != got { + t.Errorf("response.LastPage: %v, want %v", got, want) + } +} + +func TestResponse_populatePageValues_invalid(t *testing.T) { + r := http.Response{ + Header: http.Header{ + "Link": {`,` + + `; rel="first",` + + `https://api.github.com/?page=2; rel="prev",` + + `; rel="next",` + + `; rel="last"`, + }, + }, + } + + response := newResponse(&r) + if got, want := response.FirstPage, 0; got != want { + t.Errorf("response.FirstPage: %v, want %v", got, want) + } + if got, want := response.PrevPage, 0; got != want { + t.Errorf("response.PrevPage: %v, want %v", got, want) + } + if got, want := response.NextPage, 0; got != want { + t.Errorf("response.NextPage: %v, want %v", got, want) + } + if got, want := response.LastPage, 0; got != want { + t.Errorf("response.LastPage: %v, want %v", got, want) + } + + // more invalid URLs + r = http.Response{ + Header: http.Header{ + "Link": {`; rel="first"`}, + }, + } + + response = newResponse(&r) + if got, want := response.FirstPage, 0; got != want { + t.Errorf("response.FirstPage: %v, want %v", got, want) + } +} + +func TestDo(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + type foo struct { + A string + } + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"A":"a"}`) + }) + + req, _ := client.NewRequest("GET", ".", nil) + body := new(foo) + client.Do(context.Background(), req, body) + + want := &foo{"a"} + if !reflect.DeepEqual(body, want) { + t.Errorf("Response body = %v, want %v", body, want) + } +} + +func TestDo_httpError(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Bad Request", 400) + }) + + req, _ := client.NewRequest("GET", ".", nil) + resp, err := client.Do(context.Background(), req, nil) + + if err == nil { + t.Fatal("Expected HTTP 400 error, got no error.") + } + if resp.StatusCode != 400 { + t.Errorf("Expected HTTP 400 error, got %d status code.", resp.StatusCode) + } +} + +// Test handling of an error caused by the internal http client's Do() +// function. A redirect loop is pretty unlikely to occur within the GitHub +// API, but does allow us to exercise the right code path. +func TestDo_redirectLoop(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, baseURLPath, http.StatusFound) + }) + + req, _ := client.NewRequest("GET", ".", nil) + _, err := client.Do(context.Background(), req, nil) + + if err == nil { + t.Error("Expected error to be returned.") + } + if err, ok := err.(*url.Error); !ok { + t.Errorf("Expected a URL error; got %#v.", err) + } +} + +// Test that an error caused by the internal http client's Do() function +// does not leak the client secret. +func TestDo_sanitizeURL(t *testing.T) { + tp := &UnauthenticatedRateLimitedTransport{ + ClientID: "id", + ClientSecret: "secret", + } + unauthedClient := NewClient(tp.Client()) + unauthedClient.BaseURL = &url.URL{Scheme: "http", Host: "127.0.0.1:0", Path: "/"} // Use port 0 on purpose to trigger a dial TCP error, expect to get "dial tcp 127.0.0.1:0: connect: can't assign requested address". + req, err := unauthedClient.NewRequest("GET", ".", nil) + if err != nil { + t.Fatalf("NewRequest returned unexpected error: %v", err) + } + _, err = unauthedClient.Do(context.Background(), req, nil) + if err == nil { + t.Fatal("Expected error to be returned.") + } + if strings.Contains(err.Error(), "client_secret=secret") { + t.Errorf("Do error contains secret, should be redacted:\n%q", err) + } +} + +func TestDo_rateLimit(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(headerRateLimit, "60") + w.Header().Set(headerRateRemaining, "59") + w.Header().Set(headerRateReset, "1372700873") + }) + + req, _ := client.NewRequest("GET", ".", nil) + resp, err := client.Do(context.Background(), req, nil) + if err != nil { + t.Errorf("Do returned unexpected error: %v", err) + } + if got, want := resp.Rate.Limit, 60; got != want { + t.Errorf("Client rate limit = %v, want %v", got, want) + } + if got, want := resp.Rate.Remaining, 59; got != want { + t.Errorf("Client rate remaining = %v, want %v", got, want) + } + reset := time.Date(2013, time.July, 1, 17, 47, 53, 0, time.UTC) + if resp.Rate.Reset.UTC() != reset { + t.Errorf("Client rate reset = %v, want %v", resp.Rate.Reset, reset) + } +} + +// ensure rate limit is still parsed, even for error responses +func TestDo_rateLimit_errorResponse(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(headerRateLimit, "60") + w.Header().Set(headerRateRemaining, "59") + w.Header().Set(headerRateReset, "1372700873") + http.Error(w, "Bad Request", 400) + }) + + req, _ := client.NewRequest("GET", ".", nil) + resp, err := client.Do(context.Background(), req, nil) + if err == nil { + t.Error("Expected error to be returned.") + } + if _, ok := err.(*RateLimitError); ok { + t.Errorf("Did not expect a *RateLimitError error; got %#v.", err) + } + if got, want := resp.Rate.Limit, 60; got != want { + t.Errorf("Client rate limit = %v, want %v", got, want) + } + if got, want := resp.Rate.Remaining, 59; got != want { + t.Errorf("Client rate remaining = %v, want %v", got, want) + } + reset := time.Date(2013, time.July, 1, 17, 47, 53, 0, time.UTC) + if resp.Rate.Reset.UTC() != reset { + t.Errorf("Client rate reset = %v, want %v", resp.Rate.Reset, reset) + } +} + +// Ensure *RateLimitError is returned when API rate limit is exceeded. +func TestDo_rateLimit_rateLimitError(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(headerRateLimit, "60") + w.Header().Set(headerRateRemaining, "0") + w.Header().Set(headerRateReset, "1372700873") + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusForbidden) + fmt.Fprintln(w, `{ + "message": "API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)", + "documentation_url": "https://developer.github.com/v3/#rate-limiting" +}`) + }) + + req, _ := client.NewRequest("GET", ".", nil) + _, err := client.Do(context.Background(), req, nil) + + if err == nil { + t.Error("Expected error to be returned.") + } + rateLimitErr, ok := err.(*RateLimitError) + if !ok { + t.Fatalf("Expected a *RateLimitError error; got %#v.", err) + } + if got, want := rateLimitErr.Rate.Limit, 60; got != want { + t.Errorf("rateLimitErr rate limit = %v, want %v", got, want) + } + if got, want := rateLimitErr.Rate.Remaining, 0; got != want { + t.Errorf("rateLimitErr rate remaining = %v, want %v", got, want) + } + reset := time.Date(2013, time.July, 1, 17, 47, 53, 0, time.UTC) + if rateLimitErr.Rate.Reset.UTC() != reset { + t.Errorf("rateLimitErr rate reset = %v, want %v", rateLimitErr.Rate.Reset.UTC(), reset) + } +} + +// Ensure a network call is not made when it's known that API rate limit is still exceeded. +func TestDo_rateLimit_noNetworkCall(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + reset := time.Now().UTC().Add(time.Minute).Round(time.Second) // Rate reset is a minute from now, with 1 second precision. + + mux.HandleFunc("/first", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(headerRateLimit, "60") + w.Header().Set(headerRateRemaining, "0") + w.Header().Set(headerRateReset, fmt.Sprint(reset.Unix())) + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusForbidden) + fmt.Fprintln(w, `{ + "message": "API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)", + "documentation_url": "https://developer.github.com/v3/#rate-limiting" +}`) + }) + + madeNetworkCall := false + mux.HandleFunc("/second", func(w http.ResponseWriter, r *http.Request) { + madeNetworkCall = true + }) + + // First request is made, and it makes the client aware of rate reset time being in the future. + req, _ := client.NewRequest("GET", "first", nil) + client.Do(context.Background(), req, nil) + + // Second request should not cause a network call to be made, since client can predict a rate limit error. + req, _ = client.NewRequest("GET", "second", nil) + _, err := client.Do(context.Background(), req, nil) + + if madeNetworkCall { + t.Fatal("Network call was made, even though rate limit is known to still be exceeded.") + } + + if err == nil { + t.Error("Expected error to be returned.") + } + rateLimitErr, ok := err.(*RateLimitError) + if !ok { + t.Fatalf("Expected a *RateLimitError error; got %#v.", err) + } + if got, want := rateLimitErr.Rate.Limit, 60; got != want { + t.Errorf("rateLimitErr rate limit = %v, want %v", got, want) + } + if got, want := rateLimitErr.Rate.Remaining, 0; got != want { + t.Errorf("rateLimitErr rate remaining = %v, want %v", got, want) + } + if rateLimitErr.Rate.Reset.UTC() != reset { + t.Errorf("rateLimitErr rate reset = %v, want %v", rateLimitErr.Rate.Reset.UTC(), reset) + } +} + +// Ensure *AbuseRateLimitError is returned when the response indicates that +// the client has triggered an abuse detection mechanism. +func TestDo_rateLimit_abuseRateLimitError(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusForbidden) + // When the abuse rate limit error is of the "temporarily blocked from content creation" type, + // there is no "Retry-After" header. + fmt.Fprintln(w, `{ + "message": "You have triggered an abuse detection mechanism and have been temporarily blocked from content creation. Please retry your request again later.", + "documentation_url": "https://developer.github.com/v3/#abuse-rate-limits" +}`) + }) + + req, _ := client.NewRequest("GET", ".", nil) + _, err := client.Do(context.Background(), req, nil) + + if err == nil { + t.Error("Expected error to be returned.") + } + abuseRateLimitErr, ok := err.(*AbuseRateLimitError) + if !ok { + t.Fatalf("Expected a *AbuseRateLimitError error; got %#v.", err) + } + if got, want := abuseRateLimitErr.RetryAfter, (*time.Duration)(nil); got != want { + t.Errorf("abuseRateLimitErr RetryAfter = %v, want %v", got, want) + } +} + +// Ensure *AbuseRateLimitError is returned when the response indicates that +// the client has triggered an abuse detection mechanism on GitHub Enterprise. +func TestDo_rateLimit_abuseRateLimitErrorEnterprise(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusForbidden) + // When the abuse rate limit error is of the "temporarily blocked from content creation" type, + // there is no "Retry-After" header. + // This response returns a documentation url like the one returned for GitHub Enterprise, this + // url changes between versions but follows roughly the same format. + fmt.Fprintln(w, `{ + "message": "You have triggered an abuse detection mechanism and have been temporarily blocked from content creation. Please retry your request again later.", + "documentation_url": "https://developer.github.com/enterprise/2.12/v3/#abuse-rate-limits" +}`) + }) + + req, _ := client.NewRequest("GET", ".", nil) + _, err := client.Do(context.Background(), req, nil) + + if err == nil { + t.Error("Expected error to be returned.") + } + abuseRateLimitErr, ok := err.(*AbuseRateLimitError) + if !ok { + t.Fatalf("Expected a *AbuseRateLimitError error; got %#v.", err) + } + if got, want := abuseRateLimitErr.RetryAfter, (*time.Duration)(nil); got != want { + t.Errorf("abuseRateLimitErr RetryAfter = %v, want %v", got, want) + } +} + +// Ensure *AbuseRateLimitError.RetryAfter is parsed correctly. +func TestDo_rateLimit_abuseRateLimitError_retryAfter(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Header().Set("Retry-After", "123") // Retry after value of 123 seconds. + w.WriteHeader(http.StatusForbidden) + fmt.Fprintln(w, `{ + "message": "You have triggered an abuse detection mechanism ...", + "documentation_url": "https://developer.github.com/v3/#abuse-rate-limits" +}`) + }) + + req, _ := client.NewRequest("GET", ".", nil) + _, err := client.Do(context.Background(), req, nil) + + if err == nil { + t.Error("Expected error to be returned.") + } + abuseRateLimitErr, ok := err.(*AbuseRateLimitError) + if !ok { + t.Fatalf("Expected a *AbuseRateLimitError error; got %#v.", err) + } + if abuseRateLimitErr.RetryAfter == nil { + t.Fatalf("abuseRateLimitErr RetryAfter is nil, expected not-nil") + } + if got, want := *abuseRateLimitErr.RetryAfter, 123*time.Second; got != want { + t.Errorf("abuseRateLimitErr RetryAfter = %v, want %v", got, want) + } +} + +func TestDo_noContent(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNoContent) + }) + + var body json.RawMessage + + req, _ := client.NewRequest("GET", ".", nil) + _, err := client.Do(context.Background(), req, &body) + if err != nil { + t.Fatalf("Do returned unexpected error: %v", err) + } +} + +func TestSanitizeURL(t *testing.T) { + tests := []struct { + in, want string + }{ + {"/?a=b", "/?a=b"}, + {"/?a=b&client_secret=secret", "/?a=b&client_secret=REDACTED"}, + {"/?a=b&client_id=id&client_secret=secret", "/?a=b&client_id=id&client_secret=REDACTED"}, + } + + for _, tt := range tests { + inURL, _ := url.Parse(tt.in) + want, _ := url.Parse(tt.want) + + if got := sanitizeURL(inURL); !reflect.DeepEqual(got, want) { + t.Errorf("sanitizeURL(%v) returned %v, want %v", tt.in, got, want) + } + } +} + +func TestCheckResponse(t *testing.T) { + res := &http.Response{ + Request: &http.Request{}, + StatusCode: http.StatusBadRequest, + Body: ioutil.NopCloser(strings.NewReader(`{"message":"m", + "errors": [{"resource": "r", "field": "f", "code": "c"}], + "block": {"reason": "dmca", "created_at": "2016-03-17T15:39:46Z"}}`)), + } + err := CheckResponse(res).(*ErrorResponse) + + if err == nil { + t.Errorf("Expected error response.") + } + + want := &ErrorResponse{ + Response: res, + Message: "m", + Errors: []Error{{Resource: "r", Field: "f", Code: "c"}}, + Block: &struct { + Reason string `json:"reason,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + }{ + Reason: "dmca", + CreatedAt: &Timestamp{time.Date(2016, time.March, 17, 15, 39, 46, 0, time.UTC)}, + }, + } + if !reflect.DeepEqual(err, want) { + t.Errorf("Error = %#v, want %#v", err, want) + } +} + +// ensure that we properly handle API errors that do not contain a response body +func TestCheckResponse_noBody(t *testing.T) { + res := &http.Response{ + Request: &http.Request{}, + StatusCode: http.StatusBadRequest, + Body: ioutil.NopCloser(strings.NewReader("")), + } + err := CheckResponse(res).(*ErrorResponse) + + if err == nil { + t.Errorf("Expected error response.") + } + + want := &ErrorResponse{ + Response: res, + } + if !reflect.DeepEqual(err, want) { + t.Errorf("Error = %#v, want %#v", err, want) + } +} + +func TestParseBooleanResponse_true(t *testing.T) { + result, err := parseBoolResponse(nil) + if err != nil { + t.Errorf("parseBoolResponse returned error: %+v", err) + } + + if want := true; result != want { + t.Errorf("parseBoolResponse returned %+v, want: %+v", result, want) + } +} + +func TestParseBooleanResponse_false(t *testing.T) { + v := &ErrorResponse{Response: &http.Response{StatusCode: http.StatusNotFound}} + result, err := parseBoolResponse(v) + if err != nil { + t.Errorf("parseBoolResponse returned error: %+v", err) + } + + if want := false; result != want { + t.Errorf("parseBoolResponse returned %+v, want: %+v", result, want) + } +} + +func TestParseBooleanResponse_error(t *testing.T) { + v := &ErrorResponse{Response: &http.Response{StatusCode: http.StatusBadRequest}} + result, err := parseBoolResponse(v) + + if err == nil { + t.Errorf("Expected error to be returned.") + } + + if want := false; result != want { + t.Errorf("parseBoolResponse returned %+v, want: %+v", result, want) + } +} + +func TestErrorResponse_Error(t *testing.T) { + res := &http.Response{Request: &http.Request{}} + err := ErrorResponse{Message: "m", Response: res} + if err.Error() == "" { + t.Errorf("Expected non-empty ErrorResponse.Error()") + } +} + +func TestError_Error(t *testing.T) { + err := Error{} + if err.Error() == "" { + t.Errorf("Expected non-empty Error.Error()") + } +} + +func TestRateLimits(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/rate_limit", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"resources":{ + "core": {"limit":2,"remaining":1,"reset":1372700873}, + "search": {"limit":3,"remaining":2,"reset":1372700874} + }}`) + }) + + rate, _, err := client.RateLimits(context.Background()) + if err != nil { + t.Errorf("RateLimits returned error: %v", err) + } + + want := &RateLimits{ + Core: &Rate{ + Limit: 2, + Remaining: 1, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 53, 0, time.UTC).Local()}, + }, + Search: &Rate{ + Limit: 3, + Remaining: 2, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 47, 54, 0, time.UTC).Local()}, + }, + } + if !reflect.DeepEqual(rate, want) { + t.Errorf("RateLimits returned %+v, want %+v", rate, want) + } + + if got, want := client.rateLimits[coreCategory], *want.Core; got != want { + t.Errorf("client.rateLimits[coreCategory] is %+v, want %+v", got, want) + } + if got, want := client.rateLimits[searchCategory], *want.Search; got != want { + t.Errorf("client.rateLimits[searchCategory] is %+v, want %+v", got, want) + } +} + +func TestUnauthenticatedRateLimitedTransport(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + var v, want string + q := r.URL.Query() + if v, want = q.Get("client_id"), "id"; v != want { + t.Errorf("OAuth Client ID = %v, want %v", v, want) + } + if v, want = q.Get("client_secret"), "secret"; v != want { + t.Errorf("OAuth Client Secret = %v, want %v", v, want) + } + }) + + tp := &UnauthenticatedRateLimitedTransport{ + ClientID: "id", + ClientSecret: "secret", + } + unauthedClient := NewClient(tp.Client()) + unauthedClient.BaseURL = client.BaseURL + req, _ := unauthedClient.NewRequest("GET", ".", nil) + unauthedClient.Do(context.Background(), req, nil) +} + +func TestUnauthenticatedRateLimitedTransport_missingFields(t *testing.T) { + // missing ClientID + tp := &UnauthenticatedRateLimitedTransport{ + ClientSecret: "secret", + } + _, err := tp.RoundTrip(nil) + if err == nil { + t.Errorf("Expected error to be returned") + } + + // missing ClientSecret + tp = &UnauthenticatedRateLimitedTransport{ + ClientID: "id", + } + _, err = tp.RoundTrip(nil) + if err == nil { + t.Errorf("Expected error to be returned") + } +} + +func TestUnauthenticatedRateLimitedTransport_transport(t *testing.T) { + // default transport + tp := &UnauthenticatedRateLimitedTransport{ + ClientID: "id", + ClientSecret: "secret", + } + if tp.transport() != http.DefaultTransport { + t.Errorf("Expected http.DefaultTransport to be used.") + } + + // custom transport + tp = &UnauthenticatedRateLimitedTransport{ + ClientID: "id", + ClientSecret: "secret", + Transport: &http.Transport{}, + } + if tp.transport() == http.DefaultTransport { + t.Errorf("Expected custom transport to be used.") + } +} + +func TestBasicAuthTransport(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + username, password, otp := "u", "p", "123456" + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + u, p, ok := r.BasicAuth() + if !ok { + t.Errorf("request does not contain basic auth credentials") + } + if u != username { + t.Errorf("request contained basic auth username %q, want %q", u, username) + } + if p != password { + t.Errorf("request contained basic auth password %q, want %q", p, password) + } + if got, want := r.Header.Get(headerOTP), otp; got != want { + t.Errorf("request contained OTP %q, want %q", got, want) + } + }) + + tp := &BasicAuthTransport{ + Username: username, + Password: password, + OTP: otp, + } + basicAuthClient := NewClient(tp.Client()) + basicAuthClient.BaseURL = client.BaseURL + req, _ := basicAuthClient.NewRequest("GET", ".", nil) + basicAuthClient.Do(context.Background(), req, nil) +} + +func TestBasicAuthTransport_transport(t *testing.T) { + // default transport + tp := &BasicAuthTransport{} + if tp.transport() != http.DefaultTransport { + t.Errorf("Expected http.DefaultTransport to be used.") + } + + // custom transport + tp = &BasicAuthTransport{ + Transport: &http.Transport{}, + } + if tp.transport() == http.DefaultTransport { + t.Errorf("Expected custom transport to be used.") + } +} + +func TestFormatRateReset(t *testing.T) { + d := 120*time.Minute + 12*time.Second + got := formatRateReset(d) + want := "[rate reset in 120m12s]" + if got != want { + t.Errorf("Format is wrong. got: %v, want: %v", got, want) + } + + d = 14*time.Minute + 2*time.Second + got = formatRateReset(d) + want = "[rate reset in 14m02s]" + if got != want { + t.Errorf("Format is wrong. got: %v, want: %v", got, want) + } + + d = 2*time.Minute + 2*time.Second + got = formatRateReset(d) + want = "[rate reset in 2m02s]" + if got != want { + t.Errorf("Format is wrong. got: %v, want: %v", got, want) + } + + d = 12 * time.Second + got = formatRateReset(d) + want = "[rate reset in 12s]" + if got != want { + t.Errorf("Format is wrong. got: %v, want: %v", got, want) + } + + d = -1 * (2*time.Hour + 2*time.Second) + got = formatRateReset(d) + want = "[rate limit was reset 120m02s ago]" + if got != want { + t.Errorf("Format is wrong. got: %v, want: %v", got, want) + } +} + +func TestNestedStructAccessorNoPanic(t *testing.T) { + issue := &Issue{User: nil} + got := issue.GetUser().GetPlan().GetName() + want := "" + if got != want { + t.Errorf("Issues.Get.GetUser().GetPlan().GetName() returned %+v, want %+v", got, want) + } +} diff --git a/vendor/github.com/google/go-github/github/gitignore.go b/vendor/github.com/google/go-github/github/gitignore.go new file mode 100644 index 0000000..2f691bc --- /dev/null +++ b/vendor/github.com/google/go-github/github/gitignore.go @@ -0,0 +1,64 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// GitignoresService provides access to the gitignore related functions in the +// GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/gitignore/ +type GitignoresService service + +// Gitignore represents a .gitignore file as returned by the GitHub API. +type Gitignore struct { + Name *string `json:"name,omitempty"` + Source *string `json:"source,omitempty"` +} + +func (g Gitignore) String() string { + return Stringify(g) +} + +// List all available Gitignore templates. +// +// GitHub API docs: https://developer.github.com/v3/gitignore/#listing-available-templates +func (s GitignoresService) List(ctx context.Context) ([]string, *Response, error) { + req, err := s.client.NewRequest("GET", "gitignore/templates", nil) + if err != nil { + return nil, nil, err + } + + var availableTemplates []string + resp, err := s.client.Do(ctx, req, &availableTemplates) + if err != nil { + return nil, resp, err + } + + return availableTemplates, resp, nil +} + +// Get a Gitignore by name. +// +// GitHub API docs: https://developer.github.com/v3/gitignore/#get-a-single-template +func (s GitignoresService) Get(ctx context.Context, name string) (*Gitignore, *Response, error) { + u := fmt.Sprintf("gitignore/templates/%v", name) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + gitignore := new(Gitignore) + resp, err := s.client.Do(ctx, req, gitignore) + if err != nil { + return nil, resp, err + } + + return gitignore, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/gitignore_test.go b/vendor/github.com/google/go-github/github/gitignore_test.go new file mode 100644 index 0000000..cdb8223 --- /dev/null +++ b/vendor/github.com/google/go-github/github/gitignore_test.go @@ -0,0 +1,62 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestGitignoresService_List(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gitignore/templates", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `["C", "Go"]`) + }) + + available, _, err := client.Gitignores.List(context.Background()) + if err != nil { + t.Errorf("Gitignores.List returned error: %v", err) + } + + want := []string{"C", "Go"} + if !reflect.DeepEqual(available, want) { + t.Errorf("Gitignores.List returned %+v, want %+v", available, want) + } +} + +func TestGitignoresService_Get(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/gitignore/templates/name", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"name":"Name","source":"template source"}`) + }) + + gitignore, _, err := client.Gitignores.Get(context.Background(), "name") + if err != nil { + t.Errorf("Gitignores.List returned error: %v", err) + } + + want := &Gitignore{Name: String("Name"), Source: String("template source")} + if !reflect.DeepEqual(gitignore, want) { + t.Errorf("Gitignores.Get returned %+v, want %+v", gitignore, want) + } +} + +func TestGitignoresService_Get_invalidTemplate(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Gitignores.Get(context.Background(), "%") + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/issues.go b/vendor/github.com/google/go-github/github/issues.go new file mode 100644 index 0000000..1e0991c --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues.go @@ -0,0 +1,347 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "strings" + "time" +) + +// IssuesService handles communication with the issue related +// methods of the GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/issues/ +type IssuesService service + +// Issue represents a GitHub issue on a repository. +// +// Note: As far as the GitHub API is concerned, every pull request is an issue, +// but not every issue is a pull request. Some endpoints, events, and webhooks +// may also return pull requests via this struct. If PullRequestLinks is nil, +// this is an issue, and if PullRequestLinks is not nil, this is a pull request. +// The IsPullRequest helper method can be used to check that. +type Issue struct { + ID *int64 `json:"id,omitempty"` + Number *int `json:"number,omitempty"` + State *string `json:"state,omitempty"` + Locked *bool `json:"locked,omitempty"` + Title *string `json:"title,omitempty"` + Body *string `json:"body,omitempty"` + User *User `json:"user,omitempty"` + Labels []Label `json:"labels,omitempty"` + Assignee *User `json:"assignee,omitempty"` + Comments *int `json:"comments,omitempty"` + ClosedAt *time.Time `json:"closed_at,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + ClosedBy *User `json:"closed_by,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + CommentsURL *string `json:"comments_url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + LabelsURL *string `json:"labels_url,omitempty"` + RepositoryURL *string `json:"repository_url,omitempty"` + Milestone *Milestone `json:"milestone,omitempty"` + PullRequestLinks *PullRequestLinks `json:"pull_request,omitempty"` + Repository *Repository `json:"repository,omitempty"` + Reactions *Reactions `json:"reactions,omitempty"` + Assignees []*User `json:"assignees,omitempty"` + NodeID *string `json:"node_id,omitempty"` + + // TextMatches is only populated from search results that request text matches + // See: search.go and https://developer.github.com/v3/search/#text-match-metadata + TextMatches []TextMatch `json:"text_matches,omitempty"` + + // ActiveLockReason is populated only when LockReason is provided while locking the issue. + // Possible values are: "off-topic", "too heated", "resolved", and "spam". + ActiveLockReason *string `json:"active_lock_reason,omitempty"` +} + +func (i Issue) String() string { + return Stringify(i) +} + +// IsPullRequest reports whether the issue is also a pull request. It uses the +// method recommended by GitHub's API documentation, which is to check whether +// PullRequestLinks is non-nil. +func (i Issue) IsPullRequest() bool { + return i.PullRequestLinks != nil +} + +// IssueRequest represents a request to create/edit an issue. +// It is separate from Issue above because otherwise Labels +// and Assignee fail to serialize to the correct JSON. +type IssueRequest struct { + Title *string `json:"title,omitempty"` + Body *string `json:"body,omitempty"` + Labels *[]string `json:"labels,omitempty"` + Assignee *string `json:"assignee,omitempty"` + State *string `json:"state,omitempty"` + Milestone *int `json:"milestone,omitempty"` + Assignees *[]string `json:"assignees,omitempty"` +} + +// IssueListOptions specifies the optional parameters to the IssuesService.List +// and IssuesService.ListByOrg methods. +type IssueListOptions struct { + // Filter specifies which issues to list. Possible values are: assigned, + // created, mentioned, subscribed, all. Default is "assigned". + Filter string `url:"filter,omitempty"` + + // State filters issues based on their state. Possible values are: open, + // closed, all. Default is "open". + State string `url:"state,omitempty"` + + // Labels filters issues based on their label. + Labels []string `url:"labels,comma,omitempty"` + + // Sort specifies how to sort issues. Possible values are: created, updated, + // and comments. Default value is "created". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort issues. Possible values are: asc, desc. + // Default is "desc". + Direction string `url:"direction,omitempty"` + + // Since filters issues by time. + Since time.Time `url:"since,omitempty"` + + ListOptions +} + +// PullRequestLinks object is added to the Issue object when it's an issue included +// in the IssueCommentEvent webhook payload, if the webhook is fired by a comment on a PR. +type PullRequestLinks struct { + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + DiffURL *string `json:"diff_url,omitempty"` + PatchURL *string `json:"patch_url,omitempty"` +} + +// List the issues for the authenticated user. If all is true, list issues +// across all the user's visible repositories including owned, member, and +// organization repositories; if false, list only owned and member +// repositories. +// +// GitHub API docs: https://developer.github.com/v3/issues/#list-issues +func (s *IssuesService) List(ctx context.Context, all bool, opt *IssueListOptions) ([]*Issue, *Response, error) { + var u string + if all { + u = "issues" + } else { + u = "user/issues" + } + return s.listIssues(ctx, u, opt) +} + +// ListByOrg fetches the issues in the specified organization for the +// authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/issues/#list-issues +func (s *IssuesService) ListByOrg(ctx context.Context, org string, opt *IssueListOptions) ([]*Issue, *Response, error) { + u := fmt.Sprintf("orgs/%v/issues", org) + return s.listIssues(ctx, u, opt) +} + +func (s *IssuesService) listIssues(ctx context.Context, u string, opt *IssueListOptions) ([]*Issue, *Response, error) { + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + acceptHeaders := []string{mediaTypeReactionsPreview, mediaTypeLabelDescriptionSearchPreview, mediaTypeLockReasonPreview} + req.Header.Set("Accept", strings.Join(acceptHeaders, ", ")) + + var issues []*Issue + resp, err := s.client.Do(ctx, req, &issues) + if err != nil { + return nil, resp, err + } + + return issues, resp, nil +} + +// IssueListByRepoOptions specifies the optional parameters to the +// IssuesService.ListByRepo method. +type IssueListByRepoOptions struct { + // Milestone limits issues for the specified milestone. Possible values are + // a milestone number, "none" for issues with no milestone, "*" for issues + // with any milestone. + Milestone string `url:"milestone,omitempty"` + + // State filters issues based on their state. Possible values are: open, + // closed, all. Default is "open". + State string `url:"state,omitempty"` + + // Assignee filters issues based on their assignee. Possible values are a + // user name, "none" for issues that are not assigned, "*" for issues with + // any assigned user. + Assignee string `url:"assignee,omitempty"` + + // Creator filters issues based on their creator. + Creator string `url:"creator,omitempty"` + + // Mentioned filters issues to those mentioned a specific user. + Mentioned string `url:"mentioned,omitempty"` + + // Labels filters issues based on their label. + Labels []string `url:"labels,omitempty,comma"` + + // Sort specifies how to sort issues. Possible values are: created, updated, + // and comments. Default value is "created". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort issues. Possible values are: asc, desc. + // Default is "desc". + Direction string `url:"direction,omitempty"` + + // Since filters issues by time. + Since time.Time `url:"since,omitempty"` + + ListOptions +} + +// ListByRepo lists the issues for the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/issues/#list-issues-for-a-repository +func (s *IssuesService) ListByRepo(ctx context.Context, owner string, repo string, opt *IssueListByRepoOptions) ([]*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + acceptHeaders := []string{mediaTypeReactionsPreview, mediaTypeLabelDescriptionSearchPreview, mediaTypeIntegrationPreview} + req.Header.Set("Accept", strings.Join(acceptHeaders, ", ")) + + var issues []*Issue + resp, err := s.client.Do(ctx, req, &issues) + if err != nil { + return nil, resp, err + } + + return issues, resp, nil +} + +// Get a single issue. +// +// GitHub API docs: https://developer.github.com/v3/issues/#get-a-single-issue +func (s *IssuesService) Get(ctx context.Context, owner string, repo string, number int) (*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d", owner, repo, number) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + acceptHeaders := []string{mediaTypeReactionsPreview, mediaTypeLabelDescriptionSearchPreview, mediaTypeLockReasonPreview} + req.Header.Set("Accept", strings.Join(acceptHeaders, ", ")) + + issue := new(Issue) + resp, err := s.client.Do(ctx, req, issue) + if err != nil { + return nil, resp, err + } + + return issue, resp, nil +} + +// Create a new issue on the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/issues/#create-an-issue +func (s *IssuesService) Create(ctx context.Context, owner string, repo string, issue *IssueRequest) (*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues", owner, repo) + req, err := s.client.NewRequest("POST", u, issue) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview) + + i := new(Issue) + resp, err := s.client.Do(ctx, req, i) + if err != nil { + return nil, resp, err + } + + return i, resp, nil +} + +// Edit an issue. +// +// GitHub API docs: https://developer.github.com/v3/issues/#edit-an-issue +func (s *IssuesService) Edit(ctx context.Context, owner string, repo string, number int, issue *IssueRequest) (*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d", owner, repo, number) + req, err := s.client.NewRequest("PATCH", u, issue) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview) + + i := new(Issue) + resp, err := s.client.Do(ctx, req, i) + if err != nil { + return nil, resp, err + } + + return i, resp, nil +} + +// LockIssueOptions specifies the optional parameters to the +// IssuesService.Lock method. +type LockIssueOptions struct { + // LockReason specifies the reason to lock this issue. + // Providing a lock reason can help make it clearer to contributors why an issue + // was locked. Possible values are: "off-topic", "too heated", "resolved", and "spam". + LockReason string `json:"lock_reason,omitempty"` +} + +// Lock an issue's conversation. +// +// GitHub API docs: https://developer.github.com/v3/issues/#lock-an-issue +func (s *IssuesService) Lock(ctx context.Context, owner string, repo string, number int, opt *LockIssueOptions) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/lock", owner, repo, number) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, err + } + + if opt != nil { + req.Header.Set("Accept", mediaTypeLockReasonPreview) + } + + return s.client.Do(ctx, req, nil) +} + +// Unlock an issue's conversation. +// +// GitHub API docs: https://developer.github.com/v3/issues/#unlock-an-issue +func (s *IssuesService) Unlock(ctx context.Context, owner string, repo string, number int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/lock", owner, repo, number) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/issues_assignees.go b/vendor/github.com/google/go-github/github/issues_assignees.go new file mode 100644 index 0000000..9cb366f --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_assignees.go @@ -0,0 +1,85 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ListAssignees fetches all available assignees (owners and collaborators) to +// which issues may be assigned. +// +// GitHub API docs: https://developer.github.com/v3/issues/assignees/#list-assignees +func (s *IssuesService) ListAssignees(ctx context.Context, owner, repo string, opt *ListOptions) ([]*User, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/assignees", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + var assignees []*User + resp, err := s.client.Do(ctx, req, &assignees) + if err != nil { + return nil, resp, err + } + + return assignees, resp, nil +} + +// IsAssignee checks if a user is an assignee for the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/issues/assignees/#check-assignee +func (s *IssuesService) IsAssignee(ctx context.Context, owner, repo, user string) (bool, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/assignees/%v", owner, repo, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + resp, err := s.client.Do(ctx, req, nil) + assignee, err := parseBoolResponse(err) + return assignee, resp, err +} + +// AddAssignees adds the provided GitHub users as assignees to the issue. +// +// GitHub API docs: https://developer.github.com/v3/issues/assignees/#add-assignees-to-an-issue +func (s *IssuesService) AddAssignees(ctx context.Context, owner, repo string, number int, assignees []string) (*Issue, *Response, error) { + users := &struct { + Assignees []string `json:"assignees,omitempty"` + }{Assignees: assignees} + u := fmt.Sprintf("repos/%v/%v/issues/%v/assignees", owner, repo, number) + req, err := s.client.NewRequest("POST", u, users) + if err != nil { + return nil, nil, err + } + + issue := &Issue{} + resp, err := s.client.Do(ctx, req, issue) + return issue, resp, err +} + +// RemoveAssignees removes the provided GitHub users as assignees from the issue. +// +// GitHub API docs: https://developer.github.com/v3/issues/assignees/#remove-assignees-from-an-issue +func (s *IssuesService) RemoveAssignees(ctx context.Context, owner, repo string, number int, assignees []string) (*Issue, *Response, error) { + users := &struct { + Assignees []string `json:"assignees,omitempty"` + }{Assignees: assignees} + u := fmt.Sprintf("repos/%v/%v/issues/%v/assignees", owner, repo, number) + req, err := s.client.NewRequest("DELETE", u, users) + if err != nil { + return nil, nil, err + } + + issue := &Issue{} + resp, err := s.client.Do(ctx, req, issue) + return issue, resp, err +} diff --git a/vendor/github.com/google/go-github/github/issues_assignees_test.go b/vendor/github.com/google/go-github/github/issues_assignees_test.go new file mode 100644 index 0000000..bb2bf4f --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_assignees_test.go @@ -0,0 +1,164 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestIssuesService_ListAssignees(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/assignees", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + assignees, _, err := client.Issues.ListAssignees(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Issues.ListAssignees returned error: %v", err) + } + + want := []*User{{ID: Int64(1)}} + if !reflect.DeepEqual(assignees, want) { + t.Errorf("Issues.ListAssignees returned %+v, want %+v", assignees, want) + } +} + +func TestIssuesService_ListAssignees_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.ListAssignees(context.Background(), "%", "r", nil) + testURLParseError(t, err) +} + +func TestIssuesService_IsAssignee_true(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/assignees/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + }) + + assignee, _, err := client.Issues.IsAssignee(context.Background(), "o", "r", "u") + if err != nil { + t.Errorf("Issues.IsAssignee returned error: %v", err) + } + if want := true; assignee != want { + t.Errorf("Issues.IsAssignee returned %+v, want %+v", assignee, want) + } +} + +func TestIssuesService_IsAssignee_false(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/assignees/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + assignee, _, err := client.Issues.IsAssignee(context.Background(), "o", "r", "u") + if err != nil { + t.Errorf("Issues.IsAssignee returned error: %v", err) + } + if want := false; assignee != want { + t.Errorf("Issues.IsAssignee returned %+v, want %+v", assignee, want) + } +} + +func TestIssuesService_IsAssignee_error(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/assignees/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + assignee, _, err := client.Issues.IsAssignee(context.Background(), "o", "r", "u") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; assignee != want { + t.Errorf("Issues.IsAssignee returned %+v, want %+v", assignee, want) + } +} + +func TestIssuesService_IsAssignee_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.IsAssignee(context.Background(), "%", "r", "u") + testURLParseError(t, err) +} + +func TestIssuesService_AddAssignees(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/assignees", func(w http.ResponseWriter, r *http.Request) { + var assignees struct { + Assignees []string `json:"assignees,omitempty"` + } + json.NewDecoder(r.Body).Decode(&assignees) + + testMethod(t, r, "POST") + want := []string{"user1", "user2"} + if !reflect.DeepEqual(assignees.Assignees, want) { + t.Errorf("assignees = %+v, want %+v", assignees, want) + } + fmt.Fprint(w, `{"number":1,"assignees":[{"login":"user1"},{"login":"user2"}]}`) + }) + + got, _, err := client.Issues.AddAssignees(context.Background(), "o", "r", 1, []string{"user1", "user2"}) + if err != nil { + t.Errorf("Issues.AddAssignees returned error: %v", err) + } + + want := &Issue{Number: Int(1), Assignees: []*User{{Login: String("user1")}, {Login: String("user2")}}} + if !reflect.DeepEqual(got, want) { + t.Errorf("Issues.AddAssignees = %+v, want %+v", got, want) + } +} + +func TestIssuesService_RemoveAssignees(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/assignees", func(w http.ResponseWriter, r *http.Request) { + var assignees struct { + Assignees []string `json:"assignees,omitempty"` + } + json.NewDecoder(r.Body).Decode(&assignees) + + testMethod(t, r, "DELETE") + want := []string{"user1", "user2"} + if !reflect.DeepEqual(assignees.Assignees, want) { + t.Errorf("assignees = %+v, want %+v", assignees, want) + } + fmt.Fprint(w, `{"number":1,"assignees":[]}`) + }) + + got, _, err := client.Issues.RemoveAssignees(context.Background(), "o", "r", 1, []string{"user1", "user2"}) + if err != nil { + t.Errorf("Issues.RemoveAssignees returned error: %v", err) + } + + want := &Issue{Number: Int(1), Assignees: []*User{}} + if !reflect.DeepEqual(got, want) { + t.Errorf("Issues.RemoveAssignees = %+v, want %+v", got, want) + } +} diff --git a/vendor/github.com/google/go-github/github/issues_comments.go b/vendor/github.com/google/go-github/github/issues_comments.go new file mode 100644 index 0000000..e6f6f21 --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_comments.go @@ -0,0 +1,152 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// IssueComment represents a comment left on an issue. +type IssueComment struct { + ID *int64 `json:"id,omitempty"` + Body *string `json:"body,omitempty"` + User *User `json:"user,omitempty"` + Reactions *Reactions `json:"reactions,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + // AuthorAssociation is the comment author's relationship to the issue's repository. + // Possible values are "COLLABORATOR", "CONTRIBUTOR", "FIRST_TIMER", "FIRST_TIME_CONTRIBUTOR", "MEMBER", "OWNER", or "NONE". + AuthorAssociation *string `json:"author_association,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + IssueURL *string `json:"issue_url,omitempty"` +} + +func (i IssueComment) String() string { + return Stringify(i) +} + +// IssueListCommentsOptions specifies the optional parameters to the +// IssuesService.ListComments method. +type IssueListCommentsOptions struct { + // Sort specifies how to sort comments. Possible values are: created, updated. + Sort string `url:"sort,omitempty"` + + // Direction in which to sort comments. Possible values are: asc, desc. + Direction string `url:"direction,omitempty"` + + // Since filters comments by time. + Since time.Time `url:"since,omitempty"` + + ListOptions +} + +// ListComments lists all comments on the specified issue. Specifying an issue +// number of 0 will return all comments on all issues for the repository. +// +// GitHub API docs: https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue +func (s *IssuesService) ListComments(ctx context.Context, owner string, repo string, number int, opt *IssueListCommentsOptions) ([]*IssueComment, *Response, error) { + var u string + if number == 0 { + u = fmt.Sprintf("repos/%v/%v/issues/comments", owner, repo) + } else { + u = fmt.Sprintf("repos/%v/%v/issues/%d/comments", owner, repo, number) + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var comments []*IssueComment + resp, err := s.client.Do(ctx, req, &comments) + if err != nil { + return nil, resp, err + } + + return comments, resp, nil +} + +// GetComment fetches the specified issue comment. +// +// GitHub API docs: https://developer.github.com/v3/issues/comments/#get-a-single-comment +func (s *IssuesService) GetComment(ctx context.Context, owner string, repo string, commentID int64) (*IssueComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/comments/%d", owner, repo, commentID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + comment := new(IssueComment) + resp, err := s.client.Do(ctx, req, comment) + if err != nil { + return nil, resp, err + } + + return comment, resp, nil +} + +// CreateComment creates a new comment on the specified issue. +// +// GitHub API docs: https://developer.github.com/v3/issues/comments/#create-a-comment +func (s *IssuesService) CreateComment(ctx context.Context, owner string, repo string, number int, comment *IssueComment) (*IssueComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/comments", owner, repo, number) + req, err := s.client.NewRequest("POST", u, comment) + if err != nil { + return nil, nil, err + } + c := new(IssueComment) + resp, err := s.client.Do(ctx, req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, nil +} + +// EditComment updates an issue comment. +// A non-nil comment.Body must be provided. Other comment fields should be left nil. +// +// GitHub API docs: https://developer.github.com/v3/issues/comments/#edit-a-comment +func (s *IssuesService) EditComment(ctx context.Context, owner string, repo string, commentID int64, comment *IssueComment) (*IssueComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/comments/%d", owner, repo, commentID) + req, err := s.client.NewRequest("PATCH", u, comment) + if err != nil { + return nil, nil, err + } + c := new(IssueComment) + resp, err := s.client.Do(ctx, req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, nil +} + +// DeleteComment deletes an issue comment. +// +// GitHub API docs: https://developer.github.com/v3/issues/comments/#delete-a-comment +func (s *IssuesService) DeleteComment(ctx context.Context, owner string, repo string, commentID int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/comments/%d", owner, repo, commentID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/issues_comments_test.go b/vendor/github.com/google/go-github/github/issues_comments_test.go new file mode 100644 index 0000000..6911a79 --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_comments_test.go @@ -0,0 +1,203 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestIssuesService_ListComments_allIssues(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + testFormValues(t, r, values{ + "sort": "updated", + "direction": "desc", + "since": "2002-02-10T15:30:00Z", + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &IssueListCommentsOptions{ + Sort: "updated", + Direction: "desc", + Since: time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC), + ListOptions: ListOptions{Page: 2}, + } + comments, _, err := client.Issues.ListComments(context.Background(), "o", "r", 0, opt) + if err != nil { + t.Errorf("Issues.ListComments returned error: %v", err) + } + + want := []*IssueComment{{ID: Int64(1)}} + if !reflect.DeepEqual(comments, want) { + t.Errorf("Issues.ListComments returned %+v, want %+v", comments, want) + } +} + +func TestIssuesService_ListComments_specificIssue(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + fmt.Fprint(w, `[{"id":1}]`) + }) + + comments, _, err := client.Issues.ListComments(context.Background(), "o", "r", 1, nil) + if err != nil { + t.Errorf("Issues.ListComments returned error: %v", err) + } + + want := []*IssueComment{{ID: Int64(1)}} + if !reflect.DeepEqual(comments, want) { + t.Errorf("Issues.ListComments returned %+v, want %+v", comments, want) + } +} + +func TestIssuesService_ListComments_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.ListComments(context.Background(), "%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_GetComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Issues.GetComment(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Issues.GetComment returned error: %v", err) + } + + want := &IssueComment{ID: Int64(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Issues.GetComment returned %+v, want %+v", comment, want) + } +} + +func TestIssuesService_GetComment_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.GetComment(context.Background(), "%", "r", 1) + testURLParseError(t, err) +} + +func TestIssuesService_CreateComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &IssueComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/issues/1/comments", func(w http.ResponseWriter, r *http.Request) { + v := new(IssueComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Issues.CreateComment(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("Issues.CreateComment returned error: %v", err) + } + + want := &IssueComment{ID: Int64(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Issues.CreateComment returned %+v, want %+v", comment, want) + } +} + +func TestIssuesService_CreateComment_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.CreateComment(context.Background(), "%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_EditComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &IssueComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/issues/comments/1", func(w http.ResponseWriter, r *http.Request) { + v := new(IssueComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Issues.EditComment(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("Issues.EditComment returned error: %v", err) + } + + want := &IssueComment{ID: Int64(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Issues.EditComment returned %+v, want %+v", comment, want) + } +} + +func TestIssuesService_EditComment_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.EditComment(context.Background(), "%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_DeleteComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Issues.DeleteComment(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Issues.DeleteComments returned error: %v", err) + } +} + +func TestIssuesService_DeleteComment_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Issues.DeleteComment(context.Background(), "%", "r", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/issues_events.go b/vendor/github.com/google/go-github/github/issues_events.go new file mode 100644 index 0000000..f71e463 --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_events.go @@ -0,0 +1,158 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// IssueEvent represents an event that occurred around an Issue or Pull Request. +type IssueEvent struct { + ID *int64 `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + + // The User that generated this event. + Actor *User `json:"actor,omitempty"` + + // Event identifies the actual type of Event that occurred. Possible + // values are: + // + // closed + // The Actor closed the issue. + // If the issue was closed by commit message, CommitID holds the SHA1 hash of the commit. + // + // merged + // The Actor merged into master a branch containing a commit mentioning the issue. + // CommitID holds the SHA1 of the merge commit. + // + // referenced + // The Actor committed to master a commit mentioning the issue in its commit message. + // CommitID holds the SHA1 of the commit. + // + // reopened, unlocked + // The Actor did that to the issue. + // + // locked + // The Actor locked the issue. + // LockReason holds the reason of locking the issue (if provided while locking). + // + // renamed + // The Actor changed the issue title from Rename.From to Rename.To. + // + // mentioned + // Someone unspecified @mentioned the Actor [sic] in an issue comment body. + // + // assigned, unassigned + // The Assigner assigned the issue to or removed the assignment from the Assignee. + // + // labeled, unlabeled + // The Actor added or removed the Label from the issue. + // + // milestoned, demilestoned + // The Actor added or removed the issue from the Milestone. + // + // subscribed, unsubscribed + // The Actor subscribed to or unsubscribed from notifications for an issue. + // + // head_ref_deleted, head_ref_restored + // The pull request’s branch was deleted or restored. + // + Event *string `json:"event,omitempty"` + + CreatedAt *time.Time `json:"created_at,omitempty"` + Issue *Issue `json:"issue,omitempty"` + + // Only present on certain events; see above. + Assignee *User `json:"assignee,omitempty"` + Assigner *User `json:"assigner,omitempty"` + CommitID *string `json:"commit_id,omitempty"` + Milestone *Milestone `json:"milestone,omitempty"` + Label *Label `json:"label,omitempty"` + Rename *Rename `json:"rename,omitempty"` + LockReason *string `json:"lock_reason,omitempty"` +} + +// ListIssueEvents lists events for the specified issue. +// +// GitHub API docs: https://developer.github.com/v3/issues/events/#list-events-for-an-issue +func (s *IssuesService) ListIssueEvents(ctx context.Context, owner, repo string, number int, opt *ListOptions) ([]*IssueEvent, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/events", owner, repo, number) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeLockReasonPreview) + + var events []*IssueEvent + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListRepositoryEvents lists events for the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/issues/events/#list-events-for-a-repository +func (s *IssuesService) ListRepositoryEvents(ctx context.Context, owner, repo string, opt *ListOptions) ([]*IssueEvent, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/events", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var events []*IssueEvent + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// GetEvent returns the specified issue event. +// +// GitHub API docs: https://developer.github.com/v3/issues/events/#get-a-single-event +func (s *IssuesService) GetEvent(ctx context.Context, owner, repo string, id int64) (*IssueEvent, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/events/%v", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + event := new(IssueEvent) + resp, err := s.client.Do(ctx, req, event) + if err != nil { + return nil, resp, err + } + + return event, resp, nil +} + +// Rename contains details for 'renamed' events. +type Rename struct { + From *string `json:"from,omitempty"` + To *string `json:"to,omitempty"` +} + +func (r Rename) String() string { + return Stringify(r) +} diff --git a/vendor/github.com/google/go-github/github/issues_events_test.go b/vendor/github.com/google/go-github/github/issues_events_test.go new file mode 100644 index 0000000..fe21ecb --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_events_test.go @@ -0,0 +1,84 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestIssuesService_ListIssueEvents(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + events, _, err := client.Issues.ListIssueEvents(context.Background(), "o", "r", 1, opt) + if err != nil { + t.Errorf("Issues.ListIssueEvents returned error: %v", err) + } + + want := []*IssueEvent{{ID: Int64(1)}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Issues.ListIssueEvents returned %+v, want %+v", events, want) + } +} + +func TestIssuesService_ListRepositoryEvents(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + events, _, err := client.Issues.ListRepositoryEvents(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Issues.ListRepositoryEvents returned error: %v", err) + } + + want := []*IssueEvent{{ID: Int64(1)}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Issues.ListRepositoryEvents returned %+v, want %+v", events, want) + } +} + +func TestIssuesService_GetEvent(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/events/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + event, _, err := client.Issues.GetEvent(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Issues.GetEvent returned error: %v", err) + } + + want := &IssueEvent{ID: Int64(1)} + if !reflect.DeepEqual(event, want) { + t.Errorf("Issues.GetEvent returned %+v, want %+v", event, want) + } +} diff --git a/vendor/github.com/google/go-github/github/issues_labels.go b/vendor/github.com/google/go-github/github/issues_labels.go new file mode 100644 index 0000000..adcbe06 --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_labels.go @@ -0,0 +1,261 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// Label represents a GitHub label on an Issue +type Label struct { + ID *int64 `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + Name *string `json:"name,omitempty"` + Color *string `json:"color,omitempty"` + Description *string `json:"description,omitempty"` + Default *bool `json:"default,omitempty"` + NodeID *string `json:"node_id,omitempty"` +} + +func (l Label) String() string { + return Stringify(l) +} + +// ListLabels lists all labels for a repository. +// +// GitHub API docs: https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository +func (s *IssuesService) ListLabels(ctx context.Context, owner string, repo string, opt *ListOptions) ([]*Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/labels", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview) + + var labels []*Label + resp, err := s.client.Do(ctx, req, &labels) + if err != nil { + return nil, resp, err + } + + return labels, resp, nil +} + +// GetLabel gets a single label. +// +// GitHub API docs: https://developer.github.com/v3/issues/labels/#get-a-single-label +func (s *IssuesService) GetLabel(ctx context.Context, owner string, repo string, name string) (*Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/labels/%v", owner, repo, name) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview) + + label := new(Label) + resp, err := s.client.Do(ctx, req, label) + if err != nil { + return nil, resp, err + } + + return label, resp, nil +} + +// CreateLabel creates a new label on the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/issues/labels/#create-a-label +func (s *IssuesService) CreateLabel(ctx context.Context, owner string, repo string, label *Label) (*Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/labels", owner, repo) + req, err := s.client.NewRequest("POST", u, label) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview) + + l := new(Label) + resp, err := s.client.Do(ctx, req, l) + if err != nil { + return nil, resp, err + } + + return l, resp, nil +} + +// EditLabel edits a label. +// +// GitHub API docs: https://developer.github.com/v3/issues/labels/#update-a-label +func (s *IssuesService) EditLabel(ctx context.Context, owner string, repo string, name string, label *Label) (*Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/labels/%v", owner, repo, name) + req, err := s.client.NewRequest("PATCH", u, label) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview) + + l := new(Label) + resp, err := s.client.Do(ctx, req, l) + if err != nil { + return nil, resp, err + } + + return l, resp, nil +} + +// DeleteLabel deletes a label. +// +// GitHub API docs: https://developer.github.com/v3/issues/labels/#delete-a-label +func (s *IssuesService) DeleteLabel(ctx context.Context, owner string, repo string, name string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/labels/%v", owner, repo, name) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// ListLabelsByIssue lists all labels for an issue. +// +// GitHub API docs: https://developer.github.com/v3/issues/labels/#list-labels-on-an-issue +func (s *IssuesService) ListLabelsByIssue(ctx context.Context, owner string, repo string, number int, opt *ListOptions) ([]*Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview) + + var labels []*Label + resp, err := s.client.Do(ctx, req, &labels) + if err != nil { + return nil, resp, err + } + + return labels, resp, nil +} + +// AddLabelsToIssue adds labels to an issue. +// +// GitHub API docs: https://developer.github.com/v3/issues/labels/#add-labels-to-an-issue +func (s *IssuesService) AddLabelsToIssue(ctx context.Context, owner string, repo string, number int, labels []string) ([]*Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number) + req, err := s.client.NewRequest("POST", u, labels) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview) + + var l []*Label + resp, err := s.client.Do(ctx, req, &l) + if err != nil { + return nil, resp, err + } + + return l, resp, nil +} + +// RemoveLabelForIssue removes a label for an issue. +// +// GitHub API docs: https://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue +func (s *IssuesService) RemoveLabelForIssue(ctx context.Context, owner string, repo string, number int, label string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/labels/%v", owner, repo, number, label) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview) + + return s.client.Do(ctx, req, nil) +} + +// ReplaceLabelsForIssue replaces all labels for an issue. +// +// GitHub API docs: https://developer.github.com/v3/issues/labels/#replace-all-labels-for-an-issue +func (s *IssuesService) ReplaceLabelsForIssue(ctx context.Context, owner string, repo string, number int, labels []string) ([]*Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number) + req, err := s.client.NewRequest("PUT", u, labels) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview) + + var l []*Label + resp, err := s.client.Do(ctx, req, &l) + if err != nil { + return nil, resp, err + } + + return l, resp, nil +} + +// RemoveLabelsForIssue removes all labels for an issue. +// +// GitHub API docs: https://developer.github.com/v3/issues/labels/#remove-all-labels-from-an-issue +func (s *IssuesService) RemoveLabelsForIssue(ctx context.Context, owner string, repo string, number int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview) + + return s.client.Do(ctx, req, nil) +} + +// ListLabelsForMilestone lists labels for every issue in a milestone. +// +// GitHub API docs: https://developer.github.com/v3/issues/labels/#get-labels-for-every-issue-in-a-milestone +func (s *IssuesService) ListLabelsForMilestone(ctx context.Context, owner string, repo string, number int, opt *ListOptions) ([]*Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/milestones/%d/labels", owner, repo, number) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview) + + var labels []*Label + resp, err := s.client.Do(ctx, req, &labels) + if err != nil { + return nil, resp, err + } + + return labels, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/issues_labels_test.go b/vendor/github.com/google/go-github/github/issues_labels_test.go new file mode 100644 index 0000000..6437d88 --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_labels_test.go @@ -0,0 +1,360 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestIssuesService_ListLabels(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/labels", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeLabelDescriptionSearchPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"name": "a"},{"name": "b"}]`) + }) + + opt := &ListOptions{Page: 2} + labels, _, err := client.Issues.ListLabels(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Issues.ListLabels returned error: %v", err) + } + + want := []*Label{{Name: String("a")}, {Name: String("b")}} + if !reflect.DeepEqual(labels, want) { + t.Errorf("Issues.ListLabels returned %+v, want %+v", labels, want) + } +} + +func TestIssuesService_ListLabels_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.ListLabels(context.Background(), "%", "%", nil) + testURLParseError(t, err) +} + +func TestIssuesService_GetLabel(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/labels/n", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeLabelDescriptionSearchPreview) + fmt.Fprint(w, `{"url":"u", "name": "n", "color": "c", "description": "d"}`) + }) + + label, _, err := client.Issues.GetLabel(context.Background(), "o", "r", "n") + if err != nil { + t.Errorf("Issues.GetLabel returned error: %v", err) + } + + want := &Label{URL: String("u"), Name: String("n"), Color: String("c"), Description: String("d")} + if !reflect.DeepEqual(label, want) { + t.Errorf("Issues.GetLabel returned %+v, want %+v", label, want) + } +} + +func TestIssuesService_GetLabel_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.GetLabel(context.Background(), "%", "%", "%") + testURLParseError(t, err) +} + +func TestIssuesService_CreateLabel(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Label{Name: String("n")} + + mux.HandleFunc("/repos/o/r/labels", func(w http.ResponseWriter, r *http.Request) { + v := new(Label) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeLabelDescriptionSearchPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"url":"u"}`) + }) + + label, _, err := client.Issues.CreateLabel(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("Issues.CreateLabel returned error: %v", err) + } + + want := &Label{URL: String("u")} + if !reflect.DeepEqual(label, want) { + t.Errorf("Issues.CreateLabel returned %+v, want %+v", label, want) + } +} + +func TestIssuesService_CreateLabel_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.CreateLabel(context.Background(), "%", "%", nil) + testURLParseError(t, err) +} + +func TestIssuesService_EditLabel(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Label{Name: String("z")} + + mux.HandleFunc("/repos/o/r/labels/n", func(w http.ResponseWriter, r *http.Request) { + v := new(Label) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeLabelDescriptionSearchPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"url":"u"}`) + }) + + label, _, err := client.Issues.EditLabel(context.Background(), "o", "r", "n", input) + if err != nil { + t.Errorf("Issues.EditLabel returned error: %v", err) + } + + want := &Label{URL: String("u")} + if !reflect.DeepEqual(label, want) { + t.Errorf("Issues.EditLabel returned %+v, want %+v", label, want) + } +} + +func TestIssuesService_EditLabel_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.EditLabel(context.Background(), "%", "%", "%", nil) + testURLParseError(t, err) +} + +func TestIssuesService_DeleteLabel(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/labels/n", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Issues.DeleteLabel(context.Background(), "o", "r", "n") + if err != nil { + t.Errorf("Issues.DeleteLabel returned error: %v", err) + } +} + +func TestIssuesService_DeleteLabel_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Issues.DeleteLabel(context.Background(), "%", "%", "%") + testURLParseError(t, err) +} + +func TestIssuesService_ListLabelsByIssue(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeLabelDescriptionSearchPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"name":"a","id":1},{"name":"b","id":2}]`) + }) + + opt := &ListOptions{Page: 2} + labels, _, err := client.Issues.ListLabelsByIssue(context.Background(), "o", "r", 1, opt) + if err != nil { + t.Errorf("Issues.ListLabelsByIssue returned error: %v", err) + } + + want := []*Label{ + {Name: String("a"), ID: Int64(1)}, + {Name: String("b"), ID: Int64(2)}, + } + if !reflect.DeepEqual(labels, want) { + t.Errorf("Issues.ListLabelsByIssue returned %+v, want %+v", labels, want) + } +} + +func TestIssuesService_ListLabelsByIssue_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.ListLabelsByIssue(context.Background(), "%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_AddLabelsToIssue(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := []string{"a", "b"} + + mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) { + var v []string + json.NewDecoder(r.Body).Decode(&v) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeLabelDescriptionSearchPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `[{"url":"u"}]`) + }) + + labels, _, err := client.Issues.AddLabelsToIssue(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("Issues.AddLabelsToIssue returned error: %v", err) + } + + want := []*Label{{URL: String("u")}} + if !reflect.DeepEqual(labels, want) { + t.Errorf("Issues.AddLabelsToIssue returned %+v, want %+v", labels, want) + } +} + +func TestIssuesService_AddLabelsToIssue_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.AddLabelsToIssue(context.Background(), "%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_RemoveLabelForIssue(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/labels/l", func(w http.ResponseWriter, r *http.Request) { + testHeader(t, r, "Accept", mediaTypeLabelDescriptionSearchPreview) + testMethod(t, r, "DELETE") + }) + + _, err := client.Issues.RemoveLabelForIssue(context.Background(), "o", "r", 1, "l") + if err != nil { + t.Errorf("Issues.RemoveLabelForIssue returned error: %v", err) + } +} + +func TestIssuesService_RemoveLabelForIssue_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Issues.RemoveLabelForIssue(context.Background(), "%", "%", 1, "%") + testURLParseError(t, err) +} + +func TestIssuesService_ReplaceLabelsForIssue(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := []string{"a", "b"} + + mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) { + var v []string + json.NewDecoder(r.Body).Decode(&v) + + testMethod(t, r, "PUT") + testHeader(t, r, "Accept", mediaTypeLabelDescriptionSearchPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `[{"url":"u"}]`) + }) + + labels, _, err := client.Issues.ReplaceLabelsForIssue(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("Issues.ReplaceLabelsForIssue returned error: %v", err) + } + + want := []*Label{{URL: String("u")}} + if !reflect.DeepEqual(labels, want) { + t.Errorf("Issues.ReplaceLabelsForIssue returned %+v, want %+v", labels, want) + } +} + +func TestIssuesService_ReplaceLabelsForIssue_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.ReplaceLabelsForIssue(context.Background(), "%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_RemoveLabelsForIssue(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeLabelDescriptionSearchPreview) + }) + + _, err := client.Issues.RemoveLabelsForIssue(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Issues.RemoveLabelsForIssue returned error: %v", err) + } +} + +func TestIssuesService_RemoveLabelsForIssue_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Issues.RemoveLabelsForIssue(context.Background(), "%", "%", 1) + testURLParseError(t, err) +} + +func TestIssuesService_ListLabelsForMilestone(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/milestones/1/labels", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeLabelDescriptionSearchPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"name": "a"},{"name": "b"}]`) + }) + + opt := &ListOptions{Page: 2} + labels, _, err := client.Issues.ListLabelsForMilestone(context.Background(), "o", "r", 1, opt) + if err != nil { + t.Errorf("Issues.ListLabelsForMilestone returned error: %v", err) + } + + want := []*Label{{Name: String("a")}, {Name: String("b")}} + if !reflect.DeepEqual(labels, want) { + t.Errorf("Issues.ListLabelsForMilestone returned %+v, want %+v", labels, want) + } +} + +func TestIssuesService_ListLabelsForMilestone_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.ListLabelsForMilestone(context.Background(), "%", "%", 1, nil) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/issues_milestones.go b/vendor/github.com/google/go-github/github/issues_milestones.go new file mode 100644 index 0000000..ffe9aae --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_milestones.go @@ -0,0 +1,148 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// Milestone represents a GitHub repository milestone. +type Milestone struct { + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + LabelsURL *string `json:"labels_url,omitempty"` + ID *int64 `json:"id,omitempty"` + Number *int `json:"number,omitempty"` + State *string `json:"state,omitempty"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` + Creator *User `json:"creator,omitempty"` + OpenIssues *int `json:"open_issues,omitempty"` + ClosedIssues *int `json:"closed_issues,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + ClosedAt *time.Time `json:"closed_at,omitempty"` + DueOn *time.Time `json:"due_on,omitempty"` + NodeID *string `json:"node_id,omitempty"` +} + +func (m Milestone) String() string { + return Stringify(m) +} + +// MilestoneListOptions specifies the optional parameters to the +// IssuesService.ListMilestones method. +type MilestoneListOptions struct { + // State filters milestones based on their state. Possible values are: + // open, closed, all. Default is "open". + State string `url:"state,omitempty"` + + // Sort specifies how to sort milestones. Possible values are: due_on, completeness. + // Default value is "due_on". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort milestones. Possible values are: asc, desc. + // Default is "asc". + Direction string `url:"direction,omitempty"` + + ListOptions +} + +// ListMilestones lists all milestones for a repository. +// +// GitHub API docs: https://developer.github.com/v3/issues/milestones/#list-milestones-for-a-repository +func (s *IssuesService) ListMilestones(ctx context.Context, owner string, repo string, opt *MilestoneListOptions) ([]*Milestone, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/milestones", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var milestones []*Milestone + resp, err := s.client.Do(ctx, req, &milestones) + if err != nil { + return nil, resp, err + } + + return milestones, resp, nil +} + +// GetMilestone gets a single milestone. +// +// GitHub API docs: https://developer.github.com/v3/issues/milestones/#get-a-single-milestone +func (s *IssuesService) GetMilestone(ctx context.Context, owner string, repo string, number int) (*Milestone, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/milestones/%d", owner, repo, number) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + milestone := new(Milestone) + resp, err := s.client.Do(ctx, req, milestone) + if err != nil { + return nil, resp, err + } + + return milestone, resp, nil +} + +// CreateMilestone creates a new milestone on the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/issues/milestones/#create-a-milestone +func (s *IssuesService) CreateMilestone(ctx context.Context, owner string, repo string, milestone *Milestone) (*Milestone, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/milestones", owner, repo) + req, err := s.client.NewRequest("POST", u, milestone) + if err != nil { + return nil, nil, err + } + + m := new(Milestone) + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// EditMilestone edits a milestone. +// +// GitHub API docs: https://developer.github.com/v3/issues/milestones/#update-a-milestone +func (s *IssuesService) EditMilestone(ctx context.Context, owner string, repo string, number int, milestone *Milestone) (*Milestone, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/milestones/%d", owner, repo, number) + req, err := s.client.NewRequest("PATCH", u, milestone) + if err != nil { + return nil, nil, err + } + + m := new(Milestone) + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// DeleteMilestone deletes a milestone. +// +// GitHub API docs: https://developer.github.com/v3/issues/milestones/#delete-a-milestone +func (s *IssuesService) DeleteMilestone(ctx context.Context, owner string, repo string, number int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/milestones/%d", owner, repo, number) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/issues_milestones_test.go b/vendor/github.com/google/go-github/github/issues_milestones_test.go new file mode 100644 index 0000000..019f00a --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_milestones_test.go @@ -0,0 +1,174 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestIssuesService_ListMilestones(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/milestones", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "state": "closed", + "sort": "due_date", + "direction": "asc", + "page": "2", + }) + fmt.Fprint(w, `[{"number":1}]`) + }) + + opt := &MilestoneListOptions{"closed", "due_date", "asc", ListOptions{Page: 2}} + milestones, _, err := client.Issues.ListMilestones(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("IssuesService.ListMilestones returned error: %v", err) + } + + want := []*Milestone{{Number: Int(1)}} + if !reflect.DeepEqual(milestones, want) { + t.Errorf("IssuesService.ListMilestones returned %+v, want %+v", milestones, want) + } +} + +func TestIssuesService_ListMilestones_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.ListMilestones(context.Background(), "%", "r", nil) + testURLParseError(t, err) +} + +func TestIssuesService_GetMilestone(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/milestones/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"number":1}`) + }) + + milestone, _, err := client.Issues.GetMilestone(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("IssuesService.GetMilestone returned error: %v", err) + } + + want := &Milestone{Number: Int(1)} + if !reflect.DeepEqual(milestone, want) { + t.Errorf("IssuesService.GetMilestone returned %+v, want %+v", milestone, want) + } +} + +func TestIssuesService_GetMilestone_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.GetMilestone(context.Background(), "%", "r", 1) + testURLParseError(t, err) +} + +func TestIssuesService_CreateMilestone(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Milestone{Title: String("t")} + + mux.HandleFunc("/repos/o/r/milestones", func(w http.ResponseWriter, r *http.Request) { + v := new(Milestone) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"number":1}`) + }) + + milestone, _, err := client.Issues.CreateMilestone(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("IssuesService.CreateMilestone returned error: %v", err) + } + + want := &Milestone{Number: Int(1)} + if !reflect.DeepEqual(milestone, want) { + t.Errorf("IssuesService.CreateMilestone returned %+v, want %+v", milestone, want) + } +} + +func TestIssuesService_CreateMilestone_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.CreateMilestone(context.Background(), "%", "r", nil) + testURLParseError(t, err) +} + +func TestIssuesService_EditMilestone(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Milestone{Title: String("t")} + + mux.HandleFunc("/repos/o/r/milestones/1", func(w http.ResponseWriter, r *http.Request) { + v := new(Milestone) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"number":1}`) + }) + + milestone, _, err := client.Issues.EditMilestone(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("IssuesService.EditMilestone returned error: %v", err) + } + + want := &Milestone{Number: Int(1)} + if !reflect.DeepEqual(milestone, want) { + t.Errorf("IssuesService.EditMilestone returned %+v, want %+v", milestone, want) + } +} + +func TestIssuesService_EditMilestone_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.EditMilestone(context.Background(), "%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_DeleteMilestone(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/milestones/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Issues.DeleteMilestone(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("IssuesService.DeleteMilestone returned error: %v", err) + } +} + +func TestIssuesService_DeleteMilestone_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Issues.DeleteMilestone(context.Background(), "%", "r", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/issues_test.go b/vendor/github.com/google/go-github/github/issues_test.go new file mode 100644 index 0000000..22190a0 --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_test.go @@ -0,0 +1,328 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "strings" + "testing" + "time" +) + +func TestIssuesService_List_all(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + acceptHeaders := []string{mediaTypeReactionsPreview, mediaTypeLabelDescriptionSearchPreview, mediaTypeLockReasonPreview} + mux.HandleFunc("/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + testFormValues(t, r, values{ + "filter": "all", + "state": "closed", + "labels": "a,b", + "sort": "updated", + "direction": "asc", + "since": "2002-02-10T15:30:00Z", + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `[{"number":1}]`) + }) + + opt := &IssueListOptions{ + "all", "closed", []string{"a", "b"}, "updated", "asc", + time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC), + ListOptions{Page: 1, PerPage: 2}, + } + issues, _, err := client.Issues.List(context.Background(), true, opt) + if err != nil { + t.Errorf("Issues.List returned error: %v", err) + } + + want := []*Issue{{Number: Int(1)}} + if !reflect.DeepEqual(issues, want) { + t.Errorf("Issues.List returned %+v, want %+v", issues, want) + } +} + +func TestIssuesService_List_owned(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + acceptHeaders := []string{mediaTypeReactionsPreview, mediaTypeLabelDescriptionSearchPreview, mediaTypeLockReasonPreview} + mux.HandleFunc("/user/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + fmt.Fprint(w, `[{"number":1}]`) + }) + + issues, _, err := client.Issues.List(context.Background(), false, nil) + if err != nil { + t.Errorf("Issues.List returned error: %v", err) + } + + want := []*Issue{{Number: Int(1)}} + if !reflect.DeepEqual(issues, want) { + t.Errorf("Issues.List returned %+v, want %+v", issues, want) + } +} + +func TestIssuesService_ListByOrg(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + acceptHeaders := []string{mediaTypeReactionsPreview, mediaTypeLabelDescriptionSearchPreview, mediaTypeLockReasonPreview} + mux.HandleFunc("/orgs/o/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + fmt.Fprint(w, `[{"number":1}]`) + }) + + issues, _, err := client.Issues.ListByOrg(context.Background(), "o", nil) + if err != nil { + t.Errorf("Issues.ListByOrg returned error: %v", err) + } + + want := []*Issue{{Number: Int(1)}} + if !reflect.DeepEqual(issues, want) { + t.Errorf("Issues.List returned %+v, want %+v", issues, want) + } +} + +func TestIssuesService_ListByOrg_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.ListByOrg(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestIssuesService_ListByRepo(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + acceptHeaders := []string{mediaTypeReactionsPreview, mediaTypeLabelDescriptionSearchPreview, mediaTypeIntegrationPreview} + mux.HandleFunc("/repos/o/r/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + testFormValues(t, r, values{ + "milestone": "*", + "state": "closed", + "assignee": "a", + "creator": "c", + "mentioned": "m", + "labels": "a,b", + "sort": "updated", + "direction": "asc", + "since": "2002-02-10T15:30:00Z", + }) + fmt.Fprint(w, `[{"number":1}]`) + }) + + opt := &IssueListByRepoOptions{ + "*", "closed", "a", "c", "m", []string{"a", "b"}, "updated", "asc", + time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC), + ListOptions{0, 0}, + } + issues, _, err := client.Issues.ListByRepo(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Issues.ListByOrg returned error: %v", err) + } + + want := []*Issue{{Number: Int(1)}} + if !reflect.DeepEqual(issues, want) { + t.Errorf("Issues.List returned %+v, want %+v", issues, want) + } +} + +func TestIssuesService_ListByRepo_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.ListByRepo(context.Background(), "%", "r", nil) + testURLParseError(t, err) +} + +func TestIssuesService_Get(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + acceptHeaders := []string{mediaTypeReactionsPreview, mediaTypeLabelDescriptionSearchPreview, mediaTypeLockReasonPreview} + mux.HandleFunc("/repos/o/r/issues/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + fmt.Fprint(w, `{"number":1, "labels": [{"url": "u", "name": "n", "color": "c"}]}`) + }) + + issue, _, err := client.Issues.Get(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Issues.Get returned error: %v", err) + } + + want := &Issue{ + Number: Int(1), + Labels: []Label{{ + URL: String("u"), + Name: String("n"), + Color: String("c"), + }}, + } + if !reflect.DeepEqual(issue, want) { + t.Errorf("Issues.Get returned %+v, want %+v", issue, want) + } +} + +func TestIssuesService_Get_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.Get(context.Background(), "%", "r", 1) + testURLParseError(t, err) +} + +func TestIssuesService_Create(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &IssueRequest{ + Title: String("t"), + Body: String("b"), + Assignee: String("a"), + Labels: &[]string{"l1", "l2"}, + } + + mux.HandleFunc("/repos/o/r/issues", func(w http.ResponseWriter, r *http.Request) { + v := new(IssueRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeLabelDescriptionSearchPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"number":1}`) + }) + + issue, _, err := client.Issues.Create(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("Issues.Create returned error: %v", err) + } + + want := &Issue{Number: Int(1)} + if !reflect.DeepEqual(issue, want) { + t.Errorf("Issues.Create returned %+v, want %+v", issue, want) + } +} + +func TestIssuesService_Create_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.Create(context.Background(), "%", "r", nil) + testURLParseError(t, err) +} + +func TestIssuesService_Edit(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &IssueRequest{Title: String("t")} + + mux.HandleFunc("/repos/o/r/issues/1", func(w http.ResponseWriter, r *http.Request) { + v := new(IssueRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeLabelDescriptionSearchPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"number":1}`) + }) + + issue, _, err := client.Issues.Edit(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("Issues.Edit returned error: %v", err) + } + + want := &Issue{Number: Int(1)} + if !reflect.DeepEqual(issue, want) { + t.Errorf("Issues.Edit returned %+v, want %+v", issue, want) + } +} + +func TestIssuesService_Edit_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Issues.Edit(context.Background(), "%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_Lock(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/lock", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + + w.WriteHeader(http.StatusNoContent) + }) + + if _, err := client.Issues.Lock(context.Background(), "o", "r", 1, nil); err != nil { + t.Errorf("Issues.Lock returned error: %v", err) + } +} + +func TestIssuesService_LockWithReason(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/lock", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testHeader(t, r, "Accept", mediaTypeLockReasonPreview) + w.WriteHeader(http.StatusNoContent) + }) + + opt := &LockIssueOptions{LockReason: "off-topic"} + + if _, err := client.Issues.Lock(context.Background(), "o", "r", 1, opt); err != nil { + t.Errorf("Issues.Lock returned error: %v", err) + } +} + +func TestIssuesService_Unlock(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/lock", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + + w.WriteHeader(http.StatusNoContent) + }) + + if _, err := client.Issues.Unlock(context.Background(), "o", "r", 1); err != nil { + t.Errorf("Issues.Unlock returned error: %v", err) + } +} + +func TestIsPullRequest(t *testing.T) { + i := new(Issue) + if i.IsPullRequest() == true { + t.Errorf("expected i.IsPullRequest (%v) to return false, got true", i) + } + i.PullRequestLinks = &PullRequestLinks{URL: String("http://example.com")} + if i.IsPullRequest() == false { + t.Errorf("expected i.IsPullRequest (%v) to return true, got false", i) + } +} diff --git a/vendor/github.com/google/go-github/github/issues_timeline.go b/vendor/github.com/google/go-github/github/issues_timeline.go new file mode 100644 index 0000000..9cfda83 --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_timeline.go @@ -0,0 +1,149 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// Timeline represents an event that occurred around an Issue or Pull Request. +// +// It is similar to an IssueEvent but may contain more information. +// GitHub API docs: https://developer.github.com/v3/issues/timeline/ +type Timeline struct { + ID *int64 `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + CommitURL *string `json:"commit_url,omitempty"` + + // The User object that generated the event. + Actor *User `json:"actor,omitempty"` + + // Event identifies the actual type of Event that occurred. Possible values + // are: + // + // assigned + // The issue was assigned to the assignee. + // + // closed + // The issue was closed by the actor. When the commit_id is present, it + // identifies the commit that closed the issue using "closes / fixes #NN" + // syntax. + // + // commented + // A comment was added to the issue. + // + // committed + // A commit was added to the pull request's 'HEAD' branch. Only provided + // for pull requests. + // + // cross-referenced + // The issue was referenced from another issue. The 'source' attribute + // contains the 'id', 'actor', and 'url' of the reference's source. + // + // demilestoned + // The issue was removed from a milestone. + // + // head_ref_deleted + // The pull request's branch was deleted. + // + // head_ref_restored + // The pull request's branch was restored. + // + // labeled + // A label was added to the issue. + // + // locked + // The issue was locked by the actor. + // + // mentioned + // The actor was @mentioned in an issue body. + // + // merged + // The issue was merged by the actor. The 'commit_id' attribute is the + // SHA1 of the HEAD commit that was merged. + // + // milestoned + // The issue was added to a milestone. + // + // referenced + // The issue was referenced from a commit message. The 'commit_id' + // attribute is the commit SHA1 of where that happened. + // + // renamed + // The issue title was changed. + // + // reopened + // The issue was reopened by the actor. + // + // subscribed + // The actor subscribed to receive notifications for an issue. + // + // unassigned + // The assignee was unassigned from the issue. + // + // unlabeled + // A label was removed from the issue. + // + // unlocked + // The issue was unlocked by the actor. + // + // unsubscribed + // The actor unsubscribed to stop receiving notifications for an issue. + // + Event *string `json:"event,omitempty"` + + // The string SHA of a commit that referenced this Issue or Pull Request. + CommitID *string `json:"commit_id,omitempty"` + // The timestamp indicating when the event occurred. + CreatedAt *time.Time `json:"created_at,omitempty"` + // The Label object including `name` and `color` attributes. Only provided for + // 'labeled' and 'unlabeled' events. + Label *Label `json:"label,omitempty"` + // The User object which was assigned to (or unassigned from) this Issue or + // Pull Request. Only provided for 'assigned' and 'unassigned' events. + Assignee *User `json:"assignee,omitempty"` + // The Milestone object including a 'title' attribute. + // Only provided for 'milestoned' and 'demilestoned' events. + Milestone *Milestone `json:"milestone,omitempty"` + // The 'id', 'actor', and 'url' for the source of a reference from another issue. + // Only provided for 'cross-referenced' events. + Source *Source `json:"source,omitempty"` + // An object containing rename details including 'from' and 'to' attributes. + // Only provided for 'renamed' events. + Rename *Rename `json:"rename,omitempty"` +} + +// Source represents a reference's source. +type Source struct { + ID *int64 `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + Actor *User `json:"actor,omitempty"` +} + +// ListIssueTimeline lists events for the specified issue. +// +// GitHub API docs: https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue +func (s *IssuesService) ListIssueTimeline(ctx context.Context, owner, repo string, number int, opt *ListOptions) ([]*Timeline, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/timeline", owner, repo, number) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTimelinePreview) + + var events []*Timeline + resp, err := s.client.Do(ctx, req, &events) + return events, resp, err +} diff --git a/vendor/github.com/google/go-github/github/issues_timeline_test.go b/vendor/github.com/google/go-github/github/issues_timeline_test.go new file mode 100644 index 0000000..93f6236 --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_timeline_test.go @@ -0,0 +1,40 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestIssuesService_ListIssueTimeline(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/timeline", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeTimelinePreview) + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + events, _, err := client.Issues.ListIssueTimeline(context.Background(), "o", "r", 1, opt) + if err != nil { + t.Errorf("Issues.ListIssueTimeline returned error: %v", err) + } + + want := []*Timeline{{ID: Int64(1)}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Issues.ListIssueTimeline = %+v, want %+v", events, want) + } +} diff --git a/vendor/github.com/google/go-github/github/licenses.go b/vendor/github.com/google/go-github/github/licenses.go new file mode 100644 index 0000000..1176d3a --- /dev/null +++ b/vendor/github.com/google/go-github/github/licenses.go @@ -0,0 +1,97 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// LicensesService handles communication with the license related +// methods of the GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/licenses/ +type LicensesService service + +// RepositoryLicense represents the license for a repository. +type RepositoryLicense struct { + Name *string `json:"name,omitempty"` + Path *string `json:"path,omitempty"` + + SHA *string `json:"sha,omitempty"` + Size *int `json:"size,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + GitURL *string `json:"git_url,omitempty"` + DownloadURL *string `json:"download_url,omitempty"` + Type *string `json:"type,omitempty"` + Content *string `json:"content,omitempty"` + Encoding *string `json:"encoding,omitempty"` + License *License `json:"license,omitempty"` +} + +func (l RepositoryLicense) String() string { + return Stringify(l) +} + +// License represents an open source license. +type License struct { + Key *string `json:"key,omitempty"` + Name *string `json:"name,omitempty"` + URL *string `json:"url,omitempty"` + + SPDXID *string `json:"spdx_id,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + Featured *bool `json:"featured,omitempty"` + Description *string `json:"description,omitempty"` + Implementation *string `json:"implementation,omitempty"` + Permissions *[]string `json:"permissions,omitempty"` + Conditions *[]string `json:"conditions,omitempty"` + Limitations *[]string `json:"limitations,omitempty"` + Body *string `json:"body,omitempty"` +} + +func (l License) String() string { + return Stringify(l) +} + +// List popular open source licenses. +// +// GitHub API docs: https://developer.github.com/v3/licenses/#list-all-licenses +func (s *LicensesService) List(ctx context.Context) ([]*License, *Response, error) { + req, err := s.client.NewRequest("GET", "licenses", nil) + if err != nil { + return nil, nil, err + } + + var licenses []*License + resp, err := s.client.Do(ctx, req, &licenses) + if err != nil { + return nil, resp, err + } + + return licenses, resp, nil +} + +// Get extended metadata for one license. +// +// GitHub API docs: https://developer.github.com/v3/licenses/#get-an-individual-license +func (s *LicensesService) Get(ctx context.Context, licenseName string) (*License, *Response, error) { + u := fmt.Sprintf("licenses/%s", licenseName) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + license := new(License) + resp, err := s.client.Do(ctx, req, license) + if err != nil { + return nil, resp, err + } + + return license, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/licenses_test.go b/vendor/github.com/google/go-github/github/licenses_test.go new file mode 100644 index 0000000..a71f6a4 --- /dev/null +++ b/vendor/github.com/google/go-github/github/licenses_test.go @@ -0,0 +1,68 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestLicensesService_List(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/licenses", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"key":"mit","name":"MIT","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","featured":true}]`) + }) + + licenses, _, err := client.Licenses.List(context.Background()) + if err != nil { + t.Errorf("Licenses.List returned error: %v", err) + } + + want := []*License{{ + Key: String("mit"), + Name: String("MIT"), + SPDXID: String("MIT"), + URL: String("https://api.github.com/licenses/mit"), + Featured: Bool(true), + }} + if !reflect.DeepEqual(licenses, want) { + t.Errorf("Licenses.List returned %+v, want %+v", licenses, want) + } +} + +func TestLicensesService_Get(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/licenses/mit", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"key":"mit","name":"MIT"}`) + }) + + license, _, err := client.Licenses.Get(context.Background(), "mit") + if err != nil { + t.Errorf("Licenses.Get returned error: %v", err) + } + + want := &License{Key: String("mit"), Name: String("MIT")} + if !reflect.DeepEqual(license, want) { + t.Errorf("Licenses.Get returned %+v, want %+v", license, want) + } +} + +func TestLicensesService_Get_invalidTemplate(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Licenses.Get(context.Background(), "%") + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/messages.go b/vendor/github.com/google/go-github/github/messages.go new file mode 100644 index 0000000..519c1c0 --- /dev/null +++ b/vendor/github.com/google/go-github/github/messages.go @@ -0,0 +1,247 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file provides functions for validating payloads from GitHub Webhooks. +// GitHub API docs: https://developer.github.com/webhooks/securing/#validating-payloads-from-github + +package github + +import ( + "crypto/hmac" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "hash" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +const ( + // sha1Prefix is the prefix used by GitHub before the HMAC hexdigest. + sha1Prefix = "sha1" + // sha256Prefix and sha512Prefix are provided for future compatibility. + sha256Prefix = "sha256" + sha512Prefix = "sha512" + // signatureHeader is the GitHub header key used to pass the HMAC hexdigest. + signatureHeader = "X-Hub-Signature" + // eventTypeHeader is the GitHub header key used to pass the event type. + eventTypeHeader = "X-Github-Event" + // deliveryIDHeader is the GitHub header key used to pass the unique ID for the webhook event. + deliveryIDHeader = "X-Github-Delivery" +) + +var ( + // eventTypeMapping maps webhooks types to their corresponding go-github struct types. + eventTypeMapping = map[string]string{ + "check_run": "CheckRunEvent", + "check_suite": "CheckSuiteEvent", + "commit_comment": "CommitCommentEvent", + "create": "CreateEvent", + "delete": "DeleteEvent", + "deployment": "DeploymentEvent", + "deployment_status": "DeploymentStatusEvent", + "fork": "ForkEvent", + "gollum": "GollumEvent", + "installation": "InstallationEvent", + "installation_repositories": "InstallationRepositoriesEvent", + "issue_comment": "IssueCommentEvent", + "issues": "IssuesEvent", + "label": "LabelEvent", + "marketplace_purchase": "MarketplacePurchaseEvent", + "member": "MemberEvent", + "membership": "MembershipEvent", + "milestone": "MilestoneEvent", + "organization": "OrganizationEvent", + "org_block": "OrgBlockEvent", + "page_build": "PageBuildEvent", + "ping": "PingEvent", + "project": "ProjectEvent", + "project_card": "ProjectCardEvent", + "project_column": "ProjectColumnEvent", + "public": "PublicEvent", + "pull_request_review": "PullRequestReviewEvent", + "pull_request_review_comment": "PullRequestReviewCommentEvent", + "pull_request": "PullRequestEvent", + "push": "PushEvent", + "repository": "RepositoryEvent", + "release": "ReleaseEvent", + "status": "StatusEvent", + "team": "TeamEvent", + "team_add": "TeamAddEvent", + "watch": "WatchEvent", + } +) + +// genMAC generates the HMAC signature for a message provided the secret key +// and hashFunc. +func genMAC(message, key []byte, hashFunc func() hash.Hash) []byte { + mac := hmac.New(hashFunc, key) + mac.Write(message) + return mac.Sum(nil) +} + +// checkMAC reports whether messageMAC is a valid HMAC tag for message. +func checkMAC(message, messageMAC, key []byte, hashFunc func() hash.Hash) bool { + expectedMAC := genMAC(message, key, hashFunc) + return hmac.Equal(messageMAC, expectedMAC) +} + +// messageMAC returns the hex-decoded HMAC tag from the signature and its +// corresponding hash function. +func messageMAC(signature string) ([]byte, func() hash.Hash, error) { + if signature == "" { + return nil, nil, errors.New("missing signature") + } + sigParts := strings.SplitN(signature, "=", 2) + if len(sigParts) != 2 { + return nil, nil, fmt.Errorf("error parsing signature %q", signature) + } + + var hashFunc func() hash.Hash + switch sigParts[0] { + case sha1Prefix: + hashFunc = sha1.New + case sha256Prefix: + hashFunc = sha256.New + case sha512Prefix: + hashFunc = sha512.New + default: + return nil, nil, fmt.Errorf("unknown hash type prefix: %q", sigParts[0]) + } + + buf, err := hex.DecodeString(sigParts[1]) + if err != nil { + return nil, nil, fmt.Errorf("error decoding signature %q: %v", signature, err) + } + return buf, hashFunc, nil +} + +// ValidatePayload validates an incoming GitHub Webhook event request +// and returns the (JSON) payload. +// The Content-Type header of the payload can be "application/json" or "application/x-www-form-urlencoded". +// If the Content-Type is neither then an error is returned. +// secretKey is the GitHub Webhook secret message. +// +// Example usage: +// +// func (s *GitHubEventMonitor) ServeHTTP(w http.ResponseWriter, r *http.Request) { +// payload, err := github.ValidatePayload(r, s.webhookSecretKey) +// if err != nil { ... } +// // Process payload... +// } +// +func ValidatePayload(r *http.Request, secretKey []byte) (payload []byte, err error) { + var body []byte // Raw body that GitHub uses to calculate the signature. + + switch ct := r.Header.Get("Content-Type"); ct { + case "application/json": + var err error + if body, err = ioutil.ReadAll(r.Body); err != nil { + return nil, err + } + + // If the content type is application/json, + // the JSON payload is just the original body. + payload = body + + case "application/x-www-form-urlencoded": + // payloadFormParam is the name of the form parameter that the JSON payload + // will be in if a webhook has its content type set to application/x-www-form-urlencoded. + const payloadFormParam = "payload" + + var err error + if body, err = ioutil.ReadAll(r.Body); err != nil { + return nil, err + } + + // If the content type is application/x-www-form-urlencoded, + // the JSON payload will be under the "payload" form param. + form, err := url.ParseQuery(string(body)) + if err != nil { + return nil, err + } + payload = []byte(form.Get(payloadFormParam)) + + default: + return nil, fmt.Errorf("Webhook request has unsupported Content-Type %q", ct) + } + + sig := r.Header.Get(signatureHeader) + if err := ValidateSignature(sig, body, secretKey); err != nil { + return nil, err + } + return payload, nil +} + +// ValidateSignature validates the signature for the given payload. +// signature is the GitHub hash signature delivered in the X-Hub-Signature header. +// payload is the JSON payload sent by GitHub Webhooks. +// secretKey is the GitHub Webhook secret message. +// +// GitHub API docs: https://developer.github.com/webhooks/securing/#validating-payloads-from-github +func ValidateSignature(signature string, payload, secretKey []byte) error { + messageMAC, hashFunc, err := messageMAC(signature) + if err != nil { + return err + } + if !checkMAC(payload, messageMAC, secretKey, hashFunc) { + return errors.New("payload signature check failed") + } + return nil +} + +// WebHookType returns the event type of webhook request r. +// +// GitHub API docs: https://developer.github.com/v3/repos/hooks/#webhook-headers +func WebHookType(r *http.Request) string { + return r.Header.Get(eventTypeHeader) +} + +// DeliveryID returns the unique delivery ID of webhook request r. +// +// GitHub API docs: https://developer.github.com/v3/repos/hooks/#webhook-headers +func DeliveryID(r *http.Request) string { + return r.Header.Get(deliveryIDHeader) +} + +// ParseWebHook parses the event payload. For recognized event types, a +// value of the corresponding struct type will be returned (as returned +// by Event.ParsePayload()). An error will be returned for unrecognized event +// types. +// +// Example usage: +// +// func (s *GitHubEventMonitor) ServeHTTP(w http.ResponseWriter, r *http.Request) { +// payload, err := github.ValidatePayload(r, s.webhookSecretKey) +// if err != nil { ... } +// event, err := github.ParseWebHook(github.WebHookType(r), payload) +// if err != nil { ... } +// switch event := event.(type) { +// case *github.CommitCommentEvent: +// processCommitCommentEvent(event) +// case *github.CreateEvent: +// processCreateEvent(event) +// ... +// } +// } +// +func ParseWebHook(messageType string, payload []byte) (interface{}, error) { + eventType, ok := eventTypeMapping[messageType] + if !ok { + return nil, fmt.Errorf("unknown X-Github-Event in message: %v", messageType) + } + + event := Event{ + Type: &eventType, + RawPayload: (*json.RawMessage)(&payload), + } + return event.ParsePayload() +} diff --git a/vendor/github.com/google/go-github/github/messages_test.go b/vendor/github.com/google/go-github/github/messages_test.go new file mode 100644 index 0000000..0b677ca --- /dev/null +++ b/vendor/github.com/google/go-github/github/messages_test.go @@ -0,0 +1,338 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "bytes" + "encoding/json" + "net/http" + "net/url" + "reflect" + "strings" + "testing" +) + +func TestValidatePayload(t *testing.T) { + const defaultBody = `{"yo":true}` // All tests below use the default request body and signature. + const defaultSignature = "sha1=126f2c800419c60137ce748d7672e77b65cf16d6" + secretKey := []byte("0123456789abcdef") + tests := []struct { + signature string + eventID string + event string + wantEventID string + wantEvent string + wantPayload string + }{ + // The following tests generate expected errors: + {}, // Missing signature + {signature: "yo"}, // Missing signature prefix + {signature: "sha1=yo"}, // Signature not hex string + {signature: "sha1=012345"}, // Invalid signature + // The following tests expect err=nil: + { + signature: defaultSignature, + eventID: "dead-beef", + event: "ping", + wantEventID: "dead-beef", + wantEvent: "ping", + wantPayload: defaultBody, + }, + { + signature: defaultSignature, + event: "ping", + wantEvent: "ping", + wantPayload: defaultBody, + }, + { + signature: "sha256=b1f8020f5b4cd42042f807dd939015c4a418bc1ff7f604dd55b0a19b5d953d9b", + event: "ping", + wantEvent: "ping", + wantPayload: defaultBody, + }, + { + signature: "sha512=8456767023c1195682e182a23b3f5d19150ecea598fde8cb85918f7281b16079471b1329f92b912c4d8bd7455cb159777db8f29608b20c7c87323ba65ae62e1f", + event: "ping", + wantEvent: "ping", + wantPayload: defaultBody, + }, + } + + for _, test := range tests { + buf := bytes.NewBufferString(defaultBody) + req, err := http.NewRequest("GET", "http://localhost/event", buf) + if err != nil { + t.Fatalf("NewRequest: %v", err) + } + if test.signature != "" { + req.Header.Set(signatureHeader, test.signature) + } + req.Header.Set("Content-Type", "application/json") + + got, err := ValidatePayload(req, secretKey) + if err != nil { + if test.wantPayload != "" { + t.Errorf("ValidatePayload(%#v): err = %v, want nil", test, err) + } + continue + } + if string(got) != test.wantPayload { + t.Errorf("ValidatePayload = %q, want %q", got, test.wantPayload) + } + } +} + +func TestValidatePayload_FormGet(t *testing.T) { + payload := `{"yo":true}` + signature := "sha1=3374ef144403e8035423b23b02e2c9d7a4c50368" + secretKey := []byte("0123456789abcdef") + + form := url.Values{} + form.Add("payload", payload) + req, err := http.NewRequest("POST", "http://localhost/event", strings.NewReader(form.Encode())) + if err != nil { + t.Fatalf("NewRequest: %v", err) + } + req.PostForm = form + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set(signatureHeader, signature) + + got, err := ValidatePayload(req, secretKey) + if err != nil { + t.Errorf("ValidatePayload(%#v): err = %v, want nil", payload, err) + } + if string(got) != payload { + t.Errorf("ValidatePayload = %q, want %q", got, payload) + } + + // check that if payload is invalid we get error + req.Header.Set(signatureHeader, "invalid signature") + if _, err = ValidatePayload(req, nil); err == nil { + t.Error("ValidatePayload = nil, want err") + } +} + +func TestValidatePayload_FormPost(t *testing.T) { + payload := `{"yo":true}` + signature := "sha1=3374ef144403e8035423b23b02e2c9d7a4c50368" + secretKey := []byte("0123456789abcdef") + + form := url.Values{} + form.Set("payload", payload) + buf := bytes.NewBufferString(form.Encode()) + req, err := http.NewRequest("POST", "http://localhost/event", buf) + if err != nil { + t.Fatalf("NewRequest: %v", err) + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set(signatureHeader, signature) + + got, err := ValidatePayload(req, secretKey) + if err != nil { + t.Errorf("ValidatePayload(%#v): err = %v, want nil", payload, err) + } + if string(got) != payload { + t.Errorf("ValidatePayload = %q, want %q", got, payload) + } + + // check that if payload is invalid we get error + req.Header.Set(signatureHeader, "invalid signature") + if _, err = ValidatePayload(req, nil); err == nil { + t.Error("ValidatePayload = nil, want err") + } +} + +func TestValidatePayload_InvalidContentType(t *testing.T) { + req, err := http.NewRequest("POST", "http://localhost/event", nil) + if err != nil { + t.Fatalf("NewRequest: %v", err) + } + req.Header.Set("Content-Type", "invalid content type") + if _, err = ValidatePayload(req, nil); err == nil { + t.Error("ValidatePayload = nil, want err") + } +} + +func TestParseWebHook(t *testing.T) { + tests := []struct { + payload interface{} + messageType string + }{ + { + payload: &CheckRunEvent{}, + messageType: "check_run", + }, + { + payload: &CheckSuiteEvent{}, + messageType: "check_suite", + }, + { + payload: &CommitCommentEvent{}, + messageType: "commit_comment", + }, + { + payload: &CreateEvent{}, + messageType: "create", + }, + { + payload: &DeleteEvent{}, + messageType: "delete", + }, + { + payload: &DeploymentEvent{}, + messageType: "deployment", + }, + + { + payload: &DeploymentStatusEvent{}, + messageType: "deployment_status", + }, + { + payload: &ForkEvent{}, + messageType: "fork", + }, + { + payload: &GollumEvent{}, + messageType: "gollum", + }, + { + payload: &InstallationEvent{}, + messageType: "installation", + }, + { + payload: &InstallationRepositoriesEvent{}, + messageType: "installation_repositories", + }, + { + payload: &IssueCommentEvent{}, + messageType: "issue_comment", + }, + { + payload: &IssuesEvent{}, + messageType: "issues", + }, + { + payload: &LabelEvent{}, + messageType: "label", + }, + { + payload: &MarketplacePurchaseEvent{}, + messageType: "marketplace_purchase", + }, + { + payload: &MemberEvent{}, + messageType: "member", + }, + { + payload: &MembershipEvent{}, + messageType: "membership", + }, + { + payload: &MilestoneEvent{}, + messageType: "milestone", + }, + { + payload: &OrganizationEvent{}, + messageType: "organization", + }, + { + payload: &OrgBlockEvent{}, + messageType: "org_block", + }, + { + payload: &PageBuildEvent{}, + messageType: "page_build", + }, + { + payload: &PingEvent{}, + messageType: "ping", + }, + { + payload: &ProjectEvent{}, + messageType: "project", + }, + { + payload: &ProjectCardEvent{}, + messageType: "project_card", + }, + { + payload: &ProjectColumnEvent{}, + messageType: "project_column", + }, + { + payload: &PublicEvent{}, + messageType: "public", + }, + { + payload: &PullRequestEvent{}, + messageType: "pull_request", + }, + { + payload: &PullRequestReviewEvent{}, + messageType: "pull_request_review", + }, + { + payload: &PullRequestReviewCommentEvent{}, + messageType: "pull_request_review_comment", + }, + { + payload: &PushEvent{}, + messageType: "push", + }, + { + payload: &ReleaseEvent{}, + messageType: "release", + }, + { + payload: &RepositoryEvent{}, + messageType: "repository", + }, + { + payload: &StatusEvent{}, + messageType: "status", + }, + { + payload: &TeamEvent{}, + messageType: "team", + }, + { + payload: &TeamAddEvent{}, + messageType: "team_add", + }, + { + payload: &WatchEvent{}, + messageType: "watch", + }, + } + + for _, test := range tests { + p, err := json.Marshal(test.payload) + if err != nil { + t.Fatalf("Marshal(%#v): %v", test.payload, err) + } + got, err := ParseWebHook(test.messageType, p) + if err != nil { + t.Fatalf("ParseWebHook: %v", err) + } + if want := test.payload; !reflect.DeepEqual(got, want) { + t.Errorf("ParseWebHook(%#v, %#v) = %#v, want %#v", test.messageType, p, got, want) + } + } +} + +func TestDeliveryID(t *testing.T) { + id := "8970a780-244e-11e7-91ca-da3aabcb9793" + req, err := http.NewRequest("POST", "http://localhost", nil) + if err != nil { + t.Fatalf("DeliveryID: %v", err) + } + req.Header.Set("X-Github-Delivery", id) + + got := DeliveryID(req) + if got != id { + t.Errorf("DeliveryID(%#v) = %q, want %q", req, got, id) + } +} diff --git a/vendor/github.com/google/go-github/github/migrations.go b/vendor/github.com/google/go-github/github/migrations.go new file mode 100644 index 0000000..90cc1fa --- /dev/null +++ b/vendor/github.com/google/go-github/github/migrations.go @@ -0,0 +1,224 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" +) + +// MigrationService provides access to the migration related functions +// in the GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/migration/ +type MigrationService service + +// Migration represents a GitHub migration (archival). +type Migration struct { + ID *int64 `json:"id,omitempty"` + GUID *string `json:"guid,omitempty"` + // State is the current state of a migration. + // Possible values are: + // "pending" which means the migration hasn't started yet, + // "exporting" which means the migration is in progress, + // "exported" which means the migration finished successfully, or + // "failed" which means the migration failed. + State *string `json:"state,omitempty"` + // LockRepositories indicates whether repositories are locked (to prevent + // manipulation) while migrating data. + LockRepositories *bool `json:"lock_repositories,omitempty"` + // ExcludeAttachments indicates whether attachments should be excluded from + // the migration (to reduce migration archive file size). + ExcludeAttachments *bool `json:"exclude_attachments,omitempty"` + URL *string `json:"url,omitempty"` + CreatedAt *string `json:"created_at,omitempty"` + UpdatedAt *string `json:"updated_at,omitempty"` + Repositories []*Repository `json:"repositories,omitempty"` +} + +func (m Migration) String() string { + return Stringify(m) +} + +// MigrationOptions specifies the optional parameters to Migration methods. +type MigrationOptions struct { + // LockRepositories indicates whether repositories should be locked (to prevent + // manipulation) while migrating data. + LockRepositories bool + + // ExcludeAttachments indicates whether attachments should be excluded from + // the migration (to reduce migration archive file size). + ExcludeAttachments bool +} + +// startMigration represents the body of a StartMigration request. +type startMigration struct { + // Repositories is a slice of repository names to migrate. + Repositories []string `json:"repositories,omitempty"` + + // LockRepositories indicates whether repositories should be locked (to prevent + // manipulation) while migrating data. + LockRepositories *bool `json:"lock_repositories,omitempty"` + + // ExcludeAttachments indicates whether attachments should be excluded from + // the migration (to reduce migration archive file size). + ExcludeAttachments *bool `json:"exclude_attachments,omitempty"` +} + +// StartMigration starts the generation of a migration archive. +// repos is a slice of repository names to migrate. +// +// GitHub API docs: https://developer.github.com/v3/migration/migrations/#start-a-migration +func (s *MigrationService) StartMigration(ctx context.Context, org string, repos []string, opt *MigrationOptions) (*Migration, *Response, error) { + u := fmt.Sprintf("orgs/%v/migrations", org) + + body := &startMigration{Repositories: repos} + if opt != nil { + body.LockRepositories = Bool(opt.LockRepositories) + body.ExcludeAttachments = Bool(opt.ExcludeAttachments) + } + + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeMigrationsPreview) + + m := &Migration{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// ListMigrations lists the most recent migrations. +// +// GitHub API docs: https://developer.github.com/v3/migration/migrations/#get-a-list-of-migrations +func (s *MigrationService) ListMigrations(ctx context.Context, org string) ([]*Migration, *Response, error) { + u := fmt.Sprintf("orgs/%v/migrations", org) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeMigrationsPreview) + + var m []*Migration + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// MigrationStatus gets the status of a specific migration archive. +// id is the migration ID. +// +// GitHub API docs: https://developer.github.com/v3/migration/migrations/#get-the-status-of-a-migration +func (s *MigrationService) MigrationStatus(ctx context.Context, org string, id int64) (*Migration, *Response, error) { + u := fmt.Sprintf("orgs/%v/migrations/%v", org, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeMigrationsPreview) + + m := &Migration{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// MigrationArchiveURL fetches a migration archive URL. +// id is the migration ID. +// +// GitHub API docs: https://developer.github.com/v3/migration/migrations/#download-a-migration-archive +func (s *MigrationService) MigrationArchiveURL(ctx context.Context, org string, id int64) (url string, err error) { + u := fmt.Sprintf("orgs/%v/migrations/%v/archive", org, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return "", err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeMigrationsPreview) + + s.client.clientMu.Lock() + defer s.client.clientMu.Unlock() + + // Disable the redirect mechanism because AWS fails if the GitHub auth token is provided. + var loc string + saveRedirect := s.client.client.CheckRedirect + s.client.client.CheckRedirect = func(req *http.Request, via []*http.Request) error { + loc = req.URL.String() + return errors.New("disable redirect") + } + defer func() { s.client.client.CheckRedirect = saveRedirect }() + + _, err = s.client.Do(ctx, req, nil) // expect error from disable redirect + if err == nil { + return "", errors.New("expected redirect, none provided") + } + if !strings.Contains(err.Error(), "disable redirect") { + return "", err + } + return loc, nil +} + +// DeleteMigration deletes a previous migration archive. +// id is the migration ID. +// +// GitHub API docs: https://developer.github.com/v3/migration/migrations/#delete-a-migration-archive +func (s *MigrationService) DeleteMigration(ctx context.Context, org string, id int64) (*Response, error) { + u := fmt.Sprintf("orgs/%v/migrations/%v/archive", org, id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeMigrationsPreview) + + return s.client.Do(ctx, req, nil) +} + +// UnlockRepo unlocks a repository that was locked for migration. +// id is the migration ID. +// You should unlock each migrated repository and delete them when the migration +// is complete and you no longer need the source data. +// +// GitHub API docs: https://developer.github.com/v3/migration/migrations/#unlock-a-repository +func (s *MigrationService) UnlockRepo(ctx context.Context, org string, id int64, repo string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/migrations/%v/repos/%v/lock", org, id, repo) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeMigrationsPreview) + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/migrations_source_import.go b/vendor/github.com/google/go-github/github/migrations_source_import.go new file mode 100644 index 0000000..fd45e78 --- /dev/null +++ b/vendor/github.com/google/go-github/github/migrations_source_import.go @@ -0,0 +1,329 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// Import represents a repository import request. +type Import struct { + // The URL of the originating repository. + VCSURL *string `json:"vcs_url,omitempty"` + // The originating VCS type. Can be one of 'subversion', 'git', + // 'mercurial', or 'tfvc'. Without this parameter, the import job will + // take additional time to detect the VCS type before beginning the + // import. This detection step will be reflected in the response. + VCS *string `json:"vcs,omitempty"` + // VCSUsername and VCSPassword are only used for StartImport calls that + // are importing a password-protected repository. + VCSUsername *string `json:"vcs_username,omitempty"` + VCSPassword *string `json:"vcs_password,omitempty"` + // For a tfvc import, the name of the project that is being imported. + TFVCProject *string `json:"tfvc_project,omitempty"` + + // LFS related fields that may be preset in the Import Progress response + + // Describes whether the import has been opted in or out of using Git + // LFS. The value can be 'opt_in', 'opt_out', or 'undecided' if no + // action has been taken. + UseLFS *string `json:"use_lfs,omitempty"` + // Describes whether files larger than 100MB were found during the + // importing step. + HasLargeFiles *bool `json:"has_large_files,omitempty"` + // The total size in gigabytes of files larger than 100MB found in the + // originating repository. + LargeFilesSize *int `json:"large_files_size,omitempty"` + // The total number of files larger than 100MB found in the originating + // repository. To see a list of these files, call LargeFiles. + LargeFilesCount *int `json:"large_files_count,omitempty"` + + // Identifies the current status of an import. An import that does not + // have errors will progress through these steps: + // + // detecting - the "detection" step of the import is in progress + // because the request did not include a VCS parameter. The + // import is identifying the type of source control present at + // the URL. + // importing - the "raw" step of the import is in progress. This is + // where commit data is fetched from the original repository. + // The import progress response will include CommitCount (the + // total number of raw commits that will be imported) and + // Percent (0 - 100, the current progress through the import). + // mapping - the "rewrite" step of the import is in progress. This + // is where SVN branches are converted to Git branches, and + // where author updates are applied. The import progress + // response does not include progress information. + // pushing - the "push" step of the import is in progress. This is + // where the importer updates the repository on GitHub. The + // import progress response will include PushPercent, which is + // the percent value reported by git push when it is "Writing + // objects". + // complete - the import is complete, and the repository is ready + // on GitHub. + // + // If there are problems, you will see one of these in the status field: + // + // auth_failed - the import requires authentication in order to + // connect to the original repository. Make an UpdateImport + // request, and include VCSUsername and VCSPassword. + // error - the import encountered an error. The import progress + // response will include the FailedStep and an error message. + // Contact GitHub support for more information. + // detection_needs_auth - the importer requires authentication for + // the originating repository to continue detection. Make an + // UpdatImport request, and include VCSUsername and + // VCSPassword. + // detection_found_nothing - the importer didn't recognize any + // source control at the URL. + // detection_found_multiple - the importer found several projects + // or repositories at the provided URL. When this is the case, + // the Import Progress response will also include a + // ProjectChoices field with the possible project choices as + // values. Make an UpdateImport request, and include VCS and + // (if applicable) TFVCProject. + Status *string `json:"status,omitempty"` + CommitCount *int `json:"commit_count,omitempty"` + StatusText *string `json:"status_text,omitempty"` + AuthorsCount *int `json:"authors_count,omitempty"` + Percent *int `json:"percent,omitempty"` + PushPercent *int `json:"push_percent,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + AuthorsURL *string `json:"authors_url,omitempty"` + RepositoryURL *string `json:"repository_url,omitempty"` + Message *string `json:"message,omitempty"` + FailedStep *string `json:"failed_step,omitempty"` + + // Human readable display name, provided when the Import appears as + // part of ProjectChoices. + HumanName *string `json:"human_name,omitempty"` + + // When the importer finds several projects or repositories at the + // provided URLs, this will identify the available choices. Call + // UpdateImport with the selected Import value. + ProjectChoices []Import `json:"project_choices,omitempty"` +} + +func (i Import) String() string { + return Stringify(i) +} + +// SourceImportAuthor identifies an author imported from a source repository. +// +// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#get-commit-authors +type SourceImportAuthor struct { + ID *int64 `json:"id,omitempty"` + RemoteID *string `json:"remote_id,omitempty"` + RemoteName *string `json:"remote_name,omitempty"` + Email *string `json:"email,omitempty"` + Name *string `json:"name,omitempty"` + URL *string `json:"url,omitempty"` + ImportURL *string `json:"import_url,omitempty"` +} + +func (a SourceImportAuthor) String() string { + return Stringify(a) +} + +// LargeFile identifies a file larger than 100MB found during a repository import. +// +// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#get-large-files +type LargeFile struct { + RefName *string `json:"ref_name,omitempty"` + Path *string `json:"path,omitempty"` + OID *string `json:"oid,omitempty"` + Size *int `json:"size,omitempty"` +} + +func (f LargeFile) String() string { + return Stringify(f) +} + +// StartImport initiates a repository import. +// +// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#start-an-import +func (s *MigrationService) StartImport(ctx context.Context, owner, repo string, in *Import) (*Import, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/import", owner, repo) + req, err := s.client.NewRequest("PUT", u, in) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeImportPreview) + + out := new(Import) + resp, err := s.client.Do(ctx, req, out) + if err != nil { + return nil, resp, err + } + + return out, resp, nil +} + +// ImportProgress queries for the status and progress of an ongoing repository import. +// +// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#get-import-progress +func (s *MigrationService) ImportProgress(ctx context.Context, owner, repo string) (*Import, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/import", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeImportPreview) + + out := new(Import) + resp, err := s.client.Do(ctx, req, out) + if err != nil { + return nil, resp, err + } + + return out, resp, nil +} + +// UpdateImport initiates a repository import. +// +// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#update-existing-import +func (s *MigrationService) UpdateImport(ctx context.Context, owner, repo string, in *Import) (*Import, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/import", owner, repo) + req, err := s.client.NewRequest("PATCH", u, in) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeImportPreview) + + out := new(Import) + resp, err := s.client.Do(ctx, req, out) + if err != nil { + return nil, resp, err + } + + return out, resp, nil +} + +// CommitAuthors gets the authors mapped from the original repository. +// +// Each type of source control system represents authors in a different way. +// For example, a Git commit author has a display name and an email address, +// but a Subversion commit author just has a username. The GitHub Importer will +// make the author information valid, but the author might not be correct. For +// example, it will change the bare Subversion username "hubot" into something +// like "hubot ". +// +// This method and MapCommitAuthor allow you to provide correct Git author +// information. +// +// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#get-commit-authors +func (s *MigrationService) CommitAuthors(ctx context.Context, owner, repo string) ([]*SourceImportAuthor, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/import/authors", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeImportPreview) + + var authors []*SourceImportAuthor + resp, err := s.client.Do(ctx, req, &authors) + if err != nil { + return nil, resp, err + } + + return authors, resp, nil +} + +// MapCommitAuthor updates an author's identity for the import. Your +// application can continue updating authors any time before you push new +// commits to the repository. +// +// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#map-a-commit-author +func (s *MigrationService) MapCommitAuthor(ctx context.Context, owner, repo string, id int64, author *SourceImportAuthor) (*SourceImportAuthor, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/import/authors/%v", owner, repo, id) + req, err := s.client.NewRequest("PATCH", u, author) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeImportPreview) + + out := new(SourceImportAuthor) + resp, err := s.client.Do(ctx, req, out) + if err != nil { + return nil, resp, err + } + + return out, resp, nil +} + +// SetLFSPreference sets whether imported repositories should use Git LFS for +// files larger than 100MB. Only the UseLFS field on the provided Import is +// used. +// +// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#set-git-lfs-preference +func (s *MigrationService) SetLFSPreference(ctx context.Context, owner, repo string, in *Import) (*Import, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/import/lfs", owner, repo) + req, err := s.client.NewRequest("PATCH", u, in) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeImportPreview) + + out := new(Import) + resp, err := s.client.Do(ctx, req, out) + if err != nil { + return nil, resp, err + } + + return out, resp, nil +} + +// LargeFiles lists files larger than 100MB found during the import. +// +// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#get-large-files +func (s *MigrationService) LargeFiles(ctx context.Context, owner, repo string) ([]*LargeFile, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/import/large_files", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeImportPreview) + + var files []*LargeFile + resp, err := s.client.Do(ctx, req, &files) + if err != nil { + return nil, resp, err + } + + return files, resp, nil +} + +// CancelImport stops an import for a repository. +// +// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#cancel-an-import +func (s *MigrationService) CancelImport(ctx context.Context, owner, repo string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/import", owner, repo) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeImportPreview) + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/migrations_source_import_test.go b/vendor/github.com/google/go-github/github/migrations_source_import_test.go new file mode 100644 index 0000000..e9e9be0 --- /dev/null +++ b/vendor/github.com/google/go-github/github/migrations_source_import_test.go @@ -0,0 +1,226 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestMigrationService_StartImport(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Import{ + VCS: String("git"), + VCSURL: String("url"), + VCSUsername: String("u"), + VCSPassword: String("p"), + } + + mux.HandleFunc("/repos/o/r/import", func(w http.ResponseWriter, r *http.Request) { + v := new(Import) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + testHeader(t, r, "Accept", mediaTypeImportPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"status":"importing"}`) + }) + + got, _, err := client.Migrations.StartImport(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("StartImport returned error: %v", err) + } + want := &Import{Status: String("importing")} + if !reflect.DeepEqual(got, want) { + t.Errorf("StartImport = %+v, want %+v", got, want) + } +} + +func TestMigrationService_ImportProgress(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/import", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeImportPreview) + fmt.Fprint(w, `{"status":"complete"}`) + }) + + got, _, err := client.Migrations.ImportProgress(context.Background(), "o", "r") + if err != nil { + t.Errorf("ImportProgress returned error: %v", err) + } + want := &Import{Status: String("complete")} + if !reflect.DeepEqual(got, want) { + t.Errorf("ImportProgress = %+v, want %+v", got, want) + } +} + +func TestMigrationService_UpdateImport(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Import{ + VCS: String("git"), + VCSURL: String("url"), + VCSUsername: String("u"), + VCSPassword: String("p"), + } + + mux.HandleFunc("/repos/o/r/import", func(w http.ResponseWriter, r *http.Request) { + v := new(Import) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeImportPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"status":"importing"}`) + }) + + got, _, err := client.Migrations.UpdateImport(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("UpdateImport returned error: %v", err) + } + want := &Import{Status: String("importing")} + if !reflect.DeepEqual(got, want) { + t.Errorf("UpdateImport = %+v, want %+v", got, want) + } +} + +func TestMigrationService_CommitAuthors(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/import/authors", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeImportPreview) + fmt.Fprint(w, `[{"id":1,"name":"a"},{"id":2,"name":"b"}]`) + }) + + got, _, err := client.Migrations.CommitAuthors(context.Background(), "o", "r") + if err != nil { + t.Errorf("CommitAuthors returned error: %v", err) + } + want := []*SourceImportAuthor{ + {ID: Int64(1), Name: String("a")}, + {ID: Int64(2), Name: String("b")}, + } + if !reflect.DeepEqual(got, want) { + t.Errorf("CommitAuthors = %+v, want %+v", got, want) + } +} + +func TestMigrationService_MapCommitAuthor(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &SourceImportAuthor{Name: String("n"), Email: String("e")} + + mux.HandleFunc("/repos/o/r/import/authors/1", func(w http.ResponseWriter, r *http.Request) { + v := new(SourceImportAuthor) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeImportPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id": 1}`) + }) + + got, _, err := client.Migrations.MapCommitAuthor(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("MapCommitAuthor returned error: %v", err) + } + want := &SourceImportAuthor{ID: Int64(1)} + if !reflect.DeepEqual(got, want) { + t.Errorf("MapCommitAuthor = %+v, want %+v", got, want) + } +} + +func TestMigrationService_SetLFSPreference(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Import{UseLFS: String("opt_in")} + + mux.HandleFunc("/repos/o/r/import/lfs", func(w http.ResponseWriter, r *http.Request) { + v := new(Import) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeImportPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"status":"importing"}`) + }) + + got, _, err := client.Migrations.SetLFSPreference(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("SetLFSPreference returned error: %v", err) + } + want := &Import{Status: String("importing")} + if !reflect.DeepEqual(got, want) { + t.Errorf("SetLFSPreference = %+v, want %+v", got, want) + } +} + +func TestMigrationService_LargeFiles(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/import/large_files", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeImportPreview) + fmt.Fprint(w, `[{"oid":"a"},{"oid":"b"}]`) + }) + + got, _, err := client.Migrations.LargeFiles(context.Background(), "o", "r") + if err != nil { + t.Errorf("LargeFiles returned error: %v", err) + } + want := []*LargeFile{ + {OID: String("a")}, + {OID: String("b")}, + } + if !reflect.DeepEqual(got, want) { + t.Errorf("LargeFiles = %+v, want %+v", got, want) + } +} + +func TestMigrationService_CancelImport(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/import", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeImportPreview) + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Migrations.CancelImport(context.Background(), "o", "r") + if err != nil { + t.Errorf("CancelImport returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/migrations_test.go b/vendor/github.com/google/go-github/github/migrations_test.go new file mode 100644 index 0000000..687f89a --- /dev/null +++ b/vendor/github.com/google/go-github/github/migrations_test.go @@ -0,0 +1,178 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "strings" + "testing" +) + +func TestMigrationService_StartMigration(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/migrations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeMigrationsPreview) + + w.WriteHeader(http.StatusCreated) + w.Write(migrationJSON) + }) + + opt := &MigrationOptions{ + LockRepositories: true, + ExcludeAttachments: false, + } + got, _, err := client.Migrations.StartMigration(context.Background(), "o", []string{"r"}, opt) + if err != nil { + t.Errorf("StartMigration returned error: %v", err) + } + if want := wantMigration; !reflect.DeepEqual(got, want) { + t.Errorf("StartMigration = %+v, want %+v", got, want) + } +} + +func TestMigrationService_ListMigrations(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/migrations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeMigrationsPreview) + + w.WriteHeader(http.StatusOK) + w.Write([]byte(fmt.Sprintf("[%s]", migrationJSON))) + }) + + got, _, err := client.Migrations.ListMigrations(context.Background(), "o") + if err != nil { + t.Errorf("ListMigrations returned error: %v", err) + } + if want := []*Migration{wantMigration}; !reflect.DeepEqual(got, want) { + t.Errorf("ListMigrations = %+v, want %+v", got, want) + } +} + +func TestMigrationService_MigrationStatus(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/migrations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeMigrationsPreview) + + w.WriteHeader(http.StatusOK) + w.Write(migrationJSON) + }) + + got, _, err := client.Migrations.MigrationStatus(context.Background(), "o", 1) + if err != nil { + t.Errorf("MigrationStatus returned error: %v", err) + } + if want := wantMigration; !reflect.DeepEqual(got, want) { + t.Errorf("MigrationStatus = %+v, want %+v", got, want) + } +} + +func TestMigrationService_MigrationArchiveURL(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/migrations/1/archive", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeMigrationsPreview) + + http.Redirect(w, r, "/yo", http.StatusFound) + }) + mux.HandleFunc("/yo", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + w.WriteHeader(http.StatusOK) + w.Write([]byte("0123456789abcdef")) + }) + + got, err := client.Migrations.MigrationArchiveURL(context.Background(), "o", 1) + if err != nil { + t.Errorf("MigrationStatus returned error: %v", err) + } + if want := "/yo"; !strings.HasSuffix(got, want) { + t.Errorf("MigrationArchiveURL = %+v, want %+v", got, want) + } +} + +func TestMigrationService_DeleteMigration(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/migrations/1/archive", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeMigrationsPreview) + + w.WriteHeader(http.StatusNoContent) + }) + + if _, err := client.Migrations.DeleteMigration(context.Background(), "o", 1); err != nil { + t.Errorf("DeleteMigration returned error: %v", err) + } +} + +func TestMigrationService_UnlockRepo(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/migrations/1/repos/r/lock", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeMigrationsPreview) + + w.WriteHeader(http.StatusNoContent) + }) + + if _, err := client.Migrations.UnlockRepo(context.Background(), "o", 1, "r"); err != nil { + t.Errorf("UnlockRepo returned error: %v", err) + } +} + +var migrationJSON = []byte(`{ + "id": 79, + "guid": "0b989ba4-242f-11e5-81e1-c7b6966d2516", + "state": "pending", + "lock_repositories": true, + "exclude_attachments": false, + "url": "https://api.github.com/orgs/octo-org/migrations/79", + "created_at": "2015-07-06T15:33:38-07:00", + "updated_at": "2015-07-06T15:33:38-07:00", + "repositories": [ + { + "id": 1296269, + "name": "Hello-World", + "full_name": "octocat/Hello-World", + "description": "This your first repo!" + } + ] +}`) + +var wantMigration = &Migration{ + ID: Int64(79), + GUID: String("0b989ba4-242f-11e5-81e1-c7b6966d2516"), + State: String("pending"), + LockRepositories: Bool(true), + ExcludeAttachments: Bool(false), + URL: String("https://api.github.com/orgs/octo-org/migrations/79"), + CreatedAt: String("2015-07-06T15:33:38-07:00"), + UpdatedAt: String("2015-07-06T15:33:38-07:00"), + Repositories: []*Repository{ + { + ID: Int64(1296269), + Name: String("Hello-World"), + FullName: String("octocat/Hello-World"), + Description: String("This your first repo!"), + }, + }, +} diff --git a/vendor/github.com/google/go-github/github/migrations_user.go b/vendor/github.com/google/go-github/github/migrations_user.go new file mode 100644 index 0000000..ae53e68 --- /dev/null +++ b/vendor/github.com/google/go-github/github/migrations_user.go @@ -0,0 +1,214 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "errors" + "fmt" + "net/http" +) + +// UserMigration represents a GitHub migration (archival). +type UserMigration struct { + ID *int64 `json:"id,omitempty"` + GUID *string `json:"guid,omitempty"` + // State is the current state of a migration. + // Possible values are: + // "pending" which means the migration hasn't started yet, + // "exporting" which means the migration is in progress, + // "exported" which means the migration finished successfully, or + // "failed" which means the migration failed. + State *string `json:"state,omitempty"` + // LockRepositories indicates whether repositories are locked (to prevent + // manipulation) while migrating data. + LockRepositories *bool `json:"lock_repositories,omitempty"` + // ExcludeAttachments indicates whether attachments should be excluded from + // the migration (to reduce migration archive file size). + ExcludeAttachments *bool `json:"exclude_attachments,omitempty"` + URL *string `json:"url,omitempty"` + CreatedAt *string `json:"created_at,omitempty"` + UpdatedAt *string `json:"updated_at,omitempty"` + Repositories []*Repository `json:"repositories,omitempty"` +} + +func (m UserMigration) String() string { + return Stringify(m) +} + +// UserMigrationOptions specifies the optional parameters to Migration methods. +type UserMigrationOptions struct { + // LockRepositories indicates whether repositories should be locked (to prevent + // manipulation) while migrating data. + LockRepositories bool + + // ExcludeAttachments indicates whether attachments should be excluded from + // the migration (to reduce migration archive file size). + ExcludeAttachments bool +} + +// startUserMigration represents the body of a StartMigration request. +type startUserMigration struct { + // Repositories is a slice of repository names to migrate. + Repositories []string `json:"repositories,omitempty"` + + // LockRepositories indicates whether repositories should be locked (to prevent + // manipulation) while migrating data. + LockRepositories *bool `json:"lock_repositories,omitempty"` + + // ExcludeAttachments indicates whether attachments should be excluded from + // the migration (to reduce migration archive file size). + ExcludeAttachments *bool `json:"exclude_attachments,omitempty"` +} + +// StartUserMigration starts the generation of a migration archive. +// repos is a slice of repository names to migrate. +// +// GitHub API docs: https://developer.github.com/v3/migrations/users/#start-a-user-migration +func (s *MigrationService) StartUserMigration(ctx context.Context, repos []string, opt *UserMigrationOptions) (*UserMigration, *Response, error) { + u := "user/migrations" + + body := &startUserMigration{Repositories: repos} + if opt != nil { + body.LockRepositories = Bool(opt.LockRepositories) + body.ExcludeAttachments = Bool(opt.ExcludeAttachments) + } + + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeMigrationsPreview) + + m := &UserMigration{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// ListUserMigrations lists the most recent migrations. +// +// GitHub API docs: https://developer.github.com/v3/migrations/users/#get-a-list-of-user-migrations +func (s *MigrationService) ListUserMigrations(ctx context.Context) ([]*UserMigration, *Response, error) { + u := "user/migrations" + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeMigrationsPreview) + + var m []*UserMigration + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// UserMigrationStatus gets the status of a specific migration archive. +// id is the migration ID. +// +// GitHub API docs: https://developer.github.com/v3/migrations/users/#get-the-status-of-a-user-migration +func (s *MigrationService) UserMigrationStatus(ctx context.Context, id int64) (*UserMigration, *Response, error) { + u := fmt.Sprintf("user/migrations/%v", id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeMigrationsPreview) + + m := &UserMigration{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// UserMigrationArchiveURL gets the URL for a specific migration archive. +// id is the migration ID. +// +// GitHub API docs: https://developer.github.com/v3/migrations/users/#download-a-user-migration-archive +func (s *MigrationService) UserMigrationArchiveURL(ctx context.Context, id int64) (string, error) { + url := fmt.Sprintf("user/migrations/%v/archive", id) + + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return "", err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeMigrationsPreview) + + m := &UserMigration{} + + var loc string + originalRedirect := s.client.client.CheckRedirect + s.client.client.CheckRedirect = func(req *http.Request, via []*http.Request) error { + loc = req.URL.String() + return http.ErrUseLastResponse + } + defer func() { + s.client.client.CheckRedirect = originalRedirect + }() + resp, err := s.client.Do(ctx, req, m) + if err == nil { + return "", errors.New("expected redirect, none provided") + } + loc = resp.Header.Get("Location") + return loc, nil +} + +// DeleteUserMigration will delete a previous migration archive. +// id is the migration ID. +// +// GitHub API docs: https://developer.github.com/v3/migrations/users/#delete-a-user-migration-archive +func (s *MigrationService) DeleteUserMigration(ctx context.Context, id int64) (*Response, error) { + url := fmt.Sprintf("user/migrations/%v/archive", id) + + req, err := s.client.NewRequest("DELETE", url, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeMigrationsPreview) + + return s.client.Do(ctx, req, nil) +} + +// UnlockUserRepository will unlock a repo that was locked for migration. +// id is migration ID. +// You should unlock each migrated repository and delete them when the migration +// is complete and you no longer need the source data. +// +// GitHub API docs: https://developer.github.com/v3/migrations/users/#unlock-a-user-repository +func (s *MigrationService) UnlockUserRepo(ctx context.Context, id int64, repo string) (*Response, error) { + url := fmt.Sprintf("user/migrations/%v/repos/%v/lock", id, repo) + + req, err := s.client.NewRequest("DELETE", url, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeMigrationsPreview) + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/migrations_user_test.go b/vendor/github.com/google/go-github/github/migrations_user_test.go new file mode 100644 index 0000000..c271e02 --- /dev/null +++ b/vendor/github.com/google/go-github/github/migrations_user_test.go @@ -0,0 +1,197 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "strings" + "testing" +) + +func TestMigrationService_StartUserMigration(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/migrations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeMigrationsPreview) + + w.WriteHeader(http.StatusCreated) + w.Write(userMigrationJSON) + }) + + opt := &UserMigrationOptions{ + LockRepositories: true, + ExcludeAttachments: false, + } + + got, _, err := client.Migrations.StartUserMigration(context.Background(), []string{"r"}, opt) + if err != nil { + t.Errorf("StartUserMigration returned error: %v", err) + } + + want := wantUserMigration + if !reflect.DeepEqual(want, got) { + t.Errorf("StartUserMigration = %v, want = %v", got, want) + } +} + +func TestMigrationService_ListUserMigrations(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/migrations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeMigrationsPreview) + + w.WriteHeader(http.StatusOK) + w.Write([]byte(fmt.Sprintf("[%s]", userMigrationJSON))) + }) + + got, _, err := client.Migrations.ListUserMigrations(context.Background()) + if err != nil { + t.Errorf("ListUserMigrations returned error %v", err) + } + + want := []*UserMigration{wantUserMigration} + if !reflect.DeepEqual(want, got) { + t.Errorf("ListUserMigrations = %v, want = %v", got, want) + } +} + +func TestMigrationService_UserMigrationStatus(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/migrations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeMigrationsPreview) + + w.WriteHeader(http.StatusOK) + w.Write(userMigrationJSON) + }) + + got, _, err := client.Migrations.UserMigrationStatus(context.Background(), 1) + if err != nil { + t.Errorf("UserMigrationStatus returned error %v", err) + } + + want := wantUserMigration + if !reflect.DeepEqual(want, got) { + t.Errorf("UserMigrationStatus = %v, want = %v", got, want) + } +} + +func TestMigrationService_UserMigrationArchiveURL(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/migrations/1/archive", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeMigrationsPreview) + + http.Redirect(w, r, "/go-github", http.StatusFound) + }) + + mux.HandleFunc("/go-github", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + w.WriteHeader(http.StatusOK) + }) + + got, err := client.Migrations.UserMigrationArchiveURL(context.Background(), 1) + if err != nil { + t.Errorf("UserMigrationArchiveURL returned error %v", err) + } + + want := "/go-github" + if !strings.HasSuffix(got, want) { + t.Errorf("UserMigrationArchiveURL = %v, want = %v", got, want) + } +} + +func TestMigrationService_DeleteUserMigration(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/migrations/1/archive", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeMigrationsPreview) + + w.WriteHeader(http.StatusNoContent) + }) + + got, err := client.Migrations.DeleteUserMigration(context.Background(), 1) + if err != nil { + t.Errorf("DeleteUserMigration returned error %v", err) + } + + if got.StatusCode != http.StatusNoContent { + t.Errorf("DeleteUserMigration returned status = %v, want = %v", got.StatusCode, http.StatusNoContent) + } +} + +func TestMigrationService_UnlockUserRepo(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/migrations/1/repos/r/lock", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeMigrationsPreview) + + w.WriteHeader(http.StatusNoContent) + }) + + got, err := client.Migrations.UnlockUserRepo(context.Background(), 1, "r") + if err != nil { + t.Errorf("UnlockUserRepo returned error %v", err) + } + + if got.StatusCode != http.StatusNoContent { + t.Errorf("UnlockUserRepo returned status = %v, want = %v", got.StatusCode, http.StatusNoContent) + } +} + +var userMigrationJSON = []byte(`{ + "id": 79, + "guid": "0b989ba4-242f-11e5-81e1-c7b6966d2516", + "state": "pending", + "lock_repositories": true, + "exclude_attachments": false, + "url": "https://api.github.com/orgs/octo-org/migrations/79", + "created_at": "2015-07-06T15:33:38-07:00", + "updated_at": "2015-07-06T15:33:38-07:00", + "repositories": [ + { + "id": 1296269, + "name": "Hello-World", + "full_name": "octocat/Hello-World", + "description": "This your first repo!" + } + ] +}`) + +var wantUserMigration = &UserMigration{ + ID: Int64(79), + GUID: String("0b989ba4-242f-11e5-81e1-c7b6966d2516"), + State: String("pending"), + LockRepositories: Bool(true), + ExcludeAttachments: Bool(false), + URL: String("https://api.github.com/orgs/octo-org/migrations/79"), + CreatedAt: String("2015-07-06T15:33:38-07:00"), + UpdatedAt: String("2015-07-06T15:33:38-07:00"), + Repositories: []*Repository{ + { + ID: Int64(1296269), + Name: String("Hello-World"), + FullName: String("octocat/Hello-World"), + Description: String("This your first repo!"), + }, + }, +} diff --git a/vendor/github.com/google/go-github/github/misc.go b/vendor/github.com/google/go-github/github/misc.go new file mode 100644 index 0000000..e9b0ea2 --- /dev/null +++ b/vendor/github.com/google/go-github/github/misc.go @@ -0,0 +1,257 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "bytes" + "context" + "fmt" + "net/url" +) + +// MarkdownOptions specifies optional parameters to the Markdown method. +type MarkdownOptions struct { + // Mode identifies the rendering mode. Possible values are: + // markdown - render a document as plain Markdown, just like + // README files are rendered. + // + // gfm - to render a document as user-content, e.g. like user + // comments or issues are rendered. In GFM mode, hard line breaks are + // always taken into account, and issue and user mentions are linked + // accordingly. + // + // Default is "markdown". + Mode string + + // Context identifies the repository context. Only taken into account + // when rendering as "gfm". + Context string +} + +type markdownRequest struct { + Text *string `json:"text,omitempty"` + Mode *string `json:"mode,omitempty"` + Context *string `json:"context,omitempty"` +} + +// Markdown renders an arbitrary Markdown document. +// +// GitHub API docs: https://developer.github.com/v3/markdown/ +func (c *Client) Markdown(ctx context.Context, text string, opt *MarkdownOptions) (string, *Response, error) { + request := &markdownRequest{Text: String(text)} + if opt != nil { + if opt.Mode != "" { + request.Mode = String(opt.Mode) + } + if opt.Context != "" { + request.Context = String(opt.Context) + } + } + + req, err := c.NewRequest("POST", "markdown", request) + if err != nil { + return "", nil, err + } + + buf := new(bytes.Buffer) + resp, err := c.Do(ctx, req, buf) + if err != nil { + return "", resp, err + } + + return buf.String(), resp, nil +} + +// ListEmojis returns the emojis available to use on GitHub. +// +// GitHub API docs: https://developer.github.com/v3/emojis/ +func (c *Client) ListEmojis(ctx context.Context) (map[string]string, *Response, error) { + req, err := c.NewRequest("GET", "emojis", nil) + if err != nil { + return nil, nil, err + } + + var emoji map[string]string + resp, err := c.Do(ctx, req, &emoji) + if err != nil { + return nil, resp, err + } + + return emoji, resp, nil +} + +// CodeOfConduct represents a code of conduct. +type CodeOfConduct struct { + Name *string `json:"name,omitempty"` + Key *string `json:"key,omitempty"` + URL *string `json:"url,omitempty"` + Body *string `json:"body,omitempty"` +} + +func (c *CodeOfConduct) String() string { + return Stringify(c) +} + +// ListCodesOfConduct returns all codes of conduct. +// +// GitHub API docs: https://developer.github.com/v3/codes_of_conduct/#list-all-codes-of-conduct +func (c *Client) ListCodesOfConduct(ctx context.Context) ([]*CodeOfConduct, *Response, error) { + req, err := c.NewRequest("GET", "codes_of_conduct", nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeCodesOfConductPreview) + + var cs []*CodeOfConduct + resp, err := c.Do(ctx, req, &cs) + if err != nil { + return nil, resp, err + } + + return cs, resp, nil +} + +// GetCodeOfConduct returns an individual code of conduct. +// +// https://developer.github.com/v3/codes_of_conduct/#get-an-individual-code-of-conduct +func (c *Client) GetCodeOfConduct(ctx context.Context, key string) (*CodeOfConduct, *Response, error) { + u := fmt.Sprintf("codes_of_conduct/%s", key) + req, err := c.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeCodesOfConductPreview) + + coc := new(CodeOfConduct) + resp, err := c.Do(ctx, req, coc) + if err != nil { + return nil, resp, err + } + + return coc, resp, nil +} + +// APIMeta represents metadata about the GitHub API. +type APIMeta struct { + // An Array of IP addresses in CIDR format specifying the addresses + // that incoming service hooks will originate from on GitHub.com. + Hooks []string `json:"hooks,omitempty"` + + // An Array of IP addresses in CIDR format specifying the Git servers + // for GitHub.com. + Git []string `json:"git,omitempty"` + + // Whether authentication with username and password is supported. + // (GitHub Enterprise instances using CAS or OAuth for authentication + // will return false. Features like Basic Authentication with a + // username and password, sudo mode, and two-factor authentication are + // not supported on these servers.) + VerifiablePasswordAuthentication *bool `json:"verifiable_password_authentication,omitempty"` + + // An array of IP addresses in CIDR format specifying the addresses + // which serve GitHub Pages websites. + Pages []string `json:"pages,omitempty"` + + // An Array of IP addresses specifying the addresses that source imports + // will originate from on GitHub.com. + Importer []string `json:"importer,omitempty"` +} + +// APIMeta returns information about GitHub.com, the service. Or, if you access +// this endpoint on your organization’s GitHub Enterprise installation, this +// endpoint provides information about that installation. +// +// GitHub API docs: https://developer.github.com/v3/meta/ +func (c *Client) APIMeta(ctx context.Context) (*APIMeta, *Response, error) { + req, err := c.NewRequest("GET", "meta", nil) + if err != nil { + return nil, nil, err + } + + meta := new(APIMeta) + resp, err := c.Do(ctx, req, meta) + if err != nil { + return nil, resp, err + } + + return meta, resp, nil +} + +// Octocat returns an ASCII art octocat with the specified message in a speech +// bubble. If message is empty, a random zen phrase is used. +func (c *Client) Octocat(ctx context.Context, message string) (string, *Response, error) { + u := "octocat" + if message != "" { + u = fmt.Sprintf("%s?s=%s", u, url.QueryEscape(message)) + } + + req, err := c.NewRequest("GET", u, nil) + if err != nil { + return "", nil, err + } + + buf := new(bytes.Buffer) + resp, err := c.Do(ctx, req, buf) + if err != nil { + return "", resp, err + } + + return buf.String(), resp, nil +} + +// Zen returns a random line from The Zen of GitHub. +// +// see also: http://warpspire.com/posts/taste/ +func (c *Client) Zen(ctx context.Context) (string, *Response, error) { + req, err := c.NewRequest("GET", "zen", nil) + if err != nil { + return "", nil, err + } + + buf := new(bytes.Buffer) + resp, err := c.Do(ctx, req, buf) + if err != nil { + return "", resp, err + } + + return buf.String(), resp, nil +} + +// ServiceHook represents a hook that has configuration settings, a list of +// available events, and default events. +type ServiceHook struct { + Name *string `json:"name,omitempty"` + Events []string `json:"events,omitempty"` + SupportedEvents []string `json:"supported_events,omitempty"` + Schema [][]string `json:"schema,omitempty"` +} + +func (s *ServiceHook) String() string { + return Stringify(s) +} + +// ListServiceHooks lists all of the available service hooks. +// +// GitHub API docs: https://developer.github.com/webhooks/#services +func (c *Client) ListServiceHooks(ctx context.Context) ([]*ServiceHook, *Response, error) { + u := "hooks" + req, err := c.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var hooks []*ServiceHook + resp, err := c.Do(ctx, req, &hooks) + if err != nil { + return nil, resp, err + } + + return hooks, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/misc_test.go b/vendor/github.com/google/go-github/github/misc_test.go new file mode 100644 index 0000000..b7144f9 --- /dev/null +++ b/vendor/github.com/google/go-github/github/misc_test.go @@ -0,0 +1,234 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestMarkdown(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &markdownRequest{ + Text: String("# text #"), + Mode: String("gfm"), + Context: String("google/go-github"), + } + mux.HandleFunc("/markdown", func(w http.ResponseWriter, r *http.Request) { + v := new(markdownRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + fmt.Fprint(w, `

text

`) + }) + + md, _, err := client.Markdown(context.Background(), "# text #", &MarkdownOptions{ + Mode: "gfm", + Context: "google/go-github", + }) + if err != nil { + t.Errorf("Markdown returned error: %v", err) + } + + if want := "

text

"; want != md { + t.Errorf("Markdown returned %+v, want %+v", md, want) + } +} + +func TestListEmojis(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/emojis", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"+1": "+1.png"}`) + }) + + emoji, _, err := client.ListEmojis(context.Background()) + if err != nil { + t.Errorf("ListEmojis returned error: %v", err) + } + + want := map[string]string{"+1": "+1.png"} + if !reflect.DeepEqual(want, emoji) { + t.Errorf("ListEmojis returned %+v, want %+v", emoji, want) + } +} + +func TestListCodesOfConduct(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/codes_of_conduct", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeCodesOfConductPreview) + fmt.Fprint(w, `[{ + "key": "key", + "name": "name", + "url": "url"} + ]`) + }) + + cs, _, err := client.ListCodesOfConduct(context.Background()) + if err != nil { + t.Errorf("ListCodesOfConduct returned error: %v", err) + } + + want := []*CodeOfConduct{ + { + Key: String("key"), + Name: String("name"), + URL: String("url"), + }} + if !reflect.DeepEqual(want, cs) { + t.Errorf("ListCodesOfConduct returned %+v, want %+v", cs, want) + } +} + +func TestGetCodeOfConduct(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/codes_of_conduct/k", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeCodesOfConductPreview) + fmt.Fprint(w, `{ + "key": "key", + "name": "name", + "url": "url", + "body": "body"}`, + ) + }) + + coc, _, err := client.GetCodeOfConduct(context.Background(), "k") + if err != nil { + t.Errorf("ListCodesOfConduct returned error: %v", err) + } + + want := &CodeOfConduct{ + Key: String("key"), + Name: String("name"), + URL: String("url"), + Body: String("body"), + } + if !reflect.DeepEqual(want, coc) { + t.Errorf("GetCodeOfConductByKey returned %+v, want %+v", coc, want) + } +} + +func TestAPIMeta(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/meta", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"hooks":["h"], "git":["g"], "pages":["p"], "importer":["i"], "verifiable_password_authentication": true}`) + }) + + meta, _, err := client.APIMeta(context.Background()) + if err != nil { + t.Errorf("APIMeta returned error: %v", err) + } + + want := &APIMeta{ + Hooks: []string{"h"}, + Git: []string{"g"}, + Pages: []string{"p"}, + Importer: []string{"i"}, + + VerifiablePasswordAuthentication: Bool(true), + } + if !reflect.DeepEqual(want, meta) { + t.Errorf("APIMeta returned %+v, want %+v", meta, want) + } +} + +func TestOctocat(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := "input" + output := "sample text" + + mux.HandleFunc("/octocat", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"s": input}) + w.Header().Set("Content-Type", "application/octocat-stream") + fmt.Fprint(w, output) + }) + + got, _, err := client.Octocat(context.Background(), input) + if err != nil { + t.Errorf("Octocat returned error: %v", err) + } + + if want := output; got != want { + t.Errorf("Octocat returned %+v, want %+v", got, want) + } +} + +func TestZen(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + output := "sample text" + + mux.HandleFunc("/zen", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.Header().Set("Content-Type", "text/plain;charset=utf-8") + fmt.Fprint(w, output) + }) + + got, _, err := client.Zen(context.Background()) + if err != nil { + t.Errorf("Zen returned error: %v", err) + } + + if want := output; got != want { + t.Errorf("Zen returned %+v, want %+v", got, want) + } +} + +func TestListServiceHooks(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/hooks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{ + "name":"n", + "events":["e"], + "supported_events":["s"], + "schema":[ + ["a", "b"] + ] + }]`) + }) + + hooks, _, err := client.ListServiceHooks(context.Background()) + if err != nil { + t.Errorf("ListServiceHooks returned error: %v", err) + } + + want := []*ServiceHook{{ + Name: String("n"), + Events: []string{"e"}, + SupportedEvents: []string{"s"}, + Schema: [][]string{{"a", "b"}}, + }} + if !reflect.DeepEqual(hooks, want) { + t.Errorf("ListServiceHooks returned %+v, want %+v", hooks, want) + } +} diff --git a/vendor/github.com/google/go-github/github/orgs.go b/vendor/github.com/google/go-github/github/orgs.go new file mode 100644 index 0000000..3be91a3 --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs.go @@ -0,0 +1,198 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// OrganizationsService provides access to the organization related functions +// in the GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/orgs/ +type OrganizationsService service + +// Organization represents a GitHub organization account. +type Organization struct { + Login *string `json:"login,omitempty"` + ID *int64 `json:"id,omitempty"` + NodeID *string `json:"node_id,omitempty"` + AvatarURL *string `json:"avatar_url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + Name *string `json:"name,omitempty"` + Company *string `json:"company,omitempty"` + Blog *string `json:"blog,omitempty"` + Location *string `json:"location,omitempty"` + Email *string `json:"email,omitempty"` + Description *string `json:"description,omitempty"` + PublicRepos *int `json:"public_repos,omitempty"` + PublicGists *int `json:"public_gists,omitempty"` + Followers *int `json:"followers,omitempty"` + Following *int `json:"following,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + TotalPrivateRepos *int `json:"total_private_repos,omitempty"` + OwnedPrivateRepos *int `json:"owned_private_repos,omitempty"` + PrivateGists *int `json:"private_gists,omitempty"` + DiskUsage *int `json:"disk_usage,omitempty"` + Collaborators *int `json:"collaborators,omitempty"` + BillingEmail *string `json:"billing_email,omitempty"` + Type *string `json:"type,omitempty"` + Plan *Plan `json:"plan,omitempty"` + TwoFactorRequirementEnabled *bool `json:"two_factor_requirement_enabled,omitempty"` + + // API URLs + URL *string `json:"url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + HooksURL *string `json:"hooks_url,omitempty"` + IssuesURL *string `json:"issues_url,omitempty"` + MembersURL *string `json:"members_url,omitempty"` + PublicMembersURL *string `json:"public_members_url,omitempty"` + ReposURL *string `json:"repos_url,omitempty"` +} + +func (o Organization) String() string { + return Stringify(o) +} + +// Plan represents the payment plan for an account. See plans at https://github.com/plans. +type Plan struct { + Name *string `json:"name,omitempty"` + Space *int `json:"space,omitempty"` + Collaborators *int `json:"collaborators,omitempty"` + PrivateRepos *int `json:"private_repos,omitempty"` +} + +func (p Plan) String() string { + return Stringify(p) +} + +// OrganizationsListOptions specifies the optional parameters to the +// OrganizationsService.ListAll method. +type OrganizationsListOptions struct { + // Since filters Organizations by ID. + Since int64 `url:"since,omitempty"` + + // Note: Pagination is powered exclusively by the Since parameter, + // ListOptions.Page has no effect. + // ListOptions.PerPage controls an undocumented GitHub API parameter. + ListOptions +} + +// ListAll lists all organizations, in the order that they were created on GitHub. +// +// Note: Pagination is powered exclusively by the since parameter. To continue +// listing the next set of organizations, use the ID of the last-returned organization +// as the opts.Since parameter for the next call. +// +// GitHub API docs: https://developer.github.com/v3/orgs/#list-all-organizations +func (s *OrganizationsService) ListAll(ctx context.Context, opt *OrganizationsListOptions) ([]*Organization, *Response, error) { + u, err := addOptions("organizations", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + orgs := []*Organization{} + resp, err := s.client.Do(ctx, req, &orgs) + if err != nil { + return nil, resp, err + } + return orgs, resp, nil +} + +// List the organizations for a user. Passing the empty string will list +// organizations for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/orgs/#list-user-organizations +func (s *OrganizationsService) List(ctx context.Context, user string, opt *ListOptions) ([]*Organization, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/orgs", user) + } else { + u = "user/orgs" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var orgs []*Organization + resp, err := s.client.Do(ctx, req, &orgs) + if err != nil { + return nil, resp, err + } + + return orgs, resp, nil +} + +// Get fetches an organization by name. +// +// GitHub API docs: https://developer.github.com/v3/orgs/#get-an-organization +func (s *OrganizationsService) Get(ctx context.Context, org string) (*Organization, *Response, error) { + u := fmt.Sprintf("orgs/%v", org) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + organization := new(Organization) + resp, err := s.client.Do(ctx, req, organization) + if err != nil { + return nil, resp, err + } + + return organization, resp, nil +} + +// GetByID fetches an organization. +// +// Note: GetByID uses the undocumented GitHub API endpoint /organizations/:id. +func (s *OrganizationsService) GetByID(ctx context.Context, id int64) (*Organization, *Response, error) { + u := fmt.Sprintf("organizations/%d", id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + organization := new(Organization) + resp, err := s.client.Do(ctx, req, organization) + if err != nil { + return nil, resp, err + } + + return organization, resp, nil +} + +// Edit an organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/#edit-an-organization +func (s *OrganizationsService) Edit(ctx context.Context, name string, org *Organization) (*Organization, *Response, error) { + u := fmt.Sprintf("orgs/%v", name) + req, err := s.client.NewRequest("PATCH", u, org) + if err != nil { + return nil, nil, err + } + + o := new(Organization) + resp, err := s.client.Do(ctx, req, o) + if err != nil { + return nil, resp, err + } + + return o, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/orgs_hooks.go b/vendor/github.com/google/go-github/github/orgs_hooks.go new file mode 100644 index 0000000..ab1d02d --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_hooks.go @@ -0,0 +1,107 @@ +// Copyright 2015 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ListHooks lists all Hooks for the specified organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#list-hooks +func (s *OrganizationsService) ListHooks(ctx context.Context, org string, opt *ListOptions) ([]*Hook, *Response, error) { + u := fmt.Sprintf("orgs/%v/hooks", org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var hooks []*Hook + resp, err := s.client.Do(ctx, req, &hooks) + if err != nil { + return nil, resp, err + } + + return hooks, resp, nil +} + +// GetHook returns a single specified Hook. +// +// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#get-single-hook +func (s *OrganizationsService) GetHook(ctx context.Context, org string, id int64) (*Hook, *Response, error) { + u := fmt.Sprintf("orgs/%v/hooks/%d", org, id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + hook := new(Hook) + resp, err := s.client.Do(ctx, req, hook) + return hook, resp, err +} + +// CreateHook creates a Hook for the specified org. +// Name and Config are required fields. +// +// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#create-a-hook +func (s *OrganizationsService) CreateHook(ctx context.Context, org string, hook *Hook) (*Hook, *Response, error) { + u := fmt.Sprintf("orgs/%v/hooks", org) + req, err := s.client.NewRequest("POST", u, hook) + if err != nil { + return nil, nil, err + } + + h := new(Hook) + resp, err := s.client.Do(ctx, req, h) + if err != nil { + return nil, resp, err + } + + return h, resp, nil +} + +// EditHook updates a specified Hook. +// +// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#edit-a-hook +func (s *OrganizationsService) EditHook(ctx context.Context, org string, id int64, hook *Hook) (*Hook, *Response, error) { + u := fmt.Sprintf("orgs/%v/hooks/%d", org, id) + req, err := s.client.NewRequest("PATCH", u, hook) + if err != nil { + return nil, nil, err + } + h := new(Hook) + resp, err := s.client.Do(ctx, req, h) + return h, resp, err +} + +// PingHook triggers a 'ping' event to be sent to the Hook. +// +// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#ping-a-hook +func (s *OrganizationsService) PingHook(ctx context.Context, org string, id int64) (*Response, error) { + u := fmt.Sprintf("orgs/%v/hooks/%d/pings", org, id) + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// DeleteHook deletes a specified Hook. +// +// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#delete-a-hook +func (s *OrganizationsService) DeleteHook(ctx context.Context, org string, id int64) (*Response, error) { + u := fmt.Sprintf("orgs/%v/hooks/%d", org, id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/orgs_hooks_test.go b/vendor/github.com/google/go-github/github/orgs_hooks_test.go new file mode 100644 index 0000000..1adfef2 --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_hooks_test.go @@ -0,0 +1,147 @@ +// Copyright 2015 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestOrganizationsService_ListHooks(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/hooks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + + hooks, _, err := client.Organizations.ListHooks(context.Background(), "o", opt) + if err != nil { + t.Errorf("Organizations.ListHooks returned error: %v", err) + } + + want := []*Hook{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(hooks, want) { + t.Errorf("Organizations.ListHooks returned %+v, want %+v", hooks, want) + } +} + +func TestOrganizationsService_ListHooks_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Organizations.ListHooks(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_GetHook(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/hooks/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + hook, _, err := client.Organizations.GetHook(context.Background(), "o", 1) + if err != nil { + t.Errorf("Organizations.GetHook returned error: %v", err) + } + + want := &Hook{ID: Int64(1)} + if !reflect.DeepEqual(hook, want) { + t.Errorf("Organizations.GetHook returned %+v, want %+v", hook, want) + } +} + +func TestOrganizationsService_GetHook_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Organizations.GetHook(context.Background(), "%", 1) + testURLParseError(t, err) +} + +func TestOrganizationsService_EditHook(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Hook{Name: String("t")} + + mux.HandleFunc("/orgs/o/hooks/1", func(w http.ResponseWriter, r *http.Request) { + v := new(Hook) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + hook, _, err := client.Organizations.EditHook(context.Background(), "o", 1, input) + if err != nil { + t.Errorf("Organizations.EditHook returned error: %v", err) + } + + want := &Hook{ID: Int64(1)} + if !reflect.DeepEqual(hook, want) { + t.Errorf("Organizations.EditHook returned %+v, want %+v", hook, want) + } +} + +func TestOrganizationsService_EditHook_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Organizations.EditHook(context.Background(), "%", 1, nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_PingHook(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/hooks/1/pings", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + }) + + _, err := client.Organizations.PingHook(context.Background(), "o", 1) + if err != nil { + t.Errorf("Organizations.PingHook returned error: %v", err) + } +} + +func TestOrganizationsService_DeleteHook(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/hooks/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Organizations.DeleteHook(context.Background(), "o", 1) + if err != nil { + t.Errorf("Organizations.DeleteHook returned error: %v", err) + } +} + +func TestOrganizationsService_DeleteHook_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Organizations.DeleteHook(context.Background(), "%", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/orgs_members.go b/vendor/github.com/google/go-github/github/orgs_members.go new file mode 100644 index 0000000..d184359 --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_members.go @@ -0,0 +1,370 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// Membership represents the status of a user's membership in an organization or team. +type Membership struct { + URL *string `json:"url,omitempty"` + + // State is the user's status within the organization or team. + // Possible values are: "active", "pending" + State *string `json:"state,omitempty"` + + // Role identifies the user's role within the organization or team. + // Possible values for organization membership: + // member - non-owner organization member + // admin - organization owner + // + // Possible values for team membership are: + // member - a normal member of the team + // maintainer - a team maintainer. Able to add/remove other team + // members, promote other team members to team + // maintainer, and edit the team’s name and description + Role *string `json:"role,omitempty"` + + // For organization membership, the API URL of the organization. + OrganizationURL *string `json:"organization_url,omitempty"` + + // For organization membership, the organization the membership is for. + Organization *Organization `json:"organization,omitempty"` + + // For organization membership, the user the membership is for. + User *User `json:"user,omitempty"` +} + +func (m Membership) String() string { + return Stringify(m) +} + +// ListMembersOptions specifies optional parameters to the +// OrganizationsService.ListMembers method. +type ListMembersOptions struct { + // If true (or if the authenticated user is not an owner of the + // organization), list only publicly visible members. + PublicOnly bool `url:"-"` + + // Filter members returned in the list. Possible values are: + // 2fa_disabled, all. Default is "all". + Filter string `url:"filter,omitempty"` + + // Role filters members returned by their role in the organization. + // Possible values are: + // all - all members of the organization, regardless of role + // admin - organization owners + // member - non-owner organization members + // + // Default is "all". + Role string `url:"role,omitempty"` + + ListOptions +} + +// ListMembers lists the members for an organization. If the authenticated +// user is an owner of the organization, this will return both concealed and +// public members, otherwise it will only return public members. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#members-list +func (s *OrganizationsService) ListMembers(ctx context.Context, org string, opt *ListMembersOptions) ([]*User, *Response, error) { + var u string + if opt != nil && opt.PublicOnly { + u = fmt.Sprintf("orgs/%v/public_members", org) + } else { + u = fmt.Sprintf("orgs/%v/members", org) + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var members []*User + resp, err := s.client.Do(ctx, req, &members) + if err != nil { + return nil, resp, err + } + + return members, resp, nil +} + +// IsMember checks if a user is a member of an organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#check-membership +func (s *OrganizationsService) IsMember(ctx context.Context, org, user string) (bool, *Response, error) { + u := fmt.Sprintf("orgs/%v/members/%v", org, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + member, err := parseBoolResponse(err) + return member, resp, err +} + +// IsPublicMember checks if a user is a public member of an organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#check-public-membership +func (s *OrganizationsService) IsPublicMember(ctx context.Context, org, user string) (bool, *Response, error) { + u := fmt.Sprintf("orgs/%v/public_members/%v", org, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + member, err := parseBoolResponse(err) + return member, resp, err +} + +// RemoveMember removes a user from all teams of an organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#remove-a-member +func (s *OrganizationsService) RemoveMember(ctx context.Context, org, user string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/members/%v", org, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// PublicizeMembership publicizes a user's membership in an organization. (A +// user cannot publicize the membership for another user.) +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#publicize-a-users-membership +func (s *OrganizationsService) PublicizeMembership(ctx context.Context, org, user string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/public_members/%v", org, user) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// ConcealMembership conceals a user's membership in an organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#conceal-a-users-membership +func (s *OrganizationsService) ConcealMembership(ctx context.Context, org, user string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/public_members/%v", org, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// ListOrgMembershipsOptions specifies optional parameters to the +// OrganizationsService.ListOrgMemberships method. +type ListOrgMembershipsOptions struct { + // Filter memberships to include only those with the specified state. + // Possible values are: "active", "pending". + State string `url:"state,omitempty"` + + ListOptions +} + +// ListOrgMemberships lists the organization memberships for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#list-your-organization-memberships +func (s *OrganizationsService) ListOrgMemberships(ctx context.Context, opt *ListOrgMembershipsOptions) ([]*Membership, *Response, error) { + u := "user/memberships/orgs" + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var memberships []*Membership + resp, err := s.client.Do(ctx, req, &memberships) + if err != nil { + return nil, resp, err + } + + return memberships, resp, nil +} + +// GetOrgMembership gets the membership for a user in a specified organization. +// Passing an empty string for user will get the membership for the +// authenticated user. +// +// GitHub API docs: +// https://developer.github.com/v3/orgs/members/#get-organization-membership +// https://developer.github.com/v3/orgs/members/#get-your-organization-membership +func (s *OrganizationsService) GetOrgMembership(ctx context.Context, user, org string) (*Membership, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("orgs/%v/memberships/%v", org, user) + } else { + u = fmt.Sprintf("user/memberships/orgs/%v", org) + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + membership := new(Membership) + resp, err := s.client.Do(ctx, req, membership) + if err != nil { + return nil, resp, err + } + + return membership, resp, nil +} + +// EditOrgMembership edits the membership for user in specified organization. +// Passing an empty string for user will edit the membership for the +// authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#add-or-update-organization-membership +// GitHub API docs: https://developer.github.com/v3/orgs/members/#edit-your-organization-membership +func (s *OrganizationsService) EditOrgMembership(ctx context.Context, user, org string, membership *Membership) (*Membership, *Response, error) { + var u, method string + if user != "" { + u = fmt.Sprintf("orgs/%v/memberships/%v", org, user) + method = "PUT" + } else { + u = fmt.Sprintf("user/memberships/orgs/%v", org) + method = "PATCH" + } + + req, err := s.client.NewRequest(method, u, membership) + if err != nil { + return nil, nil, err + } + + m := new(Membership) + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// RemoveOrgMembership removes user from the specified organization. If the +// user has been invited to the organization, this will cancel their invitation. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#remove-organization-membership +func (s *OrganizationsService) RemoveOrgMembership(ctx context.Context, user, org string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/memberships/%v", org, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// ListPendingOrgInvitations returns a list of pending invitations. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#list-pending-organization-invitations +func (s *OrganizationsService) ListPendingOrgInvitations(ctx context.Context, org string, opt *ListOptions) ([]*Invitation, *Response, error) { + u := fmt.Sprintf("orgs/%v/invitations", org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var pendingInvitations []*Invitation + resp, err := s.client.Do(ctx, req, &pendingInvitations) + if err != nil { + return nil, resp, err + } + return pendingInvitations, resp, nil +} + +// CreateOrgInvitationOptions specifies the parameters to the OrganizationService.Invite +// method. +type CreateOrgInvitationOptions struct { + // GitHub user ID for the person you are inviting. Not required if you provide Email. + InviteeID *int64 `json:"invitee_id,omitempty"` + // Email address of the person you are inviting, which can be an existing GitHub user. + // Not required if you provide InviteeID + Email *string `json:"email,omitempty"` + // Specify role for new member. Can be one of: + // * admin - Organization owners with full administrative rights to the + // organization and complete access to all repositories and teams. + // * direct_member - Non-owner organization members with ability to see + // other members and join teams by invitation. + // * billing_manager - Non-owner organization members with ability to + // manage the billing settings of your organization. + // Default is "direct_member". + Role *string `json:"role"` + TeamID []int64 `json:"team_ids"` +} + +// CreateOrgInvitation invites people to an organization by using their GitHub user ID or their email address. +// In order to create invitations in an organization, +// the authenticated user must be an organization owner. +// +// https://developer.github.com/v3/orgs/members/#create-organization-invitation +func (s *OrganizationsService) CreateOrgInvitation(ctx context.Context, org string, opt *CreateOrgInvitationOptions) (*Invitation, *Response, error) { + u := fmt.Sprintf("orgs/%v/invitations", org) + + req, err := s.client.NewRequest("POST", u, opt) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeOrganizationInvitationPreview) + + var invitation *Invitation + resp, err := s.client.Do(ctx, req, &invitation) + if err != nil { + return nil, resp, err + } + return invitation, resp, nil +} + +// ListOrgInvitationTeams lists all teams associated with an invitation. In order to see invitations in an organization, +// the authenticated user must be an organization owner. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#list-organization-invitation-teams +func (s *OrganizationsService) ListOrgInvitationTeams(ctx context.Context, org, invitationID string, opt *ListOptions) ([]*Team, *Response, error) { + u := fmt.Sprintf("orgs/%v/invitations/%v/teams", org, invitationID) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeOrganizationInvitationPreview) + + var orgInvitationTeams []*Team + resp, err := s.client.Do(ctx, req, &orgInvitationTeams) + if err != nil { + return nil, resp, err + } + return orgInvitationTeams, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/orgs_members_test.go b/vendor/github.com/google/go-github/github/orgs_members_test.go new file mode 100644 index 0000000..e524d03 --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_members_test.go @@ -0,0 +1,535 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestOrganizationsService_ListMembers(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/members", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "filter": "2fa_disabled", + "role": "admin", + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListMembersOptions{ + PublicOnly: false, + Filter: "2fa_disabled", + Role: "admin", + ListOptions: ListOptions{Page: 2}, + } + members, _, err := client.Organizations.ListMembers(context.Background(), "o", opt) + if err != nil { + t.Errorf("Organizations.ListMembers returned error: %v", err) + } + + want := []*User{{ID: Int64(1)}} + if !reflect.DeepEqual(members, want) { + t.Errorf("Organizations.ListMembers returned %+v, want %+v", members, want) + } +} + +func TestOrganizationsService_ListMembers_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Organizations.ListMembers(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_ListMembers_public(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListMembersOptions{PublicOnly: true} + members, _, err := client.Organizations.ListMembers(context.Background(), "o", opt) + if err != nil { + t.Errorf("Organizations.ListMembers returned error: %v", err) + } + + want := []*User{{ID: Int64(1)}} + if !reflect.DeepEqual(members, want) { + t.Errorf("Organizations.ListMembers returned %+v, want %+v", members, want) + } +} + +func TestOrganizationsService_IsMember(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + member, _, err := client.Organizations.IsMember(context.Background(), "o", "u") + if err != nil { + t.Errorf("Organizations.IsMember returned error: %v", err) + } + if want := true; member != want { + t.Errorf("Organizations.IsMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 404 response is interpreted as "false" and not an error +func TestOrganizationsService_IsMember_notMember(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + member, _, err := client.Organizations.IsMember(context.Background(), "o", "u") + if err != nil { + t.Errorf("Organizations.IsMember returned error: %+v", err) + } + if want := false; member != want { + t.Errorf("Organizations.IsMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 400 response is interpreted as an actual error, and not simply +// as "false" like the above case of a 404 +func TestOrganizationsService_IsMember_error(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + member, _, err := client.Organizations.IsMember(context.Background(), "o", "u") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; member != want { + t.Errorf("Organizations.IsMember returned %+v, want %+v", member, want) + } +} + +func TestOrganizationsService_IsMember_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Organizations.IsMember(context.Background(), "%", "u") + testURLParseError(t, err) +} + +func TestOrganizationsService_IsPublicMember(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + member, _, err := client.Organizations.IsPublicMember(context.Background(), "o", "u") + if err != nil { + t.Errorf("Organizations.IsPublicMember returned error: %v", err) + } + if want := true; member != want { + t.Errorf("Organizations.IsPublicMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 404 response is interpreted as "false" and not an error +func TestOrganizationsService_IsPublicMember_notMember(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + member, _, err := client.Organizations.IsPublicMember(context.Background(), "o", "u") + if err != nil { + t.Errorf("Organizations.IsPublicMember returned error: %v", err) + } + if want := false; member != want { + t.Errorf("Organizations.IsPublicMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 400 response is interpreted as an actual error, and not simply +// as "false" like the above case of a 404 +func TestOrganizationsService_IsPublicMember_error(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + member, _, err := client.Organizations.IsPublicMember(context.Background(), "o", "u") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; member != want { + t.Errorf("Organizations.IsPublicMember returned %+v, want %+v", member, want) + } +} + +func TestOrganizationsService_IsPublicMember_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Organizations.IsPublicMember(context.Background(), "%", "u") + testURLParseError(t, err) +} + +func TestOrganizationsService_RemoveMember(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Organizations.RemoveMember(context.Background(), "o", "u") + if err != nil { + t.Errorf("Organizations.RemoveMember returned error: %v", err) + } +} + +func TestOrganizationsService_RemoveMember_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Organizations.RemoveMember(context.Background(), "%", "u") + testURLParseError(t, err) +} + +func TestOrganizationsService_ListOrgMemberships(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/memberships/orgs", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "state": "active", + "page": "2", + }) + fmt.Fprint(w, `[{"url":"u"}]`) + }) + + opt := &ListOrgMembershipsOptions{ + State: "active", + ListOptions: ListOptions{Page: 2}, + } + memberships, _, err := client.Organizations.ListOrgMemberships(context.Background(), opt) + if err != nil { + t.Errorf("Organizations.ListOrgMemberships returned error: %v", err) + } + + want := []*Membership{{URL: String("u")}} + if !reflect.DeepEqual(memberships, want) { + t.Errorf("Organizations.ListOrgMemberships returned %+v, want %+v", memberships, want) + } +} + +func TestOrganizationsService_GetOrgMembership_AuthenticatedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/memberships/orgs/o", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"url":"u"}`) + }) + + membership, _, err := client.Organizations.GetOrgMembership(context.Background(), "", "o") + if err != nil { + t.Errorf("Organizations.GetOrgMembership returned error: %v", err) + } + + want := &Membership{URL: String("u")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Organizations.GetOrgMembership returned %+v, want %+v", membership, want) + } +} + +func TestOrganizationsService_GetOrgMembership_SpecifiedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/memberships/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"url":"u"}`) + }) + + membership, _, err := client.Organizations.GetOrgMembership(context.Background(), "u", "o") + if err != nil { + t.Errorf("Organizations.GetOrgMembership returned error: %v", err) + } + + want := &Membership{URL: String("u")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Organizations.GetOrgMembership returned %+v, want %+v", membership, want) + } +} + +func TestOrganizationsService_EditOrgMembership_AuthenticatedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Membership{State: String("active")} + + mux.HandleFunc("/user/memberships/orgs/o", func(w http.ResponseWriter, r *http.Request) { + v := new(Membership) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"url":"u"}`) + }) + + membership, _, err := client.Organizations.EditOrgMembership(context.Background(), "", "o", input) + if err != nil { + t.Errorf("Organizations.EditOrgMembership returned error: %v", err) + } + + want := &Membership{URL: String("u")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Organizations.EditOrgMembership returned %+v, want %+v", membership, want) + } +} + +func TestOrganizationsService_EditOrgMembership_SpecifiedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Membership{State: String("active")} + + mux.HandleFunc("/orgs/o/memberships/u", func(w http.ResponseWriter, r *http.Request) { + v := new(Membership) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"url":"u"}`) + }) + + membership, _, err := client.Organizations.EditOrgMembership(context.Background(), "u", "o", input) + if err != nil { + t.Errorf("Organizations.EditOrgMembership returned error: %v", err) + } + + want := &Membership{URL: String("u")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Organizations.EditOrgMembership returned %+v, want %+v", membership, want) + } +} + +func TestOrganizationsService_RemoveOrgMembership(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/memberships/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.RemoveOrgMembership(context.Background(), "u", "o") + if err != nil { + t.Errorf("Organizations.RemoveOrgMembership returned error: %v", err) + } +} + +func TestOrganizationsService_ListPendingOrgInvitations(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/invitations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "1"}) + fmt.Fprint(w, `[ + { + "id": 1, + "login": "monalisa", + "email": "octocat@github.com", + "role": "direct_member", + "created_at": "2017-01-21T00:00:00Z", + "inviter": { + "login": "other_user", + "id": 1, + "avatar_url": "https://github.com/images/error/other_user_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/other_user", + "html_url": "https://github.com/other_user", + "followers_url": "https://api.github.com/users/other_user/followers", + "following_url": "https://api.github.com/users/other_user/following/other_user", + "gists_url": "https://api.github.com/users/other_user/gists/gist_id", + "starred_url": "https://api.github.com/users/other_user/starred/owner/repo", + "subscriptions_url": "https://api.github.com/users/other_user/subscriptions", + "organizations_url": "https://api.github.com/users/other_user/orgs", + "repos_url": "https://api.github.com/users/other_user/repos", + "events_url": "https://api.github.com/users/other_user/events/privacy", + "received_events_url": "https://api.github.com/users/other_user/received_events/privacy", + "type": "User", + "site_admin": false + }, + "team_count": 2, + "invitation_team_url": "https://api.github.com/organizations/2/invitations/1/teams" + } + ]`) + }) + + opt := &ListOptions{Page: 1} + invitations, _, err := client.Organizations.ListPendingOrgInvitations(context.Background(), "o", opt) + if err != nil { + t.Errorf("Organizations.ListPendingOrgInvitations returned error: %v", err) + } + + createdAt := time.Date(2017, time.January, 21, 0, 0, 0, 0, time.UTC) + want := []*Invitation{ + { + ID: Int64(1), + Login: String("monalisa"), + Email: String("octocat@github.com"), + Role: String("direct_member"), + CreatedAt: &createdAt, + Inviter: &User{ + Login: String("other_user"), + ID: Int64(1), + AvatarURL: String("https://github.com/images/error/other_user_happy.gif"), + GravatarID: String(""), + URL: String("https://api.github.com/users/other_user"), + HTMLURL: String("https://github.com/other_user"), + FollowersURL: String("https://api.github.com/users/other_user/followers"), + FollowingURL: String("https://api.github.com/users/other_user/following/other_user"), + GistsURL: String("https://api.github.com/users/other_user/gists/gist_id"), + StarredURL: String("https://api.github.com/users/other_user/starred/owner/repo"), + SubscriptionsURL: String("https://api.github.com/users/other_user/subscriptions"), + OrganizationsURL: String("https://api.github.com/users/other_user/orgs"), + ReposURL: String("https://api.github.com/users/other_user/repos"), + EventsURL: String("https://api.github.com/users/other_user/events/privacy"), + ReceivedEventsURL: String("https://api.github.com/users/other_user/received_events/privacy"), + Type: String("User"), + SiteAdmin: Bool(false), + }, + TeamCount: Int(2), + InvitationTeamURL: String("https://api.github.com/organizations/2/invitations/1/teams"), + }} + + if !reflect.DeepEqual(invitations, want) { + t.Errorf("Organizations.ListPendingOrgInvitations returned %+v, want %+v", invitations, want) + } +} + +func TestOrganizationsService_CreateOrgInvitation(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + input := &CreateOrgInvitationOptions{ + Email: String("octocat@github.com"), + Role: String("direct_member"), + TeamID: []int64{ + 12, + 26, + }, + } + + mux.HandleFunc("/orgs/o/invitations", func(w http.ResponseWriter, r *http.Request) { + v := new(CreateOrgInvitationOptions) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeOrganizationInvitationPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprintln(w, `{"email": "octocat@github.com"}`) + + }) + + invitations, _, err := client.Organizations.CreateOrgInvitation(context.Background(), "o", input) + if err != nil { + t.Errorf("Organizations.CreateOrgInvitation returned error: %v", err) + } + + want := &Invitation{Email: String("octocat@github.com")} + if !reflect.DeepEqual(invitations, want) { + t.Errorf("Organizations.ListPendingOrgInvitations returned %+v, want %+v", invitations, want) + } +} + +func TestOrganizationsService_ListOrgInvitationTeams(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/invitations/22/teams", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "1"}) + testHeader(t, r, "Accept", mediaTypeOrganizationInvitationPreview) + fmt.Fprint(w, `[ + { + "id": 1, + "url": "https://api.github.com/teams/1", + "name": "Justice League", + "slug": "justice-league", + "description": "A great team.", + "privacy": "closed", + "permission": "admin", + "members_url": "https://api.github.com/teams/1/members{/member}", + "repositories_url": "https://api.github.com/teams/1/repos" + } + ]`) + }) + + opt := &ListOptions{Page: 1} + invitations, _, err := client.Organizations.ListOrgInvitationTeams(context.Background(), "o", "22", opt) + if err != nil { + t.Errorf("Organizations.ListOrgInvitationTeams returned error: %v", err) + } + + want := []*Team{ + { + ID: Int64(1), + URL: String("https://api.github.com/teams/1"), + Name: String("Justice League"), + Slug: String("justice-league"), + Description: String("A great team."), + Privacy: String("closed"), + Permission: String("admin"), + MembersURL: String("https://api.github.com/teams/1/members{/member}"), + RepositoriesURL: String("https://api.github.com/teams/1/repos"), + }, + } + + if !reflect.DeepEqual(invitations, want) { + t.Errorf("Organizations.ListOrgInvitationTeams returned %+v, want %+v", invitations, want) + } +} diff --git a/vendor/github.com/google/go-github/github/orgs_outside_collaborators.go b/vendor/github.com/google/go-github/github/orgs_outside_collaborators.go new file mode 100644 index 0000000..85ffd05 --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_outside_collaborators.go @@ -0,0 +1,81 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ListOutsideCollaboratorsOptions specifies optional parameters to the +// OrganizationsService.ListOutsideCollaborators method. +type ListOutsideCollaboratorsOptions struct { + // Filter outside collaborators returned in the list. Possible values are: + // 2fa_disabled, all. Default is "all". + Filter string `url:"filter,omitempty"` + + ListOptions +} + +// ListOutsideCollaborators lists outside collaborators of organization's repositories. +// This will only work if the authenticated +// user is an owner of the organization. +// +// Warning: The API may change without advance notice during the preview period. +// Preview features are not supported for production use. +// +// GitHub API docs: https://developer.github.com/v3/orgs/outside_collaborators/#list-outside-collaborators +func (s *OrganizationsService) ListOutsideCollaborators(ctx context.Context, org string, opt *ListOutsideCollaboratorsOptions) ([]*User, *Response, error) { + u := fmt.Sprintf("orgs/%v/outside_collaborators", org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var members []*User + resp, err := s.client.Do(ctx, req, &members) + if err != nil { + return nil, resp, err + } + + return members, resp, nil +} + +// RemoveOutsideCollaborator removes a user from the list of outside collaborators; +// consequently, removing them from all the organization's repositories. +// +// GitHub API docs: https://developer.github.com/v3/orgs/outside_collaborators/#remove-outside-collaborator +func (s *OrganizationsService) RemoveOutsideCollaborator(ctx context.Context, org string, user string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/outside_collaborators/%v", org, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// ConvertMemberToOutsideCollaborator reduces the permission level of a member of the +// organization to that of an outside collaborator. Therefore, they will only +// have access to the repositories that their current team membership allows. +// Responses for converting a non-member or the last owner to an outside collaborator +// are listed in GitHub API docs. +// +// GitHub API docs: https://developer.github.com/v3/orgs/outside_collaborators/#convert-member-to-outside-collaborator +func (s *OrganizationsService) ConvertMemberToOutsideCollaborator(ctx context.Context, org string, user string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/outside_collaborators/%v", org, user) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/orgs_outside_collaborators_test.go b/vendor/github.com/google/go-github/github/orgs_outside_collaborators_test.go new file mode 100644 index 0000000..8b72feb --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_outside_collaborators_test.go @@ -0,0 +1,134 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestOrganizationsService_ListOutsideCollaborators(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/outside_collaborators", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "filter": "2fa_disabled", + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOutsideCollaboratorsOptions{ + Filter: "2fa_disabled", + ListOptions: ListOptions{Page: 2}, + } + members, _, err := client.Organizations.ListOutsideCollaborators(context.Background(), "o", opt) + if err != nil { + t.Errorf("Organizations.ListOutsideCollaborators returned error: %v", err) + } + + want := []*User{{ID: Int64(1)}} + if !reflect.DeepEqual(members, want) { + t.Errorf("Organizations.ListOutsideCollaborators returned %+v, want %+v", members, want) + } +} + +func TestOrganizationsService_ListOutsideCollaborators_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Organizations.ListOutsideCollaborators(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_RemoveOutsideCollaborator(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + } + mux.HandleFunc("/orgs/o/outside_collaborators/u", handler) + + _, err := client.Organizations.RemoveOutsideCollaborator(context.Background(), "o", "u") + if err != nil { + t.Errorf("Organizations.RemoveOutsideCollaborator returned error: %v", err) + } +} + +func TestOrganizationsService_RemoveOutsideCollaborator_NonMember(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNotFound) + } + mux.HandleFunc("/orgs/o/outside_collaborators/u", handler) + + _, err := client.Organizations.RemoveOutsideCollaborator(context.Background(), "o", "u") + if err, ok := err.(*ErrorResponse); !ok { + t.Errorf("Organizations.RemoveOutsideCollaborator did not return an error") + } else if err.Response.StatusCode != http.StatusNotFound { + t.Errorf("Organizations.RemoveOutsideCollaborator did not return 404 status code") + } +} + +func TestOrganizationsService_RemoveOutsideCollaborator_Member(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusUnprocessableEntity) + } + mux.HandleFunc("/orgs/o/outside_collaborators/u", handler) + + _, err := client.Organizations.RemoveOutsideCollaborator(context.Background(), "o", "u") + if err, ok := err.(*ErrorResponse); !ok { + t.Errorf("Organizations.RemoveOutsideCollaborator did not return an error") + } else if err.Response.StatusCode != http.StatusUnprocessableEntity { + t.Errorf("Organizations.RemoveOutsideCollaborator did not return 422 status code") + } +} + +func TestOrganizationsService_ConvertMemberToOutsideCollaborator(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + } + mux.HandleFunc("/orgs/o/outside_collaborators/u", handler) + + _, err := client.Organizations.ConvertMemberToOutsideCollaborator(context.Background(), "o", "u") + if err != nil { + t.Errorf("Organizations.ConvertMemberToOutsideCollaborator returned error: %v", err) + } +} + +func TestOrganizationsService_ConvertMemberToOutsideCollaborator_NonMemberOrLastOwner(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusForbidden) + } + mux.HandleFunc("/orgs/o/outside_collaborators/u", handler) + + _, err := client.Organizations.ConvertMemberToOutsideCollaborator(context.Background(), "o", "u") + if err, ok := err.(*ErrorResponse); !ok { + t.Errorf("Organizations.ConvertMemberToOutsideCollaborator did not return an error") + } else if err.Response.StatusCode != http.StatusForbidden { + t.Errorf("Organizations.ConvertMemberToOutsideCollaborator did not return 403 status code") + } +} diff --git a/vendor/github.com/google/go-github/github/orgs_projects.go b/vendor/github.com/google/go-github/github/orgs_projects.go new file mode 100644 index 0000000..e57cba9 --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_projects.go @@ -0,0 +1,60 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ListProjects lists the projects for an organization. +// +// GitHub API docs: https://developer.github.com/v3/projects/#list-organization-projects +func (s *OrganizationsService) ListProjects(ctx context.Context, org string, opt *ProjectListOptions) ([]*Project, *Response, error) { + u := fmt.Sprintf("orgs/%v/projects", org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + var projects []*Project + resp, err := s.client.Do(ctx, req, &projects) + if err != nil { + return nil, resp, err + } + + return projects, resp, nil +} + +// CreateProject creates a GitHub Project for the specified organization. +// +// GitHub API docs: https://developer.github.com/v3/projects/#create-an-organization-project +func (s *OrganizationsService) CreateProject(ctx context.Context, org string, opt *ProjectOptions) (*Project, *Response, error) { + u := fmt.Sprintf("orgs/%v/projects", org) + req, err := s.client.NewRequest("POST", u, opt) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + project := &Project{} + resp, err := s.client.Do(ctx, req, project) + if err != nil { + return nil, resp, err + } + + return project, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/orgs_projects_test.go b/vendor/github.com/google/go-github/github/orgs_projects_test.go new file mode 100644 index 0000000..efea5cb --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_projects_test.go @@ -0,0 +1,68 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestOrganizationsService_ListProjects(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/projects", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + testFormValues(t, r, values{"state": "open", "page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ProjectListOptions{State: "open", ListOptions: ListOptions{Page: 2}} + projects, _, err := client.Organizations.ListProjects(context.Background(), "o", opt) + if err != nil { + t.Errorf("Organizations.ListProjects returned error: %v", err) + } + + want := []*Project{{ID: Int64(1)}} + if !reflect.DeepEqual(projects, want) { + t.Errorf("Organizations.ListProjects returned %+v, want %+v", projects, want) + } +} + +func TestOrganizationsService_CreateProject(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &ProjectOptions{Name: String("Project Name"), Body: String("Project body.")} + + mux.HandleFunc("/orgs/o/projects", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + + v := &ProjectOptions{} + json.NewDecoder(r.Body).Decode(v) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + project, _, err := client.Organizations.CreateProject(context.Background(), "o", input) + if err != nil { + t.Errorf("Organizations.CreateProject returned error: %v", err) + } + + want := &Project{ID: Int64(1)} + if !reflect.DeepEqual(project, want) { + t.Errorf("Organizations.CreateProject returned %+v, want %+v", project, want) + } +} diff --git a/vendor/github.com/google/go-github/github/orgs_test.go b/vendor/github.com/google/go-github/github/orgs_test.go new file mode 100644 index 0000000..9fe484d --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_test.go @@ -0,0 +1,173 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestOrganizationsService_ListAll(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + since := int64(1342004) + mux.HandleFunc("/organizations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"since": "1342004"}) + fmt.Fprint(w, `[{"id":4314092}]`) + }) + + opt := &OrganizationsListOptions{Since: since} + orgs, _, err := client.Organizations.ListAll(context.Background(), opt) + if err != nil { + t.Errorf("Organizations.ListAll returned error: %v", err) + } + + want := []*Organization{{ID: Int64(4314092)}} + if !reflect.DeepEqual(orgs, want) { + t.Errorf("Organizations.ListAll returned %+v, want %+v", orgs, want) + } +} + +func TestOrganizationsService_List_authenticatedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/orgs", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + orgs, _, err := client.Organizations.List(context.Background(), "", nil) + if err != nil { + t.Errorf("Organizations.List returned error: %v", err) + } + + want := []*Organization{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(orgs, want) { + t.Errorf("Organizations.List returned %+v, want %+v", orgs, want) + } +} + +func TestOrganizationsService_List_specifiedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/orgs", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + orgs, _, err := client.Organizations.List(context.Background(), "u", opt) + if err != nil { + t.Errorf("Organizations.List returned error: %v", err) + } + + want := []*Organization{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(orgs, want) { + t.Errorf("Organizations.List returned %+v, want %+v", orgs, want) + } +} + +func TestOrganizationsService_List_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Organizations.List(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_Get(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1, "login":"l", "url":"u", "avatar_url": "a", "location":"l"}`) + }) + + org, _, err := client.Organizations.Get(context.Background(), "o") + if err != nil { + t.Errorf("Organizations.Get returned error: %v", err) + } + + want := &Organization{ID: Int64(1), Login: String("l"), URL: String("u"), AvatarURL: String("a"), Location: String("l")} + if !reflect.DeepEqual(org, want) { + t.Errorf("Organizations.Get returned %+v, want %+v", org, want) + } +} + +func TestOrganizationsService_Get_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Organizations.Get(context.Background(), "%") + testURLParseError(t, err) +} + +func TestOrganizationsService_GetByID(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/organizations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1, "login":"l", "url":"u", "avatar_url": "a", "location":"l"}`) + }) + + org, _, err := client.Organizations.GetByID(context.Background(), 1) + if err != nil { + t.Fatalf("Organizations.GetByID returned error: %v", err) + } + + want := &Organization{ID: Int64(1), Login: String("l"), URL: String("u"), AvatarURL: String("a"), Location: String("l")} + if !reflect.DeepEqual(org, want) { + t.Errorf("Organizations.GetByID returned %+v, want %+v", org, want) + } +} + +func TestOrganizationsService_Edit(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Organization{Login: String("l")} + + mux.HandleFunc("/orgs/o", func(w http.ResponseWriter, r *http.Request) { + v := new(Organization) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + org, _, err := client.Organizations.Edit(context.Background(), "o", input) + if err != nil { + t.Errorf("Organizations.Edit returned error: %v", err) + } + + want := &Organization{ID: Int64(1)} + if !reflect.DeepEqual(org, want) { + t.Errorf("Organizations.Edit returned %+v, want %+v", org, want) + } +} + +func TestOrganizationsService_Edit_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Organizations.Edit(context.Background(), "%", nil) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/orgs_users_blocking.go b/vendor/github.com/google/go-github/github/orgs_users_blocking.go new file mode 100644 index 0000000..b1aecf4 --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_users_blocking.go @@ -0,0 +1,91 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ListBlockedUsers lists all the users blocked by an organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/blocking/#list-blocked-users +func (s *OrganizationsService) ListBlockedUsers(ctx context.Context, org string, opt *ListOptions) ([]*User, *Response, error) { + u := fmt.Sprintf("orgs/%v/blocks", org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeBlockUsersPreview) + + var blockedUsers []*User + resp, err := s.client.Do(ctx, req, &blockedUsers) + if err != nil { + return nil, resp, err + } + + return blockedUsers, resp, nil +} + +// IsBlocked reports whether specified user is blocked from an organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/blocking/#check-whether-a-user-is-blocked-from-an-organization +func (s *OrganizationsService) IsBlocked(ctx context.Context, org string, user string) (bool, *Response, error) { + u := fmt.Sprintf("orgs/%v/blocks/%v", org, user) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeBlockUsersPreview) + + resp, err := s.client.Do(ctx, req, nil) + isBlocked, err := parseBoolResponse(err) + return isBlocked, resp, err +} + +// BlockUser blocks specified user from an organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/blocking/#block-a-user +func (s *OrganizationsService) BlockUser(ctx context.Context, org string, user string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/blocks/%v", org, user) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeBlockUsersPreview) + + return s.client.Do(ctx, req, nil) +} + +// UnblockUser unblocks specified user from an organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/blocking/#unblock-a-user +func (s *OrganizationsService) UnblockUser(ctx context.Context, org string, user string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/blocks/%v", org, user) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeBlockUsersPreview) + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/orgs_users_blocking_test.go b/vendor/github.com/google/go-github/github/orgs_users_blocking_test.go new file mode 100644 index 0000000..5d38cc4 --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_users_blocking_test.go @@ -0,0 +1,90 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestOrganizationsService_ListBlockedUsers(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/blocks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeBlockUsersPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{ + "login": "octocat" + }]`) + }) + + opt := &ListOptions{Page: 2} + blockedUsers, _, err := client.Organizations.ListBlockedUsers(context.Background(), "o", opt) + if err != nil { + t.Errorf("Organizations.ListBlockedUsers returned error: %v", err) + } + + want := []*User{{Login: String("octocat")}} + if !reflect.DeepEqual(blockedUsers, want) { + t.Errorf("Organizations.ListBlockedUsers returned %+v, want %+v", blockedUsers, want) + } +} + +func TestOrganizationsService_IsBlocked(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/blocks/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeBlockUsersPreview) + w.WriteHeader(http.StatusNoContent) + }) + + isBlocked, _, err := client.Organizations.IsBlocked(context.Background(), "o", "u") + if err != nil { + t.Errorf("Organizations.IsBlocked returned error: %v", err) + } + if want := true; isBlocked != want { + t.Errorf("Organizations.IsBlocked returned %+v, want %+v", isBlocked, want) + } +} + +func TestOrganizationsService_BlockUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/blocks/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testHeader(t, r, "Accept", mediaTypeBlockUsersPreview) + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.BlockUser(context.Background(), "o", "u") + if err != nil { + t.Errorf("Organizations.BlockUser returned error: %v", err) + } +} + +func TestOrganizationsService_UnblockUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/blocks/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeBlockUsersPreview) + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.UnblockUser(context.Background(), "o", "u") + if err != nil { + t.Errorf("Organizations.UnblockUser returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/projects.go b/vendor/github.com/google/go-github/github/projects.go new file mode 100644 index 0000000..76ef1e0 --- /dev/null +++ b/vendor/github.com/google/go-github/github/projects.go @@ -0,0 +1,462 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ProjectsService provides access to the projects functions in the +// GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/projects/ +type ProjectsService service + +// Project represents a GitHub Project. +type Project struct { + ID *int64 `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + ColumnsURL *string `json:"columns_url,omitempty"` + OwnerURL *string `json:"owner_url,omitempty"` + Name *string `json:"name,omitempty"` + Body *string `json:"body,omitempty"` + Number *int `json:"number,omitempty"` + State *string `json:"state,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + NodeID *string `json:"node_id,omitempty"` + + // The User object that generated the project. + Creator *User `json:"creator,omitempty"` +} + +func (p Project) String() string { + return Stringify(p) +} + +// GetProject gets a GitHub Project for a repo. +// +// GitHub API docs: https://developer.github.com/v3/projects/#get-a-project +func (s *ProjectsService) GetProject(ctx context.Context, id int64) (*Project, *Response, error) { + u := fmt.Sprintf("projects/%v", id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + project := &Project{} + resp, err := s.client.Do(ctx, req, project) + if err != nil { + return nil, resp, err + } + + return project, resp, nil +} + +// ProjectOptions specifies the parameters to the +// RepositoriesService.CreateProject and +// ProjectsService.UpdateProject methods. +type ProjectOptions struct { + // The name of the project. (Required for creation; optional for update.) + Name *string `json:"name,omitempty"` + // The body of the project. (Optional.) + Body *string `json:"body,omitempty"` + + // The following field(s) are only applicable for update. + // They should be left with zero values for creation. + + // State of the project. Either "open" or "closed". (Optional.) + State *string `json:"state,omitempty"` + // The permission level that all members of the project's organization + // will have on this project. + // Setting the organization permission is only available + // for organization projects. (Optional.) + OrganizationPermission *string `json:"organization_permission,omitempty"` + // Sets visibility of the project within the organization. + // Setting visibility is only available + // for organization projects.(Optional.) + Public *bool `json:"public,omitempty"` +} + +// UpdateProject updates a repository project. +// +// GitHub API docs: https://developer.github.com/v3/projects/#update-a-project +func (s *ProjectsService) UpdateProject(ctx context.Context, id int64, opt *ProjectOptions) (*Project, *Response, error) { + u := fmt.Sprintf("projects/%v", id) + req, err := s.client.NewRequest("PATCH", u, opt) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + project := &Project{} + resp, err := s.client.Do(ctx, req, project) + if err != nil { + return nil, resp, err + } + + return project, resp, nil +} + +// DeleteProject deletes a GitHub Project from a repository. +// +// GitHub API docs: https://developer.github.com/v3/projects/#delete-a-project +func (s *ProjectsService) DeleteProject(ctx context.Context, id int64) (*Response, error) { + u := fmt.Sprintf("projects/%v", id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + return s.client.Do(ctx, req, nil) +} + +// ProjectColumn represents a column of a GitHub Project. +// +// GitHub API docs: https://developer.github.com/v3/repos/projects/ +type ProjectColumn struct { + ID *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + URL *string `json:"url,omitempty"` + ProjectURL *string `json:"project_url,omitempty"` + CardsURL *string `json:"cards_url,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + NodeID *string `json:"node_id,omitempty"` +} + +// ListProjectColumns lists the columns of a GitHub Project for a repo. +// +// GitHub API docs: https://developer.github.com/v3/projects/columns/#list-project-columns +func (s *ProjectsService) ListProjectColumns(ctx context.Context, projectID int64, opt *ListOptions) ([]*ProjectColumn, *Response, error) { + u := fmt.Sprintf("projects/%v/columns", projectID) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + columns := []*ProjectColumn{} + resp, err := s.client.Do(ctx, req, &columns) + if err != nil { + return nil, resp, err + } + + return columns, resp, nil +} + +// GetProjectColumn gets a column of a GitHub Project for a repo. +// +// GitHub API docs: https://developer.github.com/v3/projects/columns/#get-a-project-column +func (s *ProjectsService) GetProjectColumn(ctx context.Context, id int64) (*ProjectColumn, *Response, error) { + u := fmt.Sprintf("projects/columns/%v", id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + column := &ProjectColumn{} + resp, err := s.client.Do(ctx, req, column) + if err != nil { + return nil, resp, err + } + + return column, resp, nil +} + +// ProjectColumnOptions specifies the parameters to the +// ProjectsService.CreateProjectColumn and +// ProjectsService.UpdateProjectColumn methods. +type ProjectColumnOptions struct { + // The name of the project column. (Required for creation and update.) + Name string `json:"name"` +} + +// CreateProjectColumn creates a column for the specified (by number) project. +// +// GitHub API docs: https://developer.github.com/v3/projects/columns/#create-a-project-column +func (s *ProjectsService) CreateProjectColumn(ctx context.Context, projectID int64, opt *ProjectColumnOptions) (*ProjectColumn, *Response, error) { + u := fmt.Sprintf("projects/%v/columns", projectID) + req, err := s.client.NewRequest("POST", u, opt) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + column := &ProjectColumn{} + resp, err := s.client.Do(ctx, req, column) + if err != nil { + return nil, resp, err + } + + return column, resp, nil +} + +// UpdateProjectColumn updates a column of a GitHub Project. +// +// GitHub API docs: https://developer.github.com/v3/projects/columns/#update-a-project-column +func (s *ProjectsService) UpdateProjectColumn(ctx context.Context, columnID int64, opt *ProjectColumnOptions) (*ProjectColumn, *Response, error) { + u := fmt.Sprintf("projects/columns/%v", columnID) + req, err := s.client.NewRequest("PATCH", u, opt) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + column := &ProjectColumn{} + resp, err := s.client.Do(ctx, req, column) + if err != nil { + return nil, resp, err + } + + return column, resp, nil +} + +// DeleteProjectColumn deletes a column from a GitHub Project. +// +// GitHub API docs: https://developer.github.com/v3/projects/columns/#delete-a-project-column +func (s *ProjectsService) DeleteProjectColumn(ctx context.Context, columnID int64) (*Response, error) { + u := fmt.Sprintf("projects/columns/%v", columnID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + return s.client.Do(ctx, req, nil) +} + +// ProjectColumnMoveOptions specifies the parameters to the +// ProjectsService.MoveProjectColumn method. +type ProjectColumnMoveOptions struct { + // Position can be one of "first", "last", or "after:", where + // is the ID of a column in the same project. (Required.) + Position string `json:"position"` +} + +// MoveProjectColumn moves a column within a GitHub Project. +// +// GitHub API docs: https://developer.github.com/v3/projects/columns/#move-a-project-column +func (s *ProjectsService) MoveProjectColumn(ctx context.Context, columnID int64, opt *ProjectColumnMoveOptions) (*Response, error) { + u := fmt.Sprintf("projects/columns/%v/moves", columnID) + req, err := s.client.NewRequest("POST", u, opt) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + return s.client.Do(ctx, req, nil) +} + +// ProjectCard represents a card in a column of a GitHub Project. +// +// GitHub API docs: https://developer.github.com/v3/projects/cards/#get-a-project-card +type ProjectCard struct { + URL *string `json:"url,omitempty"` + ColumnURL *string `json:"column_url,omitempty"` + ContentURL *string `json:"content_url,omitempty"` + ID *int64 `json:"id,omitempty"` + Note *string `json:"note,omitempty"` + Creator *User `json:"creator,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + NodeID *string `json:"node_id,omitempty"` + Archived *bool `json:"archived,omitempty"` + + // The following fields are only populated by Webhook events. + ColumnID *int64 `json:"column_id,omitempty"` +} + +// ProjectCardListOptions specifies the optional parameters to the +// ProjectsService.ListProjectCards method. +type ProjectCardListOptions struct { + // ArchivedState is used to list all, archived, or not_archived project cards. + // Defaults to not_archived when you omit this parameter. + ArchivedState *string `url:"archived_state,omitempty"` + + ListOptions +} + +// ListProjectCards lists the cards in a column of a GitHub Project. +// +// GitHub API docs: https://developer.github.com/v3/projects/cards/#list-project-cards +func (s *ProjectsService) ListProjectCards(ctx context.Context, columnID int64, opt *ProjectCardListOptions) ([]*ProjectCard, *Response, error) { + u := fmt.Sprintf("projects/columns/%v/cards", columnID) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + cards := []*ProjectCard{} + resp, err := s.client.Do(ctx, req, &cards) + if err != nil { + return nil, resp, err + } + + return cards, resp, nil +} + +// GetProjectCard gets a card in a column of a GitHub Project. +// +// GitHub API docs: https://developer.github.com/v3/projects/cards/#get-a-project-card +func (s *ProjectsService) GetProjectCard(ctx context.Context, columnID int64) (*ProjectCard, *Response, error) { + u := fmt.Sprintf("projects/columns/cards/%v", columnID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + card := &ProjectCard{} + resp, err := s.client.Do(ctx, req, card) + if err != nil { + return nil, resp, err + } + + return card, resp, nil +} + +// ProjectCardOptions specifies the parameters to the +// ProjectsService.CreateProjectCard and +// ProjectsService.UpdateProjectCard methods. +type ProjectCardOptions struct { + // The note of the card. Note and ContentID are mutually exclusive. + Note string `json:"note,omitempty"` + // The ID (not Number) of the Issue to associate with this card. + // Note and ContentID are mutually exclusive. + ContentID int64 `json:"content_id,omitempty"` + // The type of content to associate with this card. Possible values are: "Issue". + ContentType string `json:"content_type,omitempty"` + // Use true to archive a project card. + // Specify false if you need to restore a previously archived project card. + Archived *bool `json:"archived,omitempty"` +} + +// CreateProjectCard creates a card in the specified column of a GitHub Project. +// +// GitHub API docs: https://developer.github.com/v3/projects/cards/#create-a-project-card +func (s *ProjectsService) CreateProjectCard(ctx context.Context, columnID int64, opt *ProjectCardOptions) (*ProjectCard, *Response, error) { + u := fmt.Sprintf("projects/columns/%v/cards", columnID) + req, err := s.client.NewRequest("POST", u, opt) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + card := &ProjectCard{} + resp, err := s.client.Do(ctx, req, card) + if err != nil { + return nil, resp, err + } + + return card, resp, nil +} + +// UpdateProjectCard updates a card of a GitHub Project. +// +// GitHub API docs: https://developer.github.com/v3/projects/cards/#update-a-project-card +func (s *ProjectsService) UpdateProjectCard(ctx context.Context, cardID int64, opt *ProjectCardOptions) (*ProjectCard, *Response, error) { + u := fmt.Sprintf("projects/columns/cards/%v", cardID) + req, err := s.client.NewRequest("PATCH", u, opt) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + card := &ProjectCard{} + resp, err := s.client.Do(ctx, req, card) + if err != nil { + return nil, resp, err + } + + return card, resp, nil +} + +// DeleteProjectCard deletes a card from a GitHub Project. +// +// GitHub API docs: https://developer.github.com/v3/projects/cards/#delete-a-project-card +func (s *ProjectsService) DeleteProjectCard(ctx context.Context, cardID int64) (*Response, error) { + u := fmt.Sprintf("projects/columns/cards/%v", cardID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + return s.client.Do(ctx, req, nil) +} + +// ProjectCardMoveOptions specifies the parameters to the +// ProjectsService.MoveProjectCard method. +type ProjectCardMoveOptions struct { + // Position can be one of "top", "bottom", or "after:", where + // is the ID of a card in the same project. + Position string `json:"position"` + // ColumnID is the ID of a column in the same project. Note that ColumnID + // is required when using Position "after:" when that card is in + // another column; otherwise it is optional. + ColumnID int64 `json:"column_id,omitempty"` +} + +// MoveProjectCard moves a card within a GitHub Project. +// +// GitHub API docs: https://developer.github.com/v3/projects/cards/#move-a-project-card +func (s *ProjectsService) MoveProjectCard(ctx context.Context, cardID int64, opt *ProjectCardMoveOptions) (*Response, error) { + u := fmt.Sprintf("projects/columns/cards/%v/moves", cardID) + req, err := s.client.NewRequest("POST", u, opt) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/projects_test.go b/vendor/github.com/google/go-github/github/projects_test.go new file mode 100644 index 0000000..9f12272 --- /dev/null +++ b/vendor/github.com/google/go-github/github/projects_test.go @@ -0,0 +1,384 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "strings" + "testing" +) + +func TestProjectsService_UpdateProject(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &ProjectOptions{ + Name: String("Project Name"), + Body: String("Project body."), + State: String("open"), + Public: Bool(true), + + OrganizationPermission: String("read"), + } + + mux.HandleFunc("/projects/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + + v := &ProjectOptions{} + json.NewDecoder(r.Body).Decode(v) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + project, _, err := client.Projects.UpdateProject(context.Background(), 1, input) + if err != nil { + t.Errorf("Projects.UpdateProject returned error: %v", err) + } + + want := &Project{ID: Int64(1)} + if !reflect.DeepEqual(project, want) { + t.Errorf("Projects.UpdateProject returned %+v, want %+v", project, want) + } +} + +func TestProjectsService_GetProject(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/projects/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + fmt.Fprint(w, `{"id":1}`) + }) + + project, _, err := client.Projects.GetProject(context.Background(), 1) + if err != nil { + t.Errorf("Projects.GetProject returned error: %v", err) + } + + want := &Project{ID: Int64(1)} + if !reflect.DeepEqual(project, want) { + t.Errorf("Projects.GetProject returned %+v, want %+v", project, want) + } +} + +func TestProjectsService_DeleteProject(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/projects/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + }) + + _, err := client.Projects.DeleteProject(context.Background(), 1) + if err != nil { + t.Errorf("Projects.DeleteProject returned error: %v", err) + } +} + +func TestProjectsService_ListProjectColumns(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + acceptHeaders := []string{mediaTypeProjectsPreview} + mux.HandleFunc("/projects/1/columns", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + columns, _, err := client.Projects.ListProjectColumns(context.Background(), 1, opt) + if err != nil { + t.Errorf("Projects.ListProjectColumns returned error: %v", err) + } + + want := []*ProjectColumn{{ID: Int64(1)}} + if !reflect.DeepEqual(columns, want) { + t.Errorf("Projects.ListProjectColumns returned %+v, want %+v", columns, want) + } +} + +func TestProjectsService_GetProjectColumn(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/projects/columns/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + fmt.Fprint(w, `{"id":1}`) + }) + + column, _, err := client.Projects.GetProjectColumn(context.Background(), 1) + if err != nil { + t.Errorf("Projects.GetProjectColumn returned error: %v", err) + } + + want := &ProjectColumn{ID: Int64(1)} + if !reflect.DeepEqual(column, want) { + t.Errorf("Projects.GetProjectColumn returned %+v, want %+v", column, want) + } +} + +func TestProjectsService_CreateProjectColumn(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &ProjectColumnOptions{Name: "Column Name"} + + mux.HandleFunc("/projects/1/columns", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + + v := &ProjectColumnOptions{} + json.NewDecoder(r.Body).Decode(v) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + column, _, err := client.Projects.CreateProjectColumn(context.Background(), 1, input) + if err != nil { + t.Errorf("Projects.CreateProjectColumn returned error: %v", err) + } + + want := &ProjectColumn{ID: Int64(1)} + if !reflect.DeepEqual(column, want) { + t.Errorf("Projects.CreateProjectColumn returned %+v, want %+v", column, want) + } +} + +func TestProjectsService_UpdateProjectColumn(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &ProjectColumnOptions{Name: "Column Name"} + + mux.HandleFunc("/projects/columns/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + + v := &ProjectColumnOptions{} + json.NewDecoder(r.Body).Decode(v) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + column, _, err := client.Projects.UpdateProjectColumn(context.Background(), 1, input) + if err != nil { + t.Errorf("Projects.UpdateProjectColumn returned error: %v", err) + } + + want := &ProjectColumn{ID: Int64(1)} + if !reflect.DeepEqual(column, want) { + t.Errorf("Projects.UpdateProjectColumn returned %+v, want %+v", column, want) + } +} + +func TestProjectsService_DeleteProjectColumn(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/projects/columns/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + }) + + _, err := client.Projects.DeleteProjectColumn(context.Background(), 1) + if err != nil { + t.Errorf("Projects.DeleteProjectColumn returned error: %v", err) + } +} + +func TestProjectsService_MoveProjectColumn(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &ProjectColumnMoveOptions{Position: "after:12345"} + + mux.HandleFunc("/projects/columns/1/moves", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + + v := &ProjectColumnMoveOptions{} + json.NewDecoder(r.Body).Decode(v) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + }) + + _, err := client.Projects.MoveProjectColumn(context.Background(), 1, input) + if err != nil { + t.Errorf("Projects.MoveProjectColumn returned error: %v", err) + } +} + +func TestProjectsService_ListProjectCards(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/projects/columns/1/cards", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + testFormValues(t, r, values{ + "archived_state": "all", + "page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ProjectCardListOptions{ + ArchivedState: String("all"), + ListOptions: ListOptions{Page: 2}} + cards, _, err := client.Projects.ListProjectCards(context.Background(), 1, opt) + if err != nil { + t.Errorf("Projects.ListProjectCards returned error: %v", err) + } + + want := []*ProjectCard{{ID: Int64(1)}} + if !reflect.DeepEqual(cards, want) { + t.Errorf("Projects.ListProjectCards returned %+v, want %+v", cards, want) + } +} + +func TestProjectsService_GetProjectCard(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/projects/columns/cards/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + fmt.Fprint(w, `{"id":1}`) + }) + + card, _, err := client.Projects.GetProjectCard(context.Background(), 1) + if err != nil { + t.Errorf("Projects.GetProjectCard returned error: %v", err) + } + + want := &ProjectCard{ID: Int64(1)} + if !reflect.DeepEqual(card, want) { + t.Errorf("Projects.GetProjectCard returned %+v, want %+v", card, want) + } +} + +func TestProjectsService_CreateProjectCard(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &ProjectCardOptions{ + ContentID: 12345, + ContentType: "Issue", + } + + mux.HandleFunc("/projects/columns/1/cards", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + + v := &ProjectCardOptions{} + json.NewDecoder(r.Body).Decode(v) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + card, _, err := client.Projects.CreateProjectCard(context.Background(), 1, input) + if err != nil { + t.Errorf("Projects.CreateProjectCard returned error: %v", err) + } + + want := &ProjectCard{ID: Int64(1)} + if !reflect.DeepEqual(card, want) { + t.Errorf("Projects.CreateProjectCard returned %+v, want %+v", card, want) + } +} + +func TestProjectsService_UpdateProjectCard(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &ProjectCardOptions{ + ContentID: 12345, + ContentType: "Issue", + } + + mux.HandleFunc("/projects/columns/cards/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + + v := &ProjectCardOptions{} + json.NewDecoder(r.Body).Decode(v) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1, "archived":false}`) + }) + + card, _, err := client.Projects.UpdateProjectCard(context.Background(), 1, input) + if err != nil { + t.Errorf("Projects.UpdateProjectCard returned error: %v", err) + } + + want := &ProjectCard{ID: Int64(1), Archived: Bool(false)} + if !reflect.DeepEqual(card, want) { + t.Errorf("Projects.UpdateProjectCard returned %+v, want %+v", card, want) + } +} + +func TestProjectsService_DeleteProjectCard(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/projects/columns/cards/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + }) + + _, err := client.Projects.DeleteProjectCard(context.Background(), 1) + if err != nil { + t.Errorf("Projects.DeleteProjectCard returned error: %v", err) + } +} + +func TestProjectsService_MoveProjectCard(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &ProjectCardMoveOptions{Position: "after:12345"} + + mux.HandleFunc("/projects/columns/cards/1/moves", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + + v := &ProjectCardMoveOptions{} + json.NewDecoder(r.Body).Decode(v) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + }) + + _, err := client.Projects.MoveProjectCard(context.Background(), 1, input) + if err != nil { + t.Errorf("Projects.MoveProjectCard returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/pulls.go b/vendor/github.com/google/go-github/github/pulls.go new file mode 100644 index 0000000..a123ec5 --- /dev/null +++ b/vendor/github.com/google/go-github/github/pulls.go @@ -0,0 +1,383 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "bytes" + "context" + "fmt" + "strings" + "time" +) + +// PullRequestsService handles communication with the pull request related +// methods of the GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/pulls/ +type PullRequestsService service + +// PullRequest represents a GitHub pull request on a repository. +type PullRequest struct { + ID *int64 `json:"id,omitempty"` + Number *int `json:"number,omitempty"` + State *string `json:"state,omitempty"` + Title *string `json:"title,omitempty"` + Body *string `json:"body,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + ClosedAt *time.Time `json:"closed_at,omitempty"` + MergedAt *time.Time `json:"merged_at,omitempty"` + Labels []*Label `json:"labels,omitempty"` + User *User `json:"user,omitempty"` + Merged *bool `json:"merged,omitempty"` + Mergeable *bool `json:"mergeable,omitempty"` + MergeableState *string `json:"mergeable_state,omitempty"` + MergedBy *User `json:"merged_by,omitempty"` + MergeCommitSHA *string `json:"merge_commit_sha,omitempty"` + Comments *int `json:"comments,omitempty"` + Commits *int `json:"commits,omitempty"` + Additions *int `json:"additions,omitempty"` + Deletions *int `json:"deletions,omitempty"` + ChangedFiles *int `json:"changed_files,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + IssueURL *string `json:"issue_url,omitempty"` + StatusesURL *string `json:"statuses_url,omitempty"` + DiffURL *string `json:"diff_url,omitempty"` + PatchURL *string `json:"patch_url,omitempty"` + CommitsURL *string `json:"commits_url,omitempty"` + CommentsURL *string `json:"comments_url,omitempty"` + ReviewCommentsURL *string `json:"review_comments_url,omitempty"` + ReviewCommentURL *string `json:"review_comment_url,omitempty"` + Assignee *User `json:"assignee,omitempty"` + Assignees []*User `json:"assignees,omitempty"` + Milestone *Milestone `json:"milestone,omitempty"` + MaintainerCanModify *bool `json:"maintainer_can_modify,omitempty"` + AuthorAssociation *string `json:"author_association,omitempty"` + NodeID *string `json:"node_id,omitempty"` + RequestedReviewers []*User `json:"requested_reviewers,omitempty"` + + Head *PullRequestBranch `json:"head,omitempty"` + Base *PullRequestBranch `json:"base,omitempty"` + + // ActiveLockReason is populated only when LockReason is provided while locking the pull request. + // Possible values are: "off-topic", "too heated", "resolved", and "spam". + ActiveLockReason *string `json:"active_lock_reason,omitempty"` +} + +func (p PullRequest) String() string { + return Stringify(p) +} + +// PullRequestBranch represents a base or head branch in a GitHub pull request. +type PullRequestBranch struct { + Label *string `json:"label,omitempty"` + Ref *string `json:"ref,omitempty"` + SHA *string `json:"sha,omitempty"` + Repo *Repository `json:"repo,omitempty"` + User *User `json:"user,omitempty"` +} + +// PullRequestListOptions specifies the optional parameters to the +// PullRequestsService.List method. +type PullRequestListOptions struct { + // State filters pull requests based on their state. Possible values are: + // open, closed. Default is "open". + State string `url:"state,omitempty"` + + // Head filters pull requests by head user and branch name in the format of: + // "user:ref-name". + Head string `url:"head,omitempty"` + + // Base filters pull requests by base branch name. + Base string `url:"base,omitempty"` + + // Sort specifies how to sort pull requests. Possible values are: created, + // updated, popularity, long-running. Default is "created". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort pull requests. Possible values are: asc, desc. + // If Sort is "created" or not specified, Default is "desc", otherwise Default + // is "asc" + Direction string `url:"direction,omitempty"` + + ListOptions +} + +// List the pull requests for the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#list-pull-requests +func (s *PullRequestsService) List(ctx context.Context, owner string, repo string, opt *PullRequestListOptions) ([]*PullRequest, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + acceptHeaders := []string{mediaTypeLabelDescriptionSearchPreview, mediaTypeLockReasonPreview} + req.Header.Set("Accept", strings.Join(acceptHeaders, ", ")) + + var pulls []*PullRequest + resp, err := s.client.Do(ctx, req, &pulls) + if err != nil { + return nil, resp, err + } + + return pulls, resp, nil +} + +// Get a single pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#get-a-single-pull-request +func (s *PullRequestsService) Get(ctx context.Context, owner string, repo string, number int) (*PullRequest, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + acceptHeaders := []string{mediaTypeLabelDescriptionSearchPreview, mediaTypeLockReasonPreview} + req.Header.Set("Accept", strings.Join(acceptHeaders, ", ")) + + pull := new(PullRequest) + resp, err := s.client.Do(ctx, req, pull) + if err != nil { + return nil, resp, err + } + + return pull, resp, nil +} + +// GetRaw gets a single pull request in raw (diff or patch) format. +func (s *PullRequestsService) GetRaw(ctx context.Context, owner string, repo string, number int, opt RawOptions) (string, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return "", nil, err + } + + switch opt.Type { + case Diff: + req.Header.Set("Accept", mediaTypeV3Diff) + case Patch: + req.Header.Set("Accept", mediaTypeV3Patch) + default: + return "", nil, fmt.Errorf("unsupported raw type %d", opt.Type) + } + + var buf bytes.Buffer + resp, err := s.client.Do(ctx, req, &buf) + if err != nil { + return "", resp, err + } + + return buf.String(), resp, nil +} + +// NewPullRequest represents a new pull request to be created. +type NewPullRequest struct { + Title *string `json:"title,omitempty"` + Head *string `json:"head,omitempty"` + Base *string `json:"base,omitempty"` + Body *string `json:"body,omitempty"` + Issue *int `json:"issue,omitempty"` + MaintainerCanModify *bool `json:"maintainer_can_modify,omitempty"` +} + +// Create a new pull request on the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#create-a-pull-request +func (s *PullRequestsService) Create(ctx context.Context, owner string, repo string, pull *NewPullRequest) (*PullRequest, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo) + req, err := s.client.NewRequest("POST", u, pull) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview) + + p := new(PullRequest) + resp, err := s.client.Do(ctx, req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, nil +} + +type pullRequestUpdate struct { + Title *string `json:"title,omitempty"` + Body *string `json:"body,omitempty"` + State *string `json:"state,omitempty"` + Base *string `json:"base,omitempty"` + MaintainerCanModify *bool `json:"maintainer_can_modify,omitempty"` +} + +// Edit a pull request. +// pull must not be nil. +// +// The following fields are editable: Title, Body, State, Base.Ref and MaintainerCanModify. +// Base.Ref updates the base branch of the pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#update-a-pull-request +func (s *PullRequestsService) Edit(ctx context.Context, owner string, repo string, number int, pull *PullRequest) (*PullRequest, *Response, error) { + if pull == nil { + return nil, nil, fmt.Errorf("pull must be provided") + } + + u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number) + + update := &pullRequestUpdate{ + Title: pull.Title, + Body: pull.Body, + State: pull.State, + MaintainerCanModify: pull.MaintainerCanModify, + } + if pull.Base != nil { + update.Base = pull.Base.Ref + } + + req, err := s.client.NewRequest("PATCH", u, update) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + acceptHeaders := []string{mediaTypeLabelDescriptionSearchPreview, mediaTypeLockReasonPreview} + req.Header.Set("Accept", strings.Join(acceptHeaders, ", ")) + + p := new(PullRequest) + resp, err := s.client.Do(ctx, req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, nil +} + +// ListCommits lists the commits in a pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request +func (s *PullRequestsService) ListCommits(ctx context.Context, owner string, repo string, number int, opt *ListOptions) ([]*RepositoryCommit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/commits", owner, repo, number) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeGitSigningPreview) + + var commits []*RepositoryCommit + resp, err := s.client.Do(ctx, req, &commits) + if err != nil { + return nil, resp, err + } + + return commits, resp, nil +} + +// ListFiles lists the files in a pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#list-pull-requests-files +func (s *PullRequestsService) ListFiles(ctx context.Context, owner string, repo string, number int, opt *ListOptions) ([]*CommitFile, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/files", owner, repo, number) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var commitFiles []*CommitFile + resp, err := s.client.Do(ctx, req, &commitFiles) + if err != nil { + return nil, resp, err + } + + return commitFiles, resp, nil +} + +// IsMerged checks if a pull request has been merged. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#get-if-a-pull-request-has-been-merged +func (s *PullRequestsService) IsMerged(ctx context.Context, owner string, repo string, number int) (bool, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/merge", owner, repo, number) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + merged, err := parseBoolResponse(err) + return merged, resp, err +} + +// PullRequestMergeResult represents the result of merging a pull request. +type PullRequestMergeResult struct { + SHA *string `json:"sha,omitempty"` + Merged *bool `json:"merged,omitempty"` + Message *string `json:"message,omitempty"` +} + +// PullRequestOptions lets you define how a pull request will be merged. +type PullRequestOptions struct { + CommitTitle string // Extra detail to append to automatic commit message. (Optional.) + SHA string // SHA that pull request head must match to allow merge. (Optional.) + + // The merge method to use. Possible values include: "merge", "squash", and "rebase" with the default being merge. (Optional.) + MergeMethod string +} + +type pullRequestMergeRequest struct { + CommitMessage string `json:"commit_message"` + CommitTitle string `json:"commit_title,omitempty"` + MergeMethod string `json:"merge_method,omitempty"` + SHA string `json:"sha,omitempty"` +} + +// Merge a pull request (Merge Button™). +// commitMessage is the title for the automatic commit message. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-buttontrade +func (s *PullRequestsService) Merge(ctx context.Context, owner string, repo string, number int, commitMessage string, options *PullRequestOptions) (*PullRequestMergeResult, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/merge", owner, repo, number) + + pullRequestBody := &pullRequestMergeRequest{CommitMessage: commitMessage} + if options != nil { + pullRequestBody.CommitTitle = options.CommitTitle + pullRequestBody.MergeMethod = options.MergeMethod + pullRequestBody.SHA = options.SHA + } + req, err := s.client.NewRequest("PUT", u, pullRequestBody) + if err != nil { + return nil, nil, err + } + + mergeResult := new(PullRequestMergeResult) + resp, err := s.client.Do(ctx, req, mergeResult) + if err != nil { + return nil, resp, err + } + + return mergeResult, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/pulls_comments.go b/vendor/github.com/google/go-github/github/pulls_comments.go new file mode 100644 index 0000000..f306776 --- /dev/null +++ b/vendor/github.com/google/go-github/github/pulls_comments.go @@ -0,0 +1,188 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// PullRequestComment represents a comment left on a pull request. +type PullRequestComment struct { + ID *int64 `json:"id,omitempty"` + InReplyTo *int64 `json:"in_reply_to_id,omitempty"` + Body *string `json:"body,omitempty"` + Path *string `json:"path,omitempty"` + DiffHunk *string `json:"diff_hunk,omitempty"` + PullRequestReviewID *int64 `json:"pull_request_review_id,omitempty"` + Position *int `json:"position,omitempty"` + OriginalPosition *int `json:"original_position,omitempty"` + CommitID *string `json:"commit_id,omitempty"` + OriginalCommitID *string `json:"original_commit_id,omitempty"` + User *User `json:"user,omitempty"` + Reactions *Reactions `json:"reactions,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + // AuthorAssociation is the comment author's relationship to the pull request's repository. + // Possible values are "COLLABORATOR", "CONTRIBUTOR", "FIRST_TIMER", "FIRST_TIME_CONTRIBUTOR", "MEMBER", "OWNER", or "NONE". + AuthorAssociation *string `json:"author_association,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + PullRequestURL *string `json:"pull_request_url,omitempty"` +} + +func (p PullRequestComment) String() string { + return Stringify(p) +} + +// PullRequestListCommentsOptions specifies the optional parameters to the +// PullRequestsService.ListComments method. +type PullRequestListCommentsOptions struct { + // Sort specifies how to sort comments. Possible values are: created, updated. + Sort string `url:"sort,omitempty"` + + // Direction in which to sort comments. Possible values are: asc, desc. + Direction string `url:"direction,omitempty"` + + // Since filters comments by time. + Since time.Time `url:"since,omitempty"` + + ListOptions +} + +// ListComments lists all comments on the specified pull request. Specifying a +// pull request number of 0 will return all comments on all pull requests for +// the repository. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#list-comments-on-a-pull-request +func (s *PullRequestsService) ListComments(ctx context.Context, owner string, repo string, number int, opt *PullRequestListCommentsOptions) ([]*PullRequestComment, *Response, error) { + var u string + if number == 0 { + u = fmt.Sprintf("repos/%v/%v/pulls/comments", owner, repo) + } else { + u = fmt.Sprintf("repos/%v/%v/pulls/%d/comments", owner, repo, number) + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var comments []*PullRequestComment + resp, err := s.client.Do(ctx, req, &comments) + if err != nil { + return nil, resp, err + } + + return comments, resp, nil +} + +// GetComment fetches the specified pull request comment. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#get-a-single-comment +func (s *PullRequestsService) GetComment(ctx context.Context, owner string, repo string, commentID int64) (*PullRequestComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/comments/%d", owner, repo, commentID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + comment := new(PullRequestComment) + resp, err := s.client.Do(ctx, req, comment) + if err != nil { + return nil, resp, err + } + + return comment, resp, nil +} + +// CreateComment creates a new comment on the specified pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#create-a-comment +func (s *PullRequestsService) CreateComment(ctx context.Context, owner string, repo string, number int, comment *PullRequestComment) (*PullRequestComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/comments", owner, repo, number) + req, err := s.client.NewRequest("POST", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(PullRequestComment) + resp, err := s.client.Do(ctx, req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, nil +} + +// CreateCommentInReplyTo creates a new comment as a reply to an existing pull request comment. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#alternative-input +func (s *PullRequestsService) CreateCommentInReplyTo(ctx context.Context, owner string, repo string, number int, body string, commentID int64) (*PullRequestComment, *Response, error) { + comment := &struct { + Body string `json:"body,omitempty"` + InReplyTo int64 `json:"in_reply_to,omitempty"` + }{ + Body: body, + InReplyTo: commentID, + } + u := fmt.Sprintf("repos/%v/%v/pulls/%d/comments", owner, repo, number) + req, err := s.client.NewRequest("POST", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(PullRequestComment) + resp, err := s.client.Do(ctx, req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, nil +} + +// EditComment updates a pull request comment. +// A non-nil comment.Body must be provided. Other comment fields should be left nil. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#edit-a-comment +func (s *PullRequestsService) EditComment(ctx context.Context, owner string, repo string, commentID int64, comment *PullRequestComment) (*PullRequestComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/comments/%d", owner, repo, commentID) + req, err := s.client.NewRequest("PATCH", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(PullRequestComment) + resp, err := s.client.Do(ctx, req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, nil +} + +// DeleteComment deletes a pull request comment. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#delete-a-comment +func (s *PullRequestsService) DeleteComment(ctx context.Context, owner string, repo string, commentID int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/comments/%d", owner, repo, commentID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/pulls_comments_test.go b/vendor/github.com/google/go-github/github/pulls_comments_test.go new file mode 100644 index 0000000..76b78cf --- /dev/null +++ b/vendor/github.com/google/go-github/github/pulls_comments_test.go @@ -0,0 +1,203 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestPullRequestsService_ListComments_allPulls(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + testFormValues(t, r, values{ + "sort": "updated", + "direction": "desc", + "since": "2002-02-10T15:30:00Z", + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &PullRequestListCommentsOptions{ + Sort: "updated", + Direction: "desc", + Since: time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC), + ListOptions: ListOptions{Page: 2}, + } + pulls, _, err := client.PullRequests.ListComments(context.Background(), "o", "r", 0, opt) + if err != nil { + t.Errorf("PullRequests.ListComments returned error: %v", err) + } + + want := []*PullRequestComment{{ID: Int64(1)}} + if !reflect.DeepEqual(pulls, want) { + t.Errorf("PullRequests.ListComments returned %+v, want %+v", pulls, want) + } +} + +func TestPullRequestsService_ListComments_specificPull(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + fmt.Fprint(w, `[{"id":1, "pull_request_review_id":42}]`) + }) + + pulls, _, err := client.PullRequests.ListComments(context.Background(), "o", "r", 1, nil) + if err != nil { + t.Errorf("PullRequests.ListComments returned error: %v", err) + } + + want := []*PullRequestComment{{ID: Int64(1), PullRequestReviewID: Int64(42)}} + if !reflect.DeepEqual(pulls, want) { + t.Errorf("PullRequests.ListComments returned %+v, want %+v", pulls, want) + } +} + +func TestPullRequestsService_ListComments_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.ListComments(context.Background(), "%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_GetComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.PullRequests.GetComment(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("PullRequests.GetComment returned error: %v", err) + } + + want := &PullRequestComment{ID: Int64(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("PullRequests.GetComment returned %+v, want %+v", comment, want) + } +} + +func TestPullRequestsService_GetComment_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.GetComment(context.Background(), "%", "r", 1) + testURLParseError(t, err) +} + +func TestPullRequestsService_CreateComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &PullRequestComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/pulls/1/comments", func(w http.ResponseWriter, r *http.Request) { + v := new(PullRequestComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.PullRequests.CreateComment(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("PullRequests.CreateComment returned error: %v", err) + } + + want := &PullRequestComment{ID: Int64(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("PullRequests.CreateComment returned %+v, want %+v", comment, want) + } +} + +func TestPullRequestsService_CreateComment_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.CreateComment(context.Background(), "%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_EditComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &PullRequestComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/pulls/comments/1", func(w http.ResponseWriter, r *http.Request) { + v := new(PullRequestComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.PullRequests.EditComment(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("PullRequests.EditComment returned error: %v", err) + } + + want := &PullRequestComment{ID: Int64(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("PullRequests.EditComment returned %+v, want %+v", comment, want) + } +} + +func TestPullRequestsService_EditComment_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.EditComment(context.Background(), "%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_DeleteComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.PullRequests.DeleteComment(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("PullRequests.DeleteComment returned error: %v", err) + } +} + +func TestPullRequestsService_DeleteComment_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.PullRequests.DeleteComment(context.Background(), "%", "r", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/pulls_reviewers.go b/vendor/github.com/google/go-github/github/pulls_reviewers.go new file mode 100644 index 0000000..a1d7853 --- /dev/null +++ b/vendor/github.com/google/go-github/github/pulls_reviewers.go @@ -0,0 +1,79 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ReviewersRequest specifies users and teams for a pull request review request. +type ReviewersRequest struct { + Reviewers []string `json:"reviewers,omitempty"` + TeamReviewers []string `json:"team_reviewers,omitempty"` +} + +// Reviewers represents reviewers of a pull request. +type Reviewers struct { + Users []*User `json:"users,omitempty"` + Teams []*Team `json:"teams,omitempty"` +} + +// RequestReviewers creates a review request for the provided reviewers for the specified pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/review_requests/#create-a-review-request +func (s *PullRequestsService) RequestReviewers(ctx context.Context, owner, repo string, number int, reviewers ReviewersRequest) (*PullRequest, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/pulls/%d/requested_reviewers", owner, repo, number) + req, err := s.client.NewRequest("POST", u, &reviewers) + if err != nil { + return nil, nil, err + } + + r := new(PullRequest) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// ListReviewers lists reviewers whose reviews have been requested on the specified pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/review_requests/#list-review-requests +func (s *PullRequestsService) ListReviewers(ctx context.Context, owner, repo string, number int, opt *ListOptions) (*Reviewers, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/requested_reviewers", owner, repo, number) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + reviewers := new(Reviewers) + resp, err := s.client.Do(ctx, req, reviewers) + if err != nil { + return nil, resp, err + } + + return reviewers, resp, nil +} + +// RemoveReviewers removes the review request for the provided reviewers for the specified pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/review_requests/#delete-a-review-request +func (s *PullRequestsService) RemoveReviewers(ctx context.Context, owner, repo string, number int, reviewers ReviewersRequest) (*Response, error) { + u := fmt.Sprintf("repos/%s/%s/pulls/%d/requested_reviewers", owner, repo, number) + req, err := s.client.NewRequest("DELETE", u, &reviewers) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/pulls_reviewers_test.go b/vendor/github.com/google/go-github/github/pulls_reviewers_test.go new file mode 100644 index 0000000..7c550e1 --- /dev/null +++ b/vendor/github.com/google/go-github/github/pulls_reviewers_test.go @@ -0,0 +1,101 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRequestReviewers(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/requested_reviewers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testBody(t, r, `{"reviewers":["octocat","googlebot"],"team_reviewers":["justice-league","injustice-league"]}`+"\n") + fmt.Fprint(w, `{"number":1}`) + }) + + // This returns a PR, unmarshalling of which is tested elsewhere + pull, _, err := client.PullRequests.RequestReviewers(context.Background(), "o", "r", 1, ReviewersRequest{Reviewers: []string{"octocat", "googlebot"}, TeamReviewers: []string{"justice-league", "injustice-league"}}) + if err != nil { + t.Errorf("PullRequests.RequestReviewers returned error: %v", err) + } + want := &PullRequest{Number: Int(1)} + if !reflect.DeepEqual(pull, want) { + t.Errorf("PullRequests.RequestReviewers returned %+v, want %+v", pull, want) + } +} + +func TestRemoveReviewers(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/requested_reviewers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testBody(t, r, `{"reviewers":["octocat","googlebot"],"team_reviewers":["justice-league"]}`+"\n") + }) + + _, err := client.PullRequests.RemoveReviewers(context.Background(), "o", "r", 1, ReviewersRequest{Reviewers: []string{"octocat", "googlebot"}, TeamReviewers: []string{"justice-league"}}) + if err != nil { + t.Errorf("PullRequests.RemoveReviewers returned error: %v", err) + } +} + +func TestListReviewers(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/requested_reviewers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"users":[{"login":"octocat","id":1}],"teams":[{"id":1,"name":"Justice League"}]}`) + }) + + reviewers, _, err := client.PullRequests.ListReviewers(context.Background(), "o", "r", 1, nil) + if err != nil { + t.Errorf("PullRequests.ListReviewers returned error: %v", err) + } + + want := &Reviewers{ + Users: []*User{ + { + Login: String("octocat"), + ID: Int64(1), + }, + }, + Teams: []*Team{ + { + ID: Int64(1), + Name: String("Justice League"), + }, + }, + } + if !reflect.DeepEqual(reviewers, want) { + t.Errorf("PullRequests.ListReviewers returned %+v, want %+v", reviewers, want) + } +} + +func TestListReviewers_withOptions(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/requested_reviewers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `{}`) + }) + + _, _, err := client.PullRequests.ListReviewers(context.Background(), "o", "r", 1, &ListOptions{Page: 2}) + if err != nil { + t.Errorf("PullRequests.ListReviewers returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/pulls_reviews.go b/vendor/github.com/google/go-github/github/pulls_reviews.go new file mode 100644 index 0000000..57d3c63 --- /dev/null +++ b/vendor/github.com/google/go-github/github/pulls_reviews.go @@ -0,0 +1,236 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// PullRequestReview represents a review of a pull request. +type PullRequestReview struct { + ID *int64 `json:"id,omitempty"` + User *User `json:"user,omitempty"` + Body *string `json:"body,omitempty"` + SubmittedAt *time.Time `json:"submitted_at,omitempty"` + CommitID *string `json:"commit_id,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + PullRequestURL *string `json:"pull_request_url,omitempty"` + State *string `json:"state,omitempty"` +} + +func (p PullRequestReview) String() string { + return Stringify(p) +} + +// DraftReviewComment represents a comment part of the review. +type DraftReviewComment struct { + Path *string `json:"path,omitempty"` + Position *int `json:"position,omitempty"` + Body *string `json:"body,omitempty"` +} + +func (c DraftReviewComment) String() string { + return Stringify(c) +} + +// PullRequestReviewRequest represents a request to create a review. +type PullRequestReviewRequest struct { + CommitID *string `json:"commit_id,omitempty"` + Body *string `json:"body,omitempty"` + Event *string `json:"event,omitempty"` + Comments []*DraftReviewComment `json:"comments,omitempty"` +} + +func (r PullRequestReviewRequest) String() string { + return Stringify(r) +} + +// PullRequestReviewDismissalRequest represents a request to dismiss a review. +type PullRequestReviewDismissalRequest struct { + Message *string `json:"message,omitempty"` +} + +func (r PullRequestReviewDismissalRequest) String() string { + return Stringify(r) +} + +// ListReviews lists all reviews on the specified pull request. +// +// TODO: Follow up with GitHub support about an issue with this method's +// returned error format and remove this comment once it's fixed. +// Read more about it here - https://github.com/google/go-github/issues/540 +// +// GitHub API docs: https://developer.github.com/v3/pulls/reviews/#list-reviews-on-a-pull-request +func (s *PullRequestsService) ListReviews(ctx context.Context, owner, repo string, number int, opt *ListOptions) ([]*PullRequestReview, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews", owner, repo, number) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var reviews []*PullRequestReview + resp, err := s.client.Do(ctx, req, &reviews) + if err != nil { + return nil, resp, err + } + + return reviews, resp, nil +} + +// GetReview fetches the specified pull request review. +// +// TODO: Follow up with GitHub support about an issue with this method's +// returned error format and remove this comment once it's fixed. +// Read more about it here - https://github.com/google/go-github/issues/540 +// +// GitHub API docs: https://developer.github.com/v3/pulls/reviews/#get-a-single-review +func (s *PullRequestsService) GetReview(ctx context.Context, owner, repo string, number int, reviewID int64) (*PullRequestReview, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d", owner, repo, number, reviewID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + review := new(PullRequestReview) + resp, err := s.client.Do(ctx, req, review) + if err != nil { + return nil, resp, err + } + + return review, resp, nil +} + +// DeletePendingReview deletes the specified pull request pending review. +// +// TODO: Follow up with GitHub support about an issue with this method's +// returned error format and remove this comment once it's fixed. +// Read more about it here - https://github.com/google/go-github/issues/540 +// +// GitHub API docs: https://developer.github.com/v3/pulls/reviews/#delete-a-pending-review +func (s *PullRequestsService) DeletePendingReview(ctx context.Context, owner, repo string, number int, reviewID int64) (*PullRequestReview, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d", owner, repo, number, reviewID) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, nil, err + } + + review := new(PullRequestReview) + resp, err := s.client.Do(ctx, req, review) + if err != nil { + return nil, resp, err + } + + return review, resp, nil +} + +// ListReviewComments lists all the comments for the specified review. +// +// TODO: Follow up with GitHub support about an issue with this method's +// returned error format and remove this comment once it's fixed. +// Read more about it here - https://github.com/google/go-github/issues/540 +// +// GitHub API docs: https://developer.github.com/v3/pulls/reviews/#get-comments-for-a-single-review +func (s *PullRequestsService) ListReviewComments(ctx context.Context, owner, repo string, number int, reviewID int64, opt *ListOptions) ([]*PullRequestComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d/comments", owner, repo, number, reviewID) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var comments []*PullRequestComment + resp, err := s.client.Do(ctx, req, &comments) + if err != nil { + return nil, resp, err + } + + return comments, resp, nil +} + +// CreateReview creates a new review on the specified pull request. +// +// TODO: Follow up with GitHub support about an issue with this method's +// returned error format and remove this comment once it's fixed. +// Read more about it here - https://github.com/google/go-github/issues/540 +// +// GitHub API docs: https://developer.github.com/v3/pulls/reviews/#create-a-pull-request-review +func (s *PullRequestsService) CreateReview(ctx context.Context, owner, repo string, number int, review *PullRequestReviewRequest) (*PullRequestReview, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews", owner, repo, number) + + req, err := s.client.NewRequest("POST", u, review) + if err != nil { + return nil, nil, err + } + + r := new(PullRequestReview) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// SubmitReview submits a specified review on the specified pull request. +// +// TODO: Follow up with GitHub support about an issue with this method's +// returned error format and remove this comment once it's fixed. +// Read more about it here - https://github.com/google/go-github/issues/540 +// +// GitHub API docs: https://developer.github.com/v3/pulls/reviews/#submit-a-pull-request-review +func (s *PullRequestsService) SubmitReview(ctx context.Context, owner, repo string, number int, reviewID int64, review *PullRequestReviewRequest) (*PullRequestReview, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d/events", owner, repo, number, reviewID) + + req, err := s.client.NewRequest("POST", u, review) + if err != nil { + return nil, nil, err + } + + r := new(PullRequestReview) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// DismissReview dismisses a specified review on the specified pull request. +// +// TODO: Follow up with GitHub support about an issue with this method's +// returned error format and remove this comment once it's fixed. +// Read more about it here - https://github.com/google/go-github/issues/540 +// +// GitHub API docs: https://developer.github.com/v3/pulls/reviews/#dismiss-a-pull-request-review +func (s *PullRequestsService) DismissReview(ctx context.Context, owner, repo string, number int, reviewID int64, review *PullRequestReviewDismissalRequest) (*PullRequestReview, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d/dismissals", owner, repo, number, reviewID) + + req, err := s.client.NewRequest("PUT", u, review) + if err != nil { + return nil, nil, err + } + + r := new(PullRequestReview) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/pulls_reviews_test.go b/vendor/github.com/google/go-github/github/pulls_reviews_test.go new file mode 100644 index 0000000..58e301b --- /dev/null +++ b/vendor/github.com/google/go-github/github/pulls_reviews_test.go @@ -0,0 +1,273 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestPullRequestsService_ListReviews(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/reviews", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + reviews, _, err := client.PullRequests.ListReviews(context.Background(), "o", "r", 1, opt) + if err != nil { + t.Errorf("PullRequests.ListReviews returned error: %v", err) + } + + want := []*PullRequestReview{ + {ID: Int64(1)}, + {ID: Int64(2)}, + } + if !reflect.DeepEqual(reviews, want) { + t.Errorf("PullRequests.ListReviews returned %+v, want %+v", reviews, want) + } +} + +func TestPullRequestsService_ListReviews_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.ListReviews(context.Background(), "%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_GetReview(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/reviews/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + review, _, err := client.PullRequests.GetReview(context.Background(), "o", "r", 1, 1) + if err != nil { + t.Errorf("PullRequests.GetReview returned error: %v", err) + } + + want := &PullRequestReview{ID: Int64(1)} + if !reflect.DeepEqual(review, want) { + t.Errorf("PullRequests.GetReview returned %+v, want %+v", review, want) + } +} + +func TestPullRequestsService_GetReview_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.GetReview(context.Background(), "%", "r", 1, 1) + testURLParseError(t, err) +} + +func TestPullRequestsService_DeletePendingReview(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/reviews/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + fmt.Fprint(w, `{"id":1}`) + }) + + review, _, err := client.PullRequests.DeletePendingReview(context.Background(), "o", "r", 1, 1) + if err != nil { + t.Errorf("PullRequests.DeletePendingReview returned error: %v", err) + } + + want := &PullRequestReview{ID: Int64(1)} + if !reflect.DeepEqual(review, want) { + t.Errorf("PullRequests.DeletePendingReview returned %+v, want %+v", review, want) + } +} + +func TestPullRequestsService_DeletePendingReview_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.DeletePendingReview(context.Background(), "%", "r", 1, 1) + testURLParseError(t, err) +} + +func TestPullRequestsService_ListReviewComments(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/reviews/1/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + comments, _, err := client.PullRequests.ListReviewComments(context.Background(), "o", "r", 1, 1, nil) + if err != nil { + t.Errorf("PullRequests.ListReviewComments returned error: %v", err) + } + + want := []*PullRequestComment{ + {ID: Int64(1)}, + {ID: Int64(2)}, + } + if !reflect.DeepEqual(comments, want) { + t.Errorf("PullRequests.ListReviewComments returned %+v, want %+v", comments, want) + } +} + +func TestPullRequestsService_ListReviewComments_withOptions(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/reviews/1/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[]`) + }) + + _, _, err := client.PullRequests.ListReviewComments(context.Background(), "o", "r", 1, 1, &ListOptions{Page: 2}) + if err != nil { + t.Errorf("PullRequests.ListReviewComments returned error: %v", err) + } +} + +func TestPullRequestsService_ListReviewComments_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.ListReviewComments(context.Background(), "%", "r", 1, 1, nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_CreateReview(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &PullRequestReviewRequest{ + CommitID: String("commit_id"), + Body: String("b"), + Event: String("APPROVE"), + } + + mux.HandleFunc("/repos/o/r/pulls/1/reviews", func(w http.ResponseWriter, r *http.Request) { + v := new(PullRequestReviewRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + review, _, err := client.PullRequests.CreateReview(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("PullRequests.CreateReview returned error: %v", err) + } + + want := &PullRequestReview{ID: Int64(1)} + if !reflect.DeepEqual(review, want) { + t.Errorf("PullRequests.CreateReview returned %+v, want %+v", review, want) + } +} + +func TestPullRequestsService_CreateReview_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.CreateReview(context.Background(), "%", "r", 1, &PullRequestReviewRequest{}) + testURLParseError(t, err) +} + +func TestPullRequestsService_SubmitReview(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &PullRequestReviewRequest{ + Body: String("b"), + Event: String("APPROVE"), + } + + mux.HandleFunc("/repos/o/r/pulls/1/reviews/1/events", func(w http.ResponseWriter, r *http.Request) { + v := new(PullRequestReviewRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + review, _, err := client.PullRequests.SubmitReview(context.Background(), "o", "r", 1, 1, input) + if err != nil { + t.Errorf("PullRequests.SubmitReview returned error: %v", err) + } + + want := &PullRequestReview{ID: Int64(1)} + if !reflect.DeepEqual(review, want) { + t.Errorf("PullRequests.SubmitReview returned %+v, want %+v", review, want) + } +} + +func TestPullRequestsService_SubmitReview_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.SubmitReview(context.Background(), "%", "r", 1, 1, &PullRequestReviewRequest{}) + testURLParseError(t, err) +} + +func TestPullRequestsService_DismissReview(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &PullRequestReviewDismissalRequest{Message: String("m")} + + mux.HandleFunc("/repos/o/r/pulls/1/reviews/1/dismissals", func(w http.ResponseWriter, r *http.Request) { + v := new(PullRequestReviewDismissalRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + review, _, err := client.PullRequests.DismissReview(context.Background(), "o", "r", 1, 1, input) + if err != nil { + t.Errorf("PullRequests.DismissReview returned error: %v", err) + } + + want := &PullRequestReview{ID: Int64(1)} + if !reflect.DeepEqual(review, want) { + t.Errorf("PullRequests.DismissReview returned %+v, want %+v", review, want) + } +} + +func TestPullRequestsService_DismissReview_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.DismissReview(context.Background(), "%", "r", 1, 1, &PullRequestReviewDismissalRequest{}) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/pulls_test.go b/vendor/github.com/google/go-github/github/pulls_test.go new file mode 100644 index 0000000..3b2449f --- /dev/null +++ b/vendor/github.com/google/go-github/github/pulls_test.go @@ -0,0 +1,532 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "reflect" + "strings" + "testing" +) + +func TestPullRequestsService_List(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + acceptHeaders := []string{mediaTypeLabelDescriptionSearchPreview, mediaTypeLockReasonPreview} + mux.HandleFunc("/repos/o/r/pulls", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + testFormValues(t, r, values{ + "state": "closed", + "head": "h", + "base": "b", + "sort": "created", + "direction": "desc", + "page": "2", + }) + fmt.Fprint(w, `[{"number":1}]`) + }) + + opt := &PullRequestListOptions{"closed", "h", "b", "created", "desc", ListOptions{Page: 2}} + pulls, _, err := client.PullRequests.List(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("PullRequests.List returned error: %v", err) + } + + want := []*PullRequest{{Number: Int(1)}} + if !reflect.DeepEqual(pulls, want) { + t.Errorf("PullRequests.List returned %+v, want %+v", pulls, want) + } +} + +func TestPullRequestsService_List_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.List(context.Background(), "%", "r", nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_Get(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + acceptHeaders := []string{mediaTypeLabelDescriptionSearchPreview, mediaTypeLockReasonPreview} + mux.HandleFunc("/repos/o/r/pulls/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + fmt.Fprint(w, `{"number":1}`) + }) + + pull, _, err := client.PullRequests.Get(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("PullRequests.Get returned error: %v", err) + } + + want := &PullRequest{Number: Int(1)} + if !reflect.DeepEqual(pull, want) { + t.Errorf("PullRequests.Get returned %+v, want %+v", pull, want) + } +} + +func TestPullRequestsService_GetRaw_diff(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + const rawStr = "@@diff content" + + mux.HandleFunc("/repos/o/r/pulls/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeV3Diff) + fmt.Fprint(w, rawStr) + }) + + got, _, err := client.PullRequests.GetRaw(context.Background(), "o", "r", 1, RawOptions{Diff}) + if err != nil { + t.Fatalf("PullRequests.GetRaw returned error: %v", err) + } + want := rawStr + if got != want { + t.Errorf("PullRequests.GetRaw returned %s want %s", got, want) + } +} + +func TestPullRequestsService_GetRaw_patch(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + const rawStr = "@@patch content" + + mux.HandleFunc("/repos/o/r/pulls/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeV3Patch) + fmt.Fprint(w, rawStr) + }) + + got, _, err := client.PullRequests.GetRaw(context.Background(), "o", "r", 1, RawOptions{Patch}) + if err != nil { + t.Fatalf("PullRequests.GetRaw returned error: %v", err) + } + want := rawStr + if got != want { + t.Errorf("PullRequests.GetRaw returned %s want %s", got, want) + } +} + +func TestPullRequestsService_GetRaw_invalid(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.GetRaw(context.Background(), "o", "r", 1, RawOptions{100}) + if err == nil { + t.Fatal("PullRequests.GetRaw should return error") + } + if !strings.Contains(err.Error(), "unsupported raw type") { + t.Error("PullRequests.GetRaw should return unsupported raw type error") + } +} + +func TestPullRequestsService_Get_headAndBase(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"number":1,"head":{"ref":"r2","repo":{"id":2}},"base":{"ref":"r1","repo":{"id":1}}}`) + }) + + pull, _, err := client.PullRequests.Get(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("PullRequests.Get returned error: %v", err) + } + + want := &PullRequest{ + Number: Int(1), + Head: &PullRequestBranch{ + Ref: String("r2"), + Repo: &Repository{ID: Int64(2)}, + }, + Base: &PullRequestBranch{ + Ref: String("r1"), + Repo: &Repository{ID: Int64(1)}, + }, + } + if !reflect.DeepEqual(pull, want) { + t.Errorf("PullRequests.Get returned %+v, want %+v", pull, want) + } +} + +func TestPullRequestsService_Get_urlFields(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"number":1, + "url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347", + "html_url": "https://github.com/octocat/Hello-World/pull/1347", + "issue_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347", + "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff", + "patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch", + "review_comments_url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/comments", + "review_comment_url": "https://api.github.com/repos/octocat/Hello-World/pulls/comments{/number}"}`) + }) + + pull, _, err := client.PullRequests.Get(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("PullRequests.Get returned error: %v", err) + } + + want := &PullRequest{ + Number: Int(1), + URL: String("https://api.github.com/repos/octocat/Hello-World/pulls/1347"), + HTMLURL: String("https://github.com/octocat/Hello-World/pull/1347"), + IssueURL: String("https://api.github.com/repos/octocat/Hello-World/issues/1347"), + StatusesURL: String("https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e"), + DiffURL: String("https://github.com/octocat/Hello-World/pull/1347.diff"), + PatchURL: String("https://github.com/octocat/Hello-World/pull/1347.patch"), + ReviewCommentsURL: String("https://api.github.com/repos/octocat/Hello-World/pulls/1347/comments"), + ReviewCommentURL: String("https://api.github.com/repos/octocat/Hello-World/pulls/comments{/number}"), + } + + if !reflect.DeepEqual(pull, want) { + t.Errorf("PullRequests.Get returned %+v, want %+v", pull, want) + } +} + +func TestPullRequestsService_Get_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.Get(context.Background(), "%", "r", 1) + testURLParseError(t, err) +} + +func TestPullRequestsService_Create(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &NewPullRequest{Title: String("t")} + + mux.HandleFunc("/repos/o/r/pulls", func(w http.ResponseWriter, r *http.Request) { + v := new(NewPullRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeLabelDescriptionSearchPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"number":1}`) + }) + + pull, _, err := client.PullRequests.Create(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("PullRequests.Create returned error: %v", err) + } + + want := &PullRequest{Number: Int(1)} + if !reflect.DeepEqual(pull, want) { + t.Errorf("PullRequests.Create returned %+v, want %+v", pull, want) + } +} + +func TestPullRequestsService_Create_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.Create(context.Background(), "%", "r", nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_Edit(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + tests := []struct { + input *PullRequest + sendResponse string + + wantUpdate string + want *PullRequest + }{ + { + input: &PullRequest{Title: String("t")}, + sendResponse: `{"number":1}`, + wantUpdate: `{"title":"t"}`, + want: &PullRequest{Number: Int(1)}, + }, + { + // base update + input: &PullRequest{Base: &PullRequestBranch{Ref: String("master")}}, + sendResponse: `{"number":1,"base":{"ref":"master"}}`, + wantUpdate: `{"base":"master"}`, + want: &PullRequest{ + Number: Int(1), + Base: &PullRequestBranch{Ref: String("master")}, + }, + }, + } + + for i, tt := range tests { + madeRequest := false + acceptHeaders := []string{mediaTypeLabelDescriptionSearchPreview, mediaTypeLockReasonPreview} + mux.HandleFunc(fmt.Sprintf("/repos/o/r/pulls/%v", i), func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + testBody(t, r, tt.wantUpdate+"\n") + io.WriteString(w, tt.sendResponse) + madeRequest = true + }) + + pull, _, err := client.PullRequests.Edit(context.Background(), "o", "r", i, tt.input) + if err != nil { + t.Errorf("%d: PullRequests.Edit returned error: %v", i, err) + } + + if !reflect.DeepEqual(pull, tt.want) { + t.Errorf("%d: PullRequests.Edit returned %+v, want %+v", i, pull, tt.want) + } + + if !madeRequest { + t.Errorf("%d: PullRequest.Edit did not make the expected request", i) + } + } +} + +func TestPullRequestsService_Edit_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.PullRequests.Edit(context.Background(), "%", "r", 1, &PullRequest{}) + testURLParseError(t, err) +} + +func TestPullRequestsService_ListCommits(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/commits", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeGitSigningPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, ` + [ + { + "sha": "3", + "parents": [ + { + "sha": "2" + } + ] + }, + { + "sha": "2", + "parents": [ + { + "sha": "1" + } + ] + } + ]`) + }) + + opt := &ListOptions{Page: 2} + commits, _, err := client.PullRequests.ListCommits(context.Background(), "o", "r", 1, opt) + if err != nil { + t.Errorf("PullRequests.ListCommits returned error: %v", err) + } + + want := []*RepositoryCommit{ + { + SHA: String("3"), + Parents: []Commit{ + { + SHA: String("2"), + }, + }, + }, + { + SHA: String("2"), + Parents: []Commit{ + { + SHA: String("1"), + }, + }, + }, + } + if !reflect.DeepEqual(commits, want) { + t.Errorf("PullRequests.ListCommits returned %+v, want %+v", commits, want) + } +} + +func TestPullRequestsService_ListFiles(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/files", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, ` + [ + { + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "filename": "file1.txt", + "status": "added", + "additions": 103, + "deletions": 21, + "changes": 124, + "patch": "@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test" + }, + { + "sha": "f61aebed695e2e4193db5e6dcb09b5b57875f334", + "filename": "file2.txt", + "status": "modified", + "additions": 5, + "deletions": 3, + "changes": 103, + "patch": "@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test" + } + ]`) + }) + + opt := &ListOptions{Page: 2} + commitFiles, _, err := client.PullRequests.ListFiles(context.Background(), "o", "r", 1, opt) + if err != nil { + t.Errorf("PullRequests.ListFiles returned error: %v", err) + } + + want := []*CommitFile{ + { + SHA: String("6dcb09b5b57875f334f61aebed695e2e4193db5e"), + Filename: String("file1.txt"), + Additions: Int(103), + Deletions: Int(21), + Changes: Int(124), + Status: String("added"), + Patch: String("@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test"), + }, + { + SHA: String("f61aebed695e2e4193db5e6dcb09b5b57875f334"), + Filename: String("file2.txt"), + Additions: Int(5), + Deletions: Int(3), + Changes: Int(103), + Status: String("modified"), + Patch: String("@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test"), + }, + } + + if !reflect.DeepEqual(commitFiles, want) { + t.Errorf("PullRequests.ListFiles returned %+v, want %+v", commitFiles, want) + } +} + +func TestPullRequestsService_IsMerged(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/merge", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + isMerged, _, err := client.PullRequests.IsMerged(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("PullRequests.IsMerged returned error: %v", err) + } + + want := true + if !reflect.DeepEqual(isMerged, want) { + t.Errorf("PullRequests.IsMerged returned %+v, want %+v", isMerged, want) + } +} + +func TestPullRequestsService_Merge(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/merge", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + fmt.Fprint(w, ` + { + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "merged": true, + "message": "Pull Request successfully merged" + }`) + }) + + options := &PullRequestOptions{MergeMethod: "rebase"} + merge, _, err := client.PullRequests.Merge(context.Background(), "o", "r", 1, "merging pull request", options) + if err != nil { + t.Errorf("PullRequests.Merge returned error: %v", err) + } + + want := &PullRequestMergeResult{ + SHA: String("6dcb09b5b57875f334f61aebed695e2e4193db5e"), + Merged: Bool(true), + Message: String("Pull Request successfully merged"), + } + if !reflect.DeepEqual(merge, want) { + t.Errorf("PullRequests.Merge returned %+v, want %+v", merge, want) + } +} + +// Test that different merge options produce expected PUT requests. See issue https://github.com/google/go-github/issues/500. +func TestPullRequestsService_Merge_options(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + tests := []struct { + options *PullRequestOptions + wantBody string + }{ + { + options: nil, + wantBody: `{"commit_message":"merging pull request"}`, + }, + { + options: &PullRequestOptions{}, + wantBody: `{"commit_message":"merging pull request"}`, + }, + { + options: &PullRequestOptions{MergeMethod: "rebase"}, + wantBody: `{"commit_message":"merging pull request","merge_method":"rebase"}`, + }, + { + options: &PullRequestOptions{SHA: "6dcb09b5b57875f334f61aebed695e2e4193db5e"}, + wantBody: `{"commit_message":"merging pull request","sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}`, + }, + { + options: &PullRequestOptions{ + CommitTitle: "Extra detail", + SHA: "6dcb09b5b57875f334f61aebed695e2e4193db5e", + MergeMethod: "squash", + }, + wantBody: `{"commit_message":"merging pull request","commit_title":"Extra detail","merge_method":"squash","sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}`, + }, + } + + for i, test := range tests { + madeRequest := false + mux.HandleFunc(fmt.Sprintf("/repos/o/r/pulls/%d/merge", i), func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testBody(t, r, test.wantBody+"\n") + madeRequest = true + }) + _, _, _ = client.PullRequests.Merge(context.Background(), "o", "r", i, "merging pull request", test.options) + if !madeRequest { + t.Errorf("%d: PullRequests.Merge(%#v): expected request was not made", i, test.options) + } + } +} diff --git a/vendor/github.com/google/go-github/github/reactions.go b/vendor/github.com/google/go-github/github/reactions.go new file mode 100644 index 0000000..ddc055c --- /dev/null +++ b/vendor/github.com/google/go-github/github/reactions.go @@ -0,0 +1,375 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ReactionsService provides access to the reactions-related functions in the +// GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/reactions/ +type ReactionsService service + +// Reaction represents a GitHub reaction. +type Reaction struct { + // ID is the Reaction ID. + ID *int64 `json:"id,omitempty"` + User *User `json:"user,omitempty"` + NodeID *string `json:"node_id,omitempty"` + // Content is the type of reaction. + // Possible values are: + // "+1", "-1", "laugh", "confused", "heart", "hooray". + Content *string `json:"content,omitempty"` +} + +// Reactions represents a summary of GitHub reactions. +type Reactions struct { + TotalCount *int `json:"total_count,omitempty"` + PlusOne *int `json:"+1,omitempty"` + MinusOne *int `json:"-1,omitempty"` + Laugh *int `json:"laugh,omitempty"` + Confused *int `json:"confused,omitempty"` + Heart *int `json:"heart,omitempty"` + Hooray *int `json:"hooray,omitempty"` + URL *string `json:"url,omitempty"` +} + +func (r Reaction) String() string { + return Stringify(r) +} + +// ListCommentReactions lists the reactions for a commit comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-a-commit-comment +func (s *ReactionsService) ListCommentReactions(ctx context.Context, owner, repo string, id int64, opt *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v/reactions", owner, repo, id) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// CreateCommentReaction creates a reaction for a commit comment. +// Note that if you have already created a reaction of type content, the +// previously created reaction will be returned with Status: 200 OK. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-a-commit-comment +func (s ReactionsService) CreateCommentReaction(ctx context.Context, owner, repo string, id int64, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v/reactions", owner, repo, id) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// ListIssueReactions lists the reactions for an issue. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-an-issue +func (s *ReactionsService) ListIssueReactions(ctx context.Context, owner, repo string, number int, opt *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/reactions", owner, repo, number) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// CreateIssueReaction creates a reaction for an issue. +// Note that if you have already created a reaction of type content, the +// previously created reaction will be returned with Status: 200 OK. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-an-issue +func (s ReactionsService) CreateIssueReaction(ctx context.Context, owner, repo string, number int, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/reactions", owner, repo, number) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// ListIssueCommentReactions lists the reactions for an issue comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-an-issue-comment +func (s *ReactionsService) ListIssueCommentReactions(ctx context.Context, owner, repo string, id int64, opt *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/comments/%v/reactions", owner, repo, id) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// CreateIssueCommentReaction creates a reaction for an issue comment. +// Note that if you have already created a reaction of type content, the +// previously created reaction will be returned with Status: 200 OK. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-an-issue-comment +func (s ReactionsService) CreateIssueCommentReaction(ctx context.Context, owner, repo string, id int64, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/comments/%v/reactions", owner, repo, id) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// ListPullRequestCommentReactions lists the reactions for a pull request review comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-an-issue-comment +func (s *ReactionsService) ListPullRequestCommentReactions(ctx context.Context, owner, repo string, id int64, opt *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/comments/%v/reactions", owner, repo, id) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// CreatePullRequestCommentReaction creates a reaction for a pull request review comment. +// Note that if you have already created a reaction of type content, the +// previously created reaction will be returned with Status: 200 OK. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-an-issue-comment +func (s ReactionsService) CreatePullRequestCommentReaction(ctx context.Context, owner, repo string, id int64, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/comments/%v/reactions", owner, repo, id) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// ListTeamDiscussionReactions lists the reactions for a team discussion. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-a-team-discussion +func (s *ReactionsService) ListTeamDiscussionReactions(ctx context.Context, teamID int64, discussionNumber int, opt *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/reactions", teamID, discussionNumber) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// CreateTeamDiscussionReaction creates a reaction for a team discussion. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-a-team-discussion +func (s *ReactionsService) CreateTeamDiscussionReaction(ctx context.Context, teamID int64, discussionNumber int, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/reactions", teamID, discussionNumber) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// ListTeamDiscussionCommentReactions lists the reactions for a team discussion comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-a-team-discussion-comment +func (s *ReactionsService) ListTeamDiscussionCommentReactions(ctx context.Context, teamID int64, discussionNumber, commentNumber int, opt *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v/reactions", teamID, discussionNumber, commentNumber) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + + return m, resp, nil +} + +// CreateTeamDiscussionCommentReaction creates a reaction for a team discussion comment. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-a-team-discussion-comment +func (s *ReactionsService) CreateTeamDiscussionCommentReaction(ctx context.Context, teamID int64, discussionNumber, commentNumber int, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v/reactions", teamID, discussionNumber, commentNumber) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// DeleteReaction deletes a reaction. +// +// GitHub API docs: https://developer.github.com/v3/reaction/reactions/#delete-a-reaction-archive +func (s *ReactionsService) DeleteReaction(ctx context.Context, id int64) (*Response, error) { + u := fmt.Sprintf("reactions/%v", id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/reactions_test.go b/vendor/github.com/google/go-github/github/reactions_test.go new file mode 100644 index 0000000..e1baec3 --- /dev/null +++ b/vendor/github.com/google/go-github/github/reactions_test.go @@ -0,0 +1,293 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "net/http" + "reflect" + "testing" +) + +func TestReactionsService_ListCommentReactions(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/comments/1/reactions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + + w.WriteHeader(http.StatusOK) + w.Write([]byte(`[{"id":1,"user":{"login":"l","id":2},"content":"+1"}]`)) + }) + + got, _, err := client.Reactions.ListCommentReactions(context.Background(), "o", "r", 1, nil) + if err != nil { + t.Errorf("ListCommentReactions returned error: %v", err) + } + want := []*Reaction{{ID: Int64(1), User: &User{Login: String("l"), ID: Int64(2)}, Content: String("+1")}} + if !reflect.DeepEqual(got, want) { + t.Errorf("ListCommentReactions = %+v, want %+v", got, want) + } +} + +func TestReactionsService_CreateCommentReaction(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/comments/1/reactions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + + w.WriteHeader(http.StatusCreated) + w.Write([]byte(`{"id":1,"user":{"login":"l","id":2},"content":"+1"}`)) + }) + + got, _, err := client.Reactions.CreateCommentReaction(context.Background(), "o", "r", 1, "+1") + if err != nil { + t.Errorf("CreateCommentReaction returned error: %v", err) + } + want := &Reaction{ID: Int64(1), User: &User{Login: String("l"), ID: Int64(2)}, Content: String("+1")} + if !reflect.DeepEqual(got, want) { + t.Errorf("CreateCommentReaction = %+v, want %+v", got, want) + } +} + +func TestReactionsService_ListIssueReactions(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/reactions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + + w.WriteHeader(http.StatusOK) + w.Write([]byte(`[{"id":1,"user":{"login":"l","id":2},"content":"+1"}]`)) + }) + + got, _, err := client.Reactions.ListIssueReactions(context.Background(), "o", "r", 1, nil) + if err != nil { + t.Errorf("ListIssueReactions returned error: %v", err) + } + want := []*Reaction{{ID: Int64(1), User: &User{Login: String("l"), ID: Int64(2)}, Content: String("+1")}} + if !reflect.DeepEqual(got, want) { + t.Errorf("ListIssueReactions = %+v, want %+v", got, want) + } +} + +func TestReactionsService_CreateIssueReaction(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/reactions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + + w.WriteHeader(http.StatusCreated) + w.Write([]byte(`{"id":1,"user":{"login":"l","id":2},"content":"+1"}`)) + }) + + got, _, err := client.Reactions.CreateIssueReaction(context.Background(), "o", "r", 1, "+1") + if err != nil { + t.Errorf("CreateIssueReaction returned error: %v", err) + } + want := &Reaction{ID: Int64(1), User: &User{Login: String("l"), ID: Int64(2)}, Content: String("+1")} + if !reflect.DeepEqual(got, want) { + t.Errorf("CreateIssueReaction = %+v, want %+v", got, want) + } +} + +func TestReactionsService_ListIssueCommentReactions(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/comments/1/reactions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + + w.WriteHeader(http.StatusOK) + w.Write([]byte(`[{"id":1,"user":{"login":"l","id":2},"content":"+1"}]`)) + }) + + got, _, err := client.Reactions.ListIssueCommentReactions(context.Background(), "o", "r", 1, nil) + if err != nil { + t.Errorf("ListIssueCommentReactions returned error: %v", err) + } + want := []*Reaction{{ID: Int64(1), User: &User{Login: String("l"), ID: Int64(2)}, Content: String("+1")}} + if !reflect.DeepEqual(got, want) { + t.Errorf("ListIssueCommentReactions = %+v, want %+v", got, want) + } +} + +func TestReactionsService_CreateIssueCommentReaction(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/comments/1/reactions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + + w.WriteHeader(http.StatusCreated) + w.Write([]byte(`{"id":1,"user":{"login":"l","id":2},"content":"+1"}`)) + }) + + got, _, err := client.Reactions.CreateIssueCommentReaction(context.Background(), "o", "r", 1, "+1") + if err != nil { + t.Errorf("CreateIssueCommentReaction returned error: %v", err) + } + want := &Reaction{ID: Int64(1), User: &User{Login: String("l"), ID: Int64(2)}, Content: String("+1")} + if !reflect.DeepEqual(got, want) { + t.Errorf("CreateIssueCommentReaction = %+v, want %+v", got, want) + } +} + +func TestReactionsService_ListPullRequestCommentReactions(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/comments/1/reactions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + + w.WriteHeader(http.StatusOK) + w.Write([]byte(`[{"id":1,"user":{"login":"l","id":2},"content":"+1"}]`)) + }) + + got, _, err := client.Reactions.ListPullRequestCommentReactions(context.Background(), "o", "r", 1, nil) + if err != nil { + t.Errorf("ListPullRequestCommentReactions returned error: %v", err) + } + want := []*Reaction{{ID: Int64(1), User: &User{Login: String("l"), ID: Int64(2)}, Content: String("+1")}} + if !reflect.DeepEqual(got, want) { + t.Errorf("ListPullRequestCommentReactions = %+v, want %+v", got, want) + } +} + +func TestReactionsService_CreatePullRequestCommentReaction(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/comments/1/reactions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + + w.WriteHeader(http.StatusCreated) + w.Write([]byte(`{"id":1,"user":{"login":"l","id":2},"content":"+1"}`)) + }) + + got, _, err := client.Reactions.CreatePullRequestCommentReaction(context.Background(), "o", "r", 1, "+1") + if err != nil { + t.Errorf("CreatePullRequestCommentReaction returned error: %v", err) + } + want := &Reaction{ID: Int64(1), User: &User{Login: String("l"), ID: Int64(2)}, Content: String("+1")} + if !reflect.DeepEqual(got, want) { + t.Errorf("CreatePullRequestCommentReaction = %+v, want %+v", got, want) + } +} + +func TestReactionsService_ListTeamDiscussionReactions(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/discussions/2/reactions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + + w.WriteHeader(http.StatusOK) + w.Write([]byte(`[{"id":1,"user":{"login":"l","id":2},"content":"+1"}]`)) + }) + + got, _, err := client.Reactions.ListTeamDiscussionReactions(context.Background(), 1, 2, nil) + if err != nil { + t.Errorf("ListTeamDiscussionReactions returned error: %v", err) + } + want := []*Reaction{{ID: Int64(1), User: &User{Login: String("l"), ID: Int64(2)}, Content: String("+1")}} + if !reflect.DeepEqual(got, want) { + t.Errorf("ListTeamDiscussionReactions = %+v, want %+v", got, want) + } +} + +func TestReactionsService_CreateTeamDiscussionReaction(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/discussions/2/reactions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + + w.WriteHeader(http.StatusCreated) + w.Write([]byte(`{"id":1,"user":{"login":"l","id":2},"content":"+1"}`)) + }) + + got, _, err := client.Reactions.CreateTeamDiscussionReaction(context.Background(), 1, 2, "+1") + if err != nil { + t.Errorf("CreateTeamDiscussionReaction returned error: %v", err) + } + want := &Reaction{ID: Int64(1), User: &User{Login: String("l"), ID: Int64(2)}, Content: String("+1")} + if !reflect.DeepEqual(got, want) { + t.Errorf("CreateTeamDiscussionReaction = %+v, want %+v", got, want) + } +} + +func TestReactionService_ListTeamDiscussionCommentReactions(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/discussions/2/comments/3/reactions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + + w.WriteHeader(http.StatusOK) + w.Write([]byte(`[{"id":1,"user":{"login":"l","id":2},"content":"+1"}]`)) + }) + + got, _, err := client.Reactions.ListTeamDiscussionCommentReactions(context.Background(), 1, 2, 3, nil) + if err != nil { + t.Errorf("ListTeamDiscussionCommentReactions returned error: %v", err) + } + want := []*Reaction{{ID: Int64(1), User: &User{Login: String("l"), ID: Int64(2)}, Content: String("+1")}} + if !reflect.DeepEqual(got, want) { + t.Errorf("ListTeamDiscussionCommentReactions = %+v, want %+v", got, want) + } +} + +func TestReactionService_CreateTeamDiscussionCommentReaction(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/discussions/2/comments/3/reactions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + + w.WriteHeader(http.StatusCreated) + w.Write([]byte(`{"id":1,"user":{"login":"l","id":2},"content":"+1"}`)) + }) + + got, _, err := client.Reactions.CreateTeamDiscussionCommentReaction(context.Background(), 1, 2, 3, "+1") + if err != nil { + t.Errorf("CreateTeamDiscussionCommentReaction returned error: %v", err) + } + want := &Reaction{ID: Int64(1), User: &User{Login: String("l"), ID: Int64(2)}, Content: String("+1")} + if !reflect.DeepEqual(got, want) { + t.Errorf("CreateTeamDiscussionCommentReaction = %+v, want %+v", got, want) + } +} + +func TestReactionsService_DeleteReaction(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/reactions/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + + w.WriteHeader(http.StatusNoContent) + }) + + if _, err := client.Reactions.DeleteReaction(context.Background(), 1); err != nil { + t.Errorf("DeleteReaction returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/repos.go b/vendor/github.com/google/go-github/github/repos.go new file mode 100644 index 0000000..fe05272 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos.go @@ -0,0 +1,1082 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "strings" +) + +// RepositoriesService handles communication with the repository related +// methods of the GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/repos/ +type RepositoriesService service + +// Repository represents a GitHub repository. +type Repository struct { + ID *int64 `json:"id,omitempty"` + NodeID *string `json:"node_id,omitempty"` + Owner *User `json:"owner,omitempty"` + Name *string `json:"name,omitempty"` + FullName *string `json:"full_name,omitempty"` + Description *string `json:"description,omitempty"` + Homepage *string `json:"homepage,omitempty"` + CodeOfConduct *CodeOfConduct `json:"code_of_conduct,omitempty"` + DefaultBranch *string `json:"default_branch,omitempty"` + MasterBranch *string `json:"master_branch,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + PushedAt *Timestamp `json:"pushed_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + CloneURL *string `json:"clone_url,omitempty"` + GitURL *string `json:"git_url,omitempty"` + MirrorURL *string `json:"mirror_url,omitempty"` + SSHURL *string `json:"ssh_url,omitempty"` + SVNURL *string `json:"svn_url,omitempty"` + Language *string `json:"language,omitempty"` + Fork *bool `json:"fork,omitempty"` + ForksCount *int `json:"forks_count,omitempty"` + NetworkCount *int `json:"network_count,omitempty"` + OpenIssuesCount *int `json:"open_issues_count,omitempty"` + StargazersCount *int `json:"stargazers_count,omitempty"` + SubscribersCount *int `json:"subscribers_count,omitempty"` + WatchersCount *int `json:"watchers_count,omitempty"` + Size *int `json:"size,omitempty"` + AutoInit *bool `json:"auto_init,omitempty"` + Parent *Repository `json:"parent,omitempty"` + Source *Repository `json:"source,omitempty"` + Organization *Organization `json:"organization,omitempty"` + Permissions *map[string]bool `json:"permissions,omitempty"` + AllowRebaseMerge *bool `json:"allow_rebase_merge,omitempty"` + AllowSquashMerge *bool `json:"allow_squash_merge,omitempty"` + AllowMergeCommit *bool `json:"allow_merge_commit,omitempty"` + Topics []string `json:"topics,omitempty"` + + // Only provided when using RepositoriesService.Get while in preview + License *License `json:"license,omitempty"` + + // Additional mutable fields when creating and editing a repository + Private *bool `json:"private,omitempty"` + HasIssues *bool `json:"has_issues,omitempty"` + HasWiki *bool `json:"has_wiki,omitempty"` + HasPages *bool `json:"has_pages,omitempty"` + HasProjects *bool `json:"has_projects,omitempty"` + HasDownloads *bool `json:"has_downloads,omitempty"` + LicenseTemplate *string `json:"license_template,omitempty"` + GitignoreTemplate *string `json:"gitignore_template,omitempty"` + Archived *bool `json:"archived,omitempty"` + + // Creating an organization repository. Required for non-owners. + TeamID *int64 `json:"team_id,omitempty"` + + // API URLs + URL *string `json:"url,omitempty"` + ArchiveURL *string `json:"archive_url,omitempty"` + AssigneesURL *string `json:"assignees_url,omitempty"` + BlobsURL *string `json:"blobs_url,omitempty"` + BranchesURL *string `json:"branches_url,omitempty"` + CollaboratorsURL *string `json:"collaborators_url,omitempty"` + CommentsURL *string `json:"comments_url,omitempty"` + CommitsURL *string `json:"commits_url,omitempty"` + CompareURL *string `json:"compare_url,omitempty"` + ContentsURL *string `json:"contents_url,omitempty"` + ContributorsURL *string `json:"contributors_url,omitempty"` + DeploymentsURL *string `json:"deployments_url,omitempty"` + DownloadsURL *string `json:"downloads_url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + ForksURL *string `json:"forks_url,omitempty"` + GitCommitsURL *string `json:"git_commits_url,omitempty"` + GitRefsURL *string `json:"git_refs_url,omitempty"` + GitTagsURL *string `json:"git_tags_url,omitempty"` + HooksURL *string `json:"hooks_url,omitempty"` + IssueCommentURL *string `json:"issue_comment_url,omitempty"` + IssueEventsURL *string `json:"issue_events_url,omitempty"` + IssuesURL *string `json:"issues_url,omitempty"` + KeysURL *string `json:"keys_url,omitempty"` + LabelsURL *string `json:"labels_url,omitempty"` + LanguagesURL *string `json:"languages_url,omitempty"` + MergesURL *string `json:"merges_url,omitempty"` + MilestonesURL *string `json:"milestones_url,omitempty"` + NotificationsURL *string `json:"notifications_url,omitempty"` + PullsURL *string `json:"pulls_url,omitempty"` + ReleasesURL *string `json:"releases_url,omitempty"` + StargazersURL *string `json:"stargazers_url,omitempty"` + StatusesURL *string `json:"statuses_url,omitempty"` + SubscribersURL *string `json:"subscribers_url,omitempty"` + SubscriptionURL *string `json:"subscription_url,omitempty"` + TagsURL *string `json:"tags_url,omitempty"` + TreesURL *string `json:"trees_url,omitempty"` + TeamsURL *string `json:"teams_url,omitempty"` + + // TextMatches is only populated from search results that request text matches + // See: search.go and https://developer.github.com/v3/search/#text-match-metadata + TextMatches []TextMatch `json:"text_matches,omitempty"` +} + +func (r Repository) String() string { + return Stringify(r) +} + +// RepositoryListOptions specifies the optional parameters to the +// RepositoriesService.List method. +type RepositoryListOptions struct { + // Visibility of repositories to list. Can be one of all, public, or private. + // Default: all + Visibility string `url:"visibility,omitempty"` + + // List repos of given affiliation[s]. + // Comma-separated list of values. Can include: + // * owner: Repositories that are owned by the authenticated user. + // * collaborator: Repositories that the user has been added to as a + // collaborator. + // * organization_member: Repositories that the user has access to through + // being a member of an organization. This includes every repository on + // every team that the user is on. + // Default: owner,collaborator,organization_member + Affiliation string `url:"affiliation,omitempty"` + + // Type of repositories to list. + // Can be one of all, owner, public, private, member. Default: all + // Will cause a 422 error if used in the same request as visibility or + // affiliation. + Type string `url:"type,omitempty"` + + // How to sort the repository list. Can be one of created, updated, pushed, + // full_name. Default: full_name + Sort string `url:"sort,omitempty"` + + // Direction in which to sort repositories. Can be one of asc or desc. + // Default: when using full_name: asc; otherwise desc + Direction string `url:"direction,omitempty"` + + ListOptions +} + +// List the repositories for a user. Passing the empty string will list +// repositories for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/repos/#list-user-repositories +func (s *RepositoriesService) List(ctx context.Context, user string, opt *RepositoryListOptions) ([]*Repository, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/repos", user) + } else { + u = "user/repos" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + acceptHeaders := []string{mediaTypeCodesOfConductPreview, mediaTypeTopicsPreview} + req.Header.Set("Accept", strings.Join(acceptHeaders, ", ")) + + var repos []*Repository + resp, err := s.client.Do(ctx, req, &repos) + if err != nil { + return nil, resp, err + } + + return repos, resp, nil +} + +// RepositoryListByOrgOptions specifies the optional parameters to the +// RepositoriesService.ListByOrg method. +type RepositoryListByOrgOptions struct { + // Type of repositories to list. Possible values are: all, public, private, + // forks, sources, member. Default is "all". + Type string `url:"type,omitempty"` + + ListOptions +} + +// ListByOrg lists the repositories for an organization. +// +// GitHub API docs: https://developer.github.com/v3/repos/#list-organization-repositories +func (s *RepositoriesService) ListByOrg(ctx context.Context, org string, opt *RepositoryListByOrgOptions) ([]*Repository, *Response, error) { + u := fmt.Sprintf("orgs/%v/repos", org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + acceptHeaders := []string{mediaTypeCodesOfConductPreview, mediaTypeTopicsPreview} + req.Header.Set("Accept", strings.Join(acceptHeaders, ", ")) + + var repos []*Repository + resp, err := s.client.Do(ctx, req, &repos) + if err != nil { + return nil, resp, err + } + + return repos, resp, nil +} + +// RepositoryListAllOptions specifies the optional parameters to the +// RepositoriesService.ListAll method. +type RepositoryListAllOptions struct { + // ID of the last repository seen + Since int64 `url:"since,omitempty"` +} + +// ListAll lists all GitHub repositories in the order that they were created. +// +// GitHub API docs: https://developer.github.com/v3/repos/#list-all-public-repositories +func (s *RepositoriesService) ListAll(ctx context.Context, opt *RepositoryListAllOptions) ([]*Repository, *Response, error) { + u, err := addOptions("repositories", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var repos []*Repository + resp, err := s.client.Do(ctx, req, &repos) + if err != nil { + return nil, resp, err + } + + return repos, resp, nil +} + +// Create a new repository. If an organization is specified, the new +// repository will be created under that org. If the empty string is +// specified, it will be created for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/repos/#create +func (s *RepositoriesService) Create(ctx context.Context, org string, repo *Repository) (*Repository, *Response, error) { + var u string + if org != "" { + u = fmt.Sprintf("orgs/%v/repos", org) + } else { + u = "user/repos" + } + + req, err := s.client.NewRequest("POST", u, repo) + if err != nil { + return nil, nil, err + } + + r := new(Repository) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// Get fetches a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#get +func (s *RepositoriesService) Get(ctx context.Context, owner, repo string) (*Repository, *Response, error) { + u := fmt.Sprintf("repos/%v/%v", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when the license support fully launches + // https://developer.github.com/v3/licenses/#get-a-repositorys-license + acceptHeaders := []string{mediaTypeCodesOfConductPreview, mediaTypeTopicsPreview} + req.Header.Set("Accept", strings.Join(acceptHeaders, ", ")) + + repository := new(Repository) + resp, err := s.client.Do(ctx, req, repository) + if err != nil { + return nil, resp, err + } + + return repository, resp, nil +} + +// GetCodeOfConduct gets the contents of a repository's code of conduct. +// +// GitHub API docs: https://developer.github.com/v3/codes_of_conduct/#get-the-contents-of-a-repositorys-code-of-conduct +func (s *RepositoriesService) GetCodeOfConduct(ctx context.Context, owner, repo string) (*CodeOfConduct, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/community/code_of_conduct", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeCodesOfConductPreview) + + coc := new(CodeOfConduct) + resp, err := s.client.Do(ctx, req, coc) + if err != nil { + return nil, resp, err + } + + return coc, resp, nil +} + +// GetByID fetches a repository. +// +// Note: GetByID uses the undocumented GitHub API endpoint /repositories/:id. +func (s *RepositoriesService) GetByID(ctx context.Context, id int64) (*Repository, *Response, error) { + u := fmt.Sprintf("repositories/%d", id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + repository := new(Repository) + resp, err := s.client.Do(ctx, req, repository) + if err != nil { + return nil, resp, err + } + + return repository, resp, nil +} + +// Edit updates a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#edit +func (s *RepositoriesService) Edit(ctx context.Context, owner, repo string, repository *Repository) (*Repository, *Response, error) { + u := fmt.Sprintf("repos/%v/%v", owner, repo) + req, err := s.client.NewRequest("PATCH", u, repository) + if err != nil { + return nil, nil, err + } + + r := new(Repository) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// Delete a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#delete-a-repository +func (s *RepositoriesService) Delete(ctx context.Context, owner, repo string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v", owner, repo) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// Contributor represents a repository contributor +type Contributor struct { + Login *string `json:"login,omitempty"` + ID *int64 `json:"id,omitempty"` + AvatarURL *string `json:"avatar_url,omitempty"` + GravatarID *string `json:"gravatar_id,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + FollowersURL *string `json:"followers_url,omitempty"` + FollowingURL *string `json:"following_url,omitempty"` + GistsURL *string `json:"gists_url,omitempty"` + StarredURL *string `json:"starred_url,omitempty"` + SubscriptionsURL *string `json:"subscriptions_url,omitempty"` + OrganizationsURL *string `json:"organizations_url,omitempty"` + ReposURL *string `json:"repos_url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + ReceivedEventsURL *string `json:"received_events_url,omitempty"` + Type *string `json:"type,omitempty"` + SiteAdmin *bool `json:"site_admin,omitempty"` + Contributions *int `json:"contributions,omitempty"` +} + +// ListContributorsOptions specifies the optional parameters to the +// RepositoriesService.ListContributors method. +type ListContributorsOptions struct { + // Include anonymous contributors in results or not + Anon string `url:"anon,omitempty"` + + ListOptions +} + +// ListContributors lists contributors for a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#list-contributors +func (s *RepositoriesService) ListContributors(ctx context.Context, owner string, repository string, opt *ListContributorsOptions) ([]*Contributor, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/contributors", owner, repository) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var contributor []*Contributor + resp, err := s.client.Do(ctx, req, &contributor) + if err != nil { + return nil, nil, err + } + + return contributor, resp, nil +} + +// ListLanguages lists languages for the specified repository. The returned map +// specifies the languages and the number of bytes of code written in that +// language. For example: +// +// { +// "C": 78769, +// "Python": 7769 +// } +// +// GitHub API docs: https://developer.github.com/v3/repos/#list-languages +func (s *RepositoriesService) ListLanguages(ctx context.Context, owner string, repo string) (map[string]int, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/languages", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + languages := make(map[string]int) + resp, err := s.client.Do(ctx, req, &languages) + if err != nil { + return nil, resp, err + } + + return languages, resp, nil +} + +// ListTeams lists the teams for the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#list-teams +func (s *RepositoriesService) ListTeams(ctx context.Context, owner string, repo string, opt *ListOptions) ([]*Team, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/teams", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + var teams []*Team + resp, err := s.client.Do(ctx, req, &teams) + if err != nil { + return nil, resp, err + } + + return teams, resp, nil +} + +// RepositoryTag represents a repository tag. +type RepositoryTag struct { + Name *string `json:"name,omitempty"` + Commit *Commit `json:"commit,omitempty"` + ZipballURL *string `json:"zipball_url,omitempty"` + TarballURL *string `json:"tarball_url,omitempty"` +} + +// ListTags lists tags for the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#list-tags +func (s *RepositoriesService) ListTags(ctx context.Context, owner string, repo string, opt *ListOptions) ([]*RepositoryTag, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/tags", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var tags []*RepositoryTag + resp, err := s.client.Do(ctx, req, &tags) + if err != nil { + return nil, resp, err + } + + return tags, resp, nil +} + +// Branch represents a repository branch +type Branch struct { + Name *string `json:"name,omitempty"` + Commit *RepositoryCommit `json:"commit,omitempty"` + Protected *bool `json:"protected,omitempty"` +} + +// Protection represents a repository branch's protection. +type Protection struct { + RequiredStatusChecks *RequiredStatusChecks `json:"required_status_checks"` + RequiredPullRequestReviews *PullRequestReviewsEnforcement `json:"required_pull_request_reviews"` + EnforceAdmins *AdminEnforcement `json:"enforce_admins"` + Restrictions *BranchRestrictions `json:"restrictions"` +} + +// ProtectionRequest represents a request to create/edit a branch's protection. +type ProtectionRequest struct { + RequiredStatusChecks *RequiredStatusChecks `json:"required_status_checks"` + RequiredPullRequestReviews *PullRequestReviewsEnforcementRequest `json:"required_pull_request_reviews"` + EnforceAdmins bool `json:"enforce_admins"` + Restrictions *BranchRestrictionsRequest `json:"restrictions"` +} + +// RequiredStatusChecks represents the protection status of a individual branch. +type RequiredStatusChecks struct { + // Require branches to be up to date before merging. (Required.) + Strict bool `json:"strict"` + // The list of status checks to require in order to merge into this + // branch. (Required; use []string{} instead of nil for empty list.) + Contexts []string `json:"contexts"` +} + +// RequiredStatusChecksRequest represents a request to edit a protected branch's status checks. +type RequiredStatusChecksRequest struct { + Strict *bool `json:"strict,omitempty"` + Contexts []string `json:"contexts,omitempty"` +} + +// PullRequestReviewsEnforcement represents the pull request reviews enforcement of a protected branch. +type PullRequestReviewsEnforcement struct { + // Specifies which users and teams can dismiss pull request reviews. + DismissalRestrictions DismissalRestrictions `json:"dismissal_restrictions"` + // Specifies if approved reviews are dismissed automatically, when a new commit is pushed. + DismissStaleReviews bool `json:"dismiss_stale_reviews"` + // RequireCodeOwnerReviews specifies if an approved review is required in pull requests including files with a designated code owner. + RequireCodeOwnerReviews bool `json:"require_code_owner_reviews"` + // RequiredApprovingReviewCount specifies the number of approvals required before the pull request can be merged. + // Valid values are 1-6. + RequiredApprovingReviewCount int `json:"required_approving_review_count"` +} + +// PullRequestReviewsEnforcementRequest represents request to set the pull request review +// enforcement of a protected branch. It is separate from PullRequestReviewsEnforcement above +// because the request structure is different from the response structure. +type PullRequestReviewsEnforcementRequest struct { + // Specifies which users and teams should be allowed to dismiss pull request reviews. + // User and team dismissal restrictions are only available for + // organization-owned repositories. Must be nil for personal repositories. + DismissalRestrictionsRequest *DismissalRestrictionsRequest `json:"dismissal_restrictions,omitempty"` + // Specifies if approved reviews can be dismissed automatically, when a new commit is pushed. (Required) + DismissStaleReviews bool `json:"dismiss_stale_reviews"` + // RequireCodeOwnerReviews specifies if an approved review is required in pull requests including files with a designated code owner. + RequireCodeOwnerReviews bool `json:"require_code_owner_reviews"` + // RequiredApprovingReviewCount specifies the number of approvals required before the pull request can be merged. + // Valid values are 1-6. + RequiredApprovingReviewCount int `json:"required_approving_review_count"` +} + +// PullRequestReviewsEnforcementUpdate represents request to patch the pull request review +// enforcement of a protected branch. It is separate from PullRequestReviewsEnforcementRequest above +// because the patch request does not require all fields to be initialized. +type PullRequestReviewsEnforcementUpdate struct { + // Specifies which users and teams can dismiss pull request reviews. Can be omitted. + DismissalRestrictionsRequest *DismissalRestrictionsRequest `json:"dismissal_restrictions,omitempty"` + // Specifies if approved reviews can be dismissed automatically, when a new commit is pushed. Can be omitted. + DismissStaleReviews *bool `json:"dismiss_stale_reviews,omitempty"` + // RequireCodeOwnerReviews specifies if an approved review is required in pull requests including files with a designated code owner. + RequireCodeOwnerReviews bool `json:"require_code_owner_reviews,omitempty"` + // RequiredApprovingReviewCount specifies the number of approvals required before the pull request can be merged. + // Valid values are 1 - 6. + RequiredApprovingReviewCount int `json:"required_approving_review_count"` +} + +// AdminEnforcement represents the configuration to enforce required status checks for repository administrators. +type AdminEnforcement struct { + URL *string `json:"url,omitempty"` + Enabled bool `json:"enabled"` +} + +// BranchRestrictions represents the restriction that only certain users or +// teams may push to a branch. +type BranchRestrictions struct { + // The list of user logins with push access. + Users []*User `json:"users"` + // The list of team slugs with push access. + Teams []*Team `json:"teams"` +} + +// BranchRestrictionsRequest represents the request to create/edit the +// restriction that only certain users or teams may push to a branch. It is +// separate from BranchRestrictions above because the request structure is +// different from the response structure. +type BranchRestrictionsRequest struct { + // The list of user logins with push access. (Required; use []string{} instead of nil for empty list.) + Users []string `json:"users"` + // The list of team slugs with push access. (Required; use []string{} instead of nil for empty list.) + Teams []string `json:"teams"` +} + +// DismissalRestrictions specifies which users and teams can dismiss pull request reviews. +type DismissalRestrictions struct { + // The list of users who can dimiss pull request reviews. + Users []*User `json:"users"` + // The list of teams which can dismiss pull request reviews. + Teams []*Team `json:"teams"` +} + +// DismissalRestrictionsRequest represents the request to create/edit the +// restriction to allows only specific users or teams to dimiss pull request reviews. It is +// separate from DismissalRestrictions above because the request structure is +// different from the response structure. +// Note: Both Users and Teams must be nil, or both must be non-nil. +type DismissalRestrictionsRequest struct { + // The list of user logins who can dismiss pull request reviews. (Required; use nil to disable dismissal_restrictions or &[]string{} otherwise.) + Users *[]string `json:"users,omitempty"` + // The list of team slugs which can dismiss pull request reviews. (Required; use nil to disable dismissal_restrictions or &[]string{} otherwise.) + Teams *[]string `json:"teams,omitempty"` +} + +// ListBranches lists branches for the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#list-branches +func (s *RepositoriesService) ListBranches(ctx context.Context, owner string, repo string, opt *ListOptions) ([]*Branch, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeRequiredApprovingReviewsPreview) + + var branches []*Branch + resp, err := s.client.Do(ctx, req, &branches) + if err != nil { + return nil, resp, err + } + + return branches, resp, nil +} + +// GetBranch gets the specified branch for a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#get-branch +func (s *RepositoriesService) GetBranch(ctx context.Context, owner, repo, branch string) (*Branch, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v", owner, repo, branch) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeRequiredApprovingReviewsPreview) + + b := new(Branch) + resp, err := s.client.Do(ctx, req, b) + if err != nil { + return nil, resp, err + } + + return b, resp, nil +} + +// GetBranchProtection gets the protection of a given branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#get-branch-protection +func (s *RepositoriesService) GetBranchProtection(ctx context.Context, owner, repo, branch string) (*Protection, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection", owner, repo, branch) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeRequiredApprovingReviewsPreview) + + p := new(Protection) + resp, err := s.client.Do(ctx, req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, nil +} + +// GetRequiredStatusChecks gets the required status checks for a given protected branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#get-required-status-checks-of-protected-branch +func (s *RepositoriesService) GetRequiredStatusChecks(ctx context.Context, owner, repo, branch string) (*RequiredStatusChecks, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/required_status_checks", owner, repo, branch) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeRequiredApprovingReviewsPreview) + + p := new(RequiredStatusChecks) + resp, err := s.client.Do(ctx, req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, nil +} + +// ListRequiredStatusChecksContexts lists the required status checks contexts for a given protected branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#list-required-status-checks-contexts-of-protected-branch +func (s *RepositoriesService) ListRequiredStatusChecksContexts(ctx context.Context, owner, repo, branch string) (contexts []string, resp *Response, err error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/required_status_checks/contexts", owner, repo, branch) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeRequiredApprovingReviewsPreview) + + resp, err = s.client.Do(ctx, req, &contexts) + if err != nil { + return nil, resp, err + } + + return contexts, resp, nil +} + +// UpdateBranchProtection updates the protection of a given branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#update-branch-protection +func (s *RepositoriesService) UpdateBranchProtection(ctx context.Context, owner, repo, branch string, preq *ProtectionRequest) (*Protection, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection", owner, repo, branch) + req, err := s.client.NewRequest("PUT", u, preq) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeRequiredApprovingReviewsPreview) + + p := new(Protection) + resp, err := s.client.Do(ctx, req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, nil +} + +// RemoveBranchProtection removes the protection of a given branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#remove-branch-protection +func (s *RepositoriesService) RemoveBranchProtection(ctx context.Context, owner, repo, branch string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection", owner, repo, branch) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeRequiredApprovingReviewsPreview) + + return s.client.Do(ctx, req, nil) +} + +// UpdateRequiredStatusChecks updates the required status checks for a given protected branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#update-required-status-checks-of-protected-branch +func (s *RepositoriesService) UpdateRequiredStatusChecks(ctx context.Context, owner, repo, branch string, sreq *RequiredStatusChecksRequest) (*RequiredStatusChecks, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/required_status_checks", owner, repo, branch) + req, err := s.client.NewRequest("PATCH", u, sreq) + if err != nil { + return nil, nil, err + } + + sc := new(RequiredStatusChecks) + resp, err := s.client.Do(ctx, req, sc) + if err != nil { + return nil, resp, err + } + + return sc, resp, nil +} + +// License gets the contents of a repository's license if one is detected. +// +// GitHub API docs: https://developer.github.com/v3/licenses/#get-the-contents-of-a-repositorys-license +func (s *RepositoriesService) License(ctx context.Context, owner, repo string) (*RepositoryLicense, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/license", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + r := &RepositoryLicense{} + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// GetPullRequestReviewEnforcement gets pull request review enforcement of a protected branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#get-pull-request-review-enforcement-of-protected-branch +func (s *RepositoriesService) GetPullRequestReviewEnforcement(ctx context.Context, owner, repo, branch string) (*PullRequestReviewsEnforcement, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/required_pull_request_reviews", owner, repo, branch) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeRequiredApprovingReviewsPreview) + + r := new(PullRequestReviewsEnforcement) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// UpdatePullRequestReviewEnforcement patches pull request review enforcement of a protected branch. +// It requires admin access and branch protection to be enabled. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#update-pull-request-review-enforcement-of-protected-branch +func (s *RepositoriesService) UpdatePullRequestReviewEnforcement(ctx context.Context, owner, repo, branch string, patch *PullRequestReviewsEnforcementUpdate) (*PullRequestReviewsEnforcement, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/required_pull_request_reviews", owner, repo, branch) + req, err := s.client.NewRequest("PATCH", u, patch) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeRequiredApprovingReviewsPreview) + + r := new(PullRequestReviewsEnforcement) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// DisableDismissalRestrictions disables dismissal restrictions of a protected branch. +// It requires admin access and branch protection to be enabled. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#update-pull-request-review-enforcement-of-protected-branch +func (s *RepositoriesService) DisableDismissalRestrictions(ctx context.Context, owner, repo, branch string) (*PullRequestReviewsEnforcement, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/required_pull_request_reviews", owner, repo, branch) + + data := struct { + R []interface{} `json:"dismissal_restrictions"` + }{[]interface{}{}} + + req, err := s.client.NewRequest("PATCH", u, data) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeRequiredApprovingReviewsPreview) + + r := new(PullRequestReviewsEnforcement) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// RemovePullRequestReviewEnforcement removes pull request enforcement of a protected branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#remove-pull-request-review-enforcement-of-protected-branch +func (s *RepositoriesService) RemovePullRequestReviewEnforcement(ctx context.Context, owner, repo, branch string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/required_pull_request_reviews", owner, repo, branch) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeRequiredApprovingReviewsPreview) + + return s.client.Do(ctx, req, nil) +} + +// GetAdminEnforcement gets admin enforcement information of a protected branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#get-admin-enforcement-of-protected-branch +func (s *RepositoriesService) GetAdminEnforcement(ctx context.Context, owner, repo, branch string) (*AdminEnforcement, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/enforce_admins", owner, repo, branch) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeRequiredApprovingReviewsPreview) + + r := new(AdminEnforcement) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// AddAdminEnforcement adds admin enforcement to a protected branch. +// It requires admin access and branch protection to be enabled. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#add-admin-enforcement-of-protected-branch +func (s *RepositoriesService) AddAdminEnforcement(ctx context.Context, owner, repo, branch string) (*AdminEnforcement, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/enforce_admins", owner, repo, branch) + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeRequiredApprovingReviewsPreview) + + r := new(AdminEnforcement) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// RemoveAdminEnforcement removes admin enforcement from a protected branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#remove-admin-enforcement-of-protected-branch +func (s *RepositoriesService) RemoveAdminEnforcement(ctx context.Context, owner, repo, branch string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/enforce_admins", owner, repo, branch) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeRequiredApprovingReviewsPreview) + + return s.client.Do(ctx, req, nil) +} + +// repositoryTopics represents a collection of repository topics. +type repositoryTopics struct { + Names []string `json:"names"` +} + +// ListAllTopics lists topics for a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#list-all-topics-for-a-repository +func (s *RepositoriesService) ListAllTopics(ctx context.Context, owner, repo string) ([]string, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/topics", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTopicsPreview) + + topics := new(repositoryTopics) + resp, err := s.client.Do(ctx, req, topics) + if err != nil { + return nil, resp, err + } + + return topics.Names, resp, nil +} + +// ReplaceAllTopics replaces topics for a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#replace-all-topics-for-a-repository +func (s *RepositoriesService) ReplaceAllTopics(ctx context.Context, owner, repo string, topics []string) ([]string, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/topics", owner, repo) + t := &repositoryTopics{ + Names: topics, + } + if t.Names == nil { + t.Names = []string{} + } + req, err := s.client.NewRequest("PUT", u, t) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTopicsPreview) + + t = new(repositoryTopics) + resp, err := s.client.Do(ctx, req, t) + if err != nil { + return nil, resp, err + } + + return t.Names, resp, nil +} + +// TransferRequest represents a request to transfer a repository. +type TransferRequest struct { + NewOwner string `json:"new_owner"` + TeamID []int64 `json:"team_ids,omitempty"` +} + +// Transfer transfers a repository from one account or organization to another. +// +// This method might return an *AcceptedError and a status code of +// 202. This is because this is the status that GitHub returns to signify that +// it has now scheduled the transfer of the repository in a background task. +// A follow up request, after a delay of a second or so, should result +// in a successful request. +// +// GitHub API docs: https://developer.github.com/v3/repos/#transfer-a-repository +func (s *RepositoriesService) Transfer(ctx context.Context, owner, repo string, transfer TransferRequest) (*Repository, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/transfer", owner, repo) + + req, err := s.client.NewRequest("POST", u, &transfer) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryTransferPreview) + + r := new(Repository) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/repos_collaborators.go b/vendor/github.com/google/go-github/github/repos_collaborators.go new file mode 100644 index 0000000..61ee9d3 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_collaborators.go @@ -0,0 +1,140 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ListCollaboratorsOptions specifies the optional parameters to the +// RepositoriesService.ListCollaborators method. +type ListCollaboratorsOptions struct { + // Affiliation specifies how collaborators should be filtered by their affiliation. + // Possible values are: + // outside - All outside collaborators of an organization-owned repository + // direct - All collaborators with permissions to an organization-owned repository, + // regardless of organization membership status + // all - All collaborators the authenticated user can see + // + // Default value is "all". + Affiliation string `url:"affiliation,omitempty"` + + ListOptions +} + +// ListCollaborators lists the GitHub users that have access to the repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/collaborators/#list-collaborators +func (s *RepositoriesService) ListCollaborators(ctx context.Context, owner, repo string, opt *ListCollaboratorsOptions) ([]*User, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/collaborators", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + var users []*User + resp, err := s.client.Do(ctx, req, &users) + if err != nil { + return nil, resp, err + } + + return users, resp, nil +} + +// IsCollaborator checks whether the specified GitHub user has collaborator +// access to the given repo. +// Note: This will return false if the user is not a collaborator OR the user +// is not a GitHub user. +// +// GitHub API docs: https://developer.github.com/v3/repos/collaborators/#get +func (s *RepositoriesService) IsCollaborator(ctx context.Context, owner, repo, user string) (bool, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/collaborators/%v", owner, repo, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + isCollab, err := parseBoolResponse(err) + return isCollab, resp, err +} + +// RepositoryPermissionLevel represents the permission level an organization +// member has for a given repository. +type RepositoryPermissionLevel struct { + // Possible values: "admin", "write", "read", "none" + Permission *string `json:"permission,omitempty"` + + User *User `json:"user,omitempty"` +} + +// GetPermissionLevel retrieves the specific permission level a collaborator has for a given repository. +// GitHub API docs: https://developer.github.com/v3/repos/collaborators/#review-a-users-permission-level +func (s *RepositoriesService) GetPermissionLevel(ctx context.Context, owner, repo, user string) (*RepositoryPermissionLevel, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/collaborators/%v/permission", owner, repo, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + rpl := new(RepositoryPermissionLevel) + resp, err := s.client.Do(ctx, req, rpl) + if err != nil { + return nil, resp, err + } + return rpl, resp, nil +} + +// RepositoryAddCollaboratorOptions specifies the optional parameters to the +// RepositoriesService.AddCollaborator method. +type RepositoryAddCollaboratorOptions struct { + // Permission specifies the permission to grant the user on this repository. + // Possible values are: + // pull - team members can pull, but not push to or administer this repository + // push - team members can pull and push, but not administer this repository + // admin - team members can pull, push and administer this repository + // + // Default value is "push". This option is only valid for organization-owned repositories. + Permission string `json:"permission,omitempty"` +} + +// AddCollaborator sends an invitation to the specified GitHub user +// to become a collaborator to the given repo. +// +// GitHub API docs: https://developer.github.com/v3/repos/collaborators/#add-user-as-a-collaborator +func (s *RepositoriesService) AddCollaborator(ctx context.Context, owner, repo, user string, opt *RepositoryAddCollaboratorOptions) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/collaborators/%v", owner, repo, user) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryInvitationsPreview) + + return s.client.Do(ctx, req, nil) +} + +// RemoveCollaborator removes the specified GitHub user as collaborator from the given repo. +// Note: Does not return error if a valid user that is not a collaborator is removed. +// +// GitHub API docs: https://developer.github.com/v3/repos/collaborators/#remove-collaborator +func (s *RepositoriesService) RemoveCollaborator(ctx context.Context, owner, repo, user string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/collaborators/%v", owner, repo, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/repos_collaborators_test.go b/vendor/github.com/google/go-github/github/repos_collaborators_test.go new file mode 100644 index 0000000..c814a5f --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_collaborators_test.go @@ -0,0 +1,201 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListCollaborators(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/collaborators", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprintf(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListCollaboratorsOptions{ + ListOptions: ListOptions{Page: 2}, + } + users, _, err := client.Repositories.ListCollaborators(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListCollaborators returned error: %v", err) + } + + want := []*User{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Repositori es.ListCollaborators returned %+v, want %+v", users, want) + } +} + +func TestRepositoriesService_ListCollaborators_withAffiliation(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/collaborators", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"affiliation": "all", "page": "2"}) + fmt.Fprintf(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListCollaboratorsOptions{ + ListOptions: ListOptions{Page: 2}, + Affiliation: "all", + } + users, _, err := client.Repositories.ListCollaborators(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListCollaborators returned error: %v", err) + } + + want := []*User{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Repositories.ListCollaborators returned %+v, want %+v", users, want) + } +} + +func TestRepositoriesService_ListCollaborators_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.ListCollaborators(context.Background(), "%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_IsCollaborator_True(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + isCollab, _, err := client.Repositories.IsCollaborator(context.Background(), "o", "r", "u") + if err != nil { + t.Errorf("Repositories.IsCollaborator returned error: %v", err) + } + + if !isCollab { + t.Errorf("Repositories.IsCollaborator returned false, want true") + } +} + +func TestRepositoriesService_IsCollaborator_False(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + isCollab, _, err := client.Repositories.IsCollaborator(context.Background(), "o", "r", "u") + if err != nil { + t.Errorf("Repositories.IsCollaborator returned error: %v", err) + } + + if isCollab { + t.Errorf("Repositories.IsCollaborator returned true, want false") + } +} + +func TestRepositoriesService_IsCollaborator_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.IsCollaborator(context.Background(), "%", "%", "%") + testURLParseError(t, err) +} + +func TestRepositoryService_GetPermissionLevel(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/collaborators/u/permission", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprintf(w, `{"permission":"admin","user":{"login":"u"}}`) + }) + + rpl, _, err := client.Repositories.GetPermissionLevel(context.Background(), "o", "r", "u") + if err != nil { + t.Errorf("Repositories.GetPermissionLevel returned error: %v", err) + } + + want := &RepositoryPermissionLevel{ + Permission: String("admin"), + User: &User{ + Login: String("u"), + }, + } + + if !reflect.DeepEqual(rpl, want) { + t.Errorf("Repositories.GetPermissionLevel returned %+v, want %+v", rpl, want) + } +} + +func TestRepositoriesService_AddCollaborator(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + opt := &RepositoryAddCollaboratorOptions{Permission: "admin"} + + mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) { + v := new(RepositoryAddCollaboratorOptions) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + testHeader(t, r, "Accept", mediaTypeRepositoryInvitationsPreview) + if !reflect.DeepEqual(v, opt) { + t.Errorf("Request body = %+v, want %+v", v, opt) + } + + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Repositories.AddCollaborator(context.Background(), "o", "r", "u", opt) + if err != nil { + t.Errorf("Repositories.AddCollaborator returned error: %v", err) + } +} + +func TestRepositoriesService_AddCollaborator_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Repositories.AddCollaborator(context.Background(), "%", "%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_RemoveCollaborator(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Repositories.RemoveCollaborator(context.Background(), "o", "r", "u") + if err != nil { + t.Errorf("Repositories.RemoveCollaborator returned error: %v", err) + } +} + +func TestRepositoriesService_RemoveCollaborator_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Repositories.RemoveCollaborator(context.Background(), "%", "%", "%") + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/repos_comments.go b/vendor/github.com/google/go-github/github/repos_comments.go new file mode 100644 index 0000000..fa2377d --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_comments.go @@ -0,0 +1,161 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// RepositoryComment represents a comment for a commit, file, or line in a repository. +type RepositoryComment struct { + HTMLURL *string `json:"html_url,omitempty"` + URL *string `json:"url,omitempty"` + ID *int64 `json:"id,omitempty"` + CommitID *string `json:"commit_id,omitempty"` + User *User `json:"user,omitempty"` + Reactions *Reactions `json:"reactions,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + + // User-mutable fields + Body *string `json:"body"` + // User-initialized fields + Path *string `json:"path,omitempty"` + Position *int `json:"position,omitempty"` +} + +func (r RepositoryComment) String() string { + return Stringify(r) +} + +// ListComments lists all the comments for the repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/comments/#list-commit-comments-for-a-repository +func (s *RepositoriesService) ListComments(ctx context.Context, owner, repo string, opt *ListOptions) ([]*RepositoryComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var comments []*RepositoryComment + resp, err := s.client.Do(ctx, req, &comments) + if err != nil { + return nil, resp, err + } + + return comments, resp, nil +} + +// ListCommitComments lists all the comments for a given commit SHA. +// +// GitHub API docs: https://developer.github.com/v3/repos/comments/#list-comments-for-a-single-commit +func (s *RepositoriesService) ListCommitComments(ctx context.Context, owner, repo, sha string, opt *ListOptions) ([]*RepositoryComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v/comments", owner, repo, sha) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var comments []*RepositoryComment + resp, err := s.client.Do(ctx, req, &comments) + if err != nil { + return nil, resp, err + } + + return comments, resp, nil +} + +// CreateComment creates a comment for the given commit. +// Note: GitHub allows for comments to be created for non-existing files and positions. +// +// GitHub API docs: https://developer.github.com/v3/repos/comments/#create-a-commit-comment +func (s *RepositoriesService) CreateComment(ctx context.Context, owner, repo, sha string, comment *RepositoryComment) (*RepositoryComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v/comments", owner, repo, sha) + req, err := s.client.NewRequest("POST", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(RepositoryComment) + resp, err := s.client.Do(ctx, req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, nil +} + +// GetComment gets a single comment from a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/comments/#get-a-single-commit-comment +func (s *RepositoriesService) GetComment(ctx context.Context, owner, repo string, id int64) (*RepositoryComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v", owner, repo, id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + c := new(RepositoryComment) + resp, err := s.client.Do(ctx, req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, nil +} + +// UpdateComment updates the body of a single comment. +// +// GitHub API docs: https://developer.github.com/v3/repos/comments/#update-a-commit-comment +func (s *RepositoriesService) UpdateComment(ctx context.Context, owner, repo string, id int64, comment *RepositoryComment) (*RepositoryComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v", owner, repo, id) + req, err := s.client.NewRequest("PATCH", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(RepositoryComment) + resp, err := s.client.Do(ctx, req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, nil +} + +// DeleteComment deletes a single comment from a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/comments/#delete-a-commit-comment +func (s *RepositoriesService) DeleteComment(ctx context.Context, owner, repo string, id int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v", owner, repo, id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/repos_comments_test.go b/vendor/github.com/google/go-github/github/repos_comments_test.go new file mode 100644 index 0000000..0eb54eb --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_comments_test.go @@ -0,0 +1,202 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListComments(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + comments, _, err := client.Repositories.ListComments(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListComments returned error: %v", err) + } + + want := []*RepositoryComment{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(comments, want) { + t.Errorf("Repositories.ListComments returned %+v, want %+v", comments, want) + } +} + +func TestRepositoriesService_ListComments_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.ListComments(context.Background(), "%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_ListCommitComments(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/commits/s/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + comments, _, err := client.Repositories.ListCommitComments(context.Background(), "o", "r", "s", opt) + if err != nil { + t.Errorf("Repositories.ListCommitComments returned error: %v", err) + } + + want := []*RepositoryComment{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(comments, want) { + t.Errorf("Repositories.ListCommitComments returned %+v, want %+v", comments, want) + } +} + +func TestRepositoriesService_ListCommitComments_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.ListCommitComments(context.Background(), "%", "%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_CreateComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &RepositoryComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/commits/s/comments", func(w http.ResponseWriter, r *http.Request) { + v := new(RepositoryComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Repositories.CreateComment(context.Background(), "o", "r", "s", input) + if err != nil { + t.Errorf("Repositories.CreateComment returned error: %v", err) + } + + want := &RepositoryComment{ID: Int64(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Repositories.CreateComment returned %+v, want %+v", comment, want) + } +} + +func TestRepositoriesService_CreateComment_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.CreateComment(context.Background(), "%", "%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_GetComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Repositories.GetComment(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetComment returned error: %v", err) + } + + want := &RepositoryComment{ID: Int64(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Repositories.GetComment returned %+v, want %+v", comment, want) + } +} + +func TestRepositoriesService_GetComment_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.GetComment(context.Background(), "%", "%", 1) + testURLParseError(t, err) +} + +func TestRepositoriesService_UpdateComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &RepositoryComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/comments/1", func(w http.ResponseWriter, r *http.Request) { + v := new(RepositoryComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Repositories.UpdateComment(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.UpdateComment returned error: %v", err) + } + + want := &RepositoryComment{ID: Int64(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Repositories.UpdateComment returned %+v, want %+v", comment, want) + } +} + +func TestRepositoriesService_UpdateComment_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.UpdateComment(context.Background(), "%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_DeleteComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeleteComment(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteComment returned error: %v", err) + } +} + +func TestRepositoriesService_DeleteComment_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Repositories.DeleteComment(context.Background(), "%", "%", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/repos_commits.go b/vendor/github.com/google/go-github/github/repos_commits.go new file mode 100644 index 0000000..04faa3e --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_commits.go @@ -0,0 +1,237 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "bytes" + "context" + "fmt" + "time" +) + +// RepositoryCommit represents a commit in a repo. +// Note that it's wrapping a Commit, so author/committer information is in two places, +// but contain different details about them: in RepositoryCommit "github details", in Commit - "git details". +type RepositoryCommit struct { + SHA *string `json:"sha,omitempty"` + Commit *Commit `json:"commit,omitempty"` + Author *User `json:"author,omitempty"` + Committer *User `json:"committer,omitempty"` + Parents []Commit `json:"parents,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + URL *string `json:"url,omitempty"` + CommentsURL *string `json:"comments_url,omitempty"` + + // Details about how many changes were made in this commit. Only filled in during GetCommit! + Stats *CommitStats `json:"stats,omitempty"` + // Details about which files, and how this commit touched. Only filled in during GetCommit! + Files []CommitFile `json:"files,omitempty"` +} + +func (r RepositoryCommit) String() string { + return Stringify(r) +} + +// CommitStats represents the number of additions / deletions from a file in a given RepositoryCommit or GistCommit. +type CommitStats struct { + Additions *int `json:"additions,omitempty"` + Deletions *int `json:"deletions,omitempty"` + Total *int `json:"total,omitempty"` +} + +func (c CommitStats) String() string { + return Stringify(c) +} + +// CommitFile represents a file modified in a commit. +type CommitFile struct { + SHA *string `json:"sha,omitempty"` + Filename *string `json:"filename,omitempty"` + Additions *int `json:"additions,omitempty"` + Deletions *int `json:"deletions,omitempty"` + Changes *int `json:"changes,omitempty"` + Status *string `json:"status,omitempty"` + Patch *string `json:"patch,omitempty"` + BlobURL *string `json:"blob_url,omitempty"` + RawURL *string `json:"raw_url,omitempty"` + ContentsURL *string `json:"contents_url,omitempty"` +} + +func (c CommitFile) String() string { + return Stringify(c) +} + +// CommitsComparison is the result of comparing two commits. +// See CompareCommits() for details. +type CommitsComparison struct { + BaseCommit *RepositoryCommit `json:"base_commit,omitempty"` + MergeBaseCommit *RepositoryCommit `json:"merge_base_commit,omitempty"` + + // Head can be 'behind' or 'ahead' + Status *string `json:"status,omitempty"` + AheadBy *int `json:"ahead_by,omitempty"` + BehindBy *int `json:"behind_by,omitempty"` + TotalCommits *int `json:"total_commits,omitempty"` + + Commits []RepositoryCommit `json:"commits,omitempty"` + + Files []CommitFile `json:"files,omitempty"` + + HTMLURL *string `json:"html_url,omitempty"` + PermalinkURL *string `json:"permalink_url,omitempty"` + DiffURL *string `json:"diff_url,omitempty"` + PatchURL *string `json:"patch_url,omitempty"` + URL *string `json:"url,omitempty"` // API URL. +} + +func (c CommitsComparison) String() string { + return Stringify(c) +} + +// CommitsListOptions specifies the optional parameters to the +// RepositoriesService.ListCommits method. +type CommitsListOptions struct { + // SHA or branch to start listing Commits from. + SHA string `url:"sha,omitempty"` + + // Path that should be touched by the returned Commits. + Path string `url:"path,omitempty"` + + // Author of by which to filter Commits. + Author string `url:"author,omitempty"` + + // Since when should Commits be included in the response. + Since time.Time `url:"since,omitempty"` + + // Until when should Commits be included in the response. + Until time.Time `url:"until,omitempty"` + + ListOptions +} + +// ListCommits lists the commits of a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/commits/#list +func (s *RepositoriesService) ListCommits(ctx context.Context, owner, repo string, opt *CommitsListOptions) ([]*RepositoryCommit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeGitSigningPreview) + + var commits []*RepositoryCommit + resp, err := s.client.Do(ctx, req, &commits) + if err != nil { + return nil, resp, err + } + + return commits, resp, nil +} + +// GetCommit fetches the specified commit, including all details about it. +// +// GitHub API docs: https://developer.github.com/v3/repos/commits/#get-a-single-commit +// See also: https://developer.github.com/v3/git/commits/#get-a-single-commit provides the same functionality +func (s *RepositoriesService) GetCommit(ctx context.Context, owner, repo, sha string) (*RepositoryCommit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v", owner, repo, sha) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeGitSigningPreview) + + commit := new(RepositoryCommit) + resp, err := s.client.Do(ctx, req, commit) + if err != nil { + return nil, resp, err + } + + return commit, resp, nil +} + +// GetCommitRaw fetches the specified commit in raw (diff or patch) format. +func (s *RepositoriesService) GetCommitRaw(ctx context.Context, owner string, repo string, sha string, opt RawOptions) (string, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v", owner, repo, sha) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return "", nil, err + } + + switch opt.Type { + case Diff: + req.Header.Set("Accept", mediaTypeV3Diff) + case Patch: + req.Header.Set("Accept", mediaTypeV3Patch) + default: + return "", nil, fmt.Errorf("unsupported raw type %d", opt.Type) + } + + var buf bytes.Buffer + resp, err := s.client.Do(ctx, req, &buf) + if err != nil { + return "", resp, err + } + + return buf.String(), resp, nil +} + +// GetCommitSHA1 gets the SHA-1 of a commit reference. If a last-known SHA1 is +// supplied and no new commits have occurred, a 304 Unmodified response is returned. +// +// GitHub API docs: https://developer.github.com/v3/repos/commits/#get-the-sha-1-of-a-commit-reference +func (s *RepositoriesService) GetCommitSHA1(ctx context.Context, owner, repo, ref, lastSHA string) (string, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v", owner, repo, ref) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return "", nil, err + } + if lastSHA != "" { + req.Header.Set("If-None-Match", `"`+lastSHA+`"`) + } + + req.Header.Set("Accept", mediaTypeV3SHA) + + var buf bytes.Buffer + resp, err := s.client.Do(ctx, req, &buf) + if err != nil { + return "", resp, err + } + + return buf.String(), resp, nil +} + +// CompareCommits compares a range of commits with each other. +// todo: support media formats - https://github.com/google/go-github/issues/6 +// +// GitHub API docs: https://developer.github.com/v3/repos/commits/#compare-two-commits +func (s *RepositoriesService) CompareCommits(ctx context.Context, owner, repo string, base, head string) (*CommitsComparison, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/compare/%v...%v", owner, repo, base, head) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + comp := new(CommitsComparison) + resp, err := s.client.Do(ctx, req, comp) + if err != nil { + return nil, resp, err + } + + return comp, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/repos_commits_test.go b/vendor/github.com/google/go-github/github/repos_commits_test.go new file mode 100644 index 0000000..3f7039d --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_commits_test.go @@ -0,0 +1,325 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "strings" + "testing" + "time" +) + +func TestRepositoriesService_ListCommits(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + // given + mux.HandleFunc("/repos/o/r/commits", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeGitSigningPreview) + testFormValues(t, r, + values{ + "sha": "s", + "path": "p", + "author": "a", + "since": "2013-08-01T00:00:00Z", + "until": "2013-09-03T00:00:00Z", + }) + fmt.Fprintf(w, `[{"sha": "s"}]`) + }) + + opt := &CommitsListOptions{ + SHA: "s", + Path: "p", + Author: "a", + Since: time.Date(2013, time.August, 1, 0, 0, 0, 0, time.UTC), + Until: time.Date(2013, time.September, 3, 0, 0, 0, 0, time.UTC), + } + commits, _, err := client.Repositories.ListCommits(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListCommits returned error: %v", err) + } + + want := []*RepositoryCommit{{SHA: String("s")}} + if !reflect.DeepEqual(commits, want) { + t.Errorf("Repositories.ListCommits returned %+v, want %+v", commits, want) + } +} + +func TestRepositoriesService_GetCommit(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/commits/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeGitSigningPreview) + fmt.Fprintf(w, `{ + "sha": "s", + "commit": { "message": "m" }, + "author": { "login": "l" }, + "committer": { "login": "l" }, + "parents": [ { "sha": "s" } ], + "stats": { "additions": 104, "deletions": 4, "total": 108 }, + "files": [ + { + "filename": "f", + "additions": 10, + "deletions": 2, + "changes": 12, + "status": "s", + "patch": "p", + "blob_url": "b", + "raw_url": "r", + "contents_url": "c" + } + ] + }`) + }) + + commit, _, err := client.Repositories.GetCommit(context.Background(), "o", "r", "s") + if err != nil { + t.Errorf("Repositories.GetCommit returned error: %v", err) + } + + want := &RepositoryCommit{ + SHA: String("s"), + Commit: &Commit{ + Message: String("m"), + }, + Author: &User{ + Login: String("l"), + }, + Committer: &User{ + Login: String("l"), + }, + Parents: []Commit{ + { + SHA: String("s"), + }, + }, + Stats: &CommitStats{ + Additions: Int(104), + Deletions: Int(4), + Total: Int(108), + }, + Files: []CommitFile{ + { + Filename: String("f"), + Additions: Int(10), + Deletions: Int(2), + Changes: Int(12), + Status: String("s"), + Patch: String("p"), + BlobURL: String("b"), + RawURL: String("r"), + ContentsURL: String("c"), + }, + }, + } + if !reflect.DeepEqual(commit, want) { + t.Errorf("Repositories.GetCommit returned \n%+v, want \n%+v", commit, want) + } +} + +func TestRepositoriesService_GetCommitRaw_diff(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + const rawStr = "@@diff content" + + mux.HandleFunc("/repos/o/r/commits/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeV3Diff) + fmt.Fprint(w, rawStr) + }) + + got, _, err := client.Repositories.GetCommitRaw(context.Background(), "o", "r", "s", RawOptions{Type: Diff}) + if err != nil { + t.Fatalf("Repositories.GetCommitRaw returned error: %v", err) + } + want := rawStr + if got != want { + t.Errorf("Repositories.GetCommitRaw returned %s want %s", got, want) + } +} + +func TestRepositoriesService_GetCommitRaw_patch(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + const rawStr = "@@patch content" + + mux.HandleFunc("/repos/o/r/commits/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeV3Patch) + fmt.Fprint(w, rawStr) + }) + + got, _, err := client.Repositories.GetCommitRaw(context.Background(), "o", "r", "s", RawOptions{Type: Patch}) + if err != nil { + t.Fatalf("Repositories.GetCommitRaw returned error: %v", err) + } + want := rawStr + if got != want { + t.Errorf("Repositories.GetCommitRaw returned %s want %s", got, want) + } +} + +func TestRepositoriesService_GetCommitRaw_invalid(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.GetCommitRaw(context.Background(), "o", "r", "s", RawOptions{100}) + if err == nil { + t.Fatal("Repositories.GetCommitRaw should return error") + } + if !strings.Contains(err.Error(), "unsupported raw type") { + t.Error("Repositories.GetCommitRaw should return unsupported raw type error") + } +} + +func TestRepositoriesService_GetCommitSHA1(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + const sha1 = "01234abcde" + + mux.HandleFunc("/repos/o/r/commits/master", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeV3SHA) + + fmt.Fprintf(w, sha1) + }) + + got, _, err := client.Repositories.GetCommitSHA1(context.Background(), "o", "r", "master", "") + if err != nil { + t.Errorf("Repositories.GetCommitSHA1 returned error: %v", err) + } + + want := sha1 + if got != want { + t.Errorf("Repositories.GetCommitSHA1 = %v, want %v", got, want) + } + + mux.HandleFunc("/repos/o/r/commits/tag", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeV3SHA) + testHeader(t, r, "If-None-Match", `"`+sha1+`"`) + + w.WriteHeader(http.StatusNotModified) + }) + + got, _, err = client.Repositories.GetCommitSHA1(context.Background(), "o", "r", "tag", sha1) + if err == nil { + t.Errorf("Expected HTTP 304 response") + } + + want = "" + if got != want { + t.Errorf("Repositories.GetCommitSHA1 = %v, want %v", got, want) + } +} + +func TestRepositoriesService_CompareCommits(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/compare/b...h", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprintf(w, `{ + "base_commit": { + "sha": "s", + "commit": { + "author": { "name": "n" }, + "committer": { "name": "n" }, + "message": "m", + "tree": { "sha": "t" } + }, + "author": { "login": "l" }, + "committer": { "login": "l" }, + "parents": [ { "sha": "s" } ] + }, + "status": "s", + "ahead_by": 1, + "behind_by": 2, + "total_commits": 1, + "commits": [ + { + "sha": "s", + "commit": { "author": { "name": "n" } }, + "author": { "login": "l" }, + "committer": { "login": "l" }, + "parents": [ { "sha": "s" } ] + } + ], + "files": [ { "filename": "f" } ], + "html_url": "https://github.com/o/r/compare/b...h", + "permalink_url": "https://github.com/o/r/compare/o:bbcd538c8e72b8c175046e27cc8f907076331401...o:0328041d1152db8ae77652d1618a02e57f745f17", + "diff_url": "https://github.com/o/r/compare/b...h.diff", + "patch_url": "https://github.com/o/r/compare/b...h.patch", + "url": "https://api.github.com/repos/o/r/compare/b...h" + }`) + }) + + got, _, err := client.Repositories.CompareCommits(context.Background(), "o", "r", "b", "h") + if err != nil { + t.Errorf("Repositories.CompareCommits returned error: %v", err) + } + + want := &CommitsComparison{ + BaseCommit: &RepositoryCommit{ + SHA: String("s"), + Commit: &Commit{ + Author: &CommitAuthor{Name: String("n")}, + Committer: &CommitAuthor{Name: String("n")}, + Message: String("m"), + Tree: &Tree{SHA: String("t")}, + }, + Author: &User{Login: String("l")}, + Committer: &User{Login: String("l")}, + Parents: []Commit{ + { + SHA: String("s"), + }, + }, + }, + Status: String("s"), + AheadBy: Int(1), + BehindBy: Int(2), + TotalCommits: Int(1), + Commits: []RepositoryCommit{ + { + SHA: String("s"), + Commit: &Commit{ + Author: &CommitAuthor{Name: String("n")}, + }, + Author: &User{Login: String("l")}, + Committer: &User{Login: String("l")}, + Parents: []Commit{ + { + SHA: String("s"), + }, + }, + }, + }, + Files: []CommitFile{ + { + Filename: String("f"), + }, + }, + HTMLURL: String("https://github.com/o/r/compare/b...h"), + PermalinkURL: String("https://github.com/o/r/compare/o:bbcd538c8e72b8c175046e27cc8f907076331401...o:0328041d1152db8ae77652d1618a02e57f745f17"), + DiffURL: String("https://github.com/o/r/compare/b...h.diff"), + PatchURL: String("https://github.com/o/r/compare/b...h.patch"), + URL: String("https://api.github.com/repos/o/r/compare/b...h"), + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("Repositories.CompareCommits returned \n%+v, want \n%+v", got, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_community_health.go b/vendor/github.com/google/go-github/github/repos_community_health.go new file mode 100644 index 0000000..73d1d57 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_community_health.go @@ -0,0 +1,59 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// Metric represents the different fields for one file in community health files. +type Metric struct { + Name *string `json:"name"` + Key *string `json:"key"` + URL *string `json:"url"` + HTMLURL *string `json:"html_url"` +} + +// CommunityHealthFiles represents the different files in the community health metrics response. +type CommunityHealthFiles struct { + CodeOfConduct *Metric `json:"code_of_conduct"` + Contributing *Metric `json:"contributing"` + IssueTemplate *Metric `json:"issue_template"` + PullRequestTemplate *Metric `json:"pull_request_template"` + License *Metric `json:"license"` + Readme *Metric `json:"readme"` +} + +// CommunityHealthMetrics represents a response containing the community metrics of a repository. +type CommunityHealthMetrics struct { + HealthPercentage *int `json:"health_percentage"` + Files *CommunityHealthFiles `json:"files"` + UpdatedAt *time.Time `json:"updated_at"` +} + +// GetCommunityHealthMetrics retrieves all the community health metrics for a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/community/#retrieve-community-health-metrics +func (s *RepositoriesService) GetCommunityHealthMetrics(ctx context.Context, owner, repo string) (*CommunityHealthMetrics, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/community/profile", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryCommunityHealthMetricsPreview) + + metrics := &CommunityHealthMetrics{} + resp, err := s.client.Do(ctx, req, metrics) + if err != nil { + return nil, resp, err + } + + return metrics, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/repos_community_health_test.go b/vendor/github.com/google/go-github/github/repos_community_health_test.go new file mode 100644 index 0000000..6092af0 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_community_health_test.go @@ -0,0 +1,86 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestRepositoriesService_GetCommunityHealthMetrics(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/community/profile", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeRepositoryCommunityHealthMetricsPreview) + fmt.Fprintf(w, `{ + "health_percentage": 100, + "files": { + "code_of_conduct": { + "name": "Contributor Covenant", + "key": "contributor_covenant", + "url": null, + "html_url": "https://github.com/octocat/Hello-World/blob/master/CODE_OF_CONDUCT.md" + }, + "contributing": { + "url": "https://api.github.com/repos/octocat/Hello-World/contents/CONTRIBUTING", + "html_url": "https://github.com/octocat/Hello-World/blob/master/CONTRIBUTING" + }, + "license": { + "name": "MIT License", + "key": "mit", + "url": "https://api.github.com/licenses/mit", + "html_url": "https://github.com/octocat/Hello-World/blob/master/LICENSE" + }, + "readme": { + "url": "https://api.github.com/repos/octocat/Hello-World/contents/README.md", + "html_url": "https://github.com/octocat/Hello-World/blob/master/README.md" + } + }, + "updated_at": "2017-02-28T00:00:00Z" + }`) + }) + + got, _, err := client.Repositories.GetCommunityHealthMetrics(context.Background(), "o", "r") + if err != nil { + t.Errorf("Repositories.GetCommunityHealthMetrics returned error: %v", err) + } + + updatedAt := time.Date(2017, time.February, 28, 0, 0, 0, 0, time.UTC) + want := &CommunityHealthMetrics{ + HealthPercentage: Int(100), + UpdatedAt: &updatedAt, + Files: &CommunityHealthFiles{ + CodeOfConduct: &Metric{ + Name: String("Contributor Covenant"), + Key: String("contributor_covenant"), + HTMLURL: String("https://github.com/octocat/Hello-World/blob/master/CODE_OF_CONDUCT.md"), + }, + Contributing: &Metric{ + URL: String("https://api.github.com/repos/octocat/Hello-World/contents/CONTRIBUTING"), + HTMLURL: String("https://github.com/octocat/Hello-World/blob/master/CONTRIBUTING"), + }, + License: &Metric{ + Name: String("MIT License"), + Key: String("mit"), + URL: String("https://api.github.com/licenses/mit"), + HTMLURL: String("https://github.com/octocat/Hello-World/blob/master/LICENSE"), + }, + Readme: &Metric{ + URL: String("https://api.github.com/repos/octocat/Hello-World/contents/README.md"), + HTMLURL: String("https://github.com/octocat/Hello-World/blob/master/README.md"), + }, + }, + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Repositories.GetCommunityHealthMetrics:\ngot:\n%v\nwant:\n%v", Stringify(got), Stringify(want)) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_contents.go b/vendor/github.com/google/go-github/github/repos_contents.go new file mode 100644 index 0000000..ffb56b9 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_contents.go @@ -0,0 +1,266 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Repository contents API methods. +// GitHub API docs: https://developer.github.com/v3/repos/contents/ + +package github + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" +) + +// RepositoryContent represents a file or directory in a github repository. +type RepositoryContent struct { + Type *string `json:"type,omitempty"` + Encoding *string `json:"encoding,omitempty"` + Size *int `json:"size,omitempty"` + Name *string `json:"name,omitempty"` + Path *string `json:"path,omitempty"` + // Content contains the actual file content, which may be encoded. + // Callers should call GetContent which will decode the content if + // necessary. + Content *string `json:"content,omitempty"` + SHA *string `json:"sha,omitempty"` + URL *string `json:"url,omitempty"` + GitURL *string `json:"git_url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + DownloadURL *string `json:"download_url,omitempty"` +} + +// RepositoryContentResponse holds the parsed response from CreateFile, UpdateFile, and DeleteFile. +type RepositoryContentResponse struct { + Content *RepositoryContent `json:"content,omitempty"` + Commit `json:"commit,omitempty"` +} + +// RepositoryContentFileOptions specifies optional parameters for CreateFile, UpdateFile, and DeleteFile. +type RepositoryContentFileOptions struct { + Message *string `json:"message,omitempty"` + Content []byte `json:"content,omitempty"` // unencoded + SHA *string `json:"sha,omitempty"` + Branch *string `json:"branch,omitempty"` + Author *CommitAuthor `json:"author,omitempty"` + Committer *CommitAuthor `json:"committer,omitempty"` +} + +// RepositoryContentGetOptions represents an optional ref parameter, which can be a SHA, +// branch, or tag +type RepositoryContentGetOptions struct { + Ref string `url:"ref,omitempty"` +} + +// String converts RepositoryContent to a string. It's primarily for testing. +func (r RepositoryContent) String() string { + return Stringify(r) +} + +// GetContent returns the content of r, decoding it if necessary. +func (r *RepositoryContent) GetContent() (string, error) { + var encoding string + if r.Encoding != nil { + encoding = *r.Encoding + } + + switch encoding { + case "base64": + c, err := base64.StdEncoding.DecodeString(*r.Content) + return string(c), err + case "": + if r.Content == nil { + return "", nil + } + return *r.Content, nil + default: + return "", fmt.Errorf("unsupported content encoding: %v", encoding) + } +} + +// GetReadme gets the Readme file for the repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/contents/#get-the-readme +func (s *RepositoriesService) GetReadme(ctx context.Context, owner, repo string, opt *RepositoryContentGetOptions) (*RepositoryContent, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/readme", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + readme := new(RepositoryContent) + resp, err := s.client.Do(ctx, req, readme) + if err != nil { + return nil, resp, err + } + return readme, resp, nil +} + +// DownloadContents returns an io.ReadCloser that reads the contents of the +// specified file. This function will work with files of any size, as opposed +// to GetContents which is limited to 1 Mb files. It is the caller's +// responsibility to close the ReadCloser. +func (s *RepositoriesService) DownloadContents(ctx context.Context, owner, repo, filepath string, opt *RepositoryContentGetOptions) (io.ReadCloser, error) { + dir := path.Dir(filepath) + filename := path.Base(filepath) + _, dirContents, _, err := s.GetContents(ctx, owner, repo, dir, opt) + if err != nil { + return nil, err + } + for _, contents := range dirContents { + if *contents.Name == filename { + if contents.DownloadURL == nil || *contents.DownloadURL == "" { + return nil, fmt.Errorf("No download link found for %s", filepath) + } + resp, err := s.client.client.Get(*contents.DownloadURL) + if err != nil { + return nil, err + } + return resp.Body, nil + } + } + return nil, fmt.Errorf("No file named %s found in %s", filename, dir) +} + +// GetContents can return either the metadata and content of a single file +// (when path references a file) or the metadata of all the files and/or +// subdirectories of a directory (when path references a directory). To make it +// easy to distinguish between both result types and to mimic the API as much +// as possible, both result types will be returned but only one will contain a +// value and the other will be nil. +// +// GitHub API docs: https://developer.github.com/v3/repos/contents/#get-contents +func (s *RepositoriesService) GetContents(ctx context.Context, owner, repo, path string, opt *RepositoryContentGetOptions) (fileContent *RepositoryContent, directoryContent []*RepositoryContent, resp *Response, err error) { + escapedPath := (&url.URL{Path: path}).String() + u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, escapedPath) + u, err = addOptions(u, opt) + if err != nil { + return nil, nil, nil, err + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, nil, err + } + var rawJSON json.RawMessage + resp, err = s.client.Do(ctx, req, &rawJSON) + if err != nil { + return nil, nil, resp, err + } + fileUnmarshalError := json.Unmarshal(rawJSON, &fileContent) + if fileUnmarshalError == nil { + return fileContent, nil, resp, nil + } + directoryUnmarshalError := json.Unmarshal(rawJSON, &directoryContent) + if directoryUnmarshalError == nil { + return nil, directoryContent, resp, nil + } + return nil, nil, resp, fmt.Errorf("unmarshalling failed for both file and directory content: %s and %s", fileUnmarshalError, directoryUnmarshalError) +} + +// CreateFile creates a new file in a repository at the given path and returns +// the commit and file metadata. +// +// GitHub API docs: https://developer.github.com/v3/repos/contents/#create-a-file +func (s *RepositoriesService) CreateFile(ctx context.Context, owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, nil, err + } + createResponse := new(RepositoryContentResponse) + resp, err := s.client.Do(ctx, req, createResponse) + if err != nil { + return nil, resp, err + } + return createResponse, resp, nil +} + +// UpdateFile updates a file in a repository at the given path and returns the +// commit and file metadata. Requires the blob SHA of the file being updated. +// +// GitHub API docs: https://developer.github.com/v3/repos/contents/#update-a-file +func (s *RepositoriesService) UpdateFile(ctx context.Context, owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, nil, err + } + updateResponse := new(RepositoryContentResponse) + resp, err := s.client.Do(ctx, req, updateResponse) + if err != nil { + return nil, resp, err + } + return updateResponse, resp, nil +} + +// DeleteFile deletes a file from a repository and returns the commit. +// Requires the blob SHA of the file to be deleted. +// +// GitHub API docs: https://developer.github.com/v3/repos/contents/#delete-a-file +func (s *RepositoriesService) DeleteFile(ctx context.Context, owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path) + req, err := s.client.NewRequest("DELETE", u, opt) + if err != nil { + return nil, nil, err + } + deleteResponse := new(RepositoryContentResponse) + resp, err := s.client.Do(ctx, req, deleteResponse) + if err != nil { + return nil, resp, err + } + return deleteResponse, resp, nil +} + +// archiveFormat is used to define the archive type when calling GetArchiveLink. +type archiveFormat string + +const ( + // Tarball specifies an archive in gzipped tar format. + Tarball archiveFormat = "tarball" + + // Zipball specifies an archive in zip format. + Zipball archiveFormat = "zipball" +) + +// GetArchiveLink returns an URL to download a tarball or zipball archive for a +// repository. The archiveFormat can be specified by either the github.Tarball +// or github.Zipball constant. +// +// GitHub API docs: https://developer.github.com/v3/repos/contents/#get-archive-link +func (s *RepositoriesService) GetArchiveLink(ctx context.Context, owner, repo string, archiveformat archiveFormat, opt *RepositoryContentGetOptions) (*url.URL, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/%s", owner, repo, archiveformat) + if opt != nil && opt.Ref != "" { + u += fmt.Sprintf("/%s", opt.Ref) + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + var resp *http.Response + // Use http.DefaultTransport if no custom Transport is configured + req = withContext(ctx, req) + if s.client.client.Transport == nil { + resp, err = http.DefaultTransport.RoundTrip(req) + } else { + resp, err = s.client.client.Transport.RoundTrip(req) + } + if err != nil { + return nil, nil, err + } + resp.Body.Close() + if resp.StatusCode != http.StatusFound { + return nil, newResponse(resp), fmt.Errorf("unexpected status code: %s", resp.Status) + } + parsedURL, err := url.Parse(resp.Header.Get("Location")) + return parsedURL, newResponse(resp), err +} diff --git a/vendor/github.com/google/go-github/github/repos_contents_test.go b/vendor/github.com/google/go-github/github/repos_contents_test.go new file mode 100644 index 0000000..29bab8d --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_contents_test.go @@ -0,0 +1,389 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "io/ioutil" + "net/http" + "reflect" + "testing" +) + +func TestRepositoryContent_GetContent(t *testing.T) { + tests := []struct { + encoding, content *string // input encoding and content + want string // desired output + wantErr bool // whether an error is expected + }{ + { + encoding: String(""), + content: String("hello"), + want: "hello", + wantErr: false, + }, + { + encoding: nil, + content: String("hello"), + want: "hello", + wantErr: false, + }, + { + encoding: nil, + content: nil, + want: "", + wantErr: false, + }, + { + encoding: String("base64"), + content: String("aGVsbG8="), + want: "hello", + wantErr: false, + }, + { + encoding: String("bad"), + content: String("aGVsbG8="), + want: "", + wantErr: true, + }, + } + + for _, tt := range tests { + r := RepositoryContent{Encoding: tt.encoding, Content: tt.content} + got, err := r.GetContent() + if err != nil && !tt.wantErr { + t.Errorf("RepositoryContent(%s, %s) returned unexpected error: %v", + stringOrNil(tt.encoding), stringOrNil(tt.content), err) + } + if err == nil && tt.wantErr { + t.Errorf("RepositoryContent(%s, %s) did not return unexpected error", + stringOrNil(tt.encoding), stringOrNil(tt.content)) + } + if want := tt.want; got != want { + t.Errorf("RepositoryContent.GetContent returned %+v, want %+v", got, want) + } + } +} + +// stringOrNil converts a potentially null string pointer to string. +// For non-nil input pointer, the returned string is enclosed in double-quotes. +func stringOrNil(s *string) string { + if s == nil { + return "" + } + return fmt.Sprintf("%q", *s) +} + +func TestRepositoriesService_GetReadme(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + mux.HandleFunc("/repos/o/r/readme", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{ + "type": "file", + "encoding": "base64", + "size": 5362, + "name": "README.md", + "path": "README.md" + }`) + }) + readme, _, err := client.Repositories.GetReadme(context.Background(), "o", "r", &RepositoryContentGetOptions{}) + if err != nil { + t.Errorf("Repositories.GetReadme returned error: %v", err) + } + want := &RepositoryContent{Type: String("file"), Name: String("README.md"), Size: Int(5362), Encoding: String("base64"), Path: String("README.md")} + if !reflect.DeepEqual(readme, want) { + t.Errorf("Repositories.GetReadme returned %+v, want %+v", readme, want) + } +} + +func TestRepositoriesService_DownloadContents_Success(t *testing.T) { + client, mux, serverURL, teardown := setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{ + "type": "file", + "name": "f", + "download_url": "`+serverURL+baseURLPath+`/download/f" + }]`) + }) + mux.HandleFunc("/download/f", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, "foo") + }) + + r, err := client.Repositories.DownloadContents(context.Background(), "o", "r", "d/f", nil) + if err != nil { + t.Errorf("Repositories.DownloadContents returned error: %v", err) + } + + bytes, err := ioutil.ReadAll(r) + if err != nil { + t.Errorf("Error reading response body: %v", err) + } + r.Close() + + if got, want := string(bytes), "foo"; got != want { + t.Errorf("Repositories.DownloadContents returned %v, want %v", got, want) + } +} + +func TestRepositoriesService_DownloadContents_NoDownloadURL(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{ + "type": "file", + "name": "f", + }]`) + }) + + _, err := client.Repositories.DownloadContents(context.Background(), "o", "r", "d/f", nil) + if err == nil { + t.Errorf("Repositories.DownloadContents did not return expected error") + } +} + +func TestRepositoriesService_DownloadContents_NoFile(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[]`) + }) + + _, err := client.Repositories.DownloadContents(context.Background(), "o", "r", "d/f", nil) + if err == nil { + t.Errorf("Repositories.DownloadContents did not return expected error") + } +} + +func TestRepositoriesService_GetContents_File(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{ + "type": "file", + "encoding": "base64", + "size": 20678, + "name": "LICENSE", + "path": "LICENSE" + }`) + }) + fileContents, _, _, err := client.Repositories.GetContents(context.Background(), "o", "r", "p", &RepositoryContentGetOptions{}) + if err != nil { + t.Errorf("Repositories.GetContents returned error: %v", err) + } + want := &RepositoryContent{Type: String("file"), Name: String("LICENSE"), Size: Int(20678), Encoding: String("base64"), Path: String("LICENSE")} + if !reflect.DeepEqual(fileContents, want) { + t.Errorf("Repositories.GetContents returned %+v, want %+v", fileContents, want) + } +} + +func TestRepositoriesService_GetContents_FilenameNeedsEscape(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p#?%/中.go", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{}`) + }) + _, _, _, err := client.Repositories.GetContents(context.Background(), "o", "r", "p#?%/中.go", &RepositoryContentGetOptions{}) + if err != nil { + t.Fatalf("Repositories.GetContents returned error: %v", err) + } +} + +func TestRepositoriesService_GetContents_DirectoryWithSpaces(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/some directory/file.go", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{}`) + }) + _, _, _, err := client.Repositories.GetContents(context.Background(), "o", "r", "some directory/file.go", &RepositoryContentGetOptions{}) + if err != nil { + t.Fatalf("Repositories.GetContents returned error: %v", err) + } +} + +func TestRepositoriesService_GetContents_DirectoryWithPlusChars(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/some directory+name/file.go", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{}`) + }) + _, _, _, err := client.Repositories.GetContents(context.Background(), "o", "r", "some directory+name/file.go", &RepositoryContentGetOptions{}) + if err != nil { + t.Fatalf("Repositories.GetContents returned error: %v", err) + } +} + +func TestRepositoriesService_GetContents_Directory(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{ + "type": "dir", + "name": "lib", + "path": "lib" + }, + { + "type": "file", + "size": 20678, + "name": "LICENSE", + "path": "LICENSE" + }]`) + }) + _, directoryContents, _, err := client.Repositories.GetContents(context.Background(), "o", "r", "p", &RepositoryContentGetOptions{}) + if err != nil { + t.Errorf("Repositories.GetContents returned error: %v", err) + } + want := []*RepositoryContent{{Type: String("dir"), Name: String("lib"), Path: String("lib")}, + {Type: String("file"), Name: String("LICENSE"), Size: Int(20678), Path: String("LICENSE")}} + if !reflect.DeepEqual(directoryContents, want) { + t.Errorf("Repositories.GetContents_Directory returned %+v, want %+v", directoryContents, want) + } +} + +func TestRepositoriesService_CreateFile(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + fmt.Fprint(w, `{ + "content":{ + "name":"p" + }, + "commit":{ + "message":"m", + "sha":"f5f369044773ff9c6383c087466d12adb6fa0828" + } + }`) + }) + message := "m" + content := []byte("c") + repositoryContentsOptions := &RepositoryContentFileOptions{ + Message: &message, + Content: content, + Committer: &CommitAuthor{Name: String("n"), Email: String("e")}, + } + createResponse, _, err := client.Repositories.CreateFile(context.Background(), "o", "r", "p", repositoryContentsOptions) + if err != nil { + t.Errorf("Repositories.CreateFile returned error: %v", err) + } + want := &RepositoryContentResponse{ + Content: &RepositoryContent{Name: String("p")}, + Commit: Commit{ + Message: String("m"), + SHA: String("f5f369044773ff9c6383c087466d12adb6fa0828"), + }, + } + if !reflect.DeepEqual(createResponse, want) { + t.Errorf("Repositories.CreateFile returned %+v, want %+v", createResponse, want) + } +} + +func TestRepositoriesService_UpdateFile(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + fmt.Fprint(w, `{ + "content":{ + "name":"p" + }, + "commit":{ + "message":"m", + "sha":"f5f369044773ff9c6383c087466d12adb6fa0828" + } + }`) + }) + message := "m" + content := []byte("c") + sha := "f5f369044773ff9c6383c087466d12adb6fa0828" + repositoryContentsOptions := &RepositoryContentFileOptions{ + Message: &message, + Content: content, + SHA: &sha, + Committer: &CommitAuthor{Name: String("n"), Email: String("e")}, + } + updateResponse, _, err := client.Repositories.UpdateFile(context.Background(), "o", "r", "p", repositoryContentsOptions) + if err != nil { + t.Errorf("Repositories.UpdateFile returned error: %v", err) + } + want := &RepositoryContentResponse{ + Content: &RepositoryContent{Name: String("p")}, + Commit: Commit{ + Message: String("m"), + SHA: String("f5f369044773ff9c6383c087466d12adb6fa0828"), + }, + } + if !reflect.DeepEqual(updateResponse, want) { + t.Errorf("Repositories.UpdateFile returned %+v, want %+v", updateResponse, want) + } +} + +func TestRepositoriesService_DeleteFile(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + fmt.Fprint(w, `{ + "content": null, + "commit":{ + "message":"m", + "sha":"f5f369044773ff9c6383c087466d12adb6fa0828" + } + }`) + }) + message := "m" + sha := "f5f369044773ff9c6383c087466d12adb6fa0828" + repositoryContentsOptions := &RepositoryContentFileOptions{ + Message: &message, + SHA: &sha, + Committer: &CommitAuthor{Name: String("n"), Email: String("e")}, + } + deleteResponse, _, err := client.Repositories.DeleteFile(context.Background(), "o", "r", "p", repositoryContentsOptions) + if err != nil { + t.Errorf("Repositories.DeleteFile returned error: %v", err) + } + want := &RepositoryContentResponse{ + Content: nil, + Commit: Commit{ + Message: String("m"), + SHA: String("f5f369044773ff9c6383c087466d12adb6fa0828"), + }, + } + if !reflect.DeepEqual(deleteResponse, want) { + t.Errorf("Repositories.DeleteFile returned %+v, want %+v", deleteResponse, want) + } +} + +func TestRepositoriesService_GetArchiveLink(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + mux.HandleFunc("/repos/o/r/tarball", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Redirect(w, r, "http://github.com/a", http.StatusFound) + }) + url, resp, err := client.Repositories.GetArchiveLink(context.Background(), "o", "r", Tarball, &RepositoryContentGetOptions{}) + if err != nil { + t.Errorf("Repositories.GetArchiveLink returned error: %v", err) + } + if resp.StatusCode != http.StatusFound { + t.Errorf("Repositories.GetArchiveLink returned status: %d, want %d", resp.StatusCode, http.StatusFound) + } + want := "http://github.com/a" + if url.String() != want { + t.Errorf("Repositories.GetArchiveLink returned %+v, want %+v", url.String(), want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_deployments.go b/vendor/github.com/google/go-github/github/repos_deployments.go new file mode 100644 index 0000000..794c323 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_deployments.go @@ -0,0 +1,224 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" +) + +// Deployment represents a deployment in a repo +type Deployment struct { + URL *string `json:"url,omitempty"` + ID *int64 `json:"id,omitempty"` + SHA *string `json:"sha,omitempty"` + Ref *string `json:"ref,omitempty"` + Task *string `json:"task,omitempty"` + Payload json.RawMessage `json:"payload,omitempty"` + Environment *string `json:"environment,omitempty"` + Description *string `json:"description,omitempty"` + Creator *User `json:"creator,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + StatusesURL *string `json:"statuses_url,omitempty"` + RepositoryURL *string `json:"repository_url,omitempty"` + NodeID *string `json:"node_id,omitempty"` +} + +// DeploymentRequest represents a deployment request +type DeploymentRequest struct { + Ref *string `json:"ref,omitempty"` + Task *string `json:"task,omitempty"` + AutoMerge *bool `json:"auto_merge,omitempty"` + RequiredContexts *[]string `json:"required_contexts,omitempty"` + Payload *string `json:"payload,omitempty"` + Environment *string `json:"environment,omitempty"` + Description *string `json:"description,omitempty"` + TransientEnvironment *bool `json:"transient_environment,omitempty"` + ProductionEnvironment *bool `json:"production_environment,omitempty"` +} + +// DeploymentsListOptions specifies the optional parameters to the +// RepositoriesService.ListDeployments method. +type DeploymentsListOptions struct { + // SHA of the Deployment. + SHA string `url:"sha,omitempty"` + + // List deployments for a given ref. + Ref string `url:"ref,omitempty"` + + // List deployments for a given task. + Task string `url:"task,omitempty"` + + // List deployments for a given environment. + Environment string `url:"environment,omitempty"` + + ListOptions +} + +// ListDeployments lists the deployments of a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/deployments/#list-deployments +func (s *RepositoriesService) ListDeployments(ctx context.Context, owner, repo string, opt *DeploymentsListOptions) ([]*Deployment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/deployments", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var deployments []*Deployment + resp, err := s.client.Do(ctx, req, &deployments) + if err != nil { + return nil, resp, err + } + + return deployments, resp, nil +} + +// GetDeployment returns a single deployment of a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/deployments/#get-a-single-deployment +func (s *RepositoriesService) GetDeployment(ctx context.Context, owner, repo string, deploymentID int64) (*Deployment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/deployments/%v", owner, repo, deploymentID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + deployment := new(Deployment) + resp, err := s.client.Do(ctx, req, deployment) + if err != nil { + return nil, resp, err + } + + return deployment, resp, nil +} + +// CreateDeployment creates a new deployment for a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/deployments/#create-a-deployment +func (s *RepositoriesService) CreateDeployment(ctx context.Context, owner, repo string, request *DeploymentRequest) (*Deployment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/deployments", owner, repo) + + req, err := s.client.NewRequest("POST", u, request) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeDeploymentStatusPreview) + + d := new(Deployment) + resp, err := s.client.Do(ctx, req, d) + if err != nil { + return nil, resp, err + } + + return d, resp, nil +} + +// DeploymentStatus represents the status of a +// particular deployment. +type DeploymentStatus struct { + ID *int64 `json:"id,omitempty"` + // State is the deployment state. + // Possible values are: "pending", "success", "failure", "error", "inactive". + State *string `json:"state,omitempty"` + Creator *User `json:"creator,omitempty"` + Description *string `json:"description,omitempty"` + TargetURL *string `json:"target_url,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + DeploymentURL *string `json:"deployment_url,omitempty"` + RepositoryURL *string `json:"repository_url,omitempty"` + NodeID *string `json:"node_id,omitempty"` +} + +// DeploymentStatusRequest represents a deployment request +type DeploymentStatusRequest struct { + State *string `json:"state,omitempty"` + LogURL *string `json:"log_url,omitempty"` + Description *string `json:"description,omitempty"` + EnvironmentURL *string `json:"environment_url,omitempty"` + AutoInactive *bool `json:"auto_inactive,omitempty"` +} + +// ListDeploymentStatuses lists the statuses of a given deployment of a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/deployments/#list-deployment-statuses +func (s *RepositoriesService) ListDeploymentStatuses(ctx context.Context, owner, repo string, deployment int64, opt *ListOptions) ([]*DeploymentStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/deployments/%v/statuses", owner, repo, deployment) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var statuses []*DeploymentStatus + resp, err := s.client.Do(ctx, req, &statuses) + if err != nil { + return nil, resp, err + } + + return statuses, resp, nil +} + +// GetDeploymentStatus returns a single deployment status of a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/deployments/#get-a-single-deployment-status +func (s *RepositoriesService) GetDeploymentStatus(ctx context.Context, owner, repo string, deploymentID, deploymentStatusID int64) (*DeploymentStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/deployments/%v/statuses/%v", owner, repo, deploymentID, deploymentStatusID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeDeploymentStatusPreview) + + d := new(DeploymentStatus) + resp, err := s.client.Do(ctx, req, d) + if err != nil { + return nil, resp, err + } + + return d, resp, nil +} + +// CreateDeploymentStatus creates a new status for a deployment. +// +// GitHub API docs: https://developer.github.com/v3/repos/deployments/#create-a-deployment-status +func (s *RepositoriesService) CreateDeploymentStatus(ctx context.Context, owner, repo string, deployment int64, request *DeploymentStatusRequest) (*DeploymentStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/deployments/%v/statuses", owner, repo, deployment) + + req, err := s.client.NewRequest("POST", u, request) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeDeploymentStatusPreview) + + d := new(DeploymentStatus) + resp, err := s.client.Do(ctx, req, d) + if err != nil { + return nil, resp, err + } + + return d, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/repos_deployments_test.go b/vendor/github.com/google/go-github/github/repos_deployments_test.go new file mode 100644 index 0000000..f6f333a --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_deployments_test.go @@ -0,0 +1,161 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListDeployments(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/deployments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"environment": "test"}) + fmt.Fprint(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &DeploymentsListOptions{Environment: "test"} + deployments, _, err := client.Repositories.ListDeployments(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListDeployments returned error: %v", err) + } + + want := []*Deployment{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(deployments, want) { + t.Errorf("Repositories.ListDeployments returned %+v, want %+v", deployments, want) + } +} + +func TestRepositoriesService_GetDeployment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/deployments/3", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":3}`) + }) + + deployment, _, err := client.Repositories.GetDeployment(context.Background(), "o", "r", 3) + if err != nil { + t.Errorf("Repositories.GetDeployment returned error: %v", err) + } + + want := &Deployment{ID: Int64(3)} + + if !reflect.DeepEqual(deployment, want) { + t.Errorf("Repositories.GetDeployment returned %+v, want %+v", deployment, want) + } +} + +func TestRepositoriesService_CreateDeployment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &DeploymentRequest{Ref: String("1111"), Task: String("deploy"), TransientEnvironment: Bool(true)} + + mux.HandleFunc("/repos/o/r/deployments", func(w http.ResponseWriter, r *http.Request) { + v := new(DeploymentRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeDeploymentStatusPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"ref": "1111", "task": "deploy"}`) + }) + + deployment, _, err := client.Repositories.CreateDeployment(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("Repositories.CreateDeployment returned error: %v", err) + } + + want := &Deployment{Ref: String("1111"), Task: String("deploy")} + if !reflect.DeepEqual(deployment, want) { + t.Errorf("Repositories.CreateDeployment returned %+v, want %+v", deployment, want) + } +} + +func TestRepositoriesService_ListDeploymentStatuses(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/deployments/1/statuses", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + statutses, _, err := client.Repositories.ListDeploymentStatuses(context.Background(), "o", "r", 1, opt) + if err != nil { + t.Errorf("Repositories.ListDeploymentStatuses returned error: %v", err) + } + + want := []*DeploymentStatus{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(statutses, want) { + t.Errorf("Repositories.ListDeploymentStatuses returned %+v, want %+v", statutses, want) + } +} + +func TestRepositoriesService_GetDeploymentStatus(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/deployments/3/statuses/4", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeDeploymentStatusPreview) + fmt.Fprint(w, `{"id":4}`) + }) + + deploymentStatus, _, err := client.Repositories.GetDeploymentStatus(context.Background(), "o", "r", 3, 4) + if err != nil { + t.Errorf("Repositories.GetDeploymentStatus returned error: %v", err) + } + + want := &DeploymentStatus{ID: Int64(4)} + if !reflect.DeepEqual(deploymentStatus, want) { + t.Errorf("Repositories.GetDeploymentStatus returned %+v, want %+v", deploymentStatus, want) + } +} + +func TestRepositoriesService_CreateDeploymentStatus(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &DeploymentStatusRequest{State: String("inactive"), Description: String("deploy"), AutoInactive: Bool(false)} + + mux.HandleFunc("/repos/o/r/deployments/1/statuses", func(w http.ResponseWriter, r *http.Request) { + v := new(DeploymentStatusRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeDeploymentStatusPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"state": "inactive", "description": "deploy"}`) + }) + + deploymentStatus, _, err := client.Repositories.CreateDeploymentStatus(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.CreateDeploymentStatus returned error: %v", err) + } + + want := &DeploymentStatus{State: String("inactive"), Description: String("deploy")} + if !reflect.DeepEqual(deploymentStatus, want) { + t.Errorf("Repositories.CreateDeploymentStatus returned %+v, want %+v", deploymentStatus, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_forks.go b/vendor/github.com/google/go-github/github/repos_forks.go new file mode 100644 index 0000000..d0bff54 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_forks.go @@ -0,0 +1,89 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// RepositoryListForksOptions specifies the optional parameters to the +// RepositoriesService.ListForks method. +type RepositoryListForksOptions struct { + // How to sort the forks list. Possible values are: newest, oldest, + // watchers. Default is "newest". + Sort string `url:"sort,omitempty"` + + ListOptions +} + +// ListForks lists the forks of the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/forks/#list-forks +func (s *RepositoriesService) ListForks(ctx context.Context, owner, repo string, opt *RepositoryListForksOptions) ([]*Repository, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/forks", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when topics API fully launches. + req.Header.Set("Accept", mediaTypeTopicsPreview) + + var repos []*Repository + resp, err := s.client.Do(ctx, req, &repos) + if err != nil { + return nil, resp, err + } + + return repos, resp, nil +} + +// RepositoryCreateForkOptions specifies the optional parameters to the +// RepositoriesService.CreateFork method. +type RepositoryCreateForkOptions struct { + // The organization to fork the repository into. + Organization string `url:"organization,omitempty"` +} + +// CreateFork creates a fork of the specified repository. +// +// This method might return an *AcceptedError and a status code of +// 202. This is because this is the status that GitHub returns to signify that +// it is now computing creating the fork in a background task. In this event, +// the Repository value will be returned, which includes the details about the pending fork. +// A follow up request, after a delay of a second or so, should result +// in a successful request. +// +// GitHub API docs: https://developer.github.com/v3/repos/forks/#create-a-fork +func (s *RepositoriesService) CreateFork(ctx context.Context, owner, repo string, opt *RepositoryCreateForkOptions) (*Repository, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/forks", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + fork := new(Repository) + resp, err := s.client.Do(ctx, req, fork) + if _, ok := err.(*AcceptedError); ok { + return fork, resp, err + } + if err != nil { + return nil, resp, err + } + + return fork, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/repos_forks_test.go b/vendor/github.com/google/go-github/github/repos_forks_test.go new file mode 100644 index 0000000..065f592 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_forks_test.go @@ -0,0 +1,105 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListForks(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/forks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeTopicsPreview) + testFormValues(t, r, values{ + "sort": "newest", + "page": "3", + }) + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + opt := &RepositoryListForksOptions{ + Sort: "newest", + ListOptions: ListOptions{Page: 3}, + } + repos, _, err := client.Repositories.ListForks(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListForks returned error: %v", err) + } + + want := []*Repository{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.ListForks returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_ListForks_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.ListForks(context.Background(), "%", "r", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_CreateFork(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/forks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testFormValues(t, r, values{"organization": "o"}) + fmt.Fprint(w, `{"id":1}`) + }) + + opt := &RepositoryCreateForkOptions{Organization: "o"} + repo, _, err := client.Repositories.CreateFork(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.CreateFork returned error: %v", err) + } + + want := &Repository{ID: Int64(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.CreateFork returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_CreateFork_deferred(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/forks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testFormValues(t, r, values{"organization": "o"}) + // This response indicates the fork will happen asynchronously. + w.WriteHeader(http.StatusAccepted) + fmt.Fprint(w, `{"id":1}`) + }) + + opt := &RepositoryCreateForkOptions{Organization: "o"} + repo, _, err := client.Repositories.CreateFork(context.Background(), "o", "r", opt) + if _, ok := err.(*AcceptedError); !ok { + t.Errorf("Repositories.CreateFork returned error: %v (want AcceptedError)", err) + } + + want := &Repository{ID: Int64(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.CreateFork returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_CreateFork_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.CreateFork(context.Background(), "%", "r", nil) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/repos_hooks.go b/vendor/github.com/google/go-github/github/repos_hooks.go new file mode 100644 index 0000000..1e9e884 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_hooks.go @@ -0,0 +1,200 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// WebHookPayload represents the data that is received from GitHub when a push +// event hook is triggered. The format of these payloads pre-date most of the +// GitHub v3 API, so there are lots of minor incompatibilities with the types +// defined in the rest of the API. Therefore, several types are duplicated +// here to account for these differences. +// +// GitHub API docs: https://help.github.com/articles/post-receive-hooks +type WebHookPayload struct { + After *string `json:"after,omitempty"` + Before *string `json:"before,omitempty"` + Commits []WebHookCommit `json:"commits,omitempty"` + Compare *string `json:"compare,omitempty"` + Created *bool `json:"created,omitempty"` + Deleted *bool `json:"deleted,omitempty"` + Forced *bool `json:"forced,omitempty"` + HeadCommit *WebHookCommit `json:"head_commit,omitempty"` + Pusher *User `json:"pusher,omitempty"` + Ref *string `json:"ref,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` +} + +func (w WebHookPayload) String() string { + return Stringify(w) +} + +// WebHookCommit represents the commit variant we receive from GitHub in a +// WebHookPayload. +type WebHookCommit struct { + Added []string `json:"added,omitempty"` + Author *WebHookAuthor `json:"author,omitempty"` + Committer *WebHookAuthor `json:"committer,omitempty"` + Distinct *bool `json:"distinct,omitempty"` + ID *string `json:"id,omitempty"` + Message *string `json:"message,omitempty"` + Modified []string `json:"modified,omitempty"` + Removed []string `json:"removed,omitempty"` + Timestamp *time.Time `json:"timestamp,omitempty"` +} + +func (w WebHookCommit) String() string { + return Stringify(w) +} + +// WebHookAuthor represents the author or committer of a commit, as specified +// in a WebHookCommit. The commit author may not correspond to a GitHub User. +type WebHookAuthor struct { + Email *string `json:"email,omitempty"` + Name *string `json:"name,omitempty"` + Username *string `json:"username,omitempty"` +} + +func (w WebHookAuthor) String() string { + return Stringify(w) +} + +// Hook represents a GitHub (web and service) hook for a repository. +type Hook struct { + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + Name *string `json:"name,omitempty"` + URL *string `json:"url,omitempty"` + Events []string `json:"events,omitempty"` + Active *bool `json:"active,omitempty"` + Config map[string]interface{} `json:"config,omitempty"` + ID *int64 `json:"id,omitempty"` +} + +func (h Hook) String() string { + return Stringify(h) +} + +// CreateHook creates a Hook for the specified repository. +// Name and Config are required fields. +// +// GitHub API docs: https://developer.github.com/v3/repos/hooks/#create-a-hook +func (s *RepositoriesService) CreateHook(ctx context.Context, owner, repo string, hook *Hook) (*Hook, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks", owner, repo) + req, err := s.client.NewRequest("POST", u, hook) + if err != nil { + return nil, nil, err + } + + h := new(Hook) + resp, err := s.client.Do(ctx, req, h) + if err != nil { + return nil, resp, err + } + + return h, resp, nil +} + +// ListHooks lists all Hooks for the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/hooks/#list +func (s *RepositoriesService) ListHooks(ctx context.Context, owner, repo string, opt *ListOptions) ([]*Hook, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var hooks []*Hook + resp, err := s.client.Do(ctx, req, &hooks) + if err != nil { + return nil, resp, err + } + + return hooks, resp, nil +} + +// GetHook returns a single specified Hook. +// +// GitHub API docs: https://developer.github.com/v3/repos/hooks/#get-single-hook +func (s *RepositoriesService) GetHook(ctx context.Context, owner, repo string, id int64) (*Hook, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks/%d", owner, repo, id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + h := new(Hook) + resp, err := s.client.Do(ctx, req, h) + if err != nil { + return nil, resp, err + } + + return h, resp, nil +} + +// EditHook updates a specified Hook. +// +// GitHub API docs: https://developer.github.com/v3/repos/hooks/#edit-a-hook +func (s *RepositoriesService) EditHook(ctx context.Context, owner, repo string, id int64, hook *Hook) (*Hook, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks/%d", owner, repo, id) + req, err := s.client.NewRequest("PATCH", u, hook) + if err != nil { + return nil, nil, err + } + h := new(Hook) + resp, err := s.client.Do(ctx, req, h) + if err != nil { + return nil, resp, err + } + + return h, resp, nil +} + +// DeleteHook deletes a specified Hook. +// +// GitHub API docs: https://developer.github.com/v3/repos/hooks/#delete-a-hook +func (s *RepositoriesService) DeleteHook(ctx context.Context, owner, repo string, id int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks/%d", owner, repo, id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// PingHook triggers a 'ping' event to be sent to the Hook. +// +// GitHub API docs: https://developer.github.com/v3/repos/hooks/#ping-a-hook +func (s *RepositoriesService) PingHook(ctx context.Context, owner, repo string, id int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks/%d/pings", owner, repo, id) + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// TestHook triggers a test Hook by github. +// +// GitHub API docs: https://developer.github.com/v3/repos/hooks/#test-a-push-hook +func (s *RepositoriesService) TestHook(ctx context.Context, owner, repo string, id int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks/%d/tests", owner, repo, id) + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/repos_hooks_test.go b/vendor/github.com/google/go-github/github/repos_hooks_test.go new file mode 100644 index 0000000..a143561 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_hooks_test.go @@ -0,0 +1,206 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_CreateHook(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Hook{Name: String("t")} + + mux.HandleFunc("/repos/o/r/hooks", func(w http.ResponseWriter, r *http.Request) { + v := new(Hook) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + hook, _, err := client.Repositories.CreateHook(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("Repositories.CreateHook returned error: %v", err) + } + + want := &Hook{ID: Int64(1)} + if !reflect.DeepEqual(hook, want) { + t.Errorf("Repositories.CreateHook returned %+v, want %+v", hook, want) + } +} + +func TestRepositoriesService_CreateHook_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.CreateHook(context.Background(), "%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_ListHooks(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/hooks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + + hooks, _, err := client.Repositories.ListHooks(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListHooks returned error: %v", err) + } + + want := []*Hook{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(hooks, want) { + t.Errorf("Repositories.ListHooks returned %+v, want %+v", hooks, want) + } +} + +func TestRepositoriesService_ListHooks_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.ListHooks(context.Background(), "%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_GetHook(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/hooks/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + hook, _, err := client.Repositories.GetHook(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetHook returned error: %v", err) + } + + want := &Hook{ID: Int64(1)} + if !reflect.DeepEqual(hook, want) { + t.Errorf("Repositories.GetHook returned %+v, want %+v", hook, want) + } +} + +func TestRepositoriesService_GetHook_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.GetHook(context.Background(), "%", "%", 1) + testURLParseError(t, err) +} + +func TestRepositoriesService_EditHook(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Hook{Name: String("t")} + + mux.HandleFunc("/repos/o/r/hooks/1", func(w http.ResponseWriter, r *http.Request) { + v := new(Hook) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + hook, _, err := client.Repositories.EditHook(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.EditHook returned error: %v", err) + } + + want := &Hook{ID: Int64(1)} + if !reflect.DeepEqual(hook, want) { + t.Errorf("Repositories.EditHook returned %+v, want %+v", hook, want) + } +} + +func TestRepositoriesService_EditHook_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.EditHook(context.Background(), "%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_DeleteHook(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/hooks/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeleteHook(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteHook returned error: %v", err) + } +} + +func TestRepositoriesService_DeleteHook_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Repositories.DeleteHook(context.Background(), "%", "%", 1) + testURLParseError(t, err) +} + +func TestRepositoriesService_PingHook(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/hooks/1/pings", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + }) + + _, err := client.Repositories.PingHook(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.PingHook returned error: %v", err) + } +} + +func TestRepositoriesService_TestHook(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/hooks/1/tests", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + }) + + _, err := client.Repositories.TestHook(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.TestHook returned error: %v", err) + } +} + +func TestRepositoriesService_TestHook_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Repositories.TestHook(context.Background(), "%", "%", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/repos_invitations.go b/vendor/github.com/google/go-github/github/repos_invitations.go new file mode 100644 index 0000000..34bf383 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_invitations.go @@ -0,0 +1,98 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// RepositoryInvitation represents an invitation to collaborate on a repo. +type RepositoryInvitation struct { + ID *int64 `json:"id,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Invitee *User `json:"invitee,omitempty"` + Inviter *User `json:"inviter,omitempty"` + + // Permissions represents the permissions that the associated user will have + // on the repository. Possible values are: "read", "write", "admin". + Permissions *string `json:"permissions,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` +} + +// ListInvitations lists all currently-open repository invitations. +// +// GitHub API docs: https://developer.github.com/v3/repos/invitations/#list-invitations-for-a-repository +func (s *RepositoriesService) ListInvitations(ctx context.Context, owner, repo string, opt *ListOptions) ([]*RepositoryInvitation, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/invitations", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryInvitationsPreview) + + invites := []*RepositoryInvitation{} + resp, err := s.client.Do(ctx, req, &invites) + if err != nil { + return nil, resp, err + } + + return invites, resp, nil +} + +// DeleteInvitation deletes a repository invitation. +// +// GitHub API docs: https://developer.github.com/v3/repos/invitations/#delete-a-repository-invitation +func (s *RepositoriesService) DeleteInvitation(ctx context.Context, owner, repo string, invitationID int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/invitations/%v", owner, repo, invitationID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryInvitationsPreview) + + return s.client.Do(ctx, req, nil) +} + +// UpdateInvitation updates the permissions associated with a repository +// invitation. +// +// permissions represents the permissions that the associated user will have +// on the repository. Possible values are: "read", "write", "admin". +// +// GitHub API docs: https://developer.github.com/v3/repos/invitations/#update-a-repository-invitation +func (s *RepositoriesService) UpdateInvitation(ctx context.Context, owner, repo string, invitationID int64, permissions string) (*RepositoryInvitation, *Response, error) { + opts := &struct { + Permissions string `json:"permissions"` + }{Permissions: permissions} + u := fmt.Sprintf("repos/%v/%v/invitations/%v", owner, repo, invitationID) + req, err := s.client.NewRequest("PATCH", u, opts) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryInvitationsPreview) + + invite := &RepositoryInvitation{} + resp, err := s.client.Do(ctx, req, invite) + if err != nil { + return nil, resp, err + } + + return invite, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/repos_invitations_test.go b/vendor/github.com/google/go-github/github/repos_invitations_test.go new file mode 100644 index 0000000..27bec41 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_invitations_test.go @@ -0,0 +1,74 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListInvitations(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/invitations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeRepositoryInvitationsPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprintf(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + got, _, err := client.Repositories.ListInvitations(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListInvitations returned error: %v", err) + } + + want := []*RepositoryInvitation{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(got, want) { + t.Errorf("Repositories.ListInvitations = %+v, want %+v", got, want) + } +} + +func TestRepositoriesService_DeleteInvitation(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/invitations/2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeRepositoryInvitationsPreview) + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Repositories.DeleteInvitation(context.Background(), "o", "r", 2) + if err != nil { + t.Errorf("Repositories.DeleteInvitation returned error: %v", err) + } +} + +func TestRepositoriesService_UpdateInvitation(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/invitations/2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeRepositoryInvitationsPreview) + fmt.Fprintf(w, `{"id":1}`) + }) + + got, _, err := client.Repositories.UpdateInvitation(context.Background(), "o", "r", 2, "write") + if err != nil { + t.Errorf("Repositories.UpdateInvitation returned error: %v", err) + } + + want := &RepositoryInvitation{ID: Int64(1)} + if !reflect.DeepEqual(got, want) { + t.Errorf("Repositories.UpdateInvitation = %+v, want %+v", got, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_keys.go b/vendor/github.com/google/go-github/github/repos_keys.go new file mode 100644 index 0000000..b484f84 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_keys.go @@ -0,0 +1,111 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// The Key type is defined in users_keys.go + +// ListKeys lists the deploy keys for a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/keys/#list +func (s *RepositoriesService) ListKeys(ctx context.Context, owner string, repo string, opt *ListOptions) ([]*Key, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/keys", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var keys []*Key + resp, err := s.client.Do(ctx, req, &keys) + if err != nil { + return nil, resp, err + } + + return keys, resp, nil +} + +// GetKey fetches a single deploy key. +// +// GitHub API docs: https://developer.github.com/v3/repos/keys/#get +func (s *RepositoriesService) GetKey(ctx context.Context, owner string, repo string, id int64) (*Key, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/keys/%v", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + key := new(Key) + resp, err := s.client.Do(ctx, req, key) + if err != nil { + return nil, resp, err + } + + return key, resp, nil +} + +// CreateKey adds a deploy key for a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/keys/#create +func (s *RepositoriesService) CreateKey(ctx context.Context, owner string, repo string, key *Key) (*Key, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/keys", owner, repo) + + req, err := s.client.NewRequest("POST", u, key) + if err != nil { + return nil, nil, err + } + + k := new(Key) + resp, err := s.client.Do(ctx, req, k) + if err != nil { + return nil, resp, err + } + + return k, resp, nil +} + +// EditKey edits a deploy key. +// +// GitHub API docs: https://developer.github.com/v3/repos/keys/#edit +func (s *RepositoriesService) EditKey(ctx context.Context, owner string, repo string, id int64, key *Key) (*Key, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/keys/%v", owner, repo, id) + + req, err := s.client.NewRequest("PATCH", u, key) + if err != nil { + return nil, nil, err + } + + k := new(Key) + resp, err := s.client.Do(ctx, req, k) + if err != nil { + return nil, resp, err + } + + return k, resp, nil +} + +// DeleteKey deletes a deploy key. +// +// GitHub API docs: https://developer.github.com/v3/repos/keys/#delete +func (s *RepositoriesService) DeleteKey(ctx context.Context, owner string, repo string, id int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/keys/%v", owner, repo, id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/repos_keys_test.go b/vendor/github.com/google/go-github/github/repos_keys_test.go new file mode 100644 index 0000000..75a1735 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_keys_test.go @@ -0,0 +1,169 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListKeys(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/keys", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + keys, _, err := client.Repositories.ListKeys(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListKeys returned error: %v", err) + } + + want := []*Key{{ID: Int64(1)}} + if !reflect.DeepEqual(keys, want) { + t.Errorf("Repositories.ListKeys returned %+v, want %+v", keys, want) + } +} + +func TestRepositoriesService_ListKeys_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.ListKeys(context.Background(), "%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_GetKey(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/keys/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + key, _, err := client.Repositories.GetKey(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetKey returned error: %v", err) + } + + want := &Key{ID: Int64(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Repositories.GetKey returned %+v, want %+v", key, want) + } +} + +func TestRepositoriesService_GetKey_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.GetKey(context.Background(), "%", "%", 1) + testURLParseError(t, err) +} + +func TestRepositoriesService_CreateKey(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Key{Key: String("k"), Title: String("t")} + + mux.HandleFunc("/repos/o/r/keys", func(w http.ResponseWriter, r *http.Request) { + v := new(Key) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + key, _, err := client.Repositories.CreateKey(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("Repositories.GetKey returned error: %v", err) + } + + want := &Key{ID: Int64(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Repositories.GetKey returned %+v, want %+v", key, want) + } +} + +func TestRepositoriesService_CreateKey_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.CreateKey(context.Background(), "%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_EditKey(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Key{Key: String("k"), Title: String("t")} + + mux.HandleFunc("/repos/o/r/keys/1", func(w http.ResponseWriter, r *http.Request) { + v := new(Key) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + key, _, err := client.Repositories.EditKey(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.EditKey returned error: %v", err) + } + + want := &Key{ID: Int64(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Repositories.EditKey returned %+v, want %+v", key, want) + } +} + +func TestRepositoriesService_EditKey_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.EditKey(context.Background(), "%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_DeleteKey(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/keys/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeleteKey(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteKey returned error: %v", err) + } +} + +func TestRepositoriesService_DeleteKey_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Repositories.DeleteKey(context.Background(), "%", "%", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/repos_merging.go b/vendor/github.com/google/go-github/github/repos_merging.go new file mode 100644 index 0000000..04383c1 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_merging.go @@ -0,0 +1,38 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// RepositoryMergeRequest represents a request to merge a branch in a +// repository. +type RepositoryMergeRequest struct { + Base *string `json:"base,omitempty"` + Head *string `json:"head,omitempty"` + CommitMessage *string `json:"commit_message,omitempty"` +} + +// Merge a branch in the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/merging/#perform-a-merge +func (s *RepositoriesService) Merge(ctx context.Context, owner, repo string, request *RepositoryMergeRequest) (*RepositoryCommit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/merges", owner, repo) + req, err := s.client.NewRequest("POST", u, request) + if err != nil { + return nil, nil, err + } + + commit := new(RepositoryCommit) + resp, err := s.client.Do(ctx, req, commit) + if err != nil { + return nil, resp, err + } + + return commit, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/repos_merging_test.go b/vendor/github.com/google/go-github/github/repos_merging_test.go new file mode 100644 index 0000000..086f24b --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_merging_test.go @@ -0,0 +1,48 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_Merge(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &RepositoryMergeRequest{ + Base: String("b"), + Head: String("h"), + CommitMessage: String("c"), + } + + mux.HandleFunc("/repos/o/r/merges", func(w http.ResponseWriter, r *http.Request) { + v := new(RepositoryMergeRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"sha":"s"}`) + }) + + commit, _, err := client.Repositories.Merge(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("Repositories.Merge returned error: %v", err) + } + + want := &RepositoryCommit{SHA: String("s")} + if !reflect.DeepEqual(commit, want) { + t.Errorf("Repositories.Merge returned %+v, want %+v", commit, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_pages.go b/vendor/github.com/google/go-github/github/repos_pages.go new file mode 100644 index 0000000..94a95f2 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_pages.go @@ -0,0 +1,143 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// Pages represents a GitHub Pages site configuration. +type Pages struct { + URL *string `json:"url,omitempty"` + Status *string `json:"status,omitempty"` + CNAME *string `json:"cname,omitempty"` + Custom404 *bool `json:"custom_404,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` +} + +// PagesError represents a build error for a GitHub Pages site. +type PagesError struct { + Message *string `json:"message,omitempty"` +} + +// PagesBuild represents the build information for a GitHub Pages site. +type PagesBuild struct { + URL *string `json:"url,omitempty"` + Status *string `json:"status,omitempty"` + Error *PagesError `json:"error,omitempty"` + Pusher *User `json:"pusher,omitempty"` + Commit *string `json:"commit,omitempty"` + Duration *int `json:"duration,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` +} + +// GetPagesInfo fetches information about a GitHub Pages site. +// +// GitHub API docs: https://developer.github.com/v3/repos/pages/#get-information-about-a-pages-site +func (s *RepositoriesService) GetPagesInfo(ctx context.Context, owner, repo string) (*Pages, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pages", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypePagesPreview) + + site := new(Pages) + resp, err := s.client.Do(ctx, req, site) + if err != nil { + return nil, resp, err + } + + return site, resp, nil +} + +// ListPagesBuilds lists the builds for a GitHub Pages site. +// +// GitHub API docs: https://developer.github.com/v3/repos/pages/#list-pages-builds +func (s *RepositoriesService) ListPagesBuilds(ctx context.Context, owner, repo string, opt *ListOptions) ([]*PagesBuild, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pages/builds", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var pages []*PagesBuild + resp, err := s.client.Do(ctx, req, &pages) + if err != nil { + return nil, resp, err + } + + return pages, resp, nil +} + +// GetLatestPagesBuild fetches the latest build information for a GitHub pages site. +// +// GitHub API docs: https://developer.github.com/v3/repos/pages/#list-latest-pages-build +func (s *RepositoriesService) GetLatestPagesBuild(ctx context.Context, owner, repo string) (*PagesBuild, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pages/builds/latest", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + build := new(PagesBuild) + resp, err := s.client.Do(ctx, req, build) + if err != nil { + return nil, resp, err + } + + return build, resp, nil +} + +// GetPageBuild fetches the specific build information for a GitHub pages site. +// +// GitHub API docs: https://developer.github.com/v3/repos/pages/#list-a-specific-pages-build +func (s *RepositoriesService) GetPageBuild(ctx context.Context, owner, repo string, id int64) (*PagesBuild, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pages/builds/%v", owner, repo, id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + build := new(PagesBuild) + resp, err := s.client.Do(ctx, req, build) + if err != nil { + return nil, resp, err + } + + return build, resp, nil +} + +// RequestPageBuild requests a build of a GitHub Pages site without needing to push new commit. +// +// GitHub API docs: https://developer.github.com/v3/repos/pages/#request-a-page-build +func (s *RepositoriesService) RequestPageBuild(ctx context.Context, owner, repo string) (*PagesBuild, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pages/builds", owner, repo) + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypePagesPreview) + + build := new(PagesBuild) + resp, err := s.client.Do(ctx, req, build) + if err != nil { + return nil, resp, err + } + + return build, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/repos_pages_test.go b/vendor/github.com/google/go-github/github/repos_pages_test.go new file mode 100644 index 0000000..21a00f9 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_pages_test.go @@ -0,0 +1,134 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_GetPagesInfo(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pages", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypePagesPreview) + fmt.Fprint(w, `{"url":"u","status":"s","cname":"c","custom_404":false,"html_url":"h"}`) + }) + + page, _, err := client.Repositories.GetPagesInfo(context.Background(), "o", "r") + if err != nil { + t.Errorf("Repositories.GetPagesInfo returned error: %v", err) + } + + want := &Pages{URL: String("u"), Status: String("s"), CNAME: String("c"), Custom404: Bool(false), HTMLURL: String("h")} + if !reflect.DeepEqual(page, want) { + t.Errorf("Repositories.GetPagesInfo returned %+v, want %+v", page, want) + } +} + +func TestRepositoriesService_ListPagesBuilds(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pages/builds", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"url":"u","status":"s","commit":"c"}]`) + }) + + pages, _, err := client.Repositories.ListPagesBuilds(context.Background(), "o", "r", nil) + if err != nil { + t.Errorf("Repositories.ListPagesBuilds returned error: %v", err) + } + + want := []*PagesBuild{{URL: String("u"), Status: String("s"), Commit: String("c")}} + if !reflect.DeepEqual(pages, want) { + t.Errorf("Repositories.ListPagesBuilds returned %+v, want %+v", pages, want) + } +} + +func TestRepositoriesService_ListPagesBuilds_withOptions(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pages/builds", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[]`) + }) + + _, _, err := client.Repositories.ListPagesBuilds(context.Background(), "o", "r", &ListOptions{Page: 2}) + if err != nil { + t.Errorf("Repositories.ListPagesBuilds returned error: %v", err) + } +} + +func TestRepositoriesService_GetLatestPagesBuild(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pages/builds/latest", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"url":"u","status":"s","commit":"c"}`) + }) + + build, _, err := client.Repositories.GetLatestPagesBuild(context.Background(), "o", "r") + if err != nil { + t.Errorf("Repositories.GetLatestPagesBuild returned error: %v", err) + } + + want := &PagesBuild{URL: String("u"), Status: String("s"), Commit: String("c")} + if !reflect.DeepEqual(build, want) { + t.Errorf("Repositories.GetLatestPagesBuild returned %+v, want %+v", build, want) + } +} + +func TestRepositoriesService_GetPageBuild(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pages/builds/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"url":"u","status":"s","commit":"c"}`) + }) + + build, _, err := client.Repositories.GetPageBuild(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetPageBuild returned error: %v", err) + } + + want := &PagesBuild{URL: String("u"), Status: String("s"), Commit: String("c")} + if !reflect.DeepEqual(build, want) { + t.Errorf("Repositories.GetPageBuild returned %+v, want %+v", build, want) + } +} + +func TestRepositoriesService_RequestPageBuild(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pages/builds", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypePagesPreview) + fmt.Fprint(w, `{"url":"u","status":"s"}`) + }) + + build, _, err := client.Repositories.RequestPageBuild(context.Background(), "o", "r") + if err != nil { + t.Errorf("Repositories.RequestPageBuild returned error: %v", err) + } + + want := &PagesBuild{URL: String("u"), Status: String("s")} + if !reflect.DeepEqual(build, want) { + t.Errorf("Repositories.RequestPageBuild returned %+v, want %+v", build, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_prereceive_hooks.go b/vendor/github.com/google/go-github/github/repos_prereceive_hooks.go new file mode 100644 index 0000000..cab09f7 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_prereceive_hooks.go @@ -0,0 +1,110 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// PreReceiveHook represents a GitHub pre-receive hook for a repository. +type PreReceiveHook struct { + ID *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Enforcement *string `json:"enforcement,omitempty"` + ConfigURL *string `json:"configuration_url,omitempty"` +} + +func (p PreReceiveHook) String() string { + return Stringify(p) +} + +// ListPreReceiveHooks lists all pre-receive hooks for the specified repository. +// +// GitHub API docs: https://developer.github.com/enterprise/2.13/v3/repos/pre_receive_hooks/#list-pre-receive-hooks +func (s *RepositoriesService) ListPreReceiveHooks(ctx context.Context, owner, repo string, opt *ListOptions) ([]*PreReceiveHook, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pre-receive-hooks", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypePreReceiveHooksPreview) + + var hooks []*PreReceiveHook + resp, err := s.client.Do(ctx, req, &hooks) + if err != nil { + return nil, resp, err + } + + return hooks, resp, nil +} + +// GetPreReceiveHook returns a single specified pre-receive hook. +// +// GitHub API docs: https://developer.github.com/enterprise/2.13/v3/repos/pre_receive_hooks/#get-a-single-pre-receive-hook +func (s *RepositoriesService) GetPreReceiveHook(ctx context.Context, owner, repo string, id int64) (*PreReceiveHook, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pre-receive-hooks/%d", owner, repo, id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypePreReceiveHooksPreview) + + h := new(PreReceiveHook) + resp, err := s.client.Do(ctx, req, h) + if err != nil { + return nil, resp, err + } + + return h, resp, nil +} + +// UpdatePreReceiveHook updates a specified pre-receive hook. +// +// GitHub API docs: https://developer.github.com/enterprise/2.13/v3/repos/pre_receive_hooks/#update-pre-receive-hook-enforcement +func (s *RepositoriesService) UpdatePreReceiveHook(ctx context.Context, owner, repo string, id int64, hook *PreReceiveHook) (*PreReceiveHook, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pre-receive-hooks/%d", owner, repo, id) + req, err := s.client.NewRequest("PATCH", u, hook) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypePreReceiveHooksPreview) + + h := new(PreReceiveHook) + resp, err := s.client.Do(ctx, req, h) + if err != nil { + return nil, resp, err + } + + return h, resp, nil +} + +// DeletePreReceiveHook deletes a specified pre-receive hook. +// +// GitHub API docs: https://developer.github.com/enterprise/2.13/v3/repos/pre_receive_hooks/#remove-enforcement-overrides-for-a-pre-receive-hook +func (s *RepositoriesService) DeletePreReceiveHook(ctx context.Context, owner, repo string, id int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/pre-receive-hooks/%d", owner, repo, id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypePreReceiveHooksPreview) + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/repos_prereceive_hooks_test.go b/vendor/github.com/google/go-github/github/repos_prereceive_hooks_test.go new file mode 100644 index 0000000..8660e59 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_prereceive_hooks_test.go @@ -0,0 +1,135 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListPreReceiveHooks(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pre-receive-hooks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypePreReceiveHooksPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + + hooks, _, err := client.Repositories.ListPreReceiveHooks(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListHooks returned error: %v", err) + } + + want := []*PreReceiveHook{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(hooks, want) { + t.Errorf("Repositories.ListPreReceiveHooks returned %+v, want %+v", hooks, want) + } +} + +func TestRepositoriesService_ListPreReceiveHooks_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.ListPreReceiveHooks(context.Background(), "%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_GetPreReceiveHook(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pre-receive-hooks/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypePreReceiveHooksPreview) + fmt.Fprint(w, `{"id":1}`) + }) + + hook, _, err := client.Repositories.GetPreReceiveHook(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetPreReceiveHook returned error: %v", err) + } + + want := &PreReceiveHook{ID: Int64(1)} + if !reflect.DeepEqual(hook, want) { + t.Errorf("Repositories.GetPreReceiveHook returned %+v, want %+v", hook, want) + } +} + +func TestRepositoriesService_GetPreReceiveHook_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.GetPreReceiveHook(context.Background(), "%", "%", 1) + testURLParseError(t, err) +} + +func TestRepositoriesService_UpdatePreReceiveHook(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &PreReceiveHook{Name: String("t")} + + mux.HandleFunc("/repos/o/r/pre-receive-hooks/1", func(w http.ResponseWriter, r *http.Request) { + v := new(PreReceiveHook) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + hook, _, err := client.Repositories.UpdatePreReceiveHook(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.UpdatePreReceiveHook returned error: %v", err) + } + + want := &PreReceiveHook{ID: Int64(1)} + if !reflect.DeepEqual(hook, want) { + t.Errorf("Repositories.UpdatePreReceiveHook returned %+v, want %+v", hook, want) + } +} + +func TestRepositoriesService_PreReceiveHook_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.UpdatePreReceiveHook(context.Background(), "%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_DeletePreReceiveHook(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pre-receive-hooks/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeletePreReceiveHook(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeletePreReceiveHook returned error: %v", err) + } +} + +func TestRepositoriesService_DeletePreReceiveHook_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Repositories.DeletePreReceiveHook(context.Background(), "%", "%", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/repos_projects.go b/vendor/github.com/google/go-github/github/repos_projects.go new file mode 100644 index 0000000..d6486d2 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_projects.go @@ -0,0 +1,69 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ProjectListOptions specifies the optional parameters to the +// OrganizationsService.ListProjects and RepositoriesService.ListProjects methods. +type ProjectListOptions struct { + // Indicates the state of the projects to return. Can be either open, closed, or all. Default: open + State string `url:"state,omitempty"` + + ListOptions +} + +// ListProjects lists the projects for a repo. +// +// GitHub API docs: https://developer.github.com/v3/projects/#list-repository-projects +func (s *RepositoriesService) ListProjects(ctx context.Context, owner, repo string, opt *ProjectListOptions) ([]*Project, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/projects", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + var projects []*Project + resp, err := s.client.Do(ctx, req, &projects) + if err != nil { + return nil, resp, err + } + + return projects, resp, nil +} + +// CreateProject creates a GitHub Project for the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/projects/#create-a-repository-project +func (s *RepositoriesService) CreateProject(ctx context.Context, owner, repo string, opt *ProjectOptions) (*Project, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/projects", owner, repo) + req, err := s.client.NewRequest("POST", u, opt) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeProjectsPreview) + + project := &Project{} + resp, err := s.client.Do(ctx, req, project) + if err != nil { + return nil, resp, err + } + + return project, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/repos_projects_test.go b/vendor/github.com/google/go-github/github/repos_projects_test.go new file mode 100644 index 0000000..2d54eaa --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_projects_test.go @@ -0,0 +1,68 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListProjects(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/projects", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ProjectListOptions{ListOptions: ListOptions{Page: 2}} + projects, _, err := client.Repositories.ListProjects(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListProjects returned error: %v", err) + } + + want := []*Project{{ID: Int64(1)}} + if !reflect.DeepEqual(projects, want) { + t.Errorf("Repositories.ListProjects returned %+v, want %+v", projects, want) + } +} + +func TestRepositoriesService_CreateProject(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &ProjectOptions{Name: String("Project Name"), Body: String("Project body.")} + + mux.HandleFunc("/repos/o/r/projects", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeProjectsPreview) + + v := &ProjectOptions{} + json.NewDecoder(r.Body).Decode(v) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + project, _, err := client.Repositories.CreateProject(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("Repositories.CreateProject returned error: %v", err) + } + + want := &Project{ID: Int64(1)} + if !reflect.DeepEqual(project, want) { + t.Errorf("Repositories.CreateProject returned %+v, want %+v", project, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_releases.go b/vendor/github.com/google/go-github/github/repos_releases.go new file mode 100644 index 0000000..bf58743 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_releases.go @@ -0,0 +1,370 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "errors" + "fmt" + "io" + "mime" + "net/http" + "os" + "path/filepath" + "strings" +) + +// RepositoryRelease represents a GitHub release in a repository. +type RepositoryRelease struct { + TagName *string `json:"tag_name,omitempty"` + TargetCommitish *string `json:"target_commitish,omitempty"` + Name *string `json:"name,omitempty"` + Body *string `json:"body,omitempty"` + Draft *bool `json:"draft,omitempty"` + Prerelease *bool `json:"prerelease,omitempty"` + + // The following fields are not used in CreateRelease or EditRelease: + ID *int64 `json:"id,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + PublishedAt *Timestamp `json:"published_at,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + AssetsURL *string `json:"assets_url,omitempty"` + Assets []ReleaseAsset `json:"assets,omitempty"` + UploadURL *string `json:"upload_url,omitempty"` + ZipballURL *string `json:"zipball_url,omitempty"` + TarballURL *string `json:"tarball_url,omitempty"` + Author *User `json:"author,omitempty"` + NodeID *string `json:"node_id,omitempty"` +} + +func (r RepositoryRelease) String() string { + return Stringify(r) +} + +// ReleaseAsset represents a GitHub release asset in a repository. +type ReleaseAsset struct { + ID *int64 `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + Name *string `json:"name,omitempty"` + Label *string `json:"label,omitempty"` + State *string `json:"state,omitempty"` + ContentType *string `json:"content_type,omitempty"` + Size *int `json:"size,omitempty"` + DownloadCount *int `json:"download_count,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + BrowserDownloadURL *string `json:"browser_download_url,omitempty"` + Uploader *User `json:"uploader,omitempty"` + NodeID *string `json:"node_id,omitempty"` +} + +func (r ReleaseAsset) String() string { + return Stringify(r) +} + +// ListReleases lists the releases for a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository +func (s *RepositoriesService) ListReleases(ctx context.Context, owner, repo string, opt *ListOptions) ([]*RepositoryRelease, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var releases []*RepositoryRelease + resp, err := s.client.Do(ctx, req, &releases) + if err != nil { + return nil, resp, err + } + return releases, resp, nil +} + +// GetRelease fetches a single release. +// +// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-a-single-release +func (s *RepositoriesService) GetRelease(ctx context.Context, owner, repo string, id int64) (*RepositoryRelease, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) + return s.getSingleRelease(ctx, u) +} + +// GetLatestRelease fetches the latest published release for the repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-the-latest-release +func (s *RepositoriesService) GetLatestRelease(ctx context.Context, owner, repo string) (*RepositoryRelease, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/latest", owner, repo) + return s.getSingleRelease(ctx, u) +} + +// GetReleaseByTag fetches a release with the specified tag. +// +// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-a-release-by-tag-name +func (s *RepositoriesService) GetReleaseByTag(ctx context.Context, owner, repo, tag string) (*RepositoryRelease, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/tags/%s", owner, repo, tag) + return s.getSingleRelease(ctx, u) +} + +func (s *RepositoriesService) getSingleRelease(ctx context.Context, url string) (*RepositoryRelease, *Response, error) { + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + release := new(RepositoryRelease) + resp, err := s.client.Do(ctx, req, release) + if err != nil { + return nil, resp, err + } + return release, resp, nil +} + +// repositoryReleaseRequest is a subset of RepositoryRelease and +// is used internally by CreateRelease and EditRelease to pass +// only the known fields for these endpoints. +// +// See https://github.com/google/go-github/issues/992 for more +// information. +type repositoryReleaseRequest struct { + TagName *string `json:"tag_name,omitempty"` + TargetCommitish *string `json:"target_commitish,omitempty"` + Name *string `json:"name,omitempty"` + Body *string `json:"body,omitempty"` + Draft *bool `json:"draft,omitempty"` + Prerelease *bool `json:"prerelease,omitempty"` +} + +// CreateRelease adds a new release for a repository. +// +// Note that only a subset of the release fields are used. +// See RepositoryRelease for more information. +// +// GitHub API docs: https://developer.github.com/v3/repos/releases/#create-a-release +func (s *RepositoriesService) CreateRelease(ctx context.Context, owner, repo string, release *RepositoryRelease) (*RepositoryRelease, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases", owner, repo) + + releaseReq := &repositoryReleaseRequest{ + TagName: release.TagName, + TargetCommitish: release.TargetCommitish, + Name: release.Name, + Body: release.Body, + Draft: release.Draft, + Prerelease: release.Prerelease, + } + + req, err := s.client.NewRequest("POST", u, releaseReq) + if err != nil { + return nil, nil, err + } + + r := new(RepositoryRelease) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + return r, resp, nil +} + +// EditRelease edits a repository release. +// +// Note that only a subset of the release fields are used. +// See RepositoryRelease for more information. +// +// GitHub API docs: https://developer.github.com/v3/repos/releases/#edit-a-release +func (s *RepositoriesService) EditRelease(ctx context.Context, owner, repo string, id int64, release *RepositoryRelease) (*RepositoryRelease, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) + + releaseReq := &repositoryReleaseRequest{ + TagName: release.TagName, + TargetCommitish: release.TargetCommitish, + Name: release.Name, + Body: release.Body, + Draft: release.Draft, + Prerelease: release.Prerelease, + } + + req, err := s.client.NewRequest("PATCH", u, releaseReq) + if err != nil { + return nil, nil, err + } + + r := new(RepositoryRelease) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + return r, resp, nil +} + +// DeleteRelease delete a single release from a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/releases/#delete-a-release +func (s *RepositoriesService) DeleteRelease(ctx context.Context, owner, repo string, id int64) (*Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// ListReleaseAssets lists the release's assets. +// +// GitHub API docs: https://developer.github.com/v3/repos/releases/#list-assets-for-a-release +func (s *RepositoriesService) ListReleaseAssets(ctx context.Context, owner, repo string, id int64, opt *ListOptions) ([]*ReleaseAsset, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var assets []*ReleaseAsset + resp, err := s.client.Do(ctx, req, &assets) + if err != nil { + return nil, resp, err + } + return assets, resp, nil +} + +// GetReleaseAsset fetches a single release asset. +// +// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-a-single-release-asset +func (s *RepositoriesService) GetReleaseAsset(ctx context.Context, owner, repo string, id int64) (*ReleaseAsset, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + asset := new(ReleaseAsset) + resp, err := s.client.Do(ctx, req, asset) + if err != nil { + return nil, resp, err + } + return asset, resp, nil +} + +// DownloadReleaseAsset downloads a release asset or returns a redirect URL. +// +// DownloadReleaseAsset returns an io.ReadCloser that reads the contents of the +// specified release asset. It is the caller's responsibility to close the ReadCloser. +// If a redirect is returned, the redirect URL will be returned as a string instead +// of the io.ReadCloser. Exactly one of rc and redirectURL will be zero. +// +// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-a-single-release-asset +func (s *RepositoriesService) DownloadReleaseAsset(ctx context.Context, owner, repo string, id int64) (rc io.ReadCloser, redirectURL string, err error) { + u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, "", err + } + req.Header.Set("Accept", defaultMediaType) + + s.client.clientMu.Lock() + defer s.client.clientMu.Unlock() + + var loc string + saveRedirect := s.client.client.CheckRedirect + s.client.client.CheckRedirect = func(req *http.Request, via []*http.Request) error { + loc = req.URL.String() + return errors.New("disable redirect") + } + defer func() { s.client.client.CheckRedirect = saveRedirect }() + + req = withContext(ctx, req) + resp, err := s.client.client.Do(req) + if err != nil { + if !strings.Contains(err.Error(), "disable redirect") { + return nil, "", err + } + return nil, loc, nil // Intentionally return no error with valid redirect URL. + } + + if err := CheckResponse(resp); err != nil { + resp.Body.Close() + return nil, "", err + } + + return resp.Body, "", nil +} + +// EditReleaseAsset edits a repository release asset. +// +// GitHub API docs: https://developer.github.com/v3/repos/releases/#edit-a-release-asset +func (s *RepositoriesService) EditReleaseAsset(ctx context.Context, owner, repo string, id int64, release *ReleaseAsset) (*ReleaseAsset, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) + + req, err := s.client.NewRequest("PATCH", u, release) + if err != nil { + return nil, nil, err + } + + asset := new(ReleaseAsset) + resp, err := s.client.Do(ctx, req, asset) + if err != nil { + return nil, resp, err + } + return asset, resp, nil +} + +// DeleteReleaseAsset delete a single release asset from a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/releases/#delete-a-release-asset +func (s *RepositoriesService) DeleteReleaseAsset(ctx context.Context, owner, repo string, id int64) (*Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// UploadReleaseAsset creates an asset by uploading a file into a release repository. +// To upload assets that cannot be represented by an os.File, call NewUploadRequest directly. +// +// GitHub API docs: https://developer.github.com/v3/repos/releases/#upload-a-release-asset +func (s *RepositoriesService) UploadReleaseAsset(ctx context.Context, owner, repo string, id int64, opt *UploadOptions, file *os.File) (*ReleaseAsset, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + stat, err := file.Stat() + if err != nil { + return nil, nil, err + } + if stat.IsDir() { + return nil, nil, errors.New("the asset to upload can't be a directory") + } + + mediaType := mime.TypeByExtension(filepath.Ext(file.Name())) + req, err := s.client.NewUploadRequest(u, file, stat.Size(), mediaType) + if err != nil { + return nil, nil, err + } + + asset := new(ReleaseAsset) + resp, err := s.client.Do(ctx, req, asset) + if err != nil { + return nil, resp, err + } + return asset, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/repos_releases_test.go b/vendor/github.com/google/go-github/github/repos_releases_test.go new file mode 100644 index 0000000..d9a9332 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_releases_test.go @@ -0,0 +1,385 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "reflect" + "strings" + "testing" +) + +func TestRepositoriesService_ListReleases(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + releases, _, err := client.Repositories.ListReleases(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListReleases returned error: %v", err) + } + want := []*RepositoryRelease{{ID: Int64(1)}} + if !reflect.DeepEqual(releases, want) { + t.Errorf("Repositories.ListReleases returned %+v, want %+v", releases, want) + } +} + +func TestRepositoriesService_GetRelease(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1,"author":{"login":"l"}}`) + }) + + release, resp, err := client.Repositories.GetRelease(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetRelease returned error: %v\n%v", err, resp.Body) + } + + want := &RepositoryRelease{ID: Int64(1), Author: &User{Login: String("l")}} + if !reflect.DeepEqual(release, want) { + t.Errorf("Repositories.GetRelease returned %+v, want %+v", release, want) + } +} + +func TestRepositoriesService_GetLatestRelease(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/latest", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":3}`) + }) + + release, resp, err := client.Repositories.GetLatestRelease(context.Background(), "o", "r") + if err != nil { + t.Errorf("Repositories.GetLatestRelease returned error: %v\n%v", err, resp.Body) + } + + want := &RepositoryRelease{ID: Int64(3)} + if !reflect.DeepEqual(release, want) { + t.Errorf("Repositories.GetLatestRelease returned %+v, want %+v", release, want) + } +} + +func TestRepositoriesService_GetReleaseByTag(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/tags/foo", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":13}`) + }) + + release, resp, err := client.Repositories.GetReleaseByTag(context.Background(), "o", "r", "foo") + if err != nil { + t.Errorf("Repositories.GetReleaseByTag returned error: %v\n%v", err, resp.Body) + } + + want := &RepositoryRelease{ID: Int64(13)} + if !reflect.DeepEqual(release, want) { + t.Errorf("Repositories.GetReleaseByTag returned %+v, want %+v", release, want) + } +} + +func TestRepositoriesService_CreateRelease(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &RepositoryRelease{ + Name: String("v1.0"), + // Fields to be removed: + ID: Int64(2), + CreatedAt: &Timestamp{referenceTime}, + PublishedAt: &Timestamp{referenceTime}, + URL: String("http://url/"), + HTMLURL: String("http://htmlurl/"), + AssetsURL: String("http://assetsurl/"), + Assets: []ReleaseAsset{{ID: Int64(5)}}, + UploadURL: String("http://uploadurl/"), + ZipballURL: String("http://zipballurl/"), + TarballURL: String("http://tarballurl/"), + Author: &User{Name: String("octocat")}, + NodeID: String("nodeid"), + } + + mux.HandleFunc("/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) { + v := new(repositoryReleaseRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + want := &repositoryReleaseRequest{Name: String("v1.0")} + if !reflect.DeepEqual(v, want) { + t.Errorf("Request body = %+v, want %+v", v, want) + } + fmt.Fprint(w, `{"id":1}`) + }) + + release, _, err := client.Repositories.CreateRelease(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("Repositories.CreateRelease returned error: %v", err) + } + + want := &RepositoryRelease{ID: Int64(1)} + if !reflect.DeepEqual(release, want) { + t.Errorf("Repositories.CreateRelease returned %+v, want %+v", release, want) + } +} + +func TestRepositoriesService_EditRelease(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &RepositoryRelease{ + Name: String("n"), + // Fields to be removed: + ID: Int64(2), + CreatedAt: &Timestamp{referenceTime}, + PublishedAt: &Timestamp{referenceTime}, + URL: String("http://url/"), + HTMLURL: String("http://htmlurl/"), + AssetsURL: String("http://assetsurl/"), + Assets: []ReleaseAsset{{ID: Int64(5)}}, + UploadURL: String("http://uploadurl/"), + ZipballURL: String("http://zipballurl/"), + TarballURL: String("http://tarballurl/"), + Author: &User{Name: String("octocat")}, + NodeID: String("nodeid"), + } + + mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) { + v := new(repositoryReleaseRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + want := &repositoryReleaseRequest{Name: String("n")} + if !reflect.DeepEqual(v, want) { + t.Errorf("Request body = %+v, want %+v", v, want) + } + fmt.Fprint(w, `{"id":1}`) + }) + + release, _, err := client.Repositories.EditRelease(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.EditRelease returned error: %v", err) + } + want := &RepositoryRelease{ID: Int64(1)} + if !reflect.DeepEqual(release, want) { + t.Errorf("Repositories.EditRelease returned = %+v, want %+v", release, want) + } +} + +func TestRepositoriesService_DeleteRelease(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeleteRelease(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteRelease returned error: %v", err) + } +} + +func TestRepositoriesService_ListReleaseAssets(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/1/assets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + assets, _, err := client.Repositories.ListReleaseAssets(context.Background(), "o", "r", 1, opt) + if err != nil { + t.Errorf("Repositories.ListReleaseAssets returned error: %v", err) + } + want := []*ReleaseAsset{{ID: Int64(1)}} + if !reflect.DeepEqual(assets, want) { + t.Errorf("Repositories.ListReleaseAssets returned %+v, want %+v", assets, want) + } +} + +func TestRepositoriesService_GetReleaseAsset(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + asset, _, err := client.Repositories.GetReleaseAsset(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetReleaseAsset returned error: %v", err) + } + want := &ReleaseAsset{ID: Int64(1)} + if !reflect.DeepEqual(asset, want) { + t.Errorf("Repositories.GetReleaseAsset returned %+v, want %+v", asset, want) + } +} + +func TestRepositoriesService_DownloadReleaseAsset_Stream(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", defaultMediaType) + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Disposition", "attachment; filename=hello-world.txt") + fmt.Fprint(w, "Hello World") + }) + + reader, _, err := client.Repositories.DownloadReleaseAsset(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.DownloadReleaseAsset returned error: %v", err) + } + want := []byte("Hello World") + content, err := ioutil.ReadAll(reader) + if err != nil { + t.Errorf("Repositories.DownloadReleaseAsset returned bad reader: %v", err) + } + if !bytes.Equal(want, content) { + t.Errorf("Repositories.DownloadReleaseAsset returned %+v, want %+v", content, want) + } +} + +func TestRepositoriesService_DownloadReleaseAsset_Redirect(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", defaultMediaType) + http.Redirect(w, r, "/yo", http.StatusFound) + }) + + _, got, err := client.Repositories.DownloadReleaseAsset(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.DownloadReleaseAsset returned error: %v", err) + } + want := "/yo" + if !strings.HasSuffix(got, want) { + t.Errorf("Repositories.DownloadReleaseAsset returned %+v, want %+v", got, want) + } +} + +func TestRepositoriesService_DownloadReleaseAsset_APIError(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", defaultMediaType) + w.WriteHeader(http.StatusNotFound) + fmt.Fprint(w, `{"message":"Not Found","documentation_url":"https://developer.github.com/v3"}`) + }) + + resp, loc, err := client.Repositories.DownloadReleaseAsset(context.Background(), "o", "r", 1) + if err == nil { + t.Error("Repositories.DownloadReleaseAsset did not return an error") + } + + if resp != nil { + resp.Close() + t.Error("Repositories.DownloadReleaseAsset returned stream, want nil") + } + + if loc != "" { + t.Errorf(`Repositories.DownloadReleaseAsset returned "%s", want empty ""`, loc) + } +} + +func TestRepositoriesService_EditReleaseAsset(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &ReleaseAsset{Name: String("n")} + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + v := new(ReleaseAsset) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + fmt.Fprint(w, `{"id":1}`) + }) + + asset, _, err := client.Repositories.EditReleaseAsset(context.Background(), "o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.EditReleaseAsset returned error: %v", err) + } + want := &ReleaseAsset{ID: Int64(1)} + if !reflect.DeepEqual(asset, want) { + t.Errorf("Repositories.EditReleaseAsset returned = %+v, want %+v", asset, want) + } +} + +func TestRepositoriesService_DeleteReleaseAsset(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeleteReleaseAsset(context.Background(), "o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteReleaseAsset returned error: %v", err) + } +} + +func TestRepositoriesService_UploadReleaseAsset(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/1/assets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Content-Type", "text/plain; charset=utf-8") + testHeader(t, r, "Content-Length", "12") + testFormValues(t, r, values{"name": "n"}) + testBody(t, r, "Upload me !\n") + + fmt.Fprintf(w, `{"id":1}`) + }) + + file, dir, err := openTestFile("upload.txt", "Upload me !\n") + if err != nil { + t.Fatalf("Unable to create temp file: %v", err) + } + defer os.RemoveAll(dir) + + opt := &UploadOptions{Name: "n"} + asset, _, err := client.Repositories.UploadReleaseAsset(context.Background(), "o", "r", 1, opt, file) + if err != nil { + t.Errorf("Repositories.UploadReleaseAssert returned error: %v", err) + } + want := &ReleaseAsset{ID: Int64(1)} + if !reflect.DeepEqual(asset, want) { + t.Errorf("Repositories.UploadReleaseAssert returned %+v, want %+v", asset, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_stats.go b/vendor/github.com/google/go-github/github/repos_stats.go new file mode 100644 index 0000000..bb355ae --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_stats.go @@ -0,0 +1,226 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// ContributorStats represents a contributor to a repository and their +// weekly contributions to a given repo. +type ContributorStats struct { + Author *Contributor `json:"author,omitempty"` + Total *int `json:"total,omitempty"` + Weeks []WeeklyStats `json:"weeks,omitempty"` +} + +func (c ContributorStats) String() string { + return Stringify(c) +} + +// WeeklyStats represents the number of additions, deletions and commits +// a Contributor made in a given week. +type WeeklyStats struct { + Week *Timestamp `json:"w,omitempty"` + Additions *int `json:"a,omitempty"` + Deletions *int `json:"d,omitempty"` + Commits *int `json:"c,omitempty"` +} + +func (w WeeklyStats) String() string { + return Stringify(w) +} + +// ListContributorsStats gets a repo's contributor list with additions, +// deletions and commit counts. +// +// If this is the first time these statistics are requested for the given +// repository, this method will return an *AcceptedError and a status code of +// 202. This is because this is the status that GitHub returns to signify that +// it is now computing the requested statistics. A follow up request, after a +// delay of a second or so, should result in a successful request. +// +// GitHub API docs: https://developer.github.com/v3/repos/statistics/#contributors +func (s *RepositoriesService) ListContributorsStats(ctx context.Context, owner, repo string) ([]*ContributorStats, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stats/contributors", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var contributorStats []*ContributorStats + resp, err := s.client.Do(ctx, req, &contributorStats) + if err != nil { + return nil, resp, err + } + + return contributorStats, resp, nil +} + +// WeeklyCommitActivity represents the weekly commit activity for a repository. +// The days array is a group of commits per day, starting on Sunday. +type WeeklyCommitActivity struct { + Days []int `json:"days,omitempty"` + Total *int `json:"total,omitempty"` + Week *Timestamp `json:"week,omitempty"` +} + +func (w WeeklyCommitActivity) String() string { + return Stringify(w) +} + +// ListCommitActivity returns the last year of commit activity +// grouped by week. The days array is a group of commits per day, +// starting on Sunday. +// +// If this is the first time these statistics are requested for the given +// repository, this method will return an *AcceptedError and a status code of +// 202. This is because this is the status that GitHub returns to signify that +// it is now computing the requested statistics. A follow up request, after a +// delay of a second or so, should result in a successful request. +// +// GitHub API docs: https://developer.github.com/v3/repos/statistics/#commit-activity +func (s *RepositoriesService) ListCommitActivity(ctx context.Context, owner, repo string) ([]*WeeklyCommitActivity, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stats/commit_activity", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var weeklyCommitActivity []*WeeklyCommitActivity + resp, err := s.client.Do(ctx, req, &weeklyCommitActivity) + if err != nil { + return nil, resp, err + } + + return weeklyCommitActivity, resp, nil +} + +// ListCodeFrequency returns a weekly aggregate of the number of additions and +// deletions pushed to a repository. Returned WeeklyStats will contain +// additions and deletions, but not total commits. +// +// If this is the first time these statistics are requested for the given +// repository, this method will return an *AcceptedError and a status code of +// 202. This is because this is the status that GitHub returns to signify that +// it is now computing the requested statistics. A follow up request, after a +// delay of a second or so, should result in a successful request. +// +// GitHub API docs: https://developer.github.com/v3/repos/statistics/#code-frequency +func (s *RepositoriesService) ListCodeFrequency(ctx context.Context, owner, repo string) ([]*WeeklyStats, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stats/code_frequency", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var weeks [][]int + resp, err := s.client.Do(ctx, req, &weeks) + + // convert int slices into WeeklyStats + var stats []*WeeklyStats + for _, week := range weeks { + if len(week) != 3 { + continue + } + stat := &WeeklyStats{ + Week: &Timestamp{time.Unix(int64(week[0]), 0)}, + Additions: Int(week[1]), + Deletions: Int(week[2]), + } + stats = append(stats, stat) + } + + return stats, resp, err +} + +// RepositoryParticipation is the number of commits by everyone +// who has contributed to the repository (including the owner) +// as well as the number of commits by the owner themself. +type RepositoryParticipation struct { + All []int `json:"all,omitempty"` + Owner []int `json:"owner,omitempty"` +} + +func (r RepositoryParticipation) String() string { + return Stringify(r) +} + +// ListParticipation returns the total commit counts for the 'owner' +// and total commit counts in 'all'. 'all' is everyone combined, +// including the 'owner' in the last 52 weeks. If you’d like to get +// the commit counts for non-owners, you can subtract 'all' from 'owner'. +// +// The array order is oldest week (index 0) to most recent week. +// +// If this is the first time these statistics are requested for the given +// repository, this method will return an *AcceptedError and a status code of +// 202. This is because this is the status that GitHub returns to signify that +// it is now computing the requested statistics. A follow up request, after a +// delay of a second or so, should result in a successful request. +// +// GitHub API docs: https://developer.github.com/v3/repos/statistics/#participation +func (s *RepositoriesService) ListParticipation(ctx context.Context, owner, repo string) (*RepositoryParticipation, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stats/participation", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + participation := new(RepositoryParticipation) + resp, err := s.client.Do(ctx, req, participation) + if err != nil { + return nil, resp, err + } + + return participation, resp, nil +} + +// PunchCard represents the number of commits made during a given hour of a +// day of the week. +type PunchCard struct { + Day *int // Day of the week (0-6: =Sunday - Saturday). + Hour *int // Hour of day (0-23). + Commits *int // Number of commits. +} + +// ListPunchCard returns the number of commits per hour in each day. +// +// If this is the first time these statistics are requested for the given +// repository, this method will return an *AcceptedError and a status code of +// 202. This is because this is the status that GitHub returns to signify that +// it is now computing the requested statistics. A follow up request, after a +// delay of a second or so, should result in a successful request. +// +// GitHub API docs: https://developer.github.com/v3/repos/statistics/#punch-card +func (s *RepositoriesService) ListPunchCard(ctx context.Context, owner, repo string) ([]*PunchCard, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stats/punch_card", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var results [][]int + resp, err := s.client.Do(ctx, req, &results) + + // convert int slices into Punchcards + var cards []*PunchCard + for _, result := range results { + if len(result) != 3 { + continue + } + card := &PunchCard{ + Day: Int(result[0]), + Hour: Int(result[1]), + Commits: Int(result[2]), + } + cards = append(cards, card) + } + + return cards, resp, err +} diff --git a/vendor/github.com/google/go-github/github/repos_stats_test.go b/vendor/github.com/google/go-github/github/repos_stats_test.go new file mode 100644 index 0000000..e0d7be7 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_stats_test.go @@ -0,0 +1,211 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestRepositoriesService_ListContributorsStats(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stats/contributors", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, ` +[ + { + "author": { + "id": 1 + }, + "total": 135, + "weeks": [ + { + "w": 1367712000, + "a": 6898, + "d": 77, + "c": 10 + } + ] + } +] +`) + }) + + stats, _, err := client.Repositories.ListContributorsStats(context.Background(), "o", "r") + if err != nil { + t.Errorf("RepositoriesService.ListContributorsStats returned error: %v", err) + } + + want := []*ContributorStats{ + { + Author: &Contributor{ + ID: Int64(1), + }, + Total: Int(135), + Weeks: []WeeklyStats{ + { + Week: &Timestamp{time.Date(2013, time.May, 05, 00, 00, 00, 0, time.UTC).Local()}, + Additions: Int(6898), + Deletions: Int(77), + Commits: Int(10), + }, + }, + }, + } + + if !reflect.DeepEqual(stats, want) { + t.Errorf("RepositoriesService.ListContributorsStats returned %+v, want %+v", stats, want) + } +} + +func TestRepositoriesService_ListCommitActivity(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stats/commit_activity", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, ` +[ + { + "days": [0, 3, 26, 20, 39, 1, 0], + "total": 89, + "week": 1336280400 + } +] +`) + }) + + activity, _, err := client.Repositories.ListCommitActivity(context.Background(), "o", "r") + if err != nil { + t.Errorf("RepositoriesService.ListCommitActivity returned error: %v", err) + } + + want := []*WeeklyCommitActivity{ + { + Days: []int{0, 3, 26, 20, 39, 1, 0}, + Total: Int(89), + Week: &Timestamp{time.Date(2012, time.May, 06, 05, 00, 00, 0, time.UTC).Local()}, + }, + } + + if !reflect.DeepEqual(activity, want) { + t.Errorf("RepositoriesService.ListCommitActivity returned %+v, want %+v", activity, want) + } +} + +func TestRepositoriesService_ListCodeFrequency(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stats/code_frequency", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, `[[1302998400, 1124, -435]]`) + }) + + code, _, err := client.Repositories.ListCodeFrequency(context.Background(), "o", "r") + if err != nil { + t.Errorf("RepositoriesService.ListCodeFrequency returned error: %v", err) + } + + want := []*WeeklyStats{{ + Week: &Timestamp{time.Date(2011, time.April, 17, 00, 00, 00, 0, time.UTC).Local()}, + Additions: Int(1124), + Deletions: Int(-435), + }} + + if !reflect.DeepEqual(code, want) { + t.Errorf("RepositoriesService.ListCodeFrequency returned %+v, want %+v", code, want) + } +} + +func TestRepositoriesService_Participation(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stats/participation", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, ` +{ + "all": [ + 11,21,15,2,8,1,8,23,17,21,11,10,33, + 91,38,34,22,23,32,3,43,87,71,18,13,5, + 13,16,66,27,12,45,110,117,13,8,18,9,19, + 26,39,12,20,31,46,91,45,10,24,9,29,7 + ], + "owner": [ + 3,2,3,0,2,0,5,14,7,9,1,5,0, + 48,19,2,0,1,10,2,23,40,35,8,8,2, + 10,6,30,0,2,9,53,104,3,3,10,4,7, + 11,21,4,4,22,26,63,11,2,14,1,10,3 + ] +} +`) + }) + + participation, _, err := client.Repositories.ListParticipation(context.Background(), "o", "r") + if err != nil { + t.Errorf("RepositoriesService.ListParticipation returned error: %v", err) + } + + want := &RepositoryParticipation{ + All: []int{ + 11, 21, 15, 2, 8, 1, 8, 23, 17, 21, 11, 10, 33, + 91, 38, 34, 22, 23, 32, 3, 43, 87, 71, 18, 13, 5, + 13, 16, 66, 27, 12, 45, 110, 117, 13, 8, 18, 9, 19, + 26, 39, 12, 20, 31, 46, 91, 45, 10, 24, 9, 29, 7, + }, + Owner: []int{ + 3, 2, 3, 0, 2, 0, 5, 14, 7, 9, 1, 5, 0, + 48, 19, 2, 0, 1, 10, 2, 23, 40, 35, 8, 8, 2, + 10, 6, 30, 0, 2, 9, 53, 104, 3, 3, 10, 4, 7, + 11, 21, 4, 4, 22, 26, 63, 11, 2, 14, 1, 10, 3, + }, + } + + if !reflect.DeepEqual(participation, want) { + t.Errorf("RepositoriesService.ListParticipation returned %+v, want %+v", participation, want) + } +} + +func TestRepositoriesService_ListPunchCard(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stats/punch_card", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, `[ + [0, 0, 5], + [0, 1, 43], + [0, 2, 21] + ]`) + }) + + card, _, err := client.Repositories.ListPunchCard(context.Background(), "o", "r") + if err != nil { + t.Errorf("RepositoriesService.ListPunchCard returned error: %v", err) + } + + want := []*PunchCard{ + {Day: Int(0), Hour: Int(0), Commits: Int(5)}, + {Day: Int(0), Hour: Int(1), Commits: Int(43)}, + {Day: Int(0), Hour: Int(2), Commits: Int(21)}, + } + + if !reflect.DeepEqual(card, want) { + t.Errorf("RepositoriesService.ListPunchCard returned %+v, want %+v", card, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_statuses.go b/vendor/github.com/google/go-github/github/repos_statuses.go new file mode 100644 index 0000000..f94fdc8 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_statuses.go @@ -0,0 +1,129 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// RepoStatus represents the status of a repository at a particular reference. +type RepoStatus struct { + ID *int64 `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + + // State is the current state of the repository. Possible values are: + // pending, success, error, or failure. + State *string `json:"state,omitempty"` + + // TargetURL is the URL of the page representing this status. It will be + // linked from the GitHub UI to allow users to see the source of the status. + TargetURL *string `json:"target_url,omitempty"` + + // Description is a short high level summary of the status. + Description *string `json:"description,omitempty"` + + // A string label to differentiate this status from the statuses of other systems. + Context *string `json:"context,omitempty"` + + Creator *User `json:"creator,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +func (r RepoStatus) String() string { + return Stringify(r) +} + +// ListStatuses lists the statuses of a repository at the specified +// reference. ref can be a SHA, a branch name, or a tag name. +// +// GitHub API docs: https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref +func (s *RepositoriesService) ListStatuses(ctx context.Context, owner, repo, ref string, opt *ListOptions) ([]*RepoStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v/statuses", owner, repo, ref) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var statuses []*RepoStatus + resp, err := s.client.Do(ctx, req, &statuses) + if err != nil { + return nil, resp, err + } + + return statuses, resp, nil +} + +// CreateStatus creates a new status for a repository at the specified +// reference. Ref can be a SHA, a branch name, or a tag name. +// +// GitHub API docs: https://developer.github.com/v3/repos/statuses/#create-a-status +func (s *RepositoriesService) CreateStatus(ctx context.Context, owner, repo, ref string, status *RepoStatus) (*RepoStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/statuses/%v", owner, repo, ref) + req, err := s.client.NewRequest("POST", u, status) + if err != nil { + return nil, nil, err + } + + repoStatus := new(RepoStatus) + resp, err := s.client.Do(ctx, req, repoStatus) + if err != nil { + return nil, resp, err + } + + return repoStatus, resp, nil +} + +// CombinedStatus represents the combined status of a repository at a particular reference. +type CombinedStatus struct { + // State is the combined state of the repository. Possible values are: + // failure, pending, or success. + State *string `json:"state,omitempty"` + + Name *string `json:"name,omitempty"` + SHA *string `json:"sha,omitempty"` + TotalCount *int `json:"total_count,omitempty"` + Statuses []RepoStatus `json:"statuses,omitempty"` + + CommitURL *string `json:"commit_url,omitempty"` + RepositoryURL *string `json:"repository_url,omitempty"` +} + +func (s CombinedStatus) String() string { + return Stringify(s) +} + +// GetCombinedStatus returns the combined status of a repository at the specified +// reference. ref can be a SHA, a branch name, or a tag name. +// +// GitHub API docs: https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref +func (s *RepositoriesService) GetCombinedStatus(ctx context.Context, owner, repo, ref string, opt *ListOptions) (*CombinedStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v/status", owner, repo, ref) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + status := new(CombinedStatus) + resp, err := s.client.Do(ctx, req, status) + if err != nil { + return nil, resp, err + } + + return status, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/repos_statuses_test.go b/vendor/github.com/google/go-github/github/repos_statuses_test.go new file mode 100644 index 0000000..b556b71 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_statuses_test.go @@ -0,0 +1,103 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListStatuses(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/commits/r/statuses", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + statuses, _, err := client.Repositories.ListStatuses(context.Background(), "o", "r", "r", opt) + if err != nil { + t.Errorf("Repositories.ListStatuses returned error: %v", err) + } + + want := []*RepoStatus{{ID: Int64(1)}} + if !reflect.DeepEqual(statuses, want) { + t.Errorf("Repositories.ListStatuses returned %+v, want %+v", statuses, want) + } +} + +func TestRepositoriesService_ListStatuses_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.ListStatuses(context.Background(), "%", "r", "r", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_CreateStatus(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &RepoStatus{State: String("s"), TargetURL: String("t"), Description: String("d")} + + mux.HandleFunc("/repos/o/r/statuses/r", func(w http.ResponseWriter, r *http.Request) { + v := new(RepoStatus) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + fmt.Fprint(w, `{"id":1}`) + }) + + status, _, err := client.Repositories.CreateStatus(context.Background(), "o", "r", "r", input) + if err != nil { + t.Errorf("Repositories.CreateStatus returned error: %v", err) + } + + want := &RepoStatus{ID: Int64(1)} + if !reflect.DeepEqual(status, want) { + t.Errorf("Repositories.CreateStatus returned %+v, want %+v", status, want) + } +} + +func TestRepositoriesService_CreateStatus_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.CreateStatus(context.Background(), "%", "r", "r", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_GetCombinedStatus(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/commits/r/status", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `{"state":"success", "statuses":[{"id":1}]}`) + }) + + opt := &ListOptions{Page: 2} + status, _, err := client.Repositories.GetCombinedStatus(context.Background(), "o", "r", "r", opt) + if err != nil { + t.Errorf("Repositories.GetCombinedStatus returned error: %v", err) + } + + want := &CombinedStatus{State: String("success"), Statuses: []RepoStatus{{ID: Int64(1)}}} + if !reflect.DeepEqual(status, want) { + t.Errorf("Repositories.GetCombinedStatus returned %+v, want %+v", status, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_test.go b/vendor/github.com/google/go-github/github/repos_test.go new file mode 100644 index 0000000..80aca7a --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_test.go @@ -0,0 +1,1238 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "strings" + "testing" +) + +func TestRepositoriesService_List_authenticatedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + acceptHeaders := []string{mediaTypeCodesOfConductPreview, mediaTypeTopicsPreview} + mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + repos, _, err := client.Repositories.List(context.Background(), "", nil) + if err != nil { + t.Errorf("Repositories.List returned error: %v", err) + } + + want := []*Repository{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.List returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_List_specifiedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + acceptHeaders := []string{mediaTypeCodesOfConductPreview, mediaTypeTopicsPreview} + mux.HandleFunc("/users/u/repos", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + testFormValues(t, r, values{ + "visibility": "public", + "affiliation": "owner,collaborator", + "sort": "created", + "direction": "asc", + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &RepositoryListOptions{ + Visibility: "public", + Affiliation: "owner,collaborator", + Sort: "created", + Direction: "asc", + ListOptions: ListOptions{Page: 2}, + } + repos, _, err := client.Repositories.List(context.Background(), "u", opt) + if err != nil { + t.Errorf("Repositories.List returned error: %v", err) + } + + want := []*Repository{{ID: Int64(1)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.List returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_List_specifiedUser_type(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + acceptHeaders := []string{mediaTypeCodesOfConductPreview, mediaTypeTopicsPreview} + mux.HandleFunc("/users/u/repos", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + testFormValues(t, r, values{ + "type": "owner", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &RepositoryListOptions{ + Type: "owner", + } + repos, _, err := client.Repositories.List(context.Background(), "u", opt) + if err != nil { + t.Errorf("Repositories.List returned error: %v", err) + } + + want := []*Repository{{ID: Int64(1)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.List returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_List_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.List(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_ListByOrg(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + acceptHeaders := []string{mediaTypeCodesOfConductPreview, mediaTypeTopicsPreview} + mux.HandleFunc("/orgs/o/repos", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + testFormValues(t, r, values{ + "type": "forks", + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &RepositoryListByOrgOptions{"forks", ListOptions{Page: 2}} + repos, _, err := client.Repositories.ListByOrg(context.Background(), "o", opt) + if err != nil { + t.Errorf("Repositories.ListByOrg returned error: %v", err) + } + + want := []*Repository{{ID: Int64(1)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.ListByOrg returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_ListByOrg_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.ListByOrg(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_ListAll(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repositories", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "since": "1", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &RepositoryListAllOptions{1} + repos, _, err := client.Repositories.ListAll(context.Background(), opt) + if err != nil { + t.Errorf("Repositories.ListAll returned error: %v", err) + } + + want := []*Repository{{ID: Int64(1)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.ListAll returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_Create_user(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Repository{Name: String("n")} + + mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) { + v := new(Repository) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + repo, _, err := client.Repositories.Create(context.Background(), "", input) + if err != nil { + t.Errorf("Repositories.Create returned error: %v", err) + } + + want := &Repository{ID: Int64(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.Create returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_Create_org(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Repository{Name: String("n")} + + mux.HandleFunc("/orgs/o/repos", func(w http.ResponseWriter, r *http.Request) { + v := new(Repository) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + repo, _, err := client.Repositories.Create(context.Background(), "o", input) + if err != nil { + t.Errorf("Repositories.Create returned error: %v", err) + } + + want := &Repository{ID: Int64(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.Create returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_Create_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.Create(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_Get(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + acceptHeaders := []string{mediaTypeCodesOfConductPreview, mediaTypeTopicsPreview} + mux.HandleFunc("/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + fmt.Fprint(w, `{"id":1,"name":"n","description":"d","owner":{"login":"l"},"license":{"key":"mit"}}`) + }) + + repo, _, err := client.Repositories.Get(context.Background(), "o", "r") + if err != nil { + t.Errorf("Repositories.Get returned error: %v", err) + } + + want := &Repository{ID: Int64(1), Name: String("n"), Description: String("d"), Owner: &User{Login: String("l")}, License: &License{Key: String("mit")}} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.Get returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_GetCodeOfConduct(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/community/code_of_conduct", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeCodesOfConductPreview) + fmt.Fprint(w, `{ + "key": "key", + "name": "name", + "url": "url", + "body": "body"}`, + ) + }) + + coc, _, err := client.Repositories.GetCodeOfConduct(context.Background(), "o", "r") + if err != nil { + t.Errorf("Repositories.GetCodeOfConduct returned error: %v", err) + } + + want := &CodeOfConduct{ + Key: String("key"), + Name: String("name"), + URL: String("url"), + Body: String("body"), + } + + if !reflect.DeepEqual(coc, want) { + t.Errorf("Repositories.Get returned %+v, want %+v", coc, want) + } +} + +func TestRepositoriesService_GetByID(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repositories/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1,"name":"n","description":"d","owner":{"login":"l"},"license":{"key":"mit"}}`) + }) + + repo, _, err := client.Repositories.GetByID(context.Background(), 1) + if err != nil { + t.Fatalf("Repositories.GetByID returned error: %v", err) + } + + want := &Repository{ID: Int64(1), Name: String("n"), Description: String("d"), Owner: &User{Login: String("l")}, License: &License{Key: String("mit")}} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.GetByID returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_Edit(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + i := true + input := &Repository{HasIssues: &i} + + mux.HandleFunc("/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + v := new(Repository) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + fmt.Fprint(w, `{"id":1}`) + }) + + repo, _, err := client.Repositories.Edit(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("Repositories.Edit returned error: %v", err) + } + + want := &Repository{ID: Int64(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.Edit returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_Delete(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.Delete(context.Background(), "o", "r") + if err != nil { + t.Errorf("Repositories.Delete returned error: %v", err) + } +} + +func TestRepositoriesService_Get_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.Get(context.Background(), "%", "r") + testURLParseError(t, err) +} + +func TestRepositoriesService_Edit_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.Edit(context.Background(), "%", "r", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_ListContributors(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/contributors", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "anon": "true", + "page": "2", + }) + fmt.Fprint(w, `[{"contributions":42}]`) + }) + + opts := &ListContributorsOptions{Anon: "true", ListOptions: ListOptions{Page: 2}} + contributors, _, err := client.Repositories.ListContributors(context.Background(), "o", "r", opts) + if err != nil { + t.Errorf("Repositories.ListContributors returned error: %v", err) + } + + want := []*Contributor{{Contributions: Int(42)}} + if !reflect.DeepEqual(contributors, want) { + t.Errorf("Repositories.ListContributors returned %+v, want %+v", contributors, want) + } +} + +func TestRepositoriesService_ListLanguages(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/languages", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"go":1}`) + }) + + languages, _, err := client.Repositories.ListLanguages(context.Background(), "o", "r") + if err != nil { + t.Errorf("Repositories.ListLanguages returned error: %v", err) + } + + want := map[string]int{"go": 1} + if !reflect.DeepEqual(languages, want) { + t.Errorf("Repositories.ListLanguages returned %+v, want %+v", languages, want) + } +} + +func TestRepositoriesService_ListTeams(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/teams", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + teams, _, err := client.Repositories.ListTeams(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListTeams returned error: %v", err) + } + + want := []*Team{{ID: Int64(1)}} + if !reflect.DeepEqual(teams, want) { + t.Errorf("Repositories.ListTeams returned %+v, want %+v", teams, want) + } +} + +func TestRepositoriesService_ListTags(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/tags", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"name":"n", "commit" : {"sha" : "s", "url" : "u"}, "zipball_url": "z", "tarball_url": "t"}]`) + }) + + opt := &ListOptions{Page: 2} + tags, _, err := client.Repositories.ListTags(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListTags returned error: %v", err) + } + + want := []*RepositoryTag{ + { + Name: String("n"), + Commit: &Commit{ + SHA: String("s"), + URL: String("u"), + }, + ZipballURL: String("z"), + TarballURL: String("t"), + }, + } + if !reflect.DeepEqual(tags, want) { + t.Errorf("Repositories.ListTags returned %+v, want %+v", tags, want) + } +} + +func TestRepositoriesService_ListBranches(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + // TODO: remove custom Accept header when this API fully launches + testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"name":"master", "commit" : {"sha" : "a57781", "url" : "https://api.github.com/repos/o/r/commits/a57781"}}]`) + }) + + opt := &ListOptions{Page: 2} + branches, _, err := client.Repositories.ListBranches(context.Background(), "o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListBranches returned error: %v", err) + } + + want := []*Branch{{Name: String("master"), Commit: &RepositoryCommit{SHA: String("a57781"), URL: String("https://api.github.com/repos/o/r/commits/a57781")}}} + if !reflect.DeepEqual(branches, want) { + t.Errorf("Repositories.ListBranches returned %+v, want %+v", branches, want) + } +} + +func TestRepositoriesService_GetBranch(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + // TODO: remove custom Accept header when this API fully launches + testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview) + fmt.Fprint(w, `{"name":"n", "commit":{"sha":"s","commit":{"message":"m"}}, "protected":true}`) + }) + + branch, _, err := client.Repositories.GetBranch(context.Background(), "o", "r", "b") + if err != nil { + t.Errorf("Repositories.GetBranch returned error: %v", err) + } + + want := &Branch{ + Name: String("n"), + Commit: &RepositoryCommit{ + SHA: String("s"), + Commit: &Commit{ + Message: String("m"), + }, + }, + Protected: Bool(true), + } + + if !reflect.DeepEqual(branch, want) { + t.Errorf("Repositories.GetBranch returned %+v, want %+v", branch, want) + } +} + +func TestRepositoriesService_GetBranchProtection(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b/protection", func(w http.ResponseWriter, r *http.Request) { + v := new(ProtectionRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "GET") + // TODO: remove custom Accept header when this API fully launches + testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview) + fmt.Fprintf(w, `{ + "required_status_checks":{ + "strict":true, + "contexts":["continuous-integration"] + }, + "required_pull_request_reviews":{ + "dismissal_restrictions":{ + "users":[{ + "id":3, + "login":"u" + }], + "teams":[{ + "id":4, + "slug":"t" + }] + }, + "dismiss_stale_reviews":true, + "require_code_owner_reviews":true, + "required_approving_review_count":1 + }, + "enforce_admins":{ + "url":"/repos/o/r/branches/b/protection/enforce_admins", + "enabled":true + }, + "restrictions":{ + "users":[{"id":1,"login":"u"}], + "teams":[{"id":2,"slug":"t"}] + } + }`) + }) + + protection, _, err := client.Repositories.GetBranchProtection(context.Background(), "o", "r", "b") + if err != nil { + t.Errorf("Repositories.GetBranchProtection returned error: %v", err) + } + + want := &Protection{ + RequiredStatusChecks: &RequiredStatusChecks{ + Strict: true, + Contexts: []string{"continuous-integration"}, + }, + RequiredPullRequestReviews: &PullRequestReviewsEnforcement{ + DismissStaleReviews: true, + DismissalRestrictions: DismissalRestrictions{ + Users: []*User{ + {Login: String("u"), ID: Int64(3)}, + }, + Teams: []*Team{ + {Slug: String("t"), ID: Int64(4)}, + }, + }, + RequireCodeOwnerReviews: true, + RequiredApprovingReviewCount: 1, + }, + EnforceAdmins: &AdminEnforcement{ + URL: String("/repos/o/r/branches/b/protection/enforce_admins"), + Enabled: true, + }, + Restrictions: &BranchRestrictions{ + Users: []*User{ + {Login: String("u"), ID: Int64(1)}, + }, + Teams: []*Team{ + {Slug: String("t"), ID: Int64(2)}, + }, + }, + } + if !reflect.DeepEqual(protection, want) { + t.Errorf("Repositories.GetBranchProtection returned %+v, want %+v", protection, want) + } +} + +func TestRepositoriesService_UpdateBranchProtection(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &ProtectionRequest{ + RequiredStatusChecks: &RequiredStatusChecks{ + Strict: true, + Contexts: []string{"continuous-integration"}, + }, + RequiredPullRequestReviews: &PullRequestReviewsEnforcementRequest{ + DismissStaleReviews: true, + DismissalRestrictionsRequest: &DismissalRestrictionsRequest{ + Users: &[]string{"uu"}, + Teams: &[]string{"tt"}, + }, + }, + Restrictions: &BranchRestrictionsRequest{ + Users: []string{"u"}, + Teams: []string{"t"}, + }, + } + + mux.HandleFunc("/repos/o/r/branches/b/protection", func(w http.ResponseWriter, r *http.Request) { + v := new(ProtectionRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + // TODO: remove custom Accept header when this API fully launches + testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview) + fmt.Fprintf(w, `{ + "required_status_checks":{ + "strict":true, + "contexts":["continuous-integration"] + }, + "required_pull_request_reviews":{ + "dismissal_restrictions":{ + "users":[{ + "id":3, + "login":"uu" + }], + "teams":[{ + "id":4, + "slug":"tt" + }] + }, + "dismiss_stale_reviews":true, + "require_code_owner_reviews":true + }, + "restrictions":{ + "users":[{"id":1,"login":"u"}], + "teams":[{"id":2,"slug":"t"}] + } + }`) + }) + + protection, _, err := client.Repositories.UpdateBranchProtection(context.Background(), "o", "r", "b", input) + if err != nil { + t.Errorf("Repositories.UpdateBranchProtection returned error: %v", err) + } + + want := &Protection{ + RequiredStatusChecks: &RequiredStatusChecks{ + Strict: true, + Contexts: []string{"continuous-integration"}, + }, + RequiredPullRequestReviews: &PullRequestReviewsEnforcement{ + DismissStaleReviews: true, + DismissalRestrictions: DismissalRestrictions{ + Users: []*User{ + {Login: String("uu"), ID: Int64(3)}, + }, + Teams: []*Team{ + {Slug: String("tt"), ID: Int64(4)}, + }, + }, + RequireCodeOwnerReviews: true, + }, + Restrictions: &BranchRestrictions{ + Users: []*User{ + {Login: String("u"), ID: Int64(1)}, + }, + Teams: []*Team{ + {Slug: String("t"), ID: Int64(2)}, + }, + }, + } + if !reflect.DeepEqual(protection, want) { + t.Errorf("Repositories.UpdateBranchProtection returned %+v, want %+v", protection, want) + } +} + +func TestRepositoriesService_RemoveBranchProtection(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b/protection", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + // TODO: remove custom Accept header when this API fully launches + testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview) + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Repositories.RemoveBranchProtection(context.Background(), "o", "r", "b") + if err != nil { + t.Errorf("Repositories.RemoveBranchProtection returned error: %v", err) + } +} + +func TestRepositoriesService_ListLanguages_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Repositories.ListLanguages(context.Background(), "%", "%") + testURLParseError(t, err) +} + +func TestRepositoriesService_License(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/license", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"name": "LICENSE", "path": "LICENSE", "license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","featured":true}}`) + }) + + got, _, err := client.Repositories.License(context.Background(), "o", "r") + if err != nil { + t.Errorf("Repositories.License returned error: %v", err) + } + + want := &RepositoryLicense{ + Name: String("LICENSE"), + Path: String("LICENSE"), + License: &License{ + Name: String("MIT License"), + Key: String("mit"), + SPDXID: String("MIT"), + URL: String("https://api.github.com/licenses/mit"), + Featured: Bool(true), + }, + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("Repositories.License returned %+v, want %+v", got, want) + } +} + +func TestRepositoriesService_GetRequiredStatusChecks(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b/protection/required_status_checks", func(w http.ResponseWriter, r *http.Request) { + v := new(ProtectionRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "GET") + // TODO: remove custom Accept header when this API fully launches + testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview) + fmt.Fprint(w, `{"strict": true,"contexts": ["x","y","z"]}`) + }) + + checks, _, err := client.Repositories.GetRequiredStatusChecks(context.Background(), "o", "r", "b") + if err != nil { + t.Errorf("Repositories.GetRequiredStatusChecks returned error: %v", err) + } + + want := &RequiredStatusChecks{ + Strict: true, + Contexts: []string{"x", "y", "z"}, + } + if !reflect.DeepEqual(checks, want) { + t.Errorf("Repositories.GetRequiredStatusChecks returned %+v, want %+v", checks, want) + } +} + +func TestRepositoriesService_UpdateRequiredStatusChecks(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &RequiredStatusChecksRequest{ + Strict: Bool(true), + Contexts: []string{"continuous-integration"}, + } + + mux.HandleFunc("/repos/o/r/branches/b/protection/required_status_checks", func(w http.ResponseWriter, r *http.Request) { + v := new(RequiredStatusChecksRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + testHeader(t, r, "Accept", mediaTypeV3) + fmt.Fprintf(w, `{"strict":true,"contexts":["continuous-integration"]}`) + }) + + statusChecks, _, err := client.Repositories.UpdateRequiredStatusChecks(context.Background(), "o", "r", "b", input) + if err != nil { + t.Errorf("Repositories.UpdateRequiredStatusChecks returned error: %v", err) + } + + want := &RequiredStatusChecks{ + Strict: true, + Contexts: []string{"continuous-integration"}, + } + if !reflect.DeepEqual(statusChecks, want) { + t.Errorf("Repositories.UpdateRequiredStatusChecks returned %+v, want %+v", statusChecks, want) + } +} + +func TestRepositoriesService_ListRequiredStatusChecksContexts(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b/protection/required_status_checks/contexts", func(w http.ResponseWriter, r *http.Request) { + v := new(ProtectionRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "GET") + // TODO: remove custom Accept header when this API fully launches + testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview) + fmt.Fprint(w, `["x", "y", "z"]`) + }) + + contexts, _, err := client.Repositories.ListRequiredStatusChecksContexts(context.Background(), "o", "r", "b") + if err != nil { + t.Errorf("Repositories.ListRequiredStatusChecksContexts returned error: %v", err) + } + + want := []string{"x", "y", "z"} + if !reflect.DeepEqual(contexts, want) { + t.Errorf("Repositories.ListRequiredStatusChecksContexts returned %+v, want %+v", contexts, want) + } +} + +func TestRepositoriesService_GetPullRequestReviewEnforcement(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b/protection/required_pull_request_reviews", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + // TODO: remove custom Accept header when this API fully launches + testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview) + fmt.Fprintf(w, `{ + "dismissal_restrictions":{ + "users":[{"id":1,"login":"u"}], + "teams":[{"id":2,"slug":"t"}] + }, + "dismiss_stale_reviews":true, + "require_code_owner_reviews":true, + "required_approving_review_count":1 + }`) + }) + + enforcement, _, err := client.Repositories.GetPullRequestReviewEnforcement(context.Background(), "o", "r", "b") + if err != nil { + t.Errorf("Repositories.GetPullRequestReviewEnforcement returned error: %v", err) + } + + want := &PullRequestReviewsEnforcement{ + DismissStaleReviews: true, + DismissalRestrictions: DismissalRestrictions{ + Users: []*User{ + {Login: String("u"), ID: Int64(1)}, + }, + Teams: []*Team{ + {Slug: String("t"), ID: Int64(2)}, + }, + }, + RequireCodeOwnerReviews: true, + RequiredApprovingReviewCount: 1, + } + + if !reflect.DeepEqual(enforcement, want) { + t.Errorf("Repositories.GetPullRequestReviewEnforcement returned %+v, want %+v", enforcement, want) + } +} + +func TestRepositoriesService_UpdatePullRequestReviewEnforcement(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &PullRequestReviewsEnforcementUpdate{ + DismissalRestrictionsRequest: &DismissalRestrictionsRequest{ + Users: &[]string{"u"}, + Teams: &[]string{"t"}, + }, + } + + mux.HandleFunc("/repos/o/r/branches/b/protection/required_pull_request_reviews", func(w http.ResponseWriter, r *http.Request) { + v := new(PullRequestReviewsEnforcementUpdate) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + // TODO: remove custom Accept header when this API fully launches + testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview) + fmt.Fprintf(w, `{ + "dismissal_restrictions":{ + "users":[{"id":1,"login":"u"}], + "teams":[{"id":2,"slug":"t"}] + }, + "dismiss_stale_reviews":true, + "require_code_owner_reviews":true, + "required_approving_review_count":3 + }`) + }) + + enforcement, _, err := client.Repositories.UpdatePullRequestReviewEnforcement(context.Background(), "o", "r", "b", input) + if err != nil { + t.Errorf("Repositories.UpdatePullRequestReviewEnforcement returned error: %v", err) + } + + want := &PullRequestReviewsEnforcement{ + DismissStaleReviews: true, + DismissalRestrictions: DismissalRestrictions{ + Users: []*User{ + {Login: String("u"), ID: Int64(1)}, + }, + Teams: []*Team{ + {Slug: String("t"), ID: Int64(2)}, + }, + }, + RequireCodeOwnerReviews: true, + RequiredApprovingReviewCount: 3, + } + if !reflect.DeepEqual(enforcement, want) { + t.Errorf("Repositories.UpdatePullRequestReviewEnforcement returned %+v, want %+v", enforcement, want) + } +} + +func TestRepositoriesService_DisableDismissalRestrictions(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b/protection/required_pull_request_reviews", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + // TODO: remove custom Accept header when this API fully launches + testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview) + testBody(t, r, `{"dismissal_restrictions":[]}`+"\n") + fmt.Fprintf(w, `{"dismissal_restrictions":{"users":[],"teams":[]},"dismiss_stale_reviews":true,"require_code_owner_reviews":true,"required_approving_review_count":1}`) + }) + + enforcement, _, err := client.Repositories.DisableDismissalRestrictions(context.Background(), "o", "r", "b") + if err != nil { + t.Errorf("Repositories.DisableDismissalRestrictions returned error: %v", err) + } + + want := &PullRequestReviewsEnforcement{ + DismissStaleReviews: true, + DismissalRestrictions: DismissalRestrictions{ + Users: []*User{}, + Teams: []*Team{}, + }, + RequireCodeOwnerReviews: true, + RequiredApprovingReviewCount: 1, + } + if !reflect.DeepEqual(enforcement, want) { + t.Errorf("Repositories.DisableDismissalRestrictions returned %+v, want %+v", enforcement, want) + } +} + +func TestRepositoriesService_RemovePullRequestReviewEnforcement(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b/protection/required_pull_request_reviews", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview) + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Repositories.RemovePullRequestReviewEnforcement(context.Background(), "o", "r", "b") + if err != nil { + t.Errorf("Repositories.RemovePullRequestReviewEnforcement returned error: %v", err) + } +} + +func TestRepositoriesService_GetAdminEnforcement(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b/protection/enforce_admins", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview) + fmt.Fprintf(w, `{"url":"/repos/o/r/branches/b/protection/enforce_admins","enabled":true}`) + }) + + enforcement, _, err := client.Repositories.GetAdminEnforcement(context.Background(), "o", "r", "b") + if err != nil { + t.Errorf("Repositories.GetAdminEnforcement returned error: %v", err) + } + + want := &AdminEnforcement{ + URL: String("/repos/o/r/branches/b/protection/enforce_admins"), + Enabled: true, + } + + if !reflect.DeepEqual(enforcement, want) { + t.Errorf("Repositories.GetAdminEnforcement returned %+v, want %+v", enforcement, want) + } +} + +func TestRepositoriesService_AddAdminEnforcement(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b/protection/enforce_admins", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview) + fmt.Fprintf(w, `{"url":"/repos/o/r/branches/b/protection/enforce_admins","enabled":true}`) + }) + + enforcement, _, err := client.Repositories.AddAdminEnforcement(context.Background(), "o", "r", "b") + if err != nil { + t.Errorf("Repositories.AddAdminEnforcement returned error: %v", err) + } + + want := &AdminEnforcement{ + URL: String("/repos/o/r/branches/b/protection/enforce_admins"), + Enabled: true, + } + if !reflect.DeepEqual(enforcement, want) { + t.Errorf("Repositories.AddAdminEnforcement returned %+v, want %+v", enforcement, want) + } +} + +func TestRepositoriesService_RemoveAdminEnforcement(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b/protection/enforce_admins", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview) + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Repositories.RemoveAdminEnforcement(context.Background(), "o", "r", "b") + if err != nil { + t.Errorf("Repositories.RemoveAdminEnforcement returned error: %v", err) + } +} + +func TestPullRequestReviewsEnforcementRequest_MarshalJSON_nilDismissalRestirctions(t *testing.T) { + req := PullRequestReviewsEnforcementRequest{} + + got, err := json.Marshal(req) + if err != nil { + t.Errorf("PullRequestReviewsEnforcementRequest.MarshalJSON returned error: %v", err) + } + + want := `{"dismiss_stale_reviews":false,"require_code_owner_reviews":false,"required_approving_review_count":0}` + if want != string(got) { + t.Errorf("PullRequestReviewsEnforcementRequest.MarshalJSON returned %+v, want %+v", string(got), want) + } + + req = PullRequestReviewsEnforcementRequest{ + DismissalRestrictionsRequest: &DismissalRestrictionsRequest{}, + } + + got, err = json.Marshal(req) + if err != nil { + t.Errorf("PullRequestReviewsEnforcementRequest.MarshalJSON returned error: %v", err) + } + + want = `{"dismissal_restrictions":{},"dismiss_stale_reviews":false,"require_code_owner_reviews":false,"required_approving_review_count":0}` + if want != string(got) { + t.Errorf("PullRequestReviewsEnforcementRequest.MarshalJSON returned %+v, want %+v", string(got), want) + } + + req = PullRequestReviewsEnforcementRequest{ + DismissalRestrictionsRequest: &DismissalRestrictionsRequest{ + Users: &[]string{}, + Teams: &[]string{}, + }, + } + + got, err = json.Marshal(req) + if err != nil { + t.Errorf("PullRequestReviewsEnforcementRequest.MarshalJSON returned error: %v", err) + } + + want = `{"dismissal_restrictions":{"users":[],"teams":[]},"dismiss_stale_reviews":false,"require_code_owner_reviews":false,"required_approving_review_count":0}` + if want != string(got) { + t.Errorf("PullRequestReviewsEnforcementRequest.MarshalJSON returned %+v, want %+v", string(got), want) + } +} + +func TestRepositoriesService_ListAllTopics(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/topics", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeTopicsPreview) + fmt.Fprint(w, `{"names":["go", "go-github", "github"]}`) + }) + + got, _, err := client.Repositories.ListAllTopics(context.Background(), "o", "r") + if err != nil { + t.Fatalf("Repositories.ListAllTopics returned error: %v", err) + } + + want := []string{"go", "go-github", "github"} + if !reflect.DeepEqual(got, want) { + t.Errorf("Repositories.ListAllTopics returned %+v, want %+v", got, want) + } +} + +func TestRepositoriesService_ListAllTopics_emptyTopics(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/topics", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeTopicsPreview) + fmt.Fprint(w, `{"names":[]}`) + }) + + got, _, err := client.Repositories.ListAllTopics(context.Background(), "o", "r") + if err != nil { + t.Fatalf("Repositories.ListAllTopics returned error: %v", err) + } + + want := []string{} + if !reflect.DeepEqual(got, want) { + t.Errorf("Repositories.ListAllTopics returned %+v, want %+v", got, want) + } +} + +func TestRepositoriesService_ReplaceAllTopics(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/topics", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testHeader(t, r, "Accept", mediaTypeTopicsPreview) + fmt.Fprint(w, `{"names":["go", "go-github", "github"]}`) + }) + + got, _, err := client.Repositories.ReplaceAllTopics(context.Background(), "o", "r", []string{"go", "go-github", "github"}) + if err != nil { + t.Fatalf("Repositories.ReplaceAllTopics returned error: %v", err) + } + + want := []string{"go", "go-github", "github"} + if !reflect.DeepEqual(got, want) { + t.Errorf("Repositories.ReplaceAllTopics returned %+v, want %+v", got, want) + } +} + +func TestRepositoriesService_ReplaceAllTopics_nilSlice(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/topics", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testHeader(t, r, "Accept", mediaTypeTopicsPreview) + testBody(t, r, `{"names":[]}`+"\n") + fmt.Fprint(w, `{"names":[]}`) + }) + + got, _, err := client.Repositories.ReplaceAllTopics(context.Background(), "o", "r", nil) + if err != nil { + t.Fatalf("Repositories.ReplaceAllTopics returned error: %v", err) + } + + want := []string{} + if !reflect.DeepEqual(got, want) { + t.Errorf("Repositories.ReplaceAllTopics returned %+v, want %+v", got, want) + } +} + +func TestRepositoriesService_ReplaceAllTopics_emptySlice(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/topics", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testHeader(t, r, "Accept", mediaTypeTopicsPreview) + testBody(t, r, `{"names":[]}`+"\n") + fmt.Fprint(w, `{"names":[]}`) + }) + + got, _, err := client.Repositories.ReplaceAllTopics(context.Background(), "o", "r", []string{}) + if err != nil { + t.Fatalf("Repositories.ReplaceAllTopics returned error: %v", err) + } + + want := []string{} + if !reflect.DeepEqual(got, want) { + t.Errorf("Repositories.ReplaceAllTopics returned %+v, want %+v", got, want) + } +} + +func TestRepositoriesService_Transfer(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := TransferRequest{NewOwner: "a", TeamID: []int64{123}} + + mux.HandleFunc("/repos/o/r/transfer", func(w http.ResponseWriter, r *http.Request) { + var v TransferRequest + json.NewDecoder(r.Body).Decode(&v) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeRepositoryTransferPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"owner":{"login":"a"}}`) + }) + + got, _, err := client.Repositories.Transfer(context.Background(), "o", "r", input) + if err != nil { + t.Errorf("Repositories.Transfer returned error: %v", err) + } + + want := &Repository{Owner: &User{Login: String("a")}} + if !reflect.DeepEqual(got, want) { + t.Errorf("Repositories.Transfer returned %+v, want %+v", got, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_traffic.go b/vendor/github.com/google/go-github/github/repos_traffic.go new file mode 100644 index 0000000..fb1c976 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_traffic.go @@ -0,0 +1,141 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// TrafficReferrer represent information about traffic from a referrer . +type TrafficReferrer struct { + Referrer *string `json:"referrer,omitempty"` + Count *int `json:"count,omitempty"` + Uniques *int `json:"uniques,omitempty"` +} + +// TrafficPath represent information about the traffic on a path of the repo. +type TrafficPath struct { + Path *string `json:"path,omitempty"` + Title *string `json:"title,omitempty"` + Count *int `json:"count,omitempty"` + Uniques *int `json:"uniques,omitempty"` +} + +// TrafficData represent information about a specific timestamp in views or clones list. +type TrafficData struct { + Timestamp *Timestamp `json:"timestamp,omitempty"` + Count *int `json:"count,omitempty"` + Uniques *int `json:"uniques,omitempty"` +} + +// TrafficViews represent information about the number of views in the last 14 days. +type TrafficViews struct { + Views []*TrafficData `json:"views,omitempty"` + Count *int `json:"count,omitempty"` + Uniques *int `json:"uniques,omitempty"` +} + +// TrafficClones represent information about the number of clones in the last 14 days. +type TrafficClones struct { + Clones []*TrafficData `json:"clones,omitempty"` + Count *int `json:"count,omitempty"` + Uniques *int `json:"uniques,omitempty"` +} + +// TrafficBreakdownOptions specifies the parameters to methods that support breakdown per day or week. +// Can be one of: day, week. Default: day. +type TrafficBreakdownOptions struct { + Per string `url:"per,omitempty"` +} + +// ListTrafficReferrers list the top 10 referrers over the last 14 days. +// +// GitHub API docs: https://developer.github.com/v3/repos/traffic/#list-referrers +func (s *RepositoriesService) ListTrafficReferrers(ctx context.Context, owner, repo string) ([]*TrafficReferrer, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/traffic/popular/referrers", owner, repo) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var trafficReferrers []*TrafficReferrer + resp, err := s.client.Do(ctx, req, &trafficReferrers) + if err != nil { + return nil, resp, err + } + + return trafficReferrers, resp, nil +} + +// ListTrafficPaths list the top 10 popular content over the last 14 days. +// +// GitHub API docs: https://developer.github.com/v3/repos/traffic/#list-paths +func (s *RepositoriesService) ListTrafficPaths(ctx context.Context, owner, repo string) ([]*TrafficPath, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/traffic/popular/paths", owner, repo) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var paths []*TrafficPath + resp, err := s.client.Do(ctx, req, &paths) + if err != nil { + return nil, resp, err + } + + return paths, resp, nil +} + +// ListTrafficViews get total number of views for the last 14 days and breaks it down either per day or week. +// +// GitHub API docs: https://developer.github.com/v3/repos/traffic/#views +func (s *RepositoriesService) ListTrafficViews(ctx context.Context, owner, repo string, opt *TrafficBreakdownOptions) (*TrafficViews, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/traffic/views", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + trafficViews := new(TrafficViews) + resp, err := s.client.Do(ctx, req, &trafficViews) + if err != nil { + return nil, resp, err + } + + return trafficViews, resp, nil +} + +// ListTrafficClones get total number of clones for the last 14 days and breaks it down either per day or week for the last 14 days. +// +// GitHub API docs: https://developer.github.com/v3/repos/traffic/#views +func (s *RepositoriesService) ListTrafficClones(ctx context.Context, owner, repo string, opt *TrafficBreakdownOptions) (*TrafficClones, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/traffic/clones", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + trafficClones := new(TrafficClones) + resp, err := s.client.Do(ctx, req, &trafficClones) + if err != nil { + return nil, resp, err + } + + return trafficClones, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/repos_traffic_test.go b/vendor/github.com/google/go-github/github/repos_traffic_test.go new file mode 100644 index 0000000..0a2eb4f --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_traffic_test.go @@ -0,0 +1,145 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestRepositoriesService_ListTrafficReferrers(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/traffic/popular/referrers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprintf(w, `[{ + "referrer": "Google", + "count": 4, + "uniques": 3 + }]`) + }) + referrers, _, err := client.Repositories.ListTrafficReferrers(context.Background(), "o", "r") + if err != nil { + t.Errorf("Repositories.ListPaths returned error: %+v", err) + } + + want := []*TrafficReferrer{{ + Referrer: String("Google"), + Count: Int(4), + Uniques: Int(3), + }} + if !reflect.DeepEqual(referrers, want) { + t.Errorf("Repositories.ListReferrers returned %+v, want %+v", referrers, want) + } + +} + +func TestRepositoriesService_ListTrafficPaths(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/traffic/popular/paths", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprintf(w, `[{ + "path": "/github/hubot", + "title": "github/hubot: A customizable life embetterment robot.", + "count": 3542, + "uniques": 2225 + }]`) + }) + paths, _, err := client.Repositories.ListTrafficPaths(context.Background(), "o", "r") + if err != nil { + t.Errorf("Repositories.ListPaths returned error: %+v", err) + } + + want := []*TrafficPath{{ + Path: String("/github/hubot"), + Title: String("github/hubot: A customizable life embetterment robot."), + Count: Int(3542), + Uniques: Int(2225), + }} + if !reflect.DeepEqual(paths, want) { + t.Errorf("Repositories.ListPaths returned %+v, want %+v", paths, want) + } + +} + +func TestRepositoriesService_ListTrafficViews(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/traffic/views", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprintf(w, `{"count": 7, + "uniques": 6, + "views": [{ + "timestamp": "2016-05-31T16:00:00.000Z", + "count": 7, + "uniques": 6 + }]}`) + }) + + views, _, err := client.Repositories.ListTrafficViews(context.Background(), "o", "r", nil) + if err != nil { + t.Errorf("Repositories.ListPaths returned error: %+v", err) + } + + want := &TrafficViews{ + Views: []*TrafficData{{ + Timestamp: &Timestamp{time.Date(2016, time.May, 31, 16, 0, 0, 0, time.UTC)}, + Count: Int(7), + Uniques: Int(6), + }}, + Count: Int(7), + Uniques: Int(6), + } + + if !reflect.DeepEqual(views, want) { + t.Errorf("Repositories.ListViews returned %+v, want %+v", views, want) + } + +} + +func TestRepositoriesService_ListTrafficClones(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/traffic/clones", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprintf(w, `{"count": 7, + "uniques": 6, + "clones": [{ + "timestamp": "2016-05-31T16:00:00.00Z", + "count": 7, + "uniques": 6 + }]}`) + }) + + clones, _, err := client.Repositories.ListTrafficClones(context.Background(), "o", "r", nil) + if err != nil { + t.Errorf("Repositories.ListPaths returned error: %+v", err) + } + + want := &TrafficClones{ + Clones: []*TrafficData{{ + Timestamp: &Timestamp{time.Date(2016, time.May, 31, 16, 0, 0, 0, time.UTC)}, + Count: Int(7), + Uniques: Int(6), + }}, + Count: Int(7), + Uniques: Int(6), + } + + if !reflect.DeepEqual(clones, want) { + t.Errorf("Repositories.ListViews returned %+v, want %+v", clones, want) + } + +} diff --git a/vendor/github.com/google/go-github/github/search.go b/vendor/github.com/google/go-github/github/search.go new file mode 100644 index 0000000..abaf5e1 --- /dev/null +++ b/vendor/github.com/google/go-github/github/search.go @@ -0,0 +1,261 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/url" + "strconv" + "strings" + + qs "github.com/google/go-querystring/query" +) + +// SearchService provides access to the search related functions +// in the GitHub API. +// +// Each method takes a query string defining the search keywords and any search qualifiers. +// For example, when searching issues, the query "gopher is:issue language:go" will search +// for issues containing the word "gopher" in Go repositories. The method call +// opts := &github.SearchOptions{Sort: "created", Order: "asc"} +// cl.Search.Issues(ctx, "gopher is:issue language:go", opts) +// will search for such issues, sorting by creation date in ascending order +// (i.e., oldest first). +// +// GitHub API docs: https://developer.github.com/v3/search/ +type SearchService service + +// SearchOptions specifies optional parameters to the SearchService methods. +type SearchOptions struct { + // How to sort the search results. Possible values are: + // - for repositories: stars, fork, updated + // - for commits: author-date, committer-date + // - for code: indexed + // - for issues: comments, created, updated + // - for users: followers, repositories, joined + // + // Default is to sort by best match. + Sort string `url:"sort,omitempty"` + + // Sort order if sort parameter is provided. Possible values are: asc, + // desc. Default is desc. + Order string `url:"order,omitempty"` + + // Whether to retrieve text match metadata with a query + TextMatch bool `url:"-"` + + ListOptions +} + +// Common search parameters. +type searchParameters struct { + Query string + RepositoryID *int64 // Sent if non-nil. +} + +// RepositoriesSearchResult represents the result of a repositories search. +type RepositoriesSearchResult struct { + Total *int `json:"total_count,omitempty"` + IncompleteResults *bool `json:"incomplete_results,omitempty"` + Repositories []Repository `json:"items,omitempty"` +} + +// Repositories searches repositories via various criteria. +// +// GitHub API docs: https://developer.github.com/v3/search/#search-repositories +func (s *SearchService) Repositories(ctx context.Context, query string, opt *SearchOptions) (*RepositoriesSearchResult, *Response, error) { + result := new(RepositoriesSearchResult) + resp, err := s.search(ctx, "repositories", &searchParameters{Query: query}, opt, result) + return result, resp, err +} + +// CommitsSearchResult represents the result of a commits search. +type CommitsSearchResult struct { + Total *int `json:"total_count,omitempty"` + IncompleteResults *bool `json:"incomplete_results,omitempty"` + Commits []*CommitResult `json:"items,omitempty"` +} + +// CommitResult represents a commit object as returned in commit search endpoint response. +type CommitResult struct { + SHA *string `json:"sha,omitempty"` + Commit *Commit `json:"commit,omitempty"` + Author *User `json:"author,omitempty"` + Committer *User `json:"committer,omitempty"` + Parents []*Commit `json:"parents,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + URL *string `json:"url,omitempty"` + CommentsURL *string `json:"comments_url,omitempty"` + + Repository *Repository `json:"repository,omitempty"` + Score *float64 `json:"score,omitempty"` +} + +// Commits searches commits via various criteria. +// +// GitHub API docs: https://developer.github.com/v3/search/#search-commits +func (s *SearchService) Commits(ctx context.Context, query string, opt *SearchOptions) (*CommitsSearchResult, *Response, error) { + result := new(CommitsSearchResult) + resp, err := s.search(ctx, "commits", &searchParameters{Query: query}, opt, result) + return result, resp, err +} + +// IssuesSearchResult represents the result of an issues search. +type IssuesSearchResult struct { + Total *int `json:"total_count,omitempty"` + IncompleteResults *bool `json:"incomplete_results,omitempty"` + Issues []Issue `json:"items,omitempty"` +} + +// Issues searches issues via various criteria. +// +// GitHub API docs: https://developer.github.com/v3/search/#search-issues +func (s *SearchService) Issues(ctx context.Context, query string, opt *SearchOptions) (*IssuesSearchResult, *Response, error) { + result := new(IssuesSearchResult) + resp, err := s.search(ctx, "issues", &searchParameters{Query: query}, opt, result) + return result, resp, err +} + +// UsersSearchResult represents the result of a users search. +type UsersSearchResult struct { + Total *int `json:"total_count,omitempty"` + IncompleteResults *bool `json:"incomplete_results,omitempty"` + Users []User `json:"items,omitempty"` +} + +// Users searches users via various criteria. +// +// GitHub API docs: https://developer.github.com/v3/search/#search-users +func (s *SearchService) Users(ctx context.Context, query string, opt *SearchOptions) (*UsersSearchResult, *Response, error) { + result := new(UsersSearchResult) + resp, err := s.search(ctx, "users", &searchParameters{Query: query}, opt, result) + return result, resp, err +} + +// Match represents a single text match. +type Match struct { + Text *string `json:"text,omitempty"` + Indices []int `json:"indices,omitempty"` +} + +// TextMatch represents a text match for a SearchResult +type TextMatch struct { + ObjectURL *string `json:"object_url,omitempty"` + ObjectType *string `json:"object_type,omitempty"` + Property *string `json:"property,omitempty"` + Fragment *string `json:"fragment,omitempty"` + Matches []Match `json:"matches,omitempty"` +} + +func (tm TextMatch) String() string { + return Stringify(tm) +} + +// CodeSearchResult represents the result of a code search. +type CodeSearchResult struct { + Total *int `json:"total_count,omitempty"` + IncompleteResults *bool `json:"incomplete_results,omitempty"` + CodeResults []CodeResult `json:"items,omitempty"` +} + +// CodeResult represents a single search result. +type CodeResult struct { + Name *string `json:"name,omitempty"` + Path *string `json:"path,omitempty"` + SHA *string `json:"sha,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + Repository *Repository `json:"repository,omitempty"` + TextMatches []TextMatch `json:"text_matches,omitempty"` +} + +func (c CodeResult) String() string { + return Stringify(c) +} + +// Code searches code via various criteria. +// +// GitHub API docs: https://developer.github.com/v3/search/#search-code +func (s *SearchService) Code(ctx context.Context, query string, opt *SearchOptions) (*CodeSearchResult, *Response, error) { + result := new(CodeSearchResult) + resp, err := s.search(ctx, "code", &searchParameters{Query: query}, opt, result) + return result, resp, err +} + +// LabelsSearchResult represents the result of a code search. +type LabelsSearchResult struct { + Total *int `json:"total_count,omitempty"` + IncompleteResults *bool `json:"incomplete_results,omitempty"` + Labels []*LabelResult `json:"items,omitempty"` +} + +// LabelResult represents a single search result. +type LabelResult struct { + ID *int64 `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + Name *string `json:"name,omitempty"` + Color *string `json:"color,omitempty"` + Default *bool `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Score *float64 `json:"score,omitempty"` +} + +func (l LabelResult) String() string { + return Stringify(l) +} + +// Labels searches labels in the repository with ID repoID via various criteria. +// +// GitHub API docs: https://developer.github.com/v3/search/#search-labels +func (s *SearchService) Labels(ctx context.Context, repoID int64, query string, opt *SearchOptions) (*LabelsSearchResult, *Response, error) { + result := new(LabelsSearchResult) + resp, err := s.search(ctx, "labels", &searchParameters{RepositoryID: &repoID, Query: query}, opt, result) + return result, resp, err +} + +// Helper function that executes search queries against different +// GitHub search types (repositories, commits, code, issues, users, labels) +func (s *SearchService) search(ctx context.Context, searchType string, parameters *searchParameters, opt *SearchOptions, result interface{}) (*Response, error) { + params, err := qs.Values(opt) + if err != nil { + return nil, err + } + q := strings.Replace(parameters.Query, " ", "+", -1) + if parameters.RepositoryID != nil { + params.Set("repository_id", strconv.FormatInt(*parameters.RepositoryID, 10)) + } + query := "q=" + url.PathEscape(q) + if v := params.Encode(); v != "" { + query = query + "&" + v + } + u := fmt.Sprintf("search/%s?%s", searchType, query) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, err + } + + switch { + case searchType == "commits": + // Accept header for search commits preview endpoint + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeCommitSearchPreview) + case searchType == "repositories": + // Accept header for search repositories based on topics preview endpoint + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTopicsPreview) + case searchType == "labels": + // Accept header for search labels based on label description preview endpoint. + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview) + case opt != nil && opt.TextMatch: + // Accept header defaults to "application/vnd.github.v3+json" + // We change it here to fetch back text-match metadata + req.Header.Set("Accept", "application/vnd.github.v3.text-match+json") + } + + return s.client.Do(ctx, req, result) +} diff --git a/vendor/github.com/google/go-github/github/search_test.go b/vendor/github.com/google/go-github/github/search_test.go new file mode 100644 index 0000000..f6e40d2 --- /dev/null +++ b/vendor/github.com/google/go-github/github/search_test.go @@ -0,0 +1,351 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + + "testing" +) + +func TestSearchService_Repositories(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/search/repositories", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": "blah", + "sort": "forks", + "order": "desc", + "page": "2", + "per_page": "2", + }) + + fmt.Fprint(w, `{"total_count": 4, "incomplete_results": false, "items": [{"id":1},{"id":2}]}`) + }) + + opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} + result, _, err := client.Search.Repositories(context.Background(), "blah", opts) + if err != nil { + t.Errorf("Search.Repositories returned error: %v", err) + } + + want := &RepositoriesSearchResult{ + Total: Int(4), + IncompleteResults: Bool(false), + Repositories: []Repository{{ID: Int64(1)}, {ID: Int64(2)}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Repositories returned %+v, want %+v", result, want) + } +} + +func TestSearchService_Commits(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/search/commits", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": "blah", + "sort": "author-date", + "order": "desc", + }) + + fmt.Fprint(w, `{"total_count": 4, "incomplete_results": false, "items": [{"sha":"random_hash1"},{"sha":"random_hash2"}]}`) + }) + + opts := &SearchOptions{Sort: "author-date", Order: "desc"} + result, _, err := client.Search.Commits(context.Background(), "blah", opts) + if err != nil { + t.Errorf("Search.Commits returned error: %v", err) + } + + want := &CommitsSearchResult{ + Total: Int(4), + IncompleteResults: Bool(false), + Commits: []*CommitResult{{SHA: String("random_hash1")}, {SHA: String("random_hash2")}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Commits returned %+v, want %+v", result, want) + } +} + +func TestSearchService_Issues(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/search/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": "blah", + "sort": "forks", + "order": "desc", + "page": "2", + "per_page": "2", + }) + + fmt.Fprint(w, `{"total_count": 4, "incomplete_results": true, "items": [{"number":1},{"number":2}]}`) + }) + + opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} + result, _, err := client.Search.Issues(context.Background(), "blah", opts) + if err != nil { + t.Errorf("Search.Issues returned error: %v", err) + } + + want := &IssuesSearchResult{ + Total: Int(4), + IncompleteResults: Bool(true), + Issues: []Issue{{Number: Int(1)}, {Number: Int(2)}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Issues returned %+v, want %+v", result, want) + } +} + +func TestSearchService_Issues_withQualifiersNoOpts(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + const q = "gopher is:issue label:bug language:go pushed:>=2018-01-01 stars:>=200" + + var requestURI string + mux.HandleFunc("/search/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": q, + }) + requestURI = r.RequestURI + + fmt.Fprint(w, `{"total_count": 4, "incomplete_results": true, "items": [{"number":1},{"number":2}]}`) + }) + + opts := &SearchOptions{} + result, _, err := client.Search.Issues(context.Background(), q, opts) + if err != nil { + t.Errorf("Search.Issues returned error: %v", err) + } + + if want := "/api-v3/search/issues?q=gopher+is:issue+label:bug+language:go+pushed:%3E=2018-01-01+stars:%3E=200"; requestURI != want { + t.Fatalf("URI encoding failed: got %v, want %v", requestURI, want) + } + + want := &IssuesSearchResult{ + Total: Int(4), + IncompleteResults: Bool(true), + Issues: []Issue{{Number: Int(1)}, {Number: Int(2)}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Issues returned %+v, want %+v", result, want) + } +} + +func TestSearchService_Issues_withQualifiersAndOpts(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + const q = "gopher is:issue label:bug language:go pushed:>=2018-01-01 stars:>=200" + + var requestURI string + mux.HandleFunc("/search/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": q, + "sort": "forks", + }) + requestURI = r.RequestURI + + fmt.Fprint(w, `{"total_count": 4, "incomplete_results": true, "items": [{"number":1},{"number":2}]}`) + }) + + opts := &SearchOptions{Sort: "forks"} + result, _, err := client.Search.Issues(context.Background(), q, opts) + if err != nil { + t.Errorf("Search.Issues returned error: %v", err) + } + + if want := "/api-v3/search/issues?q=gopher+is:issue+label:bug+language:go+pushed:%3E=2018-01-01+stars:%3E=200&sort=forks"; requestURI != want { + t.Fatalf("URI encoding failed: got %v, want %v", requestURI, want) + } + + want := &IssuesSearchResult{ + Total: Int(4), + IncompleteResults: Bool(true), + Issues: []Issue{{Number: Int(1)}, {Number: Int(2)}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Issues returned %+v, want %+v", result, want) + } +} + +func TestSearchService_Users(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/search/users", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": "blah", + "sort": "forks", + "order": "desc", + "page": "2", + "per_page": "2", + }) + + fmt.Fprint(w, `{"total_count": 4, "incomplete_results": false, "items": [{"id":1},{"id":2}]}`) + }) + + opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} + result, _, err := client.Search.Users(context.Background(), "blah", opts) + if err != nil { + t.Errorf("Search.Issues returned error: %v", err) + } + + want := &UsersSearchResult{ + Total: Int(4), + IncompleteResults: Bool(false), + Users: []User{{ID: Int64(1)}, {ID: Int64(2)}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Users returned %+v, want %+v", result, want) + } +} + +func TestSearchService_Code(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/search/code", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": "blah", + "sort": "forks", + "order": "desc", + "page": "2", + "per_page": "2", + }) + + fmt.Fprint(w, `{"total_count": 4, "incomplete_results": false, "items": [{"name":"1"},{"name":"2"}]}`) + }) + + opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} + result, _, err := client.Search.Code(context.Background(), "blah", opts) + if err != nil { + t.Errorf("Search.Code returned error: %v", err) + } + + want := &CodeSearchResult{ + Total: Int(4), + IncompleteResults: Bool(false), + CodeResults: []CodeResult{{Name: String("1")}, {Name: String("2")}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Code returned %+v, want %+v", result, want) + } +} + +func TestSearchService_CodeTextMatch(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/search/code", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + textMatchResponse := ` + { + "total_count": 1, + "incomplete_results": false, + "items": [ + { + "name":"gopher1", + "text_matches": [ + { + "fragment": "I'm afraid my friend what you have found\nIs a gopher who lives to feed", + "matches": [ + { + "text": "gopher", + "indices": [ + 14, + 21 + ] + } + ] + } + ] + } + ] + } + ` + + fmt.Fprint(w, textMatchResponse) + }) + + opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}, TextMatch: true} + result, _, err := client.Search.Code(context.Background(), "blah", opts) + if err != nil { + t.Errorf("Search.Code returned error: %v", err) + } + + wantedCodeResult := CodeResult{ + Name: String("gopher1"), + TextMatches: []TextMatch{{ + Fragment: String("I'm afraid my friend what you have found\nIs a gopher who lives to feed"), + Matches: []Match{{Text: String("gopher"), Indices: []int{14, 21}}}, + }, + }, + } + + want := &CodeSearchResult{ + Total: Int(1), + IncompleteResults: Bool(false), + CodeResults: []CodeResult{wantedCodeResult}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Code returned %+v, want %+v", result, want) + } +} + +func TestSearchService_Labels(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/search/labels", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "repository_id": "1234", + "q": "blah", + "sort": "updated", + "order": "desc", + "page": "2", + "per_page": "2", + }) + + fmt.Fprint(w, `{"total_count": 4, "incomplete_results": false, "items": [{"id": 1234, "name":"bug", "description": "some text"},{"id": 4567, "name":"feature"}]}`) + }) + + opts := &SearchOptions{Sort: "updated", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} + result, _, err := client.Search.Labels(context.Background(), 1234, "blah", opts) + if err != nil { + t.Errorf("Search.Code returned error: %v", err) + } + + want := &LabelsSearchResult{ + Total: Int(4), + IncompleteResults: Bool(false), + Labels: []*LabelResult{ + {ID: Int64(1234), Name: String("bug"), Description: String("some text")}, + {ID: Int64(4567), Name: String("feature")}, + }, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Labels returned %+v, want %+v", result, want) + } +} diff --git a/vendor/github.com/google/go-github/github/strings.go b/vendor/github.com/google/go-github/github/strings.go new file mode 100644 index 0000000..431e1cc --- /dev/null +++ b/vendor/github.com/google/go-github/github/strings.go @@ -0,0 +1,93 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "bytes" + "fmt" + "io" + + "reflect" +) + +var timestampType = reflect.TypeOf(Timestamp{}) + +// Stringify attempts to create a reasonable string representation of types in +// the GitHub library. It does things like resolve pointers to their values +// and omits struct fields with nil values. +func Stringify(message interface{}) string { + var buf bytes.Buffer + v := reflect.ValueOf(message) + stringifyValue(&buf, v) + return buf.String() +} + +// stringifyValue was heavily inspired by the goprotobuf library. + +func stringifyValue(w io.Writer, val reflect.Value) { + if val.Kind() == reflect.Ptr && val.IsNil() { + w.Write([]byte("")) + return + } + + v := reflect.Indirect(val) + + switch v.Kind() { + case reflect.String: + fmt.Fprintf(w, `"%s"`, v) + case reflect.Slice: + w.Write([]byte{'['}) + for i := 0; i < v.Len(); i++ { + if i > 0 { + w.Write([]byte{' '}) + } + + stringifyValue(w, v.Index(i)) + } + + w.Write([]byte{']'}) + return + case reflect.Struct: + if v.Type().Name() != "" { + w.Write([]byte(v.Type().String())) + } + + // special handling of Timestamp values + if v.Type() == timestampType { + fmt.Fprintf(w, "{%s}", v.Interface()) + return + } + + w.Write([]byte{'{'}) + + var sep bool + for i := 0; i < v.NumField(); i++ { + fv := v.Field(i) + if fv.Kind() == reflect.Ptr && fv.IsNil() { + continue + } + if fv.Kind() == reflect.Slice && fv.IsNil() { + continue + } + + if sep { + w.Write([]byte(", ")) + } else { + sep = true + } + + w.Write([]byte(v.Type().Field(i).Name)) + w.Write([]byte{':'}) + stringifyValue(w, fv) + } + + w.Write([]byte{'}'}) + default: + if v.CanInterface() { + fmt.Fprint(w, v.Interface()) + } + } +} diff --git a/vendor/github.com/google/go-github/github/strings_test.go b/vendor/github.com/google/go-github/github/strings_test.go new file mode 100644 index 0000000..beef089 --- /dev/null +++ b/vendor/github.com/google/go-github/github/strings_test.go @@ -0,0 +1,141 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "testing" + "time" +) + +func TestStringify(t *testing.T) { + var nilPointer *string + + var tests = []struct { + in interface{} + out string + }{ + // basic types + {"foo", `"foo"`}, + {123, `123`}, + {1.5, `1.5`}, + {false, `false`}, + { + []string{"a", "b"}, + `["a" "b"]`, + }, + { + struct { + A []string + }{nil}, + // nil slice is skipped + `{}`, + }, + { + struct { + A string + }{"foo"}, + // structs not of a named type get no prefix + `{A:"foo"}`, + }, + + // pointers + {nilPointer, ``}, + {String("foo"), `"foo"`}, + {Int(123), `123`}, + {Bool(false), `false`}, + { + []*string{String("a"), String("b")}, + `["a" "b"]`, + }, + + // actual GitHub structs + { + Timestamp{time.Date(2006, time.January, 02, 15, 04, 05, 0, time.UTC)}, + `github.Timestamp{2006-01-02 15:04:05 +0000 UTC}`, + }, + { + &Timestamp{time.Date(2006, time.January, 02, 15, 04, 05, 0, time.UTC)}, + `github.Timestamp{2006-01-02 15:04:05 +0000 UTC}`, + }, + { + User{ID: Int64(123), Name: String("n")}, + `github.User{ID:123, Name:"n"}`, + }, + { + Repository{Owner: &User{ID: Int64(123)}}, + `github.Repository{Owner:github.User{ID:123}}`, + }, + } + + for i, tt := range tests { + s := Stringify(tt.in) + if s != tt.out { + t.Errorf("%d. Stringify(%q) => %q, want %q", i, tt.in, s, tt.out) + } + } +} + +// Directly test the String() methods on various GitHub types. We don't do an +// exaustive test of all the various field types, since TestStringify() above +// takes care of that. Rather, we just make sure that Stringify() is being +// used to build the strings, which we do by verifying that pointers are +// stringified as their underlying value. +func TestString(t *testing.T) { + var tests = []struct { + in interface{} + out string + }{ + {CodeResult{Name: String("n")}, `github.CodeResult{Name:"n"}`}, + {CommitAuthor{Name: String("n")}, `github.CommitAuthor{Name:"n"}`}, + {CommitFile{SHA: String("s")}, `github.CommitFile{SHA:"s"}`}, + {CommitStats{Total: Int(1)}, `github.CommitStats{Total:1}`}, + {CommitsComparison{TotalCommits: Int(1)}, `github.CommitsComparison{TotalCommits:1}`}, + {Commit{SHA: String("s")}, `github.Commit{SHA:"s"}`}, + {Event{ID: String("1")}, `github.Event{ID:"1"}`}, + {GistComment{ID: Int64(1)}, `github.GistComment{ID:1}`}, + {GistFile{Size: Int(1)}, `github.GistFile{Size:1}`}, + {Gist{ID: String("1")}, `github.Gist{ID:"1", Files:map[]}`}, + {GitObject{SHA: String("s")}, `github.GitObject{SHA:"s"}`}, + {Gitignore{Name: String("n")}, `github.Gitignore{Name:"n"}`}, + {Hook{ID: Int64(1)}, `github.Hook{Config:map[], ID:1}`}, + {IssueComment{ID: Int64(1)}, `github.IssueComment{ID:1}`}, + {Issue{Number: Int(1)}, `github.Issue{Number:1}`}, + {Key{ID: Int64(1)}, `github.Key{ID:1}`}, + {Label{ID: Int64(1), Name: String("l")}, `github.Label{ID:1, Name:"l"}`}, + {Organization{ID: Int64(1)}, `github.Organization{ID:1}`}, + {PullRequestComment{ID: Int64(1)}, `github.PullRequestComment{ID:1}`}, + {PullRequest{Number: Int(1)}, `github.PullRequest{Number:1}`}, + {PullRequestReview{ID: Int64(1)}, `github.PullRequestReview{ID:1}`}, + {DraftReviewComment{Position: Int(1)}, `github.DraftReviewComment{Position:1}`}, + {PullRequestReviewRequest{Body: String("r")}, `github.PullRequestReviewRequest{Body:"r"}`}, + {PullRequestReviewDismissalRequest{Message: String("r")}, `github.PullRequestReviewDismissalRequest{Message:"r"}`}, + {PushEventCommit{SHA: String("s")}, `github.PushEventCommit{SHA:"s"}`}, + {PushEvent{PushID: Int64(1)}, `github.PushEvent{PushID:1}`}, + {Reference{Ref: String("r")}, `github.Reference{Ref:"r"}`}, + {ReleaseAsset{ID: Int64(1)}, `github.ReleaseAsset{ID:1}`}, + {RepoStatus{ID: Int64(1)}, `github.RepoStatus{ID:1}`}, + {RepositoryComment{ID: Int64(1)}, `github.RepositoryComment{ID:1}`}, + {RepositoryCommit{SHA: String("s")}, `github.RepositoryCommit{SHA:"s"}`}, + {RepositoryContent{Name: String("n")}, `github.RepositoryContent{Name:"n"}`}, + {RepositoryRelease{ID: Int64(1)}, `github.RepositoryRelease{ID:1}`}, + {Repository{ID: Int64(1)}, `github.Repository{ID:1}`}, + {Team{ID: Int64(1)}, `github.Team{ID:1}`}, + {TreeEntry{SHA: String("s")}, `github.TreeEntry{SHA:"s"}`}, + {Tree{SHA: String("s")}, `github.Tree{SHA:"s"}`}, + {User{ID: Int64(1)}, `github.User{ID:1}`}, + {WebHookAuthor{Name: String("n")}, `github.WebHookAuthor{Name:"n"}`}, + {WebHookCommit{ID: String("1")}, `github.WebHookCommit{ID:"1"}`}, + {WebHookPayload{Ref: String("r")}, `github.WebHookPayload{Ref:"r"}`}, + } + + for i, tt := range tests { + s := tt.in.(fmt.Stringer).String() + if s != tt.out { + t.Errorf("%d. String() => %q, want %q", i, tt.in, tt.out) + } + } +} diff --git a/vendor/github.com/google/go-github/github/teams.go b/vendor/github.com/google/go-github/github/teams.go new file mode 100644 index 0000000..c3773e0 --- /dev/null +++ b/vendor/github.com/google/go-github/github/teams.go @@ -0,0 +1,357 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "strings" + "time" +) + +// TeamsService provides access to the team-related functions +// in the GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/teams/ +type TeamsService service + +// Team represents a team within a GitHub organization. Teams are used to +// manage access to an organization's repositories. +type Team struct { + ID *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + URL *string `json:"url,omitempty"` + Slug *string `json:"slug,omitempty"` + + // Permission specifies the default permission for repositories owned by the team. + Permission *string `json:"permission,omitempty"` + + // Privacy identifies the level of privacy this team should have. + // Possible values are: + // secret - only visible to organization owners and members of this team + // closed - visible to all members of this organization + // Default is "secret". + Privacy *string `json:"privacy,omitempty"` + + MembersCount *int `json:"members_count,omitempty"` + ReposCount *int `json:"repos_count,omitempty"` + Organization *Organization `json:"organization,omitempty"` + MembersURL *string `json:"members_url,omitempty"` + RepositoriesURL *string `json:"repositories_url,omitempty"` + Parent *Team `json:"parent,omitempty"` + + // LDAPDN is only available in GitHub Enterprise and when the team + // membership is synchronized with LDAP. + LDAPDN *string `json:"ldap_dn,omitempty"` +} + +func (t Team) String() string { + return Stringify(t) +} + +// Invitation represents a team member's invitation status. +type Invitation struct { + ID *int64 `json:"id,omitempty"` + Login *string `json:"login,omitempty"` + Email *string `json:"email,omitempty"` + // Role can be one of the values - 'direct_member', 'admin', 'billing_manager', 'hiring_manager', or 'reinstate'. + Role *string `json:"role,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + Inviter *User `json:"inviter,omitempty"` + TeamCount *int `json:"team_count,omitempty"` + InvitationTeamURL *string `json:"invitation_team_url,omitempty"` +} + +func (i Invitation) String() string { + return Stringify(i) +} + +// ListTeams lists all of the teams for an organization. +// +// GitHub API docs: https://developer.github.com/v3/teams/#list-teams +func (s *TeamsService) ListTeams(ctx context.Context, org string, opt *ListOptions) ([]*Team, *Response, error) { + u := fmt.Sprintf("orgs/%v/teams", org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + var teams []*Team + resp, err := s.client.Do(ctx, req, &teams) + if err != nil { + return nil, resp, err + } + + return teams, resp, nil +} + +// GetTeam fetches a team by ID. +// +// GitHub API docs: https://developer.github.com/v3/teams/#get-team +func (s *TeamsService) GetTeam(ctx context.Context, team int64) (*Team, *Response, error) { + u := fmt.Sprintf("teams/%v", team) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + t := new(Team) + resp, err := s.client.Do(ctx, req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, nil +} + +// NewTeam represents a team to be created or modified. +type NewTeam struct { + Name string `json:"name"` // Name of the team. (Required.) + Description *string `json:"description,omitempty"` + Maintainers []string `json:"maintainers,omitempty"` + RepoNames []string `json:"repo_names,omitempty"` + ParentTeamID *int64 `json:"parent_team_id,omitempty"` + + // Deprecated: Permission is deprecated when creating or editing a team in an org + // using the new GitHub permission model. It no longer identifies the + // permission a team has on its repos, but only specifies the default + // permission a repo is initially added with. Avoid confusion by + // specifying a permission value when calling AddTeamRepo. + Permission *string `json:"permission,omitempty"` + + // Privacy identifies the level of privacy this team should have. + // Possible values are: + // secret - only visible to organization owners and members of this team + // closed - visible to all members of this organization + // Default is "secret". + Privacy *string `json:"privacy,omitempty"` + + // LDAPDN may be used in GitHub Enterprise when the team membership + // is synchronized with LDAP. + LDAPDN *string `json:"ldap_dn,omitempty"` +} + +func (s NewTeam) String() string { + return Stringify(s) +} + +// CreateTeam creates a new team within an organization. +// +// GitHub API docs: https://developer.github.com/v3/teams/#create-team +func (s *TeamsService) CreateTeam(ctx context.Context, org string, team NewTeam) (*Team, *Response, error) { + u := fmt.Sprintf("orgs/%v/teams", org) + req, err := s.client.NewRequest("POST", u, team) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + t := new(Team) + resp, err := s.client.Do(ctx, req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, nil +} + +// EditTeam edits a team. +// +// GitHub API docs: https://developer.github.com/v3/teams/#edit-team +func (s *TeamsService) EditTeam(ctx context.Context, id int64, team NewTeam) (*Team, *Response, error) { + u := fmt.Sprintf("teams/%v", id) + req, err := s.client.NewRequest("PATCH", u, team) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + t := new(Team) + resp, err := s.client.Do(ctx, req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, nil +} + +// DeleteTeam deletes a team. +// +// GitHub API docs: https://developer.github.com/v3/teams/#delete-team +func (s *TeamsService) DeleteTeam(ctx context.Context, team int64) (*Response, error) { + u := fmt.Sprintf("teams/%v", team) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + return s.client.Do(ctx, req, nil) +} + +// ListChildTeams lists child teams for a team. +// +// GitHub API docs: https://developer.github.com/v3/teams/#list-child-teams +func (s *TeamsService) ListChildTeams(ctx context.Context, teamID int64, opt *ListOptions) ([]*Team, *Response, error) { + u := fmt.Sprintf("teams/%v/teams", teamID) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + var teams []*Team + resp, err := s.client.Do(ctx, req, &teams) + if err != nil { + return nil, resp, err + } + + return teams, resp, nil +} + +// ListTeamRepos lists the repositories that the specified team has access to. +// +// GitHub API docs: https://developer.github.com/v3/teams/#list-team-repos +func (s *TeamsService) ListTeamRepos(ctx context.Context, team int64, opt *ListOptions) ([]*Repository, *Response, error) { + u := fmt.Sprintf("teams/%v/repos", team) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when topics API fully launches. + headers := []string{mediaTypeTopicsPreview, mediaTypeNestedTeamsPreview} + req.Header.Set("Accept", strings.Join(headers, ", ")) + + var repos []*Repository + resp, err := s.client.Do(ctx, req, &repos) + if err != nil { + return nil, resp, err + } + + return repos, resp, nil +} + +// IsTeamRepo checks if a team manages the specified repository. If the +// repository is managed by team, a Repository is returned which includes the +// permissions team has for that repo. +// +// GitHub API docs: https://developer.github.com/v3/teams/#check-if-a-team-manages-a-repository +func (s *TeamsService) IsTeamRepo(ctx context.Context, team int64, owner string, repo string) (*Repository, *Response, error) { + u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + headers := []string{mediaTypeOrgPermissionRepo, mediaTypeNestedTeamsPreview} + req.Header.Set("Accept", strings.Join(headers, ", ")) + + repository := new(Repository) + resp, err := s.client.Do(ctx, req, repository) + if err != nil { + return nil, resp, err + } + + return repository, resp, nil +} + +// TeamAddTeamRepoOptions specifies the optional parameters to the +// TeamsService.AddTeamRepo method. +type TeamAddTeamRepoOptions struct { + // Permission specifies the permission to grant the team on this repository. + // Possible values are: + // pull - team members can pull, but not push to or administer this repository + // push - team members can pull and push, but not administer this repository + // admin - team members can pull, push and administer this repository + // + // If not specified, the team's permission attribute will be used. + Permission string `json:"permission,omitempty"` +} + +// AddTeamRepo adds a repository to be managed by the specified team. The +// specified repository must be owned by the organization to which the team +// belongs, or a direct fork of a repository owned by the organization. +// +// GitHub API docs: https://developer.github.com/v3/teams/#add-team-repo +func (s *TeamsService) AddTeamRepo(ctx context.Context, team int64, owner string, repo string, opt *TeamAddTeamRepoOptions) (*Response, error) { + u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// RemoveTeamRepo removes a repository from being managed by the specified +// team. Note that this does not delete the repository, it just removes it +// from the team. +// +// GitHub API docs: https://developer.github.com/v3/teams/#remove-team-repo +func (s *TeamsService) RemoveTeamRepo(ctx context.Context, team int64, owner string, repo string) (*Response, error) { + u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// ListUserTeams lists a user's teams +// GitHub API docs: https://developer.github.com/v3/teams/#list-user-teams +func (s *TeamsService) ListUserTeams(ctx context.Context, opt *ListOptions) ([]*Team, *Response, error) { + u := "user/teams" + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + var teams []*Team + resp, err := s.client.Do(ctx, req, &teams) + if err != nil { + return nil, resp, err + } + + return teams, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/teams_discussion_comments.go b/vendor/github.com/google/go-github/github/teams_discussion_comments.go new file mode 100644 index 0000000..a0206b9 --- /dev/null +++ b/vendor/github.com/google/go-github/github/teams_discussion_comments.go @@ -0,0 +1,155 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// DiscussionComment represents a GitHub dicussion in a team. +type DiscussionComment struct { + Author *User `json:"author,omitempty"` + Body *string `json:"body,omitempty"` + BodyHTML *string `json:"body_html,omitempty"` + BodyVersion *string `json:"body_version,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + LastEditedAt *Timestamp `json:"last_edited_at,omitempty"` + DiscussionURL *string `json:"discussion_url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + NodeID *string `json:"node_id,omitempty"` + Number *int `json:"number,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + URL *string `json:"url,omitempty"` + Reactions *Reactions `json:"reactions,omitempty"` +} + +func (c DiscussionComment) String() string { + return Stringify(c) +} + +// DiscussionCommentListOptions specifies optional parameters to the +// TeamServices.ListComments method. +type DiscussionCommentListOptions struct { + // Sorts the discussion comments by the date they were created. + // Accepted values are asc and desc. Default is desc. + Direction string `url:"direction,omitempty"` +} + +// ListComments lists all comments on a team discussion. +// Authenticated user must grant read:discussion scope. +// +// GitHub API docs: https://developer.github.com/v3/teams/discussion_comments/#list-comments +func (s *TeamsService) ListComments(ctx context.Context, teamID int64, discussionNumber int, options *DiscussionCommentListOptions) ([]*DiscussionComment, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/comments", teamID, discussionNumber) + u, err := addOptions(u, options) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview) + + var comments []*DiscussionComment + resp, err := s.client.Do(ctx, req, &comments) + if err != nil { + return nil, resp, err + } + + return comments, resp, nil +} + +// GetComment gets a specific comment on a team discussion. +// Authenticated user must grant read:discussion scope. +// +// GitHub API docs: https://developer.github.com/v3/teams/discussion_comments/#get-a-single-comment +func (s *TeamsService) GetComment(ctx context.Context, teamID int64, discussionNumber, commentNumber int) (*DiscussionComment, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v", teamID, discussionNumber, commentNumber) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview) + + discussionComment := &DiscussionComment{} + resp, err := s.client.Do(ctx, req, discussionComment) + if err != nil { + return nil, resp, err + } + + return discussionComment, resp, nil +} + +// CreateComment creates a new discussion post on a team discussion. +// Authenticated user must grant write:discussion scope. +// +// GitHub API docs: https://developer.github.com/v3/teams/discussion_comments/#create-a-comment +func (s *TeamsService) CreateComment(ctx context.Context, teamID int64, discsusionNumber int, comment DiscussionComment) (*DiscussionComment, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/comments", teamID, discsusionNumber) + req, err := s.client.NewRequest("POST", u, comment) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview) + + discussionComment := &DiscussionComment{} + resp, err := s.client.Do(ctx, req, discussionComment) + if err != nil { + return nil, resp, err + } + + return discussionComment, resp, nil +} + +// EditComment edits the body text of a discussion comment. +// Authenticated user must grant write:discussion scope. +// User is allowed to edit body of a comment only. +// +// GitHub API docs: https://developer.github.com/v3/teams/discussion_comments/#edit-a-comment +func (s *TeamsService) EditComment(ctx context.Context, teamID int64, discussionNumber, commentNumber int, comment DiscussionComment) (*DiscussionComment, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v", teamID, discussionNumber, commentNumber) + req, err := s.client.NewRequest("PATCH", u, comment) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview) + + discussionComment := &DiscussionComment{} + resp, err := s.client.Do(ctx, req, discussionComment) + if err != nil { + return nil, resp, err + } + + return discussionComment, resp, nil +} + +// DeleteComment deletes a comment on a team discussion. +// Authenticated user must grant write:discussion scope. +// +// GitHub API docs: https://developer.github.com/v3/teams/discussion_comments/#delete-a-comment +func (s *TeamsService) DeleteComment(ctx context.Context, teamID int64, discussionNumber, commentNumber int) (*Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v", teamID, discussionNumber, commentNumber) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview) + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/teams_discussion_comments_test.go b/vendor/github.com/google/go-github/github/teams_discussion_comments_test.go new file mode 100644 index 0000000..f4c9f96 --- /dev/null +++ b/vendor/github.com/google/go-github/github/teams_discussion_comments_test.go @@ -0,0 +1,203 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestTeamsService_ListComments(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/2/discussions/3/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeTeamDiscussionsPreview) + testFormValues(t, r, values{ + "direction": "desc", + }) + fmt.Fprintf(w, + `[ + { + "author": { + "login": "author", + "id": 0, + "avatar_url": "https://avatars1.githubusercontent.com/u/0?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/author", + "html_url": "https://github.com/author", + "followers_url": "https://api.github.com/users/author/followers", + "following_url": "https://api.github.com/users/author/following{/other_user}", + "gists_url": "https://api.github.com/users/author/gists{/gist_id}", + "starred_url": "https://api.github.com/users/author/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/author/subscriptions", + "organizations_url": "https://api.github.com/users/author/orgs", + "repos_url": "https://api.github.com/users/author/repos", + "events_url": "https://api.github.com/users/author/events{/privacy}", + "received_events_url": "https://api.github.com/users/author/received_events", + "type": "User", + "site_admin": false + }, + "body": "comment", + "body_html": "

comment

", + "body_version": "version", + "created_at": "2018-01-01T00:00:00Z", + "last_edited_at": null, + "discussion_url": "https://api.github.com/teams/2/discussions/3", + "html_url": "https://github.com/orgs/1/teams/2/discussions/3/comments/4", + "node_id": "node", + "number": 4, + "updated_at": "2018-01-01T00:00:00Z", + "url": "https://api.github.com/teams/2/discussions/3/comments/4" + } + ]`) + }) + + comments, _, err := client.Teams.ListComments(context.Background(), 2, 3, &DiscussionCommentListOptions{"desc"}) + if err != nil { + t.Errorf("Teams.ListComments returned error: %v", err) + } + + want := []*DiscussionComment{ + { + Author: &User{ + Login: String("author"), + ID: Int64(0), + AvatarURL: String("https://avatars1.githubusercontent.com/u/0?v=4"), + GravatarID: String(""), + URL: String("https://api.github.com/users/author"), + HTMLURL: String("https://github.com/author"), + FollowersURL: String("https://api.github.com/users/author/followers"), + FollowingURL: String("https://api.github.com/users/author/following{/other_user}"), + GistsURL: String("https://api.github.com/users/author/gists{/gist_id}"), + StarredURL: String("https://api.github.com/users/author/starred{/owner}{/repo}"), + SubscriptionsURL: String("https://api.github.com/users/author/subscriptions"), + OrganizationsURL: String("https://api.github.com/users/author/orgs"), + ReposURL: String("https://api.github.com/users/author/repos"), + EventsURL: String("https://api.github.com/users/author/events{/privacy}"), + ReceivedEventsURL: String("https://api.github.com/users/author/received_events"), + Type: String("User"), + SiteAdmin: Bool(false), + }, + Body: String("comment"), + BodyHTML: String("

comment

"), + BodyVersion: String("version"), + CreatedAt: &Timestamp{time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC)}, + LastEditedAt: nil, + DiscussionURL: String("https://api.github.com/teams/2/discussions/3"), + HTMLURL: String("https://github.com/orgs/1/teams/2/discussions/3/comments/4"), + NodeID: String("node"), + Number: Int(4), + UpdatedAt: &Timestamp{time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC)}, + URL: String("https://api.github.com/teams/2/discussions/3/comments/4"), + }, + } + if !reflect.DeepEqual(comments, want) { + t.Errorf("Teams.ListComments returned %+v, want %+v", comments, want) + } +} + +func TestTeamsService_GetComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/2/discussions/3/comments/4", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeTeamDiscussionsPreview) + fmt.Fprint(w, `{"number":4}`) + }) + + comment, _, err := client.Teams.GetComment(context.Background(), 2, 3, 4) + if err != nil { + t.Errorf("Teams.GetComment returned error: %v", err) + } + + want := &DiscussionComment{Number: Int(4)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Teams.GetComment returned %+v, want %+v", comment, want) + } +} + +func TestTeamsService_CreateComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := DiscussionComment{Body: String("c")} + + mux.HandleFunc("/teams/2/discussions/3/comments", func(w http.ResponseWriter, r *http.Request) { + v := new(DiscussionComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeTeamDiscussionsPreview) + if !reflect.DeepEqual(v, &input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"number":4}`) + }) + + comment, _, err := client.Teams.CreateComment(context.Background(), 2, 3, input) + if err != nil { + t.Errorf("Teams.CreateComment returned error: %v", err) + } + + want := &DiscussionComment{Number: Int(4)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Teams.CreateComment returned %+v, want %+v", comment, want) + } +} + +func TestTeamsService_EditComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := DiscussionComment{Body: String("e")} + + mux.HandleFunc("/teams/2/discussions/3/comments/4", func(w http.ResponseWriter, r *http.Request) { + v := new(DiscussionComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeTeamDiscussionsPreview) + if !reflect.DeepEqual(v, &input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"number":4}`) + }) + + comment, _, err := client.Teams.EditComment(context.Background(), 2, 3, 4, input) + if err != nil { + t.Errorf("Teams.EditComment returned error: %v", err) + } + + want := &DiscussionComment{Number: Int(4)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Teams.EditComment returned %+v, want %+v", comment, want) + } +} + +func TestTeamsService_DeleteComment(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/2/discussions/3/comments/4", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeTeamDiscussionsPreview) + }) + + _, err := client.Teams.DeleteComment(context.Background(), 2, 3, 4) + if err != nil { + t.Errorf("Teams.DeleteComment returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/teams_discussions.go b/vendor/github.com/google/go-github/github/teams_discussions.go new file mode 100644 index 0000000..f491c9d --- /dev/null +++ b/vendor/github.com/google/go-github/github/teams_discussions.go @@ -0,0 +1,160 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// TeamDiscussion represents a GitHub dicussion in a team. +type TeamDiscussion struct { + Author *User `json:"author,omitempty"` + Body *string `json:"body,omitempty"` + BodyHTML *string `json:"body_html,omitempty"` + BodyVersion *string `json:"body_version,omitempty"` + CommentsCount *int `json:"comments_count,omitempty"` + CommentsURL *string `json:"comments_url,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + LastEditedAt *Timestamp `json:"last_edited_at,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + NodeID *string `json:"node_id,omitempty"` + Number *int `json:"number,omitempty"` + Pinned *bool `json:"pinned,omitempty"` + Private *bool `json:"private,omitempty"` + TeamURL *string `json:"team_url,omitempty"` + Title *string `json:"title,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + URL *string `json:"url,omitempty"` + Reactions *Reactions `json:"reactions,omitempty"` +} + +func (d TeamDiscussion) String() string { + return Stringify(d) +} + +// DiscussionListOptions specifies optional parameters to the +// TeamServices.ListDiscussions method. +type DiscussionListOptions struct { + // Sorts the discussion by the date they were created. + // Accepted values are asc and desc. Default is desc. + Direction string `url:"direction,omitempty"` +} + +// ListDiscussions lists all discussions on team's page. +// Authenticated user must grant read:discussion scope. +// +// GitHub API docs: https://developer.github.com/v3/teams/discussions/#list-discussions +func (s *TeamsService) ListDiscussions(ctx context.Context, teamID int64, options *DiscussionListOptions) ([]*TeamDiscussion, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions", teamID) + u, err := addOptions(u, options) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview) + + var teamDiscussions []*TeamDiscussion + resp, err := s.client.Do(ctx, req, &teamDiscussions) + if err != nil { + return nil, resp, err + } + + return teamDiscussions, resp, nil +} + +// GetDiscussion gets a specific discussion on a team's page. +// Authenticated user must grant read:discussion scope. +// +// GitHub API docs: https://developer.github.com/v3/teams/discussions/#get-a-single-discussion +func (s *TeamsService) GetDiscussion(ctx context.Context, teamID int64, discussionNumber int) (*TeamDiscussion, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v", teamID, discussionNumber) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview) + + teamDiscussion := &TeamDiscussion{} + resp, err := s.client.Do(ctx, req, teamDiscussion) + if err != nil { + return nil, resp, err + } + + return teamDiscussion, resp, nil +} + +// CreateDiscussion creates a new discussion post on a team's page. +// Authenticated user must grant write:discussion scope. +// +// GitHub API docs: https://developer.github.com/v3/teams/discussions/#create-a-discussion +func (s *TeamsService) CreateDiscussion(ctx context.Context, teamID int64, discussion TeamDiscussion) (*TeamDiscussion, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions", teamID) + req, err := s.client.NewRequest("POST", u, discussion) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview) + + teamDiscussion := &TeamDiscussion{} + resp, err := s.client.Do(ctx, req, teamDiscussion) + if err != nil { + return nil, resp, err + } + + return teamDiscussion, resp, nil +} + +// EditDiscussion edits the title and body text of a discussion post. +// Authenticated user must grant write:discussion scope. +// User is allowed to change Title and Body of a discussion only. +// +// GitHub API docs: https://developer.github.com/v3/teams/discussions/#edit-a-discussion +func (s *TeamsService) EditDiscussion(ctx context.Context, teamID int64, discussionNumber int, discussion TeamDiscussion) (*TeamDiscussion, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v", teamID, discussionNumber) + req, err := s.client.NewRequest("PATCH", u, discussion) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview) + + teamDiscussion := &TeamDiscussion{} + resp, err := s.client.Do(ctx, req, teamDiscussion) + if err != nil { + return nil, resp, err + } + + return teamDiscussion, resp, nil +} + +// DeleteDiscussion deletes a discussion from team's page. +// Authenticated user must grant write:discussion scope. +// +// GitHub API docs: https://developer.github.com/v3/teams/discussions/#delete-a-discussion +func (s *TeamsService) DeleteDiscussion(ctx context.Context, teamID int64, discussionNumber int) (*Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v", teamID, discussionNumber) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeTeamDiscussionsPreview) + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/teams_discussions_test.go b/vendor/github.com/google/go-github/github/teams_discussions_test.go new file mode 100644 index 0000000..c588c22 --- /dev/null +++ b/vendor/github.com/google/go-github/github/teams_discussions_test.go @@ -0,0 +1,212 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestTeamsService_ListDiscussions(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/2/discussions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeTeamDiscussionsPreview) + testFormValues(t, r, values{ + "direction": "desc", + }) + fmt.Fprintf(w, + `[ + { + "author": { + "login": "author", + "id": 0, + "avatar_url": "https://avatars1.githubusercontent.com/u/0?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/author", + "html_url": "https://github.com/author", + "followers_url": "https://api.github.com/users/author/followers", + "following_url": "https://api.github.com/users/author/following{/other_user}", + "gists_url": "https://api.github.com/users/author/gists{/gist_id}", + "starred_url": "https://api.github.com/users/author/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/author/subscriptions", + "organizations_url": "https://api.github.com/users/author/orgs", + "repos_url": "https://api.github.com/users/author/repos", + "events_url": "https://api.github.com/users/author/events{/privacy}", + "received_events_url": "https://api.github.com/users/author/received_events", + "type": "User", + "site_admin": false + }, + "body": "test", + "body_html": "

test

", + "body_version": "version", + "comments_count": 1, + "comments_url": "https://api.github.com/teams/2/discussions/3/comments", + "created_at": "2018-01-01T00:00:00Z", + "last_edited_at": null, + "html_url": "https://github.com/orgs/1/teams/2/discussions/3", + "node_id": "node", + "number": 3, + "pinned": false, + "private": false, + "team_url": "https://api.github.com/teams/2", + "title": "test", + "updated_at": "2018-01-01T00:00:00Z", + "url": "https://api.github.com/teams/2/discussions/3" + } + ]`) + }) + discussions, _, err := client.Teams.ListDiscussions(context.Background(), 2, &DiscussionListOptions{"desc"}) + if err != nil { + t.Errorf("Teams.ListDiscussions returned error: %v", err) + } + + want := []*TeamDiscussion{ + { + Author: &User{ + Login: String("author"), + ID: Int64(0), + AvatarURL: String("https://avatars1.githubusercontent.com/u/0?v=4"), + GravatarID: String(""), + URL: String("https://api.github.com/users/author"), + HTMLURL: String("https://github.com/author"), + FollowersURL: String("https://api.github.com/users/author/followers"), + FollowingURL: String("https://api.github.com/users/author/following{/other_user}"), + GistsURL: String("https://api.github.com/users/author/gists{/gist_id}"), + StarredURL: String("https://api.github.com/users/author/starred{/owner}{/repo}"), + SubscriptionsURL: String("https://api.github.com/users/author/subscriptions"), + OrganizationsURL: String("https://api.github.com/users/author/orgs"), + ReposURL: String("https://api.github.com/users/author/repos"), + EventsURL: String("https://api.github.com/users/author/events{/privacy}"), + ReceivedEventsURL: String("https://api.github.com/users/author/received_events"), + Type: String("User"), + SiteAdmin: Bool(false), + }, + Body: String("test"), + BodyHTML: String("

test

"), + BodyVersion: String("version"), + CommentsCount: Int(1), + CommentsURL: String("https://api.github.com/teams/2/discussions/3/comments"), + CreatedAt: &Timestamp{time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC)}, + LastEditedAt: nil, + HTMLURL: String("https://github.com/orgs/1/teams/2/discussions/3"), + NodeID: String("node"), + Number: Int(3), + Pinned: Bool(false), + Private: Bool(false), + TeamURL: String("https://api.github.com/teams/2"), + Title: String("test"), + UpdatedAt: &Timestamp{time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC)}, + URL: String("https://api.github.com/teams/2/discussions/3"), + }, + } + if !reflect.DeepEqual(discussions, want) { + t.Errorf("Teams.ListDiscussions returned %+v, want %+v", discussions, want) + } +} + +func TestTeamsService_GetDiscussion(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/2/discussions/3", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeTeamDiscussionsPreview) + fmt.Fprint(w, `{"number":3}`) + }) + + discussion, _, err := client.Teams.GetDiscussion(context.Background(), 2, 3) + if err != nil { + t.Errorf("Teams.GetDiscussion returned error: %v", err) + } + + want := &TeamDiscussion{Number: Int(3)} + if !reflect.DeepEqual(discussion, want) { + t.Errorf("Teams.GetDiscussion returned %+v, want %+v", discussion, want) + } +} + +func TestTeamsService_CreateDiscussion(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := TeamDiscussion{Title: String("c_t"), Body: String("c_b")} + + mux.HandleFunc("/teams/2/discussions", func(w http.ResponseWriter, r *http.Request) { + v := new(TeamDiscussion) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeTeamDiscussionsPreview) + if !reflect.DeepEqual(v, &input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"number":3}`) + }) + + comment, _, err := client.Teams.CreateDiscussion(context.Background(), 2, input) + if err != nil { + t.Errorf("Teams.CreateDiscussion returned error: %v", err) + } + + want := &TeamDiscussion{Number: Int(3)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Teams.CreateDiscussion returned %+v, want %+v", comment, want) + } +} + +func TestTeamsService_EditDiscussion(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := TeamDiscussion{Title: String("e_t"), Body: String("e_b")} + + mux.HandleFunc("/teams/2/discussions/3", func(w http.ResponseWriter, r *http.Request) { + v := new(TeamDiscussion) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeTeamDiscussionsPreview) + if !reflect.DeepEqual(v, &input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"number":3}`) + }) + + comment, _, err := client.Teams.EditDiscussion(context.Background(), 2, 3, input) + if err != nil { + t.Errorf("Teams.EditDiscussion returned error: %v", err) + } + + want := &TeamDiscussion{Number: Int(3)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Teams.EditDiscussion returned %+v, want %+v", comment, want) + } +} + +func TestTeamsService_DeleteDiscussion(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/2/discussions/3", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeTeamDiscussionsPreview) + }) + + _, err := client.Teams.DeleteDiscussion(context.Background(), 2, 3) + if err != nil { + t.Errorf("Teams.DeleteDiscussion returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/teams_members.go b/vendor/github.com/google/go-github/github/teams_members.go new file mode 100644 index 0000000..d5cfa0d --- /dev/null +++ b/vendor/github.com/google/go-github/github/teams_members.go @@ -0,0 +1,174 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// TeamListTeamMembersOptions specifies the optional parameters to the +// TeamsService.ListTeamMembers method. +type TeamListTeamMembersOptions struct { + // Role filters members returned by their role in the team. Possible + // values are "all", "member", "maintainer". Default is "all". + Role string `url:"role,omitempty"` + + ListOptions +} + +// ListTeamMembers lists all of the users who are members of the specified +// team. +// +// GitHub API docs: https://developer.github.com/v3/teams/members/#list-team-members +func (s *TeamsService) ListTeamMembers(ctx context.Context, team int64, opt *TeamListTeamMembersOptions) ([]*User, *Response, error) { + u := fmt.Sprintf("teams/%v/members", team) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + var members []*User + resp, err := s.client.Do(ctx, req, &members) + if err != nil { + return nil, resp, err + } + + return members, resp, nil +} + +// IsTeamMember checks if a user is a member of the specified team. +// +// GitHub API docs: https://developer.github.com/v3/teams/members/#get-team-member +// +// Deprecated: This API has been marked as deprecated in the Github API docs, +// TeamsService.GetTeamMembership method should be used instead. +func (s *TeamsService) IsTeamMember(ctx context.Context, team int64, user string) (bool, *Response, error) { + u := fmt.Sprintf("teams/%v/members/%v", team, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + member, err := parseBoolResponse(err) + return member, resp, err +} + +// GetTeamMembership returns the membership status for a user in a team. +// +// GitHub API docs: https://developer.github.com/v3/teams/members/#get-team-membership +func (s *TeamsService) GetTeamMembership(ctx context.Context, team int64, user string) (*Membership, *Response, error) { + u := fmt.Sprintf("teams/%v/memberships/%v", team, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeNestedTeamsPreview) + + t := new(Membership) + resp, err := s.client.Do(ctx, req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, nil +} + +// TeamAddTeamMembershipOptions specifies the optional +// parameters to the TeamsService.AddTeamMembership method. +type TeamAddTeamMembershipOptions struct { + // Role specifies the role the user should have in the team. Possible + // values are: + // member - a normal member of the team + // maintainer - a team maintainer. Able to add/remove other team + // members, promote other team members to team + // maintainer, and edit the team’s name and description + // + // Default value is "member". + Role string `json:"role,omitempty"` +} + +// AddTeamMembership adds or invites a user to a team. +// +// In order to add a membership between a user and a team, the authenticated +// user must have 'admin' permissions to the team or be an owner of the +// organization that the team is associated with. +// +// If the user is already a part of the team's organization (meaning they're on +// at least one other team in the organization), this endpoint will add the +// user to the team. +// +// If the user is completely unaffiliated with the team's organization (meaning +// they're on none of the organization's teams), this endpoint will send an +// invitation to the user via email. This newly-created membership will be in +// the "pending" state until the user accepts the invitation, at which point +// the membership will transition to the "active" state and the user will be +// added as a member of the team. +// +// GitHub API docs: https://developer.github.com/v3/teams/members/#add-or-update-team-membership +func (s *TeamsService) AddTeamMembership(ctx context.Context, team int64, user string, opt *TeamAddTeamMembershipOptions) (*Membership, *Response, error) { + u := fmt.Sprintf("teams/%v/memberships/%v", team, user) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, nil, err + } + + t := new(Membership) + resp, err := s.client.Do(ctx, req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, nil +} + +// RemoveTeamMembership removes a user from a team. +// +// GitHub API docs: https://developer.github.com/v3/teams/members/#remove-team-membership +func (s *TeamsService) RemoveTeamMembership(ctx context.Context, team int64, user string) (*Response, error) { + u := fmt.Sprintf("teams/%v/memberships/%v", team, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// ListPendingTeamInvitations get pending invitaion list in team. +// Warning: The API may change without advance notice during the preview period. +// Preview features are not supported for production use. +// +// GitHub API docs: https://developer.github.com/v3/teams/members/#list-pending-team-invitations +func (s *TeamsService) ListPendingTeamInvitations(ctx context.Context, team int64, opt *ListOptions) ([]*Invitation, *Response, error) { + u := fmt.Sprintf("teams/%v/invitations", team) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var pendingInvitations []*Invitation + resp, err := s.client.Do(ctx, req, &pendingInvitations) + if err != nil { + return nil, resp, err + } + + return pendingInvitations, resp, nil +} diff --git a/vendor/github.com/google/go-github/github/teams_members_test.go b/vendor/github.com/google/go-github/github/teams_members_test.go new file mode 100644 index 0000000..2489da0 --- /dev/null +++ b/vendor/github.com/google/go-github/github/teams_members_test.go @@ -0,0 +1,167 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestTeamsService__ListTeamMembers(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/members", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + testFormValues(t, r, values{"role": "member", "page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &TeamListTeamMembersOptions{Role: "member", ListOptions: ListOptions{Page: 2}} + members, _, err := client.Teams.ListTeamMembers(context.Background(), 1, opt) + if err != nil { + t.Errorf("Teams.ListTeamMembers returned error: %v", err) + } + + want := []*User{{ID: Int64(1)}} + if !reflect.DeepEqual(members, want) { + t.Errorf("Teams.ListTeamMembers returned %+v, want %+v", members, want) + } +} + +func TestTeamsService__IsTeamMember_true(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + }) + + member, _, err := client.Teams.IsTeamMember(context.Background(), 1, "u") + if err != nil { + t.Errorf("Teams.IsTeamMember returned error: %v", err) + } + if want := true; member != want { + t.Errorf("Teams.IsTeamMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 404 response is interpreted as "false" and not an error +func TestTeamsService__IsTeamMember_false(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + member, _, err := client.Teams.IsTeamMember(context.Background(), 1, "u") + if err != nil { + t.Errorf("Teams.IsTeamMember returned error: %+v", err) + } + if want := false; member != want { + t.Errorf("Teams.IsTeamMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 400 response is interpreted as an actual error, and not simply +// as "false" like the above case of a 404 +func TestTeamsService__IsTeamMember_error(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + member, _, err := client.Teams.IsTeamMember(context.Background(), 1, "u") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; member != want { + t.Errorf("Teams.IsTeamMember returned %+v, want %+v", member, want) + } +} + +func TestTeamsService__IsTeamMember_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Teams.IsTeamMember(context.Background(), 1, "%") + testURLParseError(t, err) +} + +func TestTeamsService__GetTeamMembership(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + fmt.Fprint(w, `{"url":"u", "state":"active"}`) + }) + + membership, _, err := client.Teams.GetTeamMembership(context.Background(), 1, "u") + if err != nil { + t.Errorf("Teams.GetTeamMembership returned error: %v", err) + } + + want := &Membership{URL: String("u"), State: String("active")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Teams.GetTeamMembership returned %+v, want %+v", membership, want) + } +} + +func TestTeamsService__AddTeamMembership(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + opt := &TeamAddTeamMembershipOptions{Role: "maintainer"} + + mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { + v := new(TeamAddTeamMembershipOptions) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, opt) { + t.Errorf("Request body = %+v, want %+v", v, opt) + } + + fmt.Fprint(w, `{"url":"u", "state":"pending"}`) + }) + + membership, _, err := client.Teams.AddTeamMembership(context.Background(), 1, "u", opt) + if err != nil { + t.Errorf("Teams.AddTeamMembership returned error: %v", err) + } + + want := &Membership{URL: String("u"), State: String("pending")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Teams.AddTeamMembership returned %+v, want %+v", membership, want) + } +} + +func TestTeamsService__RemoveTeamMembership(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Teams.RemoveTeamMembership(context.Background(), 1, "u") + if err != nil { + t.Errorf("Teams.RemoveTeamMembership returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/teams_test.go b/vendor/github.com/google/go-github/github/teams_test.go new file mode 100644 index 0000000..89708b4 --- /dev/null +++ b/vendor/github.com/google/go-github/github/teams_test.go @@ -0,0 +1,465 @@ +// Copyright 2018 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "strings" + "testing" + "time" +) + +func TestTeamsService_ListTeams(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/teams", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + teams, _, err := client.Teams.ListTeams(context.Background(), "o", opt) + if err != nil { + t.Errorf("Teams.ListTeams returned error: %v", err) + } + + want := []*Team{{ID: Int64(1)}} + if !reflect.DeepEqual(teams, want) { + t.Errorf("Teams.ListTeams returned %+v, want %+v", teams, want) + } +} + +func TestTeamsService_ListTeams_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Teams.ListTeams(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestTeamsService_GetTeam(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + fmt.Fprint(w, `{"id":1, "name":"n", "description": "d", "url":"u", "slug": "s", "permission":"p", "ldap_dn":"cn=n,ou=groups,dc=example,dc=com", "parent":null}`) + }) + + team, _, err := client.Teams.GetTeam(context.Background(), 1) + if err != nil { + t.Errorf("Teams.GetTeam returned error: %v", err) + } + + want := &Team{ID: Int64(1), Name: String("n"), Description: String("d"), URL: String("u"), Slug: String("s"), Permission: String("p"), LDAPDN: String("cn=n,ou=groups,dc=example,dc=com")} + if !reflect.DeepEqual(team, want) { + t.Errorf("Teams.GetTeam returned %+v, want %+v", team, want) + } +} + +func TestTeamsService_GetTeam_nestedTeams(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + fmt.Fprint(w, `{"id":1, "name":"n", "description": "d", "url":"u", "slug": "s", "permission":"p", + "parent": {"id":2, "name":"n", "description": "d", "parent": null}}`) + }) + + team, _, err := client.Teams.GetTeam(context.Background(), 1) + if err != nil { + t.Errorf("Teams.GetTeam returned error: %v", err) + } + + want := &Team{ID: Int64(1), Name: String("n"), Description: String("d"), URL: String("u"), Slug: String("s"), Permission: String("p"), + Parent: &Team{ID: Int64(2), Name: String("n"), Description: String("d")}, + } + if !reflect.DeepEqual(team, want) { + t.Errorf("Teams.GetTeam returned %+v, want %+v", team, want) + } +} + +func TestTeamsService_CreateTeam(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := NewTeam{Name: "n", Privacy: String("closed"), RepoNames: []string{"r"}} + + mux.HandleFunc("/orgs/o/teams", func(w http.ResponseWriter, r *http.Request) { + v := new(NewTeam) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + if !reflect.DeepEqual(v, &input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + team, _, err := client.Teams.CreateTeam(context.Background(), "o", input) + if err != nil { + t.Errorf("Teams.CreateTeam returned error: %v", err) + } + + want := &Team{ID: Int64(1)} + if !reflect.DeepEqual(team, want) { + t.Errorf("Teams.CreateTeam returned %+v, want %+v", team, want) + } +} + +func TestTeamsService_CreateTeam_invalidOrg(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Teams.CreateTeam(context.Background(), "%", NewTeam{}) + testURLParseError(t, err) +} + +func TestTeamsService_EditTeam(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := NewTeam{Name: "n", Privacy: String("closed")} + + mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { + v := new(NewTeam) + json.NewDecoder(r.Body).Decode(v) + + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, &input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + team, _, err := client.Teams.EditTeam(context.Background(), 1, input) + if err != nil { + t.Errorf("Teams.EditTeam returned error: %v", err) + } + + want := &Team{ID: Int64(1)} + if !reflect.DeepEqual(team, want) { + t.Errorf("Teams.EditTeam returned %+v, want %+v", team, want) + } +} + +func TestTeamsService_DeleteTeam(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + }) + + _, err := client.Teams.DeleteTeam(context.Background(), 1) + if err != nil { + t.Errorf("Teams.DeleteTeam returned error: %v", err) + } +} + +func TestTeamsService_ListChildTeams(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/teams", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + teams, _, err := client.Teams.ListChildTeams(context.Background(), 1, opt) + if err != nil { + t.Errorf("Teams.ListTeams returned error: %v", err) + } + + want := []*Team{{ID: Int64(2)}} + if !reflect.DeepEqual(teams, want) { + t.Errorf("Teams.ListTeams returned %+v, want %+v", teams, want) + } +} + +func TestTeamsService_ListTeamRepos(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + acceptHeaders := []string{mediaTypeTopicsPreview, mediaTypeNestedTeamsPreview} + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + members, _, err := client.Teams.ListTeamRepos(context.Background(), 1, opt) + if err != nil { + t.Errorf("Teams.ListTeamRepos returned error: %v", err) + } + + want := []*Repository{{ID: Int64(1)}} + if !reflect.DeepEqual(members, want) { + t.Errorf("Teams.ListTeamRepos returned %+v, want %+v", members, want) + } +} + +func TestTeamsService_IsTeamRepo_true(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + acceptHeaders := []string{mediaTypeOrgPermissionRepo, mediaTypeNestedTeamsPreview} + testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", ")) + fmt.Fprint(w, `{"id":1}`) + }) + + repo, _, err := client.Teams.IsTeamRepo(context.Background(), 1, "o", "r") + if err != nil { + t.Errorf("Teams.IsTeamRepo returned error: %v", err) + } + + want := &Repository{ID: Int64(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Teams.IsTeamRepo returned %+v, want %+v", repo, want) + } +} + +func TestTeamsService_IsTeamRepo_false(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + repo, resp, err := client.Teams.IsTeamRepo(context.Background(), 1, "o", "r") + if err == nil { + t.Errorf("Expected HTTP 404 response") + } + if got, want := resp.Response.StatusCode, http.StatusNotFound; got != want { + t.Errorf("Teams.IsTeamRepo returned status %d, want %d", got, want) + } + if repo != nil { + t.Errorf("Teams.IsTeamRepo returned %+v, want nil", repo) + } +} + +func TestTeamsService_IsTeamRepo_error(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + repo, resp, err := client.Teams.IsTeamRepo(context.Background(), 1, "o", "r") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if got, want := resp.Response.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("Teams.IsTeamRepo returned status %d, want %d", got, want) + } + if repo != nil { + t.Errorf("Teams.IsTeamRepo returned %+v, want nil", repo) + } +} + +func TestTeamsService_IsTeamRepo_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Teams.IsTeamRepo(context.Background(), 1, "%", "r") + testURLParseError(t, err) +} + +func TestTeamsService_AddTeamRepo(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + opt := &TeamAddTeamRepoOptions{Permission: "admin"} + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + v := new(TeamAddTeamRepoOptions) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, opt) { + t.Errorf("Request body = %+v, want %+v", v, opt) + } + + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Teams.AddTeamRepo(context.Background(), 1, "o", "r", opt) + if err != nil { + t.Errorf("Teams.AddTeamRepo returned error: %v", err) + } +} + +func TestTeamsService_AddTeamRepo_noAccess(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusUnprocessableEntity) + }) + + _, err := client.Teams.AddTeamRepo(context.Background(), 1, "o", "r", nil) + if err == nil { + t.Errorf("Expcted error to be returned") + } +} + +func TestTeamsService_AddTeamRepo_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Teams.AddTeamRepo(context.Background(), 1, "%", "r", nil) + testURLParseError(t, err) +} + +func TestTeamsService_RemoveTeamRepo(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Teams.RemoveTeamRepo(context.Background(), 1, "o", "r") + if err != nil { + t.Errorf("Teams.RemoveTeamRepo returned error: %v", err) + } +} + +func TestTeamsService_RemoveTeamRepo_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Teams.RemoveTeamRepo(context.Background(), 1, "%", "r") + testURLParseError(t, err) +} + +func TestTeamsService_ListUserTeams(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/teams", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeNestedTeamsPreview) + testFormValues(t, r, values{"page": "1"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1} + teams, _, err := client.Teams.ListUserTeams(context.Background(), opt) + if err != nil { + t.Errorf("Teams.ListUserTeams returned error: %v", err) + } + + want := []*Team{{ID: Int64(1)}} + if !reflect.DeepEqual(teams, want) { + t.Errorf("Teams.ListUserTeams returned %+v, want %+v", teams, want) + } +} + +func TestTeamsService_ListPendingTeamInvitations(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/teams/1/invitations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "1"}) + fmt.Fprint(w, `[ + { + "id": 1, + "login": "monalisa", + "email": "octocat@github.com", + "role": "direct_member", + "created_at": "2017-01-21T00:00:00Z", + "inviter": { + "login": "other_user", + "id": 1, + "avatar_url": "https://github.com/images/error/other_user_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/other_user", + "html_url": "https://github.com/other_user", + "followers_url": "https://api.github.com/users/other_user/followers", + "following_url": "https://api.github.com/users/other_user/following/other_user", + "gists_url": "https://api.github.com/users/other_user/gists/gist_id", + "starred_url": "https://api.github.com/users/other_user/starred/owner/repo", + "subscriptions_url": "https://api.github.com/users/other_user/subscriptions", + "organizations_url": "https://api.github.com/users/other_user/orgs", + "repos_url": "https://api.github.com/users/other_user/repos", + "events_url": "https://api.github.com/users/other_user/events/privacy", + "received_events_url": "https://api.github.com/users/other_user/received_events/privacy", + "type": "User", + "site_admin": false + } + } + ]`) + }) + + opt := &ListOptions{Page: 1} + invitations, _, err := client.Teams.ListPendingTeamInvitations(context.Background(), 1, opt) + if err != nil { + t.Errorf("Teams.ListPendingTeamInvitations returned error: %v", err) + } + + createdAt := time.Date(2017, time.January, 21, 0, 0, 0, 0, time.UTC) + want := []*Invitation{ + { + ID: Int64(1), + Login: String("monalisa"), + Email: String("octocat@github.com"), + Role: String("direct_member"), + CreatedAt: &createdAt, + Inviter: &User{ + Login: String("other_user"), + ID: Int64(1), + AvatarURL: String("https://github.com/images/error/other_user_happy.gif"), + GravatarID: String(""), + URL: String("https://api.github.com/users/other_user"), + HTMLURL: String("https://github.com/other_user"), + FollowersURL: String("https://api.github.com/users/other_user/followers"), + FollowingURL: String("https://api.github.com/users/other_user/following/other_user"), + GistsURL: String("https://api.github.com/users/other_user/gists/gist_id"), + StarredURL: String("https://api.github.com/users/other_user/starred/owner/repo"), + SubscriptionsURL: String("https://api.github.com/users/other_user/subscriptions"), + OrganizationsURL: String("https://api.github.com/users/other_user/orgs"), + ReposURL: String("https://api.github.com/users/other_user/repos"), + EventsURL: String("https://api.github.com/users/other_user/events/privacy"), + ReceivedEventsURL: String("https://api.github.com/users/other_user/received_events/privacy"), + Type: String("User"), + SiteAdmin: Bool(false), + }, + }} + + if !reflect.DeepEqual(invitations, want) { + t.Errorf("Teams.ListPendingTeamInvitations returned %+v, want %+v", invitations, want) + } +} diff --git a/vendor/github.com/google/go-github/github/timestamp.go b/vendor/github.com/google/go-github/github/timestamp.go new file mode 100644 index 0000000..90929d5 --- /dev/null +++ b/vendor/github.com/google/go-github/github/timestamp.go @@ -0,0 +1,41 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "strconv" + "time" +) + +// Timestamp represents a time that can be unmarshalled from a JSON string +// formatted as either an RFC3339 or Unix timestamp. This is necessary for some +// fields since the GitHub API is inconsistent in how it represents times. All +// exported methods of time.Time can be called on Timestamp. +type Timestamp struct { + time.Time +} + +func (t Timestamp) String() string { + return t.Time.String() +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// Time is expected in RFC3339 or Unix format. +func (t *Timestamp) UnmarshalJSON(data []byte) (err error) { + str := string(data) + i, err := strconv.ParseInt(str, 10, 64) + if err == nil { + t.Time = time.Unix(i, 0) + } else { + t.Time, err = time.Parse(`"`+time.RFC3339+`"`, str) + } + return +} + +// Equal reports whether t and u are equal based on time.Equal +func (t Timestamp) Equal(u Timestamp) bool { + return t.Time.Equal(u.Time) +} diff --git a/vendor/github.com/google/go-github/github/timestamp_test.go b/vendor/github.com/google/go-github/github/timestamp_test.go new file mode 100644 index 0000000..dc09eaf --- /dev/null +++ b/vendor/github.com/google/go-github/github/timestamp_test.go @@ -0,0 +1,189 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "testing" + "time" +) + +const ( + emptyTimeStr = `"0001-01-01T00:00:00Z"` + referenceTimeStr = `"2006-01-02T15:04:05Z"` + referenceTimeStrFractional = `"2006-01-02T15:04:05.000Z"` // This format was returned by the Projects API before October 1, 2017. + referenceUnixTimeStr = `1136214245` +) + +var ( + referenceTime = time.Date(2006, time.January, 02, 15, 04, 05, 0, time.UTC) + unixOrigin = time.Unix(0, 0).In(time.UTC) +) + +func TestTimestamp_Marshal(t *testing.T) { + testCases := []struct { + desc string + data Timestamp + want string + wantErr bool + equal bool + }{ + {"Reference", Timestamp{referenceTime}, referenceTimeStr, false, true}, + {"Empty", Timestamp{}, emptyTimeStr, false, true}, + {"Mismatch", Timestamp{}, referenceTimeStr, false, false}, + } + for _, tc := range testCases { + out, err := json.Marshal(tc.data) + if gotErr := err != nil; gotErr != tc.wantErr { + t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err) + } + got := string(out) + equal := got == tc.want + if (got == tc.want) != tc.equal { + t.Errorf("%s: got=%s, want=%s, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal) + } + } +} + +func TestTimestamp_Unmarshal(t *testing.T) { + testCases := []struct { + desc string + data string + want Timestamp + wantErr bool + equal bool + }{ + {"Reference", referenceTimeStr, Timestamp{referenceTime}, false, true}, + {"ReferenceUnix", referenceUnixTimeStr, Timestamp{referenceTime}, false, true}, + {"ReferenceFractional", referenceTimeStrFractional, Timestamp{referenceTime}, false, true}, + {"Empty", emptyTimeStr, Timestamp{}, false, true}, + {"UnixStart", `0`, Timestamp{unixOrigin}, false, true}, + {"Mismatch", referenceTimeStr, Timestamp{}, false, false}, + {"MismatchUnix", `0`, Timestamp{}, false, false}, + {"Invalid", `"asdf"`, Timestamp{referenceTime}, true, false}, + } + for _, tc := range testCases { + var got Timestamp + err := json.Unmarshal([]byte(tc.data), &got) + if gotErr := err != nil; gotErr != tc.wantErr { + t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err) + continue + } + equal := got.Equal(tc.want) + if equal != tc.equal { + t.Errorf("%s: got=%#v, want=%#v, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal) + } + } +} + +func TestTimstamp_MarshalReflexivity(t *testing.T) { + testCases := []struct { + desc string + data Timestamp + }{ + {"Reference", Timestamp{referenceTime}}, + {"Empty", Timestamp{}}, + } + for _, tc := range testCases { + data, err := json.Marshal(tc.data) + if err != nil { + t.Errorf("%s: Marshal err=%v", tc.desc, err) + } + var got Timestamp + err = json.Unmarshal(data, &got) + if err != nil { + t.Errorf("%s: Unmarshal err=%v", tc.desc, err) + } + if !got.Equal(tc.data) { + t.Errorf("%s: %+v != %+v", tc.desc, got, data) + } + } +} + +type WrappedTimestamp struct { + A int + Time Timestamp +} + +func TestWrappedTimstamp_Marshal(t *testing.T) { + testCases := []struct { + desc string + data WrappedTimestamp + want string + wantErr bool + equal bool + }{ + {"Reference", WrappedTimestamp{0, Timestamp{referenceTime}}, fmt.Sprintf(`{"A":0,"Time":%s}`, referenceTimeStr), false, true}, + {"Empty", WrappedTimestamp{}, fmt.Sprintf(`{"A":0,"Time":%s}`, emptyTimeStr), false, true}, + {"Mismatch", WrappedTimestamp{}, fmt.Sprintf(`{"A":0,"Time":%s}`, referenceTimeStr), false, false}, + } + for _, tc := range testCases { + out, err := json.Marshal(tc.data) + if gotErr := err != nil; gotErr != tc.wantErr { + t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err) + } + got := string(out) + equal := got == tc.want + if equal != tc.equal { + t.Errorf("%s: got=%s, want=%s, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal) + } + } +} + +func TestWrappedTimstamp_Unmarshal(t *testing.T) { + testCases := []struct { + desc string + data string + want WrappedTimestamp + wantErr bool + equal bool + }{ + {"Reference", referenceTimeStr, WrappedTimestamp{0, Timestamp{referenceTime}}, false, true}, + {"ReferenceUnix", referenceUnixTimeStr, WrappedTimestamp{0, Timestamp{referenceTime}}, false, true}, + {"Empty", emptyTimeStr, WrappedTimestamp{0, Timestamp{}}, false, true}, + {"UnixStart", `0`, WrappedTimestamp{0, Timestamp{unixOrigin}}, false, true}, + {"Mismatch", referenceTimeStr, WrappedTimestamp{0, Timestamp{}}, false, false}, + {"MismatchUnix", `0`, WrappedTimestamp{0, Timestamp{}}, false, false}, + {"Invalid", `"asdf"`, WrappedTimestamp{0, Timestamp{referenceTime}}, true, false}, + } + for _, tc := range testCases { + var got Timestamp + err := json.Unmarshal([]byte(tc.data), &got) + if gotErr := err != nil; gotErr != tc.wantErr { + t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err) + continue + } + equal := got.Time.Equal(tc.want.Time.Time) + if equal != tc.equal { + t.Errorf("%s: got=%#v, want=%#v, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal) + } + } +} + +func TestWrappedTimstamp_MarshalReflexivity(t *testing.T) { + testCases := []struct { + desc string + data WrappedTimestamp + }{ + {"Reference", WrappedTimestamp{0, Timestamp{referenceTime}}}, + {"Empty", WrappedTimestamp{0, Timestamp{}}}, + } + for _, tc := range testCases { + bytes, err := json.Marshal(tc.data) + if err != nil { + t.Errorf("%s: Marshal err=%v", tc.desc, err) + } + var got WrappedTimestamp + err = json.Unmarshal(bytes, &got) + if err != nil { + t.Errorf("%s: Unmarshal err=%v", tc.desc, err) + } + if !got.Time.Equal(tc.data.Time) { + t.Errorf("%s: %+v != %+v", tc.desc, got, tc.data) + } + } +} diff --git a/vendor/github.com/google/go-github/github/users.go b/vendor/github.com/google/go-github/github/users.go new file mode 100644 index 0000000..f164d55 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users.go @@ -0,0 +1,284 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// UsersService handles communication with the user related +// methods of the GitHub API. +// +// GitHub API docs: https://developer.github.com/v3/users/ +type UsersService service + +// User represents a GitHub user. +type User struct { + Login *string `json:"login,omitempty"` + ID *int64 `json:"id,omitempty"` + NodeID *string `json:"node_id,omitempty"` + AvatarURL *string `json:"avatar_url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + GravatarID *string `json:"gravatar_id,omitempty"` + Name *string `json:"name,omitempty"` + Company *string `json:"company,omitempty"` + Blog *string `json:"blog,omitempty"` + Location *string `json:"location,omitempty"` + Email *string `json:"email,omitempty"` + Hireable *bool `json:"hireable,omitempty"` + Bio *string `json:"bio,omitempty"` + PublicRepos *int `json:"public_repos,omitempty"` + PublicGists *int `json:"public_gists,omitempty"` + Followers *int `json:"followers,omitempty"` + Following *int `json:"following,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + SuspendedAt *Timestamp `json:"suspended_at,omitempty"` + Type *string `json:"type,omitempty"` + SiteAdmin *bool `json:"site_admin,omitempty"` + TotalPrivateRepos *int `json:"total_private_repos,omitempty"` + OwnedPrivateRepos *int `json:"owned_private_repos,omitempty"` + PrivateGists *int `json:"private_gists,omitempty"` + DiskUsage *int `json:"disk_usage,omitempty"` + Collaborators *int `json:"collaborators,omitempty"` + Plan *Plan `json:"plan,omitempty"` + + // API URLs + URL *string `json:"url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + FollowingURL *string `json:"following_url,omitempty"` + FollowersURL *string `json:"followers_url,omitempty"` + GistsURL *string `json:"gists_url,omitempty"` + OrganizationsURL *string `json:"organizations_url,omitempty"` + ReceivedEventsURL *string `json:"received_events_url,omitempty"` + ReposURL *string `json:"repos_url,omitempty"` + StarredURL *string `json:"starred_url,omitempty"` + SubscriptionsURL *string `json:"subscriptions_url,omitempty"` + + // TextMatches is only populated from search results that request text matches + // See: search.go and https://developer.github.com/v3/search/#text-match-metadata + TextMatches []TextMatch `json:"text_matches,omitempty"` + + // Permissions identifies the permissions that a user has on a given + // repository. This is only populated when calling Repositories.ListCollaborators. + Permissions *map[string]bool `json:"permissions,omitempty"` +} + +func (u User) String() string { + return Stringify(u) +} + +// Get fetches a user. Passing the empty string will fetch the authenticated +// user. +// +// GitHub API docs: https://developer.github.com/v3/users/#get-a-single-user +func (s *UsersService) Get(ctx context.Context, user string) (*User, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v", user) + } else { + u = "user" + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + uResp := new(User) + resp, err := s.client.Do(ctx, req, uResp) + if err != nil { + return nil, resp, err + } + + return uResp, resp, nil +} + +// GetByID fetches a user. +// +// Note: GetByID uses the undocumented GitHub API endpoint /user/:id. +func (s *UsersService) GetByID(ctx context.Context, id int64) (*User, *Response, error) { + u := fmt.Sprintf("user/%d", id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + user := new(User) + resp, err := s.client.Do(ctx, req, user) + if err != nil { + return nil, resp, err + } + + return user, resp, nil +} + +// Edit the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/users/#update-the-authenticated-user +func (s *UsersService) Edit(ctx context.Context, user *User) (*User, *Response, error) { + u := "user" + req, err := s.client.NewRequest("PATCH", u, user) + if err != nil { + return nil, nil, err + } + + uResp := new(User) + resp, err := s.client.Do(ctx, req, uResp) + if err != nil { + return nil, resp, err + } + + return uResp, resp, nil +} + +// HovercardOptions specifies optional parameters to the UsersService.GetHovercard +// method. +type HovercardOptions struct { + // SubjectType specifies the additional information to be received about the hovercard. + // Possible values are: organization, repository, issue, pull_request. (Required when using subject_id.) + SubjectType string `url:"subject_type"` + + // SubjectID specifies the ID for the SubjectType. (Required when using subject_type.) + SubjectID string `url:"subject_id"` +} + +// Hovercard represents hovercard information about a user. +type Hovercard struct { + Contexts []*UserContext `json:"contexts,omitempty"` +} + +// UserContext represents the contextual information about user. +type UserContext struct { + Message *string `json:"message,omitempty"` + Octicon *string `json:"octicon,omitempty"` +} + +// GetHovercard fetches contextual information about user. It requires authentication +// via Basic Auth or via OAuth with the repo scope. +// +// GitHub API docs: https://developer.github.com/v3/users/#get-contextual-information-about-a-user +func (s *UsersService) GetHovercard(ctx context.Context, user string, opt *HovercardOptions) (*Hovercard, *Response, error) { + u := fmt.Sprintf("users/%v/hovercard", user) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeHovercardPreview) + + hc := new(Hovercard) + resp, err := s.client.Do(ctx, req, hc) + if err != nil { + return nil, resp, err + } + + return hc, resp, nil +} + +// UserListOptions specifies optional parameters to the UsersService.ListAll +// method. +type UserListOptions struct { + // ID of the last user seen + Since int64 `url:"since,omitempty"` + + // Note: Pagination is powered exclusively by the Since parameter, + // ListOptions.Page has no effect. + // ListOptions.PerPage controls an undocumented GitHub API parameter. + ListOptions +} + +// ListAll lists all GitHub users. +// +// To paginate through all users, populate 'Since' with the ID of the last user. +// +// GitHub API docs: https://developer.github.com/v3/users/#get-all-users +func (s *UsersService) ListAll(ctx context.Context, opt *UserListOptions) ([]*User, *Response, error) { + u, err := addOptions("users", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var users []*User + resp, err := s.client.Do(ctx, req, &users) + if err != nil { + return nil, resp, err + } + + return users, resp, nil +} + +// ListInvitations lists all currently-open repository invitations for the +// authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/repos/invitations/#list-a-users-repository-invitations +func (s *UsersService) ListInvitations(ctx context.Context, opt *ListOptions) ([]*RepositoryInvitation, *Response, error) { + u, err := addOptions("user/repository_invitations", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryInvitationsPreview) + + invites := []*RepositoryInvitation{} + resp, err := s.client.Do(ctx, req, &invites) + if err != nil { + return nil, resp, err + } + + return invites, resp, nil +} + +// AcceptInvitation accepts the currently-open repository invitation for the +// authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/repos/invitations/#accept-a-repository-invitation +func (s *UsersService) AcceptInvitation(ctx context.Context, invitationID int64) (*Response, error) { + u := fmt.Sprintf("user/repository_invitations/%v", invitationID) + req, err := s.client.NewRequest("PATCH", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryInvitationsPreview) + + return s.client.Do(ctx, req, nil) +} + +// DeclineInvitation declines the currently-open repository invitation for the +// authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/repos/invitations/#decline-a-repository-invitation +func (s *UsersService) DeclineInvitation(ctx context.Context, invitationID int64) (*Response, error) { + u := fmt.Sprintf("user/repository_invitations/%v", invitationID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryInvitationsPreview) + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/users_administration.go b/vendor/github.com/google/go-github/github/users_administration.go new file mode 100644 index 0000000..e042398 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_administration.go @@ -0,0 +1,67 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// PromoteSiteAdmin promotes a user to a site administrator of a GitHub Enterprise instance. +// +// GitHub API docs: https://developer.github.com/v3/users/administration/#promote-an-ordinary-user-to-a-site-administrator +func (s *UsersService) PromoteSiteAdmin(ctx context.Context, user string) (*Response, error) { + u := fmt.Sprintf("users/%v/site_admin", user) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// DemoteSiteAdmin demotes a user from site administrator of a GitHub Enterprise instance. +// +// GitHub API docs: https://developer.github.com/v3/users/administration/#demote-a-site-administrator-to-an-ordinary-user +func (s *UsersService) DemoteSiteAdmin(ctx context.Context, user string) (*Response, error) { + u := fmt.Sprintf("users/%v/site_admin", user) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// Suspend a user on a GitHub Enterprise instance. +// +// GitHub API docs: https://developer.github.com/v3/users/administration/#suspend-a-user +func (s *UsersService) Suspend(ctx context.Context, user string) (*Response, error) { + u := fmt.Sprintf("users/%v/suspended", user) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// Unsuspend a user on a GitHub Enterprise instance. +// +// GitHub API docs: https://developer.github.com/v3/users/administration/#unsuspend-a-user +func (s *UsersService) Unsuspend(ctx context.Context, user string) (*Response, error) { + u := fmt.Sprintf("users/%v/suspended", user) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/users_administration_test.go b/vendor/github.com/google/go-github/github/users_administration_test.go new file mode 100644 index 0000000..4b0955e --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_administration_test.go @@ -0,0 +1,72 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "net/http" + "testing" +) + +func TestUsersService_PromoteSiteAdmin(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/site_admin", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Users.PromoteSiteAdmin(context.Background(), "u") + if err != nil { + t.Errorf("Users.PromoteSiteAdmin returned error: %v", err) + } +} + +func TestUsersService_DemoteSiteAdmin(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/site_admin", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Users.DemoteSiteAdmin(context.Background(), "u") + if err != nil { + t.Errorf("Users.DemoteSiteAdmin returned error: %v", err) + } +} + +func TestUsersService_Suspend(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/suspended", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Users.Suspend(context.Background(), "u") + if err != nil { + t.Errorf("Users.Suspend returned error: %v", err) + } +} + +func TestUsersService_Unsuspend(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/suspended", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Users.Unsuspend(context.Background(), "u") + if err != nil { + t.Errorf("Users.Unsuspend returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/users_blocking.go b/vendor/github.com/google/go-github/github/users_blocking.go new file mode 100644 index 0000000..39e4560 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_blocking.go @@ -0,0 +1,91 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ListBlockedUsers lists all the blocked users by the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/users/blocking/#list-blocked-users +func (s *UsersService) ListBlockedUsers(ctx context.Context, opt *ListOptions) ([]*User, *Response, error) { + u := "user/blocks" + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeBlockUsersPreview) + + var blockedUsers []*User + resp, err := s.client.Do(ctx, req, &blockedUsers) + if err != nil { + return nil, resp, err + } + + return blockedUsers, resp, nil +} + +// IsBlocked reports whether specified user is blocked by the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/users/blocking/#check-whether-youve-blocked-a-user +func (s *UsersService) IsBlocked(ctx context.Context, user string) (bool, *Response, error) { + u := fmt.Sprintf("user/blocks/%v", user) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeBlockUsersPreview) + + resp, err := s.client.Do(ctx, req, nil) + isBlocked, err := parseBoolResponse(err) + return isBlocked, resp, err +} + +// BlockUser blocks specified user for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/users/blocking/#block-a-user +func (s *UsersService) BlockUser(ctx context.Context, user string) (*Response, error) { + u := fmt.Sprintf("user/blocks/%v", user) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeBlockUsersPreview) + + return s.client.Do(ctx, req, nil) +} + +// UnblockUser unblocks specified user for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/users/blocking/#unblock-a-user +func (s *UsersService) UnblockUser(ctx context.Context, user string) (*Response, error) { + u := fmt.Sprintf("user/blocks/%v", user) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeBlockUsersPreview) + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/users_blocking_test.go b/vendor/github.com/google/go-github/github/users_blocking_test.go new file mode 100644 index 0000000..83e8e30 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_blocking_test.go @@ -0,0 +1,90 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestUsersService_ListBlockedUsers(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/blocks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeBlockUsersPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{ + "login": "octocat" + }]`) + }) + + opt := &ListOptions{Page: 2} + blockedUsers, _, err := client.Users.ListBlockedUsers(context.Background(), opt) + if err != nil { + t.Errorf("Users.ListBlockedUsers returned error: %v", err) + } + + want := []*User{{Login: String("octocat")}} + if !reflect.DeepEqual(blockedUsers, want) { + t.Errorf("Users.ListBlockedUsers returned %+v, want %+v", blockedUsers, want) + } +} + +func TestUsersService_IsBlocked(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/blocks/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeBlockUsersPreview) + w.WriteHeader(http.StatusNoContent) + }) + + isBlocked, _, err := client.Users.IsBlocked(context.Background(), "u") + if err != nil { + t.Errorf("Users.IsBlocked returned error: %v", err) + } + if want := true; isBlocked != want { + t.Errorf("Users.IsBlocked returned %+v, want %+v", isBlocked, want) + } +} + +func TestUsersService_BlockUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/blocks/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testHeader(t, r, "Accept", mediaTypeBlockUsersPreview) + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Users.BlockUser(context.Background(), "u") + if err != nil { + t.Errorf("Users.BlockUser returned error: %v", err) + } +} + +func TestUsersService_UnblockUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/blocks/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeBlockUsersPreview) + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Users.UnblockUser(context.Background(), "u") + if err != nil { + t.Errorf("Users.UnblockUser returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/users_emails.go b/vendor/github.com/google/go-github/github/users_emails.go new file mode 100644 index 0000000..0bbd462 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_emails.go @@ -0,0 +1,71 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "context" + +// UserEmail represents user's email address +type UserEmail struct { + Email *string `json:"email,omitempty"` + Primary *bool `json:"primary,omitempty"` + Verified *bool `json:"verified,omitempty"` +} + +// ListEmails lists all email addresses for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user +func (s *UsersService) ListEmails(ctx context.Context, opt *ListOptions) ([]*UserEmail, *Response, error) { + u := "user/emails" + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var emails []*UserEmail + resp, err := s.client.Do(ctx, req, &emails) + if err != nil { + return nil, resp, err + } + + return emails, resp, nil +} + +// AddEmails adds email addresses of the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/users/emails/#add-email-addresses +func (s *UsersService) AddEmails(ctx context.Context, emails []string) ([]*UserEmail, *Response, error) { + u := "user/emails" + req, err := s.client.NewRequest("POST", u, emails) + if err != nil { + return nil, nil, err + } + + var e []*UserEmail + resp, err := s.client.Do(ctx, req, &e) + if err != nil { + return nil, resp, err + } + + return e, resp, nil +} + +// DeleteEmails deletes email addresses from authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/users/emails/#delete-email-addresses +func (s *UsersService) DeleteEmails(ctx context.Context, emails []string) (*Response, error) { + u := "user/emails" + req, err := s.client.NewRequest("DELETE", u, emails) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/users_emails_test.go b/vendor/github.com/google/go-github/github/users_emails_test.go new file mode 100644 index 0000000..3f72b40 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_emails_test.go @@ -0,0 +1,95 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestUsersService_ListEmails(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/emails", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{ + "email": "user@example.com", + "verified": false, + "primary": true + }]`) + }) + + opt := &ListOptions{Page: 2} + emails, _, err := client.Users.ListEmails(context.Background(), opt) + if err != nil { + t.Errorf("Users.ListEmails returned error: %v", err) + } + + want := []*UserEmail{{Email: String("user@example.com"), Verified: Bool(false), Primary: Bool(true)}} + if !reflect.DeepEqual(emails, want) { + t.Errorf("Users.ListEmails returned %+v, want %+v", emails, want) + } +} + +func TestUsersService_AddEmails(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := []string{"new@example.com"} + + mux.HandleFunc("/user/emails", func(w http.ResponseWriter, r *http.Request) { + var v []string + json.NewDecoder(r.Body).Decode(&v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `[{"email":"old@example.com"}, {"email":"new@example.com"}]`) + }) + + emails, _, err := client.Users.AddEmails(context.Background(), input) + if err != nil { + t.Errorf("Users.AddEmails returned error: %v", err) + } + + want := []*UserEmail{ + {Email: String("old@example.com")}, + {Email: String("new@example.com")}, + } + if !reflect.DeepEqual(emails, want) { + t.Errorf("Users.AddEmails returned %+v, want %+v", emails, want) + } +} + +func TestUsersService_DeleteEmails(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := []string{"user@example.com"} + + mux.HandleFunc("/user/emails", func(w http.ResponseWriter, r *http.Request) { + var v []string + json.NewDecoder(r.Body).Decode(&v) + + testMethod(t, r, "DELETE") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + }) + + _, err := client.Users.DeleteEmails(context.Background(), input) + if err != nil { + t.Errorf("Users.DeleteEmails returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/users_followers.go b/vendor/github.com/google/go-github/github/users_followers.go new file mode 100644 index 0000000..c222409 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_followers.go @@ -0,0 +1,119 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ListFollowers lists the followers for a user. Passing the empty string will +// fetch followers for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/users/followers/#list-followers-of-a-user +func (s *UsersService) ListFollowers(ctx context.Context, user string, opt *ListOptions) ([]*User, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/followers", user) + } else { + u = "user/followers" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var users []*User + resp, err := s.client.Do(ctx, req, &users) + if err != nil { + return nil, resp, err + } + + return users, resp, nil +} + +// ListFollowing lists the people that a user is following. Passing the empty +// string will list people the authenticated user is following. +// +// GitHub API docs: https://developer.github.com/v3/users/followers/#list-users-followed-by-another-user +func (s *UsersService) ListFollowing(ctx context.Context, user string, opt *ListOptions) ([]*User, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/following", user) + } else { + u = "user/following" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var users []*User + resp, err := s.client.Do(ctx, req, &users) + if err != nil { + return nil, resp, err + } + + return users, resp, nil +} + +// IsFollowing checks if "user" is following "target". Passing the empty +// string for "user" will check if the authenticated user is following "target". +// +// GitHub API docs: https://developer.github.com/v3/users/followers/#check-if-you-are-following-a-user +func (s *UsersService) IsFollowing(ctx context.Context, user, target string) (bool, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/following/%v", user, target) + } else { + u = fmt.Sprintf("user/following/%v", target) + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + following, err := parseBoolResponse(err) + return following, resp, err +} + +// Follow will cause the authenticated user to follow the specified user. +// +// GitHub API docs: https://developer.github.com/v3/users/followers/#follow-a-user +func (s *UsersService) Follow(ctx context.Context, user string) (*Response, error) { + u := fmt.Sprintf("user/following/%v", user) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// Unfollow will cause the authenticated user to unfollow the specified user. +// +// GitHub API docs: https://developer.github.com/v3/users/followers/#unfollow-a-user +func (s *UsersService) Unfollow(ctx context.Context, user string) (*Response, error) { + u := fmt.Sprintf("user/following/%v", user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/users_followers_test.go b/vendor/github.com/google/go-github/github/users_followers_test.go new file mode 100644 index 0000000..777fe80 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_followers_test.go @@ -0,0 +1,238 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestUsersService_ListFollowers_authenticatedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/followers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + users, _, err := client.Users.ListFollowers(context.Background(), "", opt) + if err != nil { + t.Errorf("Users.ListFollowers returned error: %v", err) + } + + want := []*User{{ID: Int64(1)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Users.ListFollowers returned %+v, want %+v", users, want) + } +} + +func TestUsersService_ListFollowers_specifiedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/followers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + users, _, err := client.Users.ListFollowers(context.Background(), "u", nil) + if err != nil { + t.Errorf("Users.ListFollowers returned error: %v", err) + } + + want := []*User{{ID: Int64(1)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Users.ListFollowers returned %+v, want %+v", users, want) + } +} + +func TestUsersService_ListFollowers_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Users.ListFollowers(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestUsersService_ListFollowing_authenticatedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/following", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opts := &ListOptions{Page: 2} + users, _, err := client.Users.ListFollowing(context.Background(), "", opts) + if err != nil { + t.Errorf("Users.ListFollowing returned error: %v", err) + } + + want := []*User{{ID: Int64(1)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Users.ListFollowing returned %+v, want %+v", users, want) + } +} + +func TestUsersService_ListFollowing_specifiedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/following", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + users, _, err := client.Users.ListFollowing(context.Background(), "u", nil) + if err != nil { + t.Errorf("Users.ListFollowing returned error: %v", err) + } + + want := []*User{{ID: Int64(1)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Users.ListFollowing returned %+v, want %+v", users, want) + } +} + +func TestUsersService_ListFollowing_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Users.ListFollowing(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestUsersService_IsFollowing_authenticatedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/following/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + following, _, err := client.Users.IsFollowing(context.Background(), "", "t") + if err != nil { + t.Errorf("Users.IsFollowing returned error: %v", err) + } + if want := true; following != want { + t.Errorf("Users.IsFollowing returned %+v, want %+v", following, want) + } +} + +func TestUsersService_IsFollowing_specifiedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/following/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + following, _, err := client.Users.IsFollowing(context.Background(), "u", "t") + if err != nil { + t.Errorf("Users.IsFollowing returned error: %v", err) + } + if want := true; following != want { + t.Errorf("Users.IsFollowing returned %+v, want %+v", following, want) + } +} + +func TestUsersService_IsFollowing_false(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/following/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + following, _, err := client.Users.IsFollowing(context.Background(), "u", "t") + if err != nil { + t.Errorf("Users.IsFollowing returned error: %v", err) + } + if want := false; following != want { + t.Errorf("Users.IsFollowing returned %+v, want %+v", following, want) + } +} + +func TestUsersService_IsFollowing_error(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/following/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + following, _, err := client.Users.IsFollowing(context.Background(), "u", "t") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; following != want { + t.Errorf("Users.IsFollowing returned %+v, want %+v", following, want) + } +} + +func TestUsersService_IsFollowing_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Users.IsFollowing(context.Background(), "%", "%") + testURLParseError(t, err) +} + +func TestUsersService_Follow(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/following/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + }) + + _, err := client.Users.Follow(context.Background(), "u") + if err != nil { + t.Errorf("Users.Follow returned error: %v", err) + } +} + +func TestUsersService_Follow_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Users.Follow(context.Background(), "%") + testURLParseError(t, err) +} + +func TestUsersService_Unfollow(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/following/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Users.Unfollow(context.Background(), "u") + if err != nil { + t.Errorf("Users.Follow returned error: %v", err) + } +} + +func TestUsersService_Unfollow_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, err := client.Users.Unfollow(context.Background(), "%") + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/users_gpg_keys.go b/vendor/github.com/google/go-github/github/users_gpg_keys.go new file mode 100644 index 0000000..d8bbc52 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_gpg_keys.go @@ -0,0 +1,140 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "time" +) + +// GPGKey represents a GitHub user's public GPG key used to verify GPG signed commits and tags. +// +// https://developer.github.com/changes/2016-04-04-git-signing-api-preview/ +type GPGKey struct { + ID *int64 `json:"id,omitempty"` + PrimaryKeyID *int64 `json:"primary_key_id,omitempty"` + KeyID *string `json:"key_id,omitempty"` + PublicKey *string `json:"public_key,omitempty"` + Emails []GPGEmail `json:"emails,omitempty"` + Subkeys []GPGKey `json:"subkeys,omitempty"` + CanSign *bool `json:"can_sign,omitempty"` + CanEncryptComms *bool `json:"can_encrypt_comms,omitempty"` + CanEncryptStorage *bool `json:"can_encrypt_storage,omitempty"` + CanCertify *bool `json:"can_certify,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + ExpiresAt *time.Time `json:"expires_at,omitempty"` +} + +// String stringifies a GPGKey. +func (k GPGKey) String() string { + return Stringify(k) +} + +// GPGEmail represents an email address associated to a GPG key. +type GPGEmail struct { + Email *string `json:"email,omitempty"` + Verified *bool `json:"verified,omitempty"` +} + +// ListGPGKeys lists the public GPG keys for a user. Passing the empty +// string will fetch keys for the authenticated user. It requires authentication +// via Basic Auth or via OAuth with at least read:gpg_key scope. +// +// GitHub API docs: https://developer.github.com/v3/users/gpg_keys/#list-gpg-keys-for-a-user +func (s *UsersService) ListGPGKeys(ctx context.Context, user string, opt *ListOptions) ([]*GPGKey, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/gpg_keys", user) + } else { + u = "user/gpg_keys" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeGitSigningPreview) + + var keys []*GPGKey + resp, err := s.client.Do(ctx, req, &keys) + if err != nil { + return nil, resp, err + } + + return keys, resp, nil +} + +// GetGPGKey gets extended details for a single GPG key. It requires authentication +// via Basic Auth or via OAuth with at least read:gpg_key scope. +// +// GitHub API docs: https://developer.github.com/v3/users/gpg_keys/#get-a-single-gpg-key +func (s *UsersService) GetGPGKey(ctx context.Context, id int64) (*GPGKey, *Response, error) { + u := fmt.Sprintf("user/gpg_keys/%v", id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeGitSigningPreview) + + key := &GPGKey{} + resp, err := s.client.Do(ctx, req, key) + if err != nil { + return nil, resp, err + } + + return key, resp, nil +} + +// CreateGPGKey creates a GPG key. It requires authenticatation via Basic Auth +// or OAuth with at least write:gpg_key scope. +// +// GitHub API docs: https://developer.github.com/v3/users/gpg_keys/#create-a-gpg-key +func (s *UsersService) CreateGPGKey(ctx context.Context, armoredPublicKey string) (*GPGKey, *Response, error) { + gpgKey := &struct { + ArmoredPublicKey string `json:"armored_public_key"` + }{ArmoredPublicKey: armoredPublicKey} + req, err := s.client.NewRequest("POST", "user/gpg_keys", gpgKey) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeGitSigningPreview) + + key := &GPGKey{} + resp, err := s.client.Do(ctx, req, key) + if err != nil { + return nil, resp, err + } + + return key, resp, nil +} + +// DeleteGPGKey deletes a GPG key. It requires authentication via Basic Auth or +// via OAuth with at least admin:gpg_key scope. +// +// GitHub API docs: https://developer.github.com/v3/users/gpg_keys/#delete-a-gpg-key +func (s *UsersService) DeleteGPGKey(ctx context.Context, id int64) (*Response, error) { + u := fmt.Sprintf("user/gpg_keys/%v", id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeGitSigningPreview) + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/users_gpg_keys_test.go b/vendor/github.com/google/go-github/github/users_gpg_keys_test.go new file mode 100644 index 0000000..ca0207e --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_gpg_keys_test.go @@ -0,0 +1,142 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestUsersService_ListGPGKeys_authenticatedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/gpg_keys", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeGitSigningPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1,"primary_key_id":2}]`) + }) + + opt := &ListOptions{Page: 2} + keys, _, err := client.Users.ListGPGKeys(context.Background(), "", opt) + if err != nil { + t.Errorf("Users.ListGPGKeys returned error: %v", err) + } + + want := []*GPGKey{{ID: Int64(1), PrimaryKeyID: Int64(2)}} + if !reflect.DeepEqual(keys, want) { + t.Errorf("Users.ListGPGKeys = %+v, want %+v", keys, want) + } +} + +func TestUsersService_ListGPGKeys_specifiedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/gpg_keys", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeGitSigningPreview) + fmt.Fprint(w, `[{"id":1,"primary_key_id":2}]`) + }) + + keys, _, err := client.Users.ListGPGKeys(context.Background(), "u", nil) + if err != nil { + t.Errorf("Users.ListGPGKeys returned error: %v", err) + } + + want := []*GPGKey{{ID: Int64(1), PrimaryKeyID: Int64(2)}} + if !reflect.DeepEqual(keys, want) { + t.Errorf("Users.ListGPGKeys = %+v, want %+v", keys, want) + } +} + +func TestUsersService_ListGPGKeys_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Users.ListGPGKeys(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestUsersService_GetGPGKey(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/gpg_keys/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeGitSigningPreview) + fmt.Fprint(w, `{"id":1}`) + }) + + key, _, err := client.Users.GetGPGKey(context.Background(), 1) + if err != nil { + t.Errorf("Users.GetGPGKey returned error: %v", err) + } + + want := &GPGKey{ID: Int64(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Users.GetGPGKey = %+v, want %+v", key, want) + } +} + +func TestUsersService_CreateGPGKey(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := ` +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: GPGTools - https://gpgtools.org + +mQINBFcEd9kBEACo54TDbGhKlXKWMvJgecEUKPPcv7XdnpKdGb3LRw5MvFwT0V0f +... +=tqfb +-----END PGP PUBLIC KEY BLOCK-----` + + mux.HandleFunc("/user/gpg_keys", func(w http.ResponseWriter, r *http.Request) { + var gpgKey struct { + ArmoredPublicKey *string `json:"armored_public_key,omitempty"` + } + json.NewDecoder(r.Body).Decode(&gpgKey) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeGitSigningPreview) + if gpgKey.ArmoredPublicKey == nil || *gpgKey.ArmoredPublicKey != input { + t.Errorf("gpgKey = %+v, want %q", gpgKey, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + gpgKey, _, err := client.Users.CreateGPGKey(context.Background(), input) + if err != nil { + t.Errorf("Users.GetGPGKey returned error: %v", err) + } + + want := &GPGKey{ID: Int64(1)} + if !reflect.DeepEqual(gpgKey, want) { + t.Errorf("Users.GetGPGKey = %+v, want %+v", gpgKey, want) + } +} + +func TestUsersService_DeleteGPGKey(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/gpg_keys/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeGitSigningPreview) + }) + + _, err := client.Users.DeleteGPGKey(context.Background(), 1) + if err != nil { + t.Errorf("Users.DeleteGPGKey returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/users_keys.go b/vendor/github.com/google/go-github/github/users_keys.go new file mode 100644 index 0000000..ddc832a --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_keys.go @@ -0,0 +1,108 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// Key represents a public SSH key used to authenticate a user or deploy script. +type Key struct { + ID *int64 `json:"id,omitempty"` + Key *string `json:"key,omitempty"` + URL *string `json:"url,omitempty"` + Title *string `json:"title,omitempty"` + ReadOnly *bool `json:"read_only,omitempty"` +} + +func (k Key) String() string { + return Stringify(k) +} + +// ListKeys lists the verified public keys for a user. Passing the empty +// string will fetch keys for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/users/keys/#list-public-keys-for-a-user +func (s *UsersService) ListKeys(ctx context.Context, user string, opt *ListOptions) ([]*Key, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/keys", user) + } else { + u = "user/keys" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var keys []*Key + resp, err := s.client.Do(ctx, req, &keys) + if err != nil { + return nil, resp, err + } + + return keys, resp, nil +} + +// GetKey fetches a single public key. +// +// GitHub API docs: https://developer.github.com/v3/users/keys/#get-a-single-public-key +func (s *UsersService) GetKey(ctx context.Context, id int64) (*Key, *Response, error) { + u := fmt.Sprintf("user/keys/%v", id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + key := new(Key) + resp, err := s.client.Do(ctx, req, key) + if err != nil { + return nil, resp, err + } + + return key, resp, nil +} + +// CreateKey adds a public key for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/users/keys/#create-a-public-key +func (s *UsersService) CreateKey(ctx context.Context, key *Key) (*Key, *Response, error) { + u := "user/keys" + + req, err := s.client.NewRequest("POST", u, key) + if err != nil { + return nil, nil, err + } + + k := new(Key) + resp, err := s.client.Do(ctx, req, k) + if err != nil { + return nil, resp, err + } + + return k, resp, nil +} + +// DeleteKey deletes a public key. +// +// GitHub API docs: https://developer.github.com/v3/users/keys/#delete-a-public-key +func (s *UsersService) DeleteKey(ctx context.Context, id int64) (*Response, error) { + u := fmt.Sprintf("user/keys/%v", id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/github/users_keys_test.go b/vendor/github.com/google/go-github/github/users_keys_test.go new file mode 100644 index 0000000..3115fdb --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_keys_test.go @@ -0,0 +1,128 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestUsersService_ListKeys_authenticatedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/keys", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + keys, _, err := client.Users.ListKeys(context.Background(), "", opt) + if err != nil { + t.Errorf("Users.ListKeys returned error: %v", err) + } + + want := []*Key{{ID: Int64(1)}} + if !reflect.DeepEqual(keys, want) { + t.Errorf("Users.ListKeys returned %+v, want %+v", keys, want) + } +} + +func TestUsersService_ListKeys_specifiedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/keys", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + keys, _, err := client.Users.ListKeys(context.Background(), "u", nil) + if err != nil { + t.Errorf("Users.ListKeys returned error: %v", err) + } + + want := []*Key{{ID: Int64(1)}} + if !reflect.DeepEqual(keys, want) { + t.Errorf("Users.ListKeys returned %+v, want %+v", keys, want) + } +} + +func TestUsersService_ListKeys_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Users.ListKeys(context.Background(), "%", nil) + testURLParseError(t, err) +} + +func TestUsersService_GetKey(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/keys/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + key, _, err := client.Users.GetKey(context.Background(), 1) + if err != nil { + t.Errorf("Users.GetKey returned error: %v", err) + } + + want := &Key{ID: Int64(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Users.GetKey returned %+v, want %+v", key, want) + } +} + +func TestUsersService_CreateKey(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &Key{Key: String("k"), Title: String("t")} + + mux.HandleFunc("/user/keys", func(w http.ResponseWriter, r *http.Request) { + v := new(Key) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + key, _, err := client.Users.CreateKey(context.Background(), input) + if err != nil { + t.Errorf("Users.GetKey returned error: %v", err) + } + + want := &Key{ID: Int64(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Users.GetKey returned %+v, want %+v", key, want) + } +} + +func TestUsersService_DeleteKey(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/keys/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Users.DeleteKey(context.Background(), 1) + if err != nil { + t.Errorf("Users.DeleteKey returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/users_test.go b/vendor/github.com/google/go-github/github/users_test.go new file mode 100644 index 0000000..b011908 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_test.go @@ -0,0 +1,268 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestUser_marshall(t *testing.T) { + testJSONMarshal(t, &User{}, "{}") + + u := &User{ + Login: String("l"), + ID: Int64(1), + URL: String("u"), + AvatarURL: String("a"), + GravatarID: String("g"), + Name: String("n"), + Company: String("c"), + Blog: String("b"), + Location: String("l"), + Email: String("e"), + Hireable: Bool(true), + PublicRepos: Int(1), + Followers: Int(1), + Following: Int(1), + CreatedAt: &Timestamp{referenceTime}, + SuspendedAt: &Timestamp{referenceTime}, + } + want := `{ + "login": "l", + "id": 1, + "avatar_url": "a", + "gravatar_id": "g", + "name": "n", + "company": "c", + "blog": "b", + "location": "l", + "email": "e", + "hireable": true, + "public_repos": 1, + "followers": 1, + "following": 1, + "created_at": ` + referenceTimeStr + `, + "suspended_at": ` + referenceTimeStr + `, + "url": "u" + }` + testJSONMarshal(t, u, want) +} + +func TestUsersService_Get_authenticatedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + user, _, err := client.Users.Get(context.Background(), "") + if err != nil { + t.Errorf("Users.Get returned error: %v", err) + } + + want := &User{ID: Int64(1)} + if !reflect.DeepEqual(user, want) { + t.Errorf("Users.Get returned %+v, want %+v", user, want) + } +} + +func TestUsersService_Get_specifiedUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + user, _, err := client.Users.Get(context.Background(), "u") + if err != nil { + t.Errorf("Users.Get returned error: %v", err) + } + + want := &User{ID: Int64(1)} + if !reflect.DeepEqual(user, want) { + t.Errorf("Users.Get returned %+v, want %+v", user, want) + } +} + +func TestUsersService_Get_invalidUser(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + _, _, err := client.Users.Get(context.Background(), "%") + testURLParseError(t, err) +} + +func TestUsersService_GetByID(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + user, _, err := client.Users.GetByID(context.Background(), 1) + if err != nil { + t.Fatalf("Users.GetByID returned error: %v", err) + } + + want := &User{ID: Int64(1)} + if !reflect.DeepEqual(user, want) { + t.Errorf("Users.GetByID returned %+v, want %+v", user, want) + } +} + +func TestUsersService_Edit(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + input := &User{Name: String("n")} + + mux.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { + v := new(User) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + user, _, err := client.Users.Edit(context.Background(), input) + if err != nil { + t.Errorf("Users.Edit returned error: %v", err) + } + + want := &User{ID: Int64(1)} + if !reflect.DeepEqual(user, want) { + t.Errorf("Users.Edit returned %+v, want %+v", user, want) + } +} + +func TestUsersService_GetHovercard(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users/u/hovercard", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeHovercardPreview) + testFormValues(t, r, values{"subject_type": "repository", "subject_id": "20180408"}) + fmt.Fprint(w, `{"contexts": [{"message":"Owns this repository", "octicon": "repo"}]}`) + }) + + opt := &HovercardOptions{SubjectType: "repository", SubjectID: "20180408"} + hovercard, _, err := client.Users.GetHovercard(context.Background(), "u", opt) + if err != nil { + t.Errorf("Users.GetHovercard returned error: %v", err) + } + + want := &Hovercard{Contexts: []*UserContext{{Message: String("Owns this repository"), Octicon: String("repo")}}} + if !reflect.DeepEqual(hovercard, want) { + t.Errorf("Users.GetHovercard returned %+v, want %+v", hovercard, want) + } +} + +func TestUsersService_ListAll(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"since": "1", "page": "2"}) + fmt.Fprint(w, `[{"id":2}]`) + }) + + opt := &UserListOptions{1, ListOptions{Page: 2}} + users, _, err := client.Users.ListAll(context.Background(), opt) + if err != nil { + t.Errorf("Users.Get returned error: %v", err) + } + + want := []*User{{ID: Int64(2)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Users.ListAll returned %+v, want %+v", users, want) + } +} + +func TestUsersService_ListInvitations(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/repository_invitations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeRepositoryInvitationsPreview) + fmt.Fprintf(w, `[{"id":1}, {"id":2}]`) + }) + + got, _, err := client.Users.ListInvitations(context.Background(), nil) + if err != nil { + t.Errorf("Users.ListInvitations returned error: %v", err) + } + + want := []*RepositoryInvitation{{ID: Int64(1)}, {ID: Int64(2)}} + if !reflect.DeepEqual(got, want) { + t.Errorf("Users.ListInvitations = %+v, want %+v", got, want) + } +} + +func TestUsersService_ListInvitations_withOptions(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/repository_invitations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + testHeader(t, r, "Accept", mediaTypeRepositoryInvitationsPreview) + fmt.Fprintf(w, `[{"id":1}, {"id":2}]`) + }) + + _, _, err := client.Users.ListInvitations(context.Background(), &ListOptions{Page: 2}) + if err != nil { + t.Errorf("Users.ListInvitations returned error: %v", err) + } +} +func TestUsersService_AcceptInvitation(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/repository_invitations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeRepositoryInvitationsPreview) + w.WriteHeader(http.StatusNoContent) + }) + + if _, err := client.Users.AcceptInvitation(context.Background(), 1); err != nil { + t.Errorf("Users.AcceptInvitation returned error: %v", err) + } +} + +func TestUsersService_DeclineInvitation(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/user/repository_invitations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeRepositoryInvitationsPreview) + w.WriteHeader(http.StatusNoContent) + }) + + if _, err := client.Users.DeclineInvitation(context.Background(), 1); err != nil { + t.Errorf("Users.DeclineInvitation returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/with_appengine.go b/vendor/github.com/google/go-github/github/with_appengine.go new file mode 100644 index 0000000..59ce26b --- /dev/null +++ b/vendor/github.com/google/go-github/github/with_appengine.go @@ -0,0 +1,20 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build appengine + +// This file provides glue for making github work on App Engine. + +package github + +import ( + "context" + "net/http" +) + +func withContext(ctx context.Context, req *http.Request) *http.Request { + // No-op because App Engine adds context to a request differently. + return req +} diff --git a/vendor/github.com/google/go-github/github/without_appengine.go b/vendor/github.com/google/go-github/github/without_appengine.go new file mode 100644 index 0000000..6f8fdac --- /dev/null +++ b/vendor/github.com/google/go-github/github/without_appengine.go @@ -0,0 +1,19 @@ +// Copyright 2017 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !appengine + +// This file provides glue for making github work without App Engine. + +package github + +import ( + "context" + "net/http" +) + +func withContext(ctx context.Context, req *http.Request) *http.Request { + return req.WithContext(ctx) +} diff --git a/vendor/github.com/google/go-github/go.mod b/vendor/github.com/google/go-github/go.mod new file mode 100644 index 0000000..9fb89f5 --- /dev/null +++ b/vendor/github.com/google/go-github/go.mod @@ -0,0 +1,11 @@ +module github.com/google/go-github/v18 + +require ( + github.com/golang/protobuf v1.2.0 // indirect + github.com/google/go-querystring v1.0.0 + golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac + golang.org/x/net v0.0.0-20180826012351-8a410e7b638d // indirect + golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be + golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87 // indirect + google.golang.org/appengine v1.1.0 +) diff --git a/vendor/github.com/google/go-github/go.sum b/vendor/github.com/google/go-github/go.sum new file mode 100644 index 0000000..0f7b90e --- /dev/null +++ b/vendor/github.com/google/go-github/go.sum @@ -0,0 +1,15 @@ +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= +github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac h1:7d7lG9fHOLdL6jZPtnV4LpI41SbohIJ1Atq7U991dMg= +golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87 h1:GqwDwfvIpC33dK9bA1fD+JiDUNsuAiQiEkpHqUKze4o= +golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= diff --git a/vendor/github.com/google/go-github/test/README.md b/vendor/github.com/google/go-github/test/README.md new file mode 100644 index 0000000..cb3ef55 --- /dev/null +++ b/vendor/github.com/google/go-github/test/README.md @@ -0,0 +1,62 @@ +go-github tests +=============== + +This directory contains additional test suites beyond the unit tests already in +[../github](../github). Whereas the unit tests run very quickly (since they +don't make any network calls) and are run by Travis on every commit, the tests +in this directory are only run manually. + +The test packages are: + +integration +----------- + +This will exercise the entire go-github library (or at least as much as is +practical) against the live GitHub API. These tests will verify that the +library is properly coded against the actual behavior of the API, and will +(hopefully) fail upon any incompatible change in the API. + +Because these tests are running using live data, there is a much higher +probability of false positives in test failures due to network issues, test +data having been changed, etc. + +These tests send real network traffic to the GitHub API and will exhaust the +default unregistered rate limit (60 requests per hour) very quickly. +Additionally, in order to test the methods that modify data, a real OAuth token +will need to be present. While the tests will try to be well-behaved in terms +of what data they modify, it is **strongly** recommended that these tests only +be run using a dedicated test account. + +Run tests using: + + GITHUB_AUTH_TOKEN=XXX go test -v -tags=integration ./integration + +Additionally there are a set of integration tests for the Authorizations API. +These tests require a GitHub user (username and password), and also that a +[GitHub Application](https://github.com/settings/applications/new) (with +attendant Client ID and Client Secret) be available. Then, to execute just the +Authorization tests: + + GITHUB_USERNAME='' GITHUB_PASSWORD='' GITHUB_CLIENT_ID='' GITHUB_CLIENT_SECRET='' go test -v -tags=integration -run=Authorizations ./integration + +If some or all of these environment variables are not available, certain of the +Authorization integration tests will be skipped. + +fields +------ + +This will identify the fields being returned by the live GitHub API that are +not currently being mapped into the relevant Go data type. Sometimes fields +are deliberately not mapped, so the results of this tool should just be taken +as a hint. + +This test sends real network traffic to the GitHub API and will exhaust the +default unregistered rate limit (60 requests per hour) very quickly. +Additionally, some data is only returned for authenticated API calls. Unlike +the integration tests above, these tests only read data, so it's less +imperitive that these be run using a dedicated test account (though you still +really should). + +Run the fields tool using: + + GITHUB_AUTH_TOKEN=XXX go run ./fields/fields.go diff --git a/vendor/github.com/google/go-github/test/fields/fields.go b/vendor/github.com/google/go-github/test/fields/fields.go new file mode 100644 index 0000000..1a75a4c --- /dev/null +++ b/vendor/github.com/google/go-github/test/fields/fields.go @@ -0,0 +1,148 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This tool tests for the JSON mappings in the go-github data types. It will +// identify fields that are returned by the live GitHub API, but that are not +// currently mapped into a struct field of the relevant go-github type. This +// helps to ensure that all relevant data returned by the API is being made +// accessible, particularly new fields that are periodically (and sometimes +// quietly) added to the API over time. +// +// These tests simply aid in identifying which fields aren't being mapped; it +// is not necessarily true that every one of them should always be mapped. +// Some fields may be undocumented for a reason, either because they aren't +// actually used yet or should not be relied upon. +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + "reflect" + "strings" + + "github.com/google/go-github/v18/github" + "golang.org/x/oauth2" +) + +var ( + client *github.Client + + // auth indicates whether tests are being run with an OAuth token. + // Tests can use this flag to skip certain tests when run without auth. + auth bool + + skipURLs = flag.Bool("skip_urls", false, "skip url fields") +) + +func main() { + flag.Parse() + + token := os.Getenv("GITHUB_AUTH_TOKEN") + if token == "" { + print("!!! No OAuth token. Some tests won't run. !!!\n\n") + client = github.NewClient(nil) + } else { + tc := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: token}, + )) + client = github.NewClient(tc) + auth = true + } + + for _, tt := range []struct { + url string + typ interface{} + }{ + //{"rate_limit", &github.RateLimits{}}, + {"users/octocat", &github.User{}}, + {"user", &github.User{}}, + {"users/willnorris/keys", &[]github.Key{}}, + {"orgs/google-test", &github.Organization{}}, + {"repos/google/go-github", &github.Repository{}}, + {"repos/google/go-github/issues/1", &github.Issue{}}, + {"/gists/9257657", &github.Gist{}}, + } { + err := testType(tt.url, tt.typ) + if err != nil { + fmt.Printf("error: %v\n", err) + } + } +} + +// testType fetches the JSON resource at urlStr and compares its keys to the +// struct fields of typ. +func testType(urlStr string, typ interface{}) error { + slice := reflect.Indirect(reflect.ValueOf(typ)).Kind() == reflect.Slice + + req, err := client.NewRequest("GET", urlStr, nil) + if err != nil { + return err + } + + // start with a json.RawMessage so we can decode multiple ways below + raw := new(json.RawMessage) + _, err = client.Do(context.Background(), req, raw) + if err != nil { + return err + } + + // unmarshal directly to a map + var m1 map[string]interface{} + if slice { + var s []map[string]interface{} + err = json.Unmarshal(*raw, &s) + if err != nil { + return err + } + m1 = s[0] + } else { + err = json.Unmarshal(*raw, &m1) + if err != nil { + return err + } + } + + // unmarshal to typ first, then re-marshal and unmarshal to a map + err = json.Unmarshal(*raw, typ) + if err != nil { + return err + } + + var byt []byte + if slice { + // use first item in slice + v := reflect.Indirect(reflect.ValueOf(typ)) + byt, err = json.Marshal(v.Index(0).Interface()) + if err != nil { + return err + } + } else { + byt, err = json.Marshal(typ) + if err != nil { + return err + } + } + + var m2 map[string]interface{} + err = json.Unmarshal(byt, &m2) + if err != nil { + return err + } + + // now compare the two maps + for k, v := range m1 { + if *skipURLs && strings.HasSuffix(k, "_url") { + continue + } + if _, ok := m2[k]; !ok { + fmt.Printf("%v missing field for key: %v (example value: %v)\n", reflect.TypeOf(typ), k, v) + } + } + + return nil +} diff --git a/vendor/github.com/google/go-github/test/integration/activity_test.go b/vendor/github.com/google/go-github/test/integration/activity_test.go new file mode 100644 index 0000000..df6c34d --- /dev/null +++ b/vendor/github.com/google/go-github/test/integration/activity_test.go @@ -0,0 +1,141 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build integration + +package integration + +import ( + "context" + "testing" + + "github.com/google/go-github/github" +) + +const ( + owner = "google" + repo = "go-github" +) + +func TestActivity_Starring(t *testing.T) { + stargazers, _, err := client.Activity.ListStargazers(context.Background(), owner, repo, nil) + if err != nil { + t.Fatalf("Activity.ListStargazers returned error: %v", err) + } + + if len(stargazers) == 0 { + t.Errorf("Activity.ListStargazers(%q, %q) returned no stargazers", owner, repo) + } + + // the rest of the tests requires auth + if !checkAuth("TestActivity_Starring") { + return + } + + // first, check if already starred the target repository + star, _, err := client.Activity.IsStarred(context.Background(), owner, repo) + if err != nil { + t.Fatalf("Activity.IsStarred returned error: %v", err) + } + if star { + t.Fatalf("Already starring %v/%v. Please manually unstar it first.", owner, repo) + } + + // star the target repository + _, err = client.Activity.Star(context.Background(), owner, repo) + if err != nil { + t.Fatalf("Activity.Star returned error: %v", err) + } + + // check again and verify starred + star, _, err = client.Activity.IsStarred(context.Background(), owner, repo) + if err != nil { + t.Fatalf("Activity.IsStarred returned error: %v", err) + } + if !star { + t.Fatalf("Not starred %v/%v after starring it.", owner, repo) + } + + // unstar + _, err = client.Activity.Unstar(context.Background(), owner, repo) + if err != nil { + t.Fatalf("Activity.Unstar returned error: %v", err) + } + + // check again and verify not watching + star, _, err = client.Activity.IsStarred(context.Background(), owner, repo) + if err != nil { + t.Fatalf("Activity.IsStarred returned error: %v", err) + } + if star { + t.Fatalf("Still starred %v/%v after unstarring it.", owner, repo) + } +} + +func deleteSubscription(t *testing.T) { + // delete subscription + _, err := client.Activity.DeleteRepositorySubscription(context.Background(), owner, repo) + if err != nil { + t.Fatalf("Activity.DeleteRepositorySubscription returned error: %v", err) + } + + // check again and verify not watching + sub, _, err := client.Activity.GetRepositorySubscription(context.Background(), owner, repo) + if err != nil { + t.Fatalf("Activity.GetRepositorySubscription returned error: %v", err) + } + if sub != nil { + t.Fatalf("Still watching %v/%v after deleting subscription.", owner, repo) + } +} + +func createSubscription(t *testing.T) { + // watch the target repository + sub := &github.Subscription{Subscribed: github.Bool(true)} + _, _, err := client.Activity.SetRepositorySubscription(context.Background(), owner, repo, sub) + if err != nil { + t.Fatalf("Activity.SetRepositorySubscription returned error: %v", err) + } + + // check again and verify watching + sub, _, err = client.Activity.GetRepositorySubscription(context.Background(), owner, repo) + if err != nil { + t.Fatalf("Activity.GetRepositorySubscription returned error: %v", err) + } + if sub == nil || !*sub.Subscribed { + t.Fatalf("Not watching %v/%v after setting subscription.", owner, repo) + } +} + +func TestActivity_Watching(t *testing.T) { + watchers, _, err := client.Activity.ListWatchers(context.Background(), owner, repo, nil) + if err != nil { + t.Fatalf("Activity.ListWatchers returned error: %v", err) + } + + if len(watchers) == 0 { + t.Errorf("Activity.ListWatchers(%q, %q) returned no watchers", owner, repo) + } + + // the rest of the tests requires auth + if !checkAuth("TestActivity_Watching") { + return + } + + // first, check if already watching the target repository + sub, _, err := client.Activity.GetRepositorySubscription(context.Background(), owner, repo) + if err != nil { + t.Fatalf("Activity.GetRepositorySubscription returned error: %v", err) + } + + switch { + case sub != nil: // If already subscribing, delete then recreate subscription. + deleteSubscription(t) + createSubscription(t) + case sub == nil: // Otherwise, create subscription and then delete it. + createSubscription(t) + deleteSubscription(t) + } +} diff --git a/vendor/github.com/google/go-github/test/integration/authorizations_test.go b/vendor/github.com/google/go-github/test/integration/authorizations_test.go new file mode 100644 index 0000000..532ac76 --- /dev/null +++ b/vendor/github.com/google/go-github/test/integration/authorizations_test.go @@ -0,0 +1,306 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build integration + +package integration + +import ( + "context" + "math/rand" + "os" + "strconv" + "strings" + "testing" + "time" + + "github.com/google/go-github/github" +) + +const msgEnvMissing = "Skipping test because the required environment variable (%v) is not present." +const envKeyGitHubUsername = "GITHUB_USERNAME" +const envKeyGitHubPassword = "GITHUB_PASSWORD" +const envKeyClientID = "GITHUB_CLIENT_ID" +const envKeyClientSecret = "GITHUB_CLIENT_SECRET" +const InvalidTokenValue = "iamnotacroken" + +// TestAuthorizationsBasicOperations tests the basic CRUD operations of the API (mostly for +// the Personal Access Token scenario). +func TestAuthorizationsBasicOperations(t *testing.T) { + + client := getUserPassClient(t) + + auths, resp, err := client.Authorizations.List(context.Background(), nil) + failOnError(t, err) + failIfNotStatusCode(t, resp, 200) + + initialAuthCount := len(auths) + + authReq := generatePersonalAuthTokenRequest() + + createdAuth, resp, err := client.Authorizations.Create(context.Background(), authReq) + failOnError(t, err) + failIfNotStatusCode(t, resp, 201) + + if *authReq.Note != *createdAuth.Note { + t.Fatal("Returned Authorization does not match the requested Authorization.") + } + + auths, resp, err = client.Authorizations.List(context.Background(), nil) + failOnError(t, err) + failIfNotStatusCode(t, resp, 200) + + if len(auths) != initialAuthCount+1 { + t.Fatalf("The number of Authorizations should have increased. Expected [%v], was [%v]", initialAuthCount+1, len(auths)) + } + + // Test updating the authorization + authUpdate := new(github.AuthorizationUpdateRequest) + authUpdate.Note = github.String("Updated note: " + randString()) + + updatedAuth, resp, err := client.Authorizations.Edit(context.Background(), *createdAuth.ID, authUpdate) + failOnError(t, err) + failIfNotStatusCode(t, resp, 200) + + if *updatedAuth.Note != *authUpdate.Note { + t.Fatal("The returned Authorization does not match the requested updated value.") + } + + // Verify that the Get operation also reflects the update + retrievedAuth, resp, err := client.Authorizations.Get(context.Background(), *createdAuth.ID) + failOnError(t, err) + failIfNotStatusCode(t, resp, 200) + + if *retrievedAuth.Note != *updatedAuth.Note { + t.Fatal("The retrieved Authorization does not match the expected (updated) value.") + } + + // Now, let's delete... + resp, err = client.Authorizations.Delete(context.Background(), *createdAuth.ID) + failOnError(t, err) + failIfNotStatusCode(t, resp, 204) + + // Verify that we can no longer retrieve the auth + retrievedAuth, resp, err = client.Authorizations.Get(context.Background(), *createdAuth.ID) + if err == nil { + t.Fatal("Should have failed due to 404") + } + failIfNotStatusCode(t, resp, 404) + + // Verify that our count reset back to the initial value + auths, resp, err = client.Authorizations.List(context.Background(), nil) + failOnError(t, err) + failIfNotStatusCode(t, resp, 200) + + if len(auths) != initialAuthCount { + t.Fatalf("The number of Authorizations should match the initial count Expected [%v], got [%v]", initialAuthCount, len(auths)) + } + +} + +// TestAuthorizationsAppOperations tests the application/token related operations, such +// as creating, testing, resetting and revoking application OAuth tokens. +func TestAuthorizationsAppOperations(t *testing.T) { + + userAuthenticatedClient := getUserPassClient(t) + + appAuthenticatedClient := getOAuthAppClient(t) + + // We know these vars are set because getOAuthAppClient would have + // skipped the test by now + clientID := os.Getenv(envKeyClientID) + clientSecret := os.Getenv(envKeyClientSecret) + + authRequest := generateAppAuthTokenRequest(clientID, clientSecret) + + createdAuth, resp, err := userAuthenticatedClient.Authorizations.GetOrCreateForApp(context.Background(), clientID, authRequest) + failOnError(t, err) + failIfNotStatusCode(t, resp, 201) + + // Quick sanity check: + if *createdAuth.Note != *authRequest.Note { + t.Fatal("The returned auth does not match expected value.") + } + + // Let's try the same request again, this time it should return the same + // auth instead of creating a new one + secondAuth, resp, err := userAuthenticatedClient.Authorizations.GetOrCreateForApp(context.Background(), clientID, authRequest) + failOnError(t, err) + failIfNotStatusCode(t, resp, 200) + + // Verify that the IDs are the same + if *createdAuth.ID != *secondAuth.ID { + t.Fatalf("The ID of the second returned auth should be the same as the first. Expected [%v], got [%v]", createdAuth.ID, secondAuth.ID) + } + + // Verify the token + appAuth, resp, err := appAuthenticatedClient.Authorizations.Check(context.Background(), clientID, *createdAuth.Token) + failOnError(t, err) + failIfNotStatusCode(t, resp, 200) + + // Quick sanity check + if *appAuth.ID != *createdAuth.ID || *appAuth.Token != *createdAuth.Token { + t.Fatal("The returned auth/token does not match.") + } + + // Let's verify that we get a 404 for a non-existent token + _, resp, err = appAuthenticatedClient.Authorizations.Check(context.Background(), clientID, InvalidTokenValue) + if err == nil { + t.Fatal("An error should have been returned because of the invalid token.") + } + failIfNotStatusCode(t, resp, 404) + + // Let's reset the token + resetAuth, resp, err := appAuthenticatedClient.Authorizations.Reset(context.Background(), clientID, *createdAuth.Token) + failOnError(t, err) + failIfNotStatusCode(t, resp, 200) + + // Let's verify that we get a 404 for a non-existent token + _, resp, err = appAuthenticatedClient.Authorizations.Reset(context.Background(), clientID, InvalidTokenValue) + if err == nil { + t.Fatal("An error should have been returned because of the invalid token.") + } + failIfNotStatusCode(t, resp, 404) + + // Verify that the token has changed + if resetAuth.Token == createdAuth.Token { + t.Fatal("The reset token should be different from the original.") + } + + // Verify that we do have a token value + if *resetAuth.Token == "" { + t.Fatal("A token value should have been returned.") + } + + // Verify that the original token is now invalid + _, resp, err = appAuthenticatedClient.Authorizations.Check(context.Background(), clientID, *createdAuth.Token) + if err == nil { + t.Fatal("The original token should be invalid.") + } + failIfNotStatusCode(t, resp, 404) + + // Check that the reset token is valid + _, resp, err = appAuthenticatedClient.Authorizations.Check(context.Background(), clientID, *resetAuth.Token) + failOnError(t, err) + failIfNotStatusCode(t, resp, 200) + + // Let's revoke the token + resp, err = appAuthenticatedClient.Authorizations.Revoke(context.Background(), clientID, *resetAuth.Token) + failOnError(t, err) + failIfNotStatusCode(t, resp, 204) + + // Sleep for two seconds... I've seen cases where the revocation appears not + // to have take place immediately. + time.Sleep(time.Second * 2) + + // Now, the reset token should also be invalid + _, resp, err = appAuthenticatedClient.Authorizations.Check(context.Background(), clientID, *resetAuth.Token) + if err == nil { + t.Fatal("The reset token should be invalid.") + } + failIfNotStatusCode(t, resp, 404) +} + +// generatePersonalAuthTokenRequest is a helper function that generates an +// AuthorizationRequest for a Personal Access Token (no client id). +func generatePersonalAuthTokenRequest() *github.AuthorizationRequest { + + rand := randString() + auth := github.AuthorizationRequest{ + Note: github.String("Personal token: Note generated by test: " + rand), + Scopes: []github.Scope{github.ScopePublicRepo}, + Fingerprint: github.String("Personal token: Fingerprint generated by test: " + rand), + } + + return &auth +} + +// generatePersonalAuthTokenRequest is a helper function that generates an +// AuthorizationRequest for an OAuth application Token (uses client id). +func generateAppAuthTokenRequest(clientID string, clientSecret string) *github.AuthorizationRequest { + + rand := randString() + auth := github.AuthorizationRequest{ + Note: github.String("App token: Note generated by test: " + rand), + Scopes: []github.Scope{github.ScopePublicRepo}, + Fingerprint: github.String("App token: Fingerprint generated by test: " + rand), + ClientID: github.String(clientID), + ClientSecret: github.String(clientSecret), + } + + return &auth +} + +// randString returns a (kinda) random string for uniqueness purposes. +func randString() string { + return strconv.FormatInt(rand.NewSource(time.Now().UnixNano()).Int63(), 10) +} + +// failOnError invokes t.Fatal() if err is present. +func failOnError(t *testing.T, err error) { + + if err != nil { + t.Fatal(err) + } +} + +// failIfNotStatusCode invokes t.Fatal() if the response's status code doesn't match the expected code. +func failIfNotStatusCode(t *testing.T, resp *github.Response, expectedCode int) { + + if resp.StatusCode != expectedCode { + t.Fatalf("Expected HTTP status code [%v] but received [%v]", expectedCode, resp.StatusCode) + } + +} + +// getUserPassClient returns a GitHub client for authorization testing. The client +// uses BasicAuth via GH username and password passed in environment variables +// (and will skip the calling test if those vars are not present). +func getUserPassClient(t *testing.T) *github.Client { + username, ok := os.LookupEnv(envKeyGitHubUsername) + if !ok { + t.Skipf(msgEnvMissing, envKeyGitHubUsername) + } + + password, ok := os.LookupEnv(envKeyGitHubPassword) + if !ok { + t.Skipf(msgEnvMissing, envKeyGitHubPassword) + } + + tp := github.BasicAuthTransport{ + Username: strings.TrimSpace(username), + Password: strings.TrimSpace(password), + } + + return github.NewClient(tp.Client()) +} + +// getOAuthAppClient returns a GitHub client for authorization testing. The client +// uses BasicAuth, but instead of username and password, it uses the client id +// and client secret passed in via environment variables +// (and will skip the calling test if those vars are not present). Certain API operations (check +// an authorization; reset an authorization; revoke an authorization for an app) +// require this authentication mechanism. +// +// See GitHub API docs: https://developer.com/v3/oauth_authorizations/#check-an-authorization +func getOAuthAppClient(t *testing.T) *github.Client { + + username, ok := os.LookupEnv(envKeyClientID) + if !ok { + t.Skipf(msgEnvMissing, envKeyClientID) + } + + password, ok := os.LookupEnv(envKeyClientSecret) + if !ok { + t.Skipf(msgEnvMissing, envKeyClientSecret) + } + + tp := github.BasicAuthTransport{ + Username: strings.TrimSpace(username), + Password: strings.TrimSpace(password), + } + + return github.NewClient(tp.Client()) +} diff --git a/vendor/github.com/google/go-github/test/integration/doc.go b/vendor/github.com/google/go-github/test/integration/doc.go new file mode 100644 index 0000000..5ee470c --- /dev/null +++ b/vendor/github.com/google/go-github/test/integration/doc.go @@ -0,0 +1,11 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package integration contains integration tests. +// +// These tests call the live GitHub API, and therefore require a little more +// setup to run. See https://github.com/google/go-github/tree/master/test#integration +// for more information. +package integration diff --git a/vendor/github.com/google/go-github/test/integration/github_test.go b/vendor/github.com/google/go-github/test/integration/github_test.go new file mode 100644 index 0000000..489d38a --- /dev/null +++ b/vendor/github.com/google/go-github/test/integration/github_test.go @@ -0,0 +1,84 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build integration + +package integration + +import ( + "context" + "fmt" + "math/rand" + "net/http" + "os" + + "github.com/google/go-github/github" + "golang.org/x/oauth2" +) + +var ( + client *github.Client + + // auth indicates whether tests are being run with an OAuth token. + // Tests can use this flag to skip certain tests when run without auth. + auth bool +) + +func init() { + token := os.Getenv("GITHUB_AUTH_TOKEN") + if token == "" { + print("!!! No OAuth token. Some tests won't run. !!!\n\n") + client = github.NewClient(nil) + } else { + tc := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: token}, + )) + client = github.NewClient(tc) + auth = true + } + + // Environment variables required for Authorization integration tests + vars := []string{envKeyGitHubUsername, envKeyGitHubPassword, envKeyClientID, envKeyClientSecret} + + for _, v := range vars { + value := os.Getenv(v) + if value == "" { + print("!!! " + fmt.Sprintf(msgEnvMissing, v) + " !!!\n\n") + } + } + +} + +func checkAuth(name string) bool { + if !auth { + fmt.Printf("No auth - skipping portions of %v\n", name) + } + return auth +} + +func createRandomTestRepository(owner string, autoinit bool) (*github.Repository, error) { + // create random repo name that does not currently exist + var repoName string + for { + repoName = fmt.Sprintf("test-%d", rand.Int()) + _, resp, err := client.Repositories.Get(context.Background(), owner, repoName) + if err != nil { + if resp.StatusCode == http.StatusNotFound { + // found a non-existent repo, perfect + break + } + + return nil, err + } + } + + // create the repository + repo, _, err := client.Repositories.Create(context.Background(), "", &github.Repository{Name: github.String(repoName), AutoInit: github.Bool(autoinit)}) + if err != nil { + return nil, err + } + + return repo, nil +} diff --git a/vendor/github.com/google/go-github/test/integration/issues_test.go b/vendor/github.com/google/go-github/test/integration/issues_test.go new file mode 100644 index 0000000..a5e4890 --- /dev/null +++ b/vendor/github.com/google/go-github/test/integration/issues_test.go @@ -0,0 +1,42 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build integration + +package integration + +import ( + "context" + "testing" +) + +func TestIssueEvents(t *testing.T) { + events, _, err := client.Issues.ListRepositoryEvents(context.Background(), "google", "go-github", nil) + if err != nil { + t.Fatalf("Issues.ListRepositoryEvents returned error: %v", err) + } + + if len(events) == 0 { + t.Errorf("ListRepositoryEvents returned no events") + } + + events, _, err = client.Issues.ListIssueEvents(context.Background(), "google", "go-github", 1, nil) + if err != nil { + t.Fatalf("Issues.ListIssueEvents returned error: %v", err) + } + + if len(events) == 0 { + t.Errorf("ListIssueEvents returned no events") + } + + event, _, err := client.Issues.GetEvent(context.Background(), "google", "go-github", *events[0].ID) + if err != nil { + t.Fatalf("Issues.GetEvent returned error: %v", err) + } + + if *event.URL != *events[0].URL { + t.Fatalf("Issues.GetEvent returned event URL: %v, want %v", *event.URL, *events[0].URL) + } +} diff --git a/vendor/github.com/google/go-github/test/integration/misc_test.go b/vendor/github.com/google/go-github/test/integration/misc_test.go new file mode 100644 index 0000000..1595cf0 --- /dev/null +++ b/vendor/github.com/google/go-github/test/integration/misc_test.go @@ -0,0 +1,79 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build integration + +package integration + +import ( + "context" + "testing" + "time" +) + +func TestEmojis(t *testing.T) { + emoji, _, err := client.ListEmojis(context.Background()) + if err != nil { + t.Fatalf("ListEmojis returned error: %v", err) + } + + if len(emoji) == 0 { + t.Errorf("ListEmojis returned no emojis") + } + + if _, ok := emoji["+1"]; !ok { + t.Errorf("ListEmojis missing '+1' emoji") + } +} + +func TestAPIMeta(t *testing.T) { + meta, _, err := client.APIMeta(context.Background()) + if err != nil { + t.Fatalf("APIMeta returned error: %v", err) + } + + if len(meta.Hooks) == 0 { + t.Errorf("APIMeta returned no hook addresses") + } + + if len(meta.Git) == 0 { + t.Errorf("APIMeta returned no git addresses") + } + + if !*meta.VerifiablePasswordAuthentication { + t.Errorf("APIMeta VerifiablePasswordAuthentication is false") + } +} + +func TestRateLimits(t *testing.T) { + limits, _, err := client.RateLimits(context.Background()) + if err != nil { + t.Fatalf("RateLimits returned error: %v", err) + } + + // do some sanity checks + if limits.Core.Limit == 0 { + t.Errorf("RateLimits returned 0 core limit") + } + + if limits.Core.Limit < limits.Core.Remaining { + t.Errorf("Core.Limits is less than Core.Remaining.") + } + + if limits.Core.Reset.Time.Before(time.Now().Add(-1 * time.Minute)) { + t.Errorf("Core.Reset is more than 1 minute in the past; that doesn't seem right.") + } +} + +func TestListServiceHooks(t *testing.T) { + hooks, _, err := client.ListServiceHooks(context.Background()) + if err != nil { + t.Fatalf("ListServiceHooks returned error: %v", err) + } + + if len(hooks) == 0 { + t.Fatalf("ListServiceHooks returned no hooks") + } +} diff --git a/vendor/github.com/google/go-github/test/integration/pulls_test.go b/vendor/github.com/google/go-github/test/integration/pulls_test.go new file mode 100644 index 0000000..4aadfbd --- /dev/null +++ b/vendor/github.com/google/go-github/test/integration/pulls_test.go @@ -0,0 +1,28 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build integration + +package integration + +import ( + "context" + "testing" +) + +func TestPullRequests_ListCommits(t *testing.T) { + commits, _, err := client.PullRequests.ListCommits(context.Background(), "google", "go-github", 2, nil) + if err != nil { + t.Fatalf("PullRequests.ListCommits() returned error: %v", err) + } + + if got, want := len(commits), 3; got != want { + t.Fatalf("PullRequests.ListCommits() returned %d commits, want %d", got, want) + } + + if got, want := *commits[0].Author.Login, "sqs"; got != want { + t.Fatalf("PullRequests.ListCommits()[0].Author.Login returned %v, want %v", got, want) + } +} diff --git a/vendor/github.com/google/go-github/test/integration/repos_test.go b/vendor/github.com/google/go-github/test/integration/repos_test.go new file mode 100644 index 0000000..58aa182 --- /dev/null +++ b/vendor/github.com/google/go-github/test/integration/repos_test.go @@ -0,0 +1,181 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build integration + +package integration + +import ( + "context" + "net/http" + "reflect" + "testing" + + "github.com/google/go-github/github" +) + +func TestRepositories_CRUD(t *testing.T) { + if !checkAuth("TestRepositories_CRUD") { + return + } + + // get authenticated user + me, _, err := client.Users.Get(context.Background(), "") + if err != nil { + t.Fatalf("Users.Get('') returned error: %v", err) + } + + repo, err := createRandomTestRepository(*me.Login, false) + if err != nil { + t.Fatalf("createRandomTestRepository returned error: %v", err) + } + + // update the repository description + repo.Description = github.String("description") + repo.DefaultBranch = nil // FIXME: this shouldn't be necessary + _, _, err = client.Repositories.Edit(context.Background(), *repo.Owner.Login, *repo.Name, repo) + if err != nil { + t.Fatalf("Repositories.Edit() returned error: %v", err) + } + + // delete the repository + _, err = client.Repositories.Delete(context.Background(), *repo.Owner.Login, *repo.Name) + if err != nil { + t.Fatalf("Repositories.Delete() returned error: %v", err) + } + + // verify that the repository was deleted + _, resp, err := client.Repositories.Get(context.Background(), *repo.Owner.Login, *repo.Name) + if err == nil { + t.Fatalf("Test repository still exists after deleting it.") + } + if err != nil && resp.StatusCode != http.StatusNotFound { + t.Fatalf("Repositories.Get() returned error: %v", err) + } +} + +func TestRepositories_BranchesTags(t *testing.T) { + // branches + branches, _, err := client.Repositories.ListBranches(context.Background(), "git", "git", nil) + if err != nil { + t.Fatalf("Repositories.ListBranches() returned error: %v", err) + } + + if len(branches) == 0 { + t.Fatalf("Repositories.ListBranches('git', 'git') returned no branches") + } + + _, _, err = client.Repositories.GetBranch(context.Background(), "git", "git", *branches[0].Name) + if err != nil { + t.Fatalf("Repositories.GetBranch() returned error: %v", err) + } + + // tags + tags, _, err := client.Repositories.ListTags(context.Background(), "git", "git", nil) + if err != nil { + t.Fatalf("Repositories.ListTags() returned error: %v", err) + } + + if len(tags) == 0 { + t.Fatalf("Repositories.ListTags('git', 'git') returned no tags") + } +} + +func TestRepositories_EditBranches(t *testing.T) { + if !checkAuth("TestRepositories_EditBranches") { + return + } + + // get authenticated user + me, _, err := client.Users.Get(context.Background(), "") + if err != nil { + t.Fatalf("Users.Get('') returned error: %v", err) + } + + repo, err := createRandomTestRepository(*me.Login, true) + if err != nil { + t.Fatalf("createRandomTestRepository returned error: %v", err) + } + + branch, _, err := client.Repositories.GetBranch(context.Background(), *repo.Owner.Login, *repo.Name, "master") + if err != nil { + t.Fatalf("Repositories.GetBranch() returned error: %v", err) + } + + if *branch.Protected { + t.Fatalf("Branch %v of repo %v is already protected", "master", *repo.Name) + } + + protectionRequest := &github.ProtectionRequest{ + RequiredStatusChecks: &github.RequiredStatusChecks{ + Strict: true, + Contexts: []string{"continuous-integration"}, + }, + RequiredPullRequestReviews: &github.PullRequestReviewsEnforcementRequest{ + DismissStaleReviews: true, + }, + EnforceAdmins: true, + // TODO: Only organization repositories can have users and team restrictions. + // In order to be able to test these Restrictions, need to add support + // for creating temporary organization repositories. + Restrictions: nil, + } + + protection, _, err := client.Repositories.UpdateBranchProtection(context.Background(), *repo.Owner.Login, *repo.Name, "master", protectionRequest) + if err != nil { + t.Fatalf("Repositories.UpdateBranchProtection() returned error: %v", err) + } + + want := &github.Protection{ + RequiredStatusChecks: &github.RequiredStatusChecks{ + Strict: true, + Contexts: []string{"continuous-integration"}, + }, + RequiredPullRequestReviews: &github.PullRequestReviewsEnforcement{ + DismissStaleReviews: true, + RequiredApprovingReviewCount: 0, + }, + EnforceAdmins: &github.AdminEnforcement{ + URL: github.String("https://api.github.com/repos/" + *repo.Owner.Login + "/" + *repo.Name + "/branches/master/protection/enforce_admins"), + Enabled: true, + }, + Restrictions: nil, + } + if !reflect.DeepEqual(protection, want) { + t.Errorf("Repositories.UpdateBranchProtection() returned %+v, want %+v", protection, want) + } + + _, err = client.Repositories.Delete(context.Background(), *repo.Owner.Login, *repo.Name) + if err != nil { + t.Fatalf("Repositories.Delete() returned error: %v", err) + } +} + +func TestRepositories_List(t *testing.T) { + if !checkAuth("TestRepositories_List") { + return + } + + _, _, err := client.Repositories.List(context.Background(), "", nil) + if err != nil { + t.Fatalf("Repositories.List('') returned error: %v", err) + } + + _, _, err = client.Repositories.List(context.Background(), "google", nil) + if err != nil { + t.Fatalf("Repositories.List('google') returned error: %v", err) + } + + opt := github.RepositoryListOptions{Sort: "created"} + repos, _, err := client.Repositories.List(context.Background(), "google", &opt) + if err != nil { + t.Fatalf("Repositories.List('google') with Sort opt returned error: %v", err) + } + for i, repo := range repos { + if i > 0 && (*repos[i-1].CreatedAt).Time.Before((*repo.CreatedAt).Time) { + t.Fatalf("Repositories.List('google') with default descending Sort returned incorrect order") + } + } +} diff --git a/vendor/github.com/google/go-github/test/integration/users_test.go b/vendor/github.com/google/go-github/test/integration/users_test.go new file mode 100644 index 0000000..3c47c36 --- /dev/null +++ b/vendor/github.com/google/go-github/test/integration/users_test.go @@ -0,0 +1,244 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build integration + +package integration + +import ( + "context" + "fmt" + "math/rand" + "testing" + + "github.com/google/go-github/github" +) + +func TestUsers_Get(t *testing.T) { + // list all users + users, _, err := client.Users.ListAll(context.Background(), nil) + if err != nil { + t.Fatalf("Users.ListAll returned error: %v", err) + } + + if len(users) == 0 { + t.Errorf("Users.ListAll returned no users") + } + + // mojombo is user #1 + if want := "mojombo"; want != *users[0].Login { + t.Errorf("user[0].Login was %q, wanted %q", *users[0].Login, want) + } + + // get individual user + u, _, err := client.Users.Get(context.Background(), "octocat") + if err != nil { + t.Fatalf("Users.Get('octocat') returned error: %v", err) + } + + if want := "octocat"; want != *u.Login { + t.Errorf("user.Login was %q, wanted %q", *u.Login, want) + } + if want := "The Octocat"; want != *u.Name { + t.Errorf("user.Name was %q, wanted %q", *u.Name, want) + } +} + +func TestUsers_Update(t *testing.T) { + if !checkAuth("TestUsers_Get") { + return + } + + u, _, err := client.Users.Get(context.Background(), "") + if err != nil { + t.Fatalf("Users.Get('') returned error: %v", err) + } + + if *u.Login == "" { + t.Errorf("wanted non-empty values for user.Login") + } + + // save original location + var location string + if u.Location != nil { + location = *u.Location + } + + // update location to test value + testLoc := fmt.Sprintf("test-%d", rand.Int()) + u.Location = &testLoc + + _, _, err = client.Users.Edit(context.Background(), u) + if err != nil { + t.Fatalf("Users.Update returned error: %v", err) + } + + // refetch user and check location value + u, _, err = client.Users.Get(context.Background(), "") + if err != nil { + t.Fatalf("Users.Get('') returned error: %v", err) + } + + if testLoc != *u.Location { + t.Errorf("Users.Get('') has location: %v, want: %v", *u.Location, testLoc) + } + + // set location back to the original value + u.Location = &location + _, _, err = client.Users.Edit(context.Background(), u) + if err != nil { + t.Fatalf("Users.Edit returned error: %v", err) + } +} + +func TestUsers_Emails(t *testing.T) { + if !checkAuth("TestUsers_Emails") { + return + } + + emails, _, err := client.Users.ListEmails(context.Background(), nil) + if err != nil { + t.Fatalf("Users.ListEmails() returned error: %v", err) + } + + // create random address not currently in user's emails + var email string +EmailLoop: + for { + email = fmt.Sprintf("test-%d@example.com", rand.Int()) + for _, e := range emails { + if e.Email != nil && *e.Email == email { + continue EmailLoop + } + } + break + } + + // Add new address + _, _, err = client.Users.AddEmails(context.Background(), []string{email}) + if err != nil { + t.Fatalf("Users.AddEmails() returned error: %v", err) + } + + // List emails again and verify new email is present + emails, _, err = client.Users.ListEmails(context.Background(), nil) + if err != nil { + t.Fatalf("Users.ListEmails() returned error: %v", err) + } + + var found bool + for _, e := range emails { + if e.Email != nil && *e.Email == email { + found = true + break + } + } + + if !found { + t.Fatalf("Users.ListEmails() does not contain new address: %v", email) + } + + // Remove new address + _, err = client.Users.DeleteEmails(context.Background(), []string{email}) + if err != nil { + t.Fatalf("Users.DeleteEmails() returned error: %v", err) + } + + // List emails again and verify new email was removed + emails, _, err = client.Users.ListEmails(context.Background(), nil) + if err != nil { + t.Fatalf("Users.ListEmails() returned error: %v", err) + } + + for _, e := range emails { + if e.Email != nil && *e.Email == email { + t.Fatalf("Users.ListEmails() still contains address %v after removing it", email) + } + } +} + +func TestUsers_Keys(t *testing.T) { + keys, _, err := client.Users.ListKeys(context.Background(), "willnorris", nil) + if err != nil { + t.Fatalf("Users.ListKeys('willnorris') returned error: %v", err) + } + + if len(keys) == 0 { + t.Errorf("Users.ListKeys('willnorris') returned no keys") + } + + // the rest of the tests requires auth + if !checkAuth("TestUsers_Keys") { + return + } + + // TODO: make this integration test work for any authenticated user. + keys, _, err = client.Users.ListKeys(context.Background(), "", nil) + if err != nil { + t.Fatalf("Users.ListKeys('') returned error: %v", err) + } + + // ssh public key for testing (fingerprint: a7:22:ad:8c:36:9f:68:65:eb:ae:a1:e4:59:73:c1:76) + key := "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCy/RIqaMFj2wjkOEjx9EAU0ReLAIhodga82/feo5nnT9UUkHLbL9xrIavfdLHx28lD3xYgPfAoSicUMaAeNwuQhmuerr2c2LFGxzrdXP8pVsQ+Ol7y7OdmFPfe0KrzoZaLJs9aSiZ4VKyY4z5Se/k2UgcJTdgQVlLfw/P96aqCx8yUu94BiWqkDqYEvgWKRNHrTiIo1EXeVBCCcfgNZe1suFfNJUJSUU2T3EG2bpwBbSOCjE3FyH8+Lz3K3BOGzm3df8E7Regj9j4YIcD8cWJYO86jLJoGgQ0L5MSOq+ishNaHQXech22Ix03D1lVMjCvDT7S/C94Z1LzhI2lhvyff" + for _, k := range keys { + if k.Key != nil && *k.Key == key { + t.Fatalf("Test key already exists for user. Please manually remove it first.") + } + } + + // Add new key + _, _, err = client.Users.CreateKey(context.Background(), &github.Key{ + Title: github.String("go-github test key"), + Key: github.String(key), + }) + if err != nil { + t.Fatalf("Users.CreateKey() returned error: %v", err) + } + + // List keys again and verify new key is present + keys, _, err = client.Users.ListKeys(context.Background(), "", nil) + if err != nil { + t.Fatalf("Users.ListKeys('') returned error: %v", err) + } + + var id int64 + for _, k := range keys { + if k.Key != nil && *k.Key == key { + id = *k.ID + break + } + } + + if id == 0 { + t.Fatalf("Users.ListKeys('') does not contain added test key") + } + + // Verify that fetching individual key works + k, _, err := client.Users.GetKey(context.Background(), id) + if err != nil { + t.Fatalf("Users.GetKey(%q) returned error: %v", id, err) + } + if *k.Key != key { + t.Fatalf("Users.GetKey(%q) returned key %v, want %v", id, *k.Key, key) + } + + // Remove test key + _, err = client.Users.DeleteKey(context.Background(), id) + if err != nil { + t.Fatalf("Users.DeleteKey(%d) returned error: %v", id, err) + } + + // List keys again and verify test key was removed + keys, _, err = client.Users.ListKeys(context.Background(), "", nil) + if err != nil { + t.Fatalf("Users.ListKeys('') returned error: %v", err) + } + + for _, k := range keys { + if k.Key != nil && *k.Key == key { + t.Fatalf("Users.ListKeys('') still contains test key after removing it") + } + } +} diff --git a/vendor/github.com/google/go-querystring/.gitignore b/vendor/github.com/google/go-querystring/.gitignore new file mode 100644 index 0000000..9ed3b07 --- /dev/null +++ b/vendor/github.com/google/go-querystring/.gitignore @@ -0,0 +1 @@ +*.test diff --git a/vendor/github.com/google/go-querystring/CONTRIBUTING.md b/vendor/github.com/google/go-querystring/CONTRIBUTING.md new file mode 100644 index 0000000..51cf5cd --- /dev/null +++ b/vendor/github.com/google/go-querystring/CONTRIBUTING.md @@ -0,0 +1,67 @@ +# How to contribute # + +We'd love to accept your patches and contributions to this project. There are +a just a few small guidelines you need to follow. + + +## Contributor License Agreement ## + +Contributions to any Google project must be accompanied by a Contributor +License Agreement. This is not a copyright **assignment**, it simply gives +Google permission to use and redistribute your contributions as part of the +project. + + * If you are an individual writing original source code and you're sure you + own the intellectual property, then you'll need to sign an [individual + CLA][]. + + * If you work for a company that wants to allow you to contribute your work, + then you'll need to sign a [corporate CLA][]. + +You generally only need to submit a CLA once, so if you've already submitted +one (even if it was for a different project), you probably don't need to do it +again. + +[individual CLA]: https://developers.google.com/open-source/cla/individual +[corporate CLA]: https://developers.google.com/open-source/cla/corporate + + +## Submitting a patch ## + + 1. It's generally best to start by opening a new issue describing the bug or + feature you're intending to fix. Even if you think it's relatively minor, + it's helpful to know what people are working on. Mention in the initial + issue that you are planning to work on that bug or feature so that it can + be assigned to you. + + 1. Follow the normal process of [forking][] the project, and setup a new + branch to work in. It's important that each group of changes be done in + separate branches in order to ensure that a pull request only includes the + commits related to that bug or feature. + + 1. Go makes it very simple to ensure properly formatted code, so always run + `go fmt` on your code before committing it. You should also run + [golint][] over your code. As noted in the [golint readme][], it's not + strictly necessary that your code be completely "lint-free", but this will + help you find common style issues. + + 1. Any significant changes should almost always be accompanied by tests. The + project already has good test coverage, so look at some of the existing + tests if you're unsure how to go about it. [gocov][] and [gocov-html][] + are invaluable tools for seeing which parts of your code aren't being + exercised by your tests. + + 1. Do your best to have [well-formed commit messages][] for each change. + This provides consistency throughout the project, and ensures that commit + messages are able to be formatted properly by various git tools. + + 1. Finally, push the commits to your fork and submit a [pull request][]. + +[forking]: https://help.github.com/articles/fork-a-repo +[golint]: https://github.com/golang/lint +[golint readme]: https://github.com/golang/lint/blob/master/README +[gocov]: https://github.com/axw/gocov +[gocov-html]: https://github.com/matm/gocov-html +[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html +[squash]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits +[pull request]: https://help.github.com/articles/creating-a-pull-request diff --git a/vendor/github.com/google/go-querystring/LICENSE b/vendor/github.com/google/go-querystring/LICENSE new file mode 100644 index 0000000..ae121a1 --- /dev/null +++ b/vendor/github.com/google/go-querystring/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013 Google. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/google/go-querystring/README.md b/vendor/github.com/google/go-querystring/README.md new file mode 100644 index 0000000..03e9370 --- /dev/null +++ b/vendor/github.com/google/go-querystring/README.md @@ -0,0 +1,39 @@ +# go-querystring # + +go-querystring is Go library for encoding structs into URL query parameters. + + +**Documentation:** +**Build Status:** [![Build Status](https://drone.io/github.com/google/go-querystring/status.png)](https://drone.io/github.com/google/go-querystring/latest) + +## Usage ## + +```go +import "github.com/google/go-querystring/query" +``` + +go-querystring is designed to assist in scenarios where you want to construct a +URL using a struct that represents the URL query parameters. You might do this +to enforce the type safety of your parameters, for example, as is done in the +[go-github][] library. + +The query package exports a single `Values()` function. A simple example: + +```go +type Options struct { + Query string `url:"q"` + ShowAll bool `url:"all"` + Page int `url:"page"` +} + +opt := Options{ "foo", true, 2 } +v, _ := query.Values(opt) +fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2" +``` + +[go-github]: https://github.com/google/go-github/commit/994f6f8405f052a117d2d0b500054341048fbb08 + +## License ## + +This library is distributed under the BSD-style license found in the [LICENSE](./LICENSE) +file. diff --git a/vendor/github.com/google/go-querystring/go.mod b/vendor/github.com/google/go-querystring/go.mod new file mode 100644 index 0000000..45dca2d --- /dev/null +++ b/vendor/github.com/google/go-querystring/go.mod @@ -0,0 +1 @@ +module github.com/google/go-querystring diff --git a/vendor/github.com/google/go-querystring/query/encode.go b/vendor/github.com/google/go-querystring/query/encode.go new file mode 100644 index 0000000..37080b1 --- /dev/null +++ b/vendor/github.com/google/go-querystring/query/encode.go @@ -0,0 +1,320 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package query implements encoding of structs into URL query parameters. +// +// As a simple example: +// +// type Options struct { +// Query string `url:"q"` +// ShowAll bool `url:"all"` +// Page int `url:"page"` +// } +// +// opt := Options{ "foo", true, 2 } +// v, _ := query.Values(opt) +// fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2" +// +// The exact mapping between Go values and url.Values is described in the +// documentation for the Values() function. +package query + +import ( + "bytes" + "fmt" + "net/url" + "reflect" + "strconv" + "strings" + "time" +) + +var timeType = reflect.TypeOf(time.Time{}) + +var encoderType = reflect.TypeOf(new(Encoder)).Elem() + +// Encoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type Encoder interface { + EncodeValues(key string, v *url.Values) error +} + +// Values returns the url.Values encoding of v. +// +// Values expects to be passed a struct, and traverses it recursively using the +// following encoding rules. +// +// Each exported struct field is encoded as a URL parameter unless +// +// - the field's tag is "-", or +// - the field is empty and its tag specifies the "omitempty" option +// +// The empty values are false, 0, any nil pointer or interface value, any array +// slice, map, or string of length zero, and any time.Time that returns true +// for IsZero(). +// +// The URL parameter name defaults to the struct field name but can be +// specified in the struct field's tag value. The "url" key in the struct +// field's tag value is the key name, followed by an optional comma and +// options. For example: +// +// // Field is ignored by this package. +// Field int `url:"-"` +// +// // Field appears as URL parameter "myName". +// Field int `url:"myName"` +// +// // Field appears as URL parameter "myName" and the field is omitted if +// // its value is empty +// Field int `url:"myName,omitempty"` +// +// // Field appears as URL parameter "Field" (the default), but the field +// // is skipped if empty. Note the leading comma. +// Field int `url:",omitempty"` +// +// For encoding individual field values, the following type-dependent rules +// apply: +// +// Boolean values default to encoding as the strings "true" or "false". +// Including the "int" option signals that the field should be encoded as the +// strings "1" or "0". +// +// time.Time values default to encoding as RFC3339 timestamps. Including the +// "unix" option signals that the field should be encoded as a Unix time (see +// time.Unix()) +// +// Slice and Array values default to encoding as multiple URL values of the +// same name. Including the "comma" option signals that the field should be +// encoded as a single comma-delimited value. Including the "space" option +// similarly encodes the value as a single space-delimited string. Including +// the "semicolon" option will encode the value as a semicolon-delimited string. +// Including the "brackets" option signals that the multiple URL values should +// have "[]" appended to the value name. "numbered" will append a number to +// the end of each incidence of the value name, example: +// name0=value0&name1=value1, etc. +// +// Anonymous struct fields are usually encoded as if their inner exported +// fields were fields in the outer struct, subject to the standard Go +// visibility rules. An anonymous struct field with a name given in its URL +// tag is treated as having that name, rather than being anonymous. +// +// Non-nil pointer values are encoded as the value pointed to. +// +// Nested structs are encoded including parent fields in value names for +// scoping. e.g: +// +// "user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO" +// +// All other values are encoded using their default string representation. +// +// Multiple fields that encode to the same URL parameter name will be included +// as multiple URL values of the same name. +func Values(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + var embedded []reflect.Value + + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { // unexported + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "-" { + continue + } + name, opts := parseTag(tag) + if name == "" { + if sf.Anonymous && sv.Kind() == reflect.Struct { + // save embedded struct for later processing + embedded = append(embedded, sv) + continue + } + + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(encoderType) { + if !reflect.Indirect(sv).IsValid() { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(Encoder) + if err := m.EncodeValues(name, &values); err != nil { + return err + } + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + var del byte + if opts.Contains("comma") { + del = ',' + } else if opts.Contains("space") { + del = ' ' + } else if opts.Contains("semicolon") { + del = ';' + } else if opts.Contains("brackets") { + name = name + "[]" + } + + if del != 0 { + s := new(bytes.Buffer) + first := true + for i := 0; i < sv.Len(); i++ { + if first { + first = false + } else { + s.WriteByte(del) + } + s.WriteString(valueString(sv.Index(i), opts)) + } + values.Add(name, s.String()) + } else { + for i := 0; i < sv.Len(); i++ { + k := name + if opts.Contains("numbered") { + k = fmt.Sprintf("%s%d", name, i) + } + values.Add(k, valueString(sv.Index(i), opts)) + } + } + continue + } + + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == timeType { + values.Add(name, valueString(sv, opts)) + continue + } + + if sv.Kind() == reflect.Struct { + reflectValue(values, sv, name) + continue + } + + values.Add(name, valueString(sv, opts)) + } + + for _, f := range embedded { + if err := reflectValue(values, f, scope); err != nil { + return err + } + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Kind() == reflect.Bool && opts.Contains("int") { + if v.Bool() { + return "1" + } + return "0" + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if opts.Contains("unix") { + return strconv.FormatInt(t.Unix(), 10) + } + return t.Format(time.RFC3339) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + + if v.Type() == timeType { + return v.Interface().(time.Time).IsZero() + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/vendor/github.com/google/go-querystring/query/encode_test.go b/vendor/github.com/google/go-querystring/query/encode_test.go new file mode 100644 index 0000000..0f26a77 --- /dev/null +++ b/vendor/github.com/google/go-querystring/query/encode_test.go @@ -0,0 +1,328 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package query + +import ( + "fmt" + "net/url" + "reflect" + "testing" + "time" +) + +type Nested struct { + A SubNested `url:"a"` + B *SubNested `url:"b"` + Ptr *SubNested `url:"ptr,omitempty"` +} + +type SubNested struct { + Value string `url:"value"` +} + +func TestValues_types(t *testing.T) { + str := "string" + strPtr := &str + timeVal := time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC) + + tests := []struct { + in interface{} + want url.Values + }{ + { + // basic primitives + struct { + A string + B int + C uint + D float32 + E bool + }{}, + url.Values{ + "A": {""}, + "B": {"0"}, + "C": {"0"}, + "D": {"0"}, + "E": {"false"}, + }, + }, + { + // pointers + struct { + A *string + B *int + C **string + D *time.Time + }{ + A: strPtr, + C: &strPtr, + D: &timeVal, + }, + url.Values{ + "A": {str}, + "B": {""}, + "C": {str}, + "D": {"2000-01-01T12:34:56Z"}, + }, + }, + { + // slices and arrays + struct { + A []string + B []string `url:",comma"` + C []string `url:",space"` + D [2]string + E [2]string `url:",comma"` + F [2]string `url:",space"` + G []*string `url:",space"` + H []bool `url:",int,space"` + I []string `url:",brackets"` + J []string `url:",semicolon"` + K []string `url:",numbered"` + }{ + A: []string{"a", "b"}, + B: []string{"a", "b"}, + C: []string{"a", "b"}, + D: [2]string{"a", "b"}, + E: [2]string{"a", "b"}, + F: [2]string{"a", "b"}, + G: []*string{&str, &str}, + H: []bool{true, false}, + I: []string{"a", "b"}, + J: []string{"a", "b"}, + K: []string{"a", "b"}, + }, + url.Values{ + "A": {"a", "b"}, + "B": {"a,b"}, + "C": {"a b"}, + "D": {"a", "b"}, + "E": {"a,b"}, + "F": {"a b"}, + "G": {"string string"}, + "H": {"1 0"}, + "I[]": {"a", "b"}, + "J": {"a;b"}, + "K0": {"a"}, + "K1": {"b"}, + }, + }, + { + // other types + struct { + A time.Time + B time.Time `url:",unix"` + C bool `url:",int"` + D bool `url:",int"` + }{ + A: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC), + B: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC), + C: true, + D: false, + }, + url.Values{ + "A": {"2000-01-01T12:34:56Z"}, + "B": {"946730096"}, + "C": {"1"}, + "D": {"0"}, + }, + }, + { + struct { + Nest Nested `url:"nest"` + }{ + Nested{ + A: SubNested{ + Value: "that", + }, + }, + }, + url.Values{ + "nest[a][value]": {"that"}, + "nest[b]": {""}, + }, + }, + { + struct { + Nest Nested `url:"nest"` + }{ + Nested{ + Ptr: &SubNested{ + Value: "that", + }, + }, + }, + url.Values{ + "nest[a][value]": {""}, + "nest[b]": {""}, + "nest[ptr][value]": {"that"}, + }, + }, + { + nil, + url.Values{}, + }, + } + + for i, tt := range tests { + v, err := Values(tt.in) + if err != nil { + t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err) + } + + if !reflect.DeepEqual(tt.want, v) { + t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want) + } + } +} + +func TestValues_omitEmpty(t *testing.T) { + str := "" + s := struct { + a string + A string + B string `url:",omitempty"` + C string `url:"-"` + D string `url:"omitempty"` // actually named omitempty, not an option + E *string `url:",omitempty"` + }{E: &str} + + v, err := Values(s) + if err != nil { + t.Errorf("Values(%q) returned error: %v", s, err) + } + + want := url.Values{ + "A": {""}, + "omitempty": {""}, + "E": {""}, // E is included because the pointer is not empty, even though the string being pointed to is + } + if !reflect.DeepEqual(want, v) { + t.Errorf("Values(%q) returned %v, want %v", s, v, want) + } +} + +type A struct { + B +} + +type B struct { + C string +} + +type D struct { + B + C string +} + +type e struct { + B + C string +} + +type F struct { + e +} + +func TestValues_embeddedStructs(t *testing.T) { + tests := []struct { + in interface{} + want url.Values + }{ + { + A{B{C: "foo"}}, + url.Values{"C": {"foo"}}, + }, + { + D{B: B{C: "bar"}, C: "foo"}, + url.Values{"C": {"foo", "bar"}}, + }, + { + F{e{B: B{C: "bar"}, C: "foo"}}, // With unexported embed + url.Values{"C": {"foo", "bar"}}, + }, + } + + for i, tt := range tests { + v, err := Values(tt.in) + if err != nil { + t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err) + } + + if !reflect.DeepEqual(tt.want, v) { + t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want) + } + } +} + +func TestValues_invalidInput(t *testing.T) { + _, err := Values("") + if err == nil { + t.Errorf("expected Values() to return an error on invalid input") + } +} + +type EncodedArgs []string + +func (m EncodedArgs) EncodeValues(key string, v *url.Values) error { + for i, arg := range m { + v.Set(fmt.Sprintf("%s.%d", key, i), arg) + } + return nil +} + +func TestValues_Marshaler(t *testing.T) { + s := struct { + Args EncodedArgs `url:"arg"` + }{[]string{"a", "b", "c"}} + v, err := Values(s) + if err != nil { + t.Errorf("Values(%q) returned error: %v", s, err) + } + + want := url.Values{ + "arg.0": {"a"}, + "arg.1": {"b"}, + "arg.2": {"c"}, + } + if !reflect.DeepEqual(want, v) { + t.Errorf("Values(%q) returned %v, want %v", s, v, want) + } +} + +func TestValues_MarshalerWithNilPointer(t *testing.T) { + s := struct { + Args *EncodedArgs `url:"arg"` + }{} + v, err := Values(s) + if err != nil { + t.Errorf("Values(%q) returned error: %v", s, err) + } + + want := url.Values{} + if !reflect.DeepEqual(want, v) { + t.Errorf("Values(%q) returned %v, want %v", s, v, want) + } +} + +func TestTagParsing(t *testing.T) { + name, opts := parseTag("field,foobar,foo") + if name != "field" { + t.Fatalf("name = %q, want field", name) + } + for _, tt := range []struct { + opt string + want bool + }{ + {"foobar", true}, + {"foo", true}, + {"bar", false}, + {"field", false}, + } { + if opts.Contains(tt.opt) != tt.want { + t.Errorf("Contains(%q) = %v", tt.opt, !tt.want) + } + } +}