Merge branch 'master' into reviewers-in-pr-view
This commit is contained in:
commit
d2d0b47ecc
22 changed files with 416 additions and 136 deletions
35
.github/workflows/releases.yml
vendored
35
.github/workflows/releases.yml
vendored
|
|
@ -17,6 +17,7 @@ jobs:
|
|||
go-version: 1.13
|
||||
- name: Generate changelog
|
||||
run: |
|
||||
echo ::set-env name=GORELEASER_CURRENT_TAG::${GITHUB_REF#refs/tags/}
|
||||
git fetch --unshallow
|
||||
script/changelog | tee CHANGELOG.md
|
||||
- name: Run GoReleaser
|
||||
|
|
@ -26,18 +27,6 @@ jobs:
|
|||
args: release --release-notes=CHANGELOG.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.UPLOAD_GITHUB_TOKEN}}
|
||||
- name: move cards
|
||||
if: "!contains(github.ref, '-')"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
PENDING_COLUMN: 8189733
|
||||
DONE_COLUMN: 7110130
|
||||
shell: bash
|
||||
run: |
|
||||
curl -fsSL https://github.com/github/hub/raw/master/script/get | bash -s 2.14.1
|
||||
api() { bin/hub api -H 'accept: application/vnd.github.inertia-preview+json' "$@"; }
|
||||
cards=$(api projects/columns/$PENDING_COLUMN/cards | jq ".[].id")
|
||||
for card in $cards; do api projects/columns/cards/$card/moves --field position=top --field column_id=$DONE_COLUMN; done
|
||||
- name: Bump homebrew-core formula
|
||||
uses: mislav/bump-homebrew-formula-action@v1
|
||||
if: "!contains(github.ref, '-')" # skip prereleases
|
||||
|
|
@ -45,6 +34,28 @@ jobs:
|
|||
formula-name: gh
|
||||
env:
|
||||
COMMITTER_TOKEN: ${{ secrets.UPLOAD_GITHUB_TOKEN }}
|
||||
- name: Checkout documentation site
|
||||
if: "!contains(github.ref, '-')" # skip prereleases
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: github/cli.github.com
|
||||
path: site
|
||||
fetch-depth: 0
|
||||
token: ${{secrets.SITE_GITHUB_TOKEN}}
|
||||
- name: Publish documentation site
|
||||
if: "!contains(github.ref, '-')" # skip prereleases
|
||||
run: make site-publish
|
||||
- name: Move project cards
|
||||
if: "!contains(github.ref, '-')" # skip prereleases
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
PENDING_COLUMN: 8189733
|
||||
DONE_COLUMN: 7110130
|
||||
run: |
|
||||
curl -fsSL https://github.com/github/hub/raw/master/script/get | bash -s 2.14.1
|
||||
api() { bin/hub api -H 'accept: application/vnd.github.inertia-preview+json' "$@"; }
|
||||
cards=$(api projects/columns/$PENDING_COLUMN/cards | jq ".[].id")
|
||||
for card in $cards; do api projects/columns/cards/$card/moves --field position=top --field column_id=$DONE_COLUMN; done
|
||||
msi:
|
||||
needs: goreleaser
|
||||
runs-on: windows-latest
|
||||
|
|
|
|||
12
Makefile
12
Makefile
|
|
@ -32,5 +32,15 @@ site-docs: site
|
|||
for f in site/manual/gh*.md; do sed -i.bak -e '/^### SEE ALSO/,$$d' "$$f"; done
|
||||
rm -f site/manual/*.bak
|
||||
git -C site add 'manual/gh*.md'
|
||||
git -C site commit -m 'update help docs'
|
||||
git -C site commit -m 'update help docs' || true
|
||||
.PHONY: site-docs
|
||||
|
||||
site-publish: site-docs
|
||||
ifndef GITHUB_REF
|
||||
$(error GITHUB_REF is not set)
|
||||
endif
|
||||
sed -i.bak -E 's/(assign version = )".+"/\1"$(GITHUB_REF:refs/tags/v%=%)"/' site/index.html
|
||||
rm -f site/index.html.bak
|
||||
git -C site commit -m '$(GITHUB_REF:refs/tags/v%=%)' index.html
|
||||
git -C site push
|
||||
.PHONY: site-publish
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ the terminal next to where you are already working with `git` and your code.
|
|||
|
||||
## Availability
|
||||
|
||||
While in beta, GitHub CLI is available for repos hosted on GitHub.com only. It does not currently support repositories hosted on GitHub Enterprise Server or other hosting providers. We are planning support for GitHub Enterprise Server after GitHub CLI is out of beta (likely toward the end of 2020), and we want to ensure that the API endpoints we use are more widely available for GHES versions that most GitHub customers are on.
|
||||
While in beta, GitHub CLI is available for repos hosted on GitHub.com only. It does not currently support repositories hosted on GitHub Enterprise Server or other hosting providers. We are planning support for GitHub Enterprise Server after GitHub CLI is out of beta (likely toward the end of 2020), and we want to ensure that the API endpoints we use are more widely available for GHES versions that most GitHub customers are on.
|
||||
|
||||
## We need your feedback
|
||||
|
||||
|
|
@ -22,6 +22,7 @@ And if you spot bugs or have features that you'd really like to see in `gh`, ple
|
|||
- `gh pr [status, list, view, checkout, create]`
|
||||
- `gh issue [status, list, view, create]`
|
||||
- `gh repo [view, create, clone, fork]`
|
||||
- `gh config [get, set]`
|
||||
- `gh help`
|
||||
|
||||
## Documentation
|
||||
|
|
|
|||
|
|
@ -37,6 +37,16 @@ func AddHeader(name, value string) ClientOption {
|
|||
}
|
||||
}
|
||||
|
||||
// AddHeaderFunc is an AddHeader that gets the string value from a function
|
||||
func AddHeaderFunc(name string, value func() string) ClientOption {
|
||||
return func(tr http.RoundTripper) http.RoundTripper {
|
||||
return &funcTripper{roundTrip: func(req *http.Request) (*http.Response, error) {
|
||||
req.Header.Add(name, value())
|
||||
return tr.RoundTrip(req)
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
// VerboseLog enables request/response logging within a RoundTripper
|
||||
func VerboseLog(out io.Writer, logTraffic bool, colorize bool) ClientOption {
|
||||
logger := &httpretty.Logger{
|
||||
|
|
@ -63,6 +73,40 @@ func ReplaceTripper(tr http.RoundTripper) ClientOption {
|
|||
}
|
||||
}
|
||||
|
||||
var issuedScopesWarning bool
|
||||
|
||||
// CheckScopes checks whether an OAuth scope is present in a response
|
||||
func CheckScopes(wantedScope string, cb func(string) error) ClientOption {
|
||||
return func(tr http.RoundTripper) http.RoundTripper {
|
||||
return &funcTripper{roundTrip: func(req *http.Request) (*http.Response, error) {
|
||||
res, err := tr.RoundTrip(req)
|
||||
if err != nil || res.StatusCode > 299 || issuedScopesWarning {
|
||||
return res, err
|
||||
}
|
||||
|
||||
appID := res.Header.Get("X-Oauth-Client-Id")
|
||||
hasScopes := strings.Split(res.Header.Get("X-Oauth-Scopes"), ",")
|
||||
|
||||
hasWanted := false
|
||||
for _, s := range hasScopes {
|
||||
if wantedScope == strings.TrimSpace(s) {
|
||||
hasWanted = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasWanted {
|
||||
if err := cb(appID); err != nil {
|
||||
return res, err
|
||||
}
|
||||
issuedScopesWarning = true
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
type funcTripper struct {
|
||||
roundTrip func(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/pkg/browser"
|
||||
)
|
||||
|
|
@ -29,6 +30,7 @@ type OAuthFlow struct {
|
|||
Hostname string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
Scopes []string
|
||||
WriteSuccessHTML func(io.Writer)
|
||||
VerboseStream io.Writer
|
||||
}
|
||||
|
|
@ -45,11 +47,15 @@ func (oa *OAuthFlow) ObtainAccessToken() (accessToken string, err error) {
|
|||
}
|
||||
port := listener.Addr().(*net.TCPAddr).Port
|
||||
|
||||
scopes := "repo"
|
||||
if oa.Scopes != nil {
|
||||
scopes = strings.Join(oa.Scopes, " ")
|
||||
}
|
||||
|
||||
q := url.Values{}
|
||||
q.Set("client_id", oa.ClientID)
|
||||
q.Set("redirect_uri", fmt.Sprintf("http://127.0.0.1:%d/callback", port))
|
||||
// TODO: make scopes configurable
|
||||
q.Set("scope", "repo")
|
||||
q.Set("scope", scopes)
|
||||
q.Set("state", state)
|
||||
|
||||
startURL := fmt.Sprintf("https://%s/login/oauth/authorize?%s", oa.Hostname, q.Encode())
|
||||
|
|
|
|||
|
|
@ -504,7 +504,11 @@ func issueProjectList(issue api.Issue) string {
|
|||
|
||||
projectNames := make([]string, 0, len(issue.ProjectCards.Nodes))
|
||||
for _, project := range issue.ProjectCards.Nodes {
|
||||
projectNames = append(projectNames, fmt.Sprintf("%s (%s)", project.Project.Name, project.Column.Name))
|
||||
colName := project.Column.Name
|
||||
if colName == "" {
|
||||
colName = "Awaiting triage"
|
||||
}
|
||||
projectNames = append(projectNames, fmt.Sprintf("%s (%s)", project.Project.Name, colName))
|
||||
}
|
||||
|
||||
list := strings.Join(projectNames, ", ")
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ func TestIssueView_Preview(t *testing.T) {
|
|||
`Open • marseilles opened about 292 years ago • 9 comments`,
|
||||
`Assignees: marseilles, monaco\n`,
|
||||
`Labels: one, two, three, four, five\n`,
|
||||
`Projects: Project 1 \(column A\), Project 2 \(column B\), Project 3 \(column C\)\n`,
|
||||
`Projects: Project 1 \(column A\), Project 2 \(column B\), Project 3 \(column C\), Project 4 \(Awaiting triage\)\n`,
|
||||
`Milestone: uluru\n`,
|
||||
`bold story`,
|
||||
`View this issue on GitHub: https://github.com/OWNER/REPO/issues/123`,
|
||||
|
|
|
|||
|
|
@ -531,7 +531,11 @@ func prProjectList(pr api.PullRequest) string {
|
|||
|
||||
projectNames := make([]string, 0, len(pr.ProjectCards.Nodes))
|
||||
for _, project := range pr.ProjectCards.Nodes {
|
||||
projectNames = append(projectNames, fmt.Sprintf("%s (%s)", project.Project.Name, project.Column.Name))
|
||||
colName := project.Column.Name
|
||||
if colName == "" {
|
||||
colName = "Awaiting triage"
|
||||
}
|
||||
projectNames = append(projectNames, fmt.Sprintf("%s (%s)", project.Project.Name, colName))
|
||||
}
|
||||
|
||||
list := strings.Join(projectNames, ", ")
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ func prCheckout(cmd *cobra.Command, args []string) error {
|
|||
|
||||
baseRemote, _ := remotes.FindByRepo(baseRepo.RepoOwner(), baseRepo.RepoName())
|
||||
// baseRemoteSpec is a repository URL or a remote name to be used in git fetch
|
||||
baseURLOrName := fmt.Sprintf("https://github.com/%s.git", ghrepo.FullName(baseRepo))
|
||||
baseURLOrName := formatRemoteURL(cmd, ghrepo.FullName(baseRepo))
|
||||
if baseRemote != nil {
|
||||
baseURLOrName = baseRemote.Name
|
||||
}
|
||||
|
|
@ -97,7 +97,7 @@ func prCheckout(cmd *cobra.Command, args []string) error {
|
|||
remote := baseURLOrName
|
||||
mergeRef := ref
|
||||
if pr.MaintainerCanModify {
|
||||
remote = fmt.Sprintf("https://github.com/%s/%s.git", pr.HeadRepositoryOwner.Login, pr.HeadRepository.Name)
|
||||
remote = formatRemoteURL(cmd, fmt.Sprintf("%s/%s", pr.HeadRepositoryOwner.Login, pr.HeadRepository.Name))
|
||||
mergeRef = fmt.Sprintf("refs/heads/%s", pr.HeadRefName)
|
||||
}
|
||||
if mc, err := git.Config(fmt.Sprintf("branch.%s.merge", newBranchName)); err != nil || mc == "" {
|
||||
|
|
|
|||
|
|
@ -250,8 +250,8 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
|||
// In either case, we want to add the head repo as a new git remote so we
|
||||
// can push to it.
|
||||
if headRemote == nil {
|
||||
// TODO: support non-HTTPS git remote URLs
|
||||
headRepoURL := fmt.Sprintf("https://github.com/%s.git", ghrepo.FullName(headRepo))
|
||||
headRepoURL := formatRemoteURL(cmd, ghrepo.FullName(headRepo))
|
||||
|
||||
// TODO: prevent clashes with another remote of a same name
|
||||
gitRemote, err := git.AddRemote("fork", headRepoURL)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -430,7 +430,7 @@ func TestPRView_Preview(t *testing.T) {
|
|||
`Reviewers: 2 \(Approved\), 3 \(Commented\), 1 \(Requested\)\n`,
|
||||
`Assignees: marseilles, monaco\n`,
|
||||
`Labels: one, two, three, four, five\n`,
|
||||
`Projects: Project 1 \(column A\), Project 2 \(column B\), Project 3 \(column C\)\n`,
|
||||
`Projects: Project 1 \(column A\), Project 2 \(column B\), Project 3 \(column C\), Project 4 \(Awaiting triage\)\n`,
|
||||
`Milestone: uluru\n`,
|
||||
`blueberries taste good`,
|
||||
`View this pull request on GitHub: https://github.com/OWNER/REPO/pull/12\n`,
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ func runClone(cloneURL string, args []string) (target string, err error) {
|
|||
func repoClone(cmd *cobra.Command, args []string) error {
|
||||
cloneURL := args[0]
|
||||
if !strings.Contains(cloneURL, ":") {
|
||||
cloneURL = fmt.Sprintf("https://github.com/%s.git", cloneURL)
|
||||
cloneURL = formatRemoteURL(cmd, cloneURL)
|
||||
}
|
||||
|
||||
var repo ghrepo.Interface
|
||||
|
|
@ -158,7 +158,7 @@ func repoClone(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
if parentRepo != nil {
|
||||
err := addUpstreamRemote(parentRepo, cloneDir)
|
||||
err := addUpstreamRemote(cmd, parentRepo, cloneDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -167,9 +167,8 @@ func repoClone(cmd *cobra.Command, args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func addUpstreamRemote(parentRepo ghrepo.Interface, cloneDir string) error {
|
||||
// TODO: support SSH remote URLs
|
||||
upstreamURL := fmt.Sprintf("https://github.com/%s.git", ghrepo.FullName(parentRepo))
|
||||
func addUpstreamRemote(cmd *cobra.Command, parentRepo ghrepo.Interface, cloneDir string) error {
|
||||
upstreamURL := formatRemoteURL(cmd, ghrepo.FullName(parentRepo))
|
||||
|
||||
cloneCmd := git.GitCommand("-C", cloneDir, "remote", "add", "-f", "upstream", upstreamURL)
|
||||
cloneCmd.Stdout = os.Stdout
|
||||
|
|
@ -267,14 +266,10 @@ func repoCreate(cmd *cobra.Command, args []string) error {
|
|||
fmt.Fprintln(out, repo.URL)
|
||||
}
|
||||
|
||||
remoteURL := repo.URL + ".git"
|
||||
remoteURL := formatRemoteURL(cmd, ghrepo.FullName(repo))
|
||||
|
||||
if projectDirErr == nil {
|
||||
// TODO: use git.AddRemote
|
||||
remoteAdd := git.GitCommand("remote", "add", "origin", remoteURL)
|
||||
remoteAdd.Stdout = os.Stdout
|
||||
remoteAdd.Stderr = os.Stderr
|
||||
err = run.PrepareCmd(remoteAdd).Run()
|
||||
_, err = git.AddRemote("origin", remoteURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -338,7 +333,7 @@ func repoFork(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("unable to create client: %w", err)
|
||||
}
|
||||
|
||||
var toFork ghrepo.Interface
|
||||
var repoToFork ghrepo.Interface
|
||||
inParent := false // whether or not we're forking the repo we're currently "in"
|
||||
if len(args) == 0 {
|
||||
baseRepo, err := determineBaseRepo(cmd, ctx)
|
||||
|
|
@ -346,7 +341,7 @@ func repoFork(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("unable to determine base repository: %w", err)
|
||||
}
|
||||
inParent = true
|
||||
toFork = baseRepo
|
||||
repoToFork = baseRepo
|
||||
} else {
|
||||
repoArg := args[0]
|
||||
|
||||
|
|
@ -356,14 +351,23 @@ func repoFork(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("did not understand argument: %w", err)
|
||||
}
|
||||
|
||||
toFork, err = ghrepo.FromURL(parsedURL)
|
||||
repoToFork, err = ghrepo.FromURL(parsedURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("did not understand argument: %w", err)
|
||||
}
|
||||
|
||||
} else if strings.HasPrefix(repoArg, "git@") {
|
||||
parsedURL, err := git.ParseURL(repoArg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("did not understand argument: %w", err)
|
||||
}
|
||||
repoToFork, err = ghrepo.FromURL(parsedURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("did not understand argument: %w", err)
|
||||
}
|
||||
} else {
|
||||
toFork = ghrepo.FromFullName(repoArg)
|
||||
if toFork.RepoName() == "" || toFork.RepoOwner() == "" {
|
||||
repoToFork = ghrepo.FromFullName(repoArg)
|
||||
if repoToFork.RepoName() == "" || repoToFork.RepoOwner() == "" {
|
||||
return fmt.Errorf("could not parse owner or repo name from %s", repoArg)
|
||||
}
|
||||
}
|
||||
|
|
@ -372,12 +376,12 @@ func repoFork(cmd *cobra.Command, args []string) error {
|
|||
greenCheck := utils.Green("✓")
|
||||
out := colorableOut(cmd)
|
||||
s := utils.Spinner(out)
|
||||
loading := utils.Gray("Forking ") + utils.Bold(utils.Gray(ghrepo.FullName(toFork))) + utils.Gray("...")
|
||||
loading := utils.Gray("Forking ") + utils.Bold(utils.Gray(ghrepo.FullName(repoToFork))) + utils.Gray("...")
|
||||
s.Suffix = " " + loading
|
||||
s.FinalMSG = utils.Gray(fmt.Sprintf("- %s\n", loading))
|
||||
s.Start()
|
||||
|
||||
forkedRepo, err := api.ForkRepo(apiClient, toFork)
|
||||
forkedRepo, err := api.ForkRepo(apiClient, repoToFork)
|
||||
if err != nil {
|
||||
s.Stop()
|
||||
return fmt.Errorf("failed to fork: %w", err)
|
||||
|
|
@ -437,7 +441,9 @@ func repoFork(cmd *cobra.Command, args []string) error {
|
|||
fmt.Fprintf(out, "%s Renamed %s remote to %s\n", greenCheck, utils.Bold(remoteName), utils.Bold(renameTarget))
|
||||
}
|
||||
|
||||
_, err = git.AddRemote(remoteName, forkedRepo.CloneURL)
|
||||
forkedRepoCloneURL := formatRemoteURL(cmd, ghrepo.FullName(forkedRepo))
|
||||
|
||||
_, err = git.AddRemote(remoteName, forkedRepoCloneURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add remote: %w", err)
|
||||
}
|
||||
|
|
@ -453,12 +459,13 @@ func repoFork(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
}
|
||||
if cloneDesired {
|
||||
cloneDir, err := runClone(forkedRepo.CloneURL, []string{})
|
||||
forkedRepoCloneURL := formatRemoteURL(cmd, ghrepo.FullName(forkedRepo))
|
||||
cloneDir, err := runClone(forkedRepoCloneURL, []string{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to clone fork: %w", err)
|
||||
}
|
||||
|
||||
err = addUpstreamRemote(toFork, cloneDir)
|
||||
err = addUpstreamRemote(cmd, repoToFork, cloneDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ func TestRepoFork_in_parent_yes(t *testing.T) {
|
|||
|
||||
expectedCmds := []string{
|
||||
"git remote rename origin upstream",
|
||||
"git remote add -f origin https://github.com/someone/repo.git",
|
||||
"git remote add -f origin https://github.com/someone/REPO.git",
|
||||
}
|
||||
|
||||
for x, cmd := range seenCmds {
|
||||
|
|
@ -185,8 +185,8 @@ func TestRepoFork_outside_yes(t *testing.T) {
|
|||
|
||||
eq(t, output.Stderr(), "")
|
||||
|
||||
eq(t, strings.Join(cs.Calls[0].Args, " "), "git clone https://github.com/someone/repo.git")
|
||||
eq(t, strings.Join(cs.Calls[1].Args, " "), "git -C repo remote add -f upstream https://github.com/OWNER/REPO.git")
|
||||
eq(t, strings.Join(cs.Calls[0].Args, " "), "git clone https://github.com/someone/REPO.git")
|
||||
eq(t, strings.Join(cs.Calls[1].Args, " "), "git -C REPO remote add -f upstream https://github.com/OWNER/REPO.git")
|
||||
|
||||
test.ExpectLines(t, output.String(),
|
||||
"Created fork someone/REPO",
|
||||
|
|
@ -218,8 +218,8 @@ func TestRepoFork_outside_survey_yes(t *testing.T) {
|
|||
|
||||
eq(t, output.Stderr(), "")
|
||||
|
||||
eq(t, strings.Join(cs.Calls[0].Args, " "), "git clone https://github.com/someone/repo.git")
|
||||
eq(t, strings.Join(cs.Calls[1].Args, " "), "git -C repo remote add -f upstream https://github.com/OWNER/REPO.git")
|
||||
eq(t, strings.Join(cs.Calls[0].Args, " "), "git clone https://github.com/someone/REPO.git")
|
||||
eq(t, strings.Join(cs.Calls[1].Args, " "), "git -C REPO remote add -f upstream https://github.com/OWNER/REPO.git")
|
||||
|
||||
test.ExpectLines(t, output.String(),
|
||||
"Created fork someone/REPO",
|
||||
|
|
@ -287,7 +287,7 @@ func TestRepoFork_in_parent_survey_yes(t *testing.T) {
|
|||
|
||||
expectedCmds := []string{
|
||||
"git remote rename origin upstream",
|
||||
"git remote add -f origin https://github.com/someone/repo.git",
|
||||
"git remote add -f origin https://github.com/someone/REPO.git",
|
||||
}
|
||||
|
||||
for x, cmd := range seenCmds {
|
||||
|
|
@ -499,7 +499,11 @@ func TestRepoCreate(t *testing.T) {
|
|||
{ "data": { "createRepository": {
|
||||
"repository": {
|
||||
"id": "REPOID",
|
||||
"url": "https://github.com/OWNER/REPO"
|
||||
"url": "https://github.com/OWNER/REPO",
|
||||
"name": "REPO",
|
||||
"owner": {
|
||||
"login": "OWNER"
|
||||
}
|
||||
}
|
||||
} } }
|
||||
`))
|
||||
|
|
@ -522,7 +526,7 @@ func TestRepoCreate(t *testing.T) {
|
|||
if seenCmd == nil {
|
||||
t.Fatal("expected a command to run")
|
||||
}
|
||||
eq(t, strings.Join(seenCmd.Args, " "), "git remote add origin https://github.com/OWNER/REPO.git")
|
||||
eq(t, strings.Join(seenCmd.Args, " "), "git remote add -f origin https://github.com/OWNER/REPO.git")
|
||||
|
||||
var reqBody struct {
|
||||
Query string
|
||||
|
|
@ -564,7 +568,11 @@ func TestRepoCreate_org(t *testing.T) {
|
|||
{ "data": { "createRepository": {
|
||||
"repository": {
|
||||
"id": "REPOID",
|
||||
"url": "https://github.com/ORG/REPO"
|
||||
"url": "https://github.com/ORG/REPO",
|
||||
"name": "REPO",
|
||||
"owner": {
|
||||
"login": "ORG"
|
||||
}
|
||||
}
|
||||
} } }
|
||||
`))
|
||||
|
|
@ -587,7 +595,7 @@ func TestRepoCreate_org(t *testing.T) {
|
|||
if seenCmd == nil {
|
||||
t.Fatal("expected a command to run")
|
||||
}
|
||||
eq(t, strings.Join(seenCmd.Args, " "), "git remote add origin https://github.com/ORG/REPO.git")
|
||||
eq(t, strings.Join(seenCmd.Args, " "), "git remote add -f origin https://github.com/ORG/REPO.git")
|
||||
|
||||
var reqBody struct {
|
||||
Query string
|
||||
|
|
@ -629,7 +637,11 @@ func TestRepoCreate_orgWithTeam(t *testing.T) {
|
|||
{ "data": { "createRepository": {
|
||||
"repository": {
|
||||
"id": "REPOID",
|
||||
"url": "https://github.com/ORG/REPO"
|
||||
"url": "https://github.com/ORG/REPO",
|
||||
"name": "REPO",
|
||||
"owner": {
|
||||
"login": "ORG"
|
||||
}
|
||||
}
|
||||
} } }
|
||||
`))
|
||||
|
|
@ -652,7 +664,7 @@ func TestRepoCreate_orgWithTeam(t *testing.T) {
|
|||
if seenCmd == nil {
|
||||
t.Fatal("expected a command to run")
|
||||
}
|
||||
eq(t, strings.Join(seenCmd.Args, " "), "git remote add origin https://github.com/ORG/REPO.git")
|
||||
eq(t, strings.Join(seenCmd.Args, " "), "git remote add -f origin https://github.com/ORG/REPO.git")
|
||||
|
||||
var reqBody struct {
|
||||
Query string
|
||||
|
|
|
|||
147
command/root.go
147
command/root.go
|
|
@ -51,6 +51,8 @@ func init() {
|
|||
// TODO:
|
||||
// RootCmd.PersistentFlags().BoolP("verbose", "V", false, "enable verbose output")
|
||||
|
||||
RootCmd.SetHelpFunc(rootHelpFunc)
|
||||
|
||||
RootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
|
||||
if err == pflag.ErrHelp {
|
||||
return err
|
||||
|
|
@ -74,12 +76,9 @@ func (fe FlagError) Unwrap() error {
|
|||
|
||||
// RootCmd is the entry point of command-line execution
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "gh",
|
||||
Use: "gh <command> <subcommand> [flags]",
|
||||
Short: "GitHub CLI",
|
||||
Long: `Work seamlessly with GitHub from the command line.
|
||||
|
||||
GitHub CLI is in early stages of development, and we'd love to hear your
|
||||
feedback at <https://forms.gle/umxd3h31c7aMQFKG7>`,
|
||||
Long: `Work seamlessly with GitHub from the command line.`,
|
||||
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
|
|
@ -111,20 +110,11 @@ func BasicClient() (*api.Client, error) {
|
|||
}
|
||||
opts = append(opts, api.AddHeader("User-Agent", fmt.Sprintf("GitHub CLI %s", Version)))
|
||||
|
||||
c, err := config.ParseDefaultConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if c, err := config.ParseDefaultConfig(); err == nil {
|
||||
if token, _ := c.Get(defaultHostname, "oauth_token"); token != "" {
|
||||
opts = append(opts, api.AddHeader("Authorization", fmt.Sprintf("token %s", token)))
|
||||
}
|
||||
}
|
||||
|
||||
token, err := c.Get(defaultHostname, "oauth_token")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if token == "" {
|
||||
return nil, fmt.Errorf("no oauth_token set in config")
|
||||
}
|
||||
|
||||
opts = append(opts, api.AddHeader("Authorization", fmt.Sprintf("token %s", token)))
|
||||
return api.NewClient(opts...), nil
|
||||
}
|
||||
|
||||
|
|
@ -142,12 +132,47 @@ var apiClientForContext = func(ctx context.Context) (*api.Client, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var opts []api.ClientOption
|
||||
if verbose := os.Getenv("DEBUG"); verbose != "" {
|
||||
opts = append(opts, apiVerboseLog())
|
||||
}
|
||||
|
||||
getAuthValue := func() string {
|
||||
return fmt.Sprintf("token %s", token)
|
||||
}
|
||||
|
||||
checkScopesFunc := func(appID string) error {
|
||||
if config.IsGitHubApp(appID) && utils.IsTerminal(os.Stdin) && utils.IsTerminal(os.Stderr) {
|
||||
newToken, loginHandle, err := config.AuthFlow("Notice: additional authorization required")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg, err := ctx.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = cfg.Set(defaultHostname, "oauth_token", newToken)
|
||||
_ = cfg.Set(defaultHostname, "user", loginHandle)
|
||||
// update config file on disk
|
||||
err = cfg.Write()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// update configuration in memory
|
||||
token = newToken
|
||||
config.AuthFlowComplete()
|
||||
} else {
|
||||
fmt.Fprintln(os.Stderr, "Warning: gh now requires the `read:org` OAuth scope.")
|
||||
fmt.Fprintln(os.Stderr, "Visit https://github.com/settings/tokens and edit your token to enable `read:org`")
|
||||
fmt.Fprintln(os.Stderr, "or generate a new token and paste it via `gh config set -h github.com oauth_token MYTOKEN`")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
opts = append(opts,
|
||||
api.AddHeader("Authorization", fmt.Sprintf("token %s", token)),
|
||||
api.CheckScopes("read:org", checkScopesFunc),
|
||||
api.AddHeaderFunc("Authorization", getAuthValue),
|
||||
api.AddHeader("User-Agent", fmt.Sprintf("GitHub CLI %s", Version)),
|
||||
// antiope-preview: Checks
|
||||
api.AddHeader("Accept", "application/vnd.github.antiope-preview+json"),
|
||||
|
|
@ -222,3 +247,87 @@ func determineBaseRepo(cmd *cobra.Command, ctx context.Context) (ghrepo.Interfac
|
|||
|
||||
return baseRepo, nil
|
||||
}
|
||||
|
||||
func rootHelpFunc(command *cobra.Command, s []string) {
|
||||
type helpEntry struct {
|
||||
Title string
|
||||
Body string
|
||||
}
|
||||
|
||||
coreCommandNames := []string{"issue", "pr", "repo"}
|
||||
var coreCommands []string
|
||||
var additionalCommands []string
|
||||
for _, c := range command.Commands() {
|
||||
if c.Short == "" {
|
||||
continue
|
||||
}
|
||||
s := " " + rpad(c.Name()+":", c.NamePadding()) + c.Short
|
||||
if includes(coreCommandNames, c.Name()) {
|
||||
coreCommands = append(coreCommands, s)
|
||||
} else {
|
||||
additionalCommands = append(additionalCommands, s)
|
||||
}
|
||||
}
|
||||
|
||||
helpEntries := []helpEntry{
|
||||
{
|
||||
"",
|
||||
command.Long},
|
||||
{"USAGE", command.Use},
|
||||
{"CORE COMMANDS", strings.Join(coreCommands, "\n")},
|
||||
{"ADDITIONAL COMMANDS", strings.Join(additionalCommands, "\n")},
|
||||
{"FLAGS", strings.TrimRight(command.LocalFlags().FlagUsages(), "\n")},
|
||||
{"EXAMPLES", `
|
||||
$ gh issue create
|
||||
$ gh repo clone
|
||||
$ gh pr checkout 321`},
|
||||
{"LEARN MORE", `
|
||||
Use "gh <command> <subcommand> --help" for more information about a command.
|
||||
Read the manual at <http://cli.github.com/manual>`},
|
||||
{"FEEDBACK", `
|
||||
Fill out our feedback form <https://forms.gle/umxd3h31c7aMQFKG7>
|
||||
Open an issue using “gh issue create -R cli/cli”`},
|
||||
}
|
||||
|
||||
out := colorableOut(command)
|
||||
for _, e := range helpEntries {
|
||||
if e.Title != "" {
|
||||
fmt.Fprintln(out, utils.Bold(e.Title))
|
||||
}
|
||||
fmt.Fprintln(out, strings.TrimLeft(e.Body, "\n")+"\n")
|
||||
}
|
||||
}
|
||||
|
||||
// rpad adds padding to the right of a string.
|
||||
func rpad(s string, padding int) string {
|
||||
template := fmt.Sprintf("%%-%ds ", padding)
|
||||
return fmt.Sprintf(template, s)
|
||||
}
|
||||
|
||||
func includes(a []string, s string) bool {
|
||||
for _, x := range a {
|
||||
if x == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func formatRemoteURL(cmd *cobra.Command, fullRepoName string) string {
|
||||
ctx := contextForCommand(cmd)
|
||||
|
||||
protocol := "https"
|
||||
cfg, err := ctx.Config()
|
||||
if err != nil {
|
||||
fmt.Fprintf(colorableErr(cmd), "%s failed to load config: %s. using defaults\n", utils.Yellow("!"), err)
|
||||
} else {
|
||||
cfgProtocol, _ := cfg.Get(defaultHostname, "git_protocol")
|
||||
protocol = cfgProtocol
|
||||
}
|
||||
|
||||
if protocol == "ssh" {
|
||||
return fmt.Sprintf("git@%s:%s.git", defaultHostname, fullRepoName)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("https://%s/%s.git", defaultHostname, fullRepoName)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,3 +40,22 @@ func TestChangelogURL(t *testing.T) {
|
|||
t.Errorf("expected %s to create url %s but got %s", tag, url, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteURLFormatting_no_config(t *testing.T) {
|
||||
initBlankContext("", "OWNER/REPO", "master")
|
||||
result := formatRemoteURL(repoForkCmd, "OWNER/REPO")
|
||||
eq(t, result, "https://github.com/OWNER/REPO.git")
|
||||
}
|
||||
|
||||
func TestRemoteURLFormatting_ssh_config(t *testing.T) {
|
||||
cfg := `---
|
||||
hosts:
|
||||
github.com:
|
||||
user: OWNER
|
||||
oauth_token: MUSTBEHIGHCUZIMATOKEN
|
||||
git_protocol: ssh
|
||||
`
|
||||
initBlankContext(cfg, "OWNER/REPO", "master")
|
||||
result := formatRemoteURL(repoForkCmd, "OWNER/REPO")
|
||||
eq(t, result, "git@github.com:OWNER/REPO.git")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package command
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/cli/cli/pkg/githubtemplate"
|
||||
|
|
@ -82,6 +83,16 @@ func selectTemplate(templatePaths []string) (string, error) {
|
|||
}
|
||||
|
||||
func titleBodySurvey(cmd *cobra.Command, providedTitle, providedBody string, defs defaults, templatePaths []string) (*titleBody, error) {
|
||||
editorCommand := os.Getenv("GH_EDITOR")
|
||||
if editorCommand == "" {
|
||||
ctx := contextForCommand(cmd)
|
||||
cfg, err := ctx.Config()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read config: %w", err)
|
||||
}
|
||||
editorCommand, _ = cfg.Get(defaultHostname, "editor")
|
||||
}
|
||||
|
||||
var inProgress titleBody
|
||||
inProgress.Title = defs.Title
|
||||
templateContents := ""
|
||||
|
|
@ -109,6 +120,7 @@ func titleBodySurvey(cmd *cobra.Command, providedTitle, providedBody string, def
|
|||
bodyQuestion := &survey.Question{
|
||||
Name: "body",
|
||||
Prompt: &surveyext.GhEditor{
|
||||
EditorCommand: editorCommand,
|
||||
Editor: &survey.Editor{
|
||||
Message: "Body",
|
||||
FileName: "*.md",
|
||||
|
|
|
|||
|
|
@ -1,27 +1,17 @@
|
|||
# Releasing
|
||||
|
||||
## Test Locally
|
||||
|
||||
`go test ./...`
|
||||
|
||||
## Push new docs
|
||||
|
||||
build docs locally: `make site`
|
||||
|
||||
build and push docs to production: `make site-docs`
|
||||
|
||||
## Release locally for debugging
|
||||
|
||||
A local release can be created for testing without creating anything official on the release page.
|
||||
|
||||
1. `env GH_OAUTH_CLIENT_SECRET= GH_OAUTH_CLIENT_ID= goreleaser --skip-validate --skip-publish --rm-dist`
|
||||
2. Check and test files in `dist/`
|
||||
|
||||
## Release to production
|
||||
|
||||
This can all be done from your local terminal.
|
||||
|
||||
1. `git tag 'vVERSION_NUMBER' # example git tag 'v0.0.1'`
|
||||
2. `git push origin vVERSION_NUMBER`
|
||||
3. Wait a few minutes for the build to run and CI to pass. Look at the [actions tab](https://github.com/cli/cli/actions) to check the progress.
|
||||
4. Go to <https://github.com/cli/cli/releases> and look at the release
|
||||
1. `git tag v1.2.3`
|
||||
2. `git push origin v1.2.3`
|
||||
3. Wait a few minutes for the build to run <https://github.com/cli/cli/actions>
|
||||
4. Check <https://github.com/cli/cli/releases>
|
||||
|
||||
## Release locally for debugging
|
||||
|
||||
A local release can be created for testing without creating anything official on the release page.
|
||||
|
||||
1. `goreleaser --skip-validate --skip-publish --rm-dist`
|
||||
2. Check and test files in `dist/`
|
||||
|
|
|
|||
|
|
@ -79,9 +79,19 @@ func AddRemote(name, u string) (*Remote, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
urlParsed, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var urlParsed *url.URL
|
||||
if strings.HasPrefix(u, "https") {
|
||||
urlParsed, err = url.Parse(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
urlParsed, err = ParseURL(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return &Remote{
|
||||
|
|
|
|||
|
|
@ -24,9 +24,14 @@ var (
|
|||
oauthClientSecret = "34ddeff2b558a23d38fba8a6de74f086ede1cc0b"
|
||||
)
|
||||
|
||||
// TODO: have a conversation about whether this belongs in the "context" package
|
||||
// FIXME: make testable
|
||||
func setupConfigFile(filename string) (Config, error) {
|
||||
// IsGitHubApp reports whether an OAuth app is "GitHub CLI" or "GitHub CLI (dev)"
|
||||
func IsGitHubApp(id string) bool {
|
||||
// this intentionally doesn't use `oauthClientID` because that is a variable
|
||||
// that can potentially be changed at build time via GH_OAUTH_CLIENT_ID
|
||||
return id == "178c6fc778ccc68e1d6a" || id == "4d747ba5675d5d66553f"
|
||||
}
|
||||
|
||||
func AuthFlow(notice string) (string, string, error) {
|
||||
var verboseStream io.Writer
|
||||
if strings.Contains(os.Getenv("DEBUG"), "oauth") {
|
||||
verboseStream = os.Stderr
|
||||
|
|
@ -36,21 +41,37 @@ func setupConfigFile(filename string) (Config, error) {
|
|||
Hostname: oauthHost,
|
||||
ClientID: oauthClientID,
|
||||
ClientSecret: oauthClientSecret,
|
||||
Scopes: []string{"repo", "read:org"},
|
||||
WriteSuccessHTML: func(w io.Writer) {
|
||||
fmt.Fprintln(w, oauthSuccessPage)
|
||||
},
|
||||
VerboseStream: verboseStream,
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "Notice: authentication required")
|
||||
fmt.Fprintln(os.Stderr, notice)
|
||||
fmt.Fprintf(os.Stderr, "Press Enter to open %s in your browser... ", flow.Hostname)
|
||||
_ = waitForEnter(os.Stdin)
|
||||
token, err := flow.ObtainAccessToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
userLogin, err := getViewer(token)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return token, userLogin, nil
|
||||
}
|
||||
|
||||
func AuthFlowComplete() {
|
||||
fmt.Fprintln(os.Stderr, "Authentication complete. Press Enter to continue... ")
|
||||
_ = waitForEnter(os.Stdin)
|
||||
}
|
||||
|
||||
// FIXME: make testable
|
||||
func setupConfigFile(filename string) (Config, error) {
|
||||
token, userLogin, err := AuthFlow("Notice: authentication required")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -61,9 +82,9 @@ func setupConfigFile(filename string) (Config, error) {
|
|||
}
|
||||
|
||||
yamlHosts := map[string]map[string]string{}
|
||||
yamlHosts[flow.Hostname] = map[string]string{}
|
||||
yamlHosts[flow.Hostname]["user"] = userLogin
|
||||
yamlHosts[flow.Hostname]["oauth_token"] = token
|
||||
yamlHosts[oauthHost] = map[string]string{}
|
||||
yamlHosts[oauthHost]["user"] = userLogin
|
||||
yamlHosts[oauthHost]["oauth_token"] = token
|
||||
|
||||
defaultConfig := yamlConfig{
|
||||
Hosts: yamlHosts,
|
||||
|
|
@ -84,14 +105,9 @@ func setupConfigFile(filename string) (Config, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n, err := cfgFile.Write(yamlData)
|
||||
if err == nil && n < len(yamlData) {
|
||||
err = io.ErrShortWrite
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
fmt.Fprintln(os.Stderr, "Authentication complete. Press Enter to continue... ")
|
||||
_ = waitForEnter(os.Stdin)
|
||||
_, err = cfgFile.Write(yamlData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO cleaner error handling? this "should" always work given that we /just/ wrote the file...
|
||||
|
|
|
|||
|
|
@ -18,25 +18,34 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
bom = []byte{0xef, 0xbb, 0xbf}
|
||||
editor = "nano" // EXTENDED to switch from vim as a default editor
|
||||
bom = []byte{0xef, 0xbb, 0xbf}
|
||||
defaultEditor = "nano" // EXTENDED to switch from vim as a default editor
|
||||
)
|
||||
|
||||
func init() {
|
||||
if runtime.GOOS == "windows" {
|
||||
editor = "notepad"
|
||||
defaultEditor = "notepad"
|
||||
} else if g := os.Getenv("GIT_EDITOR"); g != "" {
|
||||
editor = g
|
||||
defaultEditor = g
|
||||
} else if v := os.Getenv("VISUAL"); v != "" {
|
||||
editor = v
|
||||
defaultEditor = v
|
||||
} else if e := os.Getenv("EDITOR"); e != "" {
|
||||
editor = e
|
||||
defaultEditor = e
|
||||
}
|
||||
}
|
||||
|
||||
// EXTENDED to enable different prompting behavior
|
||||
type GhEditor struct {
|
||||
*survey.Editor
|
||||
EditorCommand string
|
||||
}
|
||||
|
||||
func (e *GhEditor) editorCommand() string {
|
||||
if e.EditorCommand == "" {
|
||||
return defaultEditor
|
||||
}
|
||||
|
||||
return e.EditorCommand
|
||||
}
|
||||
|
||||
// EXTENDED to change prompt text
|
||||
|
|
@ -49,17 +58,17 @@ var EditorQuestionTemplate = `
|
|||
{{- else }}
|
||||
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}}
|
||||
{{- if and .Default (not .HideDefault)}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
|
||||
{{- color "cyan"}}[(e) to launch {{ .EditorName }}, enter to skip] {{color "reset"}}
|
||||
{{- color "cyan"}}[(e) to launch {{ .EditorCommand }}, enter to skip] {{color "reset"}}
|
||||
{{- end}}`
|
||||
|
||||
// EXTENDED to pass editor name (to use in prompt)
|
||||
type EditorTemplateData struct {
|
||||
survey.Editor
|
||||
EditorName string
|
||||
Answer string
|
||||
ShowAnswer bool
|
||||
ShowHelp bool
|
||||
Config *survey.PromptConfig
|
||||
EditorCommand string
|
||||
Answer string
|
||||
ShowAnswer bool
|
||||
ShowHelp bool
|
||||
Config *survey.PromptConfig
|
||||
}
|
||||
|
||||
// EXTENDED to augment prompt text and keypress handling
|
||||
|
|
@ -68,9 +77,9 @@ func (e *GhEditor) prompt(initialValue string, config *survey.PromptConfig) (int
|
|||
EditorQuestionTemplate,
|
||||
// EXTENDED to support printing editor in prompt
|
||||
EditorTemplateData{
|
||||
Editor: *e.Editor,
|
||||
EditorName: filepath.Base(editor),
|
||||
Config: config,
|
||||
Editor: *e.Editor,
|
||||
EditorCommand: filepath.Base(e.editorCommand()),
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -109,10 +118,10 @@ func (e *GhEditor) prompt(initialValue string, config *survey.PromptConfig) (int
|
|||
EditorQuestionTemplate,
|
||||
EditorTemplateData{
|
||||
// EXTENDED to support printing editor in prompt
|
||||
Editor: *e.Editor,
|
||||
EditorName: filepath.Base(editor),
|
||||
ShowHelp: true,
|
||||
Config: config,
|
||||
Editor: *e.Editor,
|
||||
EditorCommand: filepath.Base(e.editorCommand()),
|
||||
ShowHelp: true,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -155,7 +164,7 @@ func (e *GhEditor) prompt(initialValue string, config *survey.PromptConfig) (int
|
|||
|
||||
stdio := e.Stdio()
|
||||
|
||||
args, err := shellquote.Split(editor)
|
||||
args, err := shellquote.Split(e.editorCommand())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,14 @@
|
|||
"column": {
|
||||
"name": "column C"
|
||||
}
|
||||
},
|
||||
{
|
||||
"project": {
|
||||
"name": "Project 4"
|
||||
},
|
||||
"column": {
|
||||
"name": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"totalcount": 3
|
||||
|
|
|
|||
|
|
@ -99,6 +99,14 @@
|
|||
"column": {
|
||||
"name": "column C"
|
||||
}
|
||||
},
|
||||
{
|
||||
"project": {
|
||||
"name": "Project 4"
|
||||
},
|
||||
"column": {
|
||||
"name": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"totalcount": 3
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue