Skip to content

Commit 5fa6713

Browse files
Add --onto flag to rebase subcommand to allow specification of parent of commit to rebase
Signed-off-by: Jacob Stopak <jacob@initialcommit.io>
1 parent 237d681 commit 5fa6713

File tree

3 files changed

+68
-19
lines changed

3 files changed

+68
-19
lines changed

src/git_sim/commands.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,12 +265,23 @@ def rebase(
265265
),
266266
rebase_merges: bool = typer.Option(
267267
False,
268+
"--rebase-merges",
269+
"-r",
268270
help="Preserve merge structure during rebase",
269271
),
272+
onto: bool = typer.Option(
273+
False,
274+
"--onto",
275+
help="Rebase onto given branch instead of upstream",
276+
),
277+
oldparent: str = typer.Argument(
278+
None,
279+
help="The parent of the commit to rebase (to be used with --onto)",
280+
),
270281
):
271282
from git_sim.rebase import Rebase
272283

273-
scene = Rebase(branch=branch, rebase_merges=rebase_merges)
284+
scene = Rebase(branch=branch, rebase_merges=rebase_merges, onto=onto, oldparent=oldparent)
274285
handle_animations(scene=scene)
275286

276287

src/git_sim/git_sim_base_command.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import manim as m
1212

1313
from git.repo import Repo
14-
from git.exc import GitCommandError, InvalidGitRepositoryError
14+
from git.exc import GitCommandError, InvalidGitRepositoryError, BadName
1515

1616
from git_sim.settings import settings
1717
from git_sim.enums import ColorByOptions, StyleOptions
@@ -106,7 +106,11 @@ def construct(self):
106106

107107
def get_commit(self, sha_or_ref="HEAD"):
108108
if self.head_exists():
109-
return self.repo.commit(sha_or_ref)
109+
try:
110+
return self.repo.commit(sha_or_ref)
111+
except BadName:
112+
print(f"git-sim error: {sha_or_ref} did not resolve to a valid Git object.")
113+
sys.exit(1)
110114
return "dark"
111115

112116
def get_default_commits(self):
@@ -1381,6 +1385,17 @@ def generate_random_sha(self):
13811385
valid_chars = "0123456789abcdef"
13821386
return "".join(random.choices(valid_chars, k=6))
13831387

1388+
def get_mainline_distance(self, sha_or_ref1, sha_or_ref2):
1389+
commit1 = self.get_commit(sha_or_ref1)
1390+
commit2 = self.get_commit(sha_or_ref2)
1391+
if not self.repo.is_ancestor(commit1, commit2):
1392+
print(f"git-sim error: specified sha/ref '{sha_or_ref1}' must be an ancestor of sha/ref '{sha_or_ref2}'.")
1393+
sys.exit(1)
1394+
d = 0
1395+
while self.get_commit(f"{commit2.hexsha}~{d}").hexsha != commit1.hexsha:
1396+
d += 1
1397+
return d
1398+
13841399

13851400
class DottedLine(m.Line):
13861401
def __init__(self, *args, dot_spacing=0.4, dot_kwargs={}, **kwargs):

src/git_sim/rebase.py

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111

1212

1313
class Rebase(GitSimBaseCommand):
14-
def __init__(self, branch: str, rebase_merges: bool):
14+
def __init__(self, branch: str, rebase_merges: bool, onto: bool, oldparent: str):
1515
super().__init__()
1616
self.branch = branch
1717
self.rebase_merges = rebase_merges
18+
self.onto = onto
19+
self.oldparent = oldparent
1820

1921
try:
2022
git.repo.fun.rev_parse(self.repo, self.branch)
@@ -26,6 +28,14 @@ def __init__(self, branch: str, rebase_merges: bool):
2628
)
2729
sys.exit(1)
2830

31+
if self.onto:
32+
if not self.oldparent:
33+
print(
34+
"git-sim error: Please specify the parent of the commit to rebase ('oldparent')"
35+
)
36+
sys.exit(1)
37+
self.n = self.get_mainline_distance(oldparent, "HEAD")
38+
2939
if self.branch in [branch.name for branch in self.repo.heads]:
3040
self.selected_branches.append(self.branch)
3141

@@ -34,7 +44,7 @@ def __init__(self, branch: str, rebase_merges: bool):
3444
except TypeError:
3545
pass
3646

37-
self.cmd += f"{type(self).__name__.lower()} {self.branch}"
47+
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 ''}"
3848

