Skip to content

Commit

Permalink
Merge pull request #1849 from timbrel/enhance-merge-branch-list
Browse files Browse the repository at this point in the history
  • Loading branch information
kaste authored Dec 21, 2023
2 parents 827ee45 + 72021fa commit 734352a
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 51 deletions.
4 changes: 2 additions & 2 deletions core/base_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from GitSavvy.core.ui_mixins.quick_panel import show_branch_panel
from GitSavvy.core import store

from typing import Callable, Dict, Iterator, List, Literal, Protocol, TypeVar, Union
from typing import Any, Callable, Dict, Iterator, List, Literal, Protocol, TypeVar, Union


class Kont(Protocol):
Expand Down Expand Up @@ -174,7 +174,7 @@ class GsWindowCommand(


def ask_for_branch(memorize_key=None, **kw):
# type: (KnownKeys, object) -> ArgProvider
# type: (KnownKeys, Any) -> ArgProvider
def handler(self, args, done):
# type: (GsCommand, Args, Kont) -> None
def on_done(branch):
Expand Down
2 changes: 1 addition & 1 deletion core/commands/merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class gs_merge(GsWindowCommand):
"""

defaults = {
"branch": ask_for_branch(ignore_current_branch=True),
"branch": ask_for_branch(ignore_current_branch=True, merged=False),
}

@on_worker
Expand Down
62 changes: 45 additions & 17 deletions core/git_mixins/branches.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
from GitSavvy.core import store
from GitSavvy.core.git_command import mixin_base
from GitSavvy.core.fns import filter_
from GitSavvy.core.utils import yes_no_switch

from typing import Dict, List, NamedTuple, Optional, Sequence


BRANCH_DESCRIPTION_RE = re.compile(r"^branch\.(.*?)\.description (.*)$")
FOR_EACH_REF_SUPPORTS_AHEAD_BEHIND = (2, 41, 0)


class Upstream(NamedTuple):
Expand All @@ -17,6 +19,11 @@ class Upstream(NamedTuple):
status: str


class AheadBehind(NamedTuple):
ahead: int
behind: int


class Branch(NamedTuple):
# For local branches, `remote` is empty and `canonical_name == name`.
# For remote branches:
Expand All @@ -29,6 +36,7 @@ class Branch(NamedTuple):
is_remote: bool
committerdate: int
upstream: Optional[Upstream]
distance_to_head: Optional[AheadBehind]

@property
def is_local(self) -> bool:
Expand Down Expand Up @@ -80,25 +88,31 @@ def get_local_branches(self):
# type: () -> List[Branch]
return self.get_branches(refs=["refs/heads"])

def get_branches(self, *, refs=["refs/heads", "refs/remotes"]):
# type: (Sequence[str]) -> List[Branch]
def get_branches(self, *, refs=["refs/heads", "refs/remotes"], merged=None):
# type: (Sequence[str], Optional[bool]) -> List[Branch]
"""
Return a list of all local and remote branches.
Return a list of local and/or remote branches.
"""
git_supports_ahead_behind = self.git_version >= FOR_EACH_REF_SUPPORTS_AHEAD_BEHIND
stdout = self.git(
"for-each-ref",
(
"--format="
"%(HEAD)%00"
"%(refname)%00"
"%(upstream)%00"
"%(upstream:remotename)%00"
"%(upstream:track,nobracket)%00"
"%(committerdate:unix)%00"
"%(objectname)%00"
"%(contents:subject)"
"--format={}".format(
"%00".join((
"%(HEAD)",
"%(refname)",
"%(upstream)",
"%(upstream:remotename)",
"%(upstream:track,nobracket)",
"%(committerdate:unix)",
"%(objectname)",
"%(contents:subject)",
"%(ahead-behind:HEAD)" if git_supports_ahead_behind else ""
))
),
*refs
*refs,
# If `git_supports_ahead_behind` we don't use the `--[no-]merged` argument
# and instead filter here in Python land.
yes_no_switch("--merged", merged) if not git_supports_ahead_behind else None,
) # type: str
branches = [
branch
Expand All @@ -108,7 +122,18 @@ def get_branches(self, *, refs=["refs/heads", "refs/remotes"]):
)
if branch.name != "HEAD"
]
self._cache_branches(branches, refs)
if git_supports_ahead_behind:
# Cache git's full output but return a filtered result if requested.
self._cache_branches(branches, refs)
if merged is True:
branches = [b for b in branches if b.distance_to_head.ahead == 0] # type: ignore[union-attr]
elif merged is False:
branches = [b for b in branches if b.distance_to_head.ahead > 0] # type: ignore[union-attr]

elif merged is None:
# For older git versions cache git's output only if it was not filtered by `merged`.
self._cache_branches(branches, refs)

return branches

def _cache_branches(self, branches, refs):
Expand Down Expand Up @@ -150,7 +175,7 @@ def fetch_branch_description_subjects(self):
def _parse_branch_line(self, line):
# type: (str) -> Branch
(head, ref, upstream, upstream_remote, upstream_status,
committerdate, commit_hash, commit_msg) = line.split("\x00")
committerdate, commit_hash, commit_msg, ahead_behind) = line.split("\x00")

active = head == "*"
is_remote = ref.startswith("refs/remotes/")
Expand All @@ -174,6 +199,8 @@ def _parse_branch_line(self, line):
else:
ups = None

ahead_behind_ = AheadBehind(*map(int, ahead_behind.split(" "))) if ahead_behind else None

return Branch(
branch_name,
remote,
Expand All @@ -183,7 +210,8 @@ def _parse_branch_line(self, line):
active,
is_remote,
int(committerdate),
upstream=ups
upstream=ups,
distance_to_head=ahead_behind_
)

def merge(self, branch_names):
Expand Down
36 changes: 20 additions & 16 deletions core/ui_mixins/quick_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,15 @@ def on_remote_selection(self, index):


def show_branch_panel(
on_done,
on_done: Callable[[str], None],
*,
on_cancel=lambda: None,
local_branches_only=False,
remote_branches_only=False,
ignore_current_branch=False,
ask_remote_first=False,
selected_branch=None
on_cancel: Callable[[], None] = lambda: None,
local_branches_only: bool = False,
remote_branches_only: bool = False,
ignore_current_branch: bool = False,
ask_remote_first: bool = False,
selected_branch: Optional[str] = None,
merged: Optional[bool] = None,
):
"""
Show a quick panel with branches. The callback `on_done(branch)` will
Expand All @@ -255,7 +256,8 @@ def show_branch_panel(
remote_branches_only,
ignore_current_branch,
ask_remote_first,
selected_branch
selected_branch,
merged,
)
bp.show()
return bp
Expand All @@ -265,13 +267,14 @@ class BranchPanel(GitCommand):

def __init__(
self,
on_done,
on_cancel,
local_branches_only=False,
remote_branches_only=False,
ignore_current_branch=False,
ask_remote_first=False,
selected_branch=None
on_done: Callable[[str], None],
on_cancel: Callable[[], None] = lambda: None,
local_branches_only: bool = False,
remote_branches_only: bool = False,
ignore_current_branch: bool = False,
ask_remote_first: bool = False,
selected_branch: Optional[str] = None,
merged: Optional[bool] = None,
):
self.window = sublime.active_window()
self.on_done = on_done
Expand All @@ -281,6 +284,7 @@ def __init__(
self.ignore_current_branch = ignore_current_branch
self.ask_remote_first = ask_remote_first
self.selected_branch = selected_branch
self.merged = merged

def show(self):
if self.ask_remote_first:
Expand All @@ -289,7 +293,7 @@ def show(self):
self.select_branch(remote=None)

def select_branch(self, remote=None):
branches = self.get_branches()
branches = self.get_branches(merged=self.merged)
if self.local_branches_only:
self.all_branches = [b.canonical_name for b in branches if b.is_local]
elif self.remote_branches_only:
Expand Down
42 changes: 28 additions & 14 deletions tests/test_git_mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ def test_local_branch(self):
repo = GitCommand()
git_output = join0(
[" ", "refs/heads/master", "refs/remotes/origin/master", "origin", ""]
+ date_sha_and_subject)
+ date_sha_and_subject
+ ["0 0"])
when(repo).git("for-each-ref", ...).thenReturn(git_output)
when(repo).get_repo_path().thenReturn("yeah/sure")
actual = repo.get_branches()
Expand All @@ -81,15 +82,17 @@ def test_local_branch(self):
0,
git_mixins.branches.Upstream(
"origin", "master", "origin/master", ""
)
),
git_mixins.branches.AheadBehind(ahead=0, behind=0)
)
])

