Sublime Forum

GitHub/GitLab/Bitbucket Integration (commands & menu items)

#1

I’ve been wanting to generate commands and menu items for some basic form of GitHub integration for Sublime Merge for a while now, finally completed it today (now with GitLab & Bitbucket support!).

The commands and menu items are implemented using a git alias which also allows command line usage if wanted:

  • open the repo: git open repo
  • open the current branch: git open branch
  • open the current commit: git open commit
  • create a pr on the current branch: git open pr

Each command supports an extra argument for specification:

  • open a specific branch: git open branch $specific_branch
  • open a specific commit: git open commit $specific_commit
  • create a pr on a specific branch: git open pr $specific_branch
  • open a specific tag: git open tag $specific_tag

Notes/Caveats:

  • the alias only works by default on macOS, changing the open command in the last line of step 1 (open "$repo_url") to a supported command by your OS is needed
  • all commands/menu items are always enabled even for non-remote repositories, this is can be fixed once plugins are supported in SM
  • when using the command line there is no commit/tag validation, but GitHub/GitLab/Bitbucket will show an error if so
  • for commits & tags, only one remote is supported & uses origin
  1. add this git alias:

    git config --global alias.open '!f() {
        local type="${1:-branch}"
        local target="${2:-HEAD}"
        local remote="origin"
        
        if [ "$type" = "branch" -o "$type" = "pr" ]; then
            # get full name (i.e. refs/heads/*; refs/remotes/*/*); src: https://stackoverflow.com/a/9753364
            target="$(git rev-parse --symbolic-full-name "$target")"
            
            if [ "$target" != "${target#"refs/remotes/"}" ]; then
                # extract from remote branch reference
                target="${target#"refs/remotes/"}"
            else
                # extract from local branch reference; src: https://stackoverflow.com/a/9753364
                target="$(git for-each-ref --format="%(upstream:short)" "$target")"
            fi
            # split remote/branch
            remote="${target%%/*}"
            target="${target#"$remote/"}"
            
            if [ -z "$remote" ]; then
                echo "Branch ($2) does not point to a remote repository." >&2
                return 2
            fi
        fi
        
        local repo_url="$(git remote get-url "$remote" | sed -E -e "s/(\.(com|org|io))\:/\1\//" -e "s/git@/https:\/\//" -e "s/\.git$//")"
        if [ -z "$repo_url" ]; then
            echo "Cannot open: no remote repository configured under (origin)" >&2
            return 1
        fi
        
        case "$(tr "[:upper:]" "[:lower:]" <<< "$repo_url")" in
            *github*)
                [ "$type" = "commit" ] && repo_url="$repo_url/commit/$(git rev-parse "$target")"
                [ "$type" = "pr"     ] && repo_url="$repo_url/compare/$target?expand=1"
                [ "$type" = "tag"    ] && repo_url="$repo_url/releases/tag/$target"
                [ "$type" = "branch" ] && repo_url="$repo_url/tree/$target"
                ;;
            *bitbucket*)
                [ "$type" = "commit" ] && repo_url="$repo_url/commits/$(git rev-parse "$target")"
                [ "$type" = "pr"     ] && repo_url="$repo_url/pull-requests/new?source=$target"
                [ "$type" = "tag"    ] && repo_url="$repo_url/src/$target"
                [ "$type" = "branch" ] && repo_url="$repo_url/src/$target"
                ;;
            *gitlab*)
                [ "$type" = "commit" ] && repo_url="$repo_url/-/commit/$(git rev-parse "$target")"
                [ "$type" = "pr"     ] && repo_url="$repo_url/-/merge_requests/new?merge_request[source_branch]=$target"
                [ "$type" = "tag"    ] && repo_url="$repo_url/-/tags/$target"
                [ "$type" = "branch" ] && repo_url="$repo_url/-/tree/$target"
                ;;
            *)
                echo "Cannot open: not a GitHub, GitLab, or Bitbucket repository" >&2
                return 1
                ;;
        esac
        
        open "$repo_url"
    }; f'
    
  2. add these commands to Default.sublime-commands:

    [
        {
            "caption": "Open Repository in GitHub/GitLab/Bitbucket",
            "command": "git",
            "args": { "argv": ["open", "repo"] }
        },
        {
            "caption": "Open Branch in GitHub/GitLab/Bitbucket…",
            "command": "git",
            "args": { "argv": ["open", "branch", "$select_branch"] }
        },
        {
            "caption": "Create Pull Request in GitHub/GitLab/Bitbucket…",
            "command": "git",
            "args": { "argv": ["open", "pr", "$select_branch"] }
        },
        {
            "caption": "Open Tag in GitHub/GitLab/Bitbucket…",
            "command": "git",
            "args": { "argv": ["open", "tag", "$select_tag"] }
        }
    ]
    
  3. add these menu items to Branch.sublime-menu:

    [
        { "caption": "-", "id": "end" },
        {
            "caption": "Open Branch $branch in GitHub/GitLab/Bitbucket",
            "command": "git",
            "args": { "argv": ["open", "branch", "$branch"] }
        },
        {
            "caption": "Create Pull Request on $branch in GitHub/GitLab/Bitbucket",
            "command": "git",
            "args": { "argv": ["open", "pr", "$branch"] }
        }
    ]
    
  4. add these menu items to Remote Branch.sublime-menu:

    [
        { "caption": "-", "id": "end" },
        {
            "caption": "Open Branch $branch in GitHub/GitLab/Bitbucket",
            "command": "git",
            "args": { "argv": ["open", "branch", "$branch"] }
        },
        {
            "caption": "Create Pull Request on $branch in GitHub/GitLab/Bitbucket",
            "command": "git",
            "args": { "argv": ["open", "pr", "$branch"] }
        }
    ]
    
  5. add this menu item to Commit.sublime-menu:

    [
        {
            "caption": "Open Commit $short_commit in GitHub/GitLab/Bitbucket",
            "command": "git",
            "args": { "argv": ["open", "commit", "$commit"] }
        }
    ]
    
  6. add this menu item to Tag.sublime-menu:

    [
        {
            "caption": "Open Tag $tag_name in GitHub/GitLab/Bitbucket",
            "command": "git",
            "args": { "argv": ["open", "tag", "$tag_name"] }
        }
    ]
    
