From 26139cb4e1abc9b43af8fa4eb33dba093e2f37f3 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Tue, 16 Apr 2024 12:04:18 -0700 Subject: [PATCH 1/9] Fix keyerror when deleting tag that doesn't exist in set of drawn commits Signed-off-by: Jacob Stopak --- src/git_sim/tag.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/git_sim/tag.py b/src/git_sim/tag.py index fa9eee3..4eeda9c 100644 --- a/src/git_sim/tag.py +++ b/src/git_sim/tag.py @@ -43,9 +43,9 @@ def construct(self): print(f"{settings.INFO_STRING} {self.cmd}") self.show_intro() - self.parse_commits() + self.parse_commits(self.get_commit(sha_or_ref=self.name) if self.d else None) self.parse_all() - self.center_frame_on_commit(self.get_commit()) + self.center_frame_on_commit(self.get_commit(sha_or_ref=self.name) if self.d else self.get_commit()) if not self.d: tagText = m.Text( From 69044f67f49bd44c16b7b8b3791dc58ee64f514f Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Thu, 18 Apr 2024 07:27:08 -0700 Subject: [PATCH 2/9] Handle config subcommand case where option doesn't exist in config Signed-off-by: Jacob Stopak --- src/git_sim/config.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/git_sim/config.py b/src/git_sim/config.py index c901006..9669b34 100644 --- a/src/git_sim/config.py +++ b/src/git_sim/config.py @@ -12,7 +12,7 @@ from typing import List from git.repo import Repo from argparse import Namespace -from configparser import NoSectionError +from configparser import NoSectionError, NoOptionError from git.exc import GitCommandError, InvalidGitRepositoryError from git_sim.settings import settings @@ -195,6 +195,9 @@ def add_details(self): except NoSectionError: print(f"git-sim error: section '{section}' doesn't exist in config") sys.exit(1) + except NoOptionError: + print(f"git-sim error: option '{option}' doesn't exist in config") + sys.exit(1) elif len(self.settings) == 2: value = self.settings[1].strip('"').strip("'").strip("\\") section_text = ( From c39353623916851cf01dca4dd7954f9b2273e5d1 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 19 Apr 2024 11:55:44 -0700 Subject: [PATCH 3/9] Improve rebase subcommand to ignore merge commits by default, flatten diverging branch structure, color rebase dotted arrows, randomly generate simulated commit hashes Signed-off-by: Jacob Stopak --- src/git_sim/git_sim_base_command.py | 22 +++--- src/git_sim/rebase.py | 103 +++++++++++++++++++++------- src/git_sim/settings.py | 4 +- 3 files changed, 94 insertions(+), 35 deletions(-) diff --git a/src/git_sim/git_sim_base_command.py b/src/git_sim/git_sim_base_command.py index c07a8e1..f2f86ee 100644 --- a/src/git_sim/git_sim_base_command.py +++ b/src/git_sim/git_sim_base_command.py @@ -1,18 +1,20 @@ import os -import platform -import shutil -import stat +import git import sys +import stat +import numpy +import random +import shutil +import platform import tempfile -import git import manim as m -import numpy -from git.exc import GitCommandError, InvalidGitRepositoryError + from git.repo import Repo +from git.exc import GitCommandError, InvalidGitRepositoryError -from git_sim.enums import ColorByOptions, StyleOptions from git_sim.settings import settings +from git_sim.enums import ColorByOptions, StyleOptions class GitSimBaseCommand(m.MovingCameraScene): @@ -402,7 +404,7 @@ def get_nonparent_branch_names(self): exclude = [] for b1 in branches: for b2 in branches: - if b1.name != b2.name: + if b1.name != b2.name and b1.commit != b2.commit: if self.repo.is_ancestor(b1.commit, b2.commit): exclude.append(b1.name) return [b for b in branches if b.name not in exclude] @@ -1375,6 +1377,10 @@ def add_ref_to_drawn_refs_by_commit(self, hexsha, ref): ref, ] + def generate_random_sha(self): + valid_chars = "0123456789abcdef" + return "".join(random.choices(valid_chars, k=6)) + class DottedLine(m.Line): def __init__(self, *args, dot_spacing=0.4, dot_kwargs={}, **kwargs): diff --git a/src/git_sim/rebase.py b/src/git_sim/rebase.py index a455ae3..a82bd79 100644 --- a/src/git_sim/rebase.py +++ b/src/git_sim/rebase.py @@ -1,11 +1,13 @@ import sys import git -import manim as m import numpy +import random + +import manim as m -from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings +from git_sim.git_sim_base_command import GitSimBaseCommand, DottedLine class Rebase(GitSimBaseCommand): @@ -33,6 +35,16 @@ def __init__(self, branch: str): self.cmd += f"{type(self).__name__.lower()} {self.branch}" + self.alt_colors = { + 0: [m.BLUE_B, m.BLUE_E], + 1: [m.PURPLE_B, m.PURPLE_E], + 2: [m.RED_B, m.RED_E], + 3: [m.GREEN_B, m.GREEN_E], + 4: [m.MAROON_B, m.MAROON_E], + 5: [m.GOLD_B, m.GOLD_E], + 6: [m.TEAL_B, m.TEAL_E], + } + def construct(self): if not settings.stdout and not settings.output_only_path and not settings.quiet: print(f"{settings.INFO_STRING} {self.cmd}") @@ -65,9 +77,12 @@ def construct(self): branch_commit = self.get_commit(self.branch) self.parse_commits(branch_commit) head_commit = self.get_commit() + default_commits = {} + self.get_default_commits(self.get_commit(), default_commits) + default_commits = self.sort_and_flatten(default_commits) reached_base = False - for commit in self.get_default_commits(): + for commit in default_commits: if commit != "dark" and self.branch in self.repo.git.branch( "--contains", commit ): @@ -78,27 +93,45 @@ def construct(self): self.center_frame_on_commit(branch_commit) to_rebase = [] - i = 0 - current = head_commit - while self.branch not in self.repo.git.branch("--contains", current): - to_rebase.append(current) - i += 1 - if i >= self.n: - break - current = self.get_default_commits()[i] + for c in default_commits: + if self.branch not in self.repo.git.branch("--contains", c): + to_rebase.append(c) parent = branch_commit.hexsha - + branch_counts = {} + rebased_shas = [] for j, tr in enumerate(reversed(to_rebase)): + if len(tr.parents) > 1: + continue if not reached_base and j == 0: message = "..." else: message = tr.message - parent = self.setup_and_draw_parent(parent, message) - self.draw_arrow_between_commits(tr.hexsha, parent) + color_index = int(self.drawnCommits[tr.hexsha].get_center()[1] / -4) - 1 + if color_index not in branch_counts: + branch_counts[color_index] = 0 + branch_counts[color_index] += 1 + commit_color = self.alt_colors[color_index % len(self.alt_colors)][1] + parent = self.setup_and_draw_parent(parent, message, color=commit_color) + rebased_shas.append(parent) self.recenter_frame() self.scale_frame() + + branch_counts = {} + k = 0 + for j, tr in enumerate(reversed(to_rebase)): + if len(tr.parents) > 1: + k += 1 + continue + color_index = int(self.drawnCommits[tr.hexsha].get_center()[1] / -4) - 1 + if color_index not in branch_counts: + branch_counts[color_index] = 0 + branch_counts[color_index] += 1 + commit_color = self.alt_colors[color_index % len(self.alt_colors)][1] + arrow_color = self.alt_colors[color_index % len(self.alt_colors)][1 if branch_counts[color_index] % 2 == 0 else 1] + self.draw_arrow_between_commits(tr.hexsha, rebased_shas[j - k], color=arrow_color) + self.reset_head_branch(parent) self.color_by(offset=2 * len(to_rebase)) self.show_command_as_title() @@ -111,11 +144,12 @@ def setup_and_draw_parent( commitMessage="New commit", shift=numpy.array([0.0, 0.0, 0.0]), draw_arrow=True, + color=m.RED, ): circle = m.Circle( - stroke_color=m.RED, + stroke_color=color, stroke_width=self.commit_stroke_width, - fill_color=m.RED, + fill_color=color, fill_opacity=0.25, ) circle.height = 1 @@ -139,15 +173,9 @@ def setup_and_draw_parent( length = numpy.linalg.norm(start - end) - (1.5 if start[1] == end[1] else 3) arrow.set_length(length) - sha = "".join( - chr(ord(letter) + 1) - if ( - (chr(ord(letter) + 1).isalpha() and letter < "f") - or chr(ord(letter) + 1).isdigit() - ) - else letter - for letter in child[:6] - ) + sha = None + while not sha or sha in self.drawnCommits: + sha = self.generate_random_sha() commitId = m.Text( sha if commitMessage != "..." else "...", font=self.font, @@ -190,3 +218,28 @@ def setup_and_draw_parent( self.toFadeOut.add(arrow) return sha + + def get_default_commits(self, commit, default_commits, branch_index=0): + if branch_index not in default_commits: + default_commits[branch_index] = [] + if len(default_commits[branch_index]) < self.n: + if commit not in self.sort_and_flatten(default_commits): + default_commits[branch_index].append(commit) + for i, parent in enumerate(commit.parents): + self.get_default_commits(parent, default_commits, branch_index + i) + return default_commits + + def draw_arrow_between_commits(self, startsha, endsha, color): + start = self.drawnCommits[startsha].get_center() + end = self.drawnCommits[endsha].get_center() + + arrow = DottedLine( + start, end, color=color, dot_kwargs={"color": color} + ).add_tip() + length = numpy.linalg.norm(start - end) - 1.65 + arrow.set_length(length) + self.draw_arrow(True, arrow) + + def sort_and_flatten(self, d): + sorted_values = [d[key] for key in sorted(d.keys(), reverse=True)] + return sum(sorted_values, []) diff --git a/src/git_sim/settings.py b/src/git_sim/settings.py index 752b56b..2fbbd64 100644 --- a/src/git_sim/settings.py +++ b/src/git_sim/settings.py @@ -20,8 +20,8 @@ class Settings(BaseSettings): transparent_bg: bool = False logo: pathlib.Path = pathlib.Path(__file__).parent.resolve() / "logo.png" low_quality: bool = False - max_branches_per_commit: int = 1 - max_tags_per_commit: int = 1 + max_branches_per_commit: int = 2 + max_tags_per_commit: int = 2 media_dir: pathlib.Path = pathlib.Path().cwd() outro_bottom_text: str = "Learn more at initialcommit.com" outro_top_text: str = "Thanks for using Initial Commit!" From 237d681b7db02c4cfd718c6e5ca25f7a794a10e6 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 19 Apr 2024 21:55:43 -0700 Subject: [PATCH 4/9] Add --rebase-merges flag to rebase subcommand to preserve merge structure during simulated rebase Signed-off-by: Jacob Stopak --- src/git_sim/commands.py | 8 ++- src/git_sim/rebase.py | 114 ++++++++++++++++++++++++++-------------- 2 files changed, 81 insertions(+), 41 deletions(-) diff --git a/src/git_sim/commands.py b/src/git_sim/commands.py index 61dc588..379da5e 100644 --- a/src/git_sim/commands.py +++ b/src/git_sim/commands.py @@ -262,11 +262,15 @@ def rebase( branch: str = typer.Argument( ..., help="The branch to simulate rebasing the checked-out commit onto", - ) + ), + rebase_merges: bool = typer.Option( + False, + help="Preserve merge structure during rebase", + ), ): from git_sim.rebase import Rebase - scene = Rebase(branch=branch) + scene = Rebase(branch=branch, rebase_merges=rebase_merges) handle_animations(scene=scene) diff --git a/src/git_sim/rebase.py b/src/git_sim/rebase.py index a82bd79..6718d66 100644 --- a/src/git_sim/rebase.py +++ b/src/git_sim/rebase.py @@ -11,9 +11,10 @@ class Rebase(GitSimBaseCommand): - def __init__(self, branch: str): + def __init__(self, branch: str, rebase_merges: bool): super().__init__() self.branch = branch + self.rebase_merges = rebase_merges try: git.repo.fun.rev_parse(self.repo, self.branch) @@ -38,11 +39,10 @@ def __init__(self, branch: str): self.alt_colors = { 0: [m.BLUE_B, m.BLUE_E], 1: [m.PURPLE_B, m.PURPLE_E], - 2: [m.RED_B, m.RED_E], - 3: [m.GREEN_B, m.GREEN_E], + 2: [m.GOLD_B, m.GOLD_E], + 3: [m.TEAL_B, m.TEAL_E], 4: [m.MAROON_B, m.MAROON_E], - 5: [m.GOLD_B, m.GOLD_E], - 6: [m.TEAL_B, m.TEAL_E], + 5: [m.GREEN_B, m.GREEN_E], } def construct(self): @@ -79,30 +79,30 @@ def construct(self): head_commit = self.get_commit() default_commits = {} self.get_default_commits(self.get_commit(), default_commits) - default_commits = self.sort_and_flatten(default_commits) + flat_default_commits = self.sort_and_flatten(default_commits) reached_base = False - for commit in default_commits: - if commit != "dark" and self.branch in self.repo.git.branch( - "--contains", commit - ): - reached_base = True + merge_base = self.repo.git.merge_base(self.branch, self.repo.active_branch.name) + if merge_base in self.drawnCommits: + reached_base = True self.parse_commits(head_commit, shift=4 * m.DOWN) self.parse_all() self.center_frame_on_commit(branch_commit) to_rebase = [] - for c in default_commits: + for c in flat_default_commits: if self.branch not in self.repo.git.branch("--contains", c): to_rebase.append(c) parent = branch_commit.hexsha branch_counts = {} rebased_shas = [] + rebased_sha_map = {} for j, tr in enumerate(reversed(to_rebase)): - if len(tr.parents) > 1: - continue + if not self.rebase_merges: + if len(tr.parents) > 1: + continue if not reached_base and j == 0: message = "..." else: @@ -112,8 +112,9 @@ def construct(self): branch_counts[color_index] = 0 branch_counts[color_index] += 1 commit_color = self.alt_colors[color_index % len(self.alt_colors)][1] - parent = self.setup_and_draw_parent(parent, message, color=commit_color) + parent = self.setup_and_draw_parent(parent, tr.hexsha, message, color=commit_color, branch_index=color_index, default_commits=default_commits) rebased_shas.append(parent) + rebased_sha_map[tr.hexsha] = parent self.recenter_frame() self.scale_frame() @@ -121,9 +122,10 @@ def construct(self): branch_counts = {} k = 0 for j, tr in enumerate(reversed(to_rebase)): - if len(tr.parents) > 1: - k += 1 - continue + if not self.rebase_merges: + if len(tr.parents) > 1: + k += 1 + continue color_index = int(self.drawnCommits[tr.hexsha].get_center()[1] / -4) - 1 if color_index not in branch_counts: branch_counts[color_index] = 0 @@ -132,7 +134,10 @@ def construct(self): arrow_color = self.alt_colors[color_index % len(self.alt_colors)][1 if branch_counts[color_index] % 2 == 0 else 1] self.draw_arrow_between_commits(tr.hexsha, rebased_shas[j - k], color=arrow_color) - self.reset_head_branch(parent) + if self.rebase_merges: + self.reset_head_branch(rebased_sha_map[default_commits[0][0].hexsha]) + else: + self.reset_head_branch(parent) self.color_by(offset=2 * len(to_rebase)) self.show_command_as_title() self.fadeout() @@ -141,10 +146,13 @@ def construct(self): def setup_and_draw_parent( self, child, + orig, commitMessage="New commit", shift=numpy.array([0.0, 0.0, 0.0]), draw_arrow=True, color=m.RED, + branch_index=0, + default_commits={}, ): circle = m.Circle( stroke_color=color, @@ -153,25 +161,48 @@ def setup_and_draw_parent( fill_opacity=0.25, ) circle.height = 1 - circle.next_to( - self.drawnCommits[child], - m.LEFT if settings.reverse else m.RIGHT, - buff=1.5, - ) + if self.rebase_merges and branch_index > 0: + circle.move_to( + self.drawnCommits[orig].get_center(), + ).shift(m.UP * 4 + (m.LEFT if settings.reverse else m.RIGHT) * len(default_commits[0]) * 2.5) + else: + circle.next_to( + self.drawnCommits[child], + m.LEFT if settings.reverse else m.RIGHT, + buff=1.5, + ) circle.shift(shift) + arrow_start_ends = [] + arrows = [] start = circle.get_center() - end = self.drawnCommits[child].get_center() - arrow = m.Arrow( - start, - end, - color=self.fontColor, - stroke_width=self.arrow_stroke_width, - tip_shape=self.arrow_tip_shape, - max_stroke_width_to_length_ratio=1000, - ) - length = numpy.linalg.norm(start - end) - (1.5 if start[1] == end[1] else 3) - arrow.set_length(length) + if not self.rebase_merges or branch_index == 0: + end = self.drawnCommits[child].get_center() + arrow_start_ends.append((start, end)) + if self.rebase_merges: + for p in self.get_commit(orig).parents: + if self.branch in self.repo.git.branch( + "--contains", p + ): + continue + try: + end = self.drawnCommits[p.hexsha].get_center() + m.UP * 4 + (m.LEFT if settings.reverse else m.RIGHT) * len(default_commits[0]) * 2.5 + arrow_start_ends.append((start, end)) + except KeyError: + pass + + for start, end in arrow_start_ends: + arrow = m.Arrow( + start, + end, + color=self.fontColor, + stroke_width=self.arrow_stroke_width, + tip_shape=self.arrow_tip_shape, + max_stroke_width_to_length_ratio=1000, + ) + length = numpy.linalg.norm(start - end) - (1.5 if start[1] == end[1] else 3) + arrow.set_length(length) + arrows.append(arrow) sha = None while not sha or sha in self.drawnCommits: @@ -212,10 +243,13 @@ def setup_and_draw_parent( if draw_arrow: if settings.animate: - self.play(m.Create(arrow), run_time=1 / settings.speed) + for arrow in arrows: + self.play(m.Create(arrow), run_time=1 / settings.speed) + self.toFadeOut.add(arrow) else: - self.add(arrow) - self.toFadeOut.add(arrow) + for arrow in arrows: + self.add(arrow) + self.toFadeOut.add(arrow) return sha @@ -223,7 +257,9 @@ def get_default_commits(self, commit, default_commits, branch_index=0): if branch_index not in default_commits: default_commits[branch_index] = [] if len(default_commits[branch_index]) < self.n: - if commit not in self.sort_and_flatten(default_commits): + if commit not in self.sort_and_flatten(default_commits) and self.branch not in self.repo.git.branch( + "--contains", commit + ): default_commits[branch_index].append(commit) for i, parent in enumerate(commit.parents): self.get_default_commits(parent, default_commits, branch_index + i) From 5fa671353d03136c5d5fb719124095836c5305ee Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sat, 20 Apr 2024 13:49:23 -0700 Subject: [PATCH 5/9] Add --onto flag to rebase subcommand to allow specification of parent of commit to rebase Signed-off-by: Jacob Stopak --- src/git_sim/commands.py | 13 ++++++- src/git_sim/git_sim_base_command.py | 19 ++++++++-- src/git_sim/rebase.py | 55 ++++++++++++++++++++--------- 3 files changed, 68 insertions(+), 19 deletions(-) diff --git a/src/git_sim/commands.py b/src/git_sim/commands.py index 379da5e..c48c70e 100644 --- a/src/git_sim/commands.py +++ b/src/git_sim/commands.py @@ -265,12 +265,23 @@ def rebase( ), rebase_merges: bool = typer.Option( False, + "--rebase-merges", + "-r", help="Preserve merge structure during rebase", ), + onto: bool = typer.Option( + False, + "--onto", + help="Rebase onto given branch instead of upstream", + ), + oldparent: str = typer.Argument( + None, + help="The parent of the commit to rebase (to be used with --onto)", + ), ): from git_sim.rebase import Rebase - scene = Rebase(branch=branch, rebase_merges=rebase_merges) + scene = Rebase(branch=branch, rebase_merges=rebase_merges, onto=onto, oldparent=oldparent) handle_animations(scene=scene) diff --git a/src/git_sim/git_sim_base_command.py b/src/git_sim/git_sim_base_command.py index f2f86ee..c06bcaf 100644 --- a/src/git_sim/git_sim_base_command.py +++ b/src/git_sim/git_sim_base_command.py @@ -11,7 +11,7 @@ import manim as m from git.repo import Repo -from git.exc import GitCommandError, InvalidGitRepositoryError +from git.exc import GitCommandError, InvalidGitRepositoryError, BadName from git_sim.settings import settings from git_sim.enums import ColorByOptions, StyleOptions @@ -106,7 +106,11 @@ def construct(self): def get_commit(self, sha_or_ref="HEAD"): if self.head_exists(): - return self.repo.commit(sha_or_ref) + try: + return self.repo.commit(sha_or_ref) + except BadName: + print(f"git-sim error: {sha_or_ref} did not resolve to a valid Git object.") + sys.exit(1) return "dark" def get_default_commits(self): @@ -1381,6 +1385,17 @@ def generate_random_sha(self): valid_chars = "0123456789abcdef" return "".join(random.choices(valid_chars, k=6)) + def get_mainline_distance(self, sha_or_ref1, sha_or_ref2): + commit1 = self.get_commit(sha_or_ref1) + commit2 = self.get_commit(sha_or_ref2) + if not self.repo.is_ancestor(commit1, commit2): + print(f"git-sim error: specified sha/ref '{sha_or_ref1}' must be an ancestor of sha/ref '{sha_or_ref2}'.") + sys.exit(1) + d = 0 + while self.get_commit(f"{commit2.hexsha}~{d}").hexsha != commit1.hexsha: + d += 1 + return d + class DottedLine(m.Line): def __init__(self, *args, dot_spacing=0.4, dot_kwargs={}, **kwargs): diff --git a/src/git_sim/rebase.py b/src/git_sim/rebase.py index 6718d66..38cad58 100644 --- a/src/git_sim/rebase.py +++ b/src/git_sim/rebase.py @@ -11,10 +11,12 @@ class Rebase(GitSimBaseCommand): - def __init__(self, branch: str, rebase_merges: bool): + def __init__(self, branch: str, rebase_merges: bool, onto: bool, oldparent: str): super().__init__() self.branch = branch self.rebase_merges = rebase_merges + self.onto = onto + self.oldparent = oldparent try: git.repo.fun.rev_parse(self.repo, self.branch) @@ -26,6 +28,14 @@ def __init__(self, branch: str, rebase_merges: bool): ) sys.exit(1) + if self.onto: + if not self.oldparent: + print( + "git-sim error: Please specify the parent of the commit to rebase ('oldparent')" + ) + sys.exit(1) + self.n = self.get_mainline_distance(oldparent, "HEAD") + if self.branch in [branch.name for branch in self.repo.heads]: self.selected_branches.append(self.branch) @@ -34,7 +44,7 @@ def __init__(self, branch: str, rebase_merges: bool): except TypeError: pass - self.cmd += f"{type(self).__name__.lower()} {self.branch}" + self.cmd += f"{type(self).__name__.lower()}{' --rebase-merges' if self.rebase_merges else ''}{' --onto' if self.onto else ''} {self.branch}{' ' + self.oldparent if self.onto and self.oldparent else ''}" self.alt_colors = { 0: [m.BLUE_B, m.BLUE_E], @@ -81,25 +91,25 @@ def construct(self): self.get_default_commits(self.get_commit(), default_commits) flat_default_commits = self.sort_and_flatten(default_commits) - reached_base = False - merge_base = self.repo.git.merge_base(self.branch, self.repo.active_branch.name) - if merge_base in self.drawnCommits: - reached_base = True - self.parse_commits(head_commit, shift=4 * m.DOWN) self.parse_all() self.center_frame_on_commit(branch_commit) - to_rebase = [] + self.to_rebase = [] for c in flat_default_commits: if self.branch not in self.repo.git.branch("--contains", c): - to_rebase.append(c) + self.to_rebase.append(c) + + reached_base = False + merge_base = self.repo.git.merge_base(self.branch, self.repo.active_branch.name) + if merge_base in self.drawnCommits or (self.onto and self.to_rebase[-1].hexsha in self.drawnCommits): + reached_base = True parent = branch_commit.hexsha branch_counts = {} rebased_shas = [] rebased_sha_map = {} - for j, tr in enumerate(reversed(to_rebase)): + for j, tr in enumerate(reversed(self.to_rebase)): if not self.rebase_merges: if len(tr.parents) > 1: continue @@ -121,7 +131,7 @@ def construct(self): branch_counts = {} k = 0 - for j, tr in enumerate(reversed(to_rebase)): + for j, tr in enumerate(reversed(self.to_rebase)): if not self.rebase_merges: if len(tr.parents) > 1: k += 1 @@ -138,7 +148,7 @@ def construct(self): self.reset_head_branch(rebased_sha_map[default_commits[0][0].hexsha]) else: self.reset_head_branch(parent) - self.color_by(offset=2 * len(to_rebase)) + self.color_by(offset=2 * len(self.to_rebase)) self.show_command_as_title() self.fadeout() self.show_outro() @@ -161,10 +171,21 @@ def setup_and_draw_parent( fill_opacity=0.25, ) circle.height = 1 - if self.rebase_merges and branch_index > 0: + side_offset = 0 + num_branch_index_0_to_rebase = 0 + for commit in default_commits[0]: + if commit in self.to_rebase: + num_branch_index_0_to_rebase += 1 + if self.rebase_merges: + for bi in default_commits: + if bi > 0: + if len(default_commits[bi]) >= num_branch_index_0_to_rebase: + side_offset = len(default_commits[bi]) - num_branch_index_0_to_rebase + 1 + + if self.rebase_merges: circle.move_to( self.drawnCommits[orig].get_center(), - ).shift(m.UP * 4 + (m.LEFT if settings.reverse else m.RIGHT) * len(default_commits[0]) * 2.5) + ).shift(m.UP * 4 + (m.LEFT if settings.reverse else m.RIGHT) * len(default_commits[0]) * 2.5 + (m.LEFT * side_offset if settings.reverse else m.RIGHT * side_offset) * 5) else: circle.next_to( self.drawnCommits[child], @@ -183,10 +204,10 @@ def setup_and_draw_parent( for p in self.get_commit(orig).parents: if self.branch in self.repo.git.branch( "--contains", p - ): + ) or p not in self.to_rebase: continue try: - end = self.drawnCommits[p.hexsha].get_center() + m.UP * 4 + (m.LEFT if settings.reverse else m.RIGHT) * len(default_commits[0]) * 2.5 + end = self.drawnCommits[p.hexsha].get_center() + m.UP * 4 + (m.LEFT if settings.reverse else m.RIGHT) * len(default_commits[0]) * 2.5 + (m.LEFT * side_offset if settings.reverse else m.RIGHT * side_offset) * 5 arrow_start_ends.append((start, end)) except KeyError: pass @@ -257,6 +278,8 @@ def get_default_commits(self, commit, default_commits, branch_index=0): if branch_index not in default_commits: default_commits[branch_index] = [] if len(default_commits[branch_index]) < self.n: + if self.onto and commit.hexsha == self.get_commit(self.oldparent).hexsha: + return default_commits if commit not in self.sort_and_flatten(default_commits) and self.branch not in self.repo.git.branch( "--contains", commit ): From a5542d3b2f23dbcc3086ec13d0206deb9b9e397a Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sat, 20 Apr 2024 22:14:44 -0700 Subject: [PATCH 6/9] Add argument for rebase subcommand for use with --onto Signed-off-by: Jacob Stopak --- src/git_sim/commands.py | 6 +++- src/git_sim/git_sim_base_command.py | 17 ++++++---- src/git_sim/rebase.py | 51 +++++++++++++++++++++-------- 3 files changed, 54 insertions(+), 20 deletions(-) diff --git a/src/git_sim/commands.py b/src/git_sim/commands.py index c48c70e..584a412 100644 --- a/src/git_sim/commands.py +++ b/src/git_sim/commands.py @@ -278,10 +278,14 @@ def rebase( None, help="The parent of the commit to rebase (to be used with --onto)", ), + until: str = typer.Argument( + None, + help="The commit to rebase up to and including (to be used with --onto)", + ), ): from git_sim.rebase import Rebase - scene = Rebase(branch=branch, rebase_merges=rebase_merges, onto=onto, oldparent=oldparent) + scene = Rebase(branch=branch, rebase_merges=rebase_merges, onto=onto, oldparent=oldparent, until=until) handle_animations(scene=scene) diff --git a/src/git_sim/git_sim_base_command.py b/src/git_sim/git_sim_base_command.py index c06bcaf..e29a420 100644 --- a/src/git_sim/git_sim_base_command.py +++ b/src/git_sim/git_sim_base_command.py @@ -1329,12 +1329,17 @@ def add_group_to_author_groups(self, author, group): def show_command_as_title(self): if settings.show_command_as_title: - titleText = m.Text( - self.trim_cmd(self.cmd), - font=self.font, - font_size=36, - color=self.fontColor, - ) + title_len = 100 + while 1: + titleText = m.Text( + self.trim_cmd(self.cmd, title_len), + font=self.font, + font_size=36, + color=self.fontColor, + ) + if titleText.width < self.camera.frame.width: + break + title_len -= 5 top = 0 for element in self.toFadeOut: if element.get_top()[1] > top: diff --git a/src/git_sim/rebase.py b/src/git_sim/rebase.py index 38cad58..fb2d7ee 100644 --- a/src/git_sim/rebase.py +++ b/src/git_sim/rebase.py @@ -11,12 +11,13 @@ class Rebase(GitSimBaseCommand): - def __init__(self, branch: str, rebase_merges: bool, onto: bool, oldparent: str): + def __init__(self, branch: str, rebase_merges: bool, onto: bool, oldparent: str, until: str): super().__init__() self.branch = branch self.rebase_merges = rebase_merges self.onto = onto self.oldparent = oldparent + self.until = until try: git.repo.fun.rev_parse(self.repo, self.branch) @@ -34,7 +35,16 @@ def __init__(self, branch: str, rebase_merges: bool, onto: bool, oldparent: str) "git-sim error: Please specify the parent of the commit to rebase ('oldparent')" ) sys.exit(1) - self.n = self.get_mainline_distance(oldparent, "HEAD") + self.n = max(self.get_mainline_distance(self.oldparent, "HEAD"), self.n) + + if self.until: + self.until_n = self.get_mainline_distance(self.oldparent, self.until) + else: + if self.oldparent or self.until: + print( + "git-sim error: Please use --onto flag when specifying and " + ) + sys.exit(1) if self.branch in [branch.name for branch in self.repo.heads]: self.selected_branches.append(self.branch) @@ -44,7 +54,7 @@ def __init__(self, branch: str, rebase_merges: bool, onto: bool, oldparent: str) except TypeError: pass - self.cmd += f"{type(self).__name__.lower()}{' --rebase-merges' if self.rebase_merges else ''}{' --onto' if self.onto else ''} {self.branch}{' ' + self.oldparent if self.onto and self.oldparent else ''}" + self.cmd += f"{type(self).__name__.lower()}{' --rebase-merges' if self.rebase_merges else ''}{' --onto' if self.onto else ''} {self.branch}{' ' + self.oldparent if self.onto and self.oldparent else ''}{' ' + self.until if self.onto and self.until else ''}" self.alt_colors = { 0: [m.BLUE_B, m.BLUE_E], @@ -98,7 +108,12 @@ def construct(self): self.to_rebase = [] for c in flat_default_commits: if self.branch not in self.repo.git.branch("--contains", c): - self.to_rebase.append(c) + if self.onto and self.until: + range_commits = list(self.repo.iter_commits(f"{self.oldparent}...{self.until}")) + if c in range_commits: + self.to_rebase.append(c) + else: + self.to_rebase.append(c) reached_base = False merge_base = self.repo.git.merge_base(self.branch, self.repo.active_branch.name) @@ -145,7 +160,14 @@ def construct(self): self.draw_arrow_between_commits(tr.hexsha, rebased_shas[j - k], color=arrow_color) if self.rebase_merges: - self.reset_head_branch(rebased_sha_map[default_commits[0][0].hexsha]) + if self.onto and self.until: + until_sha = self.get_commit(self.until).hexsha + if until_sha == self.repo.head.commit.hexsha: + self.reset_head_branch(rebased_sha_map[until_sha]) + else: + self.reset_head(rebased_sha_map[until_sha]) + else: + self.reset_head_branch(rebased_sha_map[default_commits[0][0].hexsha]) else: self.reset_head_branch(parent) self.color_by(offset=2 * len(self.to_rebase)) @@ -194,21 +216,24 @@ def setup_and_draw_parent( ) circle.shift(shift) - arrow_start_ends = [] + arrow_start_ends = set() arrows = [] - start = circle.get_center() + start = tuple(circle.get_center()) if not self.rebase_merges or branch_index == 0: - end = self.drawnCommits[child].get_center() - arrow_start_ends.append((start, end)) + end = tuple(self.drawnCommits[child].get_center()) + arrow_start_ends.add((start, end)) if self.rebase_merges: for p in self.get_commit(orig).parents: if self.branch in self.repo.git.branch( "--contains", p - ) or p not in self.to_rebase: + ): continue try: - end = self.drawnCommits[p.hexsha].get_center() + m.UP * 4 + (m.LEFT if settings.reverse else m.RIGHT) * len(default_commits[0]) * 2.5 + (m.LEFT * side_offset if settings.reverse else m.RIGHT * side_offset) * 5 - arrow_start_ends.append((start, end)) + if p not in self.to_rebase: + end = tuple(self.drawnCommits[self.get_commit(self.branch).hexsha].get_center()) + else: + end = tuple(self.drawnCommits[p.hexsha].get_center() + m.UP * 4 + (m.LEFT if settings.reverse else m.RIGHT) * len(default_commits[0]) * 2.5 + (m.LEFT * side_offset if settings.reverse else m.RIGHT * side_offset) * 5) + arrow_start_ends.add((start, end)) except KeyError: pass @@ -221,7 +246,7 @@ def setup_and_draw_parent( tip_shape=self.arrow_tip_shape, max_stroke_width_to_length_ratio=1000, ) - length = numpy.linalg.norm(start - end) - (1.5 if start[1] == end[1] else 3) + length = numpy.linalg.norm(numpy.subtract(end, start)) - (1.5 if start[1] == end[1] else 3) arrow.set_length(length) arrows.append(arrow) From 851bad61f178b51f184a7768ed729e80be79c0b7 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Mon, 22 Apr 2024 21:27:23 -0700 Subject: [PATCH 7/9] Refactor rebase subcommand to use more general and terminology, update commit path tracings algos for accuracy Signed-off-by: Jacob Stopak --- src/git_sim/commands.py | 8 +- src/git_sim/git_sim_base_command.py | 49 ++++++++--- src/git_sim/rebase.py | 124 ++++++++++++++++------------ 3 files changed, 115 insertions(+), 66 deletions(-) diff --git a/src/git_sim/commands.py b/src/git_sim/commands.py index 584a412..03a8efe 100644 --- a/src/git_sim/commands.py +++ b/src/git_sim/commands.py @@ -259,9 +259,9 @@ def push( def rebase( - branch: str = typer.Argument( + newbase: str = typer.Argument( ..., - help="The branch to simulate rebasing the checked-out commit onto", + help="The new base commit to simulate rebasing to", ), rebase_merges: bool = typer.Option( False, @@ -274,7 +274,7 @@ def rebase( "--onto", help="Rebase onto given branch instead of upstream", ), - oldparent: str = typer.Argument( + oldbase: str = typer.Argument( None, help="The parent of the commit to rebase (to be used with --onto)", ), @@ -285,7 +285,7 @@ def rebase( ): from git_sim.rebase import Rebase - scene = Rebase(branch=branch, rebase_merges=rebase_merges, onto=onto, oldparent=oldparent, until=until) + scene = Rebase(newbase=newbase, rebase_merges=rebase_merges, onto=onto, oldbase=oldbase, until=until) handle_animations(scene=scene) diff --git a/src/git_sim/git_sim_base_command.py b/src/git_sim/git_sim_base_command.py index e29a420..fec9d97 100644 --- a/src/git_sim/git_sim_base_command.py +++ b/src/git_sim/git_sim_base_command.py @@ -13,6 +13,8 @@ from git.repo import Repo from git.exc import GitCommandError, InvalidGitRepositoryError, BadName +from collections import deque + from git_sim.settings import settings from git_sim.enums import ColorByOptions, StyleOptions @@ -1390,16 +1392,43 @@ def generate_random_sha(self): valid_chars = "0123456789abcdef" return "".join(random.choices(valid_chars, k=6)) - def get_mainline_distance(self, sha_or_ref1, sha_or_ref2): - commit1 = self.get_commit(sha_or_ref1) - commit2 = self.get_commit(sha_or_ref2) - if not self.repo.is_ancestor(commit1, commit2): - print(f"git-sim error: specified sha/ref '{sha_or_ref1}' must be an ancestor of sha/ref '{sha_or_ref2}'.") - sys.exit(1) - d = 0 - while self.get_commit(f"{commit2.hexsha}~{d}").hexsha != commit1.hexsha: - d += 1 - return d + def get_shortest_distance(self, sha_or_ref1, sha_or_ref2): + # Create a queue for BFS that stores (commit, depth) tuples + queue = deque([(self.repo.commit(sha_or_ref2), 0)]) + visited = set() + + # Perform BFS from the start commit + while queue: + current_commit, depth = queue.popleft() + + # If we reach the end commit + if current_commit.hexsha == self.repo.commit(sha_or_ref1).hexsha: + print(depth) + return depth + + # Mark this commit as visited + visited.add(current_commit.hexsha) + + # Queue all unvisited parents + for parent in current_commit.parents: + if parent.hexsha not in visited: + queue.append((parent, depth + 1)) + + # If no path found + return -1 + + def is_on_mainline(self, sha_or_ref1, sha_or_ref2): + current_commit = self.get_commit(sha_or_ref2) + + # Traverse the first parent history + while current_commit: + if current_commit.hexsha == self.get_commit(sha_or_ref1).hexsha: + return True + if current_commit.parents: + current_commit = current_commit.parents[0] + else: + break + return False class DottedLine(m.Line): diff --git a/src/git_sim/rebase.py b/src/git_sim/rebase.py index fb2d7ee..928feed 100644 --- a/src/git_sim/rebase.py +++ b/src/git_sim/rebase.py @@ -11,50 +11,57 @@ class Rebase(GitSimBaseCommand): - def __init__(self, branch: str, rebase_merges: bool, onto: bool, oldparent: str, until: str): + def __init__(self, newbase: str, rebase_merges: bool, onto: bool, oldbase: str, until: str): super().__init__() - self.branch = branch + self.newbase = newbase self.rebase_merges = rebase_merges self.onto = onto - self.oldparent = oldparent + self.oldbase = oldbase self.until = until + self.non_merge_reached = False + try: - git.repo.fun.rev_parse(self.repo, self.branch) + git.repo.fun.rev_parse(self.repo, self.newbase) except git.exc.BadName: print( "git-sim error: '" - + self.branch + + self.newbase + "' is not a valid Git ref or identifier." ) sys.exit(1) if self.onto: - if not self.oldparent: + if not self.oldbase: + print( + "git-sim error: Please specify as the parent of the commit to rebase" + ) + sys.exit(1) + elif not self.is_on_mainline(self.oldbase, "HEAD"): print( - "git-sim error: Please specify the parent of the commit to rebase ('oldparent')" + "git-sim error: Currently only mainline commit paths (i.e. paths traced following _first parents_) along to HEAD are supported for rebase simulations" ) sys.exit(1) - self.n = max(self.get_mainline_distance(self.oldparent, "HEAD"), self.n) + self.n = max(self.get_shortest_distance(self.oldbase, "HEAD"), self.n) if self.until: - self.until_n = self.get_mainline_distance(self.oldparent, self.until) + self.until_n = self.get_shortest_distance(self.oldbase, self.until) else: - if self.oldparent or self.until: + if self.oldbase or self.until: print( - "git-sim error: Please use --onto flag when specifying and " + "git-sim error: Please use --onto flag when specifying and " ) sys.exit(1) - if self.branch in [branch.name for branch in self.repo.heads]: - self.selected_branches.append(self.branch) + if self.newbase in [branch.name for branch in self.repo.heads]: + self.selected_branches.append(self.newbase) try: self.selected_branches.append(self.repo.active_branch.name) except TypeError: pass - self.cmd += f"{type(self).__name__.lower()}{' --rebase-merges' if self.rebase_merges else ''}{' --onto' if self.onto else ''} {self.branch}{' ' + self.oldparent if self.onto and self.oldparent else ''}{' ' + self.until if self.onto and self.until else ''}" + self.cmd += f"{type(self).__name__.lower()}{' --rebase-merges' if self.rebase_merges else ''}{' --onto' if self.onto else ''} {self.newbase}{' ' + self.oldbase if self.onto and self.oldbase else ''}{' ' + self.until if self.onto and self.until else ''}" self.alt_colors = { 0: [m.BLUE_B, m.BLUE_E], @@ -69,33 +76,29 @@ def construct(self): if not settings.stdout and not settings.output_only_path and not settings.quiet: print(f"{settings.INFO_STRING} {self.cmd}") - if self.branch in self.repo.git.branch( - "--contains", self.repo.active_branch.name - ): + if self.repo.is_ancestor(self.repo.head.commit.hexsha, self.newbase): print( - "git-sim error: Branch '" - + self.repo.active_branch.name - + "' is already included in the history of active branch '" - + self.branch + "git-sim error: Current HEAD '" + + self.repo.head.commit.hexsha + + "' is already included in the history of '" + + self.newbase + "'." ) sys.exit(1) - if self.repo.active_branch.name in self.repo.git.branch( - "--contains", self.branch - ): + if self.repo.is_ancestor(self.newbase, self.repo.head.commit.hexsha): print( - "git-sim error: Branch '" - + self.branch - + "' is already based on active branch '" - + self.repo.active_branch.name + "git-sim error: New base '" + + self.newbase + + "' is already based on current HEAD '" + + self.repo.head.commit.hexsha + "'." ) sys.exit(1) self.show_intro() - branch_commit = self.get_commit(self.branch) - self.parse_commits(branch_commit) + newbase_commit = self.get_commit(self.newbase) + self.parse_commits(newbase_commit) head_commit = self.get_commit() default_commits = {} self.get_default_commits(self.get_commit(), default_commits) @@ -103,24 +106,31 @@ def construct(self): self.parse_commits(head_commit, shift=4 * m.DOWN) self.parse_all() - self.center_frame_on_commit(branch_commit) + self.center_frame_on_commit(newbase_commit) self.to_rebase = [] for c in flat_default_commits: - if self.branch not in self.repo.git.branch("--contains", c): + if not self.repo.is_ancestor(c, self.newbase): if self.onto and self.until: - range_commits = list(self.repo.iter_commits(f"{self.oldparent}...{self.until}")) + range_commits = list(self.repo.iter_commits(f"{self.oldbase}...{self.until}")) if c in range_commits: self.to_rebase.append(c) else: self.to_rebase.append(c) + if self.rebase_merges: + if len(self.to_rebase[-1].parents) > 1: + self.cleaned_to_rebase = [] + for j, tr in enumerate(self.to_rebase): + if self.repo.is_ancestor(self.to_rebase[-1], tr): + self.cleaned_to_rebase.append(tr) + self.to_rebase = self.cleaned_to_rebase reached_base = False - merge_base = self.repo.git.merge_base(self.branch, self.repo.active_branch.name) + merge_base = self.repo.git.merge_base(self.newbase, self.repo.head.commit.hexsha) if merge_base in self.drawnCommits or (self.onto and self.to_rebase[-1].hexsha in self.drawnCommits): reached_base = True - parent = branch_commit.hexsha + parent = newbase_commit.hexsha branch_counts = {} rebased_shas = [] rebased_sha_map = {} @@ -159,15 +169,20 @@ def construct(self): arrow_color = self.alt_colors[color_index % len(self.alt_colors)][1 if branch_counts[color_index] % 2 == 0 else 1] self.draw_arrow_between_commits(tr.hexsha, rebased_shas[j - k], color=arrow_color) - if self.rebase_merges: - if self.onto and self.until: - until_sha = self.get_commit(self.until).hexsha - if until_sha == self.repo.head.commit.hexsha: - self.reset_head_branch(rebased_sha_map[until_sha]) - else: - self.reset_head(rebased_sha_map[until_sha]) + if self.onto and self.until: + until_sha = self.get_commit(self.until).hexsha + if until_sha == self.repo.head.commit.hexsha: + self.reset_head_branch(rebased_sha_map[until_sha]) else: - self.reset_head_branch(rebased_sha_map[default_commits[0][0].hexsha]) + try: + self.reset_head(rebased_sha_map[until_sha]) + except KeyError: + for sha in rebased_sha_map: + if len(self.get_commit(sha).parents) < 2: + self.reset_head(rebased_sha_map[sha]) + break + elif self.rebase_merges: + self.reset_head_branch(rebased_sha_map[default_commits[0][0].hexsha]) else: self.reset_head_branch(parent) self.color_by(offset=2 * len(self.to_rebase)) @@ -223,14 +238,21 @@ def setup_and_draw_parent( end = tuple(self.drawnCommits[child].get_center()) arrow_start_ends.add((start, end)) if self.rebase_merges: - for p in self.get_commit(orig).parents: - if self.branch in self.repo.git.branch( - "--contains", p - ): + orig_commit = self.get_commit(orig) + if len(orig_commit.parents) < 2: + self.non_merge_reached = True + for p in orig_commit.parents: + if self.repo.is_ancestor(p, self.newbase): continue try: if p not in self.to_rebase: - end = tuple(self.drawnCommits[self.get_commit(self.branch).hexsha].get_center()) + if branch_index > 0: + end = tuple(self.drawnCommits[self.get_commit(self.newbase).hexsha].get_center()) + elif not self.non_merge_reached: + if p.hexsha == orig_commit.parents[1].hexsha: + end = tuple(self.drawnCommits[p.hexsha].get_center()) + else: + continue else: end = tuple(self.drawnCommits[p.hexsha].get_center() + m.UP * 4 + (m.LEFT if settings.reverse else m.RIGHT) * len(default_commits[0]) * 2.5 + (m.LEFT * side_offset if settings.reverse else m.RIGHT * side_offset) * 5) arrow_start_ends.add((start, end)) @@ -303,11 +325,9 @@ def get_default_commits(self, commit, default_commits, branch_index=0): if branch_index not in default_commits: default_commits[branch_index] = [] if len(default_commits[branch_index]) < self.n: - if self.onto and commit.hexsha == self.get_commit(self.oldparent).hexsha: + if self.onto and commit.hexsha == self.get_commit(self.oldbase).hexsha: return default_commits - if commit not in self.sort_and_flatten(default_commits) and self.branch not in self.repo.git.branch( - "--contains", commit - ): + if commit not in self.sort_and_flatten(default_commits) and not self.repo.is_ancestor(commit, self.newbase): default_commits[branch_index].append(commit) for i, parent in enumerate(commit.parents): self.get_default_commits(parent, default_commits, branch_index + i) From 979152f9edffff010f8aac868ebe5f61ea01201c Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Tue, 23 Apr 2024 12:54:37 -0700 Subject: [PATCH 8/9] Adjust rebase branch reset when using --onto with , when is specified as a non-active branch Signed-off-by: Jacob Stopak --- src/git_sim/git_sim_base_command.py | 6 +++--- src/git_sim/rebase.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/git_sim/git_sim_base_command.py b/src/git_sim/git_sim_base_command.py index fec9d97..636b088 100644 --- a/src/git_sim/git_sim_base_command.py +++ b/src/git_sim/git_sim_base_command.py @@ -955,7 +955,7 @@ def center_frame_on_commit(self, commit): else: self.camera.frame.move_to(self.drawnCommits[commit.hexsha].get_center()) - def reset_head_branch(self, hexsha, shift=numpy.array([0.0, 0.0, 0.0])): + def reset_head_branch(self, hexsha, branch="HEAD", shift=numpy.array([0.0, 0.0, 0.0])): if not self.head_exists(): return @@ -968,7 +968,7 @@ def reset_head_branch(self, hexsha, shift=numpy.array([0.0, 0.0, 0.0])): 0, ) ), - self.drawnRefs[self.repo.active_branch.name].animate.move_to( + self.drawnRefs[self.repo.active_branch.name if branch == "HEAD" else branch].animate.move_to( ( self.drawnCommits[hexsha].get_center()[0] + shift[0], self.drawnCommits[hexsha].get_center()[1] + 2 + shift[1], @@ -984,7 +984,7 @@ def reset_head_branch(self, hexsha, shift=numpy.array([0.0, 0.0, 0.0])): 0, ) ) - self.drawnRefs[self.repo.active_branch.name].move_to( + self.drawnRefs[self.repo.active_branch.name if branch == "HEAD" else branch].move_to( ( self.drawnCommits[hexsha].get_center()[0] + shift[0], self.drawnCommits[hexsha].get_center()[1] + 2 + shift[1], diff --git a/src/git_sim/rebase.py b/src/git_sim/rebase.py index 928feed..22f5001 100644 --- a/src/git_sim/rebase.py +++ b/src/git_sim/rebase.py @@ -171,8 +171,8 @@ def construct(self): if self.onto and self.until: until_sha = self.get_commit(self.until).hexsha - if until_sha == self.repo.head.commit.hexsha: - self.reset_head_branch(rebased_sha_map[until_sha]) + if self.until in [b.name for b in self.repo.branches]: + self.reset_head_branch(rebased_sha_map[until_sha], branch=self.until) else: try: self.reset_head(rebased_sha_map[until_sha]) From 88085cff3e2d7657f26eb6479b308526df7d2bba Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Tue, 23 Apr 2024 20:31:24 -0700 Subject: [PATCH 9/9] Fix command as title clip, ellipses and arrow length in rebase subcommand Signed-off-by: Jacob Stopak --- src/git_sim/git_sim_base_command.py | 2 +- src/git_sim/rebase.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/git_sim/git_sim_base_command.py b/src/git_sim/git_sim_base_command.py index 636b088..087b270 100644 --- a/src/git_sim/git_sim_base_command.py +++ b/src/git_sim/git_sim_base_command.py @@ -1358,6 +1358,7 @@ def show_command_as_title(self): color=self.fontColor, ) self.toFadeOut.add(titleText, ul) + self.recenter_frame() self.scale_frame() if settings.animate: self.play(m.AddTextLetterByLetter(titleText), m.Create(ul)) @@ -1403,7 +1404,6 @@ def get_shortest_distance(self, sha_or_ref1, sha_or_ref2): # If we reach the end commit if current_commit.hexsha == self.repo.commit(sha_or_ref1).hexsha: - print(depth) return depth # Mark this commit as visited diff --git a/src/git_sim/rebase.py b/src/git_sim/rebase.py index 22f5001..d76e734 100644 --- a/src/git_sim/rebase.py +++ b/src/git_sim/rebase.py @@ -34,7 +34,7 @@ def __init__(self, newbase: str, rebase_merges: bool, onto: bool, oldbase: str, if self.onto: if not self.oldbase: print( - "git-sim error: Please specify as the parent of the commit to rebase" + "git-sim error: When using --onto, please specify as the parent of the commit to rebase" ) sys.exit(1) elif not self.is_on_mainline(self.oldbase, "HEAD"): @@ -127,6 +127,10 @@ def construct(self): reached_base = False merge_base = self.repo.git.merge_base(self.newbase, self.repo.head.commit.hexsha) + base_branch_commits = list(self.repo.iter_commits(f"{merge_base}...HEAD")) + for bc in base_branch_commits: + if merge_base in [p.hexsha for p in bc.parents]: + reached_base = True if merge_base in self.drawnCommits or (self.onto and self.to_rebase[-1].hexsha in self.drawnCommits): reached_base = True @@ -185,6 +189,7 @@ def construct(self): self.reset_head_branch(rebased_sha_map[default_commits[0][0].hexsha]) else: self.reset_head_branch(parent) + self.color_by(offset=2 * len(self.to_rebase)) self.show_command_as_title() self.fadeout() @@ -222,7 +227,7 @@ def setup_and_draw_parent( if self.rebase_merges: circle.move_to( self.drawnCommits[orig].get_center(), - ).shift(m.UP * 4 + (m.LEFT if settings.reverse else m.RIGHT) * len(default_commits[0]) * 2.5 + (m.LEFT * side_offset if settings.reverse else m.RIGHT * side_offset) * 5) + ).shift(m.UP * 4 + (m.LEFT if settings.reverse else m.RIGHT) * len(default_commits[0]) * 2.5 + (m.LEFT if settings.reverse else m.RIGHT) * (5 + side_offset)) else: circle.next_to( self.drawnCommits[child], @@ -254,7 +259,7 @@ def setup_and_draw_parent( else: continue else: - end = tuple(self.drawnCommits[p.hexsha].get_center() + m.UP * 4 + (m.LEFT if settings.reverse else m.RIGHT) * len(default_commits[0]) * 2.5 + (m.LEFT * side_offset if settings.reverse else m.RIGHT * side_offset) * 5) + end = tuple(self.drawnCommits[p.hexsha].get_center() + m.UP * 4 + (m.LEFT if settings.reverse else m.RIGHT) * len(default_commits[0]) * 2.5 + (m.LEFT if settings.reverse else m.RIGHT) * (5 + side_offset)) arrow_start_ends.add((start, end)) except KeyError: pass