def test_active_local_branch(self):
repo = GitCommand()
git_output = join0(
["*", "refs/heads/master", "refs/remotes/origin/master", "origin", ""]
+ date_sha_and_subject)
+ date_sha_and_subject
+ ["2 4"])
when(repo).git("for-each-ref", ...).thenReturn(git_output)
when(repo).get_repo_path().thenReturn("yeah/sure")
actual = repo.get_branches()
Expand All @@ -105,15 +108,17 @@ def test_active_local_branch(self):
0,
git_mixins.branches.Upstream(
"origin", "master", "origin/master", ""
)
),
git_mixins.branches.AheadBehind(ahead=2, behind=4)
)
])

def test_remote_branch(self):
repo = GitCommand()
git_output = join0(
[" ", "refs/remotes/origin/dev", "", "", ""]
+ date_sha_and_subject)
+ date_sha_and_subject
+ ["0 0"])
when(repo).git("for-each-ref", ...).thenReturn(git_output)
when(repo).get_repo_path().thenReturn("yeah/sure")
actual = repo.get_branches()
Expand All @@ -127,15 +132,17 @@ def test_remote_branch(self):
False,
True,
0,
None
None,
git_mixins.branches.AheadBehind(ahead=0, behind=0)
)
])

def test_upstream_with_dashes_in_name(self):
repo = GitCommand()
git_output = join0(
[" ", "refs/heads/master", "refs/remotes/orig/in/master", "orig/in", ""]
+ date_sha_and_subject)
+ date_sha_and_subject
+ ["0 0"])
when(repo).git("for-each-ref", ...).thenReturn(git_output)
when(repo).get_repo_path().thenReturn("yeah/sure")
actual = repo.get_branches()
Expand All @@ -151,15 +158,17 @@ def test_upstream_with_dashes_in_name(self):
0,
git_mixins.branches.Upstream(
"orig/in", "master", "orig/in/master", ""
)
),
git_mixins.branches.AheadBehind(ahead=0, behind=0)
)
])

