[
  {
    "path": ".ctags.d/go.ctags",
    "content": "-R\n--exclude=etc\n--exclude=script\n--exclude=site\n--exclude=tmp\n--exclude=vendor\n--exclude=bundle\n"
  },
  {
    "path": ".dockerignore",
    "content": "*\n!Gemfile\n!Gemfile.lock"
  },
  {
    "path": ".gitattributes",
    "content": "\n# enforce correct line endings for shell and batch files.\n*.sh text eol=lf\nscript/* text eol=lf\nscript/*.bat text eol=crlf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Unexpected or broken behavior of \"hub\" command-line tool\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Command attempted:**\n<!-- e.g. `hub pull-request --head mybranch` -->\n\n**What happened:**\n<!-- describe the faulty behavior you've been experiencing -->\n\n**More info:**\n<!-- output of `hub version`, your OS version, steps to reproduce, etc. -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest new functionality for \"hub\" command-line tool\ntitle: ''\nlabels: feature\nassignees: ''\n\n---\n\n**The problem I'm trying to solve:**\n<!-- describe the problem that you think hub might help you with -->\n\n**How I imagine hub could expose this functionality:**\n<!-- e.g. `hub my-new-command --some-flags` -->\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "Please report security vulnerabilities to mislav@github.com. Thank you!\n\nNote that, unlike the [GitHub CLI](https://github.com/cli/cli), `hub` is _not_ an eligible target for the [GitHub Bug Bounty program](https://hackerone.com/github).\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n  - package-ecosystem: \"bundler\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    ignore:\n      - dependency-name: \"*\"\n        update-types:\n          - version-update:semver-major"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non: [push, pull_request]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    env:\n      BUNDLE_BIN: bin\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: \"2.6\"\n          bundler-cache: true\n\n      - name: Set up Go\n        uses: actions/setup-go@v4\n        with:\n          go-version: \"1.18\"\n\n      - name: Run tests\n        run: make test-all\n        env:\n          CI: true\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\non:\n  push:\n    tags: \"v*\"\n\njobs:\n  release:\n    name: Publish release\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Go\n        uses: actions/setup-go@v4\n        with:\n          go-version: \"1.18\"\n\n      - name: Publish release script\n        run: script/publish-release\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - uses: mislav/bump-homebrew-formula-action@v3\n        if: \"!contains(github.ref, '-')\" # skip prereleases\n        with:\n          formula-name: hub\n        env:\n          COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "*.swp\n*~\n/bin\n.bundle\nbundle/\nshare/doc/*\nshare/man/*\n!share/man/man1/hub.1.md\ntmp/\n*.test\ntarget\n.vagrant\ntags\n/site\n/hub\n.vscode\n.DS_Store\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at opensource+hub@github.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Contributing to hub\n===================\n\nContributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE).\n\nThis project adheres to a [Code of Conduct][code-of-conduct]. By participating, you are expected to uphold this code.\n\n[code-of-conduct]: ./CODE_OF_CONDUCT.md\n\nYou will need:\n\n1. Go 1.11+\n1. Ruby 1.9+ with Bundler\n2. git 1.8+\n3. tmux & zsh (optional) - for running shell completion tests\n\nIf setting up either Go or Ruby for development proves to be a pain, you can\nrun the test suite in a prepared Docker container via `script/docker`.\n\n## What makes a good hub feature\n\nhub is a tool that wraps git to provide useful integration with GitHub. A new\nfeature is a good idea for hub if it improves some workflow for a GitHub user.\n\n* A feature that encapsulates a git workflow *not specific* to GitHub is **not**\n  a good fit for hub, since something like that is best implemented as an\n  external script.\n* If you're proposing to add a new custom command such as `hub foo`, please\n  research if there's a possibility that such a custom command could conflict\n  with other commands from popular 3rd party git projects.\n* If your contribution fixes a security vulnerability, please refer to the [SECURITY.md](./.github/SECURITY.md) security policy file\n\n## How to install dependencies and run tests\n\n1. [Clone the project](./README.md#source)\n2. Verify that existing tests pass:\n    `make test-all`\n3. Create a topic branch:\n    `git checkout -b feature`\n4. **Make your changes.**\n   (It helps a lot if you write tests first.)\n5. Verify that the tests still pass.\n6. Fork the project on GitHub:\n    `make && bin/hub fork --remote-name=origin`\n7. Push to your fork:\n    `git push -u origin HEAD`\n8. Open a pull request describing your changes:\n    `bin/hub pull-request`\n\nVendored Go dependencies are managed with [`go mod`](https://github.com/golang/go/wiki/Modules).\nCheck `go help mod` for information on how to add or update a vendored\ndependency.\n\n## How to write tests\n\nGo unit tests are in `*_test.go` files and are runnable with `make test`. These\nrun really fast (under 10s).\n\nHowever, most hub functionality is exercised through integration-style tests\nwritten in Cucumber. See [Features](./features) for more info.\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM ruby:2.6\n\nRUN apt-get update \\\n\t&& apt-get install -y sudo golang --no-install-recommends\nRUN apt-get purge --auto-remove -y curl \\\n\t&& rm -rf /var/lib/apt/lists/*\n\nRUN groupadd -r app && useradd -r -g app -G sudo app \\\n\t&& mkdir -p /home/app && chown -R app:app /home/app\nRUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers\n\nUSER app\n\n# throw errors if Gemfile has been modified since Gemfile.lock\nRUN bundle config --global frozen 1\n\nWORKDIR /home/app/workdir\n\nCOPY Gemfile Gemfile.lock ./\nRUN bundle install\n\nENV LANG C.UTF-8\nENV USER app\n"
  },
  {
    "path": "Gemfile",
    "content": "source 'https://rubygems.org'\n\ngem 'aruba', '~> 1.0.4'\ngem 'cucumber', '~> 3.1.2'\ngem 'sinatra'\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2009 Chris Wanstrath\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "SOURCES = $(shell go list -f '{{range .GoFiles}}{{$$.Dir}}/{{.}}\\\n{{end}}' ./...)\nSOURCE_DATE_EPOCH ?= $(shell date +%s)\nBUILD_DATE = $(shell date -u -d \"@$(SOURCE_DATE_EPOCH)\" '+%d %b %Y' 2>/dev/null || date -u -r \"$(SOURCE_DATE_EPOCH)\" '+%d %b %Y')\nHUB_VERSION = $(shell bin/hub version | tail -1)\n\nexport GO111MODULE=on\nunexport GOPATH\n\nexport LDFLAGS := -extldflags '$(LDFLAGS)'\nexport GCFLAGS := all=-trimpath '$(PWD)'\nexport ASMFLAGS := all=-trimpath '$(PWD)'\n\nMIN_COVERAGE = 90.2\n\nHELP_CMD = \\\n\tshare/man/man1/hub-alias.1 \\\n\tshare/man/man1/hub-api.1 \\\n\tshare/man/man1/hub-browse.1 \\\n\tshare/man/man1/hub-ci-status.1 \\\n\tshare/man/man1/hub-compare.1 \\\n\tshare/man/man1/hub-create.1 \\\n\tshare/man/man1/hub-delete.1 \\\n\tshare/man/man1/hub-fork.1 \\\n\tshare/man/man1/hub-gist.1 \\\n\tshare/man/man1/hub-pr.1 \\\n\tshare/man/man1/hub-pull-request.1 \\\n\tshare/man/man1/hub-release.1 \\\n\tshare/man/man1/hub-issue.1 \\\n\tshare/man/man1/hub-sync.1 \\\n\nHELP_EXT = \\\n\tshare/man/man1/hub-am.1 \\\n\tshare/man/man1/hub-apply.1 \\\n\tshare/man/man1/hub-checkout.1 \\\n\tshare/man/man1/hub-cherry-pick.1 \\\n\tshare/man/man1/hub-clone.1 \\\n\tshare/man/man1/hub-fetch.1 \\\n\tshare/man/man1/hub-help.1 \\\n\tshare/man/man1/hub-init.1 \\\n\tshare/man/man1/hub-merge.1 \\\n\tshare/man/man1/hub-push.1 \\\n\tshare/man/man1/hub-remote.1 \\\n\tshare/man/man1/hub-submodule.1 \\\n\nHELP_ALL = share/man/man1/hub.1 $(HELP_CMD) $(HELP_EXT)\n\nTEXT_WIDTH = 87\n\nbin/hub: $(SOURCES)\n\tscript/build -o $@\n\nbin/md2roff: $(SOURCES)\n\tgo build -o $@ github.com/github/hub/v2/md2roff-bin\n\ntest:\n\tgo test ./...\n\ntest-all: bin/cucumber\nifdef CI\n\tscript/test --coverage $(MIN_COVERAGE)\nelse\n\tscript/test\nendif\n\nbin/cucumber:\n\tscript/bootstrap\n\nfmt:\n\tgo fmt ./...\n\nman-pages: $(HELP_ALL:=.md) $(HELP_ALL) $(HELP_ALL:=.txt)\n\n%.txt: %\n\tgroff -Wall -mtty-char -mandoc -Tutf8 -rLL=$(TEXT_WIDTH)n $< | col -b >$@\n\n$(HELP_ALL): share/man/.man-pages.stamp\nshare/man/.man-pages.stamp: $(HELP_ALL:=.md) ./man-template.html bin/md2roff\n\tbin/md2roff --manual=\"hub manual\" \\\n\t\t--date=\"$(BUILD_DATE)\" --version=\"$(HUB_VERSION)\" \\\n\t\t--template=./man-template.html \\\n\t\tshare/man/man1/*.md\n\tmkdir -p share/doc/hub-doc\n\tmv share/man/*/*.html share/doc/hub-doc/\n\ttouch $@\n\n%.1.md: bin/hub\n\tbin/hub help $(*F) --plain-text >$@\n\nshare/man/man1/hub.1.md:\n\ttrue\n\ninstall: bin/hub man-pages\n\tbash < script/install.sh\n\nclean:\n\tgit clean -fdx bin share/man\n\n.PHONY: clean test test-all man-pages fmt install\n"
  },
  {
    "path": "README.md",
    "content": "hub is a command line tool that wraps `git` in order to extend it with extra\nfeatures and commands that make working with GitHub easier.\n\nFor an official, potentially more user-friendly command-line interface to GitHub,\nsee [cli.github.com](https://cli.github.com) and\n[this comparison](https://github.com/cli/cli/blob/trunk/docs/gh-vs-hub.md).\n\nThis repository and its issue tracker is **not for reporting problems with\nGitHub.com** web interface. If you have a problem with GitHub itself, please\n[contact Support](https://github.com/contact).\n\nUsage\n-----\n\n``` sh\n$ hub clone rtomayko/tilt\n#=> git clone https://github.com/rtomayko/tilt.git\n\n# or, if you prefer the SSH protocol:\n$ git config --global hub.protocol ssh\n$ hub clone rtomayko/tilt\n#=> git clone git@github.com:rtomayko/tilt.git\n```\n\nSee [usage examples](https://hub.github.com/#developer) or the [full reference\ndocumentation](https://hub.github.com/hub.1.html) to see all available commands\nand flags.\n\nhub can also be used to make shell scripts that [directly interact with the\nGitHub API](https://hub.github.com/#scripting).\n\nhub can be safely [aliased](#aliasing) as `git`, so you can type `$ git\n<command>` in the shell and have it expanded with `hub` features.\n\nInstallation\n------------\n\nThe `hub` executable has no dependencies, but since it was designed to wrap\n`git`, it's recommended to have at least **git 1.7.3** or newer.\n\nplatform | manager | command to run\n---------|---------|---------------\nmacOS, Linux | [Homebrew](https://docs.brew.sh/Installation) | `brew install hub`\nmacOS, Linux | [Nix](https://nixos.org/) | `nix-env -i hub`\nWindows | [Scoop](http://scoop.sh/) | `scoop install hub`\nWindows | [Chocolatey](https://chocolatey.org/) | `choco install hub`\nFedora Linux | [DNF](https://fedoraproject.org/wiki/DNF) | `sudo dnf install hub`\nArch Linux | [pacman](https://wiki.archlinux.org/index.php/pacman) | `sudo pacman -S hub`\nFreeBSD | [pkg(8)](http://man.freebsd.org/pkg/8) | `pkg install hub`\nDebian, Ubuntu | [apt(8)](https://manpages.debian.org/buster/apt/apt.8.en.html) | `sudo apt install hub`\nUbuntu | [Snap](https://snapcraft.io) | [We do not recommend installing the snap anymore.](https://github.com/github/hub/issues?q=is%3Aissue+snap)\nopenSUSE | [Zypper](https://en.opensuse.org/SDB:Zypper_manual) | `sudo zypper install hub`\nVoid Linux | [xbps](https://github.com/void-linux/xbps) | `sudo xbps-install -S hub`\nGentoo | [Portage](https://wiki.gentoo.org/wiki/Portage) | `sudo emerge dev-vcs/hub`\n_any_ | [conda](https://docs.conda.io/en/latest/) | `conda install -c conda-forge hub`\n\n\nPackages other than Homebrew are community-maintained (thank you!) and they\nare not guaranteed to match the [latest hub release][latest]. Check `hub\nversion` after installing a community package.\n\n#### Standalone\n\n`hub` can be easily installed as an executable. Download the [latest\nbinary][latest] for your system and put it anywhere in your executable path.\n\n#### GitHub Actions\n\nhub is ready to be used in your [GitHub Actions][] workflows:\n```yaml\nsteps:\n- uses: actions/checkout@v2\n\n- name: List open pull requests\n  run: hub pr list\n  env:\n    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n```\n\nNote that the default `secrets.GITHUB_TOKEN` will only work for API operations\nscoped to the repository that runs this workflow. If you need to interact with other\nrepositories, [generate a Personal Access Token][pat] with at least the `repo` scope\nand add it to your [repository secrets][].\n\n\n[github actions]: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions\n[pat]: https://github.com/settings/tokens\n[repository secrets]: https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets\n\n#### Source\n\nPrerequisites for building from source are:\n\n* `make`\n* [Go 1.11+](https://golang.org/doc/install)\n\nClone this repository and run `make install`:\n\n```sh\ngit clone \\\n  --config transfer.fsckobjects=false \\\n  --config receive.fsckobjects=false \\\n  --config fetch.fsckobjects=false \\\n  https://github.com/github/hub.git\n\ncd hub\nmake install prefix=/usr/local\n```\n\nAliasing\n--------\n\nSome hub features feel best when it's aliased as `git`. This is not dangerous; your\n_normal git commands will all work_. hub merely adds some sugar.\n\n`hub alias` displays instructions for the current shell. With the `-s` flag, it\noutputs a script suitable for `eval`.\n\nYou should place this command in your `.bash_profile` or other startup script:\n\n``` sh\neval \"$(hub alias -s)\"\n```\n\n#### PowerShell\n\nIf you're using PowerShell, you can set an alias for `hub` by placing the\nfollowing in your PowerShell profile (usually\n`~/Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1`):\n\n``` sh\nSet-Alias git hub\n```\n\nA simple way to do this is to run the following from the PowerShell prompt:\n\n``` sh\nAdd-Content $PROFILE \"`nSet-Alias git hub\"\n```\n\nNote: You'll need to restart your PowerShell console in order for the changes to be picked up.\n\nIf your PowerShell profile doesn't exist, you can create it by running the following:\n\n``` sh\nNew-Item -Type file -Force $PROFILE\n```\n\n### Shell tab-completion\n\nhub repository contains [tab-completion scripts](./etc) for bash, zsh and fish.\nThese scripts complement existing completion scripts that ship with git.\n\nMeta\n----\n\n* Bugs: <https://github.com/github/hub/issues>\n* Authors: <https://github.com/github/hub/contributors>\n* Our [Code of Conduct](https://github.com/github/hub/blob/master/CODE_OF_CONDUCT.md)\n\n\n[latest]: https://github.com/github/hub/releases/latest\n"
  },
  {
    "path": "cmd/cmd.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/github/hub/v2/ui\"\n)\n\n// Cmd is a project-wide struct that represents a command to be run in the console.\ntype Cmd struct {\n\tName   string\n\tArgs   []string\n\tStdin  *os.File\n\tStdout *os.File\n\tStderr *os.File\n}\n\nfunc (cmd Cmd) String() string {\n\targs := make([]string, len(cmd.Args))\n\tfor i, a := range cmd.Args {\n\t\tif strings.ContainsRune(a, '\"') {\n\t\t\targs[i] = fmt.Sprintf(`'%s'`, a)\n\t\t} else if a == \"\" || strings.ContainsRune(a, '\\'') || strings.ContainsRune(a, ' ') {\n\t\t\targs[i] = fmt.Sprintf(`\"%s\"`, a)\n\t\t} else {\n\t\t\targs[i] = a\n\t\t}\n\t}\n\treturn fmt.Sprintf(\"%s %s\", cmd.Name, strings.Join(args, \" \"))\n}\n\n// WithArg returns the current argument\nfunc (cmd *Cmd) WithArg(arg string) *Cmd {\n\tcmd.Args = append(cmd.Args, arg)\n\n\treturn cmd\n}\n\nfunc (cmd *Cmd) WithArgs(args ...string) *Cmd {\n\tfor _, arg := range args {\n\t\tcmd.WithArg(arg)\n\t}\n\n\treturn cmd\n}\n\nfunc (cmd *Cmd) Output() (string, error) {\n\tverboseLog(cmd)\n\tc := exec.Command(cmd.Name, cmd.Args...)\n\tc.Stderr = cmd.Stderr\n\toutput, err := c.Output()\n\n\treturn string(output), err\n}\n\nfunc (cmd *Cmd) CombinedOutput() (string, error) {\n\tverboseLog(cmd)\n\toutput, err := exec.Command(cmd.Name, cmd.Args...).CombinedOutput()\n\n\treturn string(output), err\n}\n\nfunc (cmd *Cmd) Success() bool {\n\tverboseLog(cmd)\n\terr := exec.Command(cmd.Name, cmd.Args...).Run()\n\treturn err == nil\n}\n\n// Run runs command with `Exec` on platforms except Windows\n// which only supports `Spawn`\nfunc (cmd *Cmd) Run() error {\n\tif isWindows() {\n\t\treturn cmd.Spawn()\n\t}\n\treturn cmd.Exec()\n}\n\nfunc isWindows() bool {\n\treturn runtime.GOOS == \"windows\" || detectWSL()\n}\n\nvar detectedWSL bool\nvar detectedWSLContents string\n\n// https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364\nfunc detectWSL() bool {\n\tif !detectedWSL {\n\t\tb := make([]byte, 1024)\n\t\tf, err := os.Open(\"/proc/version\")\n\t\tif err == nil {\n\t\t\tf.Read(b)\n\t\t\tf.Close()\n\t\t\tdetectedWSLContents = string(b)\n\t\t}\n\t\tdetectedWSL = true\n\t}\n\treturn strings.Contains(detectedWSLContents, \"Microsoft\")\n}\n\n// Spawn runs command with spawn(3)\nfunc (cmd *Cmd) Spawn() error {\n\tverboseLog(cmd)\n\tc := exec.Command(cmd.Name, cmd.Args...)\n\tc.Stdin = cmd.Stdin\n\tc.Stdout = cmd.Stdout\n\tc.Stderr = cmd.Stderr\n\n\treturn c.Run()\n}\n\n// Exec runs command with exec(3)\n// Note that Windows doesn't support exec(3): http://golang.org/src/pkg/syscall/exec_windows.go#L339\nfunc (cmd *Cmd) Exec() error {\n\tverboseLog(cmd)\n\n\tbinary, err := exec.LookPath(cmd.Name)\n\tif err != nil {\n\t\treturn &exec.Error{\n\t\t\tName: cmd.Name,\n\t\t\tErr:  fmt.Errorf(\"command not found\"),\n\t\t}\n\t}\n\n\targs := []string{binary}\n\targs = append(args, cmd.Args...)\n\n\treturn syscall.Exec(binary, args, os.Environ())\n}\n\nfunc New(name string) *Cmd {\n\treturn &Cmd{\n\t\tName:   name,\n\t\tArgs:   []string{},\n\t\tStdin:  os.Stdin,\n\t\tStdout: os.Stdout,\n\t\tStderr: os.Stderr,\n\t}\n}\n\nfunc NewWithArray(cmd []string) *Cmd {\n\treturn &Cmd{Name: cmd[0], Args: cmd[1:], Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr}\n}\n\nfunc verboseLog(cmd *Cmd) {\n\tif os.Getenv(\"HUB_VERBOSE\") != \"\" {\n\t\tmsg := fmt.Sprintf(\"$ %s\", cmd.String())\n\t\tif ui.IsTerminal(os.Stderr) {\n\t\t\tmsg = fmt.Sprintf(\"\\033[35m%s\\033[0m\", msg)\n\t\t}\n\t\tui.Errorln(msg)\n\t}\n}\n"
  },
  {
    "path": "cmd/cmd_test.go",
    "content": "package cmd\n\nimport (\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestNew(t *testing.T) {\n\texecCmd := New(\"vim --noplugin\")\n\tassert.Equal(t, \"vim --noplugin\", execCmd.Name)\n\tassert.Equal(t, 0, len(execCmd.Args))\n}\n\nfunc TestWithArg(t *testing.T) {\n\texecCmd := New(\"git\")\n\texecCmd.WithArg(\"command\").WithArg(\"--amend\").WithArg(\"-m\").WithArg(`\"\"`)\n\tassert.Equal(t, \"git\", execCmd.Name)\n\tassert.Equal(t, 4, len(execCmd.Args))\n}\n\nfunc Test_String(t *testing.T) {\n\tc := Cmd{\n\t\tName: \"echo\",\n\t\tArgs: []string{\"hi\", \"hello world\", \"don't\", `\"fake news\"`},\n\t}\n\tassert.Equal(t, `echo hi \"hello world\" \"don't\" '\"fake news\"'`, c.String())\n}\n"
  },
  {
    "path": "commands/alias.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdAlias = &Command{\n\tRun:   alias,\n\tUsage: \"alias [-s] [<SHELL>]\",\n\tLong: `Show shell instructions for wrapping git.\n\n## Options\n\t-s\n\t\tOutput shell script suitable for ''eval''.\n\n\t<SHELL>\n\t\tSpecify the type of shell (default: \"$SHELL\" environment variable).\n\n## See also:\n\nhub(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdAlias)\n}\n\nfunc alias(command *Command, args *Args) {\n\tvar shell string\n\tif args.ParamsSize() > 0 {\n\t\tshell = args.FirstParam()\n\t} else {\n\t\tshell = os.Getenv(\"SHELL\")\n\t}\n\n\tflagAliasScript := args.Flag.Bool(\"-s\")\n\tif shell == \"\" {\n\t\tcmd := \"hub alias <shell>\"\n\t\tif flagAliasScript {\n\t\t\tcmd = \"hub alias -s <shell>\"\n\t\t}\n\t\tutils.Check(fmt.Errorf(\"Error: couldn't detect shell type. Please specify your shell with `%s`\", cmd))\n\t}\n\n\tshells := []string{\"bash\", \"zsh\", \"sh\", \"ksh\", \"csh\", \"tcsh\", \"fish\", \"rc\"}\n\tshell = filepath.Base(shell)\n\tvar validShell bool\n\tfor _, s := range shells {\n\t\tif s == shell {\n\t\t\tvalidShell = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !validShell {\n\t\terr := fmt.Errorf(\"hub alias: unsupported shell\\nsupported shells: %s\", strings.Join(shells, \" \"))\n\t\tutils.Check(err)\n\t}\n\n\tif flagAliasScript {\n\t\tvar alias string\n\t\tswitch shell {\n\t\tcase \"csh\", \"tcsh\":\n\t\t\talias = \"alias git hub\"\n\t\tcase \"rc\":\n\t\t\talias = \"fn git { builtin hub $* }\"\n\t\tdefault:\n\t\t\talias = \"alias git=hub\"\n\t\t}\n\n\t\tui.Println(alias)\n\t} else {\n\t\tvar profile string\n\t\tswitch shell {\n\t\tcase \"bash\":\n\t\t\tprofile = \"~/.bash_profile\"\n\t\tcase \"zsh\":\n\t\t\tprofile = \"~/.zshrc\"\n\t\tcase \"ksh\":\n\t\t\tprofile = \"~/.profile\"\n\t\tcase \"fish\":\n\t\t\tprofile = \"~/.config/fish/functions/git.fish\"\n\t\tcase \"csh\":\n\t\t\tprofile = \"~/.cshrc\"\n\t\tcase \"tcsh\":\n\t\t\tprofile = \"~/.tcshrc\"\n\t\tcase \"rc\":\n\t\t\tprofile = \"$home/lib/profile\"\n\t\tdefault:\n\t\t\tprofile = \"your profile\"\n\t\t}\n\n\t\tmsg := fmt.Sprintf(\"# Wrap git automatically by adding the following to %s:\\n\", profile)\n\t\tui.Println(msg)\n\n\t\tvar eval string\n\t\tswitch shell {\n\t\tcase \"fish\":\n\t\t\teval = `function git --wraps hub --description 'Alias for hub, which wraps git to provide extra functionality with GitHub.'\n\thub $argv\nend`\n\t\tcase \"rc\":\n\t\t\teval = \"eval `{hub alias -s}\"\n\t\tcase \"csh\", \"tcsh\":\n\t\t\teval = \"eval \\\"`hub alias -s`\\\"\"\n\t\tdefault:\n\t\t\teval = `eval \"$(hub alias -s)\"`\n\t\t}\n\n\t\tindent := regexp.MustCompile(`(?m)^\\t+`)\n\t\teval = indent.ReplaceAllStringFunc(eval, func(match string) string {\n\t\t\treturn strings.Repeat(\" \", len(match)*4)\n\t\t})\n\n\t\tui.Println(eval)\n\t}\n\n\targs.NoForward()\n}\n"
  },
  {
    "path": "commands/api.go",
    "content": "package commands\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdAPI = &Command{\n\tRun:   apiCommand,\n\tUsage: \"api [-it] [-X <METHOD>] [-H <HEADER>] [--cache <TTL>] <ENDPOINT> [-F <FIELD>|--input <FILE>]\",\n\tLong: `Low-level GitHub API request interface.\n\n## Options:\n\t-X, --method <METHOD>\n\t\tThe HTTP method to use for the request (default: \"GET\"). The method is\n\t\tautomatically set to \"POST\" if ''--field'', ''--raw-field'', or ''--input''\n\t\tare used.\n\n\t\tUse ''-XGET'' to force serializing fields into the query string for the GET\n\t\trequest instead of JSON body of the POST request.\n\n\t-F, --field <KEY>=<VALUE>\n\t\tData to serialize with the request. <VALUE> has some magic handling; use\n\t\t''--raw-field'' for sending arbitrary string values.\n\n\t\tIf <VALUE> starts with \"@\", the rest of the value is interpreted as a\n\t\tfilename to read the value from. Use \"@-\" to read from standard input.\n\n\t\tIf <VALUE> is \"true\", \"false\", \"null\", or looks like a number, an\n\t\tappropriate JSON type is used instead of a string.\n\n\t\tIt is not possible to serialize <VALUE> as a nested JSON array or hash.\n\t\tInstead, construct the request payload externally and pass it via\n\t\t''--input''.\n\n\t\tUnless ''-XGET'' was used, all fields are sent serialized as JSON within\n\t\tthe request body. When <ENDPOINT> is \"graphql\", all fields other than\n\t\t\"query\" are grouped under \"variables\". See\n\t\t<https://graphql.org/learn/queries/#variables>\n\n\t-f, --raw-field <KEY>=<VALUE>\n\t\tSame as ''--field'', except that it allows values starting with \"@\", literal\n\t\tstrings \"true\", \"false\", and \"null\", as well as strings that look like\n\t\tnumbers.\n\n\t--input <FILE>\n\t\tThe filename to read the raw request body from. Use \"-\" to read from standard\n\t\tinput. Use this when you want to manually construct the request payload.\n\n\t-H, --header <KEY>:<VALUE>\n\t\tSet an HTTP request header.\n\n\t-i, --include\n\t\tInclude HTTP response headers in the output.\n\n\t-t, --flat\n\t\tParse response JSON and output the data in a line-based key-value format\n\t\tsuitable for use in shell scripts.\n\n\t--paginate\n\t\tAutomatically request and output the next page of results until all\n\t\tresources have been listed. For GET requests, this follows the ''<next\\>''\n\t\tresource as indicated in the \"Link\" response header. For GraphQL queries,\n\t\tthis utilizes ''pageInfo'' that must be present in the query; see EXAMPLES.\n\n\t\tNote that multiple JSON documents will be output as a result. If the API\n\t\trate limit has been reached, the final document that is output will be the\n\t\tHTTP 403 notice, and the process will exit with a non-zero status. One way\n\t\tthis can be avoided is by enabling ''--obey-ratelimit''.\n\n\t--color[=<WHEN>]\n\t\tEnable colored output even if stdout is not a terminal. <WHEN> can be one\n\t\tof \"always\" (default for ''--color''), \"never\", or \"auto\" (default).\n\n\t--cache <TTL>\n\t\tCache valid responses to GET requests for <TTL> seconds.\n\n\t\tWhen using \"graphql\" as <ENDPOINT>, caching will apply to responses to POST\n\t\trequests as well. Just make sure to not use ''--cache'' for any GraphQL\n\t\tmutations.\n\n\t--obey-ratelimit\n\t\tAfter exceeding the API rate limit, pause the process until the reset time\n\t\tof the current rate limit window and retry the request. Note that this may\n\t\tcause the process to hang for a long time (maximum of 1 hour).\n\n\t<ENDPOINT>\n\t\tThe GitHub API endpoint to send the HTTP request to (default: \"/\").\n\n\t\tTo learn about available endpoints, see <https://developer.github.com/v3/>.\n\t\tTo make GraphQL queries, use \"graphql\" as <ENDPOINT> and pass ''-F query=QUERY''.\n\n\t\tIf the literal strings \"{owner}\" or \"{repo}\" appear in <ENDPOINT> or in the\n\t\tGraphQL \"query\" field, fill in those placeholders with values read from the\n\t\tgit remote configuration of the current git repository.\n\n## Examples:\n\n\t\t# fetch information about the currently authenticated user as JSON\n\t\t$ hub api user\n\n\t\t# list user repositories as line-based output\n\t\t$ hub api --flat users/octocat/repos\n\n\t\t# post a comment to issue #23 of the current repository\n\t\t$ hub api repos/{owner}/{repo}/issues/23/comments --raw-field 'body=Nice job!'\n\n\t\t# perform a GraphQL query read from a file\n\t\t$ hub api graphql -F query=@path/to/myquery.graphql\n\n\t\t# perform pagination with GraphQL\n\t\t$ hub api --paginate graphql -f query='\n\t\t  query($endCursor: String) {\n\t\t    repositoryOwner(login: \"USER\") {\n\t\t      repositories(first: 100, after: $endCursor) {\n\t\t        nodes {\n\t\t          nameWithOwner\n\t\t        }\n\t\t        pageInfo {\n\t\t          hasNextPage\n\t\t          endCursor\n\t\t        }\n\t\t      }\n\t\t    }\n\t\t  }\n\t\t'\n\n## See also:\n\nhub(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdAPI)\n}\n\nfunc apiCommand(_ *Command, args *Args) {\n\tpath := \"\"\n\tif !args.IsParamsEmpty() {\n\t\tpath = args.GetParam(0)\n\t}\n\n\tmethod := \"GET\"\n\tif args.Flag.HasReceived(\"--method\") {\n\t\tmethod = args.Flag.Value(\"--method\")\n\t} else if args.Flag.HasReceived(\"--field\") || args.Flag.HasReceived(\"--raw-field\") || args.Flag.HasReceived(\"--input\") {\n\t\tmethod = \"POST\"\n\t}\n\tcacheTTL := args.Flag.Int(\"--cache\")\n\n\tparams := make(map[string]interface{})\n\tfor _, val := range args.Flag.AllValues(\"--field\") {\n\t\tparts := strings.SplitN(val, \"=\", 2)\n\t\tif len(parts) >= 2 {\n\t\t\tparams[parts[0]] = magicValue(parts[1])\n\t\t}\n\t}\n\tfor _, val := range args.Flag.AllValues(\"--raw-field\") {\n\t\tparts := strings.SplitN(val, \"=\", 2)\n\t\tif len(parts) >= 2 {\n\t\t\tparams[parts[0]] = parts[1]\n\t\t}\n\t}\n\n\theaders := make(map[string]string)\n\tfor _, val := range args.Flag.AllValues(\"--header\") {\n\t\tparts := strings.SplitN(val, \":\", 2)\n\t\tif len(parts) >= 2 {\n\t\t\theaders[parts[0]] = strings.TrimLeft(parts[1], \" \")\n\t\t}\n\t}\n\n\thost := \"\"\n\towner := \"\"\n\trepo := \"\"\n\tlocalRepo, localRepoErr := github.LocalRepo()\n\tif localRepoErr == nil {\n\t\tvar project *github.Project\n\t\tif project, localRepoErr = localRepo.MainProject(); localRepoErr == nil {\n\t\t\thost = project.Host\n\t\t\towner = project.Owner\n\t\t\trepo = project.Name\n\t\t}\n\t}\n\tif host == \"\" {\n\t\tdefHost, err := github.CurrentConfig().DefaultHostNoPrompt()\n\t\tutils.Check(err)\n\t\thost = defHost.Host\n\t}\n\n\tisGraphQL := path == \"graphql\"\n\tif isGraphQL && params[\"query\"] != nil {\n\t\tquery := params[\"query\"].(string)\n\t\tquery = strings.Replace(query, \"{owner}\", owner, -1)\n\t\tquery = strings.Replace(query, \"{repo}\", repo, -1)\n\n\t\tvariables := make(map[string]interface{})\n\t\tfor key, value := range params {\n\t\t\tif key != \"query\" {\n\t\t\t\tvariables[key] = value\n\t\t\t}\n\t\t}\n\t\tif len(variables) > 0 {\n\t\t\tparams = make(map[string]interface{})\n\t\t\tparams[\"variables\"] = variables\n\t\t}\n\n\t\tparams[\"query\"] = query\n\t} else {\n\t\tpath = strings.Replace(path, \"{owner}\", owner, -1)\n\t\tpath = strings.Replace(path, \"{repo}\", repo, -1)\n\t}\n\n\tvar body interface{}\n\tif args.Flag.HasReceived(\"--input\") {\n\t\tfn := args.Flag.Value(\"--input\")\n\t\tif fn == \"-\" {\n\t\t\tbody = os.Stdin\n\t\t} else {\n\t\t\tfi, err := os.Open(fn)\n\t\t\tutils.Check(err)\n\t\t\tbody = fi\n\t\t\tdefer fi.Close()\n\t\t}\n\t} else {\n\t\tbody = params\n\t}\n\n\tgh := github.NewClient(host)\n\n\tout := ui.Stdout\n\tcolorize := colorizeOutput(args.Flag.HasReceived(\"--color\"), args.Flag.Value(\"--color\"))\n\tparseJSON := args.Flag.Bool(\"--flat\")\n\tincludeHeaders := args.Flag.Bool(\"--include\")\n\tpaginate := args.Flag.Bool(\"--paginate\")\n\trateLimitWait := args.Flag.Bool(\"--obey-ratelimit\")\n\n\targs.NoForward()\n\n\tfor {\n\t\tresponse, err := gh.GenericAPIRequest(method, path, body, headers, cacheTTL)\n\t\tutils.Check(err)\n\n\t\tif rateLimitWait && response.StatusCode == 403 && response.RateLimitRemaining() == 0 {\n\t\t\tpauseUntil(response.RateLimitReset())\n\t\t\tcontinue\n\t\t}\n\n\t\tsuccess := response.StatusCode < 300\n\t\tjsonType := true\n\t\tif !success {\n\t\t\tjsonType, _ = regexp.MatchString(`[/+]json(?:;|$)`, response.Header.Get(\"Content-Type\"))\n\t\t}\n\n\t\tif includeHeaders {\n\t\t\tfmt.Fprintf(out, \"%s %s\\r\\n\", response.Proto, response.Status)\n\t\t\tresponse.Header.Write(out)\n\t\t\tfmt.Fprintf(out, \"\\r\\n\")\n\t\t}\n\n\t\tendCursor := \"\"\n\t\thasNextPage := false\n\n\t\tif parseJSON && jsonType {\n\t\t\thasNextPage, endCursor = utils.JSONPath(out, response.Body, colorize)\n\t\t} else if paginate && isGraphQL {\n\t\t\tbodyCopy := &bytes.Buffer{}\n\t\t\tio.Copy(out, io.TeeReader(response.Body, bodyCopy))\n\t\t\thasNextPage, endCursor = utils.JSONPath(ioutil.Discard, bodyCopy, false)\n\t\t} else {\n\t\t\tio.Copy(out, response.Body)\n\t\t}\n\t\tresponse.Body.Close()\n\n\t\tif !success {\n\t\t\tif ssoErr := github.ValidateGitHubSSO(response.Response); ssoErr != nil {\n\t\t\t\tui.Errorln()\n\t\t\t\tui.Errorln(ssoErr)\n\t\t\t}\n\t\t\tif scopeErr := github.ValidateSufficientOAuthScopes(response.Response); scopeErr != nil {\n\t\t\t\tui.Errorln()\n\t\t\t\tui.Errorln(scopeErr)\n\t\t\t}\n\t\t\tos.Exit(22)\n\t\t}\n\n\t\tif paginate {\n\t\t\tif isGraphQL && hasNextPage && endCursor != \"\" {\n\t\t\t\tif v, ok := params[\"variables\"]; ok {\n\t\t\t\t\tvariables := v.(map[string]interface{})\n\t\t\t\t\tvariables[\"endCursor\"] = endCursor\n\t\t\t\t} else {\n\t\t\t\t\tvariables := map[string]interface{}{\"endCursor\": endCursor}\n\t\t\t\t\tparams[\"variables\"] = variables\n\t\t\t\t}\n\t\t\t\tgoto next\n\t\t\t} else if nextLink := response.Link(\"next\"); nextLink != \"\" {\n\t\t\t\tpath = nextLink\n\t\t\t\tgoto next\n\t\t\t}\n\t\t}\n\n\t\tbreak\n\tnext:\n\t\tif !parseJSON {\n\t\t\tfmt.Fprintf(out, \"\\n\")\n\t\t}\n\n\t\tif rateLimitWait && response.RateLimitRemaining() == 0 {\n\t\t\tpauseUntil(response.RateLimitReset())\n\t\t}\n\t}\n}\n\nfunc pauseUntil(timestamp int) {\n\trollover := time.Unix(int64(timestamp)+1, 0)\n\tduration := time.Until(rollover)\n\tif duration > 0 {\n\t\tui.Errorf(\"API rate limit exceeded; pausing until %v ...\\n\", rollover)\n\t\ttime.Sleep(duration)\n\t}\n}\n\nconst (\n\ttrueVal  = \"true\"\n\tfalseVal = \"false\"\n\tnilVal   = \"null\"\n)\n\nfunc magicValue(value string) interface{} {\n\tswitch value {\n\tcase trueVal:\n\t\treturn true\n\tcase falseVal:\n\t\treturn false\n\tcase nilVal:\n\t\treturn nil\n\tdefault:\n\t\tif strings.HasPrefix(value, \"@\") {\n\t\t\treturn string(readFile(value[1:]))\n\t\t} else if i, err := strconv.Atoi(value); err == nil {\n\t\t\treturn i\n\t\t} else {\n\t\t\treturn value\n\t\t}\n\t}\n}\n\nfunc readFile(file string) (content []byte) {\n\tvar err error\n\tif file == \"-\" {\n\t\tcontent, err = ioutil.ReadAll(os.Stdin)\n\t} else {\n\t\tcontent, err = ioutil.ReadFile(file)\n\t}\n\tutils.Check(err)\n\treturn\n}\n"
  },
  {
    "path": "commands/apply.go",
    "content": "package commands\n\nimport (\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"regexp\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdApply = &Command{\n\tRun:          apply,\n\tGitExtension: true,\n\tUsage:        \"apply <GITHUB-URL>\",\n\tLong: `Download a patch from GitHub and apply it locally.\n\n## Options:\n\t<GITHUB-URL>\n\t\tA URL to a pull request or commit on GitHub.\n\n## Examples:\n\t\t$ hub apply https://github.com/jingweno/gh/pull/55\n\t\t> curl https://github.com/jingweno/gh/pull/55.patch -o /tmp/55.patch\n\t\t> git apply /tmp/55.patch\n\n## See also:\n\nhub-am(1), hub(1), git-apply(1)\n`,\n}\n\nvar cmdAm = &Command{\n\tRun:          apply,\n\tGitExtension: true,\n\tUsage:        \"am [-3] <GITHUB-URL>\",\n\tLong: `Replicate commits from a GitHub pull request locally.\n\n## Options:\n\t-3\n\t\t(Recommended) See git-am(1).\n\n\t<GITHUB-URL>\n\t\tA URL to a pull request or commit on GitHub.\n\n## Examples:\n\t\t$ hub am -3 https://github.com/jingweno/gh/pull/55\n\t\t> curl https://github.com/jingweno/gh/pull/55.patch -o /tmp/55.patch\n\t\t> git am -3 /tmp/55.patch\n\n## See also:\n\nhub-apply(1), hub-cherry-pick(1), hub(1), git-am(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdApply)\n\tCmdRunner.Use(cmdAm)\n}\n\nfunc apply(command *Command, args *Args) {\n\tif !args.IsParamsEmpty() {\n\t\ttransformApplyArgs(args)\n\t}\n}\n\nfunc transformApplyArgs(args *Args) {\n\tgistRegexp := regexp.MustCompile(\"^https?://gist\\\\.github\\\\.com/([\\\\w.-]+/)?([a-f0-9]+)\")\n\tcommitRegexp := regexp.MustCompile(\"^(commit|pull/[0-9]+/commits)/([0-9a-f]+)\")\n\tpullRegexp := regexp.MustCompile(\"^pull/([0-9]+)\")\n\tfor idx, arg := range args.Params {\n\t\tvar (\n\t\t\tpatch    io.ReadCloser\n\t\t\tapiError error\n\t\t)\n\t\tprojectURL, err := github.ParseURL(arg)\n\t\tif err == nil {\n\t\t\tgh := github.NewClient(projectURL.Project.Host)\n\t\t\tif match := commitRegexp.FindStringSubmatch(projectURL.ProjectPath()); match != nil {\n\t\t\t\tpatch, apiError = gh.CommitPatch(projectURL.Project, match[2])\n\t\t\t} else if match := pullRegexp.FindStringSubmatch(projectURL.ProjectPath()); match != nil {\n\t\t\t\tpatch, apiError = gh.PullRequestPatch(projectURL.Project, match[1])\n\t\t\t}\n\t\t} else {\n\t\t\tmatch := gistRegexp.FindStringSubmatch(arg)\n\t\t\tif match != nil {\n\t\t\t\t// TODO: support Enterprise gist\n\t\t\t\tgh := github.NewClient(github.GitHubHost)\n\t\t\t\tpatch, apiError = gh.GistPatch(match[2])\n\t\t\t}\n\t\t}\n\n\t\tutils.Check(apiError)\n\t\tif patch == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\ttempDir := os.TempDir()\n\t\terr = os.MkdirAll(tempDir, 0775)\n\t\tutils.Check(err)\n\t\tpatchFile, err := ioutil.TempFile(tempDir, \"hub\")\n\t\tutils.Check(err)\n\n\t\t_, err = io.Copy(patchFile, patch)\n\t\tutils.Check(err)\n\n\t\tpatchFile.Close()\n\t\tpatch.Close()\n\n\t\targs.ReplaceParam(idx, patchFile.Name())\n\t}\n}\n"
  },
  {
    "path": "commands/args.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/cmd\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\ntype Args struct {\n\tExecutable  string\n\tGlobalFlags []string\n\tCommand     string\n\tProgramPath string\n\tParams      []string\n\tbeforeChain []*cmd.Cmd\n\tafterChain  []*cmd.Cmd\n\tNoop        bool\n\tTerminator  bool\n\tnoForward   bool\n\tCallbacks   []func() error\n\tFlag        *utils.ArgsParser\n}\n\nfunc (a *Args) Words() []string {\n\taa := make([]string, 0)\n\tfor _, p := range a.Params {\n\t\tif !looksLikeFlag(p) {\n\t\t\taa = append(aa, p)\n\t\t}\n\t}\n\n\treturn aa\n}\n\nfunc (a *Args) Before(command ...string) {\n\ta.beforeChain = append(a.beforeChain, cmd.NewWithArray(command))\n}\n\nfunc (a *Args) After(command ...string) {\n\ta.afterChain = append(a.afterChain, cmd.NewWithArray(command))\n}\n\nfunc (a *Args) AfterFn(fn func() error) {\n\ta.Callbacks = append(a.Callbacks, fn)\n}\n\nfunc (a *Args) NoForward() {\n\ta.noForward = true\n}\n\nfunc (a *Args) Replace(executable, command string, params ...string) {\n\ta.Executable = executable\n\ta.Command = command\n\ta.Params = params\n\ta.GlobalFlags = []string{}\n\ta.noForward = false\n}\n\nfunc (a *Args) Commands() []*cmd.Cmd {\n\tresult := []*cmd.Cmd{}\n\tappendFromChain := func(c *cmd.Cmd) {\n\t\tif c.Name == \"git\" {\n\t\t\tga := []string{c.Name}\n\t\t\tga = append(ga, a.GlobalFlags...)\n\t\t\tga = append(ga, c.Args...)\n\t\t\tresult = append(result, cmd.NewWithArray(ga))\n\t\t} else {\n\t\t\tresult = append(result, c)\n\t\t}\n\t}\n\n\tfor _, c := range a.beforeChain {\n\t\tappendFromChain(c)\n\t}\n\tif !a.noForward {\n\t\tresult = append(result, a.ToCmd())\n\t}\n\tfor _, c := range a.afterChain {\n\t\tappendFromChain(c)\n\t}\n\n\treturn result\n}\n\nfunc (a *Args) ToCmd() *cmd.Cmd {\n\tc := cmd.NewWithArray(append([]string{a.Executable}, a.GlobalFlags...))\n\n\tif a.Command != \"\" {\n\t\tc.WithArg(a.Command)\n\t}\n\n\tfor _, arg := range a.Params {\n\t\tc.WithArg(arg)\n\t}\n\n\treturn c\n}\n\nfunc (a *Args) GetParam(i int) string {\n\treturn a.Params[i]\n}\n\nfunc (a *Args) FirstParam() string {\n\tif a.ParamsSize() == 0 {\n\t\tpanic(\"Index 0 is out of bound\")\n\t}\n\n\treturn a.Params[0]\n}\n\nfunc (a *Args) LastParam() string {\n\tif a.ParamsSize()-1 < 0 {\n\t\tpanic(fmt.Sprintf(\"Index %d is out of bound\", a.ParamsSize()-1))\n\t}\n\n\treturn a.Params[a.ParamsSize()-1]\n}\n\nfunc (a *Args) HasSubcommand() bool {\n\treturn !a.IsParamsEmpty() && a.Params[0][0] != '-'\n}\n\nfunc (a *Args) InsertParam(i int, items ...string) {\n\tif i < 0 {\n\t\tpanic(fmt.Sprintf(\"Index %d is out of bound\", i))\n\t}\n\n\tif i > a.ParamsSize() {\n\t\ti = a.ParamsSize()\n\t}\n\n\tnewParams := make([]string, 0)\n\tnewParams = append(newParams, a.Params[:i]...)\n\tnewParams = append(newParams, items...)\n\tnewParams = append(newParams, a.Params[i:]...)\n\n\ta.Params = newParams\n}\n\nfunc (a *Args) RemoveParam(i int) string {\n\titem := a.Params[i]\n\ta.Params = append(a.Params[:i], a.Params[i+1:]...)\n\treturn item\n}\n\nfunc (a *Args) ReplaceParam(i int, item string) {\n\tif i < 0 || i > a.ParamsSize()-1 {\n\t\tpanic(fmt.Sprintf(\"Index %d is out of bound\", i))\n\t}\n\n\ta.Params[i] = item\n}\n\nfunc (a *Args) IndexOfParam(param string) int {\n\tfor i, p := range a.Params {\n\t\tif p == param {\n\t\t\treturn i\n\t\t}\n\t}\n\n\treturn -1\n}\n\nfunc (a *Args) ParamsSize() int {\n\treturn len(a.Params)\n}\n\nfunc (a *Args) IsParamsEmpty() bool {\n\treturn a.ParamsSize() == 0\n}\n\nfunc (a *Args) PrependParams(params ...string) {\n\ta.Params = append(params, a.Params...)\n}\n\nfunc (a *Args) AppendParams(params ...string) {\n\ta.Params = append(a.Params, params...)\n}\n\nfunc NewArgs(args []string) *Args {\n\tvar (\n\t\tcommand string\n\t\tparams  []string\n\t\tnoop    bool\n\t)\n\n\tcmdIdx := findCommandIndex(args)\n\tglobalFlags := args[:cmdIdx]\n\tif cmdIdx > 0 {\n\t\targs = args[cmdIdx:]\n\t\tfor i := len(globalFlags) - 1; i >= 0; i-- {\n\t\t\tif globalFlags[i] == noopFlag {\n\t\t\t\tnoop = true\n\t\t\t\tglobalFlags = append(globalFlags[:i], globalFlags[i+1:]...)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(args) != 0 {\n\t\tcommand = args[0]\n\t\tparams = args[1:]\n\t}\n\n\treturn &Args{\n\t\tExecutable:  \"git\",\n\t\tGlobalFlags: globalFlags,\n\t\tCommand:     command,\n\t\tParams:      params,\n\t\tNoop:        noop,\n\t\tbeforeChain: make([]*cmd.Cmd, 0),\n\t\tafterChain:  make([]*cmd.Cmd, 0),\n\t}\n}\n\nconst (\n\tnoopFlag    = \"--noop\"\n\tversionFlag = \"--version\"\n\tlistCmds    = \"--list-cmds=\"\n\thelpFlag    = \"--help\"\n\tconfigFlag  = \"-c\"\n\tchdirFlag   = \"-C\"\n\tflagPrefix  = \"-\"\n)\n\nfunc looksLikeFlag(value string) bool {\n\treturn strings.HasPrefix(value, flagPrefix)\n}\n\nfunc findCommandIndex(args []string) int {\n\tslurpNextValue := false\n\tcommandIndex := 0\n\n\tfor i, arg := range args {\n\t\tif slurpNextValue {\n\t\t\tcommandIndex = i + 1\n\t\t\tslurpNextValue = false\n\t\t} else if arg == versionFlag || arg == helpFlag || strings.HasPrefix(arg, listCmds) || !looksLikeFlag(arg) {\n\t\t\tbreak\n\t\t} else {\n\t\t\tcommandIndex = i + 1\n\t\t\tif arg == configFlag || arg == chdirFlag {\n\t\t\t\tslurpNextValue = true\n\t\t\t}\n\t\t}\n\t}\n\treturn commandIndex\n}\n"
  },
  {
    "path": "commands/args_test.go",
    "content": "package commands\n\nimport (\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestNewArgs(t *testing.T) {\n\targs := NewArgs([]string{})\n\tassert.Equal(t, \"\", args.Command)\n\tassert.Equal(t, 0, args.ParamsSize())\n\n\targs = NewArgs([]string{\"command\"})\n\tassert.Equal(t, \"command\", args.Command)\n\tassert.Equal(t, 0, args.ParamsSize())\n\n\targs = NewArgs([]string{\"command\", \"args\"})\n\tassert.Equal(t, \"command\", args.Command)\n\tassert.Equal(t, 1, args.ParamsSize())\n\n\targs = NewArgs([]string{\"--version\"})\n\tassert.Equal(t, \"--version\", args.Command)\n\tassert.Equal(t, 0, args.ParamsSize())\n\n\targs = NewArgs([]string{\"--help\"})\n\tassert.Equal(t, \"--help\", args.Command)\n\tassert.Equal(t, 0, args.ParamsSize())\n}\n\nfunc TestArgs_Words(t *testing.T) {\n\targs := NewArgs([]string{\"merge\", \"--no-ff\", \"master\", \"-m\", \"message\"})\n\ta := args.Words()\n\n\tassert.Equal(t, 2, len(a))\n\tassert.Equal(t, \"master\", a[0])\n\tassert.Equal(t, \"message\", a[1])\n}\n\nfunc TestArgs_Insert(t *testing.T) {\n\targs := NewArgs([]string{\"command\", \"1\", \"2\", \"3\", \"4\"})\n\targs.InsertParam(0, \"foo\")\n\n\tassert.Equal(t, 5, args.ParamsSize())\n\tassert.Equal(t, \"foo\", args.FirstParam())\n\n\targs = NewArgs([]string{\"command\", \"1\", \"2\", \"3\", \"4\"})\n\targs.InsertParam(3, \"foo\")\n\n\tassert.Equal(t, 5, args.ParamsSize())\n\tassert.Equal(t, \"foo\", args.Params[3])\n\n\targs = NewArgs([]string{\"checkout\", \"-b\"})\n\targs.InsertParam(1, \"foo\")\n\n\tassert.Equal(t, 2, args.ParamsSize())\n\tassert.Equal(t, \"-b\", args.Params[0])\n\tassert.Equal(t, \"foo\", args.Params[1])\n\n\targs = NewArgs([]string{\"checkout\"})\n\targs.InsertParam(1, \"foo\")\n\n\tassert.Equal(t, 1, args.ParamsSize())\n\tassert.Equal(t, \"foo\", args.Params[0])\n}\n\nfunc TestArgs_Remove(t *testing.T) {\n\targs := NewArgs([]string{\"1\", \"2\", \"3\", \"4\"})\n\n\titem := args.RemoveParam(1)\n\tassert.Equal(t, \"3\", item)\n\tassert.Equal(t, 2, args.ParamsSize())\n\tassert.Equal(t, \"2\", args.FirstParam())\n\tassert.Equal(t, \"4\", args.GetParam(1))\n}\n\nfunc TestArgs_GlobalFlags(t *testing.T) {\n\targs := NewArgs([]string{\"-c\", \"key=value\", \"status\", \"-s\", \"-b\"})\n\tassert.Equal(t, \"status\", args.Command)\n\tassert.Equal(t, []string{\"-c\", \"key=value\"}, args.GlobalFlags)\n\tassert.Equal(t, []string{\"-s\", \"-b\"}, args.Params)\n\tassert.Equal(t, false, args.Noop)\n}\n\nfunc TestArgs_GlobalFlags_Noop(t *testing.T) {\n\targs := NewArgs([]string{\"-c\", \"key=value\", \"--noop\", \"--literal-pathspecs\", \"status\", \"-s\", \"-b\"})\n\tassert.Equal(t, \"status\", args.Command)\n\tassert.Equal(t, []string{\"-c\", \"key=value\", \"--literal-pathspecs\"}, args.GlobalFlags)\n\tassert.Equal(t, []string{\"-s\", \"-b\"}, args.Params)\n\tassert.Equal(t, true, args.Noop)\n}\n\nfunc TestArgs_GlobalFlags_NoopTwice(t *testing.T) {\n\targs := NewArgs([]string{\"--noop\", \"--bare\", \"--noop\", \"status\"})\n\tassert.Equal(t, \"status\", args.Command)\n\tassert.Equal(t, []string{\"--bare\"}, args.GlobalFlags)\n\tassert.Equal(t, 0, len(args.Params))\n\tassert.Equal(t, true, args.Noop)\n}\n\nfunc TestArgs_GlobalFlags_Repeated(t *testing.T) {\n\targs := NewArgs([]string{\"-C\", \"mydir\", \"-c\", \"a=b\", \"--bare\", \"-c\", \"c=d\", \"-c\", \"e=f\", \"status\"})\n\tassert.Equal(t, \"status\", args.Command)\n\tassert.Equal(t, []string{\"-C\", \"mydir\", \"-c\", \"a=b\", \"--bare\", \"-c\", \"c=d\", \"-c\", \"e=f\"}, args.GlobalFlags)\n\tassert.Equal(t, 0, len(args.Params))\n\tassert.Equal(t, false, args.Noop)\n}\n\nfunc TestArgs_GlobalFlags_Propagate(t *testing.T) {\n\targs := NewArgs([]string{\"-c\", \"key=value\", \"status\"})\n\tcmd := args.ToCmd()\n\tassert.Equal(t, []string{\"-c\", \"key=value\", \"status\"}, cmd.Args)\n}\n\nfunc TestArgs_GlobalFlags_Replaced(t *testing.T) {\n\targs := NewArgs([]string{\"-c\", \"key=value\", \"status\"})\n\targs.Replace(\"open\", \"\", \"-a\", \"http://example.com\")\n\tcmd := args.ToCmd()\n\tassert.Equal(t, \"open\", cmd.Name)\n\tassert.Equal(t, []string{\"-a\", \"http://example.com\"}, cmd.Args)\n}\n\nfunc TestArgs_ToCmd(t *testing.T) {\n\targs := NewArgs([]string{\"a\", \"\", \"b\", \"\"})\n\tcmd := args.ToCmd()\n\tassert.Equal(t, []string{\"a\", \"\", \"b\", \"\"}, cmd.Args)\n}\n\nfunc TestArgs_GlobalFlags_BeforeAfterChain(t *testing.T) {\n\targs := NewArgs([]string{\"-c\", \"key=value\", \"-C\", \"dir\", \"status\"})\n\targs.Before(\"git\", \"remote\", \"add\")\n\targs.After(\"git\", \"clean\")\n\targs.After(\"echo\", \"done!\")\n\tcmds := args.Commands()\n\tassert.Equal(t, 4, len(cmds))\n\tassert.Equal(t, \"git -c key=value -C dir remote add\", cmds[0].String())\n\tassert.Equal(t, \"git -c key=value -C dir status\", cmds[1].String())\n\tassert.Equal(t, \"git -c key=value -C dir clean\", cmds[2].String())\n\tassert.Equal(t, \"echo done!\", cmds[3].String())\n}\n"
  },
  {
    "path": "commands/browse.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdBrowse = &Command{\n\tRun:   browse,\n\tUsage: \"browse [-uc] [[<USER>/]<REPOSITORY>|--] [<SUBPAGE>]\",\n\tLong: `Open a GitHub repository in a web browser.\n\n## Options:\n\t-u, --url\n\t\tPrint the URL instead of opening it.\n\n\t-c, --copy\n\t\tPut the URL in clipboard instead of opening it.\n\n\t[<USER>/]<REPOSITORY>\n\t\tDefaults to repository in the current working directory.\n\n\t<SUBPAGE>\n\t\tOne of \"wiki\", \"commits\", \"issues\", or other (default: \"tree\").\n\n## Examples:\n\t\t$ hub browse\n\t\t> open https://github.com/REPO\n\n\t\t$ hub browse -- issues\n\t\t> open https://github.com/REPO/issues\n\n\t\t$ hub browse jingweno/gh\n\t\t> open https://github.com/jingweno/gh\n\n\t\t$ hub browse gh wiki\n\t\t> open https://github.com/USER/gh/wiki\n\n## See also:\n\nhub-compare(1), hub(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdBrowse)\n}\n\nfunc browse(command *Command, args *Args) {\n\tvar (\n\t\tdest    string\n\t\tsubpage string\n\t\tpath    string\n\t\tproject *github.Project\n\t\tbranch  *github.Branch\n\t\terr     error\n\t)\n\n\tif !args.IsParamsEmpty() {\n\t\tdest = args.RemoveParam(0)\n\t}\n\n\tif !args.IsParamsEmpty() {\n\t\tsubpage = args.RemoveParam(0)\n\t}\n\n\tif args.Terminator {\n\t\tsubpage = dest\n\t\tdest = \"\"\n\t}\n\n\tlocalRepo, _ := github.LocalRepo()\n\tif dest != \"\" {\n\t\tproject = github.NewProject(\"\", dest, \"\")\n\t\tbranch = localRepo.MasterBranch()\n\t} else if subpage != \"\" && subpage != \"commits\" && subpage != \"tree\" && subpage != \"blob\" && subpage != \"settings\" {\n\t\tproject, err = localRepo.MainProject()\n\t\tbranch = localRepo.MasterBranch()\n\t\tutils.Check(err)\n\t} else {\n\t\tcurrentBranch, err := localRepo.CurrentBranch()\n\t\tif err != nil {\n\t\t\tcurrentBranch = localRepo.MasterBranch()\n\t\t}\n\n\t\tvar owner string\n\t\tmainProject, err := localRepo.MainProject()\n\t\tif err == nil {\n\t\t\thost, err := github.CurrentConfig().PromptForHost(mainProject.Host)\n\t\t\tif err != nil {\n\t\t\t\tutils.Check(github.FormatError(\"in browse\", err))\n\t\t\t} else {\n\t\t\t\towner = host.User\n\t\t\t}\n\t\t}\n\n\t\tbranch, project, _ = localRepo.RemoteBranchAndProject(owner, currentBranch.IsMaster())\n\t\tif branch == nil {\n\t\t\tbranch = localRepo.MasterBranch()\n\t\t}\n\t}\n\n\tif project == nil {\n\t\tutils.Check(command.UsageError(\"\"))\n\t}\n\n\tif subpage == \"commits\" {\n\t\tpath = fmt.Sprintf(\"commits/%s\", branchInURL(branch))\n\t} else if subpage == \"tree\" || subpage == \"\" {\n\t\tif !branch.IsMaster() {\n\t\t\tpath = fmt.Sprintf(\"tree/%s\", branchInURL(branch))\n\t\t}\n\t} else {\n\t\tpath = subpage\n\t}\n\n\tpageURL := project.WebURL(\"\", \"\", path)\n\n\targs.NoForward()\n\tflagBrowseURLPrint := args.Flag.Bool(\"--url\")\n\tflagBrowseURLCopy := args.Flag.Bool(\"--copy\")\n\tprintBrowseOrCopy(args, pageURL, !flagBrowseURLPrint && !flagBrowseURLCopy, flagBrowseURLCopy)\n}\n\nfunc branchInURL(branch *github.Branch) string {\n\tparts := strings.Split(branch.ShortName(), \"/\")\n\tnewPath := make([]string, len(parts))\n\tfor i, s := range parts {\n\t\tnewPath[i] = url.QueryEscape(s)\n\t}\n\treturn strings.Join(newPath, \"/\")\n}\n"
  },
  {
    "path": "commands/checkout.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/github/hub/v2/git\"\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdCheckout = &Command{\n\tRun:          checkout,\n\tGitExtension: true,\n\tUsage:        \"checkout <PULLREQ-URL> [<BRANCH>]\",\n\tLong: `Check out the head of a pull request as a local branch.\n\n## Examples:\n\t\t$ hub checkout https://github.com/jingweno/gh/pull/73\n\t\t> git fetch origin pull/73/head:jingweno-feature\n\t\t> git checkout jingweno-feature\n\n## See also:\n\nhub-merge(1), hub-am(1), hub(1), git-checkout(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdCheckout)\n}\n\nfunc checkout(command *Command, args *Args) {\n\twords := args.Words()\n\n\tif len(words) == 0 {\n\t\treturn\n\t}\n\n\tcheckoutURL := words[0]\n\tvar newBranchName string\n\tif len(words) > 1 {\n\t\tnewBranchName = words[1]\n\t}\n\n\turl, err := github.ParseURL(checkoutURL)\n\tif err != nil {\n\t\t// not a valid GitHub URL\n\t\treturn\n\t}\n\n\tpullURLRegex := regexp.MustCompile(\"^pull/(\\\\d+)\")\n\tprojectPath := url.ProjectPath()\n\tif !pullURLRegex.MatchString(projectPath) {\n\t\t// not a valid PR URL\n\t\treturn\n\t}\n\n\terr = sanitizeCheckoutFlags(args)\n\tutils.Check(err)\n\n\tid := pullURLRegex.FindStringSubmatch(projectPath)[1]\n\tgh := github.NewClient(url.Project.Host)\n\tpullRequest, err := gh.PullRequest(url.Project, id)\n\tutils.Check(err)\n\n\tnewArgs, err := transformCheckoutArgs(args, pullRequest, newBranchName)\n\tutils.Check(err)\n\n\tif idx := args.IndexOfParam(newBranchName); idx >= 0 {\n\t\targs.RemoveParam(idx)\n\t}\n\treplaceCheckoutParam(args, checkoutURL, newArgs...)\n}\n\nfunc transformCheckoutArgs(args *Args, pullRequest *github.PullRequest, newBranchName string) (newArgs []string, err error) {\n\trepo, err := github.LocalRepo()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tbaseRemote, err := repo.RemoteForRepo(pullRequest.Base.Repo)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvar headRemote *github.Remote\n\tif pullRequest.IsSameRepo() {\n\t\theadRemote = baseRemote\n\t} else if pullRequest.Head.Repo != nil {\n\t\theadRemote, _ = repo.RemoteForRepo(pullRequest.Head.Repo)\n\t}\n\n\tif headRemote != nil {\n\t\tif newBranchName == \"\" {\n\t\t\tnewBranchName = pullRequest.Head.Ref\n\t\t}\n\t\tremoteBranch := fmt.Sprintf(\"%s/%s\", headRemote.Name, pullRequest.Head.Ref)\n\t\trefSpec := fmt.Sprintf(\"+refs/heads/%s:refs/remotes/%s\", pullRequest.Head.Ref, remoteBranch)\n\t\tif git.HasFile(\"refs\", \"heads\", newBranchName) {\n\t\t\tnewArgs = append(newArgs, newBranchName)\n\t\t\targs.After(\"git\", \"merge\", \"--ff-only\", fmt.Sprintf(\"refs/remotes/%s\", remoteBranch))\n\t\t} else {\n\t\t\tnewArgs = append(newArgs, \"-b\", newBranchName, \"--no-track\", remoteBranch)\n\t\t\targs.After(\"git\", \"config\", fmt.Sprintf(\"branch.%s.remote\", newBranchName), headRemote.Name)\n\t\t\targs.After(\"git\", \"config\", fmt.Sprintf(\"branch.%s.merge\", newBranchName), \"refs/heads/\"+pullRequest.Head.Ref)\n\t\t}\n\t\targs.Before(\"git\", \"fetch\", headRemote.Name, refSpec)\n\t} else {\n\t\tif newBranchName == \"\" {\n\t\t\tnewBranchName = pullRequest.Head.Ref\n\t\t\tif pullRequest.Head.Repo != nil && newBranchName == pullRequest.Head.Repo.DefaultBranch {\n\t\t\t\tnewBranchName = fmt.Sprintf(\"%s-%s\", pullRequest.Head.Repo.Owner.Login, newBranchName)\n\t\t\t}\n\t\t}\n\t\tnewArgs = append(newArgs, newBranchName)\n\n\t\tb, errB := repo.CurrentBranch()\n\t\tisCurrentBranch := errB == nil && b.ShortName() == newBranchName\n\n\t\tref := fmt.Sprintf(\"refs/pull/%d/head\", pullRequest.Number)\n\t\tif isCurrentBranch {\n\t\t\targs.Before(\"git\", \"fetch\", baseRemote.Name, ref)\n\t\t\targs.After(\"git\", \"merge\", \"--ff-only\", \"FETCH_HEAD\")\n\t\t} else {\n\t\t\targs.Before(\"git\", \"fetch\", baseRemote.Name, fmt.Sprintf(\"%s:%s\", ref, newBranchName))\n\t\t}\n\n\t\tremote := baseRemote.Name\n\t\tmergeRef := ref\n\t\tif pullRequest.MaintainerCanModify && pullRequest.Head.Repo != nil {\n\t\t\tvar project *github.Project\n\t\t\tproject, err = github.NewProjectFromRepo(pullRequest.Head.Repo)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tremote = project.GitURL(\"\", \"\", true)\n\t\t\tmergeRef = fmt.Sprintf(\"refs/heads/%s\", pullRequest.Head.Ref)\n\t\t}\n\n\t\tif mc, err := git.Config(fmt.Sprintf(\"branch.%s.merge\", newBranchName)); err != nil || mc == \"\" {\n\t\t\targs.After(\"git\", \"config\", fmt.Sprintf(\"branch.%s.remote\", newBranchName), remote)\n\t\t\targs.After(\"git\", \"config\", fmt.Sprintf(\"branch.%s.merge\", newBranchName), mergeRef)\n\t\t}\n\t}\n\treturn\n}\n\nfunc sanitizeCheckoutFlags(args *Args) error {\n\tif i := args.IndexOfParam(\"-b\"); i != -1 {\n\t\treturn fmt.Errorf(\"Unsupported flag -b when checking out pull request\")\n\t}\n\n\tif i := args.IndexOfParam(\"--orphan\"); i != -1 {\n\t\treturn fmt.Errorf(\"Unsupported flag --orphan when checking out pull request\")\n\t}\n\n\treturn nil\n}\n\nfunc replaceCheckoutParam(args *Args, checkoutURL string, replacement ...string) {\n\tidx := args.IndexOfParam(checkoutURL)\n\targs.RemoveParam(idx)\n\targs.InsertParam(idx, replacement...)\n}\n"
  },
  {
    "path": "commands/cherry_pick.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdCherryPick = &Command{\n\tRun:          cherryPick,\n\tGitExtension: true,\n\tUsage: `\ncherry-pick <COMMIT-URL>\ncherry-pick <USER>@<SHA>\n`,\n\tLong: `Cherry-pick a commit from a fork on GitHub.\n\n## See also:\n\nhub-am(1), hub(1), git-cherry-pick(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdCherryPick)\n}\n\nfunc cherryPick(command *Command, args *Args) {\n\tif args.IndexOfParam(\"-m\") == -1 && args.IndexOfParam(\"--mainline\") == -1 {\n\t\ttransformCherryPickArgs(args)\n\t}\n}\n\nfunc transformCherryPickArgs(args *Args) {\n\tif args.IsParamsEmpty() {\n\t\treturn\n\t}\n\n\tvar project *github.Project\n\tvar sha, refspec string\n\tshaRe := \"[a-f0-9]{7,40}\"\n\n\tvar mainProject *github.Project\n\tlocalRepo, mainProjectErr := github.LocalRepo()\n\tif mainProjectErr == nil {\n\t\tmainProject, mainProjectErr = localRepo.MainProject()\n\t}\n\n\tref := args.LastParam()\n\tif url, err := github.ParseURL(ref); err == nil {\n\t\tprojectPath := url.ProjectPath()\n\t\tcommitRegex := regexp.MustCompile(fmt.Sprintf(\"^commit/(%s)\", shaRe))\n\t\tpullRegex := regexp.MustCompile(fmt.Sprintf(`^pull/(\\d+)/commits/(%s)`, shaRe))\n\t\tif matches := commitRegex.FindStringSubmatch(projectPath); len(matches) > 0 {\n\t\t\tsha = matches[1]\n\t\t\tproject = url.Project\n\t\t} else if matches := pullRegex.FindStringSubmatch(projectPath); len(matches) > 0 {\n\t\t\tpullID := matches[1]\n\t\t\tsha = matches[2]\n\t\t\tutils.Check(mainProjectErr)\n\t\t\tproject = mainProject\n\t\t\trefspec = fmt.Sprintf(\"refs/pull/%s/head\", pullID)\n\t\t}\n\t} else {\n\t\townerWithShaRegexp := regexp.MustCompile(fmt.Sprintf(\"^(%s)@(%s)$\", OwnerRe, shaRe))\n\t\tif matches := ownerWithShaRegexp.FindStringSubmatch(ref); len(matches) > 0 {\n\t\t\tutils.Check(mainProjectErr)\n\t\t\tproject = mainProject\n\t\t\tproject.Owner = matches[1]\n\t\t\tsha = matches[2]\n\t\t}\n\t}\n\n\tif project != nil {\n\t\targs.ReplaceParam(args.IndexOfParam(ref), sha)\n\n\t\ttmpName := \"_hub-cherry-pick\"\n\t\tremoteName := tmpName\n\n\t\tif remote, err := localRepo.RemoteForProject(project); err == nil {\n\t\t\tremoteName = remote.Name\n\t\t} else {\n\t\t\targs.Before(\"git\", \"remote\", \"add\", remoteName, project.GitURL(\"\", \"\", false))\n\t\t}\n\n\t\tfetchArgs := []string{\"git\", \"fetch\", \"-q\", \"--no-tags\", remoteName}\n\t\tif refspec != \"\" {\n\t\t\tfetchArgs = append(fetchArgs, refspec)\n\t\t}\n\t\targs.Before(fetchArgs...)\n\n\t\tif remoteName == tmpName {\n\t\t\targs.Before(\"git\", \"remote\", \"rm\", remoteName)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "commands/ci_status.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"sort\"\n\n\t\"github.com/github/hub/v2/git\"\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdCiStatus = &Command{\n\tRun:   ciStatus,\n\tUsage: \"ci-status [-v] [<COMMIT>]\",\n\tLong: `Display status of GitHub checks for a commit.\n\n## Options:\n\t-v, --verbose\n\t\tPrint detailed report of all status checks and their URLs.\n\n\t-f, --format <FORMAT>\n\t\tPretty print all status checks using <FORMAT> (implies ''--verbose''). See the\n\t\t\"PRETTY FORMATS\" section of git-log(1) for some additional details on how\n\t\tplaceholders are used in format. The available placeholders for checks are:\n\n\t\t%U: the URL of this status check\n\n\t\t%S: check state (e.g. \"success\", \"failure\")\n\n\t\t%sC: set color to red, green, or yellow, depending on state\n\n\t\t%t: name of the status check\n\n\t--color[=<WHEN>]\n\t\tEnable colored output even if stdout is not a terminal. <WHEN> can be one\n\t\tof \"always\" (default for ''--color''), \"never\", or \"auto\" (default).\n\n\t<COMMIT>\n\t\tA commit SHA or branch name (default: \"HEAD\").\n\nPossible outputs and exit statuses:\n\n- success, neutral: 0\n- failure, error, action_required, cancelled, timed_out: 1\n- pending: 2\n\n## See also:\n\nhub-pull-request(1), hub(1)\n`,\n}\n\nvar severityList []string\n\nfunc init() {\n\tCmdRunner.Use(cmdCiStatus)\n\n\tseverityList = []string{\n\t\t\"neutral\",\n\t\t\"success\",\n\t\t\"pending\",\n\t\t\"cancelled\",\n\t\t\"timed_out\",\n\t\t\"action_required\",\n\t\t\"failure\",\n\t\t\"error\",\n\t}\n}\n\nfunc checkSeverity(targetState string) int {\n\tfor i, state := range severityList {\n\t\tif state == targetState {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc ciStatus(cmd *Command, args *Args) {\n\tref := \"HEAD\"\n\tif !args.IsParamsEmpty() {\n\t\tref = args.RemoveParam(0)\n\t}\n\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\tsha, err := git.Ref(ref)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"Aborted: no revision could be determined from '%s'\", ref)\n\t}\n\tutils.Check(err)\n\n\tif args.Noop {\n\t\tui.Printf(\"Would request CI status for %s\\n\", sha)\n\t} else {\n\t\tgh := github.NewClient(project.Host)\n\t\tresponse, err := gh.FetchCIStatus(project, sha)\n\t\tutils.Check(err)\n\n\t\tstate := \"\"\n\t\tif len(response.Statuses) > 0 {\n\t\t\tfor _, status := range response.Statuses {\n\t\t\t\tif checkSeverity(status.State) > checkSeverity(state) {\n\t\t\t\t\tstate = status.State\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvar exitCode int\n\t\tswitch state {\n\t\tcase \"success\", \"neutral\":\n\t\t\texitCode = 0\n\t\tcase \"failure\", \"error\", \"action_required\", \"cancelled\", \"timed_out\":\n\t\t\texitCode = 1\n\t\tcase \"pending\":\n\t\t\texitCode = 2\n\t\tdefault:\n\t\t\texitCode = 3\n\t\t}\n\n\t\tverbose := args.Flag.Bool(\"--verbose\") || args.Flag.HasReceived(\"--format\")\n\t\tif verbose && len(response.Statuses) > 0 {\n\t\t\tcolorize := colorizeOutput(args.Flag.HasReceived(\"--color\"), args.Flag.Value(\"--color\"))\n\t\t\tciVerboseFormat(response.Statuses, args.Flag.Value(\"--format\"), colorize)\n\t\t} else {\n\t\t\tif state != \"\" {\n\t\t\t\tui.Println(state)\n\t\t\t} else {\n\t\t\t\tui.Println(\"no status\")\n\t\t\t}\n\t\t}\n\n\t\tos.Exit(exitCode)\n\t}\n}\n\nfunc ciVerboseFormat(statuses []github.CIStatus, formatString string, colorize bool) {\n\tcontextWidth := 0\n\tfor _, status := range statuses {\n\t\tif len(status.Context) > contextWidth {\n\t\t\tcontextWidth = len(status.Context)\n\t\t}\n\t}\n\n\tsort.SliceStable(statuses, func(a, b int) bool {\n\t\treturn stateRank(statuses[a].State) < stateRank(statuses[b].State)\n\t})\n\n\tfor _, status := range statuses {\n\t\tvar color int\n\t\tvar stateMarker string\n\t\tswitch status.State {\n\t\tcase \"success\":\n\t\t\tstateMarker = \"✔︎\"\n\t\t\tcolor = 32\n\t\tcase \"failure\", \"error\", \"action_required\", \"cancelled\", \"timed_out\":\n\t\t\tstateMarker = \"✖︎\"\n\t\t\tcolor = 31\n\t\tcase \"neutral\":\n\t\t\tstateMarker = \"◦\"\n\t\t\tcolor = 30\n\t\tcase \"pending\":\n\t\t\tstateMarker = \"●\"\n\t\t\tcolor = 33\n\t\t}\n\n\t\tplaceholders := map[string]string{\n\t\t\t\"S\":  status.State,\n\t\t\t\"sC\": \"\",\n\t\t\t\"t\":  status.Context,\n\t\t\t\"U\":  status.TargetURL,\n\t\t}\n\n\t\tif colorize {\n\t\t\tplaceholders[\"sC\"] = fmt.Sprintf(\"\\033[%dm\", color)\n\t\t}\n\n\t\tformat := formatString\n\t\tif format == \"\" {\n\t\t\tif status.TargetURL == \"\" {\n\t\t\t\tformat = fmt.Sprintf(\"%%sC%s%%Creset\\t%%t\\n\", stateMarker)\n\t\t\t} else {\n\t\t\t\tformat = fmt.Sprintf(\"%%sC%s%%Creset\\t%%<(%d)%%t\\t%%U\\n\", stateMarker, contextWidth)\n\t\t\t}\n\t\t}\n\t\tui.Print(ui.Expand(format, placeholders, colorize))\n\t}\n}\n\nfunc stateRank(state string) uint32 {\n\tswitch state {\n\tcase \"failure\", \"error\", \"action_required\", \"cancelled\", \"timed_out\":\n\t\treturn 1\n\tcase \"success\", \"neutral\":\n\t\treturn 3\n\tdefault:\n\t\treturn 2\n\t}\n}\n"
  },
  {
    "path": "commands/clone.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdClone = &Command{\n\tRun:          clone,\n\tGitExtension: true,\n\tUsage:        \"clone [-p] [<OPTIONS>] [<USER>/]<REPOSITORY> [<DESTINATION>]\",\n\tLong: `Clone a repository from GitHub.\n\n## Options:\n\t-p\n\t\t(Deprecated) Clone private repositories over SSH.\n\n\t[<USER>/]<REPOSITORY>\n\t\t<USER> defaults to your own GitHub username.\n\n\t<DESTINATION>\n\t\tDirectory name to clone into (default: <REPOSITORY>).\n\n## Protocol used for cloning\n\nHTTPS protocol is used by hub as the default. Alternatively, hub can be\nconfigured to use SSH protocol for all git operations. See \"SSH instead\nof HTTPS protocol\" and \"HUB_PROTOCOL\" of hub(1).\n\n## Examples:\n\t\t$ hub clone rtomayko/ronn\n\t\t> git clone https://github.com/rtomayko/ronn.git\n\n## See also:\n\nhub-fork(1), hub(1), git-clone(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdClone)\n}\n\nfunc clone(command *Command, args *Args) {\n\tif !args.IsParamsEmpty() {\n\t\ttransformCloneArgs(args)\n\t}\n}\n\nfunc transformCloneArgs(args *Args) {\n\tisPrivate := parseClonePrivateFlag(args)\n\n\t// git help clone | grep -e '^ \\+-.\\+<'\n\tp := utils.NewArgsParser()\n\tp.RegisterValue(\"--branch\", \"-b\")\n\tp.RegisterValue(\"--depth\")\n\tp.RegisterValue(\"--reference\")\n\tif args.Command == \"submodule\" {\n\t\tp.RegisterValue(\"--name\")\n\t} else {\n\t\tp.RegisterValue(\"--config\", \"-c\")\n\t\tp.RegisterValue(\"--jobs\", \"-j\")\n\t\tp.RegisterValue(\"--origin\", \"-o\")\n\t\tp.RegisterValue(\"--reference-if-able\")\n\t\tp.RegisterValue(\"--separate-git-dir\")\n\t\tp.RegisterValue(\"--shallow-exclude\")\n\t\tp.RegisterValue(\"--shallow-since\")\n\t\tp.RegisterValue(\"--template\")\n\t\tp.RegisterValue(\"--upload-pack\", \"-u\")\n\t}\n\tp.Parse(args.Params)\n\n\tnameWithOwnerRegexp := regexp.MustCompile(NameWithOwnerRe)\n\tif len(p.PositionalIndices) > 0 {\n\t\ti := p.PositionalIndices[0]\n\t\ta := args.Params[i]\n\t\tif nameWithOwnerRegexp.MatchString(a) && !isCloneable(a) {\n\t\t\turl := getCloneURL(a, isPrivate, args.Command != \"submodule\")\n\t\t\targs.ReplaceParam(i, url)\n\t\t}\n\t}\n}\n\nfunc parseClonePrivateFlag(args *Args) bool {\n\tif i := args.IndexOfParam(\"-p\"); i != -1 {\n\t\targs.RemoveParam(i)\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc getCloneURL(nameWithOwner string, allowPush, allowPrivate bool) string {\n\tname := nameWithOwner\n\towner := \"\"\n\tif strings.Contains(name, \"/\") {\n\t\tsplit := strings.SplitN(name, \"/\", 2)\n\t\towner = split[0]\n\t\tname = split[1]\n\t}\n\n\tvar host *github.Host\n\tif owner == \"\" {\n\t\tconfig := github.CurrentConfig()\n\t\th, err := config.DefaultHost()\n\t\tif err != nil {\n\t\t\tutils.Check(github.FormatError(\"cloning repository\", err))\n\t\t}\n\n\t\thost = h\n\t\towner = host.User\n\t}\n\n\tvar hostStr string\n\tif host != nil {\n\t\thostStr = host.Host\n\t}\n\n\texpectWiki := strings.HasSuffix(name, \".wiki\")\n\tif expectWiki {\n\t\tname = strings.TrimSuffix(name, \".wiki\")\n\t}\n\n\tproject := github.NewProject(owner, name, hostStr)\n\tgh := github.NewClient(project.Host)\n\trepo, err := gh.Repository(project)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"HTTP 404\") {\n\t\t\terr = fmt.Errorf(\"Error: repository %s/%s doesn't exist\", project.Owner, project.Name)\n\t\t}\n\t\tutils.Check(err)\n\t}\n\n\towner = repo.Owner.Login\n\tname = repo.Name\n\tif expectWiki {\n\t\tif !repo.HasWiki {\n\t\t\tutils.Check(fmt.Errorf(\"Error: %s/%s doesn't have a wiki\", owner, name))\n\t\t} else {\n\t\t\tname = name + \".wiki\"\n\t\t}\n\t}\n\n\tif !allowPush && allowPrivate {\n\t\tallowPush = repo.Private || repo.Permissions.Push\n\t}\n\n\treturn project.GitURL(name, owner, allowPush)\n}\n"
  },
  {
    "path": "commands/commands.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar (\n\tNameRe          = `[\\w.-]+`\n\tOwnerRe         = \"[a-zA-Z0-9][a-zA-Z0-9-]*\"\n\tNameWithOwnerRe = fmt.Sprintf(`^(%s/)?%s$`, OwnerRe, NameRe)\n\n\tCmdRunner = NewRunner()\n)\n\ntype Command struct {\n\tRun func(cmd *Command, args *Args)\n\n\tKey          string\n\tUsage        string\n\tLong         string\n\tKnownFlags   string\n\tGitExtension bool\n\n\tsubCommands   map[string]*Command\n\tparentCommand *Command\n}\n\nfunc (c *Command) Call(args *Args) (err error) {\n\trunCommand, err := c.lookupSubCommand(args)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif !c.GitExtension {\n\t\terr = runCommand.parseArguments(args)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\trunCommand.Run(runCommand, args)\n\n\treturn\n}\n\ntype ErrHelp struct {\n\terr string\n}\n\nfunc (e ErrHelp) Error() string {\n\treturn e.err\n}\n\nfunc (c *Command) parseArguments(args *Args) error {\n\tknownFlags := c.KnownFlags\n\tif knownFlags == \"\" {\n\t\tknownFlags = c.Long\n\t}\n\targs.Flag = utils.NewArgsParserWithUsage(\"-h, --help\\n\" + knownFlags)\n\n\trest, err := args.Flag.Parse(args.Params)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%s\\n%s\", err, c.Synopsis())\n\t}\n\tif args.Flag.Bool(\"--help\") {\n\t\treturn &ErrHelp{err: c.Synopsis()}\n\t}\n\targs.Params = rest\n\targs.Terminator = args.Flag.HasTerminated\n\treturn nil\n}\n\nfunc (c *Command) Use(subCommand *Command) {\n\tif c.subCommands == nil {\n\t\tc.subCommands = make(map[string]*Command)\n\t}\n\tc.subCommands[subCommand.Name()] = subCommand\n\tsubCommand.parentCommand = c\n}\n\nfunc (c *Command) UsageError(msg string) error {\n\tnl := \"\"\n\tif msg != \"\" {\n\t\tnl = \"\\n\"\n\t}\n\treturn fmt.Errorf(\"%s%s%s\", msg, nl, c.Synopsis())\n}\n\nfunc (c *Command) Synopsis() string {\n\tlines := []string{}\n\tusagePrefix := \"Usage:\"\n\tusageStr := c.Usage\n\tif usageStr == \"\" && c.parentCommand != nil {\n\t\tusageStr = c.parentCommand.Usage\n\t}\n\n\tfor _, line := range strings.Split(usageStr, \"\\n\") {\n\t\tif line != \"\" {\n\t\t\tusage := fmt.Sprintf(\"%s hub %s\", usagePrefix, line)\n\t\t\tusagePrefix = \"      \"\n\t\t\tlines = append(lines, usage)\n\t\t}\n\t}\n\treturn strings.Join(lines, \"\\n\")\n}\n\nfunc (c *Command) HelpText() string {\n\tusage := strings.Replace(c.Usage, \"-^\", \"`-^`\", 1)\n\tusageRe := regexp.MustCompile(`(?m)^([a-z-]+)(.*)$`)\n\tusage = usageRe.ReplaceAllString(usage, \"`hub $1`$2  \")\n\tusage = strings.TrimSpace(usage)\n\n\tvar desc string\n\tlong := strings.TrimSpace(c.Long)\n\tif lines := strings.Split(long, \"\\n\"); len(lines) > 1 {\n\t\tdesc = lines[0]\n\t\tlong = strings.Join(lines[1:], \"\\n\")\n\t}\n\n\tlong = strings.Replace(long, \"''\", \"`\", -1)\n\theadingRe := regexp.MustCompile(`(?m)^(## .+):$`)\n\tlong = headingRe.ReplaceAllString(long, \"$1\")\n\n\tindentRe := regexp.MustCompile(`(?m)^\\t`)\n\tlong = indentRe.ReplaceAllLiteralString(long, \"\")\n\tdefinitionListRe := regexp.MustCompile(`(?m)^(\\* )?([^#\\s][^\\n]*?):?\\n\\t`)\n\tlong = definitionListRe.ReplaceAllString(long, \"$2\\n:\\t\")\n\n\treturn fmt.Sprintf(\"hub-%s(1) -- %s\\n===\\n\\n## Synopsis\\n\\n%s\\n%s\", c.Name(), desc, usage, long)\n}\n\nfunc (c *Command) Name() string {\n\tif c.Key != \"\" {\n\t\treturn c.Key\n\t}\n\tusageLine := strings.Split(strings.TrimSpace(c.Usage), \"\\n\")[0]\n\treturn strings.Split(usageLine, \" \")[0]\n}\n\nfunc (c *Command) Runnable() bool {\n\treturn c.Run != nil\n}\n\nfunc (c *Command) lookupSubCommand(args *Args) (runCommand *Command, err error) {\n\tif len(c.subCommands) > 0 && args.HasSubcommand() {\n\t\tsubCommandName := args.FirstParam()\n\t\tif subCommand, ok := c.subCommands[subCommandName]; ok {\n\t\t\trunCommand = subCommand\n\t\t\targs.Params = args.Params[1:]\n\t\t} else {\n\t\t\terr = fmt.Errorf(\"error: Unknown subcommand: %s\", subCommandName)\n\t\t}\n\t} else {\n\t\trunCommand = c\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "commands/commands_test.go",
    "content": "package commands\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/internal/assert\"\n\t\"github.com/github/hub/v2/ui\"\n)\n\nfunc TestMain(m *testing.M) {\n\tui.Default = ui.Console{Stdout: ioutil.Discard, Stderr: ioutil.Discard}\n\tos.Exit(m.Run())\n}\n\nfunc TestCommandUseSelf(t *testing.T) {\n\tc := &Command{Usage: \"foo\"}\n\n\targs := NewArgs([]string{\"foo\"})\n\n\trun, err := c.lookupSubCommand(args)\n\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, c, run)\n}\n\nfunc TestCommandUseSubcommand(t *testing.T) {\n\tc := &Command{Usage: \"foo\"}\n\ts := &Command{Usage: \"bar\"}\n\tc.Use(s)\n\n\targs := NewArgs([]string{\"foo\", \"bar\"})\n\n\trun, err := c.lookupSubCommand(args)\n\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, s, run)\n}\n\nfunc TestCommandUseErrorWhenMissingSubcommand(t *testing.T) {\n\tc := &Command{Usage: \"foo\"}\n\ts := &Command{Usage: \"bar\"}\n\tc.Use(s)\n\n\targs := NewArgs([]string{\"foo\", \"baz\"})\n\n\t_, err := c.lookupSubCommand(args)\n\n\tassert.NotEqual(t, nil, err)\n}\n\nfunc TestArgsForCommand(t *testing.T) {\n\tc := &Command{Usage: \"foo\"}\n\n\targs := NewArgs([]string{\"foo\", \"bar\", \"baz\"})\n\n\tc.lookupSubCommand(args)\n\n\tassert.Equal(t, 2, len(args.Params))\n}\n\nfunc TestArgsForSubCommand(t *testing.T) {\n\tc := &Command{Usage: \"foo\"}\n\ts := &Command{Usage: \"bar\"}\n\tc.Use(s)\n\n\targs := NewArgs([]string{\"foo\", \"bar\", \"baz\"})\n\n\tc.lookupSubCommand(args)\n\n\tassert.Equal(t, 1, len(args.Params))\n}\n\nfunc TestFlagsAfterArguments(t *testing.T) {\n\tc := &Command{Long: \"-m, --message MSG\"}\n\n\targs := NewArgs([]string{\"foo\", \"bar\", \"-m\", \"baz\"})\n\terr := c.parseArguments(args)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"baz\", args.Flag.Value(\"--message\"))\n\tassert.Equal(t, 1, len(args.Params))\n\tassert.Equal(t, \"bar\", args.LastParam())\n}\n\nfunc TestCommandNameTakeKey(t *testing.T) {\n\tc := &Command{Key: \"bar\", Usage: \"foo -t -v --foo\"}\n\tassert.Equal(t, \"bar\", c.Name())\n}\n\nfunc TestCommandCall(t *testing.T) {\n\tvar result string\n\tf := func(c *Command, args *Args) { result = args.FirstParam() }\n\n\tc := &Command{Usage: \"foo\", Run: f}\n\targs := NewArgs([]string{\"foo\", \"bar\"})\n\n\tc.Call(args)\n\tassert.Equal(t, \"bar\", result)\n}\n\nfunc TestCommandHelp(t *testing.T) {\n\tvar result string\n\tf := func(c *Command, args *Args) { result = args.FirstParam() }\n\tc := &Command{Usage: \"foo\", Run: f}\n\targs := NewArgs([]string{\"foo\", \"-h\"})\n\n\tc.Call(args)\n\tassert.Equal(t, \"\", result)\n}\n\nfunc TestSubCommandCall(t *testing.T) {\n\tvar result string\n\tf1 := func(c *Command, args *Args) { result = \"noop\" }\n\tf2 := func(c *Command, args *Args) { result = args.LastParam() }\n\n\tc := &Command{Usage: \"foo\", Run: f1}\n\ts := &Command{Key: \"bar\", Usage: \"foo bar\", Run: f2}\n\tc.Use(s)\n\n\targs := NewArgs([]string{\"foo\", \"bar\", \"baz\"})\n\n\tc.Call(args)\n\tassert.Equal(t, \"baz\", result)\n}\n\nfunc Test_NameWithOwnerRe(t *testing.T) {\n\tre := regexp.MustCompile(NameWithOwnerRe)\n\n\tassert.Equal(t, true, re.MatchString(\"o/n\"))\n\tassert.Equal(t, true, re.MatchString(\"own-er/my-project.git\"))\n\tassert.Equal(t, true, re.MatchString(\"my-project.git\"))\n\tassert.Equal(t, true, re.MatchString(\"my_project\"))\n\tassert.Equal(t, true, re.MatchString(\"-dash\"))\n\tassert.Equal(t, true, re.MatchString(\".dotfiles\"))\n\n\tassert.Equal(t, false, re.MatchString(\"\"))\n\tassert.Equal(t, false, re.MatchString(\"/\"))\n\tassert.Equal(t, false, re.MatchString(\" \"))\n\tassert.Equal(t, false, re.MatchString(\"owner/na me\"))\n\tassert.Equal(t, false, re.MatchString(\"owner/na/me\"))\n\tassert.Equal(t, false, re.MatchString(\"own.er/name\"))\n\tassert.Equal(t, false, re.MatchString(\"own_er/name\"))\n\tassert.Equal(t, false, re.MatchString(\"-owner/name\"))\n}\n"
  },
  {
    "path": "commands/compare.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdCompare = &Command{\n\tRun: compare,\n\tUsage: `\ncompare [-uc] [-b <BASE>]\ncompare [-uc] [<OWNER>] [<BASE>...]<HEAD>\n`,\n\tLong: `Open a GitHub compare page in a web browser.\n\n## Options:\n\t-u, --url\n\t\tPrint the URL instead of opening it.\n\n\t-c, --copy\n\t\tPut the URL to clipboard instead of opening it.\n\n\t-b, --base <BASE>\n\t\tBase branch to compare against in case no explicit arguments were given.\n\n\t[<BASE>...]<HEAD>\n\t\tBranch names, tag names, or commit SHAs specifying the range to compare.\n\t\tIf a range with two dots (''A..B'') is given, it will be transformed into a\n\t\trange with three dots.\n\n\t\tThe <BASE> portion defaults to the default branch of the repository.\n\n\t\tThe <HEAD> argument defaults to the current branch. If the current branch\n\t\tis not pushed to a remote, the command will error.\n\n\t<OWNER>\n\t\tOptionally specify the owner of the repository for the compare page URL.\n\n## Examples:\n\t\t$ hub compare\n\t\t> open https://github.com/OWNER/REPO/compare/BRANCH\n\n\t\t$ hub compare refactor\n\t\t> open https://github.com/OWNER/REPO/compare/refactor\n\n\t\t$ hub compare v1.0..v1.1\n\t\t> open https://github.com/OWNER/REPO/compare/v1.0...v1.1\n\n\t\t$ hub compare -u jingweno feature\n\t\thttps://github.com/jingweno/REPO/compare/feature\n\n## See also:\n\nhub-browse(1), hub(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdCompare)\n}\n\nfunc compare(command *Command, args *Args) {\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tmainProject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\thost, err := github.CurrentConfig().PromptForHost(mainProject.Host)\n\tutils.Check(err)\n\n\tvar r string\n\tflagCompareBase := args.Flag.Value(\"--base\")\n\n\tif args.IsParamsEmpty() {\n\t\tcurrentBranch, err := localRepo.CurrentBranch()\n\t\tif err != nil {\n\t\t\tutils.Check(command.UsageError(err.Error()))\n\t\t}\n\n\t\tvar remoteBranch *github.Branch\n\t\tvar remoteProject *github.Project\n\n\t\tremoteBranch, remoteProject, err = findPushTarget(currentBranch)\n\t\tif err != nil {\n\t\t\tif remoteProject, err = deducePushTarget(currentBranch, host.User); err == nil {\n\t\t\t\tremoteBranch = currentBranch\n\t\t\t} else {\n\t\t\t\tutils.Check(fmt.Errorf(\"the current branch '%s' doesn't seem pushed to a remote\", currentBranch.ShortName()))\n\t\t\t}\n\t\t}\n\n\t\tr = remoteBranch.ShortName()\n\t\tif remoteProject.SameAs(mainProject) {\n\t\t\tif flagCompareBase == \"\" && remoteBranch.IsMaster() {\n\t\t\t\tutils.Check(fmt.Errorf(\"the branch to compare '%s' is the default branch\", remoteBranch.ShortName()))\n\t\t\t}\n\t\t} else {\n\t\t\tr = fmt.Sprintf(\"%s:%s\", remoteProject.Owner, r)\n\t\t}\n\n\t\tif flagCompareBase == r {\n\t\t\tutils.Check(fmt.Errorf(\"the branch to compare '%s' is the same as --base\", r))\n\t\t} else if flagCompareBase != \"\" {\n\t\t\tr = fmt.Sprintf(\"%s...%s\", flagCompareBase, r)\n\t\t}\n\t} else {\n\t\tif flagCompareBase != \"\" {\n\t\t\tutils.Check(command.UsageError(\"\"))\n\t\t} else {\n\t\t\tr = parseCompareRange(args.RemoveParam(args.ParamsSize() - 1))\n\t\t\tif !args.IsParamsEmpty() {\n\t\t\t\towner := args.RemoveParam(args.ParamsSize() - 1)\n\t\t\t\tmainProject = github.NewProject(owner, mainProject.Name, mainProject.Host)\n\t\t\t}\n\t\t}\n\t}\n\n\turl := mainProject.WebURL(\"\", \"\", \"compare/\"+rangeQueryEscape(r))\n\n\targs.NoForward()\n\tflagCompareURLOnly := args.Flag.Bool(\"--url\")\n\tflagCompareCopy := args.Flag.Bool(\"--copy\")\n\tprintBrowseOrCopy(args, url, !flagCompareURLOnly && !flagCompareCopy, flagCompareCopy)\n}\n\nfunc parseCompareRange(r string) string {\n\tshaOrTag := fmt.Sprintf(\"((?:%s:)?\\\\w(?:[\\\\w/.-]*\\\\w)?)\", OwnerRe)\n\tshaOrTagRange := fmt.Sprintf(\"^%s\\\\.\\\\.%s$\", shaOrTag, shaOrTag)\n\tshaOrTagRangeRegexp := regexp.MustCompile(shaOrTagRange)\n\treturn shaOrTagRangeRegexp.ReplaceAllString(r, \"$1...$2\")\n}\n\n// characters we want to allow unencoded in compare views\nvar compareUnescaper = strings.NewReplacer(\n\t\"%2F\", \"/\",\n\t\"%3A\", \":\",\n\t\"%5E\", \"^\",\n\t\"%7E\", \"~\",\n\t\"%2A\", \"*\",\n\t\"%21\", \"!\",\n)\n\nfunc rangeQueryEscape(r string) string {\n\tif strings.Contains(r, \"..\") {\n\t\treturn r\n\t}\n\treturn compareUnescaper.Replace(url.QueryEscape(r))\n}\n"
  },
  {
    "path": "commands/compare_test.go",
    "content": "package commands\n\nimport (\n\t\"github.com/github/hub/v2/internal/assert\"\n\t\"testing\"\n)\n\nfunc TestParseRange(t *testing.T) {\n\ts := \"1.0..2.0\"\n\tassert.Equal(t, \"1.0...2.0\", parseCompareRange(s))\n\n\ts = \"1.0...2.0\"\n\tassert.Equal(t, \"1.0...2.0\", parseCompareRange(s))\n}\n"
  },
  {
    "path": "commands/create.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/git\"\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdCreate = &Command{\n\tRun:   create,\n\tUsage: \"create [-poc] [-d <DESCRIPTION>] [-h <HOMEPAGE>] [[<ORGANIZATION>/]<NAME>]\",\n\tLong: `Create a new repository on GitHub and add a git remote for it.\n\n## Options:\n\t-p, --private\n\t\tCreate a private repository.\n\n\t-d, --description <DESCRIPTION>\n\t\tA short description of the GitHub repository.\n\n\t-h, --homepage <HOMEPAGE>\n\t\tA URL with more information about the repository. Use this, for example, if\n\t\tyour project has an external website.\n\n\t--remote-name <REMOTE>\n\t\tSet the name for the new git remote (default: \"origin\").\n\n\t-o, --browse\n\t\tOpen the new repository in a web browser.\n\n\t-c, --copy\n\t\tPut the URL of the new repository to clipboard instead of printing it.\n\n\t[<ORGANIZATION>/]<NAME>\n\t\tThe name for the repository on GitHub (default: name of the current working\n\t\tdirectory).\n\n\t\tOptionally, create the repository within <ORGANIZATION>.\n\n## Examples:\n\t\t$ hub create\n\t\t[ repo created on GitHub ]\n\t\t> git remote add -f origin git@github.com:USER/REPO.git\n\n\t\t$ hub create sinatra/recipes\n\t\t[ repo created in GitHub organization ]\n\t\t> git remote add -f origin git@github.com:sinatra/recipes.git\n\n## See also:\n\nhub-init(1), hub(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdCreate)\n}\n\nfunc create(command *Command, args *Args) {\n\t_, err := git.Dir()\n\tif err != nil {\n\t\terr = fmt.Errorf(\"'create' must be run from inside a git repository\")\n\t\tutils.Check(err)\n\t}\n\n\tvar newRepoName string\n\tif args.IsParamsEmpty() {\n\t\tdirName, err := git.WorkdirName()\n\t\tutils.Check(err)\n\t\tnewRepoName = github.SanitizeProjectName(dirName)\n\t} else {\n\t\tnewRepoName = args.FirstParam()\n\t\tif newRepoName == \"\" {\n\t\t\tutils.Check(command.UsageError(\"\"))\n\t\t}\n\t}\n\n\tconfig := github.CurrentConfig()\n\thost, err := config.DefaultHost()\n\tif err != nil {\n\t\tutils.Check(github.FormatError(\"creating repository\", err))\n\t}\n\n\towner := host.User\n\tif strings.Contains(newRepoName, \"/\") {\n\t\tsplit := strings.SplitN(newRepoName, \"/\", 2)\n\t\towner = split[0]\n\t\tnewRepoName = split[1]\n\t}\n\n\tproject := github.NewProject(owner, newRepoName, host.Host)\n\tgh := github.NewClient(project.Host)\n\n\tflagCreatePrivate := args.Flag.Bool(\"--private\")\n\n\trepo, err := gh.Repository(project)\n\tif err == nil {\n\t\tfoundProject := github.NewProject(repo.FullName, \"\", project.Host)\n\t\tif foundProject.SameAs(project) {\n\t\t\tif !repo.Private && flagCreatePrivate {\n\t\t\t\terr = fmt.Errorf(\"Repository '%s' already exists and is public\", repo.FullName)\n\t\t\t\tutils.Check(err)\n\t\t\t} else {\n\t\t\t\tui.Errorln(\"Existing repository detected\")\n\t\t\t\tproject = foundProject\n\t\t\t}\n\t\t} else {\n\t\t\trepo = nil\n\t\t}\n\t} else {\n\t\trepo = nil\n\t}\n\n\tif repo == nil {\n\t\tif !args.Noop {\n\t\t\tflagCreateDescription := args.Flag.Value(\"--description\")\n\t\t\tflagCreateHomepage := args.Flag.Value(\"--homepage\")\n\t\t\trepo, err := gh.CreateRepository(project, flagCreateDescription, flagCreateHomepage, flagCreatePrivate)\n\t\t\tutils.Check(err)\n\t\t\tproject = github.NewProject(repo.FullName, \"\", project.Host)\n\t\t}\n\t}\n\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\toriginName := args.Flag.Value(\"--remote-name\")\n\tif originName == \"\" {\n\t\toriginName = \"origin\"\n\t}\n\n\tif originRemote, err := localRepo.RemoteByName(originName); err == nil {\n\t\toriginProject, err := originRemote.Project()\n\t\tif err != nil || !originProject.SameAs(project) {\n\t\t\tui.Errorf(\"A git remote named '%s' already exists and is set to push to '%s'.\\n\", originRemote.Name, originRemote.PushURL)\n\t\t}\n\t} else {\n\t\turl := project.GitURL(\"\", \"\", true)\n\t\targs.Before(\"git\", \"remote\", \"add\", \"-f\", originName, url)\n\t}\n\n\twebURL := project.WebURL(\"\", \"\", \"\")\n\targs.NoForward()\n\tflagCreateBrowse := args.Flag.Bool(\"--browse\")\n\tflagCreateCopy := args.Flag.Bool(\"--copy\")\n\tprintBrowseOrCopy(args, webURL, flagCreateBrowse, flagCreateCopy)\n}\n"
  },
  {
    "path": "commands/delete.go",
    "content": "package commands\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdDelete = &Command{\n\tRun:   deleteRepo,\n\tUsage: \"delete [-y] [<ORGANIZATION>/]<NAME>\",\n\tLong: `Delete an existing repository on GitHub.\n\n## Options:\n\n\t-y, --yes\n\t\tSkip the confirmation prompt and immediately delete the repository.\n\n\t[<ORGANIZATION>/]<NAME>\n\t\tThe name for the repository on GitHub.\n\n## Examples:\n\t\t$ hub delete recipes\n\t\t[ personal repo deleted on GitHub ]\n\n\t\t$ hub delete sinatra/recipes\n\t\t[ repo deleted in GitHub organization ]\n\n## See also:\n\nhub-init(1), hub(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdDelete)\n}\n\nfunc deleteRepo(command *Command, args *Args) {\n\tvar repoName string\n\tif !args.IsParamsEmpty() {\n\t\trepoName = args.FirstParam()\n\t}\n\n\tre := regexp.MustCompile(NameWithOwnerRe)\n\tif !re.MatchString(repoName) {\n\t\tutils.Check(command.UsageError(\"\"))\n\t}\n\n\tconfig := github.CurrentConfig()\n\thost, err := config.DefaultHost()\n\tif err != nil {\n\t\tutils.Check(github.FormatError(\"deleting repository\", err))\n\t}\n\n\towner := host.User\n\tif strings.Contains(repoName, \"/\") {\n\t\tsplit := strings.SplitN(repoName, \"/\", 2)\n\t\towner, repoName = split[0], split[1]\n\t}\n\n\tproject := github.NewProject(owner, repoName, host.Host)\n\tgh := github.NewClient(project.Host)\n\n\tif !args.Flag.Bool(\"--yes\") {\n\t\tui.Printf(\"Really delete repository '%s' (yes/N)? \", project)\n\t\tanswer := \"\"\n\t\tscanner := bufio.NewScanner(os.Stdin)\n\t\tif scanner.Scan() {\n\t\t\tanswer = strings.TrimSpace(scanner.Text())\n\t\t}\n\t\tutils.Check(scanner.Err())\n\t\tif answer != \"yes\" {\n\t\t\tutils.Check(fmt.Errorf(\"Please type 'yes' for confirmation.\"))\n\t\t}\n\t}\n\n\tif args.Noop {\n\t\tui.Printf(\"Would delete repository '%s'.\\n\", project)\n\t} else {\n\t\terr = gh.DeleteRepository(project)\n\t\tif err != nil && strings.Contains(err.Error(), \"HTTP 403\") {\n\t\t\tui.Errorf(\"Please edit the token used for hub at https://%s/settings/tokens\\n\", project.Host)\n\t\t\tui.Errorln(\"and verify that the `delete_repo` scope is enabled.\")\n\t\t}\n\t\tutils.Check(err)\n\t\tui.Printf(\"Deleted repository '%s'.\\n\", project)\n\t}\n\n\targs.NoForward()\n}\n"
  },
  {
    "path": "commands/fetch.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdFetch = &Command{\n\tRun:          fetch,\n\tGitExtension: true,\n\tUsage:        \"fetch <USER>[,<USER2>...]\",\n\tLong: `Add missing remotes prior to performing git fetch.\n\n## Examples:\n\t\t$ hub fetch --multiple jingweno mislav\n\t\t> git remote add jingweno git://github.com/jingweno/REPO.git\n\t\t> git remote add mislav git://github.com/mislav/REPO.git\n\t\t> git fetch jingweno\n\t\t> git fetch mislav\n\n## See also:\n\nhub-remote(1), hub(1), git-fetch(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdFetch)\n}\n\nfunc fetch(command *Command, args *Args) {\n\tif !args.IsParamsEmpty() {\n\t\terr := transformFetchArgs(args)\n\t\tutils.Check(err)\n\t}\n}\n\nfunc transformFetchArgs(args *Args) error {\n\tnames := parseRemoteNames(args)\n\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tcurrentProject, currentProjectErr := localRepo.CurrentProject()\n\n\tprojects := make(map[*github.Project]bool)\n\townerRegexp := regexp.MustCompile(fmt.Sprintf(\"^%s$\", OwnerRe))\n\tfor _, name := range names {\n\t\tif ownerRegexp.MatchString(name) && !isCloneable(name) {\n\t\t\t_, err := localRepo.RemoteByName(name)\n\t\t\tif err != nil {\n\t\t\t\tutils.Check(currentProjectErr)\n\t\t\t\tproject := github.NewProject(name, currentProject.Name, \"\")\n\t\t\t\tgh := github.NewClient(project.Host)\n\t\t\t\trepo, err := gh.Repository(project)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tprojects[project] = repo.Private || repo.Permissions.Push\n\t\t\t}\n\t\t}\n\t}\n\n\tfor project, private := range projects {\n\t\targs.Before(\"git\", \"remote\", \"add\", project.Owner, project.GitURL(\"\", \"\", private))\n\t}\n\n\treturn nil\n}\n\nfunc parseRemoteNames(args *Args) (names []string) {\n\twords := args.Words()\n\tif i := args.IndexOfParam(\"--multiple\"); i != -1 {\n\t\tif args.ParamsSize() > 1 {\n\t\t\tnames = words\n\t\t}\n\t} else if len(words) > 0 {\n\t\tremoteName := words[0]\n\t\tcommaPattern := fmt.Sprintf(\"^%s(,%s)+$\", OwnerRe, OwnerRe)\n\t\tremoteNameRegexp := regexp.MustCompile(commaPattern)\n\t\tif remoteNameRegexp.MatchString(remoteName) {\n\t\t\ti := args.IndexOfParam(remoteName)\n\t\t\targs.RemoveParam(i)\n\t\t\tnames = strings.Split(remoteName, \",\")\n\t\t\targs.InsertParam(i, names...)\n\t\t\targs.InsertParam(i, \"--multiple\")\n\t\t} else {\n\t\t\tnames = append(names, remoteName)\n\t\t}\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "commands/fetch_test.go",
    "content": "package commands\n\nimport (\n\t\"github.com/github/hub/v2/internal/assert\"\n\t\"testing\"\n)\n\nfunc TestParseRemoteNames(t *testing.T) {\n\targs := NewArgs([]string{\"fetch\", \"jingweno,foo\"})\n\tnames := parseRemoteNames(args)\n\n\tassert.Equal(t, 2, len(names))\n\tassert.Equal(t, \"jingweno\", names[0])\n\tassert.Equal(t, \"foo\", names[1])\n\tcmd := args.ToCmd()\n\tassert.Equal(t, \"git fetch --multiple jingweno foo\", cmd.String())\n\n\targs = NewArgs([]string{\"fetch\", \"--multiple\", \"jingweno\", \"foo\"})\n\tnames = parseRemoteNames(args)\n\tassert.Equal(t, 2, len(names))\n\tassert.Equal(t, \"jingweno\", names[0])\n\tassert.Equal(t, \"foo\", names[1])\n\n\targs = NewArgs([]string{\"fetch\", \"--multiple\"})\n\tnames = parseRemoteNames(args)\n\tassert.Equal(t, 0, len(names))\n}\n"
  },
  {
    "path": "commands/fork.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdFork = &Command{\n\tRun:   fork,\n\tUsage: \"fork [--no-remote] [--remote-name <REMOTE>] [--org <ORGANIZATION>]\",\n\tLong: `Fork the current repository on GitHub and add a git remote for it.\n\n## Options:\n\t--no-remote\n\t\tSkip adding a git remote for the fork.\n\n\t--remote-name <REMOTE>\n\t\tSet the name for the new git remote.\n\n\t--org <ORGANIZATION>\n\t\tFork the repository within this organization.\n\n## Examples:\n\t\t$ hub fork\n\t\t[ repo forked on GitHub ]\n\t\t> git remote add -f USER git@github.com:USER/REPO.git\n\n\t\t$ hub fork --org=ORGANIZATION\n\t\t[ repo forked on GitHub into the ORGANIZATION organization]\n\t\t> git remote add -f ORGANIZATION git@github.com:ORGANIZATION/REPO.git\n\n## See also:\n\nhub-clone(1), hub(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdFork)\n}\n\nfunc fork(cmd *Command, args *Args) {\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\tconfig := github.CurrentConfig()\n\thost, err := config.PromptForHost(project.Host)\n\tutils.Check(github.FormatError(\"forking repository\", err))\n\n\tparams := map[string]interface{}{}\n\tforkOwner := host.User\n\tif flagForkOrganization := args.Flag.Value(\"--org\"); flagForkOrganization != \"\" {\n\t\tforkOwner = flagForkOrganization\n\t\tparams[\"organization\"] = forkOwner\n\t}\n\n\tforkProject := github.NewProject(forkOwner, project.Name, project.Host)\n\tvar newRemoteName string\n\tif flagForkRemoteName := args.Flag.Value(\"--remote-name\"); flagForkRemoteName != \"\" {\n\t\tnewRemoteName = flagForkRemoteName\n\t} else {\n\t\tnewRemoteName = forkProject.Owner\n\t}\n\n\tclient := github.NewClient(project.Host)\n\texistingRepo, err := client.Repository(forkProject)\n\tif err == nil {\n\t\texistingProject, err := github.NewProjectFromRepo(existingRepo)\n\t\tif err == nil && !existingProject.SameAs(forkProject) {\n\t\t\texistingRepo = nil\n\t\t}\n\t}\n\tif err == nil && existingRepo != nil {\n\t\tvar parentURL *github.URL\n\t\tif parent := existingRepo.Parent; parent != nil {\n\t\t\tparentURL, _ = github.ParseURL(parent.HTMLURL)\n\t\t}\n\t\tif parentURL == nil || !project.SameAs(parentURL.Project) {\n\t\t\terr = fmt.Errorf(\"Error creating fork: %s already exists on %s\",\n\t\t\t\tforkProject, forkProject.Host)\n\t\t\tutils.Check(err)\n\t\t}\n\t} else {\n\t\tif !args.Noop {\n\t\t\tnewRepo, err := client.ForkRepository(project, params)\n\t\t\tutils.Check(err)\n\t\t\tforkProject.Owner = newRepo.Owner.Login\n\t\t\tforkProject.Name = newRepo.Name\n\t\t}\n\t}\n\n\targs.NoForward()\n\tif !args.Flag.Bool(\"--no-remote\") {\n\t\toriginURL := project.GitURL(\"\", \"\", false)\n\t\turl := forkProject.GitURL(\"\", \"\", true)\n\n\t\t// Check to see if the remote already exists.\n\t\tcurrentRemote, err := localRepo.RemoteByName(newRemoteName)\n\t\tif err == nil {\n\t\t\tcurrentProject, err := currentRemote.Project()\n\t\t\tif err == nil {\n\t\t\t\tif currentProject.SameAs(forkProject) {\n\t\t\t\t\tui.Printf(\"existing remote: %s\\n\", newRemoteName)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif newRemoteName == \"origin\" {\n\t\t\t\t\t// Assume user wants to follow github guides for collaboration\n\t\t\t\t\tui.Printf(\"renaming existing \\\"origin\\\" remote to \\\"upstream\\\"\\n\")\n\t\t\t\t\targs.Before(\"git\", \"remote\", \"rename\", \"origin\", \"upstream\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\targs.Before(\"git\", \"remote\", \"add\", \"-f\", newRemoteName, originURL)\n\t\targs.Before(\"git\", \"remote\", \"set-url\", newRemoteName, url)\n\n\t\targs.AfterFn(func() error {\n\t\t\tui.Printf(\"new remote: %s\\n\", newRemoteName)\n\t\t\treturn nil\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "commands/gist.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar (\n\tcmdGist = &Command{\n\t\tRun: printGistHelp,\n\t\tUsage: `\ngist create [-oc] [--public] [<FILES>...]\ngist show <ID> [<FILENAME>]\n`,\n\t\tLong: `Create and print GitHub Gists\n\n## Commands:\n\n\t* _create_:\n\t\tCreate a new gist. If no <FILES> are specified, the content is read from\n\t\tstandard input.\n\n\t* _show_:\n\t\tPrint the contents of a gist. If the gist contains multiple files, the\n\t\toperation will error out unless <FILENAME> is specified.\n\n## Options:\n\n\t--public\n\t\tMake the new gist public (default: false).\n\n\t-o, --browse\n\t\tOpen the new gist in a web browser.\n\n\t-c, --copy\n\t\tPut the URL of the new gist to clipboard instead of printing it.\n\n## Examples:\n\n    $ echo hello | hub gist create --public\n\n    $ hub gist create file1.txt file2.txt\n\n    # print a specific file within a gist:\n    $ hub gist show ID testfile1.txt\n\n## See also:\n\nhub(1), hub-api(1)\n`,\n\t}\n\n\tcmdShowGist = &Command{\n\t\tKey: \"show\",\n\t\tRun: showGist,\n\t}\n\n\tcmdCreateGist = &Command{\n\t\tKey: \"create\",\n\t\tRun: createGist,\n\t\tKnownFlags: `\n\t\t--public\n\t\t-o, --browse\n\t\t-c, --copy\n`,\n\t}\n)\n\nfunc init() {\n\tcmdGist.Use(cmdShowGist)\n\tcmdGist.Use(cmdCreateGist)\n\tCmdRunner.Use(cmdGist)\n}\n\nfunc getGist(gh *github.Client, id string, filename string) error {\n\tgist, err := gh.FetchGist(id)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(gist.Files) > 1 && filename == \"\" {\n\t\tfilenames := []string{}\n\t\tfor name := range gist.Files {\n\t\t\tfilenames = append(filenames, name)\n\t\t}\n\t\tsort.Strings(filenames)\n\t\treturn fmt.Errorf(\"This gist contains multiple files, you must specify one:\\n  %s\", strings.Join(filenames, \"\\n  \"))\n\t}\n\n\tif filename != \"\" {\n\t\tif val, ok := gist.Files[filename]; ok {\n\t\t\tui.Println(val.Content)\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"no such file in gist\")\n\t\t}\n\t} else {\n\t\tfor name := range gist.Files {\n\t\t\tfile := gist.Files[name]\n\t\t\tui.Println(file.Content)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc printGistHelp(command *Command, args *Args) {\n\tutils.Check(command.UsageError(\"\"))\n}\n\nfunc createGist(cmd *Command, args *Args) {\n\targs.NoForward()\n\n\thost, err := github.CurrentConfig().DefaultHostNoPrompt()\n\tutils.Check(err)\n\tgh := github.NewClient(host.Host)\n\n\tfilenames := []string{}\n\tif args.IsParamsEmpty() {\n\t\tfilenames = append(filenames, \"-\")\n\t} else {\n\t\tfilenames = args.Params\n\t}\n\n\tvar gist *github.Gist\n\tif args.Noop {\n\t\tui.Println(\"Would create gist\")\n\t\tgist = &github.Gist{\n\t\t\tHTMLURL: fmt.Sprintf(\"https://gist.%s/%s\", gh.Host.Host, \"ID\"),\n\t\t}\n\t} else {\n\t\tgist, err = gh.CreateGist(filenames, args.Flag.Bool(\"--public\"))\n\t\tutils.Check(err)\n\t}\n\n\tflagIssueBrowse := args.Flag.Bool(\"--browse\")\n\tflagIssueCopy := args.Flag.Bool(\"--copy\")\n\tprintBrowseOrCopy(args, gist.HTMLURL, flagIssueBrowse, flagIssueCopy)\n}\n\nfunc showGist(cmd *Command, args *Args) {\n\targs.NoForward()\n\n\tif args.ParamsSize() < 1 {\n\t\tutils.Check(cmd.UsageError(\"you must specify a gist ID\"))\n\t}\n\n\thost, err := github.CurrentConfig().DefaultHostNoPrompt()\n\tutils.Check(err)\n\tgh := github.NewClient(host.Host)\n\n\tid := args.GetParam(0)\n\tfilename := \"\"\n\tif args.ParamsSize() > 1 {\n\t\tfilename = args.GetParam(1)\n\t}\n\n\terr = getGist(gh, id, filename)\n\tutils.Check(err)\n}\n"
  },
  {
    "path": "commands/help.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/git\"\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n\t\"github.com/kballard/go-shellquote\"\n)\n\nvar cmdHelp = &Command{\n\tRun:          runHelp,\n\tGitExtension: true,\n\tUsage: `\nhelp hub\nhelp <COMMAND>\nhelp hub-<COMMAND> [--plain-text]\n`,\n\tLong: `Show the help page for a command.\n\n## Options:\n\thub-<COMMAND>\n\t\tUse this format to view help for hub extensions to an existing git command.\n\n\t--plain-text\n\t\tSkip man page lookup mechanism and display raw help text.\n\n## See also:\n\nhub(1), git-help(1)\n`,\n}\n\nvar cmdListCmds = &Command{\n\tKey:          \"--list-cmds\",\n\tRun:          runListCmds,\n\tGitExtension: true,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdHelp, \"--help\")\n\tCmdRunner.Use(cmdListCmds)\n}\n\nfunc runHelp(helpCmd *Command, args *Args) {\n\tif args.IsParamsEmpty() {\n\t\targs.AfterFn(func() error {\n\t\t\tui.Println(helpText)\n\t\t\treturn nil\n\t\t})\n\t\treturn\n\t}\n\n\tp := utils.NewArgsParser()\n\tp.RegisterBool(\"--all\", \"-a\")\n\tp.RegisterBool(\"--plain-text\")\n\tp.RegisterBool(\"--man\", \"-m\")\n\tp.RegisterBool(\"--web\", \"-w\")\n\tp.Parse(args.Params)\n\n\tif p.Bool(\"--all\") {\n\t\targs.AfterFn(func() error {\n\t\t\tui.Printf(\"\\nhub custom commands\\n\\n  %s\\n\", strings.Join(customCommands(), \"  \"))\n\t\t\treturn nil\n\t\t})\n\t\treturn\n\t}\n\n\tisWeb := func() bool {\n\t\tif p.Bool(\"--web\") {\n\t\t\treturn true\n\t\t}\n\t\tif p.Bool(\"--man\") {\n\t\t\treturn false\n\t\t}\n\t\tif f, err := git.Config(\"help.format\"); err == nil {\n\t\t\treturn f == \"web\" || f == \"html\"\n\t\t}\n\t\treturn false\n\t}\n\n\tcmdName := \"\"\n\tif words := args.Words(); len(words) > 0 {\n\t\tcmdName = words[0]\n\t}\n\n\tif cmdName == \"hub\" {\n\t\terr := displayManPage(\"hub\", args, isWeb())\n\t\tutils.Check(err)\n\t\treturn\n\t}\n\n\tfoundCmd := lookupCmd(cmdName)\n\tif foundCmd == nil {\n\t\treturn\n\t}\n\n\tif p.Bool(\"--plain-text\") {\n\t\tui.Println(foundCmd.HelpText())\n\t\tos.Exit(0)\n\t}\n\n\tmanPage := fmt.Sprintf(\"hub-%s\", foundCmd.Name())\n\terr := displayManPage(manPage, args, isWeb())\n\tutils.Check(err)\n}\n\nfunc runListCmds(cmd *Command, args *Args) {\n\tlistOthers := false\n\tparts := strings.SplitN(args.Command, \"=\", 2)\n\tfor _, kind := range strings.Split(parts[1], \",\") {\n\t\tif kind == \"others\" {\n\t\t\tlistOthers = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif listOthers {\n\t\targs.AfterFn(func() error {\n\t\t\tui.Println(strings.Join(customCommands(), \"\\n\"))\n\t\t\treturn nil\n\t\t})\n\t}\n}\n\n// On systems where `man` was found, invoke:\n//   MANPATH={PREFIX}/share/man:$MANPATH man <page>\n//\n// otherwise:\n//   less -R {PREFIX}/share/man/man1/<page>.1.txt\nfunc displayManPage(manPage string, args *Args, isWeb bool) error {\n\tprogramPath, err := utils.CommandPath(args.ProgramPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif isWeb {\n\t\tmanPage += \".1.html\"\n\t\tmanFile := filepath.Join(programPath, \"..\", \"..\", \"share\", \"doc\", \"hub-doc\", manPage)\n\t\targs.Replace(args.Executable, \"web--browse\", manFile)\n\t\treturn nil\n\t}\n\n\tvar manArgs []string\n\tmanProgram, _ := utils.CommandPath(\"man\")\n\tif manProgram != \"\" {\n\t\tmanArgs = []string{manProgram}\n\t} else {\n\t\tmanPage += \".1.txt\"\n\t\tif manProgram = os.Getenv(\"PAGER\"); manProgram != \"\" {\n\t\t\tvar err error\n\t\t\tmanArgs, err = shellquote.Split(manProgram)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\tmanArgs = []string{\"less\", \"-R\"}\n\t\t}\n\t}\n\n\tenv := os.Environ()\n\tif strings.HasSuffix(manPage, \".txt\") {\n\t\tmanFile := filepath.Join(programPath, \"..\", \"..\", \"share\", \"man\", \"man1\", manPage)\n\t\tmanArgs = append(manArgs, manFile)\n\t} else {\n\t\tmanArgs = append(manArgs, manPage)\n\t\tmanPath := filepath.Join(programPath, \"..\", \"..\", \"share\", \"man\")\n\t\tenv = append(env, fmt.Sprintf(\"MANPATH=%s:%s\", manPath, os.Getenv(\"MANPATH\")))\n\t}\n\n\tc := exec.Command(manArgs[0], manArgs[1:]...)\n\tc.Stdin = os.Stdin\n\tc.Stdout = os.Stdout\n\tc.Stderr = os.Stderr\n\tc.Env = env\n\tif err := c.Run(); err != nil {\n\t\treturn err\n\t}\n\tos.Exit(0)\n\treturn nil\n}\n\nfunc lookupCmd(name string) *Command {\n\tif strings.HasPrefix(name, \"hub-\") {\n\t\treturn CmdRunner.Lookup(strings.TrimPrefix(name, \"hub-\"))\n\t}\n\tcmd := CmdRunner.Lookup(name)\n\tif cmd != nil && !cmd.GitExtension {\n\t\treturn cmd\n\t}\n\treturn nil\n}\n\nfunc customCommands() []string {\n\tcmds := []string{}\n\tfor n, c := range CmdRunner.All() {\n\t\tif !c.GitExtension && !strings.HasPrefix(n, \"--\") {\n\t\t\tcmds = append(cmds, n)\n\t\t}\n\t}\n\n\tsort.Strings(cmds)\n\n\treturn cmds\n}\n\nvar helpText = `\nThese GitHub commands are provided by hub:\n\n   api            Low-level GitHub API request interface\n   browse         Open a GitHub page in the default browser\n   ci-status      Show the status of GitHub checks for a commit\n   compare        Open a compare page on GitHub\n   create         Create this repository on GitHub and add GitHub as origin\n   delete         Delete a repository on GitHub\n   fork           Make a fork of a remote repository on GitHub and add as remote\n   gist           Make a gist\n   issue          List or create GitHub issues\n   pr             Manage GitHub pull requests\n   pull-request   Open a pull request on GitHub\n   release        List or create GitHub releases\n   sync           Fetch git objects from upstream and update branches\n`\n"
  },
  {
    "path": "commands/init.go",
    "content": "package commands\n\nimport (\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdInit = &Command{\n\tRun:          gitInit,\n\tGitExtension: true,\n\tUsage:        \"init -g\",\n\tLong: `Initialize a git repository and add a remote pointing to GitHub.\n\n## Options:\n\t-g\n\t\tAfter initializing the repository locally, add the \"origin\" remote pointing\n\t\tto \"<USER>/<REPO>\" repository on GitHub.\n\n\t\t<USER> is your GitHub username, while <REPO> is the name of the current\n\t\tworking directory.\n\n## Examples:\n\t\t$ hub init -g\n\t\t> git init\n\t\t> git remote add origin git@github.com:USER/REPO.git\n\n## See also:\n\nhub-create(1), hub(1), git-init(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdInit)\n}\n\nfunc gitInit(command *Command, args *Args) {\n\terr := transformInitArgs(args)\n\tutils.Check(err)\n}\n\nfunc transformInitArgs(args *Args) error {\n\tif !parseInitFlag(args) {\n\t\treturn nil\n\t}\n\n\tvar err error\n\tdirToInit := \".\"\n\thasValueRegexp := regexp.MustCompile(\"^--(template|separate-git-dir|shared)$\")\n\n\t// Find the first argument that isn't related to any of the init flags.\n\t// We assume this is the optional `directory` argument to git init.\n\tfor i := 0; i < args.ParamsSize(); i++ {\n\t\targ := args.Params[i]\n\t\tif hasValueRegexp.MatchString(arg) {\n\t\t\ti++\n\t\t} else if !strings.HasPrefix(arg, \"-\") {\n\t\t\tdirToInit = arg\n\t\t\tbreak\n\t\t}\n\t}\n\n\tdirToInit, err = filepath.Abs(dirToInit)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tconfig := github.CurrentConfig()\n\thost, err := config.DefaultHost()\n\tif err != nil {\n\t\tutils.Check(github.FormatError(\"initializing repository\", err))\n\t}\n\n\t// Assume that the name of the working directory is going to be the name of\n\t// the project on GitHub.\n\tprojectName := strings.Replace(filepath.Base(dirToInit), \" \", \"-\", -1)\n\tproject := github.NewProject(host.User, projectName, host.Host)\n\turl := project.GitURL(\"\", \"\", true)\n\n\taddRemote := []string{\n\t\t\"git\", \"--git-dir\", filepath.Join(dirToInit, \".git\"),\n\t\t\"remote\", \"add\", \"origin\", url,\n\t}\n\targs.After(addRemote...)\n\n\treturn nil\n}\n\nfunc parseInitFlag(args *Args) bool {\n\tif i := args.IndexOfParam(\"-g\"); i != -1 {\n\t\targs.RemoveParam(i)\n\t\treturn true\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "commands/init_test.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc setupInitContext() {\n\tos.Setenv(\"HUB_PROTOCOL\", \"git\")\n\tos.Setenv(\"HUB_CONFIG\", \"\")\n\tgithub.CreateTestConfigs(\"jingweno\", \"123\")\n}\n\nfunc TestEmptyParams(t *testing.T) {\n\tsetupInitContext()\n\n\targs := NewArgs([]string{\"init\"})\n\terr := transformInitArgs(args)\n\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, true, args.IsParamsEmpty())\n}\n\nfunc TestFlagToAddRemote(t *testing.T) {\n\tsetupInitContext()\n\n\targs := NewArgs([]string{\"init\", \"-g\", \"--quiet\"})\n\terr := transformInitArgs(args)\n\tassert.Equal(t, nil, err)\n\n\tcommands := args.Commands()\n\tassert.Equal(t, 2, len(commands))\n\tassert.Equal(t, \"git init --quiet\", commands[0].String())\n\n\tcurrentDir, err := os.Getwd()\n\tassert.Equal(t, nil, err)\n\n\texpected := fmt.Sprintf(\n\t\t\"git --git-dir %s remote add origin git@github.com:jingweno/%s.git\",\n\t\tfilepath.Join(currentDir, \".git\"),\n\t\tfilepath.Base(currentDir),\n\t)\n\tassert.Equal(t, expected, commands[1].String())\n}\n\nfunc TestInitInAnotherDir(t *testing.T) {\n\tsetupInitContext()\n\n\targs := NewArgs([]string{\"init\", \"-g\", \"--template\", \"mytpl\", \"--shared=umask\", \"my project\"})\n\terr := transformInitArgs(args)\n\tassert.Equal(t, nil, err)\n\n\tcommands := args.Commands()\n\tassert.Equal(t, 2, len(commands))\n\tassert.Equal(t, \"git init --template mytpl --shared=umask \\\"my project\\\"\", commands[0].String())\n\n\tcurrentDir, err := os.Getwd()\n\tassert.Equal(t, nil, err)\n\n\texpected := fmt.Sprintf(\n\t\t\"git --git-dir \\\"%s\\\" remote add origin git@github.com:jingweno/%s.git\",\n\t\tfilepath.Join(currentDir, \"my project\", \".git\"),\n\t\t\"my-project\",\n\t)\n\tassert.Equal(t, expected, commands[1].String())\n}\n\nfunc TestSeparateGitDir(t *testing.T) {\n\tsetupInitContext()\n\n\targs := NewArgs([]string{\"init\", \"-g\", \"--separate-git-dir\", \"/tmp/where-i-play.git\", \"my/playground\"})\n\terr := transformInitArgs(args)\n\tassert.Equal(t, nil, err)\n\n\tcommands := args.Commands()\n\tassert.Equal(t, 2, len(commands))\n\tassert.Equal(t, \"git init --separate-git-dir /tmp/where-i-play.git my/playground\", commands[0].String())\n\n\tcurrentDir, err := os.Getwd()\n\tassert.Equal(t, nil, err)\n\n\texpected := fmt.Sprintf(\n\t\t\"git --git-dir %s remote add origin git@github.com:jingweno/%s.git\",\n\t\tfilepath.Join(currentDir, \"my\", \"playground\", \".git\"),\n\t\t\"playground\",\n\t)\n\tassert.Equal(t, expected, commands[1].String())\n}\n"
  },
  {
    "path": "commands/issue.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/github/hub/v2/git\"\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar (\n\tcmdIssue = &Command{\n\t\tRun: listIssues,\n\t\tUsage: `\nissue [-a <ASSIGNEE>] [-c <CREATOR>] [-@ <USER>] [-s <STATE>] [-f <FORMAT>] [-M <MILESTONE>] [-l <LABELS>] [-d <DATE>] [-o <SORT_KEY> [-^]] [-L <LIMIT>]\nissue show [-f <FORMAT>] <NUMBER>\nissue create [-oc] [-m <MESSAGE>|-F <FILE>] [--edit] [-a <USERS>] [-M <MILESTONE>] [-l <LABELS>]\nissue update <NUMBER> [-m <MESSAGE>|-F <FILE>] [--edit] [-a <USERS>] [-M <MILESTONE>] [-l <LABELS>] [-s <STATE>]\nissue labels [--color]\nissue transfer <NUMBER> <REPO>\n`,\n\t\tLong: `Manage GitHub Issues for the current repository.\n\n## Commands:\n\nWith no arguments, show a list of open issues.\n\n\t* _show_:\n\t\tShow an existing issue specified by <NUMBER>.\n\n\t* _create_:\n\t\tOpen an issue in the current repository.\n\n\t* _update_:\n\t\tUpdate fields of an existing issue specified by <NUMBER>. Use ''--edit''\n\t\tto edit the title and message interactively in the text editor.\n\n\t* _labels_:\n\t\tList the labels available in this repository.\n\n\t* _transfer_:\n\t\tTransfer an issue to another repository.\n\n## Options:\n\t-a, --assignee <ASSIGNEE>\n\t\tIn list mode, display only issues assigned to <ASSIGNEE>.\n\n\t-a, --assign <USERS>\n\t\tA comma-separated list of GitHub handles to assign to the created issue.\n\n\t-c, --creator <CREATOR>\n\t\tDisplay only issues created by <CREATOR>.\n\n\t-@, --mentioned <USER>\n\t\tDisplay only issues mentioning <USER>.\n\n\t-s, --state <STATE>\n\t\tDisplay issues with state <STATE> (default: \"open\").\n\n\t-f, --format <FORMAT>\n\t\tPretty print the contents of the issues using format <FORMAT> (default:\n\t\t\"%sC%>(8)%i%Creset  %t%  l%n\"). See the \"PRETTY FORMATS\" section of\n\t\tgit-log(1) for some additional details on how placeholders are used in\n\t\tformat. The available placeholders for issues are:\n\n\t\t%I: issue number\n\n\t\t%i: issue number prefixed with \"#\"\n\n\t\t%U: the URL of this issue\n\n\t\t%S: state (i.e. \"open\", \"closed\")\n\n\t\t%sC: set color to red or green, depending on issue state.\n\n\t\t%t: title\n\n\t\t%l: colored labels\n\n\t\t%L: raw, comma-separated labels\n\n\t\t%b: body\n\n\t\t%au: login name of author\n\n\t\t%as: comma-separated list of assignees\n\n\t\t%Mn: milestone number\n\n\t\t%Mt: milestone title\n\n\t\t%NC: number of comments\n\n\t\t%Nc: number of comments wrapped in parentheses, or blank string if zero.\n\n\t\t%cD: created date-only (no time of day)\n\n\t\t%cr: created date, relative\n\n\t\t%ct: created date, UNIX timestamp\n\n\t\t%cI: created date, ISO 8601 format\n\n\t\t%uD: updated date-only (no time of day)\n\n\t\t%ur: updated date, relative\n\n\t\t%ut: updated date, UNIX timestamp\n\n\t\t%uI: updated date, ISO 8601 format\n\n\t\t%n: newline\n\n\t\t%%: a literal %\n\n\t--color[=<WHEN>]\n\t\tEnable colored output even if stdout is not a terminal. <WHEN> can be one\n\t\tof \"always\" (default for ''--color''), \"never\", or \"auto\" (default).\n\n\t-m, --message <MESSAGE>\n\t\tThe text up to the first blank line in <MESSAGE> is treated as the issue\n\t\ttitle, and the rest is used as issue description in Markdown format.\n\n\t\tWhen multiple ''--message'' are passed, their values are concatenated with a\n\t\tblank line in-between.\n\n\t\tWhen neither ''--message'' nor ''--file'' were supplied to ''issue create'', a\n\t\ttext editor will open to author the title and description in.\n\n\t-F, --file <FILE>\n\t\tRead the issue title and description from <FILE>. Pass \"-\" to read from\n\t\tstandard input instead. See ''--message'' for the formatting rules.\n\n\t-e, --edit\n\t\tOpen the issue title and description in a text editor before submitting.\n\t\tThis can be used in combination with ''--message'' or ''--file''.\n\n\t-o, --browse\n\t\tOpen the new issue in a web browser.\n\n\t-c, --copy\n\t\tPut the URL of the new issue to clipboard instead of printing it.\n\n\t-M, --milestone <NAME>\n\t\tDisplay only issues for a GitHub milestone with the name <NAME>.\n\n\t\tWhen opening an issue, add this issue to a GitHub milestone with the name <NAME>.\n\t\tPassing the milestone number is deprecated.\n\n\t-l, --labels <LABELS>\n\t\tDisplay only issues with certain labels.\n\n\t\tWhen opening an issue, add a comma-separated list of labels to this issue.\n\n\t-d, --since <DATE>\n\t\tDisplay only issues updated on or after <DATE> in ISO 8601 format.\n\n\t-o, --sort <KEY>\n\t\tSort displayed issues by \"created\" (default), \"updated\" or \"comments\".\n\n\t-^ --sort-ascending\n\t\tSort by ascending dates instead of descending.\n\n\t-L, --limit <LIMIT>\n\t\tDisplay only the first <LIMIT> issues.\n\n\t--include-pulls\n\t\tInclude pull requests as well as issues.\n\n\t--color\n\t\tEnable colored output for labels list.\n\n## See also:\n\nhub-pr(1), hub(1)\n`,\n\t\tKnownFlags: `\n\t\t-a, --assignee USER\n\t\t-s, --state STATE\n\t\t-f, --format FMT\n\t\t-M, --milestone NAME\n\t\t-c, --creator USER\n\t\t-@, --mentioned USER\n\t\t-l, --labels LIST\n\t\t-d, --since DATE\n\t\t-o, --sort KEY\n\t\t-^, --sort-ascending\n\t\t--include-pulls\n\t\t-L, --limit N\n\t\t--color\n`,\n\t}\n\n\tcmdCreateIssue = &Command{\n\t\tKey: \"create\",\n\t\tRun: createIssue,\n\t\tKnownFlags: `\n\t\t-m, --message MSG\n\t\t-F, --file FILE\n\t\t-M, --milestone NAME\n\t\t-l, --labels LIST\n\t\t-a, --assign USER\n\t\t-o, --browse\n\t\t-c, --copy\n\t\t-e, --edit\n`,\n\t}\n\n\tcmdShowIssue = &Command{\n\t\tKey: \"show\",\n\t\tRun: showIssue,\n\t\tKnownFlags: `\n\t\t-f, --format FMT\n\t\t--color\n`,\n\t}\n\n\tcmdLabel = &Command{\n\t\tKey: \"labels\",\n\t\tRun: listLabels,\n\t\tKnownFlags: `\n\t\t--color\n`,\n\t}\n\n\tcmdTransfer = &Command{\n\t\tKey: \"transfer\",\n\t\tRun: transferIssue,\n\t}\n\n\tcmdUpdate = &Command{\n\t\tKey: \"update\",\n\t\tRun: updateIssue,\n\t\tKnownFlags: `\n\t\t-m, --message MSG\n\t\t-F, --file FILE\n\t\t-M, --milestone NAME\n\t\t-l, --labels LIST\n\t\t-a, --assign USER\n\t\t-e, --edit\n\t\t-s, --state STATE\n`,\n\t}\n)\n\nfunc init() {\n\tcmdIssue.Use(cmdShowIssue)\n\tcmdIssue.Use(cmdCreateIssue)\n\tcmdIssue.Use(cmdLabel)\n\tcmdIssue.Use(cmdTransfer)\n\tcmdIssue.Use(cmdUpdate)\n\tCmdRunner.Use(cmdIssue)\n}\n\nfunc listIssues(cmd *Command, args *Args) {\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\tgh := github.NewClient(project.Host)\n\n\tif args.Noop {\n\t\tui.Printf(\"Would request list of issues for %s\\n\", project)\n\t} else {\n\t\tfilters := map[string]interface{}{}\n\t\tif args.Flag.HasReceived(\"--state\") {\n\t\t\tfilters[\"state\"] = args.Flag.Value(\"--state\")\n\t\t}\n\t\tif args.Flag.HasReceived(\"--assignee\") {\n\t\t\tfilters[\"assignee\"] = args.Flag.Value(\"--assignee\")\n\t\t}\n\t\tif args.Flag.HasReceived(\"--milestone\") {\n\t\t\tmilestoneValue := args.Flag.Value(\"--milestone\")\n\t\t\tif milestoneValue == \"none\" {\n\t\t\t\tfilters[\"milestone\"] = milestoneValue\n\t\t\t} else {\n\t\t\t\tmilestoneNumber, err := milestoneValueToNumber(milestoneValue, gh, project)\n\t\t\t\tutils.Check(err)\n\t\t\t\tif milestoneNumber > 0 {\n\t\t\t\t\tfilters[\"milestone\"] = milestoneNumber\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif args.Flag.HasReceived(\"--creator\") {\n\t\t\tfilters[\"creator\"] = args.Flag.Value(\"--creator\")\n\t\t}\n\t\tif args.Flag.HasReceived(\"--mentioned\") {\n\t\t\tfilters[\"mentioned\"] = args.Flag.Value(\"--mentioned\")\n\t\t}\n\t\tif args.Flag.HasReceived(\"--labels\") {\n\t\t\tlabels := commaSeparated(args.Flag.AllValues(\"--labels\"))\n\t\t\tfilters[\"labels\"] = strings.Join(labels, \",\")\n\t\t}\n\t\tif args.Flag.HasReceived(\"--sort\") {\n\t\t\tfilters[\"sort\"] = args.Flag.Value(\"--sort\")\n\t\t}\n\n\t\tif args.Flag.Bool(\"--sort-ascending\") {\n\t\t\tfilters[\"direction\"] = \"asc\"\n\t\t} else {\n\t\t\tfilters[\"direction\"] = \"desc\"\n\t\t}\n\n\t\tif args.Flag.HasReceived(\"--since\") {\n\t\t\tflagIssueSince := args.Flag.Value(\"--since\")\n\t\t\tif sinceTime, err := time.ParseInLocation(\"2006-01-02\", flagIssueSince, time.Local); err == nil {\n\t\t\t\tfilters[\"since\"] = sinceTime.Format(time.RFC3339)\n\t\t\t} else {\n\t\t\t\tfilters[\"since\"] = flagIssueSince\n\t\t\t}\n\t\t}\n\n\t\tflagIssueLimit := args.Flag.Int(\"--limit\")\n\t\tflagIssueIncludePulls := args.Flag.Bool(\"--include-pulls\")\n\t\tflagIssueFormat := \"%sC%>(8)%i%Creset  %t%  l%n\"\n\t\tif args.Flag.HasReceived(\"--format\") {\n\t\t\tflagIssueFormat = args.Flag.Value(\"--format\")\n\t\t}\n\n\t\tissues, err := gh.FetchIssues(project, filters, flagIssueLimit, func(issue *github.Issue) bool {\n\t\t\treturn issue.PullRequest == nil || flagIssueIncludePulls\n\t\t})\n\t\tutils.Check(err)\n\n\t\tmaxNumWidth := 0\n\t\tfor _, issue := range issues {\n\t\t\tif numWidth := len(strconv.Itoa(issue.Number)); numWidth > maxNumWidth {\n\t\t\t\tmaxNumWidth = numWidth\n\t\t\t}\n\t\t}\n\n\t\tcolorize := colorizeOutput(args.Flag.HasReceived(\"--color\"), args.Flag.Value(\"--color\"))\n\t\tfor _, issue := range issues {\n\t\t\tui.Print(formatIssue(issue, flagIssueFormat, colorize))\n\t\t}\n\t}\n\n\targs.NoForward()\n}\n\nfunc formatIssuePlaceholders(issue github.Issue, colorize bool) map[string]string {\n\tvar stateColorSwitch string\n\tif colorize {\n\t\tissueColor := 32\n\t\tif issue.State == \"closed\" {\n\t\t\tissueColor = 31\n\t\t}\n\t\tstateColorSwitch = fmt.Sprintf(\"\\033[%dm\", issueColor)\n\t}\n\n\tvar labelStrings []string\n\tvar rawLabels []string\n\tfor _, label := range issue.Labels {\n\t\tif colorize {\n\t\t\tcolor, err := utils.NewColor(label.Color)\n\t\t\tutils.Check(err)\n\t\t\tlabelStrings = append(labelStrings, colorizeLabel(label, color))\n\t\t} else {\n\t\t\tlabelStrings = append(labelStrings, fmt.Sprintf(\" %s \", label.Name))\n\t\t}\n\t\trawLabels = append(rawLabels, label.Name)\n\t}\n\n\tvar assignees []string\n\tfor _, assignee := range issue.Assignees {\n\t\tassignees = append(assignees, assignee.Login)\n\t}\n\n\tvar milestoneNumber, milestoneTitle string\n\tif issue.Milestone != nil {\n\t\tmilestoneNumber = fmt.Sprintf(\"%d\", issue.Milestone.Number)\n\t\tmilestoneTitle = issue.Milestone.Title\n\t}\n\n\tvar numCommentsWrapped string\n\tnumComments := fmt.Sprintf(\"%d\", issue.Comments)\n\tif issue.Comments > 0 {\n\t\tnumCommentsWrapped = fmt.Sprintf(\"(%d)\", issue.Comments)\n\t}\n\n\tvar createdDate, createdAtISO8601, createdAtUnix, createdAtRelative,\n\t\tupdatedDate, updatedAtISO8601, updatedAtUnix, updatedAtRelative string\n\tif !issue.CreatedAt.IsZero() {\n\t\tcreatedDate = issue.CreatedAt.Format(\"02 Jan 2006\")\n\t\tcreatedAtISO8601 = issue.CreatedAt.Format(time.RFC3339)\n\t\tcreatedAtUnix = fmt.Sprintf(\"%d\", issue.CreatedAt.Unix())\n\t\tcreatedAtRelative = utils.TimeAgo(issue.CreatedAt)\n\t}\n\tif !issue.UpdatedAt.IsZero() {\n\t\tupdatedDate = issue.UpdatedAt.Format(\"02 Jan 2006\")\n\t\tupdatedAtISO8601 = issue.UpdatedAt.Format(time.RFC3339)\n\t\tupdatedAtUnix = fmt.Sprintf(\"%d\", issue.UpdatedAt.Unix())\n\t\tupdatedAtRelative = utils.TimeAgo(issue.UpdatedAt)\n\t}\n\n\treturn map[string]string{\n\t\t\"I\":  fmt.Sprintf(\"%d\", issue.Number),\n\t\t\"i\":  fmt.Sprintf(\"#%d\", issue.Number),\n\t\t\"U\":  issue.HTMLURL,\n\t\t\"S\":  issue.State,\n\t\t\"sC\": stateColorSwitch,\n\t\t\"t\":  issue.Title,\n\t\t\"l\":  strings.Join(labelStrings, \" \"),\n\t\t\"L\":  strings.Join(rawLabels, \", \"),\n\t\t\"b\":  issue.Body,\n\t\t\"au\": issue.User.Login,\n\t\t\"as\": strings.Join(assignees, \", \"),\n\t\t\"Mn\": milestoneNumber,\n\t\t\"Mt\": milestoneTitle,\n\t\t\"NC\": numComments,\n\t\t\"Nc\": numCommentsWrapped,\n\t\t\"cD\": createdDate,\n\t\t\"cI\": createdAtISO8601,\n\t\t\"ct\": createdAtUnix,\n\t\t\"cr\": createdAtRelative,\n\t\t\"uD\": updatedDate,\n\t\t\"uI\": updatedAtISO8601,\n\t\t\"ut\": updatedAtUnix,\n\t\t\"ur\": updatedAtRelative,\n\t}\n}\n\nfunc formatPullRequestPlaceholders(pr github.PullRequest, colorize bool) map[string]string {\n\tprState := pr.State\n\tif prState == \"open\" && pr.Draft {\n\t\tprState = \"draft\"\n\t} else if !pr.MergedAt.IsZero() {\n\t\tprState = \"merged\"\n\t}\n\n\tvar stateColorSwitch string\n\tvar prColor int\n\tif colorize {\n\t\tswitch prState {\n\t\tcase \"draft\":\n\t\t\tprColor = 37\n\t\tcase \"merged\":\n\t\t\tprColor = 35\n\t\tcase \"closed\":\n\t\t\tprColor = 31\n\t\tdefault:\n\t\t\tprColor = 32\n\t\t}\n\t\tstateColorSwitch = fmt.Sprintf(\"\\033[%dm\", prColor)\n\t}\n\n\tbase := pr.Base.Ref\n\thead := pr.Head.Label\n\tif pr.IsSameRepo() {\n\t\thead = pr.Head.Ref\n\t}\n\n\tvar requestedReviewers []string\n\tfor _, requestedReviewer := range pr.RequestedReviewers {\n\t\trequestedReviewers = append(requestedReviewers, requestedReviewer.Login)\n\t}\n\tfor _, requestedTeam := range pr.RequestedTeams {\n\t\tteamSlug := fmt.Sprintf(\"%s/%s\", pr.Base.Repo.Owner.Login, requestedTeam.Slug)\n\t\trequestedReviewers = append(requestedReviewers, teamSlug)\n\t}\n\n\tvar mergedDate, mergedAtISO8601, mergedAtUnix, mergedAtRelative string\n\tif !pr.MergedAt.IsZero() {\n\t\tmergedDate = pr.MergedAt.Format(\"02 Jan 2006\")\n\t\tmergedAtISO8601 = pr.MergedAt.Format(time.RFC3339)\n\t\tmergedAtUnix = fmt.Sprintf(\"%d\", pr.MergedAt.Unix())\n\t\tmergedAtRelative = utils.TimeAgo(pr.MergedAt)\n\t}\n\n\treturn map[string]string{\n\t\t\"pS\": prState,\n\t\t\"pC\": stateColorSwitch,\n\t\t\"B\":  base,\n\t\t\"H\":  head,\n\t\t\"sB\": pr.Base.Sha,\n\t\t\"sH\": pr.Head.Sha,\n\t\t\"sm\": pr.MergeCommitSha,\n\t\t\"rs\": strings.Join(requestedReviewers, \", \"),\n\t\t\"mD\": mergedDate,\n\t\t\"mI\": mergedAtISO8601,\n\t\t\"mt\": mergedAtUnix,\n\t\t\"mr\": mergedAtRelative,\n\t}\n}\n\nfunc formatIssue(issue github.Issue, format string, colorize bool) string {\n\tplaceholders := formatIssuePlaceholders(issue, colorize)\n\treturn ui.Expand(format, placeholders, colorize)\n}\n\nfunc showIssue(cmd *Command, args *Args) {\n\tissueNumber := \"\"\n\tif args.ParamsSize() > 0 {\n\t\tissueNumber = args.GetParam(0)\n\t}\n\tif issueNumber == \"\" {\n\t\tutils.Check(cmd.UsageError(\"\"))\n\t}\n\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\tgh := github.NewClient(project.Host)\n\n\tvar issue = &github.Issue{}\n\tissue, err = gh.FetchIssue(project, issueNumber)\n\tutils.Check(err)\n\n\targs.NoForward()\n\n\tcolorize := colorizeOutput(args.Flag.HasReceived(\"--color\"), args.Flag.Value(\"--color\"))\n\tif args.Flag.HasReceived(\"--format\") {\n\t\tflagShowIssueFormat := args.Flag.Value(\"--format\")\n\t\tui.Print(formatIssue(*issue, flagShowIssueFormat, colorize))\n\t\treturn\n\t}\n\n\tvar closed = \"\"\n\tif issue.State != \"open\" {\n\t\tclosed = \"[CLOSED] \"\n\t}\n\tcommentsList, err := gh.FetchComments(project, issueNumber)\n\tutils.Check(err)\n\n\tui.Printf(\"# %s%s\\n\\n\", closed, issue.Title)\n\tui.Printf(\"* created by @%s on %s\\n\", issue.User.Login, issue.CreatedAt.String())\n\n\tif len(issue.Assignees) > 0 {\n\t\tvar assignees []string\n\t\tfor _, user := range issue.Assignees {\n\t\t\tassignees = append(assignees, user.Login)\n\t\t}\n\t\tui.Printf(\"* assignees: %s\\n\", strings.Join(assignees, \", \"))\n\t}\n\n\tui.Printf(\"\\n%s\\n\", issue.Body)\n\n\tif issue.Comments > 0 {\n\t\tui.Printf(\"\\n## Comments:\\n\")\n\t\tfor _, comment := range commentsList {\n\t\t\tui.Printf(\"\\n### comment by @%s on %s\\n\\n%s\\n\", comment.User.Login, comment.CreatedAt.String(), comment.Body)\n\t\t}\n\t}\n}\n\nfunc createIssue(cmd *Command, args *Args) {\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\tgh := github.NewClient(project.Host)\n\n\tmessageBuilder := &github.MessageBuilder{\n\t\tFilename: \"ISSUE_EDITMSG\",\n\t\tTitle:    \"issue\",\n\t}\n\n\tmessageBuilder.AddCommentedSection(fmt.Sprintf(`Creating an issue for %s\n\nWrite a message for this issue. The first block of\ntext is the title and the rest is the description.`, project))\n\n\tflagIssueEdit := args.Flag.Bool(\"--edit\")\n\tflagIssueMessage := args.Flag.AllValues(\"--message\")\n\tif len(flagIssueMessage) > 0 {\n\t\tmessageBuilder.Message = strings.Join(flagIssueMessage, \"\\n\\n\")\n\t\tmessageBuilder.Edit = flagIssueEdit\n\t} else if args.Flag.HasReceived(\"--file\") {\n\t\tmessageBuilder.Message, err = msgFromFile(args.Flag.Value(\"--file\"))\n\t\tutils.Check(err)\n\t\tmessageBuilder.Edit = flagIssueEdit\n\t} else {\n\t\tmessageBuilder.Edit = true\n\n\t\tworkdir, _ := git.WorkdirName()\n\t\tif workdir != \"\" {\n\t\t\ttemplate, err := github.ReadTemplate(github.IssueTemplate, workdir)\n\t\t\tutils.Check(err)\n\t\t\tif template != \"\" {\n\t\t\t\tmessageBuilder.Message = template\n\t\t\t}\n\t\t}\n\n\t}\n\n\ttitle, body, err := messageBuilder.Extract()\n\tutils.Check(err)\n\n\tif title == \"\" {\n\t\tutils.Check(fmt.Errorf(\"Aborting creation due to empty issue title\"))\n\t}\n\n\tparams := map[string]interface{}{\n\t\t\"title\": title,\n\t\t\"body\":  body,\n\t}\n\n\tsetLabelsFromArgs(params, args)\n\n\tsetAssigneesFromArgs(params, args)\n\n\tsetMilestoneFromArgs(params, args, gh, project)\n\n\targs.NoForward()\n\tif args.Noop {\n\t\tui.Printf(\"Would create issue `%s' for %s\\n\", params[\"title\"], project)\n\t} else {\n\t\tissue, err := gh.CreateIssue(project, params)\n\t\tutils.Check(err)\n\n\t\tflagIssueBrowse := args.Flag.Bool(\"--browse\")\n\t\tflagIssueCopy := args.Flag.Bool(\"--copy\")\n\t\tprintBrowseOrCopy(args, issue.HTMLURL, flagIssueBrowse, flagIssueCopy)\n\t}\n\n\tmessageBuilder.Cleanup()\n}\n\nfunc updateIssue(cmd *Command, args *Args) {\n\tissueNumber := 0\n\tif args.ParamsSize() > 0 {\n\t\tissueNumber, _ = strconv.Atoi(args.GetParam(0))\n\t}\n\tif issueNumber == 0 {\n\t\tutils.Check(cmd.UsageError(\"\"))\n\t}\n\tif !hasField(args, \"--message\", \"--file\", \"--labels\", \"--milestone\", \"--assign\", \"--state\", \"--edit\") {\n\t\tutils.Check(cmd.UsageError(\"please specify fields to update\"))\n\t}\n\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\tgh := github.NewClient(project.Host)\n\n\tparams := map[string]interface{}{}\n\tsetLabelsFromArgs(params, args)\n\tsetAssigneesFromArgs(params, args)\n\tsetMilestoneFromArgs(params, args, gh, project)\n\n\tif args.Flag.HasReceived(\"--state\") {\n\t\tparams[\"state\"] = args.Flag.Value(\"--state\")\n\t}\n\n\tif hasField(args, \"--message\", \"--file\", \"--edit\") {\n\t\tmessageBuilder := &github.MessageBuilder{\n\t\t\tFilename: \"ISSUE_EDITMSG\",\n\t\t\tTitle:    \"issue\",\n\t\t}\n\n\t\tmessageBuilder.AddCommentedSection(fmt.Sprintf(`Editing issue #%d for %s\n\nUpdate the message for this issue. The first block of\ntext is the title and the rest is the description.`, issueNumber, project))\n\n\t\tmessageBuilder.Edit = args.Flag.Bool(\"--edit\")\n\t\tflagIssueMessage := args.Flag.AllValues(\"--message\")\n\t\tif len(flagIssueMessage) > 0 {\n\t\t\tmessageBuilder.Message = strings.Join(flagIssueMessage, \"\\n\\n\")\n\t\t} else if args.Flag.HasReceived(\"--file\") {\n\t\t\tmessageBuilder.Message, err = msgFromFile(args.Flag.Value(\"--file\"))\n\t\t\tutils.Check(err)\n\t\t} else {\n\t\t\tissue, err := gh.FetchIssue(project, strconv.Itoa(issueNumber))\n\t\t\tutils.Check(err)\n\t\t\texistingMessage := fmt.Sprintf(\"%s\\n\\n%s\", issue.Title, issue.Body)\n\t\t\tmessageBuilder.Message = strings.Replace(existingMessage, \"\\r\\n\", \"\\n\", -1)\n\t\t}\n\n\t\ttitle, body, err := messageBuilder.Extract()\n\t\tutils.Check(err)\n\t\tif title == \"\" {\n\t\t\tutils.Check(fmt.Errorf(\"Aborting creation due to empty issue title\"))\n\t\t}\n\t\tparams[\"title\"] = title\n\t\tparams[\"body\"] = body\n\t\tdefer messageBuilder.Cleanup()\n\t}\n\n\targs.NoForward()\n\tif args.Noop {\n\t\tui.Printf(\"Would update issue #%d for %s\\n\", issueNumber, project)\n\t} else {\n\t\terr := gh.UpdateIssue(project, issueNumber, params)\n\t\tutils.Check(err)\n\t}\n}\n\nfunc listLabels(cmd *Command, args *Args) {\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\tgh := github.NewClient(project.Host)\n\n\targs.NoForward()\n\tif args.Noop {\n\t\tui.Printf(\"Would request list of labels for %s\\n\", project)\n\t\treturn\n\t}\n\n\tlabels, err := gh.FetchLabels(project)\n\tutils.Check(err)\n\n\tflagLabelsColorize := colorizeOutput(args.Flag.HasReceived(\"--color\"), args.Flag.Value(\"--color\"))\n\tfor _, label := range labels {\n\t\tui.Print(formatLabel(label, flagLabelsColorize))\n\t}\n}\n\nfunc hasField(args *Args, names ...string) bool {\n\tfound := false\n\tfor _, name := range names {\n\t\tif args.Flag.HasReceived(name) {\n\t\t\tfound = true\n\t\t}\n\t}\n\treturn found\n}\n\nfunc setLabelsFromArgs(params map[string]interface{}, args *Args) {\n\tif !args.Flag.HasReceived(\"--labels\") {\n\t\treturn\n\t}\n\tparams[\"labels\"] = commaSeparated(args.Flag.AllValues(\"--labels\"))\n}\n\nfunc setAssigneesFromArgs(params map[string]interface{}, args *Args) {\n\tif !args.Flag.HasReceived(\"--assign\") {\n\t\treturn\n\t}\n\tparams[\"assignees\"] = commaSeparated(args.Flag.AllValues(\"--assign\"))\n}\n\nfunc setMilestoneFromArgs(params map[string]interface{}, args *Args, gh *github.Client, project *github.Project) {\n\tif !args.Flag.HasReceived(\"--milestone\") {\n\t\treturn\n\t}\n\tmilestoneNumber, err := milestoneValueToNumber(args.Flag.Value(\"--milestone\"), gh, project)\n\tutils.Check(err)\n\tif milestoneNumber == 0 {\n\t\tparams[\"milestone\"] = nil\n\t} else {\n\t\tparams[\"milestone\"] = milestoneNumber\n\t}\n}\n\nfunc colorizeOutput(colorSet bool, when string) bool {\n\tif !colorSet || when == \"auto\" {\n\t\tcolorConfig, _ := git.Config(\"color.ui\")\n\t\tswitch colorConfig {\n\t\tcase \"false\", \"never\":\n\t\t\treturn false\n\t\tcase \"always\":\n\t\t\treturn true\n\t\t}\n\t\treturn ui.IsTerminal(os.Stdout)\n\t} else if when == \"never\" {\n\t\treturn false\n\t} else {\n\t\treturn true // \"always\"\n\t}\n}\n\nfunc formatLabel(label github.IssueLabel, colorize bool) string {\n\tif colorize {\n\t\tif color, err := utils.NewColor(label.Color); err == nil {\n\t\t\treturn fmt.Sprintf(\"%s\\n\", colorizeLabel(label, color))\n\t\t}\n\t}\n\treturn fmt.Sprintf(\"%s\\n\", label.Name)\n}\n\nfunc colorizeLabel(label github.IssueLabel, color *utils.Color) string {\n\tbgColorCode := utils.RgbToTermColorCode(color)\n\tfgColor := pickHighContrastTextColor(color)\n\tfgColorCode := utils.RgbToTermColorCode(fgColor)\n\treturn fmt.Sprintf(\"\\033[38;%s;48;%sm %s \\033[m\",\n\t\tfgColorCode, bgColorCode, label.Name)\n}\n\ntype contrastCandidate struct {\n\tcolor    *utils.Color\n\tcontrast float64\n}\n\nfunc pickHighContrastTextColor(color *utils.Color) *utils.Color {\n\tcandidates := []contrastCandidate{}\n\tappendCandidate := func(c *utils.Color) {\n\t\tcandidates = append(candidates, contrastCandidate{\n\t\t\tcolor:    c,\n\t\t\tcontrast: color.ContrastRatio(c),\n\t\t})\n\t}\n\n\tappendCandidate(utils.White)\n\tappendCandidate(utils.Black)\n\n\tfor _, candidate := range candidates {\n\t\tif candidate.contrast >= 7.0 {\n\t\t\treturn candidate.color\n\t\t}\n\t}\n\tfor _, candidate := range candidates {\n\t\tif candidate.contrast >= 4.5 {\n\t\t\treturn candidate.color\n\t\t}\n\t}\n\treturn utils.Black\n}\n\nfunc milestoneValueToNumber(value string, client *github.Client, project *github.Project) (int, error) {\n\tif value == \"\" {\n\t\treturn 0, nil\n\t}\n\n\tif milestoneNumber, err := strconv.Atoi(value); err == nil {\n\t\treturn milestoneNumber, nil\n\t}\n\n\tmilestones, err := client.FetchMilestones(project)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tfor _, milestone := range milestones {\n\t\tif strings.EqualFold(milestone.Title, value) {\n\t\t\treturn milestone.Number, nil\n\t\t}\n\t}\n\n\treturn 0, fmt.Errorf(\"error: no milestone found with name '%s'\", value)\n}\n\nfunc transferIssue(cmd *Command, args *Args) {\n\tif args.ParamsSize() < 2 {\n\t\tutils.Check(cmd.UsageError(\"\"))\n\t}\n\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\tissueNumber, err := strconv.Atoi(args.GetParam(0))\n\tutils.Check(err)\n\ttargetOwner := project.Owner\n\ttargetRepo := args.GetParam(1)\n\tif strings.Contains(targetRepo, \"/\") {\n\t\tparts := strings.SplitN(targetRepo, \"/\", 2)\n\t\ttargetOwner = parts[0]\n\t\ttargetRepo = parts[1]\n\t}\n\n\tgh := github.NewClient(project.Host)\n\n\tnodeIDsResponse := struct {\n\t\tSource struct {\n\t\t\tIssue struct {\n\t\t\t\tID string\n\t\t\t}\n\t\t}\n\t\tTarget struct {\n\t\t\tID string\n\t\t}\n\t}{}\n\terr = gh.GraphQL(`\n\tquery($issue: Int!, $sourceOwner: String!, $sourceRepo: String!, $targetOwner: String!, $targetRepo: String!) {\n\t\tsource: repository(owner: $sourceOwner, name: $sourceRepo) {\n\t\t\tissue(number: $issue) {\n\t\t\t\tid\n\t\t\t}\n\t\t}\n\t\ttarget: repository(owner: $targetOwner, name: $targetRepo) {\n\t\t\tid\n\t\t}\n\t}`, map[string]interface{}{\n\t\t\"issue\":       issueNumber,\n\t\t\"sourceOwner\": project.Owner,\n\t\t\"sourceRepo\":  project.Name,\n\t\t\"targetOwner\": targetOwner,\n\t\t\"targetRepo\":  targetRepo,\n\t}, &nodeIDsResponse)\n\tutils.Check(err)\n\n\tissueResponse := struct {\n\t\tTransferIssue struct {\n\t\t\tIssue struct {\n\t\t\t\tURL string\n\t\t\t}\n\t\t}\n\t}{}\n\terr = gh.GraphQL(`\n\tmutation($issue: ID!, $repo: ID!) {\n\t\ttransferIssue(input: {issueId: $issue, repositoryId: $repo}) {\n\t\t\tissue {\n\t\t\t\turl\n\t\t\t}\n\t\t}\n\t}`, map[string]interface{}{\n\t\t\"issue\": nodeIDsResponse.Source.Issue.ID,\n\t\t\"repo\":  nodeIDsResponse.Target.ID,\n\t}, &issueResponse)\n\tutils.Check(err)\n\n\tui.Println(issueResponse.TransferIssue.Issue.URL)\n\targs.NoForward()\n}\n"
  },
  {
    "path": "commands/issue_test.go",
    "content": "package commands\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/github/hub/v2/github\"\n)\n\ntype formatIssueTest struct {\n\tname     string\n\tissue    github.Issue\n\tformat   string\n\tcolorize bool\n\texpect   string\n}\n\nfunc testFormatIssue(t *testing.T, tests []formatIssueTest) {\n\tt.Helper()\n\tfor _, test := range tests {\n\t\tif got := formatIssue(test.issue, test.format, test.colorize); got != test.expect {\n\t\t\tt.Errorf(\"%s: formatIssue(..., %q, %t) = %q, want %q\", test.name, test.format, test.colorize, got, test.expect)\n\t\t}\n\t}\n}\n\nfunc TestFormatIssue(t *testing.T) {\n\tformat := \"%sC%>(8)%i%Creset  %t%  l%n\"\n\ttestFormatIssue(t, []formatIssueTest{\n\t\t{\n\t\t\tname: \"standard usage\",\n\t\t\tissue: github.Issue{\n\t\t\t\tNumber:    42,\n\t\t\t\tTitle:     \"Just an Issue\",\n\t\t\t\tState:     \"open\",\n\t\t\t\tUser:      &github.User{Login: \"pcorpet\"},\n\t\t\t\tBody:      \"Body of the\\nissue\",\n\t\t\t\tAssignees: []github.User{{Login: \"mislav\"}},\n\t\t\t},\n\t\t\tformat:   format,\n\t\t\tcolorize: true,\n\t\t\texpect:   \"\\033[32m     #42\\033[m  Just an Issue\\n\",\n\t\t},\n\t\t{\n\t\t\tname: \"closed issue colored differently\",\n\t\t\tissue: github.Issue{\n\t\t\t\tNumber: 42,\n\t\t\t\tTitle:  \"Just an Issue\",\n\t\t\t\tState:  \"closed\",\n\t\t\t\tUser:   &github.User{Login: \"octocat\"},\n\t\t\t},\n\t\t\tformat:   format,\n\t\t\tcolorize: true,\n\t\t\texpect:   \"\\033[31m     #42\\033[m  Just an Issue\\n\",\n\t\t},\n\t\t{\n\t\t\tname: \"labels\",\n\t\t\tissue: github.Issue{\n\t\t\t\tNumber: 42,\n\t\t\t\tTitle:  \"An issue with labels\",\n\t\t\t\tState:  \"open\",\n\t\t\t\tUser:   &github.User{Login: \"octocat\"},\n\t\t\t\tLabels: []github.IssueLabel{\n\t\t\t\t\t{Name: \"bug\", Color: \"800000\"},\n\t\t\t\t\t{Name: \"reproduced\", Color: \"55ff55\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tformat:   format,\n\t\t\tcolorize: true,\n\t\t\texpect:   \"\\033[32m     #42\\033[m  An issue with labels  \\033[38;2;255;255;255;48;2;128;0;0m bug \\033[m \\033[38;2;0;0;0;48;2;85;255;85m reproduced \\033[m\\n\",\n\t\t},\n\t\t{\n\t\t\tname: \"not colorized\",\n\t\t\tissue: github.Issue{\n\t\t\t\tNumber: 42,\n\t\t\t\tTitle:  \"Just an Issue\",\n\t\t\t\tState:  \"open\",\n\t\t\t\tUser:   &github.User{Login: \"octocat\"},\n\t\t\t},\n\t\t\tformat:   format,\n\t\t\tcolorize: false,\n\t\t\texpect:   \"     #42  Just an Issue\\n\",\n\t\t},\n\t\t{\n\t\t\tname: \"labels not colorized\",\n\t\t\tissue: github.Issue{\n\t\t\t\tNumber: 42,\n\t\t\t\tTitle:  \"An issue with labels\",\n\t\t\t\tState:  \"open\",\n\t\t\t\tUser:   &github.User{Login: \"octocat\"},\n\t\t\t\tLabels: []github.IssueLabel{\n\t\t\t\t\t{Name: \"bug\", Color: \"880000\"},\n\t\t\t\t\t{Name: \"reproduced\", Color: \"55ff55\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tformat:   format,\n\t\t\tcolorize: false,\n\t\t\texpect:   \"     #42  An issue with labels   bug   reproduced \\n\",\n\t\t},\n\t})\n}\n\nfunc TestFormatIssue_customFormatString(t *testing.T) {\n\tcreatedAt, err := time.Parse(time.RFC822Z, \"16 Mar 15 12:34 +0000\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tupdatedAt, err := time.Parse(time.RFC822Z, \"17 Mar 15 12:34 +0900\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tissue := github.Issue{\n\t\tNumber: 42,\n\t\tTitle:  \"Just an Issue\",\n\t\tState:  \"open\",\n\t\tUser:   &github.User{Login: \"pcorpet\"},\n\t\tBody:   \"Body of the\\nissue\",\n\t\tAssignees: []github.User{\n\t\t\t{Login: \"mislav\"},\n\t\t\t{Login: \"josh\"},\n\t\t},\n\t\tLabels: []github.IssueLabel{\n\t\t\t{Name: \"bug\", Color: \"880000\"},\n\t\t\t{Name: \"feature\", Color: \"008800\"},\n\t\t},\n\t\tHTMLURL:  \"the://url\",\n\t\tComments: 12,\n\t\tMilestone: &github.Milestone{\n\t\t\tNumber: 31,\n\t\t\tTitle:  \"2.2-stable\",\n\t\t},\n\t\tCreatedAt: createdAt,\n\t\tUpdatedAt: updatedAt,\n\t}\n\n\ttestFormatIssue(t, []formatIssueTest{\n\t\t{\n\t\t\tname:     \"number\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%I\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"42\",\n\t\t},\n\t\t{\n\t\t\tname:     \"hashed number\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%i\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"#42\",\n\t\t},\n\t\t{\n\t\t\tname:     \"state as text\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%S\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"open\",\n\t\t},\n\t\t{\n\t\t\tname:     \"state as color switch\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%sC\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"\\033[32m\",\n\t\t},\n\t\t{\n\t\t\tname:     \"state as color switch non colorized\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%sC\",\n\t\t\tcolorize: false,\n\t\t\texpect:   \"\",\n\t\t},\n\t\t{\n\t\t\tname:     \"title\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%t\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"Just an Issue\",\n\t\t},\n\t\t{\n\t\t\tname:     \"label colorized\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%l\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"\\033[38;2;255;255;255;48;2;136;0;0m bug \\033[m \\033[38;2;255;255;255;48;2;0;136;0m feature \\033[m\",\n\t\t},\n\t\t{\n\t\t\tname:     \"label not colorized\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%l\",\n\t\t\tcolorize: false,\n\t\t\texpect:   \" bug   feature \",\n\t\t},\n\t\t{\n\t\t\tname:     \"raw labels\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%L\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"bug, feature\",\n\t\t},\n\t\t{\n\t\t\tname:     \"body\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%b\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"Body of the\\nissue\",\n\t\t},\n\t\t{\n\t\t\tname:     \"user login\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%au\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"pcorpet\",\n\t\t},\n\t\t{\n\t\t\tname:     \"assignee login\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%as\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"mislav, josh\",\n\t\t},\n\t\t{\n\t\t\tname: \"assignee login but not assigned\",\n\t\t\tissue: github.Issue{\n\t\t\t\tState: \"open\",\n\t\t\t\tUser:  &github.User{Login: \"pcorpet\"},\n\t\t\t},\n\t\t\tformat:   \"%as\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"\",\n\t\t},\n\t\t{\n\t\t\tname:     \"milestone number\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%Mn\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"31\",\n\t\t},\n\t\t{\n\t\t\tname:     \"milestone title\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%Mt\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"2.2-stable\",\n\t\t},\n\t\t{\n\t\t\tname:     \"comments number\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%Nc\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"(12)\",\n\t\t},\n\t\t{\n\t\t\tname:     \"raw comments number\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%NC\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"12\",\n\t\t},\n\t\t{\n\t\t\tname:     \"issue URL\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%U\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"the://url\",\n\t\t},\n\t\t{\n\t\t\tname:     \"created date\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%cD\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"16 Mar 2015\",\n\t\t},\n\t\t{\n\t\t\tname:     \"created time ISO 8601\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%cI\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"2015-03-16T12:34:00Z\",\n\t\t},\n\t\t{\n\t\t\tname:     \"created time Unix\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%ct\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"1426509240\",\n\t\t},\n\t\t{\n\t\t\tname:     \"updated date\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%uD\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"17 Mar 2015\",\n\t\t},\n\t\t{\n\t\t\tname:     \"updated time ISO 8601\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%uI\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"2015-03-17T12:34:00+09:00\",\n\t\t},\n\t\t{\n\t\t\tname:     \"updated time Unix\",\n\t\t\tissue:    issue,\n\t\t\tformat:   \"%ut\",\n\t\t\tcolorize: true,\n\t\t\texpect:   \"1426563240\",\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "commands/merge.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdMerge = &Command{\n\tRun:          merge,\n\tGitExtension: true,\n\tUsage:        \"merge <PULLREQ-URL>\",\n\tLong: `Merge a pull request locally with a message like the GitHub Merge Button.\n\nThis creates a local merge commit in the current branch, but does not actually\nchange the state of the pull request. However, the pull request will get\nauto-closed and marked as \"merged\" as soon as the newly created merge commit is\npushed to the default branch of the remote repository.\n\nTo merge a pull request remotely, use ''hub pr merge''.\n\n## Examples:\n\t\t$ hub merge https://github.com/jingweno/gh/pull/73\n\t\t> git fetch origin refs/pull/73/head\n\t\t> git merge FETCH_HEAD --no-ff -m \"Merge pull request #73 from jingweno/feature...\"\n\n## See also:\n\nhub-pr(1), hub-checkout(1), hub(1), git-merge(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdMerge)\n}\n\nfunc merge(command *Command, args *Args) {\n\tif !args.IsParamsEmpty() {\n\t\terr := transformMergeArgs(args)\n\t\tutils.Check(err)\n\t}\n}\n\nfunc transformMergeArgs(args *Args) error {\n\twords := args.Words()\n\tif len(words) == 0 {\n\t\treturn nil\n\t}\n\n\tmergeURL := words[0]\n\turl, err := github.ParseURL(mergeURL)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tpullURLRegex := regexp.MustCompile(\"^pull/(\\\\d+)\")\n\tprojectPath := url.ProjectPath()\n\tif !pullURLRegex.MatchString(projectPath) {\n\t\treturn nil\n\t}\n\n\tid := pullURLRegex.FindStringSubmatch(projectPath)[1]\n\tgh := github.NewClient(url.Project.Host)\n\tpullRequest, err := gh.PullRequest(url.Project, id)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trepo, err := github.LocalRepo()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tremote, err := repo.RemoteForRepo(pullRequest.Base.Repo)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tbranch := pullRequest.Head.Ref\n\theadRepo := pullRequest.Head.Repo\n\tif headRepo == nil {\n\t\treturn fmt.Errorf(\"Error: that fork is not available anymore\")\n\t}\n\n\targs.Before(\"git\", \"fetch\", remote.Name, fmt.Sprintf(\"refs/pull/%s/head\", id))\n\n\t// Remove pull request URL\n\tidx := args.IndexOfParam(mergeURL)\n\targs.RemoveParam(idx)\n\n\tmergeMsg := fmt.Sprintf(\"Merge pull request #%s from %s/%s\\n\\n%s\", id, headRepo.Owner.Login, branch, pullRequest.Title)\n\targs.AppendParams(\"FETCH_HEAD\", \"-m\", mergeMsg)\n\n\tif args.IndexOfParam(\"--ff-only\") == -1 && args.IndexOfParam(\"--squash\") == -1 && args.IndexOfParam(\"--ff\") == -1 {\n\t\ti := args.IndexOfParam(\"-m\")\n\t\targs.InsertParam(i, \"--no-ff\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "commands/pr.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/git\"\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar (\n\tcmdPr = &Command{\n\t\tRun: printHelp,\n\t\tUsage: `\npr list [-s <STATE>] [-h <HEAD>] [-b <BASE>] [-o <SORT_KEY> [-^]] [-f <FORMAT>] [-L <LIMIT>]\npr checkout <PR-NUMBER> [<BRANCH>]\npr show [-uc] [-f <FORMAT>] [-h <HEAD>]\npr show [-uc] [-f <FORMAT>] <PR-NUMBER>\npr merge [-d] [--squash | --rebase] <PR-NUMBER> [-m <MESSAGE> | -F <FILE>] [--head-sha <COMMIT-SHA>]\n`,\n\t\tLong: `Manage GitHub Pull Requests for the current repository.\n\n## Commands:\n\n\t* _list_:\n\t\tList pull requests in the current repository.\n\n\t* _checkout_:\n\t\tCheck out the head of a pull request in a new branch.\n\n\t\tTo update the pull request with new commits, use ''git push''.\n\n\t* _show_:\n\t\tOpen a pull request page in a web browser. When no <PR-NUMBER> is\n\t\tspecified, <HEAD> is used to look up open pull requests and defaults to\n\t\tthe current branch name. With ''--format'', print information about the\n\t\tpull request instead of opening it.\n\n\t* _merge_:\n\t\tMerge a pull request in the current repository remotely. Select an\n\t\talternate merge method with ''--squash'' or ''--rebase''. Change the\n\t\tcommit subject and body with ''--message'' or ''--file''.\n\n## Options:\n\n\t-s, --state <STATE>\n\t\tFilter pull requests by <STATE>. Supported values are: \"open\" (default),\n\t\t\"closed\", \"merged\", or \"all\".\n\n\t-h, --head <BRANCH>\n\t\tShow pull requests started from the specified head <BRANCH>. The\n\t\t\"OWNER:BRANCH\" format must be used for pull requests from forks.\n\n\t-b, --base <BRANCH>\n\t\tShow pull requests based off the specified <BRANCH>.\n\n\t-f, --format <FORMAT>\n\t\tPretty print the list of pull requests using format <FORMAT> (default:\n\t\t\"%pC%>(8)%i%Creset  %t%  l%n\"). See the \"PRETTY FORMATS\" section of\n\t\tgit-log(1) for some additional details on how placeholders are used in\n\t\tformat. The available placeholders are:\n\n\t\t%I: pull request number\n\n\t\t%i: pull request number prefixed with \"#\"\n\n\t\t%U: the URL of this pull request\n\n\t\t%S: state (\"open\" or \"closed\")\n\n\t\t%pS: pull request state (\"open\", \"draft\", \"merged\", or \"closed\")\n\n\t\t%sC: set color to red or green, depending on state\n\n\t\t%pC: set color according to pull request state\n\n\t\t%t: title\n\n\t\t%l: colored labels\n\n\t\t%L: raw, comma-separated labels\n\n\t\t%b: body\n\n\t\t%B: base branch\n\n\t\t%sB: base commit SHA\n\n\t\t%H: head branch\n\n\t\t%sH: head commit SHA\n\n\t\t%sm: merge commit SHA\n\n\t\t%au: login name of author\n\n\t\t%as: comma-separated list of assignees\n\n\t\t%rs: comma-separated list of requested reviewers\n\n\t\t%Mn: milestone number\n\n\t\t%Mt: milestone title\n\n\t\t%cD: created date-only (no time of day)\n\n\t\t%cr: created date, relative\n\n\t\t%ct: created date, UNIX timestamp\n\n\t\t%cI: created date, ISO 8601 format\n\n\t\t%uD: updated date-only (no time of day)\n\n\t\t%ur: updated date, relative\n\n\t\t%ut: updated date, UNIX timestamp\n\n\t\t%uI: updated date, ISO 8601 format\n\n\t\t%mD: merged date-only (no time of day)\n\n\t\t%mr: merged date, relative\n\n\t\t%mt: merged date, UNIX timestamp\n\n\t\t%mI: merged date, ISO 8601 format\n\n\t\t%n: newline\n\n\t\t%%: a literal %\n\n\t--color[=<WHEN>]\n\t\tEnable colored output even if stdout is not a terminal. <WHEN> can be one\n\t\tof \"always\" (default for ''--color''), \"never\", or \"auto\" (default).\n\n\t-o, --sort <KEY>\n\t\tSort displayed pull requests by \"created\" (default), \"updated\", \"popularity\", or \"long-running\".\n\n\t-^, --sort-ascending\n\t\tSort by ascending dates instead of descending.\n\n\t-L, --limit <LIMIT>\n\t\tDisplay only the first <LIMIT> pull requests.\n\n\t-u, --url\n\t\tPrint the pull request URL instead of opening it.\n\n\t-c, --copy\n\t\tPut the pull request URL to clipboard instead of opening it.\n\n\t-m, --message <MESSAGE>\n\t\tThe text up to the first blank line in <MESSAGE> is treated as the commit\n\t\tsubject for the merge commit, and the rest is used as commit body.\n\n\t\tWhen multiple ''--message'' are passed, their values are concatenated with a\n\t\tblank line in-between.\n\n\t-F, --file <FILE>\n\t\tRead the subject and body for the merge commit from <FILE>. Pass \"-\" to read\n\t\tfrom standard input instead. See ''--message'' for the formatting rules.\n\n\t--head-sha <COMMIT-SHA>\n\t\tEnsure that the head of the pull request matches the commit SHA when merging.\n\n\t--squash\n\t\tSquash commits instead of creating a merge commit when merging a pull request.\n\n\t--rebase\n\t\tRebase commits on top of the base branch when merging a pull request.\n\n\t-d, --delete-branch\n\t\tDelete the head branch after successfully merging a pull request.\n\n## See also:\n\nhub-issue(1), hub-pull-request(1), hub(1)\n`,\n\t}\n\n\tcmdCheckoutPr = &Command{\n\t\tKey:        \"checkout\",\n\t\tRun:        checkoutPr,\n\t\tKnownFlags: \"\\n\",\n\t}\n\n\tcmdListPulls = &Command{\n\t\tKey:  \"list\",\n\t\tRun:  listPulls,\n\t\tLong: cmdPr.Long,\n\t}\n\n\tcmdShowPr = &Command{\n\t\tKey: \"show\",\n\t\tRun: showPr,\n\t\tKnownFlags: `\n\t\t-h, --head HEAD\n\t\t-u, --url\n\t\t-c, --copy\n\t\t-f, --format FORMAT\n\t\t--color\n\t\t`,\n\t}\n\n\tcmdMergePr = &Command{\n\t\tKey: \"merge\",\n\t\tRun: mergePr,\n\t\tKnownFlags: `\n\t\t-m, --message MESSAGE\n\t\t-F, --file FILE\n\t\t--head-sha COMMIT\n\t\t--squash\n\t\t--rebase\n\t\t-d, --delete-branch\n\t\t`,\n\t}\n)\n\nfunc init() {\n\tcmdPr.Use(cmdListPulls)\n\tcmdPr.Use(cmdCheckoutPr)\n\tcmdPr.Use(cmdShowPr)\n\tcmdPr.Use(cmdMergePr)\n\tCmdRunner.Use(cmdPr)\n}\n\nfunc printHelp(command *Command, args *Args) {\n\tutils.Check(command.UsageError(\"\"))\n}\n\nfunc listPulls(cmd *Command, args *Args) {\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\tgh := github.NewClient(project.Host)\n\n\targs.NoForward()\n\tif args.Noop {\n\t\tui.Printf(\"Would request list of pull requests for %s\\n\", project)\n\t\treturn\n\t}\n\n\tfilters := map[string]interface{}{}\n\tif args.Flag.HasReceived(\"--state\") {\n\t\tfilters[\"state\"] = args.Flag.Value(\"--state\")\n\t}\n\tif args.Flag.HasReceived(\"--sort\") {\n\t\tfilters[\"sort\"] = args.Flag.Value(\"--sort\")\n\t}\n\tif args.Flag.HasReceived(\"--base\") {\n\t\tfilters[\"base\"] = args.Flag.Value(\"--base\")\n\t}\n\tif args.Flag.HasReceived(\"--head\") {\n\t\thead := args.Flag.Value(\"--head\")\n\t\tif !strings.Contains(head, \":\") {\n\t\t\thead = fmt.Sprintf(\"%s:%s\", project.Owner, head)\n\t\t}\n\t\tfilters[\"head\"] = head\n\t}\n\n\tif args.Flag.Bool(\"--sort-ascending\") {\n\t\tfilters[\"direction\"] = \"asc\"\n\t} else {\n\t\tfilters[\"direction\"] = \"desc\"\n\t}\n\n\tonlyMerged := false\n\tif filters[\"state\"] == \"merged\" {\n\t\tfilters[\"state\"] = \"closed\"\n\t\tonlyMerged = true\n\t}\n\n\tflagPullRequestLimit := args.Flag.Int(\"--limit\")\n\tflagPullRequestFormat := args.Flag.Value(\"--format\")\n\tif !args.Flag.HasReceived(\"--format\") {\n\t\tflagPullRequestFormat = \"%pC%>(8)%i%Creset  %t%  l%n\"\n\t}\n\n\tpulls, err := gh.FetchPullRequests(project, filters, flagPullRequestLimit, func(pr *github.PullRequest) bool {\n\t\treturn !(onlyMerged && pr.MergedAt.IsZero())\n\t})\n\tutils.Check(err)\n\n\tcolorize := colorizeOutput(args.Flag.HasReceived(\"--color\"), args.Flag.Value(\"--color\"))\n\tfor _, pr := range pulls {\n\t\tui.Print(formatPullRequest(pr, flagPullRequestFormat, colorize))\n\t}\n}\n\nfunc checkoutPr(command *Command, args *Args) {\n\twords := args.Words()\n\tvar newBranchName string\n\n\tif len(words) == 0 {\n\t\tutils.Check(fmt.Errorf(\"Error: No pull request number given\"))\n\t} else if len(words) > 1 {\n\t\tnewBranchName = words[1]\n\t}\n\n\tprNumberString := words[0]\n\t_, err := strconv.Atoi(prNumberString)\n\tutils.Check(err)\n\n\t// Figure out the PR URL\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\tbaseProject, err := localRepo.MainProject()\n\tutils.Check(err)\n\thost, err := github.CurrentConfig().PromptForHost(baseProject.Host)\n\tutils.Check(err)\n\tclient := github.NewClientWithHost(host)\n\tpr, err := client.PullRequest(baseProject, prNumberString)\n\tutils.Check(err)\n\n\tnewArgs, err := transformCheckoutArgs(args, pr, newBranchName)\n\tutils.Check(err)\n\n\targs.Replace(args.Executable, \"checkout\", newArgs...)\n}\n\nfunc showPr(command *Command, args *Args) {\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tbaseProject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\thost, err := github.CurrentConfig().PromptForHost(baseProject.Host)\n\tutils.Check(err)\n\tgh := github.NewClientWithHost(host)\n\n\twords := args.Words()\n\topenURL := \"\"\n\tprNumber := 0\n\tvar pr *github.PullRequest\n\n\tif len(words) > 0 {\n\t\tif prNumber, err = strconv.Atoi(words[0]); err == nil {\n\t\t\topenURL = baseProject.WebURL(\"\", \"\", fmt.Sprintf(\"pull/%d\", prNumber))\n\t\t} else {\n\t\t\tutils.Check(fmt.Errorf(\"invalid pull request number: '%s'\", words[0]))\n\t\t}\n\t} else {\n\t\tpr, err = findCurrentPullRequest(localRepo, gh, baseProject, args.Flag.Value(\"--head\"))\n\t\tutils.Check(err)\n\t\topenURL = pr.HTMLURL\n\t}\n\n\targs.NoForward()\n\tif format := args.Flag.Value(\"--format\"); format != \"\" {\n\t\tif pr == nil {\n\t\t\tpr, err = gh.PullRequest(baseProject, strconv.Itoa(prNumber))\n\t\t\tutils.Check(err)\n\t\t}\n\t\tcolorize := colorizeOutput(args.Flag.HasReceived(\"--color\"), args.Flag.Value(\"--color\"))\n\t\tui.Println(formatPullRequest(*pr, format, colorize))\n\t\treturn\n\t}\n\n\tprintURL := args.Flag.Bool(\"--url\")\n\tcopyURL := args.Flag.Bool(\"--copy\")\n\n\tprintBrowseOrCopy(args, openURL, !printURL && !copyURL, copyURL)\n}\n\nfunc findCurrentPullRequest(localRepo *github.GitHubRepo, gh *github.Client, baseProject *github.Project, headArg string) (*github.PullRequest, error) {\n\tfilterParams := map[string]interface{}{\n\t\t\"state\": \"open\",\n\t}\n\theadWithOwner := \"\"\n\n\tif headArg != \"\" {\n\t\theadWithOwner = headArg\n\t\tif !strings.Contains(headWithOwner, \":\") {\n\t\t\theadWithOwner = fmt.Sprintf(\"%s:%s\", baseProject.Owner, headWithOwner)\n\t\t}\n\t} else {\n\t\tcurrentBranch, err := localRepo.CurrentBranch()\n\t\tutils.Check(err)\n\t\tif headBranch, headProject, err := findPushTarget(currentBranch); err == nil {\n\t\t\theadWithOwner = fmt.Sprintf(\"%s:%s\", headProject.Owner, headBranch.ShortName())\n\t\t} else if headProject, err := deducePushTarget(currentBranch, gh.Host.User); err == nil {\n\t\t\theadWithOwner = fmt.Sprintf(\"%s:%s\", headProject.Owner, currentBranch.ShortName())\n\t\t} else {\n\t\t\theadWithOwner = fmt.Sprintf(\"%s:%s\", baseProject.Owner, currentBranch.ShortName())\n\t\t}\n\t}\n\n\tfilterParams[\"head\"] = headWithOwner\n\n\tpulls, err := gh.FetchPullRequests(baseProject, filterParams, 1, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t} else if len(pulls) == 1 {\n\t\treturn &pulls[0], nil\n\t} else {\n\t\treturn nil, fmt.Errorf(\"no open pull requests found for branch '%s'\", headWithOwner)\n\t}\n}\n\nfunc branchTrackingInformation(branch *github.Branch) (string, *github.Branch, error) {\n\tbranchRemote, err := git.Config(fmt.Sprintf(\"branch.%s.remote\", branch.ShortName()))\n\tif branchRemote == \".\" {\n\t\terr = fmt.Errorf(\"branch is tracking another local branch\")\n\t}\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tbranchMerge, err := git.Config(fmt.Sprintf(\"branch.%s.merge\", branch.ShortName()))\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\ttrackingBranch := &github.Branch{\n\t\tRepo: branch.Repo,\n\t\tName: branchMerge,\n\t}\n\treturn branchRemote, trackingBranch, nil\n}\n\nfunc findPushTarget(branch *github.Branch) (*github.Branch, *github.Project, error) {\n\tbranchRemote, headBranch, err := branchTrackingInformation(branch)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif headRemote, err := branch.Repo.RemoteByName(branchRemote); err == nil {\n\t\theadProject, err := headRemote.Project()\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\treturn headBranch, headProject, nil\n\t}\n\n\tremoteURL, err := git.ParseURL(branchRemote)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\theadProject, err := github.NewProjectFromURL(remoteURL)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn headBranch, headProject, nil\n}\n\nfunc deducePushTarget(branch *github.Branch, owner string) (*github.Project, error) {\n\tremote := branch.Repo.RemoteForBranch(branch, owner)\n\tif remote == nil {\n\t\treturn nil, fmt.Errorf(\"no remote found for branch %s\", branch.ShortName())\n\t}\n\treturn remote.Project()\n}\n\nfunc mergePr(command *Command, args *Args) {\n\twords := args.Words()\n\tif len(words) == 0 {\n\t\tutils.Check(fmt.Errorf(\"Error: No pull request number given\"))\n\t}\n\n\tprNumber, err := strconv.Atoi(words[0])\n\tutils.Check(err)\n\n\tparams := map[string]interface{}{\n\t\t\"merge_method\": \"merge\",\n\t}\n\tif args.Flag.Bool(\"--squash\") {\n\t\tparams[\"merge_method\"] = \"squash\"\n\t}\n\tif args.Flag.Bool(\"--rebase\") {\n\t\tparams[\"merge_method\"] = \"rebase\"\n\t}\n\n\tmsgs := args.Flag.AllValues(\"--message\")\n\tif len(msgs) > 0 {\n\t\tparams[\"commit_title\"] = msgs[0]\n\t\tparams[\"commit_message\"] = strings.Join(msgs[1:], \"\\n\\n\")\n\t} else if args.Flag.HasReceived(\"--file\") {\n\t\tcontent, err := msgFromFile(args.Flag.Value(\"--file\"))\n\t\tutils.Check(err)\n\t\tparams[\"commit_title\"], params[\"commit_message\"] = github.SplitTitleBody(content)\n\t}\n\n\tif headSHA := args.Flag.Value(\"--head-sha\"); headSHA != \"\" {\n\t\tparams[\"sha\"] = args.Flag.Value(\"--head-sha\")\n\t}\n\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\targs.NoForward()\n\tif args.Noop {\n\t\tui.Printf(\"Would merge pull request #%d for %s\\n\", prNumber, project)\n\t\treturn\n\t}\n\n\tgh := github.NewClient(project.Host)\n\t_, err = gh.MergePullRequest(project, prNumber, params)\n\tutils.Check(err)\n\n\tif !args.Flag.Bool(\"--delete-branch\") {\n\t\treturn\n\t}\n\n\tpr, err := gh.PullRequest(project, strconv.Itoa(prNumber))\n\tutils.Check(err)\n\tif !pr.IsSameRepo() {\n\t\treturn\n\t}\n\n\tbranchName := pr.Head.Ref\n\terr = gh.DeleteBranch(project, branchName)\n\tutils.Check(err)\n}\n\nfunc formatPullRequest(pr github.PullRequest, format string, colorize bool) string {\n\tplaceholders := formatIssuePlaceholders(github.Issue(pr), colorize)\n\tdelete(placeholders, \"NC\")\n\tdelete(placeholders, \"Nc\")\n\n\tfor key, value := range formatPullRequestPlaceholders(pr, colorize) {\n\t\tplaceholders[key] = value\n\t}\n\treturn ui.Expand(format, placeholders, colorize)\n}\n"
  },
  {
    "path": "commands/pull_request.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/github/hub/v2/git\"\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdPullRequest = &Command{\n\tRun: pullRequest,\n\tUsage: `\npull-request [-focpd] [-b <BASE>] [-h <HEAD>] [-r <REVIEWERS> ] [-a <ASSIGNEES>] [-M <MILESTONE>] [-l <LABELS>]\npull-request -m <MESSAGE> [--edit]\npull-request -F <FILE> [--edit]\npull-request -i <ISSUE>\n`,\n\tLong: `Create a GitHub Pull Request.\n\n## Options:\n\t-f, --force\n\t\tSkip the check for unpushed commits.\n\n\t-m, --message <MESSAGE>\n\t\tThe text up to the first blank line in <MESSAGE> is treated as the pull\n\t\trequest title, and the rest is used as pull request description in Markdown\n\t\tformat.\n\n\t\tWhen multiple ''--message'' are passed, their values are concatenated with a\n\t\tblank line in-between.\n\n\t\tWhen neither ''--message'' nor ''--file'' were supplied, a text editor will open\n\t\tto author the title and description in.\n\n\t--no-edit\n\t\tUse the message from the first commit on the branch as pull request title\n\t\tand description without opening a text editor.\n\n\t-F, --file <FILE>\n\t\tRead the pull request title and description from <FILE>. Pass \"-\" to read\n\t\tfrom standard input instead. See ''--message'' for the formatting rules.\n\n\t-e, --edit\n\t\tOpen the pull request title and description in a text editor before\n\t\tsubmitting. This can be used in combination with ''--message'' or ''--file''.\n\n\t-i, --issue <ISSUE>\n\t\tConvert <ISSUE> (referenced by its number) to a pull request.\n\n\t\tYou can only convert issues authored by you or that which you have admin\n\t\trights over. In most workflows it is not necessary to convert issues to\n\t\tpull requests; you can simply reference the original issue in the body of\n\t\tthe new pull request.\n\n\t-o, --browse\n\t\tOpen the new pull request in a web browser.\n\n\t-c, --copy\n\t\tPut the URL of the new pull request to clipboard instead of printing it.\n\n\t-p, --push\n\t\tPush the current branch to <HEAD> before creating the pull request.\n\n\t-b, --base <BASE>\n\t\tThe base branch in the \"[<OWNER>:]<BRANCH>\" format. Defaults to the default\n\t\tbranch of the upstream repository (usually \"master\").\n\n\t\tSee the \"CONVENTIONS\" section of hub(1) for more information on how hub\n\t\tselects the defaults in case of multiple git remotes.\n\n\t-h, --head <HEAD>\n\t\tThe head branch in \"[<OWNER>:]<BRANCH>\" format. Defaults to the currently\n\t\tchecked out branch.\n\n\t-r, --reviewer <USERS>\n\t\tA comma-separated list (no spaces around the comma) of GitHub handles to\n\t\trequest a review from.\n\n\t-a, --assign <USERS>\n\t\tA comma-separated list (no spaces around the comma) of GitHub handles to\n\t\tassign to this pull request.\n\n\t-M, --milestone <NAME>\n\t\tThe milestone name to add to this pull request. Passing the milestone number\n\t\tis deprecated.\n\n\t-l, --labels <LABELS>\n\t\tA comma-separated list (no spaces around the comma) of labels to add to\n\t\tthis pull request. Labels will be created if they do not already exist.\n\n\t-d, --draft\n\t\tCreate the pull request as a draft.\n\n\t--no-maintainer-edits\n\t\tWhen creating a pull request from a fork, this disallows projects\n\t\tmaintainers from being able to push to the head branch of this fork.\n\t\tMaintainer edits are allowed by default.\n\n## Examples:\n\t\t$ hub pull-request\n\t\t[ opens a text editor for writing title and message ]\n\t\t[ creates a pull request for the current branch ]\n\n\t\t$ hub pull-request --base OWNER:master --head MYUSER:my-branch\n\t\t[ creates a pull request with explicit base and head branches ]\n\n\t\t$ hub pull-request --browse -m \"My title\"\n\t\t[ creates a pull request with the given title and opens it in a browser ]\n\n\t\t$ hub pull-request -F - --edit < path/to/message-template.md\n\t\t[ further edit the title and message received on standard input ]\n\n## Configuration:\n\n\t* ''HUB_RETRY_TIMEOUT'':\n\t\tThe maximum time to keep retrying after HTTP 422 on ''--push'' (default: 9).\n\n## See also:\n\nhub(1), hub-merge(1), hub-checkout(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdPullRequest)\n}\n\nfunc pullRequest(cmd *Command, args *Args) {\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tcurrentBranch, currentBranchErr := localRepo.CurrentBranch()\n\n\tbaseProject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\thost, err := github.CurrentConfig().PromptForHost(baseProject.Host)\n\tif err != nil {\n\t\tutils.Check(github.FormatError(\"creating pull request\", err))\n\t}\n\tclient := github.NewClientWithHost(host)\n\n\ttrackedBranch, headProject, _ := localRepo.RemoteBranchAndProject(host.User, false)\n\tif headProject == nil {\n\t\tutils.Check(fmt.Errorf(\"could not determine project for head branch\"))\n\t}\n\n\tvar (\n\t\tbase, head string\n\t)\n\n\tif flagPullRequestBase := args.Flag.Value(\"--base\"); flagPullRequestBase != \"\" {\n\t\tbaseProject, base = parsePullRequestProject(baseProject, flagPullRequestBase)\n\t}\n\n\tif flagPullRequestHead := args.Flag.Value(\"--head\"); flagPullRequestHead != \"\" {\n\t\theadProject, head = parsePullRequestProject(headProject, flagPullRequestHead)\n\t}\n\n\tbaseRemote, _ := localRepo.RemoteForProject(baseProject)\n\tif base == \"\" && baseRemote != nil {\n\t\tbase = localRepo.DefaultBranch(baseRemote).ShortName()\n\t}\n\n\tif head == \"\" && trackedBranch != nil {\n\t\tif !trackedBranch.IsRemote() {\n\t\t\t// the current branch tracking another branch\n\t\t\t// pretend there's no upstream at all\n\t\t\ttrackedBranch = nil\n\t\t} else {\n\t\t\tif baseProject.SameAs(headProject) && base == trackedBranch.ShortName() {\n\t\t\t\te := fmt.Errorf(`Aborted: head branch is the same as base (\"%s\")`, base)\n\t\t\t\te = fmt.Errorf(\"%s\\n(use `-h <branch>` to specify an explicit pull request head)\", e)\n\t\t\t\tutils.Check(e)\n\t\t\t}\n\t\t}\n\t}\n\n\tforce := args.Flag.Bool(\"--force\")\n\tflagPullRequestPush := args.Flag.Bool(\"--push\")\n\n\tif head == \"\" {\n\t\tif trackedBranch == nil {\n\t\t\tutils.Check(currentBranchErr)\n\t\t\tif !force && !flagPullRequestPush {\n\t\t\t\tbranchRemote, branchMerge, err := branchTrackingInformation(currentBranch)\n\t\t\t\tif err != nil || (baseRemote != nil && branchRemote == baseRemote.Name && branchMerge.ShortName() == base) {\n\t\t\t\t\tif localRepo.RemoteForBranch(currentBranch, host.User) == nil {\n\t\t\t\t\t\terr = fmt.Errorf(\"Aborted: the current branch seems not yet pushed to a remote\")\n\t\t\t\t\t\terr = fmt.Errorf(\"%s\\n(use `-p` to push the branch or `-f` to skip this check)\", err)\n\t\t\t\t\t\tutils.Check(err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\thead = currentBranch.ShortName()\n\t\t} else {\n\t\t\thead = trackedBranch.ShortName()\n\t\t}\n\t}\n\n\tif headRepo, err := client.Repository(headProject); err == nil {\n\t\theadProject.Owner = headRepo.Owner.Login\n\t\theadProject.Name = headRepo.Name\n\t}\n\n\tfullBase := fmt.Sprintf(\"%s:%s\", baseProject.Owner, base)\n\tfullHead := fmt.Sprintf(\"%s:%s\", headProject.Owner, head)\n\n\tif !force && trackedBranch != nil {\n\t\tremoteCommits, err := git.RefList(trackedBranch.LongName(), \"\")\n\t\tif err == nil && len(remoteCommits) > 0 {\n\t\t\terr = fmt.Errorf(\"Aborted: %d commits are not yet pushed to %s\", len(remoteCommits), trackedBranch.LongName())\n\t\t\terr = fmt.Errorf(\"%s\\n(use `-f` to force submit a pull request anyway)\", err)\n\t\t\tutils.Check(err)\n\t\t}\n\t}\n\n\tmessageBuilder := &github.MessageBuilder{\n\t\tFilename: \"PULLREQ_EDITMSG\",\n\t\tTitle:    \"pull request\",\n\t}\n\n\tbaseTracking := base\n\theadTracking := head\n\n\tremote := baseRemote\n\tif remote != nil {\n\t\tbaseTracking = fmt.Sprintf(\"%s/%s\", remote.Name, base)\n\t}\n\tif remote == nil || !baseProject.SameAs(headProject) {\n\t\tremote, _ = localRepo.RemoteForProject(headProject)\n\t}\n\tif remote != nil {\n\t\theadTracking = fmt.Sprintf(\"%s/%s\", remote.Name, head)\n\t}\n\n\tif flagPullRequestPush && remote == nil {\n\t\tutils.Check(fmt.Errorf(\"Can't find remote for %s\", head))\n\t}\n\n\tmessageBuilder.AddCommentedSection(fmt.Sprintf(`Requesting a pull to %s from %s\n\nWrite a message for this pull request. The first block\nof text is the title and the rest is the description.`, fullBase, fullHead))\n\n\tflagPullRequestMessage := args.Flag.AllValues(\"--message\")\n\tflagPullRequestEdit := args.Flag.Bool(\"--edit\")\n\tflagPullRequestIssue := args.Flag.Value(\"--issue\")\n\tif !args.Flag.HasReceived(\"--issue\") && args.ParamsSize() > 0 {\n\t\tflagPullRequestIssue = parsePullRequestIssueNumber(args.GetParam(0))\n\t}\n\n\tif len(flagPullRequestMessage) > 0 {\n\t\tmessageBuilder.Message = strings.Join(flagPullRequestMessage, \"\\n\\n\")\n\t\tmessageBuilder.Edit = flagPullRequestEdit\n\t} else if args.Flag.HasReceived(\"--file\") {\n\t\tmessageBuilder.Message, err = msgFromFile(args.Flag.Value(\"--file\"))\n\t\tutils.Check(err)\n\t\tmessageBuilder.Edit = flagPullRequestEdit\n\t} else if args.Flag.Bool(\"--no-edit\") {\n\t\tcommits, _ := git.RefList(baseTracking, head)\n\t\tif len(commits) == 0 {\n\t\t\tutils.Check(fmt.Errorf(\"Aborted: no commits detected between %s and %s\", baseTracking, head))\n\t\t}\n\t\tmessage, err := git.Show(commits[len(commits)-1])\n\t\tutils.Check(err)\n\t\tmessageBuilder.Message = message\n\t} else if flagPullRequestIssue == \"\" {\n\t\tmessageBuilder.Edit = true\n\n\t\theadForMessage := headTracking\n\t\tif flagPullRequestPush {\n\t\t\theadForMessage = head\n\t\t}\n\n\t\tmessage := \"\"\n\n\t\tcommits, _ := git.RefList(baseTracking, headForMessage)\n\t\tif len(commits) == 1 {\n\t\t\tmessage, err = git.Show(commits[0])\n\t\t\tutils.Check(err)\n\n\t\t\tre := regexp.MustCompile(`\\n(Co-authored-by|Signed-off-by):[^\\n]+`)\n\t\t\tmessage = re.ReplaceAllString(message, \"\")\n\t\t} else if len(commits) > 1 {\n\t\t\tcommitLogs, err := git.Log(baseTracking, headForMessage)\n\t\t\tutils.Check(err)\n\n\t\t\tif commitLogs != \"\" {\n\t\t\t\tmessageBuilder.AddCommentedSection(\"\\nChanges:\\n\\n\" + strings.TrimSpace(commitLogs))\n\t\t\t}\n\t\t}\n\n\t\tworkdir, _ := git.WorkdirName()\n\t\tif workdir != \"\" {\n\t\t\ttemplate, _ := github.ReadTemplate(github.PullRequestTemplate, workdir)\n\t\t\tif template != \"\" {\n\t\t\t\tmessage = message + \"\\n\\n\\n\" + template\n\t\t\t}\n\t\t}\n\n\t\tmessageBuilder.Message = message\n\t}\n\n\ttitle, body, err := messageBuilder.Extract()\n\tutils.Check(err)\n\n\tif title == \"\" && flagPullRequestIssue == \"\" {\n\t\tutils.Check(fmt.Errorf(\"Aborting due to empty pull request title\"))\n\t}\n\n\tif flagPullRequestPush {\n\t\tif args.Noop {\n\t\t\targs.Before(fmt.Sprintf(\"Would push to %s/%s\", remote.Name, head), \"\")\n\t\t} else {\n\t\t\terr = git.Spawn(\"push\", \"--set-upstream\", remote.Name, fmt.Sprintf(\"HEAD:%s\", head))\n\t\t\tutils.Check(err)\n\t\t}\n\t}\n\n\tmilestoneNumber, err := milestoneValueToNumber(args.Flag.Value(\"--milestone\"), client, baseProject)\n\tutils.Check(err)\n\n\tvar pullRequestURL string\n\tif args.Noop {\n\t\targs.Before(fmt.Sprintf(\"Would request a pull request to %s from %s\", fullBase, fullHead), \"\")\n\t\tpullRequestURL = \"PULL_REQUEST_URL\"\n\t} else {\n\t\tparams := map[string]interface{}{\n\t\t\t\"base\":                  base,\n\t\t\t\"head\":                  fullHead,\n\t\t\t\"maintainer_can_modify\": !args.Flag.Bool(\"--no-maintainer-edits\"),\n\t\t}\n\n\t\tif args.Flag.Bool(\"--draft\") {\n\t\t\tparams[\"draft\"] = true\n\t\t}\n\n\t\tif title != \"\" {\n\t\t\tparams[\"title\"] = title\n\t\t\tif body != \"\" {\n\t\t\t\tparams[\"body\"] = body\n\t\t\t}\n\t\t} else {\n\t\t\tissueNum, _ := strconv.Atoi(flagPullRequestIssue)\n\t\t\tparams[\"issue\"] = issueNum\n\t\t}\n\n\t\tstartedAt := time.Now()\n\t\tnumRetries := 0\n\t\tretryDelay := 2\n\t\tretryAllowance := 0\n\t\tif flagPullRequestPush {\n\t\t\tif allowanceFromEnv := os.Getenv(\"HUB_RETRY_TIMEOUT\"); allowanceFromEnv != \"\" {\n\t\t\t\tretryAllowance, err = strconv.Atoi(allowanceFromEnv)\n\t\t\t\tutils.Check(err)\n\t\t\t} else {\n\t\t\t\tretryAllowance = 9\n\t\t\t}\n\t\t}\n\n\t\tvar pr *github.PullRequest\n\t\tfor {\n\t\t\tpr, err = client.CreatePullRequest(baseProject, params)\n\t\t\tif err != nil && strings.Contains(err.Error(), `Invalid value for \"head\"`) {\n\t\t\t\tif retryAllowance > 0 {\n\t\t\t\t\tretryAllowance -= retryDelay\n\t\t\t\t\ttime.Sleep(time.Duration(retryDelay) * time.Second)\n\t\t\t\t\tretryDelay++\n\t\t\t\t\tnumRetries++\n\t\t\t\t} else {\n\t\t\t\t\tif numRetries > 0 {\n\t\t\t\t\t\tduration := time.Since(startedAt)\n\t\t\t\t\t\terr = fmt.Errorf(\"%s\\nGiven up after retrying for %.1f seconds.\", err, duration.Seconds())\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif err == nil {\n\t\t\tdefer messageBuilder.Cleanup()\n\t\t}\n\n\t\tutils.Check(err)\n\n\t\tpullRequestURL = pr.HTMLURL\n\n\t\tparams = map[string]interface{}{}\n\t\tflagPullRequestLabels := commaSeparated(args.Flag.AllValues(\"--labels\"))\n\t\tif len(flagPullRequestLabels) > 0 {\n\t\t\tparams[\"labels\"] = flagPullRequestLabels\n\t\t}\n\t\tflagPullRequestAssignees := commaSeparated(args.Flag.AllValues(\"--assign\"))\n\t\tif len(flagPullRequestAssignees) > 0 {\n\t\t\tparams[\"assignees\"] = flagPullRequestAssignees\n\t\t}\n\t\tif milestoneNumber > 0 {\n\t\t\tparams[\"milestone\"] = milestoneNumber\n\t\t}\n\n\t\tif len(params) > 0 {\n\t\t\terr = client.UpdateIssue(baseProject, pr.Number, params)\n\t\t\tutils.Check(err)\n\t\t}\n\n\t\tflagPullRequestReviewers := commaSeparated(args.Flag.AllValues(\"--reviewer\"))\n\t\tif len(flagPullRequestReviewers) > 0 {\n\t\t\tuserReviewers := []string{}\n\t\t\tteamReviewers := []string{}\n\t\t\tfor _, reviewer := range flagPullRequestReviewers {\n\t\t\t\tif strings.Contains(reviewer, \"/\") {\n\t\t\t\t\tteamName := strings.SplitN(reviewer, \"/\", 2)[1]\n\t\t\t\t\tif !pr.HasRequestedTeam(teamName) {\n\t\t\t\t\t\tteamReviewers = append(teamReviewers, teamName)\n\t\t\t\t\t}\n\t\t\t\t} else if !pr.HasRequestedReviewer(reviewer) {\n\t\t\t\t\tuserReviewers = append(userReviewers, reviewer)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif len(userReviewers) > 0 || len(teamReviewers) > 0 {\n\t\t\t\terr = client.RequestReview(baseProject, pr.Number, map[string]interface{}{\n\t\t\t\t\t\"reviewers\":      userReviewers,\n\t\t\t\t\t\"team_reviewers\": teamReviewers,\n\t\t\t\t})\n\t\t\t\tutils.Check(err)\n\t\t\t}\n\t\t}\n\t}\n\n\targs.NoForward()\n\tprintBrowseOrCopy(args, pullRequestURL, args.Flag.Bool(\"--browse\"), args.Flag.Bool(\"--copy\"))\n}\n\nfunc parsePullRequestProject(context *github.Project, s string) (p *github.Project, ref string) {\n\tp = context\n\tref = s\n\n\tif strings.Contains(s, \":\") {\n\t\tsplit := strings.SplitN(s, \":\", 2)\n\t\tref = split[1]\n\t\tvar name string\n\t\tif !strings.Contains(split[0], \"/\") {\n\t\t\tname = context.Name\n\t\t}\n\t\tp = github.NewProject(split[0], name, context.Host)\n\t}\n\n\treturn\n}\n\nfunc parsePullRequestIssueNumber(url string) string {\n\tu, e := github.ParseURL(url)\n\tif e != nil {\n\t\treturn \"\"\n\t}\n\n\tr := regexp.MustCompile(`^issues\\/(\\d+)`)\n\tp := u.ProjectPath()\n\tif r.MatchString(p) {\n\t\treturn r.FindStringSubmatch(p)[1]\n\t}\n\n\treturn \"\"\n}\n\nfunc commaSeparated(l []string) []string {\n\tres := []string{}\n\tfor _, i := range l {\n\t\tif i == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tres = append(res, strings.Split(i, \",\")...)\n\t}\n\treturn res\n}\n"
  },
  {
    "path": "commands/pull_request_test.go",
    "content": "package commands\n\nimport (\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestPullRequest_ParsePullRequestProject(t *testing.T) {\n\tc := &github.Project{Host: \"github.com\", Owner: \"jingweno\", Name: \"gh\"}\n\n\ts := \"develop\"\n\tp, ref := parsePullRequestProject(c, s)\n\tassert.Equal(t, \"develop\", ref)\n\tassert.Equal(t, \"github.com\", p.Host)\n\tassert.Equal(t, \"jingweno\", p.Owner)\n\tassert.Equal(t, \"gh\", p.Name)\n\n\ts = \"mojombo:develop\"\n\tp, ref = parsePullRequestProject(c, s)\n\tassert.Equal(t, \"develop\", ref)\n\tassert.Equal(t, \"github.com\", p.Host)\n\tassert.Equal(t, \"mojombo\", p.Owner)\n\tassert.Equal(t, \"gh\", p.Name)\n\n\ts = \"mojombo/jekyll:develop\"\n\tp, ref = parsePullRequestProject(c, s)\n\tassert.Equal(t, \"develop\", ref)\n\tassert.Equal(t, \"github.com\", p.Host)\n\tassert.Equal(t, \"mojombo\", p.Owner)\n\tassert.Equal(t, \"jekyll\", p.Name)\n}\n"
  },
  {
    "path": "commands/push.go",
    "content": "package commands\n\nimport (\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdPush = &Command{\n\tRun:          push,\n\tGitExtension: true,\n\tUsage:        \"push <REMOTE>[,<REMOTE2>...] [<REF>]\",\n\tLong: `Push a git branch to each of the listed remotes.\n\n## Examples:\n\t\t$ hub push origin,staging,qa bert_timeout\n\t\t> git push origin bert_timeout\n\t\t> git push staging bert_timeout\n\t\t> git push qa bert_timeout\n\n\t\t$ hub push origin\n\t\t> git push origin HEAD\n\n## See also:\n\nhub(1), git-push(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdPush)\n}\n\nfunc push(command *Command, args *Args) {\n\tif !args.IsParamsEmpty() && strings.Contains(args.FirstParam(), \",\") {\n\t\ttransformPushArgs(args)\n\t}\n}\n\nfunc transformPushArgs(args *Args) {\n\trefs := []string{}\n\tif args.ParamsSize() > 1 {\n\t\trefs = args.Params[1:]\n\t}\n\n\tremotes := strings.Split(args.FirstParam(), \",\")\n\targs.ReplaceParam(0, remotes[0])\n\n\tif len(refs) == 0 {\n\t\tlocalRepo, err := github.LocalRepo()\n\t\tutils.Check(err)\n\n\t\thead, err := localRepo.CurrentBranch()\n\t\tutils.Check(err)\n\n\t\trefs = []string{head.ShortName()}\n\t\targs.AppendParams(refs...)\n\t}\n\n\tfor _, remote := range remotes[1:] {\n\t\tafterCmd := []string{\"git\", \"push\", remote}\n\t\tafterCmd = append(afterCmd, refs...)\n\t\targs.After(afterCmd...)\n\t}\n}\n"
  },
  {
    "path": "commands/push_test.go",
    "content": "package commands\n\nimport (\n\t\"github.com/github/hub/v2/internal/assert\"\n\t\"testing\"\n)\n\nfunc TestTransformPushArgs(t *testing.T) {\n\targs := NewArgs([]string{\"push\", \"origin,staging,qa\", \"bert_timeout\"})\n\ttransformPushArgs(args)\n\tcmds := args.Commands()\n\n\tassert.Equal(t, 3, len(cmds))\n\tassert.Equal(t, \"git push origin bert_timeout\", cmds[0].String())\n\tassert.Equal(t, \"git push staging bert_timeout\", cmds[1].String())\n\n\t// TODO: travis-ci doesn't have HEAD\n\t//args = NewArgs([]string{\"push\", \"origin\"})\n\t//transformPushArgs(args)\n\t//cmds = args.Commands()\n\n\t//assert.Equal(t, 1, len(cmds))\n\t//pushRegexp := regexp.MustCompile(\"git push origin .+\")\n\t//assert.T(t, pushRegexp.MatchString(cmds[0].String()))\n}\n"
  },
  {
    "path": "commands/release.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar (\n\tcmdRelease = &Command{\n\t\tRun: listReleases,\n\t\tUsage: `\nrelease [--include-drafts] [--exclude-prereleases] [-L <LIMIT>] [-f <FORMAT>]\nrelease show [-f <FORMAT>] <TAG>\nrelease create [-dpoc] [-a <FILE>] [-m <MESSAGE>|-F <FILE>] [-t <TARGET>] <TAG>\nrelease edit [<options>] <TAG>\nrelease download <TAG> [-i <PATTERN>]\nrelease delete <TAG>\n`,\n\t\tLong: `Manage GitHub Releases for the current repository.\n\n## Commands:\n\nWith no arguments, shows a list of existing releases.\n\n\t* _show_:\n\t\tShow GitHub release notes for <TAG>.\n\n\t\tWith ''--show-downloads'', include the \"Downloads\" section.\n\n\t* _create_:\n\t\tCreate a GitHub release for the specified <TAG> name. If git tag <TAG>\n\t\tdoes not exist, it will be created at <TARGET> (default: current branch).\n\n\t* _edit_:\n\t\tEdit the GitHub release for the specified <TAG> name. Accepts the same\n\t\toptions as _create_ command. Publish a draft with ''--draft=false''.\n\n\t\tWithout ''--message'' or ''--file'', a text editor will open pre-populated with\n\t\tthe current release title and body. To re-use existing title and body\n\t\tunchanged, pass ''-m \"\"''.\n\n\t* _download_:\n\t\tDownload the assets attached to release for the specified <TAG>.\n\n\t* _delete_:\n\t\tDelete the release and associated assets for the specified <TAG>. Note that\n\t\tthis does **not** remove the git tag <TAG>.\n\n## Options:\n\t-d, --include-drafts\n\t\tList drafts together with published releases.\n\n\t-p, --exclude-prereleases\n\t\tExclude prereleases from the list.\n\n\t-L, --limit\n\t\tDisplay only the first <LIMIT> releases.\n\n\t-d, --draft\n\t\tCreate a draft release.\n\n\t-p, --prerelease\n\t\tCreate a pre-release.\n\n\t-a, --attach <FILE>\n\t\tAttach a file as an asset for this release.\n\n\t\tIf <FILE> is in the \"<filename>#<text>\" format, the text after the \"#\"\n\t\tcharacter is taken as asset label.\n\n\t-m, --message <MESSAGE>\n\t\tThe text up to the first blank line in <MESSAGE> is treated as the release\n\t\ttitle, and the rest is used as release description in Markdown format.\n\n\t\tWhen multiple ''--message'' are passed, their values are concatenated with a\n\t\tblank line in-between.\n\n\t\tWhen neither ''--message'' nor ''--file'' were supplied to ''release create'', a\n\t\ttext editor will open to author the title and description in.\n\n\t-F, --file <FILE>\n\t\tRead the release title and description from <FILE>. Pass \"-\" to read from\n\t\tstandard input instead. See ''--message'' for the formatting rules.\n\n\t-e, --edit\n\t\tOpen the release title and description in a text editor before submitting.\n\t\tThis can be used in combination with ''--message'' or ''--file''.\n\n\t-o, --browse\n\t\tOpen the new release in a web browser.\n\n\t-c, --copy\n\t\tPut the URL of the new release to clipboard instead of printing it.\n\n\t-t, --commitish <TARGET>\n\t\tA commit SHA or branch name to attach the release to, only used if <TAG>\n\t\tdoes not already exist (default: main branch).\n\n\t-i, --include <PATTERN>\n\t\tFilter the files in the release to those that match the glob <PATTERN>.\n\n\t-f, --format <FORMAT>\n\t\tPretty print releases using <FORMAT> (default: \"%T%n\"). See the \"PRETTY\n\t\tFORMATS\" section of git-log(1) for some additional details on how\n\t\tplaceholders are used in format. The available placeholders for issues are:\n\n\t\t%U: the URL of this release\n\n\t\t%uT: tarball URL\n\n\t\t%uZ: zipball URL\n\n\t\t%uA: asset upload URL\n\n\t\t%S: state (i.e. \"draft\", \"pre-release\")\n\n\t\t%sC: set color to yellow or red, depending on state\n\n\t\t%t: release name\n\n\t\t%T: release tag\n\n\t\t%b: body\n\n\t\t%as: the list of assets attached to this release\n\n\t\t%cD: created date-only (no time of day)\n\n\t\t%cr: created date, relative\n\n\t\t%ct: created date, UNIX timestamp\n\n\t\t%cI: created date, ISO 8601 format\n\n\t\t%pD: published date-only (no time of day)\n\n\t\t%pr: published date, relative\n\n\t\t%pt: published date, UNIX timestamp\n\n\t\t%pI: published date, ISO 8601 format\n\n\t\t%n: newline\n\n\t\t%%: a literal %\n\n\t--color[=<WHEN>]\n\t\tEnable colored output even if stdout is not a terminal. <WHEN> can be one\n\t\tof \"always\" (default for ''--color''), \"never\", or \"auto\" (default).\n\n\t<TAG>\n\t\tThe git tag name for this release.\n\n## See also:\n\nhub(1), git-tag(1)\n`,\n\t\tKnownFlags: `\n\t\t-d, --include-drafts\n\t\t-p, --exclude-prereleases\n\t\t-L, --limit N\n\t\t-f, --format FMT\n\t\t--color\n`,\n\t}\n\n\tcmdShowRelease = &Command{\n\t\tKey: \"show\",\n\t\tRun: showRelease,\n\t\tKnownFlags: `\n\t\t-d, --show-downloads\n\t\t-f, --format FMT\n\t\t--color\n`,\n\t}\n\n\tcmdCreateRelease = &Command{\n\t\tKey: \"create\",\n\t\tRun: createRelease,\n\t\tKnownFlags: `\n\t\t-e, --edit\n\t\t-d, --draft\n\t\t-p, --prerelease\n\t\t-o, --browse\n\t\t-c, --copy\n\t\t-a, --attach FILE\n\t\t-m, --message MSG\n\t\t-F, --file FILE\n\t\t-t, --commitish C\n`,\n\t}\n\n\tcmdEditRelease = &Command{\n\t\tKey: \"edit\",\n\t\tRun: editRelease,\n\t\tKnownFlags: `\n\t\t-e, --edit\n\t\t-d, --draft\n\t\t-p, --prerelease\n\t\t-a, --attach FILE\n\t\t-m, --message MSG\n\t\t-F, --file FILE\n\t\t-t, --commitish C\n`,\n\t}\n\n\tcmdDownloadRelease = &Command{\n\t\tKey: \"download\",\n\t\tRun: downloadRelease,\n\t\tKnownFlags: `\n\t\t-i, --include PATTERN\n\t\t`,\n\t}\n\n\tcmdDeleteRelease = &Command{\n\t\tKey: \"delete\",\n\t\tRun: deleteRelease,\n\t}\n)\n\nfunc init() {\n\tcmdRelease.Use(cmdShowRelease)\n\tcmdRelease.Use(cmdCreateRelease)\n\tcmdRelease.Use(cmdEditRelease)\n\tcmdRelease.Use(cmdDownloadRelease)\n\tcmdRelease.Use(cmdDeleteRelease)\n\tCmdRunner.Use(cmdRelease)\n}\n\nfunc listReleases(cmd *Command, args *Args) {\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\tgh := github.NewClient(project.Host)\n\n\tflagReleaseLimit := args.Flag.Int(\"--limit\")\n\tflagReleaseIncludeDrafts := args.Flag.Bool(\"--include-drafts\")\n\tflagReleaseExcludePrereleases := args.Flag.Bool(\"--exclude-prereleases\")\n\n\tif args.Noop {\n\t\tui.Printf(\"Would request list of releases for %s\\n\", project)\n\t} else {\n\t\treleases, err := gh.FetchReleases(project, flagReleaseLimit, func(release *github.Release) bool {\n\t\t\treturn (!release.Draft || flagReleaseIncludeDrafts) &&\n\t\t\t\t(!release.Prerelease || !flagReleaseExcludePrereleases)\n\t\t})\n\t\tutils.Check(err)\n\n\t\tcolorize := colorizeOutput(args.Flag.HasReceived(\"--color\"), args.Flag.Value(\"--color\"))\n\t\tfor _, release := range releases {\n\t\t\tflagReleaseFormat := \"%T%n\"\n\t\t\tif args.Flag.HasReceived(\"--format\") {\n\t\t\t\tflagReleaseFormat = args.Flag.Value(\"--format\")\n\t\t\t}\n\t\t\tui.Print(formatRelease(release, flagReleaseFormat, colorize))\n\t\t}\n\t}\n\n\targs.NoForward()\n}\n\nfunc formatRelease(release github.Release, format string, colorize bool) string {\n\tstate := \"\"\n\tstateColorSwitch := \"\"\n\tif release.Draft {\n\t\tstate = \"draft\"\n\t\tstateColorSwitch = fmt.Sprintf(\"\\033[%dm\", 33)\n\t} else if release.Prerelease {\n\t\tstate = \"pre-release\"\n\t\tstateColorSwitch = fmt.Sprintf(\"\\033[%dm\", 31)\n\t}\n\n\tvar createdDate, createdAtISO8601, createdAtUnix, createdAtRelative,\n\t\tpublishedDate, publishedAtISO8601, publishedAtUnix, publishedAtRelative string\n\tif !release.CreatedAt.IsZero() {\n\t\tcreatedDate = release.CreatedAt.Format(\"02 Jan 2006\")\n\t\tcreatedAtISO8601 = release.CreatedAt.Format(time.RFC3339)\n\t\tcreatedAtUnix = fmt.Sprintf(\"%d\", release.CreatedAt.Unix())\n\t\tcreatedAtRelative = utils.TimeAgo(release.CreatedAt)\n\t}\n\tif !release.PublishedAt.IsZero() {\n\t\tpublishedDate = release.PublishedAt.Format(\"02 Jan 2006\")\n\t\tpublishedAtISO8601 = release.PublishedAt.Format(time.RFC3339)\n\t\tpublishedAtUnix = fmt.Sprintf(\"%d\", release.PublishedAt.Unix())\n\t\tpublishedAtRelative = utils.TimeAgo(release.PublishedAt)\n\t}\n\n\tassets := make([]string, len(release.Assets))\n\tfor i, asset := range release.Assets {\n\t\tassets[i] = fmt.Sprintf(\"%s\\t%s\", asset.DownloadURL, asset.Label)\n\t}\n\n\tplaceholders := map[string]string{\n\t\t\"U\":  release.HTMLURL,\n\t\t\"uT\": release.TarballURL,\n\t\t\"uZ\": release.ZipballURL,\n\t\t\"uA\": release.UploadURL,\n\t\t\"S\":  state,\n\t\t\"sC\": stateColorSwitch,\n\t\t\"t\":  release.Name,\n\t\t\"T\":  release.TagName,\n\t\t\"b\":  release.Body,\n\t\t\"as\": strings.Join(assets, \"\\n\"),\n\t\t\"cD\": createdDate,\n\t\t\"cI\": createdAtISO8601,\n\t\t\"ct\": createdAtUnix,\n\t\t\"cr\": createdAtRelative,\n\t\t\"pD\": publishedDate,\n\t\t\"pI\": publishedAtISO8601,\n\t\t\"pt\": publishedAtUnix,\n\t\t\"pr\": publishedAtRelative,\n\t}\n\n\treturn ui.Expand(format, placeholders, colorize)\n}\n\nfunc showRelease(cmd *Command, args *Args) {\n\ttagName := \"\"\n\tif args.ParamsSize() > 0 {\n\t\ttagName = args.GetParam(0)\n\t}\n\tif tagName == \"\" {\n\t\tutils.Check(cmd.UsageError(\"\"))\n\t}\n\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\tgh := github.NewClient(project.Host)\n\n\targs.NoForward()\n\n\tif args.Noop {\n\t\tui.Printf(\"Would display information for `%s' release\\n\", tagName)\n\t} else {\n\t\trelease, err := gh.FetchRelease(project, tagName)\n\t\tutils.Check(err)\n\n\t\tbody := strings.TrimSpace(release.Body)\n\n\t\tcolorize := colorizeOutput(args.Flag.HasReceived(\"--color\"), args.Flag.Value(\"--color\"))\n\t\tif flagShowReleaseFormat := args.Flag.Value(\"--format\"); flagShowReleaseFormat != \"\" {\n\t\t\tui.Print(formatRelease(*release, flagShowReleaseFormat, colorize))\n\t\t\treturn\n\t\t}\n\n\t\tui.Println(release.Name)\n\t\tif body != \"\" {\n\t\t\tui.Printf(\"\\n%s\\n\", body)\n\t\t}\n\t\tif args.Flag.Bool(\"--show-downloads\") {\n\t\t\tui.Printf(\"\\n## Downloads\\n\\n\")\n\t\t\tfor _, asset := range release.Assets {\n\t\t\t\tui.Println(asset.DownloadURL)\n\t\t\t}\n\t\t\tif release.ZipballURL != \"\" {\n\t\t\t\tui.Println(release.ZipballURL)\n\t\t\t\tui.Println(release.TarballURL)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc downloadRelease(cmd *Command, args *Args) {\n\ttagName := \"\"\n\tif args.ParamsSize() > 0 {\n\t\ttagName = args.GetParam(0)\n\t}\n\tif tagName == \"\" {\n\t\tutils.Check(cmd.UsageError(\"\"))\n\t}\n\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\tgh := github.NewClient(project.Host)\n\n\trelease, err := gh.FetchRelease(project, tagName)\n\tutils.Check(err)\n\n\thasPattern := args.Flag.HasReceived(\"--include\")\n\tfound := false\n\tfor _, asset := range release.Assets {\n\t\tif hasPattern {\n\t\t\tisMatch, err := filepath.Match(args.Flag.Value(\"--include\"), asset.Name)\n\t\t\tutils.Check(err)\n\t\t\tif !isMatch {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tfound = true\n\t\tui.Printf(\"Downloading %s ...\\n\", asset.Name)\n\t\terr := downloadReleaseAsset(asset, gh)\n\t\tutils.Check(err)\n\t}\n\n\tif !found && hasPattern {\n\t\tnames := []string{}\n\t\tfor _, asset := range release.Assets {\n\t\t\tnames = append(names, asset.Name)\n\t\t}\n\t\tutils.Check(fmt.Errorf(\"the `--include` pattern did not match any available assets:\\n%s\", strings.Join(names, \"\\n\")))\n\t}\n\n\targs.NoForward()\n}\n\nfunc downloadReleaseAsset(asset github.ReleaseAsset, gh *github.Client) (err error) {\n\tassetReader, err := gh.DownloadReleaseAsset(asset.APIURL)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer assetReader.Close()\n\n\tassetFile, err := os.OpenFile(asset.Name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer assetFile.Close()\n\n\t_, err = io.Copy(assetFile, assetReader)\n\tif err != nil {\n\t\treturn\n\t}\n\treturn\n}\n\nfunc createRelease(cmd *Command, args *Args) {\n\ttagName := \"\"\n\tif args.ParamsSize() > 0 {\n\t\ttagName = args.GetParam(0)\n\t}\n\tif tagName == \"\" {\n\t\tutils.Check(cmd.UsageError(\"\"))\n\t\treturn\n\t}\n\n\tassetsToUpload, close, err := openAssetFiles(args.Flag.AllValues(\"--attach\"))\n\tutils.Check(err)\n\tdefer close()\n\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\tgh := github.NewClient(project.Host)\n\n\tmessageBuilder := &github.MessageBuilder{\n\t\tFilename: \"RELEASE_EDITMSG\",\n\t\tTitle:    \"release\",\n\t}\n\n\tmessageBuilder.AddCommentedSection(fmt.Sprintf(`Creating release %s for %s\n\nWrite a message for this release. The first block of\ntext is the title and the rest is the description.`, tagName, project))\n\n\tflagReleaseMessage := args.Flag.AllValues(\"--message\")\n\tif len(flagReleaseMessage) > 0 {\n\t\tmessageBuilder.Message = strings.Join(flagReleaseMessage, \"\\n\\n\")\n\t\tmessageBuilder.Edit = args.Flag.Bool(\"--edit\")\n\t} else if args.Flag.HasReceived(\"--file\") {\n\t\tmessageBuilder.Message, err = msgFromFile(args.Flag.Value(\"--file\"))\n\t\tutils.Check(err)\n\t\tmessageBuilder.Edit = args.Flag.Bool(\"--edit\")\n\t} else {\n\t\tmessageBuilder.Edit = true\n\t}\n\n\ttitle, body, err := messageBuilder.Extract()\n\tutils.Check(err)\n\n\tif title == \"\" {\n\t\tutils.Check(fmt.Errorf(\"Aborting release due to empty release title\"))\n\t}\n\n\tparams := &github.Release{\n\t\tTagName:         tagName,\n\t\tTargetCommitish: args.Flag.Value(\"--commitish\"),\n\t\tName:            title,\n\t\tBody:            body,\n\t\tDraft:           args.Flag.Bool(\"--draft\"),\n\t\tPrerelease:      args.Flag.Bool(\"--prerelease\"),\n\t}\n\n\tvar release *github.Release\n\n\targs.NoForward()\n\tif args.Noop {\n\t\tui.Printf(\"Would create release `%s' for %s with tag name `%s'\\n\", title, project, tagName)\n\t} else {\n\t\trelease, err = gh.CreateRelease(project, params)\n\t\tutils.Check(err)\n\n\t\tflagReleaseBrowse := args.Flag.Bool(\"--browse\")\n\t\tflagReleaseCopy := args.Flag.Bool(\"--copy\")\n\t\tprintBrowseOrCopy(args, release.HTMLURL, flagReleaseBrowse, flagReleaseCopy)\n\t}\n\n\tmessageBuilder.Cleanup()\n\n\tnumAssets := len(assetsToUpload)\n\tif numAssets == 0 {\n\t\treturn\n\t}\n\tif args.Noop {\n\t\tui.Printf(\"Would attach %d %s\\n\", numAssets, pluralize(numAssets, \"asset\"))\n\t} else {\n\t\tui.Errorf(\"Attaching %d %s...\\n\", numAssets, pluralize(numAssets, \"asset\"))\n\t\tuploaded, err := gh.UploadReleaseAssets(release, assetsToUpload)\n\t\tif err != nil {\n\t\t\tfailed := []string{}\n\t\t\tfor _, a := range assetsToUpload[len(uploaded):] {\n\t\t\t\tfailed = append(failed, fmt.Sprintf(\"-a %s\", a.Name))\n\t\t\t}\n\t\t\tui.Errorf(\"The release was created, but attaching %d %s failed. \", len(failed), pluralize(len(failed), \"asset\"))\n\t\t\tui.Errorf(\"You can retry with:\\n%s release edit %s -m '' %s\\n\\n\", \"hub\", release.TagName, strings.Join(failed, \" \"))\n\t\t\tutils.Check(err)\n\t\t}\n\t}\n}\n\nfunc editRelease(cmd *Command, args *Args) {\n\ttagName := \"\"\n\tif args.ParamsSize() > 0 {\n\t\ttagName = args.GetParam(0)\n\t}\n\tif tagName == \"\" {\n\t\tutils.Check(cmd.UsageError(\"\"))\n\t\treturn\n\t}\n\n\tassetsToUpload, close, err := openAssetFiles(args.Flag.AllValues(\"--attach\"))\n\tutils.Check(err)\n\tdefer close()\n\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\tgh := github.NewClient(project.Host)\n\n\trelease, err := gh.FetchRelease(project, tagName)\n\tutils.Check(err)\n\n\tparams := map[string]interface{}{}\n\tif args.Flag.HasReceived(\"--commitish\") {\n\t\tparams[\"target_commitish\"] = args.Flag.Value(\"--commitish\")\n\t}\n\tif args.Flag.HasReceived(\"--draft\") {\n\t\tparams[\"draft\"] = args.Flag.Bool(\"--draft\")\n\t}\n\tif args.Flag.HasReceived(\"--prerelease\") {\n\t\tparams[\"prerelease\"] = args.Flag.Bool(\"--prerelease\")\n\t}\n\n\tmessageBuilder := &github.MessageBuilder{\n\t\tFilename: \"RELEASE_EDITMSG\",\n\t\tTitle:    \"release\",\n\t}\n\n\tmessageBuilder.AddCommentedSection(fmt.Sprintf(`Editing release %s for %s\n\nWrite a message for this release. The first block of\ntext is the title and the rest is the description.`, tagName, project))\n\n\tflagReleaseMessage := args.Flag.AllValues(\"--message\")\n\tif len(flagReleaseMessage) > 0 {\n\t\tmessageBuilder.Message = strings.Join(flagReleaseMessage, \"\\n\\n\")\n\t\tmessageBuilder.Edit = args.Flag.Bool(\"--edit\")\n\t} else if args.Flag.HasReceived(\"--file\") {\n\t\tmessageBuilder.Message, err = msgFromFile(args.Flag.Value(\"--file\"))\n\t\tutils.Check(err)\n\t\tmessageBuilder.Edit = args.Flag.Bool(\"--edit\")\n\t} else {\n\t\tmessageBuilder.Edit = true\n\t\tmessageBuilder.Message = strings.Replace(fmt.Sprintf(\"%s\\n\\n%s\", release.Name, release.Body), \"\\r\\n\", \"\\n\", -1)\n\t}\n\n\ttitle, body, err := messageBuilder.Extract()\n\tutils.Check(err)\n\n\tif title == \"\" && len(flagReleaseMessage) == 0 {\n\t\tutils.Check(fmt.Errorf(\"Aborting editing due to empty release title\"))\n\t}\n\n\tif title != \"\" {\n\t\tparams[\"name\"] = title\n\t}\n\tif body != \"\" {\n\t\tparams[\"body\"] = body\n\t}\n\n\targs.NoForward()\n\tif len(params) > 0 {\n\t\tif args.Noop {\n\t\t\tui.Printf(\"Would edit release `%s'\\n\", tagName)\n\t\t} else {\n\t\t\trelease, err = gh.EditRelease(release, params)\n\t\t\tutils.Check(err)\n\t\t}\n\n\t\tmessageBuilder.Cleanup()\n\t}\n\n\tnumAssets := len(assetsToUpload)\n\tif numAssets == 0 {\n\t\treturn\n\t}\n\tif args.Noop {\n\t\tui.Printf(\"Would attach %d %s\\n\", numAssets, pluralize(numAssets, \"asset\"))\n\t} else {\n\t\tui.Errorf(\"Attaching %d %s...\\n\", numAssets, pluralize(numAssets, \"asset\"))\n\t\tuploaded, err := gh.UploadReleaseAssets(release, assetsToUpload)\n\t\tif err != nil {\n\t\t\tfailed := []string{}\n\t\t\tfor _, a := range assetsToUpload[len(uploaded):] {\n\t\t\t\tfailed = append(failed, a.Name)\n\t\t\t}\n\t\t\tui.Errorf(\"Attaching these assets failed:\\n%s\\n\\n\", strings.Join(failed, \"\\n\"))\n\t\t\tutils.Check(err)\n\t\t}\n\t}\n}\n\nfunc deleteRelease(cmd *Command, args *Args) {\n\ttagName := \"\"\n\tif args.ParamsSize() > 0 {\n\t\ttagName = args.GetParam(0)\n\t}\n\tif tagName == \"\" {\n\t\tutils.Check(cmd.UsageError(\"\"))\n\t\treturn\n\t}\n\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tproject, err := localRepo.MainProject()\n\tutils.Check(err)\n\n\tgh := github.NewClient(project.Host)\n\n\trelease, err := gh.FetchRelease(project, tagName)\n\tutils.Check(err)\n\n\tif args.Noop {\n\t\tmessage := fmt.Sprintf(\"Deleting release related to %s...\", tagName)\n\t\tui.Println(message)\n\t} else {\n\t\terr = gh.DeleteRelease(release)\n\t\tutils.Check(err)\n\t}\n\n\targs.NoForward()\n}\n\nfunc openAssetFiles(args []string) ([]github.LocalAsset, func(), error) {\n\tassets := []github.LocalAsset{}\n\tfiles := []*os.File{}\n\n\tfor _, arg := range args {\n\t\tvar label string\n\t\tparts := strings.SplitN(arg, \"#\", 2)\n\t\tpath := parts[0]\n\t\tif len(parts) > 1 {\n\t\t\tlabel = parts[1]\n\t\t}\n\n\t\tfile, err := os.Open(path)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tstat, err := file.Stat()\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tfiles = append(files, file)\n\n\t\tassets = append(assets, github.LocalAsset{\n\t\t\tName:     path,\n\t\t\tLabel:    label,\n\t\t\tSize:     stat.Size(),\n\t\t\tContents: file,\n\t\t})\n\t}\n\n\tclose := func() {\n\t\tfor _, f := range files {\n\t\t\tf.Close()\n\t\t}\n\t}\n\n\treturn assets, close, nil\n}\n\nfunc pluralize(count int, label string) string {\n\tif count == 1 {\n\t\treturn label\n\t}\n\treturn fmt.Sprintf(\"%ss\", label)\n}\n"
  },
  {
    "path": "commands/remote.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/git\"\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdRemote = &Command{\n\tRun:          remote,\n\tGitExtension: true,\n\tUsage: `\nremote add [-p] [<OPTIONS>] <USER>[/<REPOSITORY>]\nremote set-url [-p] [<OPTIONS>] <NAME> <USER>[/<REPOSITORY>]\n`,\n\tLong: `Add a git remote for a GitHub repository.\n\n## Options:\n\t-p\n\t\t(Deprecated) Use the ''ssh:'' protocol instead of ''git:'' for the remote URL.\n\t\tThe writeable ''ssh:'' protocol is automatically used for own repos, GitHub\n\t\tEnterprise remotes, and private or pushable repositories.\n\n\t<USER>[/<REPOSITORY>]\n\t\tIf <USER> is \"origin\", that value will be substituted for your GitHub\n\t\tusername. <REPOSITORY> defaults to the name of the current working directory.\n\n## Examples:\n\t\t$ hub remote add jingweno\n\t\t> git remote add jingweno git://github.com/jingweno/REPO.git\n\n\t\t$ hub remote add origin\n\t\t> git remote add origin git@github.com:USER/REPO.git\n\n## See also:\n\nhub-fork(1), hub(1), git-remote(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdRemote)\n}\n\n/*\n */\nfunc remote(command *Command, args *Args) {\n\tif !args.IsParamsEmpty() && (args.FirstParam() == \"add\" || args.FirstParam() == \"set-url\") {\n\t\ttransformRemoteArgs(args)\n\t}\n}\n\nfunc transformRemoteArgs(args *Args) {\n\townerWithName := args.LastParam()\n\n\tre := regexp.MustCompile(fmt.Sprintf(`^%s(/%s)?$`, OwnerRe, NameRe))\n\tif !re.MatchString(ownerWithName) {\n\t\treturn\n\t}\n\towner := ownerWithName\n\tname := \"\"\n\tif strings.Contains(ownerWithName, \"/\") {\n\t\tparts := strings.SplitN(ownerWithName, \"/\", 2)\n\t\towner, name = parts[0], parts[1]\n\t}\n\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tvar host string\n\tmainProject, err := localRepo.MainProject()\n\tif err == nil {\n\t\thost = mainProject.Host\n\t}\n\n\tif name == \"\" {\n\t\tif mainProject != nil {\n\t\t\tname = mainProject.Name\n\t\t} else {\n\t\t\tdirName, err := git.WorkdirName()\n\t\t\tutils.Check(err)\n\t\t\tname = github.SanitizeProjectName(dirName)\n\t\t}\n\t}\n\n\tvar hostConfig *github.Host\n\tif host == \"\" {\n\t\thostConfig, err = github.CurrentConfig().DefaultHost()\n\t} else {\n\t\thostConfig, err = github.CurrentConfig().PromptForHost(host)\n\t}\n\tif err != nil {\n\t\tutils.Check(github.FormatError(\"adding remote\", err))\n\t}\n\thost = hostConfig.Host\n\n\tp := utils.NewArgsParser()\n\tp.RegisterValue(\"-t\")\n\tp.RegisterValue(\"-m\")\n\tparams, _ := p.Parse(args.Params)\n\tif len(params) > 3 {\n\t\treturn\n\t}\n\n\tfor i, pi := range p.PositionalIndices {\n\t\tif i == 1 && strings.Contains(params[i], \"/\") {\n\t\t\targs.ReplaceParam(pi, owner)\n\t\t} else if i == 2 {\n\t\t\targs.RemoveParam(pi)\n\t\t}\n\t}\n\tif len(params) == 2 && owner == \"origin\" {\n\t\towner = hostConfig.User\n\t}\n\n\tif strings.EqualFold(owner, hostConfig.User) {\n\t\towner = hostConfig.User\n\t}\n\n\tproject := github.NewProject(owner, name, host)\n\n\tisPrivate := parseRemotePrivateFlag(args) || owner == hostConfig.User || project.Host != github.GitHubHost\n\tif !isPrivate {\n\t\tgh := github.NewClient(project.Host)\n\t\trepo, err := gh.Repository(project)\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), \"HTTP 404\") {\n\t\t\t\terr = fmt.Errorf(\"Error: repository %s/%s doesn't exist\", project.Owner, project.Name)\n\t\t\t}\n\t\t\tutils.Check(err)\n\t\t}\n\t\tisPrivate = repo.Private || repo.Permissions.Push\n\t}\n\n\turl := project.GitURL(\"\", \"\", isPrivate)\n\targs.AppendParams(url)\n}\n\nfunc parseRemotePrivateFlag(args *Args) bool {\n\tif i := args.IndexOfParam(\"-p\"); i != -1 {\n\t\targs.RemoveParam(i)\n\t\treturn true\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "commands/remote_test.go",
    "content": "package commands\n\nimport (\n\t\"os\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/fixtures\"\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestTransformRemoteArgs(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\tos.Setenv(\"HUB_PROTOCOL\", \"git\")\n\tgithub.CreateTestConfigs(\"jingweno\", \"123\")\n\n\targs := NewArgs([]string{\"remote\", \"add\", \"jingweno\"})\n\ttransformRemoteArgs(args)\n\n\tassert.Equal(t, 3, args.ParamsSize())\n\tassert.Equal(t, \"add\", args.FirstParam())\n\tassert.Equal(t, \"jingweno\", args.GetParam(1))\n\treg := regexp.MustCompile(\"^git@github\\\\.com:jingweno/.+\\\\.git$\")\n\tassert.T(t, reg.MatchString(args.GetParam(2)))\n\n\targs = NewArgs([]string{\"remote\", \"add\", \"-p\", \"mislav\"})\n\ttransformRemoteArgs(args)\n\n\tassert.Equal(t, 3, args.ParamsSize())\n\tassert.Equal(t, \"add\", args.FirstParam())\n\tassert.Equal(t, \"mislav\", args.GetParam(1))\n\treg = regexp.MustCompile(\"^git@github\\\\.com:mislav/.+\\\\.git$\")\n\tassert.T(t, reg.MatchString(args.GetParam(2)))\n\n\targs = NewArgs([]string{\"remote\", \"add\", \"origin\"})\n\ttransformRemoteArgs(args)\n\n\tassert.Equal(t, 3, args.ParamsSize())\n\tassert.Equal(t, \"add\", args.FirstParam())\n\tassert.Equal(t, \"origin\", args.GetParam(1))\n\treg = regexp.MustCompile(\"^git@github\\\\.com:jingweno/.+\\\\.git$\")\n\tassert.T(t, reg.MatchString(args.GetParam(2)))\n\n\targs = NewArgs([]string{\"remote\", \"add\", \"jingweno\", \"git@github.com:jingweno/gh.git\"})\n\ttransformRemoteArgs(args)\n\n\tassert.Equal(t, 3, args.ParamsSize())\n\tassert.Equal(t, \"jingweno\", args.GetParam(1))\n\tassert.Equal(t, \"add\", args.FirstParam())\n\tassert.Equal(t, \"git@github.com:jingweno/gh.git\", args.GetParam(2))\n}\n"
  },
  {
    "path": "commands/runner.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/cmd\"\n\t\"github.com/github/hub/v2/git\"\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/kballard/go-shellquote\"\n)\n\ntype Runner struct {\n\tcommands map[string]*Command\n}\n\nfunc NewRunner() *Runner {\n\treturn &Runner{\n\t\tcommands: make(map[string]*Command),\n\t}\n}\n\nfunc (r *Runner) All() map[string]*Command {\n\treturn r.commands\n}\n\nfunc (r *Runner) Use(command *Command, aliases ...string) {\n\tr.commands[command.Name()] = command\n\tif len(aliases) > 0 {\n\t\tr.commands[aliases[0]] = command\n\t}\n}\n\nfunc (r *Runner) Lookup(name string) *Command {\n\treturn r.commands[name]\n}\n\nfunc (r *Runner) Execute(cliArgs []string) error {\n\targs := NewArgs(cliArgs[1:])\n\targs.ProgramPath = cliArgs[0]\n\tforceFail := false\n\n\tif args.Command == \"\" && len(args.GlobalFlags) == 0 {\n\t\targs.Command = \"help\"\n\t\tforceFail = true\n\t}\n\n\tcmdName := args.Command\n\tif strings.Contains(cmdName, \"=\") {\n\t\tcmdName = strings.SplitN(cmdName, \"=\", 2)[0]\n\t}\n\n\tgit.GlobalFlags = args.GlobalFlags // preserve git global flags\n\tif !isBuiltInHubCommand(cmdName) {\n\t\texpandAlias(args)\n\t\tcmdName = args.Command\n\t}\n\n\t// make `<cmd> --help` equivalent to `help <cmd>`\n\tif args.ParamsSize() == 1 && args.GetParam(0) == helpFlag {\n\t\tif c := r.Lookup(cmdName); c != nil && !c.GitExtension {\n\t\t\targs.ReplaceParam(0, cmdName)\n\t\t\targs.Command = \"help\"\n\t\t\tcmdName = args.Command\n\t\t}\n\t}\n\n\tcmd := r.Lookup(cmdName)\n\tif cmd != nil && cmd.Runnable() {\n\t\terr := callRunnableCommand(cmd, args)\n\t\tif err == nil && forceFail {\n\t\t\terr = fmt.Errorf(\"\")\n\t\t}\n\t\treturn err\n\t}\n\n\tgitArgs := []string{}\n\tif args.Command != \"\" {\n\t\tgitArgs = append(gitArgs, args.Command)\n\t}\n\tgitArgs = append(gitArgs, args.Params...)\n\n\treturn git.Run(gitArgs...)\n}\n\nfunc callRunnableCommand(cmd *Command, args *Args) error {\n\terr := cmd.Call(args)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcmds := args.Commands()\n\tif args.Noop {\n\t\tprintCommands(cmds)\n\t} else if err = executeCommands(cmds, len(args.Callbacks) == 0); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, fn := range args.Callbacks {\n\t\tif err = fn(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc printCommands(cmds []*cmd.Cmd) {\n\tfor _, c := range cmds {\n\t\tui.Println(c)\n\t}\n}\n\nfunc executeCommands(cmds []*cmd.Cmd, execFinal bool) error {\n\tfor i, c := range cmds {\n\t\tvar err error\n\t\t// Run with `Exec` for the last command in chain\n\t\tif execFinal && i == len(cmds)-1 {\n\t\t\terr = c.Run()\n\t\t} else {\n\t\t\terr = c.Spawn()\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc expandAlias(args *Args) {\n\tcmd := args.Command\n\tif cmd == \"\" {\n\t\treturn\n\t}\n\texpandedCmd, err := git.Alias(cmd)\n\n\tif err == nil && expandedCmd != \"\" && !git.IsBuiltInGitCommand(cmd) {\n\t\twords, e := splitAliasCmd(expandedCmd)\n\t\tif e == nil {\n\t\t\targs.Command = words[0]\n\t\t\targs.PrependParams(words[1:]...)\n\t\t}\n\t}\n}\n\nfunc isBuiltInHubCommand(command string) bool {\n\tfor hubCommand := range CmdRunner.All() {\n\t\tif hubCommand == command {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc splitAliasCmd(cmd string) ([]string, error) {\n\tif cmd == \"\" {\n\t\treturn nil, fmt.Errorf(\"alias can't be empty\")\n\t}\n\n\tif strings.HasPrefix(cmd, \"!\") {\n\t\treturn nil, fmt.Errorf(\"alias starting with ! can't be split\")\n\t}\n\n\twords, err := shellquote.Split(cmd)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn words, nil\n}\n"
  },
  {
    "path": "commands/runner_test.go",
    "content": "package commands\n\nimport (\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestRunner_splitAliasCmd(t *testing.T) {\n\t_, err := splitAliasCmd(\"!source ~/.zshrc\")\n\tassert.NotEqual(t, nil, err)\n\n\twords, err := splitAliasCmd(\"log --pretty=oneline --abbrev-commit --graph --decorate\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, 5, len(words))\n\n\t_, err = splitAliasCmd(\"\")\n\tassert.NotEqual(t, nil, err)\n}\n"
  },
  {
    "path": "commands/submodule.go",
    "content": "package commands\n\nvar cmdSubmodule = &Command{\n\tRun:          submodule,\n\tGitExtension: true,\n\tUsage:        \"submodule add [-p] [<OPTIONS>] [<USER>/]<REPOSITORY> <DESTINATION>\",\n\tLong: `Add a git submodule for a GitHub repository.\n\n## Examples:\n\t\t$ hub submodule add jingweno/gh vendor/gh\n\t\t> git submodule add git://github.com/jingweno/gh.git vendor/gh\n\n## See also:\n\nhub-remote(1), hub(1), git-submodule(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdSubmodule)\n}\n\nfunc submodule(command *Command, args *Args) {\n\tif !args.IsParamsEmpty() {\n\t\ttransformSubmoduleArgs(args)\n\t}\n}\n\nfunc transformSubmoduleArgs(args *Args) {\n\tvar idx int\n\tif idx = args.IndexOfParam(\"add\"); idx == -1 {\n\t\treturn\n\t}\n\targs.RemoveParam(idx)\n\ttransformCloneArgs(args)\n\targs.InsertParam(idx, \"add\")\n}\n"
  },
  {
    "path": "commands/sync.go",
    "content": "package commands\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/git\"\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\nvar cmdSync = &Command{\n\tRun:   sync,\n\tUsage: \"sync [--color]\",\n\tLong: `Fetch git objects from upstream and update local branches.\n\n- If the local branch is outdated, fast-forward it;\n- If the local branch contains unpushed work, warn about it;\n- If the branch seems merged and its upstream branch was deleted, delete it.\n\nIf a local branch does not have any upstream configuration, but has a\nsame-named branch on the remote, treat that as its upstream branch.\n\n## Options:\n\t--color[=<WHEN>]\n\t\tEnable colored output even if stdout is not a terminal. <WHEN> can be one\n\t\tof \"always\" (default for ''--color''), \"never\", or \"auto\" (default).\n\n## See also:\n\nhub(1), git-fetch(1)\n`,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdSync)\n}\n\nfunc sync(cmd *Command, args *Args) {\n\tlocalRepo, err := github.LocalRepo()\n\tutils.Check(err)\n\n\tremote, err := localRepo.MainRemote()\n\tutils.Check(err)\n\n\tdefaultBranch := localRepo.DefaultBranch(remote).ShortName()\n\tfullDefaultBranch := fmt.Sprintf(\"refs/remotes/%s/%s\", remote.Name, defaultBranch)\n\tcurrentBranch := \"\"\n\tif curBranch, err := localRepo.CurrentBranch(); err == nil {\n\t\tcurrentBranch = curBranch.ShortName()\n\t}\n\n\terr = git.Spawn(\"fetch\", \"--prune\", \"--quiet\", \"--progress\", remote.Name)\n\tutils.Check(err)\n\n\tbranchToRemote := map[string]string{}\n\tif lines, err := git.ConfigAll(\"branch.*.remote\"); err == nil {\n\t\tconfigRe := regexp.MustCompile(`^branch\\.(.+?)\\.remote (.+)`)\n\n\t\tfor _, line := range lines {\n\t\t\tif matches := configRe.FindStringSubmatch(line); len(matches) > 0 {\n\t\t\t\tbranchToRemote[matches[1]] = matches[2]\n\t\t\t}\n\t\t}\n\t}\n\n\tbranches, err := git.LocalBranches()\n\tutils.Check(err)\n\n\tvar green,\n\t\tlightGreen,\n\t\tred,\n\t\tlightRed,\n\t\tresetColor string\n\n\tcolorize := colorizeOutput(args.Flag.HasReceived(\"--color\"), args.Flag.Value(\"--color\"))\n\tif colorize {\n\t\tgreen = \"\\033[32m\"\n\t\tlightGreen = \"\\033[32;1m\"\n\t\tred = \"\\033[31m\"\n\t\tlightRed = \"\\033[31;1m\"\n\t\tresetColor = \"\\033[0m\"\n\t}\n\n\tfor _, branch := range branches {\n\t\tfullBranch := fmt.Sprintf(\"refs/heads/%s\", branch)\n\t\tremoteBranch := fmt.Sprintf(\"refs/remotes/%s/%s\", remote.Name, branch)\n\t\tgone := false\n\n\t\tif branchToRemote[branch] == remote.Name {\n\t\t\tif upstream, err := git.SymbolicFullName(fmt.Sprintf(\"%s@{upstream}\", branch)); err == nil {\n\t\t\t\tremoteBranch = upstream\n\t\t\t} else {\n\t\t\t\tremoteBranch = \"\"\n\t\t\t\tgone = true\n\t\t\t}\n\t\t} else if !git.HasFile(strings.Split(remoteBranch, \"/\")...) {\n\t\t\tremoteBranch = \"\"\n\t\t}\n\n\t\tif remoteBranch != \"\" {\n\t\t\tdiff, err := git.NewRange(fullBranch, remoteBranch)\n\t\t\tutils.Check(err)\n\n\t\t\tif diff.IsIdentical() {\n\t\t\t\tcontinue\n\t\t\t} else if diff.IsAncestor() {\n\t\t\t\tif branch == currentBranch {\n\t\t\t\t\tgit.Quiet(\"merge\", \"--ff-only\", \"--quiet\", remoteBranch)\n\t\t\t\t} else {\n\t\t\t\t\tgit.Quiet(\"update-ref\", fullBranch, remoteBranch)\n\t\t\t\t}\n\t\t\t\tui.Printf(\"%sUpdated branch %s%s%s (was %s).\\n\", green, lightGreen, branch, resetColor, diff.A[0:7])\n\t\t\t} else {\n\t\t\t\tui.Errorf(\"warning: '%s' seems to contain unpushed commits\\n\", branch)\n\t\t\t}\n\t\t} else if gone {\n\t\t\tdiff, err := git.NewRange(fullBranch, fullDefaultBranch)\n\t\t\tutils.Check(err)\n\n\t\t\tif diff.IsAncestor() {\n\t\t\t\tif branch == currentBranch {\n\t\t\t\t\tgit.Quiet(\"checkout\", \"--quiet\", defaultBranch)\n\t\t\t\t\tcurrentBranch = defaultBranch\n\t\t\t\t}\n\t\t\t\tgit.Quiet(\"branch\", \"-D\", branch)\n\t\t\t\tui.Printf(\"%sDeleted branch %s%s%s (was %s).\\n\", red, lightRed, branch, resetColor, diff.A[0:7])\n\t\t\t} else {\n\t\t\t\tui.Errorf(\"warning: '%s' was deleted on %s, but appears not merged into '%s'\\n\", branch, remote.Name, defaultBranch)\n\t\t\t}\n\t\t}\n\t}\n\n\targs.NoForward()\n}\n"
  },
  {
    "path": "commands/utils.go",
    "content": "package commands\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/atotto/clipboard\"\n\t\"github.com/github/hub/v2/git\"\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\ntype stringSliceValue []string\n\nfunc (s *stringSliceValue) Set(val string) error {\n\t*s = append(*s, val)\n\treturn nil\n}\n\nfunc (s *stringSliceValue) String() string {\n\treturn fmt.Sprintf(\"%s\", *s)\n}\n\ntype listFlag []string\n\nfunc (l *listFlag) String() string {\n\treturn strings.Join([]string(*l), \",\")\n}\n\nfunc (l *listFlag) Set(value string) error {\n\tfor _, flag := range strings.Split(value, \",\") {\n\t\t*l = append(*l, flag)\n\t}\n\treturn nil\n}\n\ntype messageBlocks []string\n\nfunc (m *messageBlocks) String() string {\n\treturn strings.Join([]string(*m), \"\\n\\n\")\n}\n\nfunc (m *messageBlocks) Set(value string) error {\n\t*m = append(*m, value)\n\treturn nil\n}\n\nfunc isCloneable(file string) bool {\n\tf, err := os.Open(file)\n\tif err != nil {\n\t\treturn false\n\t}\n\tdefer f.Close()\n\n\tfi, err := f.Stat()\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tif fi.IsDir() {\n\t\tgitf, err := os.Open(filepath.Join(file, \".git\"))\n\t\tif err == nil {\n\t\t\tgitf.Close()\n\t\t\treturn true\n\t\t}\n\t\treturn git.IsGitDir(file)\n\t}\n\treader := bufio.NewReader(f)\n\tline, err := reader.ReadString('\\n')\n\tif err == nil {\n\t\treturn strings.Contains(line, \"git bundle\")\n\t}\n\treturn false\n}\n\nfunc isEmptyDir(path string) bool {\n\tfullPath := filepath.Join(path, \"*\")\n\tmatch, _ := filepath.Glob(fullPath)\n\treturn match == nil\n}\n\nfunc msgFromFile(filename string) (string, error) {\n\tvar content []byte\n\tvar err error\n\n\tif filename == \"-\" {\n\t\tcontent, err = ioutil.ReadAll(os.Stdin)\n\t} else {\n\t\tcontent, err = ioutil.ReadFile(filename)\n\t}\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn strings.Replace(string(content), \"\\r\\n\", \"\\n\", -1), nil\n}\n\nfunc printBrowseOrCopy(args *Args, msg string, openBrowser bool, performCopy bool) {\n\tif performCopy {\n\t\tif err := clipboard.WriteAll(msg); err != nil {\n\t\t\tui.Errorf(\"Error copying %s to clipboard:\\n%s\\n\", msg, err.Error())\n\t\t}\n\t}\n\n\tif openBrowser {\n\t\tlauncher, err := utils.BrowserLauncher()\n\t\tutils.Check(err)\n\t\targs.Replace(launcher[0], \"\", launcher[1:]...)\n\t\targs.AppendParams(msg)\n\t}\n\n\tif !openBrowser && !performCopy {\n\t\targs.AfterFn(func() error {\n\t\t\tui.Println(msg)\n\t\t\treturn nil\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "commands/utils_test.go",
    "content": "package commands\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestDirIsNotEmpty(t *testing.T) {\n\tdir := createTempDir(t)\n\tdefer os.RemoveAll(dir)\n\tioutil.TempFile(dir, \"gh-utils-test-\")\n\n\tassert.T(t, !isEmptyDir(dir))\n}\n\nfunc TestDirIsEmpty(t *testing.T) {\n\tdir := createTempDir(t)\n\tdefer os.RemoveAll(dir)\n\n\tassert.T(t, isEmptyDir(dir))\n}\n\nfunc createTempDir(t *testing.T) string {\n\tdir, err := ioutil.TempDir(\"\", \"gh-utils-test-\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn dir\n}\n"
  },
  {
    "path": "commands/version.go",
    "content": "package commands\n\nimport (\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/version\"\n)\n\nvar cmdVersion = &Command{\n\tRun:          runVersion,\n\tUsage:        \"version\",\n\tLong:         \"Shows git version and hub client version.\",\n\tGitExtension: true,\n}\n\nfunc init() {\n\tCmdRunner.Use(cmdVersion, \"--version\")\n}\n\nfunc runVersion(cmd *Command, args *Args) {\n\tversionCmd := args.ToCmd()\n\tversionCmd.Spawn()\n\tui.Printf(\"hub version %s\\n\", version.Version)\n\targs.NoForward()\n}\n"
  },
  {
    "path": "coverage/coverage.go",
    "content": "package coverage\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"reflect\"\n\t\"runtime\"\n)\n\nvar out io.Writer\nvar seen map[string]bool\n\nfunc init() {\n\tvar err error\n\tout, err = os.OpenFile(os.Getenv(\"HUB_COVERAGE\"), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tseen = make(map[string]bool)\n}\n\nfunc Record(data interface{}, i int) {\n\t_, filename, _, _ := runtime.Caller(1)\n\tif !seen[filename] {\n\t\tseen[filename] = true\n\t\td := reflect.ValueOf(data)\n\t\tcount := reflect.ValueOf(d.FieldByName(\"Count\").Interface())\n\t\ttotal := count.Len()\n\t\tfor j := 0; j < total; j++ {\n\t\t\twrite(data, j, 0, filename)\n\t\t}\n\t}\n\twrite(data, i, 1, filename)\n}\n\nfunc write(data interface{}, i, count int, filename string) {\n\td := reflect.ValueOf(data)\n\tpos := reflect.ValueOf(d.FieldByName(\"Pos\").Interface())\n\tnumStmt := reflect.ValueOf(d.FieldByName(\"NumStmt\").Interface())\n\n\tfmt.Fprintf(\n\t\tout,\n\t\t\"%s:%d.%d,%d.%d %d %d\\n\",\n\t\tfilename,\n\t\tpos.Index(3*i).Uint(),\n\t\tpos.Index(3*i+2).Uint()&0xFFFF,\n\t\tpos.Index(3*i+1).Uint(),\n\t\tpos.Index(3*i+2).Uint()>>16&0xFFFF,\n\t\tnumStmt.Index(i).Uint(),\n\t\tcount,\n\t)\n}\n"
  },
  {
    "path": "cucumber.yml",
    "content": "default: --format progress -t 'not @completion'\ncompletion: --format pretty -t @completion\nall: --format progress\n"
  },
  {
    "path": "etc/README.md",
    "content": "# Installation instructions\n\n## Homebrew\n\nIf you're using Homebrew, just run `brew install hub` and you should be all set\nwith auto-completion. The extra steps to install hub completion scripts outlined\nbelow are *not needed*.\n\nFor bash/zsh, a one-time setup might be needed to [enable completion for all\nHomebrew programs](https://docs.brew.sh/Shell-Completion).\n\n## bash\n\nOpen your `.bashrc` file if you're on Linux, or your `.bash_profile` if you're on macOS and add:\n\n```sh\nif [ -f /path/to/hub.bash_completion ]; then\n  . /path/to/hub.bash_completion\nfi\n```\n\n## zsh\n\nCopy the file `etc/hub.zsh_completion` from the location where you downloaded\n`hub` to the folder `~/.zsh/completions/` and rename it to `_hub`:\n\n```sh\nmkdir -p ~/.zsh/completions\ncp etc/hub.zsh_completion ~/.zsh/completions/_hub\n```\n\nThen add the following lines to your `.zshrc` file:\n\n```sh\nfpath=(~/.zsh/completions $fpath) \nautoload -U compinit && compinit\n```\n\n## fish\n\nCopy the file `etc/hub.fish_completion` from the location where you downloaded\n`hub` to the folder `~/.config/fish/completions/` and rename it to `hub.fish`:\n\n```sh\nmkdir -p ~/.config/fish/completions\ncp etc/hub.fish_completion ~/.config/fish/completions/hub.fish\n```\n"
  },
  {
    "path": "etc/hub.bash_completion.sh",
    "content": "# hub tab-completion script for bash.\n# This script complements the completion script that ships with git.\n\n# If there is no git tab completion, but we have the _completion loader try to load it\nif ! declare -F _git > /dev/null && declare -F _completion_loader > /dev/null; then\n  _completion_loader git\nfi\n\n# Check that git tab completion is available and we haven't already set up completion\nif declare -F _git > /dev/null && ! declare -F __git_list_all_commands_without_hub > /dev/null; then\n  # Duplicate and rename the 'list_all_commands' function\n  eval \"$(declare -f __git_list_all_commands | \\\n        sed 's/__git_list_all_commands/__git_list_all_commands_without_hub/')\"\n\n  # Wrap the 'list_all_commands' function with extra hub commands\n  __git_list_all_commands() {\n    cat <<-EOF\nalias\npull-request\npr\nissue\nrelease\nfork\ncreate\ndelete\nbrowse\ncompare\nci-status\nsync\nEOF\n    __git_list_all_commands_without_hub\n  }\n\n  # Ensure cached commands are cleared\n  __git_all_commands=\"\"\n\n  ##########################\n  # hub command completions\n  ##########################\n\n  # hub alias [-s] [SHELL]\n  _git_alias() {\n    local i c=2 s=-s sh shells=\"bash zsh sh ksh csh fish\"\n    while [ $c -lt $cword ]; do\n      i=\"${words[c]}\"\n      case \"$i\" in\n        -s)\n          unset s\n          ;;\n        *)\n          for sh in $shells; do\n            if [ \"$sh\" = \"$i\" ]; then\n              unset shells\n              break\n            fi\n          done\n          ;;\n      esac\n      ((c++))\n    done\n    __gitcomp \"$s $shells\"\n  }\n\n  # hub browse [-u] [--|[USER/]REPOSITORY] [SUBPAGE]\n  _git_browse() {\n    local i c=2 u=-u repo subpage\n    local subpages_=\"commits issues tree wiki pulls branches stargazers\n      contributors network network/ graphs graphs/\"\n    local subpages_network=\"members\"\n    local subpages_graphs=\"commit-activity code-frequency punch-card\"\n    while [ $c -lt $cword ]; do\n      i=\"${words[c]}\"\n      case \"$i\" in\n        -u)\n          unset u\n          ;;\n        *)\n          if [ -z \"$repo\" ]; then\n            repo=$i\n          else\n            subpage=$i\n          fi\n          ;;\n      esac\n      ((c++))\n    done\n    if [ -z \"$repo\" ]; then\n      __gitcomp \"$u -- $(__hub_github_repos '\\p')\"\n    elif [ -z \"$subpage\" ]; then\n      case \"$cur\" in\n        */*)\n          local pfx=\"${cur%/*}\" cur_=\"${cur#*/}\"\n          local subpages_var=\"subpages_$pfx\"\n          __gitcomp \"${!subpages_var}\" \"$pfx/\" \"$cur_\"\n          ;;\n        *)\n          __gitcomp \"$u ${subpages_}\"\n          ;;\n      esac\n    else\n      __gitcomp \"$u\"\n    fi\n  }\n\n  # hub compare [-u] [USER[/REPOSITORY]] [[START...]END]\n  _git_compare() {\n    local i c=$((cword - 1)) u=-u user remote owner repo arg_repo rev\n    while [ $c -gt 1 ]; do\n      i=\"${words[c]}\"\n      case \"$i\" in\n        -u)\n          unset u\n          ;;\n        *)\n          if [ -z \"$rev\" ]; then\n            # Even though the logic below is able to complete both user/repo\n            # and revision in the right place, when there is only one argument\n            # (other than -u) in the command, that argument will be taken as\n            # revision. For example:\n            # $ hub compare -u upstream\n            # > https://github.com/USER/REPO/compare/upstream\n            if __hub_github_repos '\\p' | command grep -Eqx \"^$i(/[^/]+)?\"; then\n              arg_repo=$i\n            else\n              rev=$i\n            fi\n          elif [ -z \"$arg_repo\" ]; then\n            arg_repo=$i\n          fi\n          ;;\n      esac\n      ((c--))\n    done\n\n    # Here we want to find out the git remote name of user/repo, in order to\n    # generate an appropriate revision list\n    if [ -z \"$arg_repo\" ]; then\n      user=$(__hub_github_user)\n      if [ -z \"$user\" ]; then\n        for i in $(__hub_github_repos); do\n          remote=${i%%:*}\n          repo=${i#*:}\n          if [ \"$remote\" = origin ]; then\n            break\n          fi\n        done\n      else\n        for i in $(__hub_github_repos); do\n          remote=${i%%:*}\n          repo=${i#*:}\n          owner=${repo%%/*}\n          if [ \"$user\" = \"$owner\" ]; then\n            break\n          fi\n        done\n      fi\n    else\n      for i in $(__hub_github_repos); do\n        remote=${i%%:*}\n        repo=${i#*:}\n        owner=${repo%%/*}\n        case \"$arg_repo\" in\n          \"$repo\"|\"$owner\")\n            break\n            ;;\n        esac\n      done\n    fi\n\n    local pfx cur_=\"$cur\"\n    case \"$cur_\" in\n      *..*)\n        pfx=\"${cur_%%..*}...\"\n        cur_=\"${cur_##*..}\"\n        __gitcomp_nl \"$(__hub_revlist $remote)\" \"$pfx\" \"$cur_\"\n        ;;\n      *)\n        if [ -z \"${arg_repo}${rev}\" ]; then\n          __gitcomp \"$u $(__hub_github_repos '\\o\\n\\p') $(__hub_revlist $remote)\"\n        elif [ -z \"$rev\" ]; then\n          __gitcomp \"$u $(__hub_revlist $remote)\"\n        else\n          __gitcomp \"$u\"\n        fi\n        ;;\n    esac\n  }\n\n  # hub create [NAME] [-p] [-d DESCRIPTION] [-h HOMEPAGE]\n  _git_create() {\n    local i c=2 name repo flags=\"-p -d -h\"\n    while [ $c -lt $cword ]; do\n      i=\"${words[c]}\"\n      case \"$i\" in\n        -d|-h)\n          ((c++))\n          flags=${flags/$i/}\n          ;;\n        -p)\n          flags=${flags/$i/}\n          ;;\n        *)\n          name=$i\n          ;;\n      esac\n      ((c++))\n    done\n    if [ -z \"$name\" ]; then\n      repo=$(basename \"$(pwd)\")\n    fi\n    case \"$prev\" in\n      -d|-h)\n        COMPREPLY=()\n        ;;\n      -p|*)\n        __gitcomp \"$repo $flags\"\n        ;;\n    esac\n  }\n\n  # hub fork [--no-remote] [--remote-name REMOTE] [--org ORGANIZATION]\n  _git_fork() {\n    local i c=2 flags=\"--no-remote --remote-name --org\"\n    while [ $c -lt $cword ]; do\n      i=\"${words[c]}\"\n      case \"$i\" in\n        --org)\n          ((c++))\n          flags=${flags/$i/}\n          ;;\n        --remote-name)\n          ((c++))\n          flags=${flags/$i/}\n          flags=${flags/--no-remote/}\n          ;;\n        --no-remote)\n          flags=${flags/$i/}\n          flags=${flags/--remote-name/}\n          ;;\n      esac\n      ((c++))\n    done\n    case \"$prev\" in\n      --remote-name|--org)\n        COMPREPLY=()\n        ;;\n      *)\n        __gitcomp \"$flags\"\n        ;;\n    esac\n  }\n\n  # hub pull-request [-f] [-m <MESSAGE>|-F <FILE>|-i <ISSUE>|<ISSUE-URL>] [-b <BASE>] [-h <HEAD>] [-a <USER>] [-M <MILESTONE>] [-l <LABELS>]\n  _git_pull_request() {\n    local i c=2 flags=\"-f -m -F -i -b -h -a -M -l\"\n    while [ $c -lt $cword ]; do\n      i=\"${words[c]}\"\n      case \"$i\" in\n        -m|-F|-i|-b|-h|-a|-M|-l)\n          ((c++))\n          flags=${flags/$i/}\n          ;;\n        -f)\n          flags=${flags/$i/}\n          ;;\n      esac\n      ((c++))\n    done\n    case \"$prev\" in\n      -i)\n        COMPREPLY=()\n        ;;\n      -b|-h|-a|-M|-l)\n        # (Doesn't seem to need this...)\n        # Uncomment the following line when 'owner/repo:[TAB]' misbehaved\n        #_get_comp_words_by_ref -n : cur\n        __gitcomp_nl \"$(__hub_heads)\"\n        # __ltrim_colon_completions \"$cur\"\n        ;;\n      -F)\n        COMPREPLY=( \"$cur\"* )\n        ;;\n      -f|*)\n        __gitcomp \"$flags\"\n        ;;\n    esac\n  }\n\n  ###################\n  # Helper functions\n  ###################\n\n  # __hub_github_user [HOST]\n  # Return $GITHUB_USER or the default github user defined in hub config\n  # HOST - Host to be looked-up in hub config. Default is \"github.com\"\n  __hub_github_user() {\n    if [ -n \"$GITHUB_USER\" ]; then\n      echo $GITHUB_USER\n      return\n    fi\n    local line h k v host=${1:-github.com} config=${HUB_CONFIG:-~/.config/hub}\n    if [ -f \"$config\" ]; then\n      while read line; do\n        if [ \"$line\" = \"---\" ]; then\n          continue\n        fi\n        k=${line%%:*}\n        v=${line#*:}\n        if [ -z \"$v\" ]; then\n          if [ \"$h\" = \"$host\" ]; then\n            break\n          fi\n          h=$k\n          continue\n        fi\n        k=${k#* }\n        v=${v#* }\n        if [ \"$h\" = \"$host\" ] && [ \"$k\" = \"user\" ]; then\n          echo \"$v\"\n          break\n        fi\n      done < \"$config\"\n    fi\n  }\n\n  # __hub_github_repos [FORMAT]\n  # List all github hosted repository\n  # FORMAT - Format string contains multiple of these:\n  #   \\m  remote\n  #   \\p  owner/repo\n  #   \\o  owner\n  #   escaped characters (\\n, \\t ...etc) work\n  # If omitted, prints all github repos in the format of \"remote:owner/repo\"\n  __hub_github_repos() {\n    local f format=$1\n    if [ -z \"$(__gitdir)\" ]; then\n      return\n    fi\n    if [ -z \"$format\" ]; then\n      format='\\1:\\2'\n    else\n      format=${format//\\m/\\1}\n      format=${format//\\p/\\2}\n      format=${format//\\o/\\3}\n    fi\n    command git config --get-regexp 'remote\\.[^.]*\\.url' |\n    command grep -E ' ((https?|git)://|git@)github\\.com[:/][^:/]+/[^/]+$' |\n    sed -E 's#^remote\\.([^.]+)\\.url +.+[:/](([^/]+)/[^.]+)(\\.git)?$#'\"$format\"'#'\n  }\n\n  # __hub_heads\n  # List all local \"branch\", and remote \"owner/repo:branch\"\n  __hub_heads() {\n    local i remote repo branch dir=$(__gitdir)\n    if [ -d \"$dir\" ]; then\n      command git --git-dir=\"$dir\" for-each-ref --format='%(refname:short)' \\\n        \"refs/heads/\"\n      for i in $(__hub_github_repos); do\n        remote=${i%%:*}\n        repo=${i#*:}\n        command git --git-dir=\"$dir\" for-each-ref --format='%(refname:short)' \\\n          \"refs/remotes/${remote}/\" | while read branch; do\n          echo \"${repo}:${branch#${remote}/}\"\n        done\n      done\n    fi\n  }\n\n  # __hub_revlist [REMOTE]\n  # List all tags, and branches under REMOTE, without the \"remote/\" prefix\n  # REMOTE - Remote name to search branches from. Default is \"origin\"\n  __hub_revlist() {\n    local i remote=${1:-origin} dir=$(__gitdir)\n    if [ -d \"$dir\" ]; then\n      command git --git-dir=\"$dir\" for-each-ref --format='%(refname:short)' \\\n        \"refs/remotes/${remote}/\" | while read i; do\n        echo \"${i#${remote}/}\"\n      done\n      command git --git-dir=\"$dir\" for-each-ref --format='%(refname:short)' \\\n        \"refs/tags/\"\n    fi\n  }\n\n  # Enable completion for hub even when not using the alias\n  complete -o bashdefault -o default -o nospace -F _git hub 2>/dev/null \\\n    || complete -o default -o nospace -F _git hub\nfi\n"
  },
  {
    "path": "etc/hub.fish_completion",
    "content": "complete -c hub --wraps git\n\nfunction __fish_hub_needs_command\n  set cmd (commandline -opc)\n  if [ (count $cmd) -eq 1 ]\n    return 0\n  else\n    return 1\n  end\nend\n\nfunction  __fish_hub_using_command\n  set cmd (commandline -opc)\n  set subcmd_count (count $argv)\n  if [ (count $cmd) -gt \"$subcmd_count\" ]\n    for i in (seq 1 \"$subcmd_count\")\n      if [ \"$argv[$i]\" != $cmd[(math \"$i\" + 1)] ]\n        return 1\n      end\n    end\n    return 0\n  else\n    return 1\n  end\nend\n\nfunction __fish_hub_prs\n    command hub pr list -f %I\\t%t%n 2>/dev/null\nend\n\ncomplete -f -c hub -n '__fish_hub_needs_command' -a alias -d \"show shell instructions for wrapping git\"\ncomplete -f -c hub -n '__fish_hub_needs_command' -a browse -d \"browse the project on GitHub\"\ncomplete -f -c hub -n '__fish_hub_needs_command' -a compare -d \"lookup commit in GitHub Status API\"\ncomplete -f -c hub -n '__fish_hub_needs_command' -a create -d \"create new repo on GitHub for the current project\"\ncomplete -f -c hub -n '__fish_hub_needs_command' -a delete -d \"delete a GitHub repo\"\ncomplete -f -c hub -n '__fish_hub_needs_command' -a fork -d \"fork origin repo on GitHub\"\ncomplete -f -c hub -n '__fish_hub_needs_command' -a pull-request -d \"open a pull request on GitHub\"\ncomplete -f -c hub -n '__fish_hub_needs_command' -a pr -d \"list or checkout GitHub pull requests\"\ncomplete -f -c hub -n '__fish_hub_needs_command' -a issue -d \"list or create a GitHub issue\"\ncomplete -f -c hub -n '__fish_hub_needs_command' -a release -d \"list or create a GitHub release\"\ncomplete -f -c hub -n '__fish_hub_needs_command' -a ci-status -d \"display GitHub Status information for a commit\"\ncomplete -f -c hub -n '__fish_hub_needs_command' -a sync -d \"update local branches from upstream\"\n\n# alias\ncomplete -f -c hub -n ' __fish_hub_using_command alias' -a 'bash zsh sh ksh csh fish' -d \"output shell script suitable for eval\"\n# pull-request\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -s f -l force -d \"Skip the check for unpushed commits\"\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -s m -l message -d \"Set the pull request title and description separated by a blank line\"\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -l no-edit -d \"Use the message from the first commit on the branch as pull request title and description without opening a text editor\"\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -s e -l edit -d \"Open the pull request title and description in a text editor before submitting.\"\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -s i -l issue -d \"Convert <ISSUE> (referenced by its number) to a pull request\"\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -s F --file -d \"Read the pull request title and description from <FILE>\"\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -s o -l browse -d \"Open the new pull request in a web browser\"\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -s c -l copy -d \"Put the URL of the new pull request to the clipboard instead of printing it\"\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -s p -l push -d \"Push the current branch to <HEAD> before creating the pull request\"\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -s b -l base -d 'The base branch in \"[OWNER:]BRANCH\" format'\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -s h -l head -d 'The head branch in \"[OWNER:]BRANCH\" format'\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -s r -l reviewer -d 'A comma-separated list of GitHub handles to request a review from'\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -s a -l assign -d 'A comma-separated list of GitHub handles to assign to this pull request'\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -s M -l milestone -d \"The milestone name to add to this pull request. Passing the milestone number is deprecated.\"\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -s l -l labels -d \"Add a comma-separated list of labels to this pull request\"\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -s d -l draft -d \"Create the pull request as a draft\"\ncomplete -f -c hub -n ' __fish_hub_using_command pull-request' -l no-maintainer-edits -d \"When creating a pull request from a fork, this disallows project maintainers from being abler to push to the head branch of this fork\"\n# pr\nset -l pr_commands list checkout show\ncomplete -f -c hub -n ' __fish_hub_using_command pr' -l color -xa 'always never auto' -d 'enable colored output even if stdout is not a terminal. WHEN can be one of \"always\" (default for --color), \"never\", or \"auto\" (default).'\n## pr list\ncomplete -f -c hub -n \" __fish_hub_using_command pr; and not __fish_seen_subcommand_from $pr_commands\" -a list -d \"list pull requests in the current repository\"\ncomplete -f -c hub -n ' __fish_hub_using_command pr list' -s s -l state -xa 'open closed merged all' -d 'filter pull requests by STATE. default: open'\ncomplete -f -c hub -n ' __fish_hub_using_command pr list' -s h -l head -d 'show pull requests started from the specified head BRANCH in \"[OWNER:]BRANCH\" format'\ncomplete -f -c hub -n ' __fish_hub_using_command pr list' -s b -l base -d 'show pull requests based off the specified BRANCH'\ncomplete -f -c hub -n ' __fish_hub_using_command pr list' -s o -l sort -xa 'created updated popularity long-running' -d 'default: created'\ncomplete -f -c hub -n ' __fish_hub_using_command pr list' -s '^' -l sort-ascending -d 'sort by ascending dates instead of descending'\ncomplete -f -c hub -n ' __fish_hub_using_command pr list' -s f -l format -d 'pretty print the list of pull requests using format FORMAT (default: \"%pC%>(8)%i%Creset  %t%  l%n\")'\ncomplete -f -c hub -n ' __fish_hub_using_command pr list' -s L -l limit -d 'display only the first LIMIT issues'\n## pr checkout\ncomplete -f -c hub -n \" __fish_hub_using_command pr; and not __fish_seen_subcommand_from $pr_commands\" -a checkout -d \"check out the head of a pull request in a new branch\"\ncomplete -f -r -c hub -n ' __fish_hub_using_command pr checkout' -a '(__fish_hub_prs)'\n## pr show\ncomplete -f -c hub -n \" __fish_hub_using_command pr; and not __fish_seen_subcommand_from $pr_commands\" -a show -d \"open a pull request page in a web browser\"\ncomplete -f -c hub -n ' __fish_hub_using_command pr show' -a '(__fish_hub_prs)'\ncomplete -f -c hub -n ' __fish_hub_using_command pr show' -s u -d \"print the pull request URL instead of opening it\"\ncomplete -f -c hub -n ' __fish_hub_using_command pr show' -s c -d \"put the pull request URL to clipboard instead of opening it\"\n# fork\ncomplete -f -c hub -n ' __fish_hub_using_command fork' -l no-remote -d \"Skip adding a git remote for the fork\"\n# browse\ncomplete -f -c hub -n ' __fish_hub_using_command browse' -s u -d \"Print the URL instead of opening it\"\ncomplete -f -c hub -n ' __fish_hub_using_command browse' -s c -d \"Put the URL in clipboard instead of opening it\"\ncomplete -f -c hub -n ' __fish_hub_using_command browse' -a '-- commits' -d 'commits'\ncomplete -f -c hub -n ' __fish_hub_using_command browse' -a '-- contributors' -d 'contributors'\ncomplete -f -c hub -n ' __fish_hub_using_command browse' -a '-- issues' -d 'issues'\ncomplete -f -c hub -n ' __fish_hub_using_command browse' -a '-- pulls' -d 'pull requests'\ncomplete -f -c hub -n ' __fish_hub_using_command browse' -a '-- wiki' -d 'wiki'\n# compare\ncomplete -f -c hub -n ' __fish_hub_using_command compare' -s u -d 'Print the URL instead of opening it'\n# create\ncomplete -f -c hub -n ' __fish_hub_using_command create' -s o -d \"Open the new repository in a web browser\"\ncomplete -f -c hub -n ' __fish_hub_using_command create' -l browse -d \"Open the new repository in a web browser\"\ncomplete -f -c hub -n ' __fish_hub_using_command create' -s p -d \"Create a private repository\"\ncomplete -f -c hub -n ' __fish_hub_using_command create' -s c -d \"Put the URL of the new repository to clipboard instead of printing it.\"\ncomplete -f -c hub -n ' __fish_hub_using_command create' -l copy -d \"Put the URL of the new repository to clipboard instead of printing it.\"\n# delete\ncomplete -f -c hub -n ' __fish_hub_using_command delete' -s y -d \"Skip the confirmation prompt\"\ncomplete -f -c hub -n ' __fish_hub_using_command delete' -l yes -d \"Skip the confirmation prompt\"\n# ci-status\ncomplete -f -c hub -n ' __fish_hub_using_command ci-status' -s v -d \"Print detailed report of all status checks and their URLs\"\n"
  },
  {
    "path": "etc/hub.zsh_completion",
    "content": "#compdef hub\n\n# Zsh will source this file when attempting to autoload the \"_hub\" function,\n# typically on the first attempt to complete the hub command.  We define two new\n# setup helper routines (one for the zsh-distributed version, one for the\n# git-distributed, bash-based version).  Then we redefine the \"_hub\" function to\n# call \"_git\" after some other interception.\n#\n# This is pretty fragile, if you think about it.  Any number of implementation\n# changes in the \"_git\" scripts could cause problems down the road.  It would be\n# better if the stock git completions were just a bit more permissive about how\n# it allowed third-party commands to be added.\n\n(( $+functions[__hub_setup_zsh_fns] )) ||\n__hub_setup_zsh_fns () {\n  (( $+functions[_git-alias] )) ||\n  _git-alias () {\n    _arguments \\\n      '-s[output shell script suitable for eval]' \\\n      '1::shell:(zsh bash csh)'\n  }\n\n  (( $+functions[_git-browse] )) ||\n  _git-browse () {\n    _arguments \\\n      '-u[output the URL]' \\\n      '2::subpage:(wiki commits issues)'\n  }\n\n  (( $+functions[_git-compare] )) ||\n  _git-compare () {\n    _arguments \\\n      '-u[output the URL]' \\\n      ':[start...]end range:'\n  }\n\n  (( $+functions[_git-create] )) ||\n  _git-create () {\n    _arguments \\\n      '::name (REPOSITORY or ORGANIZATION/REPOSITORY):' \\\n      '-p[make repository private]' \\\n      '-d[description]:description' \\\n      '-h[home page]:repository home page URL:_urls'\n  }\n\n  (( $+functions[_git-fork] )) ||\n  _git-fork () {\n    _arguments \\\n      '--no-remote[do not add a remote for the new fork]'\n  }\n\n  (( $+functions[_git-pull-request] )) ||\n  _git-pull-request () {\n    _arguments \\\n      '-f[force (skip check for local commits)]' \\\n      '-b[base]:base (\"branch\", \"owner\\:branch\", \"owner/repo\\:branch\"):' \\\n      '-h[head]:head (\"branch\", \"owner\\:branch\", \"owner/repo\\:branch\"):' \\\n      - set1 \\\n        '-m[message]' \\\n        '-F[file]' \\\n        '--no-edit[use first commit message for pull request title/description]' \\\n        '-a[user]' \\\n        '-M[milestone]' \\\n        '-l[labels]' \\\n      - set2 \\\n        '-i[issue]:issue number:' \\\n      - set3 \\\n        '::issue-url:_urls'\n  }\n\n  # stash the \"real\" command for later\n  functions[_hub_orig_git_commands]=$functions[_git_commands]\n\n  # Replace it with our own wrapper.\n  declare -f _git_commands >& /dev/null && unfunction _git_commands\n  _git_commands () {\n    local ret=1\n    # call the original routine\n    _call_function ret _hub_orig_git_commands\n\n    # Effectively \"append\" our hub commands to the behavior of the original\n    # _git_commands function.  Using this wrapper function approach ensures\n    # that we only offer the user the hub subcommands when the user is\n    # actually trying to complete subcommands.\n    hub_commands=(\n      alias:'show shell instructions for wrapping git'\n      pull-request:'open a pull request on GitHub'\n      pr:'list or checkout a GitHub pull request'\n      issue:'list or create a GitHub issue'\n      release:'list or create a GitHub release'\n      fork:'fork origin repo on GitHub'\n      create:'create new repo on GitHub for the current project'\n      delete:'delete a GitHub repo'\n      browse:'browse the project on GitHub'\n      compare:'open GitHub compare view'\n      ci-status:'show status of GitHub checks for a commit'\n      sync:'update local branches from upstream'\n    )\n    _describe -t hub-commands 'hub command' hub_commands && ret=0\n\n    return ret\n  }\n}\n\n(( $+functions[__hub_setup_bash_fns] )) ||\n__hub_setup_bash_fns () {\n  # TODO more bash-style fns needed here to complete subcommand args.  They take\n  # the form \"_git_CMD\" where \"CMD\" is something like \"pull-request\".\n\n  # Duplicate and rename the 'list_all_commands' function\n  eval \"$(declare -f __git_list_all_commands | \\\n        sed 's/__git_list_all_commands/__git_list_all_commands_without_hub/')\"\n\n  # Wrap the 'list_all_commands' function with extra hub commands\n  __git_list_all_commands() {\n    cat <<-EOF\nalias\npull-request\npr\nissue\nrelease\nfork\ncreate\ndelete\nbrowse\ncompare\nci-status\nsync\nEOF\n    __git_list_all_commands_without_hub\n  }\n\n  # Ensure cached commands are cleared\n  __git_all_commands=\"\"\n}\n\n# redefine _hub to a much smaller function in the steady state\n_hub () {\n  # only attempt to intercept the normal \"_git\" helper functions once\n  (( $+__hub_func_replacement_done )) ||\n    () {\n      # At this stage in the shell's execution the \"_git\" function has not yet\n      # been autoloaded, so the \"_git_commands\" or \"__git_list_all_commands\"\n      # functions will not be defined.  Call it now (with a bogus no-op service\n      # to prevent premature completion) so that we can wrap them.\n      if declare -f _git >& /dev/null ; then\n        _hub_noop () { __hub_zsh_provided=1 }       # zsh-provided will call this one\n        __hub_noop_main () { __hub_git_provided=1 } # git-provided will call this one\n        local service=hub_noop\n        _git\n        unfunction _hub_noop\n        unfunction __hub_noop_main\n        service=git\n      fi\n\n      if (( $__hub_zsh_provided )) ; then\n        __hub_setup_zsh_fns\n      elif (( $__hub_git_provided )) ; then\n        __hub_setup_bash_fns\n      fi\n\n      __hub_func_replacement_done=1\n    }\n\n  # Now perform the actual completion, allowing the \"_git\" function to call our\n  # replacement \"_git_commands\" function as needed.  Both versions expect\n  # service=git or they will call nonexistent routines or end up in an infinite\n  # loop.\n  service=git\n  declare -f _git >& /dev/null && _git\n}\n\n# make sure we actually attempt to complete on the first \"tab\" from the user\n_hub\n"
  },
  {
    "path": "features/README.md",
    "content": "# Cucumber features for hub\n\nHow to run all features:\n\n```sh\nmake bin/cucumber\nbin/cucumber\n```\n\nBecause this can take a couple of minutes, you may want to only run select files\nrelated to the functionality that you're developing:\n\n```sh\nbin/cucumber feature/api.feature\n```\n\nThe Cucumber test suite requires a Ruby development environment. If you want to\navoid setting that up, you can run tests inside a Docker container:\n\n```sh\nscript/docker feature/api.feature\n```\n\n## How it works\n\nEach scenario is actually making real invocations to `hub` on the command-line\nin the context of a real (dynamically created) git repository.\n\nWhenever a scenario requires talking to the GitHub API, a fake HTTP server is\nspun locally to replace the real GitHub API. This is done so that the test suite\nruns faster and is available offline as well. The fake API server is defined\nas a Sinatra app inline in each scenario:\n\n```\nGiven the GitHub API server:\n  \"\"\"\n  post('/repos/github/hub/pulls') {\n    status 200\n  }\n  \"\"\"\n```\n\n## How to write new tests\n\nThe best way to learn to write new tests is to study the existing scenarios for\ncommands that are similar to those that you want to add or change.\n\nSince Cucumber tests are written in a natural language, you mostly don't need to\nknow Ruby to write new tests.\n"
  },
  {
    "path": "features/alias.feature",
    "content": "Feature: hub alias\n\n  Scenario: bash instructions\n    Given $SHELL is \"/bin/bash\"\n    When I successfully run `hub alias`\n    Then the output should contain exactly:\n      \"\"\"\n      # Wrap git automatically by adding the following to ~/.bash_profile:\n\n      eval \"$(hub alias -s)\"\\n\n      \"\"\"\n\n  Scenario: fish instructions\n    Given $SHELL is \"/usr/local/bin/fish\"\n    When I successfully run `hub alias`\n    Then the output should contain exactly:\n      \"\"\"\n      # Wrap git automatically by adding the following to ~/.config/fish/functions/git.fish:\n\n      function git --wraps hub --description 'Alias for hub, which wraps git to provide extra functionality with GitHub.'\n          hub $argv\n      end\\n\n      \"\"\"\n  \n  Scenario: rc instructions\n    Given $SHELL is \"/usr/local/bin/rc\"\n    When I successfully run `hub alias`\n    Then the output should contain exactly:\n      \"\"\"\n      # Wrap git automatically by adding the following to $home/lib/profile:\n\n      eval `{hub alias -s}\\n\n      \"\"\"\n\n  Scenario: zsh instructions\n    Given $SHELL is \"/bin/zsh\"\n    When I successfully run `hub alias`\n    Then the output should contain exactly:\n      \"\"\"\n      # Wrap git automatically by adding the following to ~/.zshrc:\n\n      eval \"$(hub alias -s)\"\\n\n      \"\"\"\n\n  Scenario: csh instructions\n    Given $SHELL is \"/bin/csh\"\n    When I successfully run `hub alias`\n    Then the output should contain exactly:\n      \"\"\"\n      # Wrap git automatically by adding the following to ~/.cshrc:\n\n      eval \"`hub alias -s`\"\\n\n      \"\"\"\n\n  Scenario: tcsh instructions\n    Given $SHELL is \"/bin/tcsh\"\n    When I successfully run `hub alias`\n    Then the output should contain exactly:\n      \"\"\"\n      # Wrap git automatically by adding the following to ~/.tcshrc:\n\n      eval \"`hub alias -s`\"\\n\n      \"\"\"\n\n  Scenario: bash code\n    Given $SHELL is \"/bin/bash\"\n    When I successfully run `hub alias -s`\n    Then the output should contain exactly:\n      \"\"\"\n      alias git=hub\\n\n      \"\"\"\n\n  Scenario: fish code\n    Given $SHELL is \"/usr/local/bin/fish\"\n    When I successfully run `hub alias -s`\n    Then the output should contain exactly:\n      \"\"\"\n      alias git=hub\\n\n      \"\"\"\n  \n  Scenario: rc code\n    Given $SHELL is \"/usr/local/bin/rc\"\n    When I successfully run `hub alias -s`\n    Then the output should contain exactly:\n      \"\"\"\n      fn git { builtin hub $* }\\n\n      \"\"\"  \n\n  Scenario: zsh code\n    Given $SHELL is \"/bin/zsh\"\n    When I successfully run `hub alias -s`\n    Then the output should contain exactly:\n      \"\"\"\n      alias git=hub\\n\n      \"\"\"\n\n  Scenario: csh code\n    Given $SHELL is \"/bin/csh\"\n    When I successfully run `hub alias -s`\n    Then the output should contain exactly:\n      \"\"\"\n      alias git hub\\n\n      \"\"\"\n\n  Scenario: tcsh code\n    Given $SHELL is \"/bin/tcsh\"\n    When I successfully run `hub alias -s`\n    Then the output should contain exactly:\n      \"\"\"\n      alias git hub\\n\n      \"\"\"\n\n  Scenario: unsupported shell\n    Given $SHELL is \"/bin/zwoosh\"\n    When I run `hub alias -s`\n    Then the output should contain exactly:\n      \"\"\"\n      hub alias: unsupported shell\n      supported shells: bash zsh sh ksh csh tcsh fish rc\\n\n      \"\"\"\n    And the exit status should be 1\n\n  Scenario: unknown shell\n    Given $SHELL is \"\"\n    When I run `hub alias`\n    Then the output should contain exactly:\n      \"\"\"\n      Error: couldn't detect shell type. Please specify your shell with `hub alias <shell>`\\n\n      \"\"\"\n    And the exit status should be 1\n\n  Scenario: unknown shell output\n    Given $SHELL is \"\"\n    When I run `hub alias -s`\n    Then the output should contain exactly:\n      \"\"\"\n      Error: couldn't detect shell type. Please specify your shell with `hub alias -s <shell>`\\n\n      \"\"\"\n    And the exit status should be 1\n"
  },
  {
    "path": "features/am.feature",
    "content": "Feature: hub am\n  Background:\n    Given I am in \"git://github.com/mislav/dotfiles.git\" git repo\n    And I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Apply a local patch\n    When I run `hub am some.patch`\n    Then the git command should be unchanged\n\n  Scenario: Apply commits from pull request\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles/pulls/387') {\n        halt 400 unless request.env['HTTP_ACCEPT'] == 'application/vnd.github.v3.patch;charset=utf-8'\n        generate_patch \"Create a README\"\n      }\n      \"\"\"\n    When I successfully run `hub am -q -3 https://github.com/mislav/dotfiles/pull/387`\n    Then the output should not contain anything\n    Then the latest commit message should be \"Create a README\"\n\n  Scenario: Apply commits when TMPDIR is empty\n    Given $TMPDIR is \"\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles/pulls/387') {\n        halt 400 unless request.env['HTTP_ACCEPT'] == 'application/vnd.github.v3.patch;charset=utf-8'\n        generate_patch \"Create a README\"\n      }\n      \"\"\"\n    When I successfully run `hub am -q https://github.com/mislav/dotfiles/pull/387`\n    Then the latest commit message should be \"Create a README\"\n\n  Scenario: Enterprise repo\n    Given I am in \"git://git.my.org/mislav/dotfiles.git\" git repo\n    And I am \"mislav\" on git.my.org with OAuth token \"FITOKEN\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    Given the GitHub API server:\n      \"\"\"\n      get('/api/v3/repos/mislav/dotfiles/pulls/387') {\n        halt 400 unless request.env['HTTP_ACCEPT'] == 'application/vnd.github.v3.patch;charset=utf-8'\n        generate_patch \"Create a README\"\n      }\n      \"\"\"\n    When I successfully run `hub am -q -3 https://git.my.org/mislav/dotfiles/pull/387`\n    Then the latest commit message should be \"Create a README\"\n\n  Scenario: Apply patch from commit\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/davidbalbert/dotfiles/commits/fdb9921') {\n        halt 400 unless request.env['HTTP_ACCEPT'] == 'application/vnd.github.v3.patch;charset=utf-8'\n        generate_patch \"Create a README\"\n      }\n      \"\"\"\n    When I successfully run `hub am -q https://github.com/davidbalbert/dotfiles/commit/fdb9921`\n    Then the latest commit message should be \"Create a README\"\n\n  Scenario: Apply patch from commit in a pull request\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/davidbalbert/dotfiles/commits/fdb9921') {\n        halt 400 unless request.env['HTTP_ACCEPT'] == 'application/vnd.github.v3.patch;charset=utf-8'\n        generate_patch \"Create a README\"\n      }\n      \"\"\"\n    When I successfully run `hub am -q https://github.com/davidbalbert/dotfiles/pull/123/commits/fdb9921`\n    Then the latest commit message should be \"Create a README\"\n\n  Scenario: Apply patch from gist\n    Given the GitHub API server:\n      \"\"\"\n      get('/gists/8da7fb575debd88c54cf', :host_name => 'api.github.com') {\n        json :files => {\n          'file.diff' => {\n            :raw_url => \"https://gist.github.com/raw/8da7fb575debd88c54cf/SHA/file.diff\"\n          }\n        }\n      }\n      get('/raw/8da7fb575debd88c54cf/SHA/file.diff', :host_name => 'gist.github.com') {\n        halt 400 unless request.env['HTTP_ACCEPT'] == 'text/plain;charset=utf-8'\n        generate_patch \"Create a README\"\n      }\n      \"\"\"\n    When I successfully run `hub am -q https://gist.github.com/8da7fb575debd88c54cf`\n    Then the latest commit message should be \"Create a README\"\n"
  },
  {
    "path": "features/api.feature",
    "content": "@cache_clear\nFeature: hub api\n  Background:\n    Given I am \"octokitten\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: GET resource\n    Given the GitHub API server:\n      \"\"\"\n      get('/hello/world') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'\n        halt 401 unless request.env['HTTP_ACCEPT'] == 'application/vnd.github.v3+json;charset=utf-8'\n        json :name => \"Ed\"\n      }\n      \"\"\"\n    When I successfully run `hub api hello/world`\n    Then the output should contain exactly:\n      \"\"\"\n      {\"name\":\"Ed\"}\n      \"\"\"\n\n  Scenario: GET Enterprise resource\n    Given I am \"octokitten\" on git.my.org with OAuth token \"FITOKEN\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/api/v3/hello/world', :host_name => 'git.my.org') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token FITOKEN'\n        json :name => \"Ed\"\n      }\n      \"\"\"\n    And $GITHUB_HOST is \"git.my.org\"\n    When I successfully run `hub api hello/world`\n    Then the output should contain exactly:\n      \"\"\"\n      {\"name\":\"Ed\"}\n      \"\"\"\n\n  Scenario: Non-success response\n    Given the GitHub API server:\n      \"\"\"\n      get('/hello/world') {\n        status 400\n        json :name => \"Ed\"\n      }\n      \"\"\"\n    When I run `hub api hello/world`\n    Then the exit status should be 22\n    And the stdout should contain exactly:\n      \"\"\"\n      {\"name\":\"Ed\"}\n      \"\"\"\n    And the stderr should contain exactly \"\"\n\n  Scenario: Non-success response flat output\n    Given the GitHub API server:\n      \"\"\"\n      get('/hello/world') {\n        status 400\n        json :name => \"Ed\"\n      }\n      \"\"\"\n    When I run `hub api -t hello/world`\n    Then the exit status should be 22\n    And the stdout should contain exactly:\n      \"\"\"\n      .name\tEd\\n\n      \"\"\"\n    And the stderr should contain exactly \"\"\n\n  Scenario: Non-success response doesn't choke on non-JSON\n    Given the GitHub API server:\n      \"\"\"\n      get('/hello/world') {\n        status 400\n        content_type :text\n        'Something went wrong'\n      }\n      \"\"\"\n    When I run `hub api -t hello/world`\n    Then the exit status should be 22\n    And the stdout should contain exactly:\n      \"\"\"\n      Something went wrong\n      \"\"\"\n    And the stderr should contain exactly \"\"\n\n  Scenario: GET query string\n    Given the GitHub API server:\n      \"\"\"\n      get('/hello/world') {\n        json Hash[*params.sort.flatten]\n      }\n      \"\"\"\n    When I successfully run `hub api -XGET -Fname=Ed -Fnum=12 -Fbool=false -Fvoid=null hello/world`\n    Then the output should contain exactly:\n      \"\"\"\n      {\"bool\":\"false\",\"name\":\"Ed\",\"num\":\"12\",\"void\":\"\"}\n      \"\"\"\n\n  Scenario: GET full URL\n    Given the GitHub API server:\n      \"\"\"\n      get('/hello/world', :host_name => 'api.github.com') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'\n        json :name => \"Faye\"\n      }\n      \"\"\"\n    When I successfully run `hub api https://api.github.com/hello/world`\n    Then the output should contain exactly:\n      \"\"\"\n      {\"name\":\"Faye\"}\n      \"\"\"\n\n  Scenario: Paginate REST\n    Given the GitHub API server:\n      \"\"\"\n      get('/comments') {\n        assert :per_page => \"6\"\n        page = (params[:page] || 1).to_i\n        response.headers[\"Link\"] = %(<#{request.url}&page=#{page+1}>; rel=\"next\") if page < 3\n        json [{:page => page}]\n      }\n      \"\"\"\n    When I successfully run `hub api --paginate comments?per_page=6`\n    Then the output should contain exactly:\n      \"\"\"\n      [{\"page\":1}]\n      [{\"page\":2}]\n      [{\"page\":3}]\n      \"\"\"\n\n  Scenario: Paginate GraphQL\n    Given the GitHub API server:\n      \"\"\"\n      post('/graphql') {\n        variables = params[:variables] || {}\n        page = (variables[\"endCursor\"] || 1).to_i\n        json :data => {\n          :pageInfo => {\n            :hasNextPage => page < 3,\n            :endCursor => (page+1).to_s\n          }\n        }\n      }\n      \"\"\"\n    When I successfully run `hub api --paginate graphql -f query=QUERY`\n    Then the output should contain exactly:\n      \"\"\"\n      {\"data\":{\"pageInfo\":{\"hasNextPage\":true,\"endCursor\":\"2\"}}}\n      {\"data\":{\"pageInfo\":{\"hasNextPage\":true,\"endCursor\":\"3\"}}}\n      {\"data\":{\"pageInfo\":{\"hasNextPage\":false,\"endCursor\":\"4\"}}}\n      \"\"\"\n\n  Scenario: Avoid leaking token to a 3rd party\n    Given the GitHub API server:\n      \"\"\"\n      get('/hello/world', :host_name => 'example.com') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'].nil?\n        json :name => \"Jet\"\n      }\n      \"\"\"\n    When I successfully run `hub api http://example.com/hello/world`\n    Then the output should contain exactly:\n      \"\"\"\n      {\"name\":\"Jet\"}\n      \"\"\"\n\n  Scenario: Request headers\n    Given the GitHub API server:\n      \"\"\"\n      get('/hello/world') {\n        json :accept => request.env['HTTP_ACCEPT'],\n             :foo => request.env['HTTP_X_FOO']\n      }\n      \"\"\"\n      When I successfully run `hub api hello/world -H 'x-foo:bar' -H 'Accept: text/json'`\n    Then the output should contain exactly:\n      \"\"\"\n      {\"accept\":\"text/json\",\"foo\":\"bar\"}\n      \"\"\"\n\n  Scenario: Response headers\n    Given the GitHub API server:\n      \"\"\"\n      get('/hello/world') {\n        json({})\n      }\n      \"\"\"\n    When I successfully run `hub api hello/world -i`\n    Then the output should contain \"HTTP/1.1 200 OK\"\n    And the output should contain \"Content-Length: 2\"\n\n  Scenario: POST fields\n    Given the GitHub API server:\n      \"\"\"\n      post('/hello/world') {\n        json Hash[*params.sort.flatten]\n      }\n      \"\"\"\n    When I successfully run `hub api -f name=@hubot -Fnum=12 -Fbool=false -Fvoid=null hello/world`\n    Then the output should contain exactly:\n      \"\"\"\n      {\"bool\":false,\"name\":\"@hubot\",\"num\":12,\"void\":null}\n      \"\"\"\n\n  Scenario: POST raw fields\n    Given the GitHub API server:\n      \"\"\"\n      post('/hello/world') {\n        json Hash[*params.sort.flatten]\n      }\n      \"\"\"\n    When I successfully run `hub api -fnum=12 -fbool=false hello/world`\n    Then the output should contain exactly:\n      \"\"\"\n      {\"bool\":\"false\",\"num\":\"12\"}\n      \"\"\"\n\n  Scenario: POST from stdin\n    Given the GitHub API server:\n      \"\"\"\n      post('/graphql') {\n        json :query => params[:query]\n      }\n      \"\"\"\n    When I run `hub api -t -F query=@- graphql` interactively\n    And I pass in:\n      \"\"\"\n      query {\n        repository\n      }\n      \"\"\"\n    Then the output should contain exactly:\n      \"\"\"\n      .query\tquery {\\\\n  repository\\\\n}\\\\n\\n\n      \"\"\"\n\n  Scenario: POST body from file\n    Given the GitHub API server:\n      \"\"\"\n      post('/create') {\n        params[:obj].inspect\n      }\n      \"\"\"\n    Given a file named \"payload.json\" with:\n      \"\"\"\n      {\"obj\": [\"one\", 2, null]}\n      \"\"\"\n    When I successfully run `hub api create --input payload.json`\n    Then the output should contain exactly:\n      \"\"\"\n      [\"one\", 2, nil]\n      \"\"\"\n\n  Scenario: POST body from stdin\n    Given the GitHub API server:\n      \"\"\"\n      post('/create') {\n        params[:obj].inspect\n      }\n      \"\"\"\n    When I run `hub api create --input -` interactively\n    And I pass in:\n      \"\"\"\n      {\"obj\": {\"name\": \"Ein\", \"datadog\": true}}\n      \"\"\"\n    Then the output should contain exactly:\n      \"\"\"\n      {\"name\"=>\"Ein\", \"datadog\"=>true}\n      \"\"\"\n\n  Scenario: Pass extra GraphQL variables\n    Given the GitHub API server:\n      \"\"\"\n      post('/graphql') {\n        json(params[:variables])\n      }\n      \"\"\"\n    When I successfully run `hub api -F query='query {}' -Fname=Jet -Fsize=2 graphql`\n    Then the output should contain exactly:\n      \"\"\"\n      {\"name\":\"Jet\",\"size\":2}\n      \"\"\"\n\n  Scenario: Enterprise GraphQL\n    Given I am \"octokitten\" on git.my.org with OAuth token \"FITOKEN\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/api/graphql', :host_name => 'git.my.org') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token FITOKEN'\n        json :name => \"Ed\"\n      }\n      \"\"\"\n    And $GITHUB_HOST is \"git.my.org\"\n    When I successfully run `hub api graphql -f query=QUERY`\n    Then the output should contain exactly:\n      \"\"\"\n      {\"name\":\"Ed\"}\n      \"\"\"\n\n  Scenario: Repo context\n    Given I am in \"git://github.com/octocat/Hello-World.git\" git repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/octocat/Hello-World/commits') {\n        json :commits => 12\n      }\n      \"\"\"\n    When I successfully run `hub api repos/{owner}/{repo}/commits`\n    Then the output should contain exactly:\n      \"\"\"\n      {\"commits\":12}\n      \"\"\"\n\n  Scenario: Multiple string interpolation\n    Given I am in \"git://github.com/octocat/Hello-World.git\" git repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/octocat/Hello-World/pulls') {\n        json(params)\n      }\n      \"\"\"\n    When I successfully run `hub api repos/{owner}/{repo}/pulls?head={owner}:{repo}`\n    Then the output should contain exactly:\n      \"\"\"\n      {\"head\":\"octocat:Hello-World\"}\n      \"\"\"\n\n  Scenario: Repo context in graphql\n    Given I am in \"git://github.com/octocat/Hello-World.git\" git repo\n    Given the GitHub API server:\n      \"\"\"\n      post('/graphql') {\n        json :query => params[:query]\n      }\n      \"\"\"\n    When I run `hub api -t -F query=@- graphql` interactively\n    And I pass in:\n      \"\"\"\n      repository(owner: \"{owner}\", name: \"{repo}\", nameWithOwner: \"{owner}/{repo}\")\n      \"\"\"\n    Then the output should contain exactly:\n      \"\"\"\n      .query\trepository(owner: \"octocat\", name: \"Hello-World\", nameWithOwner: \"octocat/Hello-World\")\\\\n\\n\n      \"\"\"\n\n  Scenario: Cache response\n    Given the GitHub API server:\n      \"\"\"\n      count = 0\n      get('/count') {\n        count += 1\n        json :count => count\n      }\n      \"\"\"\n    When I run `hub api -t 'count?a=1&b=2' --cache 5`\n    Then it should pass with \".count\t1\"\n    When I run `hub api -t 'count?b=2&a=1' --cache 5`\n    Then it should pass with \".count\t1\"\n\n  Scenario: Cache graphql response\n    Given the GitHub API server:\n      \"\"\"\n      count = 0\n      post('/graphql') {\n        halt 400 unless params[:query] =~ /^Q\\d$/\n        count += 1\n        json :count => count\n      }\n      \"\"\"\n    When I run `hub api -t graphql -F query=Q1 --cache 5`\n    Then it should pass with \".count\t1\"\n    When I run `hub api -t graphql -F query=Q1 --cache 5`\n    Then it should pass with \".count\t1\"\n    When I run `hub api -t graphql -F query=Q2 --cache 5`\n    Then it should pass with \".count\t2\"\n\n  Scenario: Cache client error response\n    Given the GitHub API server:\n      \"\"\"\n      count = 0\n      get('/count') {\n        count += 1\n        status 404 if count == 1\n        json :count => count\n      }\n      \"\"\"\n    When I run `hub api -t count --cache 5`\n    Then it should fail with \".count\t1\"\n    When I run `hub api -t count --cache 5`\n    Then it should fail with \".count\t1\"\n    And the exit status should be 22\n\n  Scenario: Avoid caching server error response\n    Given the GitHub API server:\n      \"\"\"\n      count = 0\n      get('/count') {\n        count += 1\n        status 500 if count == 1\n        json :count => count\n      }\n      \"\"\"\n    When I run `hub api -t count --cache 5`\n    Then it should fail with \".count\t1\"\n    When I run `hub api -t count --cache 5`\n    Then it should pass with \".count\t2\"\n    When I run `hub api -t count --cache 5`\n    Then it should pass with \".count\t2\"\n\n  Scenario: Avoid caching response if the OAuth token changes\n    Given the GitHub API server:\n      \"\"\"\n      count = 0\n      get('/count') {\n        count += 1\n        json :count => count\n      }\n      \"\"\"\n    When I run `hub api -t count --cache 5`\n    Then it should pass with \".count\t1\"\n    Given I am \"octocat\" on github.com with OAuth token \"TOKEN2\"\n    When I run `hub api -t count --cache 5`\n    Then it should pass with \".count\t2\"\n\n  Scenario: Honor rate limit with pagination\n    Given the GitHub API server:\n      \"\"\"\n      get('/hello') {\n        page = (params[:page] || 1).to_i\n        if page < 2\n          response.headers['X-Ratelimit-Remaining'] = '0'\n          response.headers['X-Ratelimit-Reset'] = Time.now.utc.to_i.to_s\n          response.headers['Link'] = %(</hello?page=#{page+1}>; rel=\"next\")\n        end\n        json [{}]\n      }\n      \"\"\"\n    When I successfully run `hub api --obey-ratelimit --paginate hello`\n    Then the stderr should contain \"API rate limit exceeded; pausing until \"\n\n  Scenario: Succumb to rate limit with pagination\n    Given the GitHub API server:\n      \"\"\"\n      get('/hello') {\n        page = (params[:page] || 1).to_i\n        response.headers['X-Ratelimit-Remaining'] = '0'\n        response.headers['X-Ratelimit-Reset'] = Time.now.utc.to_i.to_s\n        if page == 2\n          status 403\n          json :message => \"API rate limit exceeded\"\n        else\n          response.headers['Link'] = %(</hello?page=#{page+1}>; rel=\"next\")\n          json [{page:page}]\n        end\n      }\n      \"\"\"\n    When I run `hub api --paginate -t hello`\n    Then the exit status should be 22\n    And the stderr should not contain \"API rate limit exceeded\"\n    And the stdout should contain exactly:\n      \"\"\"\n      .[0].page\t1\n      .message\tAPI rate limit exceeded\\n\n      \"\"\"\n\n  Scenario: Honor rate limit for 403s\n    Given the GitHub API server:\n      \"\"\"\n      count = 0\n      get('/hello') {\n        count += 1\n        if count == 1\n          response.headers['X-Ratelimit-Remaining'] = '0'\n          response.headers['X-Ratelimit-Reset'] = Time.now.utc.to_i.to_s\n          halt 403\n        end\n        json [{}]\n      }\n      \"\"\"\n    When I successfully run `hub api --obey-ratelimit hello`\n    Then the stderr should contain \"API rate limit exceeded; pausing until \"\n\n  Scenario: 403 unrelated to rate limit\n    Given the GitHub API server:\n      \"\"\"\n      get('/hello') {\n        response.headers['X-Ratelimit-Remaining'] = '1'\n        status 403\n      }\n      \"\"\"\n    When I run `hub api --obey-ratelimit hello`\n    Then the exit status should be 22\n    Then the stderr should not contain \"API rate limit exceeded\"\n\n  Scenario: Warn about insufficient OAuth scopes\n    Given the GitHub API server:\n      \"\"\"\n      get('/hello') {\n        response.headers['X-Accepted-Oauth-Scopes'] = 'repo, admin'\n        response.headers['X-Oauth-Scopes'] = 'public_repo'\n        status 403\n        json({})\n      }\n      \"\"\"\n    When I run `hub api hello`\n    Then the exit status should be 22\n    And the output should contain exactly:\n      \"\"\"\n      {}\n      Your access token may have insufficient scopes. Visit http://github.com/settings/tokens\n      to edit the 'hub' token and enable one of the following scopes: admin, repo\\n\n      \"\"\"\n\n  Scenario: Print the SSO challenge to stderr\n    Given the GitHub API server:\n      \"\"\"\n      get('/orgs/acme') {\n        response.headers['X-GitHub-SSO'] = 'required; url=http://example.com?auth=HASH'\n        status 403\n        json({})\n      }\n      \"\"\"\n    When I run `hub api orgs/acme`\n    Then the exit status should be 22\n    And the stderr should contain exactly:\n      \"\"\"\n\n      You must authorize your token to access this organization:\n      http://example.com?auth=HASH\\n\n      \"\"\"\n"
  },
  {
    "path": "features/apply.feature",
    "content": "Feature: hub apply\n  Background:\n    Given I am in \"git://github.com/mislav/dotfiles.git\" git repo\n    And I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n    And I make a commit\n\n  Scenario: Apply a local patch\n    When I run `hub apply some.patch`\n    Then the git command should be unchanged\n    And the file \"README.md\" should not exist\n\n  Scenario: Apply commits from pull request\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles/pulls/387') {\n        halt 400 unless request.env['HTTP_ACCEPT'] == 'application/vnd.github.v3.patch;charset=utf-8'\n        generate_patch \"Create a README\"\n      }\n      \"\"\"\n    When I successfully run `hub apply -3 https://github.com/mislav/dotfiles/pull/387`\n    Then a file named \"README.md\" should exist\n\n  Scenario: Apply commits when TMPDIR is empty\n    Given $TMPDIR is \"\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles/pulls/387') {\n        halt 400 unless request.env['HTTP_ACCEPT'] == 'application/vnd.github.v3.patch;charset=utf-8'\n        generate_patch \"Create a README\"\n      }\n      \"\"\"\n    When I successfully run `hub apply https://github.com/mislav/dotfiles/pull/387`\n    Then a file named \"README.md\" should exist\n\n  Scenario: Enterprise repo\n    Given I am in \"git://git.my.org/mislav/dotfiles.git\" git repo\n    And I am \"mislav\" on git.my.org with OAuth token \"FITOKEN\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    Given the GitHub API server:\n      \"\"\"\n      get('/api/v3/repos/mislav/dotfiles/pulls/387') {\n        halt 400 unless request.env['HTTP_ACCEPT'] == 'application/vnd.github.v3.patch;charset=utf-8'\n        generate_patch \"Create a README\"\n      }\n      \"\"\"\n    When I successfully run `hub apply https://git.my.org/mislav/dotfiles/pull/387`\n    Then a file named \"README.md\" should exist\n\n  Scenario: Apply patch from commit\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/davidbalbert/dotfiles/commits/fdb9921') {\n        halt 400 unless request.env['HTTP_ACCEPT'] == 'application/vnd.github.v3.patch;charset=utf-8'\n        generate_patch \"Create a README\"\n      }\n      \"\"\"\n    When I successfully run `hub apply https://github.com/davidbalbert/dotfiles/commit/fdb9921`\n    Then a file named \"README.md\" should exist\n\n  Scenario: Apply patch from gist\n    Given the GitHub API server:\n      \"\"\"\n      get('/gists/8da7fb575debd88c54cf', :host_name => 'api.github.com') {\n        json :files => {\n          'file.diff' => {\n            :raw_url => \"https://gist.github.com/raw/8da7fb575debd88c54cf/SHA/file.diff\"\n          }\n        }\n      }\n      get('/raw/8da7fb575debd88c54cf/SHA/file.diff', :host_name => 'gist.github.com') {\n        halt 400 unless request.env['HTTP_ACCEPT'] == 'text/plain;charset=utf-8'\n        generate_patch \"Create a README\"\n      }\n      \"\"\"\n    When I successfully run `hub apply https://gist.github.com/8da7fb575debd88c54cf`\n    Then a file named \"README.md\" should exist\n"
  },
  {
    "path": "features/authentication.feature",
    "content": "Feature: OAuth authentication\n  Background:\n    Given I am in \"dotfiles\" git repo\n\n  Scenario: Ask for username & password, create authorization\n    Given the GitHub API server:\n      \"\"\"\n      post('/authorizations') {\n        assert_basic_auth 'mislav', 'kitty'\n        assert :scopes => ['repo', 'gist'],\n               :note_url => 'https://hub.github.com/'\n        status 201\n        json :token => 'OTOKEN'\n      }\n      get('/user') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'\n        json :login => 'MiSlAv'\n      }\n      post('/user/repos') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    When I run `hub create` interactively\n    When I type \"mislav\"\n    And I type \"kitty\"\n    Then the output should contain \"github.com username:\"\n    And the output should contain \"github.com password for mislav (never stored):\"\n    And the exit status should be 0\n    And the file \"~/.config/hub\" should contain \"user: MiSlAv\"\n    And the file \"~/.config/hub\" should contain \"oauth_token: OTOKEN\"\n    And the file \"~/.config/hub\" should have mode \"0600\"\n\n  Scenario: Prompt for username & password, receive personal access token\n    Given the GitHub API server:\n      \"\"\"\n      get('/user') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token 0123456789012345678901234567890123456789'\n        json :login => 'llIMLLib'\n      }\n      post('/user/repos') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token 0123456789012345678901234567890123456789'\n        status 201\n        json :full_name => 'llimllib/dotfiles'\n      }\n      \"\"\"\n    When I run `hub create` interactively\n    When I type \"llimllib\"\n    And I type \"0123456789012345678901234567890123456789\"\n    And the exit status should be 0\n    And the file \"../home/.config/hub\" should contain \"user: llIMLLib\"\n    And the file \"../home/.config/hub\" should contain:\n      \"\"\"\n      oauth_token: \"0123456789012345678901234567890123456789\"\n      \"\"\"\n\n  Scenario: Ask for username & password, receive password that looks like a token\n    Given the GitHub API server:\n      \"\"\"\n      post('/authorizations') {\n        assert_basic_auth 'llimllib', '0123456789012345678901234567890123456789'\n        status 201\n        json :token => 'OTOKEN'\n      }\n      get('/user') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'\n        json :login => 'llIMLLib'\n      }\n      post('/user/repos') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'\n        status 201\n        json :full_name => 'llimllib/dotfiles'\n      }\n      \"\"\"\n    When I run `hub create` interactively\n    When I type \"llimllib\"\n    And I type \"0123456789012345678901234567890123456789\"\n    And the exit status should be 0\n    And the file \"../home/.config/hub\" should contain \"user: llIMLLib\"\n    And the file \"../home/.config/hub\" should contain \"oauth_token: OTOKEN\"\n\n  Scenario: Rename & retry creating authorization if there's a token name collision\n    Given the GitHub API server:\n      \"\"\"\n      post('/authorizations') {\n        assert_basic_auth 'mislav', 'kitty'\n        if params[:note] =~ /\\Ahub for .+ 3\\Z/\n          status 201\n          json :token => 'OTOKEN'\n        else\n          status 422\n          json :message => 'Validation Failed',\n               :errors => [{\n                 :resource => 'OauthAccess',\n                 :code => 'already_exists',\n                 :field => 'description'\n               }]\n        end\n      }\n      get('/user') {\n        json :login => 'MiSlAv'\n      }\n      post('/user/repos') {\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    When I run `hub create` interactively\n    When I type \"mislav\"\n    And I type \"kitty\"\n    Then the output should contain \"github.com username:\"\n    And the exit status should be 0\n    And the file \"../home/.config/hub\" should contain \"oauth_token: OTOKEN\"\n\n  Scenario: Avoid getting caught up in infinite recursion while retrying token names\n    Given the GitHub API server:\n      \"\"\"\n      tries = 0\n      post('/authorizations') {\n        tries += 1\n        halt 400, json(:message => \"too many tries\") if tries >= 10\n        status 422\n        json :message => 'Validation Failed',\n             :errors => [{\n               :resource => 'OauthAccess',\n               :code => 'already_exists',\n               :field => 'description'\n             }]\n      }\n      \"\"\"\n    When I run `hub create` interactively\n    When I type \"mislav\"\n    And I type \"kitty\"\n    Then the output should contain:\n      \"\"\"\n      Error creating repository: Unprocessable Entity (HTTP 422)\n      Duplicate value for \"description\"\n      \"\"\"\n    And the exit status should be 1\n    And the file \"../home/.config/hub\" should not exist\n\n  Scenario: Credentials from GITHUB_USER & GITHUB_PASSWORD\n    Given the GitHub API server:\n      \"\"\"\n      post('/authorizations') {\n        assert_basic_auth 'mislav', 'kitty'\n        status 201\n        json :token => 'OTOKEN'\n      }\n      get('/user') {\n        json :login => 'mislav'\n      }\n      post('/user/repos') {\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    Given $GITHUB_USER is \"mislav\"\n    And $GITHUB_PASSWORD is \"kitty\"\n    When I successfully run `hub create`\n    Then the output should not contain \"github.com password for mislav\"\n    And the file \"../home/.config/hub\" should contain \"oauth_token: OTOKEN\"\n\n  Scenario: XDG: legacy config found, credentials from GITHUB_USER & GITHUB_PASSWORD\n    Given I am \"mislav\" on github.com with OAuth token \"LTOKEN\"\n    And the GitHub API server:\n      \"\"\"\n      post('/authorizations') {\n        assert_basic_auth 'mislav', 'kitty'\n        status 201\n        json :token => 'OTOKEN'\n      }\n      get('/user') {\n        json :login => 'mislav'\n      }\n      post('/user/repos') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    And $GITHUB_USER is \"mislav\"\n    And $GITHUB_PASSWORD is \"kitty\"\n    And $XDG_CONFIG_HOME is \"$HOME/.xdg\"\n    When I successfully run `hub create`\n    Then the file \"../home/.xdg/hub\" should contain \"oauth_token: OTOKEN\"\n    And the stderr with expanded variables should contain exactly:\n      \"\"\"\n      Notice: config file found but not respected at: <$HOME>/.config/hub\n      You might want to move it to `<$HOME>/.xdg/hub' to avoid re-authenticating.\\n\n      \"\"\"\n\n  Scenario: XDG: config from secondary directories\n    Given I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n    And the GitHub API server:\n      \"\"\"\n      get('/user') {\n        json :login => 'mislav'\n      }\n      post('/user/repos') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    And $GITHUB_USER is \"mislav\"\n    And $GITHUB_PASSWORD is \"kitty\"\n    And $XDG_CONFIG_HOME is \"$HOME/.xdg\"\n    And $XDG_CONFIG_DIRS is \"/etc/xdg-nonsense:$HOME/.xdg-dir\"\n    When I move the file named \"../home/.config/hub\" to \"../home/.xdg-dir/hub\"\n    And I successfully run `hub create`\n    Then the file \"../home/.xdg/hub\" should not exist\n    And the stderr should contain exactly \"\"\n\n  Scenario: Credentials from GITHUB_TOKEN\n    Given the GitHub API server:\n      \"\"\"\n      get('/user') {\n        halt 401 unless request.env[\"HTTP_AUTHORIZATION\"] == \"token OTOKEN\"\n        json :login => 'mislav'\n      }\n      post('/user/repos') {\n        halt 401 unless request.env[\"HTTP_AUTHORIZATION\"] == \"token OTOKEN\"\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    Given $GITHUB_TOKEN is \"OTOKEN\"\n    When I successfully run `hub create`\n    Then the output should not contain \"github.com password\"\n    And the output should not contain \"github.com username\"\n    And the file \"../home/.config/hub\" should not exist\n\n  Scenario: Credentials from GITHUB_TOKEN when obtaining username fails\n    Given I am in \"git://github.com/monalisa/playground.git\" git repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/user') {\n        status 403\n        json :message => \"Resource not accessible by integration\",\n             :documentation_url => \"https://developer.github.com/v3/users/#get-the-authenticated-user\"\n      }\n      \"\"\"\n    Given $GITHUB_TOKEN is \"OTOKEN\"\n    Given $GITHUB_USER is \"\"\n    When I run `hub release show v1.2.0`\n    Then the output should not contain \"github.com password\"\n    And the output should not contain \"github.com username\"\n    And the file \"../home/.config/hub\" should not exist\n    And the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Error getting current user: Forbidden (HTTP 403)\n      Resource not accessible by integration\n      You must specify GITHUB_USER via environment variable.\\n\n      \"\"\"\n\n  Scenario: Credentials from GITHUB_TOKEN and GITHUB_USER\n    Given I am in \"git://github.com/monalisa/playground.git\" git repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/user') {\n        status 403\n        json :message => \"Resource not accessible by integration\",\n             :documentation_url => \"https://developer.github.com/v3/users/#get-the-authenticated-user\"\n      }\n      get('/repos/monalisa/playground/releases') {\n        halt 401 unless request.env[\"HTTP_AUTHORIZATION\"] == \"token OTOKEN\"\n        json [\n          { tag_name: 'v1.2.0',\n          }\n        ]\n      }\n      \"\"\"\n    Given $GITHUB_TOKEN is \"OTOKEN\"\n    Given $GITHUB_USER is \"hubot\"\n    When I successfully run `hub release show v1.2.0`\n    Then the output should not contain \"github.com password\"\n    And the output should not contain \"github.com username\"\n    And the file \"../home/.config/hub\" should not exist\n\n  Scenario: Credentials from GITHUB_TOKEN and GITHUB_REPOSITORY\n    Given I am in \"git://github.com/monalisa/playground.git\" git repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/user') {\n        status 403\n        json :message => \"Resource not accessible by integration\",\n             :documentation_url => \"https://developer.github.com/v3/users/#get-the-authenticated-user\"\n      }\n      get('/repos/monalisa/playground/releases') {\n        halt 401 unless request.env[\"HTTP_AUTHORIZATION\"] == \"token OTOKEN\"\n        json [\n          { tag_name: 'v1.2.0',\n          }\n        ]\n      }\n      \"\"\"\n    Given $GITHUB_TOKEN is \"OTOKEN\"\n    Given $GITHUB_REPOSITORY is \"mona-lisa/play-ground\"\n    Given $GITHUB_USER is \"\"\n    When I successfully run `hub release show v1.2.0`\n    Then the output should not contain \"github.com password\"\n    And the output should not contain \"github.com username\"\n    And the file \"../home/.config/hub\" should not exist\n\n  Scenario: Credentials from GITHUB_TOKEN override those from config file\n    Given I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/user') {\n        halt 401 unless request.env[\"HTTP_AUTHORIZATION\"] == \"token PTOKEN\"\n        json :login => 'parkr'\n      }\n      get('/repos/parkr/dotfiles') {\n        halt 401 unless request.env[\"HTTP_AUTHORIZATION\"] == \"token PTOKEN\"\n        json :private => false,\n             :name => 'dotfiles', :owner => { :login => 'parkr' },\n             :permissions => { :push => true }\n      }\n      \"\"\"\n    Given $GITHUB_TOKEN is \"PTOKEN\"\n    When I successfully run `hub clone dotfiles`\n    Then it should clone \"https://github.com/parkr/dotfiles.git\"\n    And the file \"../home/.config/hub\" should contain \"user: mislav\"\n    And the file \"../home/.config/hub\" should contain \"oauth_token: OTOKEN\"\n\n  Scenario: Wrong password\n    Given the GitHub API server:\n      \"\"\"\n      post('/authorizations') {\n        assert_basic_auth 'mislav', 'kitty'\n      }\n      \"\"\"\n    When I run `hub create` interactively\n    When I type \"mislav\"\n    And I type \"WRONG\"\n    Then the stderr should contain exactly:\n      \"\"\"\n      Error creating repository: Unauthorized (HTTP 401)\n      Bad credentials\n\n      \"\"\"\n    And the exit status should be 1\n    And the file \"../home/.config/hub\" should not exist\n\n  Scenario: Two-factor authentication, create authorization\n    Given the GitHub API server:\n      \"\"\"\n      post('/authorizations') {\n        assert_basic_auth 'mislav', 'kitty'\n        if request.env['HTTP_X_GITHUB_OTP'] == '112233'\n          status 201\n          json :token => 'OTOKEN'\n        else\n          response.headers['X-GitHub-OTP'] = 'required; app'\n          status 401\n          json :message => \"Must specify two-factor authentication OTP code.\"\n        end\n      }\n      get('/user') {\n        json :login => 'mislav'\n      }\n      post('/user/repos') {\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    When I run `hub create` interactively\n    When I type \"mislav\"\n    And I type \"kitty\"\n    And I type \"112233\"\n    Then the output should contain \"github.com password for mislav (never stored):\"\n    Then the output should contain \"two-factor authentication code:\"\n    And the output should not contain \"warning: invalid two-factor code\"\n    And the exit status should be 0\n    And the file \"../home/.config/hub\" should contain \"oauth_token: OTOKEN\"\n\n  Scenario: Retry entering two-factor authentication code\n    Given the GitHub API server:\n      \"\"\"\n      previous_otp_code = nil\n      post('/authorizations') {\n        assert_basic_auth 'mislav', 'kitty'\n        if request.env['HTTP_X_GITHUB_OTP'] == '112233'\n          halt 400 unless '666' == previous_otp_code\n          status 201\n          json :token => 'OTOKEN'\n        else\n          previous_otp_code = request.env['HTTP_X_GITHUB_OTP']\n          response.headers['X-GitHub-OTP'] = 'required; app'\n          status 401\n          json :message => \"Must specify two-factor authentication OTP code.\"\n        end\n      }\n      get('/user') {\n        json :login => 'mislav'\n      }\n      post('/user/repos') {\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    When I run `hub create` interactively\n    When I type \"mislav\"\n    And I type \"kitty\"\n    And I type \"666\"\n    And I type \"112233\"\n    Then the output should contain \"warning: invalid two-factor code\"\n    And the exit status should be 0\n    And the file \"../home/.config/hub\" should contain \"oauth_token: OTOKEN\"\n\n  Scenario: Special characters in username & password\n    Given the GitHub API server:\n      \"\"\"\n      post('/authorizations') {\n        assert_basic_auth 'mislav@example.com', 'my pass@phrase ok?'\n        status 201\n        json :token => 'OTOKEN'\n      }\n      get('/user') {\n        json :login => 'mislav'\n      }\n      get('/repos/mislav/dotfiles') {\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    When I run `hub create` interactively\n    When I type \"mislav@example.com\"\n    And I type \"my pass@phrase ok?\"\n    Then the output should contain \"github.com password for mislav@example.com (never stored):\"\n    And the exit status should be 0\n    And the file \"../home/.config/hub\" should contain \"user: mislav\"\n    And the file \"../home/.config/hub\" should contain \"oauth_token: OTOKEN\"\n\n  Scenario: Enterprise fork authentication with username & password, re-using existing authorization\n    Given the GitHub API server:\n      \"\"\"\n      require 'rack/auth/basic'\n      post('/api/v3/authorizations', :host_name => 'git.my.org') {\n        auth = Rack::Auth::Basic::Request.new(env)\n        halt 401 unless auth.credentials == %w[mislav kitty]\n        status 201\n        json :token => 'OTOKEN', :note_url => 'https://hub.github.com/'\n      }\n      get('/api/v3/user', :host_name => 'git.my.org') {\n        json :login => 'mislav'\n      }\n      post('/api/v3/repos/evilchelu/dotfiles/forks', :host_name => 'git.my.org') {\n        status 202\n        json :name => 'dotfiles', :owner => { :login => 'mislav' }\n      }\n      \"\"\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    And the \"origin\" remote has url \"git@git.my.org:evilchelu/dotfiles.git\"\n    When I run `hub fork` interactively\n    And I type \"mislav\"\n    And I type \"kitty\"\n    Then the output should contain \"git.my.org password for mislav (never stored):\"\n    And the exit status should be 0\n    And the file \"../home/.config/hub\" should contain \"git.my.org\"\n    And the file \"../home/.config/hub\" should contain \"user: mislav\"\n    And the file \"../home/.config/hub\" should contain \"oauth_token: OTOKEN\"\n    And the url for \"mislav\" should be \"https://git.my.org/mislav/dotfiles.git\"\n\n  Scenario: Broken config is missing user.\n    Given a file named \"../home/.config/hub\" with:\n      \"\"\"\n      github.com:\n      - oauth_token: OTOKEN\n        protocol: https\n      \"\"\"\n    And the \"origin\" remote has url \"git://github.com/mislav/coral.git\"\n    When I run `hub browse -u` interactively\n    And I type \"pcorpet\"\n    Then the output should contain \"github.com username:\"\n    And the file \"../home/.config/hub\" should contain \"- user: pcorpet\"\n    And the file \"../home/.config/hub\" should contain \"  oauth_token: OTOKEN\"\n\n  Scenario: Broken config is missing user and interactive input is empty.\n    Given a file named \"../home/.config/hub\" with:\n      \"\"\"\n      github.com:\n      - oauth_token: OTOKEN\n        protocol: https\n      \"\"\"\n    And the \"origin\" remote has url \"git://github.com/mislav/coral.git\"\n    When I run `hub browse -u` interactively\n    And I type \"\"\n    Then the output should contain \"github.com username:\"\n    And the output should contain \"missing user\"\n    And the file \"../home/.config/hub\" should not contain \"user\"\n    \n  Scenario: Config file is not writeable, should exit before asking for credentials\n      Given $HUB_CONFIG is \"/InvalidConfigFile\"\n      When I run `hub create` interactively\n      Then the output should contain:\n        \"\"\"\n        open /InvalidConfigFile:\n        \"\"\"\n      And the exit status should be 1\n      And the file \"../home/.config/hub\" should not exist\n      \n  Scenario: Config file is not writeable on default location, should exit before asking for credentials\n      Given a directory named \"../home/.config\" with mode \"600\"\n      When I run `hub create` interactively\n      Then the output with expanded variables should contain:\n        \"\"\"\n        <$HOME>/.config/hub: permission denied\\n\n        \"\"\"\n      And the exit status should be 1\n      And the file \"../home/.config/hub\" should not exist\n\n  Scenario: GitHub SSO challenge\n    Given I am \"monalisa\" on github.com with OAuth token \"OTOKEN\"\n    And I am in \"git://github.com/acme/playground.git\" git repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/acme/playground/releases') {\n        response.headers['X-GitHub-SSO'] = 'required; url=http://example.com?auth=HASH'\n        status 403\n      }\n      \"\"\"\n    When I run `hub release show v1.2.0`\n    Then the stderr should contain exactly:\n      \"\"\"\n      Error fetching releases: Forbidden (HTTP 403)\n      You must authorize your token to access this organization:\n      http://example.com?auth=HASH\\n\n      \"\"\"\n"
  },
  {
    "path": "features/bash_completion.feature",
    "content": "@completion\nFeature: bash tab-completion\n\n  Background:\n    Given my shell is bash\n    And I'm using git-distributed base git completions\n\n  Scenario: \"pu\" matches multiple commands including \"pull-request\"\n    When I type \"git pu\" and press <Tab>\n    Then the command should not expand\n    When I press <Tab> again\n    Then the completion menu should offer \"pull pull-request push\"\n\n  Scenario: \"ci-\" expands to \"ci-status\"\n    When I type \"git ci-\" and press <Tab>\n    Then the command should expand to \"git ci-status\"\n\n  Scenario: Offers pull-request flags\n    When I type \"git pull-request -\" and press <Tab>\n    When I press <Tab> again\n    Then the completion menu should offer \"-F -b -f -h -i -m -a -M -l\" unsorted\n\n  Scenario: Doesn't offer already used pull-request flags\n    When I type \"git pull-request -F myfile -h mybranch -\" and press <Tab>\n    When I press <Tab> again\n    Then the completion menu should offer \"-b -f -i -m -a -M -l\" unsorted\n\n  Scenario: Browse to issues\n    When I type \"git browse -- i\" and press <Tab>\n    Then the command should expand to \"git browse -- issues\"\n\n  Scenario: Browse to punch-card graph\n    When I type \"git browse -- graphs/p\" and press <Tab>\n    Then the command should expand to \"git browse -- graphs/punch-card\"\n\n  Scenario: Completion of fork argument\n    When I type \"git fork -\" and press <Tab>\n    When I press <Tab> again\n    Then the completion menu should offer \"--no-remote --remote-name --org\" unsorted\n\n  Scenario: Completion of user/repo in \"browse\"\n  Scenario: Completion of branch names in \"compare\"\n  Scenario: Completion of \"owner/repo:branch\" in \"pull-request -h/b\"\n"
  },
  {
    "path": "features/browse.feature",
    "content": "Feature: hub browse\n  Background:\n    Given I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: No repo\n    When I run `hub browse`\n    Then the exit status should be 1\n    Then the output should contain exactly \"Usage: hub browse [-uc] [[<USER>/]<REPOSITORY>|--] [<SUBPAGE>]\\n\"\n\n  Scenario: Project with owner\n    When I successfully run `hub browse mislav/dotfiles`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles\" should be run\n\n  Scenario: Project without owner\n    Given I am \"mislav\" on github.com\n    When I successfully run `hub browse dotfiles`\n    Then \"open https://github.com/mislav/dotfiles\" should be run\n\n  Scenario: Explicit project overrides current\n    Given I am in \"git://github.com/josh/rails-behaviors.git\" git repo\n    And I am \"mislav\" on github.com\n    When I successfully run `hub browse dotfiles`\n    Then \"open https://github.com/mislav/dotfiles\" should be run\n\n  Scenario: Project issues\n    When I successfully run `hub browse mislav/dotfiles issues`\n    Then \"open https://github.com/mislav/dotfiles/issues\" should be run\n\n  Scenario: Project wiki\n    When I successfully run `hub browse mislav/dotfiles wiki`\n    Then \"open https://github.com/mislav/dotfiles/wiki\" should be run\n\n  Scenario: Project commits on master\n    When I successfully run `hub browse mislav/dotfiles commits`\n    Then \"open https://github.com/mislav/dotfiles/commits/master\" should be run\n\n  Scenario: Specific commit in project\n    When I successfully run `hub browse mislav/dotfiles commit/4173c3b`\n    Then \"open https://github.com/mislav/dotfiles/commit/4173c3b\" should be run\n\n  Scenario: Output the URL instead of browse\n    When I successfully run `hub browse -u mislav/dotfiles`\n    Then the output should contain exactly \"https://github.com/mislav/dotfiles\\n\"\n    But \"open https://github.com/mislav/dotfiles\" should not be run\n\n  Scenario: Current project\n    Given I am in \"git://github.com/mislav/dotfiles.git\" git repo\n    When I successfully run `hub browse`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles\" should be run\n\n  Scenario: Commit in current project\n    Given I am in \"git://github.com/mislav/dotfiles.git\" git repo\n    When I successfully run `hub browse -- commit/abcd1234`\n    Then \"open https://github.com/mislav/dotfiles/commit/abcd1234\" should be run\n\n  Scenario: Current branch\n    Given I am in \"git://github.com/mislav/dotfiles.git\" git repo\n    And git \"push.default\" is set to \"upstream\"\n    And I am on the \"feature\" branch with upstream \"origin/experimental\"\n    When I successfully run `hub browse`\n    Then \"open https://github.com/mislav/dotfiles/tree/experimental\" should be run\n\n  Scenario: Current branch pushed to fork\n    Given I am in \"git://github.com/blueyed/dotfiles.git\" git repo\n    And the \"mislav\" remote has url \"git@github.com:mislav/dotfiles.git\"\n    And I am on the \"feature\" branch with upstream \"mislav/experimental\"\n    And git \"push.default\" is set to \"upstream\"\n    When I successfully run `hub browse`\n    Then \"open https://github.com/mislav/dotfiles/tree/experimental\" should be run\n\n  Scenario: Current branch pushed to fork with simple tracking\n    Given I am in \"git://github.com/blueyed/dotfiles.git\" git repo\n    And the \"mislav\" remote has url \"git@github.com:mislav/dotfiles.git\"\n    And I am on the \"feature\" branch with upstream \"mislav/feature\"\n    And git \"push.default\" is set to \"simple\"\n    When I successfully run `hub browse`\n    Then \"open https://github.com/mislav/dotfiles/tree/feature\" should be run\n\n  Scenario: Default branch\n    Given I am in \"git://github.com/mislav/dotfiles.git\" git repo\n    And the default branch for \"origin\" is \"develop\"\n    And I am on the \"develop\" branch with upstream \"origin/develop\"\n    When I successfully run `hub browse`\n    Then \"open https://github.com/mislav/dotfiles\" should be run\n\n  Scenario: Current branch, no tracking\n    Given I am in \"git://github.com/mislav/dotfiles.git\" git repo\n    And I am on the \"feature\" branch\n    When I successfully run `hub browse`\n    Then \"open https://github.com/mislav/dotfiles\" should be run\n\n  Scenario: Default branch in upstream repo as opposed to fork\n    Given I am in \"git://github.com/jashkenas/coffee-script.git\" git repo\n    And the \"mislav\" remote has url \"git@github.com:mislav/coffee-script.git\"\n    And the default branch for \"origin\" is \"master\"\n    And the \"master\" branch is pushed to \"mislav/master\"\n    When I successfully run `hub browse`\n    Then \"open https://github.com/jashkenas/coffee-script\" should be run\n\n  Scenario: Current branch with special chars\n    Given I am in \"git://github.com/mislav/dotfiles.git\" git repo\n    And I am on the \"fix-bug-#123\" branch with upstream \"origin/fix-bug-#123\"\n    When I successfully run `hub browse`\n    Then \"open https://github.com/mislav/dotfiles/tree/fix-bug-%23123\" should be run\n\n  Scenario: Commits on current branch\n    Given I am in \"git://github.com/mislav/dotfiles.git\" git repo\n    And git \"push.default\" is set to \"upstream\"\n    And I am on the \"feature\" branch with upstream \"origin/experimental\"\n    When I successfully run `hub browse -- commits`\n    Then \"open https://github.com/mislav/dotfiles/commits/experimental\" should be run\n\n  Scenario: Issues subpage ignores tracking configuration\n    Given I am in \"git://github.com/jashkenas/coffee-script.git\" git repo\n    And the \"mislav\" remote has url \"git@github.com:mislav/coffee-script.git\"\n    And git \"push.default\" is set to \"upstream\"\n    And I am on the \"feature\" branch with upstream \"mislav/experimental\"\n    When I successfully run `hub browse -- issues`\n    Then \"open https://github.com/jashkenas/coffee-script/issues\" should be run\n\n  Scenario: Issues subpage ignores current branch\n    Given I am in \"git://github.com/jashkenas/coffee-script.git\" git repo\n    And the \"mislav\" remote has url \"git@github.com:mislav/coffee-script.git\"\n    And I am on the \"feature\" branch pushed to \"mislav/feature\"\n    When I successfully run `hub browse -- issues`\n    Then the output should not contain anything\n    Then \"open https://github.com/jashkenas/coffee-script/issues\" should be run\n\n  Scenario: Forward Slash Delimited branch\n    Given I am in \"git://github.com/mislav/dotfiles.git\" git repo\n    And git \"push.default\" is set to \"upstream\"\n    And I am on the \"foo/bar\" branch with upstream \"origin/baz/qux/moo\"\n    When I successfully run `hub browse`\n    Then \"open https://github.com/mislav/dotfiles/tree/baz/qux/moo\" should be run\n\n  Scenario: No branch\n    Given I am in \"git://github.com/mislav/dotfiles.git\" git repo\n    And I am in detached HEAD\n    When I successfully run `hub browse`\n    Then \"open https://github.com/mislav/dotfiles\" should be run\n\n  Scenario: No branch to pulls\n    Given I am in \"git://github.com/mislav/dotfiles.git\" git repo\n    And I am in detached HEAD\n    When I successfully run `hub browse -- pulls`\n    Then \"open https://github.com/mislav/dotfiles/pulls\" should be run\n\n  Scenario: Dot Delimited branch\n    Given I am in \"git://github.com/mislav/dotfiles.git\" git repo\n    And git \"push.default\" is set to \"upstream\"\n    And I am on the \"fix-glob-for.js\" branch with upstream \"origin/fix-glob-for.js\"\n    When I successfully run `hub browse`\n    Then \"open https://github.com/mislav/dotfiles/tree/fix-glob-for.js\" should be run\n\n  Scenario: Wiki repo\n    Given I am in \"git://github.com/defunkt/hub.wiki.git\" git repo\n    When I successfully run `hub browse`\n    Then \"open https://github.com/defunkt/hub/wiki\" should be run\n\n  Scenario: Wiki commits\n    Given I am in \"git://github.com/defunkt/hub.wiki.git\" git repo\n    When I successfully run `hub browse -- commits`\n    Then \"open https://github.com/defunkt/hub/wiki/_history\" should be run\n\n  Scenario: Wiki pages\n    Given I am in \"git://github.com/defunkt/hub.wiki.git\" git repo\n    When I successfully run `hub browse -- pages`\n    Then \"open https://github.com/defunkt/hub/wiki/_pages\" should be run\n\n  Scenario: Repo with remote with local path\n    Given I am in \"git://github.com/mislav/dotfiles.git\" git repo\n    And the \"upstream\" remote has url \"../path/to/another/repo.git\"\n    When I successfully run `hub browse`\n    Then \"open https://github.com/mislav/dotfiles\" should be run\n\n  Scenario: Enterprise repo\n    Given I am in \"git://git.my.org/mislav/dotfiles.git\" git repo\n    And I am \"mislav\" on git.my.org with OAuth token \"FITOKEN\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    When I successfully run `hub browse`\n    Then \"open https://git.my.org/mislav/dotfiles\" should be run\n\n  Scenario: Multiple Enterprise repos\n    Given I am in \"git://git.my.org/mislav/dotfiles.git\" git repo\n    And I am \"mislav\" on git.my.org with OAuth token \"FITOKEN\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    And \"git.another.org\" is a whitelisted Enterprise host\n    When I successfully run `hub browse`\n    Then \"open https://git.my.org/mislav/dotfiles\" should be run\n\n  Scenario: Enterprise repo over HTTP\n    Given I am in \"git://git.my.org/mislav/dotfiles.git\" git repo\n    And I am \"mislav\" on http://git.my.org with OAuth token \"FITOKEN\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    When I successfully run `hub browse`\n    Then \"open http://git.my.org/mislav/dotfiles\" should be run\n\n  Scenario: SSH alias\n    Given the SSH config:\n      \"\"\"\n      Host gh\n        User git\n        HostName github.com\n      \"\"\"\n    Given I am in \"gh:singingwolfboy/sekrit.git\" git repo\n    When I successfully run `hub browse`\n    Then \"open https://github.com/singingwolfboy/sekrit\" should be run\n\n  Scenario: SSH GitHub alias\n    Given the SSH config:\n      \"\"\"\n      Host github.com\n        HostName ssh.github.com\n      \"\"\"\n    Given I am in \"git@github.com:suan/git-sanity.git\" git repo\n    When I successfully run `hub browse`\n    Then \"open https://github.com/suan/git-sanity\" should be run\n"
  },
  {
    "path": "features/checkout.feature",
    "content": "Feature: hub checkout <PULLREQ-URL>\n  Background:\n    Given I am in \"git://github.com/mojombo/jekyll.git\" git repo\n    And I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Unchanged command\n    When I run `hub checkout master`\n    Then \"git checkout master\" should be run\n\n  Scenario: Checkout a pull request\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/jekyll/pulls/77') {\n        halt 415 unless request.accept?('application/vnd.github.v3+json')\n        json :number => 77, :head => {\n          :ref => \"fixes\",\n          :repo => {\n            :owner => { :login => \"mislav\" },\n            :name => \"jekyll\",\n            :private => false\n          }\n        }, :base => {\n          :repo => {\n            :name => 'jekyll',\n            :html_url => 'https://github.com/mojombo/jekyll',\n            :owner => { :login => \"mojombo\" },\n          }\n        }, :maintainer_can_modify => false\n      }\n      \"\"\"\n    When I successfully run `hub checkout -f https://github.com/mojombo/jekyll/pull/77 -q`\n    Then \"git fetch origin refs/pull/77/head:fixes\" should be run\n    And \"git checkout -f fixes -q\" should be run\n    And \"fixes\" should merge \"refs/pull/77/head\" from remote \"origin\"\n\n  Scenario: Avoid overriding existing merge configuration\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/jekyll/pulls/77') {\n        json :number => 77, :head => {\n          :ref => \"fixes\",\n          :repo => {\n            :owner => { :login => \"mislav\" },\n            :name => \"jekyll\",\n            :private => false\n          }\n        }, :base => {\n          :repo => {\n            :name => 'jekyll',\n            :html_url => 'https://github.com/mojombo/jekyll',\n            :owner => { :login => \"mojombo\" },\n          }\n        }, :maintainer_can_modify => false\n      }\n      \"\"\"\n    Given I successfully run `git config branch.fixes.remote ORIG_REMOTE`\n    Given I successfully run `git config branch.fixes.merge custom/ref/spec`\n    When I successfully run `hub checkout https://github.com/mojombo/jekyll/pull/77`\n    Then \"git fetch origin refs/pull/77/head:fixes\" should be run\n    And \"git checkout fixes\" should be run\n    And \"fixes\" should merge \"custom/ref/spec\" from remote \"ORIG_REMOTE\"\n\n  Scenario: Head ref matches default branch\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/jekyll/pulls/77') {\n        json :number => 77, :head => {\n          :ref => \"master\",\n          :repo => {\n            :owner => { :login => \"mislav\" },\n            :name => \"jekyll\",\n            :default_branch => \"master\",\n            :private => false\n          }\n        }, :base => {\n          :repo => {\n            :name => 'jekyll',\n            :html_url => 'https://github.com/mojombo/jekyll',\n            :owner => { :login => \"mojombo\" },\n          }\n        }, :maintainer_can_modify => false\n      }\n      \"\"\"\n    When I successfully run `hub checkout https://github.com/mojombo/jekyll/pull/77`\n    Then \"git fetch origin refs/pull/77/head:mislav-master\" should be run\n    And \"git checkout mislav-master\" should be run\n    And \"mislav-master\" should merge \"refs/pull/77/head\" from remote \"origin\"\n\n  Scenario: No matching remotes for pull request base\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/jekyll/pulls/77') {\n        json :number => 77, :base => {\n          :repo => {\n            :name => 'jekyll',\n            :html_url => 'https://github.com/mislav/jekyll',\n            :owner => { :login => \"mislav\" },\n          }\n        }\n      }\n      \"\"\"\n    When I run `hub checkout -f https://github.com/mislav/jekyll/pull/77 -q`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      could not find a git remote for 'mislav/jekyll'\\n\n      \"\"\"\n\n  Scenario: Custom name for new branch\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/jekyll/pulls/77') {\n        json :number => 77, :head => {\n          :ref => \"fixes\",\n          :repo => {\n            :name => \"jekyll\",\n            :owner => { :login => \"mislav\" },\n          }\n        }, :base => {\n          :repo => {\n            :name => 'jekyll',\n            :html_url => 'https://github.com/mojombo/jekyll',\n            :owner => { :login => \"mojombo\" },\n          }\n        }, :maintainer_can_modify => false\n      }\n      \"\"\"\n    When I successfully run `hub checkout https://github.com/mojombo/jekyll/pull/77 fixes-from-mislav`\n    Then \"git fetch origin refs/pull/77/head:fixes-from-mislav\" should be run\n    And \"git checkout fixes-from-mislav\" should be run\n    And \"fixes-from-mislav\" should merge \"refs/pull/77/head\" from remote \"origin\"\n\n  Scenario: Same-repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/jekyll/pulls/77') {\n        json :number => 77, :head => {\n          :ref => \"fixes\",\n          :repo => {\n            :name => \"jekyll\",\n            :owner => { :login => \"mojombo\" },\n          }\n        }, :base => {\n          :repo => {\n            :name => \"jekyll\",\n            :html_url => \"https://github.com/mojombo/jekyll\",\n            :owner => { :login => \"mojombo\" },\n          }\n        }\n      }\n      \"\"\"\n    When I successfully run `hub checkout -f https://github.com/mojombo/jekyll/pull/77 -q`\n    Then \"git fetch origin +refs/heads/fixes:refs/remotes/origin/fixes\" should be run\n    And \"git checkout -f -b fixes --no-track origin/fixes -q\" should be run\n    And \"fixes\" should merge \"refs/heads/fixes\" from remote \"origin\"\n\n  Scenario: Same-repo with custom branch name\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/jekyll/pulls/77') {\n        json :number => 77, :head => {\n          :ref => \"fixes\",\n          :repo => {\n            :name => \"jekyll\",\n            :owner => { :login => \"mojombo\" },\n          }\n        }, :base => {\n          :repo => {\n            :name => \"jekyll\",\n            :html_url => \"https://github.com/mojombo/jekyll\",\n            :owner => { :login => \"mojombo\" },\n          }\n        }\n      }\n      \"\"\"\n    When I successfully run `hub checkout https://github.com/mojombo/jekyll/pull/77 mycustombranch`\n    Then \"git fetch origin +refs/heads/fixes:refs/remotes/origin/fixes\" should be run\n    And \"git checkout -b mycustombranch --no-track origin/fixes\" should be run\n    And \"mycustombranch\" should merge \"refs/heads/fixes\" from remote \"origin\"\n\n  Scenario: Unavailable fork\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/jekyll/pulls/77') {\n        json :number => 77, :head => {\n          :ref => \"fixes\",\n          :repo => nil\n        }, :base => {\n          :repo => {\n            :name => \"jekyll\",\n            :html_url => \"https://github.com/mojombo/jekyll\",\n            :owner => { :login => \"mojombo\" },\n          }\n        }\n      }\n      \"\"\"\n    When I successfully run `hub checkout https://github.com/mojombo/jekyll/pull/77`\n    Then \"git fetch origin refs/pull/77/head:fixes\" should be run\n    And \"git checkout fixes\" should be run\n    And \"fixes\" should merge \"refs/pull/77/head\" from remote \"origin\"\n\n  Scenario: Reuse existing remote for head branch\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/jekyll/pulls/77') {\n        json :number => 77, :head => {\n          :ref => \"fixes\",\n          :repo => {\n            :owner => { :login => \"mislav\" },\n            :name => \"jekyll\",\n            :private => false\n          }\n        }, :base => {\n          :repo => {\n            :name => 'jekyll',\n            :html_url => 'https://github.com/mojombo/jekyll',\n            :owner => { :login => \"mojombo\" },\n          }\n        }\n      }\n      \"\"\"\n    And the \"mislav\" remote has url \"git://github.com/mislav/jekyll.git\"\n    When I successfully run `hub checkout -f https://github.com/mojombo/jekyll/pull/77 -q`\n    Then \"git fetch mislav +refs/heads/fixes:refs/remotes/mislav/fixes\" should be run\n    And \"git checkout -f -b fixes --no-track mislav/fixes -q\" should be run\n    And \"fixes\" should merge \"refs/heads/fixes\" from remote \"mislav\"\n\n  Scenario: Reuse existing remote and branch\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/jekyll/pulls/77') {\n        json :number => 77, :head => {\n          :ref => \"fixes\",\n          :repo => {\n            :owner => { :login => \"mislav\" },\n            :name => \"jekyll\",\n            :private => false\n          }\n        }, :base => {\n          :repo => {\n            :name => 'jekyll',\n            :html_url => 'https://github.com/mojombo/jekyll',\n            :owner => { :login => \"mojombo\" },\n          }\n        }\n      }\n      \"\"\"\n    And the \"mislav\" remote has url \"git://github.com/mislav/jekyll.git\"\n    And I am on the \"fixes\" branch\n    When I successfully run `hub checkout -f https://github.com/mojombo/jekyll/pull/77 -q`\n    Then \"git fetch mislav +refs/heads/fixes:refs/remotes/mislav/fixes\" should be run\n    And \"git checkout -f fixes -q\" should be run\n    And \"git merge --ff-only refs/remotes/mislav/fixes\" should be run\n\n  Scenario: Modifiable fork\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/jekyll/pulls/77') {\n        json :number => 77, :head => {\n          :ref => \"fixes\",\n          :repo => {\n            :owner => { :login => \"mislav\" },\n            :name => \"jekyll\",\n            :html_url => \"https://github.com/mislav/jekyll.git\",\n            :private => false\n          },\n        }, :base => {\n          :repo => {\n            :name => 'jekyll',\n            :html_url => 'https://github.com/mojombo/jekyll',\n            :owner => { :login => \"mojombo\" },\n          }\n        }, :maintainer_can_modify => true\n      }\n      \"\"\"\n    And git protocol is preferred\n    When I successfully run `hub checkout -f https://github.com/mojombo/jekyll/pull/77 -q`\n    Then \"git fetch origin refs/pull/77/head:fixes\" should be run\n    And \"git checkout -f fixes -q\" should be run\n    And \"fixes\" should merge \"refs/heads/fixes\" from remote \"git@github.com:mislav/jekyll.git\"\n\n  Scenario: Modifiable fork into current branch\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/jekyll/pulls/77') {\n        json :number => 77, :head => {\n          :ref => \"fixes\",\n          :repo => {\n            :owner => { :login => \"mislav\" },\n            :name => \"jekyll\",\n            :html_url => \"https://github.com/mislav/jekyll.git\",\n            :private => false\n          },\n        }, :base => {\n          :repo => {\n            :name => 'jekyll',\n            :html_url => 'https://github.com/mojombo/jekyll',\n            :owner => { :login => \"mojombo\" },\n          }\n        }, :maintainer_can_modify => true\n      }\n      \"\"\"\n    And git protocol is preferred\n    And I am on the \"fixes\" branch\n    And there is a git FETCH_HEAD\n    When I successfully run `hub checkout https://github.com/mojombo/jekyll/pull/77`\n    Then \"git fetch origin refs/pull/77/head\" should be run\n    And \"git checkout fixes\" should be run\n    And \"git merge --ff-only FETCH_HEAD\" should be run\n    And \"fixes\" should merge \"refs/heads/fixes\" from remote \"git@github.com:mislav/jekyll.git\"\n\n  Scenario: Modifiable fork with HTTPS\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/jekyll/pulls/77') {\n        json :number => 77, :head => {\n          :ref => \"fixes\",\n          :repo => {\n            :owner => { :login => \"mislav\" },\n            :name => \"jekyll\",\n            :html_url => \"https://github.com/mislav/jekyll.git\",\n            :private => false\n          },\n        }, :base => {\n          :repo => {\n            :name => 'jekyll',\n            :html_url => 'https://github.com/mojombo/jekyll',\n            :owner => { :login => \"mojombo\" },\n          }\n        }, :maintainer_can_modify => true\n      }\n      \"\"\"\n    When I successfully run `hub checkout -f https://github.com/mojombo/jekyll/pull/77 -q`\n    Then \"git fetch origin refs/pull/77/head:fixes\" should be run\n    And \"git checkout -f fixes -q\" should be run\n    And \"fixes\" should merge \"refs/heads/fixes\" from remote \"https://github.com/mislav/jekyll.git\"\n"
  },
  {
    "path": "features/cherry_pick.feature",
    "content": "Feature: hub cherry-pick\n  Background:\n    Given I am in \"git://github.com/rtomayko/ronn.git\" git repo\n    And I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Unchanged\n    When I run `hub cherry-pick a319d88`\n    Then the git command should be unchanged\n\n  Scenario: From GitHub commit URL\n    When I run `hub cherry-pick https://github.com/rtomayko/ronn/commit/a319d88#comments`\n    Then \"git fetch -q --no-tags origin\" should be run\n    And \"git cherry-pick a319d88\" should be run\n\n  Scenario: From GitHub pull request URL\n    When I run `hub cherry-pick https://github.com/blueyed/ronn/pull/560/commits/a319d88`\n    And \"git fetch -q --no-tags origin refs/pull/560/head\" should be run\n    And \"git cherry-pick a319d88\" should be run\n\n  Scenario: From fork that has existing remote\n    Given the \"mislav\" remote has url \"git@github.com:mislav/ronn.git\"\n    When I run `hub cherry-pick https://github.com/mislav/ronn/commit/a319d88`\n    Then \"git fetch -q --no-tags mislav\" should be run\n    And \"git cherry-pick a319d88\" should be run\n\n  Scenario: Using GitHub owner@SHA notation\n    Given the \"mislav\" remote has url \"git@github.com:mislav/ronn.git\"\n    When I run `hub cherry-pick mislav@a319d88`\n    Then \"git fetch -q --no-tags mislav\" should be run\n    And \"git cherry-pick a319d88\" should be run\n\n  Scenario: Using GitHub owner@SHA notation that is too short\n    When I run `hub cherry-pick mislav@a319`\n    Then the git command should be unchanged\n\n  Scenario: Unsupported GitHub owner/repo@SHA notation\n    When I run `hub cherry-pick mislav/ronn@a319d88`\n    Then the git command should be unchanged\n\n  Scenario: Skips processing if `-m/--mainline` is specified\n    When I run `hub cherry-pick -m 42 mislav@a319d88`\n    Then the git command should be unchanged\n    When I run `hub cherry-pick --mainline 42 mislav@a319d88`\n    Then the git command should be unchanged\n\n  Scenario: Using GitHub owner@SHA notation with remote add\n    When I run `hub cherry-pick mislav@a319d88`\n    Then \"git remote add _hub-cherry-pick https://github.com/mislav/ronn.git\" should be run\n    And \"git fetch -q --no-tags _hub-cherry-pick\" should be run\n    And \"git remote rm _hub-cherry-pick\" should be run\n    And \"git cherry-pick a319d88\" should be run\n\n  Scenario: From fork that doesn't have a remote\n    When I run `hub cherry-pick https://github.com/jingweno/ronn/commit/a319d88`\n    Then \"git remote add _hub-cherry-pick https://github.com/jingweno/ronn.git\" should be run\n    And \"git fetch -q --no-tags _hub-cherry-pick\" should be run\n    And \"git remote rm _hub-cherry-pick\" should be run\n    And \"git cherry-pick a319d88\" should be run\n"
  },
  {
    "path": "features/ci_status.feature",
    "content": "Feature: hub ci-status\n\n  Background:\n    Given I am in \"git://github.com/michiels/pencilbox.git\" git repo\n    And I am \"michiels\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Fetch commit SHA\n    Given there is a commit named \"the_sha\"\n    Given the remote commit state of \"michiels/pencilbox\" \"the_sha\" is \"success\"\n    When I run `hub ci-status the_sha`\n    Then the output should contain exactly \"success\\n\"\n    And the exit status should be 0\n\n  Scenario: Fetch commit SHA with URL\n    Given there is a commit named \"the_sha\"\n    Given the remote commit state of \"michiels/pencilbox\" \"the_sha\" is \"success\"\n    When I run `hub ci-status the_sha -v`\n    Then the output should contain exactly:\n      \"\"\"\n      ✔︎\tcontinuous-integration/travis-ci/push\thttps://travis-ci.org/michiels/pencilbox/builds/1234567\\n\n      \"\"\"\n    And the exit status should be 0\n\n  Scenario: Multiple statuses with verbose output\n    Given there is a commit named \"the_sha\"\n    Given the remote commit states of \"michiels/pencilbox\" \"the_sha\" are:\n      \"\"\"\n      { :state => \"error\",\n        :statuses => [\n          { :state => \"success\",\n            :context => \"continuous-integration/travis-ci/push\",\n            :target_url => \"https://travis-ci.org/michiels/pencilbox/builds/1234567\" },\n          { :state => \"success\",\n            :context => \"continuous-integration/travis-ci/ants\",\n            :target_url => \"https://travis-ci.org/michiels/pencilbox/builds/1234568\" },\n          { :state => \"pending\",\n            :context => \"continuous-integration/travis-ci/merge\",\n            :target_url => nil },\n          { :state => \"error\",\n            :context => \"whatevs!\" },\n          { :state => \"failure\",\n            :context => \"GitHub CLA\",\n            :target_url => \"https://cla.github.com/michiels/pencilbox/accept/mislav\" },\n        ]\n      }\n      \"\"\"\n    When I run `hub ci-status -v the_sha`\n    Then the output should contain exactly:\n      \"\"\"\n      ✖︎\tGitHub CLA                            \thttps://cla.github.com/michiels/pencilbox/accept/mislav\n      ✖︎\twhatevs!\n      ●\tcontinuous-integration/travis-ci/merge\n      ✔︎\tcontinuous-integration/travis-ci/ants \thttps://travis-ci.org/michiels/pencilbox/builds/1234568\n      ✔︎\tcontinuous-integration/travis-ci/push \thttps://travis-ci.org/michiels/pencilbox/builds/1234567\\n\n      \"\"\"\n    And the exit status should be 1\n\n  Scenario: Multiple statuses with format string\n    Given there is a commit named \"the_sha\"\n    Given the remote commit states of \"michiels/pencilbox\" \"the_sha\" are:\n      \"\"\"\n      { :state => \"error\",\n        :statuses => [\n          { :state => \"success\",\n            :context => \"continuous-integration/travis-ci/push\",\n            :target_url => \"https://travis-ci.org/michiels/pencilbox/builds/1234567\" },\n          { :state => \"success\",\n            :context => \"continuous-integration/travis-ci/ants\",\n            :target_url => \"https://travis-ci.org/michiels/pencilbox/builds/1234568\" },\n          { :state => \"pending\",\n            :context => \"continuous-integration/travis-ci/merge\",\n            :target_url => nil },\n          { :state => \"error\",\n            :context => \"whatevs!\" },\n          { :state => \"failure\",\n            :context => \"GitHub CLA\",\n            :target_url => \"https://cla.github.com/michiels/pencilbox/accept/mislav\" },\n        ]\n      }\n      \"\"\"\n    When I run `hub ci-status the_sha --format '%S: %t (%U)%n'`\n    Then the output should contain exactly:\n      \"\"\"\n      failure: GitHub CLA (https://cla.github.com/michiels/pencilbox/accept/mislav)\n      error: whatevs! ()\n      pending: continuous-integration/travis-ci/merge ()\n      success: continuous-integration/travis-ci/ants (https://travis-ci.org/michiels/pencilbox/builds/1234568)\n      success: continuous-integration/travis-ci/push (https://travis-ci.org/michiels/pencilbox/builds/1234567)\\n\n      \"\"\"\n    And the exit status should be 1\n\n  Scenario: Exit status 1 for 'error' and 'failure'\n    Given the remote commit state of \"michiels/pencilbox\" \"HEAD\" is \"error\"\n    When I run `hub ci-status`\n    Then the exit status should be 1\n    And the output should contain exactly \"error\\n\"\n\n  Scenario: Use HEAD when no sha given\n    Given the remote commit state of \"michiels/pencilbox\" \"HEAD\" is \"pending\"\n    When I run `hub ci-status`\n    Then the exit status should be 2\n    And the output should contain exactly \"pending\\n\"\n\n  Scenario: Exit status 3 for no statuses available\n    Given there is a commit named \"the_sha\"\n    Given the remote commit state of \"michiels/pencilbox\" \"the_sha\" is nil\n    When I run `hub ci-status the_sha`\n    Then the output should contain exactly \"no status\\n\"\n    And the exit status should be 3\n\n  Scenario: Exit status 3 for no statuses available without URL\n    Given there is a commit named \"the_sha\"\n    Given the remote commit state of \"michiels/pencilbox\" \"the_sha\" is nil\n    When I run `hub ci-status -v the_sha`\n    Then the output should contain exactly \"no status\\n\"\n    And the exit status should be 3\n\n  Scenario: Abort with message when invalid ref given\n    When I run `hub ci-status this-is-an-invalid-ref`\n    Then the exit status should be 1\n    And the output should contain exactly \"Aborted: no revision could be determined from 'this-is-an-invalid-ref'\\n\"\n\n  Scenario: Non-GitHub repo\n    Given the \"origin\" remote has url \"mygh:Manganeez/repo.git\"\n    When I run `hub ci-status`\n    Then the stderr should contain exactly:\n      \"\"\"\n      Aborted: could not find any git remote pointing to a GitHub repository\\n\n      \"\"\"\n    And the exit status should be 1\n\n  Scenario: Enterprise CI statuses\n    Given the \"origin\" remote has url \"git@git.my.org:michiels/pencilbox.git\"\n    And I am \"michiels\" on git.my.org with OAuth token \"FITOKEN\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    Given there is a commit named \"the_sha\"\n    Given the remote commit state of \"git.my.org/michiels/pencilbox\" \"the_sha\" is \"success\"\n    When I successfully run `hub ci-status the_sha`\n    Then the output should contain exactly \"success\\n\"\n\n  Scenario: If alias named ci-status exists, it should not be expanded.\n    Given there is a commit named \"the_sha\"\n    Given the remote commit state of \"michiels/pencilbox\" \"the_sha\" is \"success\"\n    When I successfully run `git config --global alias.ci-status \"ci-status -v\"`\n    And I successfully run `hub ci-status the_sha`\n    Then the output should contain exactly \"success\\n\"\n\n  Scenario: Has Checks\n    Given there is a commit named \"the_sha\"\n    And the GitHub API server:\n      \"\"\"\n      get('/repos/michiels/pencilbox/commits/:sha/status') {\n        json({ :state => \"success\",\n               :statuses => [\n                 { :state => \"success\",\n                   :context => \"travis-ci\",\n                   :target_url => \"the://url\"}\n               ]\n        })\n      }\n      get('/repos/michiels/pencilbox/commits/:sha/check-runs') {\n        json({ :check_runs => [\n                 { :status => \"completed\",\n                   :conclusion => \"action_required\",\n                   :name => \"check 1\",\n                   :html_url => \"the://url\" },\n                 { :status => \"queued\",\n                   :conclusion => \"\",\n                   :name => \"check 2\",\n                   :html_url => \"the://url\" },\n               ]\n        })\n      }\n      \"\"\"\n    When I run `hub ci-status the_sha`\n    Then the output should contain exactly \"action_required\\n\"\n    And the exit status should be 1\n\n  Scenario: Older Enterprise version doesn't have Checks\n    Given the \"origin\" remote has url \"git@git.my.org:michiels/pencilbox.git\"\n    And I am \"michiels\" on git.my.org with OAuth token \"FITOKEN\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    And there is a commit named \"the_sha\"\n    And the GitHub API server:\n      \"\"\"\n      get('/api/v3/repos/michiels/pencilbox/commits/:sha/status', :host_name => 'git.my.org') {\n        json({ :state => \"success\",\n               :statuses => [\n                 { :state => \"success\",\n                   :context => \"travis-ci\",\n                   :target_url => \"the://url\"}\n               ]\n        })\n      }\n      get('/api/v3/repos/michiels/pencilbox/commits/:sha/check-runs', :host_name => 'git.my.org') {\n        status 403\n        json :message => \"Must have admin rights to Repository.\",\n          :documentation_url => \"https://developer.github.com/enterprise/2.13/v3/\"\n      }\n      \"\"\"\n    When I successfully run `hub ci-status the_sha`\n    Then the output should contain exactly \"success\\n\"\n"
  },
  {
    "path": "features/clone.feature",
    "content": "Feature: hub clone\n  Background:\n    Given I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Clone a public repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/rtomayko/ronn') {\n        json :private => false,\n             :name => 'ronn', :owner => { :login => 'rtomayko' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub clone rtomayko/ronn`\n    Then it should clone \"https://github.com/rtomayko/ronn.git\"\n    And the output should not contain anything\n\n  Scenario: Clone a public repo with period in name\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/hookio/hook.js') {\n        json :private => false,\n             :name => 'hook.js', :owner => { :login => 'hookio' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub clone hookio/hook.js`\n    Then it should clone \"https://github.com/hookio/hook.js.git\"\n    And the output should not contain anything\n\n  Scenario: Clone a public repo that starts with a period\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/zhuangya/.vim') {\n        json :private => false,\n             :name => '.vim', :owner => { :login => 'zhuangya' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub clone zhuangya/.vim`\n    Then it should clone \"https://github.com/zhuangya/.vim.git\"\n    And the output should not contain anything\n\n  Scenario: Clone a repo even if same-named directory exists\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/rtomayko/ronn') {\n        json :private => false,\n             :name => 'ronn', :owner => { :login => 'rtomayko' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    And a directory named \"rtomayko/ronn\"\n    When I successfully run `hub clone rtomayko/ronn`\n    Then it should clone \"https://github.com/rtomayko/ronn.git\"\n    And the output should not contain anything\n\n  Scenario: Clone a public repo with git\n    Given git protocol is preferred\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/rtomayko/ronn') {\n        json :private => false,\n             :name => 'ronn', :owner => { :login => 'rtomayko' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub clone rtomayko/ronn`\n    Then it should clone \"git://github.com/rtomayko/ronn.git\"\n    And the output should not contain anything\n\n  Scenario: Clone a public repo with HTTPS\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/rtomayko/ronn') {\n        json :private => false,\n             :name => 'ronn', :owner => { :login => 'rtomayko' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub clone rtomayko/ronn`\n    Then it should clone \"https://github.com/rtomayko/ronn.git\"\n    And the output should not contain anything\n\n  Scenario: Clone command aliased\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/rtomayko/ronn') {\n        json :private => false,\n             :name => 'ronn', :owner => { :login => 'rtomayko' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `git config --global alias.c \"clone --bare\"`\n    And I successfully run `hub c rtomayko/ronn`\n    Then \"git clone --bare https://github.com/rtomayko/ronn.git\" should be run\n    And the output should not contain anything\n\n  Scenario: Unchanged public clone\n    When I successfully run `hub clone git://github.com/rtomayko/ronn.git`\n    Then the git command should be unchanged\n\n  Scenario: Unchanged public clone with path\n    When I successfully run `hub clone git://github.com/rtomayko/ronn.git ronnie`\n    Then the git command should be unchanged\n    And the output should not contain anything\n\n  Scenario: Unchanged private clone\n    When I successfully run `hub clone git@github.com:rtomayko/ronn.git`\n    Then the git command should be unchanged\n    And the output should not contain anything\n\n  Scenario: Unchanged clone with complex arguments\n    When I successfully run `hub clone --template=one/two git://github.com/defunkt/resque.git --origin master resquetastic`\n    Then the git command should be unchanged\n    And the output should not contain anything\n\n  Scenario: Unchanged local clone\n    When I successfully run `hub clone ./dotfiles`\n    Then the git command should be unchanged\n    And the output should not contain anything\n\n  Scenario: Unchanged local clone with destination\n    Given a directory named \".git\"\n    When I successfully run `hub clone -l . ../copy`\n    Then the git command should be unchanged\n    And the output should not contain anything\n\n  Scenario: Unchanged local clone from bare repo\n    Given a bare git repo in \"rtomayko/ronn\"\n    When I successfully run `hub clone rtomayko/ronn`\n    Then the git command should be unchanged\n    And the output should not contain anything\n\n  Scenario: Unchanged clone with host alias\n    When I successfully run `hub clone shortcut:git/repo.git`\n    Then the git command should be unchanged\n    And the output should not contain anything\n\n  Scenario: Preview cloning a private repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/rtomayko/ronn') {\n        json :private => false,\n             :name => 'ronn', :owner => { :login => 'rtomayko' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub --noop clone rtomayko/ronn`\n    Then the output should contain exactly \"git clone https://github.com/rtomayko/ronn.git\\n\"\n    But it should not clone anything\n\n  Scenario: Clone a private repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/rtomayko/ronn') {\n        json :private => false,\n             :name => 'ronn', :owner => { :login => 'rtomayko' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub clone -p rtomayko/ronn`\n    Then it should clone \"https://github.com/rtomayko/ronn.git\"\n    And the output should not contain anything\n\n  Scenario: Clone my repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => false,\n             :name => 'dotfiles', :owner => { :login => 'mislav' },\n             :permissions => { :push => true }\n      }\n      \"\"\"\n    When I successfully run `hub clone dotfiles`\n    Then it should clone \"https://github.com/mislav/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: Clone my repo that doesn't exist\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') { status 404 }\n      \"\"\"\n    When I run `hub clone dotfiles`\n    Then the exit status should be 1\n    And the stdout should contain exactly \"\"\n    And the stderr should contain exactly \"Error: repository mislav/dotfiles doesn't exist\\n\"\n    And it should not clone anything\n\n  Scenario: Clone my repo with arguments\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => false,\n             :name => 'dotfiles', :owner => { :login => 'mislav' },\n             :permissions => { :push => true }\n      }\n      \"\"\"\n    When I successfully run `hub clone --bare -o master dotfiles`\n    Then \"git clone --bare -o master https://github.com/mislav/dotfiles.git\" should be run\n    And the output should not contain anything\n\n  Scenario: Clone repo to which I have push access to\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/sstephenson/rbenv') {\n        json :private => false,\n             :name => 'rbenv', :owner => { :login => 'sstephenson' },\n             :permissions => { :push => true }\n      }\n      \"\"\"\n    And git protocol is preferred\n    When I successfully run `hub clone sstephenson/rbenv`\n    Then \"git clone git@github.com:sstephenson/rbenv.git\" should be run\n    And the output should not contain anything\n\n  Scenario: Preview cloning a repo I have push access to\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/sstephenson/rbenv') {\n        json :private => false,\n             :name => 'rbenv', :owner => { :login => 'sstephenson' },\n             :permissions => { :push => true }\n      }\n      \"\"\"\n    And git protocol is preferred\n    When I successfully run `hub --noop clone sstephenson/rbenv`\n    Then the output should contain exactly \"git clone git@github.com:sstephenson/rbenv.git\\n\"\n    But it should not clone anything\n\n  Scenario: Clone my Enterprise repo\n    Given I am \"mifi\" on git.my.org with OAuth token \"FITOKEN\"\n    And $GITHUB_HOST is \"git.my.org\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/api/v3/repos/myorg/myrepo') {\n        json :private => true,\n             :name => 'myrepo', :owner => { :login => 'myorg' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub clone myorg/myrepo`\n    Then it should clone \"https://git.my.org/myorg/myrepo.git\"\n    And the output should not contain anything\n\n  Scenario: Clone from existing directory is a local clone\n    Given a directory named \"dotfiles/.git\"\n    When I successfully run `hub clone dotfiles`\n    Then the git command should be unchanged\n    And the output should not contain anything\n\n  Scenario: Clone from git bundle is a local clone\n    Given a git bundle named \"my-bundle\"\n    When I successfully run `hub clone my-bundle`\n    Then the git command should be unchanged\n    And the output should not contain anything\n\n  Scenario: Clone a wiki\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/rtomayko/ronn') {\n        json :private => false,\n             :name => 'ronin', :owner => { :login => 'RTomayko' },\n             :permissions => { :push => false },\n             :has_wiki => true\n      }\n      \"\"\"\n    When I successfully run `hub clone rtomayko/ronn.wiki`\n    Then it should clone \"https://github.com/RTomayko/ronin.wiki.git\"\n    And the output should not contain anything\n\n  Scenario: Clone a nonexisting wiki\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/rtomayko/ronn') {\n        json :private => false,\n             :name => 'ronin', :owner => { :login => 'RTomayko' },\n             :permissions => { :push => false },\n             :has_wiki => false\n      }\n      \"\"\"\n    When I run `hub clone rtomayko/ronn.wiki`\n    Then the exit status should be 1\n    And the stdout should contain exactly \"\"\n    And the stderr should contain exactly \"Error: RTomayko/ronin doesn't have a wiki\\n\"\n    And it should not clone anything\n\n  Scenario: Clone a redirected repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/rtomayko/ronn') {\n        redirect 'https://api.github.com/repositories/12345', 301\n      }\n      get('/repositories/12345', :host_name => 'api.github.com') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'\n        json :private => false,\n             :name => 'ronin', :owner => { :login => 'RTomayko' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub clone rtomayko/ronn`\n    Then it should clone \"https://github.com/RTomayko/ronin.git\"\n    And the output should not contain anything\n"
  },
  {
    "path": "features/compare.feature",
    "content": "Feature: hub compare\n  Background:\n    Given I am in \"git://github.com/mislav/dotfiles.git\" git repo\n    And I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Compare branch\n    When I successfully run `hub compare refactor`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles/compare/refactor\" should be run\n\n  Scenario: Compare complex branch\n    When I successfully run `hub compare feature/foo`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles/compare/feature/foo\" should be run\n\n  Scenario: Compare branch with funky characters\n    When I successfully run `hub compare 'my#branch!with.special+chars'`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles/compare/my%23branch!with.special%2Bchars\" should be run\n\n  Scenario: No args, no upstream\n    When I run `hub compare`\n    Then the exit status should be 1\n    And the stderr should contain exactly \"the current branch 'master' doesn't seem pushed to a remote\\n\"\n\n  Scenario: Can't compare default branch to self\n    Given the default branch for \"origin\" is \"develop\"\n    And I am on the \"develop\" branch with upstream \"origin/develop\"\n    When I run `hub compare`\n    Then the exit status should be 1\n    And the stderr should contain exactly \"the branch to compare 'develop' is the default branch\\n\"\n\n  Scenario: No args, has upstream branch\n    Given I am on the \"feature\" branch with upstream \"origin/experimental\"\n    And git \"push.default\" is set to \"upstream\"\n    When I successfully run `hub compare`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles/compare/experimental\" should be run\n\n  Scenario: Current branch has funky characters\n    Given I am on the \"feature\" branch with upstream \"origin/my#branch!with.special+chars\"\n    And git \"push.default\" is set to \"upstream\"\n    When I successfully run `hub compare`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles/compare/my%23branch!with.special%2Bchars\" should be run\n\n  Scenario: Current branch pushed to fork\n    Given I am \"monalisa\" on github.com with OAuth token \"MONATOKEN\"\n    And the \"monalisa\" remote has url \"git@github.com:monalisa/dotfiles.git\"\n    And I am on the \"topic\" branch pushed to \"monalisa/topic\"\n    When I successfully run `hub compare`\n    Then \"open https://github.com/mislav/dotfiles/compare/monalisa:topic\" should be run\n\n  Scenario: Current branch with full URL in upstream configuration\n    Given I am on the \"local-topic\" branch\n    When I successfully run `git config branch.local-topic.remote https://github.com/monalisa/dotfiles.git`\n    When I successfully run `git config branch.local-topic.merge refs/remotes/remote-topic`\n    When I successfully run `hub compare`\n    Then \"open https://github.com/mislav/dotfiles/compare/monalisa:remote-topic\" should be run\n\n  Scenario: Compare range\n    When I successfully run `hub compare 1.0...fix`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles/compare/1.0...fix\" should be run\n\n  Scenario: Output URL without opening the browser\n    When I successfully run `hub compare -u 1.0...fix`\n    Then \"open https://github.com/mislav/dotfiles/compare/1.0...fix\" should not be run\n    And the stdout should contain exactly:\n      \"\"\"\n      https://github.com/mislav/dotfiles/compare/1.0...fix\\n\n      \"\"\"\n\n  Scenario: Compare base in branch that is not master\n    Given I am on the \"feature\" branch with upstream \"origin/experimental\"\n    And git \"push.default\" is set to \"upstream\"\n    When I successfully run `hub compare -b master`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles/compare/master...experimental\" should be run\n\n  Scenario: Compare base in master branch\n    Given I am on the \"master\" branch with upstream \"origin/master\"\n    And git \"push.default\" is set to \"upstream\"\n    When I successfully run `hub compare -b experimental`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles/compare/experimental...master\" should be run\n\n  Scenario: Compare base with same branch as the current branch\n    Given I am on the \"feature\" branch with upstream \"origin/experimental\"\n    And git \"push.default\" is set to \"upstream\"\n    When I run `hub compare -b experimental`\n    Then \"open https://github.com/mislav/dotfiles/compare/experimental...experimental\" should not be run\n    And the exit status should be 1\n    And the stderr should contain exactly \"the branch to compare 'experimental' is the same as --base\\n\"\n\n  Scenario: Compare base with parameters\n    Given I am on the \"master\" branch with upstream \"origin/master\"\n    When I run `hub compare -b master experimental..master`\n    Then \"open https://github.com/mislav/dotfiles/compare/experimental...master\" should not be run\n    And the exit status should be 1\n    And the stderr should contain \"Usage: hub compare\"\n\n  Scenario: Compare 2-dots range for tags\n    When I successfully run `hub compare 1.0..fix`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles/compare/1.0...fix\" should be run\n\n  Scenario: Compare 2-dots range for SHAs\n    When I successfully run `hub compare 1234abc..3456cde`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles/compare/1234abc...3456cde\" should be run\n\n  Scenario: Compare 2-dots range with \"user:repo\" notation\n    When I successfully run `hub compare henrahmagix:master..2b10927`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles/compare/henrahmagix:master...2b10927\" should be run\n\n  Scenario: Compare 2-dots range with slashes in branch names\n    When I successfully run `hub compare one/foo..two/bar/baz`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles/compare/one/foo...two/bar/baz\" should be run\n\n  Scenario: Complex range is unchanged\n    When I successfully run `hub compare @{a..b}..@{c..d}`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles/compare/@{a..b}..@{c..d}\" should be run\n\n  Scenario: Compare wiki\n    Given the \"origin\" remote has url \"git://github.com/mislav/dotfiles.wiki.git\"\n    When I successfully run `hub compare 1.0..fix`\n    Then the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles/wiki/_compare/1.0...fix\" should be run\n\n  Scenario: Compare fork\n    When I successfully run `hub compare anotheruser feature`\n    Then the output should not contain anything\n    And \"open https://github.com/anotheruser/dotfiles/compare/feature\" should be run\n\n  Scenario: Enterprise repo over HTTP\n    Given the \"origin\" remote has url \"git://git.my.org/mislav/dotfiles.git\"\n    And I am \"mislav\" on http://git.my.org with OAuth token \"FITOKEN\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    When I successfully run `hub compare refactor`\n    Then the output should not contain anything\n    And \"open http://git.my.org/mislav/dotfiles/compare/refactor\" should be run\n\n  Scenario: Enterprise repo with explicit upstream project\n    Given the \"origin\" remote has url \"git://git.my.org/mislav/dotfiles.git\"\n    And I am \"mislav\" on git.my.org with OAuth token \"FITOKEN\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    When I successfully run `hub compare fehmicansaglam a..b`\n    Then the output should not contain anything\n    And \"open https://git.my.org/fehmicansaglam/dotfiles/compare/a...b\" should be run\n\n  Scenario: Compare in non-GitHub repo\n    Given the \"origin\" remote has url \"git@bitbucket.org:mislav/dotfiles.git\"\n    And I am on the \"feature\" branch\n    When I run `hub compare`\n    Then the stdout should contain exactly \"\"\n    And the stderr should contain exactly:\n      \"\"\"\n      Aborted: could not find any git remote pointing to a GitHub repository\\n\n      \"\"\"\n    And the exit status should be 1\n\n  Scenario: Comparing two branches while not on a local branch\n    Given I am in detached HEAD\n    And I run `hub compare refactor...master`\n    Then the exit status should be 0\n    And the output should not contain anything\n    And \"open https://github.com/mislav/dotfiles/compare/refactor...master\" should be run\n"
  },
  {
    "path": "features/create.feature",
    "content": "Feature: hub create\n  Background:\n    Given I am in \"dotfiles\" git repo\n    And I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Create repo\n    Given the GitHub API server:\n      \"\"\"\n      post('/user/repos') {\n        assert :private => false\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    When I successfully run `hub create`\n    Then the url for \"origin\" should be \"https://github.com/mislav/dotfiles.git\"\n    And the output should contain exactly \"https://github.com/mislav/dotfiles\\n\"\n\n  Scenario: Create private repo\n    Given the GitHub API server:\n      \"\"\"\n      post('/user/repos') {\n        assert :private => true\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    And git protocol is preferred\n    When I successfully run `hub create -p`\n    Then the url for \"origin\" should be \"git@github.com:mislav/dotfiles.git\"\n\n  Scenario: Alternate origin remote name\n    Given the GitHub API server:\n      \"\"\"\n      post('/user/repos') {\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    When I successfully run `hub create --remote-name=work`\n    Then the url for \"work\" should be \"https://github.com/mislav/dotfiles.git\"\n    And there should be no \"origin\" remote\n\n  Scenario: Create in organization\n    Given the GitHub API server:\n      \"\"\"\n      post('/orgs/acme/repos') {\n        status 201\n        json :full_name => 'acme/dotfiles'\n      }\n      \"\"\"\n    When I successfully run `hub create acme/dotfiles`\n    Then the url for \"origin\" should be \"https://github.com/acme/dotfiles.git\"\n    And the output should contain exactly \"https://github.com/acme/dotfiles\\n\"\n\n  Scenario: Creating repo failed\n    Given the GitHub API server:\n      \"\"\"\n      post('/user/repos') { status 500 }\n      \"\"\"\n    When I run `hub create`\n    Then the stderr should contain \"Error creating repository: Internal Server Error (HTTP 500)\"\n    And the exit status should be 1\n    And there should be no \"origin\" remote\n\n  Scenario: With custom name\n    Given the GitHub API server:\n      \"\"\"\n      post('/user/repos') {\n        assert :name => 'myconfig'\n        status 201\n        json :full_name => 'mislav/myconfig'\n      }\n      \"\"\"\n    When I successfully run `hub create myconfig`\n    Then the url for \"origin\" should be \"https://github.com/mislav/myconfig.git\"\n\n  Scenario: With description and homepage\n    Given the GitHub API server:\n      \"\"\"\n      post('/user/repos') {\n        assert :description => 'mydesc',\n               :homepage => 'http://example.com'\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    When I successfully run `hub create -d mydesc -h http://example.com`\n    Then the url for \"origin\" should be \"https://github.com/mislav/dotfiles.git\"\n\n  Scenario: Not in git repo\n    Given the current dir is not a repo\n    When I run `hub create`\n    Then the stderr should contain \"'create' must be run from inside a git repository\"\n    And the exit status should be 1\n\n  Scenario: Cannot create from bare repo\n    Given the current dir is not a repo\n    And I run `git -c init.defaultBranch=main init --bare`\n    When I run `hub create`\n    Then the stderr should contain exactly \"unable to determine git working directory\\n\"\n    And the exit status should be 1\n\n  Scenario: Origin remote already exists\n    Given the GitHub API server:\n      \"\"\"\n      post('/user/repos') {\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    And the \"origin\" remote has url \"git://github.com/mislav/dotfiles.git\"\n    When I successfully run `hub create`\n    Then the url for \"origin\" should be \"git://github.com/mislav/dotfiles.git\"\n    And the output should contain exactly \"https://github.com/mislav/dotfiles\\n\"\n\n  Scenario: Unrelated origin remote already exists\n    Given the GitHub API server:\n      \"\"\"\n      post('/user/repos') {\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    And the \"origin\" remote has url \"git://example.com/unrelated.git\"\n    When I successfully run `hub create`\n    Then the url for \"origin\" should be \"git://example.com/unrelated.git\"\n    And the stdout should contain exactly \"https://github.com/mislav/dotfiles\\n\"\n    And the stderr should contain exactly:\n      \"\"\"\n      A git remote named 'origin' already exists and is set to push to 'git://example.com/unrelated.git'.\\n\n      \"\"\"\n\n  Scenario: Another remote already exists\n    Given the GitHub API server:\n      \"\"\"\n      post('/user/repos') {\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    And the \"github\" remote has url \"git://github.com/mislav/dotfiles.git\"\n    When I successfully run `hub create`\n    Then the url for \"origin\" should be \"https://github.com/mislav/dotfiles.git\"\n\n  Scenario: GitHub repo already exists\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    When I successfully run `hub create`\n    Then the output should contain \"Existing repository detected\\n\"\n    And the url for \"origin\" should be \"https://github.com/mislav/dotfiles.git\"\n\n  Scenario: GitHub repo already exists and is not private\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :full_name => 'mislav/dotfiles',\n             :private => false\n      }\n      \"\"\"\n    When I run `hub create -p`\n    Then the output should contain \"Repository 'mislav/dotfiles' already exists and is public\\n\"\n    And the exit status should be 1\n    And there should be no \"origin\" remote\n\n  Scenario: GitHub repo already exists and is private\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :full_name => 'mislav/dotfiles',\n             :private => true\n      }\n      \"\"\"\n    And git protocol is preferred\n    When I successfully run `hub create -p`\n    Then the url for \"origin\" should be \"git@github.com:mislav/dotfiles.git\"\n\n  Scenario: Renamed GitHub repo already exists\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        redirect 'https://api.github.com/repositories/12345', 301\n      }\n      get('/repositories/12345') {\n        json :full_name => 'mislav/DOTfiles'\n      }\n      \"\"\"\n    When I successfully run `hub create`\n    And the url for \"origin\" should be \"https://github.com/mislav/DOTfiles.git\"\n\n  Scenario: Renamed GitHub repo is unrelated\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        redirect 'https://api.github.com/repositories/12345', 301\n      }\n      get('/repositories/12345') {\n        json :full_name => 'mislav/old-dotfiles'\n      }\n      post('/user/repos') {\n        status 201\n        json :full_name => 'mislav/mydotfiles'\n      }\n      \"\"\"\n    When I successfully run `hub create`\n    And the url for \"origin\" should be \"https://github.com/mislav/mydotfiles.git\"\n\n  Scenario: API response changes the clone URL\n    Given the GitHub API server:\n      \"\"\"\n      post('/user/repos') {\n        status 201\n        json :full_name => 'Mooslav/myconfig'\n      }\n      \"\"\"\n    When I successfully run `hub create`\n    Then the url for \"origin\" should be \"https://github.com/Mooslav/myconfig.git\"\n    And the output should contain exactly \"https://github.com/Mooslav/myconfig\\n\"\n\n  Scenario: Open new repository in web browser\n    Given the GitHub API server:\n      \"\"\"\n      post('/user/repos') {\n        status 201\n        json :full_name => 'Mooslav/myconfig'\n      }\n      \"\"\"\n    When I successfully run `hub create -o`\n    Then the output should contain exactly \"\"\n    And \"open https://github.com/Mooslav/myconfig\" should be run\n\n  Scenario: Current directory contains spaces\n    Given I am in \"my dot files\" git repo\n    Given the GitHub API server:\n      \"\"\"\n      post('/user/repos') {\n        assert :name => 'my-dot-files'\n        status 201\n        json :full_name => 'mislav/my-dot-files'\n      }\n      \"\"\"\n    When I successfully run `hub create`\n    Then the url for \"origin\" should be \"https://github.com/mislav/my-dot-files.git\"\n\n  Scenario: Verbose API output\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') { status 404 }\n      post('/user/repos') {\n        response['location'] = 'http://disney.com'\n        status 201\n        json :full_name => 'mislav/dotfiles'\n      }\n      \"\"\"\n    And $HUB_VERBOSE is \"on\"\n    When I successfully run `hub create`\n    Then the stderr should contain:\n      \"\"\"\n      > GET https://api.github.com/repos/mislav/dotfiles\n      > Authorization: token [REDACTED]\n      > Accept: application/vnd.github.v3+json;charset=utf-8\n      < HTTP 404\n      \"\"\"\n    And the stderr should contain:\n      \"\"\"\n      > POST https://api.github.com/user/repos\n      > Authorization: token [REDACTED]\n      \"\"\"\n    And the stderr should contain:\n      \"\"\"\n      < HTTP 201\n      < Location: http://disney.com\n      {\"full_name\":\"mislav/dotfiles\"}\\n\n      \"\"\"\n\n  Scenario: Create Enterprise repo\n    Given I am \"nsartor\" on git.my.org with OAuth token \"FITOKEN\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/api/v3/user/repos', :host_name => 'git.my.org') {\n        assert :private => false\n        status 201\n        json :full_name => 'nsartor/dotfiles'\n      }\n      \"\"\"\n    And $GITHUB_HOST is \"git.my.org\"\n    When I successfully run `hub create`\n    Then the url for \"origin\" should be \"https://git.my.org/nsartor/dotfiles.git\"\n    And the output should contain exactly \"https://git.my.org/nsartor/dotfiles\\n\"\n\n  Scenario: Invalid GITHUB_HOST\n    Given I am \"nsartor\" on {} with OAuth token \"FITOKEN\"\n    And $GITHUB_HOST is \"{}\"\n    When I run `hub create`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      invalid hostname: \"{}\"\\n\n      \"\"\"\n"
  },
  {
    "path": "features/delete.feature",
    "content": "Feature: hub delete\n  Background:\n    Given I am \"andreasbaumann\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: No argument in current repo\n    Given I am in \"git://github.com/github/hub.git\" git repo\n    When I run `hub delete`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Usage: hub delete [-y] [<ORGANIZATION>/]<NAME>\\n\n      \"\"\"\n\n  Scenario: Successful confirmation\n    Given the GitHub API server:\n      \"\"\"\n      delete('/repos/andreasbaumann/my-repo') {\n        status 204\n      }\n      \"\"\"\n    When I run `hub delete my-repo` interactively\n    And I type \"yes\"\n    Then the exit status should be 0\n    And the output should contain:\n      \"\"\"\n      Really delete repository 'andreasbaumann/my-repo' (yes/N)?\n      \"\"\"\n    And the output should contain:\n      \"\"\"\n      Deleted repository 'andreasbaumann/my-repo'.\n      \"\"\"\n\n  Scenario: Org repo\n    Given the GitHub API server:\n      \"\"\"\n      delete('/repos/our-org/my-repo') {\n        status 204\n      }\n      \"\"\"\n    When I run `hub delete our-org/my-repo` interactively\n    And I type \"yes\"\n    Then the exit status should be 0\n    And the output should contain:\n      \"\"\"\n      Really delete repository 'our-org/my-repo' (yes/N)?\n      \"\"\"\n    And the output should contain:\n      \"\"\"\n      Deleted repository 'our-org/my-repo'.\n      \"\"\"\n\n  Scenario: Invalid confirmation\n    When I run `hub delete my-repo` interactively\n    And I type \"y\"\n    Then the exit status should be 1\n    And the output should contain:\n      \"\"\"\n      Really delete repository 'andreasbaumann/my-repo' (yes/N)?\n      \"\"\"\n    And the stderr should contain exactly:\n      \"\"\"\n      Please type 'yes' for confirmation.\\n\n      \"\"\"\n\n  Scenario: HTTP 403\n    Given the GitHub API server:\n      \"\"\"\n      delete('/repos/andreasbaumann/my-repo') {\n        status 403\n      }\n      \"\"\"\n    When I run `hub delete -y my-repo`\n    Then the exit status should be 1\n    And the stderr should contain:\n      \"\"\"\n      Please edit the token used for hub at https://github.com/settings/tokens\n      and verify that the `delete_repo` scope is enabled.\n      \"\"\"\n\n  Scenario: HTTP 403 on GitHub Enterprise\n    Given I am \"mislav\" on git.my.org with OAuth token \"FITOKEN\"\n    And $GITHUB_HOST is \"git.my.org\"\n    Given the GitHub API server:\n      \"\"\"\n      delete('/api/v3/repos/mislav/my-repo', :host_name => 'git.my.org') {\n        status 403\n      }\n      \"\"\"\n    When I run `hub delete -y my-repo`\n    Then the exit status should be 1\n    And the stderr should contain:\n      \"\"\"\n      Please edit the token used for hub at https://git.my.org/settings/tokens\n      and verify that the `delete_repo` scope is enabled.\n      \"\"\"\n"
  },
  {
    "path": "features/fetch.feature",
    "content": "Feature: hub fetch\n  Background:\n    Given I am in \"dotfiles\" git repo\n    And the \"origin\" remote has url \"git://github.com/evilchelu/dotfiles.git\"\n    And I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Fetch existing remote\n    When I successfully run `hub fetch origin`\n    Then the git command should be unchanged\n    And the output should not contain anything\n\n  Scenario: Fetch existing remote from non-GitHub source\n    Given the \"origin\" remote has url \"ssh://dev@codeserver.dev.xxx.drush.in/~/repository.git\"\n    When I successfully run `hub fetch origin`\n    Then the git command should be unchanged\n    And the output should not contain anything\n\n  Scenario: Fetch from non-GitHub source via refspec\n    Given the \"origin\" remote has url \"ssh://dev@codeserver.dev.xxx.drush.in/~/repository.git\"\n    When I successfully run `hub fetch ssh://myusername@a.specific.server:1234/existing-project/gerrit-project-name refs/changes/16/6116/1`\n    Then the git command should be unchanged\n    And the output should not contain anything\n\n  Scenario: Fetch from local bundle\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => false,\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    And a git bundle named \"mislav\"\n    When I successfully run `hub fetch mislav`\n    Then the git command should be unchanged\n    And the output should not contain anything\n    And there should be no \"mislav\" remote\n\n  Scenario: Creates new remote\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => false,\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub fetch mislav`\n    Then \"git fetch mislav\" should be run\n    And the url for \"mislav\" should be \"https://github.com/mislav/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: Creates new remote with git\n    Given git protocol is preferred\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => false,\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub fetch mislav`\n    Then \"git fetch mislav\" should be run\n    And the url for \"mislav\" should be \"git://github.com/mislav/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: Owner name with dash\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/ankit-maverick/dotfiles') {\n        json :private => false,\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub fetch ankit-maverick`\n    Then \"git fetch ankit-maverick\" should be run\n    And the url for \"ankit-maverick\" should be \"https://github.com/ankit-maverick/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: Private repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => true,\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    And git protocol is preferred\n    When I successfully run `hub fetch mislav`\n    Then \"git fetch mislav\" should be run\n    And the url for \"mislav\" should be \"git@github.com:mislav/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: Writeable repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => false,\n             :permissions => { :push => true }\n      }\n      \"\"\"\n    And git protocol is preferred\n    When I successfully run `hub fetch mislav`\n    Then \"git fetch mislav\" should be run\n    And the url for \"mislav\" should be \"git@github.com:mislav/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: Fetch with options\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => false,\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub fetch --depth=1 mislav`\n    Then \"git fetch --depth=1 mislav\" should be run\n\n  Scenario: Fetch multiple\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/:owner/dotfiles') {\n        json :private => false,\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub fetch --multiple mislav rtomayko`\n    Then \"git fetch --multiple mislav rtomayko\" should be run\n    And the url for \"mislav\" should be \"https://github.com/mislav/dotfiles.git\"\n    And the url for \"rtomayko\" should be \"https://github.com/rtomayko/dotfiles.git\"\n\n  Scenario: Fetch multiple with filtering\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => false,\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `git config remotes.mygrp \"foo bar\"`\n    When I successfully run `hub fetch --multiple origin mislav mygrp https://example.com typo`\n    Then \"git fetch --multiple origin mislav mygrp https://example.com typo\" should be run\n    And the url for \"mislav\" should be \"https://github.com/mislav/dotfiles.git\"\n    But there should be no \"mygrp\" remote\n    And there should be no \"typo\" remote\n\n  Scenario: Fetch multiple comma-separated\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/:owner/dotfiles') {\n        json :private => false,\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub fetch mislav,rtomayko,dustinleblanc`\n    Then \"git fetch --multiple mislav rtomayko dustinleblanc\" should be run\n    And the url for \"mislav\" should be \"https://github.com/mislav/dotfiles.git\"\n    And the url for \"rtomayko\" should be \"https://github.com/rtomayko/dotfiles.git\"\n    And the url for \"dustinleblanc\" should be \"https://github.com/dustinleblanc/dotfiles.git\"\n\n  Scenario: Doesn't create a new remote if repo doesn't exist on GitHub\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') { status 404 }\n      \"\"\"\n    When I successfully run `hub fetch mislav`\n    Then the git command should be unchanged\n    And there should be no \"mislav\" remote\n"
  },
  {
    "path": "features/fish_completion.feature",
    "content": "@completion\nFeature: fish tab-completion\n\n  Background:\n    Given my shell is fish\n\n  Scenario: \"pu\" matches multiple commands including \"pull-request\"\n    When I type \"git pu\" and press <Tab>\n    Then the command should not expand\n    When I press <Tab> again\n    Then the completion menu should offer \"pull push pull-request\" unsorted\n\n  Scenario: \"ci-\" expands to \"ci-status\"\n    When I type \"git ci-\" and press <Tab>\n    Then the command should expand to \"git ci-status\"\n\n  Scenario: Offers pull-request flags\n    When I type \"git pull-request -\" and press <Tab>\n    When I press <Tab> again\n    Then the completion menu should offer \"-F -b -f -h -m -a -M -l -o --browse -p --help\" unsorted\n\n  Scenario: Browse to issues\n    When I type \"git browse -- i\" and press <Tab>\n    Then the command should expand to \"git browse -- issues\"\n"
  },
  {
    "path": "features/fork.feature",
    "content": "Feature: hub fork\n  Background:\n    Given I am in \"dotfiles\" git repo\n    And the \"origin\" remote has url \"git://github.com/evilchelu/dotfiles.git\"\n    And I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Fork the repository\n    Given the GitHub API server:\n      \"\"\"\n      before {\n        halt 400 unless request.env['HTTP_X_ORIGINAL_SCHEME'] == 'https'\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'\n      }\n      get('/repos/mislav/dotfiles', :host_name => 'api.github.com') { 404 }\n      post('/repos/evilchelu/dotfiles/forks', :host_name => 'api.github.com') {\n        assert :organization => nil\n        status 202\n        json :name => 'dotfiles', :owner => { :login => 'mislav' }\n      }\n      \"\"\"\n    When I successfully run `hub fork`\n    Then the output should contain exactly \"new remote: mislav\\n\"\n    And \"git remote add -f mislav https://github.com/evilchelu/dotfiles.git\" should be run\n    And \"git remote set-url mislav https://github.com/mislav/dotfiles.git\" should be run\n    And the url for \"mislav\" should be \"https://github.com/mislav/dotfiles.git\"\n\n  Scenario: Fork the repository with new remote name specified\n    Given git protocol is preferred\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') { 404 }\n      post('/repos/evilchelu/dotfiles/forks') {\n        assert :organization => nil\n        status 202\n        json :name => 'dotfiles', :owner => { :login => 'mislav' }\n      }\n      \"\"\"\n    When I successfully run `hub fork --remote-name=origin`\n    Then the output should contain exactly:\n      \"\"\"\n      renaming existing \"origin\" remote to \"upstream\"\n      new remote: origin\\n\n      \"\"\"\n    And \"git remote add -f origin git://github.com/evilchelu/dotfiles.git\" should be run\n    And \"git remote set-url origin git@github.com:mislav/dotfiles.git\" should be run\n    And the url for \"origin\" should be \"git@github.com:mislav/dotfiles.git\"\n    And the url for \"upstream\" should be \"git://github.com/evilchelu/dotfiles.git\"\n\n  Scenario: Fork the repository with redirect\n    Given git protocol is preferred\n    Given the GitHub API server:\n      \"\"\"\n      before {\n        halt 400 unless request.env['HTTP_X_ORIGINAL_SCHEME'] == 'https'\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'\n      }\n      get('/repos/mislav/dotfiles', :host_name => 'api.github.com') { 404 }\n      post('/repos/evilchelu/dotfiles/forks', :host_name => 'api.github.com') {\n        redirect 'https://api.github.com/repositories/1234/forks', 307\n      }\n      post('/repositories/1234/forks', :host_name => 'api.github.com') {\n        status 202\n        json :name => 'my-dotfiles', :owner => { :login => 'MiSlAv' }\n      }\n      \"\"\"\n    When I successfully run `hub fork`\n    Then the output should contain exactly \"new remote: mislav\\n\"\n    And \"git remote add -f mislav git://github.com/evilchelu/dotfiles.git\" should be run\n    And \"git remote set-url mislav git@github.com:MiSlAv/my-dotfiles.git\" should be run\n    And the url for \"mislav\" should be \"git@github.com:MiSlAv/my-dotfiles.git\"\n\n  Scenario: Fork the repository when origin URL is private\n    Given the \"origin\" remote has url \"git@github.com:evilchelu/dotfiles.git\"\n    And git protocol is preferred\n    Given the GitHub API server:\n      \"\"\"\n      before { halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN' }\n      get('/repos/mislav/dotfiles', :host_name => 'api.github.com') { 404 }\n      post('/repos/evilchelu/dotfiles/forks', :host_name => 'api.github.com') {\n        status 202\n        json :name => 'dotfiles', :owner => { :login => 'mislav' }\n      }\n      \"\"\"\n    When I successfully run `hub fork`\n    Then the output should contain exactly \"new remote: mislav\\n\"\n    And \"git remote add -f mislav git://github.com/evilchelu/dotfiles.git\" should be run\n    And \"git remote set-url mislav git@github.com:mislav/dotfiles.git\" should be run\n    And the url for \"mislav\" should be \"git@github.com:mislav/dotfiles.git\"\n\n  Scenario: --no-remote\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/evilchelu/dotfiles/forks') {\n        status 202\n        json :name => 'dotfiles', :owner => { :login => 'mislav' }\n      }\n      \"\"\"\n    When I successfully run `hub fork --no-remote`\n    Then the output should not contain anything\n    And there should be no \"mislav\" remote\n\n  Scenario: Fork failed\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/evilchelu/dotfiles/forks') { halt 500 }\n      \"\"\"\n    When I run `hub fork`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Error creating fork: Internal Server Error (HTTP 500)\\n\n      \"\"\"\n    And there should be no \"mislav\" remote\n\n  Scenario: Unrelated fork already exists\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        halt 406 unless request.env['HTTP_ACCEPT'] == 'application/vnd.github.v3+json;charset=utf-8'\n        json :html_url => 'https://github.com/mislav/dotfiles',\n             :parent => { :html_url => 'https://github.com/unrelated/dotfiles' }\n      }\n      \"\"\"\n    When I run `hub fork`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Error creating fork: mislav/dotfiles already exists on github.com\\n\n      \"\"\"\n    And there should be no \"mislav\" remote\n\n  Scenario: Related fork already exists\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :html_url => 'https://github.com/mislav/dotfiles',\n             :parent => { :html_url => 'https://github.com/EvilChelu/Dotfiles' }\n      }\n      \"\"\"\n    When I run `hub fork`\n    Then the exit status should be 0\n    Then the stdout should contain exactly:\n      \"\"\"\n      new remote: mislav\\n\n      \"\"\"\n    And the url for \"mislav\" should be \"https://github.com/mislav/dotfiles.git\"\n\n  Scenario: Redirected repo already exists\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        redirect 'https://api.github.com/repositories/12345', 301\n      }\n      get('/repositories/12345') {\n        json :html_url => 'https://github.com/mislav/old-dotfiles'\n      }\n      post('/repos/evilchelu/dotfiles/forks') {\n        status 202\n        json :name => 'dotfiles', :owner => { :login => 'mislav' }\n      }\n      \"\"\"\n    When I successfully run `hub fork`\n    And the stdout should contain exactly \"new remote: mislav\\n\"\n\n  Scenario: Unrelated remote already exists\n    Given the \"mislav\" remote has url \"git@github.com:mislav/unrelated.git\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles', :host_name => 'api.github.com') { 404 }\n      post('/repos/evilchelu/dotfiles/forks', :host_name => 'api.github.com') {\n        assert :organization => nil\n        status 202\n        json :name => 'dotfiles', :owner => { :login => 'mislav' }\n      }\n      \"\"\"\n    When I run `hub fork`\n    Then the exit status should not be 0\n    And the stderr should contain:\n      \"\"\"\n      remote mislav already exists.\n      \"\"\"\n    And the url for \"mislav\" should be \"git@github.com:mislav/unrelated.git\"\n\n  Scenario: Related fork and related remote already exist\n    Given the \"mislav\" remote has url \"git@github.com:mislav/dotfiles.git\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :html_url => 'https://github.com/mislav/dotfiles',\n             :parent => { :html_url => 'https://github.com/EvilChelu/Dotfiles' }\n      }\n      \"\"\"\n    When I run `hub fork`\n    Then the exit status should be 0\n    And the stdout should contain exactly:\n      \"\"\"\n      existing remote: mislav\\n\n      \"\"\"\n    And the url for \"mislav\" should be \"git@github.com:mislav/dotfiles.git\"\n\n  Scenario: Related fork and related remote, but with differing protocol, already exist\n    Given the \"mislav\" remote has url \"https://github.com/mislav/dotfiles.git\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :html_url => 'https://github.com/mislav/dotfiles',\n             :parent => { :html_url => 'https://github.com/EvilChelu/Dotfiles' }\n      }\n      \"\"\"\n    When I run `hub fork`\n    Then the exit status should be 0\n    And the stdout should contain exactly:\n      \"\"\"\n      existing remote: mislav\\n\n      \"\"\"\n    And the url for \"mislav\" should be \"https://github.com/mislav/dotfiles.git\"\n\n  Scenario: Invalid OAuth token\n    Given the GitHub API server:\n      \"\"\"\n      before { halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN' }\n      \"\"\"\n    And I am \"mislav\" on github.com with OAuth token \"WRONGTOKEN\"\n    When I run `hub fork`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Error creating fork: Unauthorized (HTTP 401)\\n\n      \"\"\"\n\n  Scenario: Not in repo\n    Given the current dir is not a repo\n    When I run `hub fork`\n    Then the exit status should be 1\n    And the stderr should contain \"fatal: Not a git repository\"\n\n  Scenario: Origin remote doesn't exist\n    Given I run `git remote rm origin`\n    When I run `hub fork`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Aborted: could not find any git remote pointing to a GitHub repository\\n\n      \"\"\"\n    And there should be no \"origin\" remote\n\n  Scenario: Unknown host\n    Given the \"origin\" remote has url \"git@git.my.org:evilchelu/dotfiles.git\"\n    When I run `hub fork`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Aborted: could not find any git remote pointing to a GitHub repository\\n\n      \"\"\"\n\n  Scenario: Enterprise fork\n    Given the GitHub API server:\n      \"\"\"\n      before {\n        halt 400 unless request.env['HTTP_X_ORIGINAL_SCHEME'] == 'https'\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token FITOKEN'\n      }\n      post('/api/v3/repos/evilchelu/dotfiles/forks', :host_name => 'git.my.org') {\n        status 202\n        json :name => 'dotfiles', :owner => { :login => 'mislav' }\n      }\n      \"\"\"\n    And the \"origin\" remote has url \"git@git.my.org:evilchelu/dotfiles.git\"\n    And I am \"mislav\" on git.my.org with OAuth token \"FITOKEN\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    When I successfully run `hub fork`\n    Then the url for \"mislav\" should be \"https://git.my.org/mislav/dotfiles.git\"\n\n  Scenario: Enterprise fork using regular HTTP\n    Given the GitHub API server:\n      \"\"\"\n      before {\n        halt 400 unless request.env['HTTP_X_ORIGINAL_SCHEME'] == 'http'\n        halt 400 unless request.env['HTTP_X_ORIGINAL_PORT'] == '80'\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token FITOKEN'\n      }\n      post('/api/v3/repos/evilchelu/dotfiles/forks', :host_name => 'git.my.org') {\n        status 202\n        json :name => 'dotfiles', :owner => { :login => 'mislav' }\n      }\n      \"\"\"\n    And the \"origin\" remote has url \"git@git.my.org:evilchelu/dotfiles.git\"\n    And I am \"mislav\" on http://git.my.org with OAuth token \"FITOKEN\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    When I successfully run `hub fork`\n    Then the url for \"mislav\" should be \"https://git.my.org/mislav/dotfiles.git\"\n\n  Scenario: Fork a repo to a specific organization\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/acme/dotfiles') { 404 }\n      post('/repos/evilchelu/dotfiles/forks') {\n        assert :organization => \"acme\"\n        status 202\n        json :name => 'dotfiles', :owner => { :login => 'acme' }\n      }\n      \"\"\"\n    When I successfully run `hub fork --org=acme`\n    Then the output should contain exactly \"new remote: acme\\n\"\n    Then the url for \"acme\" should be \"https://github.com/acme/dotfiles.git\"\n"
  },
  {
    "path": "features/gist.feature",
    "content": "Feature: hub gist\n  Background:\n    Given I am \"octokitten\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Fetch a gist with a single file\n    Given the GitHub API server:\n      \"\"\"\n      get('/gists/myhash') {\n        json({\n          :files => {\n            'hub_gist1.txt' => {\n              'content' => \"my content is here\",\n            }\n          },\n          :description => \"my gist\",\n        })\n      }\n      \"\"\"\n    When I successfully run `hub gist show myhash`\n    Then the output should contain exactly:\n      \"\"\"\n      my content is here\\n\n      \"\"\"\n  \n  Scenario: Fetch a gist with many files\n    Given the GitHub API server:\n      \"\"\"\n      get('/gists/myhash') {\n        json({\n          :files => {\n            'hub_gist1.txt' => {\n              'content' => \"my content is here\"\n            },\n            'hub_gist2.txt' => {\n              'content' => \"more content is here\"\n            }\n          },\n          :description => \"my gist\",\n          :id => \"myhash\",\n        })\n      }\n      \"\"\"\n    When I run `hub gist show myhash`\n    Then the exit status should be 1\n    Then the stderr should contain:\n      \"\"\"\n      This gist contains multiple files, you must specify one:\n        hub_gist1.txt\n        hub_gist2.txt\n      \"\"\"\n\n  Scenario: Fetch a single file from gist\n    Given the GitHub API server:\n      \"\"\"\n      get('/gists/myhash') {\n        json({\n          :files => {\n            'hub_gist1.txt' => {\n              'content' => \"my content is here\"\n            },\n            'hub_gist2.txt' => {\n              'content' => \"more content is here\"\n            }\n          },\n          :description => \"my gist\",\n          :id => \"myhash\",\n        })\n      }\n      \"\"\"\n    When I successfully run `hub gist show myhash hub_gist1.txt`\n    Then the output should contain exactly:\n      \"\"\"\n      my content is here\\n\n      \"\"\"\n\n  Scenario: Create a gist from file\n    Given the GitHub API server:\n      \"\"\"\n      post('/gists') {\n        status 201\n        json :html_url => 'http://gists.github.com/somehash'\n      }\n      \"\"\"\n    Given a file named \"testfile.txt\" with:\n      \"\"\"\n      this is a test file\n      \"\"\"\n    When I successfully run `hub gist create testfile.txt`\n    Then the output should contain exactly:\n      \"\"\"\n      http://gists.github.com/somehash\\n\n      \"\"\"\n\n  Scenario: Open the new gist in a browser\n    Given the GitHub API server:\n      \"\"\"\n      post('/gists') {\n        status 201\n        json :html_url => 'http://gists.github.com/somehash'\n      }\n      \"\"\"\n    Given a file named \"testfile.txt\" with:\n      \"\"\"\n      this is a test file\n      \"\"\"\n    When I successfully run `hub gist create -o testfile.txt`\n    Then the output should contain exactly \"\"\n    And \"open http://gists.github.com/somehash\" should be run\n\n  Scenario: Create a gist with multiple files\n    Given the GitHub API server:\n      \"\"\"\n      post('/gists') {\n        halt 400 unless params[:files][\"testfile.txt\"][\"content\"]\n        halt 400 unless params[:files][\"testfile2.txt\"][\"content\"]\n        status 201\n        json({\n          :html_url => 'http://gists.github.com/somehash',\n        })\n      }\n      \"\"\"\n    Given a file named \"testfile.txt\" with:\n      \"\"\"\n      this is a test file\n      \"\"\"\n    Given a file named \"testfile2.txt\" with:\n      \"\"\"\n      this is another test file\n      \"\"\"\n    When I successfully run `hub gist create testfile.txt testfile2.txt`\n    Then the output should contain exactly:\n      \"\"\"\n      http://gists.github.com/somehash\\n\n      \"\"\"\n\n  Scenario: Create a gist from stdin\n    Given the GitHub API server:\n      \"\"\"\n      post('/gists') {\n        halt 400 unless params[:files][\"gistfile1.txt\"][\"content\"] == \"hello\\n\"\n        status 201\n        json :html_url => 'http://gists.github.com/somehash'\n      }\n      \"\"\"\n    When I run `hub gist create` interactively\n    And I pass in:\n      \"\"\"\n      hello\n      \"\"\"\n    Then the output should contain exactly:\n      \"\"\"\n      http://gists.github.com/somehash\\n\n      \"\"\"\n\n  Scenario: Insufficient OAuth scopes\n    Given the GitHub API server:\n      \"\"\"\n      post('/gists') {\n        status 404\n        response.headers['x-accepted-oauth-scopes'] = 'gist'\n        response.headers['x-oauth-scopes'] = 'repos'\n        json({})\n      }\n      \"\"\"\n    Given a file named \"testfile.txt\" with:\n      \"\"\"\n      this is a test file\n      \"\"\"\n    When I run `hub gist create testfile.txt`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Error creating gist: Not Found (HTTP 404)\n      Your access token may have insufficient scopes. Visit http://github.com/settings/tokens\n      to edit the 'hub' token and enable one of the following scopes: gist\\n\n      \"\"\"\n\n  Scenario: Infer correct OAuth scopes for gist\n    Given the GitHub API server:\n      \"\"\"\n      post('/gists') {\n        status 404\n        response.headers['x-oauth-scopes'] = 'repos'\n        json({})\n      }\n      \"\"\"\n    Given a file named \"testfile.txt\" with:\n      \"\"\"\n      this is a test file\n      \"\"\"\n    When I run `hub gist create testfile.txt`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Error creating gist: Not Found (HTTP 404)\n      Your access token may have insufficient scopes. Visit http://github.com/settings/tokens\n      to edit the 'hub' token and enable one of the following scopes: gist\\n\n      \"\"\"\n\n  Scenario: Create error\n    Given the GitHub API server:\n      \"\"\"\n      post('/gists') {\n        status 404\n        response.headers['x-accepted-oauth-scopes'] = 'gist'\n        response.headers['x-oauth-scopes'] = 'repos, gist'\n        json({})\n      }\n      \"\"\"\n    Given a file named \"testfile.txt\" with:\n      \"\"\"\n      this is a test file\n      \"\"\"\n    When I run `hub gist create testfile.txt`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Error creating gist: Not Found (HTTP 404)\\n\n      \"\"\"\n\n"
  },
  {
    "path": "features/git_compatibility.feature",
    "content": "Feature: git-hub compatibility\n  Scenario: If alias named branch exists, it should not be expanded.\n    Given I am in \"git://github.com/rtomayko/ronn.git\" git repo\n    And the default branch for \"origin\" is \"master\"\n    When I successfully run `git config --global alias.branch \"branch -a\"`\n    When I run `hub branch`\n    Then the stdout should contain exactly \"* master\\n\"\n\n  Scenario: List commands\n    When I successfully run `hub --list-cmds=others`\n    Then the stdout should contain exactly:\n      \"\"\"\n      add\n      branch\n      commit\n      alias\n      api\n      browse\n      ci-status\n      compare\n      create\n      delete\n      fork\n      gist\n      issue\n      pr\n      pull-request\n      release\n      sync\\n\n      \"\"\"\n\n  Scenario: Doesn't sabotage --exec-path\n    When I successfully run `hub --exec-path`\n    Then the output should not contain \"These GitHub commands\"\n\n  Scenario: Shows help with --git-dir\n    When I run `hub --git-dir=.git`\n    Then the exit status should be 1\n    And the output should contain \"usage: git \"\n"
  },
  {
    "path": "features/help.feature",
    "content": "Feature: hub help\n  Scenario: Appends hub help to regular help text\n    When I successfully run `hub help`\n    Then the output should contain:\n      \"\"\"\n      These GitHub commands are provided by hub:\n\n         api            Low-level GitHub API request interface\n      \"\"\"\n    And the output should contain \"usage: git \"\n\n  Scenario: Shows help text with no arguments\n    When I run `hub`\n    Then the stdout should contain \"usage: git \"\n    And the stderr should contain exactly \"\"\n    And the exit status should be 1\n\n  Scenario: Appends hub commands to `--all` output\n    When I successfully run `hub help -a`\n    Then the output should contain \"pull-request\"\n\n  Scenario: Shows help for a hub extension\n    When I successfully run `hub help hub-help`\n    Then \"man hub-help\" should be run\n\n  Scenario: Shows help for a hub command\n    When I successfully run `hub help fork`\n    Then \"man hub-fork\" should be run\n\n  Scenario: Show help in HTML format\n    When I successfully run `hub help -w fork`\n    Then \"man hub-fork\" should not be run\n    And \"git web--browse PATH/hub-fork.1.html\" should be run\n\n  Scenario: Show help in HTML format by default\n    Given I successfully run `git config --global help.format html`\n    When I successfully run `hub help fork`\n    Then \"git web--browse PATH/hub-fork.1.html\" should be run\n\n  Scenario: Override HTML format back to man\n    Given I successfully run `git config --global help.format html`\n    When I successfully run `hub help -m fork`\n    Then \"man hub-fork\" should be run\n\n  Scenario: The --help flag opens man page\n    When I successfully run `hub fork --help`\n    Then \"man hub-fork\" should be run\n\n  Scenario: The --help flag expands alias first\n    Given I successfully run `git config --global alias.ci ci-status`\n    When I successfully run `hub ci --help`\n    Then \"man hub-ci-status\" should be run\n"
  },
  {
    "path": "features/init.feature",
    "content": "Feature: hub init\n  Background:\n    Given I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n    Given a directory named \"dotfiles\"\n    When I cd to \"dotfiles\"\n\n  Scenario: Initializes a git repo with remote\n    When I successfully run `hub init -g`\n    Then the url for \"origin\" should be \"https://github.com/mislav/dotfiles.git\"\n\n  Scenario: Initializes a git repo in a new directory with remote\n    When I successfully run `hub init -g new_dir`\n    And I cd to \"new_dir\"\n    Then the url for \"origin\" should be \"https://github.com/mislav/new_dir.git\"\n\n  Scenario: Enterprise host\n    Given $GITHUB_HOST is \"git.my.org\"\n    And I am \"mislav\" on git.my.org with OAuth token \"FITOKEN\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    When I successfully run `hub init -g`\n    Then the url for \"origin\" should be \"https://git.my.org/mislav/dotfiles.git\"\n"
  },
  {
    "path": "features/issue-transfer.feature",
    "content": "Feature: hub issue transfer\n  Background:\n    Given I am in \"git://github.com/octocat/hello-world.git\" git repo\n    And I am \"srafi1\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Transfer issue\n    Given the GitHub API server:\n    \"\"\"\n    count = 0\n    post('/graphql') {\n      halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'\n      count += 1\n      case count\n      when 1\n        assert :query => /\\A\\s*query\\(/,\n          :variables => {\n            :issue => 123,\n            :sourceOwner => \"octocat\",\n            :sourceRepo => \"hello-world\",\n            :targetOwner => \"octocat\",\n            :targetRepo => \"spoon-knife\",\n          }\n        json :data => {\n          :source => { :issue => { :id => \"ISSUE-ID\" } },\n          :target => { :id => \"REPO-ID\" },\n        }\n      when 2\n        assert :query => /\\A\\s*mutation\\(/,\n          :variables => {\n            :issue => \"ISSUE-ID\",\n            :repo => \"REPO-ID\",\n          }\n        json :data => {\n          :transferIssue => { :issue => { :url => \"the://url\" } }\n        }\n      else\n        status 400\n        json :message => \"request not stubbed\"\n      end\n    }\n    \"\"\"\n    When I successfully run `hub issue transfer 123 spoon-knife`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Transfer to another owner\n    Given the GitHub API server:\n    \"\"\"\n    count = 0\n    post('/graphql') {\n      count += 1\n      case count\n      when 1\n        assert :variables => {\n          :targetOwner => \"monalisa\",\n          :targetRepo => \"playground\",\n        }\n        json :data => {}\n      when 2\n        json :errors => [\n          { :message => \"New repository must have the same owner as the current repository\" },\n        ]\n      else\n        status 400\n        json :message => \"request not stubbed\"\n      end\n    }\n    \"\"\"\n    When I run `hub issue transfer 123 monalisa/playground`\n    Then the exit status should be 1\n    Then the stderr should contain exactly:\n      \"\"\"\n      API error: New repository must have the same owner as the current repository\\n\n      \"\"\"\n"
  },
  {
    "path": "features/issue.feature",
    "content": "Feature: hub issue\n  Background:\n    Given I am in \"git://github.com/github/hub.git\" git repo\n    And I am \"cornwe19\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Fetch issues\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/issues') {\n      assert :assignee => \"Cornwe19\",\n             :sort => nil,\n             :direction => \"desc\"\n\n      json [\n        { :number => 999,\n          :title => \"First pull\",\n          :state => \"open\",\n          :user => { :login => \"octocat\" },\n          :pull_request => { },\n        },\n        { :number => 102,\n          :title => \"First issue\",\n          :state => \"open\",\n          :user => { :login => \"octocat\" },\n        },\n        { :number => 13,\n          :title => \"Second issue\",\n          :state => \"open\",\n          :user => { :login => \"octocat\" },\n        },\n      ]\n    }\n    \"\"\"\n    When I successfully run `hub issue -a Cornwe19`\n    Then the output should contain exactly:\n      \"\"\"\n          #102  First issue\n           #13  Second issue\\n\n      \"\"\"\n\n  Scenario: List limited number of issues\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/issues') {\n      response.headers[\"Link\"] = %(<https://api.github.com/repositories/12345/issues?per_page=100&page=2>; rel=\"next\")\n      assert :per_page => \"3\"\n      json [\n        { :number => 102,\n          :title => \"First issue\",\n          :state => \"open\",\n          :user => { :login => \"octocat\" },\n        },\n        { :number => 13,\n          :title => \"Second issue\",\n          :state => \"open\",\n          :user => { :login => \"octocat\" },\n        },\n        { :number => 999,\n          :title => \"Third issue\",\n          :state => \"open\",\n          :user => { :login => \"octocat\" },\n        },\n      ]\n    }\n    \"\"\"\n    When I successfully run `hub issue -L 2`\n    Then the output should contain exactly:\n      \"\"\"\n          #102  First issue\n           #13  Second issue\\n\n      \"\"\"\n\n  Scenario: Fetch issues and pull requests\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/issues') {\n      assert :assignee => \"Cornwe19\",\n             :sort => nil,\n             :direction => \"desc\"\n\n      json [\n        { :number => 999,\n          :title => \"First pull\",\n          :state => \"open\",\n          :user => { :login => \"octocat\" },\n          :pull_request => { },\n        },\n        { :number => 102,\n          :title => \"First issue\",\n          :state => \"open\",\n          :user => { :login => \"octocat\" },\n        },\n        { :number => 13,\n          :title => \"Second issue\",\n          :state => \"open\",\n          :user => { :login => \"octocat\" },\n        },\n      ]\n    }\n    \"\"\"\n    When I successfully run `hub issue -a Cornwe19 --include-pulls`\n    Then the output should contain exactly:\n      \"\"\"\n          #999  First pull\n          #102  First issue\n           #13  Second issue\\n\n      \"\"\"\n\n  Scenario: Fetch issues not assigned to any milestone\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/issues') {\n      assert :milestone => \"none\"\n      json []\n    }\n    \"\"\"\n    When I successfully run `hub issue -M none`\n\n  Scenario: Fetch issues assigned to milestone by number\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/github/hub/issues') {\n        assert :milestone => \"12\"\n        json []\n      }\n      \"\"\"\n    When I successfully run `hub issue -M 12`\n\n  Scenario: Fetch issues assigned to milestone by name\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/github/hub/milestones') {\n        status 200\n        json [\n          { :number => 237, :title => \"prerelease\" },\n          { :number => 1337, :title => \"v1\" },\n          { :number => 41319, :title => \"Hello World!\" }\n        ]\n      }\n      get('/repos/github/hub/issues') {\n        assert :milestone => \"1337\"\n        json []\n      }\n      \"\"\"\n    When I successfully run `hub issue -M v1`\n\n  Scenario: Fetch issues created by a given user\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/issues') {\n      assert :creator => \"octocat\"\n      json []\n    }\n    \"\"\"\n    When I successfully run `hub issue -c octocat`\n\n  Scenario: Fetch issues mentioning a given user\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/issues') {\n      assert :mentioned => \"octocat\"\n      json []\n    }\n    \"\"\"\n    When I successfully run `hub issue -@ octocat`\n\n  Scenario: Fetch issues with certain labels\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/issues') {\n      assert :labels => \"foo,bar\"\n      json []\n    }\n    \"\"\"\n    When I successfully run `hub issue -l foo,bar`\n\n  Scenario: Fetch issues updated after a certain date and time\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/issues') {\n      assert :since => \"2016-08-18T09:11:32Z\"\n      json []\n    }\n    \"\"\"\n    When I successfully run `hub issue -d 2016-08-18T09:11:32Z`\n\n  Scenario: Fetch issues sorted by number of comments ascending\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/issues') {\n      assert :sort => \"comments\"\n      assert :direction => \"asc\"\n\n      json []\n    }\n    \"\"\"\n    When I successfully run `hub issue -o comments -^`\n\n  Scenario: Fetch issues across multiple pages\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/issues') {\n      assert :per_page => \"100\", :page => :no\n      response.headers[\"Link\"] = %(<https://api.github.com/repositories/12345?per_page=100&page=2>; rel=\"next\")\n      json [\n        { :number => 102,\n          :title => \"First issue\",\n          :state => \"open\",\n          :user => { :login => \"octocat\" },\n        },\n      ]\n    }\n\n    get('/repositories/12345') {\n      assert :per_page => \"100\"\n      if params[:page] == \"2\"\n        response.headers[\"Link\"] = %(<https://api.github.com/repositories/12345?per_page=100&page=3>; rel=\"next\")\n        json [\n          { :number => 13,\n            :title => \"Second issue\",\n            :state => \"open\",\n            :user => { :login => \"octocat\" },\n          },\n          { :number => 103,\n            :title => \"Issue from 2nd page\",\n            :state => \"open\",\n            :user => { :login => \"octocat\" },\n          },\n        ]\n      elsif params[:page] == \"3\"\n        json [\n          { :number => 21,\n            :title => \"Even more issuez\",\n            :state => \"open\",\n            :user => { :login => \"octocat\" },\n          },\n        ]\n      else\n        status 400\n      end\n    }\n    \"\"\"\n    When I successfully run `hub issue`\n    Then the output should contain exactly:\n      \"\"\"\n          #102  First issue\n           #13  Second issue\n          #103  Issue from 2nd page\n           #21  Even more issuez\\n\n      \"\"\"\n\n  Scenario: Custom format for issues list\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/issues') {\n      assert :assignee => 'Cornwe19'\n      json [\n        { :number => 102,\n          :title => \"First issue\",\n          :state => \"open\",\n          :user => { :login => \"lascap\" },\n        },\n        { :number => 13,\n          :title => \"Second issue\",\n          :state => \"closed\",\n          :user => { :login => \"mislav\" },\n        },\n      ]\n    }\n    \"\"\"\n    When I successfully run `hub issue -f \"%I,%au%n\" -a Cornwe19`\n    Then the output should contain exactly:\n      \"\"\"\n      102,lascap\n      13,mislav\\n\n      \"\"\"\n\n  Scenario: Custom format with no-color labels\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/issues') {\n      json [\n        { :number => 102,\n          :title => \"First issue\",\n          :state => \"open\",\n          :user => { :login => \"morganwahl\" },\n          :labels => [\n            { :name => 'Has Migration',\n              :color => 'cfcfcf' },\n            { :name => 'Maintenance Window',\n              :color => '888888' },\n          ]\n        },\n        { :number => 201,\n          :title => \"No labels\",\n          :state => \"open\",\n          :user => { :login => \"octocat\" },\n          :labels => []\n        },\n      ]\n    }\n    \"\"\"\n    When I successfully run `hub issue -f \"%I: %L%n\" --color=never`\n    Then the output should contain exactly:\n      \"\"\"\n      102: Has Migration, Maintenance Window\n      201: \\n\n      \"\"\"\n\n  Scenario: List all assignees\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/issues') {\n      json [\n        { :number => 102,\n          :title => \"First issue\",\n          :state => \"open\",\n          :user => { :login => \"octocat\" },\n          :assignees => [\n            { :login => \"mislav\" },\n            { :login => \"lascap\" },\n          ]\n        },\n        { :number => 13,\n          :title => \"Second issue\",\n          :state => \"closed\",\n          :user => { :login => \"octocat\" },\n          :assignees => [\n            { :login => \"keenahn\" },\n          ]\n        },\n      ]\n    }\n    \"\"\"\n    When I successfully run `hub issue -f \"%I:%as%n\"`\n    Then the output should contain exactly:\n      \"\"\"\n      102:mislav, lascap\n      13:keenahn\\n\n      \"\"\"\n\n  Scenario: Create an issue\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/hub/issues') {\n        assert :title => \"Not workie, pls fix\",\n               :body => \"\",\n               :labels => :no\n\n        status 201\n        json :html_url => \"https://github.com/github/hub/issues/1337\"\n      }\n      \"\"\"\n    When I successfully run `hub issue create -m \"Not workie, pls fix\"`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/github/hub/issues/1337\\n\n      \"\"\"\n\n  Scenario: Create an issue and open in browser\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/hub/issues') {\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub issue create -o -m hello`\n    Then the output should contain exactly \"\"\n    Then \"open the://url\" should be run\n\n  Scenario: Create an issue with labels\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/hub/issues') {\n        assert :title => \"hello\",\n               :body => \"\",\n               :milestone => :no,\n               :assignees => :no,\n               :labels => [\"wont fix\", \"docs\", \"nope\"]\n\n        status 201\n        json :html_url => \"https://github.com/github/hub/issues/1337\"\n      }\n      \"\"\"\n    When I successfully run `hub issue create -m \"hello\" -l \"wont fix,docs\" -lnope`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/github/hub/issues/1337\\n\n      \"\"\"\n\n  Scenario: Create an issue with milestone and assignees\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/hub/issues') {\n        assert :title => \"hello\",\n               :body => \"\",\n               :milestone => 12,\n               :assignees => [\"mislav\", \"josh\", \"pcorpet\"],\n               :labels => :no\n\n        status 201\n        json :html_url => \"https://github.com/github/hub/issues/1337\"\n      }\n      \"\"\"\n    When I successfully run `hub issue create -m \"hello\" -M 12 --assign mislav,josh -apcorpet`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/github/hub/issues/1337\\n\n      \"\"\"\n\n  Scenario: Create an issue with milestone by name\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/github/hub/milestones') {\n        status 200\n        json [\n          { :number => 237, :title => \"prerelease\" },\n          { :number => 1337, :title => \"v1\" },\n          { :number => 41319, :title => \"Hello World!\" }\n        ]\n      }\n      post('/repos/github/hub/issues') {\n        assert :milestone => 41319\n        status 201\n        json :html_url => \"https://github.com/github/hub/issues/1337\"\n      }\n      \"\"\"\n    When I successfully run `hub issue create -m \"hello\" -M \"hello world!\"`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/github/hub/issues/1337\\n\n      \"\"\"\n\n  Scenario: Editing empty issue message\n    Given the git commit editor is \"vim\"\n    And the text editor adds:\n      \"\"\"\n      hello\n\n      my nice issue\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/hub/issues') {\n        assert :title => \"hello\",\n               :body => \"my nice issue\"\n\n        status 201\n        json :html_url => \"https://github.com/github/hub/issues/1337\"\n      }\n      \"\"\"\n    When I successfully run `hub issue create -m '' --edit`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/github/hub/issues/1337\\n\n      \"\"\"\n\n  Scenario: Issue template\n    Given the git commit editor is \"vim\"\n    And the text editor adds:\n      \"\"\"\n      hello\n      \"\"\"\n    And a file named \"issue_template.md\" with:\n      \"\"\"\n      my nice issue template\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/hub/issues') {\n        assert :title => \"hello\",\n               :body => \"my nice issue template\"\n\n        status 201\n        json :html_url => \"https://github.com/github/hub/issues/1337\"\n      }\n      \"\"\"\n    When I successfully run `hub issue create`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/github/hub/issues/1337\\n\n      \"\"\"\n\n  Scenario: Issue template from a subdirectory\n    Given the git commit editor is \"vim\"\n    And the text editor adds:\n      \"\"\"\n      hello\n      \"\"\"\n    And a file named \".github/issue_template.md\" with:\n      \"\"\"\n      my nice issue template\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/hub/issues') {\n        assert :title => \"hello\",\n               :body => \"my nice issue template\"\n\n        status 201\n        json :html_url => \"https://github.com/github/hub/issues/1337\"\n      }\n      \"\"\"\n    Given a directory named \"subdir\"\n    When I cd to \"subdir\"\n    And I successfully run `hub issue create`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/github/hub/issues/1337\\n\n      \"\"\"\n\n  Scenario: Multiple issue templates\n    Given the git commit editor is \"vim\"\n    And the text editor adds:\n      \"\"\"\n      hello\n      \"\"\"\n    And a file named \".github/ISSUE_TEMPLATE/bug_report.md\" with:\n      \"\"\"\n      I want to report a bug\n      \"\"\"\n    And a file named \".github/ISSUE_TEMPLATE/feature_request.md\" with:\n      \"\"\"\n      There is a feature that I need!\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/hub/issues') {\n        assert :title => \"hello\",\n               :body => \"\"\n\n        status 201\n        json :html_url => \"https://github.com/github/hub/issues/1337\"\n      }\n      \"\"\"\n    When I successfully run `hub issue create`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/github/hub/issues/1337\\n\n      \"\"\"\n\n  Scenario: Multiple issue templates with default\n    Given the git commit editor is \"vim\"\n    And the text editor adds:\n      \"\"\"\n      hello\n      \"\"\"\n    And a directory named \".github/ISSUE_TEMPLATE\"\n    And a file named \".github/ISSUE_TEMPLATE.md\" with:\n      \"\"\"\n      The default template\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/hub/issues') {\n        assert :title => \"hello\",\n               :body => \"The default template\"\n\n        status 201\n        json :html_url => \"https://github.com/github/hub/issues/1337\"\n      }\n      \"\"\"\n    When I successfully run `hub issue create`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/github/hub/issues/1337\\n\n      \"\"\"\n\n  Scenario: A file named \".github\"\n    Given the git commit editor is \"vim\"\n    And the text editor adds:\n      \"\"\"\n      hello\n      \"\"\"\n    And a file named \".github\" with:\n      \"\"\"\n      this is ignored\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/hub/issues') {\n        assert :title => \"hello\",\n               :body => \"\"\n\n        status 201\n        json :html_url => \"https://github.com/github/hub/issues/1337\"\n      }\n      \"\"\"\n    When I successfully run `hub issue create`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/github/hub/issues/1337\\n\n      \"\"\"\n\n  Scenario: Update an issue's title\n    Given the GitHub API server:\n      \"\"\"\n      patch('/repos/github/hub/issues/1337') {\n        assert :title => \"Not workie, pls fix\",\n               :body => \"\",\n               :milestone => :no,\n               :assignees => :no,\n               :labels => :no,\n               :state => :no\n      }\n      \"\"\"\n    Then I successfully run `hub issue update 1337 -m \"Not workie, pls fix\"`\n\n  Scenario: Update an issue's state\n    Given the GitHub API server:\n      \"\"\"\n      patch('/repos/github/hub/issues/1337') {\n        assert :title => :no,\n               :labels => :no,\n               :state => \"closed\"\n      }\n      \"\"\"\n    Then I successfully run `hub issue update 1337 -s closed`\n    \n  Scenario: Update an issue's labels\n    Given the GitHub API server:\n      \"\"\"\n      patch('/repos/github/hub/issues/1337') {\n        assert :title => :no,\n               :body => :no,\n               :milestone => :no,\n               :assignees => :no,\n               :labels => [\"bug\", \"important\"]\n      }\n      \"\"\"\n    Then I successfully run `hub issue update 1337 -l bug,important`\n\n  Scenario: Update an issue's milestone\n    Given the GitHub API server:\n      \"\"\"\n      patch('/repos/github/hub/issues/1337') {\n        assert :title => :no,\n               :body => :no,\n               :milestone => 42,\n               :assignees => :no,\n               :labels => :no\n      }\n      \"\"\"\n    Then I successfully run `hub issue update 1337 -M 42`\n\n  Scenario: Update an issue's milestone by name\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/github/hub/milestones') {\n        status 200\n        json [\n          { :number => 237, :title => \"prerelease\" },\n          { :number => 42, :title => \"Hello World!\" }\n        ]\n      }\n      patch('/repos/github/hub/issues/1337') {\n        assert :title => :no,\n               :body => :no,\n               :milestone => 42,\n               :assignees => :no,\n               :labels => :no\n      }\n      \"\"\"\n    Then I successfully run `hub issue update 1337 -M \"hello world!\"`\n\n  Scenario: Update an issue's assignees\n    Given the GitHub API server:\n      \"\"\"\n      patch('/repos/github/hub/issues/1337') {\n        assert :title => :no,\n               :body => :no,\n               :milestone => :no,\n               :assignees => [\"Cornwe19\"],\n               :labels => :no\n      }\n      \"\"\"\n    Then I successfully run `hub issue update 1337 -a Cornwe19`\n\n  Scenario: Update an issue's title, labels, milestone, and assignees\n    Given the GitHub API server:\n      \"\"\"\n      patch('/repos/github/hub/issues/1337') {\n        assert :title => \"Not workie, pls fix\",\n               :body => \"\",\n               :milestone => 42,\n               :assignees => [\"Cornwe19\"],\n               :labels => [\"bug\", \"important\"]\n      }\n      \"\"\"\n    Then I successfully run `hub issue update 1337  -m \"Not workie, pls fix\" -M 42 -l bug,important -a Cornwe19`\n\n  Scenario: Clear existing issue labels, assignees, milestone\n    Given the GitHub API server:\n      \"\"\"\n      patch('/repos/github/hub/issues/1337') {\n        assert :title => :no,\n               :body => :no,\n               :milestone => nil,\n               :assignees => [],\n               :labels => []\n      }\n      \"\"\"\n    Then I successfully run `hub issue update 1337 --milestone= --assign= --labels=`\n\n  Scenario: Update an issue's title and body manually\n    Given the git commit editor is \"vim\"\n    And the text editor adds:\n      \"\"\"\n      My new title\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/github/hub/issues/1337') {\n        json \\\n          :number => 1337,\n          :title => \"My old title\",\n          :body => \"My old body\"\n      }\n      patch('/repos/github/hub/issues/1337') {\n        assert :title => \"My new title\",\n               :body => \"My old title\\n\\nMy old body\",\n               :milestone => :no,\n               :assignees => :no,\n               :labels => :no\n      }\n      \"\"\"\n    Then I successfully run `hub issue update 1337 --edit`\n\n  Scenario: Update an issue's title and body via a file\n    Given a file named \"my-issue.md\" with:\n      \"\"\"\n      My new title\n      \n      My new body\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      patch('/repos/github/hub/issues/1337') {\n        assert :title => \"My new title\",\n               :body => \"My new body\",\n               :milestone => :no,\n               :assignees => :no,\n               :labels => :no\n      }\n      \"\"\"\n    Then I successfully run `hub issue update 1337 -F my-issue.md`\n\n  Scenario: Update an issue without specifying fields to update\n    When I run `hub issue update 1337`\n    Then the exit status should be 1\n    Then the stderr should contain \"please specify fields to update\"\n    Then the stderr should contain \"Usage: hub issue\"\n\n  Scenario: Fetch issue labels\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/labels') {\n      response.headers[\"Link\"] = %(<https://api.github.com/repositories/12345/labels?per_page=100&page=2>; rel=\"next\")\n      assert :per_page => \"100\", :page => nil\n      json [\n        { :name => \"Discuss\",\n          :color => \"0000ff\",\n        },\n        { :name => \"bug\",\n          :color => \"ff0000\",\n        },\n        { :name => \"feature\",\n          :color => \"00ff00\",\n        },\n      ]\n    }\n    get('/repositories/12345/labels') {\n      assert :per_page => \"100\", :page => \"2\"\n      json [\n        { :name => \"affects\",\n          :color => \"ffffff\",\n        },\n      ]\n    }\n    \"\"\"\n    When I successfully run `hub issue labels`\n    Then the output should contain exactly:\n      \"\"\"\n      affects\n      bug\n      Discuss\n      feature\\n\n      \"\"\"\n\n  Scenario: Fetch single issue\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/github/hub/issues/102') {\n        json \\\n          :number => 102,\n          :state => \"open\",\n          :body => \"I want this feature\",\n          :title => \"Feature request for hub issue show\",\n          :created_at => \"2017-04-14T16:00:49Z\",\n          :user => { :login => \"royels\" },\n          :assignees => [{:login => \"royels\"}],\n          :comments => 1\n      }\n      get('/repos/github/hub/issues/102/comments') {\n        json [\n          { :body => \"I am from the future\",\n            :created_at => \"2011-04-14T16:00:49Z\",\n            :user => { :login => \"octocat\" }\n          },\n          { :body => \"I did the thing\",\n            :created_at => \"2013-10-30T22:20:00Z\",\n            :user => { :login => \"hubot\" }\n          },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub issue show 102`\n    Then the output should contain exactly:\n      \"\"\"\n      # Feature request for hub issue show\n\n      * created by @royels on 2017-04-14 16:00:49 +0000 UTC\n      * assignees: royels\n\n      I want this feature\n\n      ## Comments:\n\n      ### comment by @octocat on 2011-04-14 16:00:49 +0000 UTC\n\n      I am from the future\n\n      ### comment by @hubot on 2013-10-30 22:20:00 +0000 UTC\n\n      I did the thing\\n\n      \"\"\"\n\n  Scenario: Format single issue\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/github/hub/issues/102') {\n        json \\\n          :number => 102,\n          :state => \"open\",\n          :body => \"I want this feature\",\n          :title => \"Feature request for hub issue show\",\n          :created_at => \"2017-04-14T16:00:49Z\",\n          :user => { :login => \"royels\" },\n          :assignees => [{:login => \"royels\"}],\n          :comments => 1\n      }\n      get('/repos/github/hub/issues/102/comments') {\n        json [\n          { :body => \"I am from the future\",\n            :created_at => \"2011-04-14T16:00:49Z\",\n            :user => { :login => \"octocat\" }\n          },\n          { :body => \"I did the thing\",\n            :created_at => \"2013-10-30T22:20:00Z\",\n            :user => { :login => \"hubot\" }\n          },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub issue show 102 --format='%I %t%n%n%b%n'`\n    Then the output should contain exactly:\n      \"\"\"\n      102 Feature request for hub issue show\n\n      I want this feature\\n\n      \"\"\"\n\n  Scenario: Format with literal % characters\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/github/hub/issues/102') {\n        json \\\n          :number => 102,\n          :state => \"open\",\n          :title => \"Feature request % hub\",\n          :user => { :login => \"alexfornuto\" }\n      }\n      get('/repos/github/hub/issues/102/comments') {\n        json []\n      }\n      \"\"\"\n    When I successfully run `hub issue show 102 --format='%t%%t%%n%n'`\n    Then the output should contain exactly:\n      \"\"\"\n      Feature request % hub%t%n\\n\n      \"\"\"\n\n  Scenario: Did not supply an issue number\n    When I run `hub issue show`\n    Then the exit status should be 1\n    Then the stderr should contain \"Usage: hub issue\"\n\n  Scenario: Show error message if http code is not 200 for issues endpoint\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/github/hub/issues/102') {\n        status 500\n      }\n      \"\"\"\n    When I run `hub issue show 102`\n    Then the output should contain exactly:\n      \"\"\"\n      Error fetching issue: Internal Server Error (HTTP 500)\\n\n      \"\"\"\n\n  Scenario: Show error message if http code is not 200 for comments endpoint\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/github/hub/issues/102') {\n        json \\\n          :number => 102,\n          :body => \"I want this feature\",\n          :title => \"Feature request for hub issue show\",\n          :created_at => \"2017-04-14T16:00:49Z\",\n          :user => { :login => \"royels\" }\n      }\n      get('/repos/github/hub/issues/102/comments') {\n        status 404\n      }\n      \"\"\"\n    When I run `hub issue show 102`\n    Then the output should contain exactly:\n      \"\"\"\n      Error fetching comments for issue: Not Found (HTTP 404)\\n\n      \"\"\"\n"
  },
  {
    "path": "features/merge.feature",
    "content": "Feature: hub merge\n  Background:\n    Given I am in \"hub\" git repo\n    And the \"origin\" remote has url \"git://github.com/defunkt/hub.git\"\n    And I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Normal merge\n    When I run `hub merge master`\n    Then the git command should be unchanged\n\n  Scenario: Merge pull request\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/defunkt/hub/pulls/164') { json \\\n        :base => {\n          :repo => {\n            :owner => { :login => \"defunkt\" },\n            :name => \"hub\",\n            :private => false\n          }\n        },\n        :head => {\n          :ref => \"hub_merge\",\n          :repo => {\n            :owner => { :login => \"jfirebaugh\" },\n            :name => \"hub\",\n            :private => false\n          }\n        },\n        :title => \"Add `hub merge` command\"\n      }\n      \"\"\"\n    And there is a git FETCH_HEAD\n    When I successfully run `hub merge https://github.com/defunkt/hub/pull/164`\n    Then \"git fetch origin refs/pull/164/head\" should be run\n    And \"git merge FETCH_HEAD --no-ff -m Merge pull request #164 from jfirebaugh/hub_merge\" should be run\n    When I successfully run `git show -s --format=%B`\n    Then the output should contain:\n      \"\"\"\n      Merge pull request #164 from jfirebaugh/hub_merge\n\n      Add `hub merge` command\n      \"\"\"\n\n  Scenario: Merge pull request with options\n    Given the GitHub API server:\n      \"\"\"\n      require 'json'\n      get('/repos/defunkt/hub/pulls/164') { json \\\n        :base => {\n          :repo => {\n            :owner => { :login => \"defunkt\" },\n            :name => \"hub\",\n            :private => false\n          }\n        },\n        :head => {\n          :ref => \"hub_merge\",\n          :repo => {\n            :owner => { :login => \"jfirebaugh\" },\n            :name => \"hub\",\n            :private => false\n          }\n        },\n        :title => \"Add `hub merge` command\"\n      }\n      \"\"\"\n    And there is a git FETCH_HEAD\n    When I successfully run `hub merge --squash https://github.com/defunkt/hub/pull/164 --no-edit`\n    Then \"git fetch origin refs/pull/164/head\" should be run\n    And \"git merge --squash --no-edit FETCH_HEAD -m Merge pull request #164 from jfirebaugh/hub_merge\" should be run\n\n  Scenario: Merge pull request no repo\n    Given the GitHub API server:\n      \"\"\"\n      require 'json'\n      get('/repos/defunkt/hub/pulls/164') { json \\\n        :base => {\n          :repo => {\n            :owner => { :login => \"defunkt\" },\n            :name => \"hub\",\n            :private => false\n          }\n        },\n        :head => {\n          :ref => \"hub_merge\",\n          :repo => nil\n        },\n        :title => \"Add `hub merge` command\"\n      }\n      \"\"\"\n    When I run `hub merge https://github.com/defunkt/hub/pull/164`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Error: that fork is not available anymore\\n\n      \"\"\"\n"
  },
  {
    "path": "features/pr-checkout.feature",
    "content": "Feature: hub pr checkout <PULLREQ-NUMBER>\n  Background:\n    Given I am in \"git://github.com/mojombo/jekyll.git\" git repo\n    And I am \"mojombo\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Checkout a pull request\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/jekyll/pulls/77') {\n        json :number => 77, :head => {\n          :ref => \"fixes\",\n          :repo => {\n            :owner => { :login => \"mislav\" },\n            :name => \"jekyll\",\n            :private => false\n          }\n        }, :base => {\n          :repo => {\n            :name => 'jekyll',\n            :html_url => 'https://github.com/mojombo/jekyll',\n            :owner => { :login => \"mojombo\" },\n          }\n        },\n        :maintainer_can_modify => false,\n        :html_url => 'https://github.com/mojombo/jekyll/pull/77'\n      }\n      \"\"\"\n    When I successfully run `hub pr checkout 77`\n    Then \"git fetch origin refs/pull/77/head:fixes\" should be run\n    And \"git checkout fixes\" should be run\n    And \"fixes\" should merge \"refs/pull/77/head\" from remote \"origin\"\n\n  Scenario: Custom name for new branch\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/jekyll/pulls/77') {\n        json :number => 77, :head => {\n          :ref => \"fixes\",\n          :repo => {\n            :name => \"jekyll\",\n            :owner => { :login => \"mislav\" },\n          }\n        }, :base => {\n          :repo => {\n            :name => 'jekyll',\n            :html_url => 'https://github.com/mojombo/jekyll',\n            :owner => { :login => \"mojombo\" },\n          }\n        },\n        :maintainer_can_modify => false,\n        :html_url => 'https://github.com/mojombo/jekyll/pull/77'\n      }\n      \"\"\"\n    When I successfully run `hub pr checkout 77 fixes-from-mislav`\n    Then \"git fetch origin refs/pull/77/head:fixes-from-mislav\" should be run\n    And \"git checkout fixes-from-mislav\" should be run\n    And \"fixes-from-mislav\" should merge \"refs/pull/77/head\" from remote \"origin\"\n\n  Scenario: Same-repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/jekyll/pulls/77') {\n        json :number => 77, :head => {\n          :ref => \"fixes\",\n          :repo => {\n            :name => \"jekyll\",\n            :owner => { :login => \"mojombo\" },\n          }\n        }, :base => {\n          :repo => {\n            :name => \"jekyll\",\n            :html_url => \"https://github.com/mojombo/jekyll\",\n            :owner => { :login => \"mojombo\" },\n          }\n        },\n        :html_url => 'https://github.com/mojombo/jekyll/pull/77'\n      }\n      \"\"\"\n    When I successfully run `hub pr checkout 77`\n    Then \"git fetch origin +refs/heads/fixes:refs/remotes/origin/fixes\" should be run\n    And \"git checkout -b fixes --no-track origin/fixes\" should be run\n    And \"fixes\" should merge \"refs/heads/fixes\" from remote \"origin\"\n"
  },
  {
    "path": "features/pr-list.feature",
    "content": "Feature: hub pr list\n  Background:\n    Given I am in \"git://github.com/github/hub.git\" git repo\n    And I am \"defunkt\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: List pulls\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/pulls') {\n      assert :per_page => \"100\",\n             :page => :no,\n             :sort => nil,\n             :direction => \"desc\"\n\n      response.headers[\"Link\"] = %(<https://api.github.com/repositories/12345?per_page=100&page=2>; rel=\"next\")\n\n      json [\n        { :number => 999,\n          :title => \"First\",\n          :state => \"open\",\n          :base => { :ref => \"master\", :label => \"github:master\" },\n          :head => { :ref => \"patch-1\", :label => \"octocat:patch-1\" },\n          :user => { :login => \"octocat\" },\n        },\n        { :number => 102,\n          :title => \"Second\",\n          :state => \"open\",\n          :base => { :ref => \"master\", :label => \"github:master\" },\n          :head => { :ref => \"patch-2\", :label => \"octocat:patch-2\" },\n          :user => { :login => \"octocat\" },\n        },\n        { :number => 13,\n          :title => \"Third\",\n          :state => \"open\",\n          :base => { :ref => \"master\", :label => \"github:master\" },\n          :head => { :ref => \"patch-3\", :label => \"octocat:patch-3\" },\n          :user => { :login => \"octocat\" },\n        },\n      ]\n    }\n\n    get('/repositories/12345') {\n      assert :per_page => \"100\",\n             :page => \"2\"\n\n      json [\n        { :number => 7,\n          :title => \"Fourth\",\n          :state => \"open\",\n          :base => { :ref => \"master\", :label => \"github:master\" },\n          :head => { :ref => \"patch-4\", :label => \"octocat:patch-4\" },\n          :user => { :login => \"octocat\" },\n        },\n      ]\n    }\n    \"\"\"\n    When I successfully run `hub pr list`\n    Then the output should contain exactly:\n      \"\"\"\n          #999  First\n          #102  Second\n           #13  Third\n            #7  Fourth\\n\n      \"\"\"\n\n  Scenario: List pull requests with requested reviewers\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/pulls') {\n      assert :per_page => \"100\",\n             :page => :no,\n             :sort => nil,\n             :direction => \"desc\"\n\n      json [\n        { :number => 999,\n          :title => \"First\",\n          :state => \"open\",\n          :base => {\n            :ref => \"master\",\n            :label => \"github:master\",\n            :repo => { :owner => { :login => \"github\" } }\n          },\n          :head => { :ref => \"patch-1\", :label => \"octocat:patch-1\" },\n          :user => { :login => \"octocat\" },\n          :requested_reviewers => [\n            { :login => \"rey\" },\n          ],\n          :requested_teams => [\n            { :slug => \"troopers\" },\n            { :slug => \"cantina-band\" },\n          ]\n        },\n        { :number => 102,\n          :title => \"Second\",\n          :state => \"open\",\n          :base => { :ref => \"master\", :label => \"github:master\" },\n          :head => { :ref => \"patch-2\", :label => \"octocat:patch-2\" },\n          :user => { :login => \"octocat\" },\n          :requested_reviewers => [\n            { :login => \"luke\" },\n            { :login => \"jyn\" },\n          ]\n        },\n      ]\n    }\n    \"\"\"\n    When I successfully run `hub pr list -f \"%sC%>(8)%i %rs%n\"`\n    Then the output should contain exactly:\n      \"\"\"\n          #999 rey, github/troopers, github/cantina-band\n          #102 luke, jyn\\n\n      \"\"\"\n\n  @keep-ansi-escape-sequences\n  Scenario: List draft status\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/pulls') {\n      halt 400 unless env['HTTP_ACCEPT'] == 'application/vnd.github.shadow-cat-preview+json;charset=utf-8'\n\n      json [\n        { :number => 999,\n          :state => \"open\",\n          :draft => true,\n          :merged_at => nil,\n          :base => { :ref => \"master\", :label => \"github:master\" },\n          :head => { :ref => \"patch-2\", :label => \"octocat:patch-2\" },\n          :user => { :login => \"octocat\" },\n        },\n        { :number => 102,\n          :state => \"open\",\n          :draft => false,\n          :merged_at => nil,\n          :base => { :ref => \"master\", :label => \"github:master\" },\n          :head => { :ref => \"patch-1\", :label => \"octocat:patch-1\" },\n          :user => { :login => \"octocat\" },\n        },\n        { :number => 42,\n          :state => \"closed\",\n          :draft => false,\n          :merged_at => \"2018-12-11T10:50:33Z\",\n          :base => { :ref => \"master\", :label => \"github:master\" },\n          :head => { :ref => \"patch-3\", :label => \"octocat:patch-3\" },\n          :user => { :login => \"octocat\" },\n        },\n        { :number => 8,\n          :state => \"closed\",\n          :draft => false,\n          :merged_at => nil,\n          :base => { :ref => \"master\", :label => \"github:master\" },\n          :head => { :ref => \"patch-4\", :label => \"octocat:patch-4\" },\n          :user => { :login => \"octocat\" },\n        },\n      ]\n    }\n    \"\"\"\n    When I successfully run `hub pr list --format \"%I %pC %pS %Creset%n\" --color`\n    Then its output should contain exactly:\n      \"\"\"\n      999 \\e[37m draft \\e[m\n      102 \\e[32m open \\e[m\n      42 \\e[35m merged \\e[m\n      8 \\e[31m closed \\e[m\\n\n      \"\"\"\n    When I successfully run `hub -c color.ui=always pr list --format \"%I %pC %pS %Creset%n\"`\n    Then its output should contain exactly:\n      \"\"\"\n      999 \\e[37m draft \\e[m\n      102 \\e[32m open \\e[m\n      42 \\e[35m merged \\e[m\n      8 \\e[31m closed \\e[m\\n\n      \"\"\"\n    When I successfully run `hub -c color.ui=false pr list --format \"%I %pC%pS%Creset%n\" --color=auto`\n    Then its output should contain exactly:\n      \"\"\"\n      999 draft\n      102 open\n      42 merged\n      8 closed\\n\n      \"\"\"\n\n  Scenario: Sort by number of comments ascending\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/pulls') {\n      assert :sort => \"comments\",\n             :direction => \"asc\"\n\n      json []\n    }\n    \"\"\"\n    When I successfully run `hub pr list -o comments -^`\n    Then the output should contain exactly \"\"\n\n  Scenario: Filter by base and head\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/pulls') {\n      assert :base => \"develop\",\n             :head => \"github:patch-1\"\n\n      json []\n    }\n    \"\"\"\n    When I successfully run `hub pr list -b develop -h patch-1`\n    Then the output should contain exactly \"\"\n\n  Scenario: Filter by head with owner\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/pulls') {\n      assert :head => \"mislav:patch-1\"\n\n      json []\n    }\n    \"\"\"\n    When I successfully run `hub pr list -h mislav:patch-1`\n    Then the output should contain exactly \"\"\n\n  Scenario: Filter by merged state\n    Given the GitHub API server:\n    \"\"\"\n    get('/repos/github/hub/pulls') {\n      assert :state => \"closed\"\n\n      json [\n        { :number => 999,\n          :title => \"First\",\n          :state => \"closed\",\n          :merged_at => \"2018-12-11T10:50:33Z\",\n          :base => { :ref => \"master\", :label => \"github:master\" },\n          :head => { :ref => \"patch-1\", :label => \"octocat:patch-1\" },\n          :user => { :login => \"octocat\" },\n        },\n        { :number => 102,\n          :title => \"Second\",\n          :state => \"closed\",\n          :merged_at => nil,\n          :base => { :ref => \"master\", :label => \"github:master\" },\n          :head => { :ref => \"patch-2\", :label => \"octocat:patch-2\" },\n          :user => { :login => \"octocat\" },\n        },\n        { :number => 13,\n          :title => \"Third\",\n          :state => \"closed\",\n          :merged_at => \"2018-12-11T10:50:33Z\",\n          :base => { :ref => \"master\", :label => \"github:master\" },\n          :head => { :ref => \"patch-3\", :label => \"octocat:patch-3\" },\n          :user => { :login => \"octocat\" },\n        },\n      ]\n    }\n    \"\"\"\n    When I successfully run `hub pr list --state=merged`\n    Then the output should contain exactly:\n      \"\"\"\n          #999  First\n           #13  Third\\n\n      \"\"\"\n"
  },
  {
    "path": "features/pr-merge.feature",
    "content": "Feature: hub pr merge\n  Background:\n    Given I am in \"git://github.com/friederbluemle/hub.git\" git repo\n    And I am \"friederbluemle\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Default merge\n    Given the GitHub API server:\n      \"\"\"\n      put('/repos/friederbluemle/hub/pulls/12/merge'){\n        assert :merge_method => \"merge\",\n               :commit_title => :no,\n               :commit_message => :no,\n               :sha => :no\n\n        json :merged => true,\n          :sha => \"MERGESHA\",\n          :message => \"All done!\"\n      }\n      \"\"\"\n    When I successfully run `hub pr merge 12`\n    Then the output should contain exactly \"\"\n\n  Scenario: Squash merge\n    Given the GitHub API server:\n      \"\"\"\n      put('/repos/friederbluemle/hub/pulls/12/merge'){\n        assert :merge_method => \"squash\",\n               :commit_title => :no,\n               :commit_message => :no,\n               :sha => :no\n\n        json :merged => true,\n          :sha => \"MERGESHA\",\n          :message => \"All done!\"\n      }\n      \"\"\"\n    When I successfully run `hub pr merge --squash 12`\n    Then the output should contain exactly \"\"\n\n  Scenario: Merge with rebase\n    Given the GitHub API server:\n      \"\"\"\n      put('/repos/friederbluemle/hub/pulls/12/merge'){\n        assert :merge_method => \"rebase\",\n               :commit_title => :no,\n               :commit_message => :no,\n               :sha => :no\n\n        json :merged => true,\n          :sha => \"MERGESHA\",\n          :message => \"All done!\"\n      }\n      \"\"\"\n    When I successfully run `hub pr merge --rebase 12`\n    Then the output should contain exactly \"\"\n\n  Scenario: Merge with title\n    Given the GitHub API server:\n      \"\"\"\n      put('/repos/friederbluemle/hub/pulls/12/merge'){\n        assert :commit_title => \"mytitle\",\n               :commit_message => \"\"\n\n        json :merged => true,\n          :sha => \"MERGESHA\",\n          :message => \"All done!\"\n      }\n      \"\"\"\n    When I successfully run `hub pr merge 12 -m mytitle`\n    Then the output should contain exactly \"\"\n\n  Scenario: Merge with title and body\n    Given the GitHub API server:\n      \"\"\"\n      put('/repos/friederbluemle/hub/pulls/12/merge'){\n        assert :commit_title => \"mytitle\",\n               :commit_message => \"msg1\\n\\nmsg2\"\n\n        json :merged => true,\n          :sha => \"MERGESHA\",\n          :message => \"All done!\"\n      }\n      \"\"\"\n    When I successfully run `hub pr merge 12 -m mytitle -m msg1 -m msg2`\n    Then the output should contain exactly \"\"\n\n  Scenario: Merge with title and body from file\n    Given a file named \"msg.txt\" with:\n      \"\"\"\n      mytitle\n\n      msg1\n\n      msg2\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      put('/repos/friederbluemle/hub/pulls/12/merge'){\n        assert :commit_title => \"mytitle\",\n               :commit_message => \"msg1\\n\\nmsg2\"\n\n        json :merged => true,\n          :sha => \"MERGESHA\",\n          :message => \"All done!\"\n      }\n      \"\"\"\n    When I successfully run `hub pr merge 12 -F msg.txt`\n    Then the output should contain exactly \"\"\n\n  Scenario: Merge with head SHA\n    Given the GitHub API server:\n      \"\"\"\n      put('/repos/friederbluemle/hub/pulls/12/merge'){\n        assert :sha => \"MYSHA\"\n\n        json :merged => true,\n          :sha => \"MERGESHA\",\n          :message => \"All done!\"\n      }\n      \"\"\"\n    When I successfully run `hub pr merge 12 --head-sha MYSHA`\n    Then the output should contain exactly \"\"\n\n  Scenario: Delete branch\n    Given the GitHub API server:\n      \"\"\"\n      put('/repos/friederbluemle/hub/pulls/12/merge'){\n        json :merged => true,\n          :sha => \"MERGESHA\",\n          :message => \"All done!\"\n      }\n\n      get('/repos/friederbluemle/hub/pulls/12'){\n        json \\\n          :number => 12,\n          :state => \"merged\",\n          :base => {\n            :ref => \"main\",\n            :label => \"friederbluemle:main\",\n            :repo => { :owner => { :login => \"friederbluemle\" } }\n          },\n          :head => {\n            :ref => \"patch-1\",\n            :label => \"friederbluemle:patch-1\",\n            :repo => { :owner => { :login => \"friederbluemle\" } }\n          }\n      }\n\n      delete('/repos/friederbluemle/hub/git/refs/heads/patch-1'){\n        status 204\n      }\n      \"\"\"\n    When I successfully run `hub pr merge -d 12`\n    Then the output should contain exactly \"\"\n\n  Scenario: Delete already deleted branch\n    Given the GitHub API server:\n      \"\"\"\n      put('/repos/friederbluemle/hub/pulls/12/merge'){\n        json :merged => true,\n          :sha => \"MERGESHA\",\n          :message => \"All done!\"\n      }\n\n      get('/repos/friederbluemle/hub/pulls/12'){\n        json \\\n          :number => 12,\n          :state => \"merged\",\n          :base => {\n            :ref => \"main\",\n            :label => \"friederbluemle:main\",\n            :repo => { :owner => { :login => \"friederbluemle\" } }\n          },\n          :head => {\n            :ref => \"patch-1\",\n            :label => \"friederbluemle:patch-1\",\n            :repo => { :owner => { :login => \"friederbluemle\" } }\n          }\n      }\n\n      delete('/repos/friederbluemle/hub/git/refs/heads/patch-1'){\n        status 422\n        json :message => \"Invalid branch name\"\n      }\n      \"\"\"\n    When I successfully run `hub pr merge -d 12`\n    Then the output should contain exactly \"\"\n\n  Scenario: Delete branch on cross-repo PR\n    Given the GitHub API server:\n      \"\"\"\n      put('/repos/friederbluemle/hub/pulls/12/merge'){\n        json :merged => true,\n          :sha => \"MERGESHA\",\n          :message => \"All done!\"\n      }\n\n      get('/repos/friederbluemle/hub/pulls/12'){\n        json \\\n          :number => 12,\n          :state => \"merged\",\n          :base => {\n            :ref => \"main\",\n            :label => \"friederbluemle:main\",\n            :repo => { :owner => { :login => \"friederbluemle\" } }\n          },\n          :head => {\n            :ref => \"patch-1\",\n            :label => \"monalisa:patch-1\",\n            :repo => { :owner => { :login => \"monalisa\" } }\n          }\n      }\n      \"\"\"\n    When I successfully run `hub pr merge -d 12`\n    Then the output should contain exactly \"\"\n"
  },
  {
    "path": "features/pr-show.feature",
    "content": "Feature: hub pr show\n  Background:\n    Given I am in \"git://github.com/ashemesh/hub.git\" git repo\n    And I am \"ashemesh\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: Current branch\n    Given I am on the \"topic\" branch\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/ashemesh/hub/pulls'){\n        assert :state => \"open\",\n               :head => \"ashemesh:topic\"\n        json [\n          { :html_url => \"https://github.com/ashemesh/hub/pull/102\" },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub pr show`\n    Then \"open https://github.com/ashemesh/hub/pull/102\" should be run\n\n  Scenario: Current branch output URL\n    Given I am on the \"topic\" branch\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/ashemesh/hub/pulls'){\n        assert :state => \"open\",\n               :head => \"ashemesh:topic\"\n        json [\n          { :html_url => \"https://github.com/ashemesh/hub/pull/102\" },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub pr show -u`\n    Then \"open https://github.com/ashemesh/hub/pull/102\" should not be run\n    And the output should contain exactly:\n      \"\"\"\n      https://github.com/ashemesh/hub/pull/102\\n\n      \"\"\" \n\n  Scenario: Format Current branch output URL\n    Given I am on the \"topic\" branch\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/ashemesh/hub/pulls'){\n        assert :state => \"open\",\n               :head => \"ashemesh:topic\"\n        json [{\n          :number => 102,\n          :state => \"open\",\n          :base => {\n            :ref => \"master\",\n            :label => \"github:master\",\n            :repo => { :owner => { :login => \"github\" } }\n          },\n          :head => { :ref => \"patch-1\", :label => \"octocat:patch-1\" },\n          :user => { :login => \"octocat\" },\n          :requested_reviewers => [\n            { :login => \"rey\" },\n          ],\n          :requested_teams => [\n            { :slug => \"troopers\" },\n            { :slug => \"cantina-band\" },\n          ],\n          :html_url => \"https://github.com/ashemesh/hub/pull/102\",\n        }]\n      }\n      \"\"\"\n    When I successfully run `hub pr show -f \"%sC%>(8)%i %rs%n\"`\n    Then \"open https://github.com/ashemesh/hub/pull/102\" should not be run\n    And the output should contain exactly:\n      \"\"\"\n          #102 rey, github/troopers, github/cantina-band\\n\\n\n      \"\"\"\n\n  Scenario: Current branch in fork\n    Given the \"upstream\" remote has url \"git@github.com:github/hub.git\"\n    And I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/github/hub/pulls'){\n        assert :state => \"open\",\n               :head => \"ashemesh:topic\"\n        json [\n          { :html_url => \"https://github.com/github/hub/pull/102\" },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub pr show`\n    Then \"open https://github.com/github/hub/pull/102\" should be run\n\n  Scenario: Differently named branch in fork\n    Given the \"upstream\" remote has url \"git@github.com:github/hub.git\"\n    And I am on the \"local-topic\" branch with upstream \"origin/remote-topic\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/github/hub/pulls'){\n        assert :head => \"ashemesh:remote-topic\"\n        json [\n          { :html_url => \"https://github.com/github/hub/pull/102\" },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub pr show`\n    Then \"open https://github.com/github/hub/pull/102\" should be run\n\n  Scenario: Upstream configuration with HTTPS URL\n    Given I am on the \"local-topic\" branch\n    When I successfully run `git config branch.local-topic.remote https://github.com/octocat/hub.git`\n    When I successfully run `git config branch.local-topic.merge refs/remotes/remote-topic`\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/ashemesh/hub/pulls'){\n        assert :head => \"octocat:remote-topic\"\n        json [\n          { :html_url => \"https://github.com/github/hub/pull/102\" },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub pr show`\n    Then \"open https://github.com/github/hub/pull/102\" should be run\n\n  Scenario: Upstream configuration with SSH URL\n    Given I am on the \"local-topic\" branch\n    When I successfully run `git config branch.local-topic.remote git@github.com:octocat/hub.git`\n    When I successfully run `git config branch.local-topic.merge refs/remotes/remote-topic`\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/ashemesh/hub/pulls'){\n        assert :head => \"octocat:remote-topic\"\n        json [\n          { :html_url => \"https://github.com/github/hub/pull/102\" },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub pr show`\n    Then \"open https://github.com/github/hub/pull/102\" should be run\n\n  Scenario: Explicit head branch\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/ashemesh/hub/pulls'){\n        assert :state => \"open\",\n               :head => \"ashemesh:topic\"\n        json [\n          { :html_url => \"https://github.com/ashemesh/hub/pull/102\" },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub pr show --head topic`\n    Then \"open https://github.com/ashemesh/hub/pull/102\" should be run\n\n  Scenario: Explicit head branch with owner\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/ashemesh/hub/pulls'){\n        assert :state => \"open\",\n               :head => \"github:topic\"\n        json [\n          { :html_url => \"https://github.com/ashemesh/hub/pull/102\" },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub pr show --head github:topic`\n    Then \"open https://github.com/ashemesh/hub/pull/102\" should be run\n\n  Scenario: No pull request found\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/ashemesh/hub/pulls'){\n        json []\n      }\n      \"\"\"\n    When I run `hub pr show --head topic`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      no open pull requests found for branch 'ashemesh:topic'\\n\n      \"\"\"\n\n  Scenario: Show pull request by number\n    When I successfully run `hub pr show 102`\n    Then \"open https://github.com/ashemesh/hub/pull/102\" should be run\n\n  Scenario: Format pull request by number\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/ashemesh/hub/pulls/102') {\n        json :number => 102,\n          :title => \"First\",\n          :state => \"open\",\n          :base => {\n            :ref => \"master\",\n            :label => \"github:master\",\n            :repo => { :owner => { :login => \"github\" } }\n          },\n          :head => { :ref => \"patch-1\", :label => \"octocat:patch-1\" },\n          :user => { :login => \"octocat\" },\n          :requested_reviewers => [\n            { :login => \"rey\" },\n          ],\n          :requested_teams => [\n            { :slug => \"troopers\" },\n            { :slug => \"cantina-band\" },\n          ]\n      }\n      \"\"\"\n    When I successfully run `hub pr show 102 -f \"%sC%>(8)%i %rs%n\"`\n    Then \"open https://github.com/ashemesh/hub/pull/102\" should not be run\n    And the output should contain exactly:\n      \"\"\"\n          #102 rey, github/troopers, github/cantina-band\\n\\n\n      \"\"\"\n\n  Scenario: Show pull request by invalid number\n    When I run `hub pr show XYZ`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      invalid pull request number: 'XYZ'\\n\n      \"\"\"\n"
  },
  {
    "path": "features/pull_request.feature",
    "content": "Feature: hub pull-request\n  Background:\n    Given I am in \"git://github.com/mislav/coral.git\" git repo\n    And I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n    And the git commit editor is \"vim\"\n\n  Scenario: Basic pull request\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the GitHub API server:\n      \"\"\"\n      KNOWN_PARAMS = %w[title body base head draft issue maintainer_can_modify]\n      post('/repos/mislav/coral/pulls') {\n        halt 400 unless request.env['HTTP_ACCEPT'] == 'application/vnd.github.shadow-cat-preview+json;charset=utf-8'\n        halt 400 unless request.user_agent.include?('Hub')\n        halt 400 if (params.keys - KNOWN_PARAMS).any?\n        assert :title => 'hello',\n               :body => nil,\n               :base => 'master',\n               :head => 'mislav:topic',\n               :maintainer_can_modify => true,\n               :draft => nil,\n               :issue => nil\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hello`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Detached HEAD\n    Given I am in detached HEAD\n    When I run `hub pull-request`\n    Then the stderr should contain \"Aborted: not currently on any branch.\\n\"\n    And the exit status should be 1\n\n  Scenario: Detached HEAD with explicit head\n    Given I am in detached HEAD\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :head => 'mislav:feature'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -h feature -m message`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Non-GitHub repo\n    Given the \"origin\" remote has url \"mygh:Manganeez/repo.git\"\n    When I run `hub pull-request`\n    Then the stderr should contain exactly:\n      \"\"\"\n      Aborted: could not find any git remote pointing to a GitHub repository\\n\n      \"\"\"\n    And the exit status should be 1\n\n  Scenario: Create pull request respecting \"insteadOf\" configuration\n    Given the \"origin\" remote has url \"mygh:Manganeez/repo.git\"\n    When I successfully run `git config url.\"git@github.com:\".insteadOf mygh:`\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/Manganeez/repo/pulls') {\n        assert :base  => 'master',\n               :head  => 'Manganeez:topic',\n               :title => 'here we go'\n        status 201\n        json :html_url => \"https://github.com/Manganeez/repo/pull/12\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m \"here we go\"`\n    Then the output should contain exactly \"https://github.com/Manganeez/repo/pull/12\\n\"\n\n  Scenario: With Unicode characters\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        halt 400 if request.content_charset != 'utf-8'\n        assert :title => 'ăéñøü'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m ăéñøü`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Invalid flag\n    When I run `hub pull-request -yelp`\n    Then the stderr should contain \"unknown shorthand flag: 'y' in -yelp\\n\"\n    And the exit status should be 1\n\n  Scenario: With Unicode characters in the changelog\n    Given the text editor adds:\n      \"\"\"\n      I <3 encodings\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        halt 400 if request.content_charset != 'utf-8'\n        assert :title => 'I <3 encodings',\n               :body => 'ăéñøü'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    Given I am on the \"master\" branch pushed to \"origin/master\"\n    When I successfully run `git checkout --quiet -b topic`\n    Given I make a commit with message \"ăéñøü\"\n    And the \"topic\" branch is pushed to \"origin/topic\"\n    When I successfully run `hub pull-request`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Default message for single-commit pull request\n    Given the text editor adds:\n      \"\"\"\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        halt 400 if request.content_charset != 'utf-8'\n        assert :title => 'This is somewhat of a longish title that does not get wrapped & references #1234',\n               :body => 'Hello'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    Given I am on the \"master\" branch pushed to \"origin/master\"\n    When I successfully run `git checkout --quiet -b topic`\n    Given I make a commit with message:\n      \"\"\"\n      This is somewhat of a longish title that does not get wrapped & references #1234\n\n      Hello\n      Signed-off-by: NAME <email@example.com>\n      Co-authored-by: NAME <email@example.com>\n      \"\"\"\n    And the \"topic\" branch is pushed to \"origin/topic\"\n    When I successfully run `hub pull-request`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Single-commit with pull request template\n    Given the git commit editor is \"true\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        halt 400 if request.content_charset != 'utf-8'\n        assert :title => 'Commit title',\n               :body => <<BODY.chomp\n      Commit body\n\n\n       This is the pull request template\n\n      Another line of template\n      BODY\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    Given I am on the \"master\" branch pushed to \"origin/master\"\n    When I successfully run `git checkout --quiet -b topic`\n    And I make a commit with message:\n      \"\"\"\n      Commit title\n\n      Commit body\n      \"\"\"\n    And the \"topic\" branch is pushed to \"origin/topic\"\n    Given a file named \"pull_request_template.md\" with:\n      \"\"\"\n       This is the pull request template\n\n      Another line of template\n      \"\"\"\n    When I successfully run `hub pull-request`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Single-commit with PULL_REQUEST_TEMPLATE directory\n    Given the git commit editor is \"true\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :title => 'Commit title',\n               :body => 'Commit body'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    Given I am on the \"master\" branch pushed to \"origin/master\"\n    When I successfully run `git checkout --quiet -b topic`\n    And I make a commit with message:\n      \"\"\"\n      Commit title\n\n      Commit body\n      \"\"\"\n    And the \"topic\" branch is pushed to \"origin/topic\"\n    And a directory named \"PULL_REQUEST_TEMPLATE\"\n    When I successfully run `hub pull-request`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Single-commit pull request with \"--no-edit\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :title => 'Commit title 1',\n               :body => 'Commit body 1'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    Given I am on the \"master\" branch pushed to \"origin/master\"\n    When I successfully run `git checkout --quiet -b topic`\n    Given I make a commit with message:\n      \"\"\"\n      Commit title 1\n\n      Commit body 1\n      \"\"\"\n    And the \"topic\" branch is pushed to \"origin/topic\"\n    When I successfully run `hub pull-request --no-edit`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Multiple-commit pull request with \"--no-edit\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :title => 'Commit title 1',\n               :body => 'Commit body 1'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    Given I am on the \"master\" branch pushed to \"origin/master\"\n    When I successfully run `git checkout --quiet -b topic`\n    Given I make a commit with message:\n      \"\"\"\n      Commit title 1\n\n      Commit body 1\n      \"\"\"\n    Given I make a commit with message:\n      \"\"\"\n      Commit title 2\n\n      Commit body 2\n      \"\"\"\n    And the \"topic\" branch is pushed to \"origin/topic\"\n    When I successfully run `hub pull-request --no-edit`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Pull request with \"--push\" and \"--no-edit\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :title => 'Commit title 1',\n               :body => 'Commit body 1'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    Given I am on the \"master\" branch pushed to \"origin/master\"\n    When I successfully run `git checkout --quiet -b topic`\n    Given I make a commit with message:\n      \"\"\"\n      Commit title 1\n\n      Commit body 1\n      \"\"\"\n    When I successfully run `hub pull-request --push --no-edit`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: No commits with \"--no-edit\"\n    Given I am on the \"master\" branch pushed to \"origin/master\"\n    When I successfully run `git checkout --quiet -b topic`\n    Given the \"topic\" branch is pushed to \"origin/topic\"\n    When I run `hub pull-request --no-edit`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Aborted: no commits detected between origin/master and topic\\n\n      \"\"\"\n\n  Scenario: Message template should include git log summary between base and head\n    Given the text editor adds:\n      \"\"\"\n      Hello\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        status 500\n      }\n      \"\"\"\n    Given I am on the \"master\" branch\n    And I make a commit with message \"One on master\"\n    And I make a commit with message \"Two on master\"\n    And the \"master\" branch is pushed to \"origin/master\"\n    Given I successfully run `git reset --hard HEAD~2`\n    And I successfully run `git checkout --quiet -B topic origin/master`\n    Given I make a commit with message \"One on topic\"\n    And I make a commit with message \"Two on topic\"\n    Given the \"topic\" branch is pushed to \"origin/topic\"\n    And I successfully run `git reset --hard HEAD~1`\n    When I run `hub pull-request`\n    Given the SHAs and timestamps are normalized in \".git/PULLREQ_EDITMSG\"\n    Then the file \".git/PULLREQ_EDITMSG\" should contain exactly:\n      \"\"\"\n      Hello\n\n\n      # ------------------------ >8 ------------------------\n      # Do not modify or remove the line above.\n      # Everything below it will be ignored.\n\n      Requesting a pull to mislav:master from mislav:topic\n\n      Write a message for this pull request. The first block\n      of text is the title and the rest is the description.\n\n      Changes:\n\n      SHA1SHA (Hub, 0 seconds ago)\n         Two on topic\n\n      SHA1SHA (Hub, 0 seconds ago)\n         One on topic\n\n      \"\"\"\n\n  Scenario: Non-existing base\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/origin/coral/pulls') { 404 }\n      \"\"\"\n    When I run `hub pull-request -b origin:master -h topic -m here`\n    Then the exit status should be 1\n    Then the stderr should contain:\n      \"\"\"\n      Error creating pull request: Not Found (HTTP 404)\n      Are you sure that github.com/origin/coral exists?\n      \"\"\"\n\n  Scenario: Text editor adds title and body\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the text editor adds:\n      \"\"\"\n      This title comes from vim!\n\n      This body as well.\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :title => 'This title comes from vim!',\n               :body  => 'This body as well.'\n        status 201\n        json :html_url => \"https://github.com/mislav/coral/pull/12\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request`\n    Then the output should contain exactly \"https://github.com/mislav/coral/pull/12\\n\"\n    And the file \".git/PULLREQ_EDITMSG\" should not exist\n\n  Scenario: Text editor adds title and body with multiple lines\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the text editor adds:\n      \"\"\"\n\n\n      This title is on the third line\n\n\n      This body\n\n\n      has multiple\n      lines.\n\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :title => 'This title is on the third line',\n               :body  => \"This body\\n\\n\\nhas multiple\\nlines.\"\n        status 201\n        json :html_url => \"https://github.com/mislav/coral/pull/12\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request`\n    Then the output should contain exactly \"https://github.com/mislav/coral/pull/12\\n\"\n    And the file \".git/PULLREQ_EDITMSG\" should not exist\n\n  Scenario: Text editor with custom commentchar\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given git \"core.commentchar\" is set to \"/\"\n    And the text editor adds:\n      \"\"\"\n      # Dat title\n\n      / This line is not commented out.\n\n      Dem body.\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :title => '# Dat title',\n               :body  => \"/ This line is not commented out.\\n\\nDem body.\"\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Failed pull request preserves previous message\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the text editor adds:\n      \"\"\"\n      This title will fail\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        halt 422 if params[:title].include?(\"fail\")\n        assert :body => \"This title will fail\",\n               :title => \"But this title will prevail\"\n        status 201\n        json :html_url => \"https://github.com/mislav/coral/pull/12\"\n      }\n      \"\"\"\n    When I run `hub pull-request`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Error creating pull request: Unprocessable Entity (HTTP 422)\\n\n      \"\"\"\n    Given the text editor adds:\n      \"\"\"\n      But this title will prevail\n      \"\"\"\n    When I successfully run `hub pull-request`\n    Then the file \".git/PULLREQ_EDITMSG\" should not exist\n\n  Scenario: Text editor fails\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the text editor exits with error status\n    And an empty file named \".git/PULLREQ_EDITMSG\"\n    When I run `hub pull-request`\n    Then the stderr should contain \"error using text editor for pull request message\"\n    And the exit status should be 1\n    And the file \".git/PULLREQ_EDITMSG\" should not exist\n\n  Scenario: Title and body from file\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :title => 'Title from file',\n               :body  => \"Body from file as well.\\n\\nMultiline, even!\"\n        status 201\n        json :html_url => \"https://github.com/mislav/coral/pull/12\"\n      }\n      \"\"\"\n    And a file named \"pullreq-msg\" with:\n      \"\"\"\n      Title from file\n\n      Body from file as well.\n\n      Multiline, even!\n      \"\"\"\n    When I successfully run `hub pull-request -F pullreq-msg`\n    Then the output should contain exactly \"https://github.com/mislav/coral/pull/12\\n\"\n    And the file \".git/PULLREQ_EDITMSG\" should not exist\n\n  Scenario: Edit title and body from file\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :title => 'Hello from editor',\n               :body  => \"Title from file\\n\\nBody from file as well.\"\n        status 201\n        json :html_url => \"https://github.com/mislav/coral/pull/12\"\n      }\n      \"\"\"\n    And a file named \"pullreq-msg\" with:\n      \"\"\"\n      Title from file\n\n      Body from file as well.\n      \"\"\"\n    And the text editor adds:\n      \"\"\"\n      Hello from editor\n      \"\"\"\n    When I successfully run `hub pull-request -F pullreq-msg --edit`\n    Then the file \".git/PULLREQ_EDITMSG\" should not exist\n\n  Scenario: Title and body from stdin\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :title => 'Unix piping is great',\n               :body  => 'Just look at this ăéñøü'\n        status 201\n        json :html_url => \"https://github.com/mislav/coral/pull/12\"\n      }\n      \"\"\"\n    When I run `hub pull-request -F -` interactively\n    And I pass in:\n      \"\"\"\n      Unix piping is great\n\n      Just look at this ăéñøü\n      \"\"\"\n    Then the output should contain exactly \"https://github.com/mislav/coral/pull/12\\n\"\n    And the exit status should be 0\n    And the file \".git/PULLREQ_EDITMSG\" should not exist\n\n  Scenario: Title and body from command-line argument\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :title => 'I am just a pull',\n               :body  => 'A little pull'\n        status 201\n        json :html_url => \"https://github.com/mislav/coral/pull/12\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m \"I am just a pull\\n\\nA little pull\"`\n    Then the output should contain exactly \"https://github.com/mislav/coral/pull/12\\n\"\n    And the file \".git/PULLREQ_EDITMSG\" should not exist\n\n  Scenario: Title and body from multiple command-line arguments\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :title => 'I am just a pull',\n               :body  => \"A little pull\\n\\nAnd description\"\n        status 201\n        json :html_url => \"https://github.com/mislav/coral/pull/12\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m \"I am just a pull\" -m \"A little pull\" -m \"And description\"`\n    Then the output should contain exactly \"https://github.com/mislav/coral/pull/12\\n\"\n\n  Scenario: Error when implicit head is the same as base\n    Given I am on the \"master\" branch with upstream \"origin/master\"\n    When I run `hub pull-request`\n    Then the stderr should contain exactly:\n      \"\"\"\n      Aborted: head branch is the same as base (\"master\")\n      (use `-h <branch>` to specify an explicit pull request head)\\n\n      \"\"\"\n\n  Scenario: Explicit head\n    Given I am on the \"master\" branch\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :head => 'mislav:feature'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -h feature -m message`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Explicit head with owner\n    Given I am on the \"master\" branch\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :head => 'mojombo:feature'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -h mojombo:feature -m message`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Explicit base\n    Given I am on the \"feature\" branch pushed to \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :base => 'develop',\n               :head => 'mislav:feature'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -b develop -m message`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Implicit base by detecting main branch\n    Given the default branch for \"origin\" is \"develop\"\n    And the \"master\" branch is pushed to \"origin/master\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :base => 'develop',\n               :head => 'mislav:master'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m message`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Explicit base with owner\n    Given I am on the \"master\" branch pushed to \"origin/master\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mojombo/coral/pulls') {\n        assert :base => 'develop'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -b mojombo:develop -m message`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Explicit base with owner and repo name\n    Given I am on the \"master\" branch pushed to \"origin/master\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mojombo/coralify/pulls') {\n        assert :base => 'develop'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -b mojombo/coralify:develop -m message`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Error when there are unpushed commits\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    When I make 2 commits\n    And I run `hub pull-request`\n    Then the stderr should contain exactly:\n      \"\"\"\n      Aborted: 2 commits are not yet pushed to origin/feature\n      (use `-f` to force submit a pull request anyway)\\n\n      \"\"\"\n\n  Scenario: Ignore unpushed commits with `-f`\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :head => 'mislav:feature'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I make 2 commits\n    And I successfully run `hub pull-request -f -m message`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Error from an unpushed branch\n    Given I am on the \"feature\" branch\n    When I run `hub pull-request -m hello`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Aborted: the current branch seems not yet pushed to a remote\n      (use `-p` to push the branch or `-f` to skip this check)\\n\n      \"\"\"\n\n  Scenario: Error from an unpushed branch with upstream same as base branch\n    Given I am on the \"feature\" branch with upstream \"origin/master\"\n    When I run `hub pull-request -m hello`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Aborted: the current branch seems not yet pushed to a remote\n      (use `-p` to push the branch or `-f` to skip this check)\\n\n      \"\"\"\n\n  Scenario: Pull request fails on the server\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      tries = 0\n      post('/repos/mislav/coral/pulls') {\n        tries += 1\n        if tries == 1\n          status 422\n          json :message => 'Validation Failed',\n               :errors => [{\n                 :resource => 'PullRequest',\n                 :code => 'invalid',\n                 :field => 'head'\n               }]\n        else\n          status 400\n        end\n      }\n      \"\"\"\n    When I run `hub pull-request -m message`\n    Then the stderr should contain exactly:\n      \"\"\"\n      Error creating pull request: Unprocessable Entity (HTTP 422)\n      Invalid value for \"head\"\\n\n      \"\"\"\n    And the exit status should be 1\n\n  Scenario: Convert issue to pull request\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :issue => 92\n        status 201\n        json :html_url => \"https://github.com/mislav/coral/pull/92\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -i 92`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/mislav/coral/pull/92\\n\n      \"\"\"\n\n  Scenario: Convert issue URL to pull request\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :issue => 92\n        status 201\n        json :html_url => \"https://github.com/mislav/coral/pull/92\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request https://github.com/mislav/coral/issues/92`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/mislav/coral/pull/92\\n\n      \"\"\"\n\n  Scenario: Enterprise host\n    Given the \"origin\" remote has url \"git@git.my.org:mislav/coral.git\"\n    And I am \"mislav\" on git.my.org with OAuth token \"FITOKEN\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    And I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/api/v3/repos/mislav/coral/pulls', :host_name => 'git.my.org') {\n        assert :base => 'master',\n               :head => 'mislav:topic'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m enterprisey`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Enterprise remote witch matching branch but no tracking\n    Given the \"origin\" remote has url \"git@git.my.org:mislav/coral.git\"\n    And I am \"mislav\" on git.my.org with OAuth token \"FITOKEN\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    And I am on the \"feature\" branch pushed to \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/api/v3/repos/mislav/coral/pulls', :host_name => 'git.my.org') {\n        assert :base => 'master',\n               :head => 'mislav:feature'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m enterprisey`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Create pull request from branch on the same remote\n    Given the \"origin\" remote has url \"git://github.com/github/coral.git\"\n    And the \"mislav\" remote has url \"git://github.com/mislav/coral.git\"\n    And I am on the \"feature\" branch pushed to \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/coral/pulls') {\n        assert :base  => 'master',\n               :head  => 'github:feature',\n               :title => 'hereyougo'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Create pull request from branch on the personal fork case sensitive\n    Given the \"origin\" remote has url \"git://github.com/github/coral.git\"\n    And the \"doge\" remote has url \"git://github.com/Mislav/coral.git\"\n    And I am on the \"feature\" branch pushed to \"doge/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/coral/pulls') {\n        assert :base  => 'master',\n               :head  => 'Mislav:feature',\n               :title => 'hereyougo'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Create pull request from branch on the personal fork\n    Given the \"origin\" remote has url \"git://github.com/github/coral.git\"\n    And the \"doge\" remote has url \"git://github.com/mislav/coral.git\"\n    And I am on the \"feature\" branch pushed to \"doge/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/coral/pulls') {\n        assert :base  => 'master',\n               :head  => 'mislav:feature',\n               :title => 'hereyougo'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Create pull request from branch on the personal fork, capitalized\n    Given the \"origin\" remote has url \"git://github.com/LightAlf/FirstRepo.git\"\n    And the \"Kristinita\" remote has url \"git@github.com:Kristinita/FirstRepo.git\"\n    And I am on the \"add-py3kwarn\" branch pushed to \"Kristinita/add-py3kwarn\"\n    And I am \"Kristinita\" on github.com with OAuth token \"OTOKEN\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/LightAlf/FirstRepo/pulls') {\n        assert :base  => 'master',\n               :head  => 'Kristinita:add-py3kwarn',\n               :title => 'hereyougo'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Create pull request to \"upstream\" remote\n    Given the \"upstream\" remote has url \"git://github.com/github/coral.git\"\n    And I am on the \"master\" branch pushed to \"origin/master\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/coral/pulls') {\n        assert :base  => 'master',\n               :head  => 'mislav:master',\n               :title => 'hereyougo'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Create pull request to \"upstream\" remote with differently-named default branch\n    Given I am on the \"master\" branch pushed to \"origin/master\"\n    And the \"upstream\" remote has url \"git://github.com/github/coral.git\"\n    And the default branch for \"upstream\" is \"develop\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/coral/pulls') {\n        assert :base  => 'develop',\n               :head  => 'mislav:master',\n               :title => 'hereyougo'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Create pull request to \"github\" remote when \"upstream\" is non-GitHub\n    Given I am on the \"master\" branch pushed to \"origin/master\"\n    And the \"github\" remote has url \"git://github.com/github/coral.git\"\n    And the \"upstream\" remote has url \"git://example.com/coral.git\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/coral/pulls') {\n        assert :base  => 'master',\n               :head  => 'mislav:master',\n               :title => 'hereyougo'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Create pull request to \"github\" remote when \"origin\" is non-GitHub\n    Given the \"github\" remote has url \"git@github.com:sam-hart-swanson/debug.git\"\n    Given the \"origin\" remote has url \"ssh://git@private.server.com/path/to/repo.git\"\n    And I am on the \"feat/123-some-branch\" branch pushed to \"github/feat/123-some-branch\"\n    And an empty file named \".git/refs/remotes/origin/feat/123-some-branch\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/sam-hart-swanson/debug/pulls') {\n        assert :base  => 'master',\n               :head  => 'sam-hart-swanson:feat/123-some-branch',\n               :title => 'hereyougo'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Open pull request in web browser\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -o -m hereyougo`\n    Then \"open the://url\" should be run\n\n  Scenario: Current branch is tracking local branch\n    Given git \"push.default\" is set to \"upstream\"\n    And I am on the \"feature\" branch pushed to \"origin/feature\"\n    When I successfully run `git config branch.feature.remote .`\n    When I successfully run `git config branch.feature.merge refs/heads/master`\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :base  => 'master',\n               :head  => 'mislav:feature'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Current branch is pushed to remote without upstream configuration\n    Given the \"upstream\" remote has url \"git://github.com/lestephane/coral.git\"\n    And I am on the \"feature\" branch pushed to \"origin/feature\"\n    And git \"push.default\" is set to \"upstream\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/lestephane/coral/pulls') {\n        assert :base  => 'master',\n               :head  => 'mislav:feature'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Branch with quotation mark in name\n    Given I am on the \"feat'ure\" branch with upstream \"origin/feat'ure\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :head  => \"mislav:feat'ure\"\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Pull request with assignees\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :head  => \"mislav:feature\"\n        status 201\n        json :html_url => \"the://url\", :number => 1234\n      }\n      patch('/repos/mislav/coral/issues/1234') {\n        assert :assignees => [\"mislav\", \"josh\", \"pcorpet\"], :labels => :no\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo -a mislav,josh -apcorpet`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Pull request with reviewers\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :head  => \"mislav:feature\"\n        status 201\n        json :html_url => \"the://url\", :number => 1234\n      }\n      post('/repos/mislav/coral/pulls/1234/requested_reviewers') {\n        assert :reviewers => [\"mislav\", \"josh\", \"pcorpet\"]\n        assert :team_reviewers => [\"robots\", \"js\"]\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo -r mislav,josh -rgithub/robots -rpcorpet -r github/js`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Pull request with reviewers from CODEOWNERS\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :head  => \"mislav:feature\"\n        status 201\n        json :html_url => \"the://url\", :number => 1234,\n          :requested_reviewers => [{ :login => \"josh\" }],\n          :requested_teams => [{ :slug => \"robots\" }]\n      }\n      post('/repos/mislav/coral/pulls/1234/requested_reviewers') {\n        assert :reviewers => [\"mislav\", \"pcorpet\"]\n        assert :team_reviewers => [\"js\"]\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo -r mislav,josh -rgithub/robots -rpcorpet -r github/js`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Pull request avoids re-requesting reviewers\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :head  => \"mislav:feature\"\n        status 201\n        json :html_url => \"the://url\", :number => 1234,\n          :requested_reviewers => [{ :login => \"josh\" }, { :login => \"mislav\" }],\n          :requested_teams => [{ :slug => \"robots\" }]\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo -r mislav,josh -rgithub/robots`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Requesting reviewers failed\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        status 201\n        json :html_url => \"the://url\", :number => 1234\n      }\n      post('/repos/mislav/coral/pulls/1234/requested_reviewers') {\n        status 422\n        json :message => \"Validation Failed\",\n          :errors => [\"Could not add requested reviewers to pull request.\"],\n          :documentation_url => \"https://developer.github.com/v3/pulls/review_requests/#create-a-review-request\"\n      }\n      \"\"\"\n    When I run `hub pull-request -m hereyougo -r pedrohc`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Error requesting reviewer: Unprocessable Entity (HTTP 422)\n      Could not add requested reviewers to pull request.\\n\n      \"\"\"\n\n  Scenario: Pull request with milestone\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/coral/milestones') {\n        status 200\n        json [\n          { :number => 237, :title => \"prerelease\" },\n          { :number => 1337, :title => \"v1\" },\n          { :number => 41319, :title => \"Hello World!\" }\n        ]\n      }\n      post('/repos/mislav/coral/pulls') {\n        assert :head  => \"mislav:feature\"\n        status 201\n        json :html_url => \"the://url\", :number => 1234\n      }\n      patch('/repos/mislav/coral/issues/1234') {\n        assert :milestone => 41319\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo -M \"Hello World!\"`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Pull request with case-insensitive milestone\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/coral/milestones') {\n        status 200\n        json [\n          { :number => 237, :title => \"prerelease\" },\n          { :number => 1337, :title => \"v1\" },\n          { :number => 41319, :title => \"Hello World!\" }\n        ]\n      }\n      post('/repos/mislav/coral/pulls') {\n        assert :head  => \"mislav:feature\"\n        status 201\n        json :html_url => \"the://url\", :number => 1234\n      }\n      patch('/repos/mislav/coral/issues/1234') {\n        assert :milestone => 41319\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo -M \"hello world!\"`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Pull request uses integer milestone number for BC\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/coral/milestones') {\n        status 200\n        json [{ :number => 237, :title => \"prerelease\" }]\n      }\n      post('/repos/mislav/coral/pulls') {\n        assert :head  => \"mislav:feature\"\n        status 201\n        json :html_url => \"the://url\", :number => 1234\n      }\n      patch('/repos/mislav/coral/issues/1234') {\n        assert :milestone => 55\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo -M 55`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Pull request fails with unknown milestone before it's created\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/coral/milestones') {\n        status 200\n        json []\n      }\n      \"\"\"\n    When I run `hub pull-request -m hereyougo -M \"unknown\"`\n    Then the exit status should be 1\n    And the stderr should contain exactly \"error: no milestone found with name 'unknown'\\n\"\n\n  Scenario: Pull request with labels\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :head  => \"mislav:feature\"\n        status 201\n        json :html_url => \"the://url\", :number => 1234\n      }\n      patch('/repos/mislav/coral/issues/1234') {\n        assert :labels => [\"feature\", \"release\", \"docs\"], :assignees => :no\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo -l feature,release -ldocs`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Pull request to a fetch-only upstream\n    Given the \"upstream\" remote has url \"git://github.com/github/coral.git\"\n    And the \"upstream\" remote has push url \"no_push\"\n    And I am on the \"master\" branch pushed to \"origin/master\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/coral/pulls') {\n        assert :base  => 'master',\n               :head  => 'mislav:master',\n               :title => 'hereyougo'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Pull request with 307 redirect\n    Given the \"origin\" remote has url \"https://github.com/mislav/coral.git\"\n    And I am on the \"feature\" branch pushed to \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/coral') {\n        redirect 'https://api.github.com/repositories/12345', 301\n      }\n      get('/repositories/12345') {\n        json :name => 'coralify', :owner => { :login => 'coral-org' }\n      }\n      post('/repos/mislav/coral/pulls') {\n        redirect 'https://api.github.com/repositories/12345', 307\n      }\n      post('/repositories/12345', :host_name => 'api.github.com') {\n        assert :base  => 'master',\n               :head  => 'coral-org:feature',\n               :title => 'hereyougo'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hereyougo`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Pull request with 301 redirect\n    Given the \"origin\" remote has url \"https://github.com/mislav/coral.git\"\n    And I am on the \"feature\" branch pushed to \"origin/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/coral') {\n        redirect 'https://api.github.com/repositories/12345', 301\n      }\n      get('/repositories/12345') {\n        json :name => 'coralify', :owner => { :login => 'coral-org' }\n      }\n      post('/repos/mislav/coral/pulls') {\n        redirect 'https://api.github.com/repositories/12345/pulls', 301\n      }\n      post('/repositories/12345/pulls', :host_name => 'api.github.com') {\n        assert :base  => 'master',\n               :head  => 'coral-org:feature',\n               :title => 'hereyougo'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I run `hub pull-request -m hereyougo`\n    Then the exit status should be 1\n    And stderr should contain exactly:\n      \"\"\"\n      Error creating pull request: Post https://api.github.com/repositories/12345/pulls: refusing to follow HTTP 301 redirect for a POST request\n      Have your site admin use HTTP 308 for this kind of redirect\\n\n      \"\"\"\n\n  Scenario: Default message with --push\n    Given the git commit editor is \"true\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :title => 'The commit I never pushed',\n               :body => nil\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    Given I am on the \"master\" branch pushed to \"origin/master\"\n    When I successfully run `git checkout --quiet -b topic`\n    Given I make a commit with message \"The commit I never pushed\"\n    When I successfully run `hub pull-request -p`\n    Then the output should contain exactly \"the://url\\n\"\n    And \"git push --set-upstream origin HEAD:topic\" should be run\n\n  Scenario: Text editor fails with --push\n    Given the text editor exits with error status\n    And I am on the \"master\" branch pushed to \"origin/master\"\n    And an empty file named \".git/PULLREQ_EDITMSG\"\n    When I successfully run `git checkout --quiet -b topic`\n    Given I make a commit\n    When I run `hub pull-request -p`\n    Then the stderr should contain \"error using text editor for pull request message\"\n    And the exit status should be 1\n    And the file \".git/PULLREQ_EDITMSG\" should not exist\n    And \"git push --set-upstream origin HEAD:topic\" should not be run\n\n  Scenario: Triangular workflow with --push\n    Given the \"upstream\" remote has url \"git://github.com/github/coral.git\"\n    And I am on the \"master\" branch pushed to \"upstream/master\"\n    # TODO: head should be \"mislav:topic\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/github/coral/pulls') {\n        assert :base  => 'master',\n               :head  => 'github:topic',\n               :title => 'hereyougo'\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `git checkout --quiet -b topic`\n    Given I make a commit with message \"Fork commit\"\n    When I successfully run `hub pull-request -p -m hereyougo`\n    Then the output should contain exactly \"the://url\\n\"\n    # TODO: the push should be to the \"origin\" remote instead\n    And \"git push --set-upstream upstream HEAD:topic\" should be run\n\n  Scenario: Automatically retry when --push resulted in 422\n    Given the default aruba exit timeout is 7 seconds\n    And the text editor adds:\n      \"\"\"\n      hello!\n      \"\"\"\n    Given the GitHub API server:\n      \"\"\"\n      first_try_at = nil\n      tries = 0\n\n      post('/repos/mislav/coral/pulls') {\n        tries += 1\n        assert :title => 'hello!', :head => 'mislav:topic'\n\n        if !first_try_at || (Time.now - first_try_at) < 5\n          first_try_at ||= Time.now\n          status 422\n          json :message => 'Validation Failed',\n               :errors => [{\n                 :resource => 'PullRequest',\n                 :code => 'invalid',\n                 :field => 'head'\n               }]\n        else\n          status 201\n          json :html_url => \"the://url?tries=#{tries}\"\n        end\n      }\n      \"\"\"\n    Given I am on the \"topic\" branch\n    When I successfully run `hub pull-request -p`\n    Then the output should contain exactly \"the://url?tries=3\\n\"\n    And the file \".git/PULLREQ_EDITMSG\" should not exist\n\n  Scenario: Eventually give up on retries for --push\n    Given the default aruba exit timeout is 7 seconds\n    And the text editor adds:\n      \"\"\"\n      hello!\n      \"\"\"\n    And $HUB_RETRY_TIMEOUT is \"5\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        status 422\n        json :message => 'Validation Failed',\n             :errors => [{\n               :resource => 'PullRequest',\n               :code => 'invalid',\n               :field => 'head'\n             }]\n      }\n      \"\"\"\n    Given I am on the \"topic\" branch\n    When I run `hub pull-request -p`\n    Then the stderr should contain:\n      \"\"\"\n      Error creating pull request: Unprocessable Entity (HTTP 422)\n      Invalid value for \"head\"\\n\n      \"\"\"\n    And the output should match /Given up after retrying for 5\\.\\d seconds\\./\n    And a file named \".git/PULLREQ_EDITMSG\" should exist\n\n  Scenario: Draft pull request\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        halt 400 unless request.env['HTTP_ACCEPT'] == 'application/vnd.github.shadow-cat-preview+json;charset=utf-8'\n        assert :draft => true\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -d -m wip`\n    Then the output should contain exactly \"the://url\\n\"\n\n  Scenario: Disallow edits from maintainers\n    Given I am on the \"topic\" branch pushed to \"origin/topic\"\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/coral/pulls') {\n        assert :maintainer_can_modify => false\n        status 201\n        json :html_url => \"the://url\"\n      }\n      \"\"\"\n    When I successfully run `hub pull-request -m hello --no-maintainer-edits`\n    Then the output should contain exactly \"the://url\\n\"\n"
  },
  {
    "path": "features/push.feature",
    "content": "Feature: hub push\n  Background:\n    Given I am in \"git://github.com/mislav/coral.git\" git repo\n\n  Scenario: Normal push\n    When I successfully run `hub push`\n    Then the git command should be unchanged\n\n  Scenario: Push current branch to multiple remotes\n    Given I am on the \"cool-feature\" branch\n    When I successfully run `hub push origin,staging`\n    Then \"git push origin cool-feature\" should be run\n    Then \"git push staging cool-feature\" should be run\n\n  Scenario: Push explicit branch to multiple remotes\n    When I successfully run `hub push origin,staging,qa cool-feature`\n    Then \"git push origin cool-feature\" should be run\n    Then \"git push staging cool-feature\" should be run\n    Then \"git push qa cool-feature\" should be run\n\n  Scenario: Push multiple refs to multiple remotes\n    When I successfully run `hub push origin,staging master new-feature`\n    Then \"git push origin master new-feature\" should be run\n    Then \"git push staging master new-feature\" should be run\n"
  },
  {
    "path": "features/release.feature",
    "content": "Feature: hub release\n\n  Background:\n    Given I am in \"git://github.com/mislav/will_paginate.git\" git repo\n    And I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n\n  Scenario: List non-draft releases\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { tag_name: 'v1.2.0',\n            name: 'will_paginate 1.2.0',\n            draft: true,\n            prerelease: false,\n          },\n          { tag_name: 'v1.2.0-pre',\n            name: 'will_paginate 1.2.0-pre',\n            draft: false,\n            prerelease: true,\n          },\n          { tag_name: 'v1.0.2',\n            name: 'will_paginate 1.0.2',\n            draft: false,\n            prerelease: false,\n          },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub release`\n    Then the output should contain exactly:\n      \"\"\"\n      v1.2.0-pre\n      v1.0.2\\n\n      \"\"\"\n\n  Scenario: List non-prerelease releases\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { tag_name: 'v1.2.0',\n            name: 'will_paginate 1.2.0',\n            draft: true,\n            prerelease: false,\n          },\n          { tag_name: 'v1.2.0-pre',\n            name: 'will_paginate 1.2.0-pre',\n            draft: false,\n            prerelease: true,\n          },\n          { tag_name: 'v1.0.2',\n            name: 'will_paginate 1.0.2',\n            draft: false,\n            prerelease: false,\n          },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub release --exclude-prereleases`\n    Then the output should contain exactly:\n      \"\"\"\n      v1.0.2\\n\n      \"\"\"\n\n  Scenario: List all releases\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { tag_name: 'v1.2.0',\n            name: 'will_paginate 1.2.0',\n            draft: true,\n            prerelease: false,\n          },\n          { tag_name: 'v1.2.0-pre',\n            name: 'will_paginate 1.2.0-pre',\n            draft: false,\n            prerelease: true,\n          },\n          { tag_name: 'v1.0.2',\n            name: 'will_paginate 1.0.2',\n            draft: false,\n            prerelease: false,\n          },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub release --include-drafts`\n    Then the output should contain exactly:\n      \"\"\"\n      v1.2.0\n      v1.2.0-pre\n      v1.0.2\\n\n      \"\"\"\n\n  Scenario: Fetch releases across multiple pages\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        assert :per_page => \"100\", :page => :no\n        response.headers[\"Link\"] = %(<https://api.github.com/repositories/12345?per_page=100&page=2>; rel=\"next\")\n        json [\n          { tag_name: 'v1.2.0',\n            name: 'will_paginate 1.2.0',\n            draft: false,\n            prerelease: false,\n          },\n        ]\n      }\n\n      get('/repositories/12345') {\n        assert :per_page => \"100\"\n        if params[:page] == \"2\"\n          response.headers[\"Link\"] = %(<https://api.github.com/repositories/12345?per_page=100&page=3>; rel=\"next\")\n          json [\n            { tag_name: 'v1.2.0-pre',\n              name: 'will_paginate 1.2.0-pre',\n              draft: false,\n              prerelease: true,\n            },\n            { tag_name: 'v1.0.2',\n              name: 'will_paginate 1.0.2',\n              draft: false,\n              prerelease: false,\n            },\n          ]\n        elsif params[:page] == \"3\"\n          json [\n            { tag_name: 'v1.0.0',\n              name: 'will_paginate 1.0.0',\n              draft: false,\n              prerelease: true,\n            },\n          ]\n        else\n          status 400\n        end\n      }\n      \"\"\"\n      When I successfully run `hub release`\n      Then the output should contain exactly:\n      \"\"\"\n      v1.2.0\n      v1.2.0-pre\n      v1.0.2\n      v1.0.0\\n\n      \"\"\"\n\n  Scenario: List limited number of releases\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        response.headers[\"Link\"] = %(<https://api.github.com/repositories/12345?per_page=100&page=2>; rel=\"next\")\n        assert :per_page => \"3\"\n        json [\n          { tag_name: 'v1.2.0',\n            name: 'will_paginate 1.2.0',\n            draft: false,\n            prerelease: false,\n          },\n          { tag_name: 'v1.2.0-pre',\n            name: 'will_paginate 1.2.0-pre',\n            draft: false,\n            prerelease: true,\n          },\n          { tag_name: 'v1.0.2',\n            name: 'will_paginate 1.0.2',\n            draft: false,\n            prerelease: false,\n          },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub release -L 2`\n    Then the output should contain exactly:\n      \"\"\"\n      v1.2.0\n      v1.2.0-pre\\n\n      \"\"\"\n\n  Scenario: Pretty-print releases\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { tag_name: 'v1.2.0',\n            name: 'will_paginate 1.2.0',\n            draft: true,\n            prerelease: false,\n            created_at: '2018-02-27T19:35:32Z',\n            published_at: '2018-04-01T19:35:32Z',\n            assets: [\n              {browser_download_url: 'the://url', label: ''},\n            ],\n          },\n          { tag_name: 'v1.2.0-pre',\n            name: 'will_paginate 1.2.0-pre',\n            draft: false,\n            prerelease: true,\n          },\n          { tag_name: 'v1.0.2',\n            name: 'will_paginate 1.0.2',\n            draft: false,\n            prerelease: false,\n          },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub release --include-drafts --format='%t (%S)%n'`\n    Then the output should contain exactly:\n      \"\"\"\n      will_paginate 1.2.0 (draft)\n      will_paginate 1.2.0-pre (pre-release)\n      will_paginate 1.0.2 ()\\n\n      \"\"\"\n\n  Scenario: Repository not found when listing releases\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        status 404\n        json message: \"Not Found\",\n             documentation_url: \"https://developer.github.com/v3\"\n      }\n      \"\"\"\n    When I run `hub release`\n    Then the stderr should contain exactly:\n      \"\"\"\n      Error fetching releases: Not Found (HTTP 404)\n      Not Found\\n\n      \"\"\"\n    And the exit status should be 1\n\n  Scenario: Server error when listing releases\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        status 504\n        '<html><title>Its fine</title></html>'\n      }\n      \"\"\"\n    When I run `hub release`\n    Then the stderr should contain exactly:\n      \"\"\"\n      Error fetching releases: invalid character '<' looking for beginning of value (HTTP 504)\\n\n      \"\"\"\n    And the exit status should be 1\n\n  Scenario: Show specific release\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { tag_name: 'v1.2.0',\n            name: 'will_paginate 1.2.0',\n            draft: true,\n            prerelease: false,\n            tarball_url: \"https://github.com/mislav/will_paginate/archive/v1.2.0.tar.gz\",\n            zipball_url: \"https://github.com/mislav/will_paginate/archive/v1.2.0.zip\",\n            assets: [\n              { browser_download_url: \"https://github.com/mislav/will_paginate/releases/download/v1.2.0/example.zip\",\n              },\n            ],\n            body: <<MARKDOWN\n### Hello to my release\n\nHere is what's broken:\n- everything\nMARKDOWN\n          },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub release show v1.2.0`\n    Then the output should contain exactly:\n      \"\"\"\n      will_paginate 1.2.0\n\n      ### Hello to my release\n\n      Here is what's broken:\n      - everything\\n\n      \"\"\"\n\n  Scenario: Show specific release including downloads\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { tag_name: 'v1.2.0',\n            name: 'will_paginate 1.2.0',\n            draft: true,\n            prerelease: false,\n            tarball_url: \"https://github.com/mislav/will_paginate/archive/v1.2.0.tar.gz\",\n            zipball_url: \"https://github.com/mislav/will_paginate/archive/v1.2.0.zip\",\n            assets: [\n              { browser_download_url: \"https://github.com/mislav/will_paginate/releases/download/v1.2.0/example.zip\",\n              },\n            ],\n            body: <<MARKDOWN\n### Hello to my release\n\nHere is what's broken:\n- everything\nMARKDOWN\n          },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub release show v1.2.0 --show-downloads`\n    Then the output should contain exactly:\n      \"\"\"\n      will_paginate 1.2.0\n\n      ### Hello to my release\n\n      Here is what's broken:\n      - everything\n\n      ## Downloads\n\n      https://github.com/mislav/will_paginate/releases/download/v1.2.0/example.zip\n      https://github.com/mislav/will_paginate/archive/v1.2.0.zip\n      https://github.com/mislav/will_paginate/archive/v1.2.0.tar.gz\\n\n      \"\"\"\n\n  Scenario: Format specific release\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { tag_name: 'v1.2.0',\n            name: 'will_paginate 1.2.0',\n            draft: true,\n            prerelease: false,\n            tarball_url: \"https://github.com/mislav/will_paginate/archive/v1.2.0.tar.gz\",\n            zipball_url: \"https://github.com/mislav/will_paginate/archive/v1.2.0.zip\",\n            assets: [\n              { browser_download_url: \"https://github.com/mislav/will_paginate/releases/download/v1.2.0/example.zip\",\n              },\n            ],\n            body: <<MARKDOWN\n### Hello to my release\n\nHere is what's broken:\n- everything\nMARKDOWN\n          },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub release show v1.2.0 --format='%t (%T)%n%as%n%n%b%n'`\n    Then the output should contain exactly:\n      \"\"\"\n      will_paginate 1.2.0 (v1.2.0)\n      https://github.com/mislav/will_paginate/releases/download/v1.2.0/example.zip\t\n\n      ### Hello to my release\n\n      Here is what's broken:\n      - everything\\n\\n\n      \"\"\"\n\n  Scenario: Show release no tag\n    When I run `hub release show`\n    Then the exit status should be 1\n    Then the stderr should contain \"hub release show\"\n\n  Scenario: Create a release\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/will_paginate/releases') {\n        assert :draft => true,\n               :tag_name => \"v1.2.0\",\n               :target_commitish => \"\",\n               :name => \"will_paginate 1.2.0: Instant Gratification Monkey\",\n               :body => \"\"\n\n        status 201\n        json :html_url => \"https://github.com/mislav/will_paginate/releases/v1.2.0\"\n      }\n      \"\"\"\n    When I successfully run `hub release create -dm \"will_paginate 1.2.0: Instant Gratification Monkey\" v1.2.0`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/mislav/will_paginate/releases/v1.2.0\\n\n      \"\"\"\n\n  Scenario: Create a release from file\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/will_paginate/releases') {\n        assert :name => \"Epic New Version\",\n               :body => \"body\\ngoes\\n\\nhere\"\n\n        status 201\n        json :html_url => \"https://github.com/mislav/will_paginate/releases/v1.2.0\"\n      }\n      \"\"\"\n    And a file named \"message.txt\" with:\n      \"\"\"\n      Epic New Version\n\n      body\n      goes\n\n      here\n      \"\"\"\n    When I successfully run `hub release create -F message.txt v1.2.0`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/mislav/will_paginate/releases/v1.2.0\\n\n      \"\"\"\n\n  Scenario: Create a release with target commitish\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/will_paginate/releases') {\n        assert :tag_name => \"v1.2.0\",\n               :target_commitish => \"my-branch\"\n\n        status 201\n        json :html_url => \"https://github.com/mislav/will_paginate/releases/v1.2.0\"\n      }\n      \"\"\"\n    When I successfully run `hub release create -m hello v1.2.0 -t my-branch`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/mislav/will_paginate/releases/v1.2.0\\n\n      \"\"\"\n\n  Scenario: Create a release with assets\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/will_paginate/releases') {\n        status 201\n        json :html_url => \"https://github.com/mislav/will_paginate/releases/v1.2.0\",\n             :upload_url => \"https://uploads.github.com/uploads/assets{?name,label}\"\n      }\n      post('/uploads/assets', :host_name => 'uploads.github.com') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'\n        assert :name => 'hello-1.2.0.tar.gz',\n               :label => 'Hello World'\n        status 201\n      }\n      \"\"\"\n    And a file named \"hello-1.2.0.tar.gz\" with:\n      \"\"\"\n      TARBALL\n      \"\"\"\n    When I successfully run `hub release create -m \"hello\" v1.2.0 -a \"./hello-1.2.0.tar.gz#Hello World\"`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/mislav/will_paginate/releases/v1.2.0\n      Attaching 1 asset...\\n\n      \"\"\"\n\n  Scenario: Retry attaching assets on 5xx errors\n    Given the GitHub API server:\n      \"\"\"\n      attempt = 0\n      post('/repos/mislav/will_paginate/releases') {\n        status 201\n        json :html_url => \"https://github.com/mislav/will_paginate/releases/v1.2.0\",\n             :upload_url => \"https://uploads.github.com/uploads/assets{?name,label}\"\n      }\n      post('/uploads/assets', :host_name => 'uploads.github.com') {\n        attempt += 1\n        halt 400 unless request.body.read.to_s == \"TARBALL\"\n        halt 502 if attempt == 1\n        status 201\n      }\n      \"\"\"\n    And a file named \"hello-1.2.0.tar.gz\" with:\n      \"\"\"\n      TARBALL\n      \"\"\"\n    When I successfully run `hub release create -m \"hello\" v1.2.0 -a hello-1.2.0.tar.gz`\n    Then the output should contain exactly:\n      \"\"\"\n      https://github.com/mislav/will_paginate/releases/v1.2.0\n      Attaching 1 asset...\\n\n      \"\"\"\n\n  Scenario: Create a release with some assets failing\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/will_paginate/releases') {\n        status 201\n        json :tag_name => \"v1.2.0\",\n             :html_url => \"https://github.com/mislav/will_paginate/releases/v1.2.0\",\n             :upload_url => \"https://uploads.github.com/uploads/assets{?name,label}\"\n      }\n      post('/uploads/assets', :host_name => 'uploads.github.com') {\n        halt 422 if params[:name] == \"two\"\n        status 201\n      }\n      \"\"\"\n    And a file named \"one\" with:\n      \"\"\"\n      ONE\n      \"\"\"\n    And a file named \"two\" with:\n      \"\"\"\n      TWO\n      \"\"\"\n    And a file named \"three\" with:\n      \"\"\"\n      THREE\n      \"\"\"\n    When I run `hub release create -m \"m\" v1.2.0 -a one -a two -a three`\n    Then the exit status should be 1\n    Then the stderr should contain exactly:\n      \"\"\"\n      Attaching 3 assets...\n      The release was created, but attaching 2 assets failed. You can retry with:\n      hub release edit v1.2.0 -m '' -a two -a three\n      \n      Error uploading release asset: Unprocessable Entity (HTTP 422)\\n\n      \"\"\"\n\n  Scenario: Create a release with nonexistent asset\n    When I run `hub release create -m \"hello\" v1.2.0 -a \"idontexis.tgz\"`\n    Then the exit status should be 1\n    Then the stderr should contain exactly:\n      \"\"\"\n      open idontexis.tgz: no such file or directory\\n\n      \"\"\"\n\n  Scenario: Open new release in web browser\n    Given the GitHub API server:\n      \"\"\"\n      post('/repos/mislav/will_paginate/releases') {\n        status 201\n        json :html_url => \"https://github.com/mislav/will_paginate/releases/v1.2.0\"\n      }\n      \"\"\"\n    When I successfully run `hub release create -o -m hello v1.2.0`\n    Then the output should contain exactly \"\"\n    And \"open https://github.com/mislav/will_paginate/releases/v1.2.0\" should be run\n\n  Scenario: Create release no tag\n    When I run `hub release create -m hello`\n    Then the exit status should be 1\n    Then the stderr should contain \"hub release create\"\n\n  Scenario: Edit existing release\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { url: 'https://api.github.com/repos/mislav/will_paginate/releases/123',\n            tag_name: 'v1.2.0',\n            name: 'will_paginate 1.2.0',\n            draft: true,\n            prerelease: false,\n            body: <<MARKDOWN\n### Hello to my release\n\nHere is what's broken:\n- everything\nMARKDOWN\n          },\n        ]\n      }\n      patch('/repos/mislav/will_paginate/releases/123') {\n        assert :name => 'KITTENS EVERYWHERE',\n               :draft => false,\n               :prerelease => nil\n        json({})\n      }\n      \"\"\"\n    Given the git commit editor is \"vim\"\n    And the text editor adds:\n      \"\"\"\n      KITTENS EVERYWHERE\n      \"\"\"\n    When I successfully run `hub release edit --draft=false v1.2.0`\n    Then the output should not contain anything\n\n  Scenario: Edit existing release when there is a fork\n    Given the \"doge\" remote has url \"git://github.com/doge/will_paginate.git\"\n    And I am on the \"feature\" branch with upstream \"doge/feature\"\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { url: 'https://api.github.com/repos/mislav/will_paginate/releases/123',\n            tag_name: 'v1.2.0',\n          },\n        ]\n      }\n      patch('/repos/mislav/will_paginate/releases/123') {\n        json({})\n      }\n      \"\"\"\n    When I successfully run `hub release edit -m \"\" v1.2.0`\n    Then the output should not contain anything\n\n  Scenario: Edit existing release no title\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { tag_name: 'v1.2.0',\n            name: 'will_paginate 1.2.0',\n          },\n        ]\n      }\n      \"\"\"\n    And a file named \"message.txt\" with:\n      \"\"\"\n      \"\"\"\n    When I run `hub release edit v1.2.0 -F message.txt`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Aborting editing due to empty release title\\n\n      \"\"\"\n\n  Scenario: Edit existing release by uploading assets\n    Given the GitHub API server:\n      \"\"\"\n      deleted = false\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { url: 'https://api.github.com/repos/mislav/will_paginate/releases/123',\n            upload_url: 'https://uploads.github.com/uploads/assets{?name,label}',\n            tag_name: 'v1.2.0',\n            name: 'will_paginate 1.2.0',\n            draft: true,\n            prerelease: false,\n            assets: [\n              { url: 'https://api.github.com/repos/mislav/will_paginate/assets/456',\n                name: 'hello-1.2.0.tar.gz',\n              },\n            ],\n          },\n        ]\n      }\n      delete('/repos/mislav/will_paginate/assets/456') {\n        deleted = true\n        status 204\n      }\n      post('/uploads/assets', :host_name => 'uploads.github.com') {\n        halt 422 unless deleted\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'\n        assert :name => 'hello-1.2.0.tar.gz',\n               :label => nil\n        status 201\n      }\n      \"\"\"\n    And a file named \"hello-1.2.0.tar.gz\" with:\n      \"\"\"\n      TARBALL\n      \"\"\"\n    When I successfully run `hub release edit -m \"\" v1.2.0 -a hello-1.2.0.tar.gz`\n    Then the output should contain exactly:\n      \"\"\"\n      Attaching 1 asset...\\n\n      \"\"\"\n\n  Scenario: Edit release no tag\n    When I run `hub release edit -m hello`\n    Then the exit status should be 1\n    Then the stderr should contain \"hub release edit\"\n\n  Scenario: Download a release asset\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { tag_name: 'v1.2.0',\n            assets: [\n              { url: 'https://api.github.com/repos/mislav/will_paginate/assets/9876',\n                name: 'hello-1.2.0.tar.gz',\n              },\n            ],\n          },\n        ]\n      }\n      get('/repos/mislav/will_paginate/assets/9876') {\n        halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'\n        halt 415 unless request.accept?('application/octet-stream')\n        status 302\n        headers['Location'] = 'https://github-cloud.s3.amazonaws.com/releases/12204602/22ea221a-cf2f-11e2-222a-b3a3c3b3aa3a.gz'\n        \"\"\n      }\n      get('/releases/12204602/22ea221a-cf2f-11e2-222a-b3a3c3b3aa3a.gz', :host_name => 'github-cloud.s3.amazonaws.com') {\n        halt 400 unless request.env['HTTP_AUTHORIZATION'].nil?\n        halt 415 unless request.accept?('application/octet-stream')\n        headers['Content-Type'] = 'application/octet-stream'\n        \"ASSET_TARBALL\"\n      }\n      \"\"\"\n      When I successfully run `hub release download v1.2.0`\n      Then the output should contain exactly:\n        \"\"\"\n        Downloading hello-1.2.0.tar.gz ...\\n\n        \"\"\"\n      And the file \"hello-1.2.0.tar.gz\" should contain exactly:\n        \"\"\"\n        ASSET_TARBALL\n        \"\"\"\n\n  Scenario: Download release assets that match pattern\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { tag_name: 'v1.2.0',\n            assets: [\n              { url: 'https://api.github.com/repos/mislav/will_paginate/assets/9876',\n                name: 'hello-amd32-1.2.0.tar.gz',\n              },\n              { url: 'https://api.github.com/repos/mislav/will_paginate/assets/9877',\n                name: 'hello-amd64-1.2.0.tar.gz',\n              },\n              { url: 'https://api.github.com/repos/mislav/will_paginate/assets/9878',\n                name: 'hello-x86-1.2.0.tar.gz',\n              },\n            ],\n          },\n        ]\n      }\n      get('/repos/mislav/will_paginate/assets/9876') { \"TARBALL\" }\n      get('/repos/mislav/will_paginate/assets/9877') { \"TARBALL\" }\n      \"\"\"\n      When I successfully run `hub release download v1.2.0 --include '*amd*'`\n      Then the output should contain exactly:\n        \"\"\"\n        Downloading hello-amd32-1.2.0.tar.gz ...\n        Downloading hello-amd64-1.2.0.tar.gz ...\\n\n        \"\"\"\n      And the file \"hello-x86-1.2.0.tar.gz\" should not exist\n\n  Scenario: Glob pattern allows exact match\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { tag_name: 'v1.2.0',\n            assets: [\n              { url: 'https://api.github.com/repos/mislav/will_paginate/assets/9876',\n                name: 'hello-amd32-1.2.0.tar.gz',\n              },\n              { url: 'https://api.github.com/repos/mislav/will_paginate/assets/9877',\n                name: 'hello-amd64-1.2.0.tar.gz',\n              },\n              { url: 'https://api.github.com/repos/mislav/will_paginate/assets/9878',\n                name: 'hello-x86-1.2.0.tar.gz',\n              },\n            ],\n          },\n        ]\n      }\n      get('/repos/mislav/will_paginate/assets/9876') { \"ASSET_TARBALL\" }\n      \"\"\"\n      When I successfully run `hub release download v1.2.0 --include hello-amd32-1.2.0.tar.gz`\n      Then the output should contain exactly:\n        \"\"\"\n        Downloading hello-amd32-1.2.0.tar.gz ...\\n\n        \"\"\"\n      And the file \"hello-amd32-1.2.0.tar.gz\" should contain exactly:\n        \"\"\"\n        ASSET_TARBALL\n        \"\"\"\n      And the file \"hello-amd64-1.2.0.tar.gz\" should not exist\n      And the file \"hello-x86-1.2.0.tar.gz\" should not exist\n\n  Scenario: Advanced glob pattern\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { tag_name: 'v1.2.0',\n            assets: [\n              { url: 'https://api.github.com/repos/mislav/will_paginate/assets/9876',\n                name: 'hello-amd32-1.2.0.tar.gz',\n              },\n              { url: 'https://api.github.com/repos/mislav/will_paginate/assets/9876',\n                name: 'hello-amd32-1.2.1.tar.gz',\n              },\n              { url: 'https://api.github.com/repos/mislav/will_paginate/assets/9876',\n                name: 'hello-amd32-1.2.2.tar.gz',\n              },\n            ],\n          },\n        ]\n      }\n      get('/repos/mislav/will_paginate/assets/9876') { \"ASSET_TARBALL\" }\n      \"\"\"\n      When I successfully run `hub release download v1.2.0 --include '*-amd32-?.?.[01].tar.gz'`\n      Then the output should contain exactly:\n        \"\"\"\n        Downloading hello-amd32-1.2.0.tar.gz ...\n        Downloading hello-amd32-1.2.1.tar.gz ...\\n\n        \"\"\"\n\n  Scenario: No matches for download pattern\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        json [\n          { tag_name: 'v1.2.0',\n            assets: [\n              { url: 'https://api.github.com/repos/mislav/will_paginate/assets/9876',\n                name: 'hello-amd32-1.2.0.tar.gz',\n              },\n              { url: 'https://api.github.com/repos/mislav/will_paginate/assets/9876',\n                name: 'hello-amd32-1.2.1.tar.gz',\n              },\n              { url: 'https://api.github.com/repos/mislav/will_paginate/assets/9876',\n                name: 'hello-amd32-1.2.2.tar.gz',\n              },\n            ],\n          },\n        ]\n      }\n      \"\"\"\n      When I run `hub release download v1.2.0 --include amd32`\n      Then the exit status should be 1\n      Then the stderr should contain exactly:\n        \"\"\"\n        the `--include` pattern did not match any available assets:\n        hello-amd32-1.2.0.tar.gz\n        hello-amd32-1.2.1.tar.gz\n        hello-amd32-1.2.2.tar.gz\\n\n        \"\"\"\n\n  Scenario: Download release no tag\n    When I run `hub release download`\n    Then the exit status should be 1\n    Then the stderr should contain \"hub release download\"\n\n  Scenario: Delete a release\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n          json [\n            { url: 'https://api.github.com/repos/mislav/will_paginate/releases/123',\n              tag_name: 'v1.2.0',\n            },\n          ]\n      }\n\n      delete('/repos/mislav/will_paginate/releases/123') {\n        status 204\n      }\n      \"\"\"\n    When I successfully run `hub release delete v1.2.0`\n    Then the output should not contain anything\n\n  Scenario: Release not found\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/will_paginate/releases') {\n        assert :per_page => \"100\"\n        json [\n          { url: 'https://api.github.com/repos/mislav/will_paginate/releases/123',\n            tag_name: 'v1.2.0',\n          },\n        ]\n      }\n\n      delete('/repos/mislav/will_paginate/releases/123') {\n        status 204\n      }\n      \"\"\"\n    When I run `hub release delete v2.0`\n    Then the exit status should be 1\n    And the stderr should contain exactly:\n      \"\"\"\n      Unable to find release with tag name `v2.0'\\n\n      \"\"\"\n\n  Scenario: Enterprise list releases\n    Given the \"origin\" remote has url \"git@git.my.org:mislav/will_paginate.git\"\n    And I am \"mislav\" on git.my.org with OAuth token \"FITOKEN\"\n    And \"git.my.org\" is a whitelisted Enterprise host\n    Given the GitHub API server:\n      \"\"\"\n      get('/api/v3/repos/mislav/will_paginate/releases', :host_name => 'git.my.org') {\n        json [\n          { tag_name: 'v1.2.0',\n            name: 'will_paginate 1.2.0',\n            draft: false,\n            prerelease: false,\n          },\n        ]\n      }\n      \"\"\"\n    When I successfully run `hub release`\n    Then the output should contain exactly:\n      \"\"\"\n      v1.2.0\\n\n      \"\"\"\n"
  },
  {
    "path": "features/remote_add.feature",
    "content": "Feature: hub remote add\n  Background:\n    Given I am \"EvilChelu\" on GitHub.com\n    And I am in \"dotfiles\" git repo\n\n  Scenario: Add origin remote for my own repo\n    Given there are no remotes\n    When I successfully run `hub remote add origin`\n    Then the url for \"origin\" should be \"https://github.com/EvilChelu/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: Add origin remote for my own repo using -C\n    Given there are no remotes\n    And I cd to \"..\"\n    When I successfully run `hub -C dotfiles remote add origin`\n    And I cd to \"dotfiles\"\n    Then the url for \"origin\" should be \"https://github.com/EvilChelu/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: Unchanged public remote add\n    When I successfully run `hub remote add origin http://github.com/defunkt/resque.git`\n    Then the url for \"origin\" should be \"http://github.com/defunkt/resque.git\"\n    And the output should not contain anything\n\n  Scenario: Unchanged private remote add\n    When I successfully run `hub remote add origin git@github.com:defunkt/resque.git`\n    Then the url for \"origin\" should be \"git@github.com:defunkt/resque.git\"\n    And the output should not contain anything\n\n  Scenario: Unchanged local path remote add\n    When I successfully run `hub remote add myremote ./path`\n    Then the git command should be unchanged\n    And the output should not contain anything\n\n  Scenario: Unchanged local absolute path remote add\n    When I successfully run `hub remote add myremote /path`\n    Then the git command should be unchanged\n    And the output should not contain anything\n\n  Scenario: Unchanged remote add with host alias\n    When I successfully run `hub remote add myremote server:/git/repo.git`\n    Then the git command should be unchanged\n    And the output should not contain anything\n\n  Scenario: Add new remote for Enterprise repo\n    Given \"git.my.org\" is a whitelisted Enterprise host\n    And git protocol is preferred\n    And I am \"ProLoser\" on git.my.org with OAuth token \"FITOKEN\"\n    And the \"origin\" remote has url \"git@git.my.org:mislav/topsekrit.git\"\n    When I successfully run `hub remote add another`\n    Then the url for \"another\" should be \"git@git.my.org:another/topsekrit.git\"\n    And the output should not contain anything\n\n  Scenario: Add public remote\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => false,\n             :name => 'dotfiles', :owner => { :login => 'mislav' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub remote add mislav`\n    Then the url for \"mislav\" should be \"https://github.com/mislav/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: Add detected private remote\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => true,\n             :name => 'dotfiles', :owner => { :login => 'mislav' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    And git protocol is preferred\n    When I successfully run `hub remote add mislav`\n    Then the url for \"mislav\" should be \"git@github.com:mislav/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: Add remote with push access\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => false,\n             :name => 'dotfiles', :owner => { :login => 'mislav' },\n             :permissions => { :push => true }\n      }\n      \"\"\"\n    When I successfully run `hub remote add mislav`\n    Then the url for \"mislav\" should be \"https://github.com/mislav/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: Add remote for missing repo\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        status 404\n      }\n      \"\"\"\n    When I run `hub remote add mislav`\n    Then the exit status should be 1\n    And the output should contain exactly:\n      \"\"\"\n      Error: repository mislav/dotfiles doesn't exist\\n\n      \"\"\"\n\n  Scenario: Add explicitly private remote\n    Given git protocol is preferred\n    When I successfully run `hub remote add -p mislav`\n    Then the url for \"mislav\" should be \"git@github.com:mislav/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: Remote for my own repo is automatically private\n    Given git protocol is preferred\n    When I successfully run `hub remote add evilchelu`\n    Then the url for \"evilchelu\" should be \"git@github.com:EvilChelu/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: Add remote with arguments\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => false,\n             :name => 'dotfiles', :owner => { :login => 'mislav' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub remote add -f mislav`\n    Then \"git remote add -f mislav https://github.com/mislav/dotfiles.git\" should be run\n    And the output should not contain anything\n\n  Scenario: Add remote with branch argument\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => false,\n             :name => 'dotfiles', :owner => { :login => 'mislav' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub remote add -f -t feature mislav`\n    Then \"git remote add -f -t feature mislav https://github.com/mislav/dotfiles.git\" should be run\n    And the output should not contain anything\n\n  Scenario: Add named public remote\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => false,\n             :name => 'dotfiles', :owner => { :login => 'mislav' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub remote add mm mislav`\n    Then the url for \"mm\" should be \"https://github.com/mislav/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: set-url\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfiles') {\n        json :private => false,\n             :name => 'dotfiles', :owner => { :login => 'mislav' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    Given the \"origin\" remote has url \"https://github.com/evilchelu/dotfiles.git\"\n    When I successfully run `hub remote set-url origin mislav`\n    Then the url for \"origin\" should be \"https://github.com/mislav/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: Add public remote including repo name\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfilez.js') {\n        json :private => false,\n             :name => 'dotfiles', :owner => { :login => 'mislav' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub remote add mislav/dotfilez.js`\n    Then the url for \"mislav\" should be \"https://github.com/mislav/dotfilez.js.git\"\n    And the output should not contain anything\n\n  Scenario: Add named public remote including repo name\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/dotfilez.js') {\n        json :private => false,\n             :name => 'dotfiles', :owner => { :login => 'mislav' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub remote add mm mislav/dotfilez.js`\n    Then the url for \"mm\" should be \"https://github.com/mislav/dotfilez.js.git\"\n    And the output should not contain anything\n\n  Scenario: Add named private remote\n    Given git protocol is preferred\n    When I successfully run `hub remote add -p mm mislav`\n    Then the url for \"mm\" should be \"git@github.com:mislav/dotfiles.git\"\n    And the output should not contain anything\n\n  Scenario: Add private remote including repo name\n    When I successfully run `hub remote add -p mislav/dotfilez.js`\n    Then the url for \"mislav\" should be \"https://github.com/mislav/dotfilez.js.git\"\n    And the output should not contain anything\n\n  Scenario: Add named private remote including repo name\n    When I successfully run `hub remote add -p mm mislav/dotfilez.js`\n    Then the url for \"mm\" should be \"https://github.com/mislav/dotfilez.js.git\"\n    And the output should not contain anything\n\n  Scenario: Add named private remote for my own repo including repo name\n    When I successfully run `hub remote add ec evilchelu/dotfilez.js`\n    Then the url for \"ec\" should be \"https://github.com/EvilChelu/dotfilez.js.git\"\n    And the output should not contain anything\n\n  Scenario: Avoid crash in argument parsing\n    When I successfully run `hub --noop remote add a b evilchelu`\n    Then the output should contain exactly \"git remote add a b evilchelu\\n\"\n"
  },
  {
    "path": "features/steps.rb",
    "content": "require 'fileutils'\n\nGiven(/^git protocol is preferred$/) do\n  set_environment_variable \"HUB_PROTOCOL\", \"git\"\nend\n\nGiven(/^there are no remotes$/) do\n  output = run_ignored_command 'git remote'\n  expect(output).to be_empty\nend\n\nGiven(/^\"([^\"]*)\" is a whitelisted Enterprise host$/) do |host|\n  run_ignored_command %(git config --global --add hub.host \"#{host}\")\nend\n\nGiven(/^git \"(.+?)\" is set to \"(.+?)\"$/) do |key, value|\n  run_ignored_command %(git config #{key} \"#{value}\")\nend\n\nGiven(/^the \"([^\"]*)\" remote has(?: (push))? url \"([^\"]*)\"$/) do |remote_name, push, url|\n  remotes = run_ignored_command 'git remote'\n  unless remotes.split(\"\\n\").include? remote_name\n    run_ignored_command %(git remote add #{remote_name} \"#{url}\")\n  else\n    run_ignored_command %(git remote set-url #{\"--push\" if push} #{remote_name} \"#{url}\")\n  end\nend\n\nGiven(/^I am \"([^\"]*)\" on ([\\S]+)(?: with OAuth token \"([^\"]*)\")?$/) do |name, host, token|\n  edit_hub_config do |cfg|\n    entry = {'user' => name}\n    host = host.sub(%r{^([\\w-]+)://}, '')\n    entry['oauth_token'] = token if token\n    entry['protocol'] = $1 if $1\n    cfg[host.downcase] = [entry]\n  end\nend\n\nGiven(/^\\$(\\w+) is \"([^\"]*)\"$/) do |name, value|\n  expanded_value = value.gsub(/\\$([A-Z_]+)/) { aruba.environment[$1] }\n  set_environment_variable(name, expanded_value)\nend\n\nGiven(/^I am in \"([^\"]*)\" git repo$/) do |dir_name|\n  if dir_name.include?(':')\n    origin_url = dir_name\n    dir_name = File.basename origin_url, '.git'\n  end\n  step %(a git repo in \"#{dir_name}\")\n  step %(I cd to \"#{dir_name}\")\n  step %(the \"origin\" remote has url \"#{origin_url}\") if origin_url\nend\n\nGiven(/^a (bare )?git repo in \"([^\"]*)\"$/) do |bare, dir_name|\n  run_ignored_command %(git -c init.defaultBranch=master init --quiet #{\"--bare\" if bare} '#{dir_name}')\nend\n\nGiven(/^a git bundle named \"([^\"]*)\"$/) do |file|\n  dest = expand_path(file)\n  FileUtils.mkdir_p(File.dirname(dest))\n\n  Dir.mktmpdir do |tmpdir|\n    Dir.chdir(tmpdir) do\n      `git -c init.defaultBranch=master init --quiet`\n      `GIT_COMMITTER_NAME=a GIT_COMMITTER_EMAIL=b git commit --quiet -m 'empty' --allow-empty --author='a <b>'`\n      `git bundle create \"#{dest}\" master 2>&1`\n    end\n  end\nend\n\nGiven(/^there is a commit named \"([^\"]+)\"$/) do |name|\n  empty_commit\n  empty_commit\n  run_ignored_command %(git tag #{name})\n  run_ignored_command %(git reset --quiet --hard HEAD^)\nend\n\nGiven(/^there is a git FETCH_HEAD$/) do\n  empty_commit\n  empty_commit\n  cd('.') do\n    File.open(\".git/FETCH_HEAD\", \"w\") do |fetch_head|\n      fetch_head.puts \"%s\\t\\t'refs/heads/made-up' of git://github.com/made/up.git\" % `git rev-parse HEAD`.chomp\n    end\n  end\n  run_ignored_command %(git reset --quiet --hard HEAD^)\nend\n\nWhen(/^I make (a|\\d+) commits?(?: with message \"([^\"]+)\")?$/) do |num, msg|\n  num = num == 'a' ? 1 : num.to_i\n  num.times { empty_commit(msg) }\nend\n\nWhen(/^I make a commit with message:$/) do |msg|\n  empty_commit(msg)\nend\n\nThen(/^the latest commit message should be \"([^\"]+)\"$/) do |subject|\n  step %(I successfully run `git log -1 --format=%s`)\n  step %(the output should contain exactly \"#{subject}\\\\n\")\nend\n\n# expand `<$HOME>` etc. in matched text\nThen(/^(the (?:output|stderr|stdout)) with expanded variables( should contain(?: exactly)?:)/) do |prefix, postfix, text|\n  step %(#{prefix}#{postfix}), text.gsub(/<\\$(\\w+)>/) { aruba.environment[$1] }\nend\n\nGiven(/^the \"([^\"]+)\" branch is pushed to \"([^\"]+)\"$/) do |name, upstream|\n  full_upstream = \".git/refs/remotes/#{upstream}\"\n  cd('.') do\n    FileUtils.mkdir_p File.dirname(full_upstream)\n    FileUtils.cp \".git/refs/heads/#{name}\", full_upstream\n  end\nend\n\nGiven(/^I am on the \"([^\"]+)\" branch(?: (pushed to|with upstream) \"([^\"]+)\")?$/) do |name, type, upstream|\n  run_ignored_command %(git checkout --quiet -b #{shell_escape name})\n  empty_commit\n\n  if upstream\n    full_upstream = upstream.start_with?('refs/') ? upstream : \"refs/remotes/#{upstream}\"\n    run_ignored_command %(git update-ref #{shell_escape full_upstream} HEAD)\n\n    if type == 'with upstream'\n      run_ignored_command %(git branch --set-upstream-to #{shell_escape upstream})\n    end\n  end\nend\n\nGiven(/^the default branch for \"([^\"]+)\" is \"([^\"]+)\"$/) do |remote, branch|\n  cd('.') do\n    ref_file = \".git/refs/remotes/#{remote}/#{branch}\"\n    unless File.exist? ref_file\n      empty_commit unless File.exist? '.git/refs/heads/master'\n      FileUtils.mkdir_p File.dirname(ref_file)\n      FileUtils.cp '.git/refs/heads/master', ref_file\n    end\n  end\n  run_ignored_command %(git remote set-head #{remote} #{branch})\nend\n\nGiven(/^I am in detached HEAD$/) do\n  empty_commit\n  empty_commit\n  run_ignored_command %(git checkout HEAD^)\nend\n\nGiven(/^the current dir is not a repo$/) do\n  FileUtils.rm_rf(expand_path('.git'))\nend\n\nGiven(/^the GitHub API server:$/) do |endpoints_str|\n  @server = Hub::LocalServer.start_sinatra do\n    eval endpoints_str, binding\n  end\n  # hit our Sinatra server instead of github.com\n  set_environment_variable 'HUB_TEST_HOST', \"http://127.0.0.1:#{@server.port}\"\nend\n\nThen(/^shell$/) do\n  cd('.') do\n    system '/bin/bash -i'\n  end\nend\n\nThen(/^\"([^\"]*)\" should be run$/) do |cmd|\n  assert_command_run cmd\nend\n\nThen(/^it should clone \"([^\"]*)\"$/) do |repo|\n  step %(\"git clone #{repo}\" should be run)\nend\n\nThen(/^it should not clone anything$/) do\n  history.each { |h| expect(h).to_not match(/^git clone/) }\nend\n\nThen(/^\"([^\"]+)\" should not be run$/) do |pattern|\n  history.each { |h| expect(h).to_not include(pattern) }\nend\n\nThen(/^the git command should be unchanged$/) do\n  expect(@commands).to_not be_empty\n  assert_command_run @commands.last.sub(/^hub\\b/, 'git')\nend\n\nThen(/^the url for \"([^\"]*)\" should be \"([^\"]*)\"$/) do |name, url|\n  output = run_ignored_command %(git config --get-all remote.#{name}.url)\n  expect(output).to include(url)\nend\n\nThen(/^the \"([^\"]*)\" submodule url should be \"([^\"]*)\"$/) do |name, url|\n  output = run_ignored_command %(git config --get-all submodule.\"#{name}\".url)\n  expect(output).to include(url)\nend\n\nThen(/^\"([^\"]*)\" should merge \"([^\"]*)\" from remote \"([^\"]*)\"$/) do |name, merge, remote|\n  output = run_ignored_command %(git config --get-all branch.#{name}.remote)\n  expect(output).to include(remote)\n\n  output = run_ignored_command %(git config --get-all branch.#{name}.merge)\n  expect(output).to include(merge)\nend\n\nThen(/^there should be no \"([^\"]*)\" remote$/) do |remote_name|\n  remotes = run_ignored_command 'git remote'\n  expect(remotes.split(\"\\n\")).to_not include(remote_name)\nend\n\nThen(/^the file \"([^\"]*)\" should have mode \"([^\"]*)\"$/) do |file, expected_mode|\n  mode = File.stat(expand_path(file)).mode\n  expect(mode.to_s(8)).to match(/#{expected_mode}$/)\nend\n\nGiven(/^the remote commit states of \"(.*?)\" \"(.*?)\" are:$/) do |proj, ref, json_value|\n  if ref == 'HEAD'\n    empty_commit\n  end\n  output = run_ignored_command %(git rev-parse #{ref})\n  rev = output.chomp\n\n  host, owner, repo = proj.split('/', 3)\n  if repo.nil?\n    repo = owner\n    owner = host\n    host = nil\n  end\n\n  status_endpoint = <<-EOS\n    get('#{'/api/v3' if host}/repos/#{owner}/#{repo}/commits/#{rev}/status'#{\", :host_name => '#{host}'\" if host}) {\n      json(#{json_value})\n    }\n    get('#{'/api/v3' if host}/repos/#{owner}/#{repo}/commits/#{rev}/check-runs'#{\", :host_name => '#{host}'\" if host}) {\n      status 422\n    }\n    EOS\n  step %{the GitHub API server:}, status_endpoint\nend\n\nGiven(/^the remote commit state of \"(.*?)\" \"(.*?)\" is \"(.*?)\"$/) do |proj, ref, status|\n  step %{the remote commit states of \"#{proj}\" \"#{ref}\" are:}, <<-EOS\n    { :state => \"#{status}\",\n      :statuses => [\n        { :state => \"#{status}\",\n          :context => \"continuous-integration/travis-ci/push\",\n          :target_url => 'https://travis-ci.org/#{proj}/builds/1234567' }\n      ]\n    }\n  EOS\nend\n\nGiven(/^the remote commit state of \"(.*?)\" \"(.*?)\" is nil$/) do |proj, ref|\n  step %{the remote commit states of \"#{proj}\" \"#{ref}\" are:},\n    %({ :state => \"pending\", :statuses => [] })\nend\n\nGiven(/^the text editor exits with error status$/) do\n  text_editor_script \"exit 1\"\nend\n\nGiven(/^the text editor adds:$/) do |text|\n  text_editor_script <<-BASH\n    file=\"$3\"\n    contents=\"$(cat \"$file\" 2>/dev/null || true)\"\n    { echo \"#{text}\"\n      echo\n      echo \"$contents\"\n    } > \"$file\"\n  BASH\nend\n\nWhen(/^I pass in:$/) do |input|\n  type(input)\n  close_input\nend\n\nGiven(/^the git commit editor is \"([^\"]+)\"$/) do |cmd|\n  set_environment_variable('GIT_EDITOR', cmd)\nend\n\nGiven(/^the SSH config:$/) do |config_lines|\n  ssh_config = expand_path('~/.ssh/config')\n  FileUtils.mkdir_p(File.dirname(ssh_config))\n  File.open(ssh_config, 'w') {|f| f << config_lines }\nend\n\nGiven(/^the SHAs and timestamps are normalized in \"([^\"]+)\"$/) do |file|\n  file = expand_path(file)\n  contents = File.read(file)\n  contents.gsub!(/[0-9a-f]{7} \\(Hub, \\d seconds? ago\\)/, \"SHA1SHA (Hub, 0 seconds ago)\")\n  File.open(file, \"w\") { |f| f.write(contents) }\nend\n\nThen(/^its (output|stderr|stdout) should( not)? contain( exactly)?:$/) do |channel, negated, exactly, expected|\n  matcher = case channel.to_sym\n            when :output\n              :have_output\n            when :stderr\n              :have_output_on_stderr\n            when :stdout\n              :have_output_on_stdout\n            end\n\n  commands = [last_command_started]\n\n  output_string_matcher = if exactly\n                            :an_output_string_being_eq\n                          else\n                            :an_output_string_including\n                          end\n\n  if negated\n    expect(commands).not_to include_an_object send(matcher, send(output_string_matcher, expected))\n  else\n    expect(commands).to include_an_object send(matcher, send(output_string_matcher, expected))\n  end\nend\n"
  },
  {
    "path": "features/submodule_add.feature",
    "content": "Feature: hub submodule add\n  Background:\n    Given I am \"mislav\" on github.com with OAuth token \"OTOKEN\"\n    Given I am in \"dotfiles\" git repo\n    # make existing repo in subdirectory so git clone isn't triggered\n    Given a git repo in \"vendor/grit\"\n    And I cd to \"vendor/grit\"\n    And I make 1 commit\n    And I cd to \"../..\"\n\n  Scenario: Add public submodule\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/grit') {\n        json :private => false,\n             :name => 'grit', :owner => { :login => 'mojombo' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub submodule add mojombo/grit vendor/grit`\n    Then the \"vendor/grit\" submodule url should be \"https://github.com/mojombo/grit.git\"\n    And the output should contain exactly:\n      \"\"\"\n      Adding existing repo at 'vendor/grit' to the index\\n\n      \"\"\"\n\n  Scenario: Add private submodule\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/grit') {\n        json :private => false,\n             :name => 'grit', :owner => { :login => 'mojombo' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub submodule add -p mojombo/grit vendor/grit`\n    Then the \"vendor/grit\" submodule url should be \"https://github.com/mojombo/grit.git\"\n\n  Scenario: A submodule for my own repo is public nevertheless\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mislav/grit') {\n        json :private => false,\n             :name => 'grit', :owner => { :login => 'mislav' },\n             :permissions => { :push => true }\n      }\n      \"\"\"\n    When I successfully run `hub submodule add grit vendor/grit`\n    Then the \"vendor/grit\" submodule url should be \"https://github.com/mislav/grit.git\"\n\n  Scenario: Add submodule with arguments\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/grit') {\n        json :private => false,\n             :name => 'grit', :owner => { :login => 'mojombo' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub submodule add -b foo --name grit mojombo/grit vendor/grit`\n    Then \"git submodule add -b foo --name grit https://github.com/mojombo/grit.git vendor/grit\" should be run\n\n  Scenario: Add submodule with branch\n    Given the GitHub API server:\n      \"\"\"\n      get('/repos/mojombo/grit') {\n        json :private => false,\n             :name => 'grit', :owner => { :login => 'mojombo' },\n             :permissions => { :push => false }\n      }\n      \"\"\"\n    When I successfully run `hub submodule add --branch foo mojombo/grit vendor/grit`\n    Then \"git submodule add --branch foo https://github.com/mojombo/grit.git vendor/grit\" should be run\n"
  },
  {
    "path": "features/support/completion.rb",
    "content": "# Driver for completion tests executed via a separate tmux pane in which we\n# spawn an interactive shell, send keystrokes to and inspect the outcome of\n# tab-completion.\n#\n# Prerequisites:\n# - tmux\n# - bash\n# - zsh\n# - fish\n# - git\n\nrequire 'fileutils'\nrequire 'rspec/expectations'\nrequire 'pathname'\n\ntmpdir = Pathname.new(ENV.fetch('TMPDIR', '/tmp')) + 'hub-test'\ncpldir = tmpdir + 'completion'\nzsh_completion = File.expand_path('../../../etc/hub.zsh_completion', __FILE__)\nbash_completion = File.expand_path('../../../etc/hub.bash_completion.sh', __FILE__)\nfish_completion = File.expand_path('../../../etc/hub.fish_completion', __FILE__)\n\n_git_prefix = nil\n\ngit_prefix = lambda {\n  _git_prefix ||= begin\n    git_core = Pathname.new(`git --exec-path`.chomp)\n    git_core.dirname.dirname\n  end\n}\n\ngit_distributed_zsh_completion = lambda {\n  [ git_prefix.call + 'contrib/completion/git-completion.zsh',\n    git_prefix.call + 'share/git-core/contrib/completion/git-completion.zsh',\n    git_prefix.call + 'share/zsh/site-functions/_git',\n  ].detect {|p| p.exist? }\n}\n\ngit_distributed_bash_completion = lambda {\n  [ git_prefix.call + 'contrib/completion/git-completion.bash',\n    git_prefix.call + 'share/git-core/contrib/completion/git-completion.bash',\n    git_prefix.call + 'share/git-core/git-completion.bash',\n    git_prefix.call + 'etc/bash_completion.d/git-completion.bash',\n    Pathname.new('/etc/bash_completion.d/git'),\n    Pathname.new('/usr/share/bash-completion/completions/git'),\n    Pathname.new('/usr/share/bash-completion/git'),\n  ].detect {|p| p.exist? }\n}\n\nlink_completion = Proc.new { |from, name|\n  raise ArgumentError, from.to_s unless File.exist?(from)\n  FileUtils.mkdir_p(cpldir)\n  FileUtils.ln_s(from, cpldir + name, :force => true)\n}\n\ncreate_file = lambda { |name, &block|\n  FileUtils.mkdir_p(File.dirname(name))\n  File.open(name, 'w', &block)\n}\n\nsetup_tmp_home = lambda { |shell|\n  FileUtils.rm_rf(tmpdir)\n\n  case shell\n  when 'zsh'\n    create_file.call(tmpdir + '.zshrc') do |zshrc|\n      zshrc.write <<-SH\n        PS1='$ '\n        for site_fn in /usr/{local/,}share/zsh/site-functions; do\n          fpath=(${fpath#\\$site_fn})\n        done\n        fpath=('#{cpldir}' $fpath)\n        alias git=hub\n        autoload -U compinit\n        compinit -i\n      SH\n    end\n  when 'bash'\n    create_file.call(tmpdir + '.bashrc') do |bashrc|\n      bashrc.write <<-SH\n        PS1='$ '\n        alias git=hub\n        . '#{git_distributed_bash_completion.call}'\n        . '#{bash_completion}'\n      SH\n    end\n  when 'fish'\n    create_file.call(tmpdir + '.config/fish/config.fish') do |fishcfg|\n      fishcfg.write <<-SH\n        function fish_prompt\n          echo '$ '\n        end\n      SH\n    end\n\n    create_file.call(tmpdir + '.config/fish/functions/git.fish') do |gitfn|\n      gitfn.write <<-SH\n        function git --wraps hub\n          hub $argv\n        end\n      SH\n    end\n\n    completion_dest = tmpdir + '.config/fish/completions/hub.fish'\n    FileUtils.mkdir_p(File.dirname(completion_dest))\n    FileUtils.ln_s(fish_completion, completion_dest)\n  end\n}\n\n$tmux = nil\n$installed_shells = Hash.new { |cache, shell|\n  `which #{shell} 2>/dev/null`\n  cache[shell] = $?.success?\n}\n\nBefore('@completion') do\n  unless $tmux\n    $tmux = %w[tmux -L hub-test]\n    system(*($tmux + %w[new-session -ds hub]))\n    at_exit do\n      system(*($tmux + %w[kill-server]))\n    end\n  end\nend\n\nAfter('@completion') do\n  tmux_kill_pane\nend\n\nWorld Module.new {\n  attr_reader :shell\n\n  def set_shell(shell)\n    if $installed_shells[shell]\n      @shell = shell\n      true\n    else\n      false\n    end\n  end\n\n  define_method(:tmux_pane) do\n    return @tmux_pane if tmux_pane?\n    Dir.chdir(tmpdir) do\n      @tmux_pane = `#{$tmux.join(' ')} new-window -dP -n test 'env HOME=\"#{tmpdir}\" #{shell}'`.chomp\n    end\n  end\n\n  def tmux_pane?\n    defined?(@tmux_pane) && @tmux_pane\n  end\n\n  def tmux_pane_contents\n    system(*($tmux + ['capture-pane', '-t', tmux_pane]))\n    `#{$tmux.join(' ')} show-buffer`.rstrip\n  end\n\n  def tmux_send_keys(*keys)\n    system(*($tmux + ['send-keys', '-t', tmux_pane, *keys]))\n  end\n\n  def tmux_send_tab\n    @last_pane_contents = tmux_pane_contents\n    tmux_send_keys('Tab')\n  end\n\n  def tmux_kill_pane\n    system(*($tmux + ['kill-pane', '-t', tmux_pane])) if tmux_pane?\n  end\n\n  def tmux_wait_for_prompt\n    num_waited = 0\n    while tmux_pane_contents !~ /\\$\\Z/\n      raise \"timeout while waiting for shell prompt\" if num_waited > 300\n      sleep 0.01\n      num_waited += 1\n    end\n  end\n\n  def tmux_wait_for_completion\n    num_waited = 0\n    raise \"tmux_send_tab not called first\" unless defined? @last_pane_contents\n    while tmux_pane_contents == @last_pane_contents\n      if num_waited > 300\n        if block_given? then return yield\n        else\n          raise \"timeout while waiting for completions to expand\"\n        end\n      end\n      sleep 0.01\n      num_waited += 1\n    end\n  end\n\n  def tmux_output_lines\n    tmux_pane_contents.split(\"\\n\").drop_while { |l| not l.start_with?('$') }.reject do |line|\n      line.start_with?('$')\n    end\n  end\n\n  def tmux_completion_menu\n    tmux_wait_for_completion\n    hash = {}\n    if 'fish' == shell\n      tmux_output_lines.each do |line|\n        line.scan(/([^(]+)\\((.+?)\\)/).each do |flags, description|\n          flags.strip.split(/\\s+/).each do |flag|\n            hash[flag] = description\n          end\n        end\n      end\n    else\n      tmux_output_lines.each do |line|\n        item, description = line.split(/ +-- +/, 2)\n        hash[item] = description if description\n      end\n    end\n    hash\n  end\n\n  def tmux_completion_menu_basic\n    tmux_wait_for_completion\n    if 'fish' == shell\n      tmux_completion_menu.keys\n    else\n      tmux_output_lines.flat_map do |line|\n        line.split(/\\s+/)\n      end\n    end\n  end\n}\n\nGiven(/^my shell is (\\w+)$/) do |shell|\n  set_shell(shell) || pending\n  setup_tmp_home.call(shell)\nend\n\nGiven(/^I'm using ((?:zsh|git)-distributed) base git completions$/) do |type|\n  link_completion.call(zsh_completion, '_hub')\n  case type\n  when 'zsh-distributed'\n    raise \"this combination makes no sense!\" if 'bash' == shell\n    expect((cpldir + '_git')).to_not be_exist\n  when 'git-distributed'\n    if 'zsh' == shell\n      if git_zsh_completion = git_distributed_zsh_completion.call\n        link_completion.call(git_zsh_completion, '_git')\n        link_completion.call(git_distributed_bash_completion.call, 'git-completion.bash')\n      else\n        warn \"warning: git-distributed zsh completion wasn't found; using zsh-distributed instead\"\n      end\n    end\n    if 'bash' == shell\n      unless git_distributed_bash_completion.call\n        raise \"git-distributed bash completion wasn't found. Completion won't work.\"\n      end\n    end\n  else\n    raise ArgumentError, type\n  end\nend\n\nWhen(/^I type \"(.+?)\" and press <Tab>$/) do |string|\n  tmux_wait_for_prompt\n  @last_command = string\n  tmux_send_keys(string)\n  tmux_send_tab\nend\n\nWhen(/^I press <Tab> again$/) do\n  tmux_send_tab\nend\n\nThen(/^the completion menu should offer \"([^\"]+?)\"( unsorted)?$/) do |items, unsorted|\n  menu = tmux_completion_menu_basic\n  if unsorted\n    menu.sort!\n    items = items.split(' ').sort.join(' ')\n  end\n  expect(menu.join(' ')).to eq(items)\nend\n\nThen(/^the completion menu should offer \"(.+?)\" with description \"(.+?)\"$/) do |item, description|\n  menu = tmux_completion_menu\n  expect(menu.keys).to include(item)\n  expect(menu[item]).to eq(description)\nend\n\nThen(/^the completion menu should offer:/) do |table|\n  menu = tmux_completion_menu\n  expect(menu).to eq(table.rows_hash)\nend\n\nThen(/^the command should expand to \"(.+?)\"$/) do |cmd|\n  tmux_wait_for_completion\n  expect(tmux_pane_contents).to match(/^\\$ #{cmd}$/)\nend\n\nThen(/^the command should not expand$/) do\n  tmux_wait_for_completion { false }\n  expect(tmux_pane_contents).to match(/^\\$ #{@last_command}$/)\nend\n"
  },
  {
    "path": "features/support/env.rb",
    "content": "require 'aruba/cucumber'\nrequire 'fileutils'\nrequire 'forwardable'\nrequire 'tmpdir'\nrequire 'open3'\n\nsystem_git = `which git 2>/dev/null`.chomp\nbin_dir = File.expand_path('../fakebin', __FILE__)\n\ntmpdir = Dir.mktmpdir('hub_test')\ntmp_bin_dir = \"#{tmpdir}/bin\"\nAruba.configure do |aruba|\n  aruba.send(:find_option, :root_directory).value = tmpdir\nend\n\nhub_dir = Dir.mktmpdir('hub_build')\nraise 'hub build failed' unless system(\"./script/build -o #{hub_dir}/hub\")\n\nBefore do\n  author_name  = \"Hub\"\n  author_email = \"hub@test.local\"\n\n  aruba.environment.update(\n    # speed up load time by skipping RubyGems\n    'RUBYOPT' => '--disable-gems',\n    # put fakebin on the PATH\n    'PATH' => \"#{hub_dir}:#{tmp_bin_dir}:#{bin_dir}:#{ENV['PATH']}\",\n    # clear out GIT if it happens to be set\n    'GIT' => nil,\n    # exclude this project's git directory from use in testing\n    'GIT_CEILING_DIRECTORIES' => File.expand_path('../../..', __FILE__),\n    # sabotage git commands that might try to access a remote host\n    'GIT_PROXY_COMMAND' => 'echo',\n    # avoids reading from current user's \"~/.gitconfig\"\n    'HOME' => expand_path('home'),\n    'TMPDIR' => tmpdir,\n    # https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables\n    'XDG_CONFIG_HOME' => nil,\n    'XDG_CONFIG_DIRS' => nil,\n    # used in fakebin/git\n    'HUB_SYSTEM_GIT' => system_git,\n    # ensure that api.github.com is actually never hit in tests\n    'HUB_TEST_HOST' => 'http://127.0.0.1:0',\n    # ensure we use fakebin `open` to test browsing\n    'BROWSER' => 'open',\n    # sabotage opening a commit message editor interactively\n    'GIT_EDITOR' => 'false',\n    # reset current localization settings\n    'LANG' => nil,\n    'LANGUAGE' => nil,\n    'LC_ALL' => 'C.UTF-8',\n    # ignore current user's token\n    'GITHUB_TOKEN' => nil,\n    'GITHUB_USER' => nil,\n    'GITHUB_PASSWORD' => nil,\n    'GITHUB_HOST' => nil,\n    'GITHUB_REPOSITORY' => nil,\n\n    'GIT_AUTHOR_NAME' =>     author_name,\n    'GIT_COMMITTER_NAME' =>  author_name,\n    'GIT_AUTHOR_EMAIL' =>    author_email,\n    'GIT_COMMITTER_EMAIL' => author_email,\n\n    'HUB_VERSION' => 'dev',\n    'HUB_REPORT_CRASH' => 'never',\n    'HUB_PROTOCOL' => nil,\n  )\n\n  FileUtils.mkdir_p(expand_path('~'))\nend\n\nAfter do\n  @server.stop if defined? @server and @server\n  FileUtils.rm_f(\"#{tmp_bin_dir}/vim\")\nend\n\nAfter('@cache_clear') do\n  FileUtils.rm_rf(\"#{tmpdir}/hub/api\")\nend\n\nRSpec::Matchers.define :be_successfully_executed do\n  match do |cmd|\n    expect(cmd).to have_exit_status(0)\n  end\n\n  failure_message do |cmd|\n    msg = %(command `#{cmd.commandline}` exited with status #{cmd.exit_status})\n    stderr = cmd.stderr\n    msg << \":\\n\" << stderr.gsub(/^/, '  ') unless stderr.empty?\n    msg\n  end\nend\n\nWorld Module.new {\n  # If there are multiple inputs, e.g., type in username and then type in password etc.,\n  # the Go program will freeze on the second input. Giving it a small time interval\n  # temporarily solves the problem.\n  # See https://github.com/cucumber/aruba/blob/7afbc5c0cbae9c9a946d70c4c2735ccb86e00f08/lib/aruba/api.rb#L379-L382\n  def type(*args)\n    super.tap { sleep 0.1 }\n  end\n\n  def history\n    histfile = expand_path('~/.history')\n    if File.exist? histfile\n      File.readlines histfile\n    else\n      []\n    end\n  end\n\n  def assert_command_run cmd\n    cmd += \"\\n\" unless cmd[-1..-1] == \"\\n\"\n    expect(history).to include(cmd)\n  end\n\n  def edit_hub_config\n    config = expand_path('~/.config/hub')\n    FileUtils.mkdir_p File.dirname(config)\n    if File.exist? config\n      data = YAML.load File.read(config)\n    else\n      data = {}\n    end\n    yield data\n    File.open(config, 'w') { |cfg| cfg << YAML.dump(data) }\n  end\n\n  define_method(:text_editor_script) do |bash_code|\n    FileUtils.mkdir_p(tmp_bin_dir)\n    File.open(\"#{tmp_bin_dir}/vim\", 'w', 0755) { |exe|\n      exe.puts \"#!/bin/bash\"\n      exe.puts \"set -e\"\n      exe.puts bash_code\n    }\n  end\n\n  def empty_commit(message = nil)\n    unless message\n      @empty_commit_count = defined?(@empty_commit_count) ? @empty_commit_count + 1 : 1\n      message = \"empty #{@empty_commit_count}\"\n    end\n    run_ignored_command \"git commit --quiet -m '#{message}' --allow-empty\"\n  end\n\n  def shell_escape(message)\n    message.to_s.gsub(/['\"\\\\ $]/) { |m| \"\\\\#{m}\" }\n  end\n\n  # runs a command entirely outside of Aruba's command system and returns its stdout\n  def run_ignored_command(cmd_string)\n    stdout, stderr, status = Open3.capture3(aruba.environment, cmd_string, chdir: expand_path('.'))\n    expect(status).to be_success\n    stdout\n  end\n}\n"
  },
  {
    "path": "features/support/fakebin/curl",
    "content": "#!/bin/bash\n\necho \"curl is not allowed\" >&2\nexit 1\n"
  },
  {
    "path": "features/support/fakebin/git",
    "content": "#!/bin/bash\n# A wrapper for system git that prevents commands such as `clone` or `fetch` to be\n# executed in testing. It logs commands to \"~/.history\" so afterwards it can be\n# asserted that they ran.\nset -e\n\ncommand=\"$1\"\ncase \"$command\" in\n  \"config\" ) ;;\n  \"web--browse\" )\n    echo git web--browse PATH/$(basename \"$2\") >> \"$HOME\"/.history\n    ;;\n  * )\n    echo git \"$@\" >> \"$HOME\"/.history\n    ;;\nesac\n\ncase \"$command\" in\n  \"--list-cmds=\"* )\n    echo add\n    echo branch\n    echo commit\n    ;;\n  \"fetch\" )\n    [[ $2 != -* && $3 == *:* && $3 != -* ]] || exit 0\n    refspec=\"$3\"\n    dest=\"${refspec#*:}\"\n    head=\"$(git rev-parse --verify -q HEAD || true)\"\n    if [ -z \"$head\" ]; then\n      git commit --allow-empty -m \"auto-commit\"\n      head=\"$(git rev-parse --verify -q HEAD)\"\n    fi\n    if [[ $dest == refs/remotes/* ]]; then\n      mkdir -p \".git/${dest%/*}\"\n      cat >\".git/${dest}\" <<<\"$head\"\n      cat >\".git/FETCH_HEAD\" <<<\"$head\"\n    else\n      \"$HUB_SYSTEM_GIT\" checkout -b \"${dest#refs/heads/}\" HEAD\n      cat >\".git/FETCH_HEAD\" <<<\"$head\"\n    fi\n    exit 0\n    ;;\n  \"clone\" | \"pull\" | \"push\" | \"web--browse\" )\n    # don't actually execute these commands\n    exit 0\n    ;;\n  * )\n    # note: `submodule add` also initiates a clone, but we work around it\n    if [ \"$command $2 $3\" = \"remote add -f\" ]; then\n      subcommand=$2\n      shift 3\n      exec \"$HUB_SYSTEM_GIT\" $command $subcommand \"$@\"\n    else\n      exec \"$HUB_SYSTEM_GIT\" \"$@\"\n    fi\n    ;;\n  esac\n"
  },
  {
    "path": "features/support/fakebin/man",
    "content": "#!/bin/sh\necho man \"$@\" >> \"$HOME\"/.history\n"
  },
  {
    "path": "features/support/fakebin/open",
    "content": "#!/bin/sh\necho open \"$@\" >> \"$HOME\"/.history\n"
  },
  {
    "path": "features/support/local_server.rb",
    "content": "# based on <github.com/jnicklas/capybara/blob/ab62b27/lib/capybara/server.rb>\nrequire 'net/http'\nrequire 'rack/handler/webrick'\nrequire 'json'\nrequire 'sinatra/base'\n\nmodule Hub\n  class LocalServer\n    class Identify < Struct.new(:app)\n      def call(env)\n        if env[\"PATH_INFO\"] == \"/__identify__\"\n          [200, {}, [app.object_id.to_s]]\n        else\n          app.call(env)\n        end\n      end\n    end\n\n    def self.ports\n      @ports ||= {}\n    end\n\n    class JsonParamsParser < Struct.new(:app)\n      def call(env)\n        if env['rack.input'] and not input_parsed?(env) and type_match?(env)\n          env['rack.request.form_input'] = env['rack.input']\n          data = env['rack.input'].read\n          env['rack.request.form_hash'] = data.empty?? {} : JSON.parse(data)\n        end\n        app.call(env)\n      end\n\n      def input_parsed? env\n        env['rack.request.form_input'].eql? env['rack.input']\n      end\n\n      def type_match? env\n        type = env['CONTENT_TYPE'] and\n          type.split(/\\s*[;,]\\s*/, 2).first.downcase =~ /[\\/+]json$/\n      end\n    end\n\n    class App < Sinatra::Base\n      def invoke\n        res = super\n        content_type :json unless response.content_type\n        response.body = '{}' if blank_response?(response.body) ||\n          (response.body.respond_to?(:[]) && blank_response?(response.body[0]))\n        res\n      end\n\n      def blank_response?(obj)\n        obj.nil? || (obj.respond_to?(:empty?) && obj.empty?)\n      end\n    end\n\n    def self.start_sinatra(&block)\n      klass = Class.new(App)\n      klass.use JsonParamsParser\n      klass.set :environment, :test\n      klass.disable :protection\n      klass.error(404, 401) { content_type :json; nil }\n      klass.class_eval(&block)\n      klass.helpers do\n        def json(value)\n          content_type :json\n          JSON.generate value\n        end\n\n        def assert(expected, data = params)\n          expected.each do |key, value|\n            if :no == value\n              halt 422, json(\n                :message => \"did not expect any value for %p; got %p\" % [key, data[key]]\n              ) if data.key?(key.to_s)\n            elsif Regexp === value\n              halt 422, json(\n                :message => \"expected %p to match %p; got %p\" % [key, value, data[key] ]\n              ) unless value =~ data[key]\n            elsif Hash === value\n              assert(value, data[key])\n            elsif data[key] != value\n              halt 422, json(\n                :message => \"expected %p to be %p; got %p\" % [key, value, data[key]]\n              )\n            end\n          end\n        end\n\n        def assert_basic_auth(*expected)\n          require 'rack/auth/basic'\n          auth = Rack::Auth::Basic::Request.new(env)\n          if auth.credentials != expected\n            halt 401, json(:message => \"Bad credentials\")\n          end\n        end\n\n        def generate_patch(subject)\n        <<PATCH\nFrom 7eb75a26ee8e402aad79fcf36a4c1461e3ec2592 Mon Sep 17 00:00:00 2001\nFrom: Mislav <mislav.marohnic@gmail.com>\nDate: Tue, 24 Jun 2014 11:07:05 -0700\nSubject: [PATCH] #{subject}\n---\ndiff --git a/README.md b/README.md\nnew file mode 100644\nindex 0000000..ce01362\n--- /dev/null\n+++ b/README.md\n+hello\n-- \n1.9.3\nPATCH\n        end\n      end\n\n      new(klass.new).start\n    end\n\n    attr_reader :app, :host, :port\n    attr_accessor :server\n\n    def initialize(app, host = '127.0.0.1')\n      @app = app\n      @host = host\n      @server = nil\n      @server_thread = nil\n    end\n\n    def responsive?\n      return false if @server_thread && @server_thread.join(0)\n\n      res = Net::HTTP.start(host, port) { |http| http.get('/__identify__') }\n\n      res.is_a?(Net::HTTPSuccess) and res.body == app.object_id.to_s\n    rescue Errno::ECONNREFUSED, Errno::EBADF\n      return false\n    end\n\n    def start\n      @port = self.class.ports[app.object_id]\n\n      if not @port or not responsive?\n        tries = 0\n        begin\n          @server_thread = start_handler(Identify.new(app)) do |server, host, port|\n            self.server = server\n            @port = self.class.ports[app.object_id] = port\n          end\n\n          Timeout.timeout(5) { @server_thread.join(0.01) until responsive? }\n        rescue Timeout::Error\n          tries += 1\n          retry if tries < 3\n          raise \"Rack application timed out during boot after #{tries} tries\"\n        end\n      end\n\n      self\n    end\n\n    def start_handler(app)\n      server = nil\n      thread = Rack::Handler::WEBrick.run(app, server_options) { |s| server = s }\n      addr = server.listeners[0].addr\n      yield server, addr[3], addr[1]\n      return thread\n    end\n\n    def server_options\n      { :Port => 0,\n        :BindAddress => '127.0.0.1',\n        :ShutdownSocketWithoutClose => true,\n        :ServerType => Thread,\n        :AccessLog => [],\n        :Logger => WEBrick::Log::new(nil, 0)\n      }\n    end\n\n    def stop\n      server.shutdown\n      @server_thread.join\n    end\n  end\nend\n\nWEBrick::HTTPStatus::StatusMessage[422] = \"Unprocessable Entity\"\n"
  },
  {
    "path": "features/support/rspec_matchers.rb",
    "content": "# Avoids over-zealous sanitize_text\n# https://github.com/cucumber/aruba/blob/v1.0.4/lib/aruba/matchers/string/output_string_eq.rb\n\nsanitize_text = ->(expected) {\n  expected.to_s.\n      # convert \"\\n\" in expectations to literal newline, unless it is preceded by another backslash\n      gsub(/(?<!\\\\)\\\\n/, \"\\n\").\n      # convert \"\\e\" in expectations to a literal ESC, unless it is preceded by another backslash\n      gsub(/(?<!\\\\)\\\\e/, \"\\e\").\n      gsub('\\\\\\\\', '\\\\')\n}\n\nRSpec::Matchers.define :output_string_eq do |expected|\n  match do |actual|\n    @expected = sanitize_text.(expected)\n    @actual = actual.to_s\n    @actual = extract_text(@actual) if aruba.config.remove_ansi_escape_sequences\n\n    @expected == @actual\n  end\n\n  diffable\n\n  description { \"output string is eq: #{description_of(self.expected)}\" }\nend\n\nRSpec::Matchers.define :have_output do |expected|\n  match do |actual|\n    @old_actual = actual\n\n    unless @old_actual.respond_to? :output\n      raise \"Expected #{@old_actual} to respond to #output\"\n    end\n\n    @old_actual.stop\n\n    @actual = actual.output\n    @actual = extract_text(@actual) if aruba.config.remove_ansi_escape_sequences\n\n    expected === @actual\n  end\n\n  diffable\n\n  description { \"have output: #{description_of(expected)}\" }\n\n  failure_message do |_actual|\n    \"expected `#{@old_actual.commandline}` to #{description_of(expected)}\\n\" \\\n      \"but was: #{description_of(@actual)}\"\n  end\nend\n\nRSpec::Matchers.alias_matcher :an_output_string_being_eq, :output_string_eq\n"
  },
  {
    "path": "features/sync.feature",
    "content": "Feature: hub sync\n  Background:\n    Given I am in \"dotfiles\" git repo\n    And I make a commit\n    And the \"origin\" remote has url \"git://github.com/lostisland/faraday.git\"\n\n  Scenario: Prunes remote branches\n    When I successfully run `hub sync`\n    Then the output should contain exactly \"\"\n    And \"git fetch --prune --quiet --progress origin\" should be run\n\n  Scenario: Fast-forwards currently checked out local branch\n    Given I am on the \"feature\" branch pushed to \"origin/feature\"\n    And I successfully run `git reset -q --hard HEAD^`\n    When I successfully run `hub sync`\n    Then the output should contain \"Updated branch feature\"\n    And \"git merge --ff-only --quiet refs/remotes/origin/feature\" should be run\n\n  Scenario: Fast-forwards other local branches in the background\n    Given I am on the \"feature\" branch pushed to \"origin/feature\"\n    And I successfully run `git reset -q --hard HEAD^`\n    And I am on the \"bugfix\" branch pushed to \"origin/bugfix\"\n    And I successfully run `git reset -q --hard HEAD^`\n    And I successfully run `git checkout -q master`\n    When I successfully run `hub sync`\n    Then the output should contain \"Updated branch feature\"\n    And the output should contain \"Updated branch bugfix\"\n\n  Scenario: Refuses to update local branch which has diverged from upstream\n    Given I am on the \"feature\" branch pushed to \"origin/feature\"\n    And I make a commit with message \"diverge\"\n    When I successfully run `hub sync`\n    Then the stderr should contain exactly:\n      \"\"\"\n      warning: 'feature' seems to contain unpushed commits\\n\n      \"\"\"\n\n  Scenario: Deletes local branch that had its upstream deleted\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    And I successfully run `git checkout -q master`\n    And I successfully run `git merge --no-ff --no-edit feature`\n    And I successfully run `git update-ref refs/remotes/origin/master HEAD`\n    And I successfully run `rm .git/refs/remotes/origin/feature`\n    And I successfully run `git checkout -q feature`\n    When I successfully run `hub sync`\n    Then the output should contain \"Deleted branch feature\"\n\n  Scenario: Refuses to delete local branch whose upstream was deleted but not merged to master\n    Given I am on the \"feature\" branch with upstream \"origin/feature\"\n    And I successfully run `rm .git/refs/remotes/origin/feature`\n    And I successfully run `git update-ref refs/remotes/origin/master master`\n    When I successfully run `hub sync`\n    Then the stderr should contain exactly:\n      \"\"\"\n      warning: 'feature' was deleted on origin, but appears not merged into 'master'\\n\n      \"\"\"\n"
  },
  {
    "path": "features/zsh_completion.feature",
    "content": "@completion\nFeature: zsh tab-completion\n\n  Background:\n    Given my shell is zsh\n    And I'm using zsh-distributed base git completions\n\n  Scenario: \"pu\" expands to \"pull-request\" after \"pull\"\n    When I type \"git pu\" and press <Tab>\n    Then the completion menu should offer \"pull-request\" with description \"open a pull request on GitHub\"\n    When I press <Tab> again\n    Then the command should expand to \"git pull\"\n    When I press <Tab> again\n    Then the command should expand to \"git pull-request\"\n\n  Scenario: \"ci-\" expands to \"ci-status\"\n    When I type \"git ci-\" and press <Tab>\n    Then the command should expand to \"git ci-status\"\n\n  Scenario: Completion of pull-request arguments\n    When I type \"git pull-request -\" and press <Tab>\n    Then the completion menu should offer:\n      | -b | base                                 |\n      | -h | head                                 |\n      | -m | message                              |\n      | -F | file                                 |\n      | -i | issue                                |\n      | -f | force (skip check for local commits) |\n      | -a | user                                 |\n      | -M | milestone                            |\n      | -l | labels                               |\n\n  Scenario: Completion of fork arguments\n    When I type \"git fork -\" and press <Tab>\n    Then the command should expand to \"git fork --no-remote\"\n\n  Scenario: Completion of 2nd browse argument\n    When I type \"git browse -- i\" and press <Tab>\n    Then the command should expand to \"git browse -- issues\"\n\n  # In this combination, zsh uses completion support from a bash script.\n  Scenario: \"ci-\" expands to \"ci-status\"\n    Given I'm using git-distributed base git completions\n    When I type \"git ci-\" and press <Tab>\n    Then the command should expand to \"git ci-status\"\n"
  },
  {
    "path": "fixtures/fixtures.go",
    "content": "package fixtures\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n)\n\nfunc Path(segment ...string) string {\n\tpwd, _ := os.Getwd()\n\tp := []string{pwd, \"..\", \"fixtures\"}\n\tp = append(p, segment...)\n\n\treturn filepath.Join(p...)\n}\n"
  },
  {
    "path": "fixtures/release_dir/dir/file2",
    "content": ""
  },
  {
    "path": "fixtures/release_dir/dir/file3",
    "content": ""
  },
  {
    "path": "fixtures/release_dir/dir/subdir/file4",
    "content": ""
  },
  {
    "path": "fixtures/release_dir/file1",
    "content": ""
  },
  {
    "path": "fixtures/test.git/HEAD",
    "content": "ref: refs/heads/master\n"
  },
  {
    "path": "fixtures/test.git/config",
    "content": "[core]\n\trepositoryformatversion = 0\n\tfilemode = true\n\tbare = true\n\tignorecase = true\n\tprecomposeunicode = false\n"
  },
  {
    "path": "fixtures/test.git/description",
    "content": "Unnamed repository; edit this file 'description' to name the repository.\n"
  },
  {
    "path": "fixtures/test.git/hooks/applypatch-msg.sample",
    "content": "#!/bin/sh\n#\n# An example hook script to check the commit log message taken by\n# applypatch from an e-mail message.\n#\n# The hook should exit with non-zero status after issuing an\n# appropriate message if it wants to stop the commit.  The hook is\n# allowed to edit the commit message file.\n#\n# To enable this hook, rename this file to \"applypatch-msg\".\n\n. git-sh-setup\ntest -x \"$GIT_DIR/hooks/commit-msg\" &&\n\texec \"$GIT_DIR/hooks/commit-msg\" ${1+\"$@\"}\n:\n"
  },
  {
    "path": "fixtures/test.git/hooks/commit-msg.sample",
    "content": "#!/bin/sh\n#\n# An example hook script to check the commit log message.\n# Called by \"git commit\" with one argument, the name of the file\n# that has the commit message.  The hook should exit with non-zero\n# status after issuing an appropriate message if it wants to stop the\n# commit.  The hook is allowed to edit the commit message file.\n#\n# To enable this hook, rename this file to \"commit-msg\".\n\n# Uncomment the below to add a Signed-off-by line to the message.\n# Doing this in a hook is a bad idea in general, but the prepare-commit-msg\n# hook is more suited to it.\n#\n# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\\(.*>\\).*$/Signed-off-by: \\1/p')\n# grep -qs \"^$SOB\" \"$1\" || echo \"$SOB\" >> \"$1\"\n\n# This example catches duplicate Signed-off-by lines.\n\ntest \"\" = \"$(grep '^Signed-off-by: ' \"$1\" |\n\t sort | uniq -c | sed -e '/^[ \t]*1[ \t]/d')\" || {\n\techo >&2 Duplicate Signed-off-by lines.\n\texit 1\n}\n"
  },
  {
    "path": "fixtures/test.git/hooks/post-update.sample",
    "content": "#!/bin/sh\n#\n# An example hook script to prepare a packed repository for use over\n# dumb transports.\n#\n# To enable this hook, rename this file to \"post-update\".\n\nexec git update-server-info\n"
  },
  {
    "path": "fixtures/test.git/hooks/pre-applypatch.sample",
    "content": "#!/bin/sh\n#\n# An example hook script to verify what is about to be committed\n# by applypatch from an e-mail message.\n#\n# The hook should exit with non-zero status after issuing an\n# appropriate message if it wants to stop the commit.\n#\n# To enable this hook, rename this file to \"pre-applypatch\".\n\n. git-sh-setup\ntest -x \"$GIT_DIR/hooks/pre-commit\" &&\n\texec \"$GIT_DIR/hooks/pre-commit\" ${1+\"$@\"}\n:\n"
  },
  {
    "path": "fixtures/test.git/hooks/pre-commit.sample",
    "content": "#!/bin/sh\n#\n# An example hook script to verify what is about to be committed.\n# Called by \"git commit\" with no arguments.  The hook should\n# exit with non-zero status after issuing an appropriate message if\n# it wants to stop the commit.\n#\n# To enable this hook, rename this file to \"pre-commit\".\n\nif git rev-parse --verify HEAD >/dev/null 2>&1\nthen\n\tagainst=HEAD\nelse\n\t# Initial commit: diff against an empty tree object\n\tagainst=4b825dc642cb6eb9a060e54bf8d69288fbee4904\nfi\n\n# If you want to allow non-ascii filenames set this variable to true.\nallownonascii=$(git config hooks.allownonascii)\n\n# Redirect output to stderr.\nexec 1>&2\n\n# Cross platform projects tend to avoid non-ascii filenames; prevent\n# them from being added to the repository. We exploit the fact that the\n# printable range starts at the space character and ends with tilde.\nif [ \"$allownonascii\" != \"true\" ] &&\n\t# Note that the use of brackets around a tr range is ok here, (it's\n\t# even required, for portability to Solaris 10's /usr/bin/tr), since\n\t# the square bracket bytes happen to fall in the designated range.\n\ttest $(git diff --cached --name-only --diff-filter=A -z $against |\n\t  LC_ALL=C tr -d '[ -~]\\0' | wc -c) != 0\nthen\n\techo \"Error: Attempt to add a non-ascii file name.\"\n\techo\n\techo \"This can cause problems if you want to work\"\n\techo \"with people on other platforms.\"\n\techo\n\techo \"To be portable it is advisable to rename the file ...\"\n\techo\n\techo \"If you know what you are doing you can disable this\"\n\techo \"check using:\"\n\techo\n\techo \"  git config hooks.allownonascii true\"\n\techo\n\texit 1\nfi\n\n# If there are whitespace errors, print the offending file names and fail.\nexec git diff-index --check --cached $against --\n"
  },
  {
    "path": "fixtures/test.git/hooks/pre-push.sample",
    "content": "#!/bin/sh\n\n# An example hook script to verify what is about to be pushed.  Called by \"git\n# push\" after it has checked the remote status, but before anything has been\n# pushed.  If this script exits with a non-zero status nothing will be pushed.\n#\n# This hook is called with the following parameters:\n#\n# $1 -- Name of the remote to which the push is being done\n# $2 -- URL to which the push is being done\n#\n# If pushing without using a named remote those arguments will be equal.\n#\n# Information about the commits which are being pushed is supplied as lines to\n# the standard input in the form:\n#\n#   <local ref> <local sha1> <remote ref> <remote sha1>\n#\n# This sample shows how to prevent push of commits where the log message starts\n# with \"WIP\" (work in progress).\n\nremote=\"$1\"\nurl=\"$2\"\n\nz40=0000000000000000000000000000000000000000\n\nIFS=' '\nwhile read local_ref local_sha remote_ref remote_sha\ndo\n\tif [ \"$local_sha\" = $z40 ]\n\tthen\n\t\t# Handle delete\n\telse\n\t\tif [ \"$remote_sha\" = $z40 ]\n\t\tthen\n\t\t\t# New branch, examine all commits\n\t\t\trange=\"$local_sha\"\n\t\telse\n\t\t\t# Update to existing branch, examine new commits\n\t\t\trange=\"$remote_sha..$local_sha\"\n\t\tfi\n\n\t\t# Check for WIP commit\n\t\tcommit=`git rev-list -n 1 --grep '^WIP' \"$range\"`\n\t\tif [ -n \"$commit\" ]\n\t\tthen\n\t\t\techo \"Found WIP commit in $local_ref, not pushing\"\n\t\t\texit 1\n\t\tfi\n\tfi\ndone\n\nexit 0\n"
  },
  {
    "path": "fixtures/test.git/hooks/pre-rebase.sample",
    "content": "#!/bin/sh\n#\n# Copyright (c) 2006, 2008 Junio C Hamano\n#\n# The \"pre-rebase\" hook is run just before \"git rebase\" starts doing\n# its job, and can prevent the command from running by exiting with\n# non-zero status.\n#\n# The hook is called with the following parameters:\n#\n# $1 -- the upstream the series was forked from.\n# $2 -- the branch being rebased (or empty when rebasing the current branch).\n#\n# This sample shows how to prevent topic branches that are already\n# merged to 'next' branch from getting rebased, because allowing it\n# would result in rebasing already published history.\n\npublish=next\nbasebranch=\"$1\"\nif test \"$#\" = 2\nthen\n\ttopic=\"refs/heads/$2\"\nelse\n\ttopic=`git symbolic-ref HEAD` ||\n\texit 0 ;# we do not interrupt rebasing detached HEAD\nfi\n\ncase \"$topic\" in\nrefs/heads/??/*)\n\t;;\n*)\n\texit 0 ;# we do not interrupt others.\n\t;;\nesac\n\n# Now we are dealing with a topic branch being rebased\n# on top of master.  Is it OK to rebase it?\n\n# Does the topic really exist?\ngit show-ref -q \"$topic\" || {\n\techo >&2 \"No such branch $topic\"\n\texit 1\n}\n\n# Is topic fully merged to master?\nnot_in_master=`git rev-list --pretty=oneline ^master \"$topic\"`\nif test -z \"$not_in_master\"\nthen\n\techo >&2 \"$topic is fully merged to master; better remove it.\"\n\texit 1 ;# we could allow it, but there is no point.\nfi\n\n# Is topic ever merged to next?  If so you should not be rebasing it.\nonly_next_1=`git rev-list ^master \"^$topic\" ${publish} | sort`\nonly_next_2=`git rev-list ^master           ${publish} | sort`\nif test \"$only_next_1\" = \"$only_next_2\"\nthen\n\tnot_in_topic=`git rev-list \"^$topic\" master`\n\tif test -z \"$not_in_topic\"\n\tthen\n\t\techo >&2 \"$topic is already up-to-date with master\"\n\t\texit 1 ;# we could allow it, but there is no point.\n\telse\n\t\texit 0\n\tfi\nelse\n\tnot_in_next=`git rev-list --pretty=oneline ^${publish} \"$topic\"`\n\t/usr/bin/perl -e '\n\t\tmy $topic = $ARGV[0];\n\t\tmy $msg = \"* $topic has commits already merged to public branch:\\n\";\n\t\tmy (%not_in_next) = map {\n\t\t\t/^([0-9a-f]+) /;\n\t\t\t($1 => 1);\n\t\t} split(/\\n/, $ARGV[1]);\n\t\tfor my $elem (map {\n\t\t\t\t/^([0-9a-f]+) (.*)$/;\n\t\t\t\t[$1 => $2];\n\t\t\t} split(/\\n/, $ARGV[2])) {\n\t\t\tif (!exists $not_in_next{$elem->[0]}) {\n\t\t\t\tif ($msg) {\n\t\t\t\t\tprint STDERR $msg;\n\t\t\t\t\tundef $msg;\n\t\t\t\t}\n\t\t\t\tprint STDERR \" $elem->[1]\\n\";\n\t\t\t}\n\t\t}\n\t' \"$topic\" \"$not_in_next\" \"$not_in_master\"\n\texit 1\nfi\n\nexit 0\n\n################################################################\n\nThis sample hook safeguards topic branches that have been\npublished from being rewound.\n\nThe workflow assumed here is:\n\n * Once a topic branch forks from \"master\", \"master\" is never\n   merged into it again (either directly or indirectly).\n\n * Once a topic branch is fully cooked and merged into \"master\",\n   it is deleted.  If you need to build on top of it to correct\n   earlier mistakes, a new topic branch is created by forking at\n   the tip of the \"master\".  This is not strictly necessary, but\n   it makes it easier to keep your history simple.\n\n * Whenever you need to test or publish your changes to topic\n   branches, merge them into \"next\" branch.\n\nThe script, being an example, hardcodes the publish branch name\nto be \"next\", but it is trivial to make it configurable via\n$GIT_DIR/config mechanism.\n\nWith this workflow, you would want to know:\n\n(1) ... if a topic branch has ever been merged to \"next\".  Young\n    topic branches can have stupid mistakes you would rather\n    clean up before publishing, and things that have not been\n    merged into other branches can be easily rebased without\n    affecting other people.  But once it is published, you would\n    not want to rewind it.\n\n(2) ... if a topic branch has been fully merged to \"master\".\n    Then you can delete it.  More importantly, you should not\n    build on top of it -- other people may already want to\n    change things related to the topic as patches against your\n    \"master\", so if you need further changes, it is better to\n    fork the topic (perhaps with the same name) afresh from the\n    tip of \"master\".\n\nLet's look at this example:\n\n\t\t   o---o---o---o---o---o---o---o---o---o \"next\"\n\t\t  /       /           /           /\n\t\t /   a---a---b A     /           /\n\t\t/   /               /           /\n\t       /   /   c---c---c---c B         /\n\t      /   /   /             \\         /\n\t     /   /   /   b---b C     \\       /\n\t    /   /   /   /             \\     /\n    ---o---o---o---o---o---o---o---o---o---o---o \"master\"\n\n\nA, B and C are topic branches.\n\n * A has one fix since it was merged up to \"next\".\n\n * B has finished.  It has been fully merged up to \"master\" and \"next\",\n   and is ready to be deleted.\n\n * C has not merged to \"next\" at all.\n\nWe would want to allow C to be rebased, refuse A, and encourage\nB to be deleted.\n\nTo compute (1):\n\n\tgit rev-list ^master ^topic next\n\tgit rev-list ^master        next\n\n\tif these match, topic has not merged in next at all.\n\nTo compute (2):\n\n\tgit rev-list master..topic\n\n\tif this is empty, it is fully merged to \"master\".\n"
  },
  {
    "path": "fixtures/test.git/hooks/prepare-commit-msg.sample",
    "content": "#!/bin/sh\n#\n# An example hook script to prepare the commit log message.\n# Called by \"git commit\" with the name of the file that has the\n# commit message, followed by the description of the commit\n# message's source.  The hook's purpose is to edit the commit\n# message file.  If the hook fails with a non-zero status,\n# the commit is aborted.\n#\n# To enable this hook, rename this file to \"prepare-commit-msg\".\n\n# This hook includes three examples.  The first comments out the\n# \"Conflicts:\" part of a merge commit.\n#\n# The second includes the output of \"git diff --name-status -r\"\n# into the message, just before the \"git status\" output.  It is\n# commented because it doesn't cope with --amend or with squashed\n# commits.\n#\n# The third example adds a Signed-off-by line to the message, that can\n# still be edited.  This is rarely a good idea.\n\ncase \"$2,$3\" in\n  merge,)\n    /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' \"$1\" ;;\n\n# ,|template,)\n#   /usr/bin/perl -i.bak -pe '\n#      print \"\\n\" . `git diff --cached --name-status -r`\n#\t if /^#/ && $first++ == 0' \"$1\" ;;\n\n  *) ;;\nesac\n\n# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\\(.*>\\).*$/Signed-off-by: \\1/p')\n# grep -qs \"^$SOB\" \"$1\" || echo \"$SOB\" >> \"$1\"\n"
  },
  {
    "path": "fixtures/test.git/hooks/update.sample",
    "content": "#!/bin/sh\n#\n# An example hook script to blocks unannotated tags from entering.\n# Called by \"git receive-pack\" with arguments: refname sha1-old sha1-new\n#\n# To enable this hook, rename this file to \"update\".\n#\n# Config\n# ------\n# hooks.allowunannotated\n#   This boolean sets whether unannotated tags will be allowed into the\n#   repository.  By default they won't be.\n# hooks.allowdeletetag\n#   This boolean sets whether deleting tags will be allowed in the\n#   repository.  By default they won't be.\n# hooks.allowmodifytag\n#   This boolean sets whether a tag may be modified after creation. By default\n#   it won't be.\n# hooks.allowdeletebranch\n#   This boolean sets whether deleting branches will be allowed in the\n#   repository.  By default they won't be.\n# hooks.denycreatebranch\n#   This boolean sets whether remotely creating branches will be denied\n#   in the repository.  By default this is allowed.\n#\n\n# --- Command line\nrefname=\"$1\"\noldrev=\"$2\"\nnewrev=\"$3\"\n\n# --- Safety check\nif [ -z \"$GIT_DIR\" ]; then\n\techo \"Don't run this script from the command line.\" >&2\n\techo \" (if you want, you could supply GIT_DIR then run\" >&2\n\techo \"  $0 <ref> <oldrev> <newrev>)\" >&2\n\texit 1\nfi\n\nif [ -z \"$refname\" -o -z \"$oldrev\" -o -z \"$newrev\" ]; then\n\techo \"usage: $0 <ref> <oldrev> <newrev>\" >&2\n\texit 1\nfi\n\n# --- Config\nallowunannotated=$(git config --bool hooks.allowunannotated)\nallowdeletebranch=$(git config --bool hooks.allowdeletebranch)\ndenycreatebranch=$(git config --bool hooks.denycreatebranch)\nallowdeletetag=$(git config --bool hooks.allowdeletetag)\nallowmodifytag=$(git config --bool hooks.allowmodifytag)\n\n# check for no description\nprojectdesc=$(sed -e '1q' \"$GIT_DIR/description\")\ncase \"$projectdesc\" in\n\"Unnamed repository\"* | \"\")\n\techo \"*** Project description file hasn't been set\" >&2\n\texit 1\n\t;;\nesac\n\n# --- Check types\n# if $newrev is 0000...0000, it's a commit to delete a ref.\nzero=\"0000000000000000000000000000000000000000\"\nif [ \"$newrev\" = \"$zero\" ]; then\n\tnewrev_type=delete\nelse\n\tnewrev_type=$(git cat-file -t $newrev)\nfi\n\ncase \"$refname\",\"$newrev_type\" in\n\trefs/tags/*,commit)\n\t\t# un-annotated tag\n\t\tshort_refname=${refname##refs/tags/}\n\t\tif [ \"$allowunannotated\" != \"true\" ]; then\n\t\t\techo \"*** The un-annotated tag, $short_refname, is not allowed in this repository\" >&2\n\t\t\techo \"*** Use 'git tag [ -a | -s ]' for tags you want to propagate.\" >&2\n\t\t\texit 1\n\t\tfi\n\t\t;;\n\trefs/tags/*,delete)\n\t\t# delete tag\n\t\tif [ \"$allowdeletetag\" != \"true\" ]; then\n\t\t\techo \"*** Deleting a tag is not allowed in this repository\" >&2\n\t\t\texit 1\n\t\tfi\n\t\t;;\n\trefs/tags/*,tag)\n\t\t# annotated tag\n\t\tif [ \"$allowmodifytag\" != \"true\" ] && git rev-parse $refname > /dev/null 2>&1\n\t\tthen\n\t\t\techo \"*** Tag '$refname' already exists.\" >&2\n\t\t\techo \"*** Modifying a tag is not allowed in this repository.\" >&2\n\t\t\texit 1\n\t\tfi\n\t\t;;\n\trefs/heads/*,commit)\n\t\t# branch\n\t\tif [ \"$oldrev\" = \"$zero\" -a \"$denycreatebranch\" = \"true\" ]; then\n\t\t\techo \"*** Creating a branch is not allowed in this repository\" >&2\n\t\t\texit 1\n\t\tfi\n\t\t;;\n\trefs/heads/*,delete)\n\t\t# delete branch\n\t\tif [ \"$allowdeletebranch\" != \"true\" ]; then\n\t\t\techo \"*** Deleting a branch is not allowed in this repository\" >&2\n\t\t\texit 1\n\t\tfi\n\t\t;;\n\trefs/remotes/*,commit)\n\t\t# tracking branch\n\t\t;;\n\trefs/remotes/*,delete)\n\t\t# delete tracking branch\n\t\tif [ \"$allowdeletebranch\" != \"true\" ]; then\n\t\t\techo \"*** Deleting a tracking branch is not allowed in this repository\" >&2\n\t\t\texit 1\n\t\tfi\n\t\t;;\n\t*)\n\t\t# Anything else (is there anything else?)\n\t\techo \"*** Update hook: unknown type of update to ref $refname of type $newrev_type\" >&2\n\t\texit 1\n\t\t;;\nesac\n\n# --- Finished\nexit 0\n"
  },
  {
    "path": "fixtures/test.git/info/exclude",
    "content": "# git ls-files --others --exclude-from=.git/info/exclude\n# Lines that start with '#' are comments.\n# For a project mostly in C, the following would be a good set of\n# exclude patterns (uncomment them if you want to use them):\n# *.[oa]\n# *~\n"
  },
  {
    "path": "fixtures/test.git/objects/08/f4b7b6513dffc6245857e497cfd6101dc47818",
    "content": "x\u0001M\n0\u0010F]\u0014s\u0001eb~nx\u0004cMi\u001a)^ߠ7p>xb9U7u\u0011\u0001b\u001d{bgٴ\u0002\u0018ă#\u000eh8\u0001>\u00024\u000fo\n'yȜ],\u0004ڄFk-\u0012jUW*zI\u0007);"
  },
  {
    "path": "fixtures/test.git/objects/9b/5a719a3d76ac9dc2fa635d9b1f34fd73994c06",
    "content": "x\u0001K\n\u00021\u0010D]\u0014}\u00013$\u001d\u0010qB\u0010ϐd\u0012\u001dq&\u0012#^s\u00027\u0005UPūXqh\u0011/ZM\tw*cbcG\u00153\u001c\u0013\u0014YIk\u001a g\n6\u0018-Us4\u001di61F#Y,K0iG\u001e|=~b\u00197 $b\rKdD13eKn\u0006\u0019ZC՟{\u0001\u000f\u001eNm"
  },
  {
    "path": "fixtures/test.git/refs/heads/master",
    "content": "9b5a719a3d76ac9dc2fa635d9b1f34fd73994c06\n"
  },
  {
    "path": "fixtures/test_configs.go",
    "content": "package fixtures\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n)\n\ntype TestConfigs struct {\n\tPath string\n}\n\nfunc (c *TestConfigs) TearDown() {\n\tos.Setenv(\"HUB_CONFIG\", \"\")\n\tos.RemoveAll(c.Path)\n}\n\nfunc SetupTomlTestConfig() *TestConfigs {\n\tfile, _ := ioutil.TempFile(\"\", \"test-gh-config-\")\n\n\tcontent := `[[hosts]]\n  host = \"github.com\"\n  user = \"jingweno\"\n  access_token = \"123\"\n  protocol = \"http\"`\n\tioutil.WriteFile(file.Name(), []byte(content), os.ModePerm)\n\tos.Setenv(\"HUB_CONFIG\", file.Name())\n\n\treturn &TestConfigs{file.Name()}\n}\n\nfunc SetupTomlTestConfigWithUnixSocket() *TestConfigs {\n\tfile, _ := ioutil.TempFile(\"\", \"test-gh-config-\")\n\n\tcontent := `[[hosts]]\n  host = \"github.com\"\n  user = \"jingweno\"\n  access_token = \"123\"\n  protocol = \"http\"\n  unix_socket = \"/tmp/go.sock\"`\n\tioutil.WriteFile(file.Name(), []byte(content), os.ModePerm)\n\tos.Setenv(\"HUB_CONFIG\", file.Name())\n\n\treturn &TestConfigs{file.Name()}\n}\n\nfunc SetupTestConfigs() *TestConfigs {\n\tfile, _ := ioutil.TempFile(\"\", \"test-gh-config-\")\n\n\tcontent := `---\ngithub.com:\n- user: jingweno\n  oauth_token: \"123\"\n  protocol: http`\n\tioutil.WriteFile(file.Name(), []byte(content), os.ModePerm)\n\tos.Setenv(\"HUB_CONFIG\", file.Name())\n\n\treturn &TestConfigs{file.Name()}\n}\n\nfunc SetupTestConfigsWithUnixSocket() *TestConfigs {\n\tfile, _ := ioutil.TempFile(\"\", \"test-gh-config-\")\n\n\tcontent := `---\ngithub.com:\n- user: jingweno\n  oauth_token: \"123\"\n  protocol: http\n  unix_socket: /tmp/go.sock`\n\tioutil.WriteFile(file.Name(), []byte(content), os.ModePerm)\n\tos.Setenv(\"HUB_CONFIG\", file.Name())\n\n\treturn &TestConfigs{file.Name()}\n}\n\nfunc SetupTestConfigsInvalidHostName() *TestConfigs {\n\tfile, _ := ioutil.TempFile(\"\", \"test-gh-config-\")\n\n\tcontent := `---\n123:\n- user: jingweno\n  oauth_token: \"123\"\n  protocol: http\n  unix_socket: /tmp/go.sock`\n\tioutil.WriteFile(file.Name(), []byte(content), os.ModePerm)\n\tos.Setenv(\"HUB_CONFIG\", file.Name())\n\n\treturn &TestConfigs{file.Name()}\n}\n\nfunc SetupTestConfigsInvalidHostEntry() *TestConfigs {\n\tfile, _ := ioutil.TempFile(\"\", \"test-gh-config-\")\n\n\tcontent := `---\ngithub.com: hello`\n\tioutil.WriteFile(file.Name(), []byte(content), os.ModePerm)\n\tos.Setenv(\"HUB_CONFIG\", file.Name())\n\n\treturn &TestConfigs{file.Name()}\n}\n\nfunc SetupTestConfigsInvalidPropertyValue() *TestConfigs {\n\tfile, _ := ioutil.TempFile(\"\", \"test-gh-config-\")\n\n\tcontent := `---\ngithub.com:\n- user:\n  oauth_token: \"123\"\n  protocol: http\n  unix_socket: /tmp/go.sock`\n\tioutil.WriteFile(file.Name(), []byte(content), os.ModePerm)\n\tos.Setenv(\"HUB_CONFIG\", file.Name())\n\n\treturn &TestConfigs{file.Name()}\n}\n"
  },
  {
    "path": "fixtures/test_repo.go",
    "content": "package fixtures\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/github/hub/v2/cmd\"\n)\n\ntype TestRepo struct {\n\tRemote   string\n\tTearDown func()\n}\n\nfunc (r *TestRepo) AddRemote(name, url, pushURL string) {\n\tadd := cmd.New(\"git\").WithArgs(\"remote\", \"add\", name, url)\n\tif _, err := add.CombinedOutput(); err != nil {\n\t\tpanic(err)\n\t}\n\tif pushURL != \"\" {\n\t\tset := cmd.New(\"git\").WithArgs(\"remote\", \"set-url\", \"--push\", name, pushURL)\n\t\tif _, err := set.CombinedOutput(); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n}\n\nfunc (r *TestRepo) AddFile(filePath string, content string) {\n\tpath := filepath.Join(os.Getenv(\"HOME\"), filePath)\n\terr := os.MkdirAll(filepath.Dir(path), 0771)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tioutil.WriteFile(path, []byte(content), os.ModePerm)\n}\n\nfunc SetupTestRepo() *TestRepo {\n\tpwd, _ := os.Getwd()\n\toldEnv := make(map[string]string)\n\toverrideEnv := func(name, value string) {\n\t\toldEnv[name] = os.Getenv(name)\n\t\tos.Setenv(name, value)\n\t}\n\n\tremotePath := filepath.Join(pwd, \"..\", \"fixtures\", \"test.git\")\n\thome, err := ioutil.TempDir(\"\", \"test-repo\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\toverrideEnv(\"HOME\", home)\n\toverrideEnv(\"XDG_CONFIG_HOME\", \"\")\n\toverrideEnv(\"XDG_CONFIG_DIRS\", \"\")\n\n\ttargetPath := filepath.Join(home, \"test.git\")\n\tcmd := cmd.New(\"git\").WithArgs(\"clone\", remotePath, targetPath)\n\tif output, err := cmd.CombinedOutput(); err != nil {\n\t\tpanic(fmt.Errorf(\"error running %s\\n%s\\n%s\", cmd, err, output))\n\t}\n\n\tif err = os.Chdir(targetPath); err != nil {\n\t\tpanic(err)\n\t}\n\n\ttearDown := func() {\n\t\tif err := os.Chdir(pwd); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tfor name, value := range oldEnv {\n\t\t\tos.Setenv(name, value)\n\t\t}\n\t\tif err = os.RemoveAll(home); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\treturn &TestRepo{Remote: remotePath, TearDown: tearDown}\n}\n"
  },
  {
    "path": "git/git.go",
    "content": "package git\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/cmd\"\n)\n\nvar GlobalFlags []string\n\nfunc Version() (string, error) {\n\tversionCmd := gitCmd(\"version\")\n\toutput, err := versionCmd.Output()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error running git version: %s\", err)\n\t}\n\treturn firstLine(output), nil\n}\n\nvar cachedDir string\n\nfunc Dir() (string, error) {\n\tif cachedDir != \"\" {\n\t\treturn cachedDir, nil\n\t}\n\n\tdirCmd := gitCmd(\"rev-parse\", \"-q\", \"--git-dir\")\n\tdirCmd.Stderr = nil\n\toutput, err := dirCmd.Output()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"Not a git repository (or any of the parent directories): .git\")\n\t}\n\n\tvar chdir string\n\tfor i, flag := range GlobalFlags {\n\t\tif flag == \"-C\" {\n\t\t\tdir := GlobalFlags[i+1]\n\t\t\tif filepath.IsAbs(dir) {\n\t\t\t\tchdir = dir\n\t\t\t} else {\n\t\t\t\tchdir = filepath.Join(chdir, dir)\n\t\t\t}\n\t\t}\n\t}\n\n\tgitDir := firstLine(output)\n\n\tif !filepath.IsAbs(gitDir) {\n\t\tif chdir != \"\" {\n\t\t\tgitDir = filepath.Join(chdir, gitDir)\n\t\t}\n\n\t\tgitDir, err = filepath.Abs(gitDir)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tgitDir = filepath.Clean(gitDir)\n\t}\n\n\tcachedDir = gitDir\n\treturn gitDir, nil\n}\n\nfunc WorkdirName() (string, error) {\n\ttoplevelCmd := gitCmd(\"rev-parse\", \"--show-toplevel\")\n\ttoplevelCmd.Stderr = nil\n\toutput, err := toplevelCmd.Output()\n\tdir := firstLine(output)\n\tif dir == \"\" {\n\t\treturn \"\", fmt.Errorf(\"unable to determine git working directory\")\n\t}\n\treturn dir, err\n}\n\nfunc HasFile(segments ...string) bool {\n\t// The blessed way to resolve paths within git dir since Git 2.5.0\n\tpathCmd := gitCmd(\"rev-parse\", \"-q\", \"--git-path\", filepath.Join(segments...))\n\tpathCmd.Stderr = nil\n\tif output, err := pathCmd.Output(); err == nil {\n\t\tif lines := outputLines(output); len(lines) == 1 {\n\t\t\tif _, err := os.Stat(lines[0]); err == nil {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\t// Fallback for older git versions\n\tdir, err := Dir()\n\tif err != nil {\n\t\treturn false\n\t}\n\n\ts := []string{dir}\n\ts = append(s, segments...)\n\tpath := filepath.Join(s...)\n\tif _, err := os.Stat(path); err == nil {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc Editor() (string, error) {\n\tvarCmd := gitCmd(\"var\", \"GIT_EDITOR\")\n\tvarCmd.Stderr = nil\n\toutput, err := varCmd.Output()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"Can't load git var: GIT_EDITOR\")\n\t}\n\n\treturn os.ExpandEnv(firstLine(output)), nil\n}\n\nfunc Head() (string, error) {\n\treturn SymbolicRef(\"HEAD\")\n}\n\n// SymbolicRef reads a branch name from a ref such as \"HEAD\"\nfunc SymbolicRef(ref string) (string, error) {\n\trefCmd := gitCmd(\"symbolic-ref\", ref)\n\trefCmd.Stderr = nil\n\toutput, err := refCmd.Output()\n\treturn firstLine(output), err\n}\n\n// SymbolicFullName reads a branch name from a ref such as \"@{upstream}\"\nfunc SymbolicFullName(name string) (string, error) {\n\tparseCmd := gitCmd(\"rev-parse\", \"--symbolic-full-name\", name)\n\tparseCmd.Stderr = nil\n\toutput, err := parseCmd.Output()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"Unknown revision or path not in the working tree: %s\", name)\n\t}\n\n\treturn firstLine(output), nil\n}\n\nfunc Ref(ref string) (string, error) {\n\tparseCmd := gitCmd(\"rev-parse\", \"-q\", ref)\n\tparseCmd.Stderr = nil\n\toutput, err := parseCmd.Output()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"Unknown revision or path not in the working tree: %s\", ref)\n\t}\n\n\treturn firstLine(output), nil\n}\n\nfunc RefList(a, b string) ([]string, error) {\n\tref := fmt.Sprintf(\"%s...%s\", a, b)\n\tlistCmd := gitCmd(\"rev-list\", \"--cherry-pick\", \"--right-only\", \"--no-merges\", ref)\n\tlistCmd.Stderr = nil\n\toutput, err := listCmd.Output()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Can't load rev-list for %s\", ref)\n\t}\n\n\treturn outputLines(output), nil\n}\n\nfunc NewRange(a, b string) (*Range, error) {\n\tparseCmd := gitCmd(\"rev-parse\", \"-q\", a, b)\n\tparseCmd.Stderr = nil\n\toutput, err := parseCmd.Output()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlines := outputLines(output)\n\tif len(lines) != 2 {\n\t\treturn nil, fmt.Errorf(\"Can't parse range %s..%s\", a, b)\n\t}\n\treturn &Range{lines[0], lines[1]}, nil\n}\n\ntype Range struct {\n\tA string\n\tB string\n}\n\nfunc (r *Range) IsIdentical() bool {\n\treturn strings.EqualFold(r.A, r.B)\n}\n\nfunc (r *Range) IsAncestor() bool {\n\tcmd := gitCmd(\"merge-base\", \"--is-ancestor\", r.A, r.B)\n\treturn cmd.Success()\n}\n\nfunc CommentChar(text string) (string, error) {\n\tchar, err := Config(\"core.commentchar\")\n\tif err != nil {\n\t\treturn \"#\", nil\n\t} else if char == \"auto\" {\n\t\tlines := strings.Split(text, \"\\n\")\n\t\tcommentCharCandidates := strings.Split(\"#;@!$%^&|:\", \"\")\n\tcandidateLoop:\n\t\tfor _, candidate := range commentCharCandidates {\n\t\t\tfor _, line := range lines {\n\t\t\t\tif strings.HasPrefix(line, candidate) {\n\t\t\t\t\tcontinue candidateLoop\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn candidate, nil\n\t\t}\n\t\treturn \"\", fmt.Errorf(\"unable to select a comment character that is not used in the current message\")\n\t} else {\n\t\treturn char, nil\n\t}\n}\n\nfunc Show(sha string) (string, error) {\n\tcmd := cmd.New(\"git\")\n\tcmd.Stderr = nil\n\tcmd.WithArg(\"-c\").WithArg(\"log.showSignature=false\")\n\tcmd.WithArg(\"show\").WithArg(\"-s\").WithArg(\"--format=%s%n%+b\").WithArg(sha)\n\n\toutput, err := cmd.Output()\n\treturn strings.TrimSpace(output), err\n}\n\nfunc Log(sha1, sha2 string) (string, error) {\n\texecCmd := cmd.New(\"git\")\n\texecCmd.WithArg(\"-c\").WithArg(\"log.showSignature=false\").WithArg(\"log\").WithArg(\"--no-color\")\n\texecCmd.WithArg(\"--format=%h (%aN, %ar)%n%w(78,3,3)%s%n%+b\")\n\texecCmd.WithArg(\"--cherry\")\n\tshaRange := fmt.Sprintf(\"%s...%s\", sha1, sha2)\n\texecCmd.WithArg(shaRange)\n\n\toutputs, err := execCmd.Output()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"Can't load git log %s..%s\", sha1, sha2)\n\t}\n\n\treturn outputs, nil\n}\n\nfunc Remotes() ([]string, error) {\n\tremoteCmd := gitCmd(\"remote\", \"-v\")\n\tremoteCmd.Stderr = nil\n\toutput, err := remoteCmd.Output()\n\treturn outputLines(output), err\n}\n\nfunc Config(name string) (string, error) {\n\treturn gitGetConfig(name)\n}\n\nfunc ConfigAll(name string) ([]string, error) {\n\tmode := \"--get-all\"\n\tif strings.Contains(name, \"*\") {\n\t\tmode = \"--get-regexp\"\n\t}\n\n\tconfigCmd := gitCmd(gitConfigCommand([]string{mode, name})...)\n\toutput, err := configCmd.Output()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Unknown config %s\", name)\n\t}\n\treturn outputLines(output), nil\n}\n\nfunc GlobalConfig(name string) (string, error) {\n\treturn gitGetConfig(\"--global\", name)\n}\n\nfunc SetGlobalConfig(name, value string) error {\n\t_, err := gitConfig(\"--global\", name, value)\n\treturn err\n}\n\nfunc gitGetConfig(args ...string) (string, error) {\n\tconfigCmd := gitCmd(gitConfigCommand(args)...)\n\toutput, err := configCmd.Output()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"Unknown config %s\", args[len(args)-1])\n\t}\n\n\treturn firstLine(output), nil\n}\n\nfunc gitConfig(args ...string) ([]string, error) {\n\tconfigCmd := gitCmd(gitConfigCommand(args)...)\n\toutput, err := configCmd.Output()\n\treturn outputLines(output), err\n}\n\nfunc gitConfigCommand(args []string) []string {\n\tcmd := []string{\"config\"}\n\treturn append(cmd, args...)\n}\n\nfunc Alias(name string) (string, error) {\n\treturn Config(fmt.Sprintf(\"alias.%s\", name))\n}\n\nfunc Run(args ...string) error {\n\tcmd := gitCmd(args...)\n\treturn cmd.Run()\n}\n\nfunc Spawn(args ...string) error {\n\tcmd := gitCmd(args...)\n\treturn cmd.Spawn()\n}\n\nfunc Quiet(args ...string) bool {\n\tcmd := gitCmd(args...)\n\treturn cmd.Success()\n}\n\nfunc IsGitDir(dir string) bool {\n\tcmd := cmd.New(\"git\")\n\tcmd.WithArgs(\"--git-dir=\"+dir, \"rev-parse\", \"--git-dir\")\n\treturn cmd.Success()\n}\n\nfunc LocalBranches() ([]string, error) {\n\tbranchesCmd := gitCmd(\"branch\", \"--list\")\n\toutput, err := branchesCmd.Output()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbranches := []string{}\n\tfor _, branch := range outputLines(output) {\n\t\tbranches = append(branches, branch[2:])\n\t}\n\treturn branches, nil\n}\n\nfunc outputLines(output string) []string {\n\toutput = strings.TrimSuffix(output, \"\\n\")\n\tif output == \"\" {\n\t\treturn []string{}\n\t}\n\treturn strings.Split(output, \"\\n\")\n}\n\nfunc firstLine(output string) string {\n\tif i := strings.Index(output, \"\\n\"); i >= 0 {\n\t\treturn output[0:i]\n\t}\n\treturn output\n}\n\nfunc gitCmd(args ...string) *cmd.Cmd {\n\tcmd := cmd.New(\"git\")\n\n\tfor _, v := range GlobalFlags {\n\t\tcmd.WithArg(v)\n\t}\n\n\tfor _, a := range args {\n\t\tcmd.WithArg(a)\n\t}\n\n\treturn cmd\n}\n\nfunc IsBuiltInGitCommand(command string) bool {\n\thelpCommand := gitCmd(\"help\", \"--no-verbose\", \"-a\")\n\thelpCommand.Stderr = nil\n\thelpCommandOutput, err := helpCommand.Output()\n\tif err != nil {\n\t\t// support git versions that don't recognize --no-verbose\n\t\thelpCommand := gitCmd(\"help\", \"-a\")\n\t\thelpCommandOutput, err = helpCommand.Output()\n\t}\n\tif err != nil {\n\t\treturn false\n\t}\n\tfor _, helpCommandOutputLine := range outputLines(helpCommandOutput) {\n\t\tif strings.HasPrefix(helpCommandOutputLine, \"  \") {\n\t\t\tfor _, gitCommand := range strings.Split(helpCommandOutputLine, \" \") {\n\t\t\t\tif gitCommand == command {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "git/git_test.go",
    "content": "package git\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/fixtures\"\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestGitDir(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\tgitDir, _ := Dir()\n\tassert.T(t, strings.Contains(gitDir, \".git\"))\n}\n\nfunc TestGitEditor(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\teditor := os.Getenv(\"GIT_EDITOR\")\n\tif err := os.Unsetenv(\"GIT_EDITOR\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\trepo.TearDown()\n\t\tif err := os.Setenv(\"GIT_EDITOR\", editor); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tos.Unsetenv(\"FOO\")\n\t\tos.Unsetenv(\"BAR\")\n\t}()\n\n\tos.Setenv(\"FOO\", \"hello\")\n\tos.Setenv(\"BAR\", \"happy world\")\n\n\tSetGlobalConfig(\"core.editor\", `$FOO \"${BAR}\"`)\n\tgitEditor, err := Editor()\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, `hello \"happy world\"`, gitEditor)\n}\n\nfunc TestGitLog(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\tlog, err := Log(\"08f4b7b6513dffc6245857e497cfd6101dc47818\", \"9b5a719a3d76ac9dc2fa635d9b1f34fd73994c06\")\n\tassert.Equal(t, nil, err)\n\tassert.NotEqual(t, \"\", log)\n}\n\nfunc TestGitRef(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\tref := \"08f4b7b6513dffc6245857e497cfd6101dc47818\"\n\tgitRef, err := Ref(ref)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, ref, gitRef)\n}\n\nfunc TestGitRefList(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\trefList, err := RefList(\"08f4b7b6513dffc6245857e497cfd6101dc47818\", \"9b5a719a3d76ac9dc2fa635d9b1f34fd73994c06\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, 1, len(refList))\n\n\tassert.Equal(t, \"9b5a719a3d76ac9dc2fa635d9b1f34fd73994c06\", refList[0])\n}\n\nfunc TestGitShow(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\toutput, err := Show(\"9b5a719a3d76ac9dc2fa635d9b1f34fd73994c06\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"First comment\\n\\nMore comment\", output)\n}\n\nfunc TestGitConfig(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\t_, err := GlobalConfig(\"hub.test\")\n\tassert.NotEqual(t, nil, err)\n\n\tSetGlobalConfig(\"hub.test\", \"1\")\n\tv, err := GlobalConfig(\"hub.test\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"1\", v)\n\n\tSetGlobalConfig(\"hub.test\", \"\")\n\tv, err = GlobalConfig(\"hub.test\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"\", v)\n}\n\nfunc TestRemotes(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\ttype remote struct {\n\t\tname    string\n\t\turl     string\n\t\tpushURL string\n\t}\n\ttestCases := map[string]remote{\n\t\t\"testremote1\": {\n\t\t\t\"testremote1\",\n\t\t\t\"https://example.com/test1/project1.git\",\n\t\t\t\"no_push\",\n\t\t},\n\t\t\"testremote2\": {\n\t\t\t\"testremote2\",\n\t\t\t\"user@example.com:test2/project2.git\",\n\t\t\t\"http://example.com/project.git\",\n\t\t},\n\t\t\"testremote3\": {\n\t\t\t\"testremote3\",\n\t\t\t\"https://example.com/test1/project2.git\",\n\t\t\t\"\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\trepo.AddRemote(tc.name, tc.url, tc.pushURL)\n\t}\n\n\tremotes, err := Remotes()\n\tassert.Equal(t, nil, err)\n\n\t// In addition to the remotes we added to the repo, repo will\n\t// also have an additional remote \"origin\". So add it to the\n\t// expected cases to test.\n\twantCases := map[string]struct{}{\n\t\tfmt.Sprintf(\"origin\t%s (fetch)\", repo.Remote): {},\n\t\tfmt.Sprintf(\"origin\t%s (push)\", repo.Remote): {},\n\t\t\"testremote1\thttps://example.com/test1/project1.git (fetch)\": {},\n\t\t\"testremote1\tno_push (push)\": {},\n\t\t\"testremote2\tuser@example.com:test2/project2.git (fetch)\": {},\n\t\t\"testremote2\thttp://example.com/project.git (push)\": {},\n\t\t\"testremote3\thttps://example.com/test1/project2.git (fetch)\": {},\n\t\t\"testremote3\thttps://example.com/test1/project2.git (push)\": {},\n\t}\n\n\tassert.Equal(t, len(remotes), len(wantCases))\n\tfor _, got := range remotes {\n\t\tif _, ok := wantCases[got]; !ok {\n\t\t\tt.Errorf(\"Unexpected remote: %s\", got)\n\t\t}\n\t}\n}\n\nfunc TestCommentChar(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\tchar, err := CommentChar(\"\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"#\", char)\n\n\tSetGlobalConfig(\"core.commentchar\", \";\")\n\tchar, err = CommentChar(\"\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \";\", char)\n\n\tSetGlobalConfig(\"core.commentchar\", \"auto\")\n\tchar, err = CommentChar(\"\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"#\", char)\n\n\tchar, err = CommentChar(\"hello\\n#nice\\nworld\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \";\", char)\n\n\tchar, err = CommentChar(\"hello\\n#nice\\n;world\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"@\", char)\n\n\t_, err = CommentChar(\"#\\n;\\n@\\n!\\n$\\n%\\n^\\n&\\n|\\n:\")\n\tassert.Equal(t, \"unable to select a comment character that is not used in the current message\", err.Error())\n}\n"
  },
  {
    "path": "git/ssh_config.go",
    "content": "package git\n\nimport (\n\t\"bufio\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/mitchellh/go-homedir\"\n)\n\nconst (\n\thostReStr = \"(?i)^[ \\t]*(host|hostname)[ \\t]+(.+)$\"\n)\n\ntype SSHConfig map[string]string\n\nfunc newSSHConfigReader() *SSHConfigReader {\n\tconfigFiles := []string{\n\t\t\"/etc/ssh_config\",\n\t\t\"/etc/ssh/ssh_config\",\n\t}\n\tif homedir, err := homedir.Dir(); err == nil {\n\t\tuserConfig := filepath.Join(homedir, \".ssh\", \"config\")\n\t\tconfigFiles = append([]string{userConfig}, configFiles...)\n\t}\n\treturn &SSHConfigReader{\n\t\tFiles: configFiles,\n\t}\n}\n\ntype SSHConfigReader struct {\n\tFiles []string\n}\n\nfunc (r *SSHConfigReader) Read() SSHConfig {\n\tconfig := make(SSHConfig)\n\thostRe := regexp.MustCompile(hostReStr)\n\n\tfor _, filename := range r.Files {\n\t\tr.readFile(config, hostRe, filename)\n\t}\n\n\treturn config\n}\n\nfunc (r *SSHConfigReader) readFile(c SSHConfig, re *regexp.Regexp, f string) error {\n\tfile, err := os.Open(f)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\n\thosts := []string{\"*\"}\n\tscanner := bufio.NewScanner(file)\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tmatch := re.FindStringSubmatch(line)\n\t\tif match == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tnames := strings.Fields(match[2])\n\t\tif strings.EqualFold(match[1], \"host\") {\n\t\t\thosts = names\n\t\t} else {\n\t\t\tfor _, host := range hosts {\n\t\t\t\tfor _, name := range names {\n\t\t\t\t\tc[host] = expandTokens(name, host)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn scanner.Err()\n}\n\nfunc expandTokens(text, host string) string {\n\tre := regexp.MustCompile(`%[%h]`)\n\treturn re.ReplaceAllStringFunc(text, func(match string) string {\n\t\tswitch match {\n\t\tcase \"%h\":\n\t\t\treturn host\n\t\tcase \"%%\":\n\t\t\treturn \"%\"\n\t\t}\n\t\treturn \"\"\n\t})\n}\n"
  },
  {
    "path": "git/ssh_config_test.go",
    "content": "package git\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestSSHConfigReader_Read(t *testing.T) {\n\tf, _ := ioutil.TempFile(\"\", \"ssh-config\")\n\tc := `Host github.com\n  Hostname ssh.github.com\n  Port 443\n\n\thost other\n\tHostname 10.0.0.1\n\t`\n\n\tioutil.WriteFile(f.Name(), []byte(c), os.ModePerm)\n\n\tr := &SSHConfigReader{[]string{f.Name()}}\n\tsc := r.Read()\n\tassert.Equal(t, \"ssh.github.com\", sc[\"github.com\"])\n}\n\nfunc TestSSHConfigReader_ExpandTokens(t *testing.T) {\n\tf, _ := ioutil.TempFile(\"\", \"ssh-config\")\n\tc := `Host github.com example.org\n  Hostname 1-%h-2-%%h-3-%h-%%\n\t`\n\n\tioutil.WriteFile(f.Name(), []byte(c), os.ModePerm)\n\n\tr := &SSHConfigReader{[]string{f.Name()}}\n\tsc := r.Read()\n\tassert.Equal(t, \"1-github.com-2-%h-3-github.com-%\", sc[\"github.com\"])\n\tassert.Equal(t, \"1-example.org-2-%h-3-example.org-%\", sc[\"example.org\"])\n}\n"
  },
  {
    "path": "git/url.go",
    "content": "package git\n\nimport (\n\t\"net/url\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nvar (\n\tcachedSSHConfig SSHConfig\n\tprotocolRe      = regexp.MustCompile(\"^[a-zA-Z_+-]+://\")\n)\n\ntype URLParser struct {\n\tSSHConfig SSHConfig\n}\n\nfunc (p *URLParser) Parse(rawURL string) (u *url.URL, err error) {\n\tif !protocolRe.MatchString(rawURL) &&\n\t\tstrings.Contains(rawURL, \":\") &&\n\t\t// not a Windows path\n\t\t!strings.Contains(rawURL, \"\\\\\") {\n\t\trawURL = \"ssh://\" + strings.Replace(rawURL, \":\", \"/\", 1)\n\t}\n\n\tu, err = url.Parse(rawURL)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif u.Scheme == \"git+ssh\" {\n\t\tu.Scheme = \"ssh\"\n\t}\n\n\tif u.Scheme != \"ssh\" {\n\t\treturn\n\t}\n\n\tif strings.HasPrefix(u.Path, \"//\") {\n\t\tu.Path = strings.TrimPrefix(u.Path, \"/\")\n\t}\n\n\tif idx := strings.Index(u.Host, \":\"); idx >= 0 {\n\t\tu.Host = u.Host[0:idx]\n\t}\n\n\tsshHost := p.SSHConfig[u.Host]\n\t// ignore replacing host that fixes for limited network\n\t// https://help.github.com/articles/using-ssh-over-the-https-port\n\tignoredHost := u.Host == \"github.com\" && sshHost == \"ssh.github.com\"\n\tif !ignoredHost && sshHost != \"\" {\n\t\tu.Host = sshHost\n\t}\n\n\treturn\n}\n\nfunc ParseURL(rawURL string) (u *url.URL, err error) {\n\tif cachedSSHConfig == nil {\n\t\tcachedSSHConfig = newSSHConfigReader().Read()\n\t}\n\n\tp := &URLParser{cachedSSHConfig}\n\n\treturn p.Parse(rawURL)\n}\n"
  },
  {
    "path": "git/url_test.go",
    "content": "package git\n\nimport (\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc createURLParser() *URLParser {\n\tc := make(SSHConfig)\n\tc[\"github.com\"] = \"ssh.github.com\"\n\tc[\"gh\"] = \"github.com\"\n\tc[\"git.company.com\"] = \"ssh.git.company.com\"\n\n\treturn &URLParser{c}\n}\n\nfunc TestURLParser_ParseURL_HTTPURL(t *testing.T) {\n\tp := createURLParser()\n\n\tu, err := p.Parse(\"https://github.com/octokit/go-octokit.git\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"github.com\", u.Host)\n\tassert.Equal(t, \"https\", u.Scheme)\n\tassert.Equal(t, \"/octokit/go-octokit.git\", u.Path)\n}\n\nfunc TestURLParser_ParseURL_GitURL(t *testing.T) {\n\tp := createURLParser()\n\n\tu, err := p.Parse(\"git://github.com/octokit/go-octokit.git\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"github.com\", u.Host)\n\tassert.Equal(t, \"git\", u.Scheme)\n\tassert.Equal(t, \"/octokit/go-octokit.git\", u.Path)\n\n\tu, err = p.Parse(\"https://git.company.com/octokit/go-octokit.git\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"git.company.com\", u.Host)\n\tassert.Equal(t, \"https\", u.Scheme)\n\tassert.Equal(t, \"/octokit/go-octokit.git\", u.Path)\n\n\tu, err = p.Parse(\"git://git.company.com/octokit/go-octokit.git\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"git.company.com\", u.Host)\n\tassert.Equal(t, \"git\", u.Scheme)\n\tassert.Equal(t, \"/octokit/go-octokit.git\", u.Path)\n}\n\nfunc TestURLParser_ParseURL_SSHURL(t *testing.T) {\n\tp := createURLParser()\n\n\tu, err := p.Parse(\"git@github.com:lostisland/go-sawyer.git\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"github.com\", u.Host)\n\tassert.Equal(t, \"ssh\", u.Scheme)\n\tassert.Equal(t, \"git\", u.User.Username())\n\tassert.Equal(t, \"/lostisland/go-sawyer.git\", u.Path)\n\n\tu, err = p.Parse(\"gh:octokit/go-octokit\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"github.com\", u.Host)\n\tassert.Equal(t, \"ssh\", u.Scheme)\n\tassert.Equal(t, \"/octokit/go-octokit\", u.Path)\n\n\tu, err = p.Parse(\"git@git.company.com:octokit/go-octokit\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"ssh.git.company.com\", u.Host)\n\tassert.Equal(t, \"ssh\", u.Scheme)\n\tassert.Equal(t, \"/octokit/go-octokit\", u.Path)\n}\n\nfunc TestURLParser_ParseURL_LocalPath(t *testing.T) {\n\tp := createURLParser()\n\n\tu, err := p.Parse(\"/path/to/repo.git\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"\", u.Host)\n\tassert.Equal(t, \"\", u.Scheme)\n\tassert.Equal(t, \"/path/to/repo.git\", u.Path)\n\n\tu, err = p.Parse(`c:\\path\\to\\repo.git`)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, `c:\\path\\to\\repo.git`, u.String())\n}\n"
  },
  {
    "path": "github/branch.go",
    "content": "package github\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/git\"\n)\n\ntype Branch struct {\n\tRepo *GitHubRepo\n\tName string\n}\n\nfunc (b *Branch) ShortName() string {\n\treg := regexp.MustCompile(\"^refs/(remotes/)?.+?/\")\n\treturn reg.ReplaceAllString(b.Name, \"\")\n}\n\nfunc (b *Branch) LongName() string {\n\treg := regexp.MustCompile(\"^refs/(remotes/)?\")\n\treturn reg.ReplaceAllString(b.Name, \"\")\n}\n\nfunc (b *Branch) RemoteName() string {\n\treg := regexp.MustCompile(\"^refs/remotes/([^/]+)\")\n\tif reg.MatchString(b.Name) {\n\t\treturn reg.FindStringSubmatch(b.Name)[1]\n\t}\n\n\treturn \"\"\n}\n\nfunc (b *Branch) Upstream() (u *Branch, err error) {\n\tname, err := git.SymbolicFullName(fmt.Sprintf(\"%s@{upstream}\", b.ShortName()))\n\tif err != nil {\n\t\treturn\n\t}\n\n\tu = &Branch{b.Repo, name}\n\n\treturn\n}\n\nfunc (b *Branch) IsMaster() bool {\n\tmasterName := b.Repo.MasterBranch().ShortName()\n\treturn b.ShortName() == masterName\n}\n\nfunc (b *Branch) IsRemote() bool {\n\treturn strings.HasPrefix(b.Name, \"refs/remotes\")\n}\n"
  },
  {
    "path": "github/branch_test.go",
    "content": "package github\n\nimport (\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestBranch_ShortName(t *testing.T) {\n\tlp, _ := LocalRepo()\n\tb := Branch{lp, \"refs/heads/master\"}\n\tassert.Equal(t, \"master\", b.ShortName())\n}\n\nfunc TestBranch_LongName(t *testing.T) {\n\tlp, _ := LocalRepo()\n\n\tb := Branch{lp, \"refs/heads/master\"}\n\tassert.Equal(t, \"heads/master\", b.LongName())\n\n\tb = Branch{lp, \"refs/remotes/origin/master\"}\n\tassert.Equal(t, \"origin/master\", b.LongName())\n}\n\nfunc TestBranch_RemoteName(t *testing.T) {\n\tlp, _ := LocalRepo()\n\n\tb := Branch{lp, \"refs/remotes/origin/master\"}\n\tassert.Equal(t, \"origin\", b.RemoteName())\n\n\tb = Branch{lp, \"refs/head/master\"}\n\tassert.Equal(t, \"\", b.RemoteName())\n}\n\nfunc TestBranch_IsRemote(t *testing.T) {\n\tlp, _ := LocalRepo()\n\n\tb := Branch{lp, \"refs/remotes/origin/master\"}\n\tassert.T(t, b.IsRemote())\n}\n"
  },
  {
    "path": "github/client.go",
    "content": "package github\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/github/hub/v2/version\"\n)\n\nconst (\n\tGitHubHost  string = \"github.com\"\n\tOAuthAppURL string = \"https://hub.github.com/\"\n)\n\nvar UserAgent = \"Hub \" + version.Version\n\nfunc NewClient(h string) *Client {\n\treturn NewClientWithHost(&Host{Host: h})\n}\n\nfunc NewClientWithHost(host *Host) *Client {\n\treturn &Client{Host: host}\n}\n\ntype Client struct {\n\tHost         *Host\n\tcachedClient *simpleClient\n}\n\ntype Gist struct {\n\tFiles       map[string]GistFile `json:\"files\"`\n\tDescription string              `json:\"description,omitempty\"`\n\tID          string              `json:\"id,omitempty\"`\n\tPublic      bool                `json:\"public\"`\n\tHTMLURL     string              `json:\"html_url\"`\n}\n\ntype GistFile struct {\n\tType     string `json:\"type,omitempty\"`\n\tLanguage string `json:\"language,omitempty\"`\n\tContent  string `json:\"content\"`\n\tRawURL   string `json:\"raw_url\"`\n}\n\nfunc (client *Client) FetchPullRequests(project *Project, filterParams map[string]interface{}, limit int, filter func(*PullRequest) bool) (pulls []PullRequest, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tpath := fmt.Sprintf(\"repos/%s/%s/pulls?per_page=%d\", project.Owner, project.Name, perPage(limit, 100))\n\tif filterParams != nil {\n\t\tpath = addQuery(path, filterParams)\n\t}\n\n\tpulls = []PullRequest{}\n\tvar res *simpleResponse\n\n\tfor path != \"\" {\n\t\tres, err = api.GetFile(path, draftsType)\n\t\tif err = checkStatus(200, \"fetching pull requests\", res, err); err != nil {\n\t\t\treturn\n\t\t}\n\t\tpath = res.Link(\"next\")\n\n\t\tpullsPage := []PullRequest{}\n\t\tif err = res.Unmarshal(&pullsPage); err != nil {\n\t\t\treturn\n\t\t}\n\t\tfor _, pr := range pullsPage {\n\t\t\tif filter == nil || filter(&pr) {\n\t\t\t\tpulls = append(pulls, pr)\n\t\t\t\tif limit > 0 && len(pulls) == limit {\n\t\t\t\t\tpath = \"\"\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (client *Client) PullRequest(project *Project, id string) (pr *PullRequest, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.Get(fmt.Sprintf(\"repos/%s/%s/pulls/%s\", project.Owner, project.Name, id))\n\tif err = checkStatus(200, \"getting pull request\", res, err); err != nil {\n\t\treturn\n\t}\n\n\tpr = &PullRequest{}\n\terr = res.Unmarshal(pr)\n\n\treturn\n}\n\nfunc (client *Client) PullRequestPatch(project *Project, id string) (patch io.ReadCloser, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.GetFile(fmt.Sprintf(\"repos/%s/%s/pulls/%s\", project.Owner, project.Name, id), patchMediaType)\n\tif err = checkStatus(200, \"getting pull request patch\", res, err); err != nil {\n\t\treturn\n\t}\n\n\treturn res.Body, nil\n}\n\nfunc (client *Client) CreatePullRequest(project *Project, params map[string]interface{}) (pr *PullRequest, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.PostJSONPreview(fmt.Sprintf(\"repos/%s/%s/pulls\", project.Owner, project.Name), params, draftsType)\n\tif err = checkStatus(201, \"creating pull request\", res, err); err != nil {\n\t\tif res != nil && res.StatusCode == 404 {\n\t\t\tprojectURL := strings.SplitN(project.WebURL(\"\", \"\", \"\"), \"://\", 2)[1]\n\t\t\terr = fmt.Errorf(\"%s\\nAre you sure that %s exists?\", err, projectURL)\n\t\t}\n\t\treturn\n\t}\n\n\tpr = &PullRequest{}\n\terr = res.Unmarshal(pr)\n\n\treturn\n}\n\ntype PullRequestMergeResponse struct {\n\tSHA     string\n\tMerged  bool\n\tMessage string\n}\n\nfunc (client *Client) MergePullRequest(project *Project, prNumber int, params map[string]interface{}) (mr PullRequestMergeResponse, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.PutJSON(fmt.Sprintf(\"repos/%s/%s/pulls/%d/merge\", project.Owner, project.Name, prNumber), params)\n\tif err = checkStatus(200, \"merging pull request\", res, err); err != nil {\n\t\treturn\n\t}\n\tdefer res.Body.Close()\n\n\terr = res.Unmarshal(&mr)\n\treturn\n}\n\nfunc (client *Client) DeleteBranch(project *Project, branchName string) (err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.Delete(fmt.Sprintf(\"repos/%s/%s/git/refs/heads/%s\", project.Owner, project.Name, branchName))\n\tif err == nil {\n\t\tdefer res.Body.Close()\n\t\tif res.StatusCode == 422 {\n\t\t\treturn\n\t\t}\n\t}\n\tif err = checkStatus(204, \"deleting branch\", res, err); err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (client *Client) RequestReview(project *Project, prNumber int, params map[string]interface{}) (err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.PostJSON(fmt.Sprintf(\"repos/%s/%s/pulls/%d/requested_reviewers\", project.Owner, project.Name, prNumber), params)\n\tif err = checkStatus(201, \"requesting reviewer\", res, err); err != nil {\n\t\treturn\n\t}\n\n\tres.Body.Close()\n\treturn\n}\n\nfunc (client *Client) CommitPatch(project *Project, sha string) (patch io.ReadCloser, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.GetFile(fmt.Sprintf(\"repos/%s/%s/commits/%s\", project.Owner, project.Name, sha), patchMediaType)\n\tif err = checkStatus(200, \"getting commit patch\", res, err); err != nil {\n\t\treturn\n\t}\n\n\treturn res.Body, nil\n}\n\nfunc (client *Client) GistPatch(id string) (patch io.ReadCloser, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.Get(fmt.Sprintf(\"gists/%s\", id))\n\tif err = checkStatus(200, \"getting gist patch\", res, err); err != nil {\n\t\treturn\n\t}\n\n\tgist := Gist{}\n\tif err = res.Unmarshal(&gist); err != nil {\n\t\treturn\n\t}\n\trawURL := \"\"\n\tfor _, file := range gist.Files {\n\t\trawURL = file.RawURL\n\t\tbreak\n\t}\n\n\tres, err = api.GetFile(rawURL, textMediaType)\n\tif err = checkStatus(200, \"getting gist patch\", res, err); err != nil {\n\t\treturn\n\t}\n\n\treturn res.Body, nil\n}\n\nfunc (client *Client) Repository(project *Project) (repo *Repository, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.Get(fmt.Sprintf(\"repos/%s/%s\", project.Owner, project.Name))\n\tif err = checkStatus(200, \"getting repository info\", res, err); err != nil {\n\t\treturn\n\t}\n\n\trepo = &Repository{}\n\terr = res.Unmarshal(&repo)\n\treturn\n}\n\nfunc (client *Client) CreateRepository(project *Project, description, homepage string, isPrivate bool) (repo *Repository, err error) {\n\trepoURL := \"user/repos\"\n\tif project.Owner != client.Host.User {\n\t\trepoURL = fmt.Sprintf(\"orgs/%s/repos\", project.Owner)\n\t}\n\n\tparams := map[string]interface{}{\n\t\t\"name\":        project.Name,\n\t\t\"description\": description,\n\t\t\"homepage\":    homepage,\n\t\t\"private\":     isPrivate,\n\t}\n\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.PostJSON(repoURL, params)\n\tif err = checkStatus(201, \"creating repository\", res, err); err != nil {\n\t\treturn\n\t}\n\n\trepo = &Repository{}\n\terr = res.Unmarshal(repo)\n\treturn\n}\n\nfunc (client *Client) DeleteRepository(project *Project) error {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trepoURL := fmt.Sprintf(\"repos/%s/%s\", project.Owner, project.Name)\n\tres, err := api.Delete(repoURL)\n\treturn checkStatus(204, \"deleting repository\", res, err)\n}\n\ntype Release struct {\n\tName            string         `json:\"name\"`\n\tTagName         string         `json:\"tag_name\"`\n\tTargetCommitish string         `json:\"target_commitish\"`\n\tBody            string         `json:\"body\"`\n\tDraft           bool           `json:\"draft\"`\n\tPrerelease      bool           `json:\"prerelease\"`\n\tAssets          []ReleaseAsset `json:\"assets\"`\n\tTarballURL      string         `json:\"tarball_url\"`\n\tZipballURL      string         `json:\"zipball_url\"`\n\tHTMLURL         string         `json:\"html_url\"`\n\tUploadURL       string         `json:\"upload_url\"`\n\tAPIURL          string         `json:\"url\"`\n\tCreatedAt       time.Time      `json:\"created_at\"`\n\tPublishedAt     time.Time      `json:\"published_at\"`\n}\n\ntype ReleaseAsset struct {\n\tName        string `json:\"name\"`\n\tLabel       string `json:\"label\"`\n\tDownloadURL string `json:\"browser_download_url\"`\n\tAPIURL      string `json:\"url\"`\n}\n\nfunc (client *Client) FetchReleases(project *Project, limit int, filter func(*Release) bool) (releases []Release, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tpath := fmt.Sprintf(\"repos/%s/%s/releases?per_page=%d\", project.Owner, project.Name, perPage(limit, 100))\n\n\treleases = []Release{}\n\tvar res *simpleResponse\n\n\tfor path != \"\" {\n\t\tres, err = api.Get(path)\n\t\tif err = checkStatus(200, \"fetching releases\", res, err); err != nil {\n\t\t\treturn\n\t\t}\n\t\tpath = res.Link(\"next\")\n\n\t\treleasesPage := []Release{}\n\t\tif err = res.Unmarshal(&releasesPage); err != nil {\n\t\t\treturn\n\t\t}\n\t\tfor _, release := range releasesPage {\n\t\t\tif filter == nil || filter(&release) {\n\t\t\t\treleases = append(releases, release)\n\t\t\t\tif limit > 0 && len(releases) == limit {\n\t\t\t\t\tpath = \"\"\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (client *Client) FetchRelease(project *Project, tagName string) (*Release, error) {\n\treleases, err := client.FetchReleases(project, 100, func(release *Release) bool {\n\t\treturn release.TagName == tagName\n\t})\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(releases) < 1 {\n\t\treturn nil, fmt.Errorf(\"Unable to find release with tag name `%s'\", tagName)\n\t}\n\treturn &releases[0], nil\n}\n\nfunc (client *Client) CreateRelease(project *Project, releaseParams *Release) (release *Release, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.PostJSON(fmt.Sprintf(\"repos/%s/%s/releases\", project.Owner, project.Name), releaseParams)\n\tif err = checkStatus(201, \"creating release\", res, err); err != nil {\n\t\treturn\n\t}\n\n\trelease = &Release{}\n\terr = res.Unmarshal(release)\n\treturn\n}\n\nfunc (client *Client) EditRelease(release *Release, releaseParams map[string]interface{}) (updatedRelease *Release, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.PatchJSON(release.APIURL, releaseParams)\n\tif err = checkStatus(200, \"editing release\", res, err); err != nil {\n\t\treturn\n\t}\n\n\tupdatedRelease = &Release{}\n\terr = res.Unmarshal(updatedRelease)\n\treturn\n}\n\nfunc (client *Client) DeleteRelease(release *Release) (err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.Delete(release.APIURL)\n\tif err = checkStatus(204, \"deleting release\", res, err); err != nil {\n\t\treturn\n\t}\n\n\treturn\n}\n\ntype LocalAsset struct {\n\tName     string\n\tLabel    string\n\tContents io.Reader\n\tSize     int64\n}\n\nfunc (client *Client) UploadReleaseAssets(release *Release, assets []LocalAsset) (doneAssets []*ReleaseAsset, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tidx := strings.Index(release.UploadURL, \"{\")\n\tuploadURL := release.UploadURL[0:idx]\n\n\tfor _, asset := range assets {\n\t\tfor _, existingAsset := range release.Assets {\n\t\t\tif existingAsset.Name == asset.Name {\n\t\t\t\tif err = client.DeleteReleaseAsset(&existingAsset); err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tparams := map[string]interface{}{\"name\": filepath.Base(asset.Name)}\n\t\tif asset.Label != \"\" {\n\t\t\tparams[\"label\"] = asset.Label\n\t\t}\n\t\tuploadPath := addQuery(uploadURL, params)\n\n\t\tvar res *simpleResponse\n\t\tattempts := 0\n\t\tmaxAttempts := 3\n\t\tbody := asset.Contents\n\t\tfor {\n\t\t\tres, err = api.PostFile(uploadPath, body, asset.Size)\n\t\t\tif err == nil && res.StatusCode >= 500 && res.StatusCode < 600 && attempts < maxAttempts {\n\t\t\t\tattempts++\n\t\t\t\ttime.Sleep(time.Second * time.Duration(attempts))\n\t\t\t\tvar f *os.File\n\t\t\t\tf, err = os.Open(asset.Name)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tdefer f.Close()\n\t\t\t\tbody = f\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err = checkStatus(201, \"uploading release asset\", res, err); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\tnewAsset := ReleaseAsset{}\n\t\terr = res.Unmarshal(&newAsset)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tdoneAssets = append(doneAssets, &newAsset)\n\t}\n\n\treturn\n}\n\nfunc (client *Client) DeleteReleaseAsset(asset *ReleaseAsset) (err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.Delete(asset.APIURL)\n\terr = checkStatus(204, \"deleting release asset\", res, err)\n\n\treturn\n}\n\nfunc (client *Client) DownloadReleaseAsset(url string) (asset io.ReadCloser, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tresp, err := api.GetFile(url, \"application/octet-stream\")\n\tif err = checkStatus(200, \"downloading asset\", resp, err); err != nil {\n\t\treturn\n\t}\n\n\treturn resp.Body, err\n}\n\ntype CIStatusResponse struct {\n\tState    string     `json:\"state\"`\n\tStatuses []CIStatus `json:\"statuses\"`\n}\n\ntype CIStatus struct {\n\tState     string `json:\"state\"`\n\tContext   string `json:\"context\"`\n\tTargetURL string `json:\"target_url\"`\n}\n\ntype CheckRunsResponse struct {\n\tCheckRuns []CheckRun `json:\"check_runs\"`\n}\n\ntype CheckRun struct {\n\tStatus     string `json:\"status\"`\n\tConclusion string `json:\"conclusion\"`\n\tName       string `json:\"name\"`\n\tHTMLURL    string `json:\"html_url\"`\n}\n\nfunc (client *Client) FetchCIStatus(project *Project, sha string) (status *CIStatusResponse, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.Get(fmt.Sprintf(\"repos/%s/%s/commits/%s/status?per_page=100\", project.Owner, project.Name, sha))\n\tif err = checkStatus(200, \"fetching statuses\", res, err); err != nil {\n\t\treturn\n\t}\n\n\tstatus = &CIStatusResponse{}\n\tif err = res.Unmarshal(status); err != nil {\n\t\treturn\n\t}\n\n\tsortStatuses := func() {\n\t\tsort.Slice(status.Statuses, func(a, b int) bool {\n\t\t\tsA := status.Statuses[a]\n\t\t\tsB := status.Statuses[b]\n\t\t\tcmp := strings.Compare(strings.ToLower(sA.Context), strings.ToLower(sB.Context))\n\t\t\tif cmp == 0 {\n\t\t\t\treturn strings.Compare(sA.TargetURL, sB.TargetURL) < 0\n\t\t\t}\n\t\t\treturn cmp < 0\n\t\t})\n\t}\n\tsortStatuses()\n\n\tres, err = api.GetFile(fmt.Sprintf(\"repos/%s/%s/commits/%s/check-runs?per_page=100\", project.Owner, project.Name, sha), checksType)\n\tif err == nil && (res.StatusCode == 403 || res.StatusCode == 404 || res.StatusCode == 422) {\n\t\treturn\n\t}\n\tif err = checkStatus(200, \"fetching checks\", res, err); err != nil {\n\t\treturn\n\t}\n\n\tchecks := &CheckRunsResponse{}\n\tif err = res.Unmarshal(checks); err != nil {\n\t\treturn\n\t}\n\n\tfor _, checkRun := range checks.CheckRuns {\n\t\tstate := \"pending\"\n\t\tif checkRun.Status == \"completed\" {\n\t\t\tstate = checkRun.Conclusion\n\t\t}\n\t\tcheckStatus := CIStatus{\n\t\t\tState:     state,\n\t\t\tContext:   checkRun.Name,\n\t\t\tTargetURL: checkRun.HTMLURL,\n\t\t}\n\t\tstatus.Statuses = append(status.Statuses, checkStatus)\n\t}\n\n\tsortStatuses()\n\n\treturn\n}\n\ntype Repository struct {\n\tName          string                 `json:\"name\"`\n\tFullName      string                 `json:\"full_name\"`\n\tParent        *Repository            `json:\"parent\"`\n\tOwner         *User                  `json:\"owner\"`\n\tPrivate       bool                   `json:\"private\"`\n\tHasWiki       bool                   `json:\"has_wiki\"`\n\tPermissions   *RepositoryPermissions `json:\"permissions\"`\n\tHTMLURL       string                 `json:\"html_url\"`\n\tDefaultBranch string                 `json:\"default_branch\"`\n}\n\ntype RepositoryPermissions struct {\n\tAdmin bool `json:\"admin\"`\n\tPush  bool `json:\"push\"`\n\tPull  bool `json:\"pull\"`\n}\n\nfunc (client *Client) ForkRepository(project *Project, params map[string]interface{}) (repo *Repository, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.PostJSON(fmt.Sprintf(\"repos/%s/%s/forks\", project.Owner, project.Name), params)\n\tif err = checkStatus(202, \"creating fork\", res, err); err != nil {\n\t\treturn\n\t}\n\n\trepo = &Repository{}\n\terr = res.Unmarshal(repo)\n\n\treturn\n}\n\ntype Comment struct {\n\tID        int       `json:\"id\"`\n\tBody      string    `json:\"body\"`\n\tUser      *User     `json:\"user\"`\n\tCreatedAt time.Time `json:\"created_at\"`\n}\n\ntype Issue struct {\n\tNumber int    `json:\"number\"`\n\tState  string `json:\"state\"`\n\tTitle  string `json:\"title\"`\n\tBody   string `json:\"body\"`\n\tUser   *User  `json:\"user\"`\n\n\tPullRequest *PullRequest     `json:\"pull_request\"`\n\tHead        *PullRequestSpec `json:\"head\"`\n\tBase        *PullRequestSpec `json:\"base\"`\n\n\tMergeCommitSha      string `json:\"merge_commit_sha\"`\n\tMaintainerCanModify bool   `json:\"maintainer_can_modify\"`\n\tDraft               bool   `json:\"draft\"`\n\n\tComments  int          `json:\"comments\"`\n\tLabels    []IssueLabel `json:\"labels\"`\n\tAssignees []User       `json:\"assignees\"`\n\tMilestone *Milestone   `json:\"milestone\"`\n\tCreatedAt time.Time    `json:\"created_at\"`\n\tUpdatedAt time.Time    `json:\"updated_at\"`\n\tMergedAt  time.Time    `json:\"merged_at\"`\n\n\tRequestedReviewers []User `json:\"requested_reviewers\"`\n\tRequestedTeams     []Team `json:\"requested_teams\"`\n\n\tAPIURL  string `json:\"url\"`\n\tHTMLURL string `json:\"html_url\"`\n\n\tClosedBy *User `json:\"closed_by\"`\n}\n\ntype PullRequest Issue\n\ntype PullRequestSpec struct {\n\tLabel string      `json:\"label\"`\n\tRef   string      `json:\"ref\"`\n\tSha   string      `json:\"sha\"`\n\tRepo  *Repository `json:\"repo\"`\n}\n\nfunc (pr *PullRequest) IsSameRepo() bool {\n\treturn pr.Head != nil && pr.Head.Repo != nil &&\n\t\tpr.Head.Repo.Name == pr.Base.Repo.Name &&\n\t\tpr.Head.Repo.Owner.Login == pr.Base.Repo.Owner.Login\n}\n\nfunc (pr *PullRequest) HasRequestedReviewer(name string) bool {\n\tfor _, user := range pr.RequestedReviewers {\n\t\tif strings.EqualFold(user.Login, name) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (pr *PullRequest) HasRequestedTeam(name string) bool {\n\tfor _, team := range pr.RequestedTeams {\n\t\tif strings.EqualFold(team.Slug, name) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\ntype IssueLabel struct {\n\tName  string `json:\"name\"`\n\tColor string `json:\"color\"`\n}\n\ntype User struct {\n\tLogin string `json:\"login\"`\n}\n\ntype Team struct {\n\tName string `json:\"name\"`\n\tSlug string `json:\"slug\"`\n}\n\ntype Milestone struct {\n\tNumber int    `json:\"number\"`\n\tTitle  string `json:\"title\"`\n}\n\nfunc (client *Client) FetchIssues(project *Project, filterParams map[string]interface{}, limit int, filter func(*Issue) bool) (issues []Issue, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tpath := fmt.Sprintf(\"repos/%s/%s/issues?per_page=%d\", project.Owner, project.Name, perPage(limit, 100))\n\tif filterParams != nil {\n\t\tpath = addQuery(path, filterParams)\n\t}\n\n\tissues = []Issue{}\n\tvar res *simpleResponse\n\n\tfor path != \"\" {\n\t\tres, err = api.Get(path)\n\t\tif err = checkStatus(200, \"fetching issues\", res, err); err != nil {\n\t\t\treturn\n\t\t}\n\t\tpath = res.Link(\"next\")\n\n\t\tissuesPage := []Issue{}\n\t\tif err = res.Unmarshal(&issuesPage); err != nil {\n\t\t\treturn\n\t\t}\n\t\tfor _, issue := range issuesPage {\n\t\t\tif filter == nil || filter(&issue) {\n\t\t\t\tissues = append(issues, issue)\n\t\t\t\tif limit > 0 && len(issues) == limit {\n\t\t\t\t\tpath = \"\"\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (client *Client) FetchIssue(project *Project, number string) (issue *Issue, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.Get(fmt.Sprintf(\"repos/%s/%s/issues/%s\", project.Owner, project.Name, number))\n\tif err = checkStatus(200, \"fetching issue\", res, err); err != nil {\n\t\treturn nil, err\n\t}\n\n\tissue = &Issue{}\n\terr = res.Unmarshal(issue)\n\treturn\n}\n\nfunc (client *Client) FetchComments(project *Project, number string) (comments []Comment, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.Get(fmt.Sprintf(\"repos/%s/%s/issues/%s/comments\", project.Owner, project.Name, number))\n\tif err = checkStatus(200, \"fetching comments for issue\", res, err); err != nil {\n\t\treturn nil, err\n\t}\n\n\tcomments = []Comment{}\n\terr = res.Unmarshal(&comments)\n\treturn\n}\n\nfunc (client *Client) CreateIssue(project *Project, params interface{}) (issue *Issue, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.PostJSON(fmt.Sprintf(\"repos/%s/%s/issues\", project.Owner, project.Name), params)\n\tif err = checkStatus(201, \"creating issue\", res, err); err != nil {\n\t\treturn\n\t}\n\n\tissue = &Issue{}\n\terr = res.Unmarshal(issue)\n\treturn\n}\n\nfunc (client *Client) UpdateIssue(project *Project, issueNumber int, params map[string]interface{}) (err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.PatchJSON(fmt.Sprintf(\"repos/%s/%s/issues/%d\", project.Owner, project.Name, issueNumber), params)\n\tif err = checkStatus(200, \"updating issue\", res, err); err != nil {\n\t\treturn\n\t}\n\n\tres.Body.Close()\n\treturn\n}\n\ntype sortedLabels []IssueLabel\n\nfunc (s sortedLabels) Len() int {\n\treturn len(s)\n}\nfunc (s sortedLabels) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\nfunc (s sortedLabels) Less(i, j int) bool {\n\treturn strings.Compare(strings.ToLower(s[i].Name), strings.ToLower(s[j].Name)) < 0\n}\n\nfunc (client *Client) FetchLabels(project *Project) (labels []IssueLabel, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tpath := fmt.Sprintf(\"repos/%s/%s/labels?per_page=100\", project.Owner, project.Name)\n\n\tlabels = []IssueLabel{}\n\tvar res *simpleResponse\n\n\tfor path != \"\" {\n\t\tres, err = api.Get(path)\n\t\tif err = checkStatus(200, \"fetching labels\", res, err); err != nil {\n\t\t\treturn\n\t\t}\n\t\tpath = res.Link(\"next\")\n\n\t\tlabelsPage := []IssueLabel{}\n\t\tif err = res.Unmarshal(&labelsPage); err != nil {\n\t\t\treturn\n\t\t}\n\t\tlabels = append(labels, labelsPage...)\n\t}\n\n\tsort.Sort(sortedLabels(labels))\n\n\treturn\n}\n\nfunc (client *Client) FetchMilestones(project *Project) (milestones []Milestone, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tpath := fmt.Sprintf(\"repos/%s/%s/milestones?per_page=100\", project.Owner, project.Name)\n\n\tmilestones = []Milestone{}\n\tvar res *simpleResponse\n\n\tfor path != \"\" {\n\t\tres, err = api.Get(path)\n\t\tif err = checkStatus(200, \"fetching milestones\", res, err); err != nil {\n\t\t\treturn\n\t\t}\n\t\tpath = res.Link(\"next\")\n\n\t\tmilestonesPage := []Milestone{}\n\t\tif err = res.Unmarshal(&milestonesPage); err != nil {\n\t\t\treturn\n\t\t}\n\t\tmilestones = append(milestones, milestonesPage...)\n\t}\n\n\treturn\n}\n\nfunc (client *Client) GenericAPIRequest(method, path string, data interface{}, headers map[string]string, ttl int) (*simpleResponse, error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tapi.CacheTTL = ttl\n\n\tvar body io.Reader\n\tswitch d := data.(type) {\n\tcase map[string]interface{}:\n\t\tif method == \"GET\" {\n\t\t\tpath = addQuery(path, d)\n\t\t} else if len(d) > 0 {\n\t\t\tjson, err := json.Marshal(d)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tbody = bytes.NewBuffer(json)\n\t\t}\n\tcase io.Reader:\n\t\tbody = d\n\t}\n\n\treturn api.performRequest(method, path, body, func(req *http.Request) {\n\t\tif body != nil {\n\t\t\treq.Header.Set(\"Content-Type\", \"application/json; charset=utf-8\")\n\t\t}\n\t\tfor key, value := range headers {\n\t\t\treq.Header.Set(key, value)\n\t\t}\n\t})\n}\n\n// GraphQL facilitates performing a GraphQL request and parsing the response\nfunc (client *Client) GraphQL(query string, variables interface{}, data interface{}) error {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpayload := map[string]interface{}{\n\t\t\"query\":     query,\n\t\t\"variables\": variables,\n\t}\n\tresp, err := api.PostJSON(\"graphql\", payload)\n\tif err = checkStatus(200, \"performing GraphQL\", resp, err); err != nil {\n\t\treturn err\n\t}\n\n\tresponseData := struct {\n\t\tData   interface{}\n\t\tErrors []struct {\n\t\t\tMessage string\n\t\t}\n\t}{\n\t\tData: data,\n\t}\n\terr = resp.Unmarshal(&responseData)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(responseData.Errors) > 0 {\n\t\tmessages := []string{}\n\t\tfor _, e := range responseData.Errors {\n\t\t\tmessages = append(messages, e.Message)\n\t\t}\n\t\treturn fmt.Errorf(\"API error: %s\", strings.Join(messages, \"; \"))\n\t}\n\treturn nil\n}\n\nfunc (client *Client) CurrentUser() (user *User, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tres, err := api.Get(\"user\")\n\tif err = checkStatus(200, \"getting current user\", res, err); err != nil {\n\t\treturn\n\t}\n\n\tuser = &User{}\n\terr = res.Unmarshal(user)\n\treturn\n}\n\ntype AuthorizationEntry struct {\n\tToken string `json:\"token\"`\n}\n\nfunc isToken(api *simpleClient, password string) bool {\n\tapi.PrepareRequest = func(req *http.Request) {\n\t\treq.Header.Set(\"Authorization\", \"token \"+password)\n\t}\n\n\tres, _ := api.Get(\"user\")\n\tif res != nil && res.StatusCode == 200 {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (client *Client) FindOrCreateToken(user, password, twoFactorCode string) (token string, err error) {\n\tapi := client.apiClient()\n\n\tif len(password) >= 40 && isToken(api, password) {\n\t\treturn password, nil\n\t}\n\n\tparams := map[string]interface{}{\n\t\t\"scopes\":   []string{\"repo\", \"gist\"},\n\t\t\"note_url\": OAuthAppURL,\n\t}\n\n\tapi.PrepareRequest = func(req *http.Request) {\n\t\treq.SetBasicAuth(user, password)\n\t\tif twoFactorCode != \"\" {\n\t\t\treq.Header.Set(\"X-GitHub-OTP\", twoFactorCode)\n\t\t}\n\t}\n\n\tcount := 1\n\tmaxTries := 9\n\tfor {\n\t\tparams[\"note\"], err = authTokenNote(count)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tres, postErr := api.PostJSON(\"authorizations\", params)\n\t\tif postErr != nil {\n\t\t\terr = postErr\n\t\t\tbreak\n\t\t}\n\n\t\tif res.StatusCode == 201 {\n\t\t\tauth := &AuthorizationEntry{}\n\t\t\tif err = res.Unmarshal(auth); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttoken = auth.Token\n\t\t\tbreak\n\t\t} else if res.StatusCode == 422 && count < maxTries {\n\t\t\tcount++\n\t\t} else {\n\t\t\terrInfo, e := res.ErrorInfo()\n\t\t\tif e == nil {\n\t\t\t\terr = errInfo\n\t\t\t} else {\n\t\t\t\terr = e\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (client *Client) ensureAccessToken() error {\n\tif client.Host.AccessToken == \"\" {\n\t\thost, err := CurrentConfig().PromptForHost(client.Host.Host)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tclient.Host = host\n\t}\n\treturn nil\n}\n\nfunc (client *Client) simpleAPI() (c *simpleClient, err error) {\n\terr = client.ensureAccessToken()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif client.cachedClient != nil {\n\t\tc = client.cachedClient\n\t\treturn\n\t}\n\n\tc = client.apiClient()\n\tc.PrepareRequest = func(req *http.Request) {\n\t\tclientDomain := normalizeHost(client.Host.Host)\n\t\tif strings.HasPrefix(clientDomain, \"api.github.\") {\n\t\t\tclientDomain = strings.TrimPrefix(clientDomain, \"api.\")\n\t\t}\n\t\trequestHost := strings.ToLower(req.URL.Host)\n\t\tif requestHost == clientDomain || strings.HasSuffix(requestHost, \".\"+clientDomain) {\n\t\t\treq.Header.Set(\"Authorization\", \"token \"+client.Host.AccessToken)\n\t\t}\n\t}\n\n\tclient.cachedClient = c\n\treturn\n}\n\nfunc (client *Client) apiClient() *simpleClient {\n\tunixSocket := os.ExpandEnv(client.Host.UnixSocket)\n\thttpClient := newHTTPClient(os.Getenv(\"HUB_TEST_HOST\"), os.Getenv(\"HUB_VERBOSE\") != \"\", unixSocket)\n\tapiRoot := client.absolute(normalizeHost(client.Host.Host))\n\tif !strings.HasPrefix(apiRoot.Host, \"api.github.\") {\n\t\tapiRoot.Path = \"/api/v3/\"\n\t}\n\n\treturn &simpleClient{\n\t\thttpClient: httpClient,\n\t\trootURL:    apiRoot,\n\t}\n}\n\nfunc (client *Client) absolute(host string) *url.URL {\n\tu, err := url.Parse(\"https://\" + host + \"/\")\n\tif err != nil {\n\t\tpanic(err)\n\t} else if client.Host != nil && client.Host.Protocol != \"\" {\n\t\tu.Scheme = client.Host.Protocol\n\t}\n\treturn u\n}\n\nfunc (client *Client) FetchGist(id string) (gist *Gist, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tresponse, err := api.Get(fmt.Sprintf(\"gists/%s\", id))\n\tif err = checkStatus(200, \"getting gist\", response, err); err != nil {\n\t\treturn\n\t}\n\n\tresponse.Unmarshal(&gist)\n\treturn\n}\n\nfunc (client *Client) CreateGist(filenames []string, public bool) (gist *Gist, err error) {\n\tapi, err := client.simpleAPI()\n\tif err != nil {\n\t\treturn\n\t}\n\tfiles := map[string]GistFile{}\n\tvar basename string\n\tvar content []byte\n\tvar gf GistFile\n\n\tfor _, file := range filenames {\n\t\tif file == \"-\" {\n\t\t\tcontent, err = ioutil.ReadAll(os.Stdin)\n\t\t\tbasename = \"gistfile1.txt\"\n\t\t} else {\n\t\t\tcontent, err = ioutil.ReadFile(file)\n\t\t\tbasename = path.Base(file)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tgf = GistFile{Content: string(content)}\n\t\tfiles[basename] = gf\n\t}\n\n\tg := Gist{\n\t\tFiles:  files,\n\t\tPublic: public,\n\t}\n\n\tres, err := api.PostJSON(\"gists\", &g)\n\tif err = checkStatus(201, \"creating gist\", res, err); err != nil {\n\t\treturn\n\t}\n\n\terr = res.Unmarshal(&gist)\n\treturn\n}\n\nfunc normalizeHost(host string) string {\n\tif host == \"\" {\n\t\treturn GitHubHost\n\t} else if strings.EqualFold(host, GitHubHost) {\n\t\treturn \"api.github.com\"\n\t} else if strings.EqualFold(host, \"github.localhost\") {\n\t\treturn \"api.github.localhost\"\n\t} else {\n\t\treturn strings.ToLower(host)\n\t}\n}\n\nfunc reverseNormalizeHost(host string) string {\n\tswitch host {\n\tcase \"api.github.com\":\n\t\treturn GitHubHost\n\tcase \"api.github.localhost\":\n\t\treturn \"github.localhost\"\n\tdefault:\n\t\treturn host\n\t}\n}\n\nfunc checkStatus(expectedStatus int, action string, response *simpleResponse, err error) error {\n\tif err != nil {\n\t\terrStr := err.Error()\n\t\tif urlErr, isURLErr := err.(*url.Error); isURLErr {\n\t\t\terrStr = fmt.Sprintf(\"%s %s: %s\", urlErr.Op, urlErr.URL, urlErr.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"Error %s: %s\", action, errStr)\n\t} else if response.StatusCode != expectedStatus {\n\t\terrInfo, err := response.ErrorInfo()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Error %s: %s (HTTP %d)\", action, err.Error(), response.StatusCode)\n\t\t}\n\t\treturn FormatError(action, errInfo)\n\t}\n\treturn nil\n}\n\n// FormatError annotates an HTTP response error with user-friendly messages\nfunc FormatError(action string, err error) error {\n\tif e, ok := err.(*errorInfo); ok {\n\t\treturn formatError(action, e)\n\t}\n\treturn err\n}\n\nfunc formatError(action string, e *errorInfo) error {\n\tvar reason string\n\tif s := strings.SplitN(e.Response.Status, \" \", 2); len(s) >= 2 {\n\t\treason = strings.TrimSpace(s[1])\n\t}\n\n\terrStr := fmt.Sprintf(\"Error %s: %s (HTTP %d)\", action, reason, e.Response.StatusCode)\n\n\tvar errorSentences []string\n\tfor _, err := range e.Errors {\n\t\tswitch err.Code {\n\t\tcase \"custom\":\n\t\t\terrorSentences = append(errorSentences, err.Message)\n\t\tcase \"missing_field\":\n\t\t\terrorSentences = append(errorSentences, fmt.Sprintf(\"Missing field: \\\"%s\\\"\", err.Field))\n\t\tcase \"already_exists\":\n\t\t\terrorSentences = append(errorSentences, fmt.Sprintf(\"Duplicate value for \\\"%s\\\"\", err.Field))\n\t\tcase \"invalid\":\n\t\t\terrorSentences = append(errorSentences, fmt.Sprintf(\"Invalid value for \\\"%s\\\"\", err.Field))\n\t\tcase \"unauthorized\":\n\t\t\terrorSentences = append(errorSentences, fmt.Sprintf(\"Not allowed to change field \\\"%s\\\"\", err.Field))\n\t\t}\n\t}\n\n\tvar errorMessage string\n\tif len(errorSentences) > 0 {\n\t\terrorMessage = strings.Join(errorSentences, \"\\n\")\n\t} else {\n\t\terrorMessage = e.Message\n\t\tif action == \"getting current user\" && e.Message == \"Resource not accessible by integration\" {\n\t\t\terrorMessage = errorMessage + \"\\nYou must specify GITHUB_USER via environment variable.\"\n\t\t}\n\t}\n\tif errorMessage != \"\" {\n\t\terrStr = fmt.Sprintf(\"%s\\n%s\", errStr, errorMessage)\n\t}\n\n\tif ssoErr := ValidateGitHubSSO(e.Response); ssoErr != nil {\n\t\treturn fmt.Errorf(\"%s\\n%s\", errStr, ssoErr)\n\t}\n\n\tif scopeErr := ValidateSufficientOAuthScopes(e.Response); scopeErr != nil {\n\t\treturn fmt.Errorf(\"%s\\n%s\", errStr, scopeErr)\n\t}\n\n\treturn errors.New(errStr)\n}\n\n// ValidateGitHubSSO checks for the challenge via `X-Github-Sso` header\nfunc ValidateGitHubSSO(res *http.Response) error {\n\tif res.StatusCode != 403 {\n\t\treturn nil\n\t}\n\n\tsso := res.Header.Get(\"X-Github-Sso\")\n\tif !strings.HasPrefix(sso, \"required; url=\") {\n\t\treturn nil\n\t}\n\n\turl := sso[strings.IndexByte(sso, '=')+1:]\n\treturn fmt.Errorf(\"You must authorize your token to access this organization:\\n%s\", url)\n}\n\n// ValidateSufficientOAuthScopes warns about insufficient OAuth scopes\nfunc ValidateSufficientOAuthScopes(res *http.Response) error {\n\tif res.StatusCode != 404 && res.StatusCode != 403 {\n\t\treturn nil\n\t}\n\n\tneedScopes := newScopeSet(res.Header.Get(\"X-Accepted-Oauth-Scopes\"))\n\tif len(needScopes) == 0 && isGistWrite(res.Request) {\n\t\t// compensate for a GitHub bug: gist APIs omit proper `X-Accepted-Oauth-Scopes` in responses\n\t\tneedScopes = newScopeSet(\"gist\")\n\t}\n\n\thaveScopes := newScopeSet(res.Header.Get(\"X-Oauth-Scopes\"))\n\tif len(needScopes) == 0 || needScopes.Intersects(haveScopes) {\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"Your access token may have insufficient scopes. Visit %s://%s/settings/tokens\\n\"+\n\t\t\"to edit the 'hub' token and enable one of the following scopes: %s\",\n\t\tres.Request.URL.Scheme,\n\t\treverseNormalizeHost(res.Request.Host),\n\t\tneedScopes)\n}\n\nfunc isGistWrite(req *http.Request) bool {\n\tif req.Method == \"GET\" {\n\t\treturn false\n\t}\n\tpath := strings.TrimPrefix(req.URL.Path, \"/v3\")\n\treturn strings.HasPrefix(path, \"/gists\")\n}\n\ntype scopeSet map[string]struct{}\n\nfunc (s scopeSet) String() string {\n\tscopes := make([]string, 0, len(s))\n\tfor scope := range s {\n\t\tscopes = append(scopes, scope)\n\t}\n\tsort.Strings(scopes)\n\treturn strings.Join(scopes, \", \")\n}\n\nfunc (s scopeSet) Intersects(other scopeSet) bool {\n\tfor scope := range s {\n\t\tif _, found := other[scope]; found {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc newScopeSet(s string) scopeSet {\n\tscopes := scopeSet{}\n\tfor _, s := range strings.SplitN(s, \",\", -1) {\n\t\tif s = strings.TrimSpace(s); s != \"\" {\n\t\t\tscopes[s] = struct{}{}\n\t\t}\n\t}\n\treturn scopes\n}\n\nfunc authTokenNote(num int) (string, error) {\n\tn := os.Getenv(\"USER\")\n\n\tif n == \"\" {\n\t\tn = os.Getenv(\"USERNAME\")\n\t}\n\n\tif n == \"\" {\n\t\twhoami := exec.Command(\"whoami\")\n\t\twhoamiOut, err := whoami.Output()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tn = strings.TrimSpace(string(whoamiOut))\n\t}\n\n\th, err := os.Hostname()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif num > 1 {\n\t\treturn fmt.Sprintf(\"hub for %s@%s %d\", n, h, num), nil\n\t}\n\n\treturn fmt.Sprintf(\"hub for %s@%s\", n, h), nil\n}\n\nfunc perPage(limit, max int) int {\n\tif limit > 0 {\n\t\tlimit = limit + (limit / 2)\n\t\tif limit < max {\n\t\t\treturn limit\n\t\t}\n\t}\n\treturn max\n}\n\nfunc addQuery(path string, params map[string]interface{}) string {\n\tif len(params) == 0 {\n\t\treturn path\n\t}\n\n\tquery := url.Values{}\n\tfor key, value := range params {\n\t\tswitch v := value.(type) {\n\t\tcase string:\n\t\t\tquery.Add(key, v)\n\t\tcase nil:\n\t\t\tquery.Add(key, \"\")\n\t\tcase int:\n\t\t\tquery.Add(key, fmt.Sprintf(\"%d\", v))\n\t\tcase bool:\n\t\t\tquery.Add(key, fmt.Sprintf(\"%v\", v))\n\t\t}\n\t}\n\n\tsep := \"?\"\n\tif strings.Contains(path, sep) {\n\t\tsep = \"&\"\n\t}\n\treturn path + sep + query.Encode()\n}\n"
  },
  {
    "path": "github/client_test.go",
    "content": "package github\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestClient_FormatError(t *testing.T) {\n\te := &errorInfo{\n\t\tResponse: &http.Response{\n\t\t\tStatusCode: 401,\n\t\t\tStatus:     \"401 Not Found\",\n\t\t},\n\t}\n\n\terr := FormatError(\"action\", e)\n\tassert.Equal(t, \"Error action: Not Found (HTTP 401)\", fmt.Sprintf(\"%s\", err))\n\n\te = &errorInfo{\n\t\tResponse: &http.Response{\n\t\t\tStatusCode: 422,\n\t\t\tStatus:     \"422 Unprocessable Entity\",\n\t\t},\n\t\tMessage: \"error message\",\n\t}\n\terr = FormatError(\"action\", e)\n\tassert.Equal(t, \"Error action: Unprocessable Entity (HTTP 422)\\nerror message\", fmt.Sprintf(\"%s\", err))\n}\n\nfunc TestAuthTokenNote(t *testing.T) {\n\tnote, err := authTokenNote(1)\n\tassert.Equal(t, nil, err)\n\n\treg := regexp.MustCompile(\"hub for (.+)@(.+)\")\n\tassert.T(t, reg.MatchString(note))\n\n\tnote, err = authTokenNote(2)\n\tassert.Equal(t, nil, err)\n\n\treg = regexp.MustCompile(\"hub for (.+)@(.+) 2\")\n\tassert.T(t, reg.MatchString(note))\n\n}\n"
  },
  {
    "path": "github/config.go",
    "content": "package github\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n\t\"github.com/mitchellh/go-homedir\"\n\t\"golang.org/x/crypto/ssh/terminal\"\n)\n\ntype yamlHost struct {\n\tUser       string `yaml:\"user\"`\n\tOAuthToken string `yaml:\"oauth_token\"`\n\tProtocol   string `yaml:\"protocol\"`\n\tUnixSocket string `yaml:\"unix_socket,omitempty\"`\n}\n\ntype Host struct {\n\tHost        string `toml:\"host\"`\n\tUser        string `toml:\"user\"`\n\tAccessToken string `toml:\"access_token\"`\n\tProtocol    string `toml:\"protocol\"`\n\tUnixSocket  string `toml:\"unix_socket,omitempty\"`\n}\n\ntype Config struct {\n\tHosts []*Host `toml:\"hosts\"`\n\n\tstdinScanner *bufio.Scanner\n}\n\nfunc (c *Config) PromptForHost(host string) (h *Host, err error) {\n\ttoken := c.DetectToken()\n\ttokenFromEnv := token != \"\"\n\n\tif host != GitHubHost {\n\t\tif _, e := url.Parse(\"https://\" + host); e != nil {\n\t\t\terr = fmt.Errorf(\"invalid hostname: %q\", host)\n\t\t\treturn\n\t\t}\n\t}\n\n\th = c.Find(host)\n\tif h != nil {\n\t\tif h.User == \"\" {\n\t\t\tutils.Check(CheckWriteable(configsFile()))\n\t\t\t// User is missing from the config: this is a broken config probably\n\t\t\t// because it was created with an old (broken) version of hub. Let's fix\n\t\t\t// it now. See issue #1007 for details.\n\t\t\tuser := c.PromptForUser(host)\n\t\t\tif user == \"\" {\n\t\t\t\tutils.Check(fmt.Errorf(\"missing user\"))\n\t\t\t}\n\t\t\th.User = user\n\t\t\terr := newConfigService().Save(configsFile(), c)\n\t\t\tutils.Check(err)\n\t\t}\n\t\tif tokenFromEnv {\n\t\t\th.AccessToken = token\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\th = &Host{\n\t\t\tHost:        host,\n\t\t\tAccessToken: token,\n\t\t\tProtocol:    \"https\",\n\t\t}\n\t\tc.Hosts = append(c.Hosts, h)\n\t}\n\n\tclient := NewClientWithHost(h)\n\n\tif !tokenFromEnv {\n\t\tutils.Check(CheckWriteable(configsFile()))\n\t\terr = c.authorizeClient(client, host)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tuserFromEnv := os.Getenv(\"GITHUB_USER\")\n\trepoFromEnv := os.Getenv(\"GITHUB_REPOSITORY\")\n\tif userFromEnv == \"\" && repoFromEnv != \"\" {\n\t\trepoParts := strings.SplitN(repoFromEnv, \"/\", 2)\n\t\tif len(repoParts) > 0 {\n\t\t\tuserFromEnv = repoParts[0]\n\t\t}\n\t}\n\tif tokenFromEnv && userFromEnv != \"\" {\n\t\th.User = userFromEnv\n\t} else {\n\t\tvar currentUser *User\n\t\tcurrentUser, err = client.CurrentUser()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\th.User = currentUser.Login\n\t}\n\n\tif !tokenFromEnv {\n\t\terr = newConfigService().Save(configsFile(), c)\n\t}\n\n\treturn\n}\n\nfunc (c *Config) authorizeClient(client *Client, host string) (err error) {\n\tuser := c.PromptForUser(host)\n\tpass := c.PromptForPassword(host, user)\n\n\tvar code, token string\n\tfor {\n\t\ttoken, err = client.FindOrCreateToken(user, pass, code)\n\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif ae, ok := err.(*errorInfo); ok && strings.HasPrefix(ae.Response.Header.Get(\"X-GitHub-OTP\"), \"required;\") {\n\t\t\tif code != \"\" {\n\t\t\t\tui.Errorln(\"warning: invalid two-factor code\")\n\t\t\t}\n\t\t\tcode = c.PromptForOTP()\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif err == nil {\n\t\tclient.Host.AccessToken = token\n\t}\n\n\treturn\n}\n\nfunc (c *Config) DetectToken() string {\n\treturn os.Getenv(\"GITHUB_TOKEN\")\n}\n\nfunc (c *Config) PromptForUser(host string) (user string) {\n\tuser = os.Getenv(\"GITHUB_USER\")\n\tif user != \"\" {\n\t\treturn\n\t}\n\n\tui.Printf(\"%s username: \", host)\n\tuser = c.scanLine()\n\n\treturn\n}\n\nfunc (c *Config) PromptForPassword(host, user string) (pass string) {\n\tpass = os.Getenv(\"GITHUB_PASSWORD\")\n\tif pass != \"\" {\n\t\treturn\n\t}\n\n\tui.Printf(\"%s password for %s (never stored): \", host, user)\n\tif ui.IsTerminal(os.Stdin) {\n\t\tif password, err := getPassword(); err == nil {\n\t\t\tpass = password\n\t\t}\n\t} else {\n\t\tpass = c.scanLine()\n\t}\n\n\treturn\n}\n\nfunc (c *Config) PromptForOTP() string {\n\tfmt.Print(\"two-factor authentication code: \")\n\treturn c.scanLine()\n}\n\nfunc (c *Config) scanLine() string {\n\tif c.stdinScanner == nil {\n\t\tc.stdinScanner = bufio.NewScanner(os.Stdin)\n\t}\n\tvar line string\n\tscanner := c.stdinScanner\n\tif scanner.Scan() {\n\t\tline = scanner.Text()\n\t}\n\tutils.Check(scanner.Err())\n\n\treturn line\n}\n\nfunc getPassword() (string, error) {\n\tstdin := int(syscall.Stdin)\n\tinitialTermState, err := terminal.GetState(stdin)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, syscall.SIGTERM)\n\tgo func() {\n\t\ts := <-c\n\t\tterminal.Restore(stdin, initialTermState)\n\t\tswitch sig := s.(type) {\n\t\tcase syscall.Signal:\n\t\t\tif int(sig) == 2 {\n\t\t\t\tfmt.Println(\"^C\")\n\t\t\t}\n\t\t}\n\t\tos.Exit(1)\n\t}()\n\n\tpassBytes, err := terminal.ReadPassword(stdin)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tsignal.Stop(c)\n\tfmt.Print(\"\\n\")\n\treturn string(passBytes), nil\n}\n\nfunc (c *Config) Find(host string) *Host {\n\tfor _, h := range c.Hosts {\n\t\tif h.Host == host {\n\t\t\treturn h\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) selectHost() *Host {\n\toptions := len(c.Hosts)\n\n\tif options == 1 {\n\t\treturn c.Hosts[0]\n\t}\n\n\tprompt := \"Select host:\\n\"\n\tfor idx, host := range c.Hosts {\n\t\tprompt += fmt.Sprintf(\" %d. %s\\n\", idx+1, host.Host)\n\t}\n\tprompt += fmt.Sprint(\"> \")\n\n\tui.Printf(prompt)\n\tindex := c.scanLine()\n\ti, err := strconv.Atoi(index)\n\tif err != nil || i < 1 || i > options {\n\t\tutils.Check(fmt.Errorf(\"Error: must enter a number [1-%d]\", options))\n\t}\n\n\treturn c.Hosts[i-1]\n}\n\nvar defaultConfigsFile string\n\nfunc configsFile() string {\n\tif configFromEnv := os.Getenv(\"HUB_CONFIG\"); configFromEnv != \"\" {\n\t\treturn configFromEnv\n\t}\n\tif defaultConfigsFile == \"\" {\n\t\tvar err error\n\t\tdefaultConfigsFile, err = determineConfigLocation()\n\t\tutils.Check(err)\n\t}\n\treturn defaultConfigsFile\n}\n\nfunc homeConfig() (string, error) {\n\thome, err := homedir.Dir()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn filepath.Join(home, \".config\"), nil\n}\n\nfunc determineConfigLocation() (string, error) {\n\tvar err error\n\n\txdgHome := os.Getenv(\"XDG_CONFIG_HOME\")\n\tconfigDir := xdgHome\n\tif configDir == \"\" {\n\t\tif configDir, err = homeConfig(); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\n\txdgDirs := os.Getenv(\"XDG_CONFIG_DIRS\")\n\tif xdgDirs == \"\" {\n\t\txdgDirs = \"/etc/xdg\"\n\t}\n\tsearchDirs := append([]string{configDir}, strings.Split(xdgDirs, \":\")...)\n\n\tfor _, dir := range searchDirs {\n\t\tfilename := filepath.Join(dir, \"hub\")\n\t\tif _, err := os.Stat(filename); err == nil {\n\t\t\treturn filename, nil\n\t\t}\n\t}\n\n\tconfigFile := filepath.Join(configDir, \"hub\")\n\n\tif configDir == xdgHome {\n\t\tif homeDir, _ := homeConfig(); homeDir != \"\" {\n\t\t\tlegacyConfig := filepath.Join(homeDir, \"hub\")\n\t\t\tif _, err = os.Stat(legacyConfig); err == nil {\n\t\t\t\tui.Errorf(\"Notice: config file found but not respected at: %s\\n\", legacyConfig)\n\t\t\t\tui.Errorf(\"You might want to move it to `%s' to avoid re-authenticating.\\n\", configFile)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn configFile, nil\n}\n\nvar currentConfig *Config\nvar configLoadedFrom = \"\"\n\nfunc CurrentConfig() *Config {\n\tfilename := configsFile()\n\tif configLoadedFrom != filename {\n\t\tcurrentConfig = &Config{}\n\t\tnewConfigService().Load(filename, currentConfig)\n\t\tconfigLoadedFrom = filename\n\t}\n\n\treturn currentConfig\n}\n\nfunc (c *Config) DefaultHost() (host *Host, err error) {\n\tif GitHubHostEnv != \"\" {\n\t\thost, err = c.PromptForHost(GitHubHostEnv)\n\t} else if len(c.Hosts) > 0 {\n\t\thost = c.selectHost()\n\t\t// HACK: forces host to inherit GITHUB_TOKEN if applicable\n\t\thost, err = c.PromptForHost(host.Host)\n\t} else {\n\t\thost, err = c.PromptForHost(DefaultGitHubHost())\n\t}\n\n\treturn\n}\n\nfunc (c *Config) DefaultHostNoPrompt() (*Host, error) {\n\tif GitHubHostEnv != \"\" {\n\t\treturn c.PromptForHost(GitHubHostEnv)\n\t} else if len(c.Hosts) > 0 {\n\t\thost := c.Hosts[0]\n\t\t// HACK: forces host to inherit GITHUB_TOKEN if applicable\n\t\treturn c.PromptForHost(host.Host)\n\t} else {\n\t\treturn c.PromptForHost(GitHubHost)\n\t}\n}\n\n// CheckWriteable checks if config file is writeable. This should\n// be called before asking for credentials and only if current\n// operation needs to update the file. See issue #1314 for details.\nfunc CheckWriteable(filename string) error {\n\t// Check if file exists already. if it doesn't, we will delete it after\n\t// checking for writeabilty\n\tfileExistsAlready := false\n\n\tif _, err := os.Stat(filename); err == nil {\n\t\tfileExistsAlready = true\n\t}\n\n\terr := os.MkdirAll(filepath.Dir(filename), 0771)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tw, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)\n\tif err != nil {\n\t\treturn err\n\t}\n\tw.Close()\n\n\tif !fileExistsAlready {\n\t\terr := os.Remove(filename)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// CreateTestConfigs is public for testing purposes\nfunc CreateTestConfigs(user, token string) *Config {\n\tf, _ := ioutil.TempFile(\"\", \"test-config\")\n\tos.Setenv(\"HUB_CONFIG\", f.Name())\n\n\thost := &Host{\n\t\tUser:        \"jingweno\",\n\t\tAccessToken: \"123\",\n\t\tHost:        GitHubHost,\n\t}\n\n\tc := &Config{Hosts: []*Host{host}}\n\terr := newConfigService().Save(f.Name(), c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn c\n}\n"
  },
  {
    "path": "github/config_decoder.go",
    "content": "package github\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\n\t\"github.com/BurntSushi/toml\"\n\t\"gopkg.in/yaml.v2\"\n)\n\ntype configDecoder interface {\n\tDecode(r io.Reader, c *Config) error\n}\n\ntype tomlConfigDecoder struct {\n}\n\nfunc (t *tomlConfigDecoder) Decode(r io.Reader, c *Config) error {\n\t_, err := toml.DecodeReader(r, c)\n\treturn err\n}\n\ntype yamlConfigDecoder struct {\n}\n\nfunc (y *yamlConfigDecoder) Decode(r io.Reader, c *Config) error {\n\td, err := ioutil.ReadAll(r)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tyc := yaml.MapSlice{}\n\terr = yaml.Unmarshal(d, &yc)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, hostEntry := range yc {\n\t\tv, ok := hostEntry.Value.([]interface{})\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"value of host entry is must be array but got %#v\", hostEntry.Value)\n\t\t}\n\t\tif len(v) < 1 {\n\t\t\tcontinue\n\t\t}\n\t\thostName, ok := hostEntry.Key.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"host name is must be string but got %#v\", hostEntry.Key)\n\t\t}\n\t\thost := &Host{Host: hostName}\n\t\tfor _, prop := range v[0].(yaml.MapSlice) {\n\t\t\tpropName, ok := prop.Key.(string)\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"property name is must be string but got %#v\", prop.Key)\n\t\t\t}\n\t\t\tswitch propName {\n\t\t\tcase \"user\":\n\t\t\t\thost.User, ok = prop.Value.(string)\n\t\t\tcase \"oauth_token\":\n\t\t\t\thost.AccessToken, ok = prop.Value.(string)\n\t\t\tcase \"protocol\":\n\t\t\t\thost.Protocol, ok = prop.Value.(string)\n\t\t\tcase \"unix_socket\":\n\t\t\t\thost.UnixSocket, ok = prop.Value.(string)\n\t\t\t}\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"%s is must be string but got %#v\", propName, prop.Value)\n\t\t\t}\n\t\t}\n\t\tc.Hosts = append(c.Hosts, host)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "github/config_encoder.go",
    "content": "package github\n\nimport (\n\t\"io\"\n\n\t\"github.com/BurntSushi/toml\"\n\t\"gopkg.in/yaml.v2\"\n)\n\ntype configEncoder interface {\n\tEncode(w io.Writer, c *Config) error\n}\n\ntype tomlConfigEncoder struct {\n}\n\nfunc (t *tomlConfigEncoder) Encode(w io.Writer, c *Config) error {\n\tenc := toml.NewEncoder(w)\n\treturn enc.Encode(c)\n}\n\ntype yamlConfigEncoder struct {\n}\n\nfunc (y *yamlConfigEncoder) Encode(w io.Writer, c *Config) error {\n\tyc := yaml.MapSlice{}\n\tfor _, h := range c.Hosts {\n\t\tyc = append(yc, yaml.MapItem{\n\t\t\tKey: h.Host,\n\t\t\tValue: []yamlHost{\n\t\t\t\t{\n\t\t\t\t\tUser:       h.User,\n\t\t\t\t\tOAuthToken: h.AccessToken,\n\t\t\t\t\tProtocol:   h.Protocol,\n\t\t\t\t\tUnixSocket: h.UnixSocket,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t}\n\n\td, err := yaml.Marshal(yc)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tn, err := w.Write(d)\n\tif err == nil && n < len(d) {\n\t\terr = io.ErrShortWrite\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "github/config_service.go",
    "content": "package github\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n)\n\nfunc newConfigService() *configService {\n\treturn &configService{\n\t\tEncoder: &yamlConfigEncoder{},\n\t\tDecoder: &yamlConfigDecoder{},\n\t}\n}\n\ntype configService struct {\n\tEncoder configEncoder\n\tDecoder configDecoder\n}\n\nfunc (s *configService) Save(filename string, c *Config) error {\n\terr := os.MkdirAll(filepath.Dir(filename), 0771)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tw, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer w.Close()\n\n\treturn s.Encoder.Encode(w, c)\n}\n\nfunc (s *configService) Load(filename string, c *Config) error {\n\tr, err := os.Open(filename)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer r.Close()\n\n\treturn s.Decoder.Decode(r, c)\n}\n"
  },
  {
    "path": "github/config_service_test.go",
    "content": "package github\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/fixtures\"\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestConfigService_TomlLoad(t *testing.T) {\n\ttestConfig := fixtures.SetupTomlTestConfig()\n\tdefer testConfig.TearDown()\n\n\tcc := &Config{}\n\tcs := &configService{\n\t\tEncoder: &tomlConfigEncoder{},\n\t\tDecoder: &tomlConfigDecoder{},\n\t}\n\terr := cs.Load(testConfig.Path, cc)\n\tassert.Equal(t, nil, err)\n\n\tassert.Equal(t, 1, len(cc.Hosts))\n\thost := cc.Hosts[0]\n\tassert.Equal(t, \"github.com\", host.Host)\n\tassert.Equal(t, \"jingweno\", host.User)\n\tassert.Equal(t, \"123\", host.AccessToken)\n\tassert.Equal(t, \"http\", host.Protocol)\n}\n\nfunc TestConfigService_TomlLoad_UnixSocket(t *testing.T) {\n\ttestConfigUnixSocket := fixtures.SetupTomlTestConfigWithUnixSocket()\n\tdefer testConfigUnixSocket.TearDown()\n\n\tcc := &Config{}\n\tcs := &configService{\n\t\tEncoder: &tomlConfigEncoder{},\n\t\tDecoder: &tomlConfigDecoder{},\n\t}\n\n\terr := cs.Load(testConfigUnixSocket.Path, cc)\n\tassert.Equal(t, nil, err)\n\n\tassert.Equal(t, 1, len(cc.Hosts))\n\thost := cc.Hosts[0]\n\tassert.Equal(t, \"github.com\", host.Host)\n\tassert.Equal(t, \"jingweno\", host.User)\n\tassert.Equal(t, \"123\", host.AccessToken)\n\tassert.Equal(t, \"http\", host.Protocol)\n\tassert.Equal(t, \"/tmp/go.sock\", host.UnixSocket)\n}\n\nfunc TestConfigService_YamlLoad(t *testing.T) {\n\ttestConfig := fixtures.SetupTestConfigs()\n\tdefer testConfig.TearDown()\n\n\tcc := &Config{}\n\tcs := &configService{\n\t\tEncoder: &yamlConfigEncoder{},\n\t\tDecoder: &yamlConfigDecoder{},\n\t}\n\terr := cs.Load(testConfig.Path, cc)\n\tassert.Equal(t, nil, err)\n\n\tassert.Equal(t, 1, len(cc.Hosts))\n\thost := cc.Hosts[0]\n\tassert.Equal(t, \"github.com\", host.Host)\n\tassert.Equal(t, \"jingweno\", host.User)\n\tassert.Equal(t, \"123\", host.AccessToken)\n\tassert.Equal(t, \"http\", host.Protocol)\n}\n\nfunc TestConfigService_YamlLoad_Unix_Socket(t *testing.T) {\n\ttestConfigUnixSocket := fixtures.SetupTestConfigsWithUnixSocket()\n\tdefer testConfigUnixSocket.TearDown()\n\n\tcc := &Config{}\n\tcs := &configService{\n\t\tEncoder: &yamlConfigEncoder{},\n\t\tDecoder: &yamlConfigDecoder{},\n\t}\n\n\terr := cs.Load(testConfigUnixSocket.Path, cc)\n\tassert.Equal(t, nil, err)\n\n\tassert.Equal(t, 1, len(cc.Hosts))\n\thost := cc.Hosts[0]\n\tassert.Equal(t, \"github.com\", host.Host)\n\tassert.Equal(t, \"jingweno\", host.User)\n\tassert.Equal(t, \"123\", host.AccessToken)\n\tassert.Equal(t, \"http\", host.Protocol)\n\tassert.Equal(t, \"/tmp/go.sock\", host.UnixSocket)\n}\n\nfunc TestConfigService_YamlLoad_Invalid_HostName(t *testing.T) {\n\ttestConfigInvalidHostName := fixtures.SetupTestConfigsInvalidHostName()\n\tdefer testConfigInvalidHostName.TearDown()\n\n\tcc := &Config{}\n\tcs := &configService{\n\t\tEncoder: &yamlConfigEncoder{},\n\t\tDecoder: &yamlConfigDecoder{},\n\t}\n\n\terr := cs.Load(testConfigInvalidHostName.Path, cc)\n\tassert.NotEqual(t, nil, err)\n\tassert.Equal(t, \"host name is must be string but got 123\", err.Error())\n}\n\nfunc TestConfigService_YamlLoad_Invalid_HostEntry(t *testing.T) {\n\ttestConfigInvalidHostEntry := fixtures.SetupTestConfigsInvalidHostEntry()\n\tdefer testConfigInvalidHostEntry.TearDown()\n\n\tcc := &Config{}\n\tcs := &configService{\n\t\tEncoder: &yamlConfigEncoder{},\n\t\tDecoder: &yamlConfigDecoder{},\n\t}\n\n\terr := cs.Load(testConfigInvalidHostEntry.Path, cc)\n\tassert.NotEqual(t, nil, err)\n\tassert.Equal(t, \"value of host entry is must be array but got \\\"hello\\\"\", err.Error())\n}\n\nfunc TestConfigService_YamlLoad_Invalid_PropertyValue(t *testing.T) {\n\ttestConfigInvalidPropertyValue := fixtures.SetupTestConfigsInvalidPropertyValue()\n\tdefer testConfigInvalidPropertyValue.TearDown()\n\n\tcc := &Config{}\n\tcs := &configService{\n\t\tEncoder: &yamlConfigEncoder{},\n\t\tDecoder: &yamlConfigDecoder{},\n\t}\n\n\terr := cs.Load(testConfigInvalidPropertyValue.Path, cc)\n\tassert.NotEqual(t, nil, err)\n\tassert.Equal(t, \"user is must be string but got <nil>\", err.Error())\n}\n\nfunc TestConfigService_TomlSave(t *testing.T) {\n\tfile, _ := ioutil.TempFile(\"\", \"test-gh-config-\")\n\tdefer os.RemoveAll(file.Name())\n\n\thost := &Host{\n\t\tHost:        \"github.com\",\n\t\tUser:        \"jingweno\",\n\t\tAccessToken: \"123\",\n\t\tProtocol:    \"https\",\n\t}\n\tc := &Config{Hosts: []*Host{host}}\n\n\tcs := &configService{\n\t\tEncoder: &tomlConfigEncoder{},\n\t\tDecoder: &tomlConfigDecoder{},\n\t}\n\terr := cs.Save(file.Name(), c)\n\tassert.Equal(t, nil, err)\n\n\tb, _ := ioutil.ReadFile(file.Name())\n\tcontent := `[[hosts]]\n  host = \"github.com\"\n  user = \"jingweno\"\n  access_token = \"123\"\n  protocol = \"https\"`\n\tassert.Equal(t, content, strings.TrimSpace(string(b)))\n}\n\nfunc TestConfigService_TomlSave_UnixSocket(t *testing.T) {\n\tfile, _ := ioutil.TempFile(\"\", \"test-gh-config-\")\n\tdefer os.RemoveAll(file.Name())\n\n\thost := &Host{\n\t\tHost:        \"github.com\",\n\t\tUser:        \"jingweno\",\n\t\tAccessToken: \"123\",\n\t\tProtocol:    \"https\",\n\t\tUnixSocket:  \"/tmp/go.sock\",\n\t}\n\tc := &Config{Hosts: []*Host{host}}\n\n\tcs := &configService{\n\t\tEncoder: &tomlConfigEncoder{},\n\t\tDecoder: &tomlConfigDecoder{},\n\t}\n\terr := cs.Save(file.Name(), c)\n\tassert.Equal(t, nil, err)\n\n\tb, _ := ioutil.ReadFile(file.Name())\n\tcontent := `[[hosts]]\n  host = \"github.com\"\n  user = \"jingweno\"\n  access_token = \"123\"\n  protocol = \"https\"\n  unix_socket = \"/tmp/go.sock\"`\n\tassert.Equal(t, content, strings.TrimSpace(string(b)))\n}\n\nfunc TestConfigService_YamlSave(t *testing.T) {\n\tfile, _ := ioutil.TempFile(\"\", \"test-gh-config-\")\n\tdefer os.RemoveAll(file.Name())\n\n\thost := &Host{\n\t\tHost:        \"github.com\",\n\t\tUser:        \"jingweno\",\n\t\tAccessToken: \"123\",\n\t\tProtocol:    \"https\",\n\t}\n\tc := &Config{Hosts: []*Host{host}}\n\n\tcs := &configService{\n\t\tEncoder: &yamlConfigEncoder{},\n\t\tDecoder: &yamlConfigDecoder{},\n\t}\n\terr := cs.Save(file.Name(), c)\n\tassert.Equal(t, nil, err)\n\n\tb, _ := ioutil.ReadFile(file.Name())\n\tcontent := `github.com:\n- user: jingweno\n  oauth_token: \"123\"\n  protocol: https`\n\tassert.Equal(t, content, strings.TrimSpace(string(b)))\n}\n\nfunc TestConfigService_YamlSave_UnixSocket(t *testing.T) {\n\tfile, _ := ioutil.TempFile(\"\", \"test-gh-config-\")\n\tdefer os.RemoveAll(file.Name())\n\n\thost := &Host{\n\t\tHost:        \"github.com\",\n\t\tUser:        \"jingweno\",\n\t\tAccessToken: \"123\",\n\t\tProtocol:    \"https\",\n\t\tUnixSocket:  \"/tmp/go.sock\",\n\t}\n\tc := &Config{Hosts: []*Host{host}}\n\n\tcs := &configService{\n\t\tEncoder: &yamlConfigEncoder{},\n\t\tDecoder: &yamlConfigDecoder{},\n\t}\n\terr := cs.Save(file.Name(), c)\n\tassert.Equal(t, nil, err)\n\n\tb, _ := ioutil.ReadFile(file.Name())\n\tcontent := `github.com:\n- user: jingweno\n  oauth_token: \"123\"\n  protocol: https\n  unix_socket: /tmp/go.sock`\n\tassert.Equal(t, content, strings.TrimSpace(string(b)))\n}\n"
  },
  {
    "path": "github/crash_report.go",
    "content": "package github\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/git\"\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n\t\"github.com/github/hub/v2/version\"\n)\n\nconst (\n\thubReportCrashConfig = \"hub.reportCrash\"\n\thubProjectOwner      = \"github\"\n\thubProjectName       = \"hub\"\n)\n\nfunc CaptureCrash() {\n\tif rec := recover(); rec != nil {\n\t\tswitch err := rec.(type) {\n\t\tcase error:\n\t\t\treportCrash(err)\n\t\tcase string:\n\t\t\treportCrash(errors.New(err))\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t\tos.Exit(1)\n\t}\n}\n\nfunc reportCrash(err error) {\n\tbuf := make([]byte, 10000)\n\truntime.Stack(buf, false)\n\tstack := formatStack(buf)\n\n\tui.Errorf(\"%v\\n\\n\", err)\n\tui.Errorln(stack)\n\n\tisTerm := ui.IsTerminal(os.Stdin) && ui.IsTerminal(os.Stdout)\n\tif !isTerm || reportCrashConfig() == \"never\" {\n\t\treturn\n\t}\n\n\tui.Print(\"Would you like to open an issue? ([y]es / [N]o / n[e]ver): \")\n\tvar confirm string\n\tprompt := bufio.NewScanner(os.Stdin)\n\tif prompt.Scan() {\n\t\tconfirm = prompt.Text()\n\t}\n\tif prompt.Err() != nil {\n\t\treturn\n\t}\n\n\tif isOption(confirm, \"y\", \"yes\") {\n\t\treport(err, stack)\n\t} else if isOption(confirm, \"e\", \"never\") {\n\t\tgit.SetGlobalConfig(hubReportCrashConfig, \"never\")\n\t}\n}\n\nfunc isOption(confirm, short, long string) bool {\n\treturn strings.EqualFold(confirm, short) || strings.EqualFold(confirm, long)\n}\n\nfunc report(reportedError error, stack string) {\n\ttitle, body, err := reportTitleAndBody(reportedError, stack)\n\tutils.Check(err)\n\n\tproject := NewProject(hubProjectOwner, hubProjectName, GitHubHost)\n\n\tgh := NewClient(project.Host)\n\n\tparams := map[string]interface{}{\n\t\t\"title\":  title,\n\t\t\"body\":   body,\n\t\t\"labels\": []string{\"Crash Report\"},\n\t}\n\n\tissue, err := gh.CreateIssue(project, params)\n\tutils.Check(err)\n\n\tui.Println(issue.HTMLURL)\n}\n\nconst crashReportTmpl = \"Crash report - %v\\n\\n\" +\n\t\"Error (%s): `%v`\\n\\n\" +\n\t\"Stack:\\n\\n```\\n%s\\n```\\n\\n\" +\n\t\"Runtime:\\n\\n```\\n%s\\n```\\n\\n\" +\n\t\"Version:\\n\\n```\\n%s\\nhub version %s\\n```\\n\"\n\nfunc reportTitleAndBody(reportedError error, stack string) (title, body string, err error) {\n\terrType := reflect.TypeOf(reportedError).String()\n\tgitVersion, gitErr := git.Version()\n\tif gitErr != nil {\n\t\tgitVersion = \"git unavailable!\"\n\t}\n\tmessage := fmt.Sprintf(\n\t\tcrashReportTmpl,\n\t\treportedError,\n\t\terrType,\n\t\treportedError,\n\t\tstack,\n\t\truntimeInfo(),\n\t\tgitVersion,\n\t\tversion.Version,\n\t)\n\n\tmessageBuilder := &MessageBuilder{\n\t\tFilename: \"CRASH_REPORT\",\n\t\tTitle:    \"crash report\",\n\t\tMessage:  message,\n\t\tEdit:     true,\n\t}\n\tmessageBuilder.AddCommentedSection(`Creating crash report:\n\nThis information will be posted as a new issue under github/hub.\nWe're NOT including any information about the command that you were executing,\nbut knowing a little bit more about it would really help us to solve this problem.\nFeel free to modify the title and the description for this issue.`)\n\n\ttitle, body, err = messageBuilder.Extract()\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer messageBuilder.Cleanup()\n\n\treturn\n}\n\nfunc runtimeInfo() string {\n\treturn fmt.Sprintf(\"GOOS: %s\\nGOARCH: %s\", runtime.GOOS, runtime.GOARCH)\n}\n\nfunc formatStack(buf []byte) string {\n\tbuf = bytes.Trim(buf, \"\\x00\")\n\n\tstack := strings.Split(string(buf), \"\\n\")\n\tstack = append(stack[0:1], stack[5:]...)\n\n\treturn strings.Join(stack, \"\\n\")\n}\n\nfunc reportCrashConfig() (opt string) {\n\topt = os.Getenv(\"HUB_REPORT_CRASH\")\n\tif opt == \"\" {\n\t\topt, _ = git.GlobalConfig(hubReportCrashConfig)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "github/crash_report_test.go",
    "content": "package github\n\nimport (\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestStackRemoveSelfAndPanic(t *testing.T) {\n\tactual := `goroutine 1 [running]:\nruntime.panic(0x2bca00, 0x665b8a)\n\t/usr/local/go/src/pkg/runtime/panic.c:266 +0xb6\ngithub.com/jingweno/gh/github.ReportCrash(0xc2000b5000, 0xc2000b49c0)\n\t/Users/calavera/github/go/src/github.com/jingweno/gh/github/crash_report.go:16 +0x97\ngithub.com/jingweno/gh/commands.create(0x47f8a0, 0xc2000cf770)\n\t/Users/calavera/github/go/src/github.com/jingweno/gh/commands/create.go:54 +0x63\ngithub.com/jingweno/gh/commands.(*Runner).Execute(0xc200094640, 0xc200094640, 0x21, 0xc2000b0a40)\n\t/Users/calavera/github/go/src/github.com/jingweno/gh/commands/runner.go:72 +0x3b7\nmain.main()\n\t/Users/calavera/github/go/src/github.com/jingweno/gh/main.go:10 +0xad`\n\n\texpected := `goroutine 1 [running]:\ngithub.com/jingweno/gh/commands.create(0x47f8a0, 0xc2000cf770)\n\t/Users/calavera/github/go/src/github.com/jingweno/gh/commands/create.go:54 +0x63\ngithub.com/jingweno/gh/commands.(*Runner).Execute(0xc200094640, 0xc200094640, 0x21, 0xc2000b0a40)\n\t/Users/calavera/github/go/src/github.com/jingweno/gh/commands/runner.go:72 +0x3b7\nmain.main()\n\t/Users/calavera/github/go/src/github.com/jingweno/gh/main.go:10 +0xad`\n\n\ts := formatStack([]byte(actual))\n\tassert.Equal(t, expected, s)\n}\n"
  },
  {
    "path": "github/editor.go",
    "content": "package github\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/cmd\"\n\t\"github.com/github/hub/v2/git\"\n\t\"github.com/kballard/go-shellquote\"\n)\n\nconst Scissors = \"------------------------ >8 ------------------------\"\n\nfunc NewEditor(filename, topic, message string) (editor *Editor, err error) {\n\tgitDir, err := git.Dir()\n\tif err != nil {\n\t\treturn\n\t}\n\tmessageFile := filepath.Join(gitDir, filename)\n\n\tprogram, err := git.Editor()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcs, err := git.CommentChar(message)\n\tif err != nil {\n\t\treturn\n\t}\n\n\teditor = &Editor{\n\t\tProgram:    program,\n\t\tTopic:      topic,\n\t\tFile:       messageFile,\n\t\tMessage:    message,\n\t\tCS:         cs,\n\t\topenEditor: openTextEditor,\n\t}\n\n\treturn\n}\n\ntype Editor struct {\n\tProgram           string\n\tTopic             string\n\tFile              string\n\tMessage           string\n\tCS                string\n\taddedFirstComment bool\n\topenEditor        func(program, file string) error\n}\n\nfunc (e *Editor) AddCommentedSection(text string) {\n\tif !e.addedFirstComment {\n\t\tscissors := e.CS + \" \" + Scissors + \"\\n\"\n\t\tscissors += e.CS + \" Do not modify or remove the line above.\\n\"\n\t\tscissors += e.CS + \" Everything below it will be ignored.\\n\"\n\t\te.Message = e.Message + \"\\n\" + scissors\n\t\te.addedFirstComment = true\n\t}\n\n\te.Message = e.Message + \"\\n\" + text\n}\n\nfunc (e *Editor) DeleteFile() error {\n\treturn os.Remove(e.File)\n}\n\nfunc (e *Editor) EditContent() (content string, err error) {\n\tb, err := e.openAndEdit()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tb = bytes.TrimSpace(b)\n\treader := bytes.NewReader(b)\n\tscanner := bufio.NewScanner(reader)\n\tunquotedLines := []string{}\n\n\tscissorsLine := e.CS + \" \" + Scissors\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tif line == scissorsLine {\n\t\t\tbreak\n\t\t}\n\t\tunquotedLines = append(unquotedLines, line)\n\t}\n\tif err = scanner.Err(); err != nil {\n\t\treturn\n\t}\n\n\tcontent = strings.Join(unquotedLines, \"\\n\")\n\treturn\n}\n\nfunc (e *Editor) openAndEdit() (content []byte, err error) {\n\terr = e.writeContent()\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = e.openEditor(e.Program, e.File)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"error using text editor for %s message\", e.Topic)\n\t\tdefer e.DeleteFile()\n\t\treturn\n\t}\n\n\tcontent, err = e.readContent()\n\n\treturn\n}\n\nfunc (e *Editor) writeContent() (err error) {\n\tif !e.isFileExist() {\n\t\terr = ioutil.WriteFile(e.File, []byte(e.Message), 0644)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (e *Editor) isFileExist() bool {\n\t_, err := os.Stat(e.File)\n\treturn err == nil || !os.IsNotExist(err)\n}\n\nfunc (e *Editor) readContent() (content []byte, err error) {\n\treturn ioutil.ReadFile(e.File)\n}\n\nfunc openTextEditor(program, file string) error {\n\tprogramArgs, err := shellquote.Split(program)\n\tif err != nil {\n\t\treturn err\n\t}\n\teditCmd := cmd.NewWithArray(programArgs)\n\tr := regexp.MustCompile(`\\b(?:[gm]?vim)(?:\\.exe)?$`)\n\tif r.MatchString(editCmd.Name) {\n\t\teditCmd.WithArg(\"--cmd\")\n\t\teditCmd.WithArg(\"set ft=gitcommit tw=0 wrap lbr\")\n\t}\n\teditCmd.WithArg(file)\n\t// Reattach stdin to the console before opening the editor\n\tsetConsole(editCmd)\n\n\treturn editCmd.Spawn()\n}\n"
  },
  {
    "path": "github/editor_test.go",
    "content": "package github\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestEditor_openAndEdit_deleteFileWhenOpeningEditorFails(t *testing.T) {\n\ttempFile, _ := ioutil.TempFile(\"\", \"editor-test\")\n\ttempFile.Close()\n\n\tioutil.WriteFile(tempFile.Name(), []byte(\"hello\"), 0644)\n\teditor := Editor{\n\t\tProgram: \"memory\",\n\t\tFile:    tempFile.Name(),\n\t\tTopic:   \"test\",\n\t\topenEditor: func(program string, file string) error {\n\t\t\tassert.Equal(t, \"memory\", program)\n\t\t\tassert.Equal(t, tempFile.Name(), file)\n\t\t\treturn fmt.Errorf(\"error\")\n\t\t},\n\t}\n\n\t_, err := os.Stat(tempFile.Name())\n\tassert.Equal(t, nil, err)\n\n\t_, err = editor.openAndEdit()\n\tassert.Equal(t, \"error using text editor for test message\", fmt.Sprintf(\"%s\", err))\n\n\t// file is removed if there's error\n\t_, err = os.Stat(tempFile.Name())\n\tassert.T(t, os.IsNotExist(err))\n}\n\nfunc TestEditor_openAndEdit_readFileIfExist(t *testing.T) {\n\ttempFile, _ := ioutil.TempFile(\"\", \"editor-test\")\n\ttempFile.Close()\n\n\tioutil.WriteFile(tempFile.Name(), []byte(\"hello\"), 0644)\n\teditor := Editor{\n\t\tProgram: \"memory\",\n\t\tFile:    tempFile.Name(),\n\t\topenEditor: func(program string, file string) error {\n\t\t\tassert.Equal(t, \"memory\", program)\n\t\t\tassert.Equal(t, tempFile.Name(), file)\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcontent, err := editor.openAndEdit()\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"hello\", string(content))\n}\n\nfunc TestEditor_openAndEdit_writeFileIfNotExist(t *testing.T) {\n\ttempFile, _ := ioutil.TempFile(\"\", \"PULLREQ\")\n\ttempFile.Close()\n\n\teditor := Editor{\n\t\tProgram: \"memory\",\n\t\tFile:    tempFile.Name(),\n\t\topenEditor: func(program string, file string) error {\n\t\t\tassert.Equal(t, \"memory\", program)\n\t\t\tassert.Equal(t, tempFile.Name(), file)\n\n\t\t\treturn ioutil.WriteFile(file, []byte(\"hello\"), 0644)\n\t\t},\n\t}\n\n\tcontent, err := editor.openAndEdit()\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"hello\", string(content))\n}\n"
  },
  {
    "path": "github/hosts.go",
    "content": "package github\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/git\"\n)\n\nvar (\n\tGitHubHostEnv = os.Getenv(\"GITHUB_HOST\")\n\tcachedHosts   []string\n)\n\ntype HostError struct {\n\turl *url.URL\n}\n\nfunc (e *HostError) Error() string {\n\treturn fmt.Sprintf(\"Invalid GitHub URL: %s\", e.url)\n}\n\nfunc knownGitHubHostsInclude(host string) bool {\n\tfor _, hh := range knownGitHubHosts() {\n\t\tif hh == host {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc knownGitHubHosts() []string {\n\tif cachedHosts != nil {\n\t\treturn cachedHosts\n\t}\n\n\thosts := []string{}\n\tdefaultHost := DefaultGitHubHost()\n\thosts = append(hosts, defaultHost)\n\thosts = append(hosts, \"ssh.github.com\")\n\n\tghHosts, _ := git.ConfigAll(\"hub.host\")\n\tfor _, ghHost := range ghHosts {\n\t\tghHost = strings.TrimSpace(ghHost)\n\t\tif ghHost != \"\" {\n\t\t\thosts = append(hosts, ghHost)\n\t\t}\n\t}\n\n\tcachedHosts = hosts\n\treturn hosts\n}\n\nfunc DefaultGitHubHost() string {\n\tdefaultHost := GitHubHostEnv\n\tif defaultHost == \"\" {\n\t\tdefaultHost = GitHubHost\n\t}\n\n\treturn defaultHost\n}\n"
  },
  {
    "path": "github/http.go",
    "content": "package github\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/md5\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/github/hub/v2/utils\"\n\t\"golang.org/x/net/http/httpproxy\"\n)\n\nconst apiPayloadVersion = \"application/vnd.github.v3+json;charset=utf-8\"\nconst patchMediaType = \"application/vnd.github.v3.patch;charset=utf-8\"\nconst textMediaType = \"text/plain;charset=utf-8\"\nconst checksType = \"application/vnd.github.antiope-preview+json;charset=utf-8\"\nconst draftsType = \"application/vnd.github.shadow-cat-preview+json;charset=utf-8\"\nconst cacheVersion = 2\n\nconst (\n\trateLimitRemainingHeader = \"X-Ratelimit-Remaining\"\n\trateLimitResetHeader     = \"X-Ratelimit-Reset\"\n)\n\nvar inspectHeaders = []string{\n\t\"Authorization\",\n\t\"X-GitHub-OTP\",\n\t\"X-GitHub-SSO\",\n\t\"X-Oauth-Scopes\",\n\t\"X-Accepted-Oauth-Scopes\",\n\t\"X-Oauth-Client-Id\",\n\t\"X-GitHub-Enterprise-Version\",\n\t\"Location\",\n\t\"Link\",\n\t\"Accept\",\n}\n\ntype verboseTransport struct {\n\tTransport   *http.Transport\n\tVerbose     bool\n\tOverrideURL *url.URL\n\tOut         io.Writer\n\tColorized   bool\n}\n\nfunc (t *verboseTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {\n\tif t.Verbose {\n\t\tt.dumpRequest(req)\n\t}\n\n\tif t.OverrideURL != nil {\n\t\tport := \"80\"\n\t\tif s := strings.Split(req.URL.Host, \":\"); len(s) > 1 {\n\t\t\tport = s[1]\n\t\t}\n\n\t\treq = cloneRequest(req)\n\t\treq.Header.Set(\"X-Original-Scheme\", req.URL.Scheme)\n\t\treq.Header.Set(\"X-Original-Port\", port)\n\t\treq.Host = req.URL.Host\n\t\treq.URL.Scheme = t.OverrideURL.Scheme\n\t\treq.URL.Host = t.OverrideURL.Host\n\t}\n\n\tresp, err = t.Transport.RoundTrip(req)\n\n\tif err == nil && t.Verbose {\n\t\tt.dumpResponse(resp)\n\t}\n\n\treturn\n}\n\nfunc (t *verboseTransport) dumpRequest(req *http.Request) {\n\tinfo := fmt.Sprintf(\"> %s %s://%s%s\", req.Method, req.URL.Scheme, req.URL.Host, req.URL.RequestURI())\n\tt.verbosePrintln(info)\n\tt.dumpHeaders(req.Header, \">\")\n\tif inspectableType(req.Header.Get(\"content-type\")) {\n\t\tbody := t.dumpBody(req.Body)\n\t\tif body != nil {\n\t\t\t// reset body since it's been read\n\t\t\treq.Body = body\n\t\t}\n\t}\n}\n\nfunc (t *verboseTransport) dumpResponse(resp *http.Response) {\n\tinfo := fmt.Sprintf(\"< HTTP %d\", resp.StatusCode)\n\tt.verbosePrintln(info)\n\tt.dumpHeaders(resp.Header, \"<\")\n\tif inspectableType(resp.Header.Get(\"content-type\")) {\n\t\tbody := t.dumpBody(resp.Body)\n\t\tif body != nil {\n\t\t\t// reset body since it's been read\n\t\t\tresp.Body = body\n\t\t}\n\t}\n}\n\nfunc (t *verboseTransport) dumpHeaders(header http.Header, indent string) {\n\tfor _, listed := range inspectHeaders {\n\t\tfor name, vv := range header {\n\t\t\tif !strings.EqualFold(name, listed) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor _, v := range vv {\n\t\t\t\tif v != \"\" {\n\t\t\t\t\tr := regexp.MustCompile(\"(?i)^(basic|token) (.+)\")\n\t\t\t\t\tif r.MatchString(v) {\n\t\t\t\t\t\tv = r.ReplaceAllString(v, \"$1 [REDACTED]\")\n\t\t\t\t\t}\n\n\t\t\t\t\tinfo := fmt.Sprintf(\"%s %s: %s\", indent, name, v)\n\t\t\t\t\tt.verbosePrintln(info)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (t *verboseTransport) dumpBody(body io.ReadCloser) io.ReadCloser {\n\tif body == nil {\n\t\treturn nil\n\t}\n\n\tdefer body.Close()\n\tbuf := new(bytes.Buffer)\n\t_, err := io.Copy(buf, body)\n\tutils.Check(err)\n\n\tif buf.Len() > 0 {\n\t\tt.verbosePrintln(buf.String())\n\t}\n\n\treturn ioutil.NopCloser(buf)\n}\n\nfunc (t *verboseTransport) verbosePrintln(msg string) {\n\tif t.Colorized {\n\t\tmsg = fmt.Sprintf(\"\\033[36m%s\\033[0m\", msg)\n\t}\n\n\tfmt.Fprintln(t.Out, msg)\n}\n\nvar jsonTypeRE = regexp.MustCompile(`[/+]json($|;)`)\n\nfunc inspectableType(ct string) bool {\n\treturn strings.HasPrefix(ct, \"text/\") || jsonTypeRE.MatchString(ct)\n}\n\nfunc newHTTPClient(testHost string, verbose bool, unixSocket string) *http.Client {\n\tvar testURL *url.URL\n\tif testHost != \"\" {\n\t\ttestURL, _ = url.Parse(testHost)\n\t}\n\tvar httpTransport *http.Transport\n\tif unixSocket != \"\" {\n\t\tdialFunc := func(network, addr string) (net.Conn, error) {\n\t\t\treturn net.Dial(\"unix\", unixSocket)\n\t\t}\n\t\tdialContext := func(_ context.Context, _, _ string) (net.Conn, error) {\n\t\t\treturn net.Dial(\"unix\", unixSocket)\n\t\t}\n\t\thttpTransport = &http.Transport{\n\t\t\tDialContext:           dialContext,\n\t\t\tDialTLS:               dialFunc,\n\t\t\tResponseHeaderTimeout: 30 * time.Second,\n\t\t\tExpectContinueTimeout: 10 * time.Second,\n\t\t\tTLSHandshakeTimeout:   10 * time.Second,\n\t\t}\n\t} else {\n\t\thttpTransport = &http.Transport{\n\t\t\tProxy: proxyFromEnvironment,\n\t\t\tDialContext: (&net.Dialer{\n\t\t\t\tTimeout:   30 * time.Second,\n\t\t\t\tKeepAlive: 30 * time.Second,\n\t\t\t}).DialContext,\n\t\t\tTLSHandshakeTimeout: 10 * time.Second,\n\t\t}\n\t}\n\ttr := &verboseTransport{\n\t\tTransport:   httpTransport,\n\t\tVerbose:     verbose,\n\t\tOverrideURL: testURL,\n\t\tOut:         ui.Stderr,\n\t\tColorized:   ui.IsTerminal(os.Stderr),\n\t}\n\n\treturn &http.Client{\n\t\tTransport:     tr,\n\t\tCheckRedirect: checkRedirect,\n\t}\n}\n\nfunc checkRedirect(req *http.Request, via []*http.Request) error {\n\tvar recommendedCode int\n\tswitch req.Response.StatusCode {\n\tcase 301:\n\t\trecommendedCode = 308\n\tcase 302:\n\t\trecommendedCode = 307\n\t}\n\n\torigMethod := via[len(via)-1].Method\n\tif recommendedCode != 0 && !strings.EqualFold(req.Method, origMethod) {\n\t\treturn fmt.Errorf(\n\t\t\t\"refusing to follow HTTP %d redirect for a %s request\\n\"+\n\t\t\t\t\"Have your site admin use HTTP %d for this kind of redirect\",\n\t\t\treq.Response.StatusCode, origMethod, recommendedCode)\n\t}\n\n\t// inherited from stdlib defaultCheckRedirect\n\tif len(via) >= 10 {\n\t\treturn errors.New(\"stopped after 10 redirects\")\n\t}\n\treturn nil\n}\n\nfunc cloneRequest(req *http.Request) *http.Request {\n\tdup := new(http.Request)\n\t*dup = *req\n\tdup.URL, _ = url.Parse(req.URL.String())\n\tdup.Header = make(http.Header)\n\tfor k, s := range req.Header {\n\t\tdup.Header[k] = s\n\t}\n\treturn dup\n}\n\nvar proxyFunc func(*url.URL) (*url.URL, error)\n\nfunc proxyFromEnvironment(req *http.Request) (*url.URL, error) {\n\tif proxyFunc == nil {\n\t\tproxyFunc = httpproxy.FromEnvironment().ProxyFunc()\n\t}\n\treturn proxyFunc(req.URL)\n}\n\ntype simpleClient struct {\n\thttpClient     *http.Client\n\trootURL        *url.URL\n\tPrepareRequest func(*http.Request)\n\tCacheTTL       int\n}\n\nfunc (c *simpleClient) performRequest(method, path string, body io.Reader, configure func(*http.Request)) (*simpleResponse, error) {\n\tif path == \"graphql\" {\n\t\t// FIXME: This dirty workaround cancels out the \"v3\" portion of the\n\t\t// \"/api/v3\" prefix used for Enterprise. Find a better place for this.\n\t\tpath = \"../graphql\"\n\t}\n\turl, err := url.Parse(path)\n\tif err == nil {\n\t\turl = c.rootURL.ResolveReference(url)\n\t\treturn c.performRequestURL(method, url, body, configure)\n\t}\n\treturn nil, err\n}\n\nfunc (c *simpleClient) performRequestURL(method string, url *url.URL, body io.Reader, configure func(*http.Request)) (res *simpleResponse, err error) {\n\treq, err := http.NewRequest(method, url.String(), body)\n\tif err != nil {\n\t\treturn\n\t}\n\tif c.PrepareRequest != nil {\n\t\tc.PrepareRequest(req)\n\t}\n\treq.Header.Set(\"User-Agent\", UserAgent)\n\treq.Header.Set(\"Accept\", apiPayloadVersion)\n\n\tif configure != nil {\n\t\tconfigure(req)\n\t}\n\n\tkey := cacheKey(req)\n\tif cachedResponse := c.cacheRead(key, req); cachedResponse != nil {\n\t\tres = &simpleResponse{cachedResponse}\n\t\treturn\n\t}\n\n\thttpResponse, err := c.httpClient.Do(req)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tc.cacheWrite(key, httpResponse)\n\tres = &simpleResponse{httpResponse}\n\n\treturn\n}\n\nfunc isGraphQL(req *http.Request) bool {\n\treturn req.URL.Path == \"/graphql\"\n}\n\nfunc canCache(req *http.Request) bool {\n\treturn strings.EqualFold(req.Method, \"GET\") || isGraphQL(req)\n}\n\nfunc (c *simpleClient) cacheRead(key string, req *http.Request) (res *http.Response) {\n\tif c.CacheTTL > 0 && canCache(req) {\n\t\tf := cacheFile(key)\n\t\tcacheInfo, err := os.Stat(f)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif time.Since(cacheInfo.ModTime()).Seconds() > float64(c.CacheTTL) {\n\t\t\treturn\n\t\t}\n\t\tcf, err := os.Open(f)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tdefer cf.Close()\n\n\t\tcb, err := ioutil.ReadAll(cf)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tparts := strings.SplitN(string(cb), \"\\r\\n\\r\\n\", 2)\n\t\tif len(parts) < 2 {\n\t\t\treturn\n\t\t}\n\n\t\tres = &http.Response{\n\t\t\tBody:    ioutil.NopCloser(bytes.NewBufferString(parts[1])),\n\t\t\tHeader:  http.Header{},\n\t\t\tRequest: req,\n\t\t}\n\t\theaderLines := strings.Split(parts[0], \"\\r\\n\")\n\t\tif len(headerLines) < 1 {\n\t\t\treturn\n\t\t}\n\t\tif proto := strings.SplitN(headerLines[0], \" \", 3); len(proto) >= 3 {\n\t\t\tres.Proto = proto[0]\n\t\t\tres.Status = fmt.Sprintf(\"%s %s\", proto[1], proto[2])\n\t\t\tif code, _ := strconv.Atoi(proto[1]); code > 0 {\n\t\t\t\tres.StatusCode = code\n\t\t\t}\n\t\t}\n\t\tfor _, line := range headerLines[1:] {\n\t\t\tkv := strings.SplitN(line, \":\", 2)\n\t\t\tif len(kv) >= 2 {\n\t\t\t\tres.Header.Add(kv[0], strings.TrimLeft(kv[1], \" \"))\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n\nfunc (c *simpleClient) cacheWrite(key string, res *http.Response) {\n\tif c.CacheTTL > 0 && canCache(res.Request) && res.StatusCode < 500 && res.StatusCode != 403 {\n\t\tbodyCopy := &bytes.Buffer{}\n\t\tbodyReplacement := readCloserCallback{\n\t\t\tReader: io.TeeReader(res.Body, bodyCopy),\n\t\t\tCloser: res.Body,\n\t\t\tCallback: func() {\n\t\t\t\tf := cacheFile(key)\n\t\t\t\terr := os.MkdirAll(filepath.Dir(f), 0771)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tcf, err := os.OpenFile(f, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tdefer cf.Close()\n\t\t\t\tfmt.Fprintf(cf, \"%s %s\\r\\n\", res.Proto, res.Status)\n\t\t\t\tres.Header.Write(cf)\n\t\t\t\tfmt.Fprintf(cf, \"\\r\\n\")\n\t\t\t\tio.Copy(cf, bodyCopy)\n\t\t\t},\n\t\t}\n\t\tres.Body = &bodyReplacement\n\t}\n}\n\ntype readCloserCallback struct {\n\tCallback func()\n\tCloser   io.Closer\n\tio.Reader\n}\n\nfunc (rc *readCloserCallback) Close() error {\n\terr := rc.Closer.Close()\n\tif err == nil {\n\t\trc.Callback()\n\t}\n\treturn err\n}\n\nfunc cacheKey(req *http.Request) string {\n\tpath := strings.Replace(req.URL.EscapedPath(), \"/\", \"-\", -1)\n\tif len(path) > 1 {\n\t\tpath = strings.TrimPrefix(path, \"-\")\n\t}\n\thost := req.Host\n\tif host == \"\" {\n\t\thost = req.URL.Host\n\t}\n\thash := md5.New()\n\tfmt.Fprintf(hash, \"%d:\", cacheVersion)\n\tio.WriteString(hash, req.Header.Get(\"Accept\"))\n\tio.WriteString(hash, req.Header.Get(\"Authorization\"))\n\tqueryParts := strings.Split(req.URL.RawQuery, \"&\")\n\tsort.Strings(queryParts)\n\tfor _, q := range queryParts {\n\t\tfmt.Fprintf(hash, \"%s&\", q)\n\t}\n\tif isGraphQL(req) && req.Body != nil {\n\t\tif b, err := ioutil.ReadAll(req.Body); err == nil {\n\t\t\treq.Body = ioutil.NopCloser(bytes.NewBuffer(b))\n\t\t\thash.Write(b)\n\t\t}\n\t}\n\treturn fmt.Sprintf(\"%s/%s_%x\", host, path, hash.Sum(nil))\n}\n\nfunc cacheFile(key string) string {\n\treturn path.Join(os.TempDir(), \"hub\", \"api\", key)\n}\n\nfunc (c *simpleClient) jsonRequest(method, path string, body interface{}, configure func(*http.Request)) (*simpleResponse, error) {\n\tjson, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbuf := bytes.NewBuffer(json)\n\n\treturn c.performRequest(method, path, buf, func(req *http.Request) {\n\t\treq.Header.Set(\"Content-Type\", \"application/json; charset=utf-8\")\n\t\tif configure != nil {\n\t\t\tconfigure(req)\n\t\t}\n\t})\n}\n\nfunc (c *simpleClient) Get(path string) (*simpleResponse, error) {\n\treturn c.performRequest(\"GET\", path, nil, nil)\n}\n\nfunc (c *simpleClient) GetFile(path string, mimeType string) (*simpleResponse, error) {\n\treturn c.performRequest(\"GET\", path, nil, func(req *http.Request) {\n\t\treq.Header.Set(\"Accept\", mimeType)\n\t})\n}\n\nfunc (c *simpleClient) Delete(path string) (*simpleResponse, error) {\n\treturn c.performRequest(\"DELETE\", path, nil, nil)\n}\n\nfunc (c *simpleClient) PostJSON(path string, payload interface{}) (*simpleResponse, error) {\n\treturn c.jsonRequest(\"POST\", path, payload, nil)\n}\n\nfunc (c *simpleClient) PostJSONPreview(path string, payload interface{}, mimeType string) (*simpleResponse, error) {\n\treturn c.jsonRequest(\"POST\", path, payload, func(req *http.Request) {\n\t\treq.Header.Set(\"Accept\", mimeType)\n\t})\n}\n\nfunc (c *simpleClient) PutJSON(path string, payload interface{}) (*simpleResponse, error) {\n\treturn c.jsonRequest(\"PUT\", path, payload, nil)\n}\n\nfunc (c *simpleClient) PatchJSON(path string, payload interface{}) (*simpleResponse, error) {\n\treturn c.jsonRequest(\"PATCH\", path, payload, nil)\n}\n\nfunc (c *simpleClient) PostFile(path string, contents io.Reader, fileSize int64) (*simpleResponse, error) {\n\treturn c.performRequest(\"POST\", path, contents, func(req *http.Request) {\n\t\tif fileSize > 0 {\n\t\t\treq.ContentLength = fileSize\n\t\t}\n\t\treq.Header.Set(\"Content-Type\", \"application/octet-stream\")\n\t})\n}\n\ntype simpleResponse struct {\n\t*http.Response\n}\n\ntype errorInfo struct {\n\tMessage  string       `json:\"message\"`\n\tErrors   []fieldError `json:\"errors\"`\n\tResponse *http.Response\n}\ntype errorInfoSimple struct {\n\tMessage string   `json:\"message\"`\n\tErrors  []string `json:\"errors\"`\n}\ntype fieldError struct {\n\tResource string `json:\"resource\"`\n\tMessage  string `json:\"message\"`\n\tCode     string `json:\"code\"`\n\tField    string `json:\"field\"`\n}\n\nfunc (e *errorInfo) Error() string {\n\treturn e.Message\n}\n\nfunc (res *simpleResponse) Unmarshal(dest interface{}) (err error) {\n\tdefer res.Body.Close()\n\n\tbody, err := ioutil.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn json.Unmarshal(body, dest)\n}\n\nfunc (res *simpleResponse) ErrorInfo() (msg *errorInfo, err error) {\n\tdefer res.Body.Close()\n\n\tbody, err := ioutil.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tmsg = &errorInfo{}\n\terr = json.Unmarshal(body, msg)\n\tif err != nil {\n\t\tmsgSimple := &errorInfoSimple{}\n\t\tif err = json.Unmarshal(body, msgSimple); err == nil {\n\t\t\tmsg.Message = msgSimple.Message\n\t\t\tfor _, errMsg := range msgSimple.Errors {\n\t\t\t\tmsg.Errors = append(msg.Errors, fieldError{\n\t\t\t\t\tCode:    \"custom\",\n\t\t\t\t\tMessage: errMsg,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\tif err == nil {\n\t\tmsg.Response = res.Response\n\t}\n\n\treturn\n}\n\nfunc (res *simpleResponse) Link(name string) string {\n\tlinkVal := res.Header.Get(\"Link\")\n\tre := regexp.MustCompile(`<([^>]+)>; rel=\"([^\"]+)\"`)\n\tfor _, match := range re.FindAllStringSubmatch(linkVal, -1) {\n\t\tif match[2] == name {\n\t\t\treturn match[1]\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc (res *simpleResponse) RateLimitRemaining() int {\n\tif v := res.Header.Get(rateLimitRemainingHeader); len(v) > 0 {\n\t\tif num, err := strconv.Atoi(v); err == nil {\n\t\t\treturn num\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (res *simpleResponse) RateLimitReset() int {\n\tif v := res.Header.Get(rateLimitResetHeader); len(v) > 0 {\n\t\tif ts, err := strconv.Atoi(v); err == nil {\n\t\t\treturn ts\n\t\t}\n\t}\n\treturn -1\n}\n"
  },
  {
    "path": "github/http_test.go",
    "content": "package github\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc setupTestServer(unixSocket string) *testServer {\n\tm := http.NewServeMux()\n\ts := httptest.NewServer(m)\n\tu, _ := url.Parse(s.URL)\n\n\tif unixSocket != \"\" {\n\t\tos.Remove(unixSocket)\n\t\tunixListener, err := net.Listen(\"unix\", unixSocket)\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"Unable to listen on unix-socket: \", err)\n\t\t}\n\t\tgo http.Serve(unixListener, m)\n\t}\n\n\treturn &testServer{\n\t\tServer:   s,\n\t\tServeMux: m,\n\t\tURL:      u,\n\t}\n}\n\ntype testServer struct {\n\t*http.ServeMux\n\tServer *httptest.Server\n\tURL    *url.URL\n}\n\nfunc (s *testServer) Close() {\n\ts.Server.Close()\n}\n\nfunc TestNewHttpClient_OverrideURL(t *testing.T) {\n\ts := setupTestServer(\"\")\n\tdefer s.Close()\n\n\ts.HandleFunc(\"/override\", func(w http.ResponseWriter, r *http.Request) {\n\t\tassert.Equal(t, \"https\", r.Header.Get(\"X-Original-Scheme\"))\n\t\tassert.Equal(t, \"example.com\", r.Host)\n\t})\n\n\tc := newHTTPClient(s.URL.String(), false, \"\")\n\tc.Get(\"https://example.com/override\")\n\n\ts.HandleFunc(\"/not-override\", func(w http.ResponseWriter, r *http.Request) {\n\t\tassert.Equal(t, \"\", r.Header.Get(\"X-Original-Scheme\"))\n\t\tassert.Equal(t, s.URL.Host, r.Host)\n\t})\n\n\tc = newHTTPClient(\"\", false, \"\")\n\tc.Get(fmt.Sprintf(\"%s/not-override\", s.URL.String()))\n}\n\nfunc TestNewHttpClient_UnixSocket(t *testing.T) {\n\tsock := \"/tmp/hub-go.sock\"\n\ts := setupTestServer(sock)\n\tdefer s.Close()\n\n\ts.HandleFunc(\"/unix-socket\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Write([]byte(\"unix-socket-works\"))\n\t})\n\tc := newHTTPClient(\"\", false, sock)\n\tresp, err := c.Get(fmt.Sprintf(\"%s/unix-socket\", s.URL.String()))\n\tassert.Equal(t, nil, err)\n\tresult, _ := ioutil.ReadAll(resp.Body)\n\tassert.Equal(t, \"unix-socket-works\", string(result))\n}\n\nfunc TestVerboseTransport_VerbosePrintln(t *testing.T) {\n\tvar b bytes.Buffer\n\ttr := &verboseTransport{\n\t\tOut:       &b,\n\t\tColorized: true,\n\t}\n\n\ttr.verbosePrintln(\"foo\")\n\tassert.Equal(t, \"\\033[36mfoo\\033[0m\\n\", b.String())\n}\n"
  },
  {
    "path": "github/localrepo.go",
    "content": "package github\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/git\"\n)\n\nfunc LocalRepo() (repo *GitHubRepo, err error) {\n\trepo = &GitHubRepo{}\n\n\t_, err = git.Dir()\n\tif err != nil {\n\t\terr = fmt.Errorf(\"fatal: Not a git repository\")\n\t\treturn\n\t}\n\n\treturn\n}\n\ntype GitHubRepo struct {\n\tremotes []Remote\n}\n\nfunc (r *GitHubRepo) loadRemotes() error {\n\tif r.remotes != nil {\n\t\treturn nil\n\t}\n\n\tremotes, err := Remotes()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.remotes = remotes\n\n\treturn nil\n}\n\nfunc (r *GitHubRepo) RemoteByName(name string) (*Remote, error) {\n\tif err := r.loadRemotes(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, remote := range r.remotes {\n\t\tif remote.Name == name {\n\t\t\treturn &remote, nil\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"No git remote with name %s\", name)\n}\n\nfunc (r *GitHubRepo) remotesForPublish(owner string) (remotes []Remote) {\n\tr.loadRemotes()\n\tremotesMap := make(map[string]Remote)\n\n\tif owner != \"\" {\n\t\tfor _, remote := range r.remotes {\n\t\t\tp, e := remote.Project()\n\t\t\tif e == nil && strings.EqualFold(p.Owner, owner) {\n\t\t\t\tremotesMap[remote.Name] = remote\n\t\t\t}\n\t\t}\n\t}\n\n\tnames := OriginNamesInLookupOrder\n\tfor _, name := range names {\n\t\tif _, ok := remotesMap[name]; ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tremote, err := r.RemoteByName(name)\n\t\tif err == nil {\n\t\t\tremotesMap[remote.Name] = *remote\n\t\t}\n\t}\n\n\tfor i := len(names) - 1; i >= 0; i-- {\n\t\tname := names[i]\n\t\tif remote, ok := remotesMap[name]; ok {\n\t\t\tremotes = append(remotes, remote)\n\t\t\tdelete(remotesMap, name)\n\t\t}\n\t}\n\n\t// anything other than names has higher priority\n\tfor _, remote := range remotesMap {\n\t\tremotes = append([]Remote{remote}, remotes...)\n\t}\n\n\treturn\n}\n\nfunc (r *GitHubRepo) CurrentBranch() (branch *Branch, err error) {\n\thead, err := git.Head()\n\tif err != nil {\n\t\terr = fmt.Errorf(\"Aborted: not currently on any branch.\")\n\t\treturn\n\t}\n\n\tbranch = &Branch{r, head}\n\treturn\n}\n\nfunc (r *GitHubRepo) MasterBranch() *Branch {\n\tif remote, err := r.MainRemote(); err == nil {\n\t\treturn r.DefaultBranch(remote)\n\t}\n\treturn r.DefaultBranch(nil)\n}\n\nfunc (r *GitHubRepo) DefaultBranch(remote *Remote) *Branch {\n\tb := Branch{\n\t\tRepo: r,\n\t\tName: \"refs/heads/master\",\n\t}\n\tif remote != nil {\n\t\tif name, err := git.SymbolicRef(fmt.Sprintf(\"refs/remotes/%s/HEAD\", remote.Name)); err == nil {\n\t\t\tb.Name = name\n\t\t}\n\t}\n\treturn &b\n}\n\nfunc (r *GitHubRepo) RemoteBranchAndProject(owner string, preferUpstream bool) (branch *Branch, project *Project, err error) {\n\tif err = r.loadRemotes(); err != nil {\n\t\treturn\n\t}\n\n\tfor _, remote := range r.remotes {\n\t\tif p, err := remote.Project(); err == nil {\n\t\t\tproject = p\n\t\t\tbreak\n\t\t}\n\t}\n\n\tbranch, err = r.CurrentBranch()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif project == nil {\n\t\treturn\n\t}\n\n\tpushDefault, _ := git.Config(\"push.default\")\n\tif pushDefault == \"upstream\" || pushDefault == \"tracking\" {\n\t\tupstream, e := branch.Upstream()\n\t\tif e == nil && upstream.IsRemote() {\n\t\t\tremote, e := r.RemoteByName(upstream.RemoteName())\n\t\t\tif e == nil {\n\t\t\t\tp, e := remote.Project()\n\t\t\t\tif e == nil {\n\t\t\t\t\tbranch = upstream\n\t\t\t\t\tproject = p\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tshortName := branch.ShortName()\n\tremotes := r.remotesForPublish(owner)\n\tif preferUpstream {\n\t\t// reverse the remote lookup order; see OriginNamesInLookupOrder\n\t\tremotesInOrder := []Remote{}\n\t\tfor i := len(remotes) - 1; i >= 0; i-- {\n\t\t\tremotesInOrder = append(remotesInOrder, remotes[i])\n\t\t}\n\t\tremotes = remotesInOrder\n\t}\n\n\tfor _, remote := range remotes {\n\t\tp, e := remote.Project()\n\t\tif e != nil {\n\t\t\tcontinue\n\t\t}\n\t\t// NOTE: this is similar RemoteForBranch\n\t\tif git.HasFile(\"refs\", \"remotes\", remote.Name, shortName) {\n\t\t\tname := fmt.Sprintf(\"refs/remotes/%s/%s\", remote.Name, shortName)\n\t\t\tbranch = &Branch{r, name}\n\t\t\tproject = p\n\t\t\treturn\n\t\t}\n\t}\n\n\tbranch = nil\n\treturn\n}\n\nfunc (r *GitHubRepo) RemoteForBranch(branch *Branch, owner string) *Remote {\n\tbranchName := branch.ShortName()\n\tfor _, remote := range r.remotesForPublish(owner) {\n\t\tif git.HasFile(\"refs\", \"remotes\", remote.Name, branchName) {\n\t\t\treturn &remote\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *GitHubRepo) RemoteForRepo(repo *Repository) (*Remote, error) {\n\tif err := r.loadRemotes(); err != nil {\n\t\treturn nil, err\n\t}\n\n\trepoURL, err := url.Parse(repo.HTMLURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tproject := NewProject(repo.Owner.Login, repo.Name, repoURL.Host)\n\n\tfor _, remote := range r.remotes {\n\t\tif rp, err := remote.Project(); err == nil {\n\t\t\tif rp.SameAs(project) {\n\t\t\t\treturn &remote, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn nil, fmt.Errorf(\"could not find a git remote for '%s/%s'\", repo.Owner.Login, repo.Name)\n}\n\nfunc (r *GitHubRepo) RemoteForProject(project *Project) (*Remote, error) {\n\tif err := r.loadRemotes(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, remote := range r.remotes {\n\t\tremoteProject, err := remote.Project()\n\t\tif err == nil && remoteProject.SameAs(project) {\n\t\t\treturn &remote, nil\n\t\t}\n\t}\n\treturn nil, fmt.Errorf(\"could not find a git remote for '%s'\", project)\n}\n\nfunc (r *GitHubRepo) MainRemote() (*Remote, error) {\n\tr.loadRemotes()\n\n\tif len(r.remotes) > 0 {\n\t\treturn &r.remotes[0], nil\n\t}\n\treturn nil, fmt.Errorf(\"no git remotes found\")\n}\n\nfunc (r *GitHubRepo) MainProject() (*Project, error) {\n\tr.loadRemotes()\n\n\tfor _, remote := range r.remotes {\n\t\tif project, err := remote.Project(); err == nil {\n\t\t\treturn project, nil\n\t\t}\n\t}\n\treturn nil, fmt.Errorf(\"Aborted: could not find any git remote pointing to a GitHub repository\")\n}\n\nfunc (r *GitHubRepo) CurrentProject() (project *Project, err error) {\n\tproject, err = r.UpstreamProject()\n\tif err != nil {\n\t\tproject, err = r.MainProject()\n\t}\n\n\treturn\n}\n\nfunc (r *GitHubRepo) UpstreamProject() (project *Project, err error) {\n\tcurrentBranch, err := r.CurrentBranch()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tupstream, err := currentBranch.Upstream()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tremote, err := r.RemoteByName(upstream.RemoteName())\n\tif err != nil {\n\t\treturn\n\t}\n\n\tproject, err = remote.Project()\n\n\treturn\n}\n"
  },
  {
    "path": "github/localrepo_test.go",
    "content": "package github\n\nimport (\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestGitHubRepo_remotesForPublish(t *testing.T) {\n\turl, _ := url.Parse(\"ssh://git@github.com/Owner/repo.git\")\n\tremotes := []Remote{\n\t\t{\n\t\t\tName: \"Owner\",\n\t\t\tURL:  url,\n\t\t},\n\t}\n\trepo := GitHubRepo{remotes}\n\tremotesForPublish := repo.remotesForPublish(\"owner\")\n\n\tassert.Equal(t, 1, len(remotesForPublish))\n\tassert.Equal(t, \"Owner\", remotesForPublish[0].Name)\n\tassert.Equal(t, url.String(), remotesForPublish[0].URL.String())\n}\n"
  },
  {
    "path": "github/message_builder.go",
    "content": "package github\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n)\n\ntype MessageBuilder struct {\n\tTitle             string\n\tFilename          string\n\tMessage           string\n\tEdit              bool\n\tcommentedSections []string\n\teditor            *Editor\n}\n\nfunc (b *MessageBuilder) AddCommentedSection(section string) {\n\tb.commentedSections = append(b.commentedSections, section)\n}\n\nfunc (b *MessageBuilder) Extract() (title, body string, err error) {\n\tcontent := b.Message\n\n\tif b.Edit {\n\t\tb.editor, err = NewEditor(b.Filename, b.Title, content)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tfor _, section := range b.commentedSections {\n\t\t\tb.editor.AddCommentedSection(section)\n\t\t}\n\t\tcontent, err = b.editor.EditContent()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tnl := regexp.MustCompile(`\\r?\\n`)\n\t\tcontent = nl.ReplaceAllString(content, \"\\n\")\n\t}\n\n\ttitle, body = SplitTitleBody(content)\n\tif title == \"\" {\n\t\tdefer b.Cleanup()\n\t}\n\n\treturn\n}\n\nfunc (b *MessageBuilder) Cleanup() {\n\tif b.editor != nil {\n\t\tb.editor.DeleteFile()\n\t}\n}\n\nfunc SplitTitleBody(content string) (title string, body string) {\n\tparts := strings.SplitN(content, \"\\n\\n\", 2)\n\tif len(parts) >= 1 {\n\t\ttitle = strings.TrimSpace(strings.Replace(parts[0], \"\\n\", \" \", -1))\n\t}\n\tif len(parts) >= 2 {\n\t\tbody = strings.TrimSpace(parts[1])\n\t}\n\treturn\n}\n"
  },
  {
    "path": "github/message_builder_test.go",
    "content": "package github\n\nimport (\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestMessageBuilder_multiline_title(t *testing.T) {\n\tbuilder := &MessageBuilder{\n\t\tMessage: `hello\nmultiline\ntext\n\nthe rest is\ndescription`,\n\t}\n\n\ttitle, body, err := builder.Extract()\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"hello multiline text\", title)\n\tassert.Equal(t, \"the rest is\\ndescription\", body)\n}\n"
  },
  {
    "path": "github/project.go",
    "content": "package github\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/git\"\n\t\"github.com/github/hub/v2/utils\"\n)\n\ntype Project struct {\n\tName     string\n\tOwner    string\n\tHost     string\n\tProtocol string\n}\n\nfunc (p Project) String() string {\n\treturn fmt.Sprintf(\"%s/%s\", p.Owner, p.Name)\n}\n\nfunc (p *Project) SameAs(other *Project) bool {\n\treturn strings.EqualFold(p.Owner, other.Owner) &&\n\t\tstrings.EqualFold(p.Name, other.Name) &&\n\t\tstrings.EqualFold(p.Host, other.Host)\n}\n\nfunc (p *Project) WebURL(name, owner, path string) string {\n\tif owner == \"\" {\n\t\towner = p.Owner\n\t}\n\tif name == \"\" {\n\t\tname = p.Name\n\t}\n\n\townerWithName := fmt.Sprintf(\"%s/%s\", owner, name)\n\tif strings.Contains(ownerWithName, \".wiki\") {\n\t\townerWithName = strings.TrimSuffix(ownerWithName, \".wiki\")\n\t\tif path != \"wiki\" {\n\t\t\tif strings.HasPrefix(path, \"commits\") {\n\t\t\t\tpath = \"_history\"\n\t\t\t} else if path != \"\" {\n\t\t\t\tpath = fmt.Sprintf(\"_%s\", path)\n\t\t\t}\n\n\t\t\tif path != \"\" {\n\t\t\t\tpath = utils.ConcatPaths(\"wiki\", path)\n\t\t\t} else {\n\t\t\t\tpath = \"wiki\"\n\t\t\t}\n\t\t}\n\t}\n\n\turl := fmt.Sprintf(\"%s://%s\", p.Protocol, utils.ConcatPaths(p.Host, ownerWithName))\n\tif path != \"\" {\n\t\turl = utils.ConcatPaths(url, path)\n\t}\n\n\treturn url\n}\n\nfunc (p *Project) GitURL(name, owner string, allowPush bool) string {\n\tif name == \"\" {\n\t\tname = p.Name\n\t}\n\tif owner == \"\" {\n\t\towner = p.Owner\n\t}\n\n\thost := rawHost(p.Host)\n\n\tswitch preferredProtocol() {\n\tcase \"git\":\n\t\tif allowPush {\n\t\t\treturn fmt.Sprintf(\"git@%s:%s/%s.git\", host, owner, name)\n\t\t}\n\t\treturn fmt.Sprintf(\"git://%s/%s/%s.git\", host, owner, name)\n\tcase \"ssh\":\n\t\treturn fmt.Sprintf(\"git@%s:%s/%s.git\", host, owner, name)\n\tdefault:\n\t\treturn fmt.Sprintf(\"https://%s/%s/%s.git\", host, owner, name)\n\t}\n}\n\n// Remove the scheme from host when the host url is absolute.\nfunc rawHost(host string) string {\n\tu, err := url.Parse(host)\n\tutils.Check(err)\n\n\tif u.IsAbs() {\n\t\treturn u.Host\n\t}\n\treturn u.Path\n}\n\nfunc preferredProtocol() string {\n\tuserProtocol := os.Getenv(\"HUB_PROTOCOL\")\n\tif userProtocol == \"\" {\n\t\tuserProtocol, _ = git.Config(\"hub.protocol\")\n\t}\n\treturn userProtocol\n}\n\nfunc NewProjectFromRepo(repo *Repository) (p *Project, err error) {\n\turl, err := url.Parse(repo.HTMLURL)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tp, err = NewProjectFromURL(url)\n\treturn\n}\n\nfunc NewProjectFromURL(url *url.URL) (p *Project, err error) {\n\tif !knownGitHubHostsInclude(url.Host) {\n\t\terr = &HostError{url}\n\t\treturn\n\t}\n\n\tparts := strings.SplitN(url.Path, \"/\", 4)\n\tif len(parts) <= 2 {\n\t\terr = fmt.Errorf(\"Invalid GitHub URL: %s\", url)\n\t\treturn\n\t}\n\n\tname := strings.TrimSuffix(parts[2], \".git\")\n\tp = newProject(parts[1], name, url.Host, url.Scheme)\n\n\treturn\n}\n\nfunc NewProject(owner, name, host string) *Project {\n\treturn newProject(owner, name, host, \"\")\n}\n\nfunc newProject(owner, name, host, protocol string) *Project {\n\tif strings.Contains(owner, \"/\") {\n\t\tresult := strings.SplitN(owner, \"/\", 2)\n\t\towner = result[0]\n\t\tif name == \"\" {\n\t\t\tname = result[1]\n\t\t}\n\t} else if strings.Contains(name, \"/\") {\n\t\tresult := strings.SplitN(name, \"/\", 2)\n\t\tif owner == \"\" {\n\t\t\towner = result[0]\n\t\t}\n\t\tname = result[1]\n\t}\n\n\tif host == \"\" {\n\t\thost = DefaultGitHubHost()\n\t}\n\tif host == \"ssh.github.com\" {\n\t\thost = GitHubHost\n\t}\n\n\tif protocol != \"http\" && protocol != \"https\" {\n\t\tprotocol = \"\"\n\t}\n\tif protocol == \"\" {\n\t\th := CurrentConfig().Find(host)\n\t\tif h != nil {\n\t\t\tprotocol = h.Protocol\n\t\t}\n\t}\n\tif protocol == \"\" {\n\t\tprotocol = \"https\"\n\t}\n\n\tif owner == \"\" {\n\t\th := CurrentConfig().Find(host)\n\t\tif h != nil {\n\t\t\towner = h.User\n\t\t}\n\t}\n\n\treturn &Project{\n\t\tName:     name,\n\t\tOwner:    owner,\n\t\tHost:     host,\n\t\tProtocol: protocol,\n\t}\n}\n\nfunc SanitizeProjectName(name string) string {\n\tname = filepath.Base(name)\n\treturn strings.Replace(name, \" \", \"-\", -1)\n}\n"
  },
  {
    "path": "github/project_test.go",
    "content": "package github\n\nimport (\n\t\"net/url\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/fixtures\"\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestSameAs(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tproject1 *Project\n\t\tproject2 *Project\n\t\twant     bool\n\t}{\n\t\t{\n\t\t\tname: \"same project\",\n\t\t\tproject1: &Project{\n\t\t\t\tOwner: \"fOo\",\n\t\t\t\tName:  \"baR\",\n\t\t\t\tHost:  \"gItHUb.com\",\n\t\t\t},\n\t\t\tproject2: &Project{\n\t\t\t\tOwner: \"FoO\",\n\t\t\t\tName:  \"BAr\",\n\t\t\t\tHost:  \"GithUB.com\",\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"different project\",\n\t\t\tproject1: &Project{\n\t\t\t\tOwner: \"foo\",\n\t\t\t\tName:  \"bar\",\n\t\t\t\tHost:  \"github.com\",\n\t\t\t},\n\t\t\tproject2: &Project{\n\t\t\t\tOwner: \"foo\",\n\t\t\t\tName:  \"baz\",\n\t\t\t\tHost:  \"github.com\",\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tgot := test.project1.SameAs(test.project2)\n\t\t\twant := test.want\n\n\t\t\tassert.Equal(t, want, got)\n\t\t})\n\t}\n}\n\nfunc TestProject_WebURL(t *testing.T) {\n\tproject := Project{\n\t\tName:     \"foo\",\n\t\tOwner:    \"bar\",\n\t\tHost:     \"github.com\",\n\t\tProtocol: \"https\",\n\t}\n\n\turl := project.WebURL(\"\", \"\", \"baz\")\n\tassert.Equal(t, \"https://github.com/bar/foo/baz\", url)\n\n\turl = project.WebURL(\"1\", \"2\", \"\")\n\tassert.Equal(t, \"https://github.com/2/1\", url)\n\n\turl = project.WebURL(\"hub.wiki\", \"defunkt\", \"\")\n\tassert.Equal(t, \"https://github.com/defunkt/hub/wiki\", url)\n\n\turl = project.WebURL(\"hub.wiki\", \"defunkt\", \"commits\")\n\tassert.Equal(t, \"https://github.com/defunkt/hub/wiki/_history\", url)\n\n\turl = project.WebURL(\"hub.wiki\", \"defunkt\", \"pages\")\n\tassert.Equal(t, \"https://github.com/defunkt/hub/wiki/_pages\", url)\n}\n\nfunc TestProject_GitURL(t *testing.T) {\n\tos.Setenv(\"HUB_PROTOCOL\", \"https\")\n\tdefer os.Setenv(\"HUB_PROTOCOL\", \"\")\n\n\tproject := Project{\n\t\tName:  \"foo\",\n\t\tOwner: \"bar\",\n\t\tHost:  \"github.com\",\n\t}\n\n\turl := project.GitURL(\"gh\", \"jingweno\", false)\n\tassert.Equal(t, \"https://github.com/jingweno/gh.git\", url)\n\n\tos.Setenv(\"HUB_PROTOCOL\", \"git\")\n\turl = project.GitURL(\"gh\", \"jingweno\", false)\n\tassert.Equal(t, \"git://github.com/jingweno/gh.git\", url)\n\n\tos.Setenv(\"HUB_PROTOCOL\", \"ssh\")\n\turl = project.GitURL(\"gh\", \"jingweno\", true)\n\tassert.Equal(t, \"git@github.com:jingweno/gh.git\", url)\n\n\turl = project.GitURL(\"gh\", \"jingweno\", true)\n\tassert.Equal(t, \"git@github.com:jingweno/gh.git\", url)\n}\n\nfunc TestProject_GitURLEnterprise(t *testing.T) {\n\tproject := Project{\n\t\tName:  \"foo\",\n\t\tOwner: \"bar\",\n\t\tHost:  \"https://github.corporate.com\",\n\t}\n\n\tdefer os.Setenv(\"HUB_PROTOCOL\", \"\")\n\n\tos.Setenv(\"HUB_PROTOCOL\", \"https\")\n\turl := project.GitURL(\"gh\", \"jingweno\", false)\n\tassert.Equal(t, \"https://github.corporate.com/jingweno/gh.git\", url)\n\n\tos.Setenv(\"HUB_PROTOCOL\", \"ssh\")\n\turl = project.GitURL(\"gh\", \"jingweno\", false)\n\tassert.Equal(t, \"git@github.corporate.com:jingweno/gh.git\", url)\n\n\tos.Setenv(\"HUB_PROTOCOL\", \"git\")\n\turl = project.GitURL(\"gh\", \"jingweno\", false)\n\tassert.Equal(t, \"git://github.corporate.com/jingweno/gh.git\", url)\n\n\turl = project.GitURL(\"gh\", \"jingweno\", true)\n\tassert.Equal(t, \"git@github.corporate.com:jingweno/gh.git\", url)\n}\n\nfunc TestProject_NewProjectFromURL(t *testing.T) {\n\ttestConfigs := fixtures.SetupTestConfigs()\n\tdefer testConfigs.TearDown()\n\n\tu, _ := url.Parse(\"ssh://git@github.com/octokit/go-octokit.git\")\n\tp, err := NewProjectFromURL(u)\n\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"go-octokit\", p.Name)\n\tassert.Equal(t, \"octokit\", p.Owner)\n\tassert.Equal(t, \"github.com\", p.Host)\n\tassert.Equal(t, \"http\", p.Protocol)\n\n\tu, _ = url.Parse(\"ssh://ssh.github.com/octokit/go-octokit.git\")\n\tp, err = NewProjectFromURL(u)\n\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"go-octokit\", p.Name)\n\tassert.Equal(t, \"octokit\", p.Owner)\n\tassert.Equal(t, \"github.com\", p.Host)\n\tassert.Equal(t, \"http\", p.Protocol)\n\n\tu, _ = url.Parse(\"git://github.com/octokit/go-octokit.git\")\n\tp, err = NewProjectFromURL(u)\n\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"go-octokit\", p.Name)\n\tassert.Equal(t, \"octokit\", p.Owner)\n\tassert.Equal(t, \"github.com\", p.Host)\n\tassert.Equal(t, \"http\", p.Protocol)\n\n\tu, _ = url.Parse(\"https://github.com/octokit/go-octokit\")\n\tp, err = NewProjectFromURL(u)\n\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"go-octokit\", p.Name)\n\tassert.Equal(t, \"octokit\", p.Owner)\n\tassert.Equal(t, \"github.com\", p.Host)\n\tassert.Equal(t, \"https\", p.Protocol)\n\n\tu, _ = url.Parse(\"origin/master\")\n\t_, err = NewProjectFromURL(u)\n\n\tassert.NotEqual(t, nil, err)\n}\n"
  },
  {
    "path": "github/remote.go",
    "content": "package github\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/github/hub/v2/git\"\n)\n\nvar (\n\tOriginNamesInLookupOrder = []string{\"upstream\", \"github\", \"origin\"}\n)\n\ntype Remote struct {\n\tName    string\n\tURL     *url.URL\n\tPushURL *url.URL\n}\n\nfunc (remote *Remote) String() string {\n\treturn remote.Name\n}\n\nfunc (remote *Remote) Project() (*Project, error) {\n\tp, err := NewProjectFromURL(remote.URL)\n\tif _, ok := err.(*HostError); ok {\n\t\treturn NewProjectFromURL(remote.PushURL)\n\t}\n\treturn p, err\n}\n\nfunc Remotes() (remotes []Remote, err error) {\n\tre := regexp.MustCompile(`(.+)\\s+(.+)\\s+\\((push|fetch)\\)`)\n\n\trs, err := git.Remotes()\n\tif err != nil {\n\t\terr = fmt.Errorf(\"Can't load git remote\")\n\t\treturn\n\t}\n\n\t// build the remotes map\n\tremotesMap := make(map[string]map[string]string)\n\tfor _, r := range rs {\n\t\tif re.MatchString(r) {\n\t\t\tmatch := re.FindStringSubmatch(r)\n\t\t\tname := strings.TrimSpace(match[1])\n\t\t\turl := strings.TrimSpace(match[2])\n\t\t\turlType := strings.TrimSpace(match[3])\n\t\t\tutm, ok := remotesMap[name]\n\t\t\tif !ok {\n\t\t\t\tutm = make(map[string]string)\n\t\t\t\tremotesMap[name] = utm\n\t\t\t}\n\t\t\tutm[urlType] = url\n\t\t}\n\t}\n\n\t// construct remotes in priority order\n\tnames := OriginNamesInLookupOrder\n\tfor _, name := range names {\n\t\tif u, ok := remotesMap[name]; ok {\n\t\t\tr, err := newRemote(name, u)\n\t\t\tif err == nil {\n\t\t\t\tremotes = append(remotes, r)\n\t\t\t\tdelete(remotesMap, name)\n\t\t\t}\n\t\t}\n\t}\n\n\t// the rest of the remotes\n\tfor n, u := range remotesMap {\n\t\tr, err := newRemote(n, u)\n\t\tif err == nil {\n\t\t\tremotes = append(remotes, r)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc newRemote(name string, urlMap map[string]string) (Remote, error) {\n\tr := Remote{}\n\n\tfetchURL, ferr := git.ParseURL(urlMap[\"fetch\"])\n\tpushURL, perr := git.ParseURL(urlMap[\"push\"])\n\tif ferr != nil && perr != nil {\n\t\treturn r, fmt.Errorf(\"No valid remote URLs\")\n\t}\n\n\tr.Name = name\n\tif ferr == nil {\n\t\tr.URL = fetchURL\n\t}\n\tif perr == nil {\n\t\tr.PushURL = pushURL\n\t}\n\n\treturn r, nil\n}\n"
  },
  {
    "path": "github/remote_test.go",
    "content": "package github\n\nimport (\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/fixtures\"\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestGithubRemote_NoPush(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\tremoteName := \"upstream\"\n\trepo.AddRemote(remoteName, \"user@example.com:test/project.git\", \"no_push\")\n\n\tremotes, err := Remotes()\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, len(remotes), 2)\n\tassert.Equal(t, remotes[0].Name, remoteName)\n\tassert.Equal(t, remotes[0].URL.Scheme, \"ssh\")\n\tassert.Equal(t, remotes[0].URL.Host, \"example.com\")\n\tassert.Equal(t, remotes[0].URL.Path, \"/test/project.git\")\n\tassert.Equal(t, remotes[1].Name, \"origin\")\n\tassert.Equal(t, remotes[1].URL.Path, repo.Remote)\n}\n\nfunc TestGithubRemote_GitPlusSsh(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\tremoteName := \"upstream\"\n\trepo.AddRemote(remoteName, \"git+ssh://git@github.com/frozencemetery/python-gssapi\", \"\")\n\n\tremotes, err := Remotes()\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, len(remotes), 2)\n\tassert.Equal(t, remotes[0].Name, remoteName)\n\tassert.Equal(t, remotes[0].URL.Scheme, \"ssh\")\n\tassert.Equal(t, remotes[0].URL.Host, \"github.com\")\n\tassert.Equal(t, remotes[0].URL.Path, \"/frozencemetery/python-gssapi\")\n\tassert.Equal(t, remotes[1].Name, \"origin\")\n\tassert.Equal(t, remotes[1].URL.Path, repo.Remote)\n}\n\nfunc TestGithubRemote_SshPort(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\tremoteName := \"upstream\"\n\trepo.AddRemote(remoteName, \"ssh://git@github.com:22/hakatashi/dotfiles.git\", \"\")\n\n\tremotes, err := Remotes()\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, len(remotes), 2)\n\tassert.Equal(t, remotes[0].Name, remoteName)\n\tassert.Equal(t, remotes[0].URL.Scheme, \"ssh\")\n\tassert.Equal(t, remotes[0].URL.Host, \"github.com\")\n\tassert.Equal(t, remotes[0].URL.Path, \"/hakatashi/dotfiles.git\")\n\tassert.Equal(t, remotes[1].Name, \"origin\")\n\tassert.Equal(t, remotes[1].URL.Path, repo.Remote)\n}\n\nfunc TestGithubRemote_ColonSlash(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\tremoteName := \"upstream\"\n\trepo.AddRemote(remoteName, \"git@github.com:/fatso83/my-project.git\", \"\")\n\n\tremotes, err := Remotes()\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, len(remotes), 2)\n\tassert.Equal(t, remotes[0].Name, remoteName)\n\tassert.Equal(t, remotes[0].URL.Scheme, \"ssh\")\n\tassert.Equal(t, remotes[0].URL.Host, \"github.com\")\n\tassert.Equal(t, remotes[0].URL.Path, \"/fatso83/my-project.git\")\n\tassert.Equal(t, remotes[1].Name, \"origin\")\n\tassert.Equal(t, remotes[1].URL.Path, repo.Remote)\n}\n"
  },
  {
    "path": "github/reset_console.go",
    "content": "//go:build !windows\n// +build !windows\n\npackage github\n\nimport (\n\t\"os\"\n\n\t\"github.com/github/hub/v2/cmd\"\n)\n\nfunc setConsole(cmd *cmd.Cmd) {\n\n\tstdin, err := os.OpenFile(\"/dev/tty\", os.O_RDONLY, 0660)\n\tif err == nil {\n\t\tcmd.Stdin = stdin\n\t}\n}\n"
  },
  {
    "path": "github/reset_console_windows.go",
    "content": "//go:build windows\n// +build windows\n\npackage github\n\nimport \"github.com/github/hub/v2/cmd\"\n\n// This does nothing on windows\nfunc setConsole(cmd *cmd.Cmd) {\n}\n"
  },
  {
    "path": "github/template.go",
    "content": "package github\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n)\n\nconst (\n\tPullRequestTemplate = \"pull_request_template\"\n\tIssueTemplate       = \"issue_template\"\n\tgithubTemplateDir   = \".github\"\n\tdocsDir             = \"docs\"\n)\n\nfunc ReadTemplate(kind, workdir string) (body string, err error) {\n\ttemplateDir := filepath.Join(workdir, githubTemplateDir)\n\n\tpath, err := getFilePath(templateDir, kind)\n\tif err != nil || path == \"\" {\n\t\tdocsDir := filepath.Join(workdir, docsDir)\n\t\tpath, err = getFilePath(docsDir, kind)\n\t}\n\tif err != nil || path == \"\" {\n\t\tpath, err = getFilePath(workdir, kind)\n\t}\n\n\tif path != \"\" {\n\t\tbody, err = readContentsFromFile(path)\n\t}\n\treturn\n}\n\ntype sortedFiles []os.FileInfo\n\nfunc (s sortedFiles) Len() int {\n\treturn len(s)\n}\nfunc (s sortedFiles) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\nfunc (s sortedFiles) Less(i, j int) bool {\n\treturn strings.Compare(strings.ToLower(s[i].Name()), strings.ToLower(s[j].Name())) > 0\n}\n\nfunc getFilePath(dir, pattern string) (found string, err error) {\n\tfiles, err := ioutil.ReadDir(dir)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tsort.Sort(sortedFiles(files))\n\n\tfor _, file := range files {\n\t\tfileName := file.Name()\n\t\tpath := strings.TrimSuffix(fileName, \".md\")\n\t\tpath = strings.TrimSuffix(path, \".txt\")\n\n\t\tif strings.EqualFold(pattern, path) {\n\t\t\tfound = filepath.Join(dir, fileName)\n\t\t\treturn\n\t\t}\n\t}\n\treturn\n}\n\nfunc readContentsFromFile(filename string) (contents string, err error) {\n\tcontent, err := ioutil.ReadFile(filename)\n\tif err != nil {\n\t\tif strings.HasSuffix(err.Error(), \" is a directory\") {\n\t\t\terr = nil\n\t\t}\n\t\treturn\n\t}\n\n\tcontents = strings.Replace(string(content), \"\\r\\n\", \"\\n\", -1)\n\tcontents = strings.TrimSuffix(contents, \"\\n\")\n\treturn\n}\n"
  },
  {
    "path": "github/template_test.go",
    "content": "package github\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/fixtures\"\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nvar prContent = `Description\n-----------\n[Enter your pull request description here]`\n\nvar issueContent = `Description\n-----------\n[Enter your issue description here]`\n\nfunc TestGithubTemplate_withoutTemplate(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\tpwd, _ := os.Getwd()\n\ttpl, err := ReadTemplate(PullRequestTemplate, pwd)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"\", tpl)\n\n\ttpl, err = ReadTemplate(IssueTemplate, pwd)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"\", tpl)\n}\n\nfunc TestGithubTemplate_withInvalidTemplate(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\taddGithubTemplates(repo, map[string]string{\"dir\": \"invalidPath\"})\n\n\tpwd, _ := os.Getwd()\n\ttpl, err := ReadTemplate(PullRequestTemplate, pwd)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"\", tpl)\n\n\ttpl, err = ReadTemplate(IssueTemplate, pwd)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"\", tpl)\n}\n\nfunc TestGithubTemplate_WithMarkdown(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\taddGithubTemplates(repo,\n\t\tmap[string]string{\n\t\t\t\"prTemplate\":    PullRequestTemplate + \".md\",\n\t\t\t\"issueTemplate\": IssueTemplate + \".md\",\n\t\t})\n\n\tpwd, _ := os.Getwd()\n\ttpl, err := ReadTemplate(PullRequestTemplate, pwd)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, prContent, tpl)\n\n\ttpl, err = ReadTemplate(IssueTemplate, pwd)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, issueContent, tpl)\n}\n\nfunc TestGithubTemplate_WithTemplateInHome(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\taddGithubTemplates(repo, map[string]string{})\n\n\tpwd, _ := os.Getwd()\n\ttpl, err := ReadTemplate(PullRequestTemplate, pwd)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, prContent, tpl)\n\n\ttpl, err = ReadTemplate(IssueTemplate, pwd)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, issueContent, tpl)\n}\n\nfunc TestGithubTemplate_WithTemplateInGithubDir(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\taddGithubTemplates(repo, map[string]string{\"dir\": githubTemplateDir})\n\n\tpwd, _ := os.Getwd()\n\ttpl, err := ReadTemplate(PullRequestTemplate, pwd)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, prContent, tpl)\n\n\ttpl, err = ReadTemplate(IssueTemplate, pwd)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, issueContent, tpl)\n}\n\nfunc TestGithubTemplate_WithTemplateInGithubDirAndMarkdown(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\taddGithubTemplates(repo,\n\t\tmap[string]string{\n\t\t\t\"prTemplate\":    PullRequestTemplate + \".md\",\n\t\t\t\"issueTemplate\": IssueTemplate + \".md\",\n\t\t\t\"dir\":           githubTemplateDir,\n\t\t})\n\n\tpwd, _ := os.Getwd()\n\ttpl, err := ReadTemplate(PullRequestTemplate, pwd)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, prContent, tpl)\n\n\ttpl, err = ReadTemplate(IssueTemplate, pwd)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, issueContent, tpl)\n}\n\nfunc TestGithubTemplate_WithTemplateInDocsDir(t *testing.T) {\n\trepo := fixtures.SetupTestRepo()\n\tdefer repo.TearDown()\n\n\taddGithubTemplates(repo, map[string]string{\"dir\": docsDir})\n\n\tpwd, _ := os.Getwd()\n\ttpl, err := ReadTemplate(PullRequestTemplate, pwd)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, prContent, tpl)\n\n\ttpl, err = ReadTemplate(IssueTemplate, pwd)\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, issueContent, tpl)\n}\n\nfunc addGithubTemplates(r *fixtures.TestRepo, config map[string]string) {\n\trepoDir := \"test.git\"\n\tif dir := config[\"dir\"]; dir != \"\" {\n\t\trepoDir = filepath.Join(repoDir, dir)\n\t}\n\n\tprTemplatePath := filepath.Join(repoDir, PullRequestTemplate)\n\tif prTmplPath := config[\"prTemplate\"]; prTmplPath != \"\" {\n\t\tprTemplatePath = filepath.Join(repoDir, prTmplPath)\n\t}\n\n\tissueTemplatePath := filepath.Join(repoDir, IssueTemplate)\n\tif issueTmplPath := config[\"issueTemplate\"]; issueTmplPath != \"\" {\n\t\tissueTemplatePath = filepath.Join(repoDir, issueTmplPath)\n\t}\n\n\tr.AddFile(prTemplatePath, prContent)\n\tr.AddFile(issueTemplatePath, issueContent)\n}\n"
  },
  {
    "path": "github/url.go",
    "content": "package github\n\nimport (\n\t\"net/url\"\n\t\"strings\"\n)\n\ntype URL struct {\n\turl.URL\n\t*Project\n}\n\nfunc (url URL) ProjectPath() (projectPath string) {\n\tsplit := strings.SplitN(url.Path, \"/\", 4)\n\tif len(split) > 3 {\n\t\tprojectPath = split[3]\n\t}\n\n\treturn\n}\n\nfunc ParseURL(rawurl string) (*URL, error) {\n\turl, err := url.Parse(rawurl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tproject, err := NewProjectFromURL(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &URL{Project: project, URL: *url}, nil\n}\n"
  },
  {
    "path": "github/url_test.go",
    "content": "package github\n\nimport (\n\t\"testing\"\n\n\t\"github.com/github/hub/v2/fixtures\"\n\t\"github.com/github/hub/v2/internal/assert\"\n)\n\nfunc TestParseURL(t *testing.T) {\n\ttestConfigs := fixtures.SetupTestConfigs()\n\tdefer testConfigs.TearDown()\n\n\turl, err :=\n\t\tParseURL(\"https://github.com/jingweno/gh/pulls/21\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"jingweno\", url.Owner)\n\tassert.Equal(t, \"gh\", url.Name)\n\tassert.Equal(t, \"pulls/21\", url.ProjectPath())\n\n\turl, err =\n\t\tParseURL(\"https://github.com/jingweno/gh\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"jingweno\", url.Owner)\n\tassert.Equal(t, \"gh\", url.Name)\n\tassert.Equal(t, \"\", url.ProjectPath())\n\n\turl, err =\n\t\tParseURL(\"https://github.com/jingweno/gh/\")\n\tassert.Equal(t, nil, err)\n\tassert.Equal(t, \"jingweno\", url.Owner)\n\tassert.Equal(t, \"gh\", url.Name)\n\tassert.Equal(t, \"\", url.ProjectPath())\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/github/hub/v2\n\ngo 1.11\n\nrequire (\n\tgithub.com/BurntSushi/toml v0.3.0\n\tgithub.com/atotto/clipboard v0.0.0-20171229224153-bc5958e1c833\n\tgithub.com/google/go-cmp v0.4.0\n\tgithub.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657\n\tgithub.com/mattn/go-colorable v0.0.9\n\tgithub.com/mattn/go-isatty v0.0.3\n\tgithub.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747\n\tgithub.com/russross/blackfriday v0.0.0-20180526075726-670777b536d3\n\tgithub.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect\n\tgolang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b\n\tgolang.org/x/net v0.7.0\n\tgopkg.in/yaml.v2 v2.2.8\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY=\ngithub.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/atotto/clipboard v0.0.0-20171229224153-bc5958e1c833 h1:h/E5ryZTJAtOY6T3K6u/JA1OURt0nk1C4fITywxOp4E=\ngithub.com/atotto/clipboard v0.0.0-20171229224153-bc5958e1c833/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=\ngithub.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657 h1:vE7J1m7cCpiRVEIr1B5ccDxRpbPsWT5JU3if2Di5nE4=\ngithub.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=\ngithub.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=\ngithub.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=\ngithub.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=\ngithub.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 h1:eQox4Rh4ewJF+mqYPxCkmBAirRnPaHEB26UkNuPyjlk=\ngithub.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/russross/blackfriday v0.0.0-20180526075726-670777b536d3 h1:vZXiDtLzqEDYbeAt94qcQZ2H9SGHwbZiOFdsRT5rrng=\ngithub.com/russross/blackfriday v0.0.0-20180526075726-670777b536d3/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=\ngithub.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY=\ngithub.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b h1:Qwe1rC8PSniVfAFPFJeyUkB+zcysC3RgJBAGk7eqBEU=\ngolang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\n"
  },
  {
    "path": "internal/assert/assert.go",
    "content": "// Package assert provides functions for testing.\npackage assert\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n)\n\n// Equal makes the test as failed using default formatting if got is not equal to want.\nfunc Equal(t testing.TB, want, got interface{}, args ...interface{}) {\n\tt.Helper()\n\tif !reflect.DeepEqual(want, got) {\n\t\tmsg := fmt.Sprint(args...)\n\t\tt.Errorf(\"%s\\n%s\", msg, cmp.Diff(want, got))\n\t}\n}\n\n// NotEqual makes the test as failed using default formatting if got is equal to want.\nfunc NotEqual(t testing.TB, want, got interface{}, args ...interface{}) {\n\tt.Helper()\n\tif reflect.DeepEqual(want, got) {\n\t\tmsg := fmt.Sprint(args...)\n\t\tt.Errorf(\"%s\\nUnexpected: <%#v>\", msg, want)\n\t}\n}\n\n// T makes the test as failed using default formatting if ok is false.\nfunc T(t testing.TB, ok bool, args ...interface{}) {\n\tt.Helper()\n\tif !ok {\n\t\tmsg := fmt.Sprint(args...)\n\t\tt.Errorf(\"%s\\nFailure\", msg)\n\t}\n}\n"
  },
  {
    "path": "main.go",
    "content": "//go:build go1.8\n// +build go1.8\n\npackage main\n\nimport (\n\t\"os\"\n\t\"os/exec\"\n\t\"syscall\"\n\n\t\"github.com/github/hub/v2/commands\"\n\t\"github.com/github/hub/v2/github\"\n\t\"github.com/github/hub/v2/ui\"\n)\n\nfunc main() {\n\tdefer github.CaptureCrash()\n\terr := commands.CmdRunner.Execute(os.Args)\n\texitCode := handleError(err)\n\tos.Exit(exitCode)\n}\n\nfunc handleError(err error) int {\n\tif err == nil {\n\t\treturn 0\n\t}\n\n\tswitch e := err.(type) {\n\tcase *exec.ExitError:\n\t\tif status, ok := e.Sys().(syscall.WaitStatus); ok {\n\t\t\treturn status.ExitStatus()\n\t\t}\n\t\treturn 1\n\tcase *commands.ErrHelp:\n\t\tui.Println(err)\n\t\treturn 0\n\tdefault:\n\t\tif errString := err.Error(); errString != \"\" {\n\t\t\tui.Errorln(err)\n\t\t}\n\t\treturn 1\n\t}\n}\n"
  },
  {
    "path": "man-template.html",
    "content": "<!doctype html>\n<title>{{.Name}}({{.Section}}) - {{.Title}}</title>\n<meta charset=\"utf-8\">\n\n<style>\nbody {\n  margin: 0;\n  font: 15px/1.4 -apple-system,Segoe UI,Helvetica,Arial,sans-serif;\n}\npre, code, var, dt, .man-head, [id=\"synopsis\"] + p {\n  font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;\n}\nheader, footer {\n  padding: .5em 2em;\n}\n.man-head {\n  color: #999;\n  padding: 0;\n  width: 100%;\n  float: left;\n  list-style-type: none;\n}\nheader .man-head {\n  text-transform: uppercase;\n}\n.man-head li {\n  width: 33%;\n  float: left;\n}\n.tl { text-align: left }\n.tc { text-align: center }\n.tr { text-align: right; float: right }\narticle {\n  max-width: 110ex;\n  margin: 4em auto 2em;\n}\nh1 {\n  font-size: 1em;\n  font-weight: normal;\n}\nh2 {\n  text-transform: uppercase;\n}\ncode {\n  color: darkslategray;\n  font-weight: bold;\n}\nvar {\n  color: orangered;\n  font-weight: normal;\n  font-style: normal;\n}\ndt {\n  margin: .5em 0;\n}\ndd {\n  margin-bottom: 1em;\n}\npre, [id=\"synopsis\"] + p {\n  background: #eee;\n  padding: 1em 1.5em;\n}\n[id=\"synopsis\"] + p {\n  white-space: nowrap;\n  overflow-x: auto;\n}\npre code {\n  color: inherit;\n  font-weight: inherit;\n}\nvar::before { content: \"<\" }\nvar::after { content: \">\" }\na:link, a:hover, a:visited { color: blue }\n</style>\n\n<header>\n  <ol class=\"man-head\">\n    <li class=\"tl\">{{.Name}}({{.Section}})</li>\n    <li class=\"tc\">{{.Manual}}</li>\n    <li class=\"tr\">{{.Name}}({{.Section}})</li>\n  </ol>\n</header>\n\n<article>\n  <h1>{{.Title}}</h1>\n  {{.Contents}}\n</article>\n\n<footer>\n  <ol class=\"man-head\">\n    <li class=\"tl\">{{.Version}}</li>\n    <li class=\"tc\">{{.Date}}</li>\n    <li class=\"tr\"></li>\n  </ol>\n</footer>\n"
  },
  {
    "path": "md2roff/renderer.go",
    "content": "package md2roff\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/russross/blackfriday\"\n)\n\n// https://github.com/russross/blackfriday/blob/v2/markdown.go\nconst (\n\tParserExtensions = blackfriday.NoIntraEmphasis |\n\t\tblackfriday.FencedCode |\n\t\tblackfriday.SpaceHeadings |\n\t\tblackfriday.AutoHeadingIDs |\n\t\tblackfriday.DefinitionLists\n)\n\nvar (\n\tbackslash     = []byte{'\\\\'}\n\tenterVar      = []byte(\"<var>\")\n\tcloseVar      = []byte(\"</var>\")\n\ttilde         = []byte(`\\(ti`)\n\thtmlEscape    = regexp.MustCompile(`<([A-Za-z][A-Za-z0-9_-]*)>`)\n\troffEscape    = regexp.MustCompile(`[&'\\_-]`)\n\theadingEscape = regexp.MustCompile(`[\"]`)\n\ttitleRe       = regexp.MustCompile(`(?P<name>[A-Za-z][A-Za-z0-9_-]+)\\((?P<num>\\d)\\) -- (?P<title>.+)`)\n)\n\nfunc escape(src []byte, re *regexp.Regexp) []byte {\n\treturn re.ReplaceAllFunc(src, func(c []byte) []byte {\n\t\treturn append(backslash, c...)\n\t})\n}\n\nfunc roffText(src []byte) []byte {\n\treturn bytes.Replace(escape(src, roffEscape), []byte{'~'}, tilde, -1)\n}\n\ntype RoffRenderer struct {\n\tManual  string\n\tVersion string\n\tDate    string\n\tTitle   string\n\tName    string\n\tSection uint8\n\n\tlistWasTerm bool\n}\n\nfunc (r *RoffRenderer) RenderHeader(buf io.Writer, ast *blackfriday.Node) {\n}\n\nfunc (r *RoffRenderer) RenderFooter(buf io.Writer, ast *blackfriday.Node) {\n}\n\nfunc (r *RoffRenderer) RenderNode(buf io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {\n\tif entering {\n\t\tswitch node.Type {\n\t\tcase blackfriday.Emph:\n\t\t\tio.WriteString(buf, `\\fI`)\n\t\tcase blackfriday.Strong:\n\t\t\tio.WriteString(buf, `\\fB`)\n\t\tcase blackfriday.Link:\n\t\t\tio.WriteString(buf, `\\[la]`)\n\t\tcase blackfriday.Code:\n\t\t\tio.WriteString(buf, `\\fB\\fC`)\n\t\tcase blackfriday.Hardbreak:\n\t\t\tio.WriteString(buf, \"\\n.br\\n\")\n\t\tcase blackfriday.Paragraph:\n\t\t\tif node.Parent.Type != blackfriday.Item {\n\t\t\t\tio.WriteString(buf, \".P\\n\")\n\t\t\t} else if node.Parent.FirstChild != node {\n\t\t\t\tio.WriteString(buf, \".sp\\n\")\n\t\t\t\tif node.Prev.Type == blackfriday.List {\n\t\t\t\t\tio.WriteString(buf, \".PP\\n\")\n\t\t\t\t}\n\t\t\t}\n\t\tcase blackfriday.CodeBlock:\n\t\t\tio.WriteString(buf, \".PP\\n.RS 4\\n.nf\\n\")\n\t\tcase blackfriday.Item:\n\t\t\tif node.ListFlags&blackfriday.ListTypeDefinition == 0 {\n\t\t\t\tif node.Parent.ListData.Tight && node.Parent.FirstChild != node {\n\t\t\t\t\tio.WriteString(buf, \".sp -1\\n\")\n\t\t\t\t}\n\t\t\t\tif node.Parent.ListData.Tight {\n\t\t\t\t\tio.WriteString(buf, \".IP \\\\(bu 2.3\\n\")\n\t\t\t\t} else {\n\t\t\t\t\tio.WriteString(buf, \".IP \\\\(bu 4\\n\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif node.ListFlags&blackfriday.ListTypeTerm != 0 {\n\t\t\t\t\tio.WriteString(buf, \".PP\\n\")\n\t\t\t\t} else {\n\t\t\t\t\tio.WriteString(buf, \".RS 4\\n\")\n\t\t\t\t}\n\t\t\t}\n\t\tcase blackfriday.Heading:\n\t\t\tr.renderHeading(buf, node)\n\t\t\treturn blackfriday.SkipChildren\n\t\t}\n\t}\n\n\tleaf := len(node.Literal) > 0\n\tif leaf {\n\t\tif bytes.Equal(node.Literal, enterVar) {\n\t\t\tio.WriteString(buf, `\\fI`)\n\t\t} else if bytes.Equal(node.Literal, closeVar) {\n\t\t\tio.WriteString(buf, `\\fP`)\n\t\t} else {\n\t\t\tbuf.Write(roffText(node.Literal))\n\t\t}\n\t}\n\n\tif !entering || leaf {\n\t\tswitch node.Type {\n\t\tcase blackfriday.Emph,\n\t\t\tblackfriday.Strong:\n\t\t\tio.WriteString(buf, `\\fP`)\n\t\tcase blackfriday.Link:\n\t\t\tio.WriteString(buf, `\\[ra]`)\n\t\tcase blackfriday.Code:\n\t\t\tio.WriteString(buf, `\\fR`)\n\t\tcase blackfriday.CodeBlock:\n\t\t\tio.WriteString(buf, \".fi\\n.RE\\n\")\n\t\tcase blackfriday.HTMLSpan,\n\t\t\tblackfriday.Del,\n\t\t\tblackfriday.Image:\n\t\tcase blackfriday.List:\n\t\t\tio.WriteString(buf, \".br\\n\")\n\t\tcase blackfriday.Item:\n\t\t\tif node.ListFlags&blackfriday.ListTypeDefinition != 0 &&\n\t\t\t\tnode.ListFlags&blackfriday.ListTypeTerm == 0 {\n\t\t\t\tio.WriteString(buf, \".RE\\n\")\n\t\t\t}\n\t\tdefault:\n\t\t\tif !leaf {\n\t\t\t\tio.WriteString(buf, \"\\n\")\n\t\t\t}\n\t\t}\n\t}\n\n\treturn blackfriday.GoToNext\n}\n\nfunc textContent(node *blackfriday.Node) []byte {\n\tvar buf bytes.Buffer\n\tnode.Walk(func(n *blackfriday.Node, entering bool) blackfriday.WalkStatus {\n\t\tif entering && len(n.Literal) > 0 {\n\t\t\tbuf.Write(n.Literal)\n\t\t}\n\t\treturn blackfriday.GoToNext\n\t})\n\treturn buf.Bytes()\n}\n\nfunc (r *RoffRenderer) renderHeading(buf io.Writer, node *blackfriday.Node) {\n\ttext := textContent(node)\n\tswitch node.HeadingData.Level {\n\tcase 1:\n\t\tvar name []byte\n\t\tvar num []byte\n\t\tif match := titleRe.FindAllSubmatch(text, 1); match != nil {\n\t\t\tname, num, text = match[0][1], match[0][2], match[0][3]\n\t\t\tr.Name = string(name)\n\t\t\tif sectionNum, err := strconv.Atoi(string(num)); err == nil {\n\t\t\t\tr.Section = uint8(sectionNum)\n\t\t\t}\n\t\t\tr.Title = string(text)\n\t\t}\n\t\tfmt.Fprintf(buf, \".TH \\\"%s\\\" \\\"%s\\\" \\\"%s\\\" \\\"%s\\\" \\\"%s\\\"\\n\",\n\t\t\tescape(name, headingEscape),\n\t\t\tnum,\n\t\t\tescape([]byte(r.Date), headingEscape),\n\t\t\tescape([]byte(r.Version), headingEscape),\n\t\t\tescape([]byte(r.Manual), headingEscape),\n\t\t)\n\t\tio.WriteString(buf, \".nh\\n\")   // disable hyphenation\n\t\tio.WriteString(buf, \".ad l\\n\") // disable justification\n\t\tio.WriteString(buf, \".SH \\\"NAME\\\"\\n\")\n\t\tfmt.Fprintf(buf, \"%s \\\\- %s\\n\",\n\t\t\troffText(name),\n\t\t\troffText(text),\n\t\t)\n\tcase 2:\n\t\tfmt.Fprintf(buf, \".SH \\\"%s\\\"\\n\", strings.ToUpper(string(escape(text, headingEscape))))\n\tcase 3:\n\t\tfmt.Fprintf(buf, \".SS \\\"%s\\\"\\n\", escape(text, headingEscape))\n\t}\n}\n\nfunc sanitizeInput(src []byte) []byte {\n\treturn htmlEscape.ReplaceAllFunc(src, func(match []byte) []byte {\n\t\tres := append(enterVar, match[1:len(match)-1]...)\n\t\treturn append(res, closeVar...)\n\t})\n}\n\ntype renderOption struct {\n\trenderer blackfriday.Renderer\n\tbuffer   io.Writer\n}\n\nfunc Opt(buffer io.Writer, renderer blackfriday.Renderer) *renderOption {\n\treturn &renderOption{renderer, buffer}\n}\n\nfunc Generate(src []byte, opts ...*renderOption) {\n\tparser := blackfriday.New(blackfriday.WithExtensions(ParserExtensions))\n\tast := parser.Parse(sanitizeInput(src))\n\n\tfor _, opt := range opts {\n\t\topt.renderer.RenderHeader(opt.buffer, ast)\n\t\tast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {\n\t\t\treturn opt.renderer.RenderNode(opt.buffer, node, entering)\n\t\t})\n\t\topt.renderer.RenderFooter(opt.buffer, ast)\n\t}\n}\n"
  },
  {
    "path": "md2roff-bin/cmd.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path\"\n\t\"regexp\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/github/hub/v2/md2roff\"\n\t\"github.com/github/hub/v2/utils\"\n\t\"github.com/russross/blackfriday\"\n)\n\nvar (\n\tflagManual,\n\tflagVersion,\n\tflagTemplate,\n\tflagDate string\n\n\txRefRe = regexp.MustCompile(`\\b(?P<name>[a-z][\\w-]*)\\((?P<section>\\d)\\)`)\n\n\tpageIndex map[string]bool\n)\n\nfunc init() {\n\tpageIndex = make(map[string]bool)\n}\n\ntype templateData struct {\n\tContents string\n\tManual   string\n\tDate     string\n\tVersion  string\n\tTitle    string\n\tName     string\n\tSection  uint8\n}\n\nfunc generateFromFile(mdFile string) error {\n\tcontent, err := ioutil.ReadFile(mdFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%s (%q)\", err, mdFile)\n\t}\n\n\troffFile := strings.TrimSuffix(mdFile, \".md\")\n\troffBuf, err := os.OpenFile(roffFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%s (%q)\", err, roffFile)\n\t}\n\tdefer roffBuf.Close()\n\n\thtmlFile := strings.TrimSuffix(mdFile, \".md\") + \".html\"\n\thtmlBuf, err := os.OpenFile(htmlFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%s (%q)\", err, htmlFile)\n\t}\n\tdefer htmlBuf.Close()\n\n\thtml := blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{\n\t\tFlags: blackfriday.HTMLFlagsNone,\n\t})\n\troff := &md2roff.RoffRenderer{\n\t\tManual:  flagManual,\n\t\tVersion: flagVersion,\n\t\tDate:    flagDate,\n\t}\n\n\thtmlGenBuf := &bytes.Buffer{}\n\tvar htmlWriteBuf io.Writer = htmlBuf\n\tif flagTemplate != \"\" {\n\t\thtmlWriteBuf = htmlGenBuf\n\t}\n\n\tmd2roff.Generate(content,\n\t\tmd2roff.Opt(roffBuf, roff),\n\t\tmd2roff.Opt(htmlWriteBuf, html),\n\t)\n\n\tif flagTemplate != \"\" {\n\t\thtmlGenBytes, err := ioutil.ReadAll(htmlGenBuf)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%s [%s]\", err, \"htmlGenBuf\")\n\t\t}\n\t\tcontent := \"\"\n\t\tif contentLines := strings.Split(string(htmlGenBytes), \"\\n\"); len(contentLines) > 1 {\n\t\t\tcontent = strings.Join(contentLines[1:], \"\\n\")\n\t\t}\n\n\t\tcurrentPage := fmt.Sprintf(\"%s(%d)\", roff.Name, roff.Section)\n\t\tcontent = xRefRe.ReplaceAllStringFunc(content, func(match string) string {\n\t\t\tif match == currentPage {\n\t\t\t\treturn match\n\t\t\t}\n\t\t\tmatches := xRefRe.FindAllStringSubmatch(match, 1)\n\t\t\tfileName := fmt.Sprintf(\"%s.%s\", matches[0][1], matches[0][2])\n\t\t\tif pageIndex[fileName] {\n\t\t\t\treturn fmt.Sprintf(`<a href=\"./%s.html\">%s</a>`, fileName, match)\n\t\t\t}\n\t\t\treturn match\n\t\t})\n\n\t\ttmplData := templateData{\n\t\t\tManual:   flagManual,\n\t\t\tDate:     flagDate,\n\t\t\tContents: content,\n\t\t\tTitle:    roff.Title,\n\t\t\tSection:  roff.Section,\n\t\t\tName:     roff.Name,\n\t\t\tVersion:  flagVersion,\n\t\t}\n\n\t\ttemplateFile, err := ioutil.ReadFile(flagTemplate)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%s (%q)\", err, flagTemplate)\n\t\t}\n\t\ttmpl, err := template.New(\"test\").Parse(string(templateFile))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = tmpl.Execute(htmlBuf, tmplData)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc main() {\n\tp := utils.NewArgsParserWithUsage(`\n\t\t--manual NAME\n\t\t--version STR\n\t\t--template FILE\n\t\t--date DATE\n\t`)\n\tfiles, err := p.Parse(os.Args[1:])\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tflagManual = p.Value(\"--manual\")\n\tflagVersion = p.Value(\"--version\")\n\tflagTemplate = p.Value(\"--template\")\n\tflagDate = p.Value(\"--date\")\n\n\tfor _, infile := range files {\n\t\tname := path.Base(infile)\n\t\tname = strings.TrimSuffix(name, \".md\")\n\t\tpageIndex[name] = true\n\t}\n\n\tfor _, infile := range files {\n\t\terr := generateFromFile(infile)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "script/bootstrap",
    "content": "#!/usr/bin/env bash\nset -e\n\nSTATUS=0\n\n{ ruby --version\n  bundle install\n  bundle binstub cucumber --path bin\n} || {\n  echo \"You need Ruby 1.9 or higher and Bundler to run hub tests\" >&2\n  STATUS=1\n}\n\nif [ $STATUS -eq 0 ]; then\n  echo \"Everything OK.\"\nfi\n\nexit $STATUS\n"
  },
  {
    "path": "script/build",
    "content": "#!/usr/bin/env bash\n# Usage: script/build [-o <BIN>]\n#        script/build files\n\nset -e\n\nwindows=\n[[ $OS == Windows* ]] && windows=1\n\nfind_source_files() {\n  find . -maxdepth 2 -name '*.go' '!' -name '*_test.go' \"$@\"\n}\n\nbuild_hub() {\n  mkdir -p \"$(dirname \"$1\")\"\n  go build \\\n\t  -ldflags \"-X github.com/github/hub/v2/version.Version=`./script/version` $LDFLAGS\" \\\n\t  -gcflags \"$GCFLAGS\" \\\n\t  -asmflags \"$ASMFLAGS\" \\\n\t  -o \"$1\"\n}\n\n[ $# -gt 0 ] || set -- -o \"bin/hub${windows:+.exe}\"\n\ncase \"$1\" in\n-o )\n  build_hub \"${2?}\"\n  ;;\nfiles )\n  find_source_files\n  ;;\n-h | --help )\n  sed -ne '/^#/!q;s/.\\{1,2\\}//;1d;p' < \"$0\"\n  exit\n  ;;\n* )\n  \"$0\" --help >&2\n  exit 1\nesac\n"
  },
  {
    "path": "script/build.bat",
    "content": "@echo off\r\n\r\nbash script\\build %*\r\n"
  },
  {
    "path": "script/changelog",
    "content": "#!/bin/bash\n# vi:ft=sh:\n# Usage: script/changelog [HEAD]\n#\n# Show changes to runtime files between HEAD and previous release tag.\nset -e\n\ncurrent_tag=\"${GITHUB_REF#refs/tags/}\"\nstart_ref=\"HEAD\"\n\n# Find the previous release on the same branch, skipping prereleases if the\n# current tag is a full release\nprevious_tag=\"\"\nwhile [[ -z $previous_tag || ( $previous_tag == *-* && $current_tag != *-* ) ]]; do\n  previous_tag=\"$(git describe --tags \"$start_ref\"^ --abbrev=0)\"\n  start_ref=\"$previous_tag\"\ndone\n\ngit log --first-parent --format='%C(auto,green)* %s%C(auto,reset)%n%w(0,2,2)%+b' \\\n  --reverse \"${previous_tag}..\" -- `script/build files` etc share\n"
  },
  {
    "path": "script/coverage",
    "content": "#!/bin/bash\nset -e\n\nsource_files() {\n  script/build files | grep -vE '^\\./(coverage|fixtures|version)/'\n}\n\nprepare() {\n  local changed_files=\"$(source_files | xargs git diff --name-only --)\"\n  if [ -n \"$changed_files\" ]; then\n    echo \"Aborted: please commit the following files before continuing\" >&2\n    cat <<<\"$changed_files\" >&2\n    exit 1\n  fi\n\n  local n=0\n  for f in $(source_files); do\n    go tool cover -mode=set -var=\"LiveCoverage$((++n))\" \"$f\" > \"$f\"~\n    sed -E '\n      /^package /a\\\n      import \"github.com/github/hub/v2/coverage\"\n      s/(LiveCoverage[0-9]+)\\.Count\\[([0-9]+)\\][^;]+/coverage.Record(\\1, \\2)/g\n    ' < \"$f\"~ > \"$f\"\n    rm \"$f\"~\n  done\n\n  rm -rf \"$HUB_COVERAGE\"\n  mkdir -p \"${HUB_COVERAGE%/*}\"\n}\n\ngenerate() {\n  source_files | xargs git checkout --\n\n  echo 'mode: count' > \"$HUB_COVERAGE\"~\n  sed -E 's!^.+/(github.com/github/hub/v2/)!\\1!' \"$HUB_COVERAGE\" | awk '\n    { a[substr($0, 0, length()-2)] += $(NF) }\n    END { for (k in a) print k, a[k] }\n  ' >> \"$HUB_COVERAGE\"~\n\n  go tool cover -func=\"$HUB_COVERAGE\"~ > \"${HUB_COVERAGE%.out}.func\"\n  if [ -z \"$CI\" ]; then\n    go tool cover -html=\"$HUB_COVERAGE\"~ -o \"${HUB_COVERAGE%.out}.html\"\n  fi\n\n  awk '/^total:/ { print $(NF) }' \"${HUB_COVERAGE%.out}.func\"\n}\n\nsummarize() {\n  local total_coverage\n  local min_coverage=\"${1?}\"\n  total_coverage=\"$(generate)\"\n  echo \"Code coverage: $total_coverage\"\n  local result=\"$(bc <<<\"${total_coverage%\\%} < $min_coverage\")\"\n  if [ \"$result\" -eq 1 ]; then\n    echo \"Error: coverage dropped below the minimum threshold of ${min_coverage}%!\"\n    if [ -n \"$CI\" ]; then\n      html_result=\"${HUB_COVERAGE%.out}.html\"\n      html_result=\"${html_result#$PWD/}\"\n      printf 'Please run `script/test --coverage` locally and open `%s` to analyze the results.\\n' \"$html_result\"\n    fi\n    return 1\n  fi\n}\n\ncmd=\"${1?}\"\nshift 1\n\ncase \"$cmd\" in\n  prepare | generate | summarize )\n    \"$cmd\" \"$@\"\n    ;;\n  * )\n    exit 1\n    ;;\nesac\n"
  },
  {
    "path": "script/cross-compile",
    "content": "#!/usr/bin/env bash\n# Usage: script/cross-compile <version>\n#\n# Packages the project over a matrix of supported OS and architectures and\n# prints the asset filenames and labels suitable for upload.\nset -e\n\nversion=\"${1?}\"\n\necho '\n  darwin   amd64    macOS\n  freebsd  386      FreeBSD 32-bit\n  freebsd  amd64    FreeBSD 64-bit\n  linux    386      Linux 32-bit\n  linux    amd64    Linux 64-bit\n  linux    arm      Linux ARM 32-bit\n  linux    arm64    Linux ARM 64-bit\n  windows  386      Windows 32-bit\n  windows  amd64    Windows 64-bit\n' | {\n  while read os arch label; do\n    [ -n \"$os\" ] || continue\n\n    label=\"hub ${version} for ${label}\"\n    if ! file=\"$(script/package \"$os\" \"$arch\" \"$version\")\"; then\n      echo \"packaging $label failed\" >&2\n      continue\n    fi\n\n    printf \"%s\\t%s\\n\" \"$file\" \"$label\"\n  done\n}\n"
  },
  {
    "path": "script/docker",
    "content": "#!/bin/bash\n# Usage: script/docker [<cucumber-args>]\nset -e\n\ncontainer=hub-test\nworkdir=/home/app/workdir\n\ndocker build -t \"$container\" .\n\ndocker run -it --rm -v \"$PWD\":\"$workdir\" -w \"$workdir\" \"$container\" \\\n  /bin/bash -c \"\n# Enables running WEBrick server (see local_server.rb)\n# https://stackoverflow.com/a/45899937/11687\ncp /etc/hosts /tmp/hosts.new \\\n\t&& sed -i 's/::1\\\\tlocalhost/::1/' /tmp/hosts.new \\\n\t&& sudo cp -f /tmp/hosts.new /etc/hosts || exit 1\n\ngo test ./...\nbundle exec cucumber $@\n\""
  },
  {
    "path": "script/get",
    "content": "#!/bin/bash\n# Usage: curl -fsSL https://github.com/github/hub/raw/master/script/get | bash -s <HUB_VERSION>\n#\n# Downloads the hub binary into `bin/hub` within the current directory.\n\nset -e\n\nlatest-version() {\n  curl -fsi https://github.com/github/hub/releases/latest | awk -F/ 'tolower($1) ~ /^location:/ {print $(NF)}'\n}\n\nHUB_VERSION=\"${1#v}\"\nif [ -z \"$HUB_VERSION\" ]; then\n  latest=$(latest-version) || true\n  [ -n \"$latest\" ] || latest=\"v2.14.1\"\n  cat <<MSG >&2\nError: You must specify a version of hub via the first argument. Example:\n  curl -L <script> | bash -s ${latest#v}\nMSG\n  exit 1\nfi\n\nARCH=\"amd64\"\nOS=\"$(uname -s | tr '[:upper:]' '[:lower:]')\"\ncase \"$OS\" in\nmingw* | msys* ) OS=windows ;;\nesac\n\ndownload() {\n  case \"$OS\" in\n  windows )\n    zip=\"${1%.tgz}.zip\"\n    curl -fsSLO \"$zip\"\n    unzip \"$(basename \"$zip\")\" bin/hub.exe\n    rm -f \"$(basename \"$zip\")\"\n    ;;\n  darwin )\n    curl -fsSL \"$1\" | tar xz --strip-components=1 '*/bin/hub'\n    ;;\n  * )\n    curl -fsSL \"$1\" | tar xz --strip-components=1 --wildcards '*/bin/hub'\n    ;;\n  esac\n}\n\ndownload \"https://github.com/github/hub/releases/download/v$HUB_VERSION/hub-$OS-$ARCH-$HUB_VERSION.tgz\"\n\nbin/hub version\nif [ -z \"$GITHUB_TOKEN\" ]; then\n  cat <<MSG >&2\nWarning: We recommend supplying the GITHUB_TOKEN environment variable to avoid\nbeing prompted for authentication.\nMSG\nfi\n"
  },
  {
    "path": "script/github-release",
    "content": "#!/bin/bash\n# Usage: script/cross-compile | script/github-release <tag>\n#\n# Takes in a list of asset filenames + labels via stdin and uploads them to the\n# corresponding release on GitHub. The release is created as a draft first if\n# missing and its body is the git changelog since the previous tagged release.\nset -e\n\ntag_name=\"${1?}\"\n[[ $tag_name == *-* ]] && pre=1 || pre=\n\nassets=()\nwhile read -r filename label; do\n  assets+=( -a \"${filename}#${label}\" )\ndone\n\nif hub release --include-drafts | grep -q \"^${tag_name}\\$\"; then\n  hub release edit \"$tag_name\" -m \"\" \"${assets[@]}\"\nelse\n  git tag --list \"$tag_name\" --format='%(contents:subject)%0a%0a%(contents:body)' | \\\n    hub release create ${pre:+--prerelease} -F- \"$tag_name\" \"${assets[@]}\"\nfi\n"
  },
  {
    "path": "script/install.bat",
    "content": "@echo off\r\nCLS\r\ngoto checkPrivileges\r\n\r\n:: Unfortunately, Windows doesn't have a decent built-in way to append a string to the $PATH.\r\n:: setx is convenient, but it 1) truncates paths longer than 1024 characters, and\r\n:: 2) mucks up the user path with the machine-wide path.\r\n:: This function takes care of these problems by calling Environment.Get/SetEnvironmentVariable\r\n:: via PowerShell, which lacks these issues.\r\n:appendToUserPath\r\nsetlocal EnableDelayedExpansion\r\nset \"RUNPS=powershell -NoProfile -ExecutionPolicy Bypass -Command\" :: Command to start PowerShell.\r\nset \"OLDPATHPS=[Environment]::GetEnvironmentVariable('PATH', 'User')\" :: PowerShell command to run to get the old $PATH for the current user.\r\n\r\n:: Capture the output of %RUNPS% \"%OLDPATHPS%\" and set it to OLDPATH\r\nfor /f \"delims=\" %%i in ('%RUNPS% \"%OLDPATHPS%\"') do (\r\n    set \"OLDPATH=!OLDPATH!%%i\"\r\n)\r\n\r\nset \"NEWPATH=%OLDPATH%;%1\"\r\n:: Set the new $PATH\r\n%RUNPS% \"[Environment]::SetEnvironmentVariable('PATH', '%NEWPATH%', 'User')\"\r\ngoto :eof\r\n\r\n:checkPrivileges\r\nNET FILE 1>NUL 2>NUL\r\nif '%errorlevel%' == '0' ( goto gotPrivileges ) else ( goto getPrivileges )\r\n\r\n:getPrivileges\r\nif '%1'=='ELEV' (shift & goto gotPrivileges)\r\necho.\r\necho **************************************\r\necho Installing GitHub CLI as Administrator\r\necho **************************************\r\n\r\nsetlocal DisableDelayedExpansion\r\nset \"batchPath=%~0\"\r\nsetlocal EnableDelayedExpansion\r\necho Set UAC = CreateObject^(\"Shell.Application\"^) > \"%temp%\\OEgetPrivileges.vbs\"\r\necho UAC.ShellExecute \"!batchPath!\", \"ELEV\", \"\", \"runas\", 1 >> \"%temp%\\OEgetPrivileges.vbs\"\r\n\"%temp%\\OEgetPrivileges.vbs\"\r\nexit /B\r\n\r\n:gotPrivileges\r\n\r\nsetlocal & cd /d %~dp0\r\n\r\nset HUB_BIN_PATH=\"%LOCALAPPDATA%\\GitHubCLI\\bin\"\r\nIF EXIST %HUB_BIN_PATH% GOTO DIRECTORY_EXISTS\r\nmkdir %HUB_BIN_PATH%\r\nset \"path=%PATH%;%HUB_BIN_PATH:\"=%\"\r\ncall :appendToUserPath \"%HUB_BIN_PATH:\"=%\"\r\n:DIRECTORY_EXISTS\r\n\r\n:: Delete any existing programs\r\n2>NUL del /q %HUB_BIN_PATH%\\hub*\r\n\r\n1>NUL copy .\\bin\\hub.exe %HUB_BIN_PATH%\\hub.exe\r\n\r\necho hub.exe installed successfully. Press any key to exit\r\npause > NUL\r\n"
  },
  {
    "path": "script/install.sh",
    "content": "#!/usr/bin/env bash\n# Usage: [sudo] [prefix=/usr/local] ./install\nset -e\n\ncase \"$1\" in\n'-h' | '--help' )\n  sed -ne '/^#/!q;s/.\\{1,2\\}//;1d;p' < \"$0\"\n  exit 0\n  ;;\nesac\n\nif [[ $BASH_SOURCE == */* ]]; then\n  cd \"${BASH_SOURCE%/*}\"\nfi\n\nprefix=\"${PREFIX:-$prefix}\"\nprefix=\"${prefix:-/usr/local}\"\n\nfor src in bin/hub share/man/*/*.1 share/doc/*/*.html share/vim/vimfiles/*/*.vim; do\n  dest=\"${DESTDIR}${prefix}/${src}\"\n  mkdir -p \"${dest%/*}\"\n  [[ $src == share/* ]] && mode=\"644\" || mode=755\n  install -m \"$mode\" \"$src\" \"$dest\"\ndone\n"
  },
  {
    "path": "script/package",
    "content": "#!/usr/bin/env bash\n# Usage: script/package <os> <arch> <version>\n#\n# Packages the project as a release asset and prints the archive's filename.\nset -e\n\nos=\"${1?}\"\narch=\"${2?}\"\nversion=\"${3?}\"\n\nrelease=\"hub-${os}-${arch}-${version}\"\n\ncase \"$os\" in\n  darwin | freebsd | linux | netbsd | openbsd | solaris | windows ) ;;\n  * ) echo \"unsupported OS: $os\" >&2; exit 1 ;;\nesac\n\ncase \"$arch\" in\n  386 | amd64 | arm | arm64 ) ;;\n  * ) echo \"unsupported arch: $arch\" >&2; exit 1 ;;\nesac\n\nexport GOOS=\"$os\"\nexport GOARCH=\"$arch\"\n\ntmpdir=\"tmp/${release}\"\nrm -rf \"$tmpdir\"\n\nexename=\"${tmpdir}/bin/hub\"\n[ \"$os\" != \"windows\" ] || exename=\"${exename}.exe\"\nmkdir -p \"${exename%/*}\"\nscript/build -o \"$exename\"\n\ncrlf() {\n  sed $'s/$/\\r/' \"$1\" > \"$2\"\n}\n\nif [ \"$os\" = \"windows\" ]; then\n  crlf README.md \"${tmpdir}/README.txt\"\n  crlf LICENSE \"${tmpdir}/LICENSE.txt\"\n  for man in share/doc/*/*.html; do\n    mkdir -p \"${tmpdir}/${man%/*}\"\n    cp \"$man\" \"${tmpdir}/${man}\"\n  done\n  crlf script/install.bat \"${tmpdir}/install.bat\"\nelse\n  cp -R README.md LICENSE etc share \"$tmpdir\"\n  rm -rf \"${tmpdir}/share/man/\"*/*.md\n  cp script/install.sh \"${tmpdir}/install\"\n  chmod +x \"${tmpdir}/install\"\nfi\n\nif [ \"$os\" = \"windows\" ]; then\n  file=\"${PWD}/${tmpdir}.zip\"\n  rm -f \"$file\"\n  pushd \"$tmpdir\" >/dev/null\n  zip -r \"$file\" * >/dev/null\nelse\n  file=\"${PWD}/${tmpdir}.tgz\"\n  rm -f \"$file\"\n  pushd \"${tmpdir%/*}\" >/dev/null\n  tar -czf \"$file\" \"$release\"\nfi\n\necho \"$file\"\n"
  },
  {
    "path": "script/publish-release",
    "content": "#!/usr/bin/env bash\nset -e\n\npublish_documentation() {\n  local version=\"$1\"\n  local doc_dir=\"site\"\n  local doc_branch=\"gh-pages\"\n\n  git fetch origin \"${doc_branch}:${doc_branch}\"\n  git worktree add \"$doc_dir\" \"$doc_branch\"\n  pushd \"$doc_dir\"\n\n  git rm hub*.html >/dev/null\n  cp ../share/doc/*/*.html .\n\n  git add hub*.html\n  GIT_COMMITTER_NAME='GitHub Actions' GIT_COMMITTER_EMAIL='noreply@github.com' \\\n    GIT_AUTHOR_NAME='GitHub Actions' GIT_AUTHOR_EMAIL='noreply@github.com' \\\n    git commit -m \"Update documentation for $version\"\n\n  git push origin HEAD\n  popd\n}\n\nin_default_branch() {\n  git fetch origin master --depth 10\n  git merge-base --is-ancestor \"$1\" FETCH_HEAD\n}\n\ntag_name=\"${GITHUB_REF#refs/tags/}\"\nmake man-pages\nscript/cross-compile \"${tag_name#v}\" | \\\n  PATH=\"bin:$PATH\" script/github-release \"$tag_name\"\n\nif [[ $tag_name != *-* ]] && in_default_branch \"$tag_name\"; then\n  publish_documentation \"$tag_name\"\nfi\n"
  },
  {
    "path": "script/ruby-test",
    "content": "#!/usr/bin/env bash\nset -e\n\nif [ -z \"$GITHUB_ACTIONS\" ] && tmux -V; then\n  if [ -n \"$CI\" ]; then\n    git --version\n    bash --version | head -1\n    zsh --version\n    echo\n  fi\n  profile=\"all\"\nelse\n  echo \"warning: skipping shell completion tests (install tmux to enable)\" >&2\n  profile=\"default\"\nfi\n\nbin/cucumber -p \"$profile\" \"$@\"\n"
  },
  {
    "path": "script/tag-release",
    "content": "#!/bin/bash\nset -e\n\nversion_file=\"version/version.go\"\n\nif git diff --exit-code >/dev/null -- \"$version_file\"; then\n  echo \"Update the version in $version_file and try again.\" >&2\n  exit 1\nfi\n\nversion=\"$(grep -w 'Version =' \"$version_file\" | cut -d'\"' -f2)\"\n\ngit commit -m \"hub $version\" -- \"$version_file\"\n\nnotes_file=\"$(mktemp)\"\n{ echo \"hub $version\"\n  echo\n  GITHUB_REF=\"refs/tags/v$version\" script/changelog\n} >\"$notes_file\"\ntrap \"rm -f '$notes_file'\" EXIT\n\ngit tag \"v${version}\" -F \"$notes_file\" --edit\n\ngit push origin HEAD \"v${version}\"\n"
  },
  {
    "path": "script/test",
    "content": "#!/usr/bin/env bash\n# Usage: script/test [--coverage [<MIN>]] [<FEATURES>...]\n#\n# Run Go and Cucumber test suites for hub.\n\nset -e\n\nwhile [ $# -gt 0 ]; do\n  case \"$1\" in\n  --coverage )\n    export HUB_COVERAGE=\"$PWD/tmp/cover.out\"\n    if [ \"${2%.*}\" -gt 0 ] 2>/dev/null; then\n      min_coverage=\"$2\"\n      shift 2\n    else\n      min_coverage=1\n      shift 1\n    fi\n    ;;\n  -h | --help )\n    sed -ne '/^#/!q;s/.\\{1,2\\}//;1d;p' < \"$0\"\n    exit\n    ;;\n  * )\n    break\n    ;;\n  esac\ndone\n\nSTATUS=0\n\ntrap \"exit 1\" INT\n\ncheck_formatting() {\n  make fmt >/dev/null\n  if ! git diff -U1 --exit-code; then\n    echo\n    echo \"Some go code was not formatted properly.\" >&2\n    echo \"Run \\`make fmt' locally to fix these errors.\" >&2\n    return 1\n  fi\n}\n\ninstall_test() {\n  mkdir -p share/doc/hub-doc\n  touch share/man/man1/hub.1 share/doc/hub-doc/hub.1.html\n  DESTDIR=\"$PWD/tmp/destdir\" prefix=/my/prefix bash < script/install.sh\n  test -x tmp/destdir/my/prefix/bin/hub\n  test -e tmp/destdir/my/prefix/share/man/man1/hub.1\n  test ! -x tmp/destdir/my/prefix/share/man/man1/hub.1\n  test -e tmp/destdir/my/prefix/share/doc/hub-doc/hub.1.html\n  test ! -x tmp/destdir/my/prefix/share/doc/hub-doc/hub.1.html\n  rm share/man/man1/hub.1 share/doc/hub-doc/hub.1.html\n}\n\n[ -z \"$HUB_COVERAGE\" ] || script/coverage prepare\nscript/build\ngo test ./... || STATUS=\"$?\"\nscript/ruby-test \"$@\" || STATUS=\"$?\"\n[ -z \"$HUB_COVERAGE\" ] || script/coverage summarize \"$min_coverage\" || STATUS=\"$?\"\n\nif [ -n \"$CI\" ]; then\n  check_formatting || STATUS=\"$?\"\n  install_test || STATUS=\"$?\"\nfi\n\nexit \"$STATUS\"\n"
  },
  {
    "path": "script/version",
    "content": "#!/usr/bin/env bash\n# Displays hub's release version\nset -e\n\nif [ -n \"$GITHUB_REF\" ]; then\n  echo \"${GITHUB_REF#refs/tags/v}\"\n  exit\nfi\n\nexport GIT_CEILING_DIRECTORIES=${PWD%/*}\nversion=\"$(git describe --tags HEAD 2>/dev/null || true)\"\n\nif [ -z \"$version\" ]; then\n  version=\"$(grep 'Version =' version/version.go | head -1 | cut -d '\"' -f2)\"\n  sha=\"$(git rev-parse --short HEAD 2>/dev/null || true)\"\n  [ -z \"$sha\" ] || version=\"${version}-g${sha}\"\nfi\n\necho \"${version#v}\"\n"
  },
  {
    "path": "script/version.bat",
    "content": "@echo off\r\n\r\nbash script\\version %*\r\n"
  },
  {
    "path": "share/vim/vimfiles/ftdetect/pullrequest.vim",
    "content": "autocmd BufNewFile,BufRead PULLREQ_EDITMSG set filetype=pullrequest\n"
  },
  {
    "path": "share/vim/vimfiles/syntax/pullrequest.vim",
    "content": "\" Vim syntax file\n\" Language: Hub Pull Request\n\" Maintainer: Derek Sifford <dereksifford@gmail.com>\n\" Filenames: *.git/PULLREQ_EDITMSG\n\" Latest Revision: 2018 Oct 30\n\nif exists('b:current_syntax')\n    finish\nendif\n\nsyn case match\n\nsyn include @Markdown syntax/markdown.vim\n\nsyn match pullreqBlank         contained                  \"^.*\"        contains=@Spell\nsyn match pullreqOverflow      contained                  \".*\"         contains=@Spell\nsyn match pullreqSummary       contained                  \"^.\\{0,50\\}\" contains=@Spell nextgroup=pullreqOverflow\nsyn match pullreqMetaHeader    contained                  \"^Changes:\"\nsyn match pullreqSha           contained                  \"^[a-z0-9]\\{7\\}\\ze (\"        nextgroup=pullreqCommitMeta\nsyn match pullreqCommitMeta    contained skipnl skipwhite \".*\"                         nextgroup=pullreqCommitMessage\nsyn match pullreqCommitMessage contained                  \"^\\s*\\zs.*\"\nsyn match pullreqBranchInfo    contained                  \"\\S\\+:\\S\\+\"\n\nsyn region pullreqBranchInfoLine contained transparent start=\"^Requesting a pull\" end=\"$\"             contains=pullreqBranchInfo\nsyn region pullreqMessage        keepend               start=\"^.\"                 end=\"^\\ze# [-]* >8\" contains=@Markdown,@Spell                                   nextgroup=pullreqMetadata\nsyn region pullreqMetadata       fold                  start=\"^# [-]* >8 [-]*$\"   end=\"\\%$\"           contains=pullreqMetaHeader,pullreqSha,pullreqBranchInfoLine\nsyn match  pullreqFirstLine      skipnl                \"\\%^[^#].*\"                                    contains=pullreqSummary                                     nextgroup=pullreqBlank\n\nhi def link pullreqBlank         Error\nhi def link pullreqBranchInfo    Keyword\nhi def link pullreqCommitMessage String\nhi def link pullreqMetaHeader    htmlH1\nhi def link pullreqMetadata      Comment\nhi def link pullreqSha           Constant\nhi def link pullreqSummary       Keyword\n\nlet b:current_syntax = 'pullrequest'\n"
  },
  {
    "path": "ui/format.go",
    "content": "package ui\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Expand expands a format string using `git log` message syntax.\nfunc Expand(format string, values map[string]string, colorize bool) string {\n\tf := &expander{values: values, colorize: colorize}\n\treturn f.Expand(format)\n}\n\n// An expander is a stateful helper to expand a format string.\ntype expander struct {\n\t// formatted holds the parts of the string that have already been formatted.\n\tformatted []string\n\n\t// values is the map of values that should be expanded.\n\tvalues map[string]string\n\n\t// colorize is a flag to indicate whether to use colors.\n\tcolorize bool\n\n\t// skipNext is true if the next placeholder is not a placeholder and can be\n\t// output directly as such.\n\tskipNext bool\n\n\t// padNext is an object that should be used to pad the next placeholder.\n\tpadNext *padder\n}\n\nfunc (f *expander) Expand(format string) string {\n\tparts := strings.Split(format, \"%\")\n\tf.formatted = make([]string, 0, len(parts))\n\tf.append(parts[0])\n\tfor _, p := range parts[1:] {\n\t\tv, t := f.expandOneVar(p)\n\t\tf.append(v, t)\n\t}\n\treturn f.crush()\n}\n\nfunc (f *expander) append(formattedText ...string) {\n\tf.formatted = append(f.formatted, formattedText...)\n}\n\nfunc (f *expander) crush() string {\n\ts := strings.Join(f.formatted, \"\")\n\tf.formatted = nil\n\treturn s\n}\n\nvar colorMap = map[string]string{\n\t\"black\":   \"30\",\n\t\"red\":     \"31\",\n\t\"green\":   \"32\",\n\t\"yellow\":  \"33\",\n\t\"blue\":    \"34\",\n\t\"magenta\": \"35\",\n\t\"cyan\":    \"36\",\n\t\"white\":   \"37\",\n\t\"reset\":   \"\",\n}\n\nfunc (f *expander) expandOneVar(format string) (expand string, untouched string) {\n\tif f.skipNext {\n\t\tf.skipNext = false\n\t\treturn \"\", format\n\t}\n\tif format == \"\" {\n\t\tf.skipNext = true\n\t\treturn \"\", \"%\"\n\t}\n\n\tif f.padNext != nil {\n\t\tp := f.padNext\n\t\tf.padNext = nil\n\t\te, u := f.expandOneVar(format)\n\t\treturn f.pad(e, p), u\n\t}\n\n\tif e, u, ok := f.expandSpecialChar(format[0], format[1:]); ok {\n\t\treturn e, u\n\t}\n\n\tif f.values != nil {\n\t\tfor i := 1; i <= len(format); i++ {\n\t\t\tif v, exists := f.values[format[0:i]]; exists {\n\t\t\t\treturn v, format[i:]\n\t\t\t}\n\t\t}\n\t}\n\n\treturn \"\", \"%\" + format\n}\n\nfunc (f *expander) expandSpecialChar(firstChar byte, format string) (expand string, untouched string, wasExpanded bool) {\n\tswitch firstChar {\n\tcase 'n':\n\t\treturn \"\\n\", format, true\n\tcase 'C':\n\t\tfor k, v := range colorMap {\n\t\t\tif strings.HasPrefix(format, k) {\n\t\t\t\tif f.colorize {\n\t\t\t\t\treturn \"\\033[\" + v + \"m\", format[len(k):], true\n\t\t\t\t}\n\t\t\t\treturn \"\", format[len(k):], true\n\t\t\t}\n\t\t}\n\t\t// TODO: Add custom color as specified in color.branch.* options.\n\t\t// TODO: Handle auto-coloring.\n\tcase 'x':\n\t\tif len(format) >= 2 {\n\t\t\tif v, err := strconv.ParseInt(format[:2], 16, 32); err == nil {\n\t\t\t\treturn fmt.Sprintf(\"%c\", v), format[2:], true\n\t\t\t}\n\t\t}\n\tcase '+':\n\t\te, u := f.expandOneVar(format)\n\t\tif e != \"\" {\n\t\t\treturn \"\\n\" + e, u, true\n\t\t}\n\t\treturn \"\", u, true\n\tcase ' ':\n\t\te, u := f.expandOneVar(format)\n\t\tif e != \"\" {\n\t\t\treturn \" \" + e, u, true\n\t\t}\n\t\treturn \"\", u, true\n\tcase '-':\n\t\te, u := f.expandOneVar(format)\n\t\tif e != \"\" {\n\t\t\treturn e, u, true\n\t\t}\n\t\tf.append(strings.TrimRight(f.crush(), \"\\n\"))\n\t\treturn \"\", u, true\n\tcase '<', '>':\n\t\tif m := paddingPattern.FindStringSubmatch(string(firstChar) + format); len(m) == 7 {\n\t\t\tif p := padderFromConfig(m[1], m[2], m[3], m[4], m[5]); p != nil {\n\t\t\t\tf.padNext = p\n\t\t\t\treturn \"\", m[6], true\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\", \"\", false\n}\n\nfunc (f *expander) pad(s string, p *padder) string {\n\tsize := int(p.size)\n\tif p.sizeAsColumn {\n\t\tprevious := f.crush()\n\t\tf.append(previous)\n\t\tsize -= len(previous) - strings.LastIndex(previous, \"\\n\") - 1\n\t}\n\n\tnumPadding := size - len(s)\n\tif numPadding == 0 {\n\t\treturn s\n\t}\n\n\tif numPadding < 0 {\n\t\tif p.usePreviousSpace {\n\t\t\tprevious := f.crush()\n\t\t\tnoBlanks := strings.TrimRight(previous, \" \")\n\t\t\tf.append(noBlanks)\n\t\t\tnumPadding += len(previous) - len(noBlanks)\n\t\t}\n\n\t\tif numPadding <= 0 {\n\t\t\treturn p.truncate(s, -numPadding)\n\t\t}\n\t}\n\n\tswitch p.orientation {\n\tcase padLeft:\n\t\treturn strings.Repeat(\" \", numPadding) + s\n\tcase padMiddle:\n\t\treturn strings.Repeat(\" \", numPadding/2) + s + strings.Repeat(\" \", (numPadding+1)/2)\n\t}\n\n\t// Pad right by default.\n\treturn s + strings.Repeat(\" \", numPadding)\n}\n\ntype paddingOrientation int\n\nconst (\n\tpadRight paddingOrientation = iota\n\tpadLeft\n\tpadMiddle\n)\n\ntype truncingMethod int\n\nconst (\n\ttruncLeft truncingMethod = iota\n\ttruncRight\n\ttruncMiddle\n)\n\ntype padder struct {\n\torientation      paddingOrientation\n\tsize             int64\n\tsizeAsColumn     bool\n\tusePreviousSpace bool\n\ttruncing         truncingMethod\n}\n\nvar paddingPattern = regexp.MustCompile(`^(>)?([><])(\\|)?\\((\\d+)(,[rm]?trunc)?\\)(.*)$`)\n\nfunc padderFromConfig(alsoLeft, orientation, asColumn, size, trunc string) *padder {\n\tp := &padder{}\n\n\tif orientation == \">\" {\n\t\tp.orientation = padLeft\n\t} else if alsoLeft == \"\" {\n\t\tp.orientation = padRight\n\t} else {\n\t\tp.orientation = padMiddle\n\t}\n\n\tp.sizeAsColumn = asColumn != \"\"\n\n\tvar err error\n\tif p.size, err = strconv.ParseInt(size, 10, 64); err != nil {\n\t\treturn nil\n\t}\n\n\tp.usePreviousSpace = alsoLeft != \"\" && p.orientation == padLeft\n\n\tswitch trunc {\n\tcase \",trunc\":\n\t\tp.truncing = truncLeft\n\tcase \",rtrunc\":\n\t\tp.truncing = truncRight\n\tcase \",mtrunc\":\n\t\tp.truncing = truncMiddle\n\t}\n\n\treturn p\n}\n\nfunc (p *padder) truncate(s string, numReduce int) string {\n\tif numReduce == 0 {\n\t\treturn s\n\t}\n\tnumLeft := len(s) - numReduce - 2\n\tif numLeft < 0 {\n\t\tnumLeft = 0\n\t}\n\n\tswitch p.truncing {\n\tcase truncRight:\n\t\treturn \"..\" + s[len(s)-numLeft:]\n\tcase truncMiddle:\n\t\treturn s[:numLeft/2] + \"..\" + s[len(s)-(numLeft+1)/2:]\n\t}\n\n\t// Trunc left by default.\n\treturn s[:numLeft] + \"..\"\n}\n"
  },
  {
    "path": "ui/format_test.go",
    "content": "package ui\n\nimport (\n\t\"testing\"\n)\n\ntype expanderTest struct {\n\tname     string\n\tformat   string\n\tvalues   map[string]string\n\tcolorize bool\n\texpect   string\n}\n\nfunc testExpander(t *testing.T, tests []expanderTest) {\n\tfor _, test := range tests {\n\t\tif got := Expand(test.format, test.values, test.colorize); got != test.expect {\n\t\t\tt.Errorf(\"%s: Expand(%q, ...) = %q, want %q\", test.name, test.format, got, test.expect)\n\t\t}\n\t}\n}\n\nfunc TestExpand(t *testing.T) {\n\ttestExpander(t, []expanderTest{\n\t\t{\n\t\t\tname:   \"Simple example\",\n\t\t\tformat: \"The author of %h was %an, %ar%nThe title was >>%s<<%n\",\n\t\t\tvalues: map[string]string{\n\t\t\t\t\"h\":  \"fe6e0ee\",\n\t\t\t\t\"an\": \"Junio C Hamano\",\n\t\t\t\t\"ar\": \"23 hours ago\",\n\t\t\t\t\"s\":  \"t4119: test autocomputing -p<n> for traditional diff input.\",\n\t\t\t},\n\t\t\texpect: \"The author of fe6e0ee was Junio C Hamano, 23 hours ago\\nThe title was >>t4119: test autocomputing -p<n> for traditional diff input.<<\\n\",\n\t\t},\n\t\t{\n\t\t\tname:   \"Percent sign, middle and trailing\",\n\t\t\tformat: \"%%a %%b %\",\n\t\t\tvalues: map[string]string{\"a\": \"A variable that should not be used.\"},\n\t\t\texpect: \"%a %b %\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Colors\",\n\t\t\tformat:   \"%Cred%r %Cgreen%g %Cblue%b%Creset normal\",\n\t\t\tvalues:   map[string]string{\"r\": \"RED\", \"g\": \"GREEN\", \"b\": \"BLUE\"},\n\t\t\tcolorize: true,\n\t\t\texpect:   \"\\033[31mRED \\033[32mGREEN \\033[34mBLUE\\033[m normal\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Colors not colorized\",\n\t\t\tformat:   \"%Cred%r %Cgreen%g %Cblue%b%Creset normal\",\n\t\t\tvalues:   map[string]string{\"r\": \"RED\", \"g\": \"GREEN\", \"b\": \"BLUE\"},\n\t\t\tcolorize: false,\n\t\t\texpect:   \"RED GREEN BLUE normal\",\n\t\t},\n\t\t{\n\t\t\tname:   \"Byte from hex code\",\n\t\t\tformat: \"%x00 %x3712%x61 %x%x1%xga\",\n\t\t\texpect: \"\\x00 \\x3712a %x%x1%xga\",\n\t\t},\n\t})\n}\n\nfunc TestExpand_Modifiers(t *testing.T) {\n\ttestExpander(t, []expanderTest{\n\t\t{\n\t\t\tname:   \"plus modifier, conditional line\",\n\t\t\tformat: \"line1%+a line2%+b line3\",\n\t\t\tvalues: map[string]string{\"a\": \"A\", \"b\": \"\"},\n\t\t\texpect: \"line1\\nA line2 line3\",\n\t\t},\n\t\t{\n\t\t\tname:   \"blank modifier, conditional blank\",\n\t\t\tformat: \"word1% a word2% b word3\",\n\t\t\tvalues: map[string]string{\"a\": \"A\", \"b\": \"\"},\n\t\t\texpect: \"word1 A word2 word3\",\n\t\t},\n\t\t{\n\t\t\tname:   \"minus modifier, crush preceding line-feeds\",\n\t\t\tformat: \"word1%n%n%-a\",\n\t\t\tvalues: map[string]string{\"a\": \"\"},\n\t\t\texpect: \"word1\",\n\t\t},\n\t})\n}\n\nfunc TestExpand_Padding(t *testing.T) {\n\ttestExpander(t, []expanderTest{\n\t\t{\n\t\t\tname:   \"padding\",\n\t\t\tformat: \"%<(10)%a\",\n\t\t\tvalues: map[string]string{\"a\": \"012\"},\n\t\t\texpect: \"012       \",\n\t\t},\n\t\t{\n\t\t\tname:   \"padding, wrong number\",\n\t\t\tformat: \"%<(1a)%a\",\n\t\t\tvalues: map[string]string{\"a\": \"012\"},\n\t\t\texpect: \"%<(1a)012\",\n\t\t},\n\t\t{\n\t\t\tname:   \"padding left\",\n\t\t\tformat: \"%>(10)%a\",\n\t\t\tvalues: map[string]string{\"a\": \"012\"},\n\t\t\texpect: \"       012\",\n\t\t},\n\t\t{\n\t\t\tname:   \"padding middle\",\n\t\t\tformat: \"%><(10)%a\",\n\t\t\tvalues: map[string]string{\"a\": \"0123\"},\n\t\t\texpect: \"   0123   \",\n\t\t},\n\t\t{\n\t\t\tname:   \"padding middle (odd # of blanks)\",\n\t\t\tformat: \"%><(10)%a\",\n\t\t\tvalues: map[string]string{\"a\": \"012\"},\n\t\t\texpect: \"   012    \",\n\t\t},\n\t\t{\n\t\t\tname:   \"padding uses extra blank on the left\",\n\t\t\tformat: \"%>>(5)|    %a\",\n\t\t\tvalues: map[string]string{\"a\": \"0123456\"},\n\t\t\texpect: \"|  0123456\",\n\t\t},\n\t\t{\n\t\t\tname:   \"padding until column N\",\n\t\t\tformat: \"%>|(10)abcdef%a\",\n\t\t\tvalues: map[string]string{\"a\": \"012\"},\n\t\t\texpect: \"abcdef 012\",\n\t\t},\n\t})\n}\n\nfunc TestExpand_Truncing(t *testing.T) {\n\ttestExpander(t, []expanderTest{\n\t\t{\n\t\t\tname:   \"truncing\",\n\t\t\tformat: \"%>(5,trunc)%a\",\n\t\t\tvalues: map[string]string{\"a\": \"0123456\"},\n\t\t\texpect: \"012..\",\n\t\t},\n\t\t{\n\t\t\tname:   \"truncing on the right\",\n\t\t\tformat: \"%>(5,rtrunc)%a\",\n\t\t\tvalues: map[string]string{\"a\": \"0123456\"},\n\t\t\texpect: \"..456\",\n\t\t},\n\t\t{\n\t\t\tname:   \"truncing in the middle\",\n\t\t\tformat: \"%>(6,mtrunc)%a\",\n\t\t\tvalues: map[string]string{\"a\": \"0123456\"},\n\t\t\texpect: \"01..56\",\n\t\t},\n\t\t{\n\t\t\tname:   \"truncing in the middle (odd # of chars)\",\n\t\t\tformat: \"%>(5,mtrunc)%a\",\n\t\t\tvalues: map[string]string{\"a\": \"0123456\"},\n\t\t\texpect: \"0..56\",\n\t\t},\n\t\t{\n\t\t\tname:   \"truncing not enough space\",\n\t\t\tformat: \"%>(1,trunc)%a\",\n\t\t\tvalues: map[string]string{\"a\": \"0123456\"},\n\t\t\texpect: \"..\",\n\t\t},\n\t\t{\n\t\t\tname:   \"truncing but use extra blanks on the left\",\n\t\t\tformat: \"%>>(3,trunc)|   %a\",\n\t\t\tvalues: map[string]string{\"a\": \"0123456\"},\n\t\t\texpect: \"|0123..\",\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "ui/ui.go",
    "content": "package ui\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/mattn/go-colorable\"\n\t\"github.com/mattn/go-isatty\"\n)\n\ntype UI interface {\n\tPrint(a ...interface{}) (n int, err error)\n\tPrintf(format string, a ...interface{}) (n int, err error)\n\tPrintln(a ...interface{}) (n int, err error)\n\tErrorf(format string, a ...interface{}) (n int, err error)\n\tErrorln(a ...interface{}) (n int, err error)\n}\n\nvar (\n\tStdout     = colorable.NewColorableStdout()\n\tStderr     = colorable.NewColorableStderr()\n\tDefault UI = Console{Stdout: Stdout, Stderr: Stderr}\n)\n\nfunc Print(a ...interface{}) (n int) {\n\tn, err := Default.Print(a...)\n\tif err != nil {\n\t\t// If something as basic as printing to stdout fails, just panic and exit\n\t\tos.Exit(1)\n\t}\n\treturn\n}\n\nfunc Printf(format string, a ...interface{}) (n int) {\n\tn, err := Default.Printf(format, a...)\n\tif err != nil {\n\t\t// If something as basic as printing to stdout fails, just panic and exit\n\t\tos.Exit(1)\n\t}\n\treturn\n}\n\nfunc Println(a ...interface{}) (n int) {\n\tn, err := Default.Println(a...)\n\tif err != nil {\n\t\t// If something as basic as printing to stdout fails, just panic and exit\n\t\tos.Exit(1)\n\t}\n\treturn\n}\n\nfunc Errorf(format string, a ...interface{}) (n int) {\n\tn, err := Default.Errorf(format, a...)\n\tif err != nil {\n\t\t// If something as basic as printing to stderr fails, just panic and exit\n\t\tos.Exit(1)\n\t}\n\treturn\n}\n\nfunc Errorln(a ...interface{}) (n int) {\n\tn, err := Default.Errorln(a...)\n\tif err != nil {\n\t\t// If something as basic as printing to stderr fails, just panic and exit\n\t\tos.Exit(1)\n\t}\n\treturn\n}\n\nfunc IsTerminal(f *os.File) bool {\n\treturn isatty.IsTerminal(f.Fd())\n}\n\ntype Console struct {\n\tStdout io.Writer\n\tStderr io.Writer\n}\n\nfunc (c Console) Print(a ...interface{}) (n int, err error) {\n\treturn fmt.Fprint(c.Stdout, a...)\n}\n\nfunc (c Console) Printf(format string, a ...interface{}) (n int, err error) {\n\treturn fmt.Fprintf(c.Stdout, format, a...)\n}\n\nfunc (c Console) Println(a ...interface{}) (n int, err error) {\n\treturn fmt.Fprintln(c.Stdout, a...)\n}\n\nfunc (c Console) Errorf(format string, a ...interface{}) (n int, err error) {\n\treturn fmt.Fprintf(c.Stderr, format, a...)\n}\n\nfunc (c Console) Errorln(a ...interface{}) (n int, err error) {\n\treturn fmt.Fprintln(c.Stderr, a...)\n}\n"
  },
  {
    "path": "utils/args_parser.go",
    "content": "package utils\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype argsFlag struct {\n\texpectsValue bool\n\tvalues       []string\n}\n\nfunc (f *argsFlag) addValue(v string) {\n\tf.values = append(f.values, v)\n}\n\nfunc (f *argsFlag) lastValue() string {\n\tl := len(f.values)\n\tif l > 0 {\n\t\treturn f.values[l-1]\n\t}\n\treturn \"\"\n}\n\nfunc (f *argsFlag) reset() {\n\tif len(f.values) > 0 {\n\t\tf.values = []string{}\n\t}\n}\n\ntype ArgsParser struct {\n\tflagMap           map[string]*argsFlag\n\tflagAliases       map[string]string\n\tPositionalIndices []int\n\tHasTerminated     bool\n}\n\nfunc (p *ArgsParser) Parse(args []string) ([]string, error) {\n\tvar flagName string\n\tvar flagValue string\n\tvar hasFlagValue bool\n\tvar i int\n\tvar arg string\n\n\tp.HasTerminated = false\n\tfor _, f := range p.flagMap {\n\t\tf.reset()\n\t}\n\tif len(p.PositionalIndices) > 0 {\n\t\tp.PositionalIndices = []int{}\n\t}\n\n\tpositional := []string{}\n\tvar parseError error\n\tlogError := func(f string, p ...interface{}) {\n\t\tif parseError == nil {\n\t\t\tparseError = fmt.Errorf(f, p...)\n\t\t}\n\t}\n\n\tacknowledgeFlag := func() bool {\n\t\tcanonicalFlagName := flagName\n\t\tif n, found := p.flagAliases[flagName]; found {\n\t\t\tcanonicalFlagName = n\n\t\t}\n\t\tf := p.flagMap[canonicalFlagName]\n\t\tif f == nil {\n\t\t\tif len(flagName) == 2 {\n\t\t\t\tlogError(\"unknown shorthand flag: '%s' in %s\", flagName[1:], arg)\n\t\t\t} else {\n\t\t\t\tlogError(\"unknown flag: '%s'\", flagName)\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t\tif f.expectsValue {\n\t\t\tif !hasFlagValue {\n\t\t\t\ti++\n\t\t\t\tif i < len(args) {\n\t\t\t\t\tflagValue = args[i]\n\t\t\t\t} else {\n\t\t\t\t\tlogError(\"no value given for '%s'\", flagName)\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t} else if hasFlagValue && len(flagName) <= 2 {\n\t\t\tflagValue = \"\"\n\t\t}\n\t\tf.addValue(flagValue)\n\t\treturn f.expectsValue\n\t}\n\n\tfor i = 0; i < len(args); i++ {\n\t\targ = args[i]\n\n\t\tif p.HasTerminated || len(arg) == 0 || arg == \"-\" {\n\t\t} else if arg == \"--\" {\n\t\t\tif !p.HasTerminated {\n\t\t\t\tp.HasTerminated = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else if strings.HasPrefix(arg, \"--\") {\n\t\t\tflagName = arg\n\t\t\tflagValue = \"\"\n\t\t\teq := strings.IndexByte(arg, '=')\n\t\t\thasFlagValue = eq >= 0\n\t\t\tif hasFlagValue {\n\t\t\t\tflagName = arg[:eq]\n\t\t\t\tflagValue = arg[eq+1:]\n\t\t\t}\n\t\t\tacknowledgeFlag()\n\t\t\tcontinue\n\t\t} else if arg[0] == '-' {\n\t\t\tfor j := 1; j < len(arg); j++ {\n\t\t\t\tflagName = \"-\" + arg[j:j+1]\n\t\t\t\tflagValue = \"\"\n\t\t\t\thasFlagValue = j+1 < len(arg)\n\t\t\t\tif hasFlagValue {\n\t\t\t\t\tflagValue = arg[j+1:]\n\t\t\t\t}\n\t\t\t\tif acknowledgeFlag() {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tp.PositionalIndices = append(p.PositionalIndices, i)\n\t\tpositional = append(positional, arg)\n\t}\n\n\treturn positional, parseError\n}\n\nfunc (p *ArgsParser) RegisterValue(name string, aliases ...string) {\n\tf := &argsFlag{expectsValue: true}\n\tp.flagMap[name] = f\n\tfor _, alias := range aliases {\n\t\tp.flagAliases[alias] = name\n\t}\n}\n\nfunc (p *ArgsParser) RegisterBool(name string, aliases ...string) {\n\tf := &argsFlag{expectsValue: false}\n\tp.flagMap[name] = f\n\tfor _, alias := range aliases {\n\t\tp.flagAliases[alias] = name\n\t}\n}\n\nfunc (p *ArgsParser) Value(name string) string {\n\tif f, found := p.flagMap[name]; found {\n\t\treturn f.lastValue()\n\t}\n\treturn \"\"\n}\n\nfunc (p *ArgsParser) AllValues(name string) []string {\n\tif f, found := p.flagMap[name]; found {\n\t\treturn f.values\n\t}\n\treturn []string{}\n}\n\nfunc (p *ArgsParser) Bool(name string) bool {\n\tif f, found := p.flagMap[name]; found {\n\t\treturn len(f.values) > 0 && f.lastValue() != \"false\"\n\t}\n\treturn false\n}\n\nfunc (p *ArgsParser) Int(name string) int {\n\ti, _ := strconv.Atoi(p.Value(name))\n\treturn i\n}\n\nfunc (p *ArgsParser) HasReceived(name string) bool {\n\tf, found := p.flagMap[name]\n\treturn found && len(f.values) > 0\n}\n\nfunc NewArgsParser() *ArgsParser {\n\treturn &ArgsParser{\n\t\tflagMap:     make(map[string]*argsFlag),\n\t\tflagAliases: make(map[string]string),\n\t}\n}\n\nfunc NewArgsParserWithUsage(usage string) *ArgsParser {\n\tp := NewArgsParser()\n\tf := `(-[a-zA-Z0-9@^]|--[a-z][a-z0-9-]+)(?:\\[?[ =]([a-zA-Z_<>:=-]+\\]?))?`\n\tre := regexp.MustCompile(fmt.Sprintf(`(?m)^\\s*%s(?:,\\s*%s)?$`, f, f))\n\tfor _, match := range re.FindAllStringSubmatch(usage, -1) {\n\t\tn1 := match[1]\n\t\tn2 := match[3]\n\t\thasValue := !(match[2] == \"\" || strings.HasSuffix(match[2], \"]\")) || match[4] != \"\"\n\t\tvar aliases []string\n\t\tif len(n1) == 2 && len(n2) > 2 {\n\t\t\taliases = []string{n1}\n\t\t\tn1 = n2\n\t\t} else if n2 != \"\" {\n\t\t\taliases = []string{n2}\n\t\t}\n\t\tif hasValue {\n\t\t\tp.RegisterValue(n1, aliases...)\n\t\t} else {\n\t\t\tp.RegisterBool(n1, aliases...)\n\t\t}\n\t}\n\treturn p\n}\n"
  },
  {
    "path": "utils/args_parser_test.go",
    "content": "package utils\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc equal(t *testing.T, expected, got interface{}) {\n\tt.Helper()\n\tif !reflect.DeepEqual(expected, got) {\n\t\tt.Errorf(\"expected: %#v, got: %#v\", expected, got)\n\t}\n}\n\nfunc TestArgsParser(t *testing.T) {\n\tp := NewArgsParser()\n\tp.RegisterValue(\"--hello\", \"-e\")\n\tp.RegisterValue(\"--origin\", \"-o\")\n\targs := []string{\"--hello\", \"world\", \"one\", \"--\", \"--two\"}\n\trest, err := p.Parse(args)\n\tequal(t, nil, err)\n\tequal(t, []string{\"one\", \"--two\"}, rest)\n\tequal(t, \"world\", p.Value(\"--hello\"))\n\tequal(t, true, p.HasReceived(\"--hello\"))\n\tequal(t, \"\", p.Value(\"-e\"))\n\tequal(t, false, p.HasReceived(\"-e\"))\n\tequal(t, \"\", p.Value(\"--origin\"))\n\tequal(t, false, p.HasReceived(\"--origin\"))\n\tequal(t, []int{2, 4}, p.PositionalIndices)\n}\n\nfunc TestArgsParser_RepeatedInvocation(t *testing.T) {\n\tp := NewArgsParser()\n\tp.RegisterValue(\"--hello\", \"-e\")\n\tp.RegisterValue(\"--origin\", \"-o\")\n\n\trest, err := p.Parse([]string{\"--hello\", \"world\", \"--\", \"one\"})\n\tequal(t, nil, err)\n\tequal(t, []string{\"one\"}, rest)\n\tequal(t, []int{3}, p.PositionalIndices)\n\tequal(t, true, p.HasReceived(\"--hello\"))\n\tequal(t, \"world\", p.Value(\"--hello\"))\n\tequal(t, false, p.HasReceived(\"--origin\"))\n\tequal(t, true, p.HasTerminated)\n\n\trest, err = p.Parse([]string{\"two\", \"-oupstream\"})\n\tequal(t, nil, err)\n\tequal(t, []string{\"two\"}, rest)\n\tequal(t, []int{0}, p.PositionalIndices)\n\tequal(t, false, p.HasReceived(\"--hello\"))\n\tequal(t, true, p.HasReceived(\"--origin\"))\n\tequal(t, \"upstream\", p.Value(\"--origin\"))\n\tequal(t, false, p.HasTerminated)\n}\n\nfunc TestArgsParser_UnknownFlag(t *testing.T) {\n\tp := NewArgsParser()\n\tp.RegisterValue(\"--hello\")\n\tp.RegisterBool(\"--yes\", \"-y\")\n\n\targs := []string{\"--hello\", \"world\", \"--nonexist\", \"one\", \"--\", \"--two\"}\n\trest, err := p.Parse(args)\n\tequal(t, errors.New(\"unknown flag: '--nonexist'\"), err)\n\tequal(t, []string{\"one\", \"--two\"}, rest)\n\n\trest, err = p.Parse([]string{\"one\", \"-yelp\"})\n\tequal(t, errors.New(\"unknown shorthand flag: 'e' in -yelp\"), err)\n\tequal(t, []string{\"one\"}, rest)\n\tequal(t, true, p.Bool(\"--yes\"))\n}\n\nfunc TestArgsParser_BlankArgs(t *testing.T) {\n\tp := NewArgsParser()\n\trest, err := p.Parse([]string{\"\", \"\"})\n\tequal(t, nil, err)\n\tequal(t, []string{\"\", \"\"}, rest)\n\tequal(t, []int{0, 1}, p.PositionalIndices)\n}\n\nfunc TestArgsParser_Values(t *testing.T) {\n\tp := NewArgsParser()\n\tp.RegisterValue(\"--origin\", \"-o\")\n\targs := []string{\"--origin=a=b\", \"--origin=\", \"--origin\", \"c\", \"-o\"}\n\trest, err := p.Parse(args)\n\tequal(t, errors.New(\"no value given for '-o'\"), err)\n\tequal(t, []string{}, rest)\n\tequal(t, []string{\"a=b\", \"\", \"c\"}, p.AllValues(\"--origin\"))\n}\n\nfunc TestArgsParser_Bool(t *testing.T) {\n\tp := NewArgsParser()\n\tp.RegisterBool(\"--noop\")\n\tp.RegisterBool(\"--color\")\n\tp.RegisterBool(\"--draft\", \"-d\")\n\targs := []string{\"-d\", \"--draft=false\", \"--color=auto\"}\n\trest, err := p.Parse(args)\n\tequal(t, nil, err)\n\tequal(t, []string{}, rest)\n\tequal(t, false, p.Bool(\"--draft\"))\n\tequal(t, true, p.HasReceived(\"--draft\"))\n\tequal(t, false, p.HasReceived(\"-d\"))\n\tequal(t, false, p.HasReceived(\"--noop\"))\n\tequal(t, false, p.Bool(\"--noop\"))\n\tequal(t, true, p.HasReceived(\"--color\"))\n\tequal(t, \"auto\", p.Value(\"--color\"))\n}\n\nfunc TestArgsParser_BoolValue(t *testing.T) {\n\tp := NewArgsParser()\n\tp.RegisterBool(\"--draft\")\n\targs := []string{\"--draft=yes pls\"}\n\trest, err := p.Parse(args)\n\tequal(t, nil, err)\n\tequal(t, []string{}, rest)\n\tequal(t, true, p.HasReceived(\"--draft\"))\n\tequal(t, true, p.Bool(\"--draft\"))\n\tequal(t, \"yes pls\", p.Value(\"--draft\"))\n}\n\nfunc TestArgsParser_BoolValue_multiple(t *testing.T) {\n\tp := NewArgsParser()\n\tp.RegisterBool(\"--draft\")\n\tp.RegisterBool(\"--prerelease\")\n\targs := []string{\"--draft=false\", \"--prerelease\"}\n\trest, err := p.Parse(args)\n\tequal(t, nil, err)\n\tequal(t, []string{}, rest)\n\tequal(t, false, p.Bool(\"--draft\"))\n\tequal(t, true, p.Bool(\"--prerelease\"))\n}\n\nfunc TestArgsParser_Shorthand(t *testing.T) {\n\tp := NewArgsParser()\n\tp.RegisterValue(\"--origin\", \"-o\")\n\tp.RegisterBool(\"--draft\", \"-d\")\n\tp.RegisterBool(\"--copy\", \"-c\")\n\targs := []string{\"-co\", \"one\", \"-dotwo\"}\n\trest, err := p.Parse(args)\n\tequal(t, nil, err)\n\tequal(t, []string{}, rest)\n\tequal(t, []string{\"one\", \"two\"}, p.AllValues(\"--origin\"))\n\tequal(t, true, p.Bool(\"--draft\"))\n\tequal(t, true, p.Bool(\"--copy\"))\n}\n\nfunc TestArgsParser_ShorthandEdgeCase(t *testing.T) {\n\tp := NewArgsParser()\n\tp.RegisterBool(\"--draft\", \"-d\")\n\tp.RegisterBool(\"-f\")\n\tp.RegisterBool(\"-a\")\n\tp.RegisterBool(\"-l\")\n\tp.RegisterBool(\"-s\")\n\tp.RegisterBool(\"-e\")\n\targs := []string{\"-dfalse\"}\n\trest, err := p.Parse(args)\n\tequal(t, nil, err)\n\tequal(t, []string{}, rest)\n\tequal(t, true, p.Bool(\"--draft\"))\n}\n\nfunc TestArgsParser_Dashes(t *testing.T) {\n\tp := NewArgsParser()\n\tp.RegisterValue(\"--file\", \"-F\")\n\targs := []string{\"-F-\", \"-\", \"--\", \"-F\", \"--\"}\n\trest, err := p.Parse(args)\n\tequal(t, nil, err)\n\tequal(t, []string{\"-\", \"-F\", \"--\"}, rest)\n\tequal(t, \"-\", p.Value(\"--file\"))\n}\n\nfunc TestArgsParser_RepeatedArg(t *testing.T) {\n\tp := NewArgsParser()\n\tp.RegisterValue(\"--msg\", \"-m\")\n\targs := []string{\"--msg=hello\", \"-m\", \"world\", \"--msg\", \"how\", \"-mare you?\"}\n\trest, err := p.Parse(args)\n\tequal(t, nil, err)\n\tequal(t, []string{}, rest)\n\tequal(t, \"are you?\", p.Value(\"--msg\"))\n\tequal(t, []string{\"hello\", \"world\", \"how\", \"are you?\"}, p.AllValues(\"--msg\"))\n}\n\nfunc TestArgsParser_Int(t *testing.T) {\n\tp := NewArgsParser()\n\tp.RegisterValue(\"--limit\", \"-L\")\n\tp.RegisterValue(\"--depth\", \"-d\")\n\targs := []string{\"-L24\", \"-d\", \"-3\"}\n\trest, err := p.Parse(args)\n\tequal(t, nil, err)\n\tequal(t, []string{}, rest)\n\tequal(t, true, p.HasReceived(\"--limit\"))\n\tequal(t, 24, p.Int(\"--limit\"))\n\tequal(t, true, p.HasReceived(\"--depth\"))\n\tequal(t, -3, p.Int(\"--depth\"))\n}\n\nfunc TestArgsParser_WithUsage(t *testing.T) {\n\tp := NewArgsParserWithUsage(`\n\t\t-L, --limit N\n\t\t\tretrieve at most N records\n\t\t-d, --draft\n\t\t\tsave as draft\n\t\t--message=<msg>, -m <msg>\n\t\t\tset message body\n\t`)\n\targs := []string{\"-L24\", \"-d\", \"-mhello\"}\n\trest, err := p.Parse(args)\n\tequal(t, nil, err)\n\tequal(t, []string{}, rest)\n\tequal(t, \"24\", p.Value(\"--limit\"))\n\tequal(t, true, p.Bool(\"--draft\"))\n\tequal(t, \"hello\", p.Value(\"--message\"))\n}\n"
  },
  {
    "path": "utils/color.go",
    "content": "package utils\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"strconv\"\n)\n\nvar (\n\tBlack *Color\n\tWhite *Color\n)\n\nfunc init() {\n\tinitColorCube()\n\tBlack, _ = NewColor(\"000000\")\n\tWhite, _ = NewColor(\"ffffff\")\n}\n\ntype Color struct {\n\tRed   uint8\n\tGreen uint8\n\tBlue  uint8\n}\n\nfunc NewColor(hex string) (*Color, error) {\n\tred, err := strconv.ParseUint(hex[0:2], 16, 8)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgreen, err := strconv.ParseUint(hex[2:4], 16, 8)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tblue, err := strconv.ParseUint(hex[4:6], 16, 8)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Color{\n\t\tRed:   uint8(red),\n\t\tGreen: uint8(green),\n\t\tBlue:  uint8(blue),\n\t}, nil\n}\n\nfunc (c *Color) Distance(other *Color) float64 {\n\treturn math.Sqrt(math.Pow(float64(c.Red-other.Red), 2) +\n\t\tmath.Pow(float64(c.Green-other.Green), 2) +\n\t\tmath.Pow(float64(c.Blue-other.Blue), 2))\n}\n\nfunc rgbComponentToBoldValue(component uint8) float64 {\n\tsrgb := float64(component) / 255\n\tif srgb <= 0.03928 {\n\t\treturn srgb / 12.92\n\t}\n\treturn math.Pow(((srgb + 0.055) / 1.055), 2.4)\n}\n\nfunc (c *Color) Luminance() float64 {\n\treturn 0.2126*rgbComponentToBoldValue(c.Red) +\n\t\t0.7152*rgbComponentToBoldValue(c.Green) +\n\t\t0.0722*rgbComponentToBoldValue(c.Blue)\n}\n\nfunc (c *Color) ContrastRatio(other *Color) float64 {\n\tL := c.Luminance()\n\totherL := other.Luminance()\n\tvar L1, L2 float64\n\tif L > otherL {\n\t\tL1, L2 = L, otherL\n\t} else {\n\t\tL1, L2 = otherL, L\n\t}\n\tratio := (L1 + 0.05) / (L2 + 0.05)\n\treturn ratio\n}\n\nvar x6colorIndexes = [6]uint8{0, 95, 135, 175, 215, 255}\nvar x6colorCube [216]Color\n\nfunc initColorCube() {\n\ti := 0\n\tfor iR := 0; iR < 6; iR++ {\n\t\tfor iG := 0; iG < 6; iG++ {\n\t\t\tfor iB := 0; iB < 6; iB++ {\n\t\t\t\tx6colorCube[i] = Color{\n\t\t\t\t\tx6colorIndexes[iR],\n\t\t\t\t\tx6colorIndexes[iG],\n\t\t\t\t\tx6colorIndexes[iB],\n\t\t\t\t}\n\t\t\t\ti++\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc ditherTo256ColorCode(color *Color) (code int) {\n\tiMatch := -1\n\tminDistance := float64(99999)\n\tfor i := 0; i < 216; i++ {\n\t\tdistance := color.Distance(&x6colorCube[i])\n\t\tif distance < minDistance {\n\t\t\tiMatch = i\n\t\t\tminDistance = distance\n\t\t}\n\t}\n\treturn iMatch + 16\n}\n\nvar non24bitColorTerms = []string{\n\t\"Apple_Terminal\",\n}\nvar isTerm24bitColorCapableCache bool\nvar isTerm24bitColorCapableCacheIsInit bool = false\n\nfunc isTerm24bitColorCapable() bool {\n\tif !isTerm24bitColorCapableCacheIsInit {\n\t\tisTerm24bitColorCapableCache = true\n\t\tmyTermProg := os.Getenv(\"TERM_PROGRAM\")\n\t\tfor _, brokenTerm := range non24bitColorTerms {\n\t\t\tif myTermProg == brokenTerm {\n\t\t\t\tisTerm24bitColorCapableCache = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tisTerm24bitColorCapableCacheIsInit = true\n\t}\n\treturn isTerm24bitColorCapableCache\n}\n\nfunc RgbToTermColorCode(color *Color) string {\n\tif isTerm24bitColorCapable() {\n\t\treturn fmt.Sprintf(\"2;%d;%d;%d\", color.Red, color.Green, color.Blue)\n\t}\n\tintCode := ditherTo256ColorCode(color)\n\treturn fmt.Sprintf(\"5;%d\", intCode)\n}\n"
  },
  {
    "path": "utils/json.go",
    "content": "package utils\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n)\n\ntype state struct {\n\tisObject    bool\n\tisArray     bool\n\tarrayIndex  int\n\tobjectKey   string\n\tparentState *state\n}\n\nfunc stateKey(s *state) string {\n\tk := \"\"\n\tif s.parentState != nil {\n\t\tk = stateKey(s.parentState)\n\t}\n\tif s.isObject {\n\t\treturn fmt.Sprintf(\"%s.%s\", k, s.objectKey)\n\t} else if s.isArray {\n\t\treturn fmt.Sprintf(\"%s.[%d]\", k, s.arrayIndex)\n\t} else {\n\t\treturn k\n\t}\n}\n\nfunc JSONPath(out io.Writer, src io.Reader, colorize bool) (hasNextPage bool, endCursor string) {\n\tdec := json.NewDecoder(src)\n\tdec.UseNumber()\n\n\ts := &state{}\n\tpostEmit := func() {\n\t\tif s.isObject {\n\t\t\ts.objectKey = \"\"\n\t\t} else if s.isArray {\n\t\t\ts.arrayIndex++\n\t\t}\n\t}\n\n\tcolor := func(c string, t interface{}) string {\n\t\tif colorize {\n\t\t\treturn fmt.Sprintf(\"\\033[%sm%s\\033[m\", c, t)\n\t\t} else if tt, ok := t.(string); ok {\n\t\t\treturn tt\n\t\t} else {\n\t\t\treturn fmt.Sprintf(\"%s\", t)\n\t\t}\n\t}\n\n\tfor {\n\t\ttoken, err := dec.Token()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t} else if err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tif delim, ok := token.(json.Delim); ok {\n\t\t\tswitch delim {\n\t\t\tcase '{':\n\t\t\t\ts = &state{isObject: true, parentState: s}\n\t\t\tcase '[':\n\t\t\t\ts = &state{isArray: true, parentState: s}\n\t\t\tcase '}', ']':\n\t\t\t\ts = s.parentState\n\t\t\t\tpostEmit()\n\t\t\tdefault:\n\t\t\t\tpanic(\"unknown delim\")\n\t\t\t}\n\t\t} else {\n\t\t\tif s.isObject && s.objectKey == \"\" {\n\t\t\t\ts.objectKey = token.(string)\n\t\t\t} else {\n\t\t\t\tk := stateKey(s)\n\t\t\t\tfmt.Fprintf(out, \"%s\\t\", color(\"0;36\", k))\n\n\t\t\t\tswitch tt := token.(type) {\n\t\t\t\tcase string:\n\t\t\t\t\tfmt.Fprintf(out, \"%s\\n\", strings.Replace(tt, \"\\n\", \"\\\\n\", -1))\n\t\t\t\t\tif strings.HasSuffix(k, \".pageInfo.endCursor\") {\n\t\t\t\t\t\tendCursor = tt\n\t\t\t\t\t}\n\t\t\t\tcase json.Number:\n\t\t\t\t\tfmt.Fprintf(out, \"%s\\n\", color(\"0;35\", tt))\n\t\t\t\tcase nil:\n\t\t\t\t\tfmt.Fprintf(out, \"\\n\")\n\t\t\t\tcase bool:\n\t\t\t\t\tfmt.Fprintf(out, \"%s\\n\", color(\"1;33\", fmt.Sprintf(\"%v\", tt)))\n\t\t\t\t\tif strings.HasSuffix(k, \".pageInfo.hasNextPage\") {\n\t\t\t\t\t\thasNextPage = tt\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tpanic(\"unknown type\")\n\t\t\t\t}\n\t\t\t\tpostEmit()\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n"
  },
  {
    "path": "utils/utils.go",
    "content": "package utils\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/github/hub/v2/ui\"\n\t\"github.com/kballard/go-shellquote\"\n)\n\nvar timeNow = time.Now\n\nfunc Check(err error) {\n\tif err != nil {\n\t\tui.Errorln(err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc ConcatPaths(paths ...string) string {\n\treturn strings.Join(paths, \"/\")\n}\n\nfunc BrowserLauncher() ([]string, error) {\n\tbrowser := os.Getenv(\"BROWSER\")\n\tif browser == \"\" {\n\t\tbrowser = searchBrowserLauncher(runtime.GOOS)\n\t} else {\n\t\tbrowser = os.ExpandEnv(browser)\n\t}\n\n\tif browser == \"\" {\n\t\treturn nil, errors.New(\"Please set $BROWSER to a web launcher\")\n\t}\n\n\treturn shellquote.Split(browser)\n}\n\nfunc searchBrowserLauncher(goos string) (browser string) {\n\tswitch goos {\n\tcase \"darwin\":\n\t\tbrowser = \"open\"\n\tcase \"windows\":\n\t\tbrowser = \"cmd /c start\"\n\tdefault:\n\t\tcandidates := []string{\"xdg-open\", \"cygstart\", \"x-www-browser\", \"firefox\",\n\t\t\t\"opera\", \"mozilla\", \"netscape\"}\n\t\tfor _, b := range candidates {\n\t\t\tpath, err := exec.LookPath(b)\n\t\t\tif err == nil {\n\t\t\t\tbrowser = path\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn browser\n}\n\nfunc CommandPath(cmd string) (string, error) {\n\tif runtime.GOOS == \"windows\" && !strings.HasSuffix(cmd, \".exe\") {\n\t\tcmd = cmd + \".exe\"\n\t}\n\n\tpath, err := exec.LookPath(cmd)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tpath, err = filepath.Abs(path)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn filepath.EvalSymlinks(path)\n}\n\nfunc TimeAgo(t time.Time) string {\n\tduration := timeNow().Sub(t)\n\tminutes := duration.Minutes()\n\thours := duration.Hours()\n\tdays := hours / 24\n\tmonths := days / 30\n\tyears := months / 12\n\n\tvar val int\n\tvar unit string\n\n\tif minutes < 1 {\n\t\treturn \"now\"\n\t} else if hours < 1 {\n\t\tval = int(minutes)\n\t\tunit = \"minute\"\n\t} else if days < 1 {\n\t\tval = int(hours)\n\t\tunit = \"hour\"\n\t} else if months < 1 {\n\t\tval = int(days)\n\t\tunit = \"day\"\n\t} else if years < 1 {\n\t\tval = int(months)\n\t\tunit = \"month\"\n\t} else {\n\t\tval = int(years)\n\t\tunit = \"year\"\n\t}\n\n\tvar plural string\n\tif val > 1 {\n\t\tplural = \"s\"\n\t}\n\treturn fmt.Sprintf(\"%d %s%s ago\", val, unit, plural)\n}\n"
  },
  {
    "path": "utils/utils_test.go",
    "content": "package utils\n\nimport (\n\t\"github.com/github/hub/v2/internal/assert\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestSearchBrowserLauncher(t *testing.T) {\n\tbrowser := searchBrowserLauncher(\"darwin\")\n\tassert.Equal(t, \"open\", browser)\n\n\tbrowser = searchBrowserLauncher(\"windows\")\n\tassert.Equal(t, \"cmd /c start\", browser)\n}\n\nfunc TestConcatPaths(t *testing.T) {\n\tassert.Equal(t, \"foo/bar/baz\", ConcatPaths(\"foo\", \"bar\", \"baz\"))\n}\n\nfunc TestTimeAgo(t *testing.T) {\n\ttimeNow = func() time.Time {\n\t\treturn time.Date(2018, 10, 28, 14, 34, 58, 651387237, time.UTC)\n\t}\n\n\tnow := timeNow()\n\n\tsecAgo := now.Add(-1 * time.Second)\n\tactual := TimeAgo(secAgo)\n\tassert.Equal(t, \"now\", actual)\n\n\tminAgo := now.Add(-1 * time.Minute)\n\tactual = TimeAgo(minAgo)\n\tassert.Equal(t, \"1 minute ago\", actual)\n\n\tminsAgo := now.Add(-5 * time.Minute)\n\tactual = TimeAgo(minsAgo)\n\tassert.Equal(t, \"5 minutes ago\", actual)\n\n\thourAgo := now.Add(-1 * time.Hour)\n\tactual = TimeAgo(hourAgo)\n\tassert.Equal(t, \"1 hour ago\", actual)\n\n\thoursAgo := now.Add(-3 * time.Hour)\n\tactual = TimeAgo(hoursAgo)\n\tassert.Equal(t, \"3 hours ago\", actual)\n\n\tdayAgo := now.Add(-1 * 24 * time.Hour)\n\tactual = TimeAgo(dayAgo)\n\tassert.Equal(t, \"1 day ago\", actual)\n\n\tdaysAgo := now.Add(-5 * 24 * time.Hour)\n\tactual = TimeAgo(daysAgo)\n\tassert.Equal(t, \"5 days ago\", actual)\n\n\tmonthAgo := now.Add(-1 * 24 * 31 * time.Hour)\n\tactual = TimeAgo(monthAgo)\n\tassert.Equal(t, \"1 month ago\", actual)\n\n\tmonthsAgo := now.Add(-2 * 24 * 31 * time.Hour)\n\tactual = TimeAgo(monthsAgo)\n\tassert.Equal(t, \"2 months ago\", actual)\n\n\tyearAgo := now.Add(-1 * 24 * 31 * 12 * time.Hour)\n\tactual = TimeAgo(yearAgo)\n\tassert.Equal(t, \"1 year ago\", actual)\n\n\tyearsAgo := now.Add(-2 * 24 * 31 * 12 * time.Hour)\n\tactual = TimeAgo(yearsAgo)\n\tassert.Equal(t, \"2 years ago\", actual)\n}\n"
  },
  {
    "path": "version/version.go",
    "content": "package version\n\n// Version represents the hub version number\nvar Version = \"2.14.2\"\n"
  }
]