From b2d5a6bc2b9315d741042f1bf9155b56c7368075 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Fri, 16 Oct 2020 18:06:36 +0200 Subject: [PATCH 01/37] Added gomod --- go.mod | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 go.mod diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a89c76f --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/craftamap/bb + +go 1.15 From d6a02fd8c08db779a6c896af2ae48f7f52631dab Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Fri, 16 Oct 2020 19:41:02 +0200 Subject: [PATCH 02/37] rudamentary project structure --- .gitignore | 17 +++ cmd/pr.go | 40 +++++++ cmd/root.go | 59 ++++++++++ go.mod | 7 ++ go.sum | 319 ++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 15 +++ pr.go | 1 + 7 files changed, 458 insertions(+) create mode 100644 .gitignore create mode 100644 cmd/pr.go create mode 100644 cmd/root.go create mode 100644 go.sum create mode 100644 main.go create mode 100644 pr.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a4785cf --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ rootCmd.PersistentFlags + +bb diff --git a/cmd/pr.go b/cmd/pr.go new file mode 100644 index 0000000..1d982d8 --- /dev/null +++ b/cmd/pr.go @@ -0,0 +1,40 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var ( + prCommand = cobra.Command{ + Use: "pr", + } + prListCommand = cobra.Command{ + Use: "list", + Run: list, + } + prViewCommand = cobra.Command{ + Use: "view", + Run: view, + } + prCreateCommand = cobra.Command{ + Use: "create", + Run: create, + } +) + +func init() { + prCommand.AddCommand(&prListCommand) + prCommand.AddCommand(&prViewCommand) + prCommand.AddCommand(&prCreateCommand) + rootCmd.AddCommand(&prCommand) +} + +func list(cmd *cobra.Command, args []string) { +} + +func view(cmd *cobra.Command, args []string) { +} + +func create(cmd *cobra.Command, args []string) { + +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..ea2791f --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,59 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/kirsle/configdir" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + rootCmd = &cobra.Command{ + Use: "bb", + } + + cfgFile string +) + +func Execute() error { + return rootCmd.Execute() +} + +func init() { + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/bb)") + + cobra.OnInitialize(initConfig) +} + +func initConfig() { + if cfgFile == "" { + configDir := configdir.LocalConfig("bb") + err := configdir.MakePath(configDir) + if err != nil { + panic(err) + } + cfgFile = filepath.Join(configDir, "configuration.toml") + if _, err = os.Stat(cfgFile); os.IsNotExist(err) { + fh, err := os.Create(cfgFile) + if err != nil { + panic(err) + } + defer fh.Close() + } + + viper.AddConfigPath(configDir) + viper.SetConfigName("configuration.toml") + } + + viper.SetConfigFile(cfgFile) + + viper.AutomaticEnv() + + err := viper.ReadInConfig() + if err != nil { // Handle errors reading the config file + panic(fmt.Errorf("fatal error config file: %s", err)) + } +} diff --git a/go.mod b/go.mod index a89c76f..7287cd5 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,10 @@ module github.com/craftamap/bb go 1.15 + +require ( + github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f + github.com/ktrysmt/go-bitbucket v0.6.4 // indirect + github.com/spf13/cobra v1.1.0 + github.com/spf13/viper v1.7.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c82f02f --- /dev/null +++ b/go.sum @@ -0,0 +1,319 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/k0kubun/pp v2.3.0+incompatible h1:EKhKbi34VQDWJtq+zpsKSEhkHHs9w2P8Izbq8IhLVSo= +github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= +github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f h1:dKccXx7xA56UNqOcFIbuqFjAWPVtP688j5QMgmo6OHU= +github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f/go.mod h1:4rEELDSfUAlBSyUjPG0JnaNGjf13JySHFeRdD/3dLP0= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/ktrysmt/go-bitbucket v0.6.4 h1:C8dUGp0qkwncKtAnozHCbbqhptefzEd1I0sfnuy9rYQ= +github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.1.0 h1:aq3wCKjTPmzcNWLVGnsFVN4rflK7Uzn10F8/aw8MhdQ= +github.com/spf13/cobra v1.1.0/go.mod h1:yk5b0mALVusDL5fMM6Rd1wgnoO5jUPhwsQ6LQAJTidQ= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/main.go b/main.go new file mode 100644 index 0000000..1977116 --- /dev/null +++ b/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + "os" + + "github.com/craftamap/bb/cmd" +) + +func main() { + if err := cmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(-1) + } +} diff --git a/pr.go b/pr.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/pr.go @@ -0,0 +1 @@ +package main From 857ee19606911d1767594620217815b2d1085e66 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Sat, 17 Oct 2020 13:55:57 +0200 Subject: [PATCH 03/37] draft implementation of bb pr list --- cmd/pr.go | 4 ++++ cmd/root.go | 33 ++++++++++++++++++++++++++++----- go.mod | 4 +++- go.sum | 4 ++++ internal/pr.go | 31 +++++++++++++++++++++++++++++++ pr.go | 1 - 6 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 internal/pr.go delete mode 100644 pr.go diff --git a/cmd/pr.go b/cmd/pr.go index 1d982d8..e6cbeaf 100644 --- a/cmd/pr.go +++ b/cmd/pr.go @@ -1,12 +1,16 @@ package cmd import ( + "github.com/craftamap/bb/internal" "github.com/spf13/cobra" ) var ( prCommand = cobra.Command{ Use: "pr", + Run: func(cmd *cobra.Command, args []string) { + internal.PrList(globalOpts.Username, globalOpts.Password, globalOpts.RepoOrga, globalOpts.RepoSlug) + }, } prListCommand = cobra.Command{ Use: "list", diff --git a/cmd/root.go b/cmd/root.go index ea2791f..c9a77e1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -10,12 +10,28 @@ import ( "github.com/spf13/viper" ) +type GlobalOptions struct { + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + RepoOrga string `mapstructure:"repoOrga"` + RepoSlug string `mapstructure:"repoSlug"` +} + var ( rootCmd = &cobra.Command{ Use: "bb", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + viper.Unmarshal(&globalOpts) + }, } - cfgFile string + cfgFile string + globalOpts = GlobalOptions{} + + username string + password string + repoOrga string + repoSlug string ) func Execute() error { @@ -23,9 +39,18 @@ func Execute() error { } func init() { + cobra.OnInitialize(initConfig) + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/bb)") + rootCmd.PersistentFlags().StringVar(&username, "username", "", "username") + rootCmd.PersistentFlags().StringVar(&password, "password", "", "app password") + rootCmd.PersistentFlags().StringVar(&repoOrga, "repo-orga", "", "repository organisation") + rootCmd.PersistentFlags().StringVar(&repoSlug, "repo-slug", "", "repository slug") - cobra.OnInitialize(initConfig) + viper.BindPFlag("username", rootCmd.PersistentFlags().Lookup("username")) + viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup("password")) + viper.BindPFlag("repoOrga", rootCmd.PersistentFlags().Lookup("repo-orga")) + viper.BindPFlag("repoSlug", rootCmd.PersistentFlags().Lookup("repo-slug")) } func initConfig() { @@ -43,9 +68,6 @@ func initConfig() { } defer fh.Close() } - - viper.AddConfigPath(configDir) - viper.SetConfigName("configuration.toml") } viper.SetConfigFile(cfgFile) @@ -53,6 +75,7 @@ func initConfig() { viper.AutomaticEnv() err := viper.ReadInConfig() + if err != nil { // Handle errors reading the config file panic(fmt.Errorf("fatal error config file: %s", err)) } diff --git a/go.mod b/go.mod index 7287cd5..0035435 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ go 1.15 require ( github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f - github.com/ktrysmt/go-bitbucket v0.6.4 // indirect + github.com/ktrysmt/go-bitbucket v0.6.4 + github.com/logrusorgru/aurora v2.0.3+incompatible + github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/spf13/cobra v1.1.0 github.com/spf13/viper v1.7.0 ) diff --git a/go.sum b/go.sum index c82f02f..405f5ba 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/ktrysmt/go-bitbucket v0.6.4 h1:C8dUGp0qkwncKtAnozHCbbqhptefzEd1I0sfnuy9rYQ= github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= +github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= +github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= @@ -130,6 +132,8 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= diff --git a/internal/pr.go b/internal/pr.go new file mode 100644 index 0000000..9bb4e60 --- /dev/null +++ b/internal/pr.go @@ -0,0 +1,31 @@ +package internal + +import ( + "fmt" + + "github.com/ktrysmt/go-bitbucket" + "github.com/logrusorgru/aurora" +) + +func PrList(username string, password string, repoOrga string, repoSlug string) { + client := bitbucket.NewBasicAuth(username, password) + + opt := &bitbucket.PullRequestsOptions{ + Owner: repoOrga, + RepoSlug: repoSlug, + } + + response, _ := client.Repositories.PullRequests.Gets(opt) + mapResponse := response.(map[string]interface{}) + values := mapResponse["values"].([]interface{}) + + fmt.Println() + fmt.Printf("%s Showing %d of %d open pull requests in %s/%s\n", aurora.Blue(" :: "), len(values), int(mapResponse["size"].(float64)), repoOrga, repoSlug) + fmt.Println() + for _, pr := range values { + mapPr := pr.(map[string]interface{}) + sourceName := mapPr["source"].(map[string]interface{})["branch"].(map[string]interface{})["name"] + destName := mapPr["destination"].(map[string]interface{})["branch"].(map[string]interface{})["name"] + fmt.Printf("#%03d %s %s -> %s\n", aurora.Green(int(mapPr["id"].(float64))), mapPr["title"].(string), sourceName, destName) + } +} diff --git a/pr.go b/pr.go deleted file mode 100644 index 06ab7d0..0000000 --- a/pr.go +++ /dev/null @@ -1 +0,0 @@ -package main From 3107b65e9bd9e6599cad2de6c162f393c801248d Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Sun, 18 Oct 2020 15:35:10 +0200 Subject: [PATCH 04/37] modified cmd/pr and internal/pr --- cmd/pr.go | 10 +++++++--- internal/pr.go | 51 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/cmd/pr.go b/cmd/pr.go index e6cbeaf..43bb898 100644 --- a/cmd/pr.go +++ b/cmd/pr.go @@ -1,16 +1,16 @@ package cmd import ( + "fmt" + "github.com/craftamap/bb/internal" + "github.com/logrusorgru/aurora" "github.com/spf13/cobra" ) var ( prCommand = cobra.Command{ Use: "pr", - Run: func(cmd *cobra.Command, args []string) { - internal.PrList(globalOpts.Username, globalOpts.Password, globalOpts.RepoOrga, globalOpts.RepoSlug) - }, } prListCommand = cobra.Command{ Use: "list", @@ -34,6 +34,10 @@ func init() { } func list(cmd *cobra.Command, args []string) { + err := internal.PrList(globalOpts.Username, globalOpts.Password, globalOpts.RepoOrga, globalOpts.RepoSlug) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + } } func view(cmd *cobra.Command, args []string) { diff --git a/internal/pr.go b/internal/pr.go index 9bb4e60..216513f 100644 --- a/internal/pr.go +++ b/internal/pr.go @@ -1,13 +1,15 @@ package internal import ( + "errors" "fmt" "github.com/ktrysmt/go-bitbucket" "github.com/logrusorgru/aurora" ) -func PrList(username string, password string, repoOrga string, repoSlug string) { +func PrList(username string, password string, repoOrga string, repoSlug string) error { + client := bitbucket.NewBasicAuth(username, password) opt := &bitbucket.PullRequestsOptions{ @@ -15,17 +17,52 @@ func PrList(username string, password string, repoOrga string, repoSlug string) RepoSlug: repoSlug, } - response, _ := client.Repositories.PullRequests.Gets(opt) - mapResponse := response.(map[string]interface{}) - values := mapResponse["values"].([]interface{}) + response, err := client.Repositories.PullRequests.Gets(opt) + if err != nil { + return err + } + mapResponse, ok := response.(map[string]interface{}) + if !ok { + return errors.New("type assertion failed") + } + values, ok := mapResponse["values"].([]interface{}) + if !ok { + return errors.New("type assertion failed") + } fmt.Println() fmt.Printf("%s Showing %d of %d open pull requests in %s/%s\n", aurora.Blue(" :: "), len(values), int(mapResponse["size"].(float64)), repoOrga, repoSlug) fmt.Println() for _, pr := range values { - mapPr := pr.(map[string]interface{}) - sourceName := mapPr["source"].(map[string]interface{})["branch"].(map[string]interface{})["name"] - destName := mapPr["destination"].(map[string]interface{})["branch"].(map[string]interface{})["name"] + mapPr, ok := pr.(map[string]interface{}) + if !ok { + return errors.New("type assertion failed") + } + source, ok := mapPr["source"].(map[string]interface{}) + if !ok { + return errors.New("type assertion failed") + } + + sourceBranch, ok := source["branch"].(map[string]interface{}) + if !ok { + return errors.New("type assertion failed") + } + + sourceName := sourceBranch["name"] + + dest, ok := mapPr["destination"].(map[string]interface{}) + if !ok { + return errors.New("type assertion failed") + } + + destBranch, ok := dest["branch"].(map[string]interface{}) + if !ok { + return errors.New("type assertion failed") + } + + destName := destBranch["name"] fmt.Printf("#%03d %s %s -> %s\n", aurora.Green(int(mapPr["id"].(float64))), mapPr["title"].(string), sourceName, destName) } + + return nil } From f912f25fae9dfa6a3f5bdedcebb4858df1c1be4f Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Mon, 19 Oct 2020 10:55:46 +0200 Subject: [PATCH 05/37] draft implementation of bb pr view --- cmd/pr.go | 42 +++++++++++++- go.mod | 3 +- go.sum | 39 +++++++++++++ internal/pr.go | 148 +++++++++++++++++++++++++++++++++++-------------- 4 files changed, 187 insertions(+), 45 deletions(-) diff --git a/cmd/pr.go b/cmd/pr.go index 43bb898..f9f88b4 100644 --- a/cmd/pr.go +++ b/cmd/pr.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" + "github.com/charmbracelet/glamour" "github.com/craftamap/bb/internal" "github.com/logrusorgru/aurora" "github.com/spf13/cobra" @@ -34,13 +35,52 @@ func init() { } func list(cmd *cobra.Command, args []string) { - err := internal.PrList(globalOpts.Username, globalOpts.Password, globalOpts.RepoOrga, globalOpts.RepoSlug) + prs, err := internal.PrList(globalOpts.Username, globalOpts.Password, globalOpts.RepoOrga, globalOpts.RepoSlug) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) } + + fmt.Println() + fmt.Printf("%sShowing %d of %d open pull requests in %s/%s\n", aurora.Blue(" :: "), len(prs.Values), prs.Size, globalOpts.RepoOrga, globalOpts.RepoSlug) + fmt.Println() + for _, pr := range prs.Values { + fmt.Printf("#%03d %s %s -> %s\n", aurora.Green(pr.ID), pr.Title, pr.Source.Branch.Name, pr.Destination.Branch.Name) + } } func view(cmd *cobra.Command, args []string) { + prs, err := internal.GetPrIDBySourceBranch(globalOpts.Username, globalOpts.Password, globalOpts.RepoOrga, globalOpts.RepoSlug, "bb1-branch1") + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + if len(prs.Values) == 0 { + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Nothing on this branch") + return + } + + pr, err := internal.PrView(globalOpts.Username, globalOpts.Password, globalOpts.RepoOrga, globalOpts.RepoSlug, fmt.Sprintf("%d", prs.Values[0].ID)) + + fmt.Println(aurora.Bold(pr.Title)) + var state string + if pr.State == "OPEN" { + state = aurora.Green("Open").String() + } else if pr.State == "DECLINED" { + state = aurora.Red("Declined").String() + } else { + state = pr.State + } + + infoText := aurora.BrightBlack(fmt.Sprintf("%s wants to merge X commits into %s from %s\n", pr.Author.Nickname, pr.Destination.Branch.Name, pr.Source.Branch.Name)) + fmt.Printf("%s • %s\n", state, infoText) + out, err := glamour.Render(pr.Description, "dark") + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + fmt.Println(out) + // fmt.Println(pr, err) + } func create(cmd *cobra.Command, args []string) { diff --git a/go.mod b/go.mod index 0035435..0f63da6 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,11 @@ module github.com/craftamap/bb go 1.15 require ( + github.com/charmbracelet/glamour v0.2.0 github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f github.com/ktrysmt/go-bitbucket v0.6.4 github.com/logrusorgru/aurora v2.0.3+incompatible - github.com/mitchellh/mapstructure v1.3.3 // indirect + github.com/mitchellh/mapstructure v1.3.3 github.com/spf13/cobra v1.1.0 github.com/spf13/viper v1.7.0 ) diff --git a/go.sum b/go.sum index 405f5ba..045fe14 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,12 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= +github.com/alecthomas/chroma v0.7.3 h1:NfdAERMy+esYQs8OXk0I868/qDxxCEo7FMz1WIqMAeI= +github.com/alecthomas/chroma v0.7.3/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= +github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= +github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= +github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -24,6 +30,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/charmbracelet/glamour v0.2.0 h1:mTgaiNiumpqTZp3qVM6DH9UB0NlbY17wejoMf1kM8Pg= +github.com/charmbracelet/glamour v0.2.0/go.mod h1:UA27Kwj3QHialP74iU6C+Gpc8Y7IOAKupeKMLLBURWM= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -31,10 +39,14 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= +github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -60,6 +72,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4= +github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -114,13 +128,24 @@ github.com/ktrysmt/go-bitbucket v0.6.4 h1:C8dUGp0qkwncKtAnozHCbbqhptefzEd1I0sfnu github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= +github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -136,13 +161,20 @@ github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8 github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/muesli/reflow v0.1.0 h1:oQdpLfO56lr5pgLvqD0TcjW85rDjSYSBVdiG1Ch1ddM= +github.com/muesli/reflow v0.1.0/go.mod h1:I9bWAt7QTg/que/qmUCJBGlj7wEq8OAFBjPNjc6xK4I= +github.com/muesli/termenv v0.6.0 h1:zxvzTBmo4ZcxhNGGWeMz+Tttm51eF5bmPjfy4MCRYlk= +github.com/muesli/termenv v0.6.0/go.mod h1:SohX91w6swWA4AYU+QmPx+aSgXhWO0juiyID9UZmbpA= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -159,6 +191,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -186,6 +219,8 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.2.0 h1:WOOcyaJPlzb8fZ8TloxFe8QZkhOOJx87leDa9MIT9dc= +github.com/yuin/goldmark v1.2.0/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -257,6 +292,10 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY= +golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= diff --git a/internal/pr.go b/internal/pr.go index 216513f..6fa363f 100644 --- a/internal/pr.go +++ b/internal/pr.go @@ -1,68 +1,130 @@ package internal import ( - "errors" "fmt" "github.com/ktrysmt/go-bitbucket" - "github.com/logrusorgru/aurora" + "github.com/mitchellh/mapstructure" ) -func PrList(username string, password string, repoOrga string, repoSlug string) error { +type ListPullRequests struct { + Size int `mapstructure:"size"` + Page int `mapstructure:"page"` + PageLen int `mapstructure:"pagelen"` + Next string `mapstructure:"next"` + Previous string `mapstructure:"previous"` + Values []PullRequest `mapstructure:"values"` +} + +type PullRequest struct { + ID int `mapstructure:"id"` + Title string `mapstructure:"title"` + State string `mapstructure:"state"` + Source Resource `mapstructure:"source"` + Destination Resource `mapstructure:"destination"` + Type string `mapstructure:"type"` + TaskCount int `mapstructure:"task_count"` + Description string `mapstructure:"description"` + Author User `mapstructure:"author"` + CloseSourceBranch bool `mapstructure:"close_source_branch"` + CommentCount int `mapstructure:"comment_count"` + CreatedOn string `mapstructure:"created_on"` + MergeCommit Commit `mapstructure:"merge_commit"` +} + +type Resource struct { + Branch Branch `mapstructure:"branch"` + Commit Commit `mapstructure:"commit"` + Repository Repository `mapstructure:"repository"` +} + +type Branch struct { + Name string `mapstructure:"name"` +} + +type Commit struct { + Hash string `mapstructure:"hash"` + Type string `mapstructure:"type"` +} + +type Repository struct { + FullName string `mapstructure:"full_name"` + Name string `mapstructure:"name"` + Type string `mapstructure:"type"` + UUID string `mapstructure:"uuid"` +} + +type User struct { + AccountID string `mapstructure:"account_id"` + DisplayName string `mapstructure:"display_name"` + Nickname string `mapstructure:"nickname"` + Type string `mapstructure:"user"` + UUID string `mapstructure:"uuid"` +} + +func PrList(username string, password string, repoOrga string, repoSlug string) (*ListPullRequests, error) { + client := bitbucket.NewBasicAuth(username, password) + + opt := &bitbucket.PullRequestsOptions{ + Owner: repoOrga, + RepoSlug: repoSlug, + } + + response, err := client.Repositories.PullRequests.Gets(opt) + if err != nil { + return nil, err + } + + var pullRequests ListPullRequests + err = mapstructure.Decode(response, &pullRequests) + if err != nil { + return nil, err + } + return &pullRequests, nil +} + +func GetPrIDBySourceBranch(username string, password string, repoOrga string, repoSlug string, sourceBranch string) (*ListPullRequests, error) { client := bitbucket.NewBasicAuth(username, password) opt := &bitbucket.PullRequestsOptions{ Owner: repoOrga, RepoSlug: repoSlug, + Query: fmt.Sprintf("source.branch.name = \"%s\"", sourceBranch), } response, err := client.Repositories.PullRequests.Gets(opt) if err != nil { - return err + return nil, err } - mapResponse, ok := response.(map[string]interface{}) - if !ok { - return errors.New("type assertion failed") + + var pullRequests ListPullRequests + err = mapstructure.Decode(response, &pullRequests) + if err != nil { + return nil, err } - values, ok := mapResponse["values"].([]interface{}) - if !ok { - return errors.New("type assertion failed") + + return &pullRequests, nil +} + +func PrView(username string, password string, repoOrga string, repoSlug string, id string) (*PullRequest, error) { + client := bitbucket.NewBasicAuth(username, password) + + opt := &bitbucket.PullRequestsOptions{ + Owner: repoOrga, + RepoSlug: repoSlug, + ID: id, } - fmt.Println() - fmt.Printf("%s Showing %d of %d open pull requests in %s/%s\n", aurora.Blue(" :: "), len(values), int(mapResponse["size"].(float64)), repoOrga, repoSlug) - fmt.Println() - for _, pr := range values { - mapPr, ok := pr.(map[string]interface{}) - if !ok { - return errors.New("type assertion failed") - } - source, ok := mapPr["source"].(map[string]interface{}) - if !ok { - return errors.New("type assertion failed") - } - - sourceBranch, ok := source["branch"].(map[string]interface{}) - if !ok { - return errors.New("type assertion failed") - } - - sourceName := sourceBranch["name"] - - dest, ok := mapPr["destination"].(map[string]interface{}) - if !ok { - return errors.New("type assertion failed") - } - - destBranch, ok := dest["branch"].(map[string]interface{}) - if !ok { - return errors.New("type assertion failed") - } - - destName := destBranch["name"] - fmt.Printf("#%03d %s %s -> %s\n", aurora.Green(int(mapPr["id"].(float64))), mapPr["title"].(string), sourceName, destName) + response, err := client.Repositories.PullRequests.Get(opt) + if err != nil { + return nil, err } - return nil + var pullRequest PullRequest + err = mapstructure.Decode(response, &pullRequest) + if err != nil { + return nil, err + } + return &pullRequest, nil } From 4ac87d444e32b69646efcc05bbe3f99cc263a556 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Mon, 19 Oct 2020 13:44:52 +0200 Subject: [PATCH 06/37] Added links to view --- cmd/pr.go | 7 +++++++ internal/pr.go | 31 ++++++++++++++++++------------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/cmd/pr.go b/cmd/pr.go index f9f88b4..e7c83a9 100644 --- a/cmd/pr.go +++ b/cmd/pr.go @@ -60,6 +60,10 @@ func view(cmd *cobra.Command, args []string) { } pr, err := internal.PrView(globalOpts.Username, globalOpts.Password, globalOpts.RepoOrga, globalOpts.RepoSlug, fmt.Sprintf("%d", prs.Values[0].ID)) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } fmt.Println(aurora.Bold(pr.Title)) var state string @@ -79,6 +83,9 @@ func view(cmd *cobra.Command, args []string) { return } fmt.Println(out) + + footer := aurora.BrightBlack(fmt.Sprintf("View this pull request on Bitbucket.org: %s\n", pr.Links["html"].Href)).String() + fmt.Printf(footer) // fmt.Println(pr, err) } diff --git a/internal/pr.go b/internal/pr.go index 6fa363f..1b0fe6d 100644 --- a/internal/pr.go +++ b/internal/pr.go @@ -17,19 +17,24 @@ type ListPullRequests struct { } type PullRequest struct { - ID int `mapstructure:"id"` - Title string `mapstructure:"title"` - State string `mapstructure:"state"` - Source Resource `mapstructure:"source"` - Destination Resource `mapstructure:"destination"` - Type string `mapstructure:"type"` - TaskCount int `mapstructure:"task_count"` - Description string `mapstructure:"description"` - Author User `mapstructure:"author"` - CloseSourceBranch bool `mapstructure:"close_source_branch"` - CommentCount int `mapstructure:"comment_count"` - CreatedOn string `mapstructure:"created_on"` - MergeCommit Commit `mapstructure:"merge_commit"` + ID int `mapstructure:"id"` + Title string `mapstructure:"title"` + State string `mapstructure:"state"` + Source Resource `mapstructure:"source"` + Destination Resource `mapstructure:"destination"` + Type string `mapstructure:"type"` + TaskCount int `mapstructure:"task_count"` + Description string `mapstructure:"description"` + Author User `mapstructure:"author"` + CloseSourceBranch bool `mapstructure:"close_source_branch"` + CommentCount int `mapstructure:"comment_count"` + CreatedOn string `mapstructure:"created_on"` + MergeCommit Commit `mapstructure:"merge_commit"` + Links map[string]Link `mapstructure:"links"` +} + +type Link struct { + Href string `mapstructure:"href"` } type Resource struct { From 18e234896ffd62984b91d56c61a21f4416819a06 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Mon, 19 Oct 2020 22:33:32 +0200 Subject: [PATCH 07/37] draft implementation for create --- cmd/pr.go | 124 +++++++++++++++++++++++++++++++++++++++++++++---- cmd/root.go | 2 - git/bb.go | 44 ++++++++++++++++++ go.mod | 4 +- go.sum | 59 +++++++++++++++++++++++ internal/pr.go | 25 ++++++++++ 6 files changed, 246 insertions(+), 12 deletions(-) create mode 100644 git/bb.go diff --git a/cmd/pr.go b/cmd/pr.go index e7c83a9..fd01bd0 100644 --- a/cmd/pr.go +++ b/cmd/pr.go @@ -2,11 +2,17 @@ package cmd import ( "fmt" + "strconv" + "github.com/AlecAivazis/survey/v2" "github.com/charmbracelet/glamour" "github.com/craftamap/bb/internal" "github.com/logrusorgru/aurora" "github.com/spf13/cobra" + + "github.com/cli/cli/git" + + bbgit "github.com/craftamap/bb/git" ) var ( @@ -25,9 +31,17 @@ var ( Use: "create", Run: create, } + + createOpts = struct { + Body string + Assignees []string + }{} ) func init() { + prCreateCommand.Flags().StringVarP(&createOpts.Body, "body", "b", "", "Supply a body.") + prCreateCommand.Flags().StringSliceVarP(&createOpts.Assignees, "assignee", "a", nil, "Assign people by their `login`") + prCommand.AddCommand(&prListCommand) prCommand.AddCommand(&prViewCommand) prCommand.AddCommand(&prCreateCommand) @@ -35,13 +49,18 @@ func init() { } func list(cmd *cobra.Command, args []string) { - prs, err := internal.PrList(globalOpts.Username, globalOpts.Password, globalOpts.RepoOrga, globalOpts.RepoSlug) + bbrepo, err := bbgit.GetBitbucketRepo() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + prs, err := internal.PrList(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) } fmt.Println() - fmt.Printf("%sShowing %d of %d open pull requests in %s/%s\n", aurora.Blue(" :: "), len(prs.Values), prs.Size, globalOpts.RepoOrga, globalOpts.RepoSlug) + fmt.Printf("%sShowing %d of %d open pull requests in %s/%s\n", aurora.Blue(" :: "), len(prs.Values), prs.Size, bbrepo.RepoOrga, bbrepo.RepoSlug) fmt.Println() for _, pr := range prs.Values { fmt.Printf("#%03d %s %s -> %s\n", aurora.Green(pr.ID), pr.Title, pr.Source.Branch.Name, pr.Destination.Branch.Name) @@ -49,17 +68,44 @@ func list(cmd *cobra.Command, args []string) { } func view(cmd *cobra.Command, args []string) { - prs, err := internal.GetPrIDBySourceBranch(globalOpts.Username, globalOpts.Password, globalOpts.RepoOrga, globalOpts.RepoSlug, "bb1-branch1") + var id int + var err error + + bbrepo, err := bbgit.GetBitbucketRepo() + if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } - if len(prs.Values) == 0 { - fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Nothing on this branch") - return + + if len(args) > 0 { + id, err = strconv.Atoi(args[0]) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + } else { + branchName, err := git.CurrentBranch() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + prs, err := internal.GetPrIDBySourceBranch(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, branchName) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + if len(prs.Values) == 0 { + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Nothing on this branch") + return + } + + id = prs.Values[0].ID + } - pr, err := internal.PrView(globalOpts.Username, globalOpts.Password, globalOpts.RepoOrga, globalOpts.RepoSlug, fmt.Sprintf("%d", prs.Values[0].ID)) + pr, err := internal.PrView(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, fmt.Sprintf("%d", id)) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return @@ -84,12 +130,72 @@ func view(cmd *cobra.Command, args []string) { } fmt.Println(out) - footer := aurora.BrightBlack(fmt.Sprintf("View this pull request on Bitbucket.org: %s\n", pr.Links["html"].Href)).String() - fmt.Printf(footer) + footer := aurora.BrightBlack(fmt.Sprintf("View this pull request on Bitbucket.org: %s", pr.Links["html"].Href)).String() + fmt.Println(footer) // fmt.Println(pr, err) } func create(cmd *cobra.Command, args []string) { + bbrepo, err := bbgit.GetBitbucketRepo() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + branchName, err := git.CurrentBranch() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + fmt.Printf("Creating pull request for %s into %s in %s\n", branchName, "X", fmt.Sprintf("%s/%s", bbrepo.RepoOrga, bbrepo.RepoSlug)) + fmt.Println() + answers := struct { + Title string + Action string + }{} + + // body := createOpts.Body + + var qs = []*survey.Question{ + { + Name: "title", + Prompt: &survey.Input{ + Message: "Title", + Default: branchName, + }, + Validate: survey.Required, + }, + { + Name: "action", + Prompt: &survey.Select{ + Message: "What's next?", + Options: []string{"create", "cancel", "continue in browser"}, + Default: "create", + }, + }, + } + err = survey.Ask(qs, &answers) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + if answers.Action == "create" { + response, err := internal.PrCreate(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, branchName, "master", answers.Title) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + fmt.Printf("Take a look at your pull request here:\n") + fmt.Println(response) + } } diff --git a/cmd/root.go b/cmd/root.go index c9a77e1..d624a4c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,8 +13,6 @@ import ( type GlobalOptions struct { Username string `mapstructure:"username"` Password string `mapstructure:"password"` - RepoOrga string `mapstructure:"repoOrga"` - RepoSlug string `mapstructure:"repoSlug"` } var ( diff --git a/git/bb.go b/git/bb.go new file mode 100644 index 0000000..3b5afa1 --- /dev/null +++ b/git/bb.go @@ -0,0 +1,44 @@ +package git + +import ( + "strings" + + "github.com/cli/cli/git" +) + +type BitbucketRepo struct { + RepoOrga string + RepoSlug string + Remote git.Remote +} + +func GetBitbucketRepo() (*BitbucketRepo, error) { + remotes, err := git.Remotes() + if err != nil { + return nil, err + } + + var origin git.Remote + for _, remote := range remotes { + if remote.Name == "origin" { + origin = *remote + } + } + + path := strings.Split(origin.FetchURL.Path, "/")[1:] + + repoOrga := path[0] + repoSlug := path[1] + + if origin.FetchURL.Scheme == "ssh" && strings.HasSuffix(repoSlug, ".git") { + repoSlug = strings.TrimSuffix(repoSlug, ".git") + } + + bbrepo := BitbucketRepo{ + Remote: origin, + RepoOrga: repoOrga, + RepoSlug: repoSlug, + } + + return &bbrepo, nil +} diff --git a/go.mod b/go.mod index 0f63da6..e0a7438 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,9 @@ module github.com/craftamap/bb go 1.15 require ( - github.com/charmbracelet/glamour v0.2.0 + github.com/AlecAivazis/survey/v2 v2.1.1 + github.com/charmbracelet/glamour v0.2.1-0.20200724174618-1246d13c1684 + github.com/cli/cli v1.1.0 github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f github.com/ktrysmt/go-bitbucket v0.6.4 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 045fe14..5a298de 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,13 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AlecAivazis/survey v1.8.8 h1:Y4yypp763E8cbqb5RBqZhGgkCFLRFnbRBHrxnpMMsgQ= +github.com/AlecAivazis/survey/v2 v2.1.1 h1:LEMbHE0pLj75faaVEKClEX1TM4AJmmnOh9eimREzLWI= +github.com/AlecAivazis/survey/v2 v2.1.1/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= github.com/alecthomas/chroma v0.7.3 h1:NfdAERMy+esYQs8OXk0I868/qDxxCEo7FMz1WIqMAeI= @@ -23,18 +28,26 @@ github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1p github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/briandowns/spinner v1.11.1/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/charmbracelet/glamour v0.2.0 h1:mTgaiNiumpqTZp3qVM6DH9UB0NlbY17wejoMf1kM8Pg= github.com/charmbracelet/glamour v0.2.0/go.mod h1:UA27Kwj3QHialP74iU6C+Gpc8Y7IOAKupeKMLLBURWM= +github.com/charmbracelet/glamour v0.2.1-0.20200724174618-1246d13c1684 h1:YMyvXRstOQc7n6eneHfudVMbARSCmZ7EZGjtTkkeB3A= +github.com/charmbracelet/glamour v0.2.1-0.20200724174618-1246d13c1684/go.mod h1:UA27Kwj3QHialP74iU6C+Gpc8Y7IOAKupeKMLLBURWM= +github.com/cli/cli v1.1.0 h1:Ynfk6q+1ozX/3YhY5J4QMf9J6rekO7rDpwYgc2DtEkc= +github.com/cli/cli v1.1.0/go.mod h1:9W9naQhz5tAJCqlvkp962EQE7jiEUuRW83oqk71yYsc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -47,6 +60,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/enescakir/emoji v1.0.0/go.mod h1:Bt1EKuLnKDTYpLALApstIkAjdDrS/8IAgTkKp+WKFD0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -72,15 +86,18 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4= github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -97,6 +114,7 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -106,6 +124,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/henvic/httpretty v0.0.6/go.mod h1:X38wLjWXHkXT7r2+uK8LjCMne9rsuNaBLJ+5cU2/Pmo= +github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -115,6 +135,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/k0kubun/pp v2.3.0+incompatible h1:EKhKbi34VQDWJtq+zpsKSEhkHHs9w2P8Izbq8IhLVSo= github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f h1:dKccXx7xA56UNqOcFIbuqFjAWPVtP688j5QMgmo6OHU= github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f/go.mod h1:4rEELDSfUAlBSyUjPG0JnaNGjf13JySHFeRdD/3dLP0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -123,6 +145,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/ktrysmt/go-bitbucket v0.6.4 h1:C8dUGp0qkwncKtAnozHCbbqhptefzEd1I0sfnuy9rYQ= github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= @@ -130,25 +153,35 @@ github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczG github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= @@ -165,6 +198,8 @@ github.com/muesli/reflow v0.1.0 h1:oQdpLfO56lr5pgLvqD0TcjW85rDjSYSBVdiG1Ch1ddM= github.com/muesli/reflow v0.1.0/go.mod h1:I9bWAt7QTg/que/qmUCJBGlj7wEq8OAFBjPNjc6xK4I= github.com/muesli/termenv v0.6.0 h1:zxvzTBmo4ZcxhNGGWeMz+Tttm51eF5bmPjfy4MCRYlk= github.com/muesli/termenv v0.6.0/go.mod h1:SohX91w6swWA4AYU+QmPx+aSgXhWO0juiyID9UZmbpA= +github.com/muesli/termenv v0.7.2 h1:r1raklL3uKE7rOvWgSenmEm2px+dnc33OTisZ8YR1fw= +github.com/muesli/termenv v0.7.2/go.mod h1:ct2L5N2lmix82RaY3bMWwVu/jUFc9Ule0KGDCiKYPh8= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= @@ -186,12 +221,15 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/githubv4 v0.0.0-20200802174311-f27d2ca7f6d5/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= +github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -202,6 +240,7 @@ github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.0 h1:aq3wCKjTPmzcNWLVGnsFVN4rflK7Uzn10F8/aw8MhdQ= github.com/spf13/cobra v1.1.0/go.mod h1:yk5b0mALVusDL5fMM6Rd1wgnoO5jUPhwsQ6LQAJTidQ= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= @@ -209,16 +248,21 @@ github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb6 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.2.0 h1:WOOcyaJPlzb8fZ8TloxFe8QZkhOOJx87leDa9MIT9dc= github.com/yuin/goldmark v1.2.0/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -231,7 +275,11 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -263,6 +311,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -285,10 +334,12 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -300,6 +351,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -320,6 +373,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -342,6 +396,7 @@ google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBr google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -352,9 +407,13 @@ gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/pr.go b/internal/pr.go index 1b0fe6d..a3d636d 100644 --- a/internal/pr.go +++ b/internal/pr.go @@ -133,3 +133,28 @@ func PrView(username string, password string, repoOrga string, repoSlug string, } return &pullRequest, nil } + +func PrCreate(username string, password string, repoOrga string, repoSlug string, sourceBranch string, destinationBranch string, title string) (*PullRequest, error) { + client := bitbucket.NewBasicAuth(username, password) + + opt := &bitbucket.PullRequestsOptions{ + Owner: repoOrga, + RepoSlug: repoSlug, + SourceBranch: sourceBranch, + DestinationBranch: destinationBranch, + Title: title, + } + + response, err := client.Repositories.PullRequests.Create(opt) + + if err != nil { + return nil, err + } + + var pullRequest PullRequest + err = mapstructure.Decode(response, &pullRequest) + if err != nil { + return nil, err + } + return &pullRequest, nil +} From 6085ba934b87068f33f5d23039024e23871c0043 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Tue, 20 Oct 2020 20:37:23 +0200 Subject: [PATCH 08/37] seperated package structure --- cmd/commands/pr/create/create.go | 91 ++++++++++++++ cmd/commands/pr/list/list.go | 36 ++++++ cmd/commands/pr/pr.go | 22 ++++ cmd/commands/pr/view/view.go | 91 ++++++++++++++ cmd/options/global_options.go | 6 + cmd/pr.go | 201 ------------------------------- cmd/root.go | 11 +- go.mod | 1 + go.sum | 2 + main.go | 2 +- 10 files changed, 255 insertions(+), 208 deletions(-) create mode 100644 cmd/commands/pr/create/create.go create mode 100644 cmd/commands/pr/list/list.go create mode 100644 cmd/commands/pr/pr.go create mode 100644 cmd/commands/pr/view/view.go create mode 100644 cmd/options/global_options.go delete mode 100644 cmd/pr.go diff --git a/cmd/commands/pr/create/create.go b/cmd/commands/pr/create/create.go new file mode 100644 index 0000000..7b5fd5f --- /dev/null +++ b/cmd/commands/pr/create/create.go @@ -0,0 +1,91 @@ +package create + +import ( + "fmt" + + "github.com/AlecAivazis/survey/v2" + "github.com/cli/cli/git" + "github.com/craftamap/bb/cmd/options" + bbgit "github.com/craftamap/bb/git" + "github.com/craftamap/bb/internal" + "github.com/logrusorgru/aurora" + "github.com/spf13/cobra" +) + +var ( + Body string + Assignees []string +) + +func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { + createCmd := &cobra.Command{ + Use: "create", + Run: func(cmd *cobra.Command, args []string) { + bbrepo, err := bbgit.GetBitbucketRepo() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + branchName, err := git.CurrentBranch() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + fmt.Printf("Creating pull request for %s into %s in %s\n", branchName, "X", fmt.Sprintf("%s/%s", bbrepo.RepoOrga, bbrepo.RepoSlug)) + fmt.Println() + + answers := struct { + Title string + Action string + }{} + + // body := createOpts.Body + + var qs = []*survey.Question{ + { + Name: "title", + Prompt: &survey.Input{ + Message: "Title", + Default: branchName, + }, + Validate: survey.Required, + }, + { + Name: "action", + Prompt: &survey.Select{ + Message: "What's next?", + Options: []string{"create", "cancel", "continue in browser"}, + Default: "create", + }, + }, + } + err = survey.Ask(qs, &answers) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + if answers.Action == "create" { + response, err := internal.PrCreate(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, branchName, "master", answers.Title) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + fmt.Printf("Take a look at your pull request here:\n") + fmt.Println(response) + } + + }, + } + createCmd.Flags().StringVarP(&Body, "body", "b", "", "Supply a body.") + createCmd.Flags().StringSliceVarP(&Assignees, "assignee", "a", nil, "Assign people by their `login`") + prCmd.AddCommand(createCmd) +} diff --git a/cmd/commands/pr/list/list.go b/cmd/commands/pr/list/list.go new file mode 100644 index 0000000..468f031 --- /dev/null +++ b/cmd/commands/pr/list/list.go @@ -0,0 +1,36 @@ +package list + +import ( + "fmt" + + "github.com/craftamap/bb/cmd/options" + bbgit "github.com/craftamap/bb/git" + "github.com/craftamap/bb/internal" + "github.com/logrusorgru/aurora" + "github.com/spf13/cobra" +) + +func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { + listCmd := &cobra.Command{ + Use: "list", + Run: func(cmd *cobra.Command, args []string) { + bbrepo, err := bbgit.GetBitbucketRepo() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + prs, err := internal.PrList(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + } + + fmt.Println() + fmt.Printf("%sShowing %d of %d open pull requests in %s/%s\n", aurora.Blue(" :: "), len(prs.Values), prs.Size, bbrepo.RepoOrga, bbrepo.RepoSlug) + fmt.Println() + for _, pr := range prs.Values { + fmt.Printf("#%03d %s %s -> %s\n", aurora.Green(pr.ID), pr.Title, pr.Source.Branch.Name, pr.Destination.Branch.Name) + } + }, + } + prCmd.AddCommand(listCmd) +} diff --git a/cmd/commands/pr/pr.go b/cmd/commands/pr/pr.go new file mode 100644 index 0000000..d42b1d4 --- /dev/null +++ b/cmd/commands/pr/pr.go @@ -0,0 +1,22 @@ +package pr + +import ( + "github.com/spf13/cobra" + + "github.com/craftamap/bb/cmd/commands/pr/create" + "github.com/craftamap/bb/cmd/commands/pr/list" + "github.com/craftamap/bb/cmd/commands/pr/view" + "github.com/craftamap/bb/cmd/options" +) + +func Add(rootCmd *cobra.Command, globalOpts *options.GlobalOptions) { + prCommand := cobra.Command{ + Use: "pr", + } + + list.Add(&prCommand, globalOpts) + view.Add(&prCommand, globalOpts) + create.Add(&prCommand, globalOpts) + + rootCmd.AddCommand(&prCommand) +} diff --git a/cmd/commands/pr/view/view.go b/cmd/commands/pr/view/view.go new file mode 100644 index 0000000..547206b --- /dev/null +++ b/cmd/commands/pr/view/view.go @@ -0,0 +1,91 @@ +package view + +import ( + "fmt" + "strconv" + + "github.com/charmbracelet/glamour" + "github.com/cli/cli/git" + "github.com/craftamap/bb/cmd/options" + bbgit "github.com/craftamap/bb/git" + "github.com/craftamap/bb/internal" + "github.com/logrusorgru/aurora" + "github.com/spf13/cobra" +) + +func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { + viewCmd := &cobra.Command{ + Use: "view", + Run: func(cmd *cobra.Command, args []string) { + var id int + var err error + + bbrepo, err := bbgit.GetBitbucketRepo() + + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + if len(args) > 0 { + id, err = strconv.Atoi(args[0]) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + } else { + branchName, err := git.CurrentBranch() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + prs, err := internal.GetPrIDBySourceBranch(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, branchName) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + if len(prs.Values) == 0 { + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Nothing on this branch") + return + } + + id = prs.Values[0].ID + + } + + pr, err := internal.PrView(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, fmt.Sprintf("%d", id)) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + fmt.Println(aurora.Bold(pr.Title)) + var state string + if pr.State == "OPEN" { + state = aurora.Green("Open").String() + } else if pr.State == "DECLINED" { + state = aurora.Red("Declined").String() + } else { + state = pr.State + } + + infoText := aurora.BrightBlack(fmt.Sprintf("%s wants to merge X commits into %s from %s\n", pr.Author.Nickname, pr.Destination.Branch.Name, pr.Source.Branch.Name)) + fmt.Printf("%s • %s\n", state, infoText) + if pr.Description != "" { + out, err := glamour.Render(pr.Description, "dark") + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + fmt.Println(out) + } + + footer := aurora.BrightBlack(fmt.Sprintf("View this pull request on Bitbucket.org: %s", pr.Links["html"].Href)).String() + fmt.Println(footer) + // fmt.Println(pr, err) + + }, + } + prCmd.AddCommand(viewCmd) +} diff --git a/cmd/options/global_options.go b/cmd/options/global_options.go new file mode 100644 index 0000000..97f8d0f --- /dev/null +++ b/cmd/options/global_options.go @@ -0,0 +1,6 @@ +package options + +type GlobalOptions struct { + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` +} diff --git a/cmd/pr.go b/cmd/pr.go deleted file mode 100644 index fd01bd0..0000000 --- a/cmd/pr.go +++ /dev/null @@ -1,201 +0,0 @@ -package cmd - -import ( - "fmt" - "strconv" - - "github.com/AlecAivazis/survey/v2" - "github.com/charmbracelet/glamour" - "github.com/craftamap/bb/internal" - "github.com/logrusorgru/aurora" - "github.com/spf13/cobra" - - "github.com/cli/cli/git" - - bbgit "github.com/craftamap/bb/git" -) - -var ( - prCommand = cobra.Command{ - Use: "pr", - } - prListCommand = cobra.Command{ - Use: "list", - Run: list, - } - prViewCommand = cobra.Command{ - Use: "view", - Run: view, - } - prCreateCommand = cobra.Command{ - Use: "create", - Run: create, - } - - createOpts = struct { - Body string - Assignees []string - }{} -) - -func init() { - prCreateCommand.Flags().StringVarP(&createOpts.Body, "body", "b", "", "Supply a body.") - prCreateCommand.Flags().StringSliceVarP(&createOpts.Assignees, "assignee", "a", nil, "Assign people by their `login`") - - prCommand.AddCommand(&prListCommand) - prCommand.AddCommand(&prViewCommand) - prCommand.AddCommand(&prCreateCommand) - rootCmd.AddCommand(&prCommand) -} - -func list(cmd *cobra.Command, args []string) { - bbrepo, err := bbgit.GetBitbucketRepo() - if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) - return - } - prs, err := internal.PrList(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug) - if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) - } - - fmt.Println() - fmt.Printf("%sShowing %d of %d open pull requests in %s/%s\n", aurora.Blue(" :: "), len(prs.Values), prs.Size, bbrepo.RepoOrga, bbrepo.RepoSlug) - fmt.Println() - for _, pr := range prs.Values { - fmt.Printf("#%03d %s %s -> %s\n", aurora.Green(pr.ID), pr.Title, pr.Source.Branch.Name, pr.Destination.Branch.Name) - } -} - -func view(cmd *cobra.Command, args []string) { - var id int - var err error - - bbrepo, err := bbgit.GetBitbucketRepo() - - if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) - return - } - - if len(args) > 0 { - id, err = strconv.Atoi(args[0]) - if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) - return - } - } else { - branchName, err := git.CurrentBranch() - if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) - return - } - - prs, err := internal.GetPrIDBySourceBranch(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, branchName) - if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) - return - } - if len(prs.Values) == 0 { - fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Nothing on this branch") - return - } - - id = prs.Values[0].ID - - } - - pr, err := internal.PrView(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, fmt.Sprintf("%d", id)) - if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) - return - } - - fmt.Println(aurora.Bold(pr.Title)) - var state string - if pr.State == "OPEN" { - state = aurora.Green("Open").String() - } else if pr.State == "DECLINED" { - state = aurora.Red("Declined").String() - } else { - state = pr.State - } - - infoText := aurora.BrightBlack(fmt.Sprintf("%s wants to merge X commits into %s from %s\n", pr.Author.Nickname, pr.Destination.Branch.Name, pr.Source.Branch.Name)) - fmt.Printf("%s • %s\n", state, infoText) - out, err := glamour.Render(pr.Description, "dark") - if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) - return - } - fmt.Println(out) - - footer := aurora.BrightBlack(fmt.Sprintf("View this pull request on Bitbucket.org: %s", pr.Links["html"].Href)).String() - fmt.Println(footer) - // fmt.Println(pr, err) - -} - -func create(cmd *cobra.Command, args []string) { - bbrepo, err := bbgit.GetBitbucketRepo() - if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) - return - } - - branchName, err := git.CurrentBranch() - if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) - return - } - - if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) - return - } - - fmt.Printf("Creating pull request for %s into %s in %s\n", branchName, "X", fmt.Sprintf("%s/%s", bbrepo.RepoOrga, bbrepo.RepoSlug)) - fmt.Println() - - answers := struct { - Title string - Action string - }{} - - // body := createOpts.Body - - var qs = []*survey.Question{ - { - Name: "title", - Prompt: &survey.Input{ - Message: "Title", - Default: branchName, - }, - Validate: survey.Required, - }, - { - Name: "action", - Prompt: &survey.Select{ - Message: "What's next?", - Options: []string{"create", "cancel", "continue in browser"}, - Default: "create", - }, - }, - } - err = survey.Ask(qs, &answers) - if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) - return - } - - if answers.Action == "create" { - response, err := internal.PrCreate(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, branchName, "master", answers.Title) - if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) - return - } - - fmt.Printf("Take a look at your pull request here:\n") - fmt.Println(response) - } -} diff --git a/cmd/root.go b/cmd/root.go index d624a4c..66df457 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,16 +5,13 @@ import ( "os" "path/filepath" + "github.com/craftamap/bb/cmd/commands/pr" + "github.com/craftamap/bb/cmd/options" "github.com/kirsle/configdir" "github.com/spf13/cobra" "github.com/spf13/viper" ) -type GlobalOptions struct { - Username string `mapstructure:"username"` - Password string `mapstructure:"password"` -} - var ( rootCmd = &cobra.Command{ Use: "bb", @@ -24,7 +21,7 @@ var ( } cfgFile string - globalOpts = GlobalOptions{} + globalOpts = options.GlobalOptions{} username string password string @@ -49,6 +46,8 @@ func init() { viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup("password")) viper.BindPFlag("repoOrga", rootCmd.PersistentFlags().Lookup("repo-orga")) viper.BindPFlag("repoSlug", rootCmd.PersistentFlags().Lookup("repo-slug")) + + pr.Add(rootCmd, &globalOpts) } func initConfig() { diff --git a/go.mod b/go.mod index e0a7438..b027043 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/ktrysmt/go-bitbucket v0.6.4 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/mitchellh/mapstructure v1.3.3 + github.com/spance/go-callprivate v0.0.0-20151213012958-d8b9b5523668 // indirect github.com/spf13/cobra v1.1.0 github.com/spf13/viper v1.7.0 ) diff --git a/go.sum b/go.sum index 5a298de..4f92148 100644 --- a/go.sum +++ b/go.sum @@ -235,6 +235,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spance/go-callprivate v0.0.0-20151213012958-d8b9b5523668 h1:wSXAJdKyzJNaIvTDkThQd13R+3DiVKttdi1iOUa/6xw= +github.com/spance/go-callprivate v0.0.0-20151213012958-d8b9b5523668/go.mod h1:YAHXixP6/M2dnkeoKo4MG9cK8i7OjSn0AFS0SmcBgFA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= diff --git a/main.go b/main.go index 1977116..23cb450 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,4 @@ -package main +package bb import ( "fmt" From 25b7de7b565d2c76498840cd697b42ce352c9077 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Fri, 23 Oct 2020 18:02:21 +0200 Subject: [PATCH 09/37] added repository with different libary --- cmd/commands/pr/create/create.go | 27 +++++++++++++---- go.mod | 1 + go.sum | 2 ++ internal/pr.go | 47 ++++++++++++++--------------- internal/repository.go | 51 ++++++++++++++++++++++++++++++++ internal/shared.go | 17 +++++++++++ main.go | 2 +- 7 files changed, 116 insertions(+), 31 deletions(-) create mode 100644 internal/repository.go create mode 100644 internal/shared.go diff --git a/cmd/commands/pr/create/create.go b/cmd/commands/pr/create/create.go index 7b5fd5f..cf682d0 100644 --- a/cmd/commands/pr/create/create.go +++ b/cmd/commands/pr/create/create.go @@ -21,24 +21,39 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { createCmd := &cobra.Command{ Use: "create", Run: func(cmd *cobra.Command, args []string) { + var ( + sourceBranch string + targetBranch string + title string + body string + reviewers []string + ) + bbrepo, err := bbgit.GetBitbucketRepo() if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } - branchName, err := git.CurrentBranch() + sourceBranch, err = git.CurrentBranch() if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } + repo, err := internal.RepositoryGet(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } + targetBranch = repo.MainBranch.Name - fmt.Printf("Creating pull request for %s into %s in %s\n", branchName, "X", fmt.Sprintf("%s/%s", bbrepo.RepoOrga, bbrepo.RepoSlug)) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + fmt.Printf("Creating pull request for %s into %s in %s\n", sourceBranch, "X", fmt.Sprintf("%s/%s", bbrepo.RepoOrga, bbrepo.RepoSlug)) fmt.Println() answers := struct { @@ -53,7 +68,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { Name: "title", Prompt: &survey.Input{ Message: "Title", - Default: branchName, + Default: sourceBranch, }, Validate: survey.Required, }, @@ -61,7 +76,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { Name: "action", Prompt: &survey.Select{ Message: "What's next?", - Options: []string{"create", "cancel", "continue in browser"}, + Options: []string{"create", "modify body", "set reviewers", "change destination branch", "cancel"}, Default: "create", }, }, @@ -72,8 +87,10 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { return } + title = answers.Title + if answers.Action == "create" { - response, err := internal.PrCreate(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, branchName, "master", answers.Title) + response, err := internal.PrCreate(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch, title, body, reviewers) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return diff --git a/go.mod b/go.mod index b027043..3cf1996 100644 --- a/go.mod +++ b/go.mod @@ -13,4 +13,5 @@ require ( github.com/spance/go-callprivate v0.0.0-20151213012958-d8b9b5523668 // indirect github.com/spf13/cobra v1.1.0 github.com/spf13/viper v1.7.0 + github.com/wbrefvem/go-bitbucket v0.0.0-20190128183802-fc08fd046abb ) diff --git a/go.sum b/go.sum index 4f92148..9af16a3 100644 --- a/go.sum +++ b/go.sum @@ -263,6 +263,8 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/wbrefvem/go-bitbucket v0.0.0-20190128183802-fc08fd046abb h1:KrmaSo+FHWBt1H652w/uerwzKvQqh4H7Jgyxm4hz2BQ= +github.com/wbrefvem/go-bitbucket v0.0.0-20190128183802-fc08fd046abb/go.mod h1:Z91j2jYBApRjJ0zlXDCxPrrZR8ohkkd4g0n+Hqs1w0Q= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.2.0 h1:WOOcyaJPlzb8fZ8TloxFe8QZkhOOJx87leDa9MIT9dc= diff --git a/internal/pr.go b/internal/pr.go index a3d636d..d776fab 100644 --- a/internal/pr.go +++ b/internal/pr.go @@ -25,7 +25,7 @@ type PullRequest struct { Type string `mapstructure:"type"` TaskCount int `mapstructure:"task_count"` Description string `mapstructure:"description"` - Author User `mapstructure:"author"` + Author Account `mapstructure:"author"` CloseSourceBranch bool `mapstructure:"close_source_branch"` CommentCount int `mapstructure:"comment_count"` CreatedOn string `mapstructure:"created_on"` @@ -33,40 +33,17 @@ type PullRequest struct { Links map[string]Link `mapstructure:"links"` } -type Link struct { - Href string `mapstructure:"href"` -} - type Resource struct { Branch Branch `mapstructure:"branch"` Commit Commit `mapstructure:"commit"` Repository Repository `mapstructure:"repository"` } -type Branch struct { - Name string `mapstructure:"name"` -} - type Commit struct { Hash string `mapstructure:"hash"` Type string `mapstructure:"type"` } -type Repository struct { - FullName string `mapstructure:"full_name"` - Name string `mapstructure:"name"` - Type string `mapstructure:"type"` - UUID string `mapstructure:"uuid"` -} - -type User struct { - AccountID string `mapstructure:"account_id"` - DisplayName string `mapstructure:"display_name"` - Nickname string `mapstructure:"nickname"` - Type string `mapstructure:"user"` - UUID string `mapstructure:"uuid"` -} - func PrList(username string, password string, repoOrga string, repoSlug string) (*ListPullRequests, error) { client := bitbucket.NewBasicAuth(username, password) @@ -134,7 +111,7 @@ func PrView(username string, password string, repoOrga string, repoSlug string, return &pullRequest, nil } -func PrCreate(username string, password string, repoOrga string, repoSlug string, sourceBranch string, destinationBranch string, title string) (*PullRequest, error) { +func PrCreate(username string, password string, repoOrga string, repoSlug string, sourceBranch string, destinationBranch string, title string, body string, reviewers []string) (*PullRequest, error) { client := bitbucket.NewBasicAuth(username, password) opt := &bitbucket.PullRequestsOptions{ @@ -143,6 +120,8 @@ func PrCreate(username string, password string, repoOrga string, repoSlug string SourceBranch: sourceBranch, DestinationBranch: destinationBranch, Title: title, + Description: body, + Reviewers: reviewers, } response, err := client.Repositories.PullRequests.Create(opt) @@ -158,3 +137,21 @@ func PrCreate(username string, password string, repoOrga string, repoSlug string } return &pullRequest, nil } + +func PrDefaultBody(username string, password string, repoOrga string, repoSlug string, sourceBranch string, destinationBranch string) (string, error) { + client := bitbucket.NewBasicAuth(username, password) + + opts := bitbucket.CommitsOptions{ + Owner: repoOrga, + RepoSlug: repoSlug, + Revision: sourceBranch, + Exclude: destinationBranch, + } + + response, err := client.Repositories.Commits.GetCommits(&opts) + if err != nil { + return "", err + } + + return fmt.Sprintf("%#v", response), nil +} diff --git a/internal/repository.go b/internal/repository.go new file mode 100644 index 0000000..c4d274d --- /dev/null +++ b/internal/repository.go @@ -0,0 +1,51 @@ +package internal + +import ( + "context" + "fmt" + "time" + + "github.com/mitchellh/mapstructure" + "github.com/wbrefvem/go-bitbucket" +) + +type Repository struct { + Links map[string]interface{} `mapstructure:"Links"` + UUID string `mapstructure:"Uuid"` + FullName string `mapstructure:"FullName"` + IsPrivate bool `mapstructure:"IsPrivate"` + Parent *Repository `mapstructure:"Parent"` + Owner *Account `mapstructure:"Owner"` + Name string `mapstructure:"Name"` + Description string `mapstructure:"Description"` + CreatedOn time.Time `mapstructure:"CreatedOn"` + UpdatedOn time.Time `mapstructure:"UpdatedOn"` + Size int `mapstructure:"Size"` + Language string `mapstructure:"Language"` + HasIssues bool `mapstructure:"HasIssues"` + HasWiki bool `mapstructure:"HasWiki"` + ForkPolicy string `mapstructure:"ForkPolicy"` + MainBranch *Branch `mapstructure:"Mainbranch"` + // Project Project `mapstructure:"project"` +} + +func RepositoryGet(username string, password string, repoOrga string, repoSlug string) (*Repository, error) { + client := bitbucket.NewAPIClient(bitbucket.NewConfiguration()) + response, _, err := client.RepositoriesApi.RepositoriesUsernameRepoSlugGet( + context.WithValue(context.Background(), bitbucket.ContextBasicAuth, bitbucket.BasicAuth{ + UserName: username, + Password: password, + }), repoOrga, repoSlug) + + fmt.Printf("%#v\n", response) + if err != nil { + return nil, err + } + + var repo Repository + err = mapstructure.Decode(response, &repo) + if err != nil { + return nil, err + } + return &repo, nil +} diff --git a/internal/shared.go b/internal/shared.go new file mode 100644 index 0000000..060126f --- /dev/null +++ b/internal/shared.go @@ -0,0 +1,17 @@ +package internal + +type Link struct { + Href string `mapstructure:"href"` +} + +type Branch struct { + Name string `mapstructure:"name"` +} + +type Account struct { + AccountID string `mapstructure:"account_id"` + DisplayName string `mapstructure:"display_name"` + Nickname string `mapstructure:"nickname"` + Type string `mapstructure:"user"` + UUID string `mapstructure:"uuid"` +} diff --git a/main.go b/main.go index 23cb450..1977116 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,4 @@ -package bb +package main import ( "fmt" From d55c5a2c8bb4809452942072031e3a63b011f67a Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Fri, 23 Oct 2020 19:43:47 +0200 Subject: [PATCH 10/37] change survey flow --- cmd/commands/pr/create/create.go | 74 ++++++++++++++++++-------------- internal/auth.go | 6 +++ internal/commits.go | 1 + 3 files changed, 48 insertions(+), 33 deletions(-) create mode 100644 internal/auth.go create mode 100644 internal/commits.go diff --git a/cmd/commands/pr/create/create.go b/cmd/commands/pr/create/create.go index cf682d0..5801dea 100644 --- a/cmd/commands/pr/create/create.go +++ b/cmd/commands/pr/create/create.go @@ -53,53 +53,61 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { return } - fmt.Printf("Creating pull request for %s into %s in %s\n", sourceBranch, "X", fmt.Sprintf("%s/%s", bbrepo.RepoOrga, bbrepo.RepoSlug)) + fmt.Printf("Creating pull request for %s into %s in %s\n", sourceBranch, targetBranch, fmt.Sprintf("%s/%s", bbrepo.RepoOrga, bbrepo.RepoSlug)) fmt.Println() - answers := struct { - Title string - Action string - }{} - - // body := createOpts.Body - - var qs = []*survey.Question{ - { - Name: "title", - Prompt: &survey.Input{ - Message: "Title", - Default: sourceBranch, - }, - Validate: survey.Required, - }, - { - Name: "action", - Prompt: &survey.Select{ - Message: "What's next?", - Options: []string{"create", "modify body", "set reviewers", "change destination branch", "cancel"}, - Default: "create", - }, - }, + if title == "" { + questionTitle := &survey.Input{ + Message: "Title", + Default: title, + } + err = survey.AskOne(questionTitle, &title) } - err = survey.Ask(qs, &answers) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } - - title = answers.Title - - if answers.Action == "create" { - response, err := internal.PrCreate(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch, title, body, reviewers) + for { + selectNext := &survey.Select{ + Message: "What's next?", + Options: []string{"create", "modify body", "change destination branch", "cancel"}, + Default: "create", + } + var doNext string + err = survey.AskOne(selectNext, &doNext) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } - fmt.Printf("Take a look at your pull request here:\n") - fmt.Println(response) + if doNext == "cancel" { + return + } else if doNext == "create" { + break + } + + if doNext == "modify body" { + continue + } + + if doNext == "change destination branch" { + survey.AskOne(&survey.Input{ + Message: "type your destination branch", + Default: targetBranch, + }, &targetBranch) + } + + } + + response, err := internal.PrCreate(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch, title, body, reviewers) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return } + fmt.Printf("Take a look at your pull request here:\n") + fmt.Println(response) + }, } createCmd.Flags().StringVarP(&Body, "body", "b", "", "Supply a body.") diff --git a/internal/auth.go b/internal/auth.go new file mode 100644 index 0000000..4bb4208 --- /dev/null +++ b/internal/auth.go @@ -0,0 +1,6 @@ +package internal + +type Client struct { + Username string + Password string +} diff --git a/internal/commits.go b/internal/commits.go new file mode 100644 index 0000000..5bf0569 --- /dev/null +++ b/internal/commits.go @@ -0,0 +1 @@ +package internal From 2b827ff87a82e03d651ef84b3b40813189fdc627 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Fri, 23 Oct 2020 20:19:47 +0200 Subject: [PATCH 11/37] refactoring stuff --- cmd/commands/pr/create/create.go | 14 ++++++++++---- cmd/commands/pr/list/list.go | 6 +++++- cmd/commands/pr/view/view.go | 8 ++++++-- internal/{auth.go => client.go} | 0 internal/pr.go | 20 ++++++++++---------- internal/repository.go | 6 +++--- 6 files changed, 34 insertions(+), 20 deletions(-) rename internal/{auth.go => client.go} (100%) diff --git a/cmd/commands/pr/create/create.go b/cmd/commands/pr/create/create.go index 5801dea..7ccb217 100644 --- a/cmd/commands/pr/create/create.go +++ b/cmd/commands/pr/create/create.go @@ -29,6 +29,11 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { reviewers []string ) + c := internal.Client{ + Username: globalOpts.Username, + Password: globalOpts.Password, + } + bbrepo, err := bbgit.GetBitbucketRepo() if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) @@ -41,7 +46,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { return } - repo, err := internal.RepositoryGet(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug) + repo, err := c.RepositoryGet(bbrepo.RepoOrga, bbrepo.RepoSlug) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return @@ -59,7 +64,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { if title == "" { questionTitle := &survey.Input{ Message: "Title", - Default: title, + Default: sourceBranch, } err = survey.AskOne(questionTitle, &title) } @@ -99,9 +104,10 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { } - response, err := internal.PrCreate(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch, title, body, reviewers) + response, err := c.PrCreate(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch, title, body, reviewers) + fmt.Println(targetBranch) if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + fmt.Printf("%s%s%#v\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } diff --git a/cmd/commands/pr/list/list.go b/cmd/commands/pr/list/list.go index 468f031..9982b0e 100644 --- a/cmd/commands/pr/list/list.go +++ b/cmd/commands/pr/list/list.go @@ -14,12 +14,16 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { listCmd := &cobra.Command{ Use: "list", Run: func(cmd *cobra.Command, args []string) { + c := internal.Client{ + Username: globalOpts.Username, + Password: globalOpts.Password, + } bbrepo, err := bbgit.GetBitbucketRepo() if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } - prs, err := internal.PrList(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug) + prs, err := c.PrList(bbrepo.RepoOrga, bbrepo.RepoSlug) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) } diff --git a/cmd/commands/pr/view/view.go b/cmd/commands/pr/view/view.go index 547206b..8c38a55 100644 --- a/cmd/commands/pr/view/view.go +++ b/cmd/commands/pr/view/view.go @@ -19,6 +19,10 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { Run: func(cmd *cobra.Command, args []string) { var id int var err error + c := internal.Client{ + Username: globalOpts.Username, + Password: globalOpts.Password, + } bbrepo, err := bbgit.GetBitbucketRepo() @@ -40,7 +44,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { return } - prs, err := internal.GetPrIDBySourceBranch(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, branchName) + prs, err := c.GetPrIDBySourceBranch(bbrepo.RepoOrga, bbrepo.RepoSlug, branchName) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return @@ -54,7 +58,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { } - pr, err := internal.PrView(globalOpts.Username, globalOpts.Password, bbrepo.RepoOrga, bbrepo.RepoSlug, fmt.Sprintf("%d", id)) + pr, err := c.PrView(bbrepo.RepoOrga, bbrepo.RepoSlug, fmt.Sprintf("%d", id)) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return diff --git a/internal/auth.go b/internal/client.go similarity index 100% rename from internal/auth.go rename to internal/client.go diff --git a/internal/pr.go b/internal/pr.go index d776fab..847eca5 100644 --- a/internal/pr.go +++ b/internal/pr.go @@ -44,8 +44,8 @@ type Commit struct { Type string `mapstructure:"type"` } -func PrList(username string, password string, repoOrga string, repoSlug string) (*ListPullRequests, error) { - client := bitbucket.NewBasicAuth(username, password) +func (c Client) PrList(repoOrga string, repoSlug string) (*ListPullRequests, error) { + client := bitbucket.NewBasicAuth(c.Username, c.Password) opt := &bitbucket.PullRequestsOptions{ Owner: repoOrga, @@ -66,8 +66,8 @@ func PrList(username string, password string, repoOrga string, repoSlug string) return &pullRequests, nil } -func GetPrIDBySourceBranch(username string, password string, repoOrga string, repoSlug string, sourceBranch string) (*ListPullRequests, error) { - client := bitbucket.NewBasicAuth(username, password) +func (c Client) GetPrIDBySourceBranch(repoOrga string, repoSlug string, sourceBranch string) (*ListPullRequests, error) { + client := bitbucket.NewBasicAuth(c.Username, c.Password) opt := &bitbucket.PullRequestsOptions{ Owner: repoOrga, @@ -89,8 +89,8 @@ func GetPrIDBySourceBranch(username string, password string, repoOrga string, re return &pullRequests, nil } -func PrView(username string, password string, repoOrga string, repoSlug string, id string) (*PullRequest, error) { - client := bitbucket.NewBasicAuth(username, password) +func (c Client) PrView(repoOrga string, repoSlug string, id string) (*PullRequest, error) { + client := bitbucket.NewBasicAuth(c.Username, c.Password) opt := &bitbucket.PullRequestsOptions{ Owner: repoOrga, @@ -111,8 +111,8 @@ func PrView(username string, password string, repoOrga string, repoSlug string, return &pullRequest, nil } -func PrCreate(username string, password string, repoOrga string, repoSlug string, sourceBranch string, destinationBranch string, title string, body string, reviewers []string) (*PullRequest, error) { - client := bitbucket.NewBasicAuth(username, password) +func (c Client) PrCreate(repoOrga string, repoSlug string, sourceBranch string, destinationBranch string, title string, body string, reviewers []string) (*PullRequest, error) { + client := bitbucket.NewBasicAuth(c.Username, c.Password) opt := &bitbucket.PullRequestsOptions{ Owner: repoOrga, @@ -138,8 +138,8 @@ func PrCreate(username string, password string, repoOrga string, repoSlug string return &pullRequest, nil } -func PrDefaultBody(username string, password string, repoOrga string, repoSlug string, sourceBranch string, destinationBranch string) (string, error) { - client := bitbucket.NewBasicAuth(username, password) +func (c Client) PrDefaultBody(repoOrga string, repoSlug string, sourceBranch string, destinationBranch string) (string, error) { + client := bitbucket.NewBasicAuth(c.Username, c.Password) opts := bitbucket.CommitsOptions{ Owner: repoOrga, diff --git a/internal/repository.go b/internal/repository.go index c4d274d..644133c 100644 --- a/internal/repository.go +++ b/internal/repository.go @@ -29,12 +29,12 @@ type Repository struct { // Project Project `mapstructure:"project"` } -func RepositoryGet(username string, password string, repoOrga string, repoSlug string) (*Repository, error) { +func (c Client) RepositoryGet(repoOrga string, repoSlug string) (*Repository, error) { client := bitbucket.NewAPIClient(bitbucket.NewConfiguration()) response, _, err := client.RepositoriesApi.RepositoriesUsernameRepoSlugGet( context.WithValue(context.Background(), bitbucket.ContextBasicAuth, bitbucket.BasicAuth{ - UserName: username, - Password: password, + UserName: c.Username, + Password: c.Password, }), repoOrga, repoSlug) fmt.Printf("%#v\n", response) From 43969c9ccfe92f182a84cbbb9c5812891021bc60 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Sat, 24 Oct 2020 11:34:17 +0200 Subject: [PATCH 12/37] default commit message --- cmd/commands/pr/create/create.go | 10 ++++++++ internal/commits.go | 41 ++++++++++++++++++++++++++++++++ internal/pr.go | 24 +++++++------------ internal/repository.go | 2 -- 4 files changed, 59 insertions(+), 18 deletions(-) diff --git a/cmd/commands/pr/create/create.go b/cmd/commands/pr/create/create.go index 7ccb217..26cd3cd 100644 --- a/cmd/commands/pr/create/create.go +++ b/cmd/commands/pr/create/create.go @@ -72,6 +72,16 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } + + if body == "" { + body, err = c.PrDefaultBody(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + fmt.Println(body) + } + for { selectNext := &survey.Select{ Message: "What's next?", diff --git a/internal/commits.go b/internal/commits.go index 5bf0569..c967aa3 100644 --- a/internal/commits.go +++ b/internal/commits.go @@ -1 +1,42 @@ package internal + +import ( + "github.com/ktrysmt/go-bitbucket" + "github.com/mitchellh/mapstructure" +) + +type Commit struct { + Hash string `mapstructure:"hash"` + Type string `mapstructure:"type"` + Message string `mapstructure:"message"` + Parents []*Commit `mapstructure:"parents"` + Repository *Repository `mapstructure:"repository"` + Author map[string]interface{} `mapstructure:"author"` +} + +type Commits struct { + Values []*Commit `mapstructure:"values"` +} + +func (c Client) GetCommits(repoOrga string, repoSlug string, revision string, include string, exclude string) (*Commits, error) { + client := bitbucket.NewBasicAuth(c.Username, c.Password) + + opts := bitbucket.CommitsOptions{ + Owner: repoOrga, + RepoSlug: repoSlug, + Revision: revision, + Exclude: exclude, + Include: include, + } + + var commits Commits + response, err := client.Repositories.Commits.GetCommits(&opts) + if err != nil { + return nil, err + } + err = mapstructure.Decode(response, &commits) + if err != nil { + return nil, err + } + return &commits, nil +} diff --git a/internal/pr.go b/internal/pr.go index 847eca5..4cbd251 100644 --- a/internal/pr.go +++ b/internal/pr.go @@ -2,6 +2,7 @@ package internal import ( "fmt" + "strings" "github.com/ktrysmt/go-bitbucket" "github.com/mitchellh/mapstructure" @@ -39,11 +40,6 @@ type Resource struct { Repository Repository `mapstructure:"repository"` } -type Commit struct { - Hash string `mapstructure:"hash"` - Type string `mapstructure:"type"` -} - func (c Client) PrList(repoOrga string, repoSlug string) (*ListPullRequests, error) { client := bitbucket.NewBasicAuth(c.Username, c.Password) @@ -139,19 +135,15 @@ func (c Client) PrCreate(repoOrga string, repoSlug string, sourceBranch string, } func (c Client) PrDefaultBody(repoOrga string, repoSlug string, sourceBranch string, destinationBranch string) (string, error) { - client := bitbucket.NewBasicAuth(c.Username, c.Password) - - opts := bitbucket.CommitsOptions{ - Owner: repoOrga, - RepoSlug: repoSlug, - Revision: sourceBranch, - Exclude: destinationBranch, - } - - response, err := client.Repositories.Commits.GetCommits(&opts) + commits, err := c.GetCommits(repoOrga, repoSlug, sourceBranch, "", destinationBranch) if err != nil { return "", err } - return fmt.Sprintf("%#v", response), nil + var sb strings.Builder + for _, commit := range commits.Values { + sb.WriteString(" - " + commit.Message + "\n") + } + + return sb.String(), nil } diff --git a/internal/repository.go b/internal/repository.go index 644133c..8893ba4 100644 --- a/internal/repository.go +++ b/internal/repository.go @@ -2,7 +2,6 @@ package internal import ( "context" - "fmt" "time" "github.com/mitchellh/mapstructure" @@ -37,7 +36,6 @@ func (c Client) RepositoryGet(repoOrga string, repoSlug string) (*Repository, er Password: c.Password, }), repoOrga, repoSlug) - fmt.Printf("%#v\n", response) if err != nil { return nil, err } From 570efdb35331bcb73bf2f21c357212570b7897d5 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Sat, 24 Oct 2020 13:22:39 +0200 Subject: [PATCH 13/37] seperated body --- cmd/commands/pr/create/create.go | 16 +++++++++++++++- internal/pr.go | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/cmd/commands/pr/create/create.go b/cmd/commands/pr/create/create.go index 26cd3cd..f155505 100644 --- a/cmd/commands/pr/create/create.go +++ b/cmd/commands/pr/create/create.go @@ -2,9 +2,12 @@ package create import ( "fmt" + "os" "github.com/AlecAivazis/survey/v2" + "github.com/charmbracelet/glamour" "github.com/cli/cli/git" + "github.com/cli/cli/pkg/surveyext" "github.com/craftamap/bb/cmd/options" bbgit "github.com/craftamap/bb/git" "github.com/craftamap/bb/internal" @@ -82,6 +85,11 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { fmt.Println(body) } + fmt.Println(aurora.Bold(aurora.Green("!").String() + " Body:")) + + out, _ := glamour.Render(body, "dark") + fmt.Print(out) + for { selectNext := &survey.Select{ Message: "What's next?", @@ -102,6 +110,12 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { } if doNext == "modify body" { + body, err = surveyext.Edit("vim", "", body, os.Stdin, os.Stdout, os.Stderr, nil) + if err != nil { + fmt.Printf("%s%s%#v\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + continue } @@ -122,7 +136,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { } fmt.Printf("Take a look at your pull request here:\n") - fmt.Println(response) + fmt.Println(response.Links["html"].Href) }, } diff --git a/internal/pr.go b/internal/pr.go index 4cbd251..06c9f14 100644 --- a/internal/pr.go +++ b/internal/pr.go @@ -142,7 +142,7 @@ func (c Client) PrDefaultBody(repoOrga string, repoSlug string, sourceBranch str var sb strings.Builder for _, commit := range commits.Values { - sb.WriteString(" - " + commit.Message + "\n") + sb.WriteString("- " + commit.Message + "\n") } return sb.String(), nil From 58b3b04c1a6630c2466e303a003723fc861281b5 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Wed, 28 Oct 2020 15:35:19 +0100 Subject: [PATCH 14/37] Is bitbucket check and regenerate default body --- cmd/commands/pr/create/create.go | 21 ++++++++++++++++++++- cmd/commands/pr/list/list.go | 4 ++++ cmd/commands/pr/view/view.go | 4 ++++ git/bb.go | 4 ++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/cmd/commands/pr/create/create.go b/cmd/commands/pr/create/create.go index f155505..25f5db5 100644 --- a/cmd/commands/pr/create/create.go +++ b/cmd/commands/pr/create/create.go @@ -28,6 +28,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { sourceBranch string targetBranch string title string + defaultBody string body string reviewers []string ) @@ -42,6 +43,10 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } + if !bbrepo.IsBitbucketOrg() { + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Are you sure this is a bitbucket repo?") + return + } sourceBranch, err = git.CurrentBranch() if err != nil { @@ -77,11 +82,12 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { } if body == "" { - body, err = c.PrDefaultBody(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch) + defaultBody, err = c.PrDefaultBody(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } + body = defaultBody fmt.Println(body) } @@ -124,6 +130,19 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { Message: "type your destination branch", Default: targetBranch, }, &targetBranch) + + // We need to re-generate the body, if the destination branch is changed + // but only if the body was not modified in before + + tempBody, err := c.PrDefaultBody(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + if body == defaultBody { + body = tempBody + } + } } diff --git a/cmd/commands/pr/list/list.go b/cmd/commands/pr/list/list.go index 9982b0e..7fadf64 100644 --- a/cmd/commands/pr/list/list.go +++ b/cmd/commands/pr/list/list.go @@ -23,6 +23,10 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } + if !bbrepo.IsBitbucketOrg() { + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Are you sure this is a bitbucket repo?") + return + } prs, err := c.PrList(bbrepo.RepoOrga, bbrepo.RepoSlug) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) diff --git a/cmd/commands/pr/view/view.go b/cmd/commands/pr/view/view.go index 8c38a55..469884d 100644 --- a/cmd/commands/pr/view/view.go +++ b/cmd/commands/pr/view/view.go @@ -30,6 +30,10 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } + if !bbrepo.IsBitbucketOrg() { + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Are you sure this is a bitbucket repo?") + return + } if len(args) > 0 { id, err = strconv.Atoi(args[0]) diff --git a/git/bb.go b/git/bb.go index 3b5afa1..ba0b29e 100644 --- a/git/bb.go +++ b/git/bb.go @@ -42,3 +42,7 @@ func GetBitbucketRepo() (*BitbucketRepo, error) { return &bbrepo, nil } + +func (b *BitbucketRepo) IsBitbucketOrg() bool { + return strings.Contains(b.Remote.FetchURL.String(), "bitbucket") +} From 135d3224e1b135baf21ed90c25acb537258517e7 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Wed, 28 Oct 2020 23:35:37 +0100 Subject: [PATCH 15/37] statuses --- cmd/commands/pr/create/create.go | 8 +- cmd/commands/pr/pr.go | 2 + cmd/commands/pr/statuses/statuses.go | 121 +++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 2 + internal/pr.go | 46 ++++++++++ 6 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 cmd/commands/pr/statuses/statuses.go diff --git a/cmd/commands/pr/create/create.go b/cmd/commands/pr/create/create.go index 25f5db5..78055dc 100644 --- a/cmd/commands/pr/create/create.go +++ b/cmd/commands/pr/create/create.go @@ -16,8 +16,7 @@ import ( ) var ( - Body string - Assignees []string + Body string ) func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { @@ -81,7 +80,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { return } - if body == "" { + if Body == "" { defaultBody, err = c.PrDefaultBody(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) @@ -89,6 +88,8 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { } body = defaultBody fmt.Println(body) + } else { + body = Body } fmt.Println(aurora.Bold(aurora.Green("!").String() + " Body:")) @@ -160,6 +161,5 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { }, } createCmd.Flags().StringVarP(&Body, "body", "b", "", "Supply a body.") - createCmd.Flags().StringSliceVarP(&Assignees, "assignee", "a", nil, "Assign people by their `login`") prCmd.AddCommand(createCmd) } diff --git a/cmd/commands/pr/pr.go b/cmd/commands/pr/pr.go index d42b1d4..b5d57e5 100644 --- a/cmd/commands/pr/pr.go +++ b/cmd/commands/pr/pr.go @@ -5,6 +5,7 @@ import ( "github.com/craftamap/bb/cmd/commands/pr/create" "github.com/craftamap/bb/cmd/commands/pr/list" + "github.com/craftamap/bb/cmd/commands/pr/statuses" "github.com/craftamap/bb/cmd/commands/pr/view" "github.com/craftamap/bb/cmd/options" ) @@ -17,6 +18,7 @@ func Add(rootCmd *cobra.Command, globalOpts *options.GlobalOptions) { list.Add(&prCommand, globalOpts) view.Add(&prCommand, globalOpts) create.Add(&prCommand, globalOpts) + statuses.Add(&prCommand, globalOpts) rootCmd.AddCommand(&prCommand) } diff --git a/cmd/commands/pr/statuses/statuses.go b/cmd/commands/pr/statuses/statuses.go new file mode 100644 index 0000000..f386d1c --- /dev/null +++ b/cmd/commands/pr/statuses/statuses.go @@ -0,0 +1,121 @@ +package statuses + +import ( + "fmt" + "strconv" + + "github.com/cli/cli/git" + "github.com/craftamap/bb/cmd/options" + bbgit "github.com/craftamap/bb/git" + "github.com/craftamap/bb/internal" + "github.com/logrusorgru/aurora" + "github.com/spf13/cobra" +) + +func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { + statusesCmd := &cobra.Command{ + Use: "statuses", + Run: func(cmd *cobra.Command, args []string) { + var id int + var err error + + c := internal.Client{ + Username: globalOpts.Username, + Password: globalOpts.Password, + } + + bbrepo, err := bbgit.GetBitbucketRepo() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + if !bbrepo.IsBitbucketOrg() { + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Are you sure this is a bitbucket repo?") + return + } + + if len(args) > 0 { + id, err = strconv.Atoi(args[0]) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + } else { + branchName, err := git.CurrentBranch() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + prs, err := c.GetPrIDBySourceBranch(bbrepo.RepoOrga, bbrepo.RepoSlug, branchName) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + if len(prs.Values) == 0 { + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Nothing on this branch") + return + } + + id = prs.Values[0].ID + } + statuses, err := c.PrStatuses(bbrepo.RepoOrga, bbrepo.RepoSlug, fmt.Sprintf("%d", id)) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + if len(statuses.Values) == 0 { + fmt.Printf("No builds/statuses found for this pull request") + } else { + var ( + allChecksSuccessful = true + successfulCount = 0 + failedCount = 0 + inProgressCount = 0 + stoppedCount = 0 + ) + + for _, status := range statuses.Values { + if status.State != "SUCCESSFUL" { + allChecksSuccessful = false + } + + switch status.State { + case "SUCCESSFUL": + successfulCount++ + case "FAILED": + failedCount++ + case "INPROGRESS": + inProgressCount++ + case "STOPPED": + stoppedCount++ + } + } + if allChecksSuccessful { + fmt.Println(aurora.Bold("All checks were successful").String()) + } + fmt.Printf("%d failed, %d successful, %d in progress and %d stopped\n", failedCount, successfulCount, inProgressCount, stoppedCount) + fmt.Println() + + for _, status := range statuses.Values { + var statusIcon string + switch status.State { + case "SUCCESSFUL": + statusIcon = aurora.Green("✓").String() + case "FAILED", "STOPPED": + statusIcon = aurora.Red("X").String() + case "INPROGRESS": + statusIcon = aurora.Yellow("⏱️").String() + } + + fmt.Printf("%s %s %s %s\n", statusIcon, aurora.BrightBlack(status.Type), status.Name, aurora.BrightBlack(status.URL)) + } + + } + + }, + } + + prCmd.AddCommand(statusesCmd) +} diff --git a/go.mod b/go.mod index 3cf1996..fcaf870 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/charmbracelet/glamour v0.2.1-0.20200724174618-1246d13c1684 github.com/cli/cli v1.1.0 github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f - github.com/ktrysmt/go-bitbucket v0.6.4 + github.com/ktrysmt/go-bitbucket v0.6.5 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/mitchellh/mapstructure v1.3.3 github.com/spance/go-callprivate v0.0.0-20151213012958-d8b9b5523668 // indirect diff --git a/go.sum b/go.sum index 9af16a3..31968c1 100644 --- a/go.sum +++ b/go.sum @@ -149,6 +149,8 @@ github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/ktrysmt/go-bitbucket v0.6.4 h1:C8dUGp0qkwncKtAnozHCbbqhptefzEd1I0sfnuy9rYQ= github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= +github.com/ktrysmt/go-bitbucket v0.6.5 h1:+M4oFRZbzKH+Jt/eY3rKDPwyA5uvjY0KSJQwePGvvzQ= +github.com/ktrysmt/go-bitbucket v0.6.5/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= diff --git a/internal/pr.go b/internal/pr.go index 06c9f14..c816b59 100644 --- a/internal/pr.go +++ b/internal/pr.go @@ -40,6 +40,29 @@ type Resource struct { Repository Repository `mapstructure:"repository"` } +type Status struct { + Type string `mapstructure:"type"` + Links map[string]interface{} `mapstructure:"links"` + UUID string `mapstructure:"uuid"` + Key string `mapstructure:"key"` + Refname string `mapstructure:"refname"` + URL string `mapstructure:"url"` + State string `mapstructure:"state"` + Name string `mapstructure:"name"` + Description string `mapstructure:"description"` + CreatedOn string `mapstructure:"created_on"` + UpdatedOn string `mapstructure:"updated_on"` +} + +type Statuses struct { + Size int `mapstructure:"size"` + Page int `mapstructure:"page"` + PageLen int `mapstructure:"pagelen"` + Next string `mapstructure:"next"` + Previous string `mapstructure:"previous"` + Values []Status `mapstructure:"values"` +} + func (c Client) PrList(repoOrga string, repoSlug string) (*ListPullRequests, error) { client := bitbucket.NewBasicAuth(c.Username, c.Password) @@ -134,6 +157,29 @@ func (c Client) PrCreate(repoOrga string, repoSlug string, sourceBranch string, return &pullRequest, nil } +func (c Client) PrStatuses(repoOrga string, repoSlug string, id string) (*Statuses, error) { + client := bitbucket.NewBasicAuth(c.Username, c.Password) + opt := &bitbucket.PullRequestsOptions{ + Owner: repoOrga, + RepoSlug: repoSlug, + ID: id, + } + + response, err := client.Repositories.PullRequests.Statuses(opt) + if err != nil { + return nil, err + } + + var statuses Statuses + err = mapstructure.Decode(response, &statuses) + if err != nil { + return nil, err + } + + return &statuses, nil + +} + func (c Client) PrDefaultBody(repoOrga string, repoSlug string, sourceBranch string, destinationBranch string) (string, error) { commits, err := c.GetCommits(repoOrga, repoSlug, sourceBranch, "", destinationBranch) if err != nil { From f101a36ce4f97e22573c028e1caa03610afa768f Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Thu, 29 Oct 2020 00:33:34 +0100 Subject: [PATCH 16/37] added logo --- .github/bb-logo.png | Bin 0 -> 25572 bytes .github/bb-logo.svg | 96 ++++++++++++++++++++++++++++++++++++++++++++ README.md | 4 +- 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 .github/bb-logo.png create mode 100644 .github/bb-logo.svg diff --git a/.github/bb-logo.png b/.github/bb-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4b757e7af70da9f8b14b358dad16531776308c6b GIT binary patch literal 25572 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 tPW#f0)hH=k<@u=R5wn#>bM0T|XV + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 9c4f3ac..d69145a 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# bb \ No newline at end of file +# bb + +bb logo From 654dc6ee091fc827f4abb3ffe3cac7f107aa4061 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Thu, 29 Oct 2020 20:32:42 +0100 Subject: [PATCH 17/37] updated logo --- .github/bb-logo.png | Bin 25572 -> 17578 bytes README.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/bb-logo.png b/.github/bb-logo.png index 4b757e7af70da9f8b14b358dad16531776308c6b..afdef69db8484927284a0a17a60edf60dc021bf4 100644 GIT binary patch literal 17578 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 tPW#f0)hH=k<@u=R5wn#>bM0T|XV +![bb logo](.github/bb-logo.png) From 12c0a682782a1c2f1820fa9a0fc8db2f44c71dd2 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Thu, 29 Oct 2020 22:24:15 +0100 Subject: [PATCH 18/37] Added command explanations --- cmd/commands/pr/create/create.go | 3 ++- cmd/commands/pr/list/list.go | 4 +++- cmd/commands/pr/pr.go | 4 +++- cmd/commands/pr/statuses/statuses.go | 4 +++- cmd/commands/pr/view/view.go | 4 +++- cmd/root.go | 5 ++++- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/cmd/commands/pr/create/create.go b/cmd/commands/pr/create/create.go index 78055dc..9e4f6f4 100644 --- a/cmd/commands/pr/create/create.go +++ b/cmd/commands/pr/create/create.go @@ -21,7 +21,8 @@ var ( func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { createCmd := &cobra.Command{ - Use: "create", + Use: "create", + Short: "Create a pull request", Run: func(cmd *cobra.Command, args []string) { var ( sourceBranch string diff --git a/cmd/commands/pr/list/list.go b/cmd/commands/pr/list/list.go index 7fadf64..1d984ab 100644 --- a/cmd/commands/pr/list/list.go +++ b/cmd/commands/pr/list/list.go @@ -12,7 +12,9 @@ import ( func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { listCmd := &cobra.Command{ - Use: "list", + Use: "list", + Short: "List and filter pull requests in this repository", + Long: "List and filter pull requests in this repository", Run: func(cmd *cobra.Command, args []string) { c := internal.Client{ Username: globalOpts.Username, diff --git a/cmd/commands/pr/pr.go b/cmd/commands/pr/pr.go index b5d57e5..3004971 100644 --- a/cmd/commands/pr/pr.go +++ b/cmd/commands/pr/pr.go @@ -12,7 +12,9 @@ import ( func Add(rootCmd *cobra.Command, globalOpts *options.GlobalOptions) { prCommand := cobra.Command{ - Use: "pr", + Use: "pr", + Long: "Work with pull requests", + Short: "Manage pull requests", } list.Add(&prCommand, globalOpts) diff --git a/cmd/commands/pr/statuses/statuses.go b/cmd/commands/pr/statuses/statuses.go index f386d1c..84aa61b 100644 --- a/cmd/commands/pr/statuses/statuses.go +++ b/cmd/commands/pr/statuses/statuses.go @@ -14,7 +14,9 @@ import ( func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { statusesCmd := &cobra.Command{ - Use: "statuses", + Use: "statuses", + Short: "Show CI status for a single pull request", + Long: "Show CI status for a single pull request", Run: func(cmd *cobra.Command, args []string) { var id int var err error diff --git a/cmd/commands/pr/view/view.go b/cmd/commands/pr/view/view.go index 469884d..ffb8ab0 100644 --- a/cmd/commands/pr/view/view.go +++ b/cmd/commands/pr/view/view.go @@ -15,7 +15,9 @@ import ( func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { viewCmd := &cobra.Command{ - Use: "view", + Use: "view", + Short: "View a pull request", + Long: "Display the title, body, and other information about a pull request.", Run: func(cmd *cobra.Command, args []string) { var id int var err error diff --git a/cmd/root.go b/cmd/root.go index 66df457..c9c8265 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -14,7 +14,10 @@ import ( var ( rootCmd = &cobra.Command{ - Use: "bb", + Use: "bb", + Short: "Bitbucket.org CLI", + Long: "Work seamlessly with Bitbucket.org from the command line.", + Example: `$ bb pr list`, PersistentPreRun: func(cmd *cobra.Command, args []string) { viper.Unmarshal(&globalOpts) }, From 5927038edad0e05b3a5dc9b9b9efa62a444f624d Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Sat, 31 Oct 2020 00:14:11 +0100 Subject: [PATCH 19/37] Added PR merged, squashed some bugs, fixed colors --- cmd/commands/pr/create/create.go | 9 ++- cmd/commands/pr/list/list.go | 28 +++++++++- cmd/commands/pr/merge/merge.go | 82 ++++++++++++++++++++++++++++ cmd/commands/pr/pr.go | 2 + cmd/commands/pr/statuses/statuses.go | 2 +- cmd/commands/pr/view/view.go | 68 ++++++++++++++++------- go.mod | 1 + go.sum | 2 + internal/pr.go | 46 +++++++++++++++- 9 files changed, 213 insertions(+), 27 deletions(-) create mode 100644 cmd/commands/pr/merge/merge.go diff --git a/cmd/commands/pr/create/create.go b/cmd/commands/pr/create/create.go index 9e4f6f4..a9fa928 100644 --- a/cmd/commands/pr/create/create.go +++ b/cmd/commands/pr/create/create.go @@ -16,7 +16,8 @@ import ( ) var ( - Body string + Body string + Force bool ) func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { @@ -150,17 +151,15 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { } response, err := c.PrCreate(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch, title, body, reviewers) - fmt.Println(targetBranch) if err != nil { fmt.Printf("%s%s%#v\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } - fmt.Printf("Take a look at your pull request here:\n") - fmt.Println(response.Links["html"].Href) - + fmt.Printf("Take a look at your pull request here: %s\n", aurora.Index(242, response.Links["html"].Href)) }, } createCmd.Flags().StringVarP(&Body, "body", "b", "", "Supply a body.") + createCmd.Flags().BoolVar(&Force, "force", false, "force creation") prCmd.AddCommand(createCmd) } diff --git a/cmd/commands/pr/list/list.go b/cmd/commands/pr/list/list.go index 1d984ab..2dd5141 100644 --- a/cmd/commands/pr/list/list.go +++ b/cmd/commands/pr/list/list.go @@ -2,12 +2,20 @@ package list import ( "fmt" + "strings" "github.com/craftamap/bb/cmd/options" bbgit "github.com/craftamap/bb/git" "github.com/craftamap/bb/internal" "github.com/logrusorgru/aurora" + "github.com/pkg/browser" "github.com/spf13/cobra" + "github.com/wbrefvem/go-bitbucket" +) + +var ( + Web bool + State string ) func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { @@ -29,7 +37,23 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Are you sure this is a bitbucket repo?") return } - prs, err := c.PrList(bbrepo.RepoOrga, bbrepo.RepoSlug) + + if Web { + repo, err := c.RepositoryGet(bbrepo.RepoOrga, bbrepo.RepoSlug) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + } + + linkWrapper := repo.Links["Html"].(*bitbucket.SubjectTypesRepositoryEvents) + link := linkWrapper.Href + "/pull-requests" + browser.OpenURL(link) + + return + } + + state := strings.ToUpper(State) + + prs, err := c.PrList(bbrepo.RepoOrga, bbrepo.RepoSlug, []string{state}) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) } @@ -42,5 +66,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { } }, } + listCmd.Flags().StringVar(&State, "state", "open", "Filter by state: {open|merged|declined|superseded}") + listCmd.Flags().BoolVar(&Web, "web", false, "view pull requests in your browser") prCmd.AddCommand(listCmd) } diff --git a/cmd/commands/pr/merge/merge.go b/cmd/commands/pr/merge/merge.go new file mode 100644 index 0000000..b6a8a24 --- /dev/null +++ b/cmd/commands/pr/merge/merge.go @@ -0,0 +1,82 @@ +package merge + +import ( + "fmt" + "strconv" + + "github.com/cli/cli/git" + "github.com/craftamap/bb/cmd/commands/pr/view" + "github.com/craftamap/bb/cmd/options" + bbgit "github.com/craftamap/bb/git" + "github.com/craftamap/bb/internal" + "github.com/logrusorgru/aurora" + "github.com/spf13/cobra" +) + +func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { + mergeCmd := &cobra.Command{ + Use: "merge", + Long: "Merge a pull request on Bitbucket.org", + Short: "Merge a pull request", + Run: func(cmd *cobra.Command, args []string) { + var id int + var err error + + c := internal.Client{ + Username: globalOpts.Username, + Password: globalOpts.Password, + } + + bbrepo, err := bbgit.GetBitbucketRepo() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + if !bbrepo.IsBitbucketOrg() { + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Are you sure this is a bitbucket repo?") + return + } + + if len(args) > 0 { + id, err = strconv.Atoi(args[0]) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + } else { + branchName, err := git.CurrentBranch() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + prs, err := c.GetPrIDBySourceBranch(bbrepo.RepoOrga, bbrepo.RepoSlug, branchName) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + if len(prs.Values) == 0 { + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Nothing on this branch") + return + } + + id = prs.Values[0].ID + } + pr, err := c.PrMerge(bbrepo.RepoOrga, bbrepo.RepoSlug, fmt.Sprintf("%d", id)) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + commits, err := c.PrCommits(bbrepo.RepoOrga, bbrepo.RepoSlug, fmt.Sprintf("%d", id)) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + view.PrintSummary(pr, commits) + + }, + } + + prCmd.AddCommand(mergeCmd) +} diff --git a/cmd/commands/pr/pr.go b/cmd/commands/pr/pr.go index 3004971..a35acf5 100644 --- a/cmd/commands/pr/pr.go +++ b/cmd/commands/pr/pr.go @@ -5,6 +5,7 @@ import ( "github.com/craftamap/bb/cmd/commands/pr/create" "github.com/craftamap/bb/cmd/commands/pr/list" + "github.com/craftamap/bb/cmd/commands/pr/merge" "github.com/craftamap/bb/cmd/commands/pr/statuses" "github.com/craftamap/bb/cmd/commands/pr/view" "github.com/craftamap/bb/cmd/options" @@ -21,6 +22,7 @@ func Add(rootCmd *cobra.Command, globalOpts *options.GlobalOptions) { view.Add(&prCommand, globalOpts) create.Add(&prCommand, globalOpts) statuses.Add(&prCommand, globalOpts) + merge.Add(&prCommand, globalOpts) rootCmd.AddCommand(&prCommand) } diff --git a/cmd/commands/pr/statuses/statuses.go b/cmd/commands/pr/statuses/statuses.go index 84aa61b..67213b9 100644 --- a/cmd/commands/pr/statuses/statuses.go +++ b/cmd/commands/pr/statuses/statuses.go @@ -111,7 +111,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { statusIcon = aurora.Yellow("⏱️").String() } - fmt.Printf("%s %s %s %s\n", statusIcon, aurora.BrightBlack(status.Type), status.Name, aurora.BrightBlack(status.URL)) + fmt.Printf("%s %s %s %s\n", statusIcon, aurora.Index(242, status.Type), status.Name, aurora.Index(242, status.URL)) } } diff --git a/cmd/commands/pr/view/view.go b/cmd/commands/pr/view/view.go index ffb8ab0..86faf54 100644 --- a/cmd/commands/pr/view/view.go +++ b/cmd/commands/pr/view/view.go @@ -10,9 +10,14 @@ import ( bbgit "github.com/craftamap/bb/git" "github.com/craftamap/bb/internal" "github.com/logrusorgru/aurora" + "github.com/pkg/browser" "github.com/spf13/cobra" ) +var ( + Web bool +) + func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { viewCmd := &cobra.Command{ Use: "view", @@ -69,33 +74,58 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } - - fmt.Println(aurora.Bold(pr.Title)) - var state string - if pr.State == "OPEN" { - state = aurora.Green("Open").String() - } else if pr.State == "DECLINED" { - state = aurora.Red("Declined").String() - } else { - state = pr.State - } - - infoText := aurora.BrightBlack(fmt.Sprintf("%s wants to merge X commits into %s from %s\n", pr.Author.Nickname, pr.Destination.Branch.Name, pr.Source.Branch.Name)) - fmt.Printf("%s • %s\n", state, infoText) - if pr.Description != "" { - out, err := glamour.Render(pr.Description, "dark") + if Web { + err := browser.OpenURL(pr.Links["html"].Href) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } - fmt.Println(out) + return } - footer := aurora.BrightBlack(fmt.Sprintf("View this pull request on Bitbucket.org: %s", pr.Links["html"].Href)).String() - fmt.Println(footer) - // fmt.Println(pr, err) + commits, err := c.PrCommits(bbrepo.RepoOrga, bbrepo.RepoSlug, fmt.Sprintf("%d", id)) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + PrintSummary(pr, commits) }, } + viewCmd.Flags().BoolVar(&Web, "web", false, "view the pull request in your browser") prCmd.AddCommand(viewCmd) } + +func PrintSummary(pr *internal.PullRequest, commits *internal.Commits) { + fmt.Println(aurora.Bold(pr.Title)) + var state string + if pr.State == "OPEN" { + state = aurora.Green("Open").String() + } else if pr.State == "DECLINED" { + state = aurora.Red("Declined").String() + } else { + state = pr.State + } + + nrOfCommits := len(commits.Values) + nrOfCommitsStr := fmt.Sprintf("%d", nrOfCommits) + if nrOfCommits == 10 { + nrOfCommitsStr = nrOfCommitsStr + "+" + } + + infoText := aurora.Index(242, fmt.Sprintf("%s wants to merge %s commits into %s from %s\n", pr.Author.Nickname, nrOfCommitsStr, pr.Destination.Branch.Name, pr.Source.Branch.Name)) + fmt.Printf("%s • %s\n", state, infoText) + if pr.Description != "" { + out, err := glamour.Render(pr.Description, "dark") + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + fmt.Println(out) + } + + footer := aurora.Index(242, fmt.Sprintf("View this pull request on Bitbucket.org: %s", pr.Links["html"].Href)).String() + fmt.Println(footer) + // fmt.Println(pr, err) + +} diff --git a/go.mod b/go.mod index fcaf870..0d79689 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/ktrysmt/go-bitbucket v0.6.5 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/mitchellh/mapstructure v1.3.3 + github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 github.com/spance/go-callprivate v0.0.0-20151213012958-d8b9b5523668 // indirect github.com/spf13/cobra v1.1.0 github.com/spf13/viper v1.7.0 diff --git a/go.sum b/go.sum index 31968c1..ff5be22 100644 --- a/go.sum +++ b/go.sum @@ -209,6 +209,8 @@ github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FW github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/internal/pr.go b/internal/pr.go index c816b59..35c9799 100644 --- a/internal/pr.go +++ b/internal/pr.go @@ -63,12 +63,13 @@ type Statuses struct { Values []Status `mapstructure:"values"` } -func (c Client) PrList(repoOrga string, repoSlug string) (*ListPullRequests, error) { +func (c Client) PrList(repoOrga string, repoSlug string, states []string) (*ListPullRequests, error) { client := bitbucket.NewBasicAuth(c.Username, c.Password) opt := &bitbucket.PullRequestsOptions{ Owner: repoOrga, RepoSlug: repoSlug, + States: states, } response, err := client.Repositories.PullRequests.Gets(opt) @@ -193,3 +194,46 @@ func (c Client) PrDefaultBody(repoOrga string, repoSlug string, sourceBranch str return sb.String(), nil } + +func (c Client) PrCommits(repoOrga string, repoSlug string, id string) (*Commits, error) { + client := bitbucket.NewBasicAuth(c.Username, c.Password) + + opt := &bitbucket.PullRequestsOptions{ + Owner: repoOrga, + RepoSlug: repoSlug, + ID: id, + } + response, err := client.Repositories.PullRequests.Commits(opt) + if err != nil { + return nil, err + } + + var commits Commits + err = mapstructure.Decode(response, &commits) + if err != nil { + return nil, err + } + return &commits, nil +} + +func (c Client) PrMerge(repoOrga string, repoSlug string, id string) (*PullRequest, error) { + client := bitbucket.NewBasicAuth(c.Username, c.Password) + + opt := &bitbucket.PullRequestsOptions{ + Owner: repoOrga, + RepoSlug: repoSlug, + ID: id, + } + + response, err := client.Repositories.PullRequests.Merge(opt) + if err != nil { + return nil, err + } + + var pullRequest PullRequest + err = mapstructure.Decode(response, &pullRequest) + if err != nil { + return nil, err + } + return &pullRequest, nil +} From 44bce40b05cd6bbba623a2e129f2c5645b49f201 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Sat, 31 Oct 2020 00:53:02 +0100 Subject: [PATCH 20/37] Added rudamentary api command implementation --- cmd/commands/api/api.go | 73 +++++++++++++++++++++++++++++++++++++++++ cmd/root.go | 2 ++ go.mod | 1 + go.sum | 2 ++ 4 files changed, 78 insertions(+) create mode 100644 cmd/commands/api/api.go diff --git a/cmd/commands/api/api.go b/cmd/commands/api/api.go new file mode 100644 index 0000000..83d6f86 --- /dev/null +++ b/cmd/commands/api/api.go @@ -0,0 +1,73 @@ +package api + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "github.com/craftamap/bb/cmd/options" + "github.com/logrusorgru/aurora" + "github.com/spf13/cobra" + "github.com/tidwall/pretty" +) + +var ( + Method string +) + +func Add(rootCmd *cobra.Command, globalOpts *options.GlobalOptions) { + apiCmd := &cobra.Command{ + Use: "api", + Short: "Make an authenticated api.bitbucket.org request to the rest 2.0 api", + Long: "", + Run: func(cmd *cobra.Command, args []string) { + client := http.Client{} + + url := "" + if len(args) > 0 { + url = args[0] + } + url = "https://api.bitbucket.org/2.0/" + url + + reqBody := "" + if len(args) == 2 { + reqBody = args[1] + } + + req, err := http.NewRequest(Method, url, bytes.NewBufferString(reqBody)) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + req.SetBasicAuth(globalOpts.Username, globalOpts.Password) + + response, err := client.Do(req) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + defer response.Body.Close() + + resBody, err := ioutil.ReadAll(response.Body) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + if response.StatusCode <= 200 || response.StatusCode > 299 { + fmt.Printf("%s%s%d\n", aurora.Yellow(":: "), aurora.Bold("Status Code: "), response.StatusCode) + } + + if strings.Contains(response.Header["Content-Type"][0], "json") { + fmt.Println(string(pretty.Color(pretty.Pretty(resBody), nil))) + } else { + fmt.Println(string(resBody)) + } + }, + } + + apiCmd.Flags().StringVarP(&Method, "method", "X", "GET", "The HTTP method for the request") + rootCmd.AddCommand(apiCmd) +} diff --git a/cmd/root.go b/cmd/root.go index c9c8265..94a5e3d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" + "github.com/craftamap/bb/cmd/commands/api" "github.com/craftamap/bb/cmd/commands/pr" "github.com/craftamap/bb/cmd/options" "github.com/kirsle/configdir" @@ -51,6 +52,7 @@ func init() { viper.BindPFlag("repoSlug", rootCmd.PersistentFlags().Lookup("repo-slug")) pr.Add(rootCmd, &globalOpts) + api.Add(rootCmd, &globalOpts) } func initConfig() { diff --git a/go.mod b/go.mod index 0d79689..6ccf6ca 100644 --- a/go.mod +++ b/go.mod @@ -14,5 +14,6 @@ require ( github.com/spance/go-callprivate v0.0.0-20151213012958-d8b9b5523668 // indirect github.com/spf13/cobra v1.1.0 github.com/spf13/viper v1.7.0 + github.com/tidwall/pretty v1.0.2 github.com/wbrefvem/go-bitbucket v0.0.0-20190128183802-fc08fd046abb ) diff --git a/go.sum b/go.sum index ff5be22..377fe5c 100644 --- a/go.sum +++ b/go.sum @@ -265,6 +265,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/wbrefvem/go-bitbucket v0.0.0-20190128183802-fc08fd046abb h1:KrmaSo+FHWBt1H652w/uerwzKvQqh4H7Jgyxm4hz2BQ= From 5e22a58e41507ccd81e56e93f2f4537d54e5093b Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Sat, 31 Oct 2020 13:03:16 +0100 Subject: [PATCH 21/37] Added Force Option for create --- cmd/commands/pr/create/create.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cmd/commands/pr/create/create.go b/cmd/commands/pr/create/create.go index a9fa928..c9b7aef 100644 --- a/cmd/commands/pr/create/create.go +++ b/cmd/commands/pr/create/create.go @@ -54,6 +54,19 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } + if !Force { + possiblePrs, err := c.GetPrIDBySourceBranch(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch) + // Note: We want err to be set here, since we don't want an existing pull request + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + if len(possiblePrs.Values) != 0 { + id := possiblePrs.Values[0].ID + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), fmt.Sprintf("Pull request %d already exists for this branch. Use --force to ignore this.", id)) + return + } + } repo, err := c.RepositoryGet(bbrepo.RepoOrga, bbrepo.RepoSlug) if err != nil { From ddb2f9eb18787cd71cddeb7134791d04e3ff997d Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Sat, 31 Oct 2020 13:03:36 +0100 Subject: [PATCH 22/37] Added Headers option for api --- cmd/commands/api/api.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/cmd/commands/api/api.go b/cmd/commands/api/api.go index 83d6f86..65aaa5b 100644 --- a/cmd/commands/api/api.go +++ b/cmd/commands/api/api.go @@ -14,15 +14,18 @@ import ( ) var ( - Method string + Method string + Headers []string ) func Add(rootCmd *cobra.Command, globalOpts *options.GlobalOptions) { apiCmd := &cobra.Command{ - Use: "api", + Use: "api []", Short: "Make an authenticated api.bitbucket.org request to the rest 2.0 api", - Long: "", + Long: "Make an authenticated api.bitbucket.org request to the rest 2.0 api", Run: func(cmd *cobra.Command, args []string) { + fmt.Println(Headers) + client := http.Client{} url := "" @@ -43,6 +46,13 @@ func Add(rootCmd *cobra.Command, globalOpts *options.GlobalOptions) { } req.SetBasicAuth(globalOpts.Username, globalOpts.Password) + for _, header := range Headers { + splitted := strings.SplitN(header, "=", 2) + if len(splitted) == 2 { + req.Header.Add(splitted[0], splitted[1]) + } + } + response, err := client.Do(req) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) @@ -69,5 +79,6 @@ func Add(rootCmd *cobra.Command, globalOpts *options.GlobalOptions) { } apiCmd.Flags().StringVarP(&Method, "method", "X", "GET", "The HTTP method for the request") + apiCmd.Flags().StringArrayVarP(&Headers, "header", "H", []string{}, "Add an additional HTTP request header") rootCmd.AddCommand(apiCmd) } From 053d18bca584e52f6b0f8b953a60ece61a8db02a Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Sat, 31 Oct 2020 15:36:37 +0100 Subject: [PATCH 23/37] support for default reviewers, general code cleanup --- cmd/commands/pr/create/create.go | 98 +++++++++++++++++++++----------- internal/pr.go | 3 +- internal/repository.go | 29 ++++++++++ 3 files changed, 95 insertions(+), 35 deletions(-) diff --git a/cmd/commands/pr/create/create.go b/cmd/commands/pr/create/create.go index c9b7aef..6e6c9ea 100644 --- a/cmd/commands/pr/create/create.go +++ b/cmd/commands/pr/create/create.go @@ -16,6 +16,7 @@ import ( ) var ( + Title string Body string Force bool ) @@ -27,13 +28,8 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { Run: func(cmd *cobra.Command, args []string) { var ( sourceBranch string - targetBranch string - title string - defaultBody string - body string - reviewers []string ) - + // Initialisation c := internal.Client{ Username: globalOpts.Username, Password: globalOpts.Password, @@ -49,25 +45,22 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { return } + // Get Current Branch sourceBranch, err = git.CurrentBranch() if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } - if !Force { - possiblePrs, err := c.GetPrIDBySourceBranch(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch) - // Note: We want err to be set here, since we don't want an existing pull request - if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) - return - } - if len(possiblePrs.Values) != 0 { - id := possiblePrs.Values[0].ID - fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), fmt.Sprintf("Pull request %d already exists for this branch. Use --force to ignore this.", id)) - return - } - } + // Prepare required data + var ( + targetBranch string + title string + body string + defaultBody string + reviewers []string + ) + // First, init default data repo, err := c.RepositoryGet(bbrepo.RepoOrga, bbrepo.RepoSlug) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) @@ -80,31 +73,67 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { return } - fmt.Printf("Creating pull request for %s into %s in %s\n", sourceBranch, targetBranch, fmt.Sprintf("%s/%s", bbrepo.RepoOrga, bbrepo.RepoSlug)) - fmt.Println() + title = sourceBranch - if title == "" { - questionTitle := &survey.Input{ - Message: "Title", - Default: sourceBranch, - } - err = survey.AskOne(questionTitle, &title) + body, err = c.PrDefaultBody(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch) + defaultBody = body + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return } + + defaultReviewers, err := c.GetDefaultReviewers(bbrepo.RepoOrga, bbrepo.RepoSlug) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } + for _, rev := range defaultReviewers.Values { + reviewers = append(reviewers, rev.UUID) + } - if Body == "" { - defaultBody, err = c.PrDefaultBody(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch) - if err != nil { - fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + // Then, check if a pr is already existing. If force is True, take that data + possiblePrs, err := c.GetPrIDBySourceBranch(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + if !Force { + if len(possiblePrs.Values) != 0 { + id := possiblePrs.Values[0].ID + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), fmt.Sprintf("Pull request %d already exists for this branch. Use --force to ignore this.", id)) return } - body = defaultBody - fmt.Println(body) } else { - body = Body + if len(possiblePrs.Values) > 0 { + existingPr := possiblePrs.Values[0] + title = existingPr.Title + body = existingPr.Description + reviewers = []string{} + for _, reviewer := range existingPr.Reviewers { + // TODO: make this memory efficient + reviewers = append(reviewers, reviewer.UUID) + } + } + } + verb := "Creating" + if Force { + verb = "Re-Creating" + } + + fmt.Printf("%s pull request for %s into %s in %s\n", verb, sourceBranch, targetBranch, fmt.Sprintf("%s/%s", bbrepo.RepoOrga, bbrepo.RepoSlug)) + fmt.Println() + + if Title == "" { + questionTitle := &survey.Input{ + Message: "Title", + Default: title, + } + err = survey.AskOne(questionTitle, &title) + } + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return } fmt.Println(aurora.Bold(aurora.Green("!").String() + " Body:")) @@ -173,6 +202,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { }, } createCmd.Flags().StringVarP(&Body, "body", "b", "", "Supply a body.") + createCmd.Flags().StringVarP(&Title, "title", "t", "", "Supply a title.") createCmd.Flags().BoolVar(&Force, "force", false, "force creation") prCmd.AddCommand(createCmd) } diff --git a/internal/pr.go b/internal/pr.go index 35c9799..05725be 100644 --- a/internal/pr.go +++ b/internal/pr.go @@ -31,6 +31,7 @@ type PullRequest struct { CommentCount int `mapstructure:"comment_count"` CreatedOn string `mapstructure:"created_on"` MergeCommit Commit `mapstructure:"merge_commit"` + Reviewers []Account `mapstructure:"reviewers"` Links map[string]Link `mapstructure:"links"` } @@ -189,7 +190,7 @@ func (c Client) PrDefaultBody(repoOrga string, repoSlug string, sourceBranch str var sb strings.Builder for _, commit := range commits.Values { - sb.WriteString("- " + commit.Message + "\n") + sb.WriteString("- " + strings.Split(commit.Message, "\n")[0] + "\n") } return sb.String(), nil diff --git a/internal/repository.go b/internal/repository.go index 8893ba4..2c4d52a 100644 --- a/internal/repository.go +++ b/internal/repository.go @@ -2,6 +2,7 @@ package internal import ( "context" + "encoding/json" "time" "github.com/mitchellh/mapstructure" @@ -28,6 +29,14 @@ type Repository struct { // Project Project `mapstructure:"project"` } +type DefaultReviewers struct { + PageLen int `json:"pagelen"` + Values []*Account `json:"values"` + Page int `json:"page"` + Size int `json:"size"` + Next string `json:"next"` +} + func (c Client) RepositoryGet(repoOrga string, repoSlug string) (*Repository, error) { client := bitbucket.NewAPIClient(bitbucket.NewConfiguration()) response, _, err := client.RepositoriesApi.RepositoriesUsernameRepoSlugGet( @@ -47,3 +56,23 @@ func (c Client) RepositoryGet(repoOrga string, repoSlug string) (*Repository, er } return &repo, nil } + +func (c Client) GetDefaultReviewers(repoOrga string, repoSlug string) (*DefaultReviewers, error) { + client := bitbucket.NewAPIClient(bitbucket.NewConfiguration()) + + response, err := client.PullrequestsApi.RepositoriesUsernameRepoSlugDefaultReviewersGet( + context.WithValue(context.Background(), bitbucket.ContextBasicAuth, bitbucket.BasicAuth{ + UserName: c.Username, + Password: c.Password, + }), repoOrga, repoSlug) + + if err != nil { + return nil, err + } + defer response.Body.Close() + + defaultReviewers := DefaultReviewers{} + json.NewDecoder(response.Body).Decode(&defaultReviewers) + + return &defaultReviewers, nil +} From ee78575ebddccb8a0eccd5dc85ddfb56216035a2 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Sat, 31 Oct 2020 18:54:23 +0100 Subject: [PATCH 24/37] Add basic downloads command --- cmd/commands/downloads/downloads.go | 21 ++++++++ cmd/commands/downloads/list/list.go | 53 +++++++++++++++++++ cmd/commands/downloads/upload/upload.go | 68 ++++++++++++++++++++++++ cmd/root.go | 6 +-- go.mod | 1 + go.sum | 2 + internal/downloads.go | 69 +++++++++++++++++++++++++ 7 files changed, 216 insertions(+), 4 deletions(-) create mode 100644 cmd/commands/downloads/downloads.go create mode 100644 cmd/commands/downloads/list/list.go create mode 100644 cmd/commands/downloads/upload/upload.go create mode 100644 internal/downloads.go diff --git a/cmd/commands/downloads/downloads.go b/cmd/commands/downloads/downloads.go new file mode 100644 index 0000000..6a95d7b --- /dev/null +++ b/cmd/commands/downloads/downloads.go @@ -0,0 +1,21 @@ +package downloads + +import ( + "github.com/craftamap/bb/cmd/commands/downloads/list" + "github.com/craftamap/bb/cmd/commands/downloads/upload" + "github.com/craftamap/bb/cmd/options" + "github.com/spf13/cobra" +) + +func Add(rootCmd *cobra.Command, globalOpts *options.GlobalOptions) { + downloadsCmd := &cobra.Command{ + Use: "downloads", + Short: "Manage repository downloads", + Long: "Manage repository downloads on Bitbucket.org", + } + + list.Add(downloadsCmd, globalOpts) + upload.Add(downloadsCmd, globalOpts) + + rootCmd.AddCommand(downloadsCmd) +} diff --git a/cmd/commands/downloads/list/list.go b/cmd/commands/downloads/list/list.go new file mode 100644 index 0000000..9440df6 --- /dev/null +++ b/cmd/commands/downloads/list/list.go @@ -0,0 +1,53 @@ +package list + +import ( + "fmt" + + "github.com/craftamap/bb/cmd/options" + bbgit "github.com/craftamap/bb/git" + "github.com/craftamap/bb/internal" + "github.com/dustin/go-humanize" + "github.com/logrusorgru/aurora" + "github.com/spf13/cobra" +) + +func Add(downloadsCmd *cobra.Command, globalOpts *options.GlobalOptions) { + listCmd := &cobra.Command{ + Use: "list", + Run: func(cmd *cobra.Command, args []string) { + c := internal.Client{ + Username: globalOpts.Username, + Password: globalOpts.Password, + } + + bbrepo, err := bbgit.GetBitbucketRepo() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + if !bbrepo.IsBitbucketOrg() { + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Are you sure this is a bitbucket repo?") + return + } + downloads, err := c.GetDownloads(bbrepo.RepoOrga, bbrepo.RepoSlug) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + for i := len(downloads.Values) - 1; i >= 0; i-- { + download := downloads.Values[i] + fmt.Printf( + "• %s - %s - %s - %s - %s\n", + download.Name, + aurora.Index(242, humanize.IBytes(uint64(download.Size))), + aurora.Index(242, download.User.DisplayName), + aurora.Index(242, fmt.Sprintf("%d Downloads", download.Downloads)), + aurora.Index(242, download.CreatedOn), + ) + } + }, + } + + downloadsCmd.AddCommand(listCmd) +} diff --git a/cmd/commands/downloads/upload/upload.go b/cmd/commands/downloads/upload/upload.go new file mode 100644 index 0000000..a0fbbb1 --- /dev/null +++ b/cmd/commands/downloads/upload/upload.go @@ -0,0 +1,68 @@ +package upload + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/craftamap/bb/cmd/options" + bbgit "github.com/craftamap/bb/git" + "github.com/craftamap/bb/internal" + "github.com/logrusorgru/aurora" + "github.com/spf13/cobra" +) + +func Add(downloadsCmd *cobra.Command, globalOpts *options.GlobalOptions) { + uploadCmd := &cobra.Command{ + Use: "upload", + Run: func(cmd *cobra.Command, args []string) { + c := internal.Client{ + Username: globalOpts.Username, + Password: globalOpts.Password, + } + + bbrepo, err := bbgit.GetBitbucketRepo() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + if !bbrepo.IsBitbucketOrg() { + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Are you sure this is a bitbucket repo?") + return + } + + if len(args) == 0 { + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "No file specified") + return + } + + fpath := args[0] + fmt.Println(fpath) + + if _, err := os.Stat(fpath); os.IsNotExist(err) { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + fmt.Printf("%s Uploading file %s\n", aurora.Green(":: "), filepath.Base(fpath)) + + // Workaround: As UploadDownload files currently, we need to recover + func() { + defer func() { + if r := recover(); r != nil { + fmt.Println(aurora.Bold("WORKAROUND"), ": Recovered", r) + } + }() + c.UploadDownload(bbrepo.RepoOrga, bbrepo.RepoSlug, fpath) + }() + + //if err != nil { + // fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + // return + //} + + fmt.Printf("%s Uploaded file %s\n", aurora.Green(":: "), filepath.Base(fpath)) + }, + } + + downloadsCmd.AddCommand(uploadCmd) +} diff --git a/cmd/root.go b/cmd/root.go index 94a5e3d..93c85fc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,6 +6,7 @@ import ( "path/filepath" "github.com/craftamap/bb/cmd/commands/api" + "github.com/craftamap/bb/cmd/commands/downloads" "github.com/craftamap/bb/cmd/commands/pr" "github.com/craftamap/bb/cmd/options" "github.com/kirsle/configdir" @@ -43,16 +44,13 @@ func init() { rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/bb)") rootCmd.PersistentFlags().StringVar(&username, "username", "", "username") rootCmd.PersistentFlags().StringVar(&password, "password", "", "app password") - rootCmd.PersistentFlags().StringVar(&repoOrga, "repo-orga", "", "repository organisation") - rootCmd.PersistentFlags().StringVar(&repoSlug, "repo-slug", "", "repository slug") viper.BindPFlag("username", rootCmd.PersistentFlags().Lookup("username")) viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup("password")) - viper.BindPFlag("repoOrga", rootCmd.PersistentFlags().Lookup("repo-orga")) - viper.BindPFlag("repoSlug", rootCmd.PersistentFlags().Lookup("repo-slug")) pr.Add(rootCmd, &globalOpts) api.Add(rootCmd, &globalOpts) + downloads.Add(rootCmd, &globalOpts) } func initConfig() { diff --git a/go.mod b/go.mod index 6ccf6ca..1884357 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/AlecAivazis/survey/v2 v2.1.1 github.com/charmbracelet/glamour v0.2.1-0.20200724174618-1246d13c1684 github.com/cli/cli v1.1.0 + github.com/dustin/go-humanize v1.0.0 github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f github.com/ktrysmt/go-bitbucket v0.6.5 github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/go.sum b/go.sum index 377fe5c..40258c3 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/enescakir/emoji v1.0.0/go.mod h1:Bt1EKuLnKDTYpLALApstIkAjdDrS/8IAgTkKp+WKFD0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= diff --git a/internal/downloads.go b/internal/downloads.go new file mode 100644 index 0000000..127fe82 --- /dev/null +++ b/internal/downloads.go @@ -0,0 +1,69 @@ +package internal + +import ( + "path/filepath" + + "github.com/ktrysmt/go-bitbucket" + "github.com/mitchellh/mapstructure" +) + +type Downloads struct { + Values []Download `mapstructure:"values"` +} + +type Download struct { + Name string `mapstructure:"name"` + Links map[string]Link `mapstructure:"links"` + Downloads int `mapstructure:"downloads"` + CreatedOn string `mapstructure:"created_on"` + User Account `mapstructure:"user"` + Type string `mapstructure:"type"` + Size int `mapstructure:"size"` +} + +func (c Client) GetDownloads(repoOrga string, repoSlug string) (*Downloads, error) { + client := bitbucket.NewBasicAuth(c.Username, c.Password) + + opt := &bitbucket.DownloadsOptions{ + Owner: repoOrga, + RepoSlug: repoSlug, + } + + res, err := client.Repositories.Downloads.List(opt) + if err != nil { + return nil, err + } + + downloads := &Downloads{} + err = mapstructure.Decode(res, downloads) + if err != nil { + return nil, err + } + + return downloads, nil +} + +func (c Client) UploadDownload(repoOrga string, repoSlug string, fpath string) (*Download, error) { + client := bitbucket.NewBasicAuth(c.Username, c.Password) + + opt := &bitbucket.DownloadsOptions{ + Owner: repoOrga, + RepoSlug: repoSlug, + FilePath: fpath, + FileName: filepath.Base(fpath), + } + + res, err := client.Repositories.Downloads.Create(opt) + + if err != nil { + return nil, err + } + + download := &Download{} + err = mapstructure.Decode(res, download) + if err != nil { + return nil, err + } + + return download, nil +} From 14643bc28e64fd85fd6daf2a6edc3e64d6d98061 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Sat, 31 Oct 2020 20:59:30 +0100 Subject: [PATCH 25/37] fixed minor bug in statuses --- cmd/commands/pr/statuses/statuses.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/commands/pr/statuses/statuses.go b/cmd/commands/pr/statuses/statuses.go index 67213b9..7e29a1e 100644 --- a/cmd/commands/pr/statuses/statuses.go +++ b/cmd/commands/pr/statuses/statuses.go @@ -68,7 +68,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { } if len(statuses.Values) == 0 { - fmt.Printf("No builds/statuses found for this pull request") + fmt.Println("No builds/statuses found for this pull request") } else { var ( allChecksSuccessful = true From 9e54891e62f2957e10b9b8cc6f34a9e9c480be6e Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Sun, 1 Nov 2020 23:30:23 +0100 Subject: [PATCH 26/37] add auth method --- cmd/commands/auth/auth.go | 16 ++++++++ cmd/commands/auth/login/login.go | 70 ++++++++++++++++++++++++++++++++ cmd/root.go | 2 + 3 files changed, 88 insertions(+) create mode 100644 cmd/commands/auth/auth.go create mode 100644 cmd/commands/auth/login/login.go diff --git a/cmd/commands/auth/auth.go b/cmd/commands/auth/auth.go new file mode 100644 index 0000000..e1b53ed --- /dev/null +++ b/cmd/commands/auth/auth.go @@ -0,0 +1,16 @@ +package auth + +import ( + "github.com/craftamap/bb/cmd/commands/auth/login" + "github.com/craftamap/bb/cmd/options" + "github.com/spf13/cobra" +) + +func Add(rootCmd *cobra.Command, globalOpts *options.GlobalOptions) { + authCmd := &cobra.Command{ + Use: "auth", + Short: "Manage bb authentification state", + } + login.Add(authCmd, globalOpts) + rootCmd.AddCommand(authCmd) +} diff --git a/cmd/commands/auth/login/login.go b/cmd/commands/auth/login/login.go new file mode 100644 index 0000000..1309ae2 --- /dev/null +++ b/cmd/commands/auth/login/login.go @@ -0,0 +1,70 @@ +package login + +import ( + "fmt" + + "github.com/AlecAivazis/survey/v2" + "github.com/craftamap/bb/cmd/options" + "github.com/logrusorgru/aurora" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func Add(authCmd *cobra.Command, globalOpts *options.GlobalOptions) { + loginCmd := &cobra.Command{ + Use: "login", + Run: func(cmd *cobra.Command, args []string) { + oldPw := viper.GetString("password") + + if oldPw != "" { + fmt.Println(aurora.Yellow("::"), aurora.Bold("Warning:"), "You are already logged in as", viper.GetString("username")) + cont := false + survey.AskOne(&survey.Confirm{Message: "Do you want to overwrite this?"}, &cont) + + if !cont { + return + } + } + + fmt.Println(aurora.Green("::"), "In order to use bb, you need to create an app password for bitbucket.org. Navigate to") + fmt.Println(aurora.Green("::"), aurora.Index(242, "https://bitbucket.org/account/settings/app-passwords/")) + fmt.Println(aurora.Green("::"), "And create an app password for your account with the required permissions.") + + answers := struct { + Username string + Password string + }{} + + err := survey.Ask([]*survey.Question{ + { + Name: "username", + Prompt: &survey.Input{ + Message: "Please enter your username:", + }, + }, + { + Name: "password", + Prompt: &survey.Password{ + Message: "Please enter the app password you just created:", + }, + }, + }, &answers) + + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + viper.Set("username", answers.Username) + viper.Set("password", answers.Password) + + err = viper.WriteConfig() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + }, + } + + authCmd.AddCommand(loginCmd) +} diff --git a/cmd/root.go b/cmd/root.go index 93c85fc..3c314f9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,6 +6,7 @@ import ( "path/filepath" "github.com/craftamap/bb/cmd/commands/api" + "github.com/craftamap/bb/cmd/commands/auth" "github.com/craftamap/bb/cmd/commands/downloads" "github.com/craftamap/bb/cmd/commands/pr" "github.com/craftamap/bb/cmd/options" @@ -51,6 +52,7 @@ func init() { pr.Add(rootCmd, &globalOpts) api.Add(rootCmd, &globalOpts) downloads.Add(rootCmd, &globalOpts) + auth.Add(rootCmd, &globalOpts) } func initConfig() { From e43d1d45a6d7b6e77c348ca298099506b1074029 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Mon, 2 Nov 2020 00:13:55 +0100 Subject: [PATCH 27/37] Auth warning is shown now --- cmd/commands/auth/login/login.go | 2 ++ cmd/root.go | 9 +++++++++ go.mod | 1 + go.sum | 2 ++ 4 files changed, 14 insertions(+) diff --git a/cmd/commands/auth/login/login.go b/cmd/commands/auth/login/login.go index 1309ae2..fba0696 100644 --- a/cmd/commands/auth/login/login.go +++ b/cmd/commands/auth/login/login.go @@ -63,6 +63,8 @@ func Add(authCmd *cobra.Command, globalOpts *options.GlobalOptions) { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return } + + fmt.Println(aurora.Green("::"), "Stored credentials successfully to", viper.ConfigFileUsed()) }, } diff --git a/cmd/root.go b/cmd/root.go index 3c314f9..3de114e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -11,6 +11,7 @@ import ( "github.com/craftamap/bb/cmd/commands/pr" "github.com/craftamap/bb/cmd/options" "github.com/kirsle/configdir" + "github.com/logrusorgru/aurora" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -23,6 +24,14 @@ var ( Example: `$ bb pr list`, PersistentPreRun: func(cmd *cobra.Command, args []string) { viper.Unmarshal(&globalOpts) + + if cmd.Name() != "login" { + if globalOpts.Password == "" { + fmt.Println(aurora.Yellow("::"), aurora.Bold("Warning:"), "Look's like you have not set up bb yet.") + fmt.Println(aurora.Yellow("::"), aurora.Bold("Warning:"), "Run", aurora.BgWhite(aurora.Black(" bb auth login ")), "to set up bb.") + } + } + }, } diff --git a/go.mod b/go.mod index 1884357..b6af441 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f github.com/ktrysmt/go-bitbucket v0.6.5 github.com/logrusorgru/aurora v2.0.3+incompatible + github.com/logrusorgru/aurora/v3 v3.0.0 // indirect github.com/mitchellh/mapstructure v1.3.3 github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 github.com/spance/go-callprivate v0.0.0-20151213012958-d8b9b5523668 // indirect diff --git a/go.sum b/go.sum index 40258c3..58a49ee 100644 --- a/go.sum +++ b/go.sum @@ -155,6 +155,8 @@ github.com/ktrysmt/go-bitbucket v0.6.5 h1:+M4oFRZbzKH+Jt/eY3rKDPwyA5uvjY0KSJQweP github.com/ktrysmt/go-bitbucket v0.6.5/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/logrusorgru/aurora/v3 v3.0.0 h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4= +github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= From a9a77fe6857fbb076d7ebb7fc5e14fb661b4e0f7 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Mon, 2 Nov 2020 00:20:04 +0100 Subject: [PATCH 28/37] added pipeline --- .github/workflows/build.yml | 29 +++++++++++++++++++++++++++++ .github/workflows/lint.yml | 22 ++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..f1a1ef2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,29 @@ +name: build +on: [push, pull_request] + +jobs: + build: + strategy: + matrix: + go-version: [~1.13, ^1] + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + env: + GO111MODULE: "on" + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Download Go modules + run: go mod download + + - name: Build + run: go build -v ./... + + - name: Test + run: go test ./... diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..da69d9b --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,22 @@ +name: lint +on: + push: + pull_request: + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. + version: v1.30 + # Optional: golangci-lint command line arguments. + args: --issues-exit-code=0 + # Optional: working directory, useful for monorepos + # working-directory: somedir + # Optional: show only new issues if it's a pull request. The default value is `false`. + only-new-issues: true From 73321e3f65796c736f7ab55ab10ecf34c741d4bb Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Mon, 2 Nov 2020 23:27:06 +0100 Subject: [PATCH 29/37] Fix bug with default body --- internal/commits.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/commits.go b/internal/commits.go index c967aa3..9f80ef1 100644 --- a/internal/commits.go +++ b/internal/commits.go @@ -18,15 +18,15 @@ type Commits struct { Values []*Commit `mapstructure:"values"` } -func (c Client) GetCommits(repoOrga string, repoSlug string, revision string, include string, exclude string) (*Commits, error) { +func (c Client) GetCommits(repoOrga string, repoSlug string, branchOrTag string, include string, exclude string) (*Commits, error) { client := bitbucket.NewBasicAuth(c.Username, c.Password) opts := bitbucket.CommitsOptions{ - Owner: repoOrga, - RepoSlug: repoSlug, - Revision: revision, - Exclude: exclude, - Include: include, + Owner: repoOrga, + RepoSlug: repoSlug, + Branchortag: branchOrTag, + Exclude: exclude, + Include: include, } var commits Commits From 6217fab2bb3e10f7e4e7649bcf3601625a98f3e0 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Mon, 2 Nov 2020 23:41:39 +0100 Subject: [PATCH 30/37] Improved get default title and body --- cmd/commands/pr/create/create.go | 6 ++---- go.mod | 1 + go.sum | 1 + internal/pr.go | 28 +++++++++++++++++++++------- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/cmd/commands/pr/create/create.go b/cmd/commands/pr/create/create.go index 6e6c9ea..c0a1310 100644 --- a/cmd/commands/pr/create/create.go +++ b/cmd/commands/pr/create/create.go @@ -73,9 +73,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { return } - title = sourceBranch - - body, err = c.PrDefaultBody(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch) + title, body, err = c.PrDefaultTitleAndBody(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch) defaultBody = body if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) @@ -179,7 +177,7 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { // We need to re-generate the body, if the destination branch is changed // but only if the body was not modified in before - tempBody, err := c.PrDefaultBody(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch) + _, tempBody, err := c.PrDefaultTitleAndBody(bbrepo.RepoOrga, bbrepo.RepoSlug, sourceBranch, targetBranch) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) return diff --git a/go.mod b/go.mod index b6af441..a86d8c8 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/AlecAivazis/survey/v2 v2.1.1 github.com/charmbracelet/glamour v0.2.1-0.20200724174618-1246d13c1684 github.com/cli/cli v1.1.0 + github.com/coreos/etcd v3.3.13+incompatible github.com/dustin/go-humanize v1.0.0 github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f github.com/ktrysmt/go-bitbucket v0.6.5 diff --git a/go.sum b/go.sum index 58a49ee..3a0014e 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,7 @@ github.com/cli/cli v1.1.0/go.mod h1:9W9naQhz5tAJCqlvkp962EQE7jiEUuRW83oqk71yYsc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= diff --git a/internal/pr.go b/internal/pr.go index 05725be..d3f6b8a 100644 --- a/internal/pr.go +++ b/internal/pr.go @@ -182,18 +182,32 @@ func (c Client) PrStatuses(repoOrga string, repoSlug string, id string) (*Status } -func (c Client) PrDefaultBody(repoOrga string, repoSlug string, sourceBranch string, destinationBranch string) (string, error) { +func (c Client) PrDefaultTitleAndBody(repoOrga string, repoSlug string, sourceBranch string, destinationBranch string) (string, string, error) { commits, err := c.GetCommits(repoOrga, repoSlug, sourceBranch, "", destinationBranch) if err != nil { - return "", err + return "", "", err } + if len(commits.Values) == 0 { + return sourceBranch, "", nil + } else if len(commits.Values) == 1 { + commit := commits.Values[0] - var sb strings.Builder - for _, commit := range commits.Values { - sb.WriteString("- " + strings.Split(commit.Message, "\n")[0] + "\n") - } + split := strings.SplitN(commit.Message, "\n", 2) + if len(split) == 2 { + return split[0], split[1], nil + } else if len(split) == 1 { + return split[0], "", nil + } + + return sourceBranch, "", nil + } else { + var sb strings.Builder + for _, commit := range commits.Values { + sb.WriteString("- " + strings.Split(commit.Message, "\n")[0] + "\n") + } - return sb.String(), nil + return sourceBranch, sb.String(), nil + } } func (c Client) PrCommits(repoOrga string, repoSlug string, id string) (*Commits, error) { From 275b6673c83893417f85f1818008ad91ff37a560 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Tue, 3 Nov 2020 00:03:54 +0100 Subject: [PATCH 31/37] Trimming whitespace --- internal/pr.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pr.go b/internal/pr.go index d3f6b8a..b321a48 100644 --- a/internal/pr.go +++ b/internal/pr.go @@ -194,7 +194,7 @@ func (c Client) PrDefaultTitleAndBody(repoOrga string, repoSlug string, sourceBr split := strings.SplitN(commit.Message, "\n", 2) if len(split) == 2 { - return split[0], split[1], nil + return split[0], strings.TrimSpace(split[1]), nil } else if len(split) == 1 { return split[0], "", nil } From fecf043138c23ddb3b86a48bc336b794ff6b85ed Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Tue, 3 Nov 2020 00:08:52 +0100 Subject: [PATCH 32/37] removed workaround for uploads --- cmd/commands/downloads/upload/upload.go | 14 +++++--------- go.mod | 2 +- go.sum | 2 ++ 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/cmd/commands/downloads/upload/upload.go b/cmd/commands/downloads/upload/upload.go index a0fbbb1..6707ecf 100644 --- a/cmd/commands/downloads/upload/upload.go +++ b/cmd/commands/downloads/upload/upload.go @@ -45,15 +45,11 @@ func Add(downloadsCmd *cobra.Command, globalOpts *options.GlobalOptions) { } fmt.Printf("%s Uploading file %s\n", aurora.Green(":: "), filepath.Base(fpath)) - // Workaround: As UploadDownload files currently, we need to recover - func() { - defer func() { - if r := recover(); r != nil { - fmt.Println(aurora.Bold("WORKAROUND"), ": Recovered", r) - } - }() - c.UploadDownload(bbrepo.RepoOrga, bbrepo.RepoSlug, fpath) - }() + _, err = c.UploadDownload(bbrepo.RepoOrga, bbrepo.RepoSlug, fpath) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } //if err != nil { // fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) diff --git a/go.mod b/go.mod index a86d8c8..70729bf 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/coreos/etcd v3.3.13+incompatible github.com/dustin/go-humanize v1.0.0 github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f - github.com/ktrysmt/go-bitbucket v0.6.5 + github.com/ktrysmt/go-bitbucket v0.6.6 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/logrusorgru/aurora/v3 v3.0.0 // indirect github.com/mitchellh/mapstructure v1.3.3 diff --git a/go.sum b/go.sum index 3a0014e..37cdc55 100644 --- a/go.sum +++ b/go.sum @@ -154,6 +154,8 @@ github.com/ktrysmt/go-bitbucket v0.6.4 h1:C8dUGp0qkwncKtAnozHCbbqhptefzEd1I0sfnu github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= github.com/ktrysmt/go-bitbucket v0.6.5 h1:+M4oFRZbzKH+Jt/eY3rKDPwyA5uvjY0KSJQwePGvvzQ= github.com/ktrysmt/go-bitbucket v0.6.5/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= +github.com/ktrysmt/go-bitbucket v0.6.6 h1:78sCjU8xMUvNGIBCRLSrNAjtbCk6ayVSwEr9n5LLp0U= +github.com/ktrysmt/go-bitbucket v0.6.6/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora/v3 v3.0.0 h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4= From ed49ffd19f8044e863718435403082908ea91134 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Tue, 3 Nov 2020 00:26:59 +0100 Subject: [PATCH 33/37] Added Web for downloads --- cmd/commands/downloads/list/list.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cmd/commands/downloads/list/list.go b/cmd/commands/downloads/list/list.go index 9440df6..cd68745 100644 --- a/cmd/commands/downloads/list/list.go +++ b/cmd/commands/downloads/list/list.go @@ -8,9 +8,14 @@ import ( "github.com/craftamap/bb/internal" "github.com/dustin/go-humanize" "github.com/logrusorgru/aurora" + "github.com/pkg/browser" "github.com/spf13/cobra" ) +var ( + Web bool +) + func Add(downloadsCmd *cobra.Command, globalOpts *options.GlobalOptions) { listCmd := &cobra.Command{ Use: "list", @@ -29,6 +34,14 @@ func Add(downloadsCmd *cobra.Command, globalOpts *options.GlobalOptions) { fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Are you sure this is a bitbucket repo?") return } + if Web { + err := browser.OpenURL(fmt.Sprintf("https://bitbucket.org/%s/%s/downloads", bbrepo.RepoOrga, bbrepo.RepoSlug)) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + return + } downloads, err := c.GetDownloads(bbrepo.RepoOrga, bbrepo.RepoSlug) if err != nil { fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) @@ -49,5 +62,7 @@ func Add(downloadsCmd *cobra.Command, globalOpts *options.GlobalOptions) { }, } + listCmd.Flags().BoolVar(&Web, "web", false, "show the downloads in your browsers") + downloadsCmd.AddCommand(listCmd) } From 74c8a50172f62a9d0d44d9505ff202208e672a46 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Tue, 3 Nov 2020 00:44:21 +0100 Subject: [PATCH 34/37] Started download command --- cmd/commands/downloads/download/download.go | 65 +++++++++++++++++++++ cmd/commands/downloads/downloads.go | 2 + go.mod | 1 + go.sum | 3 + 4 files changed, 71 insertions(+) create mode 100644 cmd/commands/downloads/download/download.go diff --git a/cmd/commands/downloads/download/download.go b/cmd/commands/downloads/download/download.go new file mode 100644 index 0000000..3a675c5 --- /dev/null +++ b/cmd/commands/downloads/download/download.go @@ -0,0 +1,65 @@ +package download + +import ( + "fmt" + + "github.com/craftamap/bb/cmd/options" + bbgit "github.com/craftamap/bb/git" + "github.com/craftamap/bb/internal" + "github.com/logrusorgru/aurora" + "github.com/spf13/cobra" +) + +var ( + Web bool +) + +func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { + downloadCmd := &cobra.Command{ + Use: "download []", + Short: "download files from bitbucket", + Run: func(cmd *cobra.Command, args []string) { + // TODO: Get rid of this "list" workaround + c := internal.Client{ + Username: globalOpts.Username, + Password: globalOpts.Password, + } + + bbrepo, err := bbgit.GetBitbucketRepo() + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + if !bbrepo.IsBitbucketOrg() { + fmt.Printf("%s%s%s\n", aurora.Yellow(":: "), aurora.Bold("Warning: "), "Are you sure this is a bitbucket repo?") + return + } + downloads, err := c.GetDownloads(bbrepo.RepoOrga, bbrepo.RepoSlug) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + + downloadMap := downloadsToMap(downloads) + + dwnld, ok := downloadMap[args[0]] + if !ok { + fmt.Println("Zu hülfe!!") + return + } + + fmt.Println(dwnld.Links) + }, + } + + downloadCmd.Flags().BoolVar(&Web, "web", false, "view the pull request in your browser") + prCmd.AddCommand(downloadCmd) +} + +func downloadsToMap(downloads *internal.Downloads) map[string]*internal.Download { + downloadMap := map[string]*internal.Download{} + for _, dwnld := range downloads.Values { + downloadMap[dwnld.Name] = &dwnld + } + return downloadMap +} diff --git a/cmd/commands/downloads/downloads.go b/cmd/commands/downloads/downloads.go index 6a95d7b..115f15f 100644 --- a/cmd/commands/downloads/downloads.go +++ b/cmd/commands/downloads/downloads.go @@ -1,6 +1,7 @@ package downloads import ( + "github.com/craftamap/bb/cmd/commands/downloads/download" "github.com/craftamap/bb/cmd/commands/downloads/list" "github.com/craftamap/bb/cmd/commands/downloads/upload" "github.com/craftamap/bb/cmd/options" @@ -16,6 +17,7 @@ func Add(rootCmd *cobra.Command, globalOpts *options.GlobalOptions) { list.Add(downloadsCmd, globalOpts) upload.Add(downloadsCmd, globalOpts) + download.Add(downloadsCmd, globalOpts) rootCmd.AddCommand(downloadsCmd) } diff --git a/go.mod b/go.mod index 70729bf..2b0579d 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/spance/go-callprivate v0.0.0-20151213012958-d8b9b5523668 // indirect github.com/spf13/cobra v1.1.0 github.com/spf13/viper v1.7.0 + github.com/thoas/go-funk v0.7.0 // indirect github.com/tidwall/pretty v1.0.2 github.com/wbrefvem/go-bitbucket v0.0.0-20190128183802-fc08fd046abb ) diff --git a/go.sum b/go.sum index 37cdc55..e355c8e 100644 --- a/go.sum +++ b/go.sum @@ -269,9 +269,12 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/thoas/go-funk v0.7.0 h1:GmirKrs6j6zJbhJIficOsz2aAI7700KsU/5YrdHRM1Y= +github.com/thoas/go-funk v0.7.0/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= From 7a4c891572a9eda7131c5a698bd4207a5b977053 Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Thu, 5 Nov 2020 00:06:43 +0100 Subject: [PATCH 35/37] Added content to README.md --- .github/screenshot_create_pr.png | Bin 0 -> 25998 bytes README.md | 66 +++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 .github/screenshot_create_pr.png diff --git a/.github/screenshot_create_pr.png b/.github/screenshot_create_pr.png new file mode 100644 index 0000000000000000000000000000000000000000..7a3f241eeb197f0077525282fbf44d01a5c5ccd6 GIT binary patch literal 25998 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 Date: Thu, 5 Nov 2020 00:14:43 +0100 Subject: [PATCH 36/37] Added missing error checks --- cmd/commands/auth/login/login.go | 6 +++++- cmd/commands/pr/create/create.go | 6 +++++- cmd/commands/pr/list/list.go | 6 +++++- cmd/root.go | 20 +++++++++++++++----- internal/repository.go | 5 ++++- 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/cmd/commands/auth/login/login.go b/cmd/commands/auth/login/login.go index fba0696..ea1070e 100644 --- a/cmd/commands/auth/login/login.go +++ b/cmd/commands/auth/login/login.go @@ -19,7 +19,11 @@ func Add(authCmd *cobra.Command, globalOpts *options.GlobalOptions) { if oldPw != "" { fmt.Println(aurora.Yellow("::"), aurora.Bold("Warning:"), "You are already logged in as", viper.GetString("username")) cont := false - survey.AskOne(&survey.Confirm{Message: "Do you want to overwrite this?"}, &cont) + err := survey.AskOne(&survey.Confirm{Message: "Do you want to overwrite this?"}, &cont) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } if !cont { return diff --git a/cmd/commands/pr/create/create.go b/cmd/commands/pr/create/create.go index c0a1310..c253ca5 100644 --- a/cmd/commands/pr/create/create.go +++ b/cmd/commands/pr/create/create.go @@ -169,10 +169,14 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { } if doNext == "change destination branch" { - survey.AskOne(&survey.Input{ + err := survey.AskOne(&survey.Input{ Message: "type your destination branch", Default: targetBranch, }, &targetBranch) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } // We need to re-generate the body, if the destination branch is changed // but only if the body was not modified in before diff --git a/cmd/commands/pr/list/list.go b/cmd/commands/pr/list/list.go index 2dd5141..2488aac 100644 --- a/cmd/commands/pr/list/list.go +++ b/cmd/commands/pr/list/list.go @@ -46,7 +46,11 @@ func Add(prCmd *cobra.Command, globalOpts *options.GlobalOptions) { linkWrapper := repo.Links["Html"].(*bitbucket.SubjectTypesRepositoryEvents) link := linkWrapper.Href + "/pull-requests" - browser.OpenURL(link) + err = browser.OpenURL(link) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } return } diff --git a/cmd/root.go b/cmd/root.go index 3de114e..cddba9c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -23,7 +23,11 @@ var ( Long: "Work seamlessly with Bitbucket.org from the command line.", Example: `$ bb pr list`, PersistentPreRun: func(cmd *cobra.Command, args []string) { - viper.Unmarshal(&globalOpts) + err := viper.Unmarshal(&globalOpts) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } if cmd.Name() != "login" { if globalOpts.Password == "" { @@ -40,8 +44,6 @@ var ( username string password string - repoOrga string - repoSlug string ) func Execute() error { @@ -55,8 +57,16 @@ func init() { rootCmd.PersistentFlags().StringVar(&username, "username", "", "username") rootCmd.PersistentFlags().StringVar(&password, "password", "", "app password") - viper.BindPFlag("username", rootCmd.PersistentFlags().Lookup("username")) - viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup("password")) + err := viper.BindPFlag("username", rootCmd.PersistentFlags().Lookup("username")) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } + err = viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup("password")) + if err != nil { + fmt.Printf("%s%s%s\n", aurora.Red(":: "), aurora.Bold("An error occured: "), err) + return + } pr.Add(rootCmd, &globalOpts) api.Add(rootCmd, &globalOpts) diff --git a/internal/repository.go b/internal/repository.go index 2c4d52a..3550f85 100644 --- a/internal/repository.go +++ b/internal/repository.go @@ -72,7 +72,10 @@ func (c Client) GetDefaultReviewers(repoOrga string, repoSlug string) (*DefaultR defer response.Body.Close() defaultReviewers := DefaultReviewers{} - json.NewDecoder(response.Body).Decode(&defaultReviewers) + err = json.NewDecoder(response.Body).Decode(&defaultReviewers) + if err != nil { + return nil, err + } return &defaultReviewers, nil } From 5fdf6bc57cd156a202cd373e320cdf9912be4c6a Mon Sep 17 00:00:00 2001 From: Fabian Siegel Date: Thu, 5 Nov 2020 00:33:05 +0100 Subject: [PATCH 37/37] added goreleaser --- .gitignore | 1 + .goreleaser.yml | 44 ++++++++++++++++++++++++++++++++++++++++++++ go.sum | 1 + 3 files changed, 46 insertions(+) create mode 100644 .goreleaser.yml diff --git a/.gitignore b/.gitignore index a4785cf..b521bd6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ # vendor/ rootCmd.PersistentFlags bb +dist/ diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..ef0fbf7 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,44 @@ +# This is an example goreleaser.yaml file with some sane defaults. +# Make sure to check the documentation at http://goreleaser.com +before: + hooks: + # You may remove this if you don't use go modules. + - go mod download + # you may remove this if you don't need go generate + - go generate ./... +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin +archives: + - replacements: + darwin: Darwin + linux: Linux + windows: Windows + 386: i386 + amd64: x86_64 + +nfpms: + - + vendor: craftamap + maintainer: "Fabian Siegel " + description: "inoffical Bitbucket.org command line tool" + license: MIT + formats: + - deb + - rpm + bindir: /usr/bin + +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ .Tag }}-next" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' diff --git a/go.sum b/go.sum index e355c8e..b96a8d4 100644 --- a/go.sum +++ b/go.sum @@ -129,6 +129,7 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/henvic/httpretty v0.0.6/go.mod h1:X38wLjWXHkXT7r2+uK8LjCMne9rsuNaBLJ+5cU2/Pmo= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=