X Tutup
Skip to content

Commit dcf3f60

Browse files
author
Nate Smith
authored
Merge pull request cli#4007 from bchadwic/relative-path
gh browse relative path enhancement
2 parents 6f34e4a + 70c78f2 commit dcf3f60

File tree

3 files changed

+134
-9
lines changed

3 files changed

+134
-9
lines changed

git/git.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,21 @@ func ToplevelDir() (string, error) {
366366

367367
}
368368

369+
func PathFromRepoRoot() string {
370+
showCmd, err := GitCommand("rev-parse", "--show-prefix")
371+
if err != nil {
372+
return ""
373+
}
374+
output, err := run.PrepareCmd(showCmd).Output()
375+
if err != nil {
376+
return ""
377+
}
378+
if path := firstLine(output); path != "" {
379+
return path[:len(path)-1]
380+
}
381+
return ""
382+
}
383+
369384
func outputLines(output []byte) []string {
370385
lines := strings.TrimSuffix(string(output), "\n")
371386
return strings.Split(lines, "\n")

pkg/cmd/browse/browse.go

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package browse
33
import (
44
"fmt"
55
"net/http"
6+
"path/filepath"
67
"strconv"
78
"strings"
89

@@ -21,10 +22,11 @@ type browser interface {
2122
}
2223

2324
type BrowseOptions struct {
24-
BaseRepo func() (ghrepo.Interface, error)
25-
Browser browser
26-
HttpClient func() (*http.Client, error)
27-
IO *iostreams.IOStreams
25+
BaseRepo func() (ghrepo.Interface, error)
26+
Browser browser
27+
HttpClient func() (*http.Client, error)
28+
IO *iostreams.IOStreams
29+
PathFromRepoRoot func() string
2830

2931
SelectorArg string
3032

@@ -38,9 +40,10 @@ type BrowseOptions struct {
3840

3941
func NewCmdBrowse(f *cmdutil.Factory, runF func(*BrowseOptions) error) *cobra.Command {
4042
opts := &BrowseOptions{
41-
Browser: f.Browser,
42-
HttpClient: f.HttpClient,
43-
IO: f.IOStreams,
43+
Browser: f.Browser,
44+
HttpClient: f.HttpClient,
45+
IO: f.IOStreams,
46+
PathFromRepoRoot: git.PathFromRepoRoot,
4447
}
4548

4649
cmd := &cobra.Command{
@@ -158,7 +161,7 @@ func parseSection(baseRepo ghrepo.Interface, opts *BrowseOptions) (string, error
158161
return fmt.Sprintf("issues/%s", opts.SelectorArg), nil
159162
}
160163

161-
filePath, rangeStart, rangeEnd, err := parseFile(opts.SelectorArg)
164+
filePath, rangeStart, rangeEnd, err := parseFile(*opts, opts.SelectorArg)
162165
if err != nil {
163166
return "", err
164167
}
@@ -188,14 +191,22 @@ func parseSection(baseRepo ghrepo.Interface, opts *BrowseOptions) (string, error
188191
return fmt.Sprintf("tree/%s/%s", branchName, filePath), nil
189192
}
190193

191-
func parseFile(f string) (p string, start int, end int, err error) {
194+
func parseFile(opts BrowseOptions, f string) (p string, start int, end int, err error) {
192195
parts := strings.SplitN(f, ":", 3)
193196
if len(parts) > 2 {
194197
err = fmt.Errorf("invalid file argument: %q", f)
195198
return
196199
}
197200

198201
p = parts[0]
202+
if !filepath.IsAbs(p) {
203+
p = filepath.Clean(filepath.Join(opts.PathFromRepoRoot(), p))
204+
// Ensure that a path using \ can be used in a URL
205+
p = strings.ReplaceAll(p, "\\", "/")
206+
if p == "." || strings.HasPrefix(p, "..") {
207+
p = ""
208+
}
209+
}
199210
if len(parts) < 2 {
200211
return
201212
}

pkg/cmd/browse/browse_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import (
44
"fmt"
55
"net/http"
66
"os"
7+
"path/filepath"
78
"testing"
89

10+
"github.com/cli/cli/v2/git"
911
"github.com/cli/cli/v2/internal/ghrepo"
1012
"github.com/cli/cli/v2/pkg/cmdutil"
1113
"github.com/cli/cli/v2/pkg/httpmock"
@@ -152,6 +154,7 @@ func setGitDir(t *testing.T, dir string) {
152154
}
153155

154156
func Test_runBrowse(t *testing.T) {
157+
s := string(os.PathSeparator)
155158
setGitDir(t, "../../../git/fixtures/simple.git")
156159
tests := []struct {
157160
name string
@@ -334,6 +337,32 @@ func Test_runBrowse(t *testing.T) {
334337
wantsErr: false,
335338
expectedURL: "https://github.com/vilmibm/gh-user-status/tree/6f1a2405cace1633d89a79c74c65f22fe78f9659/main.go",
336339
},
340+
{
341+
name: "relative path from browse_test.go",
342+
opts: BrowseOptions{
343+
SelectorArg: filepath.Join(".", "browse_test.go"),
344+
PathFromRepoRoot: func() string {
345+
return "pkg/cmd/browse/"
346+
},
347+
},
348+
baseRepo: ghrepo.New("bchadwic", "gh-graph"),
349+
defaultBranch: "trunk",
350+
expectedURL: "https://github.com/bchadwic/gh-graph/tree/trunk/pkg/cmd/browse/browse_test.go",
351+
wantsErr: false,
352+
},
353+
{
354+
name: "relative path to file in parent folder from browse_test.go",
355+
opts: BrowseOptions{
356+
SelectorArg: ".." + s + "pr",
357+
PathFromRepoRoot: func() string {
358+
return "pkg/cmd/browse/"
359+
},
360+
},
361+
baseRepo: ghrepo.New("bchadwic", "gh-graph"),
362+
defaultBranch: "trunk",
363+
expectedURL: "https://github.com/bchadwic/gh-graph/tree/trunk/pkg/cmd/pr",
364+
wantsErr: false,
365+
},
337366
}
338367

339368
for _, tt := range tests {
@@ -356,6 +385,9 @@ func Test_runBrowse(t *testing.T) {
356385
return &http.Client{Transport: &reg}, nil
357386
}
358387
opts.Browser = &browser
388+
if opts.PathFromRepoRoot == nil {
389+
opts.PathFromRepoRoot = git.PathFromRepoRoot
390+
}
359391

360392
err := runBrowse(&opts)
361393
if tt.wantsErr {
@@ -376,3 +408,70 @@ func Test_runBrowse(t *testing.T) {
376408
})
377409
}
378410
}
411+
412+
func Test_parsePathFromFileArg(t *testing.T) {
413+
s := string(os.PathSeparator)
414+
tests := []struct {
415+
name string
416+
fileArg string
417+
expectedPath string
418+
}{
419+
{
420+
name: "go to parent folder",
421+
fileArg: ".." + s,
422+
expectedPath: "pkg/cmd",
423+
},
424+
{
425+
name: "current folder",
426+
fileArg: ".",
427+
expectedPath: "pkg/cmd/browse",
428+
},
429+
{
430+
name: "current folder (alternative)",
431+
fileArg: "." + s,
432+
expectedPath: "pkg/cmd/browse",
433+
},
434+
{
435+
name: "file that starts with '.'",
436+
fileArg: ".gitignore",
437+
expectedPath: "pkg/cmd/browse/.gitignore",
438+
},
439+
{
440+
name: "file in current folder",
441+
fileArg: filepath.Join(".", "browse.go"),
442+
expectedPath: "pkg/cmd/browse/browse.go",
443+
},
444+
{
445+
name: "file within parent folder",
446+
fileArg: filepath.Join("..", "browse.go"),
447+
expectedPath: "pkg/cmd/browse.go",
448+
},
449+
{
450+
name: "file within parent folder uncleaned",
451+
fileArg: filepath.Join("..", ".") + s + s + s + "browse.go",
452+
expectedPath: "pkg/cmd/browse.go",
453+
},
454+
{
455+
name: "different path from root directory",
456+
fileArg: filepath.Join("..", "..", "..", "internal/build/build.go"),
457+
expectedPath: "internal/build/build.go",
458+
},
459+
{
460+
name: "go out of repository",
461+
fileArg: filepath.Join("..", "..", "..", "..", "..", "..") + s + "",
462+
expectedPath: "",
463+
},
464+
{
465+
name: "go to root of repository",
466+
fileArg: filepath.Join("..", "..", "..") + s + "",
467+
expectedPath: "",
468+
},
469+
}
470+
for _, tt := range tests {
471+
path, _, _, _ := parseFile(BrowseOptions{
472+
PathFromRepoRoot: func() string {
473+
return "pkg/cmd/browse/"
474+
}}, tt.fileArg)
475+
assert.Equal(t, tt.expectedPath, path, tt.name)
476+
}
477+
}

0 commit comments

Comments
 (0)
X Tutup