def test_tracking_status(self):
repo = GitCommand()
git_output = join0(
[" ", "refs/heads/master", "refs/remotes/origin/master", "origin", "gone"]
+ date_sha_and_subject)
+ date_sha_and_subject
+ ["0 0"])
when(repo).git("for-each-ref", ...).thenReturn(git_output)
when(repo).get_repo_path().thenReturn("yeah/sure")
actual = repo.get_branches()
Expand All @@ -175,15 +184,17 @@ def test_tracking_status(self):
0,
git_mixins.branches.Upstream(
"origin", "master", "origin/master", "gone"
)
),
git_mixins.branches.AheadBehind(ahead=0, behind=0)
)
])

def test_tracking_local_branch(self):
repo = GitCommand()
git_output = join0(
[" ", "refs/heads/test", "refs/heads/update-branch-from-upstream", ".", ""]
+ date_sha_and_subject)
+ date_sha_and_subject
+ ["0 0"])
when(repo).git("for-each-ref", ...).thenReturn(git_output)
when(repo).get_repo_path().thenReturn("yeah/sure")
actual = repo.get_branches()
Expand All @@ -199,7 +210,8 @@ def test_tracking_local_branch(self):
0,
git_mixins.branches.Upstream(
".", "update-branch-from-upstream", "update-branch-from-upstream", ""
)
),
git_mixins.branches.AheadBehind(ahead=0, behind=0)
)
])

Expand Down Expand Up @@ -271,7 +283,8 @@ def test_active_local_branch(self):
True,
False,
1671490333,
None
None,
git_mixins.branches.AheadBehind(ahead=0, behind=0)
)
])

Expand All @@ -293,6 +306,7 @@ def test_tracking_local_branch(self):
1671490333,
git_mixins.branches.Upstream(
".", "master", "master", ""
)
),
git_mixins.branches.AheadBehind(ahead=0, behind=0)
)
])
3 changes: 2 additions & 1 deletion tests/test_repo_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,8 @@ def test_formatting_and_limiting(self, _, lines, expected):
0,
git_mixins.branches.Upstream(
"origin", "master", "origin/master", ""
)
),
git_mixins.branches.AheadBehind(ahead=0, behind=0)
)
]
actual = list(active_branch.format_and_limit(lines, 5, "origin/master", branches))
Expand Down

0 comments on commit 734352a

Please sign in to comment.