Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs-master/Config.md
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,7 @@ keybinding:
createPullRequest: o
viewPullRequestOptions: O
copyPullRequestURL: <c-y>
copyBranchURL: "y"
checkoutBranchByName: c
forceCheckoutBranch: F
checkoutPreviousBranch: '-'
Expand Down
1 change: 1 addition & 0 deletions docs-master/keybindings/Keybindings_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` o `` | Create pull request | |
| `` O `` | View create pull request options | |
| `` <c-y> `` | Copy pull request URL to clipboard | |
| `` y `` | Copy branch URL to clipboard | |
| `` c `` | Checkout by name | Checkout by name. In the input box you can enter '-' to switch to the previous branch. |
| `` - `` | Checkout previous branch | |
| `` F `` | Force checkout | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
Expand Down
1 change: 1 addition & 0 deletions docs-master/keybindings/Keybindings_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` o `` | プルリクエストを作成 | |
| `` O `` | プルリクエスト作成オプションを表示 | |
| `` <c-y> `` | プルリクエストURLをクリップボードにコピー | |
| `` y `` | Copy branch URL to clipboard | |
| `` c `` | 名前でチェックアウト | 名前でチェックアウトします。入力ボックスに「-」を入力すると、最後のブランチをチェックアウトすることができます。 |
| `` - `` | 直前のブランチにチェックアウト | |
| `` F `` | 強制チェックアウト | 選択したブランチを強制的にチェックアウトします。これにより、選択したブランチをチェックアウトする前にワーキングディレクトリ内のすべてのローカル変更が破棄されます。 |
Expand Down
1 change: 1 addition & 0 deletions docs-master/keybindings/Keybindings_ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` o `` | 풀 리퀘스트 생성 | |
| `` O `` | 풀 리퀘스트 생성 옵션 | |
| `` <c-y> `` | 풀 리퀘스트 URL을 클립보드에 복사 | |
| `` y `` | Copy branch URL to clipboard | |
| `` c `` | 이름으로 체크아웃 | Checkout by name. In the input box you can enter '-' to switch to the previous branch. |
| `` - `` | Checkout previous branch | |
| `` F `` | 강제 체크아웃 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
Expand Down
1 change: 1 addition & 0 deletions docs-master/keybindings/Keybindings_nl.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` o `` | Maak een pull-request | |
| `` O `` | Bekijk opties voor pull-aanvraag | |
| `` <c-y> `` | Kopieer de URL van het pull-verzoek naar het klembord | |
| `` y `` | Copy branch URL to clipboard | |
| `` c `` | Uitchecken bij naam | Checkout by name. In the input box you can enter '-' to switch to the previous branch. |
| `` - `` | Checkout previous branch | |
| `` F `` | Forceer checkout | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
Expand Down
1 change: 1 addition & 0 deletions docs-master/keybindings/Keybindings_pl.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
| `` o `` | Utwórz żądanie ściągnięcia | |
| `` O `` | Zobacz opcje tworzenia pull requesta | |
| `` <c-y> `` | Kopiuj adres URL żądania ściągnięcia do schowka | |
| `` y `` | Copy branch URL to clipboard | |
| `` c `` | Przełącz według nazwy | Przełącz według nazwy. W polu wprowadzania możesz wpisać '-' aby przełączyć się na ostatnią gałąź. |
| `` - `` | Checkout previous branch | |
| `` F `` | Wymuś przełączenie | Wymuś przełączenie wybranej gałęzi. To spowoduje odrzucenie wszystkich lokalnych zmian w drzewie roboczym przed przełączeniem na wybraną gałąź. |
Expand Down
1 change: 1 addition & 0 deletions docs-master/keybindings/Keybindings_pt.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` o `` | Create pull request | |
| `` O `` | View create pull request options | |
| `` <c-y> `` | Copiar URL do pull request para área de transferência | |
| `` y `` | Copy branch URL to clipboard | |
| `` c `` | Checar por nome | Checar por nome. Na caixa de entrada você pode inserir '-' para trocar para a última branch |
| `` - `` | Checkout da branch anterior | |
| `` F `` | Forçar checagem | Forçar checagem da branch selecionada. Isso irá descartar todas as mudanças no seu diretório de trabalho antes cheque a branch selecionada |
Expand Down
1 change: 1 addition & 0 deletions docs-master/keybindings/Keybindings_ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ _Связки клавиш_
| `` o `` | Создать запрос на принятие изменений | |
| `` O `` | Создать параметры запроса принятие изменений | |
| `` <c-y> `` | Скопировать URL запроса на принятие изменений в буфер обмена | |
| `` y `` | Copy branch URL to clipboard | |
| `` c `` | Переключить по названию | Checkout by name. In the input box you can enter '-' to switch to the previous branch. |
| `` - `` | Checkout previous branch | |
| `` F `` | Принудительное переключение | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
Expand Down
1 change: 1 addition & 0 deletions docs-master/keybindings/Keybindings_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` o `` | 创建拉取请求 | |
| `` O `` | 创建拉取请求选项 | |
| `` <c-y> `` | 复制拉取请求 URL 到剪贴板 | |
| `` y `` | Copy branch URL to clipboard | |
| `` c `` | 按名称检出 | 按名称检出。在输入框中,您可以输入'-' 来切换到最后一个分支。 |
| `` - `` | 签出上一个分支 | |
| `` F `` | 强制检出 | 强制检出所选分支。这将在检出所选分支之前放弃工作目录中的所有本地更改。 |
Expand Down
1 change: 1 addition & 0 deletions docs-master/keybindings/Keybindings_zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
| `` o `` | 建立拉取請求 | |
| `` O `` | 建立拉取請求選項 | |
| `` <c-y> `` | 複製拉取請求的 URL 到剪貼板 | |
| `` y `` | Copy branch URL to clipboard | |
| `` c `` | 根據名稱檢出 | Checkout by name. In the input box you can enter '-' to switch to the previous branch. |
| `` - `` | Checkout previous branch | |
| `` F `` | 強制檢出 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
Expand Down
7 changes: 7 additions & 0 deletions pkg/commands/hosting_service/definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var githubServiceDef = ServiceDefinition{
pullRequestURLIntoDefaultBranch: "/compare/{{.From}}?expand=1",
pullRequestURLIntoTargetBranch: "/compare/{{.To}}...{{.From}}?expand=1",
commitURL: "/commit/{{.CommitHash}}",
branchURL: "/tree/{{.BranchName}}",
regexStrings: defaultUrlRegexStrings,
repoURLTemplate: defaultRepoURLTemplate,
}
Expand All @@ -24,6 +25,7 @@ var bitbucketServiceDef = ServiceDefinition{
pullRequestURLIntoDefaultBranch: "/pull-requests/new?source={{.From}}&t=1",
pullRequestURLIntoTargetBranch: "/pull-requests/new?source={{.From}}&dest={{.To}}&t=1",
commitURL: "/commits/{{.CommitHash}}",
branchURL: "/branch/{{.BranchName}}",
regexStrings: []string{
`^(?:https?|ssh)://.*/(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
`^.*@.*:/*(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
Expand All @@ -36,6 +38,7 @@ var gitLabServiceDef = ServiceDefinition{
pullRequestURLIntoDefaultBranch: "/-/merge_requests/new?merge_request%5Bsource_branch%5D={{.From}}",
pullRequestURLIntoTargetBranch: "/-/merge_requests/new?merge_request%5Bsource_branch%5D={{.From}}&merge_request%5Btarget_branch%5D={{.To}}",
commitURL: "/-/commit/{{.CommitHash}}",
branchURL: "/-/tree/{{.BranchName}}",
regexStrings: defaultUrlRegexStrings,
repoURLTemplate: defaultRepoURLTemplate,
}
Expand All @@ -45,6 +48,7 @@ var azdoServiceDef = ServiceDefinition{
pullRequestURLIntoDefaultBranch: "/pullrequestcreate?sourceRef={{.From}}",
pullRequestURLIntoTargetBranch: "/pullrequestcreate?sourceRef={{.From}}&targetRef={{.To}}",
commitURL: "/commit/{{.CommitHash}}",
branchURL: "?version=GB{{.BranchName}}",
regexStrings: []string{
`^.+@vs-ssh\.visualstudio\.com[:/](?:v3/)?(?P<org>[^/]+)/(?P<project>[^/]+)/(?P<repo>[^/]+?)(?:\.git)?$`,
`^git@ssh.dev.azure.com.*/(?P<org>.*)/(?P<project>.*)/(?P<repo>.*?)(?:\.git)?$`,
Expand All @@ -59,6 +63,7 @@ var bitbucketServerServiceDef = ServiceDefinition{
pullRequestURLIntoDefaultBranch: "/pull-requests?create&sourceBranch={{.From}}",
pullRequestURLIntoTargetBranch: "/pull-requests?create&targetBranch={{.To}}&sourceBranch={{.From}}",
commitURL: "/commits/{{.CommitHash}}",
branchURL: "/browse?at={{.BranchName}}",
regexStrings: []string{
`^ssh://git@.*/(?P<project>.*)/(?P<repo>.*?)(?:\.git)?$`,
`^https://.*/scm/(?P<project>.*)/(?P<repo>.*?)(?:\.git)?$`,
Expand All @@ -71,6 +76,7 @@ var giteaServiceDef = ServiceDefinition{
pullRequestURLIntoDefaultBranch: "/compare/{{.From}}",
pullRequestURLIntoTargetBranch: "/compare/{{.To}}...{{.From}}",
commitURL: "/commit/{{.CommitHash}}",
branchURL: "/src/branch/{{.BranchName}}",
regexStrings: defaultUrlRegexStrings,
repoURLTemplate: defaultRepoURLTemplate,
}
Expand All @@ -80,6 +86,7 @@ var codebergServiceDef = ServiceDefinition{
pullRequestURLIntoDefaultBranch: "/compare/{{.From}}",
pullRequestURLIntoTargetBranch: "/compare/{{.To}}...{{.From}}",
commitURL: "/commit/{{.CommitHash}}",
branchURL: "/src/branch/{{.BranchName}}",
regexStrings: defaultUrlRegexStrings,
repoURLTemplate: defaultRepoURLTemplate,
}
Expand Down
14 changes: 14 additions & 0 deletions pkg/commands/hosting_service/hosting_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ func (self *HostingServiceMgr) GetCommitURL(commitHash string) (string, error) {
return pullRequestURL, nil
}

func (self *HostingServiceMgr) GetBranchURL(branchName string) (string, error) {
gitService, err := self.getService()
if err != nil {
return "", err
}

return gitService.getBranchURL(url.QueryEscape(branchName)), nil
}

func (self *HostingServiceMgr) getService() (*Service, error) {
serviceDomain, err := self.getServiceDomain(self.remoteURL)
if err != nil {
Expand Down Expand Up @@ -141,6 +150,7 @@ type ServiceDefinition struct {
pullRequestURLIntoDefaultBranch string
pullRequestURLIntoTargetBranch string
commitURL string
branchURL string
regexStrings []string

// can expect 'webdomain' to be passed in. Otherwise, you get to pick what we match in the regex
Expand Down Expand Up @@ -177,6 +187,10 @@ func (self *Service) getCommitURL(commitHash string) string {
return self.resolveUrl(self.commitURL, map[string]string{"CommitHash": commitHash})
}

func (self *Service) getBranchURL(branchName string) string {
return self.resolveUrl(self.branchURL, map[string]string{"BranchName": branchName})
}

func (self *Service) resolveUrl(templateString string, args map[string]string) string {
return self.repoURL + utils.ResolvePlaceholderString(templateString, args)
}
93 changes: 93 additions & 0 deletions pkg/commands/hosting_service/hosting_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -577,3 +577,96 @@ func TestGetPullRequestURL(t *testing.T) {
})
}
}

func TestGetBranchURL(t *testing.T) {
type scenario struct {
testName string
branchName string
remoteUrl string
configServiceDomains map[string]string
test func(url string, err error)
}

scenarios := []scenario{
{
testName: "Returns branch URL for github (SSH)",
branchName: "feature/my-feature",
remoteUrl: "git@github.com:peter/calculator.git",
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://github.com/peter/calculator/tree/feature%2Fmy-feature", url)
},
},
{
testName: "Returns branch URL for github (HTTPS)",
branchName: "feature/my-feature",
remoteUrl: "https://github.com/peter/calculator.git",
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://github.com/peter/calculator/tree/feature%2Fmy-feature", url)
},
},
{
testName: "Returns branch URL for gitlab",
branchName: "feature/ui",
remoteUrl: "git@gitlab.com:peter/calculator.git",
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://gitlab.com/peter/calculator/-/tree/feature%2Fui", url)
},
},
{
testName: "Returns branch URL for bitbucket",
branchName: "feature/profile-page",
remoteUrl: "git@bitbucket.org:johndoe/social_network.git",
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://bitbucket.org/johndoe/social_network/branch/feature%2Fprofile-page", url)
},
},
{
testName: "Returns branch URL for gitea",
branchName: "main",
remoteUrl: "git@try.gitea.io:johndoe/myrepo.git",
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://try.gitea.io/johndoe/myrepo/src/branch/main", url)
},
},
{
testName: "Returns branch URL for codeberg",
branchName: "develop",
remoteUrl: "git@codeberg.org:johndoe/myrepo.git",
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://codeberg.org/johndoe/myrepo/src/branch/develop", url)
},
},
{
testName: "Escapes reserved URL characters in branch name",
branchName: "feature/issue#42",
remoteUrl: "git@github.com:peter/calculator.git",
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://github.com/peter/calculator/tree/feature%2Fissue%2342", url)
},
},
{
testName: "Returns error for unsupported service",
branchName: "main",
remoteUrl: "git@unknown-host.com:peter/calculator.git",
test: func(url string, err error) {
assert.Error(t, err)
},
},
}

