├── .circleci └── config.yml ├── .gitignore ├── .goreleaser.yml ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── Makefile ├── appveyor.yml ├── circle.yml ├── cmd ├── add-service.go ├── browse.go ├── browse_commits.go ├── browse_issues.go ├── browse_milestones.go ├── browse_projects.go ├── browse_pullrequests.go ├── browse_test.go ├── browse_wikis.go ├── create.go ├── create_pullrequest.go ├── create_release.go ├── ibrowse.go ├── init.go ├── list.go ├── list_issues.go ├── list_pullrequests.go ├── pull-request.go ├── release.go ├── root.go ├── selfupdate.go ├── util.go └── version.go ├── finder └── finder.go ├── git ├── branch.go ├── config.go ├── remote.go └── remote_test.go ├── hlblib ├── cmd_context.go ├── config.go ├── config_test.go ├── utils.go └── version.go ├── main.go └── readme.md /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | docker: 3 | - image: circleci/golang:1.11 4 | working_directory: /go/src/github.com/mpppk/hlb 5 | 6 | version: 2 7 | jobs: 8 | test: 9 | <<: *defaults 10 | steps: 11 | - checkout 12 | - restore_cache: 13 | key: vendor-{{ checksum "Gopkg.lock" }} 14 | 15 | # specify any bash command here prefixed with `run: ` 16 | - run: make setup 17 | - run: make build 18 | - run: make test 19 | - run: make codecov 20 | - save_cache: 21 | key: vendor-{{ checksum "Gopkg.lock" }} 22 | paths: 23 | - vendor 24 | release: 25 | <<: *defaults 26 | steps: 27 | - checkout 28 | - restore_cache: 29 | key: vendor-{{ checksum "Gopkg.lock" }} 30 | - run: curl -sL https://git.io/goreleaser | bash 31 | 32 | workflows: 33 | version: 2 34 | test_and_release: 35 | jobs: 36 | - test: 37 | filters: 38 | branches: 39 | only: /.*/ 40 | tags: 41 | only: /.*/ 42 | - release: 43 | filters: 44 | branches: 45 | ignore: /.*/ 46 | tags: 47 | only: /v[0-9]+(\.[0-9]+)*(-.*)*/ 48 | requires: 49 | - test 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Go template 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | .idea 15 | pkg/ 16 | vendor/ 17 | dist/ -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - env: 3 | - CGO_ENABLED=0 4 | goos: 5 | - darwin 6 | - linux 7 | - windows 8 | ignore: 9 | - goos: darwin 10 | goarch: 386 11 | archive: 12 | format_overrides: 13 | - goos: windows 14 | format: zip 15 | checksum: 16 | name_template: 'checksums.txt' 17 | snapshot: 18 | name_template: "{{ .Tag }}-next" 19 | changelog: 20 | sort: asc 21 | filters: 22 | exclude: 23 | - '^docs:' 24 | - '^test:' 25 | brew: 26 | github: 27 | owner: mpppk 28 | name: homebrew-mpppk 29 | homepage: "https://github.com/mpppk/hlb" 30 | description: "CLI that provides unified & interactive interface to multiple git repository hosting services" 31 | dependencies: 32 | - peco 33 | test: | 34 | system "#{bin}/hlb --help" -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:6e986a91d374d9f0c0ed4f5dc2912177712ef91c14f175c24211083e359f8085" 6 | name = "github.com/AlecAivazis/survey" 7 | packages = ["."] 8 | pruneopts = "" 9 | revision = "3409e8256f5cf23a9e9ee2a8816c4c5da78a181d" 10 | version = "v1.6.4" 11 | 12 | [[projects]] 13 | digest = "1:686d2b178b394e8470a378ec7991fcb4a78ea29518dd1a4cf65d132a8c287b73" 14 | name = "github.com/blang/semver" 15 | packages = ["."] 16 | pruneopts = "" 17 | revision = "2ee87856327ba09384cabd113bc6b5d174e9ec0f" 18 | version = "v3.5.1" 19 | 20 | [[projects]] 21 | digest = "1:a438e1e7d52dc834cf8d426c63c3280bbf99e6f8e42ceeed17d654f445b627c5" 22 | name = "github.com/briandowns/spinner" 23 | packages = ["."] 24 | pruneopts = "" 25 | revision = "dd69c579ff204842e8962816987f838e2c2c18d8" 26 | version = "1.4" 27 | 28 | [[projects]] 29 | digest = "1:e988ed0ca0d81f4d28772760c02ee95084961311291bdfefc1b04617c178b722" 30 | name = "github.com/fatih/color" 31 | packages = ["."] 32 | pruneopts = "" 33 | revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4" 34 | version = "v1.7.0" 35 | 36 | [[projects]] 37 | digest = "1:b2106f1668ea5efc1ecc480f7e922a093adb9563fd9ce58585292871f0d0f229" 38 | name = "github.com/fsnotify/fsnotify" 39 | packages = ["."] 40 | pruneopts = "" 41 | revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9" 42 | version = "v1.4.7" 43 | 44 | [[projects]] 45 | digest = "1:815d45503dceeca8ffecce0081d7edeae5e75b126107ef763d1c617154d72359" 46 | name = "github.com/golang/protobuf" 47 | packages = ["proto"] 48 | pruneopts = "" 49 | revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" 50 | version = "v1.2.0" 51 | 52 | [[projects]] 53 | digest = "1:da48a0d8983531875679a5e5e972d01e0c89be9cead711642bea836fa64eeed3" 54 | name = "github.com/google/go-github" 55 | packages = ["github"] 56 | pruneopts = "" 57 | revision = "f55b50f38167644bb7e4be03d9a2bde71d435239" 58 | version = "v18.2.0" 59 | 60 | [[projects]] 61 | digest = "1:cea4aa2038169ee558bf507d5ea02c94ca85bcca28a4c7bb99fd59b31e43a686" 62 | name = "github.com/google/go-querystring" 63 | packages = ["query"] 64 | pruneopts = "" 65 | revision = "44c6ddd0a2342c386950e880b658017258da92fc" 66 | version = "v1.0.0" 67 | 68 | [[projects]] 69 | digest = "1:1968af274d263251c0db9422810d4c3a8ca4db88daefb006db69232761f9337c" 70 | name = "github.com/hashicorp/hcl" 71 | packages = [ 72 | ".", 73 | "hcl/ast", 74 | "hcl/parser", 75 | "hcl/printer", 76 | "hcl/scanner", 77 | "hcl/strconv", 78 | "hcl/token", 79 | "json/parser", 80 | "json/scanner", 81 | "json/token", 82 | ] 83 | pruneopts = "" 84 | revision = "8cb6e5b959231cc1119e43259c4a608f9c51a241" 85 | version = "v1.0.0" 86 | 87 | [[projects]] 88 | branch = "master" 89 | digest = "1:0d37c42156531a07a84812a47c27610947b849710ffab6f62be6e98c5112c140" 90 | name = "github.com/inconshreveable/go-update" 91 | packages = [ 92 | ".", 93 | "internal/binarydist", 94 | "internal/osext", 95 | ] 96 | pruneopts = "" 97 | revision = "8152e7eb6ccf8679a64582a66b78519688d156ad" 98 | 99 | [[projects]] 100 | digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be" 101 | name = "github.com/inconshreveable/mousetrap" 102 | packages = ["."] 103 | pruneopts = "" 104 | revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" 105 | version = "v1.0" 106 | 107 | [[projects]] 108 | branch = "master" 109 | digest = "1:b43bc03bab00a9aa2fced1511fd2a36b345fb2a7f2ee33f16746a554cceedcee" 110 | name = "github.com/jbenet/go-context" 111 | packages = ["io"] 112 | pruneopts = "" 113 | revision = "d14ea06fba99483203c19d92cfcd13ebe73135f4" 114 | 115 | [[projects]] 116 | branch = "master" 117 | digest = "1:63e7368fcf6b54804076eaec26fd9cf0c4466166b272393db4b93102e1e962df" 118 | name = "github.com/kballard/go-shellquote" 119 | packages = ["."] 120 | pruneopts = "" 121 | revision = "95032a82bc518f77982ea72343cc1ade730072f0" 122 | 123 | [[projects]] 124 | digest = "1:31290d06c718829a084ab626faac0bac1e1c9c46ad4625e3da0314ffe15566fc" 125 | name = "github.com/magiconair/properties" 126 | packages = ["."] 127 | pruneopts = "" 128 | revision = "c2353362d570a7bfa228149c62842019201cfb71" 129 | version = "v1.8.0" 130 | 131 | [[projects]] 132 | digest = "1:74a81a9187cef23a4dd57d626ce2792c1452974c08cfa1ab84f193c539512950" 133 | name = "github.com/mattn/go-colorable" 134 | packages = ["."] 135 | pruneopts = "" 136 | revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" 137 | version = "v0.0.9" 138 | 139 | [[projects]] 140 | digest = "1:3140e04675a6a91d2a20ea9d10bdadf6072085502e6def6768361260aee4b967" 141 | name = "github.com/mattn/go-isatty" 142 | packages = ["."] 143 | pruneopts = "" 144 | revision = "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c" 145 | version = "v0.0.4" 146 | 147 | [[projects]] 148 | branch = "master" 149 | digest = "1:8e857dc003c8ccb5bb359195279beda9a6b85babb7b351d707a5596a6d9138b9" 150 | name = "github.com/mgutz/ansi" 151 | packages = ["."] 152 | pruneopts = "" 153 | revision = "9520e82c474b0a04dd04f8a40959027271bab992" 154 | 155 | [[projects]] 156 | digest = "1:096a8a9182648da3d00ff243b88407838902b6703fc12657f76890e08d1899bf" 157 | name = "github.com/mitchellh/go-homedir" 158 | packages = ["."] 159 | pruneopts = "" 160 | revision = "ae18d6b8b3205b561c79e8e5f69bff09736185f4" 161 | version = "v1.0.0" 162 | 163 | [[projects]] 164 | digest = "1:bcc46a0fbd9e933087bef394871256b5c60269575bb661935874729c65bbbf60" 165 | name = "github.com/mitchellh/mapstructure" 166 | packages = ["."] 167 | pruneopts = "" 168 | revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe" 169 | version = "v1.1.2" 170 | 171 | [[projects]] 172 | digest = "1:b2a15525faf5cce4d016b3b3adb7a56b58bb7bfeee9810a7bd83379b0052178b" 173 | name = "github.com/mpppk/gitany" 174 | packages = [ 175 | ".", 176 | "github", 177 | "gitlab", 178 | "mock", 179 | ] 180 | pruneopts = "" 181 | revision = "76ee0a0de56238d94eeca64651573a7a31238161" 182 | version = "v0.0.4" 183 | 184 | [[projects]] 185 | digest = "1:7c4a3dfaf6a6a70d05954f9eab61be866456a9056e8eba065b7e3a4907203a92" 186 | name = "github.com/pelletier/go-toml" 187 | packages = ["."] 188 | pruneopts = "" 189 | revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194" 190 | version = "v1.2.0" 191 | 192 | [[projects]] 193 | digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca" 194 | name = "github.com/pkg/errors" 195 | packages = ["."] 196 | pruneopts = "" 197 | revision = "645ef00459ed84a119197bfb8d8205042c6df63d" 198 | version = "v0.8.0" 199 | 200 | [[projects]] 201 | digest = "1:5fd0d8d72587ce81bddb8b7fdab4f4b18b88389163561cef6eb90ad6959a72d5" 202 | name = "github.com/rhysd/go-github-selfupdate" 203 | packages = ["selfupdate"] 204 | pruneopts = "" 205 | revision = "472a014a457bf6983bd5b28bdc4e7d14f352f187" 206 | version = "v1.1.0" 207 | 208 | [[projects]] 209 | digest = "1:1ebe873a8dc99e3316c30fea2e211038c4d1fcb605eee17e00a5e91b8817925e" 210 | name = "github.com/sergi/go-diff" 211 | packages = ["diffmatchpatch"] 212 | pruneopts = "" 213 | revision = "1744e2970ca51c86172c8190fadad617561ed6e7" 214 | version = "v1.0.0" 215 | 216 | [[projects]] 217 | branch = "master" 218 | digest = "1:979a6a584efb52bf5b821571de18fba4e5c8bc4e07dbaf7dfab3320799355e17" 219 | name = "github.com/skratchdot/open-golang" 220 | packages = ["open"] 221 | pruneopts = "" 222 | revision = "75fb7ed4208cf72d323d7d02fd1a5964a7a9073c" 223 | 224 | [[projects]] 225 | digest = "1:5e970055e182321bea7a1afe6eca4219c16ef98935ad25e3886061b4803f19b0" 226 | name = "github.com/spf13/afero" 227 | packages = [ 228 | ".", 229 | "mem", 230 | ] 231 | pruneopts = "" 232 | revision = "d40851caa0d747393da1ffb28f7f9d8b4eeffebd" 233 | version = "v1.1.2" 234 | 235 | [[projects]] 236 | digest = "1:ae3493c780092be9d576a1f746ab967293ec165e8473425631f06658b6212afc" 237 | name = "github.com/spf13/cast" 238 | packages = ["."] 239 | pruneopts = "" 240 | revision = "8c9545af88b134710ab1cd196795e7f2388358d7" 241 | version = "v1.3.0" 242 | 243 | [[projects]] 244 | digest = "1:39c598f67d5d68846c05832bb351e897091edcbee4689c57d3697f68f25f928d" 245 | name = "github.com/spf13/cobra" 246 | packages = ["."] 247 | pruneopts = "" 248 | revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385" 249 | version = "v0.0.3" 250 | 251 | [[projects]] 252 | digest = "1:9ceffa4ab5f7195ecf18b3a7fff90c837a9ed5e22e66d18069e4bccfe1f52aa0" 253 | name = "github.com/spf13/jwalterweatherman" 254 | packages = ["."] 255 | pruneopts = "" 256 | revision = "4a4406e478ca629068e7768fc33f3f044173c0a6" 257 | version = "v1.0.0" 258 | 259 | [[projects]] 260 | digest = "1:cbaf13cdbfef0e4734ed8a7504f57fe893d471d62a35b982bf6fb3f036449a66" 261 | name = "github.com/spf13/pflag" 262 | packages = ["."] 263 | pruneopts = "" 264 | revision = "298182f68c66c05229eb03ac171abe6e309ee79a" 265 | version = "v1.0.3" 266 | 267 | [[projects]] 268 | digest = "1:fe56054ddb09a6dae17ec7c5d48e8b363927a881e176e50ef644f4438ce970be" 269 | name = "github.com/spf13/viper" 270 | packages = ["."] 271 | pruneopts = "" 272 | revision = "2c12c60302a5a0e62ee102ca9bc996277c2f64f5" 273 | version = "v1.2.1" 274 | 275 | [[projects]] 276 | digest = "1:b8d0bfa9d0f3e74ff3ed5d17868335ba8b8360ee6b003b2a37d817b770c62f34" 277 | name = "github.com/src-d/gcfg" 278 | packages = [ 279 | ".", 280 | "scanner", 281 | "token", 282 | "types", 283 | ] 284 | pruneopts = "" 285 | revision = "1ac3a1ac202429a54835fe8408a92880156b489d" 286 | version = "v1.4.0" 287 | 288 | [[projects]] 289 | digest = "1:9b81d5e154dd605a05fb4cdddd02139245527f59c0ff5401a3b5fc45ec675cc2" 290 | name = "github.com/tcnksm/go-gitconfig" 291 | packages = ["."] 292 | pruneopts = "" 293 | revision = "d154598bacbf4501c095a309753c5d4af66caa81" 294 | version = "v0.1.2" 295 | 296 | [[projects]] 297 | digest = "1:ed45c8f71f0756c8319946d21b965c4ab8a060fceb0441685c16ec6fc92e2e5d" 298 | name = "github.com/ulikunitz/xz" 299 | packages = [ 300 | ".", 301 | "internal/hash", 302 | "internal/xlog", 303 | "lzma", 304 | ] 305 | pruneopts = "" 306 | revision = "590df8077fbcb06ad62d7714da06c00e5dd2316d" 307 | version = "v0.5.5" 308 | 309 | [[projects]] 310 | digest = "1:37934f4a0ac2c2efa8773024395b2d1b1d63c5125845be5e6b761ed771380454" 311 | name = "github.com/xanzy/go-gitlab" 312 | packages = ["."] 313 | pruneopts = "" 314 | revision = "954568c5d9242be1fb907039d769952fa4f6bc70" 315 | version = "v0.11.7" 316 | 317 | [[projects]] 318 | digest = "1:afc0b8068986a01e2d8f449917829753a54f6bd4d1265c2b4ad9cba75560020f" 319 | name = "github.com/xanzy/ssh-agent" 320 | packages = ["."] 321 | pruneopts = "" 322 | revision = "640f0ab560aeb89d523bb6ac322b1244d5c3796c" 323 | version = "v0.2.0" 324 | 325 | [[projects]] 326 | branch = "master" 327 | digest = "1:105cfefa1ee7d09fb4fee6258fa61952608b14edb59ae4f79c6cfce5a67ffa07" 328 | name = "golang.org/x/crypto" 329 | packages = [ 330 | "curve25519", 331 | "ed25519", 332 | "ed25519/internal/edwards25519", 333 | "internal/chacha20", 334 | "internal/subtle", 335 | "poly1305", 336 | "ssh", 337 | "ssh/agent", 338 | "ssh/knownhosts", 339 | "ssh/terminal", 340 | ] 341 | pruneopts = "" 342 | revision = "e4dc69e5b2fd71dcaf8bd5d054eb936deb78d1fa" 343 | 344 | [[projects]] 345 | branch = "master" 346 | digest = "1:396b9870ba0b86126f81b9427ad7410f09198cba8d7d1300a47847967dfeb93a" 347 | name = "golang.org/x/net" 348 | packages = [ 349 | "context", 350 | "context/ctxhttp", 351 | ] 352 | pruneopts = "" 353 | revision = "03003ca0c849e57b6ea29a4bab8d3cb6e4d568fe" 354 | 355 | [[projects]] 356 | branch = "master" 357 | digest = "1:7c91cf182cb05cf5904947b0eddf9a9e0ff6cd8745876866ad612fa6cef3b968" 358 | name = "golang.org/x/oauth2" 359 | packages = [ 360 | ".", 361 | "internal", 362 | ] 363 | pruneopts = "" 364 | revision = "f42d05182288abf10faef86d16c0d07b8d40ea2d" 365 | 366 | [[projects]] 367 | branch = "master" 368 | digest = "1:618a314d57cd528ac536368372a1c876fe4e25e8e88e2d7fc6c6dc528ac069b8" 369 | name = "golang.org/x/sys" 370 | packages = [ 371 | "unix", 372 | "windows", 373 | ] 374 | pruneopts = "" 375 | revision = "66b7b1311ac80bbafcd2daeef9a5e6e2cd1e2399" 376 | 377 | [[projects]] 378 | digest = "1:af9bfca4298ef7502c52b1459df274eed401a4f5498b900e9a92d28d3d87ac5a" 379 | name = "golang.org/x/text" 380 | packages = [ 381 | "internal/gen", 382 | "internal/triegen", 383 | "internal/ucd", 384 | "transform", 385 | "unicode/cldr", 386 | "unicode/norm", 387 | ] 388 | pruneopts = "" 389 | revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" 390 | version = "v0.3.0" 391 | 392 | [[projects]] 393 | digest = "1:4fafaaaff59108104ec13b5098b8c1395bcc092deb3f85834f9e1d2423d05df5" 394 | name = "google.golang.org/appengine" 395 | packages = [ 396 | "internal", 397 | "internal/base", 398 | "internal/datastore", 399 | "internal/log", 400 | "internal/remote_api", 401 | "internal/urlfetch", 402 | "urlfetch", 403 | ] 404 | pruneopts = "" 405 | revision = "4a4468ece617fc8205e99368fa2200e9d1fad421" 406 | version = "v1.3.0" 407 | 408 | [[projects]] 409 | digest = "1:6e986a91d374d9f0c0ed4f5dc2912177712ef91c14f175c24211083e359f8085" 410 | name = "gopkg.in/AlecAivazis/survey.v1" 411 | packages = [ 412 | "core", 413 | "terminal", 414 | ] 415 | pruneopts = "" 416 | revision = "3409e8256f5cf23a9e9ee2a8816c4c5da78a181d" 417 | version = "v1.6.4" 418 | 419 | [[projects]] 420 | digest = "1:44bd2300195e4973e58af6c50b98f62b66635216d332fe6a6417b0848bb2aab4" 421 | name = "gopkg.in/src-d/go-billy.v3" 422 | packages = [ 423 | ".", 424 | "helper/chroot", 425 | "helper/polyfill", 426 | "osfs", 427 | "util", 428 | ] 429 | pruneopts = "" 430 | revision = "c329b7bc7b9d24905d2bc1b85bfa29f7ae266314" 431 | version = "v3.1.0" 432 | 433 | [[projects]] 434 | digest = "1:67d185fa2f187b57407996251f9d55d0e7552e6ee3225b157f6fc7de6c60369e" 435 | name = "gopkg.in/src-d/go-git.v4" 436 | packages = [ 437 | ".", 438 | "config", 439 | "internal/revision", 440 | "plumbing", 441 | "plumbing/cache", 442 | "plumbing/filemode", 443 | "plumbing/format/config", 444 | "plumbing/format/diff", 445 | "plumbing/format/gitignore", 446 | "plumbing/format/idxfile", 447 | "plumbing/format/index", 448 | "plumbing/format/objfile", 449 | "plumbing/format/packfile", 450 | "plumbing/format/pktline", 451 | "plumbing/object", 452 | "plumbing/protocol/packp", 453 | "plumbing/protocol/packp/capability", 454 | "plumbing/protocol/packp/sideband", 455 | "plumbing/revlist", 456 | "plumbing/storer", 457 | "plumbing/transport", 458 | "plumbing/transport/client", 459 | "plumbing/transport/file", 460 | "plumbing/transport/git", 461 | "plumbing/transport/http", 462 | "plumbing/transport/internal/common", 463 | "plumbing/transport/server", 464 | "plumbing/transport/ssh", 465 | "storage", 466 | "storage/filesystem", 467 | "storage/filesystem/internal/dotgit", 468 | "storage/memory", 469 | "utils/binary", 470 | "utils/diff", 471 | "utils/ioutil", 472 | "utils/merkletrie", 473 | "utils/merkletrie/filesystem", 474 | "utils/merkletrie/index", 475 | "utils/merkletrie/internal/frame", 476 | "utils/merkletrie/noder", 477 | ] 478 | pruneopts = "" 479 | revision = "f9879dd043f84936a1f8acb8a53b74332a7ae135" 480 | version = "v4.0.0-rc15" 481 | 482 | [[projects]] 483 | digest = "1:ceec7e96590fb8168f36df4795fefe17051d4b0c2acc7ec4e260d8138c4dafac" 484 | name = "gopkg.in/warnings.v0" 485 | packages = ["."] 486 | pruneopts = "" 487 | revision = "ec4a0fea49c7b46c2aeb0b51aac55779c607e52b" 488 | version = "v0.1.2" 489 | 490 | [[projects]] 491 | branch = "v2" 492 | digest = "1:f0620375dd1f6251d9973b5f2596228cc8042e887cd7f827e4220bc1ce8c30e2" 493 | name = "gopkg.in/yaml.v2" 494 | packages = ["."] 495 | pruneopts = "" 496 | revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" 497 | 498 | [solve-meta] 499 | analyzer-name = "dep" 500 | analyzer-version = 1 501 | input-imports = [ 502 | "github.com/AlecAivazis/survey", 503 | "github.com/blang/semver", 504 | "github.com/briandowns/spinner", 505 | "github.com/mitchellh/go-homedir", 506 | "github.com/mpppk/gitany", 507 | "github.com/mpppk/gitany/github", 508 | "github.com/mpppk/gitany/gitlab", 509 | "github.com/mpppk/gitany/mock", 510 | "github.com/pkg/errors", 511 | "github.com/rhysd/go-github-selfupdate/selfupdate", 512 | "github.com/skratchdot/open-golang/open", 513 | "github.com/spf13/cobra", 514 | "github.com/spf13/viper", 515 | "github.com/tcnksm/go-gitconfig", 516 | "gopkg.in/src-d/go-git.v4", 517 | "gopkg.in/src-d/go-git.v4/config", 518 | "gopkg.in/src-d/go-git.v4/plumbing", 519 | "gopkg.in/yaml.v2", 520 | ] 521 | solver-name = "gps-cdcl" 522 | solver-version = 1 523 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | [[constraint]] 2 | name = "github.com/AlecAivazis/survey" 3 | version = "1.4.1" 4 | 5 | [[constraint]] 6 | name = "github.com/briandowns/spinner" 7 | version = "1.0.0" 8 | 9 | [[constraint]] 10 | name = "github.com/mitchellh/go-homedir" 11 | version = "1.0.0" 12 | 13 | [[constraint]] 14 | name = "github.com/pkg/errors" 15 | version = "0.8.0" 16 | 17 | [[constraint]] 18 | branch = "master" 19 | name = "github.com/skratchdot/open-golang" 20 | 21 | [[constraint]] 22 | name = "github.com/spf13/cobra" 23 | version = "0.0.1" 24 | 25 | [[constraint]] 26 | name = "github.com/spf13/viper" 27 | version = "1.0.0" 28 | 29 | [[constraint]] 30 | name = "github.com/tcnksm/go-gitconfig" 31 | version = "0.1.2" 32 | 33 | [[constraint]] 34 | name = "gopkg.in/src-d/go-git.v4" 35 | version = "=4.0.0-rc15" 36 | 37 | [[constraint]] 38 | branch = "v2" 39 | name = "gopkg.in/yaml.v2" 40 | 41 | [[constraint]] 42 | name = "github.com/rhysd/go-github-selfupdate" 43 | version = "1.1.0" 44 | 45 | [[constraint]] 46 | name = "github.com/mpppk/gitany" 47 | version = "0.0.4" 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2017 mpppk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL = /bin/bash 2 | 3 | .PHONY: deps 4 | deps: 5 | dep ensure -v 6 | 7 | .PHONY: deps-ci 8 | deps-ci: 9 | dep ensure -v -vendor-only=true 10 | 11 | .PHONY: setup 12 | setup: 13 | go get github.com/golang/dep/cmd/dep 14 | 15 | .PHONY: lint 16 | lint: deps-ci 17 | gometalinter 18 | 19 | .PHONY: test 20 | test: deps-ci 21 | go test ./... 22 | 23 | .PHONY: coverage 24 | coverage: deps-ci 25 | go test -race -coverprofile=coverage.txt -covermode=atomic ./... 26 | 27 | .PHONY: codecov 28 | codecov: deps-ci coverage 29 | bash <(curl -s https://codecov.io/bash) 30 | 31 | .PHONY: build 32 | build: deps-ci 33 | go build 34 | 35 | .PHONY: cross-build-snapshot 36 | cross-build: deps-ci 37 | goreleaser --rm-dist --snapshot 38 | 39 | .PHONY: install 40 | install: deps-ci 41 | go install 42 | 43 | .PHONY: circleci 44 | circleci: 45 | circleci build -e GITHUB_TOKEN=$GITHUB_TOKEN -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | platform: x64 4 | 5 | clone_folder: c:\gopath\src\github.com\mpppk\hlb 6 | 7 | environment: 8 | GOPATH: c:\gopath 9 | 10 | install: 11 | - echo %PATH% 12 | - echo %GOPATH% 13 | - git submodule update --init --recursive 14 | - go version 15 | - go env 16 | - go get -v -t -d ./... 17 | 18 | build_script: 19 | - go test -v ./... 20 | 21 | notifications: 22 | - provider: Webhook 23 | url: https://webhooks.gitter.im/e/ccf9e757374cba3932ac -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | notify: 2 | webhooks: 3 | # A list of hook hashes, containing the url field 4 | # gitter hook 5 | - url: https://webhooks.gitter.im/e/a82a45f705389a21624f -------------------------------------------------------------------------------- /cmd/add-service.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/mpppk/gitany" 7 | "github.com/mpppk/hlb/hlblib" 8 | "io/ioutil" 9 | "net/url" 10 | "os" 11 | 12 | "time" 13 | 14 | "github.com/AlecAivazis/survey" 15 | "github.com/briandowns/spinner" 16 | "github.com/spf13/cobra" 17 | "github.com/spf13/viper" 18 | "gopkg.in/yaml.v2" 19 | ) 20 | 21 | // add-serviceCmd represents the add-service command 22 | var addServiceCmd = &cobra.Command{ 23 | Use: "add-service", 24 | Short: "Add service", 25 | Long: ``, 26 | Run: func(cmd *cobra.Command, args []string) { 27 | ctx := context.Background() 28 | 29 | var config hlblib.Config 30 | err := viper.Unmarshal(&config) 31 | hlblib.PanicIfErrorExist(err) 32 | 33 | if len(args) < 2 { 34 | panic("invalid args") 35 | } 36 | 37 | serviceType := args[0] 38 | serviceUrl := args[1] 39 | 40 | parsedUrl, err := url.Parse(serviceUrl) 41 | hlblib.PanicIfErrorExist(err) 42 | 43 | serviceConfig, ok := config.FindServiceConfig(parsedUrl.Host) 44 | if ok { 45 | if serviceConfig.Token != "" { 46 | msg := "token for " + parsedUrl.Host + " is already exist.\n" 47 | msg += "Are you sure to over write token?" 48 | 49 | replaceOAuthToken := false 50 | prompt := &survey.Confirm{ 51 | Message: msg, 52 | } 53 | survey.AskOne(prompt, &replaceOAuthToken, nil) 54 | 55 | if !replaceOAuthToken { 56 | os.Exit(0) 57 | } 58 | 59 | } 60 | } else { 61 | serviceConfig = &gitany.ServiceConfig{ 62 | Host: parsedUrl.Host, 63 | Type: serviceType, 64 | Token: "", 65 | Protocol: parsedUrl.Scheme, 66 | } 67 | } 68 | 69 | username, password := gitany.PromptUserAndPassword(serviceType) 70 | 71 | s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) // Build our new spinner 72 | s.Start() // Start the spinner 73 | token, err := gitany.CreateToken(ctx, serviceConfig, username, password) 74 | hlblib.PanicIfErrorExist(err) 75 | serviceConfig.Token = token 76 | s.Stop() 77 | if !ok { 78 | fmt.Println("Add new service:", parsedUrl.Host) 79 | config.Services = append(config.Services, serviceConfig) 80 | } else { 81 | fmt.Println("Update service:", parsedUrl.Host) 82 | } 83 | 84 | f, err := yaml.Marshal(config) 85 | configFilePath, err := hlblib.GetConfigFilePath() 86 | hlblib.PanicIfErrorExist(err) 87 | ioutil.WriteFile(configFilePath, f, 0666) 88 | }, 89 | } 90 | 91 | func init() { 92 | RootCmd.AddCommand(addServiceCmd) 93 | } 94 | -------------------------------------------------------------------------------- /cmd/browse.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/mpppk/hlb/hlblib" 5 | "github.com/pkg/errors" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var urlFlag bool 10 | var browseCmd = NewCmdBrowse(hlblib.NewCmdContext) 11 | 12 | func NewCmdBrowse(cmdContextFunc func() (*hlblib.CmdContext, error)) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Args: cobra.NoArgs, 15 | Use: "browse", 16 | Short: "browse repo", 17 | Long: ``, 18 | RunE: NewBrowseCmdFunc(cmdContextFunc, func(cmdContext *hlblib.CmdContext, args []string) (string, error) { 19 | url, err := cmdContext.Client.GetRepositories().GetURL(cmdContext.Remote.Owner, cmdContext.Remote.RepoName) 20 | return url, errors.Wrap(err, "failed to get repository commits URL for browse from: "+url) 21 | }), 22 | } 23 | 24 | cmd.PersistentFlags().BoolVarP(&urlFlag, "url", "u", false, 25 | "outputs the URL rather than opening the browser") 26 | return cmd 27 | } 28 | 29 | func init() { 30 | RootCmd.AddCommand(browseCmd) 31 | } 32 | -------------------------------------------------------------------------------- /cmd/browse_commits.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/mpppk/hlb/hlblib" 5 | "github.com/pkg/errors" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | func NewCmdBrowseCommits(cmdContextFunc func() (*hlblib.CmdContext, error)) *cobra.Command { 10 | cmd := &cobra.Command{ 11 | Args: cobra.NoArgs, 12 | Use: "commits", 13 | Short: "browse commits", 14 | Long: ``, 15 | RunE: NewBrowseCmdFunc(cmdContextFunc, func(cmdContext *hlblib.CmdContext, args []string) (string, error) { 16 | url, err := cmdContext.Client.GetRepositories().GetCommitsURL(cmdContext.Remote.Owner, cmdContext.Remote.RepoName) 17 | return url, errors.Wrap(err, "failed to get repository commits URL for browse from: "+url) 18 | }), 19 | } 20 | return cmd 21 | } 22 | 23 | func init() { 24 | browseCmd.AddCommand(NewCmdBrowseCommits(hlblib.NewCmdContext)) 25 | } 26 | -------------------------------------------------------------------------------- /cmd/browse_issues.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/mpppk/hlb/hlblib" 7 | "github.com/pkg/errors" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func NewCmdBrowseIssues(cmdContextFunc func() (*hlblib.CmdContext, error)) *cobra.Command { 12 | cmd := &cobra.Command{ 13 | Args: MaximumNumArgs(1), 14 | Use: "issues", 15 | Short: "browse issues", 16 | Long: ``, 17 | RunE: NewBrowseCmdFunc(cmdContextFunc, func(cmdContext *hlblib.CmdContext, args []string) (string, error) { 18 | var url string 19 | if len(args) == 0 { 20 | u, err := cmdContext.Client.GetIssues().GetIssuesURL(cmdContext.Remote.Owner, cmdContext.Remote.RepoName) 21 | return u, errors.Wrap(err, "failed to get issues URL for browse from: "+url) 22 | } else { 23 | id, _ := strconv.Atoi(args[0]) // never return err because it already checked by args validator 24 | u, err := cmdContext.Client.GetIssues().GetURL(cmdContext.Remote.Owner, cmdContext.Remote.RepoName, id) 25 | return u, errors.Wrap(err, "failed to get issue URL for browse from: "+url) 26 | } 27 | }), 28 | } 29 | return cmd 30 | } 31 | 32 | func init() { 33 | browseCmd.AddCommand(NewCmdBrowseIssues(hlblib.NewCmdContext)) 34 | } 35 | -------------------------------------------------------------------------------- /cmd/browse_milestones.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/pkg/errors" 7 | 8 | "github.com/mpppk/hlb/hlblib" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdBrowseMilestones(cmdContextFunc func() (*hlblib.CmdContext, error)) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Args: MaximumNumArgs(1), 15 | Use: "milestones", 16 | Short: "browse milestones", 17 | Long: ``, 18 | RunE: NewBrowseCmdFunc(cmdContextFunc, func(cmdContext *hlblib.CmdContext, args []string) (string, error) { 19 | if len(args) == 0 { 20 | u, err := cmdContext.Client.GetRepositories().GetMilestonesURL(cmdContext.Remote.Owner, cmdContext.Remote.RepoName) 21 | return u, errors.Wrap(err, "failed to fetch milestones URL for browse") 22 | } else { 23 | id, _ := strconv.Atoi(args[0]) 24 | u, err := cmdContext.Client.GetRepositories().GetMilestoneURL(cmdContext.Remote.Owner, cmdContext.Remote.RepoName, id) 25 | return u, errors.Wrap(err, "failed to fetch milestone URL for browse") 26 | } 27 | }), 28 | } 29 | return cmd 30 | } 31 | 32 | func init() { 33 | browseCmd.AddCommand(NewCmdBrowseMilestones(hlblib.NewCmdContext)) 34 | } 35 | -------------------------------------------------------------------------------- /cmd/browse_projects.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/pkg/errors" 7 | 8 | "github.com/mpppk/hlb/hlblib" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdBrowseProjects(cmdContextFunc func() (*hlblib.CmdContext, error)) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Args: MaximumNumArgs(1), 15 | Use: "projects", 16 | Short: "browse projects", 17 | Long: ``, 18 | RunE: NewBrowseCmdFunc(cmdContextFunc, func(cmdContext *hlblib.CmdContext, args []string) (string, error) { 19 | if len(args) == 0 { 20 | u, err := cmdContext.Client.GetProjects().GetProjectsURL(cmdContext.Remote.Owner, cmdContext.Remote.RepoName) 21 | return u, errors.Wrap(err, "failed to fetch projects URL for browse") 22 | } else { 23 | id, _ := strconv.Atoi(args[0]) 24 | u, err := cmdContext.Client.GetProjects().GetURL(cmdContext.Remote.Owner, cmdContext.Remote.RepoName, id) 25 | return u, errors.Wrap(err, "failed to fetch project URL for browse") 26 | } 27 | }), 28 | } 29 | return cmd 30 | } 31 | 32 | func init() { 33 | browseCmd.AddCommand(NewCmdBrowseProjects(hlblib.NewCmdContext)) 34 | } 35 | -------------------------------------------------------------------------------- /cmd/browse_pullrequests.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/pkg/errors" 7 | 8 | "github.com/mpppk/hlb/hlblib" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdBrowsePullRequests(cmdContextFunc func() (*hlblib.CmdContext, error)) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Args: MaximumNumArgs(1), 15 | Use: "pull-requests", 16 | Short: "browse pull-requests", 17 | Long: ``, 18 | RunE: NewBrowseCmdFunc(cmdContextFunc, func(cmdContext *hlblib.CmdContext, args []string) (string, error) { 19 | if len(args) == 0 { 20 | u, err := cmdContext.Client.GetPullRequests().GetPullRequestsURL(cmdContext.Remote.Owner, cmdContext.Remote.RepoName) 21 | return u, errors.Wrap(err, "failed to fetch pull requests URL for browse") 22 | } else { 23 | id, _ := strconv.Atoi(args[0]) 24 | u, err := cmdContext.Client.GetPullRequests().GetURL(cmdContext.Remote.Owner, cmdContext.Remote.RepoName, id) 25 | return u, errors.Wrap(err, "failed to fetch pull request URL for browse") 26 | } 27 | }), 28 | } 29 | return cmd 30 | } 31 | 32 | func init() { 33 | browseCmd.AddCommand(NewCmdBrowsePullRequests(hlblib.NewCmdContext)) 34 | } 35 | -------------------------------------------------------------------------------- /cmd/browse_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/mpppk/gitany/mock" 9 | 10 | "github.com/mpppk/gitany" 11 | "github.com/mpppk/hlb/git" 12 | 13 | "github.com/mpppk/hlb/hlblib" 14 | ) 15 | 16 | func newMockClient(serviceConfig *gitany.ServiceConfig) gitany.Client { 17 | client := mock.NewClient() 18 | client.Repositories.URL = fmt.Sprintf("%s://%s", serviceConfig.Protocol, serviceConfig.Host) 19 | return client 20 | } 21 | 22 | func newMockCmdContext() (*hlblib.CmdContext, error) { 23 | serviceConfig := &gitany.ServiceConfig{ 24 | Host: "example.com", 25 | Type: "gitlab", 26 | Token: "xxxx", 27 | Protocol: "http", 28 | } 29 | client := newMockClient(serviceConfig) 30 | return &hlblib.CmdContext{ 31 | Remote: &git.Remote{}, 32 | ServiceConfig: serviceConfig, 33 | Client: client, 34 | }, nil 35 | } 36 | 37 | func TestBrowse(t *testing.T) { 38 | cases := []struct { 39 | cmdArgs []string 40 | cmdContext func() (hlblib.CmdContext, error) 41 | expectedOutput string 42 | }{ 43 | { 44 | cmdArgs: []string{"-u"}, 45 | expectedOutput: "http://example.com\n", 46 | }, 47 | } 48 | 49 | for _, c := range cases { 50 | buf := new(bytes.Buffer) 51 | cmd := NewCmdBrowse(newMockCmdContext) 52 | cmd.SetOutput(buf) 53 | cmd.SetArgs(c.cmdArgs) 54 | if err := cmd.Execute(); err != nil { 55 | t.Errorf("failed to execute browse cmd: %s\n", err) 56 | continue 57 | } 58 | output := buf.String() 59 | if c.expectedOutput != output { 60 | t.Errorf("unexpected response: expected:%s, actual:%s", c.expectedOutput, output) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cmd/browse_wikis.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | 6 | "github.com/mpppk/hlb/hlblib" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func NewCmdBrowseWikis(cmdContextFunc func() (*hlblib.CmdContext, error)) *cobra.Command { 11 | cmd := &cobra.Command{ 12 | Args: cobra.NoArgs, 13 | Use: "wikis", 14 | Short: "browse wikis", 15 | Long: ``, 16 | RunE: NewBrowseCmdFunc(cmdContextFunc, func(cmdContext *hlblib.CmdContext, args []string) (string, error) { 17 | url, err := cmdContext.Client.GetRepositories().GetWikisURL(cmdContext.Remote.Owner, cmdContext.Remote.RepoName) 18 | return url, errors.Wrap(err, "failed to get repository wikis URL for browse from: "+url) 19 | }), 20 | } 21 | return cmd 22 | } 23 | 24 | func init() { 25 | browseCmd.AddCommand(NewCmdBrowseWikis(hlblib.NewCmdContext)) 26 | } 27 | -------------------------------------------------------------------------------- /cmd/create.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/mpppk/gitany" 9 | 10 | "path" 11 | 12 | "time" 13 | 14 | "github.com/AlecAivazis/survey" 15 | "github.com/briandowns/spinner" 16 | "github.com/mpppk/hlb/git" 17 | "github.com/mpppk/hlb/hlblib" 18 | "github.com/pkg/errors" 19 | "github.com/spf13/cobra" 20 | "github.com/spf13/viper" 21 | ) 22 | 23 | func chooseService(host string, config *hlblib.Config) (*gitany.ServiceConfig, error) { 24 | subConfig := config 25 | if host != "" { 26 | subConfig = config.FindServiceConfigs(host) 27 | } 28 | 29 | hosts := subConfig.ListServiceConfigHost() 30 | 31 | var qs = []*survey.Question{ 32 | { 33 | Name: "serviceHost", 34 | Prompt: &survey.Select{ 35 | Message: "Choose target service:", 36 | Options: hosts, 37 | }, 38 | }, 39 | } 40 | 41 | answers := struct { 42 | ServiceHost string `survey:"serviceHost"` // survey will match the question and field names 43 | }{} 44 | 45 | // perform the questions 46 | err := survey.Ask(qs, &answers) 47 | if err != nil { 48 | return nil, errors.Wrap(err, "Error occurred while the user was selecting the git service in create command") 49 | } 50 | serviceConfig, ok := config.FindServiceConfig(answers.ServiceHost) 51 | 52 | if !ok { 53 | return nil, errors.New("host name not found in config file: " + host) 54 | } 55 | 56 | return serviceConfig, nil 57 | } 58 | 59 | var createCmd = &cobra.Command{ 60 | Use: "create", 61 | Short: "Create a new public repository", 62 | Long: ``, 63 | Run: func(cmd *cobra.Command, args []string) { 64 | ctx := context.Background() 65 | var config hlblib.Config 66 | err := viper.Unmarshal(&config) 67 | hlblib.PanicIfErrorExist(errors.Wrap(err, "Error occurred when unmarshal viper config")) 68 | 69 | host := "" 70 | if len(args) > 0 { 71 | host = args[0] 72 | } 73 | 74 | subConfig := config.FindServiceConfigs(host) 75 | interactiveFlag := true 76 | if len(subConfig.Services) == 1 { 77 | interactiveFlag = false 78 | } 79 | 80 | var serviceConfig *gitany.ServiceConfig 81 | if interactiveFlag { 82 | serviceConfig, err = chooseService(host, &config) 83 | hlblib.PanicIfErrorExist(errors.Wrap(err, "Error occurred while selecting the git service in create command")) 84 | } else { 85 | serviceConfig = subConfig.Services[0] 86 | } 87 | 88 | s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) // Build our new spinner 89 | if interactiveFlag { 90 | s.Start() 91 | } 92 | 93 | client, err := gitany.NewClient(ctx, serviceConfig) 94 | hlblib.PanicIfErrorExist(errors.Wrap(err, "Error occurred when client creating in create command")) 95 | 96 | currentDirPath, err := filepath.Abs(filepath.Dir(os.Args[0])) 97 | hlblib.PanicIfErrorExist(errors.Wrap(err, "Retrieve current directory path is failed in create command")) 98 | currentDirName := path.Base(currentDirPath) 99 | 100 | newRepo := gitany.NewRepository(currentDirName) 101 | repo, _, err := client.GetRepositories().Create(ctx, "", newRepo) 102 | hlblib.PanicIfErrorExist(errors.Wrap(err, "Repository creating is failed in create command")) 103 | 104 | _, err = git.SetRemote(".", "origin", repo.GetCloneURL()) 105 | hlblib.PanicIfErrorExist(errors.Wrap(err, "Remote URL setting is failed in create command")) 106 | s.Stop() 107 | }, 108 | } 109 | 110 | func init() { 111 | RootCmd.AddCommand(createCmd) 112 | } 113 | -------------------------------------------------------------------------------- /cmd/create_pullrequest.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/exec" 7 | 8 | "github.com/mpppk/gitany" 9 | 10 | "io/ioutil" 11 | 12 | "fmt" 13 | 14 | "bufio" 15 | "bytes" 16 | "io" 17 | "regexp" 18 | "strings" 19 | 20 | "github.com/mpppk/gitany/github" 21 | "github.com/mpppk/hlb/git" 22 | "github.com/mpppk/hlb/hlblib" 23 | "github.com/spf13/cobra" 24 | gogit "gopkg.in/src-d/go-git.v4" 25 | "gopkg.in/src-d/go-git.v4/plumbing" 26 | ) 27 | 28 | const ( 29 | DEFAULT_BRANCH_NAME = "master" 30 | DEFAULT_PR_FILE_NAME = "PULLREQ_EDITMSG" 31 | DEFAULT_CS = "#" 32 | ) 33 | 34 | var baseBranch string 35 | var headBranch string 36 | var argMessage string 37 | 38 | func readTitleAndMessage(reader io.Reader, cs string) (title, body string, err error) { 39 | var titleParts, bodyParts []string 40 | 41 | r := regexp.MustCompile("\\S") 42 | scanner := bufio.NewScanner(reader) 43 | for scanner.Scan() { 44 | line := scanner.Text() 45 | if strings.HasPrefix(line, cs) { 46 | continue 47 | } 48 | 49 | if len(bodyParts) == 0 && r.MatchString(line) { 50 | titleParts = append(titleParts, line) 51 | } else { 52 | bodyParts = append(bodyParts, line) 53 | } 54 | } 55 | 56 | if err = scanner.Err(); err != nil { 57 | return 58 | } 59 | 60 | title = strings.Join(titleParts, " ") 61 | title = strings.TrimSpace(title) 62 | 63 | body = strings.Join(bodyParts, "\n") 64 | body = strings.TrimSpace(body) 65 | return 66 | } 67 | 68 | func getInitMessage(baseBranch string) string { 69 | initMsg := "" 70 | 71 | r, err := gogit.PlainOpen(".") 72 | logs, err := r.Log(&gogit.LogOptions{}) 73 | hlblib.PanicIfErrorExist(err) 74 | 75 | branches, err := r.Branches() 76 | hlblib.PanicIfErrorExist(err) 77 | var baseBranchHash plumbing.Hash 78 | branches.ForEach(func(ref *plumbing.Reference) error { 79 | if ref.Name().Short() == baseBranch { 80 | baseBranchHash = ref.Hash() 81 | } 82 | return nil 83 | }) 84 | 85 | co, err := logs.Next() 86 | co2, err2 := logs.Next() 87 | 88 | if err == nil && err2 == nil && co2.Hash == baseBranchHash { 89 | initMsg = co.Message 90 | } 91 | 92 | logs.Close() 93 | 94 | return initMsg 95 | } 96 | 97 | func editTitleAndMessage(pullreqFileName, initMsg, cs string) (title, body string, err error) { 98 | // TODO Add commit logs 99 | comments, err := github.RenderPullRequestTpl(initMsg, cs, baseBranch, headBranch, "") 100 | hlblib.PanicIfErrorExist(err) 101 | 102 | ioutil.WriteFile(pullreqFileName, []byte(comments), 0777) 103 | 104 | editorName, err := git.GetEditorName() 105 | hlblib.PanicIfErrorExist(err) 106 | 107 | c := exec.Command(editorName, pullreqFileName) 108 | vimr := regexp.MustCompile("[mg]?vi[m]$") 109 | if vimr.MatchString(editorName) { 110 | c.Args = append(c.Args, "--cmd") 111 | c.Args = append(c.Args, "set ft=gitcommit tw=0 wrap lbr") 112 | } 113 | 114 | c.Stdin = os.Stdin 115 | c.Stdout = os.Stdout 116 | c.Stderr = os.Stderr 117 | c.Run() 118 | 119 | contents, err := ioutil.ReadFile(pullreqFileName) 120 | hlblib.PanicIfErrorExist(err) 121 | 122 | err = os.Remove(pullreqFileName) 123 | hlblib.PanicIfErrorExist(err) 124 | 125 | return readTitleAndMessage(bytes.NewReader(contents), cs) 126 | } 127 | 128 | var createpullrequestCmd = &cobra.Command{ 129 | Use: "pull-request", 130 | Short: "Create pull request", 131 | Long: ``, 132 | Run: func(cmd *cobra.Command, args []string) { 133 | 134 | base, err := hlblib.NewCmdContext() 135 | hlblib.PanicIfErrorExist(err) 136 | //sw := hlblib.ClientWrapper{Base: base} 137 | 138 | newPR := &gitany.NewPullRequest{ 139 | // TODO Set head owner from config file 140 | HeadOwner: base.Remote.Owner, 141 | BaseBranch: baseBranch, 142 | } 143 | newPR.BaseOwner = base.Remote.Owner 144 | 145 | if headBranch == "" { 146 | headBranch, err = git.GetCurrentBranch(".") 147 | hlblib.PanicIfErrorExist(err) 148 | } 149 | newPR.HeadBranch = headBranch 150 | 151 | initMsg := getInitMessage(baseBranch) 152 | 153 | var title, body string 154 | if argMessage == "" { 155 | title, body, err = editTitleAndMessage(DEFAULT_PR_FILE_NAME, initMsg, DEFAULT_CS) 156 | } else { 157 | title, body, err = readTitleAndMessage(strings.NewReader(argMessage), DEFAULT_CS) 158 | } 159 | hlblib.PanicIfErrorExist(err) 160 | newPR.Title = title 161 | newPR.Body = body 162 | 163 | ctx := context.Background() 164 | pr, err := base.Client.GetPullRequests().Create(ctx, base.Remote.RepoName, newPR) 165 | 166 | hlblib.PanicIfErrorExist(err) 167 | fmt.Println(pr.GetHTMLURL()) 168 | }, 169 | } 170 | 171 | func init() { 172 | createCmd.AddCommand(createpullrequestCmd) 173 | createpullrequestCmd.PersistentFlags().StringVarP(&baseBranch, "base", "b", DEFAULT_BRANCH_NAME, "Base branch(Default is master)") 174 | createpullrequestCmd.PersistentFlags().StringVarP(&headBranch, "head", "H", "", "Head branch(Default is current branch)") 175 | createpullrequestCmd.PersistentFlags().StringVarP(&argMessage, "message", "m", "", "Pull Request title and body") 176 | } 177 | -------------------------------------------------------------------------------- /cmd/create_release.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/mpppk/gitany" 8 | 9 | "os" 10 | 11 | "github.com/mpppk/hlb/hlblib" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | const ( 16 | DEFAULT_RELEASE_FILE_NAME = "RELEASE_EDITMSG" 17 | ) 18 | 19 | var createReleaseCmd = &cobra.Command{ 20 | Use: "release", 21 | Short: "Create release page and upload files", 22 | Long: ``, 23 | Run: func(cmd *cobra.Command, args []string) { 24 | base, err := hlblib.NewCmdContext() 25 | hlblib.PanicIfErrorExist(err) 26 | 27 | if len(args) < 1 { 28 | fmt.Println("Missed argument TAG") 29 | os.Exit(1) 30 | } 31 | 32 | title, message, err := editTitleAndMessage(DEFAULT_RELEASE_FILE_NAME, "", DEFAULT_CS) 33 | 34 | newRelease := &gitany.NewRelease{ 35 | TagName: args[0], 36 | Name: title, 37 | Body: message, 38 | } 39 | 40 | ctx := context.Background() 41 | release, _, err := base.Client.GetRepositories().CreateRelease(ctx, base.Remote.Owner, base.Remote.RepoName, newRelease) 42 | 43 | hlblib.PanicIfErrorExist(err) 44 | fmt.Println(release.GetHTMLURL()) 45 | }, 46 | } 47 | 48 | func init() { 49 | createCmd.AddCommand(createReleaseCmd) 50 | } 51 | -------------------------------------------------------------------------------- /cmd/ibrowse.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "os/exec" 7 | 8 | "github.com/mpppk/gitany" 9 | 10 | "os" 11 | 12 | "strings" 13 | 14 | "github.com/mpppk/hlb/finder" 15 | "github.com/mpppk/hlb/hlblib" 16 | "github.com/skratchdot/open-golang/open" 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | func toFilterStringerFromIssues(issues []gitany.Issue) (fss []finder.FilterStringer) { 21 | for _, fi := range finder.ToFilterableIssues(issues) { 22 | fss = append(fss, finder.FilterStringer(fi)) 23 | } 24 | return fss 25 | } 26 | 27 | func toFilterStringerFromPullRequests(pulls []gitany.PullRequest) (fss []finder.FilterStringer) { 28 | for _, fp := range finder.ToFilterablePullRequests(pulls) { 29 | fss = append(fss, finder.FilterStringer(fp)) 30 | } 31 | return fss 32 | } 33 | 34 | var ibrowseCmd = &cobra.Command{ 35 | Use: "ibrowse", 36 | Short: "Browse interactive", 37 | Long: ``, 38 | Run: func(cmd *cobra.Command, args []string) { 39 | base, err := hlblib.NewCmdContext() 40 | hlblib.PanicIfErrorExist(err) 41 | 42 | var list []finder.FilterStringer 43 | 44 | repoUrl, err := base.Client.GetRepositories().GetURL(base.Remote.Owner, base.Remote.RepoName) 45 | hlblib.PanicIfErrorExist(err) 46 | issuesUrl, err := base.Client.GetIssues().GetIssuesURL(base.Remote.Owner, base.Remote.RepoName) 47 | hlblib.PanicIfErrorExist(err) 48 | pullsUrl, err := base.Client.GetPullRequests().GetPullRequestsURL(base.Remote.Owner, base.Remote.RepoName) 49 | hlblib.PanicIfErrorExist(err) 50 | commitsUrl, err := base.Client.GetRepositories().GetCommitsURL(base.Remote.Owner, base.Remote.RepoName) 51 | hlblib.PanicIfErrorExist(err) 52 | projectsUrl, err := base.Client.GetProjects().GetProjectsURL(base.Remote.Owner, base.Remote.RepoName) 53 | hlblib.PanicIfErrorExist(err) 54 | milestonesUrl, err := base.Client.GetRepositories().GetMilestonesURL(base.Remote.Owner, base.Remote.RepoName) 55 | hlblib.PanicIfErrorExist(err) 56 | wikisUrl, err := base.Client.GetRepositories().GetWikisURL(base.Remote.Owner, base.Remote.RepoName) 57 | hlblib.PanicIfErrorExist(err) 58 | 59 | list = append(list, 60 | &finder.FilterableURL{URL: repoUrl, String: "*repo"}, 61 | &finder.FilterableURL{URL: issuesUrl, String: "#issues"}, 62 | &finder.FilterableURL{URL: pullsUrl, String: "!pullrequests"}, 63 | &finder.FilterableURL{URL: projectsUrl, String: "projects"}, 64 | &finder.FilterableURL{URL: milestonesUrl, String: "%milestones"}, 65 | &finder.FilterableURL{URL: commitsUrl, String: "commits"}, 66 | &finder.FilterableURL{URL: wikisUrl, String: "wikis"}, 67 | ) 68 | 69 | mycmd := exec.Command("peco") 70 | stdin, _ := mycmd.StdinPipe() 71 | 72 | for _, fstr := range list { 73 | io.WriteString(stdin, fstr.FilterString()+"\n") 74 | } 75 | 76 | issuesChan := make(chan []finder.FilterStringer) 77 | pullsChan := make(chan []finder.FilterStringer) 78 | 79 | ctx := context.Background() 80 | go func() { 81 | issues, _, err := base.Client.GetIssues().ListByRepo( 82 | ctx, 83 | base.Remote.Owner, 84 | base.Remote.RepoName, 85 | nil, 86 | ) 87 | hlblib.PanicIfErrorExist(err) 88 | fstrs := toFilterStringerFromIssues(issues) 89 | for _, fstr := range fstrs { 90 | io.WriteString(stdin, fstr.FilterString()+"\n") 91 | } 92 | issuesChan <- fstrs 93 | }() 94 | 95 | go func() { 96 | pulls, err := base.Client.GetPullRequests().List(ctx, base.Remote.Owner, base.Remote.RepoName) 97 | hlblib.PanicIfErrorExist(err) 98 | fstrs := toFilterStringerFromPullRequests(pulls) 99 | for _, fstr := range fstrs { 100 | io.WriteString(stdin, fstr.FilterString()+"\n") 101 | } 102 | pullsChan <- fstrs 103 | }() 104 | 105 | out, err := mycmd.Output() 106 | 107 | select { 108 | case issues := <-issuesChan: 109 | list = append(list, issues...) 110 | default: 111 | close(issuesChan) 112 | } 113 | 114 | select { 115 | case pulls := <-pullsChan: 116 | list = append(list, pulls...) 117 | default: 118 | close(pullsChan) 119 | } 120 | 121 | stdin.Close() 122 | hlblib.PanicIfErrorExist(err) 123 | 124 | selectedStr := strings.TrimSpace(string(out)) 125 | selectedStr = strings.Trim(selectedStr, "\n") 126 | 127 | for _, l := range list { 128 | if l.FilterString() == selectedStr { 129 | open.Run(l.(finder.Linker).GetURL()) 130 | os.Exit(0) 131 | } 132 | } 133 | }, 134 | } 135 | 136 | func init() { 137 | RootCmd.AddCommand(ibrowseCmd) 138 | 139 | } 140 | -------------------------------------------------------------------------------- /cmd/init.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | 8 | "github.com/mpppk/gitany" 9 | "github.com/mpppk/hlb/hlblib" 10 | 11 | "github.com/spf13/cobra" 12 | "gopkg.in/yaml.v2" 13 | ) 14 | 15 | // initCmd represents the init command 16 | var initCmd = &cobra.Command{ 17 | Use: "init", 18 | Short: "Generate setting file to ~/.config/hlb", 19 | Long: ``, 20 | Run: func(cmd *cobra.Command, args []string) { 21 | configFilePath, err := hlblib.GetConfigFilePath() 22 | hlblib.PanicIfErrorExist(err) 23 | if _, err := os.Stat(configFilePath); err != nil { 24 | hosts := []*gitany.ServiceConfig{ 25 | { 26 | Host: "github.com", 27 | Type: "github", 28 | Token: "", 29 | Protocol: "https", 30 | }, 31 | { 32 | Host: "gitlab.com", 33 | Type: "gitlab", 34 | Token: "", 35 | Protocol: "https", 36 | }, 37 | } 38 | 39 | config := hlblib.Config{Services: hosts} 40 | f, err := yaml.Marshal(config) 41 | hlblib.PanicIfErrorExist(err) 42 | configFileDirPath, err := hlblib.GetConfigDirPath() 43 | err = os.MkdirAll(configFileDirPath, 0777) 44 | hlblib.PanicIfErrorExist(err) 45 | err = ioutil.WriteFile(configFilePath, f, 0666) 46 | hlblib.PanicIfErrorExist(err) 47 | } else { 48 | fmt.Println("config file already exist:", configFilePath) 49 | } 50 | }, 51 | } 52 | 53 | func init() { 54 | RootCmd.AddCommand(initCmd) 55 | } 56 | -------------------------------------------------------------------------------- /cmd/list.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mpppk/hlb/hlblib" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | // listCmd represents the list command 11 | var listCmd = &cobra.Command{ 12 | Use: "list", 13 | Short: "List issues and pull-requests", 14 | Long: ``, 15 | Run: func(cmd *cobra.Command, args []string) { 16 | // TODO: Work your own magic here 17 | fmt.Println("list called") 18 | }, 19 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 20 | base, _ := hlblib.NewCmdContext() 21 | if base.ServiceConfig.Token == "" && base.ServiceConfig.Type == "github" { 22 | addServiceCmd.Run(nil, nil) 23 | } 24 | }, 25 | } 26 | 27 | func init() { 28 | RootCmd.AddCommand(listCmd) 29 | } 30 | -------------------------------------------------------------------------------- /cmd/list_issues.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "strconv" 8 | 9 | "github.com/mpppk/hlb/hlblib" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | // listissuesCmd represents the listissues command 14 | var listissuesCmd = &cobra.Command{ 15 | Use: "issues", 16 | Short: "List issues", 17 | Long: ``, 18 | Run: func(cmd *cobra.Command, args []string) { 19 | base, err := hlblib.NewCmdContext() 20 | hlblib.PanicIfErrorExist(err) 21 | 22 | ctx := context.Background() 23 | issues, _, err := base.Client.GetIssues().ListByRepo(ctx, base.Remote.Owner, base.Remote.RepoName, nil) 24 | hlblib.PanicIfErrorExist(err) 25 | 26 | for _, issue := range issues { 27 | info := "#" + strconv.Itoa(issue.GetNumber()) + " " + issue.GetTitle() 28 | fmt.Println(info) 29 | } 30 | }, 31 | } 32 | 33 | func init() { 34 | listCmd.AddCommand(listissuesCmd) 35 | } 36 | -------------------------------------------------------------------------------- /cmd/list_pullrequests.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "strconv" 8 | 9 | "github.com/mpppk/hlb/hlblib" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | // listpullrequestsCmd represents the listpullrequests command 14 | var listpullrequestsCmd = &cobra.Command{ 15 | Use: "pull-requests", 16 | Short: "list pull-requests", 17 | Long: ``, 18 | Run: func(cmd *cobra.Command, args []string) { 19 | base, err := hlblib.NewCmdContext() 20 | hlblib.PanicIfErrorExist(err) 21 | 22 | //pulls, err := sw.GetPullRequests() 23 | ctx := context.Background() 24 | pulls, err := base.Client.GetPullRequests().List(ctx, base.Remote.Owner, base.Remote.RepoName) 25 | 26 | hlblib.PanicIfErrorExist(err) 27 | 28 | for _, pull := range pulls { 29 | info := "#" + strconv.Itoa(pull.GetNumber()) + " " + pull.GetTitle() 30 | fmt.Println(info) 31 | } 32 | }, 33 | } 34 | 35 | func init() { 36 | listCmd.AddCommand(listpullrequestsCmd) 37 | } 38 | -------------------------------------------------------------------------------- /cmd/pull-request.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/mpppk/hlb/hlblib" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var pullrequestCmd = &cobra.Command{ 9 | Use: "pull-request", 10 | Short: "Create pull-request (experimental)", 11 | Long: ``, 12 | Run: func(cmd *cobra.Command, args []string) { 13 | cprCmd, _, err := cmd.Root().Find([]string{"create", "pull-request"}) 14 | hlblib.PanicIfErrorExist(err) 15 | cprCmd.Run(cprCmd, args) 16 | }, 17 | } 18 | 19 | func init() { 20 | RootCmd.AddCommand(pullrequestCmd) 21 | } 22 | -------------------------------------------------------------------------------- /cmd/release.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/mpppk/hlb/hlblib" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var releaseCmd = &cobra.Command{ 9 | Use: "release", 10 | Short: "Create release page and upload files (experimental)", 11 | Long: ``, 12 | Run: func(cmd *cobra.Command, args []string) { 13 | crCmd, _, err := cmd.Root().Find([]string{"create", "release"}) 14 | hlblib.PanicIfErrorExist(err) 15 | crCmd.Run(crCmd, args) 16 | }, 17 | } 18 | 19 | func init() { 20 | RootCmd.AddCommand(releaseCmd) 21 | } 22 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/mpppk/hlb/git" 8 | "github.com/mpppk/hlb/hlblib" 9 | "github.com/spf13/cobra" 10 | "github.com/spf13/viper" 11 | ) 12 | 13 | var cfgFile string 14 | 15 | // RootCmd represents the base command when called without any subcommands 16 | var RootCmd = &cobra.Command{ 17 | Use: "hlb", 18 | Short: "multi git hosting service manager", 19 | Long: ``, 20 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 21 | bypassCmds := []string{"create", "version", "init", "add-service"} 22 | configFilePath, err := hlblib.GetConfigDirPath() 23 | if err != nil { 24 | hlblib.PanicIfErrorExist(err) 25 | } 26 | 27 | for _, bypassCmd := range bypassCmds { 28 | if bypassCmd == cmd.Name() { 29 | return 30 | } 31 | } 32 | 33 | var config hlblib.Config 34 | err = viper.Unmarshal(&config) 35 | hlblib.PanicIfErrorExist(err) 36 | remote, err := git.GetDefaultRemote(".") 37 | hlblib.PanicIfErrorExist(err) 38 | serviceConfig, ok := config.FindServiceConfig(remote.ServiceHost) 39 | if !ok { 40 | fmt.Println(remote.ServiceHost, "is unknown host. Please add the service configuration to config file("+configFilePath+")") 41 | os.Exit(1) 42 | } 43 | if serviceConfig.Token == "" { 44 | serviceUrl := serviceConfig.Protocol + "://" + serviceConfig.Host 45 | addServiceCmd.Run(cmd, []string{serviceConfig.Type, serviceUrl}) 46 | } 47 | }, 48 | } 49 | 50 | // Execute adds all child commands to the root command sets flags appropriately. 51 | // This is called by main.main(). It only needs to happen once to the rootCmd. 52 | func Execute() { 53 | RootCmd.SetOutput(os.Stdout) 54 | if err := RootCmd.Execute(); err != nil { 55 | RootCmd.SetOutput(os.Stderr) 56 | RootCmd.Println(err) 57 | os.Exit(-1) 58 | } 59 | } 60 | 61 | func init() { 62 | cobra.OnInitialize(initConfig) 63 | 64 | configFilePath, err := hlblib.GetConfigFilePath() 65 | hlblib.PanicIfErrorExist(err) 66 | 67 | RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is "+configFilePath+")") 68 | // Cobra also supports local flags, which will only run 69 | // when this action is called directly. 70 | RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 71 | } 72 | 73 | // initConfig reads in config file and ENV variables if set. 74 | func initConfig() { 75 | if cfgFile != "" { // enable ability to specify config file via flag 76 | viper.SetConfigFile(cfgFile) 77 | } 78 | 79 | viper.SetConfigName(".hlb") // name of config file (without extension) 80 | configFilePath, err := hlblib.GetConfigDirPath() 81 | hlblib.PanicIfErrorExist(err) 82 | 83 | viper.AddConfigPath(configFilePath) // adding home directory as first search path 84 | viper.AutomaticEnv() // read in environment variables that match 85 | 86 | // If a config file is found, read it in. 87 | if err := viper.ReadInConfig(); err != nil { 88 | initCmd.Run(nil, nil) 89 | err := viper.ReadInConfig() 90 | hlblib.PanicIfErrorExist(err) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /cmd/selfupdate.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/AlecAivazis/survey" 6 | "github.com/blang/semver" 7 | "github.com/briandowns/spinner" 8 | "github.com/mpppk/hlb/hlblib" 9 | "github.com/rhysd/go-github-selfupdate/selfupdate" 10 | "github.com/spf13/cobra" 11 | "os" 12 | "time" 13 | ) 14 | 15 | var selfupdateCmd = &cobra.Command{ 16 | Use: "selfupdate", 17 | Short: "Update hlb", 18 | Long: ``, 19 | Run: func(cmd *cobra.Command, args []string) { 20 | version := hlblib.Version 21 | latest, found, err := selfupdate.DetectLatest("mpppk/hlb") 22 | if err != nil { 23 | fmt.Println("Error occurred while detecting version:", err) 24 | return 25 | } 26 | 27 | v := semver.MustParse(version) 28 | if !found || latest.Version.LTE(v) { 29 | fmt.Println("Current version is the latest") 30 | return 31 | } 32 | 33 | msg := fmt.Sprintf("New hlb version is available. (current: v%s latest: v%s)\n" + 34 | "Do you want to update to latest version? (y/n)", version, latest.Version) 35 | 36 | confirm := false 37 | prompt := &survey.Confirm{ 38 | Message: msg, 39 | } 40 | err = survey.AskOne(prompt, &confirm, nil) 41 | if err != nil { 42 | fmt.Println("Sorry, internal error is occurred.") 43 | return 44 | } 45 | 46 | if !confirm { 47 | return 48 | } 49 | 50 | exe, err := os.Executable() 51 | if err != nil { 52 | fmt.Println("Could not locate executable path") 53 | return 54 | } 55 | 56 | s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) // Build our new spinner 57 | s.Start() 58 | 59 | if err := selfupdate.UpdateTo(latest.AssetURL, exe); err != nil { 60 | s.Stop() 61 | fmt.Println("Error occurred while updating binary:", err) 62 | return 63 | } 64 | s.Stop() 65 | fmt.Printf("Successfully updated to v%s\n", latest.Version) 66 | }, 67 | } 68 | 69 | func init() { 70 | RootCmd.AddCommand(selfupdateCmd) 71 | } 72 | -------------------------------------------------------------------------------- /cmd/util.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/mpppk/hlb/hlblib" 8 | 9 | "github.com/pkg/errors" 10 | "github.com/skratchdot/open-golang/open" 11 | 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | func MaximumNumArgs(n int) cobra.PositionalArgs { 16 | maximumArgs := cobra.MaximumNArgs(n) 17 | return func(cmd *cobra.Command, args []string) error { 18 | if err := maximumArgs(cmd, args); err != nil { 19 | return err 20 | } 21 | 22 | if len(args) == 0 { 23 | return nil 24 | } 25 | 26 | if _, err := strconv.Atoi(args[0]); err != nil { 27 | return fmt.Errorf("accepts only number arg, received %s", args[0]) 28 | } 29 | return nil 30 | } 31 | } 32 | 33 | func NewBrowseCmdFunc(cmdContextFunc hlblib.CmdContextFunc, fetchURLFunc func(cmdContext *hlblib.CmdContext, args []string) (string, error)) func(cmd *cobra.Command, args []string) error { 34 | return func(cmd *cobra.Command, args []string) error { 35 | cmdContext, err := cmdContextFunc() 36 | if err != nil { 37 | return errors.Wrap(err, "failed to get command context") 38 | } 39 | 40 | url, err := fetchURLFunc(cmdContext, args) 41 | if err != nil { 42 | return errors.Wrap(err, "failed to fetch URL for browse") 43 | } 44 | 45 | if urlFlag { 46 | cmd.Println(url) 47 | } else { 48 | if err := open.Run(url); err != nil { 49 | return errors.Wrap(err, "failed to open repository URL: "+url) 50 | } 51 | } 52 | return nil 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mpppk/hlb/hlblib" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var versionCmd = &cobra.Command{ 11 | Use: "version", 12 | Short: "Show hlb version", 13 | Long: ``, 14 | Run: func(cmd *cobra.Command, args []string) { 15 | fmt.Println("v"+hlblib.Version) 16 | }, 17 | } 18 | 19 | func init() { 20 | RootCmd.AddCommand(versionCmd) 21 | } 22 | -------------------------------------------------------------------------------- /finder/finder.go: -------------------------------------------------------------------------------- 1 | package finder 2 | 3 | import ( 4 | "github.com/mpppk/gitany" 5 | "io" 6 | "os/exec" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type FilterStringer interface { 12 | FilterString() string 13 | } 14 | 15 | type Linker interface { 16 | GetURL() string 17 | } 18 | 19 | type FilterableURL struct { 20 | String string 21 | URL string 22 | } 23 | 24 | func (f *FilterableURL) GetURL() string{ 25 | return f.URL 26 | } 27 | 28 | func (f *FilterableURL) FilterString() string{ 29 | return f.String 30 | } 31 | 32 | type FilterableIssue struct { 33 | gitany.Issue 34 | } 35 | 36 | func (f *FilterableIssue) GetURL() string{ 37 | return f.GetHTMLURL() 38 | } 39 | 40 | func (f *FilterableIssue) FilterString() string{ 41 | return "#" + strconv.Itoa(f.GetNumber()) + " " + f.GetTitle() 42 | } 43 | 44 | func ToFilterableIssues(issues []gitany.Issue) (fis []*FilterableIssue) { 45 | for _, issue := range issues { 46 | fi := &FilterableIssue{Issue: issue} 47 | fis = append(fis, fi) 48 | } 49 | return fis 50 | } 51 | 52 | type FilterablePullRequest struct { 53 | gitany.PullRequest 54 | } 55 | 56 | func (f *FilterablePullRequest) GetURL() string{ 57 | return f.GetHTMLURL() 58 | } 59 | 60 | func (f *FilterablePullRequest) FilterString() string{ 61 | return "!" + strconv.Itoa(f.GetNumber()) + " " + f.GetTitle() 62 | } 63 | 64 | func ToFilterablePullRequests(pulls []gitany.PullRequest) (fis []*FilterablePullRequest) { 65 | for _, pull := range pulls { 66 | fp := &FilterablePullRequest{PullRequest: pull} 67 | fis = append(fis, fp) 68 | } 69 | return fis 70 | } 71 | 72 | 73 | func PipeToPeco(texts []string) (string, error) { 74 | cmd := exec.Command("peco") 75 | stdin, _ := cmd.StdinPipe() 76 | io.WriteString(stdin, strings.Join(texts, "\n")) 77 | stdin.Close() 78 | out, err := cmd.Output() 79 | 80 | // If peco is exited by Esc key, err return with exit status 1 81 | if err != nil && err.Error() != "exit status 1" { 82 | return "", err 83 | } 84 | 85 | return strings.Trim(string(out), " \n"), nil 86 | } 87 | 88 | 89 | func Find(stringers []FilterStringer) ([]FilterStringer, error){ 90 | strs := []string{} 91 | 92 | for _, stringer := range stringers { 93 | strs = append(strs, stringer.FilterString()) 94 | } 95 | 96 | selectedStr, err := PipeToPeco(strs) 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | selectedStringers := []FilterStringer{} 102 | for _, stringer := range stringers { 103 | if stringer.FilterString() == selectedStr { 104 | selectedStringers = append(selectedStringers, stringer) 105 | } 106 | } 107 | return selectedStringers, nil 108 | } 109 | -------------------------------------------------------------------------------- /git/branch.go: -------------------------------------------------------------------------------- 1 | package git 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "gopkg.in/src-d/go-git.v4" 6 | "gopkg.in/src-d/go-git.v4/plumbing" 7 | ) 8 | 9 | func GetCurrentBranch(path string) (string, error) { 10 | errMsg := "Error occured in git.GetCurrentBranch: " 11 | r, err := git.PlainOpen(path) 12 | if err != nil { 13 | return "", errors.Wrap(err, errMsg) 14 | } 15 | 16 | head, err := r.Head() 17 | 18 | if !head.Name().IsBranch() { 19 | return "", errors.New(errMsg + "You are in detached branch") 20 | } 21 | 22 | if head.Type() == plumbing.InvalidReference { 23 | return "", errors.New(errMsg + "HEAD is invalid reference") 24 | } 25 | 26 | return head.Name().Short(), nil 27 | } 28 | -------------------------------------------------------------------------------- /git/config.go: -------------------------------------------------------------------------------- 1 | package git 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "github.com/tcnksm/go-gitconfig" 6 | "gopkg.in/src-d/go-git.v4" 7 | "gopkg.in/src-d/go-git.v4/config" 8 | ) 9 | 10 | const EDITOR_KEY = "core.editor" // FIXME 11 | const DEFAULT_EDITOR_NAME = "vim" // FIXME 12 | 13 | func GetConfig(repoPath string) (*config.Config, error) { 14 | errMsg := "Error occurred in git.GetConfig: " 15 | r, err := git.PlainOpen(repoPath) 16 | if err != nil { 17 | return nil, errors.Wrap(err, errMsg) 18 | } 19 | 20 | c, err := r.Config() 21 | if err != nil { 22 | return nil, errors.Wrap(err, errMsg) 23 | } 24 | 25 | return c, nil 26 | } 27 | 28 | func GetEditorName() (string, error) { 29 | localEditorName, err := gitconfig.Local(EDITOR_KEY) 30 | 31 | if err == nil && localEditorName != "" { 32 | return localEditorName, nil 33 | } 34 | 35 | globalEditorName, err := gitconfig.Global(EDITOR_KEY) 36 | if err == nil && globalEditorName != "" { 37 | return globalEditorName, nil 38 | } 39 | 40 | return DEFAULT_EDITOR_NAME, err 41 | } 42 | -------------------------------------------------------------------------------- /git/remote.go: -------------------------------------------------------------------------------- 1 | package git 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | 7 | "github.com/pkg/errors" 8 | "gopkg.in/src-d/go-git.v4" 9 | "gopkg.in/src-d/go-git.v4/config" 10 | ) 11 | 12 | type RawRemoteConfig interface { 13 | } 14 | 15 | type RawRemote interface { 16 | Config() *RawRemoteConfig 17 | } 18 | 19 | type Remote struct { 20 | URL string 21 | ServiceHost string 22 | Owner string 23 | RepoName string 24 | } 25 | 26 | func NewRemote(remoteUrl string) (*Remote, error) { 27 | ru := remoteUrl 28 | var assigned *regexp.Regexp 29 | if strings.Contains(remoteUrl, "://") { 30 | ru = strings.Split(remoteUrl, "://")[1] 31 | assigned = regexp.MustCompile(`[.+]?(.+)/(.+)/(.+)$`) 32 | } else if strings.HasPrefix(remoteUrl, "git@") { 33 | assigned = regexp.MustCompile(`git@(.+):(.+)/(.+).git`) 34 | } else { 35 | return nil, errors.New("unknown remote: " + remoteUrl) 36 | } 37 | 38 | result := assigned.FindStringSubmatch(ru) 39 | 40 | if result == nil || len(result) < 4 { 41 | return nil, errors.New("unknown remoteUrl pattern: " + remoteUrl) 42 | } 43 | hostNames := strings.Split(result[1], "@") 44 | serviceHost := hostNames[len(hostNames)-1] 45 | return &Remote{ 46 | URL: remoteUrl, 47 | ServiceHost: serviceHost, 48 | Owner: result[2], 49 | RepoName: strings.Replace(result[3], ".git", "", -1), 50 | }, nil 51 | } 52 | 53 | func GetDefaultRemote(path string) (*Remote, error) { 54 | r, err := git.PlainOpen(path) 55 | if err != nil { 56 | return nil, errors.Wrap(err, "Error occurred in git.GetDefaultRemote") 57 | } 58 | 59 | remote, err := r.Remote(git.DefaultRemoteName) 60 | if err != nil { 61 | return nil, errors.Wrap(err, "Error occurred in git.GetDefaultRemote") 62 | } 63 | return NewRemote(remote.Config().URLs[0]) // FIXME 64 | 65 | } 66 | 67 | func SetRemote(path, remoteName, remoteUrl string) (*git.Remote, error) { 68 | r, err := git.PlainOpen(path) 69 | if err != nil { 70 | return nil, errors.Wrap(err, "Error occurred when open local git repository in git.SetRemote") 71 | } 72 | 73 | remote, err := r.CreateRemote(&config.RemoteConfig{ 74 | Name: remoteName, 75 | URLs: []string{remoteUrl}, 76 | }) 77 | 78 | if err != nil { 79 | return nil, errors.Wrap(err, "Error occurred when remote creating in git.SetRemote") 80 | } 81 | 82 | _, err = r.Remotes() 83 | if err != nil { 84 | return nil, errors.Wrap(err, "Error occurred when remotes getting in git.SetRemote") 85 | } 86 | return remote, nil 87 | } 88 | -------------------------------------------------------------------------------- /git/remote_test.go: -------------------------------------------------------------------------------- 1 | package git 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type newRemoteTest struct { 8 | url string 9 | willBeError bool 10 | expectedServiceHost string 11 | expectedOwner string 12 | expectedRepoName string 13 | } 14 | 15 | func TestNewRemote(t *testing.T) { 16 | newRemoteTests := []*newRemoteTest{ 17 | { 18 | url: "git@github.com:mpppk/hlb.git", 19 | willBeError: false, 20 | expectedServiceHost: "github.com", 21 | expectedOwner: "mpppk", 22 | expectedRepoName: "hlb", 23 | }, 24 | { 25 | url: "git@github.com:mpppk.git", 26 | willBeError: true, 27 | }, 28 | { 29 | url: "git@github.com:/hlb.git", 30 | willBeError: true, 31 | }, 32 | { 33 | url: "git@github.commpppk/hlb.git", 34 | willBeError: true, 35 | }, 36 | { 37 | url: "github.com/mpppk/hlb.git", 38 | willBeError: true, 39 | }, 40 | { 41 | url: "https://github.com/mpppk/hlb", 42 | willBeError: false, 43 | expectedServiceHost: "github.com", 44 | expectedOwner: "mpppk", 45 | expectedRepoName: "hlb", 46 | }, 47 | { 48 | url: "http://github.com/mpppk/hlb", 49 | willBeError: false, 50 | expectedServiceHost: "github.com", 51 | expectedOwner: "mpppk", 52 | expectedRepoName: "hlb", 53 | }, 54 | { 55 | url: "https://mpppk@github.com/mpppk/hlb", 56 | willBeError: false, 57 | expectedServiceHost: "github.com", 58 | expectedOwner: "mpppk", 59 | expectedRepoName: "hlb", 60 | }, 61 | { 62 | url: "git://github.com/mpppk/hlb.git", 63 | willBeError: false, 64 | expectedServiceHost: "github.com", 65 | expectedOwner: "mpppk", 66 | expectedRepoName: "hlb", 67 | }, 68 | { 69 | url: "http://github.com/mpppk", 70 | willBeError: true, 71 | }, 72 | { // https://github.com/mpppk/hlb/issues/40 73 | url: "ssh://git@my.domain.com:3789/my-group/my-app.git", 74 | willBeError: false, 75 | expectedServiceHost: "my.domain.com:3789", 76 | expectedOwner: "my-group", 77 | expectedRepoName: "my-app", 78 | }, 79 | } 80 | 81 | for i, nr := range newRemoteTests { 82 | remote, err := NewRemote(nr.url) 83 | 84 | if err != nil { 85 | if !nr.willBeError { 86 | t.Errorf("%v: Unexpected error ocured: %v, params: %v", 87 | i, err, nr) 88 | } else { 89 | continue 90 | } 91 | } else if nr.willBeError { 92 | t.Errorf("%v: Error expected, params: %v", 93 | i, nr) 94 | } 95 | 96 | if remote.ServiceHost != nr.expectedServiceHost { 97 | t.Errorf("ServiceHost field must be host name "+ 98 | "that extracted from provided URL "+ 99 | "%v: expected: %v, actual(extract from %v): %v", 100 | i, nr.expectedServiceHost, nr.url, remote.ServiceHost) 101 | } 102 | 103 | if remote.Owner != nr.expectedOwner { 104 | t.Errorf("Owner field must be owner name that extracted from provided URL, "+ 105 | "%v: expected: %v, actual(extract from %v): %v", 106 | i, nr.expectedOwner, nr.url, remote.Owner) 107 | } 108 | 109 | if remote.RepoName != nr.expectedRepoName { 110 | t.Errorf("RepoName field must be repository name that extracted from provided URL, "+ 111 | "%v: expected: %v, actual(extract from %v): %v", 112 | i, nr.expectedRepoName, nr.url, remote.RepoName) 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /hlblib/cmd_context.go: -------------------------------------------------------------------------------- 1 | package hlblib 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/mpppk/gitany" 8 | 9 | "github.com/mpppk/hlb/git" 10 | "github.com/spf13/viper" 11 | ) 12 | 13 | type CmdContext struct { 14 | Remote *git.Remote 15 | ServiceConfig *gitany.ServiceConfig 16 | Client gitany.Client 17 | } 18 | 19 | type CmdContextFunc func() (*CmdContext, error) 20 | 21 | func NewCmdContext() (*CmdContext, error) { 22 | ctx := context.Background() 23 | 24 | var config Config 25 | err := viper.Unmarshal(&config) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | remote, err := git.GetDefaultRemote(".") 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | serviceConfig, ok := config.FindServiceConfig(remote.ServiceHost) 36 | if !ok { 37 | return nil, errors.New("serviceConfig not found" + remote.ServiceHost) 38 | } 39 | 40 | client, err := gitany.NewClient(ctx, serviceConfig) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return &CmdContext{ 46 | Remote: remote, 47 | ServiceConfig: serviceConfig, 48 | Client: client, 49 | }, nil 50 | } 51 | -------------------------------------------------------------------------------- /hlblib/config.go: -------------------------------------------------------------------------------- 1 | package hlblib 2 | 3 | import ( 4 | "github.com/mpppk/gitany" 5 | "path" 6 | "strings" 7 | 8 | "github.com/mitchellh/go-homedir" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | type HostType int 13 | 14 | type Config struct { 15 | Services []*gitany.ServiceConfig 16 | } 17 | 18 | func (c *Config) FindServiceConfig(host string) (*gitany.ServiceConfig, bool) { 19 | var matchedHosts []*gitany.ServiceConfig 20 | for _, h := range c.Services { 21 | if strings.Contains(host, h.Host) { 22 | matchedHosts = append(matchedHosts, h) 23 | } 24 | } 25 | 26 | matchedHostsLen := len(matchedHosts) 27 | 28 | if matchedHostsLen <= 0 { 29 | return nil, false 30 | } 31 | 32 | if matchedHostsLen == 1 { 33 | return matchedHosts[0], true 34 | } 35 | 36 | // priority 37 | // query: example.com, matchedHosts: [example.com, example.com:81] 38 | // => return example.com 39 | // query: example.com:81, matchedHosts: [example.com, example.com:81] 40 | // => return example.com:81 41 | queryHasPortNumber := strings.Contains(host, ":") // Check that query host has port number 42 | for _, h := range matchedHosts { 43 | hostHasPortNumber := strings.Contains(h.Host, ":") 44 | if queryHasPortNumber && hostHasPortNumber { 45 | return h, true 46 | } 47 | 48 | if !queryHasPortNumber && !hostHasPortNumber { 49 | return h, true 50 | } 51 | } 52 | 53 | return matchedHosts[0], true 54 | } 55 | 56 | func (c *Config) FindServiceConfigs(host string) *Config { 57 | var serviceConfigs []*gitany.ServiceConfig 58 | for _, s := range c.Services { 59 | if strings.Contains(s.Host, host) { 60 | serviceConfigs = append(serviceConfigs, s) 61 | } 62 | } 63 | return &Config{Services: serviceConfigs} 64 | } 65 | 66 | func (c *Config) ListServiceConfigHost() (hosts []string) { 67 | for _, s := range c.Services { 68 | hosts = append(hosts, s.Host) 69 | } 70 | return hosts 71 | } 72 | 73 | func GetConfigDirName() string { 74 | return path.Join(".config", "hlb") 75 | } 76 | 77 | func GetConfigFileName() string { 78 | return ".hlb.yaml" 79 | } 80 | 81 | func GetConfigDirPath() (string, error) { 82 | dir, err := homedir.Dir() 83 | return path.Join(dir, GetConfigDirName()), errors.Wrap(err, "Error occurred in hlblib.GetConfigDirPath") 84 | } 85 | 86 | func GetConfigFilePath() (string, error) { 87 | configDirPath, err := GetConfigDirPath() 88 | return path.Join(configDirPath, GetConfigFileName()), errors.Wrap(err, "Error occurred in hlblib.GetConfigFilePath") 89 | } 90 | -------------------------------------------------------------------------------- /hlblib/config_test.go: -------------------------------------------------------------------------------- 1 | package hlblib 2 | 3 | import ( 4 | "github.com/mpppk/gitany" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | type findHostTest struct { 10 | services []*gitany.ServiceConfig 11 | targetServiceName string 12 | expectedStatus bool 13 | expectedService *gitany.ServiceConfig 14 | } 15 | 16 | func TestFindHost(t *testing.T) { 17 | services := []*gitany.ServiceConfig{ 18 | { 19 | Host: "nameA", 20 | Type: "typeA", 21 | Token: "OAuthTokenA", 22 | Protocol: "protocolA", 23 | }, 24 | { 25 | Host: "nameA:81", 26 | Type: "typeA", 27 | Token: "OAuthTokenA", 28 | Protocol: "protocolA", 29 | }, 30 | { 31 | Host: "nameB:81", 32 | Type: "typeB", 33 | Token: "OAuthTokenB", 34 | Protocol: "protocolB", 35 | }, 36 | { 37 | Host: "nameB", 38 | Type: "typeB", 39 | Token: "OAuthTokenB", 40 | Protocol: "protocolB", 41 | }, 42 | } 43 | 44 | findHostTests := []*findHostTest{ 45 | { 46 | services: services, 47 | targetServiceName: "nameA", 48 | expectedStatus: true, 49 | expectedService: services[0], 50 | }, 51 | { 52 | services: services, 53 | targetServiceName: "nameA:81", 54 | expectedStatus: true, 55 | expectedService: services[1], 56 | }, 57 | { 58 | services: services, 59 | targetServiceName: "nameB:81", 60 | expectedStatus: true, 61 | expectedService: services[2], 62 | }, 63 | { 64 | services: services, 65 | targetServiceName: "nameB", 66 | expectedStatus: true, 67 | expectedService: services[3], 68 | }, 69 | { 70 | services: services, 71 | targetServiceName: "nameC", 72 | expectedStatus: false, 73 | expectedService: nil, 74 | }, 75 | } 76 | 77 | for i, f := range findHostTests { 78 | config := Config{f.services} 79 | 80 | s, ok := config.FindServiceConfig(f.targetServiceName) 81 | 82 | if ok != f.expectedStatus { 83 | t.Errorf("%v: expected find status %v, but %v", i, f.expectedStatus, ok) 84 | } 85 | 86 | if !ok { 87 | continue 88 | } 89 | 90 | if !reflect.DeepEqual(s, f.expectedService) { 91 | t.Errorf("%v: expected service %v, but %v", i, f.expectedService, s) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /hlblib/utils.go: -------------------------------------------------------------------------------- 1 | package hlblib 2 | 3 | func PanicIfErrorExist(err error) { 4 | if err != nil { 5 | panic(err) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /hlblib/version.go: -------------------------------------------------------------------------------- 1 | package hlblib 2 | 3 | const Version = "0.0.4" 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/mpppk/hlb/cmd" 4 | import _ "github.com/mpppk/gitany/github" 5 | import _ "github.com/mpppk/gitany/gitlab" 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # hlb: git + hub/lab/bucket and more 2 | hlb is a command line tool that provides unified & interactive interface to multiple git repository hosting services. 3 | 4 | [![CircleCI](https://circleci.com/gh/mpppk/hlb/tree/master.svg?style=svg)](https://circleci.com/gh/mpppk/hlb/tree/master) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/9jw7n8ruxseys95n/branch/master?svg=true)](https://ci.appveyor.com/project/mpppk/hlb/branch/master) 6 | [![codebeat badge](https://codebeat.co/badges/544129f2-79a9-4641-8399-f06581cd2c53)](https://codebeat.co/projects/github-com-mpppk-hlb-master) 7 | 8 | ![hlb_ibrowse.gif](https://raw.githubusercontent.com/wiki/mpppk/hlb/images/hlb_ibrowse.gif) 9 | 10 | ## Features 11 | * Cross Platform 12 | * Support multi git repository hosting services 13 | * [hub](https://hub.github.com) command compatible 14 | * Interactive command 15 | 16 | ## Commands 17 | ### hlb browse 18 | * `$ hlb browse` 19 | * Open current repository page by default browser 20 | * `$ hlb browse issues` 21 | * Open issues page of current repository by browser 22 | * `$ hlb browse issues 1` 23 | * Open the page that issue ID is 1 24 | * `$ hlb browse pull-requests` or `$ hlb browse merge-requests` 25 | * Open pull-requests/merge-requests page of current repository by browser 26 | * `$ hlb browse pull-requests 1` 27 | * Open the page that pull-requests/merge-requests ID is 1 28 | 29 | ### hlb ibrowse (interactive browse) 30 | ![hlb_ibrowse](https://i.gyazo.com/510fe10751129f1716b3a99b1a5014ec.png) 31 | ![hlb_ibrowse.gif](https://raw.githubusercontent.com/wiki/mpppk/hlb/images/hlb_ibrowse.gif) 32 | 33 | ### hlb create 34 | ![hlb_create](https://i.gyazo.com/56d7fe0535e79819c22ec4248fcfabc4.png) 35 | ![hlb_create_and_browse.gif](https://raw.githubusercontent.com/wiki/mpppk/hlb/images/hlb_create_and_browse.gif) 36 | 37 | ### hlb init 38 | Create config file to `~/.config/hlb/.hlb.yaml`. 39 | 40 | ### hlb add-service 41 | Get OAuth token from git service and add to config file. 42 | 43 | ## Installation 44 | ### Homebrew 45 | ```Shell 46 | $ brew tap mpppk/mpppk 47 | $ brew install hlb 48 | ``` 49 | 50 | ### Standalone 51 | Download from [release page](https://github.com/mpppk/hlb/releases) and put it anywhere in your executable path. 52 | 53 | ### Source 54 | ```Shell 55 | $ go get github.com/mpppk/hlb 56 | ``` 57 | 58 | ## Update 59 | v0.0.3 or greater has `selfupdate` command for easy updating. 60 | 61 | ![hlb_selfupdate](https://raw.githubusercontent.com/wiki/mpppk/hlb/images/hlb_selfupdate.gif) 62 | 63 | ## Authentication 64 | authenticate infomation of hlb is stored in `~/.config/hlb/.hlb.yaml`. 65 | 66 | ### github.com & GitHub Enterprise 67 | a. Use `hlb add-service` command 68 | ```Shell 69 | $ hlb add-service github https://github.com # or your GHE server domain 70 | github username: yourname 71 | github password: 72 | ``` 73 | (Currently, add-service command only supports GitHub) 74 | 75 | b. Add below setting to `~/.config/hlb/.hlb.yaml` 76 | (If file does not exist yet, execute `hlb init` first) 77 | ```yaml 78 | services: 79 | - name: github.com # or your GHE server domain 80 | type: github 81 | protocol: https # or http 82 | oauth_token: xxxxxxxxxxxxxxxxxx 83 | ``` 84 | (oauth_token can generate from [GitHub Personal access token page](https://github.com/settings/tokens)) 85 | 86 | ### gitlab.com & your GitLab Server 87 | Add below setting to `~/.config/hlb/.hlb.yaml` 88 | 89 | ```yaml 90 | services: 91 | - name: gitlab.com # or your GitLab server domain 92 | type: gitlab 93 | protocol: https # or http 94 | oauth_token: xxxxxxxxxxxxxxxxxxxxx 95 | ``` 96 | (oauth_token can generate from [GitLab Personal access token page](https://gitlab.com/profile/personal_access_tokens)) 97 | 98 | ## TODO 99 | ### hub compatibility 100 | - [x] `hlb pull-request`(experimental) 101 | - [ ] `hlb fork` 102 | - [x] `hlb create` 103 | - [x] `hlb browse` 104 | - [ ] `hlb compare` 105 | - [ ] `hlb ci-status` 106 | 107 | ### Support Services 108 | - [x] [GitHub.com](https://github.com) / GitHub Enterprise 109 | - [x] [GitLab.com](https://gitlab.com) / Your Own GitLab Server 110 | - [ ] [BitBucket.org](https://bitbucket.org) / BitBucket Server 111 | - [ ] [GitBucket](https://github.com/gitbucket/gitbucket) 112 | - [ ] [Gogs](https://gogs.io) 113 | - [ ] [AWS CodeCommit](https://aws.amazon.com/codecommit/) 114 | - [ ] [GCP Cloud Source Repositories](https://cloud.google.com/source-repositories/) 115 | --------------------------------------------------------------------------------