9 Likes

#2

Small edit to the git alias: changed s/\.git// to s/\.git$// to only remove suffix if present.

0 Likes

#3

Updated to include support for GitLab & Bitbucket.

I’m also working on supporting the new $select_branch feature in dev build 2034.

0 Likes

#4

Updated to include support for:

  1. selecting branch/tag in global commands
  2. menu item for remote branches
  3. opening local branches which names differ from the remote name
  4. multiple remotes (opening branches on non-origin remotes)

Due to #1, the global commands now require build 2034 or newer. To support older builds, remove the $select_* argument from those commands (or copy from edit history).

1 Like

#5

@benmosher @srbs all resource https://github.com/reslear/sublime-merge-github

0 Likes

#6

I wish ST + SM supported at least GitLab. VSCode has this feature via GitLab Workflow. I wish I could manage issues/MRs/pipelines from ST (or even SM) at least on GitLab and GitHub. Other stuff would be useful too, like creating a repo, manage some stuff (the more, the better). :wink:

0 Likes

#7

quick question. how to I add the aliases if I don’t use bash? Currently I run git on Windows 10 through mingit and posh-git. Basically I used scoop to download and add them to my PATH.

0 Likes

#8

@bitsper2nd, this would be in your git config file: (~/.gitconfig or ~/.config/git/config)