for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
tr := i18n.EnglishTranslationSet()
log := &fakes.FakeFieldLogger{}
hostingServiceMgr := NewHostingServiceMgr(log, tr, s.remoteUrl, s.configServiceDomains)
s.test(hostingServiceMgr.GetBranchURL(s.branchName))
})
}
}
2 changes: 2 additions & 0 deletions pkg/config/user_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ type KeybindingBranchesConfig struct {
CreatePullRequest string `yaml:"createPullRequest"`
ViewPullRequestOptions string `yaml:"viewPullRequestOptions"`
CopyPullRequestURL string `yaml:"copyPullRequestURL"`
CopyBranchURL string `yaml:"copyBranchURL"`
CheckoutBranchByName string `yaml:"checkoutBranchByName"`
ForceCheckoutBranch string `yaml:"forceCheckoutBranch"`
CheckoutPreviousBranch string `yaml:"checkoutPreviousBranch"`
Expand Down Expand Up @@ -991,6 +992,7 @@ func GetDefaultConfig() *UserConfig {
},
Branches: KeybindingBranchesConfig{
CopyPullRequestURL: "<c-y>",
CopyBranchURL: "y",
CreatePullRequest: "o",
ViewPullRequestOptions: "O",
CheckoutBranchByName: "c",
Expand Down
29 changes: 29 additions & 0 deletions pkg/gui/controllers/branches_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.CopyPullRequestURL,
},
{
Key: opts.GetKey(opts.Config.Branches.CopyBranchURL),
Handler: self.copyBranchURL,
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.CopyBranchURL,
},
{
Key: opts.GetKey(opts.Config.Branches.CheckoutBranchByName),
Handler: self.checkoutByName,
Expand Down Expand Up @@ -465,6 +471,29 @@ func (self *BranchesController) copyPullRequestURL() error {
return nil
}

func (self *BranchesController) copyBranchURL() error {
branch := self.context().GetSelected()

branchExistsOnRemote := self.c.Git().Remote.CheckRemoteBranchExists(branch.Name)

if !branchExistsOnRemote {
return errors.New(self.c.Tr.NoBranchOnRemote)
}

url, err := self.c.Helpers().Host.GetBranchURL(branch.Name)
if err != nil {
return err
}
self.c.LogAction(self.c.Tr.Actions.CopyBranchURL)
if err := self.c.OS().CopyToClipboard(url); err != nil {
return err
}

self.c.Toast(self.c.Tr.BranchURLCopiedToClipboard)

return nil
}

func (self *BranchesController) forceCheckout() error {
branch := self.context().GetSelected()
message := self.c.Tr.SureForceCheckout
Expand Down
8 changes: 8 additions & 0 deletions pkg/gui/controllers/helpers/host_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ func (self *HostHelper) GetCommitURL(commitHash string) (string, error) {
return mgr.GetCommitURL(commitHash)
}

func (self *HostHelper) GetBranchURL(branchName string) (string, error) {
mgr, err := self.getHostingServiceMgr()
if err != nil {
return "", err
}
return mgr.GetBranchURL(branchName)
}

// getting this on every request rather than storing it in state in case our remoteURL changes
// from one invocation to the next.
func (self *HostHelper) getHostingServiceMgr() (*hosting_service.HostingServiceMgr, error) {
Expand Down
6 changes: 6 additions & 0 deletions pkg/i18n/english.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ type TranslationSet struct {
AllBranchesLogGraphReverse string
UnsupportedGitService string
CopyPullRequestURL string
CopyBranchURL string
NoBranchOnRemote string
Fetch string
FetchTooltip string
Expand Down Expand Up @@ -740,6 +741,7 @@ type TranslationSet struct {
SuggestionsSubtitle string
ExtrasTitle string
PullRequestURLCopiedToClipboard string
BranchURLCopiedToClipboard string
CommitDiffCopiedToClipboard string
CommitURLCopiedToClipboard string
CommitMessageCopiedToClipboard string
Expand Down Expand Up @@ -1076,6 +1078,7 @@ type Actions struct {
Undo string
Redo string
CopyPullRequestURL string
CopyBranchURL string
OpenMergeTool string
OpenCommitInBrowser string
OpenPullRequest string
Expand Down Expand Up @@ -1395,6 +1398,7 @@ func EnglishTranslationSet() *TranslationSet {
UnsupportedGitService: `Unsupported git service`,
CreatePullRequest: `Create pull request`,
CopyPullRequestURL: `Copy pull request URL to clipboard`,
CopyBranchURL: `Copy branch URL to clipboard`,
NoBranchOnRemote: `This branch doesn't exist on remote. You need to push it to remote first.`,
Fetch: `Fetch`,
FetchTooltip: "Fetch changes from remote.",
Expand Down Expand Up @@ -1857,6 +1861,7 @@ func EnglishTranslationSet() *TranslationSet {
SuggestionsSubtitle: "(press %s to delete, %s to edit)",
ExtrasTitle: "Command log",
PullRequestURLCopiedToClipboard: "Pull request URL copied to clipboard",
BranchURLCopiedToClipboard: "Branch URL copied to clipboard",
CommitDiffCopiedToClipboard: "Commit diff copied to clipboard",
CommitURLCopiedToClipboard: "Commit URL copied to clipboard",
CommitMessageCopiedToClipboard: "Commit message copied to clipboard",
Expand Down Expand Up @@ -2153,6 +2158,7 @@ func EnglishTranslationSet() *TranslationSet {
Undo: "Undo",
Redo: "Redo",
CopyPullRequestURL: "Copy pull request URL",
CopyBranchURL: "Copy branch URL",
OpenMergeTool: "Open merge tool",
OpenCommitInBrowser: "Open commit in browser",
OpenPullRequest: "Open pull request in browser",
Expand Down
Loading