3949
self.alt_colors = {
4050
0: [m.BLUE_B, m.BLUE_E],
@@ -81,25 +91,25 @@ def construct(self):
8191
self.get_default_commits(self.get_commit(), default_commits)
8292
flat_default_commits = self.sort_and_flatten(default_commits)
8393

84-
reached_base = False
85-
merge_base = self.repo.git.merge_base(self.branch, self.repo.active_branch.name)
86-
if merge_base in self.drawnCommits:
87-
reached_base = True
88-
8994
self.parse_commits(head_commit, shift=4 * m.DOWN)
9095
self.parse_all()
9196
self.center_frame_on_commit(branch_commit)
9297

93-
to_rebase = []
98+
self.to_rebase = []
9499
for c in flat_default_commits:
95100
if self.branch not in self.repo.git.branch("--contains", c):
96-
to_rebase.append(c)
101+
self.to_rebase.append(c)
102+
103+
reached_base = False
104+
merge_base = self.repo.git.merge_base(self.branch, self.repo.active_branch.name)
105+
if merge_base in self.drawnCommits or (self.onto and self.to_rebase[-1].hexsha in self.drawnCommits):
106+
reached_base = True
97107

98108
parent = branch_commit.hexsha
99109
branch_counts = {}
100110
rebased_shas = []
101111
rebased_sha_map = {}
102-
for j, tr in enumerate(reversed(to_rebase)):
112+
for j, tr in enumerate(reversed(self.to_rebase)):
103113
if not self.rebase_merges:
104114
if len(tr.parents) > 1:
105115
continue
@@ -121,7 +131,7 @@ def construct(self):
121131

122132
branch_counts = {}
123133
k = 0
124-
for j, tr in enumerate(reversed(to_rebase)):
134+
for j, tr in enumerate(reversed(self.to_rebase)):
125135
if not self.rebase_merges:
126136
if len(tr.parents) > 1:
127137
k += 1
@@ -138,7 +148,7 @@ def construct(self):
138148
self.reset_head_branch(rebased_sha_map[default_commits[0][0].hexsha])
139149
else:
140150
self.reset_head_branch(parent)
141-
self.color_by(offset=2 * len(to_rebase))
151+
self.color_by(offset=2 * len(self.to_rebase))
142152
self.show_command_as_title()
143153
self.fadeout()
144154
self.show_outro()
@@ -161,10 +171,21 @@ def setup_and_draw_parent(
161171
fill_opacity=0.25,
162172
)
163173
circle.height = 1
164-
if self.rebase_merges and branch_index > 0:
174+
side_offset = 0
175+
num_branch_index_0_to_rebase = 0
176+
for commit in default_commits[0]:
177+
if commit in self.to_rebase:
178+
num_branch_index_0_to_rebase += 1
179+
if self.rebase_merges:
180+
for bi in default_commits:
181+
if bi > 0:
182+
if len(default_commits[bi]) >= num_branch_index_0_to_rebase:
183+
side_offset = len(default_commits[bi]) - num_branch_index_0_to_rebase + 1
184+
185+
if self.rebase_merges:
165186
circle.move_to(
166187
self.drawnCommits[orig].get_center(),
167-
).shift(m.UP * 4 + (m.LEFT if settings.reverse else m.RIGHT) * len(default_commits[0]) * 2.5)
188+
).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)
168189
else:
169190
circle.next_to(
170191
self.drawnCommits[child],
@@ -183,10 +204,10 @@ def setup_and_draw_parent(
183204
for p in self.get_commit(orig).parents:
184205
if self.branch in self.repo.git.branch(
185206
"--contains", p
186-
):
207+
) or p not in self.to_rebase:
187208
continue
188209
try:
189-
end = self.drawnCommits[p.hexsha].get_center() + m.UP * 4 + (m.LEFT if settings.reverse else m.RIGHT) * len(default_commits[0]) * 2.5
210+
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
190211
arrow_start_ends.append((start, end))
191212
except KeyError:
192213
pass
@@ -257,6 +278,8 @@ def get_default_commits(self, commit, default_commits, branch_index=0):
257278
if branch_index not in default_commits:
258279
default_commits[branch_index] = []
259280
if len(default_commits[branch_index]) < self.n:
281+
if self.onto and commit.hexsha == self.get_commit(self.oldparent).hexsha:
282+
return default_commits
260283
if commit not in self.sort_and_flatten(default_commits) and self.branch not in self.repo.git.branch(
261284
"--contains", commit
262285
):

0 commit comments

Comments
 (0)