Merge pull request #11486 from masonmcelvain/browse-blame

feat(browse): add blame flag
This commit is contained in:
Kynan Ware 2026-03-03 19:17:35 -07:00 committed by GitHub
commit d6d0cb741c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 95 additions and 0 deletions

View file

@ -44,6 +44,7 @@ type BrowseOptions struct {
SettingsFlag bool
WikiFlag bool
ActionsFlag bool
BlameFlag bool
NoBrowserFlag bool
HasRepoOverride bool
}
@ -91,6 +92,9 @@ func NewCmdBrowse(f *cmdutil.Factory, runF func(*BrowseOptions) error) *cobra.Co
# Open main.go at line 312
$ gh browse main.go:312
# Open blame view for main.go at line 312
$ gh browse main.go:312 --blame
# Open main.go with the repository at head of bug-fix branch
$ gh browse main.go --branch bug-fix
@ -141,6 +145,10 @@ func NewCmdBrowse(f *cmdutil.Factory, runF func(*BrowseOptions) error) *cobra.Co
return err
}
if opts.BlameFlag && opts.SelectorArg == "" {
return cmdutil.FlagErrorf("`--blame` requires a file path argument")
}
if (isNumber(opts.SelectorArg) || isCommit(opts.SelectorArg)) && (opts.Branch != "" || opts.Commit != "") {
return cmdutil.FlagErrorf("%q is an invalid argument when using `--branch` or `--commit`", opts.SelectorArg)
}
@ -163,6 +171,7 @@ func NewCmdBrowse(f *cmdutil.Factory, runF func(*BrowseOptions) error) *cobra.Co
cmd.Flags().BoolVarP(&opts.WikiFlag, "wiki", "w", false, "Open repository wiki")
cmd.Flags().BoolVarP(&opts.ActionsFlag, "actions", "a", false, "Open repository actions")
cmd.Flags().BoolVarP(&opts.SettingsFlag, "settings", "s", false, "Open repository settings")
cmd.Flags().BoolVar(&opts.BlameFlag, "blame", false, "Open blame view for a file")
cmd.Flags().BoolVarP(&opts.NoBrowserFlag, "no-browser", "n", false, "Print destination URL instead of opening the browser")
cmd.Flags().StringVarP(&opts.Commit, "commit", "c", "", "Select another commit by passing in the commit SHA, default is the last commit")
cmd.Flags().StringVarP(&opts.Branch, "branch", "b", "", "Select another branch by passing in the branch name")
@ -272,9 +281,16 @@ func parseSection(baseRepo ghrepo.Interface, opts *BrowseOptions) (string, error
} else {
rangeFragment = fmt.Sprintf("L%d", rangeStart)
}
if opts.BlameFlag {
return fmt.Sprintf("blame/%s/%s#%s", escapePath(ref), escapePath(filePath), rangeFragment), nil
}
return fmt.Sprintf("blob/%s/%s?plain=1#%s", escapePath(ref), escapePath(filePath), rangeFragment), nil
}
if opts.BlameFlag {
return fmt.Sprintf("blame/%s/%s", escapePath(ref), escapePath(filePath)), nil
}
return strings.TrimSuffix(fmt.Sprintf("tree/%s/%s", escapePath(ref), escapePath(filePath)), "/"), nil
}

View file

@ -207,6 +207,29 @@ func TestNewCmdBrowse(t *testing.T) {
cli: "de07febc26e19000f8c9e821207f3bc34a3c8038 --commit=12a4",
wantsErr: true,
},
{
name: "blame flag",
cli: "main.go --blame",
wants: BrowseOptions{
BlameFlag: true,
SelectorArg: "main.go",
},
wantsErr: false,
},
{
name: "blame flag without file argument",
cli: "--blame",
wantsErr: true,
},
{
name: "blame flag with line number",
cli: "main.go:312 --blame",
wants: BrowseOptions{
BlameFlag: true,
SelectorArg: "main.go:312",
},
wantsErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -239,6 +262,7 @@ func TestNewCmdBrowse(t *testing.T) {
assert.Equal(t, tt.wants.SettingsFlag, opts.SettingsFlag)
assert.Equal(t, tt.wants.ActionsFlag, opts.ActionsFlag)
assert.Equal(t, tt.wants.Commit, opts.Commit)
assert.Equal(t, tt.wants.BlameFlag, opts.BlameFlag)
})
}
}
@ -595,6 +619,61 @@ func Test_runBrowse(t *testing.T) {
expectedURL: "https://github.com/bchadwic/test/tree/trunk/77507cd94ccafcf568f8560cfecde965fcfa63e7.txt",
wantsErr: false,
},
{
name: "file with blame flag",
opts: BrowseOptions{
SelectorArg: "path/to/file.txt",
BlameFlag: true,
},
baseRepo: ghrepo.New("owner", "repo"),
defaultBranch: "main",
expectedURL: "https://github.com/owner/repo/blame/main/path/to/file.txt",
wantsErr: false,
},
{
name: "file with blame flag and line number",
opts: BrowseOptions{
SelectorArg: "path/to/file.txt:42",
BlameFlag: true,
},
baseRepo: ghrepo.New("owner", "repo"),
defaultBranch: "main",
expectedURL: "https://github.com/owner/repo/blame/main/path/to/file.txt#L42",
wantsErr: false,
},
{
name: "file with blame flag and line range",
opts: BrowseOptions{
SelectorArg: "path/to/file.txt:10-20",
BlameFlag: true,
},
baseRepo: ghrepo.New("owner", "repo"),
defaultBranch: "main",
expectedURL: "https://github.com/owner/repo/blame/main/path/to/file.txt#L10-L20",
wantsErr: false,
},
{
name: "file with blame flag and branch",
opts: BrowseOptions{
SelectorArg: "main.go:100",
BlameFlag: true,
Branch: "feature-branch",
},
baseRepo: ghrepo.New("owner", "repo"),
expectedURL: "https://github.com/owner/repo/blame/feature-branch/main.go#L100",
wantsErr: false,
},
{
name: "file with blame flag and commit",
opts: BrowseOptions{
SelectorArg: "src/app.js:50",
BlameFlag: true,
Commit: "abc123",
},
baseRepo: ghrepo.New("owner", "repo"),
expectedURL: "https://github.com/owner/repo/blame/abc123/src/app.js#L50",
wantsErr: false,
},
}
for _, tt := range tests {