From daf9f9753b074bf68ac0eb2d2491587bca5d8964 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Wed, 22 Feb 2023 22:21:37 -0800 Subject: [PATCH 01/39] Add switch subcommand, no detached head supported Signed-off-by: Jacob Stopak --- git_sim/__main__.py | 2 + git_sim/git_sim_base_command.py | 20 ++++++ git_sim/switch.py | 119 ++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 git_sim/switch.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 17f970d..ff47875 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -19,6 +19,7 @@ import git_sim.stash import git_sim.status import git_sim.tag +import git_sim.switch from git_sim.settings import ImgFormat, VideoFormat, settings from manim import config, WHITE @@ -164,6 +165,7 @@ def main( app.command()(git_sim.stash.stash) app.command()(git_sim.status.status) app.command()(git_sim.tag.tag) +app.command()(git_sim.switch.switch) if __name__ == "__main__": diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index 8e65e80..0cd23a6 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -788,6 +788,26 @@ def reset_head_branch(self, hexsha, shift=numpy.array([0.0, 0.0, 0.0])): ) ) + def reset_head(self, hexsha, shift=numpy.array([0.0, 0.0, 0.0])): + if settings.animate: + self.play( + self.drawnRefs["HEAD"].animate.move_to( + ( + self.drawnCommits[hexsha].get_center()[0] + shift[0], + self.drawnCommits[hexsha].get_center()[1] + 2.0 + shift[1], + 0, + ) + ), + ) + else: + self.drawnRefs["HEAD"].move_to( + ( + self.drawnCommits[hexsha].get_center()[0] + shift[0], + self.drawnCommits[hexsha].get_center()[1] + 2.0 + shift[1], + 0, + ) + ) + def translate_frame(self, shift): if settings.animate: self.play(self.camera.frame.animate.shift(shift)) diff --git a/git_sim/switch.py b/git_sim/switch.py new file mode 100644 index 0000000..484689b --- /dev/null +++ b/git_sim/switch.py @@ -0,0 +1,119 @@ +import sys +from argparse import Namespace + +import git +import manim as m +import numpy +import typer + +from git_sim.animations import handle_animations +from git_sim.git_sim_base_command import GitSimBaseCommand +from git_sim.settings import settings + + +class Switch(GitSimBaseCommand): + def __init__(self, branch: str): + super().__init__() + self.branch = branch + + try: + git.repo.fun.rev_parse(self.repo, self.branch) + except git.exc.BadName: + print( + "git-sim error: '" + + self.branch + + "' is not a valid Git ref or identifier." + ) + sys.exit(1) + + self.ff = False + if self.branch in [branch.name for branch in self.repo.heads]: + self.selected_branches.append(self.branch) + + try: + self.selected_branches.append(self.repo.active_branch.name) + except TypeError: + pass + + def construct(self): + if not settings.stdout: + print( + f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.branch}" + ) + + if self.repo.active_branch.name in self.repo.git.branch( + "--contains", self.branch + ): + print( + "git-sim error: Branch '" + + self.branch + + "' is already included in the history of active branch '" + + self.repo.active_branch.name + + "'." + ) + sys.exit(1) + + self.show_intro() + self.get_commits() + self.orig_commits = self.commits + self.get_commits(start=self.branch) + + if not self.is_remote_tracking_branch(self.branch): + if self.branch in self.repo.git.branch( + "--contains", self.orig_commits[0].hexsha + ): + self.ff = True + else: + if self.branch in self.repo.git.branch( + "-r", "--contains", self.orig_commits[0].hexsha + ): + self.ff = True + + if self.ff: + self.get_commits(start=self.branch) + self.parse_commits(self.commits[0]) + reset_head_to = self.commits[0].hexsha + shift = numpy.array([0.0, 0.6, 0.0]) + + if self.no_ff: + self.center_frame_on_commit(self.commits[0]) + commitId = self.setup_and_draw_parent(self.commits[0], "Merge commit") + reset_head_to = "abcdef" + shift = numpy.array([0.0, 0.0, 0.0]) + + self.recenter_frame() + self.scale_frame() + if "HEAD" in self.drawnRefs: + self.reset_head_branch(reset_head_to, shift=shift) + else: + self.draw_ref(self.commits[0], commitId if self.no_ff else self.topref) + self.draw_ref( + self.commits[0], + self.drawnRefs["HEAD"], + text=self.repo.active_branch.name, + color=m.GREEN, + ) + + else: + self.get_commits() + self.parse_commits(self.commits[0]) + self.i = 0 + self.get_commits(start=self.branch) + self.parse_commits(self.commits[0], shift=4 * m.DOWN) + self.center_frame_on_commit(self.orig_commits[0]) + self.recenter_frame() + self.scale_frame() + self.reset_head(self.commits[0].hexsha) + + self.fadeout() + self.show_outro() + + +def switch( + branch: str = typer.Argument( + ..., + help="The name of the branch to switch to", + ), +): + scene = Switch(branch=branch) + handle_animations(scene=scene) From fc7fb1203030cf0f1070a4ad1e84b2febcfdaa79 Mon Sep 17 00:00:00 2001 From: Jacob Stopak <49353917+initialcommit-io@users.noreply.github.com> Date: Thu, 9 Mar 2023 14:24:39 -0800 Subject: [PATCH 02/39] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 133ee2f..89019e3 100644 --- a/README.md +++ b/README.md @@ -455,7 +455,7 @@ $ docker build -t git-sim . Optional: On MacOS / Linux / or GitBash in Windows, create an alias for the long docker command so your can run it as a normal `git-sim` command. To do so add the following line to your `.bashrc` or equivalent, then restart your terminal: ```bash -git-sim() { docker run --rm -v $(pwd):/usr/src/git-sim git-sim "$@" } +git-sim() { docker run --rm -v $(pwd):/usr/src/git-sim git-sim "$@"; } ``` This will enable you to run git-sim subcommands as [described above](#commands). From 6a302490c2b94f75f55ebae56855cc8023f19cba Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Thu, 9 Mar 2023 21:53:56 -0800 Subject: [PATCH 03/39] Add additional colors to palette Signed-off-by: Jacob Stopak --- git_sim/git_sim_base_command.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index 7d4c6da..a0ca411 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -40,6 +40,10 @@ def __init__(self): m.MAROON, m.PURPLE, m.GOLD, + m.TEAL, + m.RED, + m.PINK, + m.DARK_BLUE, ] self.logo = m.ImageMobject(settings.logo) @@ -1071,7 +1075,7 @@ def color_by(self, offset=0): f"{author[:15]} ({str(len(self.author_groups[author]))})", font="Monospace", font_size=36, - color=self.colors[int(i % 7)], + color=self.colors[int(i % 11)], ) authorText.move_to( [(-5 - offset) if settings.reverse else (5 + offset), -i, 0] @@ -1085,7 +1089,7 @@ def color_by(self, offset=0): else: self.add(authorText) for g in self.author_groups[author]: - g[0].set_color(self.colors[int(i % 7)]) + g[0].set_color(self.colors[int(i % 11)]) self.recenter_frame() self.scale_frame() From fe106ab5d5454ebb3a4f5295c4b2e687d705909c Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 17 Mar 2023 00:07:11 -0700 Subject: [PATCH 04/39] Update switch subcommand so it actually works properly The git-sim switch command is updated to handle 3 scenarios: - branch being switched to is an ancestor of HEAD - branch being switched to is a descendant of HEAD - branch being switched to has diverged from HEAD Signed-off-by: Jacob Stopak --- git_sim/git_sim_base_command.py | 22 +++++++ git_sim/switch.py | 106 +++++++++++++++----------------- 2 files changed, 72 insertions(+), 56 deletions(-) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index acbfe86..b94f05d 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -856,6 +856,28 @@ def reset_head(self, hexsha, shift=numpy.array([0.0, 0.0, 0.0])): 0, ) ) + + def reset_branch(self, hexsha, shift=numpy.array([0.0, 0.0, 0.0])): + if settings.animate: + self.play( + self.drawnRefs[self.repo.active_branch.name].animate.move_to( + ( + self.drawnCommits[hexsha].get_center()[0] + shift[0], + self.drawnCommits[hexsha].get_center()[1] + 1.4 + shift[1], + 0, + ) + ), + ) + else: + self.drawnRefs[self.repo.active_branch.name].move_to( + ( + self.drawnCommits[hexsha].get_center()[0] + shift[0], + self.drawnCommits[hexsha].get_center()[1] + 1.4 + shift[1], + 0, + ) + ) + + def reset_head_branch_to_ref(self, ref, shift=numpy.array([0.0, 0.0, 0.0])): if settings.animate: self.play(self.drawnRefs["HEAD"].animate.next_to(ref, m.UP)) diff --git a/git_sim/switch.py b/git_sim/switch.py index 484689b..4fd8b0d 100644 --- a/git_sim/switch.py +++ b/git_sim/switch.py @@ -26,7 +26,24 @@ def __init__(self, branch: str): ) sys.exit(1) - self.ff = False + if self.branch == self.repo.active_branch.name: + print("git-sim error: already on branch '" + self.branch + "'") + sys.exit(1) + + self.is_ancestor = False + self.is_descendant = False + + # branch being switched to is behind HEAD + if self.repo.active_branch.name in self.repo.git.branch( + "--contains", self.branch + ): + self.is_ancestor = True + # HEAD is behind branch being switched to + elif self.branch in self.repo.git.branch( + "--contains", self.repo.active_branch.name + ): + self.is_descendant = True + if self.branch in [branch.name for branch in self.repo.heads]: self.selected_branches.append(self.branch) @@ -36,75 +53,52 @@ def __init__(self, branch: str): pass def construct(self): - if not settings.stdout: + if not settings.stdout and not settings.output_only_path and not settings.quiet: print( f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.branch}" ) - if self.repo.active_branch.name in self.repo.git.branch( - "--contains", self.branch - ): - print( - "git-sim error: Branch '" - + self.branch - + "' is already included in the history of active branch '" - + self.repo.active_branch.name - + "'." - ) - sys.exit(1) - self.show_intro() - self.get_commits() - self.orig_commits = self.commits - self.get_commits(start=self.branch) - - if not self.is_remote_tracking_branch(self.branch): - if self.branch in self.repo.git.branch( - "--contains", self.orig_commits[0].hexsha - ): - self.ff = True - else: - if self.branch in self.repo.git.branch( - "-r", "--contains", self.orig_commits[0].hexsha - ): - self.ff = True - - if self.ff: - self.get_commits(start=self.branch) - self.parse_commits(self.commits[0]) - reset_head_to = self.commits[0].hexsha - shift = numpy.array([0.0, 0.6, 0.0]) - - if self.no_ff: - self.center_frame_on_commit(self.commits[0]) - commitId = self.setup_and_draw_parent(self.commits[0], "Merge commit") - reset_head_to = "abcdef" - shift = numpy.array([0.0, 0.0, 0.0]) + head_commit = self.get_commit() + branch_commit = self.get_commit(self.branch) + + if self.is_ancestor: + commits_in_range = list(self.repo.iter_commits(self.branch + '..HEAD')) + + # branch is reached from HEAD, so draw everything + if len(commits_in_range) <= self.n: + self.parse_commits(head_commit) + reset_head_to = branch_commit.hexsha + self.recenter_frame() + self.scale_frame() + self.reset_head(reset_head_to) + self.reset_branch(head_commit.hexsha) + + # branch is not reached, so start from branch + else: + self.parse_commits(branch_commit) + self.draw_ref(branch_commit, self.topref) + elif self.is_descendant: + self.parse_commits(branch_commit) + reset_head_to = branch_commit.hexsha self.recenter_frame() self.scale_frame() if "HEAD" in self.drawnRefs: - self.reset_head_branch(reset_head_to, shift=shift) + self.reset_head(reset_head_to) + self.reset_branch(head_commit.hexsha) else: - self.draw_ref(self.commits[0], commitId if self.no_ff else self.topref) - self.draw_ref( - self.commits[0], - self.drawnRefs["HEAD"], - text=self.repo.active_branch.name, - color=m.GREEN, - ) - + self.draw_ref(branch_commit, self.topref) else: - self.get_commits() - self.parse_commits(self.commits[0]) - self.i = 0 - self.get_commits(start=self.branch) - self.parse_commits(self.commits[0], shift=4 * m.DOWN) - self.center_frame_on_commit(self.orig_commits[0]) + self.parse_commits(head_commit) + self.parse_commits(branch_commit, shift=4 * m.DOWN) + self.center_frame_on_commit(branch_commit) self.recenter_frame() self.scale_frame() - self.reset_head(self.commits[0].hexsha) + self.reset_head(branch_commit.hexsha) + self.reset_branch(head_commit.hexsha) + self.color_by() self.fadeout() self.show_outro() From 5a9eab3b781d9bdb04d23d1ffcae3fa1df5485c7 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 17 Mar 2023 01:09:31 -0700 Subject: [PATCH 05/39] Add -c flag for switch subcommand This creates a new branch with the specified name and switches to it, but throws an error if a branch with that name already exists. Signed-off-by: Jacob Stopak --- git_sim/switch.py | 137 ++++++++++++++++++++++++++-------------------- 1 file changed, 77 insertions(+), 60 deletions(-) diff --git a/git_sim/switch.py b/git_sim/switch.py index 4fd8b0d..4cd93d7 100644 --- a/git_sim/switch.py +++ b/git_sim/switch.py @@ -12,37 +12,43 @@ class Switch(GitSimBaseCommand): - def __init__(self, branch: str): + def __init__(self, branch: str, c: bool): super().__init__() self.branch = branch + self.c = c - try: - git.repo.fun.rev_parse(self.repo, self.branch) - except git.exc.BadName: - print( - "git-sim error: '" - + self.branch - + "' is not a valid Git ref or identifier." - ) - sys.exit(1) - - if self.branch == self.repo.active_branch.name: - print("git-sim error: already on branch '" + self.branch + "'") - sys.exit(1) - - self.is_ancestor = False - self.is_descendant = False - - # branch being switched to is behind HEAD - if self.repo.active_branch.name in self.repo.git.branch( - "--contains", self.branch - ): - self.is_ancestor = True - # HEAD is behind branch being switched to - elif self.branch in self.repo.git.branch( - "--contains", self.repo.active_branch.name - ): - self.is_descendant = True + if self.c: + if self.branch in self.repo.heads: + print("git-sim error: can't create new branch '" + self.branch + "', it already exists") + sys.exit(1) + else: + try: + git.repo.fun.rev_parse(self.repo, self.branch) + except git.exc.BadName: + print( + "git-sim error: '" + + self.branch + + "' is not a valid Git ref or identifier." + ) + sys.exit(1) + + if self.branch == self.repo.active_branch.name: + print("git-sim error: already on branch '" + self.branch + "'") + sys.exit(1) + + self.is_ancestor = False + self.is_descendant = False + + # branch being switched to is behind HEAD + if self.repo.active_branch.name in self.repo.git.branch( + "--contains", self.branch + ): + self.is_ancestor = True + # HEAD is behind branch being switched to + elif self.branch in self.repo.git.branch( + "--contains", self.repo.active_branch.name + ): + self.is_descendant = True if self.branch in [branch.name for branch in self.repo.heads]: self.selected_branches.append(self.branch) @@ -60,43 +66,49 @@ def construct(self): self.show_intro() head_commit = self.get_commit() - branch_commit = self.get_commit(self.branch) - - if self.is_ancestor: - commits_in_range = list(self.repo.iter_commits(self.branch + '..HEAD')) - # branch is reached from HEAD, so draw everything - if len(commits_in_range) <= self.n: - self.parse_commits(head_commit) + # using -c flag, create new branch label and exit + if self.c: + self.parse_commits(head_commit) + self.draw_ref(head_commit, self.topref, text=self.branch, color=m.GREEN) + else: + branch_commit = self.get_commit(self.branch) + + if self.is_ancestor: + commits_in_range = list(self.repo.iter_commits(self.branch + '..HEAD')) + + # branch is reached from HEAD, so draw everything + if len(commits_in_range) <= self.n: + self.parse_commits(head_commit) + reset_head_to = branch_commit.hexsha + self.recenter_frame() + self.scale_frame() + self.reset_head(reset_head_to) + self.reset_branch(head_commit.hexsha) + + # branch is not reached, so start from branch + else: + self.parse_commits(branch_commit) + self.draw_ref(branch_commit, self.topref) + + elif self.is_descendant: + self.parse_commits(branch_commit) reset_head_to = branch_commit.hexsha self.recenter_frame() self.scale_frame() - self.reset_head(reset_head_to) - self.reset_branch(head_commit.hexsha) - - # branch is not reached, so start from branch + if "HEAD" in self.drawnRefs: + self.reset_head(reset_head_to) + self.reset_branch(head_commit.hexsha) + else: + self.draw_ref(branch_commit, self.topref) else: - self.parse_commits(branch_commit) - self.draw_ref(branch_commit, self.topref) - - elif self.is_descendant: - self.parse_commits(branch_commit) - reset_head_to = branch_commit.hexsha - self.recenter_frame() - self.scale_frame() - if "HEAD" in self.drawnRefs: - self.reset_head(reset_head_to) + self.parse_commits(head_commit) + self.parse_commits(branch_commit, shift=4 * m.DOWN) + self.center_frame_on_commit(branch_commit) + self.recenter_frame() + self.scale_frame() + self.reset_head(branch_commit.hexsha) self.reset_branch(head_commit.hexsha) - else: - self.draw_ref(branch_commit, self.topref) - else: - self.parse_commits(head_commit) - self.parse_commits(branch_commit, shift=4 * m.DOWN) - self.center_frame_on_commit(branch_commit) - self.recenter_frame() - self.scale_frame() - self.reset_head(branch_commit.hexsha) - self.reset_branch(head_commit.hexsha) self.color_by() self.fadeout() @@ -108,6 +120,11 @@ def switch( ..., help="The name of the branch to switch to", ), + c: bool = typer.Option( + False, + "-c", + help="Create the specified branch if it doesn't already exist", + ), ): - scene = Switch(branch=branch) + scene = Switch(branch=branch, c=c) handle_animations(scene=scene) From 396b03cdfbd71c31ec3979b8c663f7b544057523 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 17 Mar 2023 01:18:24 -0700 Subject: [PATCH 06/39] Update readme with switch subcommand info --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 89019e3..3b32507 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Example: `$ git-sim merge ` ## Features - Run a one-liner git-sim command in the terminal to generate a custom Git command visualization (.jpg) from your repo -- Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick` +- Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick`, `switch` - Generate an animated video (.mp4) instead of a static image using the `--animate` flag (note: significant performance slowdown, it is recommended to use `--low-quality` to speed up testing and remove when ready to generate presentation-quality video) - Color commits by parameter, such as author the `--color-by=author` option - Choose between dark mode (default) and light mode @@ -126,7 +126,7 @@ $ git-sim -h * [Manim (Community version)](https://www.manim.community/) ## Commands -Basic usage is similar to Git itself - `git-sim` takes a familiar set of subcommands including "log", "status", "add", "restore", "commit", "stash", "branch", "tag", "reset", "revert", "merge", "rebase", "cherry-pick", along with corresponding options. +Basic usage is similar to Git itself - `git-sim` takes a familiar set of subcommands including "log", "status", "add", "restore", "commit", "stash", "branch", "tag", "reset", "revert", "merge", "rebase", "cherry-pick", "switch", along with corresponding options. ```console $ git-sim [global options] [subcommand options] @@ -280,6 +280,12 @@ Usage: `git-sim cherry-pick ` ![git-sim-cherry-pick_01-05-23_22-23-08](https://user-images.githubusercontent.com/49353917/210942811-fa5155b1-4c6f-4afc-bea2-d39b4cd594aa.jpg) +### git switch +Usage: `git-sim switch [-c] ` + +- Switches the checked-out branch to ``, i.e. moves `HEAD` to the specified `` +- The `-c` flag creates a new branch with the specified name ``, assuming it doesn't already exist + ## Video animation examples ```console $ git-sim --animate reset HEAD^ From 9bb17841e7772db8d330b5278e5a7792f7d80705 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 17 Mar 2023 01:30:20 -0700 Subject: [PATCH 07/39] Add checkout subcommand Signed-off-by: Jacob Stopak --- git_sim/__main__.py | 2 + git_sim/checkout.py | 130 ++++++++++++++++++++++++++++++++++++++++++++ git_sim/switch.py | 2 +- 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 git_sim/checkout.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 37dda1d..c8010a8 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -20,6 +20,7 @@ import git_sim.status import git_sim.tag import git_sim.switch +import git_sim.checkout from git_sim.settings import ImgFormat, VideoFormat, settings from manim import config, WHITE @@ -204,6 +205,7 @@ def main( app.command()(git_sim.status.status) app.command()(git_sim.tag.tag) app.command()(git_sim.switch.switch) +app.command()(git_sim.checkout.checkout) if __name__ == "__main__": diff --git a/git_sim/checkout.py b/git_sim/checkout.py new file mode 100644 index 0000000..af1e6f4 --- /dev/null +++ b/git_sim/checkout.py @@ -0,0 +1,130 @@ +import sys +from argparse import Namespace + +import git +import manim as m +import numpy +import typer + +from git_sim.animations import handle_animations +from git_sim.git_sim_base_command import GitSimBaseCommand +from git_sim.settings import settings + + +class Checkout(GitSimBaseCommand): + def __init__(self, branch: str, b: bool): + super().__init__() + self.branch = branch + self.b = b + + if self.b: + if self.branch in self.repo.heads: + print("git-sim error: can't create new branch '" + self.branch + "', it already exists") + sys.exit(1) + else: + try: + git.repo.fun.rev_parse(self.repo, self.branch) + except git.exc.BadName: + print( + "git-sim error: '" + + self.branch + + "' is not a valid Git ref or identifier." + ) + sys.exit(1) + + if self.branch == self.repo.active_branch.name: + print("git-sim error: already on branch '" + self.branch + "'") + sys.exit(1) + + self.is_ancestor = False + self.is_descendant = False + + # branch being checked out is behind HEAD + if self.repo.active_branch.name in self.repo.git.branch( + "--contains", self.branch + ): + self.is_ancestor = True + # HEAD is behind branch being checked out + elif self.branch in self.repo.git.branch( + "--contains", self.repo.active_branch.name + ): + self.is_descendant = True + + if self.branch in [branch.name for branch in self.repo.heads]: + self.selected_branches.append(self.branch) + + try: + self.selected_branches.append(self.repo.active_branch.name) + except TypeError: + pass + + def construct(self): + if not settings.stdout and not settings.output_only_path and not settings.quiet: + print( + f"{settings.INFO_STRING } {type(self).__name__.lower()}{' -b' if self.b else ''} {self.branch}" + ) + + self.show_intro() + head_commit = self.get_commit() + + # using -b flag, create new branch label and exit + if self.b: + self.parse_commits(head_commit) + self.draw_ref(head_commit, self.topref, text=self.branch, color=m.GREEN) + else: + branch_commit = self.get_commit(self.branch) + + if self.is_ancestor: + commits_in_range = list(self.repo.iter_commits(self.branch + '..HEAD')) + + # branch is reached from HEAD, so draw everything + if len(commits_in_range) <= self.n: + self.parse_commits(head_commit) + reset_head_to = branch_commit.hexsha + self.recenter_frame() + self.scale_frame() + self.reset_head(reset_head_to) + self.reset_branch(head_commit.hexsha) + + # branch is not reached, so start from branch + else: + self.parse_commits(branch_commit) + self.draw_ref(branch_commit, self.topref) + + elif self.is_descendant: + self.parse_commits(branch_commit) + reset_head_to = branch_commit.hexsha + self.recenter_frame() + self.scale_frame() + if "HEAD" in self.drawnRefs: + self.reset_head(reset_head_to) + self.reset_branch(head_commit.hexsha) + else: + self.draw_ref(branch_commit, self.topref) + else: + self.parse_commits(head_commit) + self.parse_commits(branch_commit, shift=4 * m.DOWN) + self.center_frame_on_commit(branch_commit) + self.recenter_frame() + self.scale_frame() + self.reset_head(branch_commit.hexsha) + self.reset_branch(head_commit.hexsha) + + self.color_by() + self.fadeout() + self.show_outro() + + +def checkout( + branch: str = typer.Argument( + ..., + help="The name of the branch to checkout", + ), + b: bool = typer.Option( + False, + "-b", + help="Create the specified branch if it doesn't already exist", + ), +): + scene = Checkout(branch=branch, b=b) + handle_animations(scene=scene) diff --git a/git_sim/switch.py b/git_sim/switch.py index 4cd93d7..c37b912 100644 --- a/git_sim/switch.py +++ b/git_sim/switch.py @@ -61,7 +61,7 @@ def __init__(self, branch: str, c: bool): def construct(self): if not settings.stdout and not settings.output_only_path and not settings.quiet: print( - f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.branch}" + f"{settings.INFO_STRING } {type(self).__name__.lower()}{' -c' if self.c else ''} {self.branch}" ) self.show_intro() From 0756826669a408d8cef402e9833628ba7a305650 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 17 Mar 2023 10:59:41 -0700 Subject: [PATCH 08/39] Update readme to include checkout subcommand info Signed-off-by: Jacob Stopak --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3b32507..d46ad67 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Example: `$ git-sim merge ` ## Features - Run a one-liner git-sim command in the terminal to generate a custom Git command visualization (.jpg) from your repo -- Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick`, `switch` +- Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick`, `switch`, `checkout` - Generate an animated video (.mp4) instead of a static image using the `--animate` flag (note: significant performance slowdown, it is recommended to use `--low-quality` to speed up testing and remove when ready to generate presentation-quality video) - Color commits by parameter, such as author the `--color-by=author` option - Choose between dark mode (default) and light mode @@ -126,7 +126,7 @@ $ git-sim -h * [Manim (Community version)](https://www.manim.community/) ## Commands -Basic usage is similar to Git itself - `git-sim` takes a familiar set of subcommands including "log", "status", "add", "restore", "commit", "stash", "branch", "tag", "reset", "revert", "merge", "rebase", "cherry-pick", "switch", along with corresponding options. +Basic usage is similar to Git itself - `git-sim` takes a familiar set of subcommands including "log", "status", "add", "restore", "commit", "stash", "branch", "tag", "reset", "revert", "merge", "rebase", "cherry-pick", "switch", "checkout", along with corresponding options. ```console $ git-sim [global options] [subcommand options] @@ -284,7 +284,13 @@ Usage: `git-sim cherry-pick ` Usage: `git-sim switch [-c] ` - Switches the checked-out branch to ``, i.e. moves `HEAD` to the specified `` -- The `-c` flag creates a new branch with the specified name ``, assuming it doesn't already exist +- The `-c` flag creates a new branch with the specified name `` and switches to it, assuming it doesn't already exist + +### git checkout +Usage: `git-sim checkout [-b] ` + +- Checks out `` into the working directory, i.e. moves `HEAD` to the specified `` +- The `-b` flag creates a new branch with the specified name `` and checks it out, assuming it doesn't already exist ## Video animation examples ```console From 3e1b65c4cfbf65639f0ae29b77239e883a1cf706 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sat, 18 Mar 2023 23:11:45 -0700 Subject: [PATCH 09/39] Add fetch subcommand Signed-off-by: Jacob Stopak --- README.md | 9 ++++- git_sim/__main__.py | 3 ++ git_sim/fetch.py | 94 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 git_sim/fetch.py diff --git a/README.md b/README.md index d46ad67..20e2a15 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Example: `$ git-sim merge ` ## Features - Run a one-liner git-sim command in the terminal to generate a custom Git command visualization (.jpg) from your repo -- Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick`, `switch`, `checkout` +- Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick`, `switch`, `checkout`, `fetch` - Generate an animated video (.mp4) instead of a static image using the `--animate` flag (note: significant performance slowdown, it is recommended to use `--low-quality` to speed up testing and remove when ready to generate presentation-quality video) - Color commits by parameter, such as author the `--color-by=author` option - Choose between dark mode (default) and light mode @@ -126,7 +126,7 @@ $ git-sim -h * [Manim (Community version)](https://www.manim.community/) ## Commands -Basic usage is similar to Git itself - `git-sim` takes a familiar set of subcommands including "log", "status", "add", "restore", "commit", "stash", "branch", "tag", "reset", "revert", "merge", "rebase", "cherry-pick", "switch", "checkout", along with corresponding options. +Basic usage is similar to Git itself - `git-sim` takes a familiar set of subcommands including "log", "status", "add", "restore", "commit", "stash", "branch", "tag", "reset", "revert", "merge", "rebase", "cherry-pick", "switch", "checkout", "fetch", along with corresponding options. ```console $ git-sim [global options] [subcommand options] @@ -292,6 +292,11 @@ Usage: `git-sim checkout [-b] ` - Checks out `` into the working directory, i.e. moves `HEAD` to the specified `` - The `-b` flag creates a new branch with the specified name `` and checks it out, assuming it doesn't already exist +### git fetch +Usage: `git-sim fetch ` + +- Fetches the specified `` from the specified `` to the local repo + ## Video animation examples ```console $ git-sim --animate reset HEAD^ diff --git a/git_sim/__main__.py b/git_sim/__main__.py index c8010a8..35d5dc7 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -21,6 +21,8 @@ import git_sim.tag import git_sim.switch import git_sim.checkout +import git_sim.fetch + from git_sim.settings import ImgFormat, VideoFormat, settings from manim import config, WHITE @@ -206,6 +208,7 @@ def main( app.command()(git_sim.tag.tag) app.command()(git_sim.switch.switch) app.command()(git_sim.checkout.checkout) +app.command()(git_sim.fetch.fetch) if __name__ == "__main__": diff --git a/git_sim/fetch.py b/git_sim/fetch.py new file mode 100644 index 0000000..4222591 --- /dev/null +++ b/git_sim/fetch.py @@ -0,0 +1,94 @@ +import sys +import os +from argparse import Namespace + +import git +import manim as m +import numpy +import typer +import tempfile +import shutil +import stat + +from git_sim.animations import handle_animations +from git_sim.git_sim_base_command import GitSimBaseCommand +from git_sim.settings import settings + + +class Fetch(GitSimBaseCommand): + def __init__(self, remote: str, branch: str): + super().__init__() + self.remote = remote + self.branch = branch + settings.max_branches_per_commit = 2 + + if self.remote not in self.repo.remotes: + print("git-sim error: no remote with name '" + self.remote + "'") + sys.exit(1) + + def construct(self): + if not settings.stdout and not settings.output_only_path and not settings.quiet: + print( + f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.remote} {self.branch}" + ) + + self.show_intro() + + git_root = self.repo.git.rev_parse("--show-toplevel") + repo_name = os.path.basename(self.repo.working_dir) + new_dir = os.path.join(tempfile.gettempdir(), "git_sim", repo_name) + + orig_remotes = self.repo.remotes + self.repo = git.Repo.clone_from(git_root, new_dir, no_hardlinks=True) + for r1 in orig_remotes: + for r2 in self.repo.remotes: + if r1.name == r2.name: + r2.set_url(r1.url) + self.repo.git.fetch(self.remote, self.branch) + + # local branch doesn't exist + if self.branch not in self.repo.heads: + start_parse_from_remote = True + # fetched branch is ahead of local branch + elif (self.remote + "/" + self.branch) in self.repo.git.branch( + "-r", "--contains", self.branch + ): + start_parse_from_remote = True + # fetched branch is behind local branch + elif self.branch in self.repo.git.branch( + "--contains", (self.remote + "/" + self.branch) + ): + start_parse_from_remote = False + else: + start_parse_from_remote = True + + if start_parse_from_remote: + commit = self.get_commit(self.remote + "/" + self.branch) + else: + commit = self.get_commit(self.branch) + self.parse_commits(commit) + + self.recenter_frame() + self.scale_frame() + self.color_by() + self.fadeout() + self.show_outro() + self.repo.git.clear_cache() + shutil.rmtree(new_dir, onerror=del_rw) + +def del_rw(action, name, exc): + os.chmod(name, stat.S_IWRITE) + os.remove(name) + +def fetch( + remote: str = typer.Argument( + ..., + help="The name of the remote to fetch from", + ), + branch: str = typer.Argument( + ..., + help="The name of the branch to fetch", + ), +): + scene = Fetch(remote=remote, branch=branch) + handle_animations(scene=scene) From 497edaf63dca2b94ccb6c6882d1bbddbcb2e3006 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Tue, 21 Mar 2023 00:28:19 -0700 Subject: [PATCH 10/39] Add pull subcommand Signed-off-by: Jacob Stopak --- README.md | 10 +++- git_sim/__main__.py | 2 + git_sim/pull.py | 125 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 git_sim/pull.py diff --git a/README.md b/README.md index 20e2a15..897972a 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Example: `$ git-sim merge ` ## Features - Run a one-liner git-sim command in the terminal to generate a custom Git command visualization (.jpg) from your repo -- Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick`, `switch`, `checkout`, `fetch` +- Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick`, `switch`, `checkout`, `fetch`, `pull` - Generate an animated video (.mp4) instead of a static image using the `--animate` flag (note: significant performance slowdown, it is recommended to use `--low-quality` to speed up testing and remove when ready to generate presentation-quality video) - Color commits by parameter, such as author the `--color-by=author` option - Choose between dark mode (default) and light mode @@ -126,7 +126,7 @@ $ git-sim -h * [Manim (Community version)](https://www.manim.community/) ## Commands -Basic usage is similar to Git itself - `git-sim` takes a familiar set of subcommands including "log", "status", "add", "restore", "commit", "stash", "branch", "tag", "reset", "revert", "merge", "rebase", "cherry-pick", "switch", "checkout", "fetch", along with corresponding options. +Basic usage is similar to Git itself - `git-sim` takes a familiar set of subcommands including "log", "status", "add", "restore", "commit", "stash", "branch", "tag", "reset", "revert", "merge", "rebase", "cherry-pick", "switch", "checkout", "fetch", "pull", along with corresponding options. ```console $ git-sim [global options] [subcommand options] @@ -297,6 +297,12 @@ Usage: `git-sim fetch ` - Fetches the specified `` from the specified `` to the local repo +### git pull +Usage: `git-sim pull [ ]` + +- Pulls the specified `` from the specified `` to the local repo +- If `` and `` are not specified, the active branch is pulled from the default remote + ## Video animation examples ```console $ git-sim --animate reset HEAD^ diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 35d5dc7..849e2d5 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -22,6 +22,7 @@ import git_sim.switch import git_sim.checkout import git_sim.fetch +import git_sim.pull from git_sim.settings import ImgFormat, VideoFormat, settings from manim import config, WHITE @@ -209,6 +210,7 @@ def main( app.command()(git_sim.switch.switch) app.command()(git_sim.checkout.checkout) app.command()(git_sim.fetch.fetch) +app.command()(git_sim.pull.pull) if __name__ == "__main__": diff --git a/git_sim/pull.py b/git_sim/pull.py new file mode 100644 index 0000000..7788e77 --- /dev/null +++ b/git_sim/pull.py @@ -0,0 +1,125 @@ +import sys +import os +from argparse import Namespace + +import git +import manim as m +import numpy +import typer +import tempfile +import shutil +import stat +import re + +from git_sim.animations import handle_animations +from git_sim.git_sim_base_command import GitSimBaseCommand +from git_sim.settings import settings + + +class Pull(GitSimBaseCommand): + def __init__(self, remote: str = None, branch: str = None): + super().__init__() + self.remote = remote + self.branch = branch + settings.max_branches_per_commit = 2 + + if self.remote and self.remote not in self.repo.remotes: + print("git-sim error: no remote with name '" + self.remote + "'") + sys.exit(1) + + def construct(self): + if not settings.stdout and not settings.output_only_path and not settings.quiet: + print( + f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.remote if self.remote else ''} {self.branch if self.branch else ''}" + ) + + self.show_intro() + + # Configure paths to make local clone to run networked commands in + git_root = self.repo.git.rev_parse("--show-toplevel") + repo_name = os.path.basename(self.repo.working_dir) + new_dir = os.path.join(tempfile.gettempdir(), "git_sim", repo_name) + + # Save remotes and create the local clone + orig_remotes = self.repo.remotes + self.repo = git.Repo.clone_from(git_root, new_dir, no_hardlinks=True) + + # Reset the remotes in the local clone to the original remotes + for r1 in orig_remotes: + for r2 in self.repo.remotes: + if r1.name == r2.name: + r2.set_url(r1.url) + + # Pull the remote into the local clone + try: + self.repo.git.pull(self.remote, self.branch) + head_commit = self.get_commit() + self.parse_commits(head_commit) + self.recenter_frame() + self.scale_frame() + + # But if we get merge conflicts... + except git.GitCommandError as e: + if "CONFLICT" in e.stdout: + # Restrict to default number of commits since we'll show the table/zones + self.n = self.n_default + + # Get list of conflicted filenames + self.conflicted_files = re.findall(r"Merge conflict in (.+)", e.stdout) + + head_commit = self.get_commit() + self.parse_commits(head_commit) + self.recenter_frame() + self.scale_frame() + + # Show the conflicted files names in the table/zones + self.vsplit_frame() + self.setup_and_draw_zones( + first_column_name="----", + second_column_name="Conflicted files", + third_column_name="----", + ) + else: + print(f"git-sim error: git pull failed for unhandled reason: {e.stdout}") + shutil.rmtree(new_dir, onerror=del_rw) + sys.exit(1) + + self.color_by() + self.fadeout() + self.show_outro() + + # Unlink the program from the filesystem + self.repo.git.clear_cache() + + # Delete the local clone + shutil.rmtree(new_dir, onerror=del_rw) + + # Override to display conflicted filenames + def populate_zones( + self, + firstColumnFileNames, + secondColumnFileNames, + thirdColumnFileNames, + firstColumnArrowMap={}, + secondColumnArrowMap={}, + thirdColumnArrowMap={}, + ): + for filename in self.conflicted_files: + secondColumnFileNames.add(filename) + +def del_rw(action, name, exc): + os.chmod(name, stat.S_IWRITE) + os.remove(name) + +def pull( + remote: str = typer.Argument( + default=None, + help="The name of the remote to pull from", + ), + branch: str = typer.Argument( + default=None, + help="The name of the branch to pull", + ), +): + scene = Pull(remote=remote, branch=branch) + handle_animations(scene=scene) From b70bf9942679e471d14e6270650c232627b7b923 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Tue, 21 Mar 2023 14:14:30 -0700 Subject: [PATCH 11/39] Add push subcommand Signed-off-by: Jacob Stopak --- README.md | 11 +++- git_sim/__main__.py | 2 + git_sim/push.py | 130 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 git_sim/push.py diff --git a/README.md b/README.md index 897972a..8862540 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Example: `$ git-sim merge ` ## Features - Run a one-liner git-sim command in the terminal to generate a custom Git command visualization (.jpg) from your repo -- Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick`, `switch`, `checkout`, `fetch`, `pull` +- Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick`, `switch`, `checkout`, `fetch`, `pull`, `push` - Generate an animated video (.mp4) instead of a static image using the `--animate` flag (note: significant performance slowdown, it is recommended to use `--low-quality` to speed up testing and remove when ready to generate presentation-quality video) - Color commits by parameter, such as author the `--color-by=author` option - Choose between dark mode (default) and light mode @@ -126,7 +126,7 @@ $ git-sim -h * [Manim (Community version)](https://www.manim.community/) ## Commands -Basic usage is similar to Git itself - `git-sim` takes a familiar set of subcommands including "log", "status", "add", "restore", "commit", "stash", "branch", "tag", "reset", "revert", "merge", "rebase", "cherry-pick", "switch", "checkout", "fetch", "pull", along with corresponding options. +Basic usage is similar to Git itself - `git-sim` takes a familiar set of subcommands including "log", "status", "add", "restore", "commit", "stash", "branch", "tag", "reset", "revert", "merge", "rebase", "cherry-pick", "switch", "checkout", "fetch", "pull", "push" along with corresponding options. ```console $ git-sim [global options] [subcommand options] @@ -302,6 +302,13 @@ Usage: `git-sim pull [ ]` - Pulls the specified `` from the specified `` to the local repo - If `` and `` are not specified, the active branch is pulled from the default remote +- If merge conflicts occur, they are displayed in a table + +### git push +Usage: `git-sim push [ ]` + +- Pushes the specified `` to the specified `` +- If `` and `` are not specified, the active branch is pushed to the default remote ## Video animation examples ```console diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 849e2d5..8ba4e60 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -23,6 +23,7 @@ import git_sim.checkout import git_sim.fetch import git_sim.pull +import git_sim.push from git_sim.settings import ImgFormat, VideoFormat, settings from manim import config, WHITE @@ -211,6 +212,7 @@ def main( app.command()(git_sim.checkout.checkout) app.command()(git_sim.fetch.fetch) app.command()(git_sim.pull.pull) +app.command()(git_sim.push.push) if __name__ == "__main__": diff --git a/git_sim/push.py b/git_sim/push.py new file mode 100644 index 0000000..0433cc2 --- /dev/null +++ b/git_sim/push.py @@ -0,0 +1,130 @@ +import sys +import os +from argparse import Namespace + +import git +import manim as m +import numpy +import typer +import tempfile +import shutil +import stat +import re + +from git_sim.animations import handle_animations +from git_sim.git_sim_base_command import GitSimBaseCommand +from git_sim.settings import settings + + +class Push(GitSimBaseCommand): + def __init__(self, remote: str = None, branch: str = None): + super().__init__() + self.remote = remote + self.branch = branch + settings.max_branches_per_commit = 2 + + if self.remote and self.remote not in self.repo.remotes: + print("git-sim error: no remote with name '" + self.remote + "'") + sys.exit(1) + + def construct(self): + if not settings.stdout and not settings.output_only_path and not settings.quiet: + print( + f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.remote if self.remote else ''} {self.branch if self.branch else ''}" + ) + + self.show_intro() + + # Configure paths to make local clone to run networked commands in + git_root = self.repo.git.rev_parse("--show-toplevel") + repo_name = os.path.basename(self.repo.working_dir) + new_dir = os.path.join(tempfile.gettempdir(), "git_sim", repo_name) + new_dir2 = os.path.join(tempfile.gettempdir(), "git_sim", repo_name + "2") + + # Save remotes + orig_remotes = self.repo.remotes + + # Create local clone of local repo + self.repo = git.Repo.clone_from(git_root, new_dir, no_hardlinks=True) + if self.remote: + for r in orig_remotes: + if self.remote == r.name: + remote_url = r.url + break + else: + remote_url = orig_remotes[0].url + + # Create local clone of remote repo to simulate push to so we don't touch the real remote + self.remote_repo = git.Repo.clone_from(remote_url, new_dir2, no_hardlinks=True, bare=True) + + # Reset local clone remote to the local clone of remote repo + if self.remote: + for r in self.repo.remotes: + if self.remote == r.name: + r.set_url(new_dir2) + else: + self.repo.remotes[0].set_url(new_dir2) + + # Push the local clone into the local clone of the remote repo + try: + self.repo.git.push(self.remote, self.branch) + head_commit = self.get_commit() + self.parse_commits(head_commit) + self.recenter_frame() + self.scale_frame() + + # But if we get merge conflicts... + except git.GitCommandError as e: + if "CONFLICT" in e.stdout: + # Restrict to default number of commits since we'll show the table/zones + self.n = self.n_default + + # Get list of conflicted filenames + self.conflicted_files = re.findall(r"Merge conflict in (.+)", e.stdout) + + head_commit = self.get_commit() + self.parse_commits(head_commit) + self.recenter_frame() + self.scale_frame() + + # Show the conflicted files names in the table/zones + self.vsplit_frame() + self.setup_and_draw_zones( + first_column_name="----", + second_column_name="Conflicted files", + third_column_name="----", + ) + else: + print(f"git-sim error: git push failed: {e}") + self.repo.git.clear_cache() + shutil.rmtree(new_dir, onerror=del_rw) + shutil.rmtree(new_dir2, onerror=del_rw) + sys.exit(1) + + self.color_by() + self.fadeout() + self.show_outro() + + # Unlink the program from the filesystem + self.repo.git.clear_cache() + + # Delete the local clones + shutil.rmtree(new_dir, onerror=del_rw) + shutil.rmtree(new_dir2, onerror=del_rw) + +def del_rw(action, name, exc): + os.chmod(name, stat.S_IWRITE) + os.remove(name) + +def push( + remote: str = typer.Argument( + default=None, + help="The name of the remote to push to", + ), + branch: str = typer.Argument( + default=None, + help="The name of the branch to push", + ), +): + scene = Push(remote=remote, branch=branch) + handle_animations(scene=scene) From c6bbb11939f016d966fabb8a9c8ea1a58d222f6c Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Tue, 21 Mar 2023 14:16:42 -0700 Subject: [PATCH 12/39] Add missing call to unlink program from filesystem Signed-off-by: Jacob Stopak --- git_sim/pull.py | 1 + 1 file changed, 1 insertion(+) diff --git a/git_sim/pull.py b/git_sim/pull.py index 7788e77..fbe6460 100644 --- a/git_sim/pull.py +++ b/git_sim/pull.py @@ -81,6 +81,7 @@ def construct(self): ) else: print(f"git-sim error: git pull failed for unhandled reason: {e.stdout}") + self.repo.git.clear_cache() shutil.rmtree(new_dir, onerror=del_rw) sys.exit(1) From b4507302057c9b0981df04947eee3ae7a6fb6685 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Wed, 22 Mar 2023 12:20:21 -0700 Subject: [PATCH 13/39] Update push subcommand output to clarify pull-before-push Signed-off-by: Jacob Stopak --- git_sim/git_sim_base_command.py | 14 ++++-- git_sim/push.py | 89 +++++++++++++++++++++------------ 2 files changed, 68 insertions(+), 35 deletions(-) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index b94f05d..892d43b 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -78,6 +78,7 @@ def parse_commits( i=0, prevCircle=None, shift=numpy.array([0.0, 0.0, 0.0]), + make_branches_remote=False, ): commit = commit or self.get_commit() @@ -91,7 +92,7 @@ def parse_commits( if commit != "dark": if not hide_refs and isNewCommit: self.draw_head(commit, i, commitId) - self.draw_branch(commit, i) + self.draw_branch(commit, i, make_branches_remote=make_branches_remote) self.draw_tag(commit, i) if ( not isinstance(arrow, m.CurvedArrow) @@ -348,7 +349,7 @@ def draw_head(self, commit, i, commitId): if i == 0 and self.first_parse: self.topref = self.prevRef - def draw_branch(self, commit, i): + def draw_branch(self, commit, i, make_branches_remote=False): x = 0 remote_tracking_branches = self.get_remote_tracking_branches() @@ -369,7 +370,7 @@ def draw_branch(self, commit, i): and commit.hexsha == remote_tracking_branches[branch] ): branchText = m.Text( - branch, font="Monospace", font_size=20, color=self.fontColor + branch if not make_branches_remote else make_branches_remote + "/" + branch, font="Monospace", font_size=20, color=self.fontColor ) branchRec = m.Rectangle( color=m.GREEN, @@ -1137,6 +1138,13 @@ def color_by(self, offset=0): elif settings.color_by == "branch": pass + elif settings.color_by == "notlocal": + for commit_id in self.drawnCommits: + try: + self.orig_repo.commit(commit_id) + except ValueError: + self.drawnCommits[commit_id].set_color(m.GOLD) + def add_group_to_author_groups(self, author, group): if author not in self.author_groups: self.author_groups[author] = [group] diff --git a/git_sim/push.py b/git_sim/push.py index 0433cc2..7bd9850 100644 --- a/git_sim/push.py +++ b/git_sim/push.py @@ -22,6 +22,7 @@ def __init__(self, remote: str = None, branch: str = None): self.remote = remote self.branch = branch settings.max_branches_per_commit = 2 + settings.color_by = "notlocal" if self.remote and self.remote not in self.repo.remotes: print("git-sim error: no remote with name '" + self.remote + "'") @@ -66,52 +67,76 @@ def construct(self): self.repo.remotes[0].set_url(new_dir2) # Push the local clone into the local clone of the remote repo + push_result = 0 try: self.repo.git.push(self.remote, self.branch) - head_commit = self.get_commit() - self.parse_commits(head_commit) - self.recenter_frame() - self.scale_frame() - - # But if we get merge conflicts... + # If push fails... except git.GitCommandError as e: - if "CONFLICT" in e.stdout: - # Restrict to default number of commits since we'll show the table/zones - self.n = self.n_default - - # Get list of conflicted filenames - self.conflicted_files = re.findall(r"Merge conflict in (.+)", e.stdout) - - head_commit = self.get_commit() - self.parse_commits(head_commit) - self.recenter_frame() - self.scale_frame() - - # Show the conflicted files names in the table/zones - self.vsplit_frame() - self.setup_and_draw_zones( - first_column_name="----", - second_column_name="Conflicted files", - third_column_name="----", - ) - else: - print(f"git-sim error: git push failed: {e}") - self.repo.git.clear_cache() - shutil.rmtree(new_dir, onerror=del_rw) - shutil.rmtree(new_dir2, onerror=del_rw) - sys.exit(1) - + print(f"git-sim error: git push failed: {e.stderr}") + if "rejected" in e.stderr and "fetch first" in e.stderr: + push_result = 1 + self.orig_repo = self.repo + self.repo = self.remote_repo + + head_commit = self.get_commit() + self.parse_commits(head_commit, make_branches_remote=(self.remote if self.remote else self.repo.remotes[0].name)) + self.recenter_frame() + self.scale_frame() + self.failed_push(push_result) self.color_by() self.fadeout() self.show_outro() # Unlink the program from the filesystem self.repo.git.clear_cache() + if self.orig_repo: + self.orig_repo.git.clear_cache() # Delete the local clones shutil.rmtree(new_dir, onerror=del_rw) shutil.rmtree(new_dir2, onerror=del_rw) + def failed_push(self, push_result): + if push_result == 1: + text1 = m.Text( + f"'git push' failed since remote has commits that don't exist locally, run 'git pull' and try again", + font="Monospace", + font_size=20, + color=self.fontColor, + weight=m.BOLD, + ) + text1.move_to([self.camera.frame.get_center()[0], 4, 0]) + + text2 = m.Text( + f"Gold commits exist in remote repo, but not locally (need to be pulled)", + font="Monospace", + font_size=20, + color=m.GOLD, + weight=m.BOLD, + ) + text2.move_to(text1.get_center()).shift(m.DOWN / 2) + + text3 = m.Text( + f"Red commits exist in both local and remote repos", + font="Monospace", + font_size=20, + color=m.RED, + weight=m.BOLD, + ) + text3.move_to(text2.get_center()).shift(m.DOWN / 2) + + self.toFadeOut.add(text1) + self.toFadeOut.add(text2) + self.toFadeOut.add(text3) + self.recenter_frame() + self.scale_frame() + if settings.animate: + self.play(m.AddTextLetterByLetter(text1), m.AddTextLetterByLetter(text2), m.AddTextLetterByLetter(text3)) + else: + self.add(text1, text2, text3) + + + def del_rw(action, name, exc): os.chmod(name, stat.S_IWRITE) os.remove(name) From d921ba01882c1a6f221b572beb1bfa5111629efb Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Wed, 22 Mar 2023 12:50:38 -0700 Subject: [PATCH 14/39] Update push subcommand pull-before-push language Signed-off-by: Jacob Stopak --- git_sim/push.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/git_sim/push.py b/git_sim/push.py index 7bd9850..2eac8cf 100644 --- a/git_sim/push.py +++ b/git_sim/push.py @@ -99,41 +99,51 @@ def construct(self): def failed_push(self, push_result): if push_result == 1: text1 = m.Text( - f"'git push' failed since remote has commits that don't exist locally, run 'git pull' and try again", + f"'git push' failed since the remote repo has commits that don't exist locally.", font="Monospace", font_size=20, color=self.fontColor, weight=m.BOLD, ) - text1.move_to([self.camera.frame.get_center()[0], 4, 0]) + text1.move_to([self.camera.frame.get_center()[0], 5, 0]) text2 = m.Text( - f"Gold commits exist in remote repo, but not locally (need to be pulled)", + f"Run 'git pull' (or 'git-sim pull' to simulate first) and then try again.", font="Monospace", font_size=20, - color=m.GOLD, + color=self.fontColor, weight=m.BOLD, ) text2.move_to(text1.get_center()).shift(m.DOWN / 2) text3 = m.Text( - f"Red commits exist in both local and remote repos", + f"Gold commits exist in remote repo, but not locally (need to be pulled).", font="Monospace", font_size=20, - color=m.RED, + color=m.GOLD, weight=m.BOLD, ) text3.move_to(text2.get_center()).shift(m.DOWN / 2) + text4 = m.Text( + f"Red commits exist in both local and remote repos.", + font="Monospace", + font_size=20, + color=m.RED, + weight=m.BOLD, + ) + text4.move_to(text3.get_center()).shift(m.DOWN / 2) + self.toFadeOut.add(text1) self.toFadeOut.add(text2) self.toFadeOut.add(text3) + self.toFadeOut.add(text4) self.recenter_frame() self.scale_frame() if settings.animate: - self.play(m.AddTextLetterByLetter(text1), m.AddTextLetterByLetter(text2), m.AddTextLetterByLetter(text3)) + self.play(m.AddTextLetterByLetter(text2), m.AddTextLetterByLetter(text2), m.AddTextLetterByLetter(text3), m.AddTextLetterByLetter(text4)) else: - self.add(text1, text2, text3) + self.add(text1, text2, text3, text4) From 52f05f74db9e8ed964177c029e77dfb4d0c3ac67 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Wed, 22 Mar 2023 13:36:58 -0700 Subject: [PATCH 15/39] Update push subcommand so push error is only printed for non-handled push fails Signed-off-by: Jacob Stopak --- git_sim/push.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git_sim/push.py b/git_sim/push.py index 2eac8cf..e8a51ab 100644 --- a/git_sim/push.py +++ b/git_sim/push.py @@ -72,11 +72,12 @@ def construct(self): self.repo.git.push(self.remote, self.branch) # If push fails... except git.GitCommandError as e: - print(f"git-sim error: git push failed: {e.stderr}") if "rejected" in e.stderr and "fetch first" in e.stderr: push_result = 1 self.orig_repo = self.repo self.repo = self.remote_repo + else: + print(f"git-sim error: git push failed: {e.stderr}") head_commit = self.get_commit() self.parse_commits(head_commit, make_branches_remote=(self.remote if self.remote else self.repo.remotes[0].name)) From d55f2a9b085062e0a4f5d99b404a964116250fdd Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Wed, 22 Mar 2023 13:40:01 -0700 Subject: [PATCH 16/39] Update readme Signed-off-by: Jacob Stopak --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8862540..630de45 100644 --- a/README.md +++ b/README.md @@ -307,8 +307,9 @@ Usage: `git-sim pull [ ]` ### git push Usage: `git-sim push [ ]` -- Pushes the specified `` to the specified `` +- Pushes the specified `` to the specified `` and displays the local result - If `` and `` are not specified, the active branch is pushed to the default remote +- If the push fails due to remote changes that don't exist in the local repo, a message is included telling the user to pull first, along with color coding which commits need to be pulled ## Video animation examples ```console From 8b3405f15dff13e2958b9fdbac55196f9a1aabb4 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Wed, 22 Mar 2023 16:22:30 -0700 Subject: [PATCH 17/39] Add clone subcommand Signed-off-by: Jacob Stopak --- README.md | 10 +++- git_sim/__main__.py | 21 +++++---- git_sim/clone.py | 111 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 git_sim/clone.py diff --git a/README.md b/README.md index 630de45..5042b3a 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Example: `$ git-sim merge ` ## Features - Run a one-liner git-sim command in the terminal to generate a custom Git command visualization (.jpg) from your repo -- Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick`, `switch`, `checkout`, `fetch`, `pull`, `push` +- Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick`, `switch`, `checkout`, `fetch`, `pull`, `push`, `clone` - Generate an animated video (.mp4) instead of a static image using the `--animate` flag (note: significant performance slowdown, it is recommended to use `--low-quality` to speed up testing and remove when ready to generate presentation-quality video) - Color commits by parameter, such as author the `--color-by=author` option - Choose between dark mode (default) and light mode @@ -126,7 +126,7 @@ $ git-sim -h * [Manim (Community version)](https://www.manim.community/) ## Commands -Basic usage is similar to Git itself - `git-sim` takes a familiar set of subcommands including "log", "status", "add", "restore", "commit", "stash", "branch", "tag", "reset", "revert", "merge", "rebase", "cherry-pick", "switch", "checkout", "fetch", "pull", "push" along with corresponding options. +Basic usage is similar to Git itself - `git-sim` takes a familiar set of subcommands including "log", "status", "add", "restore", "commit", "stash", "branch", "tag", "reset", "revert", "merge", "rebase", "cherry-pick", "switch", "checkout", "fetch", "pull", "push", "clone", along with corresponding options. ```console $ git-sim [global options] [subcommand options] @@ -311,6 +311,12 @@ Usage: `git-sim push [ ]` - If `` and `` are not specified, the active branch is pushed to the default remote - If the push fails due to remote changes that don't exist in the local repo, a message is included telling the user to pull first, along with color coding which commits need to be pulled +### git clone +Usage: `git-sim clone ` + +- Clone the remote repo from `` (web URL or filesystem path) to a new folder in the current directory +- Output will report if clone operation is successful and show log of local clone + ## Video animation examples ```console $ git-sim --animate reset HEAD^ diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 8ba4e60..e82b7c3 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -24,6 +24,7 @@ import git_sim.fetch import git_sim.pull import git_sim.push +import git_sim.clone from git_sim.settings import ImgFormat, VideoFormat, settings from manim import config, WHITE @@ -171,14 +172,17 @@ def main( settings.all = all settings.color_by = color_by - if sys.platform == "linux" or sys.platform == "darwin": - repo_name = git.repo.Repo( - search_parent_directories=True - ).working_tree_dir.split("/")[-1] - elif sys.platform == "win32": - repo_name = git.repo.Repo( - search_parent_directories=True - ).working_tree_dir.split("\\")[-1] + try: + if sys.platform == "linux" or sys.platform == "darwin": + repo_name = git.repo.Repo( + search_parent_directories=True + ).working_tree_dir.split("/")[-1] + elif sys.platform == "win32": + repo_name = git.repo.Repo( + search_parent_directories=True + ).working_tree_dir.split("\\")[-1] + except git.InvalidGitRepositoryError as e: + repo_name = "" settings.media_dir = os.path.join(settings.media_dir, repo_name) @@ -213,6 +217,7 @@ def main( app.command()(git_sim.fetch.fetch) app.command()(git_sim.pull.pull) app.command()(git_sim.push.push) +app.command()(git_sim.clone.clone) if __name__ == "__main__": diff --git a/git_sim/clone.py b/git_sim/clone.py new file mode 100644 index 0000000..5b7f206 --- /dev/null +++ b/git_sim/clone.py @@ -0,0 +1,111 @@ +import sys +import os +from argparse import Namespace + +import git +import manim as m +import numpy +import typer +import tempfile +import shutil +import stat +import re + +from git_sim.animations import handle_animations +from git_sim.git_sim_base_command import GitSimBaseCommand +from git_sim.settings import settings + + +class Clone(GitSimBaseCommand): + # Override since 'clone' subcommand shouldn't require repo to exist + def init_repo(self): + pass + + def __init__(self, url: str): + super().__init__() + self.url = url + settings.max_branches_per_commit = 2 + + def construct(self): + if not settings.stdout and not settings.output_only_path and not settings.quiet: + print( + f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.url}" + ) + + self.show_intro() + + # Configure paths to make local clone to run networked commands in + repo_name = re.search(r"/([^/]+)/?$", self.url) + if repo_name: + repo_name = repo_name.group(1) + if repo_name.endswith(".git"): + repo_name = repo_name[:-4] + else: + print(f"git-sim error: Invalid repo URL, please confirm repo URL and try again") + sys.exit(1) + new_dir = os.path.join(tempfile.gettempdir(), "git_sim", repo_name) + + # Create local clone of local repo + try: + self.repo = git.Repo.clone_from(self.url, new_dir, no_hardlinks=True) + except git.GitCommandError as e: + print(f"git-sim error: Invalid repo URL, please confirm repo URL and try again") + sys.exit(1) + + head_commit = self.get_commit() + self.parse_commits(head_commit) + self.recenter_frame() + self.scale_frame() + self.add_details(repo_name) + self.color_by() + self.fadeout() + self.show_outro() + + # Unlink the program from the filesystem + self.repo.git.clear_cache() + + # Delete the local clones + shutil.rmtree(new_dir, onerror=del_rw) + + def add_details(self, repo_name): + text1 = m.Text( + f"Successfully cloned from {self.url} into ./{repo_name}", + font="Monospace", + font_size=20, + color=self.fontColor, + weight=m.BOLD, + ) + text1.move_to([self.camera.frame.get_center()[0], 4, 0]) + + text2 = m.Text( + f"Cloned repo log:", + font="Monospace", + font_size=20, + color=self.fontColor, + weight=m.BOLD, + ) + text2.move_to(text1.get_center()).shift(m.DOWN / 2) + + self.toFadeOut.add(text1) + self.toFadeOut.add(text2) + self.recenter_frame() + self.scale_frame() + + if settings.animate: + self.play(m.AddTextLetterByLetter(text1), m.AddTextLetterByLetter(text2)) + else: + self.add(text1, text2) + + +def del_rw(action, name, exc): + os.chmod(name, stat.S_IWRITE) + os.remove(name) + +def clone( + url: str = typer.Argument( + ..., + help="The web URL or filesystem path of the Git repo to clone", + ), +): + scene = Clone(url=url) + handle_animations(scene=scene) From a157af1dcbdae2fd7154d0bbd0d0959be478b5a9 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Wed, 22 Mar 2023 22:31:24 -0700 Subject: [PATCH 18/39] Fix display for table/zone subcommands when repo has less than 5 commits Signed-off-by: Jacob Stopak --- git_sim/git_sim_base_command.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index 892d43b..314b7ae 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -82,7 +82,10 @@ def parse_commits( ): commit = commit or self.get_commit() - isNewCommit = commit.hexsha not in self.drawnCommits + if commit != "dark": + isNewCommit = commit.hexsha not in self.drawnCommits + else: + isNewCommit = True if i < self.n: commitId, circle, arrow, hide_refs = self.draw_commit( @@ -114,7 +117,12 @@ def parse_commits( self.first_parse = False i += 1 - commitParents = list(commit.parents) + try: + commitParents = list(commit.parents) + except AttributeError: + self.parse_commits(self.create_dark_commit(), i, circle) + return + if len(commitParents) > 0: if settings.invert_branches: commitParents.reverse() @@ -124,6 +132,8 @@ def parse_commits( else: for p in range(len(commitParents)): self.parse_commits(commitParents[p], i, circle) + else: + self.parse_commits(self.create_dark_commit(), i, circle) def parse_all(self): if self.all: @@ -217,7 +227,10 @@ def draw_commit(self, commit, i, prevCircle, shift=numpy.array([0.0, 0.0, 0.0])) while any((circle.get_center() == c).all() for c in self.get_centers()): circle.shift(m.DOWN * 4) - isNewCommit = commit.hexsha not in self.drawnCommits + if commit != "dark": + isNewCommit = commit.hexsha not in self.drawnCommits + else: + isNewCommit = True if isNewCommit: start = ( From 9e1f725d9eba4a572c84203dc2376934f45db432 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Thu, 23 Mar 2023 13:47:56 -0700 Subject: [PATCH 19/39] Handle push subcommand failure scenario 'non-fast-forward' Signed-off-by: Jacob Stopak --- git_sim/git_sim_base_command.py | 11 ++++- git_sim/push.py | 72 +++++++++++++++++++++++++++------ 2 files changed, 68 insertions(+), 15 deletions(-) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index 314b7ae..f6f953d 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -382,8 +382,10 @@ def draw_branch(self, commit, i, make_branches_remote=False): self.is_remote_tracking_branch(branch) # remote tracking branch and commit.hexsha == remote_tracking_branches[branch] ): + text = (make_branches_remote + "/" + branch) if (make_branches_remote and not self.is_remote_tracking_branch(branch)) else branch + branchText = m.Text( - branch if not make_branches_remote else make_branches_remote + "/" + branch, font="Monospace", font_size=20, color=self.fontColor + text, font="Monospace", font_size=20, color=self.fontColor ) branchRec = m.Rectangle( color=m.GREEN, @@ -1151,13 +1153,18 @@ def color_by(self, offset=0): elif settings.color_by == "branch": pass - elif settings.color_by == "notlocal": + elif settings.color_by == "notlocal1": for commit_id in self.drawnCommits: try: self.orig_repo.commit(commit_id) except ValueError: self.drawnCommits[commit_id].set_color(m.GOLD) + elif settings.color_by == "notlocal2": + for commit_id in self.drawnCommits: + if not self.orig_repo.is_ancestor(commit_id, "HEAD"): + self.drawnCommits[commit_id].set_color(m.GOLD) + def add_group_to_author_groups(self, author, group): if author not in self.author_groups: self.author_groups[author] = [group] diff --git a/git_sim/push.py b/git_sim/push.py index e8a51ab..4d750e7 100644 --- a/git_sim/push.py +++ b/git_sim/push.py @@ -22,7 +22,6 @@ def __init__(self, remote: str = None, branch: str = None): self.remote = remote self.branch = branch settings.max_branches_per_commit = 2 - settings.color_by = "notlocal" if self.remote and self.remote not in self.repo.remotes: print("git-sim error: no remote with name '" + self.remote + "'") @@ -68,19 +67,30 @@ def construct(self): # Push the local clone into the local clone of the remote repo push_result = 0 + self.orig_repo = None try: self.repo.git.push(self.remote, self.branch) # If push fails... except git.GitCommandError as e: - if "rejected" in e.stderr and "fetch first" in e.stderr: + if "rejected" in e.stderr and ("fetch first" in e.stderr): push_result = 1 self.orig_repo = self.repo self.repo = self.remote_repo + settings.color_by = "notlocal1" + elif "rejected" in e.stderr and ("non-fast-forward" in e.stderr): + push_result = 2 + self.orig_repo = self.repo + self.repo = self.remote_repo + settings.color_by = "notlocal2" else: print(f"git-sim error: git push failed: {e.stderr}") + return head_commit = self.get_commit() - self.parse_commits(head_commit, make_branches_remote=(self.remote if self.remote else self.repo.remotes[0].name)) + if push_result > 0: + self.parse_commits(head_commit, make_branches_remote=(self.remote if self.remote else self.repo.remotes[0].name)) + else: + self.parse_commits(head_commit) self.recenter_frame() self.scale_frame() self.failed_push(push_result) @@ -134,17 +144,53 @@ def failed_push(self, push_result): weight=m.BOLD, ) text4.move_to(text3.get_center()).shift(m.DOWN / 2) + texts = [text1, text2, text3, text4] - self.toFadeOut.add(text1) - self.toFadeOut.add(text2) - self.toFadeOut.add(text3) - self.toFadeOut.add(text4) - self.recenter_frame() - self.scale_frame() - if settings.animate: - self.play(m.AddTextLetterByLetter(text2), m.AddTextLetterByLetter(text2), m.AddTextLetterByLetter(text3), m.AddTextLetterByLetter(text4)) - else: - self.add(text1, text2, text3, text4) + elif push_result == 2: + text1 = m.Text( + f"'git push' failed since the tip of your current branch is behind the remote.", + font="Monospace", + font_size=20, + color=self.fontColor, + weight=m.BOLD, + ) + text1.move_to([self.camera.frame.get_center()[0], 5, 0]) + + text2 = m.Text( + f"Run 'git pull' (or 'git-sim pull' to simulate first) and then try again.", + font="Monospace", + font_size=20, + color=self.fontColor, + weight=m.BOLD, + ) + text2.move_to(text1.get_center()).shift(m.DOWN / 2) + + text3 = m.Text( + f"Gold commits exist are ahead of your current branch tip (need to be pulled).", + font="Monospace", + font_size=20, + color=m.GOLD, + weight=m.BOLD, + ) + text3.move_to(text2.get_center()).shift(m.DOWN / 2) + + text4 = m.Text( + f"Red commits are up to date in both local and remote branches.", + font="Monospace", + font_size=20, + color=m.RED, + weight=m.BOLD, + ) + text4.move_to(text3.get_center()).shift(m.DOWN / 2) + texts = [text1, text2, text3, text4] + + self.toFadeOut.add(*texts) + self.recenter_frame() + self.scale_frame() + if settings.animate: + self.play(*[m.AddTextLetterByLetter(t) for t in texts]) + else: + self.add(*texts) From 6bb1b8dd90411afc33b49409eefc13927f8b998b Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Thu, 23 Mar 2023 14:17:22 -0700 Subject: [PATCH 20/39] Update test Signed-off-by: Jacob Stopak --- test.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test.py b/test.py index 55b77d7..ba262cf 100644 --- a/test.py +++ b/test.py @@ -1,15 +1,11 @@ import unittest, git, argparse from manim import * -from git_sim.git_sim import GitSim - class TestGitSim(unittest.TestCase): def test_git_sim(self): """Test git sim.""" - gs = GitSim(argparse.Namespace()) - self.assertEqual(1, 1) From ac2bb3223c9617f358969c1ca5c2ba2cda667ff5 Mon Sep 17 00:00:00 2001 From: Mathias Sven Date: Sat, 25 Mar 2023 03:25:40 +0000 Subject: [PATCH 21/39] Increase performance of typer autocompletion by breaking out subcommand functions into their own module Remove autocompletion dependence on importing manim and git to improve performance Signed-off-by: Mathias Sven --- git_sim/__main__.py | 65 ++++----- git_sim/add.py | 13 -- git_sim/branch.py | 12 -- git_sim/checkout.py | 25 +--- git_sim/cherrypick.py | 18 --- git_sim/clone.py | 27 ++-- git_sim/commands.py | 315 ++++++++++++++++++++++++++++++++++++++++++ git_sim/commit.py | 19 --- git_sim/enums.py | 14 ++ git_sim/fetch.py | 16 +-- git_sim/log.py | 18 --- git_sim/merge.py | 17 --- git_sim/pull.py | 24 +--- git_sim/push.py | 29 ++-- git_sim/rebase.py | 12 -- git_sim/reset.py | 37 +---- git_sim/restore.py | 13 -- git_sim/revert.py | 13 -- git_sim/stash.py | 24 +--- git_sim/status.py | 9 -- git_sim/switch.py | 25 +--- git_sim/tag.py | 12 -- 22 files changed, 393 insertions(+), 364 deletions(-) create mode 100644 git_sim/commands.py create mode 100644 git_sim/enums.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index e82b7c3..134da8f 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -4,30 +4,10 @@ import sys import datetime import time -import git - -import git_sim.add -import git_sim.branch -import git_sim.cherrypick -import git_sim.commit -import git_sim.log -import git_sim.merge -import git_sim.rebase -import git_sim.reset -import git_sim.restore -import git_sim.revert -import git_sim.stash -import git_sim.status -import git_sim.tag -import git_sim.switch -import git_sim.checkout -import git_sim.fetch -import git_sim.pull -import git_sim.push -import git_sim.clone + +import git_sim.commands from git_sim.settings import ImgFormat, VideoFormat, settings -from manim import config, WHITE app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]}) @@ -146,6 +126,9 @@ def main( help="Color commits by parameter, such as author", ), ): + import git + from manim import config, WHITE + settings.animate = animate settings.n = n settings.auto_open = auto_open @@ -199,25 +182,25 @@ def main( config.output_file = "git-sim-" + ctx.invoked_subcommand + "_" + t + ".mp4" -app.command()(git_sim.add.add) -app.command()(git_sim.branch.branch) -app.command()(git_sim.cherrypick.cherry_pick) -app.command()(git_sim.commit.commit) -app.command()(git_sim.log.log) -app.command()(git_sim.merge.merge) -app.command()(git_sim.rebase.rebase) -app.command()(git_sim.reset.reset) -app.command()(git_sim.restore.restore) -app.command()(git_sim.revert.revert) -app.command()(git_sim.stash.stash) -app.command()(git_sim.status.status) -app.command()(git_sim.tag.tag) -app.command()(git_sim.switch.switch) -app.command()(git_sim.checkout.checkout) -app.command()(git_sim.fetch.fetch) -app.command()(git_sim.pull.pull) -app.command()(git_sim.push.push) -app.command()(git_sim.clone.clone) +app.command()(git_sim.commands.add) +app.command()(git_sim.commands.branch) +app.command()(git_sim.commands.checkout) +app.command()(git_sim.commands.cherry_pick) +app.command()(git_sim.commands.clone) +app.command()(git_sim.commands.commit) +app.command()(git_sim.commands.fetch) +app.command()(git_sim.commands.log) +app.command()(git_sim.commands.merge) +app.command()(git_sim.commands.pull) +app.command()(git_sim.commands.push) +app.command()(git_sim.commands.rebase) +app.command()(git_sim.commands.reset) +app.command()(git_sim.commands.restore) +app.command()(git_sim.commands.revert) +app.command()(git_sim.commands.stash) +app.command()(git_sim.commands.status) +app.command()(git_sim.commands.switch) +app.command()(git_sim.commands.tag) if __name__ == "__main__": diff --git a/git_sim/add.py b/git_sim/add.py index cd436e4..8f425e1 100644 --- a/git_sim/add.py +++ b/git_sim/add.py @@ -1,11 +1,9 @@ import sys import git import manim as m -import typer from typing import List -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -82,14 +80,3 @@ def populate_zones( firstColumnArrowMap[z] = m.Arrow( stroke_width=3, color=self.fontColor ) - - -def add( - files: List[str] = typer.Argument( - default=None, - help="The names of one or more files to add to Git's staging area", - ) -): - settings.hide_first_tag = True - scene = Add(files=files) - handle_animations(scene=scene) diff --git a/git_sim/branch.py b/git_sim/branch.py index 6c949a0..97b3de2 100644 --- a/git_sim/branch.py +++ b/git_sim/branch.py @@ -1,7 +1,5 @@ import manim as m -import typer -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -52,13 +50,3 @@ def construct(self): self.color_by() self.fadeout() self.show_outro() - - -def branch( - name: str = typer.Argument( - ..., - help="The name of the new branch", - ) -): - scene = Branch(name=name) - handle_animations(scene=scene) diff --git a/git_sim/checkout.py b/git_sim/checkout.py index af1e6f4..88fe50f 100644 --- a/git_sim/checkout.py +++ b/git_sim/checkout.py @@ -4,9 +4,7 @@ import git import manim as m import numpy -import typer -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -19,7 +17,11 @@ def __init__(self, branch: str, b: bool): if self.b: if self.branch in self.repo.heads: - print("git-sim error: can't create new branch '" + self.branch + "', it already exists") + print( + "git-sim error: can't create new branch '" + + self.branch + + "', it already exists" + ) sys.exit(1) else: try: @@ -75,7 +77,7 @@ def construct(self): branch_commit = self.get_commit(self.branch) if self.is_ancestor: - commits_in_range = list(self.repo.iter_commits(self.branch + '..HEAD')) + commits_in_range = list(self.repo.iter_commits(self.branch + "..HEAD")) # branch is reached from HEAD, so draw everything if len(commits_in_range) <= self.n: @@ -113,18 +115,3 @@ def construct(self): self.color_by() self.fadeout() self.show_outro() - - -def checkout( - branch: str = typer.Argument( - ..., - help="The name of the branch to checkout", - ), - b: bool = typer.Option( - False, - "-b", - help="Create the specified branch if it doesn't already exist", - ), -): - scene = Checkout(branch=branch, b=b) - handle_animations(scene=scene) diff --git a/git_sim/cherrypick.py b/git_sim/cherrypick.py index 8da873a..9cbcd0f 100644 --- a/git_sim/cherrypick.py +++ b/git_sim/cherrypick.py @@ -2,9 +2,7 @@ import git import manim as m -import typer -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -70,19 +68,3 @@ def construct(self): self.color_by(offset=2) self.fadeout() self.show_outro() - - -def cherry_pick( - commit: str = typer.Argument( - ..., - help="The ref (branch/tag), or commit ID to simulate cherry-pick onto active branch", - ), - edit: str = typer.Option( - None, - "--edit", - "-e", - help="Specify a new commit message for the cherry-picked commit", - ), -): - scene = CherryPick(commit=commit, edit=edit) - handle_animations(scene=scene) diff --git a/git_sim/clone.py b/git_sim/clone.py index 5b7f206..114549f 100644 --- a/git_sim/clone.py +++ b/git_sim/clone.py @@ -5,13 +5,11 @@ import git import manim as m import numpy -import typer import tempfile import shutil import stat import re -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -28,12 +26,10 @@ def __init__(self, url: str): def construct(self): if not settings.stdout and not settings.output_only_path and not settings.quiet: - print( - f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.url}" - ) + print(f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.url}") self.show_intro() - + # Configure paths to make local clone to run networked commands in repo_name = re.search(r"/([^/]+)/?$", self.url) if repo_name: @@ -41,7 +37,9 @@ def construct(self): if repo_name.endswith(".git"): repo_name = repo_name[:-4] else: - print(f"git-sim error: Invalid repo URL, please confirm repo URL and try again") + print( + f"git-sim error: Invalid repo URL, please confirm repo URL and try again" + ) sys.exit(1) new_dir = os.path.join(tempfile.gettempdir(), "git_sim", repo_name) @@ -49,7 +47,9 @@ def construct(self): try: self.repo = git.Repo.clone_from(self.url, new_dir, no_hardlinks=True) except git.GitCommandError as e: - print(f"git-sim error: Invalid repo URL, please confirm repo URL and try again") + print( + f"git-sim error: Invalid repo URL, please confirm repo URL and try again" + ) sys.exit(1) head_commit = self.get_commit() @@ -69,7 +69,7 @@ def construct(self): def add_details(self, repo_name): text1 = m.Text( - f"Successfully cloned from {self.url} into ./{repo_name}", + f"Successfully cloned from {self.url} into ./{repo_name}", font="Monospace", font_size=20, color=self.fontColor, @@ -100,12 +100,3 @@ def add_details(self, repo_name): def del_rw(action, name, exc): os.chmod(name, stat.S_IWRITE) os.remove(name) - -def clone( - url: str = typer.Argument( - ..., - help="The web URL or filesystem path of the Git repo to clone", - ), -): - scene = Clone(url=url) - handle_animations(scene=scene) diff --git a/git_sim/commands.py b/git_sim/commands.py new file mode 100644 index 0000000..f418518 --- /dev/null +++ b/git_sim/commands.py @@ -0,0 +1,315 @@ +from __future__ import annotations + +import typer + +from typing import List, TYPE_CHECKING + +from git_sim.enums import ResetMode, StashSubCommand +from git_sim.settings import settings + +if TYPE_CHECKING: + from manim import Scene + + +def handle_animations(scene: Scene) -> None: + from git_sim.animations import handle_animations as _handle_animations + + return _handle_animations(scene) + + +def add( + files: List[str] = typer.Argument( + default=None, + help="The names of one or more files to add to Git's staging area", + ) +): + from git_sim.add import Add + + settings.hide_first_tag = True + scene = Add(files=files) + handle_animations(scene=scene) + + +def branch( + name: str = typer.Argument( + ..., + help="The name of the new branch", + ) +): + from git_sim.branch import Branch + + scene = Branch(name=name) + handle_animations(scene=scene) + + +def checkout( + branch: str = typer.Argument( + ..., + help="The name of the branch to checkout", + ), + b: bool = typer.Option( + False, + "-b", + help="Create the specified branch if it doesn't already exist", + ), +): + from git_sim.checkout import Checkout + + scene = Checkout(branch=branch, b=b) + handle_animations(scene=scene) + + +def cherry_pick( + commit: str = typer.Argument( + ..., + help="The ref (branch/tag), or commit ID to simulate cherry-pick onto active branch", + ), + edit: str = typer.Option( + None, + "--edit", + "-e", + help="Specify a new commit message for the cherry-picked commit", + ), +): + from git_sim.cherrypick import CherryPick + + scene = CherryPick(commit=commit, edit=edit) + handle_animations(scene=scene) + + +def clone( + url: str = typer.Argument( + ..., + help="The web URL or filesystem path of the Git repo to clone", + ), +): + from git_sim.clone import Clone + + scene = Clone(url=url) + handle_animations(scene=scene) + + +def commit( + message: str = typer.Option( + "New commit", + "--message", + "-m", + help="The commit message of the new commit", + ), + amend: bool = typer.Option( + default=False, + help="Amend the last commit message, must be used with the --message flag", + ), +): + from git_sim.commit import Commit + + settings.hide_first_tag = True + scene = Commit(message=message, amend=amend) + handle_animations(scene=scene) + + +def fetch( + remote: str = typer.Argument( + ..., + help="The name of the remote to fetch from", + ), + branch: str = typer.Argument( + ..., + help="The name of the branch to fetch", + ), +): + from git_sim.fetch import Fetch + + scene = Fetch(remote=remote, branch=branch) + handle_animations(scene=scene) + + +def log( + ctx: typer.Context, + n: int = typer.Option( + None, + "-n", + help="Number of commits to display from branch heads", + ), + all: bool = typer.Option( + False, + "--all", + help="Display all local branches in the log output", + ), +): + from git_sim.log import Log + + scene = Log(ctx=ctx, n=n, all=all) + handle_animations(scene=scene) + + +def merge( + branch: str = typer.Argument( + ..., + help="The name of the branch to merge into the active checked-out branch", + ), + no_ff: bool = typer.Option( + False, + "--no-ff", + help="Simulate creation of a merge commit in all cases, even when the merge could instead be resolved as a fast-forward", + ), +): + from git_sim.merge import Merge + + scene = Merge(branch=branch, no_ff=no_ff) + handle_animations(scene=scene) + + +def pull( + remote: str = typer.Argument( + default=None, + help="The name of the remote to pull from", + ), + branch: str = typer.Argument( + default=None, + help="The name of the branch to pull", + ), +): + from git_sim.pull import Pull + + scene = Pull(remote=remote, branch=branch) + handle_animations(scene=scene) + + +def push( + remote: str = typer.Argument( + default=None, + help="The name of the remote to push to", + ), + branch: str = typer.Argument( + default=None, + help="The name of the branch to push", + ), +): + from git_sim.push import Push + + scene = Push(remote=remote, branch=branch) + handle_animations(scene=scene) + + +def rebase( + branch: str = typer.Argument( + ..., + help="The branch to simulate rebasing the checked-out commit onto", + ) +): + from git_sim.rebase import Rebase + + scene = Rebase(branch=branch) + handle_animations(scene=scene) + + +def reset( + commit: str = typer.Argument( + default="HEAD", + help="The ref (branch/tag), or commit ID to simulate reset to", + ), + mode: ResetMode = typer.Option( + default="mixed", + help="Either mixed, soft, or hard", + ), + soft: bool = typer.Option( + default=False, + help="Simulate a soft reset, shortcut for --mode=soft", + ), + mixed: bool = typer.Option( + default=False, + help="Simulate a mixed reset, shortcut for --mode=mixed", + ), + hard: bool = typer.Option( + default=False, + help="Simulate a soft reset, shortcut for --mode=hard", + ), +): + from git_sim.reset import Reset + + settings.hide_first_tag = True + scene = Reset(commit=commit, mode=mode, soft=soft, mixed=mixed, hard=hard) + handle_animations(scene=scene) + + +def restore( + files: List[str] = typer.Argument( + default=None, + help="The names of one or more files to restore", + ) +): + from git_sim.restore import Restore + + settings.hide_first_tag = True + scene = Restore(files=files) + handle_animations(scene=scene) + + +def revert( + commit: str = typer.Argument( + default="HEAD", + help="The ref (branch/tag), or commit ID to simulate revert", + ) +): + from git_sim.revert import Revert + + settings.hide_first_tag = True + scene = Revert(commit=commit) + handle_animations(scene=scene) + + +def stash( + command: StashSubCommand = typer.Argument( + default=None, + help="Stash subcommand (push, pop, apply)", + ), + files: List[str] = typer.Argument( + default=None, + help="The name of the file to stash changes for", + ), +): + from git_sim.stash import Stash + + settings.hide_first_tag = True + scene = Stash(files=files, command=command) + handle_animations(scene=scene) + + +def status(): + from git_sim.status import Status + + settings.hide_first_tag = True + settings.allow_no_commits = True + + scene = Status() + handle_animations(scene=scene) + + +def switch( + branch: str = typer.Argument( + ..., + help="The name of the branch to switch to", + ), + c: bool = typer.Option( + False, + "-c", + help="Create the specified branch if it doesn't already exist", + ), +): + from git_sim.switch import Switch + + scene = Switch(branch=branch, c=c) + handle_animations(scene=scene) + + +def tag( + name: str = typer.Argument( + ..., + help="The name of the new tag", + ) +): + from git_sim.tag import Tag + + scene = Tag(name=name) + handle_animations(scene=scene) diff --git a/git_sim/commit.py b/git_sim/commit.py index 2152007..2fb6a99 100644 --- a/git_sim/commit.py +++ b/git_sim/commit.py @@ -2,9 +2,7 @@ import git import manim as m -import typer -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -102,20 +100,3 @@ def populate_zones( secondColumnArrowMap[y.a_path] = m.Arrow( stroke_width=3, color=self.fontColor ) - - -def commit( - message: str = typer.Option( - "New commit", - "--message", - "-m", - help="The commit message of the new commit", - ), - amend: bool = typer.Option( - default=False, - help="Amend the last commit message, must be used with the --message flag", - ), -): - settings.hide_first_tag = True - scene = Commit(message=message, amend=amend) - handle_animations(scene=scene) diff --git a/git_sim/enums.py b/git_sim/enums.py new file mode 100644 index 0000000..fe5c9c2 --- /dev/null +++ b/git_sim/enums.py @@ -0,0 +1,14 @@ +from enum import Enum + + +class ResetMode(Enum): + DEFAULT = "mixed" + SOFT = "soft" + MIXED = "mixed" + HARD = "hard" + + +class StashSubCommand(Enum): + POP = "pop" + APPLY = "apply" + PUSH = "push" diff --git a/git_sim/fetch.py b/git_sim/fetch.py index 4222591..a535948 100644 --- a/git_sim/fetch.py +++ b/git_sim/fetch.py @@ -5,12 +5,10 @@ import git import manim as m import numpy -import typer import tempfile import shutil import stat -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -76,19 +74,7 @@ def construct(self): self.repo.git.clear_cache() shutil.rmtree(new_dir, onerror=del_rw) + def del_rw(action, name, exc): os.chmod(name, stat.S_IWRITE) os.remove(name) - -def fetch( - remote: str = typer.Argument( - ..., - help="The name of the remote to fetch from", - ), - branch: str = typer.Argument( - ..., - help="The name of the branch to fetch", - ), -): - scene = Fetch(remote=remote, branch=branch) - handle_animations(scene=scene) diff --git a/git_sim/log.py b/git_sim/log.py index 491d494..33bd3d3 100644 --- a/git_sim/log.py +++ b/git_sim/log.py @@ -1,6 +1,5 @@ import typer -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings import numpy @@ -46,20 +45,3 @@ def construct(self): self.color_by() self.fadeout() self.show_outro() - - -def log( - ctx: typer.Context, - n: int = typer.Option( - None, - "-n", - help="Number of commits to display from branch heads", - ), - all: bool = typer.Option( - False, - "--all", - help="Display all local branches in the log output", - ), -): - scene = Log(ctx=ctx, n=n, all=all) - handle_animations(scene=scene) diff --git a/git_sim/merge.py b/git_sim/merge.py index f83d59f..dfbc6bb 100644 --- a/git_sim/merge.py +++ b/git_sim/merge.py @@ -4,9 +4,7 @@ import git import manim as m import numpy -import typer -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -119,18 +117,3 @@ def construct(self): self.fadeout() self.show_outro() - - -def merge( - branch: str = typer.Argument( - ..., - help="The name of the branch to merge into the active checked-out branch", - ), - no_ff: bool = typer.Option( - False, - "--no-ff", - help="Simulate creation of a merge commit in all cases, even when the merge could instead be resolved as a fast-forward", - ), -): - scene = Merge(branch=branch, no_ff=no_ff) - handle_animations(scene=scene) diff --git a/git_sim/pull.py b/git_sim/pull.py index fbe6460..628eb3f 100644 --- a/git_sim/pull.py +++ b/git_sim/pull.py @@ -5,13 +5,11 @@ import git import manim as m import numpy -import typer import tempfile import shutil import stat import re -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -34,7 +32,7 @@ def construct(self): ) self.show_intro() - + # Configure paths to make local clone to run networked commands in git_root = self.repo.git.rev_parse("--show-toplevel") repo_name = os.path.basename(self.repo.working_dir) @@ -62,7 +60,7 @@ def construct(self): except git.GitCommandError as e: if "CONFLICT" in e.stdout: # Restrict to default number of commits since we'll show the table/zones - self.n = self.n_default + self.n = self.n_default # Get list of conflicted filenames self.conflicted_files = re.findall(r"Merge conflict in (.+)", e.stdout) @@ -80,7 +78,9 @@ def construct(self): third_column_name="----", ) else: - print(f"git-sim error: git pull failed for unhandled reason: {e.stdout}") + print( + f"git-sim error: git pull failed for unhandled reason: {e.stdout}" + ) self.repo.git.clear_cache() shutil.rmtree(new_dir, onerror=del_rw) sys.exit(1) @@ -108,19 +108,7 @@ def populate_zones( for filename in self.conflicted_files: secondColumnFileNames.add(filename) + def del_rw(action, name, exc): os.chmod(name, stat.S_IWRITE) os.remove(name) - -def pull( - remote: str = typer.Argument( - default=None, - help="The name of the remote to pull from", - ), - branch: str = typer.Argument( - default=None, - help="The name of the branch to pull", - ), -): - scene = Pull(remote=remote, branch=branch) - handle_animations(scene=scene) diff --git a/git_sim/push.py b/git_sim/push.py index 4d750e7..d648a23 100644 --- a/git_sim/push.py +++ b/git_sim/push.py @@ -5,13 +5,11 @@ import git import manim as m import numpy -import typer import tempfile import shutil import stat import re -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -34,7 +32,7 @@ def construct(self): ) self.show_intro() - + # Configure paths to make local clone to run networked commands in git_root = self.repo.git.rev_parse("--show-toplevel") repo_name = os.path.basename(self.repo.working_dir) @@ -55,7 +53,9 @@ def construct(self): remote_url = orig_remotes[0].url # Create local clone of remote repo to simulate push to so we don't touch the real remote - self.remote_repo = git.Repo.clone_from(remote_url, new_dir2, no_hardlinks=True, bare=True) + self.remote_repo = git.Repo.clone_from( + remote_url, new_dir2, no_hardlinks=True, bare=True + ) # Reset local clone remote to the local clone of remote repo if self.remote: @@ -88,7 +88,12 @@ def construct(self): head_commit = self.get_commit() if push_result > 0: - self.parse_commits(head_commit, make_branches_remote=(self.remote if self.remote else self.repo.remotes[0].name)) + self.parse_commits( + head_commit, + make_branches_remote=( + self.remote if self.remote else self.repo.remotes[0].name + ), + ) else: self.parse_commits(head_commit) self.recenter_frame() @@ -193,20 +198,6 @@ def failed_push(self, push_result): self.add(*texts) - def del_rw(action, name, exc): os.chmod(name, stat.S_IWRITE) os.remove(name) - -def push( - remote: str = typer.Argument( - default=None, - help="The name of the remote to push to", - ), - branch: str = typer.Argument( - default=None, - help="The name of the branch to push", - ), -): - scene = Push(remote=remote, branch=branch) - handle_animations(scene=scene) diff --git a/git_sim/rebase.py b/git_sim/rebase.py index 6effcd8..b283127 100644 --- a/git_sim/rebase.py +++ b/git_sim/rebase.py @@ -3,9 +3,7 @@ import git import manim as m import numpy -import typer -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -179,13 +177,3 @@ def setup_and_draw_parent( self.toFadeOut.add(arrow) return sha - - -def rebase( - branch: str = typer.Argument( - ..., - help="The branch to simulate rebasing the checked-out commit onto", - ) -): - scene = Rebase(branch=branch) - handle_animations(scene=scene) diff --git a/git_sim/reset.py b/git_sim/reset.py index 3f12e08..ae1ddef 100644 --- a/git_sim/reset.py +++ b/git_sim/reset.py @@ -3,20 +3,12 @@ import git import manim as m -import typer -from git_sim.animations import handle_animations +from git_sim.enums import ResetMode from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings -class ResetMode(Enum): - DEFAULT = "mixed" - SOFT = "soft" - MIXED = "mixed" - HARD = "hard" - - class Reset(GitSimBaseCommand): def __init__( self, commit: str, mode: ResetMode, soft: bool, mixed: bool, hard: bool @@ -145,30 +137,3 @@ def populate_zones( secondColumnFileNames.add(y.a_path) elif self.mode == ResetMode.HARD: firstColumnFileNames.add(y.a_path) - - -def reset( - commit: str = typer.Argument( - default="HEAD", - help="The ref (branch/tag), or commit ID to simulate reset to", - ), - mode: ResetMode = typer.Option( - default=ResetMode.MIXED.value, - help="Either mixed, soft, or hard", - ), - soft: bool = typer.Option( - default=False, - help="Simulate a soft reset, shortcut for --mode=soft", - ), - mixed: bool = typer.Option( - default=False, - help="Simulate a mixed reset, shortcut for --mode=mixed", - ), - hard: bool = typer.Option( - default=False, - help="Simulate a soft reset, shortcut for --mode=hard", - ), -): - settings.hide_first_tag = True - scene = Reset(commit=commit, mode=mode, soft=soft, mixed=mixed, hard=hard) - handle_animations(scene=scene) diff --git a/git_sim/restore.py b/git_sim/restore.py index 378833b..a2cc5f9 100644 --- a/git_sim/restore.py +++ b/git_sim/restore.py @@ -1,10 +1,8 @@ import sys import manim as m -import typer from typing import List -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -71,14 +69,3 @@ def populate_zones( firstColumnArrowMap[y.a_path] = m.Arrow( stroke_width=3, color=self.fontColor ) - - -def restore( - files: List[str] = typer.Argument( - default=None, - help="The names of one or more files to restore", - ) -): - settings.hide_first_tag = True - scene = Restore(files=files) - handle_animations(scene=scene) diff --git a/git_sim/revert.py b/git_sim/revert.py index 32f0b55..b487fe8 100644 --- a/git_sim/revert.py +++ b/git_sim/revert.py @@ -3,9 +3,7 @@ import git import manim as m import numpy -import typer -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -157,14 +155,3 @@ def populate_zones( ): for filename in self.revert.stats.files: secondColumnFileNames.add(filename) - - -def revert( - commit: str = typer.Argument( - default="HEAD", - help="The ref (branch/tag), or commit ID to simulate revert", - ) -): - settings.hide_first_tag = True - scene = Revert(commit=commit) - handle_animations(scene=scene) diff --git a/git_sim/stash.py b/git_sim/stash.py index d490817..71b3e1b 100644 --- a/git_sim/stash.py +++ b/git_sim/stash.py @@ -1,21 +1,14 @@ import sys from enum import Enum import manim as m -import typer from typing import List -from git_sim.animations import handle_animations +from git_sim.enums import StashSubCommand from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings -class StashSubCommand(Enum): - POP = "pop" - APPLY = "apply" - PUSH = "push" - - class Stash(GitSimBaseCommand): def __init__(self, files: List[str], command: StashSubCommand): super().__init__() @@ -186,18 +179,3 @@ def populate_zones( secondColumnArrowMap[y.a_path] = m.Arrow( stroke_width=3, color=self.fontColor ) - - -def stash( - command: StashSubCommand = typer.Argument( - default=None, - help="Stash subcommand (push, pop, apply)", - ), - files: List[str] = typer.Argument( - default=None, - help="The name of the file to stash changes for", - ), -): - settings.hide_first_tag = True - scene = Stash(files=files, command=command) - handle_animations(scene=scene) diff --git a/git_sim/status.py b/git_sim/status.py index 75fa360..d237ba0 100644 --- a/git_sim/status.py +++ b/git_sim/status.py @@ -1,4 +1,3 @@ -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -24,11 +23,3 @@ def construct(self): self.setup_and_draw_zones() self.fadeout() self.show_outro() - - -def status(): - settings.hide_first_tag = True - settings.allow_no_commits = True - - scene = Status() - handle_animations(scene=scene) diff --git a/git_sim/switch.py b/git_sim/switch.py index c37b912..3da490b 100644 --- a/git_sim/switch.py +++ b/git_sim/switch.py @@ -4,9 +4,7 @@ import git import manim as m import numpy -import typer -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -19,7 +17,11 @@ def __init__(self, branch: str, c: bool): if self.c: if self.branch in self.repo.heads: - print("git-sim error: can't create new branch '" + self.branch + "', it already exists") + print( + "git-sim error: can't create new branch '" + + self.branch + + "', it already exists" + ) sys.exit(1) else: try: @@ -75,7 +77,7 @@ def construct(self): branch_commit = self.get_commit(self.branch) if self.is_ancestor: - commits_in_range = list(self.repo.iter_commits(self.branch + '..HEAD')) + commits_in_range = list(self.repo.iter_commits(self.branch + "..HEAD")) # branch is reached from HEAD, so draw everything if len(commits_in_range) <= self.n: @@ -113,18 +115,3 @@ def construct(self): self.color_by() self.fadeout() self.show_outro() - - -def switch( - branch: str = typer.Argument( - ..., - help="The name of the branch to switch to", - ), - c: bool = typer.Option( - False, - "-c", - help="Create the specified branch if it doesn't already exist", - ), -): - scene = Switch(branch=branch, c=c) - handle_animations(scene=scene) diff --git a/git_sim/tag.py b/git_sim/tag.py index cbdd772..cded5c8 100644 --- a/git_sim/tag.py +++ b/git_sim/tag.py @@ -1,7 +1,5 @@ import manim as m -import typer -from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -52,13 +50,3 @@ def construct(self): self.color_by() self.fadeout() self.show_outro() - - -def tag( - name: str = typer.Argument( - ..., - help="The name of the new tag", - ) -): - scene = Tag(name=name) - handle_animations(scene=scene) From c0e8c6c01d453d271829801d4503b5a287124ac4 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 24 Mar 2023 20:28:29 -0700 Subject: [PATCH 22/39] Format with black Signed-off-by: Jacob Stopak --- git_sim/git_sim_base_command.py | 15 +++++++++++---- git_sim/stash.py | 1 - 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index f6f953d..6664d22 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -95,7 +95,9 @@ def parse_commits( if commit != "dark": if not hide_refs and isNewCommit: self.draw_head(commit, i, commitId) - self.draw_branch(commit, i, make_branches_remote=make_branches_remote) + self.draw_branch( + commit, i, make_branches_remote=make_branches_remote + ) self.draw_tag(commit, i) if ( not isinstance(arrow, m.CurvedArrow) @@ -382,7 +384,14 @@ def draw_branch(self, commit, i, make_branches_remote=False): self.is_remote_tracking_branch(branch) # remote tracking branch and commit.hexsha == remote_tracking_branches[branch] ): - text = (make_branches_remote + "/" + branch) if (make_branches_remote and not self.is_remote_tracking_branch(branch)) else branch + text = ( + (make_branches_remote + "/" + branch) + if ( + make_branches_remote + and not self.is_remote_tracking_branch(branch) + ) + else branch + ) branchText = m.Text( text, font="Monospace", font_size=20, color=self.fontColor @@ -893,7 +902,6 @@ def reset_branch(self, hexsha, shift=numpy.array([0.0, 0.0, 0.0])): ) ) - def reset_head_branch_to_ref(self, ref, shift=numpy.array([0.0, 0.0, 0.0])): if settings.animate: self.play(self.drawnRefs["HEAD"].animate.next_to(ref, m.UP)) @@ -1071,7 +1079,6 @@ def create_zone_text( thirdColumnTitle, horizontal2, ): - for i, f in enumerate(firstColumnFileNames): text = ( m.Text( diff --git a/git_sim/stash.py b/git_sim/stash.py index 71b3e1b..89a5cd2 100644 --- a/git_sim/stash.py +++ b/git_sim/stash.py @@ -145,7 +145,6 @@ def populate_zones( secondColumnArrowMap={}, thirdColumnArrowMap={}, ): - if self.command in [StashSubCommand.POP, StashSubCommand.APPLY]: for x in self.repo.index.diff(None): thirdColumnFileNames.add(x.a_path) From 45e8ef0a9114e989c126b009787d3c00a0c575d9 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 24 Mar 2023 23:05:17 -0700 Subject: [PATCH 23/39] Fix frame centering issues with checkout and switch subcommands Signed-off-by: Jacob Stopak --- git_sim/checkout.py | 2 ++ git_sim/switch.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/git_sim/checkout.py b/git_sim/checkout.py index 88fe50f..b03746e 100644 --- a/git_sim/checkout.py +++ b/git_sim/checkout.py @@ -72,6 +72,8 @@ def construct(self): # using -b flag, create new branch label and exit if self.b: self.parse_commits(head_commit) + self.recenter_frame() + self.scale_frame() self.draw_ref(head_commit, self.topref, text=self.branch, color=m.GREEN) else: branch_commit = self.get_commit(self.branch) diff --git a/git_sim/switch.py b/git_sim/switch.py index 3da490b..51863b5 100644 --- a/git_sim/switch.py +++ b/git_sim/switch.py @@ -72,6 +72,8 @@ def construct(self): # using -c flag, create new branch label and exit if self.c: self.parse_commits(head_commit) + self.recenter_frame() + self.scale_frame() self.draw_ref(head_commit, self.topref, text=self.branch, color=m.GREEN) else: branch_commit = self.get_commit(self.branch) From aefaeac937faf6fb10fbda580a0aaca8d3980bd2 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 24 Mar 2023 23:06:29 -0700 Subject: [PATCH 24/39] Draw second parent-child arrow when possible for ff merges with --no-ff merge commit Signed-off-by: Jacob Stopak --- git_sim/merge.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/git_sim/merge.py b/git_sim/merge.py index dfbc6bb..fe325ab 100644 --- a/git_sim/merge.py +++ b/git_sim/merge.py @@ -74,6 +74,14 @@ def construct(self): if self.no_ff: self.center_frame_on_commit(branch_commit) commitId = self.setup_and_draw_parent(branch_commit, "Merge commit") + + # If pre-merge HEAD is on screen, drawn an arrow to it as 2nd parent + if head_commit.hexsha in self.drawnCommits: + start = self.drawnCommits["abcdef"].get_center() + end = self.drawnCommits[head_commit.hexsha].get_center() + arrow = m.CurvedArrow(start, end, color=self.fontColor) + self.draw_arrow(True, arrow) + reset_head_to = "abcdef" shift = numpy.array([0.0, 0.0, 0.0]) From eaac2083f60d762e8917bbb17d79277d0d35f9e6 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 24 Mar 2023 23:27:34 -0700 Subject: [PATCH 25/39] Fix bug causing extra dark commits to be drawn when -n is longer than displayed branch Signed-off-by: Jacob Stopak --- git_sim/git_sim_base_command.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index 6664d22..0a23eff 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -122,7 +122,8 @@ def parse_commits( try: commitParents = list(commit.parents) except AttributeError: - self.parse_commits(self.create_dark_commit(), i, circle) + if len(self.drawnCommits) < self.n_default: + self.parse_commits(self.create_dark_commit(), i, circle) return if len(commitParents) > 0: @@ -135,7 +136,8 @@ def parse_commits( for p in range(len(commitParents)): self.parse_commits(commitParents[p], i, circle) else: - self.parse_commits(self.create_dark_commit(), i, circle) + if len(self.drawnCommits) < self.n_default: + self.parse_commits(self.create_dark_commit(), i, circle) def parse_all(self): if self.all: From 88bf60daedaacfa9ee2717a119851de2886a1506 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sat, 25 Mar 2023 12:27:48 -0700 Subject: [PATCH 26/39] Detect and display filenames in merge conflicts Signed-off-by: Jacob Stopak --- git_sim/merge.py | 101 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 83 insertions(+), 18 deletions(-) diff --git a/git_sim/merge.py b/git_sim/merge.py index fe325ab..c700bee 100644 --- a/git_sim/merge.py +++ b/git_sim/merge.py @@ -1,9 +1,12 @@ import sys -from argparse import Namespace +import os import git import manim as m import numpy +import tempfile +import shutil +import stat from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings @@ -105,23 +108,85 @@ def construct(self): self.color_by() else: - self.parse_commits(head_commit) - self.parse_commits(branch_commit, shift=4 * m.DOWN) - self.parse_all() - self.center_frame_on_commit(head_commit) - self.setup_and_draw_parent( - head_commit, - "Merge commit", - shift=2 * m.DOWN, - draw_arrow=False, - color=m.GRAY, - ) - self.draw_arrow_between_commits("abcdef", branch_commit.hexsha) - self.draw_arrow_between_commits("abcdef", head_commit.hexsha) - self.recenter_frame() - self.scale_frame() - self.reset_head_branch("abcdef") - self.color_by(offset=2) + merge_result, new_dir = self.check_merge_conflict(self.repo.active_branch.name, self.branch) + if merge_result: + self.parse_commits(head_commit) + self.recenter_frame() + self.scale_frame() + + # Show the conflicted files names in the table/zones + self.vsplit_frame() + self.setup_and_draw_zones( + first_column_name="----", + second_column_name="Conflicted files", + third_column_name="----", + ) + self.color_by() + else: + self.parse_commits(head_commit) + self.parse_commits(branch_commit, shift=4 * m.DOWN) + self.parse_all() + self.center_frame_on_commit(head_commit) + self.setup_and_draw_parent( + head_commit, + "Merge commit", + shift=2 * m.DOWN, + draw_arrow=False, + color=m.GRAY, + ) + self.draw_arrow_between_commits("abcdef", branch_commit.hexsha) + self.draw_arrow_between_commits("abcdef", head_commit.hexsha) + self.recenter_frame() + self.scale_frame() + self.reset_head_branch("abcdef") + self.color_by(offset=2) self.fadeout() self.show_outro() + + # Unlink the program from the filesystem + self.repo.git.clear_cache() + + # Delete the local clone + shutil.rmtree(new_dir, onerror=del_rw) + + + def check_merge_conflict(self, branch1, branch2): + git_root = self.repo.git.rev_parse("--show-toplevel") + repo_name = os.path.basename(self.repo.working_dir) + new_dir = os.path.join(tempfile.gettempdir(), "git_sim", repo_name) + + orig_remotes = self.repo.remotes + self.repo = git.Repo.clone_from(git_root, new_dir, no_hardlinks=True) + self.repo.git.checkout(branch2) + self.repo.git.checkout(branch1) + + try: + self.repo.git.merge(branch2) + except git.GitCommandError as e: + if "CONFLICT" in e.stdout: + self.conflicted_files = [] + self.n = 5 + for entry in self.repo.index.entries: + if len(entry) == 2 and entry[1] > 0: + self.conflicted_files.append(entry[0]) + return 1, new_dir + return 0, new_dir + + # Override to display conflicted filenames + def populate_zones( + self, + firstColumnFileNames, + secondColumnFileNames, + thirdColumnFileNames, + firstColumnArrowMap={}, + secondColumnArrowMap={}, + thirdColumnArrowMap={}, + ): + for filename in self.conflicted_files: + secondColumnFileNames.add(filename) + + +def del_rw(action, name, exc): + os.chmod(name, stat.S_IWRITE) + os.remove(name) From b97598c1066553dbb81d11e1fbc37fa9276d66f3 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sun, 26 Mar 2023 14:53:08 -0700 Subject: [PATCH 27/39] Clean up temporary local repo clones before running commands to aboid conflicts Signed-off-by: Jacob Stopak --- git_sim/clone.py | 7 +------ git_sim/fetch.py | 7 +------ git_sim/git_sim_base_command.py | 19 +++++++++++++++++++ git_sim/merge.py | 7 +------ git_sim/pull.py | 9 ++------- git_sim/push.py | 9 ++------- 6 files changed, 26 insertions(+), 32 deletions(-) diff --git a/git_sim/clone.py b/git_sim/clone.py index 114549f..b126ddf 100644 --- a/git_sim/clone.py +++ b/git_sim/clone.py @@ -65,7 +65,7 @@ def construct(self): self.repo.git.clear_cache() # Delete the local clones - shutil.rmtree(new_dir, onerror=del_rw) + shutil.rmtree(new_dir, onerror=self.del_rw) def add_details(self, repo_name): text1 = m.Text( @@ -95,8 +95,3 @@ def add_details(self, repo_name): self.play(m.AddTextLetterByLetter(text1), m.AddTextLetterByLetter(text2)) else: self.add(text1, text2) - - -def del_rw(action, name, exc): - os.chmod(name, stat.S_IWRITE) - os.remove(name) diff --git a/git_sim/fetch.py b/git_sim/fetch.py index a535948..16ef2bc 100644 --- a/git_sim/fetch.py +++ b/git_sim/fetch.py @@ -72,9 +72,4 @@ def construct(self): self.fadeout() self.show_outro() self.repo.git.clear_cache() - shutil.rmtree(new_dir, onerror=del_rw) - - -def del_rw(action, name, exc): - os.chmod(name, stat.S_IWRITE) - os.remove(name) + shutil.rmtree(new_dir, onerror=self.del_rw) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index 0a23eff..2274157 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -1,5 +1,9 @@ import platform import sys +import os +import tempfile +import shutil +import stat import git import manim as m @@ -52,6 +56,17 @@ def __init__(self): def init_repo(self): try: self.repo = Repo(search_parent_directories=True) + repo_name = os.path.basename(self.repo.working_dir) + new_dir = os.path.join(tempfile.gettempdir(), "git_sim", repo_name) + new_dir2 = os.path.join(tempfile.gettempdir(), "git_sim", repo_name + "2") + try: + shutil.rmtree(new_dir, onerror=self.del_rw) + except FileNotFoundError: + pass + try: + shutil.rmtree(new_dir2, onerror=self.del_rw) + except FileNotFoundError: + pass except InvalidGitRepositoryError: print("git-sim error: No Git repository found at current path.") sys.exit(1) @@ -1180,6 +1195,10 @@ def add_group_to_author_groups(self, author, group): else: self.author_groups[author].append(group) + def del_rw(self, action, name, exc): + os.chmod(name, stat.S_IWRITE) + os.remove(name) + class DottedLine(m.Line): def __init__(self, *args, dot_spacing=0.4, dot_kwargs={}, **kwargs): diff --git a/git_sim/merge.py b/git_sim/merge.py index c700bee..1e9c932 100644 --- a/git_sim/merge.py +++ b/git_sim/merge.py @@ -148,7 +148,7 @@ def construct(self): self.repo.git.clear_cache() # Delete the local clone - shutil.rmtree(new_dir, onerror=del_rw) + shutil.rmtree(new_dir, onerror=self.del_rw) def check_merge_conflict(self, branch1, branch2): @@ -185,8 +185,3 @@ def populate_zones( ): for filename in self.conflicted_files: secondColumnFileNames.add(filename) - - -def del_rw(action, name, exc): - os.chmod(name, stat.S_IWRITE) - os.remove(name) diff --git a/git_sim/pull.py b/git_sim/pull.py index 628eb3f..af209d3 100644 --- a/git_sim/pull.py +++ b/git_sim/pull.py @@ -82,7 +82,7 @@ def construct(self): f"git-sim error: git pull failed for unhandled reason: {e.stdout}" ) self.repo.git.clear_cache() - shutil.rmtree(new_dir, onerror=del_rw) + shutil.rmtree(new_dir, onerror=self.del_rw) sys.exit(1) self.color_by() @@ -93,7 +93,7 @@ def construct(self): self.repo.git.clear_cache() # Delete the local clone - shutil.rmtree(new_dir, onerror=del_rw) + shutil.rmtree(new_dir, onerror=self.del_rw) # Override to display conflicted filenames def populate_zones( @@ -107,8 +107,3 @@ def populate_zones( ): for filename in self.conflicted_files: secondColumnFileNames.add(filename) - - -def del_rw(action, name, exc): - os.chmod(name, stat.S_IWRITE) - os.remove(name) diff --git a/git_sim/push.py b/git_sim/push.py index d648a23..dca8de7 100644 --- a/git_sim/push.py +++ b/git_sim/push.py @@ -109,8 +109,8 @@ def construct(self): self.orig_repo.git.clear_cache() # Delete the local clones - shutil.rmtree(new_dir, onerror=del_rw) - shutil.rmtree(new_dir2, onerror=del_rw) + shutil.rmtree(new_dir, onerror=self.del_rw) + shutil.rmtree(new_dir2, onerror=self.del_rw) def failed_push(self, push_result): if push_result == 1: @@ -196,8 +196,3 @@ def failed_push(self, push_result): self.play(*[m.AddTextLetterByLetter(t) for t in texts]) else: self.add(*texts) - - -def del_rw(action, name, exc): - os.chmod(name, stat.S_IWRITE) - os.remove(name) From 46de7888954ecb4bb0c5acd4dad0aa38dc128431 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sun, 26 Mar 2023 21:59:25 -0700 Subject: [PATCH 28/39] Fix bug in merge subcommand Signed-off-by: Jacob Stopak --- git_sim/merge.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/git_sim/merge.py b/git_sim/merge.py index 1e9c932..3818999 100644 --- a/git_sim/merge.py +++ b/git_sim/merge.py @@ -156,6 +156,7 @@ def check_merge_conflict(self, branch1, branch2): repo_name = os.path.basename(self.repo.working_dir) new_dir = os.path.join(tempfile.gettempdir(), "git_sim", repo_name) + orig_repo = self.repo orig_remotes = self.repo.remotes self.repo = git.Repo.clone_from(git_root, new_dir, no_hardlinks=True) self.repo.git.checkout(branch2) @@ -171,6 +172,7 @@ def check_merge_conflict(self, branch1, branch2): if len(entry) == 2 and entry[1] > 0: self.conflicted_files.append(entry[0]) return 1, new_dir + self.repo = orig_repo return 0, new_dir # Override to display conflicted filenames From df19c31d47d6e822dc4a0fd8daaf13425018b00d Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sun, 26 Mar 2023 22:11:10 -0700 Subject: [PATCH 29/39] Add -m option to merge subcommand to allow user to set commit message Signed-off-by: Jacob Stopak --- README.md | 6 ++++-- git_sim/commands.py | 8 +++++++- git_sim/merge.py | 7 ++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5042b3a..2da1dcc 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ Usage: `git-sim restore ... ` Usage: `git-sim commit -m "Commit message"` - Simulated output will show the new commit added to the tip of the active branch -- Specify your commit message after the -m option +- Specify a commit message with the `-m` option - HEAD and the active branch will be moved to the new commit - Simulated output will show files in the staging area being included in the new commit - Supports amending the last commit with: `$ git-sim commit --amend -m "Amended commit message"` @@ -256,12 +256,14 @@ Usage: `git-sim revert ` ![git-sim-revert_01-05-23_22-16-59](https://user-images.githubusercontent.com/49353917/210941979-6db8b55c-2881-41d8-9e2e-6263b1dece13.jpg) ### git merge -Usage: `git-sim merge ` +Usage: `git-sim merge [-m "Commit message"] [--no-ff]` - Specify `` as the branch name to merge into the active branch +- If desired, specify a commit message with the `-m` option - Simulated output will depict a fast-forward merge if possible - Otherwise, a three-way merge will be depicted - To force a merge commit when a fast-forward is possible, use `--no-ff` +- If merge fails due to merge conflicts, the conflicting files are displayed ![git-sim-merge_01-05-23_09-44-46](https://user-images.githubusercontent.com/49353917/210942030-c7229488-571a-4943-a1f4-c6e4a0c8ccf3.jpg) diff --git a/git_sim/commands.py b/git_sim/commands.py index f418518..81ab351 100644 --- a/git_sim/commands.py +++ b/git_sim/commands.py @@ -153,10 +153,16 @@ def merge( "--no-ff", help="Simulate creation of a merge commit in all cases, even when the merge could instead be resolved as a fast-forward", ), + message: str = typer.Option( + "Merge commit", + "--message", + "-m", + help="The commit message of the new merge commit", + ), ): from git_sim.merge import Merge - scene = Merge(branch=branch, no_ff=no_ff) + scene = Merge(branch=branch, no_ff=no_ff, message=message) handle_animations(scene=scene) diff --git a/git_sim/merge.py b/git_sim/merge.py index 3818999..63db6f4 100644 --- a/git_sim/merge.py +++ b/git_sim/merge.py @@ -13,10 +13,11 @@ class Merge(GitSimBaseCommand): - def __init__(self, branch: str, no_ff: bool): + def __init__(self, branch: str, no_ff: bool, message: str): super().__init__() self.branch = branch self.no_ff = no_ff + self.message = message try: git.repo.fun.rev_parse(self.repo, self.branch) @@ -76,7 +77,7 @@ def construct(self): if self.no_ff: self.center_frame_on_commit(branch_commit) - commitId = self.setup_and_draw_parent(branch_commit, "Merge commit") + commitId = self.setup_and_draw_parent(branch_commit, self.message) # If pre-merge HEAD is on screen, drawn an arrow to it as 2nd parent if head_commit.hexsha in self.drawnCommits: @@ -129,7 +130,7 @@ def construct(self): self.center_frame_on_commit(head_commit) self.setup_and_draw_parent( head_commit, - "Merge commit", + self.message, shift=2 * m.DOWN, draw_arrow=False, color=m.GRAY, From e7eef3cbdc2d70d1c63af55a66f75a848ae9ec99 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sun, 26 Mar 2023 22:52:19 -0700 Subject: [PATCH 30/39] Add global option --highlight-commit-messages to make commit message text bigger and bold, and hide commit ids Signed-off-by: Jacob Stopak --- README.md | 3 ++- git_sim/__main__.py | 5 +++++ git_sim/git_sim_base_command.py | 19 +++++++++++++------ git_sim/settings.py | 1 + 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2da1dcc..85c3851 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,8 @@ The `[global options]` apply to the overarching `git-sim` simulation itself, inc `--img-format`: Output format for the image file, i.e. `jpg` or `png`. Default output format is `jpg`. `--stdout`: Write raw image data to stdout while suppressing all other program output. `--output-only-path`: Only output the path to the generated media file to stdout. Useful for other programs to ingest. -`--quiet, -q`: Suppress all output except errors. +`--quiet, -q`: Suppress all output except errors. +`--highlight-commit-messages`: Make commit message text bigger and bold, and hide commit ids. Animation-only global options (to be used in conjunction with `--animate`): diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 134da8f..2fffa47 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -125,6 +125,10 @@ def main( settings.color_by, help="Color commits by parameter, such as author", ), + highlight_commit_messages: bool = typer.Option( + settings.highlight_commit_messages, + help="Make the displayed commit messages more prominent", + ), ): import git from manim import config, WHITE @@ -154,6 +158,7 @@ def main( settings.hide_merged_branches = hide_merged_branches settings.all = all settings.color_by = color_by + settings.highlight_commit_messages = highlight_commit_messages try: if sys.platform == "linux" or sys.platform == "darwin": diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index 2274157..369d432 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -305,22 +305,23 @@ def draw_commit(self, commit, i, prevCircle, shift=numpy.array([0.0, 0.0, 0.0])) commitMessage[j : j + 20] for j in range(0, len(commitMessage), 20) )[:100], font="Monospace", - font_size=14, + font_size=20 if settings.highlight_commit_messages else 14, color=self.fontColor, + weight=m.BOLD if settings.highlight_commit_messages else m.NORMAL, ).next_to(circle, m.DOWN) if settings.animate and commit != "dark" and isNewCommit: self.play( self.camera.frame.animate.move_to(circle.get_center()), m.Create(circle), - m.AddTextLetterByLetter(commitId), + m.Text("") if settings.highlight_commit_messages else m.AddTextLetterByLetter(commitId), m.AddTextLetterByLetter(message), run_time=1 / settings.speed, ) elif isNewCommit: - self.add(circle, commitId, message) + self.add(circle, m.Text("") if settings.highlight_commit_messages else commitId, message) else: - return commitId, circle, arrow, hide_refs + return m.Text("") if settings.highlight_commit_messages else commitId, circle, arrow, hide_refs if commit != "dark": self.drawnCommits[commit.hexsha] = circle @@ -328,7 +329,10 @@ def draw_commit(self, commit, i, prevCircle, shift=numpy.array([0.0, 0.0, 0.0])) self.add_group_to_author_groups(commit.author.name, group) self.toFadeOut.add(circle, commitId, message) - self.prevRef = commitId + if settings.highlight_commit_messages: + self.prevRef = circle + else: + self.prevRef = commitId return commitId, circle, arrow, hide_refs @@ -362,7 +366,10 @@ def draw_head(self, commit, i, commitId): headbox = m.Rectangle(color=m.BLUE, fill_color=m.BLUE, fill_opacity=0.25) headbox.width = 1 headbox.height = 0.4 - headbox.next_to(commitId, m.UP) + if settings.highlight_commit_messages: + headbox.next_to(self.drawnCommits[commit.hexsha], m.UP) + else: + headbox.next_to(commitId, m.UP) headText = m.Text( "HEAD", font="Monospace", font_size=20, color=self.fontColor ).move_to(headbox.get_center()) diff --git a/git_sim/settings.py b/git_sim/settings.py index 53c029a..12e5c0c 100644 --- a/git_sim/settings.py +++ b/git_sim/settings.py @@ -46,6 +46,7 @@ class Settings(BaseSettings): hide_merged_branches = False all = False color_by: Union[str, None] = None + highlight_commit_messages = False class Config: env_prefix = "git_sim_" From 478ff644055ecfea5106ca4d81b1b9ab53711842 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sun, 26 Mar 2023 22:55:19 -0700 Subject: [PATCH 31/39] Fix output image file names to include subcommand name after prior code refactor broke it Signed-off-by: Jacob Stopak --- git_sim/animations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_sim/animations.py b/git_sim/animations.py index 141ed8d..35bd2b9 100644 --- a/git_sim/animations.py +++ b/git_sim/animations.py @@ -37,7 +37,7 @@ def handle_animations(scene: Scene) -> None: ) image_file_name = ( "git-sim-" - + inspect.stack()[1].function + + inspect.stack()[2].function + "_" + t + "." From 0463de844d8dc79912f38420fd39323f8ed9edab Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Mon, 27 Mar 2023 13:57:08 -0700 Subject: [PATCH 32/39] Add global flag --transparent-bg to generate output images with transparent background Signed-off-by: Jacob Stopak --- git_sim/__main__.py | 9 +++++++++ git_sim/animations.py | 12 ++++++++++++ git_sim/git_sim_base_command.py | 26 ++++++++++++++++---------- git_sim/settings.py | 1 + 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 2fffa47..fdcb27f 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -39,6 +39,11 @@ def main( "--light-mode", help="Enable light-mode with white background", ), + transparent_bg: bool = typer.Option( + settings.transparent_bg, + "--transparent-bg", + help="Make background transparent", + ), logo: pathlib.Path = typer.Option( settings.logo, help="The path to a custom logo to use in the animation intro/outro", @@ -138,6 +143,7 @@ def main( settings.auto_open = auto_open settings.img_format = img_format settings.light_mode = light_mode + settings.transparent_bg = transparent_bg settings.logo = logo settings.low_quality = low_quality settings.max_branches_per_commit = max_branches_per_commit @@ -183,6 +189,9 @@ def main( if settings.light_mode: config.background_color = WHITE + if settings.transparent_bg: + settings.img_format = ImgFormat.png + t = datetime.datetime.fromtimestamp(time.time()).strftime("%m-%d-%y_%H-%M-%S") config.output_file = "git-sim-" + ctx.invoked_subcommand + "_" + t + ".mp4" diff --git a/git_sim/animations.py b/git_sim/animations.py index 35bd2b9..93c91a3 100644 --- a/git_sim/animations.py +++ b/git_sim/animations.py @@ -46,6 +46,18 @@ def handle_animations(scene: Scene) -> None: image_file_path = os.path.join( os.path.join(settings.media_dir, "images"), image_file_name ) + if settings.transparent_bg: + unsharp_image = cv2.GaussianBlur(image, (0, 0), 3) + image = cv2.addWeighted(image, 1.5, unsharp_image, -0.5, 0) + + tmp = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + if settings.light_mode: + _, alpha = cv2.threshold(tmp, 225, 255, cv2.THRESH_BINARY_INV) + else: + _, alpha = cv2.threshold(tmp, 25, 255, cv2.THRESH_BINARY) + b, g, r = cv2.split(image) + rgba = [b, g, r, alpha] + image = cv2.merge(rgba, 4) cv2.imwrite(image_file_path, image) if ( not settings.stdout diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index 369d432..ae06f49 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -53,6 +53,12 @@ def __init__(self): self.logo = m.ImageMobject(settings.logo) self.logo.width = 3 + self.fill_opacity = 0.25 + self.ref_fill_opacity = 0.25 + if settings.transparent_bg: + self.fill_opacity = 0.5 + self.ref_fill_opacity = 1.0 + def init_repo(self): try: self.repo = Repo(search_parent_directories=True) @@ -224,14 +230,14 @@ def get_centers(self): def draw_commit(self, commit, i, prevCircle, shift=numpy.array([0.0, 0.0, 0.0])): if commit == "dark": - commitFill = m.WHITE if settings.light_mode else m.BLACK + commit_fill = m.WHITE if settings.light_mode else m.BLACK elif len(commit.parents) <= 1: - commitFill = m.RED + commit_fill = m.RED else: - commitFill = m.GRAY - + commit_fill = m.GRAY + circle = m.Circle( - stroke_color=commitFill, fill_color=commitFill, fill_opacity=0.25 + stroke_color=commit_fill, fill_color=commit_fill, fill_opacity=self.fill_opacity ) circle.height = 1 @@ -363,7 +369,7 @@ def build_commit_id_and_message(self, commit, i): def draw_head(self, commit, i, commitId): if commit.hexsha == self.repo.head.commit.hexsha: - headbox = m.Rectangle(color=m.BLUE, fill_color=m.BLUE, fill_opacity=0.25) + headbox = m.Rectangle(color=m.BLUE, fill_color=m.BLUE, fill_opacity=self.ref_fill_opacity) headbox.width = 1 headbox.height = 0.4 if settings.highlight_commit_messages: @@ -423,7 +429,7 @@ def draw_branch(self, commit, i, make_branches_remote=False): branchRec = m.Rectangle( color=m.GREEN, fill_color=m.GREEN, - fill_opacity=0.25, + fill_opacity=self.ref_fill_opacity, height=0.4, width=branchText.width + 0.25, ) @@ -468,7 +474,7 @@ def draw_tag(self, commit, i): tagRec = m.Rectangle( color=m.YELLOW, fill_color=m.YELLOW, - fill_opacity=0.25, + fill_opacity=self.ref_fill_opacity, height=0.4, width=tagText.width + 0.25, ) @@ -954,7 +960,7 @@ def setup_and_draw_parent( draw_arrow=True, color=m.RED, ): - circle = m.Circle(stroke_color=color, fill_color=color, fill_opacity=0.25) + circle = m.Circle(stroke_color=color, fill_color=color, fill_opacity=self.ref_fill_opacity) circle.height = 1 circle.next_to( self.drawnCommits[child.hexsha], @@ -1033,7 +1039,7 @@ def draw_ref(self, commit, top, i=0, text="HEAD", color=m.BLUE): refbox = m.Rectangle( color=color, fill_color=color, - fill_opacity=0.25, + fill_opacity=self.ref_fill_opacity, height=0.4, width=refText.width + 0.25, ) diff --git a/git_sim/settings.py b/git_sim/settings.py index 12e5c0c..03ab880 100644 --- a/git_sim/settings.py +++ b/git_sim/settings.py @@ -26,6 +26,7 @@ class Settings(BaseSettings): img_format: ImgFormat = ImgFormat.jpg INFO_STRING = "Simulating: git" light_mode = False + transparent_bg = False logo = pathlib.Path(__file__).parent.resolve() / "logo.png" low_quality = False max_branches_per_commit = 1 From 5278a7c0d38280f3b1ebc3d787506f892ff97f51 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Mon, 27 Mar 2023 13:57:37 -0700 Subject: [PATCH 33/39] Format with black Signed-off-by: Jacob Stopak --- git_sim/git_sim_base_command.py | 31 ++++++++++++++++++++++++------- git_sim/merge.py | 5 +++-- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index ae06f49..4a3e22f 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -235,9 +235,11 @@ def draw_commit(self, commit, i, prevCircle, shift=numpy.array([0.0, 0.0, 0.0])) commit_fill = m.RED else: commit_fill = m.GRAY - + circle = m.Circle( - stroke_color=commit_fill, fill_color=commit_fill, fill_opacity=self.fill_opacity + stroke_color=commit_fill, + fill_color=commit_fill, + fill_opacity=self.fill_opacity, ) circle.height = 1 @@ -320,14 +322,25 @@ def draw_commit(self, commit, i, prevCircle, shift=numpy.array([0.0, 0.0, 0.0])) self.play( self.camera.frame.animate.move_to(circle.get_center()), m.Create(circle), - m.Text("") if settings.highlight_commit_messages else m.AddTextLetterByLetter(commitId), + m.Text("") + if settings.highlight_commit_messages + else m.AddTextLetterByLetter(commitId), m.AddTextLetterByLetter(message), run_time=1 / settings.speed, ) elif isNewCommit: - self.add(circle, m.Text("") if settings.highlight_commit_messages else commitId, message) + self.add( + circle, + m.Text("") if settings.highlight_commit_messages else commitId, + message, + ) else: - return m.Text("") if settings.highlight_commit_messages else commitId, circle, arrow, hide_refs + return ( + m.Text("") if settings.highlight_commit_messages else commitId, + circle, + arrow, + hide_refs, + ) if commit != "dark": self.drawnCommits[commit.hexsha] = circle @@ -369,7 +382,9 @@ def build_commit_id_and_message(self, commit, i): def draw_head(self, commit, i, commitId): if commit.hexsha == self.repo.head.commit.hexsha: - headbox = m.Rectangle(color=m.BLUE, fill_color=m.BLUE, fill_opacity=self.ref_fill_opacity) + headbox = m.Rectangle( + color=m.BLUE, fill_color=m.BLUE, fill_opacity=self.ref_fill_opacity + ) headbox.width = 1 headbox.height = 0.4 if settings.highlight_commit_messages: @@ -960,7 +975,9 @@ def setup_and_draw_parent( draw_arrow=True, color=m.RED, ): - circle = m.Circle(stroke_color=color, fill_color=color, fill_opacity=self.ref_fill_opacity) + circle = m.Circle( + stroke_color=color, fill_color=color, fill_opacity=self.ref_fill_opacity + ) circle.height = 1 circle.next_to( self.drawnCommits[child.hexsha], diff --git a/git_sim/merge.py b/git_sim/merge.py index 63db6f4..cb4761c 100644 --- a/git_sim/merge.py +++ b/git_sim/merge.py @@ -109,7 +109,9 @@ def construct(self): self.color_by() else: - merge_result, new_dir = self.check_merge_conflict(self.repo.active_branch.name, self.branch) + merge_result, new_dir = self.check_merge_conflict( + self.repo.active_branch.name, self.branch + ) if merge_result: self.parse_commits(head_commit) self.recenter_frame() @@ -151,7 +153,6 @@ def construct(self): # Delete the local clone shutil.rmtree(new_dir, onerror=self.del_rw) - def check_merge_conflict(self, branch1, branch2): git_root = self.repo.git.rev_parse("--show-toplevel") repo_name = os.path.basename(self.repo.working_dir) From a439e1d0b583e4365e72c180eee1f4e73b8f528b Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 31 Mar 2023 16:35:16 -0700 Subject: [PATCH 34/39] Allow fetch subcommand to work with empty arguments Signed-off-by: Jacob Stopak --- git_sim/commands.py | 4 ++-- git_sim/fetch.py | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/git_sim/commands.py b/git_sim/commands.py index 81ab351..52fc2a3 100644 --- a/git_sim/commands.py +++ b/git_sim/commands.py @@ -110,11 +110,11 @@ def commit( def fetch( remote: str = typer.Argument( - ..., + default=None, help="The name of the remote to fetch from", ), branch: str = typer.Argument( - ..., + default=None, help="The name of the branch to fetch", ), ): diff --git a/git_sim/fetch.py b/git_sim/fetch.py index 16ef2bc..0a2a9bf 100644 --- a/git_sim/fetch.py +++ b/git_sim/fetch.py @@ -20,16 +20,21 @@ def __init__(self, remote: str, branch: str): self.branch = branch settings.max_branches_per_commit = 2 - if self.remote not in self.repo.remotes: + if self.remote and self.remote not in self.repo.remotes: print("git-sim error: no remote with name '" + self.remote + "'") sys.exit(1) def construct(self): if not settings.stdout and not settings.output_only_path and not settings.quiet: print( - f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.remote} {self.branch}" + f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.remote if self.remote else ''} {self.branch if self.branch else ''}" ) + if not self.remote: + self.remote = "origin" + if not self.branch: + self.branch = self.repo.active_branch.name + self.show_intro() git_root = self.repo.git.rev_parse("--show-toplevel") @@ -42,7 +47,12 @@ def construct(self): for r2 in self.repo.remotes: if r1.name == r2.name: r2.set_url(r1.url) - self.repo.git.fetch(self.remote, self.branch) + + try: + self.repo.git.fetch(self.remote, self.branch) + except git.GitCommandError as e: + print(e) + sys.exit(1) # local branch doesn't exist if self.branch not in self.repo.heads: From 683aa3584349313357f34197a7ea843582fefbd2 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 31 Mar 2023 16:59:28 -0700 Subject: [PATCH 35/39] Fix bug in push subcommand when simulating successful push Signed-off-by: Jacob Stopak --- git_sim/push.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/git_sim/push.py b/git_sim/push.py index dca8de7..24ad905 100644 --- a/git_sim/push.py +++ b/git_sim/push.py @@ -96,6 +96,7 @@ def construct(self): ) else: self.parse_commits(head_commit) + self.recenter_frame() self.scale_frame() self.failed_push(push_result) @@ -113,6 +114,7 @@ def construct(self): shutil.rmtree(new_dir2, onerror=self.del_rw) def failed_push(self, push_result): + texts = [] if push_result == 1: text1 = m.Text( f"'git push' failed since the remote repo has commits that don't exist locally.", From a2bc839898209e6f87e6851a832aab0867bfa9f4 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 31 Mar 2023 17:11:22 -0700 Subject: [PATCH 36/39] Fix length of commit chain when creating dark commits Signed-off-by: Jacob Stopak --- git_sim/git_sim_base_command.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index 4a3e22f..cb8def5 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -29,6 +29,7 @@ def __init__(self): self.n_default = settings.n_default self.n = settings.n self.n_orig = self.n + self.n_dark_commits = 0 self.selected_branches = [] self.zone_title_offset = 2.6 if platform.system() == "Windows" else 2.6 self.arrow_map = [] @@ -143,7 +144,8 @@ def parse_commits( try: commitParents = list(commit.parents) except AttributeError: - if len(self.drawnCommits) < self.n_default: + if ((len(self.drawnCommits) + self.n_dark_commits) < self.n_default): + self.n_dark_commits += 1 self.parse_commits(self.create_dark_commit(), i, circle) return @@ -157,7 +159,8 @@ def parse_commits( for p in range(len(commitParents)): self.parse_commits(commitParents[p], i, circle) else: - if len(self.drawnCommits) < self.n_default: + if ((len(self.drawnCommits) + self.n_dark_commits) < self.n_default): + self.n_dark_commits += 1 self.parse_commits(self.create_dark_commit(), i, circle) def parse_all(self): From d69063c32b9c95ea6f7d8f5f40019e43920a14e9 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 31 Mar 2023 17:20:51 -0700 Subject: [PATCH 37/39] Hide merged branches when git pull results in a merge conflict Signed-off-by: Jacob Stopak --- git_sim/pull.py | 1 + 1 file changed, 1 insertion(+) diff --git a/git_sim/pull.py b/git_sim/pull.py index af209d3..83eb528 100644 --- a/git_sim/pull.py +++ b/git_sim/pull.py @@ -61,6 +61,7 @@ def construct(self): if "CONFLICT" in e.stdout: # Restrict to default number of commits since we'll show the table/zones self.n = self.n_default + settings.hide_merged_branches = True # Get list of conflicted filenames self.conflicted_files = re.findall(r"Merge conflict in (.+)", e.stdout) From 6c342fabf18c2adcd50b2631061135d0ddcfad65 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 31 Mar 2023 17:21:27 -0700 Subject: [PATCH 38/39] Format with black Signed-off-by: Jacob Stopak --- git_sim/git_sim_base_command.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index cb8def5..22f5e26 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -144,7 +144,7 @@ def parse_commits( try: commitParents = list(commit.parents) except AttributeError: - if ((len(self.drawnCommits) + self.n_dark_commits) < self.n_default): + if (len(self.drawnCommits) + self.n_dark_commits) < self.n_default: self.n_dark_commits += 1 self.parse_commits(self.create_dark_commit(), i, circle) return @@ -159,7 +159,7 @@ def parse_commits( for p in range(len(commitParents)): self.parse_commits(commitParents[p], i, circle) else: - if ((len(self.drawnCommits) + self.n_dark_commits) < self.n_default): + if (len(self.drawnCommits) + self.n_dark_commits) < self.n_default: self.n_dark_commits += 1 self.parse_commits(self.create_dark_commit(), i, circle) From 8ccb18addebaf0891b0eaca79c6c5b65146aebeb Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 31 Mar 2023 17:24:13 -0700 Subject: [PATCH 39/39] Bump version to 0.2.8 Signed-off-by: Jacob Stopak --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a5b5db5..7ffb1e8 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="git-sim", - version="0.2.7", + version="0.2.8", author="Jacob Stopak", author_email="jacob@initialcommit.io", description="Simulate Git commands on your own repos by generating an image (default) or video visualization depicting the command's behavior.",