[alias]
    open = "!f() {\n    local type=\"${1:-branch}\"\n    local target=\"${2:-HEAD}\"\n    local remote=\"origin\"\n    \n    if [ \"$type\" = \"branch\" -o \"$type\" = \"pr\" ]; then\n        target=\"$(git rev-parse --symbolic-full-name \"$target\")\"\n        \n        if [ \"$target\" != \"${target#\"refs/remotes/\"}\" ]; then\n            target=\"${target#\"refs/remotes/\"}\"\n        else\n            target=\"$(git for-each-ref --format=\"%(upstream:short)\" \"$target\")\"\n        fi\n        remote=\"${target%%/*}\"\n        target=\"${target#\"$remote/\"}\"\n        \n        if [ -z \"$remote\" ]; then\n            echo \"Branch ($2) does not point to a remote repository.\" >&2\n            return 2\n        fi\n    fi\n    \n    local repo_url=\"$(git remote get-url \"$remote\" | sed -E -e \"s/(\\.(com|org|io))\\:/\\1\\//\" -e \"s/git@/https:\\/\\//\" -e \"s/\\.git$//\")\"\n    if [ -z \"$repo_url\" ]; then\n        echo \"Cannot open: no remote repository configured under (origin)\" >&2\n        return 1\n    fi\n    \n    case \"$(tr \"[:upper:]\" \"[:lower:]\" <<< \"$repo_url\")\" in\n        *github*)\n            [ \"$type\" = \"commit\" ] && repo_url=\"$repo_url/commit/$(git rev-parse \"$target\")\"\n            [ \"$type\" = \"pr\"     ] && repo_url=\"$repo_url/compare/$target?expand=1\"\n            [ \"$type\" = \"tag\"    ] && repo_url=\"$repo_url/releases/tag/$target\"\n            [ \"$type\" = \"branch\" ] && repo_url=\"$repo_url/tree/$target\"\n            ;;\n        *bitbucket*)\n            [ \"$type\" = \"commit\" ] && repo_url=\"$repo_url/commits/$(git rev-parse \"$target\")\"\n            [ \"$type\" = \"pr\"     ] && repo_url=\"$repo_url/pull-requests/new?source=$target\"\n            [ \"$type\" = \"tag\"    ] && repo_url=\"$repo_url/src/$target\"\n            [ \"$type\" = \"branch\" ] && repo_url=\"$repo_url/src/$target\"\n            ;;\n        *gitlab*)\n            [ \"$type\" = \"commit\" ] && repo_url=\"$repo_url/-/commit/$(git rev-parse \"$target\")\"\n            [ \"$type\" = \"pr\"     ] && repo_url=\"$repo_url/-/merge_requests/new?merge_request[source_branch]=$target\"\n            [ \"$type\" = \"tag\"    ] && repo_url=\"$repo_url/-/tags/$target\"\n            [ \"$type\" = \"branch\" ] && repo_url=\"$repo_url/-/tree/$target\"\n            ;;\n        *)\n            echo \"Cannot open: not a GitHub, GitLab, or Bitbucket repository\" >&2\n            return 1\n            ;;\n    esac\n    \n    open \"$repo_url\"\n}; f"

Feel free to try it, but I have no idea if it’ll work with mingit.

0 Likes

#9

Thank you for this. All I had to do was replace the open at the end with powershell start. Sometimes it works. Sometimes it doesn’t. And what I mean by this is that I see the command executed with success in the Sublime Merge Git Output, but the URL won’t open the browser. And then I try running the command again and suddenly the URL to the repo opens in the browser.

0 Likes

#10

@bitsper2nd, this may be related to your issues:

1 Like

#11

Hi, I am on MacOS and running zsh. I have used this to add the alias to my gitconfig and when trying to view a commit on the remote (BitBucket), the browser opens the following URL: https://commit/commit/tree/0d4eb4bdc4a843f68c3f5404f66da6320305310f. Any ideas what could be wrong?

I am running Sublime Merge Build 2067 and system git is v.2.34.1.

Thanks!

0 Likes

#12

Apologies, I have found the issue. One of the NPM packages I had installed had added this git open command and it clashed with the new alias. :frowning:

The open commit command now opens the remote in the browser onto the commit pages as expected.

Sorry for the bother and thank you for sharing this ultra-useful command! :slight_smile:

0 Likes

#13

Someone else even made an improved version of this command.

0 Likes

#14

@bitsper2nd Ah yes, that’s mine! Thanks for your interest! My version has a dependency on having a system-wide Python install with the py shortcut installed, and adds support for Azure DevOps repositories. I am interested in creating or contributing to a plugin once we get true plugin support for Sublime Merge, but this extension works for my current needs.

Big thanks to @srbs for the original implementation.

0 Likes

#15

Thanks for the tip! :slight_smile:

0 Likes

split this topic #16

A post was split to a new topic: Problem running code in sublime

0 Likes