From 3f19d5a3901d5ec2e8005db511b2b2193386bb8b Mon Sep 17 00:00:00 2001 From: Manuel Stausberg Date: Fri, 27 Jan 2023 20:44:17 +0100 Subject: [PATCH 01/27] first draft for typer commands --- git_sim/__main__.py | 689 +++++++++++++++----------------- git_sim/animations.py | 47 +++ git_sim/git_sim_base_command.py | 127 +++--- git_sim/log.py | 39 ++ git_sim/settings.py | 25 ++ git_sim/status.py | 33 ++ setup.py | 2 +- 7 files changed, 539 insertions(+), 423 deletions(-) create mode 100644 git_sim/animations.py create mode 100644 git_sim/log.py create mode 100644 git_sim/settings.py create mode 100644 git_sim/status.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 93291a4..367d364 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -5,368 +5,341 @@ import subprocess import sys import time -from argparse import Namespace -from typing import Type - -import cv2 -import git -from manim import WHITE, config -from manim.utils.file_ops import open_file as open_media_file - -from git_sim.git_sim_add import GitSimAdd -from git_sim.git_sim_base_command import GitSimBaseCommand -from git_sim.git_sim_branch import GitSimBranch -from git_sim.git_sim_cherrypick import GitSimCherryPick -from git_sim.git_sim_commit import GitSimCommit -from git_sim.git_sim_log import GitSimLog -from git_sim.git_sim_merge import GitSimMerge -from git_sim.git_sim_rebase import GitSimRebase -from git_sim.git_sim_reset import GitSimReset -from git_sim.git_sim_restore import GitSimRestore -from git_sim.git_sim_revert import GitSimRevert -from git_sim.git_sim_stash import GitSimStash -from git_sim.git_sim_status import GitSimStatus -from git_sim.git_sim_tag import GitSimTag - - -def get_scene_for_command(args: Namespace) -> Type[GitSimBaseCommand]: - - if args.subcommand == "log": - return GitSimLog - elif args.subcommand == "status": - return GitSimStatus - elif args.subcommand == "add": - return GitSimAdd - elif args.subcommand == "restore": - return GitSimRestore - elif args.subcommand == "commit": - return GitSimCommit - elif args.subcommand == "stash": - return GitSimStash - elif args.subcommand == "branch": - return GitSimBranch - elif args.subcommand == "tag": - return GitSimTag - elif args.subcommand == "reset": - return GitSimReset - elif args.subcommand == "revert": - return GitSimRevert - elif args.subcommand == "merge": - return GitSimMerge - elif args.subcommand == "rebase": - return GitSimRebase - elif args.subcommand == "cherry-pick": - return GitSimCherryPick - - raise NotImplementedError(f"command '{args.subcommand}' is not yet implemented.") - - -def main(): - parser = argparse.ArgumentParser( - "git-sim", formatter_class=argparse.ArgumentDefaultsHelpFormatter - ) - parser.add_argument( - "--title", + +import typer + +from git_sim.log import log +from git_sim.settings import Settings +from git_sim.status import status + +app = typer.Typer() +app.command()(log) +app.command()(status) + + +@app.callback() +def main( + title: str = typer.Option( + default=Settings.title, help="Custom title to display at the beginning of the animation", - type=str, - default="Git Sim, by initialcommit.com", - ) - parser.add_argument( - "--logo", - help="The path to a custom logo to use in the animation intro/outro", - type=str, - default=os.path.join(str(pathlib.Path(__file__).parent.resolve()), "logo.png"), - ) - parser.add_argument( - "--outro-top-text", + ), + animate: bool = typer.Option( + default=Settings.animate, + help="Animate the simulation and output as an mp4 video", + ), + outro_top_text: str = typer.Option( + default=Settings.outro_top_text, help="Custom text to display above the logo during the outro", - type=str, - default="Thanks for using Initial Commit!", - ) - parser.add_argument( - "--outro-bottom-text", + ), + outro_bottom_text: str = typer.Option( + default=Settings.outro_bottom_text, help="Custom text to display below the logo during the outro", - type=str, - default="Learn more at initialcommit.com", - ) - parser.add_argument( - "--show-intro", - help="Add an intro sequence with custom logo and title", - action="store_true", - ) - parser.add_argument( - "--show-outro", - help="Add an outro sequence with custom logo and text", - action="store_true", - ) - parser.add_argument( - "--media-dir", - help="The path to output the animation data and video file", - type=str, - default=".", - ) - parser.add_argument( - "--low-quality", + ), + low_quality: bool = typer.Option( + default=Settings.low_quality, help="Render output video in low quality, useful for faster testing", - action="store_true", - ) - parser.add_argument( - "--light-mode", - help="Enable light-mode with white background", - action="store_true", - ) - parser.add_argument( - "--speed", - help="A multiple of the standard 1x animation speed (ex: 2 = twice as fast, 0.5 = half as fast)", - type=float, - default=1.5, - ) - parser.add_argument( - "--animate", - help="Animate the simulation and output as an mp4 video", - action="store_true", - ) - parser.add_argument( - "--max-branches-per-commit", - help="Maximum number of branch labels to display for each commit", - type=int, - default=1, - ) - parser.add_argument( - "--max-tags-per-commit", - help="Maximum number of tags to display for each commit", - type=int, - default=1, - ) - parser.add_argument( - "-d", - "--disable-auto-open", - help="Disable the automatic opening of the image/video file after generation", - action="store_true", - ) - parser.add_argument( - "-r", - "--reverse", - help="Display commit history in the reverse direction", - action="store_true", - ) - parser.add_argument( - "--video-format", - help="Output format for the animation files. Supports mp4 (default) and webm", - type=str, - default="mp4", - choices=["mp4", "webm"], - ) - parser.add_argument( - "--img-format", - help="Output format for the image files. Supports jpg (default) and png", - type=str, - default="jpg", - choices=["jpg", "png"], - ) - - subparsers = parser.add_subparsers(dest="subcommand", help="subcommand help") - - log = subparsers.add_parser("log", help="log -h") - log.add_argument( - "--commits", - help="The number of commits to display in the simulated log output", - type=int, - default=5, - choices=range(1, 13), - ) - - status = subparsers.add_parser("status", help="status -h") - - add = subparsers.add_parser("add", help="add -h") - add.add_argument( - "name", - nargs="+", - help="The names of one or more files to add to Git's staging area", - type=str, - ) - - restore = subparsers.add_parser("restore", help="restore -h") - restore.add_argument( - "name", nargs="+", help="The names of one or more files to restore", type=str - ) - - commit = subparsers.add_parser("commit", help="commit -h") - commit.add_argument( - "-m", - "--message", - help="The commit message of the new commit", - type=str, - default="New commit", - ) - commit.add_argument( - "--amend", - help="Amend the last commit message, must be used with the -m flag", - action="store_true", - ) - - stash = subparsers.add_parser("stash", help="stash -h") - stash.add_argument( - "name", nargs="*", help="The name of the file to stash changes for", type=str - ) - - branch = subparsers.add_parser("branch", help="branch -h") - branch.add_argument("name", help="The name of the new branch", type=str) - - tag = subparsers.add_parser("tag", help="tag -h") - tag.add_argument("name", help="The name of the new tag", type=str) - - reset = subparsers.add_parser("reset", help="reset -h") - reset.add_argument( - "commit", - nargs="?", - help="The ref (branch/tag), or commit ID to simulate reset to", - type=str, - default="HEAD", - ) - reset.add_argument( - "--mode", - help="Either mixed (default), soft, or hard", - type=str, - default="default", - ) - reset.add_argument( - "--soft", - help="Simulate a soft reset, shortcut for --mode=soft", - action="store_true", - ) - reset.add_argument( - "--mixed", - help="Simulate a mixed reset, shortcut for --mode=mixed", - action="store_true", - ) - reset.add_argument( - "--hard", - help="Simulate a soft reset, shortcut for --mode=hard", - action="store_true", - ) - - revert = subparsers.add_parser("revert", help="revert -h") - revert.add_argument( - "commit", - nargs="?", - help="The ref (branch/tag), or commit ID to simulate revert", - type=str, - default="HEAD", - ) - - merge = subparsers.add_parser("merge", help="merge -h") - merge.add_argument( - "branch", - nargs=1, - type=str, - help="The name of the branch to merge into the active checked-out branch", - ) - merge.add_argument( - "--no-ff", - help="Simulate creation of a merge commit in all cases, even when the merge could instead be resolved as a fast-forward", - action="store_true", - ) - - rebase = subparsers.add_parser("rebase", help="rebase -h") - rebase.add_argument( - "branch", - nargs=1, - type=str, - help="The branch to simulate rebasing the checked-out commit onto", - ) - - cherrypick = subparsers.add_parser("cherry-pick", help="cherry-pick -h") - cherrypick.add_argument( - "commit", - nargs=1, - type=str, - help="The ref (branch/tag), or commit ID to simulate cherry-pick onto active branch", - ) - cherrypick.add_argument( - "-e", - "--edit", - help="Specify a new commit message for the cherry-picked commit", - type=str, - ) - - if len(sys.argv) == 1: - parser.print_help() - sys.exit(1) - - args = parser.parse_args() - - if sys.platform == "linux" or sys.platform == "darwin": - repo_name = git.Repo(search_parent_directories=True).working_tree_dir.split( - "/" - )[-1] - elif sys.platform == "win32": - repo_name = git.Repo(search_parent_directories=True).working_tree_dir.split( - "\\" - )[-1] - - config.media_dir = os.path.join(os.path.expanduser(args.media_dir), "git-sim_media") - config.verbosity = "ERROR" - - # If the env variable is set and no argument provided, use the env variable value - if os.getenv("git_sim_media_dir") and args.media_dir == ".": - config.media_dir = os.path.join( - os.path.expanduser(os.getenv("git_sim_media_dir")), - "git-sim_media", - repo_name, - ) - - if args.low_quality: - config.quality = "low_quality" - - if args.light_mode: - config.background_color = WHITE - - t = datetime.datetime.fromtimestamp(time.time()).strftime("%m-%d-%y_%H-%M-%S") - config.output_file = "git-sim-" + args.subcommand + "_" + t + ".mp4" - - scene_class = get_scene_for_command(args=args) - scene = scene_class(args=args) - scene.render() - - if args.video_format == "webm": - webm_file_path = str(scene.renderer.file_writer.movie_file_path)[:-3] + "webm" - cmd = f"ffmpeg -y -i {scene.renderer.file_writer.movie_file_path} -hide_banner -loglevel error -c:v libvpx-vp9 -crf 50 -b:v 0 -b:a 128k -c:a libopus {webm_file_path}" - print("Converting video output to .webm format...") - # Start ffmpeg conversion - p = subprocess.Popen(cmd, shell=True) - p.wait() - # if the conversion is successful, delete the .mp4 - if os.path.exists(webm_file_path): - os.remove(scene.renderer.file_writer.movie_file_path) - scene.renderer.file_writer.movie_file_path = webm_file_path - - if not args.animate: - video = cv2.VideoCapture(str(scene.renderer.file_writer.movie_file_path)) - success, image = video.read() - if success: - image_file_name = ( - "git-sim-" + args.subcommand + "_" + t + "." + args.img_format - ) - image_file_path = os.path.join( - os.path.join(config.media_dir, "images"), image_file_name - ) - cv2.imwrite(image_file_path, image) - print("Output image location:", image_file_path) - else: - print("Output video location:", scene.renderer.file_writer.movie_file_path) - - if not args.disable_auto_open: - try: - if not args.animate: - open_media_file(image_file_path) - else: - open_media_file(scene.renderer.file_writer.movie_file_path) - except FileNotFoundError: - print( - "Error automatically opening media, please manually open the image or video file to view." - ) + ), + auto_open: bool = typer.Option( + default=Settings.auto_open, + help="Enable / disable the automatic opening of the image/video file after generation", + ), + light_mode: bool = typer.Option( + default=Settings.light_mode, help="Enable light-mode with white background" + ), +): + Settings.animate = animate + Settings.title = title + Settings.outro_top_text = outro_top_text + Settings.outro_bottom_text = outro_bottom_text + Settings.low_quality = low_quality + Settings.auto_open = auto_open + Settings.light_mode = light_mode if __name__ == "__main__": - main() + app() + + +# import argparse +# import datetime +# import os +# import pathlib +# import sys +# import time + +# import cv2 +# import git +# from manim import WHITE, config +# from manim.utils.file_ops import open_file as open_media_file + +# import git_sim.git_sim as gs +# from git_sim.git_sim_log import GitSimLog + + +# def main(): +# parser = argparse.ArgumentParser( +# "git-sim", formatter_class=argparse.ArgumentDefaultsHelpFormatter +# ) +# parser.add_argument( +# "--title", +# help="Custom title to display at the beginning of the animation", +# type=str, +# default="Git Sim, by initialcommit.com", +# ) +# parser.add_argument( +# "--logo", +# help="The path to a custom logo to use in the animation intro/outro", +# type=str, +# default=os.path.join(str(pathlib.Path(__file__).parent.resolve()), "logo.png"), +# ) +# parser.add_argument( +# "--outro-top-text", +# help="Custom text to display above the logo during the outro", +# type=str, +# default="Thanks for using Initial Commit!", +# ) +# parser.add_argument( +# "--outro-bottom-text", +# help="Custom text to display below the logo during the outro", +# type=str, +# default="Learn more at initialcommit.com", +# ) +# parser.add_argument( +# "--show-intro", +# help="Add an intro sequence with custom logo and title", +# action="store_true", +# ) +# parser.add_argument( +# "--show-outro", +# help="Add an outro sequence with custom logo and text", +# action="store_true", +# ) +# parser.add_argument( +# "--media-dir", +# help="The path to output the animation data and video file", +# type=str, +# default=".", +# ) +# parser.add_argument( +# "--low-quality", +# help="Render output video in low quality, useful for faster testing", +# action="store_true", +# ) +# parser.add_argument( +# "--light-mode", +# help="Enable light-mode with white background", +# action="store_true", +# ) +# parser.add_argument( +# "--speed", +# help="A multiple of the standard 1x animation speed (ex: 2 = twice as fast, 0.5 = half as fast)", +# type=float, +# default=1.5, +# ) +# parser.add_argument( +# "--animate", +# help="Animate the simulation and output as an mp4 video", +# action="store_true", +# ) +# parser.add_argument( +# "--max-branches-per-commit", +# help="Maximum number of branch labels to display for each commit", +# type=int, +# default=1, +# ) +# parser.add_argument( +# "--max-tags-per-commit", +# help="Maximum number of tags to display for each commit", +# type=int, +# default=1, +# ) +# parser.add_argument( +# "-d", +# "--disable-auto-open", +# help="Disable the automatic opening of the image/video file after generation", +# action="store_true", +# ) +# parser.add_argument( +# "-r", +# "--reverse", +# help="Display commit history in the reverse direction", +# action="store_true", +# ) + +# subparsers = parser.add_subparsers(dest="subcommand", help="subcommand help") + +# log = subparsers.add_parser("log", help="log -h") +# log.add_argument( +# "--commits", +# help="The number of commits to display in the simulated log output", +# type=int, +# default=5, +# choices=range(1, 13), +# ) + +# status = subparsers.add_parser("status", help="status -h") + +# add = subparsers.add_parser("add", help="add -h") +# add.add_argument( +# "name", +# nargs="+", +# help="The names of one or more files to add to Git's staging area", +# type=str, +# ) + +# restore = subparsers.add_parser("restore", help="restore -h") +# restore.add_argument( +# "name", nargs="+", help="The names of one or more files to restore", type=str +# ) + +# commit = subparsers.add_parser("commit", help="commit -h") +# commit.add_argument( +# "-m", +# "--message", +# help="The commit message of the new commit", +# type=str, +# default="New commit", +# ) + +# stash = subparsers.add_parser("stash", help="stash -h") +# stash.add_argument( +# "name", nargs="*", help="The name of the file to stash changes for", type=str +# ) + +# branch = subparsers.add_parser("branch", help="branch -h") +# branch.add_argument("name", help="The name of the new branch", type=str) + +# tag = subparsers.add_parser("tag", help="tag -h") +# tag.add_argument("name", help="The name of the new tag", type=str) + +# reset = subparsers.add_parser("reset", help="reset -h") +# reset.add_argument( +# "commit", +# nargs="?", +# help="The ref (branch/tag), or commit ID to simulate reset to", +# type=str, +# default="HEAD", +# ) +# reset.add_argument( +# "--mode", +# help="Either mixed (default), soft, or hard", +# type=str, +# default="default", +# ) +# reset.add_argument( +# "--soft", +# help="Simulate a soft reset, shortcut for --mode=soft", +# action="store_true", +# ) +# reset.add_argument( +# "--mixed", +# help="Simulate a mixed reset, shortcut for --mode=mixed", +# action="store_true", +# ) +# reset.add_argument( +# "--hard", +# help="Simulate a soft reset, shortcut for --mode=hard", +# action="store_true", +# ) + +# revert = subparsers.add_parser("revert", help="revert -h") +# revert.add_argument( +# "commit", +# nargs="?", +# help="The ref (branch/tag), or commit ID to simulate revert", +# type=str, +# default="HEAD", +# ) + +# merge = subparsers.add_parser("merge", help="merge -h") +# merge.add_argument( +# "branch", +# nargs=1, +# type=str, +# help="The name of the branch to merge into the active checked-out branch", +# ) +# merge.add_argument( +# "--no-ff", +# help="Simulate creation of a merge commit in all cases, even when the merge could instead be resolved as a fast-forward", +# action="store_true", +# ) + +# rebase = subparsers.add_parser("rebase", help="rebase -h") +# rebase.add_argument( +# "branch", +# nargs=1, +# type=str, +# help="The branch to simulate rebasing the checked-out commit onto", +# ) + +# cherrypick = subparsers.add_parser("cherry-pick", help="cherry-pick -h") +# cherrypick.add_argument( +# "commit", +# nargs=1, +# type=str, +# help="The ref (branch/tag), or commit ID to simulate cherry-pick onto active branch", +# ) + +# if len(sys.argv) == 1: +# parser.print_help() +# sys.exit(1) + +# args = parser.parse_args() + +# if sys.platform == "linux" or sys.platform == "darwin": +# repo_name = git.Repo(search_parent_directories=True).working_tree_dir.split( +# "/" +# )[-1] +# elif sys.platform == "win32": +# repo_name = git.Repo(search_parent_directories=True).working_tree_dir.split( +# "\\" +# )[-1] + +# config.media_dir = os.path.join(os.path.expanduser(args.media_dir), "git-sim_media") +# config.verbosity = "ERROR" + +# # If the env variable is set and no argument provided, use the env variable value +# if os.getenv("git_sim_media_dir") and args.media_dir == ".": +# config.media_dir = os.path.join( +# os.path.expanduser(os.getenv("git_sim_media_dir")), +# "git-sim_media", +# repo_name, +# ) + +# if args.low_quality: +# config.quality = "low_quality" + +# if args.light_mode: +# config.background_color = WHITE + +# scene = gs.get_scene_for_command(args=args) + +# scene.render() + +# if not args.animate: +# video = cv2.VideoCapture(str(scene.renderer.file_writer.movie_file_path)) +# success, image = video.read() +# if success: +# t = datetime.datetime.fromtimestamp(time.time()).strftime( +# "%m-%d-%y_%H-%M-%S" +# ) +# image_file_name = "git-sim-" + args.subcommand + "_" + t + ".jpg" +# image_file_path = os.path.join( +# os.path.join(config.media_dir, "images"), image_file_name +# ) +# cv2.imwrite(image_file_path, image) + +# if not args.disable_auto_open: +# try: +# if not args.animate: +# open_media_file(image_file_path) +# else: +# open_media_file(scene.renderer.file_writer.movie_file_path) +# except FileNotFoundError: +# print( +# "Error automatically opening media, please manually open the image or video file to view." +# ) + + +# if __name__ == "__main__": +# main() diff --git a/git_sim/animations.py b/git_sim/animations.py new file mode 100644 index 0000000..96d4a22 --- /dev/null +++ b/git_sim/animations.py @@ -0,0 +1,47 @@ +import datetime +import os +import pathlib +import time + +import cv2 +from manim import Scene +from manim import config as manim_config +from manim.utils.file_ops import open_file + +from git_sim.settings import Settings + + +def handle_animations(scene: Scene) -> None: + + manim_config.media_dir = os.path.join( + os.path.expanduser(pathlib.Path()), "git-sim_media" + ) + manim_config.verbosity = "ERROR" + + scene.render() + + if not Settings.animate: + video = cv2.VideoCapture(str(scene.renderer.file_writer.movie_file_path)) + success, image = video.read() + if success: + t = datetime.datetime.fromtimestamp(time.time()).strftime( + "%m-%d-%y_%H-%M-%S" + ) + image_file_name = "git-sim-log_" + t + ".jpg" + image_file_path = os.path.join( + os.path.join(manim_config.media_dir, "images"), image_file_name + ) + cv2.imwrite(image_file_path, image) + else: + raise + + if Settings.auto_open: + try: + if Settings.animate: + open_file(scene.renderer.file_writer.movie_file_path) + else: + open_file(image_file_path) + except FileNotFoundError: + print( + "Error automatically opening media, please manually open the image or video file to view." + ) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index 22bbda1..99ee5d7 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -1,20 +1,22 @@ import platform import sys -from argparse import Namespace import git import manim as m import numpy +from git.exc import GitCommandError, InvalidGitRepositoryError +from git.repo import Repo + +from git_sim.settings import Settings class GitSimBaseCommand(m.MovingCameraScene): - def __init__(self, args: Namespace): + def __init__(self): super().__init__() self.init_repo() - self.args = args - self.fontColor = m.BLACK if self.args.light_mode else m.WHITE + self.fontColor = m.BLACK if Settings.light_mode else m.WHITE self.drawnCommits = {} self.drawnRefs = {} self.drawnCommitIds = {} @@ -24,28 +26,25 @@ def __init__(self, args: Namespace): self.trimmed = False self.prevRef = None self.topref = None - self.maxrefs = None self.i = 0 - self.numCommits = 5 - self.defaultNumCommits = 5 + self.numCommits = Settings.commits + self.defaultNumCommits = Settings.commits self.selected_branches = [] - self.hide_first_tag = False self.stop = False self.zone_title_offset = 2.6 if platform.system() == "Windows" else 2.6 - self.allow_no_commits = False - self.logo = m.ImageMobject(self.args.logo) + self.logo = m.ImageMobject(Settings.logo) self.logo.width = 3 def init_repo(self): try: - self.repo = git.Repo(search_parent_directories=True) - except git.exc.InvalidGitRepositoryError: + self.repo = Repo(search_parent_directories=True) + except InvalidGitRepositoryError: print("git-sim error: No Git repository found at current path.") sys.exit(1) def execute(self): - print("Simulating: git " + self.args.subcommand) + print("Simulating: git " + Settings.subcommand) self.show_intro() self.get_commits() self.fadeout() @@ -53,7 +52,7 @@ def execute(self): def get_commits(self, start="HEAD"): if not self.numCommits: - if self.allow_no_commits: + if Settings.allow_no_commits: self.numCommits = self.defaultNumCommits self.commits = ["dark"] * 5 self.zone_title_offset = 2 @@ -78,7 +77,7 @@ def get_commits(self, start="HEAD"): self.commits.append(self.create_dark_commit()) self.numCommits = self.defaultNumCommits - except git.exc.GitCommandError: + except GitCommandError: self.numCommits -= 1 self.get_commits(start=start) @@ -110,11 +109,11 @@ def parse_commits( self.i = 0 def show_intro(self): - if self.args.animate and self.args.show_intro: + if Settings.animate and Settings.show_intro: self.add(self.logo) initialCommitText = m.Text( - self.args.title, + Settings.title, font="Monospace", font_size=36, color=self.fontColor, @@ -136,14 +135,14 @@ def show_intro(self): self.camera.frame.save_state() def show_outro(self): - if self.args.animate and self.args.show_outro: + if Settings.animate and Settings.show_outro: self.play(m.Restore(self.camera.frame)) self.play(self.logo.animate.scale(4).set_x(0).set_y(0)) outroTopText = m.Text( - self.args.outro_top_text, + Settings.outro_top_text, font="Monospace", font_size=36, color=self.fontColor, @@ -151,7 +150,7 @@ def show_outro(self): self.play(m.AddTextLetterByLetter(outroTopText)) outroBottomText = m.Text( - self.args.outro_bottom_text, + Settings.outro_bottom_text, font="Monospace", font_size=36, color=self.fontColor, @@ -161,9 +160,9 @@ def show_outro(self): self.wait(3) def fadeout(self): - if self.args.animate: + if Settings.animate: self.wait(3) - self.play(m.FadeOut(self.toFadeOut), run_time=1 / self.args.speed) + self.play(m.FadeOut(self.toFadeOut), run_time=1 / Settings.speed) else: self.wait(0.1) @@ -177,7 +176,7 @@ def draw_commit( self, commit, prevCircle, shift=numpy.array([0.0, 0.0, 0.0]), dots=False ): if commit == "dark": - commitFill = m.WHITE if self.args.light_mode else m.BLACK + commitFill = m.WHITE if Settings.light_mode else m.BLACK elif len(commit.parents) <= 1: commitFill = m.RED else: @@ -193,19 +192,19 @@ def draw_commit( if prevCircle: circle.next_to( - prevCircle, m.RIGHT if self.args.reverse else m.LEFT, buff=1.5 + prevCircle, m.RIGHT if Settings.reverse else m.LEFT, buff=1.5 ) start = ( prevCircle.get_center() if prevCircle - else (m.LEFT if self.args.reverse else m.RIGHT) + else (m.LEFT if Settings.reverse else m.RIGHT) ) end = circle.get_center() if commit == "dark": arrow = m.Arrow( - start, end, color=m.WHITE if self.args.light_mode else m.BLACK + start, end, color=m.WHITE if Settings.light_mode else m.BLACK ) elif commit.hexsha in self.drawnCommits: end = self.drawnCommits[commit.hexsha].get_center() @@ -234,13 +233,13 @@ def draw_commit( color=self.fontColor, ).next_to(circle, m.DOWN) - if self.args.animate and commit != "dark" and not self.stop: + if Settings.animate and commit != "dark" and not self.stop: self.play( self.camera.frame.animate.move_to(circle.get_center()), m.Create(circle), m.AddTextLetterByLetter(commitId), m.AddTextLetterByLetter(message), - run_time=1 / self.args.speed, + run_time=1 / Settings.speed, ) elif not self.stop: self.add(circle, commitId, message) @@ -291,8 +290,8 @@ def draw_head(self, commit, commitId): head = m.VGroup(headbox, headText) - if self.args.animate: - self.play(m.Create(head), run_time=1 / self.args.speed) + if Settings.animate: + self.play(m.Create(head), run_time=1 / Settings.speed) else: self.add(head) @@ -343,8 +342,8 @@ def draw_branch(self, commit): self.prevRef = fullbranch - if self.args.animate: - self.play(m.Create(fullbranch), run_time=1 / self.args.speed) + if Settings.animate: + self.play(m.Create(fullbranch), run_time=1 / Settings.speed) else: self.add(fullbranch) @@ -355,13 +354,13 @@ def draw_branch(self, commit): self.topref = self.prevRef x += 1 - if x >= self.args.max_branches_per_commit: + if x >= Settings.max_branches_per_commit: return def draw_tag(self, commit): x = 0 - if self.hide_first_tag and self.i == 0: + if Settings.hide_first_tag and self.i == 0: return for tag in self.repo.tags: @@ -387,11 +386,11 @@ def draw_tag(self, commit): self.prevRef = tagRec - if self.args.animate: + if Settings.animate: self.play( m.Create(tagRec), m.Create(tagText), - run_time=1 / self.args.speed, + run_time=1 / Settings.speed, ) else: self.add(tagRec, tagText) @@ -402,43 +401,43 @@ def draw_tag(self, commit): self.topref = self.prevRef x += 1 - if x >= self.args.max_tags_per_commit: + if x >= Settings.max_tags_per_commit: return except ValueError: pass def draw_arrow(self, prevCircle, arrow): if prevCircle: - if self.args.animate: - self.play(m.Create(arrow), run_time=1 / self.args.speed) + if Settings.animate: + self.play(m.Create(arrow), run_time=1 / Settings.speed) else: self.add(arrow) self.toFadeOut.add(arrow) def recenter_frame(self): - if self.args.animate: + if Settings.animate: self.play( self.camera.frame.animate.move_to(self.toFadeOut.get_center()), - run_time=1 / self.args.speed, + run_time=1 / Settings.speed, ) else: self.camera.frame.move_to(self.toFadeOut.get_center()) def scale_frame(self): - if self.args.animate: + if Settings.animate: self.play( self.camera.frame.animate.scale_to_fit_width( self.toFadeOut.get_width() * 1.1 ), - run_time=1 / self.args.speed, + run_time=1 / Settings.speed, ) if self.toFadeOut.get_height() >= self.camera.frame.get_height(): self.play( self.camera.frame.animate.scale_to_fit_height( self.toFadeOut.get_height() * 1.25 ), - run_time=1 / self.args.speed, + run_time=1 / Settings.speed, ) else: self.camera.frame.scale_to_fit_width(self.toFadeOut.get_width() * 1.1) @@ -448,7 +447,7 @@ def scale_frame(self): ) def vsplit_frame(self): - if self.args.animate: + if Settings.animate: self.play( self.camera.frame.animate.scale_to_fit_height( self.camera.frame.get_height() * 2 @@ -458,7 +457,7 @@ def vsplit_frame(self): self.camera.frame.scale_to_fit_height(self.camera.frame.get_height() * 2) try: - if self.args.animate: + if Settings.animate: self.play( self.toFadeOut.animate.align_to(self.camera.frame, m.UP).shift( m.DOWN * 0.75 @@ -570,7 +569,7 @@ def setup_and_draw_zones( thirdColumnTitle, ) - if self.args.animate: + if Settings.animate: self.play( m.Create(horizontal), m.Create(horizontal2), @@ -663,19 +662,19 @@ def setup_and_draw_zones( thirdColumnFilesDict[f] = text if len(firstColumnFiles): - if self.args.animate: + if Settings.animate: self.play(*[m.AddTextLetterByLetter(d) for d in firstColumnFiles]) else: self.add(*[d for d in firstColumnFiles]) if len(secondColumnFiles): - if self.args.animate: + if Settings.animate: self.play(*[m.AddTextLetterByLetter(w) for w in secondColumnFiles]) else: self.add(*[w for w in secondColumnFiles]) if len(thirdColumnFiles): - if self.args.animate: + if Settings.animate: self.play(*[m.AddTextLetterByLetter(s) for s in thirdColumnFiles]) else: self.add(*[s for s in thirdColumnFiles]) @@ -707,7 +706,7 @@ def setup_and_draw_zones( 0, ), ) - if self.args.animate: + if Settings.animate: self.play(m.Create(firstColumnArrowMap[filename])) else: self.add(firstColumnArrowMap[filename]) @@ -726,7 +725,7 @@ def setup_and_draw_zones( 0, ), ) - if self.args.animate: + if Settings.animate: self.play(m.Create(secondColumnArrowMap[filename])) else: self.add(secondColumnArrowMap[filename]) @@ -761,7 +760,7 @@ def populate_zones( firstColumnFileNames.add(z) def center_frame_on_commit(self, commit): - if self.args.animate: + if Settings.animate: self.play( self.camera.frame.animate.move_to( self.drawnCommits[commit.hexsha].get_center() @@ -771,7 +770,7 @@ def center_frame_on_commit(self, commit): self.camera.frame.move_to(self.drawnCommits[commit.hexsha].get_center()) def reset_head_branch(self, hexsha, shift=numpy.array([0.0, 0.0, 0.0])): - if self.args.animate: + if Settings.animate: self.play( self.drawnRefs["HEAD"].animate.move_to( ( @@ -805,7 +804,7 @@ def reset_head_branch(self, hexsha, shift=numpy.array([0.0, 0.0, 0.0])): ) def translate_frame(self, shift): - if self.args.animate: + if Settings.animate: self.play(self.camera.frame.animate.shift(shift)) else: self.camera.frame.shift(shift) @@ -822,7 +821,7 @@ def setup_and_draw_parent( circle.height = 1 circle.next_to( self.drawnCommits[child.hexsha], - m.LEFT if self.args.reverse else m.RIGHT, + m.LEFT if Settings.reverse else m.RIGHT, buff=1.5, ) circle.shift(shift) @@ -849,13 +848,13 @@ def setup_and_draw_parent( ).next_to(circle, m.DOWN) self.toFadeOut.add(message) - if self.args.animate: + if Settings.animate: self.play( self.camera.frame.animate.move_to(circle.get_center()), m.Create(circle), m.AddTextLetterByLetter(commitId), m.AddTextLetterByLetter(message), - run_time=1 / self.args.speed, + run_time=1 / Settings.speed, ) else: self.camera.frame.move_to(circle.get_center()) @@ -865,8 +864,8 @@ def setup_and_draw_parent( self.toFadeOut.add(circle) if draw_arrow: - if self.args.animate: - self.play(m.Create(arrow), run_time=1 / self.args.speed) + if Settings.animate: + self.play(m.Create(arrow), run_time=1 / Settings.speed) else: self.add(arrow) self.toFadeOut.add(arrow) @@ -906,8 +905,8 @@ def draw_ref(self, commit, top, text="HEAD", color=m.BLUE): ref = m.VGroup(refbox, refText) - if self.args.animate: - self.play(m.Create(ref), run_time=1 / self.args.speed) + if Settings.animate: + self.play(m.Create(ref), run_time=1 / Settings.speed) else: self.add(ref) @@ -920,8 +919,8 @@ def draw_ref(self, commit, top, text="HEAD", color=m.BLUE): def draw_dark_ref(self): refRec = m.Rectangle( - color=m.WHITE if self.args.light_mode else m.BLACK, - fill_color=m.WHITE if self.args.light_mode else m.BLACK, + color=m.WHITE if Settings.light_mode else m.BLACK, + fill_color=m.WHITE if Settings.light_mode else m.BLACK, height=0.4, width=1, ) diff --git a/git_sim/log.py b/git_sim/log.py new file mode 100644 index 0000000..3563c71 --- /dev/null +++ b/git_sim/log.py @@ -0,0 +1,39 @@ +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 Log(GitSimBaseCommand): + def __init__(self): + super().__init__() + try: + self.selected_branches.append(self.repo.active_branch.name) + except TypeError: + pass + + def construct(self): + print(Settings.INFO_STRING + "log") + self.show_intro() + self.get_commits() + self.parse_commits(self.commits[0]) + self.recenter_frame() + self.scale_frame() + self.fadeout() + self.show_outro() + + +def log( + commits: int = typer.Option( + default=Settings.commits, + help="The number of commits to display in the simulated log output", + min=1, + max=12, + ) +): + # Write the command options back into the shared Config. + Settings.commits = commits + # + scene = Log() + handle_animations(scene=scene) diff --git a/git_sim/settings.py b/git_sim/settings.py new file mode 100644 index 0000000..fa93bec --- /dev/null +++ b/git_sim/settings.py @@ -0,0 +1,25 @@ +import pathlib +from dataclasses import dataclass + + +@dataclass +class Settings: + commits = 5 + logo = pathlib.Path(__file__).parent.resolve() / "logo.png" + subcommand = "log" + show_intro = False + show_outro = False + animate = False + title = "Git Sim, by initialcommit.com" + outro_top_text = "Thanks for using Initial Commit!" + outro_bottom_text = "Learn more at initialcommit.com" + speed = 1.5 + light_mode = False + reverse = False + max_branches_per_commit = 1 + max_tags_per_commit = 1 + hide_first_tag = False + allow_no_commits = False + low_quality = False + auto_open = True + INFO_STRING = "Simulating: git " diff --git a/git_sim/status.py b/git_sim/status.py new file mode 100644 index 0000000..58023b0 --- /dev/null +++ b/git_sim/status.py @@ -0,0 +1,33 @@ +from git_sim.animations import handle_animations +from git_sim.git_sim_base_command import GitSimBaseCommand +from git_sim.settings import Settings + + +class Status(GitSimBaseCommand): + def __init__(self): + super().__init__() + + try: + self.selected_branches.append(self.repo.active_branch.name) + except TypeError: + pass + + def construct(self): + self.show_intro() + self.get_commits() + self.parse_commits(self.commits[0]) + self.recenter_frame() + self.scale_frame() + self.vsplit_frame() + self.setup_and_draw_zones() + self.fadeout() + self.show_outro() + + +def status(): + # Write required settings into the shared Config. + Settings.hide_first_tag = True + Settings.allow_no_commits = True + + scene = Status() + handle_animations(scene=scene) diff --git a/setup.py b/setup.py index 74d6f9d..05fb3d5 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ }, entry_points={ "console_scripts": [ - "git-sim=git_sim.__main__:main", + "git-sim=git_sim.__main__:app", ], }, include_package_data=True, From 2d549924750e28e7abdcb08187979342fdd31ebd Mon Sep 17 00:00:00 2001 From: Manuel Stausberg Date: Wed, 1 Feb 2023 21:01:06 +0100 Subject: [PATCH 02/27] make each command a separate typer app this will simplify addition of subcommands later on. --- git_sim/__main__.py | 8 ++++---- git_sim/log.py | 10 +++++++++- git_sim/status.py | 12 +++++++++++- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 367d364..f137625 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -8,14 +8,14 @@ import typer -from git_sim.log import log +import git_sim.log +import git_sim.status from git_sim.settings import Settings -from git_sim.status import status app = typer.Typer() -app.command()(log) -app.command()(status) +app.add_typer(git_sim.log.app) +app.add_typer(git_sim.status.app) @app.callback() def main( diff --git a/git_sim/log.py b/git_sim/log.py index 3563c71..45589f7 100644 --- a/git_sim/log.py +++ b/git_sim/log.py @@ -4,6 +4,8 @@ from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import Settings +app = typer.Typer() + class Log(GitSimBaseCommand): def __init__(self): @@ -24,14 +26,20 @@ def construct(self): self.show_outro() +@app.callback(invoke_without_command=True) def log( + ctx: typer.Context, commits: int = typer.Option( default=Settings.commits, help="The number of commits to display in the simulated log output", min=1, max=12, - ) + ), ): + # return early if a subcommand is executed + if ctx.invoked_subcommand is not None: + return + # Write the command options back into the shared Config. Settings.commits = commits # diff --git a/git_sim/status.py b/git_sim/status.py index 58023b0..bb8f64c 100644 --- a/git_sim/status.py +++ b/git_sim/status.py @@ -1,7 +1,11 @@ +import typer + from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import Settings +app = typer.Typer() + class Status(GitSimBaseCommand): def __init__(self): @@ -24,7 +28,13 @@ def construct(self): self.show_outro() -def status(): +@app.callback(invoke_without_command=True) +def status( + ctx: typer.Context, +): + # return early if a subcommand is executed + if ctx.invoked_subcommand is not None: + return # Write required settings into the shared Config. Settings.hide_first_tag = True Settings.allow_no_commits = True From 3abea68f07830ba86febac010ef3eed077f11493 Mon Sep 17 00:00:00 2001 From: Manuel Stausberg Date: Wed, 1 Feb 2023 21:06:56 +0100 Subject: [PATCH 03/27] remove implemented options from TODO comment --- git_sim/__main__.py | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/git_sim/__main__.py b/git_sim/__main__.py index f137625..2072bd4 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -81,30 +81,12 @@ def main( # "git-sim", formatter_class=argparse.ArgumentDefaultsHelpFormatter # ) # parser.add_argument( -# "--title", -# help="Custom title to display at the beginning of the animation", -# type=str, -# default="Git Sim, by initialcommit.com", -# ) -# parser.add_argument( # "--logo", # help="The path to a custom logo to use in the animation intro/outro", # type=str, # default=os.path.join(str(pathlib.Path(__file__).parent.resolve()), "logo.png"), # ) # parser.add_argument( -# "--outro-top-text", -# help="Custom text to display above the logo during the outro", -# type=str, -# default="Thanks for using Initial Commit!", -# ) -# parser.add_argument( -# "--outro-bottom-text", -# help="Custom text to display below the logo during the outro", -# type=str, -# default="Learn more at initialcommit.com", -# ) -# parser.add_argument( # "--show-intro", # help="Add an intro sequence with custom logo and title", # action="store_true", @@ -121,27 +103,12 @@ def main( # default=".", # ) # parser.add_argument( -# "--low-quality", -# help="Render output video in low quality, useful for faster testing", -# action="store_true", -# ) -# parser.add_argument( -# "--light-mode", -# help="Enable light-mode with white background", -# action="store_true", -# ) -# parser.add_argument( # "--speed", # help="A multiple of the standard 1x animation speed (ex: 2 = twice as fast, 0.5 = half as fast)", # type=float, # default=1.5, # ) # parser.add_argument( -# "--animate", -# help="Animate the simulation and output as an mp4 video", -# action="store_true", -# ) -# parser.add_argument( # "--max-branches-per-commit", # help="Maximum number of branch labels to display for each commit", # type=int, @@ -154,12 +121,6 @@ def main( # default=1, # ) # parser.add_argument( -# "-d", -# "--disable-auto-open", -# help="Disable the automatic opening of the image/video file after generation", -# action="store_true", -# ) -# parser.add_argument( # "-r", # "--reverse", # help="Display commit history in the reverse direction", From c2c595e8eb6185ce727b850176576e1a28aecc92 Mon Sep 17 00:00:00 2001 From: Manuel Stausberg Date: Fri, 3 Feb 2023 21:33:48 +0100 Subject: [PATCH 04/27] add logo, show_intro, show_outro, media_dir --- git_sim/__main__.py | 60 +++++++++++++++++---------------------------- git_sim/settings.py | 3 +++ 2 files changed, 25 insertions(+), 38 deletions(-) diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 2072bd4..e90eb94 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -17,6 +17,7 @@ app.add_typer(git_sim.log.app) app.add_typer(git_sim.status.app) + @app.callback() def main( title: str = typer.Option( @@ -46,6 +47,22 @@ def main( light_mode: bool = typer.Option( default=Settings.light_mode, help="Enable light-mode with white background" ), + logo: pathlib.Path = typer.Option( + default=Settings.logo, + help="The path to a custom logo to use in the animation intro/outro", + ), + show_intro: bool = typer.Option( + default=Settings.show_intro, + help="Add an intro sequence with custom logo and title", + ), + show_outtro: bool = typer.Option( + default=Settings.show_outro, + help="Add an outro sequence with custom logo and text", + ), + media_dir: pathlib.Path = typer.Option( + default=Settings.media_dir, + help="The path to output the animation data and video file", + ), ): Settings.animate = animate Settings.title = title @@ -54,54 +71,21 @@ def main( Settings.low_quality = low_quality Settings.auto_open = auto_open Settings.light_mode = light_mode + Settings.logo = logo + Settings.show_intro = show_intro + Settings.show_outro = show_outtro + Settings.media_dir = media_dir if __name__ == "__main__": app() -# import argparse -# import datetime -# import os -# import pathlib -# import sys -# import time - -# import cv2 -# import git -# from manim import WHITE, config -# from manim.utils.file_ops import open_file as open_media_file - -# import git_sim.git_sim as gs -# from git_sim.git_sim_log import GitSimLog - - # def main(): # parser = argparse.ArgumentParser( # "git-sim", formatter_class=argparse.ArgumentDefaultsHelpFormatter # ) -# parser.add_argument( -# "--logo", -# help="The path to a custom logo to use in the animation intro/outro", -# type=str, -# default=os.path.join(str(pathlib.Path(__file__).parent.resolve()), "logo.png"), -# ) -# parser.add_argument( -# "--show-intro", -# help="Add an intro sequence with custom logo and title", -# action="store_true", -# ) -# parser.add_argument( -# "--show-outro", -# help="Add an outro sequence with custom logo and text", -# action="store_true", -# ) -# parser.add_argument( -# "--media-dir", -# help="The path to output the animation data and video file", -# type=str, -# default=".", -# ) + # parser.add_argument( # "--speed", # help="A multiple of the standard 1x animation speed (ex: 2 = twice as fast, 0.5 = half as fast)", diff --git a/git_sim/settings.py b/git_sim/settings.py index fa93bec..eec8087 100644 --- a/git_sim/settings.py +++ b/git_sim/settings.py @@ -23,3 +23,6 @@ class Settings: low_quality = False auto_open = True INFO_STRING = "Simulating: git " + # os.path.join(str(pathlib.Path(__file__).parent.resolve()), "logo.png") + logo = pathlib.Path(__file__).parent.resolve() / "logo.png" + media_dir = pathlib.Path().cwd() From 0794f68a38c0ab8a7c69c7a657e21a6211dbaeee Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 7 Feb 2023 05:00:00 +0100 Subject: [PATCH 05/27] finish top-level options --- git_sim/__main__.py | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/git_sim/__main__.py b/git_sim/__main__.py index e90eb94..1db78d1 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -63,6 +63,22 @@ def main( default=Settings.media_dir, help="The path to output the animation data and video file", ), + speed: float = typer.Option( + default=Settings.speed, + help="A multiple of the standard 1x animation speed (ex: 2 = twice as fast, 0.5 = half as fast)", + ), + max_branches_per_commit: int = typer.Option( + default=Settings.max_branches_per_commit, + help="Maximum number of branch labels to display for each commit", + ), + max_tags_per_commit: int = typer.Option( + default=Settings.max_tags_per_commit, + help="Maximum number of tags to display for each commit", + ), + reverse: bool = typer.Option( + default=Settings.reverse, + help="Display commit history in the reverse direction", + ), ): Settings.animate = animate Settings.title = title @@ -75,6 +91,10 @@ def main( Settings.show_intro = show_intro Settings.show_outro = show_outtro Settings.media_dir = media_dir + Settings.speed = speed + Settings.max_branches_per_commit = max_branches_per_commit + Settings.max_tags_per_commit = max_tags_per_commit + Settings.reverse = reverse if __name__ == "__main__": @@ -86,30 +106,6 @@ def main( # "git-sim", formatter_class=argparse.ArgumentDefaultsHelpFormatter # ) -# parser.add_argument( -# "--speed", -# help="A multiple of the standard 1x animation speed (ex: 2 = twice as fast, 0.5 = half as fast)", -# type=float, -# default=1.5, -# ) -# parser.add_argument( -# "--max-branches-per-commit", -# help="Maximum number of branch labels to display for each commit", -# type=int, -# default=1, -# ) -# parser.add_argument( -# "--max-tags-per-commit", -# help="Maximum number of tags to display for each commit", -# type=int, -# default=1, -# ) -# parser.add_argument( -# "-r", -# "--reverse", -# help="Display commit history in the reverse direction", -# action="store_true", -# ) # subparsers = parser.add_subparsers(dest="subcommand", help="subcommand help") From d6a06af956eddeb54b7554172f6e1b900a4522a6 Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 7 Feb 2023 05:34:07 +0100 Subject: [PATCH 06/27] make log a command instead of a typr app --- git_sim/__main__.py | 5 ++--- git_sim/log.py | 13 ++----------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 1db78d1..41439ab 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -13,9 +13,8 @@ from git_sim.settings import Settings app = typer.Typer() - -app.add_typer(git_sim.log.app) -app.add_typer(git_sim.status.app) +app.command()(git_sim.log.log) +# app.command()(git_sim.status.status) @app.callback() diff --git a/git_sim/log.py b/git_sim/log.py index 45589f7..3323bf7 100644 --- a/git_sim/log.py +++ b/git_sim/log.py @@ -4,8 +4,6 @@ from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import Settings -app = typer.Typer() - class Log(GitSimBaseCommand): def __init__(self): @@ -16,7 +14,7 @@ def __init__(self): pass def construct(self): - print(Settings.INFO_STRING + "log") + print(Settings.INFO_STRING + type(self).__name__) self.show_intro() self.get_commits() self.parse_commits(self.commits[0]) @@ -26,9 +24,7 @@ def construct(self): self.show_outro() -@app.callback(invoke_without_command=True) def log( - ctx: typer.Context, commits: int = typer.Option( default=Settings.commits, help="The number of commits to display in the simulated log output", @@ -36,12 +32,7 @@ def log( max=12, ), ): - # return early if a subcommand is executed - if ctx.invoked_subcommand is not None: - return + Settings.commits = commits + 1 - # Write the command options back into the shared Config. - Settings.commits = commits - # scene = Log() handle_animations(scene=scene) From ed313b02c91ccb05b506732178e03edea0078e61 Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 7 Feb 2023 05:44:07 +0100 Subject: [PATCH 07/27] move print statement into GitSimBaseCommand --- git_sim/git_sim_base_command.py | 7 ++----- git_sim/log.py | 1 - 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index 99ee5d7..a554e4c 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -12,7 +12,6 @@ class GitSimBaseCommand(m.MovingCameraScene): def __init__(self): - super().__init__() self.init_repo() @@ -36,6 +35,8 @@ def __init__(self): self.logo = m.ImageMobject(Settings.logo) self.logo.width = 3 + print(f"Simulating: git {type(self).__name__.lower()}") + def init_repo(self): try: self.repo = Repo(search_parent_directories=True) @@ -44,7 +45,6 @@ def init_repo(self): sys.exit(1) def execute(self): - print("Simulating: git " + Settings.subcommand) self.show_intro() self.get_commits() self.fadeout() @@ -136,7 +136,6 @@ def show_intro(self): def show_outro(self): if Settings.animate and Settings.show_outro: - self.play(m.Restore(self.camera.frame)) self.play(self.logo.animate.scale(4).set_x(0).set_y(0)) @@ -364,7 +363,6 @@ def draw_tag(self, commit): return for tag in self.repo.tags: - try: if commit.hexsha == tag.commit.hexsha: tagText = m.Text( @@ -741,7 +739,6 @@ def populate_zones( firstColumnArrowMap={}, secondColumnArrowMap={}, ): - for x in self.repo.index.diff(None): if "git-sim_media" not in x.a_path: secondColumnFileNames.add(x.a_path) diff --git a/git_sim/log.py b/git_sim/log.py index 3323bf7..a9af80b 100644 --- a/git_sim/log.py +++ b/git_sim/log.py @@ -14,7 +14,6 @@ def __init__(self): pass def construct(self): - print(Settings.INFO_STRING + type(self).__name__) self.show_intro() self.get_commits() self.parse_commits(self.commits[0]) From 7ed7a0b41060dbf797eb4afbb6a1640ea0315126 Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 7 Feb 2023 05:44:42 +0100 Subject: [PATCH 08/27] remove dupplicate setting "logo" --- git_sim/settings.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/git_sim/settings.py b/git_sim/settings.py index eec8087..4c33cec 100644 --- a/git_sim/settings.py +++ b/git_sim/settings.py @@ -5,8 +5,7 @@ @dataclass class Settings: commits = 5 - logo = pathlib.Path(__file__).parent.resolve() / "logo.png" - subcommand = "log" + subcommand: str show_intro = False show_outro = False animate = False From c9cb2967af1d5335805560a9ff19c0b6affe2cc9 Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 7 Feb 2023 05:54:01 +0100 Subject: [PATCH 09/27] make status a command instead of a typr app --- git_sim/__main__.py | 2 +- git_sim/status.py | 14 +------------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 41439ab..237492d 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -14,7 +14,7 @@ app = typer.Typer() app.command()(git_sim.log.log) -# app.command()(git_sim.status.status) +app.command()(git_sim.status.status) @app.callback() diff --git a/git_sim/status.py b/git_sim/status.py index bb8f64c..6d379a4 100644 --- a/git_sim/status.py +++ b/git_sim/status.py @@ -1,16 +1,11 @@ -import typer - from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import Settings -app = typer.Typer() - class Status(GitSimBaseCommand): def __init__(self): super().__init__() - try: self.selected_branches.append(self.repo.active_branch.name) except TypeError: @@ -28,14 +23,7 @@ def construct(self): self.show_outro() -@app.callback(invoke_without_command=True) -def status( - ctx: typer.Context, -): - # return early if a subcommand is executed - if ctx.invoked_subcommand is not None: - return - # Write required settings into the shared Config. +def status(): Settings.hide_first_tag = True Settings.allow_no_commits = True From 8c4c940e715562d341a35200949b8c02ed402a18 Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 7 Feb 2023 06:12:52 +0100 Subject: [PATCH 10/27] Revert "move print statement into GitSimBaseCommand" This reverts commit 513fcc65dd99c3c2d5c172d5618806cfe8c6e8b4. --- git_sim/git_sim_base_command.py | 7 +++++-- git_sim/log.py | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index a554e4c..99ee5d7 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -12,6 +12,7 @@ class GitSimBaseCommand(m.MovingCameraScene): def __init__(self): + super().__init__() self.init_repo() @@ -35,8 +36,6 @@ def __init__(self): self.logo = m.ImageMobject(Settings.logo) self.logo.width = 3 - print(f"Simulating: git {type(self).__name__.lower()}") - def init_repo(self): try: self.repo = Repo(search_parent_directories=True) @@ -45,6 +44,7 @@ def init_repo(self): sys.exit(1) def execute(self): + print("Simulating: git " + Settings.subcommand) self.show_intro() self.get_commits() self.fadeout() @@ -136,6 +136,7 @@ def show_intro(self): def show_outro(self): if Settings.animate and Settings.show_outro: + self.play(m.Restore(self.camera.frame)) self.play(self.logo.animate.scale(4).set_x(0).set_y(0)) @@ -363,6 +364,7 @@ def draw_tag(self, commit): return for tag in self.repo.tags: + try: if commit.hexsha == tag.commit.hexsha: tagText = m.Text( @@ -739,6 +741,7 @@ def populate_zones( firstColumnArrowMap={}, secondColumnArrowMap={}, ): + for x in self.repo.index.diff(None): if "git-sim_media" not in x.a_path: secondColumnFileNames.add(x.a_path) diff --git a/git_sim/log.py b/git_sim/log.py index a9af80b..3323bf7 100644 --- a/git_sim/log.py +++ b/git_sim/log.py @@ -14,6 +14,7 @@ def __init__(self): pass def construct(self): + print(Settings.INFO_STRING + type(self).__name__) self.show_intro() self.get_commits() self.parse_commits(self.commits[0]) From 295865a78e7b5a1bea02879e38c002c56ec346a1 Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 7 Feb 2023 07:15:49 +0100 Subject: [PATCH 11/27] add "add" --- git_sim/__main__.py | 20 +--------- git_sim/add.py | 89 +++++++++++++++++++++++++++++++++++++++++++++ git_sim/settings.py | 2 + 3 files changed, 93 insertions(+), 18 deletions(-) create mode 100644 git_sim/add.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 237492d..6f3279e 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -8,6 +8,7 @@ import typer +import git_sim.add import git_sim.log import git_sim.status from git_sim.settings import Settings @@ -15,6 +16,7 @@ app = typer.Typer() app.command()(git_sim.log.log) app.command()(git_sim.status.status) +app.command()(git_sim.add.add) @app.callback() @@ -108,24 +110,6 @@ def main( # subparsers = parser.add_subparsers(dest="subcommand", help="subcommand help") -# log = subparsers.add_parser("log", help="log -h") -# log.add_argument( -# "--commits", -# help="The number of commits to display in the simulated log output", -# type=int, -# default=5, -# choices=range(1, 13), -# ) - -# status = subparsers.add_parser("status", help="status -h") - -# add = subparsers.add_parser("add", help="add -h") -# add.add_argument( -# "name", -# nargs="+", -# help="The names of one or more files to add to Git's staging area", -# type=str, -# ) # restore = subparsers.add_parser("restore", help="restore -h") # restore.add_argument( diff --git a/git_sim/add.py b/git_sim/add.py new file mode 100644 index 0000000..781c1a5 --- /dev/null +++ b/git_sim/add.py @@ -0,0 +1,89 @@ +import sys + +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 + + +class Add(GitSimBaseCommand): + def __init__(self, files: list[str]): + super().__init__() + self.maxrefs = 2 + self.hide_first_tag = True + self.allow_no_commits = True + self.files = files + + try: + self.selected_branches.append(self.repo.active_branch.name) + except TypeError: + pass + + for file in self.files: + if file not in [x.a_path for x in self.repo.index.diff(None)] + [ + z for z in self.repo.untracked_files + ]: + print(f"git-sim error: No modified file with name: '{file}'") + sys.exit() + + def construct(self): + print(Settings.INFO_STRING + "add " + " ".join(self.files)) + + self.show_intro() + self.get_commits() + self.parse_commits(self.commits[0]) + self.recenter_frame() + self.scale_frame() + self.vsplit_frame() + self.setup_and_draw_zones() + self.fadeout() + self.show_outro() + + def populate_zones( + self, + firstColumnFileNames, + secondColumnFileNames, + thirdColumnFileNames, + firstColumnArrowMap, + secondColumnArrowMap, + ): + for x in self.repo.index.diff(None): + if "git-sim_media" not in x.a_path: + secondColumnFileNames.add(x.a_path) + for file in self.files: + if file == x.a_path: + thirdColumnFileNames.add(x.a_path) + secondColumnArrowMap[x.a_path] = m.Arrow( + stroke_width=3, color=self.fontColor + ) + try: + for y in self.repo.index.diff("HEAD"): + if "git-sim_media" not in y.a_path: + thirdColumnFileNames.add(y.a_path) + except git.exc.BadName: + for (y, _stage), entry in self.repo.index.entries.items(): + if "git-sim_media" not in y: + thirdColumnFileNames.add(y) + + for z in self.repo.untracked_files: + if "git-sim_media" not in z: + firstColumnFileNames.add(z) + for file in self.files: + if file == z: + thirdColumnFileNames.add(z) + 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", + ) +): + scene = Add(files=files) + handle_animations(scene=scene) diff --git a/git_sim/settings.py b/git_sim/settings.py index 4c33cec..23496dd 100644 --- a/git_sim/settings.py +++ b/git_sim/settings.py @@ -1,5 +1,6 @@ import pathlib from dataclasses import dataclass +from typing import Optional @dataclass @@ -25,3 +26,4 @@ class Settings: # os.path.join(str(pathlib.Path(__file__).parent.resolve()), "logo.png") logo = pathlib.Path(__file__).parent.resolve() / "logo.png" media_dir = pathlib.Path().cwd() + files: list[pathlib.Path] | None = None From e4c7ce0b913d8f61b7cdfda230d0fa0d6b4acb3c Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 7 Feb 2023 07:46:50 +0100 Subject: [PATCH 12/27] add "restore" --- git_sim/__main__.py | 7 ++-- git_sim/restore.py | 79 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 git_sim/restore.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 6f3279e..6c485f1 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -10,6 +10,7 @@ import git_sim.add import git_sim.log +import git_sim.restore import git_sim.status from git_sim.settings import Settings @@ -17,6 +18,7 @@ app.command()(git_sim.log.log) app.command()(git_sim.status.status) app.command()(git_sim.add.add) +app.command()(git_sim.restore.restore) @app.callback() @@ -111,11 +113,6 @@ def main( # subparsers = parser.add_subparsers(dest="subcommand", help="subcommand help") -# restore = subparsers.add_parser("restore", help="restore -h") -# restore.add_argument( -# "name", nargs="+", help="The names of one or more files to restore", type=str -# ) - # commit = subparsers.add_parser("commit", help="commit -h") # commit.add_argument( # "-m", diff --git a/git_sim/restore.py b/git_sim/restore.py new file mode 100644 index 0000000..60911a0 --- /dev/null +++ b/git_sim/restore.py @@ -0,0 +1,79 @@ +import sys + +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 + + +class Restore(GitSimBaseCommand): + def __init__(self, files: list[str]): + super().__init__() + self.maxrefs = 2 + self.hide_first_tag = True + self.files = files + + try: + self.selected_branches.append(self.repo.active_branch.name) + except TypeError: + pass + + for file in self.files: + if file not in [x.a_path for x in self.repo.index.diff(None)] + [ + y.a_path for y in self.repo.index.diff("HEAD") + ]: + print(f"git-sim error: No modified or staged file with name: '{file}'") + sys.exit() + + def construct(self): + print(Settings.INFO_STRING + "restore " + " ".join(self.files)) + + self.show_intro() + self.get_commits() + self.parse_commits(self.commits[0]) + self.recenter_frame() + self.scale_frame() + self.vsplit_frame() + self.setup_and_draw_zones(reverse=True) + self.fadeout() + self.show_outro() + + def populate_zones( + self, + firstColumnFileNames, + secondColumnFileNames, + thirdColumnFileNames, + firstColumnArrowMap, + secondColumnArrowMap, + ): + for x in self.repo.index.diff(None): + if "git-sim_media" not in x.a_path: + secondColumnFileNames.add(x.a_path) + for file in self.files: + if file == x.a_path: + thirdColumnFileNames.add(x.a_path) + secondColumnArrowMap[x.a_path] = m.Arrow( + stroke_width=3, color=self.fontColor + ) + + for y in self.repo.index.diff("HEAD"): + if "git-sim_media" not in y.a_path: + firstColumnFileNames.add(y.a_path) + for file in self.files: + if file == y.a_path: + secondColumnFileNames.add(y.a_path) + 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", + ) +): + scene = Restore(files=files) + handle_animations(scene=scene) From a6bdff09287bb32d1cecec76a821f09d4532b66a Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 7 Feb 2023 08:03:08 +0100 Subject: [PATCH 13/27] add "commit" --- git_sim/__main__.py | 11 +---- git_sim/commit.py | 111 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 git_sim/commit.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 6c485f1..0ded223 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -9,6 +9,7 @@ import typer import git_sim.add +import git_sim.commit import git_sim.log import git_sim.restore import git_sim.status @@ -19,6 +20,7 @@ app.command()(git_sim.status.status) app.command()(git_sim.add.add) app.command()(git_sim.restore.restore) +app.command()(git_sim.commit.commit) @app.callback() @@ -113,15 +115,6 @@ def main( # subparsers = parser.add_subparsers(dest="subcommand", help="subcommand help") -# commit = subparsers.add_parser("commit", help="commit -h") -# commit.add_argument( -# "-m", -# "--message", -# help="The commit message of the new commit", -# type=str, -# default="New commit", -# ) - # stash = subparsers.add_parser("stash", help="stash -h") # stash.add_argument( # "name", nargs="*", help="The name of the file to stash changes for", type=str diff --git a/git_sim/commit.py b/git_sim/commit.py new file mode 100644 index 0000000..8dde11e --- /dev/null +++ b/git_sim/commit.py @@ -0,0 +1,111 @@ +import sys + +import git +import manim as m +import typer +from animations import handle_animations + +from git_sim.git_sim_base_command import GitSimBaseCommand + + +class Commit(GitSimBaseCommand): + def __init__(self, message: str, amend: bool): + super().__init__() + self.message = message + self.amend = amend + + self.maxrefs = 2 + self.defaultNumCommits = 4 if not self.amend else 5 + self.numCommits = 4 if not self.amend else 5 + self.hide_first_tag = True + + try: + self.selected_branches.append(self.repo.active_branch.name) + except TypeError: + pass + + if self.amend and self.message == "New commit": + print( + "git-sim error: The --amend flag must be used with the -m flag to specify the amended commit message." + ) + sys.exit(1) + + def construct(self): + print( + f"Simulating: git commit {' --amend' if self.amend else ''} -m '{self.message}'" + ) + + self.show_intro() + self.get_commits() + + if self.amend: + tree = self.repo.tree() + amended = git.Commit.create_from_tree( + self.repo, + tree, + self.message, + ) + self.commits[0] = amended + + self.parse_commits(self.commits[self.i]) + self.center_frame_on_commit(self.commits[0]) + + if not self.amend: + self.setup_and_draw_parent(self.commits[0], self.message) + else: + self.draw_ref(self.commits[0], self.drawnCommitIds[amended.hexsha]) + self.draw_ref( + self.commits[0], + self.drawnRefs["HEAD"], + text=self.repo.active_branch.name, + color=m.GREEN, + ) + + self.recenter_frame() + self.scale_frame() + + if not self.amend: + self.reset_head_branch("abcdef") + self.vsplit_frame() + self.setup_and_draw_zones( + first_column_name="Working directory", + second_column_name="Staging area", + third_column_name="New commit", + ) + + self.fadeout() + self.show_outro() + + def populate_zones( + self, + firstColumnFileNames, + secondColumnFileNames, + thirdColumnFileNames, + firstColumnArrowMap, + secondColumnArrowMap, + ): + for x in self.repo.index.diff(None): + if "git-sim_media" not in x.a_path: + firstColumnFileNames.add(x.a_path) + + for y in self.repo.index.diff("HEAD"): + if "git-sim_media" not in y.a_path: + secondColumnFileNames.add(y.a_path) + thirdColumnFileNames.add(y.a_path) + secondColumnArrowMap[y.a_path] = m.Arrow( + stroke_width=3, color=self.fontColor + ) + + +def commit( + message: str = typer.Option( + default="New commit", + 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", + ), +): + scene = Commit(message=message, amend=amend) + handle_animations(scene=scene) From e2b740fdc2f4160f578f7e542d67e525a5fd67d6 Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 7 Feb 2023 09:13:43 +0100 Subject: [PATCH 14/27] add "stash" --- git_sim/__main__.py | 7 ++-- git_sim/stash.py | 84 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 git_sim/stash.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 0ded223..fb3c983 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -12,6 +12,7 @@ import git_sim.commit import git_sim.log import git_sim.restore +import git_sim.stash import git_sim.status from git_sim.settings import Settings @@ -21,6 +22,7 @@ app.command()(git_sim.add.add) app.command()(git_sim.restore.restore) app.command()(git_sim.commit.commit) +app.command()(git_sim.stash.stash) @app.callback() @@ -115,11 +117,6 @@ def main( # subparsers = parser.add_subparsers(dest="subcommand", help="subcommand help") -# stash = subparsers.add_parser("stash", help="stash -h") -# stash.add_argument( -# "name", nargs="*", help="The name of the file to stash changes for", type=str -# ) - # branch = subparsers.add_parser("branch", help="branch -h") # branch.add_argument("name", help="The name of the new branch", type=str) diff --git a/git_sim/stash.py b/git_sim/stash.py new file mode 100644 index 0000000..065b50e --- /dev/null +++ b/git_sim/stash.py @@ -0,0 +1,84 @@ +import sys + +import manim as m +import typer + +from git_sim.animations import handle_animations +from git_sim.git_sim_base_command import GitSimBaseCommand + + +class Stash(GitSimBaseCommand): + def __init__(self, files: list[str]): + super().__init__() + self.hide_first_tag = True + self.files = files + + try: + self.selected_branches.append(self.repo.active_branch.name) + except TypeError: + pass + + for file in self.files: + if file not in [x.a_path for x in self.repo.index.diff(None)] + [ + y.a_path for y in self.repo.index.diff("HEAD") + ]: + print(f"git-sim error: No modified or staged file with name: '{file}'") + sys.exit() + + if not self.files: + self.files = [x.a_path for x in self.repo.index.diff(None)] + [ + y.a_path for y in self.repo.index.diff("HEAD") + ] + + def construct(self): + print("Simulating: git stash " + " ".join(self.files)) + + self.show_intro() + self.get_commits() + self.parse_commits(self.commits[0]) + self.recenter_frame() + self.scale_frame() + self.vsplit_frame() + self.setup_and_draw_zones( + first_column_name="Working directory", + second_column_name="Staging area", + third_column_name="Stashed changes", + ) + self.fadeout() + self.show_outro() + + def populate_zones( + self, + firstColumnFileNames, + secondColumnFileNames, + thirdColumnFileNames, + firstColumnArrowMap, + secondColumnArrowMap, + ): + for x in self.repo.index.diff(None): + firstColumnFileNames.add(x.a_path) + for file in self.files: + if file == x.a_path: + thirdColumnFileNames.add(x.a_path) + firstColumnArrowMap[x.a_path] = m.Arrow( + stroke_width=3, color=self.fontColor + ) + + for y in self.repo.index.diff("HEAD"): + secondColumnFileNames.add(y.a_path) + for file in self.files: + if file == y.a_path: + thirdColumnFileNames.add(y.a_path) + secondColumnArrowMap[y.a_path] = m.Arrow( + stroke_width=3, color=self.fontColor + ) + + +def stash( + files: list[str] = typer.Argument( + default=None, + help="The name of the file to stash changes for", + ) +): + scene = Stash(files=files) + handle_animations(scene=scene) From 6e94570d75920b12f2551dad625b9adc933fe14d Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 7 Feb 2023 09:37:46 +0100 Subject: [PATCH 15/27] add "branch" --- git_sim/__main__.py | 5 ++-- git_sim/branch.py | 61 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 git_sim/branch.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index fb3c983..508c134 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -9,6 +9,7 @@ import typer import git_sim.add +import git_sim.branch import git_sim.commit import git_sim.log import git_sim.restore @@ -23,6 +24,7 @@ app.command()(git_sim.restore.restore) app.command()(git_sim.commit.commit) app.command()(git_sim.stash.stash) +app.command()(git_sim.branch.branch) @app.callback() @@ -117,9 +119,6 @@ def main( # subparsers = parser.add_subparsers(dest="subcommand", help="subcommand help") -# branch = subparsers.add_parser("branch", help="branch -h") -# branch.add_argument("name", help="The name of the new branch", type=str) - # tag = subparsers.add_parser("tag", help="tag -h") # tag.add_argument("name", help="The name of the new tag", type=str) diff --git a/git_sim/branch.py b/git_sim/branch.py new file mode 100644 index 0000000..80e5322 --- /dev/null +++ b/git_sim/branch.py @@ -0,0 +1,61 @@ +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 + + +class GitSimBranch(GitSimBaseCommand): + def __init__(self, name: str): + super().__init__() + self.name = name + + def construct(self): + print(f"{Settings.INFO_STRING} branch {self.name}") + + self.show_intro() + self.get_commits() + self.parse_commits(self.commits[0]) + self.recenter_frame() + self.scale_frame() + + branchText = m.Text( + self.name, + font="Monospace", + font_size=20, + color=self.fontColor, + ) + branchRec = m.Rectangle( + color=m.GREEN, + fill_color=m.GREEN, + fill_opacity=0.25, + height=0.4, + width=branchText.width + 0.25, + ) + + branchRec.next_to(self.topref, m.UP) + branchText.move_to(branchRec.get_center()) + + fullbranch = m.VGroup(branchRec, branchText) + + if Settings.animate: + self.play(m.Create(fullbranch), run_time=1 / Settings.speed) + else: + self.add(fullbranch) + + self.toFadeOut.add(branchRec, branchText) + self.drawnRefs[self.name] = fullbranch + + self.fadeout() + self.show_outro() + + +def branch( + name: str = typer.Argument( + ..., + help="The name of the new branch", + ) +): + scene = GitSimBranch(name=name) + handle_animations(scene=scene) From 3d089c2fdefb4394de6c9b1fc6a5b97a561263bb Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 7 Feb 2023 09:47:36 +0100 Subject: [PATCH 16/27] add "tag" --- git_sim/__main__.py | 6 ++--- git_sim/tag.py | 60 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 git_sim/tag.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 508c134..f96595b 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -15,9 +15,11 @@ import git_sim.restore import git_sim.stash import git_sim.status +import git_sim.tag from git_sim.settings import Settings app = typer.Typer() + app.command()(git_sim.log.log) app.command()(git_sim.status.status) app.command()(git_sim.add.add) @@ -25,6 +27,7 @@ app.command()(git_sim.commit.commit) app.command()(git_sim.stash.stash) app.command()(git_sim.branch.branch) +app.command()(git_sim.tag.tag) @app.callback() @@ -119,9 +122,6 @@ def main( # subparsers = parser.add_subparsers(dest="subcommand", help="subcommand help") -# tag = subparsers.add_parser("tag", help="tag -h") -# tag.add_argument("name", help="The name of the new tag", type=str) - # reset = subparsers.add_parser("reset", help="reset -h") # reset.add_argument( # "commit", diff --git a/git_sim/tag.py b/git_sim/tag.py new file mode 100644 index 0000000..fdd1c38 --- /dev/null +++ b/git_sim/tag.py @@ -0,0 +1,60 @@ +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 + + +class Tag(GitSimBaseCommand): + def __init__(self, name: str): + super().__init__() + self.name = name + + def construct(self): + print(f"{Settings.INFO_STRING} tag {self.name}") + + self.show_intro() + self.get_commits() + self.parse_commits(self.commits[0]) + self.recenter_frame() + self.scale_frame() + + tagText = m.Text( + self.name, + font="Monospace", + font_size=20, + color=self.fontColor, + ) + tagRec = m.Rectangle( + color=m.YELLOW, + fill_color=m.YELLOW, + fill_opacity=0.25, + height=0.4, + width=tagText.width + 0.25, + ) + + tagRec.next_to(self.topref, m.UP) + tagText.move_to(tagRec.get_center()) + + fulltag = m.VGroup(tagRec, tagText) + + if Settings.animate: + self.play(m.Create(fulltag), run_time=1 / Settings.speed) + else: + self.add(fulltag) + + self.toFadeOut.add(tagRec, tagText) + + 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 c066631d852e9a9e72d68cf32bcc0a5c31250575 Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 7 Feb 2023 09:53:20 +0100 Subject: [PATCH 17/27] update class name for branch --- git_sim/branch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git_sim/branch.py b/git_sim/branch.py index 80e5322..19cf467 100644 --- a/git_sim/branch.py +++ b/git_sim/branch.py @@ -6,7 +6,7 @@ from git_sim.settings import Settings -class GitSimBranch(GitSimBaseCommand): +class Branch(GitSimBaseCommand): def __init__(self, name: str): super().__init__() self.name = name @@ -57,5 +57,5 @@ def branch( help="The name of the new branch", ) ): - scene = GitSimBranch(name=name) + scene = Branch(name=name) handle_animations(scene=scene) From 0f95e59565d3d5fcee6c7bd3a545de054effd324 Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 7 Feb 2023 10:48:05 +0100 Subject: [PATCH 18/27] add "reset" --- git_sim/__main__.py | 32 +-------- git_sim/reset.py | 172 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 30 deletions(-) create mode 100644 git_sim/reset.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index f96595b..5dd3a29 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -12,6 +12,7 @@ import git_sim.branch import git_sim.commit import git_sim.log +import git_sim.reset import git_sim.restore import git_sim.stash import git_sim.status @@ -28,6 +29,7 @@ app.command()(git_sim.stash.stash) app.command()(git_sim.branch.branch) app.command()(git_sim.tag.tag) +app.command()(git_sim.reset.reset) @app.callback() @@ -122,36 +124,6 @@ def main( # subparsers = parser.add_subparsers(dest="subcommand", help="subcommand help") -# reset = subparsers.add_parser("reset", help="reset -h") -# reset.add_argument( -# "commit", -# nargs="?", -# help="The ref (branch/tag), or commit ID to simulate reset to", -# type=str, -# default="HEAD", -# ) -# reset.add_argument( -# "--mode", -# help="Either mixed (default), soft, or hard", -# type=str, -# default="default", -# ) -# reset.add_argument( -# "--soft", -# help="Simulate a soft reset, shortcut for --mode=soft", -# action="store_true", -# ) -# reset.add_argument( -# "--mixed", -# help="Simulate a mixed reset, shortcut for --mode=mixed", -# action="store_true", -# ) -# reset.add_argument( -# "--hard", -# help="Simulate a soft reset, shortcut for --mode=hard", -# action="store_true", -# ) - # revert = subparsers.add_parser("revert", help="revert -h") # revert.add_argument( # "commit", diff --git a/git_sim/reset.py b/git_sim/reset.py new file mode 100644 index 0000000..1ca29e2 --- /dev/null +++ b/git_sim/reset.py @@ -0,0 +1,172 @@ +import sys +from enum import Enum + +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 + + +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 + ): + super().__init__() + self.commit = commit + self.mode = mode + + try: + self.resetTo = git.repo.fun.rev_parse(self.repo, self.commit) + except git.exc.BadName: + print( + f"git-sim error: '{self.commit}' is not a valid Git ref or identifier." + ) + sys.exit(1) + + self.commitsSinceResetTo = list(self.repo.iter_commits(self.commit + "...HEAD")) + self.maxrefs = 2 + self.hide_first_tag = True + + try: + self.selected_branches.append(self.repo.active_branch.name) + except TypeError: + pass + + if hard: + self.mode = ResetMode.HARD + if mixed: + self.mode = ResetMode.MIXED + if soft: + self.mode = ResetMode.SOFT + + def construct(self): + print( + f"{Settings.INFO_STRING} reset {' --' + self.mode.value if self.mode != ResetMode.DEFAULT else ''} {self.commit}", + ) + + self.show_intro() + self.get_commits() + self.parse_commits(self.commits[self.i]) + self.recenter_frame() + self.scale_frame() + self.reset_head_branch(self.resetTo.hexsha) + self.vsplit_frame() + self.setup_and_draw_zones(first_column_name="Changes deleted from") + self.fadeout() + self.show_outro() + + def build_commit_id_and_message(self, commit, dots=False): + hide_refs = False + if commit == "dark": + commitId = m.Text("", font="Monospace", font_size=20, color=self.fontColor) + commitMessage = "" + elif self.i == 3 and self.resetTo.hexsha not in [ + commit.hexsha for commit in self.get_nondark_commits() + ]: + commitId = m.Text( + "...", font="Monospace", font_size=20, color=self.fontColor + ) + commitMessage = "..." + hide_refs = True + elif self.i == 4 and self.resetTo.hexsha not in [ + commit.hexsha for commit in self.get_nondark_commits() + ]: + commitId = m.Text( + self.resetTo.hexsha[:6], + font="Monospace", + font_size=20, + color=self.fontColor, + ) + commitMessage = self.resetTo.message.split("\n")[0][:40].replace("\n", " ") + commit = self.resetTo + hide_refs = True + else: + commitId = m.Text( + commit.hexsha[:6], + font="Monospace", + font_size=20, + color=self.fontColor, + ) + commitMessage = commit.message.split("\n")[0][:40].replace("\n", " ") + + if ( + commit != "dark" + and commit.hexsha == self.resetTo.hexsha + and commit.hexsha != self.repo.head.commit.hexsha + ): + hide_refs = True + + return commitId, commitMessage, commit, hide_refs + + def populate_zones( + self, + firstColumnFileNames, + secondColumnFileNames, + thirdColumnFileNames, + firstColumnArrowMap={}, + secondColumnArrowMap={}, + ): + for commit in self.commitsSinceResetTo: + if commit.hexsha == self.resetTo.hexsha: + break + for filename in commit.stats.files: + if self.mode == ResetMode.SOFT: + thirdColumnFileNames.add(filename) + elif self.mode in (ResetMode.MIXED, ResetMode.DEFAULT): + secondColumnFileNames.add(filename) + elif self.mode == ResetMode.HARD: + firstColumnFileNames.add(filename) + + for x in self.repo.index.diff(None): + if "git-sim_media" not in x.a_path: + if self.mode == ResetMode.SOFT: + secondColumnFileNames.add(x.a_path) + elif self.mode in (ResetMode.MIXED, ResetMode.DEFAULT): + secondColumnFileNames.add(x.a_path) + elif self.mode == ResetMode.HARD: + firstColumnFileNames.add(x.a_path) + + for y in self.repo.index.diff("HEAD"): + if "git-sim_media" not in y.a_path: + if self.mode == ResetMode.SOFT: + thirdColumnFileNames.add(y.a_path) + elif self.mode in (ResetMode.MIXED, ResetMode.DEFAULT): + 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", + ), +): + scene = Reset(commit=commit, mode=mode, soft=soft, mixed=mixed, hard=hard) + handle_animations(scene=scene) From d405db328061700dd58b6032eb3327032dfbf4b1 Mon Sep 17 00:00:00 2001 From: manu Date: Tue, 7 Feb 2023 22:48:31 +0100 Subject: [PATCH 19/27] rebase onto v0.2.2 --- git_sim/__main__.py | 42 +++++++++++++++++++++++++++++++++--------- git_sim/log.py | 8 ++++---- git_sim/settings.py | 14 +++++++++++++- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 5dd3a29..525e7d9 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -17,7 +17,7 @@ import git_sim.stash import git_sim.status import git_sim.tag -from git_sim.settings import Settings +from git_sim.settings import ImgFormat, Settings, VideoFormat app = typer.Typer() @@ -93,6 +93,15 @@ def main( default=Settings.reverse, help="Display commit history in the reverse direction", ), + video_format: VideoFormat = typer.Option( + default=Settings.video_format.value, + help="Output format for the animation files.", + case_sensitive=False, + ), + img_format: ImgFormat = typer.Option( + default=Settings.img_format.value, + help="Output format for the image files.", + ), ): Settings.animate = animate Settings.title = title @@ -109,6 +118,8 @@ def main( Settings.max_branches_per_commit = max_branches_per_commit Settings.max_tags_per_commit = max_tags_per_commit Settings.reverse = reverse + Settings.video_format = video_format + Settings.img_format = img_format if __name__ == "__main__": @@ -162,6 +173,14 @@ def main( # help="The ref (branch/tag), or commit ID to simulate cherry-pick onto active branch", # ) + # cherrypick.add_argument( + # "-e", + # "--edit", + # help="Specify a new commit message for the cherry-picked commit", + # type=str, + # ) + + # if len(sys.argv) == 1: # parser.print_help() # sys.exit(1) @@ -191,21 +210,26 @@ def main( # if args.low_quality: # config.quality = "low_quality" -# if args.light_mode: -# config.background_color = WHITE + # if args.light_mode: + # config.background_color = WHITE -# scene = gs.get_scene_for_command(args=args) + # t = datetime.datetime.fromtimestamp(time.time()).strftime("%m-%d-%y_%H-%M-%S") + # config.output_file = "git-sim-" + args.subcommand + "_" + t + ".mp4" + + # scene_class = get_scene_for_command(args=args) + # scene = scene_class(args=args) + # scene.render() -# scene.render() # if not args.animate: # video = cv2.VideoCapture(str(scene.renderer.file_writer.movie_file_path)) # success, image = video.read() # if success: -# t = datetime.datetime.fromtimestamp(time.time()).strftime( -# "%m-%d-%y_%H-%M-%S" -# ) -# image_file_name = "git-sim-" + args.subcommand + "_" + t + ".jpg" + + # image_file_name = ( + # "git-sim-" + args.subcommand + "_" + t + "." + args.img_format + # ) + # image_file_path = os.path.join( # os.path.join(config.media_dir, "images"), image_file_name # ) diff --git a/git_sim/log.py b/git_sim/log.py index 3323bf7..61904a7 100644 --- a/git_sim/log.py +++ b/git_sim/log.py @@ -6,8 +6,10 @@ class Log(GitSimBaseCommand): - def __init__(self): + def __init__(self, commits: int): super().__init__() + self.numCommits = commits + 1 + self.defaultNumCommits = commits + 1 try: self.selected_branches.append(self.repo.active_branch.name) except TypeError: @@ -32,7 +34,5 @@ def log( max=12, ), ): - Settings.commits = commits + 1 - - scene = Log() + scene = Log(commits=commits) handle_animations(scene=scene) diff --git a/git_sim/settings.py b/git_sim/settings.py index 23496dd..02566a7 100644 --- a/git_sim/settings.py +++ b/git_sim/settings.py @@ -1,6 +1,16 @@ import pathlib from dataclasses import dataclass -from typing import Optional +from enum import Enum + + +class VideoFormat(str, Enum): + mp4 = "mp4" + webm = "webm" + + +class ImgFormat(str, Enum): + jpg = "jpg" + png = "png" @dataclass @@ -27,3 +37,5 @@ class Settings: logo = pathlib.Path(__file__).parent.resolve() / "logo.png" media_dir = pathlib.Path().cwd() files: list[pathlib.Path] | None = None + video_format: VideoFormat = VideoFormat.mp4 + img_format: ImgFormat = ImgFormat.jpg From 4fdce76242211884c0d06ca3dd3f01dba65ff324 Mon Sep 17 00:00:00 2001 From: manu Date: Wed, 8 Feb 2023 14:25:17 +0100 Subject: [PATCH 20/27] add "revert" --- git_sim/__main__.py | 11 +-- git_sim/revert.py | 166 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 9 deletions(-) create mode 100644 git_sim/revert.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 525e7d9..f2b59e9 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -14,6 +14,7 @@ import git_sim.log import git_sim.reset import git_sim.restore +import git_sim.revert import git_sim.stash import git_sim.status import git_sim.tag @@ -30,6 +31,7 @@ app.command()(git_sim.branch.branch) app.command()(git_sim.tag.tag) app.command()(git_sim.reset.reset) +app.command()(git_sim.revert.revert) @app.callback() @@ -135,15 +137,6 @@ def main( # subparsers = parser.add_subparsers(dest="subcommand", help="subcommand help") -# revert = subparsers.add_parser("revert", help="revert -h") -# revert.add_argument( -# "commit", -# nargs="?", -# help="The ref (branch/tag), or commit ID to simulate revert", -# type=str, -# default="HEAD", -# ) - # merge = subparsers.add_parser("merge", help="merge -h") # merge.add_argument( # "branch", diff --git a/git_sim/revert.py b/git_sim/revert.py new file mode 100644 index 0000000..93d7eea --- /dev/null +++ b/git_sim/revert.py @@ -0,0 +1,166 @@ +import sys + +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 Revert(GitSimBaseCommand): + def __init__(self, commit: str): + super().__init__() + self.commit = commit + + try: + self.revert = git.repo.fun.rev_parse(self.repo, self.commit) + except git.exc.BadName: + print( + "git-sim error: '" + + self.commit + + "' is not a valid Git ref or identifier." + ) + sys.exit(1) + + self.maxrefs = 2 + self.defaultNumCommits = 4 + self.numCommits = 4 + self.hide_first_tag = True + self.zone_title_offset += 0.1 + + try: + self.selected_branches.append(self.repo.active_branch.name) + except TypeError: + pass + + def construct(self): + print(Settings.INFO_STRING + "revert " + self.commit) + + self.show_intro() + self.get_commits() + self.parse_commits(self.commits[self.i]) + self.center_frame_on_commit(self.commits[0]) + self.setup_and_draw_revert_commit() + self.recenter_frame() + self.scale_frame() + self.reset_head_branch("abcdef") + self.vsplit_frame() + self.setup_and_draw_zones( + first_column_name="----", + second_column_name="Changes reverted from", + third_column_name="----", + ) + self.fadeout() + self.show_outro() + + def build_commit_id_and_message(self, commit, dots=False): + hide_refs = False + if commit == "dark": + commitId = m.Text("", font="Monospace", font_size=20, color=self.fontColor) + commitMessage = "" + elif self.i == 2 and self.revert.hexsha not in [ + commit.hexsha for commit in self.commits + ]: + commitId = m.Text( + "...", font="Monospace", font_size=20, color=self.fontColor + ) + commitMessage = "..." + hide_refs = True + elif self.i == 3 and self.revert.hexsha not in [ + commit.hexsha for commit in self.commits + ]: + commitId = m.Text( + self.revert.hexsha[:6], + font="Monospace", + font_size=20, + color=self.fontColor, + ) + commitMessage = self.revert.message.split("\n")[0][:40].replace("\n", " ") + hide_refs = True + else: + commitId = m.Text( + commit.hexsha[:6], + font="Monospace", + font_size=20, + color=self.fontColor, + ) + commitMessage = commit.message.split("\n")[0][:40].replace("\n", " ") + return commitId, commitMessage, commit, hide_refs + + def setup_and_draw_revert_commit(self): + circle = m.Circle(stroke_color=m.RED, fill_color=m.RED, fill_opacity=0.25) + circle.height = 1 + circle.next_to( + self.drawnCommits[self.commits[0].hexsha], + m.LEFT if Settings.reverse else m.RIGHT, + buff=1.5, + ) + + start = circle.get_center() + end = self.drawnCommits[self.commits[0].hexsha].get_center() + arrow = m.Arrow(start, end, color=self.fontColor) + length = numpy.linalg.norm(start - end) - (1.5 if start[1] == end[1] else 3) + arrow.set_length(length) + + commitId = m.Text( + "abcdef", font="Monospace", font_size=20, color=self.fontColor + ).next_to(circle, m.UP) + self.toFadeOut.add(commitId) + + commitMessage = "Revert " + self.revert.hexsha[0:6] + commitMessage = commitMessage[:40].replace("\n", " ") + message = m.Text( + "\n".join( + commitMessage[j : j + 20] for j in range(0, len(commitMessage), 20) + )[:100], + font="Monospace", + font_size=14, + color=self.fontColor, + ).next_to(circle, m.DOWN) + self.toFadeOut.add(message) + + if Settings.animate: + self.play( + self.camera.frame.animate.move_to(circle.get_center()), + m.Create(circle), + m.AddTextLetterByLetter(commitId), + m.AddTextLetterByLetter(message), + run_time=1 / Settings.speed, + ) + else: + self.camera.frame.move_to(circle.get_center()) + self.add(circle, commitId, message) + + self.drawnCommits["abcdef"] = circle + self.toFadeOut.add(circle) + + if Settings.animate: + self.play(m.Create(arrow), run_time=1 / Settings.speed) + else: + self.add(arrow) + + self.toFadeOut.add(arrow) + + def populate_zones( + self, + firstColumnFileNames, + secondColumnFileNames, + thirdColumnFileNames, + firstColumnArrowMap={}, + secondColumnArrowMap={}, + ): + 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", + ) +): + scene = Revert(commit=commit) + handle_animations(scene=scene) From 6897614ef71a4c9d9794a39efdc99465c43d2dbc Mon Sep 17 00:00:00 2001 From: manu Date: Wed, 8 Feb 2023 15:00:40 +0100 Subject: [PATCH 21/27] add "merge" --- git_sim/__main__.py | 47 ++++++---------- git_sim/merge.py | 133 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 29 deletions(-) create mode 100644 git_sim/merge.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index f2b59e9..71b9d4e 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -12,6 +12,7 @@ import git_sim.branch import git_sim.commit import git_sim.log +import git_sim.merge import git_sim.reset import git_sim.restore import git_sim.revert @@ -32,6 +33,7 @@ app.command()(git_sim.tag.tag) app.command()(git_sim.reset.reset) app.command()(git_sim.revert.revert) +app.command()(git_sim.merge.merge) @app.callback() @@ -137,19 +139,6 @@ def main( # subparsers = parser.add_subparsers(dest="subcommand", help="subcommand help") -# merge = subparsers.add_parser("merge", help="merge -h") -# merge.add_argument( -# "branch", -# nargs=1, -# type=str, -# help="The name of the branch to merge into the active checked-out branch", -# ) -# merge.add_argument( -# "--no-ff", -# help="Simulate creation of a merge commit in all cases, even when the merge could instead be resolved as a fast-forward", -# action="store_true", -# ) - # rebase = subparsers.add_parser("rebase", help="rebase -h") # rebase.add_argument( # "branch", @@ -166,12 +155,12 @@ def main( # help="The ref (branch/tag), or commit ID to simulate cherry-pick onto active branch", # ) - # cherrypick.add_argument( - # "-e", - # "--edit", - # help="Specify a new commit message for the cherry-picked commit", - # type=str, - # ) +# cherrypick.add_argument( +# "-e", +# "--edit", +# help="Specify a new commit message for the cherry-picked commit", +# type=str, +# ) # if len(sys.argv) == 1: @@ -203,15 +192,15 @@ def main( # if args.low_quality: # config.quality = "low_quality" - # if args.light_mode: - # config.background_color = WHITE +# if args.light_mode: +# config.background_color = WHITE - # t = datetime.datetime.fromtimestamp(time.time()).strftime("%m-%d-%y_%H-%M-%S") - # config.output_file = "git-sim-" + args.subcommand + "_" + t + ".mp4" +# t = datetime.datetime.fromtimestamp(time.time()).strftime("%m-%d-%y_%H-%M-%S") +# config.output_file = "git-sim-" + args.subcommand + "_" + t + ".mp4" - # scene_class = get_scene_for_command(args=args) - # scene = scene_class(args=args) - # scene.render() +# scene_class = get_scene_for_command(args=args) +# scene = scene_class(args=args) +# scene.render() # if not args.animate: @@ -219,9 +208,9 @@ def main( # success, image = video.read() # if success: - # image_file_name = ( - # "git-sim-" + args.subcommand + "_" + t + "." + args.img_format - # ) +# image_file_name = ( +# "git-sim-" + args.subcommand + "_" + t + "." + args.img_format +# ) # image_file_path = os.path.join( # os.path.join(config.media_dir, "images"), image_file_name diff --git a/git_sim/merge.py b/git_sim/merge.py new file mode 100644 index 0000000..ff53293 --- /dev/null +++ b/git_sim/merge.py @@ -0,0 +1,133 @@ +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 Merge(GitSimBaseCommand): + def __init__(self, branch: str, no_ff: bool): + super().__init__() + self.branch = branch + self.no_ff = no_ff + + 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 + self.maxrefs = 2 + 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): + print(Settings.INFO_STRING + "merge " + 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) + + # Use forward slash to determine if supplied branch arg is local or remote tracking 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.setup_and_draw_parent( + self.orig_commits[0], + "Merge commit", + shift=2 * m.DOWN, + draw_arrow=False, + color=m.GRAY, + ) + self.draw_arrow_between_commits("abcdef", self.commits[0].hexsha) + self.draw_arrow_between_commits("abcdef", self.orig_commits[0].hexsha) + self.recenter_frame() + self.scale_frame() + self.reset_head_branch("abcdef") + + 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) From 4d6562032bc44af2b46d6a1ae034d1cebb0bd7d4 Mon Sep 17 00:00:00 2001 From: manu Date: Wed, 8 Feb 2023 16:13:17 +0100 Subject: [PATCH 22/27] add "rebase" --- git_sim/__main__.py | 10 +-- git_sim/rebase.py | 190 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 8 deletions(-) create mode 100644 git_sim/rebase.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 71b9d4e..4b4e050 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -13,6 +13,7 @@ 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 @@ -34,6 +35,7 @@ app.command()(git_sim.reset.reset) app.command()(git_sim.revert.revert) app.command()(git_sim.merge.merge) +app.command()(git_sim.rebase.rebase) @app.callback() @@ -139,14 +141,6 @@ def main( # subparsers = parser.add_subparsers(dest="subcommand", help="subcommand help") -# rebase = subparsers.add_parser("rebase", help="rebase -h") -# rebase.add_argument( -# "branch", -# nargs=1, -# type=str, -# help="The branch to simulate rebasing the checked-out commit onto", -# ) - # cherrypick = subparsers.add_parser("cherry-pick", help="cherry-pick -h") # cherrypick.add_argument( # "commit", diff --git a/git_sim/rebase.py b/git_sim/rebase.py new file mode 100644 index 0000000..af5eb61 --- /dev/null +++ b/git_sim/rebase.py @@ -0,0 +1,190 @@ +import sys + +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 Rebase(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) + + 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): + print(Settings.INFO_STRING + "rebase " + self.branch) + + if self.branch in self.repo.git.branch( + "--contains", self.repo.active_branch.name + ): + print( + "git-sim error: Branch '" + + self.repo.active_branch.name + + "' is already included in the history of active branch '" + + self.branch + + "'." + ) + sys.exit(1) + + if self.repo.active_branch.name in self.repo.git.branch( + "--contains", self.branch + ): + print( + "git-sim error: Branch '" + + self.branch + + "' is already based on active branch '" + + self.repo.active_branch.name + + "'." + ) + sys.exit(1) + + self.show_intro() + self.get_commits(start=self.branch) + self.parse_commits(self.commits[0]) + self.orig_commits = self.commits + self.i = 0 + self.get_commits() + + reached_base = False + for commit in self.commits: + if commit != "dark" and self.branch in self.repo.git.branch( + "--contains", commit + ): + reached_base = True + + self.parse_commits( + self.commits[0], shift=4 * m.DOWN, dots=False if reached_base else True + ) + self.center_frame_on_commit(self.orig_commits[0]) + + to_rebase = [] + i = 0 + current = self.commits[i] + while self.branch not in self.repo.git.branch("--contains", current): + to_rebase.append(current) + i += 1 + if i >= len(self.commits): + break + current = self.commits[i] + + parent = self.orig_commits[0].hexsha + + for j, tr in enumerate(reversed(to_rebase)): + if not reached_base and j == 0: + message = "..." + else: + message = tr.message + parent = self.setup_and_draw_parent(parent, message) + self.draw_arrow_between_commits(tr.hexsha, parent) + + self.recenter_frame() + self.scale_frame() + self.reset_head_branch(parent) + self.fadeout() + self.show_outro() + + def setup_and_draw_parent( + self, + child, + commitMessage="New commit", + shift=numpy.array([0.0, 0.0, 0.0]), + draw_arrow=True, + ): + circle = m.Circle(stroke_color=m.RED, fill_color=m.RED, fill_opacity=0.25) + circle.height = 1 + circle.next_to( + self.drawnCommits[child], + m.LEFT if Settings.reverse else m.RIGHT, + buff=1.5, + ) + circle.shift(shift) + + start = circle.get_center() + end = self.drawnCommits[child].get_center() + arrow = m.Arrow(start, end, color=self.fontColor) + length = numpy.linalg.norm(start - end) - (1.5 if start[1] == end[1] else 3) + arrow.set_length(length) + + sha = "".join( + chr(ord(letter) + 1) + if ( + (chr(ord(letter) + 1).isalpha() and letter < "f") + or chr(ord(letter) + 1).isdigit() + ) + else letter + for letter in child[:6] + ) + commitId = m.Text( + sha if commitMessage != "..." else "...", + font="Monospace", + font_size=20, + color=self.fontColor, + ).next_to(circle, m.UP) + self.toFadeOut.add(commitId) + + commitMessage = commitMessage[:40].replace("\n", " ") + message = m.Text( + "\n".join( + commitMessage[j : j + 20] for j in range(0, len(commitMessage), 20) + )[:100], + font="Monospace", + font_size=14, + color=self.fontColor, + ).next_to(circle, m.DOWN) + self.toFadeOut.add(message) + + if Settings.animate: + self.play( + self.camera.frame.animate.move_to(circle.get_center()), + m.Create(circle), + m.AddTextLetterByLetter(commitId), + m.AddTextLetterByLetter(message), + run_time=1 / Settings.speed, + ) + else: + self.camera.frame.move_to(circle.get_center()) + self.add(circle, commitId, message) + + self.drawnCommits[sha] = circle + self.toFadeOut.add(circle) + + if draw_arrow: + if Settings.animate: + self.play(m.Create(arrow), run_time=1 / Settings.speed) + else: + self.add(arrow) + 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) From 49efb82358ae6c7630ca969dc2820afe77468560 Mon Sep 17 00:00:00 2001 From: manu Date: Wed, 8 Feb 2023 16:37:21 +0100 Subject: [PATCH 23/27] add "cherrypick" --- git_sim/__main__.py | 21 ++--------- git_sim/cherrypick.py | 84 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 19 deletions(-) create mode 100644 git_sim/cherrypick.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 4b4e050..eb29684 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -10,6 +10,7 @@ import git_sim.add import git_sim.branch +import git_sim.cherrypick import git_sim.commit import git_sim.log import git_sim.merge @@ -30,6 +31,7 @@ app.command()(git_sim.restore.restore) app.command()(git_sim.commit.commit) app.command()(git_sim.stash.stash) +app.command()(git_sim.cherrypick.cherrypick) app.command()(git_sim.branch.branch) app.command()(git_sim.tag.tag) app.command()(git_sim.reset.reset) @@ -138,25 +140,6 @@ def main( # ) -# subparsers = parser.add_subparsers(dest="subcommand", help="subcommand help") - - -# cherrypick = subparsers.add_parser("cherry-pick", help="cherry-pick -h") -# cherrypick.add_argument( -# "commit", -# nargs=1, -# type=str, -# help="The ref (branch/tag), or commit ID to simulate cherry-pick onto active branch", -# ) - -# cherrypick.add_argument( -# "-e", -# "--edit", -# help="Specify a new commit message for the cherry-picked commit", -# type=str, -# ) - - # if len(sys.argv) == 1: # parser.print_help() # sys.exit(1) diff --git a/git_sim/cherrypick.py b/git_sim/cherrypick.py new file mode 100644 index 0000000..8db3e41 --- /dev/null +++ b/git_sim/cherrypick.py @@ -0,0 +1,84 @@ +import sys + +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 + + +class CherryPick(GitSimBaseCommand): + def __init__(self, commit: str, edit: str): + super().__init__() + self.commit = commit + self.edit = edit + + try: + git.repo.fun.rev_parse(self.repo, self.commit) + except git.exc.BadName: + print( + "git-sim error: '" + + self.commit + + "' is not a valid Git ref or identifier." + ) + sys.exit(1) + + if self.commit in [branch.name for branch in self.repo.heads]: + self.selected_branches.append(self.commit) + + try: + self.selected_branches.append(self.repo.active_branch.name) + except TypeError: + pass + + def construct(self): + substring = "" + if self.edit: + substring = f' -e "{self.edit}"' + print(f"{Settings.INFO_STRING} cherrypick {self.commit}{substring}") + + if self.repo.active_branch.name in self.repo.git.branch( + "--contains", self.commit + ): + print( + "git-sim error: Commit '" + + self.commit + + "' is already included in the history of active branch '" + + self.repo.active_branch.name + + "'." + ) + sys.exit(1) + + self.show_intro() + self.get_commits() + self.parse_commits(self.commits[0]) + self.orig_commits = self.commits + self.get_commits(start=self.commit) + self.parse_commits(self.commits[0], shift=4 * m.DOWN) + self.center_frame_on_commit(self.orig_commits[0]) + self.setup_and_draw_parent( + self.orig_commits[0], + self.edit if self.edit else self.commits[0].message, + ) + self.draw_arrow_between_commits(self.commits[0].hexsha, "abcdef") + self.recenter_frame() + self.scale_frame() + self.reset_head_branch("abcdef") + self.fadeout() + self.show_outro() + + +def cherrypick( + commit: str = typer.Argument( + ..., + help="The ref (branch/tag), or commit ID to simulate cherry-pick onto active branch", + ), + edit: str = typer.Option( + default=None, + help="Specify a new commit message for the cherry-picked commit", + ), +): + scene = CherryPick(commit=commit, edit=edit) + handle_animations(scene=scene) From c52da38b9926acfcdc472d08ab6eb69c11a4bc50 Mon Sep 17 00:00:00 2001 From: manu Date: Wed, 8 Feb 2023 16:43:20 +0100 Subject: [PATCH 24/27] clean up __main__.py and animations.py --- git_sim/__main__.py | 218 +++++++++++++++--------------------------- git_sim/animations.py | 73 +++++++++++--- 2 files changed, 134 insertions(+), 157 deletions(-) diff --git a/git_sim/__main__.py b/git_sim/__main__.py index eb29684..c3580bc 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -25,186 +25,118 @@ app = typer.Typer() -app.command()(git_sim.log.log) -app.command()(git_sim.status.status) -app.command()(git_sim.add.add) -app.command()(git_sim.restore.restore) -app.command()(git_sim.commit.commit) -app.command()(git_sim.stash.stash) -app.command()(git_sim.cherrypick.cherrypick) -app.command()(git_sim.branch.branch) -app.command()(git_sim.tag.tag) -app.command()(git_sim.reset.reset) -app.command()(git_sim.revert.revert) -app.command()(git_sim.merge.merge) -app.command()(git_sim.rebase.rebase) - -@app.callback() +@app.callback(no_args_is_help=True) def main( - title: str = typer.Option( - default=Settings.title, - help="Custom title to display at the beginning of the animation", - ), animate: bool = typer.Option( - default=Settings.animate, + Settings.animate, help="Animate the simulation and output as an mp4 video", ), - outro_top_text: str = typer.Option( - default=Settings.outro_top_text, - help="Custom text to display above the logo during the outro", - ), - outro_bottom_text: str = typer.Option( - default=Settings.outro_bottom_text, - help="Custom text to display below the logo during the outro", - ), - low_quality: bool = typer.Option( - default=Settings.low_quality, - help="Render output video in low quality, useful for faster testing", - ), auto_open: bool = typer.Option( - default=Settings.auto_open, + Settings.auto_open, + "--auto-open", + " /-d", help="Enable / disable the automatic opening of the image/video file after generation", ), + img_format: ImgFormat = typer.Option( + Settings.img_format, + help="Output format for the image files.", + ), light_mode: bool = typer.Option( - default=Settings.light_mode, help="Enable light-mode with white background" + Settings.light_mode, + "--light-mode", + help="Enable light-mode with white background", ), logo: pathlib.Path = typer.Option( - default=Settings.logo, + Settings.logo, help="The path to a custom logo to use in the animation intro/outro", ), - show_intro: bool = typer.Option( - default=Settings.show_intro, - help="Add an intro sequence with custom logo and title", - ), - show_outtro: bool = typer.Option( - default=Settings.show_outro, - help="Add an outro sequence with custom logo and text", - ), - media_dir: pathlib.Path = typer.Option( - default=Settings.media_dir, - help="The path to output the animation data and video file", - ), - speed: float = typer.Option( - default=Settings.speed, - help="A multiple of the standard 1x animation speed (ex: 2 = twice as fast, 0.5 = half as fast)", + low_quality: bool = typer.Option( + Settings.low_quality, + "--low-quality", + help="Render output video in low quality, useful for faster testing", ), max_branches_per_commit: int = typer.Option( - default=Settings.max_branches_per_commit, + Settings.max_branches_per_commit, help="Maximum number of branch labels to display for each commit", ), max_tags_per_commit: int = typer.Option( - default=Settings.max_tags_per_commit, + Settings.max_tags_per_commit, help="Maximum number of tags to display for each commit", ), + media_dir: pathlib.Path = typer.Option( + Settings.media_dir, + help="The path to output the animation data and video file", + ), + outro_bottom_text: str = typer.Option( + Settings.outro_bottom_text, + help="Custom text to display below the logo during the outro", + ), + outro_top_text: str = typer.Option( + Settings.outro_top_text, + help="Custom text to display above the logo during the outro", + ), reverse: bool = typer.Option( - default=Settings.reverse, + Settings.reverse, + "--reverse", + "-r", help="Display commit history in the reverse direction", ), + show_intro: bool = typer.Option( + Settings.show_intro, + help="Add an intro sequence with custom logo and title", + ), + show_outro: bool = typer.Option( + Settings.show_outro, + help="Add an outro sequence with custom logo and text", + ), + speed: float = typer.Option( + Settings.speed, + help="A multiple of the standard 1x animation speed (ex: 2 = twice as fast, 0.5 = half as fast)", + ), + title: str = typer.Option( + Settings.title, + help="Custom title to display at the beginning of the animation", + ), video_format: VideoFormat = typer.Option( - default=Settings.video_format.value, + Settings.video_format.value, help="Output format for the animation files.", case_sensitive=False, ), - img_format: ImgFormat = typer.Option( - default=Settings.img_format.value, - help="Output format for the image files.", - ), ): Settings.animate = animate - Settings.title = title - Settings.outro_top_text = outro_top_text - Settings.outro_bottom_text = outro_bottom_text - Settings.low_quality = low_quality Settings.auto_open = auto_open + Settings.img_format = img_format Settings.light_mode = light_mode Settings.logo = logo - Settings.show_intro = show_intro - Settings.show_outro = show_outtro - Settings.media_dir = media_dir - Settings.speed = speed + Settings.low_quality = low_quality Settings.max_branches_per_commit = max_branches_per_commit Settings.max_tags_per_commit = max_tags_per_commit + Settings.media_dir = media_dir + Settings.outro_bottom_text = outro_bottom_text + Settings.outro_top_text = outro_top_text Settings.reverse = reverse + Settings.show_intro = show_intro + Settings.show_outro = show_outro + Settings.speed = speed + Settings.title = title Settings.video_format = video_format - Settings.img_format = img_format - - -if __name__ == "__main__": - app() - - -# def main(): -# parser = argparse.ArgumentParser( -# "git-sim", formatter_class=argparse.ArgumentDefaultsHelpFormatter -# ) -# if len(sys.argv) == 1: -# parser.print_help() -# sys.exit(1) - -# args = parser.parse_args() - -# if sys.platform == "linux" or sys.platform == "darwin": -# repo_name = git.Repo(search_parent_directories=True).working_tree_dir.split( -# "/" -# )[-1] -# elif sys.platform == "win32": -# repo_name = git.Repo(search_parent_directories=True).working_tree_dir.split( -# "\\" -# )[-1] - -# config.media_dir = os.path.join(os.path.expanduser(args.media_dir), "git-sim_media") -# config.verbosity = "ERROR" - -# # If the env variable is set and no argument provided, use the env variable value -# if os.getenv("git_sim_media_dir") and args.media_dir == ".": -# config.media_dir = os.path.join( -# os.path.expanduser(os.getenv("git_sim_media_dir")), -# "git-sim_media", -# repo_name, -# ) - -# if args.low_quality: -# config.quality = "low_quality" - -# if args.light_mode: -# config.background_color = WHITE - -# t = datetime.datetime.fromtimestamp(time.time()).strftime("%m-%d-%y_%H-%M-%S") -# config.output_file = "git-sim-" + args.subcommand + "_" + t + ".mp4" - -# scene_class = get_scene_for_command(args=args) -# scene = scene_class(args=args) -# scene.render() - - -# if not args.animate: -# video = cv2.VideoCapture(str(scene.renderer.file_writer.movie_file_path)) -# success, image = video.read() -# if success: - -# image_file_name = ( -# "git-sim-" + args.subcommand + "_" + t + "." + args.img_format -# ) - -# image_file_path = os.path.join( -# os.path.join(config.media_dir, "images"), image_file_name -# ) -# cv2.imwrite(image_file_path, image) - -# if not args.disable_auto_open: -# try: -# if not args.animate: -# open_media_file(image_file_path) -# else: -# open_media_file(scene.renderer.file_writer.movie_file_path) -# except FileNotFoundError: -# print( -# "Error automatically opening media, please manually open the image or video file to view." -# ) +app.command()(git_sim.add.add) +app.command()(git_sim.branch.branch) +app.command()(git_sim.cherrypick.cherrypick) +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) -# if __name__ == "__main__": -# main() +if __name__ == "__main__": + app() diff --git a/git_sim/animations.py b/git_sim/animations.py index 96d4a22..ef07ca4 100644 --- a/git_sim/animations.py +++ b/git_sim/animations.py @@ -1,46 +1,91 @@ import datetime +import inspect import os import pathlib +import subprocess +import sys import time import cv2 -from manim import Scene -from manim import config as manim_config +import git.repo +from manim import WHITE, Scene, config from manim.utils.file_ops import open_file from git_sim.settings import Settings def handle_animations(scene: Scene) -> None: + 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] - manim_config.media_dir = os.path.join( - os.path.expanduser(pathlib.Path()), "git-sim_media" + config.media_dir = os.path.join( + os.path.expanduser(Settings.media_dir), "git-sim_media" ) - manim_config.verbosity = "ERROR" + config.verbosity = "ERROR" + + # If the env variable is set and no argument provided, use the env variable value + if os.getenv("git_sim_media_dir") and Settings.media_dir == ".": + config.media_dir = os.path.join( + os.path.expanduser(os.getenv("git_sim_media_dir")), + "git-sim_media", + repo_name, + ) + + if Settings.low_quality: + config.quality = "low_quality" + + if Settings.light_mode: + config.background_color = WHITE + + t = datetime.datetime.fromtimestamp(time.time()).strftime("%m-%d-%y_%H-%M-%S") + config.output_file = "git-sim-" + inspect.stack()[1].function + "_" + t + ".mp4" scene.render() + if Settings.video_format == "webm": + webm_file_path = str(scene.renderer.file_writer.movie_file_path)[:-3] + "webm" + cmd = f"ffmpeg -y -i {scene.renderer.file_writer.movie_file_path} -hide_banner -loglevel error -c:v libvpx-vp9 -crf 50 -b:v 0 -b:a 128k -c:a libopus {webm_file_path}" + print("Converting video output to .webm format...") + # Start ffmpeg conversion + p = subprocess.Popen(cmd, shell=True) + p.wait() + # if the conversion is successful, delete the .mp4 + if os.path.exists(webm_file_path): + os.remove(scene.renderer.file_writer.movie_file_path) + scene.renderer.file_writer.movie_file_path = webm_file_path + if not Settings.animate: video = cv2.VideoCapture(str(scene.renderer.file_writer.movie_file_path)) success, image = video.read() if success: - t = datetime.datetime.fromtimestamp(time.time()).strftime( - "%m-%d-%y_%H-%M-%S" + image_file_name = ( + "git-sim-" + + inspect.stack()[1].function + + "_" + + t + + "." + + Settings.img_format ) - image_file_name = "git-sim-log_" + t + ".jpg" image_file_path = os.path.join( - os.path.join(manim_config.media_dir, "images"), image_file_name + os.path.join(config.media_dir, "images"), image_file_name ) cv2.imwrite(image_file_path, image) - else: - raise + print("Output image location:", image_file_path) + else: + print("Output video location:", scene.renderer.file_writer.movie_file_path) if Settings.auto_open: try: - if Settings.animate: - open_file(scene.renderer.file_writer.movie_file_path) - else: + if not Settings.animate: open_file(image_file_path) + else: + open_file(scene.renderer.file_writer.movie_file_path) except FileNotFoundError: print( "Error automatically opening media, please manually open the image or video file to view." From 0b2db4c5cd5b6d06b74f929b99d525fdcec9a383 Mon Sep 17 00:00:00 2001 From: manu Date: Thu, 9 Feb 2023 08:59:36 +0100 Subject: [PATCH 25/27] remove obsolete self.maxrefs --- git_sim/add.py | 1 - git_sim/commit.py | 1 - git_sim/git_sim_add.py | 1 - git_sim/git_sim_commit.py | 1 - git_sim/git_sim_merge.py | 1 - git_sim/git_sim_reset.py | 1 - git_sim/git_sim_restore.py | 1 - git_sim/git_sim_revert.py | 1 - git_sim/git_sim_stash.py | 1 - git_sim/git_sim_status.py | 1 - git_sim/merge.py | 1 - git_sim/reset.py | 1 - git_sim/restore.py | 1 - git_sim/revert.py | 1 - 14 files changed, 14 deletions(-) diff --git a/git_sim/add.py b/git_sim/add.py index 781c1a5..5c89bda 100644 --- a/git_sim/add.py +++ b/git_sim/add.py @@ -12,7 +12,6 @@ class Add(GitSimBaseCommand): def __init__(self, files: list[str]): super().__init__() - self.maxrefs = 2 self.hide_first_tag = True self.allow_no_commits = True self.files = files diff --git a/git_sim/commit.py b/git_sim/commit.py index 8dde11e..7dd7b00 100644 --- a/git_sim/commit.py +++ b/git_sim/commit.py @@ -14,7 +14,6 @@ def __init__(self, message: str, amend: bool): self.message = message self.amend = amend - self.maxrefs = 2 self.defaultNumCommits = 4 if not self.amend else 5 self.numCommits = 4 if not self.amend else 5 self.hide_first_tag = True diff --git a/git_sim/git_sim_add.py b/git_sim/git_sim_add.py index 0dc2e52..9a58e9c 100644 --- a/git_sim/git_sim_add.py +++ b/git_sim/git_sim_add.py @@ -10,7 +10,6 @@ class GitSimAdd(GitSimBaseCommand): def __init__(self, args: Namespace): super().__init__(args=args) - self.maxrefs = 2 self.hide_first_tag = True self.allow_no_commits = True diff --git a/git_sim/git_sim_commit.py b/git_sim/git_sim_commit.py index aec397a..52d6adb 100644 --- a/git_sim/git_sim_commit.py +++ b/git_sim/git_sim_commit.py @@ -10,7 +10,6 @@ class GitSimCommit(GitSimBaseCommand): def __init__(self, args: Namespace): super().__init__(args=args) - self.maxrefs = 2 self.defaultNumCommits = 4 if not self.args.amend else 5 self.numCommits = 4 if not self.args.amend else 5 self.hide_first_tag = True diff --git a/git_sim/git_sim_merge.py b/git_sim/git_sim_merge.py index 914a9fe..121ce0d 100644 --- a/git_sim/git_sim_merge.py +++ b/git_sim/git_sim_merge.py @@ -23,7 +23,6 @@ def __init__(self, args: Namespace): sys.exit(1) self.ff = False - self.maxrefs = 2 if self.args.branch[0] in [branch.name for branch in self.repo.heads]: self.selected_branches.append(self.args.branch[0]) diff --git a/git_sim/git_sim_reset.py b/git_sim/git_sim_reset.py index 4762772..97eaba2 100644 --- a/git_sim/git_sim_reset.py +++ b/git_sim/git_sim_reset.py @@ -24,7 +24,6 @@ def __init__(self, args: Namespace): self.commitsSinceResetTo = list( self.repo.iter_commits(self.args.commit + "...HEAD") ) - self.maxrefs = 2 self.hide_first_tag = True try: diff --git a/git_sim/git_sim_restore.py b/git_sim/git_sim_restore.py index 151d0e0..532fc6b 100644 --- a/git_sim/git_sim_restore.py +++ b/git_sim/git_sim_restore.py @@ -9,7 +9,6 @@ class GitSimRestore(GitSimBaseCommand): def __init__(self, args: Namespace): super().__init__(args=args) - self.maxrefs = 2 self.hide_first_tag = True try: diff --git a/git_sim/git_sim_revert.py b/git_sim/git_sim_revert.py index 460b88d..bc1ea00 100644 --- a/git_sim/git_sim_revert.py +++ b/git_sim/git_sim_revert.py @@ -22,7 +22,6 @@ def __init__(self, args: Namespace): ) sys.exit(1) - self.maxrefs = 2 self.defaultNumCommits = 4 self.numCommits = 4 self.hide_first_tag = True diff --git a/git_sim/git_sim_stash.py b/git_sim/git_sim_stash.py index 445a3d0..3997512 100644 --- a/git_sim/git_sim_stash.py +++ b/git_sim/git_sim_stash.py @@ -11,7 +11,6 @@ class GitSimStash(GitSimBaseCommand): def __init__(self, args: Namespace): super().__init__(args=args) - self.maxrefs = 2 self.hide_first_tag = True try: diff --git a/git_sim/git_sim_status.py b/git_sim/git_sim_status.py index 9d857ec..4a55e95 100644 --- a/git_sim/git_sim_status.py +++ b/git_sim/git_sim_status.py @@ -6,7 +6,6 @@ class GitSimStatus(GitSimBaseCommand): def __init__(self, args: Namespace): super().__init__(args=args) - self.maxrefs = 2 self.hide_first_tag = True self.allow_no_commits = True diff --git a/git_sim/merge.py b/git_sim/merge.py index ff53293..12470f5 100644 --- a/git_sim/merge.py +++ b/git_sim/merge.py @@ -28,7 +28,6 @@ def __init__(self, branch: str, no_ff: bool): sys.exit(1) self.ff = False - self.maxrefs = 2 if self.branch in [branch.name for branch in self.repo.heads]: self.selected_branches.append(self.branch) diff --git a/git_sim/reset.py b/git_sim/reset.py index 1ca29e2..79f0e58 100644 --- a/git_sim/reset.py +++ b/git_sim/reset.py @@ -34,7 +34,6 @@ def __init__( sys.exit(1) self.commitsSinceResetTo = list(self.repo.iter_commits(self.commit + "...HEAD")) - self.maxrefs = 2 self.hide_first_tag = True try: diff --git a/git_sim/restore.py b/git_sim/restore.py index 60911a0..8efb0eb 100644 --- a/git_sim/restore.py +++ b/git_sim/restore.py @@ -11,7 +11,6 @@ class Restore(GitSimBaseCommand): def __init__(self, files: list[str]): super().__init__() - self.maxrefs = 2 self.hide_first_tag = True self.files = files diff --git a/git_sim/revert.py b/git_sim/revert.py index 93d7eea..8ccc465 100644 --- a/git_sim/revert.py +++ b/git_sim/revert.py @@ -25,7 +25,6 @@ def __init__(self, commit: str): ) sys.exit(1) - self.maxrefs = 2 self.defaultNumCommits = 4 self.numCommits = 4 self.hide_first_tag = True From 8a7c5478bc7d12ddd58e108898b2271e390caad2 Mon Sep 17 00:00:00 2001 From: manu Date: Thu, 9 Feb 2023 09:34:31 +0100 Subject: [PATCH 26/27] move all changes back into the original files --- git_sim/__main__.py | 52 ++++----- git_sim/add.py | 88 --------------- git_sim/branch.py | 61 ---------- git_sim/cherrypick.py | 84 -------------- git_sim/commit.py | 110 ------------------ git_sim/git_sim_add.py | 40 ++++--- git_sim/git_sim_base_command.py | 4 - git_sim/git_sim_branch.py | 32 ++++-- git_sim/git_sim_cherrypick.py | 53 +++++---- git_sim/git_sim_commit.py | 50 +++++---- git_sim/git_sim_log.py | 29 +++-- git_sim/git_sim_merge.py | 58 ++++++---- git_sim/git_sim_rebase.py | 55 +++++---- git_sim/git_sim_reset.py | 94 ++++++++++------ git_sim/git_sim_restore.py | 44 ++++---- git_sim/git_sim_revert.py | 37 +++++-- git_sim/git_sim_stash.py | 49 ++++---- git_sim/git_sim_status.py | 23 ++-- git_sim/git_sim_tag.py | 30 +++-- git_sim/log.py | 38 ------- git_sim/merge.py | 132 ---------------------- git_sim/rebase.py | 190 -------------------------------- git_sim/reset.py | 171 ---------------------------- git_sim/restore.py | 78 ------------- git_sim/revert.py | 165 --------------------------- git_sim/stash.py | 84 -------------- git_sim/status.py | 31 ------ git_sim/tag.py | 60 ---------- 28 files changed, 404 insertions(+), 1538 deletions(-) delete mode 100644 git_sim/add.py delete mode 100644 git_sim/branch.py delete mode 100644 git_sim/cherrypick.py delete mode 100644 git_sim/commit.py delete mode 100644 git_sim/log.py delete mode 100644 git_sim/merge.py delete mode 100644 git_sim/rebase.py delete mode 100644 git_sim/reset.py delete mode 100644 git_sim/restore.py delete mode 100644 git_sim/revert.py delete mode 100644 git_sim/stash.py delete mode 100644 git_sim/status.py delete mode 100644 git_sim/tag.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index c3580bc..775ade1 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -8,19 +8,19 @@ import typer -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.git_sim_add +import git_sim.git_sim_branch +import git_sim.git_sim_cherrypick +import git_sim.git_sim_commit +import git_sim.git_sim_log +import git_sim.git_sim_merge +import git_sim.git_sim_rebase +import git_sim.git_sim_reset +import git_sim.git_sim_restore +import git_sim.git_sim_revert +import git_sim.git_sim_stash +import git_sim.git_sim_status +import git_sim.git_sim_tag from git_sim.settings import ImgFormat, Settings, VideoFormat app = typer.Typer() @@ -123,19 +123,19 @@ def main( Settings.video_format = video_format -app.command()(git_sim.add.add) -app.command()(git_sim.branch.branch) -app.command()(git_sim.cherrypick.cherrypick) -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.git_sim_add.add) +app.command()(git_sim.git_sim_branch.branch) +app.command()(git_sim.git_sim_cherrypick.cherrypick) +app.command()(git_sim.git_sim_commit.commit) +app.command()(git_sim.git_sim_log.log) +app.command()(git_sim.git_sim_merge.merge) +app.command()(git_sim.git_sim_rebase.rebase) +app.command()(git_sim.git_sim_reset.reset) +app.command()(git_sim.git_sim_restore.restore) +app.command()(git_sim.git_sim_revert.revert) +app.command()(git_sim.git_sim_stash.stash) +app.command()(git_sim.git_sim_status.status) +app.command()(git_sim.git_sim_tag.tag) if __name__ == "__main__": diff --git a/git_sim/add.py b/git_sim/add.py deleted file mode 100644 index 5c89bda..0000000 --- a/git_sim/add.py +++ /dev/null @@ -1,88 +0,0 @@ -import sys - -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 - - -class Add(GitSimBaseCommand): - def __init__(self, files: list[str]): - super().__init__() - self.hide_first_tag = True - self.allow_no_commits = True - self.files = files - - try: - self.selected_branches.append(self.repo.active_branch.name) - except TypeError: - pass - - for file in self.files: - if file not in [x.a_path for x in self.repo.index.diff(None)] + [ - z for z in self.repo.untracked_files - ]: - print(f"git-sim error: No modified file with name: '{file}'") - sys.exit() - - def construct(self): - print(Settings.INFO_STRING + "add " + " ".join(self.files)) - - self.show_intro() - self.get_commits() - self.parse_commits(self.commits[0]) - self.recenter_frame() - self.scale_frame() - self.vsplit_frame() - self.setup_and_draw_zones() - self.fadeout() - self.show_outro() - - def populate_zones( - self, - firstColumnFileNames, - secondColumnFileNames, - thirdColumnFileNames, - firstColumnArrowMap, - secondColumnArrowMap, - ): - for x in self.repo.index.diff(None): - if "git-sim_media" not in x.a_path: - secondColumnFileNames.add(x.a_path) - for file in self.files: - if file == x.a_path: - thirdColumnFileNames.add(x.a_path) - secondColumnArrowMap[x.a_path] = m.Arrow( - stroke_width=3, color=self.fontColor - ) - try: - for y in self.repo.index.diff("HEAD"): - if "git-sim_media" not in y.a_path: - thirdColumnFileNames.add(y.a_path) - except git.exc.BadName: - for (y, _stage), entry in self.repo.index.entries.items(): - if "git-sim_media" not in y: - thirdColumnFileNames.add(y) - - for z in self.repo.untracked_files: - if "git-sim_media" not in z: - firstColumnFileNames.add(z) - for file in self.files: - if file == z: - thirdColumnFileNames.add(z) - 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", - ) -): - scene = Add(files=files) - handle_animations(scene=scene) diff --git a/git_sim/branch.py b/git_sim/branch.py deleted file mode 100644 index 19cf467..0000000 --- a/git_sim/branch.py +++ /dev/null @@ -1,61 +0,0 @@ -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 - - -class Branch(GitSimBaseCommand): - def __init__(self, name: str): - super().__init__() - self.name = name - - def construct(self): - print(f"{Settings.INFO_STRING} branch {self.name}") - - self.show_intro() - self.get_commits() - self.parse_commits(self.commits[0]) - self.recenter_frame() - self.scale_frame() - - branchText = m.Text( - self.name, - font="Monospace", - font_size=20, - color=self.fontColor, - ) - branchRec = m.Rectangle( - color=m.GREEN, - fill_color=m.GREEN, - fill_opacity=0.25, - height=0.4, - width=branchText.width + 0.25, - ) - - branchRec.next_to(self.topref, m.UP) - branchText.move_to(branchRec.get_center()) - - fullbranch = m.VGroup(branchRec, branchText) - - if Settings.animate: - self.play(m.Create(fullbranch), run_time=1 / Settings.speed) - else: - self.add(fullbranch) - - self.toFadeOut.add(branchRec, branchText) - self.drawnRefs[self.name] = fullbranch - - 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/cherrypick.py b/git_sim/cherrypick.py deleted file mode 100644 index 8db3e41..0000000 --- a/git_sim/cherrypick.py +++ /dev/null @@ -1,84 +0,0 @@ -import sys - -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 - - -class CherryPick(GitSimBaseCommand): - def __init__(self, commit: str, edit: str): - super().__init__() - self.commit = commit - self.edit = edit - - try: - git.repo.fun.rev_parse(self.repo, self.commit) - except git.exc.BadName: - print( - "git-sim error: '" - + self.commit - + "' is not a valid Git ref or identifier." - ) - sys.exit(1) - - if self.commit in [branch.name for branch in self.repo.heads]: - self.selected_branches.append(self.commit) - - try: - self.selected_branches.append(self.repo.active_branch.name) - except TypeError: - pass - - def construct(self): - substring = "" - if self.edit: - substring = f' -e "{self.edit}"' - print(f"{Settings.INFO_STRING} cherrypick {self.commit}{substring}") - - if self.repo.active_branch.name in self.repo.git.branch( - "--contains", self.commit - ): - print( - "git-sim error: Commit '" - + self.commit - + "' is already included in the history of active branch '" - + self.repo.active_branch.name - + "'." - ) - sys.exit(1) - - self.show_intro() - self.get_commits() - self.parse_commits(self.commits[0]) - self.orig_commits = self.commits - self.get_commits(start=self.commit) - self.parse_commits(self.commits[0], shift=4 * m.DOWN) - self.center_frame_on_commit(self.orig_commits[0]) - self.setup_and_draw_parent( - self.orig_commits[0], - self.edit if self.edit else self.commits[0].message, - ) - self.draw_arrow_between_commits(self.commits[0].hexsha, "abcdef") - self.recenter_frame() - self.scale_frame() - self.reset_head_branch("abcdef") - self.fadeout() - self.show_outro() - - -def cherrypick( - commit: str = typer.Argument( - ..., - help="The ref (branch/tag), or commit ID to simulate cherry-pick onto active branch", - ), - edit: str = typer.Option( - default=None, - 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/commit.py b/git_sim/commit.py deleted file mode 100644 index 7dd7b00..0000000 --- a/git_sim/commit.py +++ /dev/null @@ -1,110 +0,0 @@ -import sys - -import git -import manim as m -import typer -from animations import handle_animations - -from git_sim.git_sim_base_command import GitSimBaseCommand - - -class Commit(GitSimBaseCommand): - def __init__(self, message: str, amend: bool): - super().__init__() - self.message = message - self.amend = amend - - self.defaultNumCommits = 4 if not self.amend else 5 - self.numCommits = 4 if not self.amend else 5 - self.hide_first_tag = True - - try: - self.selected_branches.append(self.repo.active_branch.name) - except TypeError: - pass - - if self.amend and self.message == "New commit": - print( - "git-sim error: The --amend flag must be used with the -m flag to specify the amended commit message." - ) - sys.exit(1) - - def construct(self): - print( - f"Simulating: git commit {' --amend' if self.amend else ''} -m '{self.message}'" - ) - - self.show_intro() - self.get_commits() - - if self.amend: - tree = self.repo.tree() - amended = git.Commit.create_from_tree( - self.repo, - tree, - self.message, - ) - self.commits[0] = amended - - self.parse_commits(self.commits[self.i]) - self.center_frame_on_commit(self.commits[0]) - - if not self.amend: - self.setup_and_draw_parent(self.commits[0], self.message) - else: - self.draw_ref(self.commits[0], self.drawnCommitIds[amended.hexsha]) - self.draw_ref( - self.commits[0], - self.drawnRefs["HEAD"], - text=self.repo.active_branch.name, - color=m.GREEN, - ) - - self.recenter_frame() - self.scale_frame() - - if not self.amend: - self.reset_head_branch("abcdef") - self.vsplit_frame() - self.setup_and_draw_zones( - first_column_name="Working directory", - second_column_name="Staging area", - third_column_name="New commit", - ) - - self.fadeout() - self.show_outro() - - def populate_zones( - self, - firstColumnFileNames, - secondColumnFileNames, - thirdColumnFileNames, - firstColumnArrowMap, - secondColumnArrowMap, - ): - for x in self.repo.index.diff(None): - if "git-sim_media" not in x.a_path: - firstColumnFileNames.add(x.a_path) - - for y in self.repo.index.diff("HEAD"): - if "git-sim_media" not in y.a_path: - secondColumnFileNames.add(y.a_path) - thirdColumnFileNames.add(y.a_path) - secondColumnArrowMap[y.a_path] = m.Arrow( - stroke_width=3, color=self.fontColor - ) - - -def commit( - message: str = typer.Option( - default="New commit", - 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", - ), -): - scene = Commit(message=message, amend=amend) - handle_animations(scene=scene) diff --git a/git_sim/git_sim_add.py b/git_sim/git_sim_add.py index 9a58e9c..5c89bda 100644 --- a/git_sim/git_sim_add.py +++ b/git_sim/git_sim_add.py @@ -1,34 +1,35 @@ import sys -from argparse import Namespace 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 -class GitSimAdd(GitSimBaseCommand): - def __init__(self, args: Namespace): - super().__init__(args=args) +class Add(GitSimBaseCommand): + def __init__(self, files: list[str]): + super().__init__() self.hide_first_tag = True self.allow_no_commits = True + self.files = files try: self.selected_branches.append(self.repo.active_branch.name) except TypeError: pass - for name in self.args.name: - if name not in [x.a_path for x in self.repo.index.diff(None)] + [ + for file in self.files: + if file not in [x.a_path for x in self.repo.index.diff(None)] + [ z for z in self.repo.untracked_files ]: - print("git-sim error: No modified file with name: '" + name + "'") + print(f"git-sim error: No modified file with name: '{file}'") sys.exit() def construct(self): - print( - "Simulating: git " + self.args.subcommand + " " + " ".join(self.args.name) - ) + print(Settings.INFO_STRING + "add " + " ".join(self.files)) self.show_intro() self.get_commits() @@ -48,12 +49,11 @@ def populate_zones( firstColumnArrowMap, secondColumnArrowMap, ): - for x in self.repo.index.diff(None): if "git-sim_media" not in x.a_path: secondColumnFileNames.add(x.a_path) - for name in self.args.name: - if name == x.a_path: + for file in self.files: + if file == x.a_path: thirdColumnFileNames.add(x.a_path) secondColumnArrowMap[x.a_path] = m.Arrow( stroke_width=3, color=self.fontColor @@ -70,9 +70,19 @@ def populate_zones( for z in self.repo.untracked_files: if "git-sim_media" not in z: firstColumnFileNames.add(z) - for name in self.args.name: - if name == z: + for file in self.files: + if file == z: thirdColumnFileNames.add(z) 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", + ) +): + scene = Add(files=files) + handle_animations(scene=scene) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index 99ee5d7..98be3e2 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -12,7 +12,6 @@ class GitSimBaseCommand(m.MovingCameraScene): def __init__(self): - super().__init__() self.init_repo() @@ -136,7 +135,6 @@ def show_intro(self): def show_outro(self): if Settings.animate and Settings.show_outro: - self.play(m.Restore(self.camera.frame)) self.play(self.logo.animate.scale(4).set_x(0).set_y(0)) @@ -364,7 +362,6 @@ def draw_tag(self, commit): return for tag in self.repo.tags: - try: if commit.hexsha == tag.commit.hexsha: tagText = m.Text( @@ -741,7 +738,6 @@ def populate_zones( firstColumnArrowMap={}, secondColumnArrowMap={}, ): - for x in self.repo.index.diff(None): if "git-sim_media" not in x.a_path: secondColumnFileNames.add(x.a_path) diff --git a/git_sim/git_sim_branch.py b/git_sim/git_sim_branch.py index 15b9033..19cf467 100644 --- a/git_sim/git_sim_branch.py +++ b/git_sim/git_sim_branch.py @@ -1,16 +1,18 @@ -from argparse import Namespace - 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 -class GitSimBranch(GitSimBaseCommand): - def __init__(self, args: Namespace): - super().__init__(args=args) +class Branch(GitSimBaseCommand): + def __init__(self, name: str): + super().__init__() + self.name = name def construct(self): - print("Simulating: git " + self.args.subcommand + " " + self.args.name) + print(f"{Settings.INFO_STRING} branch {self.name}") self.show_intro() self.get_commits() @@ -19,7 +21,7 @@ def construct(self): self.scale_frame() branchText = m.Text( - self.args.name, + self.name, font="Monospace", font_size=20, color=self.fontColor, @@ -37,13 +39,23 @@ def construct(self): fullbranch = m.VGroup(branchRec, branchText) - if self.args.animate: - self.play(m.Create(fullbranch), run_time=1 / self.args.speed) + if Settings.animate: + self.play(m.Create(fullbranch), run_time=1 / Settings.speed) else: self.add(fullbranch) self.toFadeOut.add(branchRec, branchText) - self.drawnRefs[self.args.name] = fullbranch + self.drawnRefs[self.name] = fullbranch 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/git_sim_cherrypick.py b/git_sim/git_sim_cherrypick.py index dd01cb2..8db3e41 100644 --- a/git_sim/git_sim_cherrypick.py +++ b/git_sim/git_sim_cherrypick.py @@ -1,28 +1,32 @@ import sys -from argparse import Namespace 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 -class GitSimCherryPick(GitSimBaseCommand): - def __init__(self, args: Namespace): - super().__init__(args=args) +class CherryPick(GitSimBaseCommand): + def __init__(self, commit: str, edit: str): + super().__init__() + self.commit = commit + self.edit = edit try: - git.repo.fun.rev_parse(self.repo, self.args.commit[0]) + git.repo.fun.rev_parse(self.repo, self.commit) except git.exc.BadName: print( "git-sim error: '" - + self.args.commit[0] + + self.commit + "' is not a valid Git ref or identifier." ) sys.exit(1) - if self.args.commit[0] in [branch.name for branch in self.repo.heads]: - self.selected_branches.append(self.args.commit[0]) + if self.commit in [branch.name for branch in self.repo.heads]: + self.selected_branches.append(self.commit) try: self.selected_branches.append(self.repo.active_branch.name) @@ -30,20 +34,17 @@ def __init__(self, args: Namespace): pass def construct(self): - print( - "Simulating: git " - + self.args.subcommand - + " " - + self.args.commit[0] - + ((' -e "' + self.args.edit + '"') if self.args.edit else "") - ) + substring = "" + if self.edit: + substring = f' -e "{self.edit}"' + print(f"{Settings.INFO_STRING} cherrypick {self.commit}{substring}") if self.repo.active_branch.name in self.repo.git.branch( - "--contains", self.args.commit[0] + "--contains", self.commit ): print( "git-sim error: Commit '" - + self.args.commit[0] + + self.commit + "' is already included in the history of active branch '" + self.repo.active_branch.name + "'." @@ -54,12 +55,12 @@ def construct(self): self.get_commits() self.parse_commits(self.commits[0]) self.orig_commits = self.commits - self.get_commits(start=self.args.commit[0]) + self.get_commits(start=self.commit) self.parse_commits(self.commits[0], shift=4 * m.DOWN) self.center_frame_on_commit(self.orig_commits[0]) self.setup_and_draw_parent( self.orig_commits[0], - self.args.edit if self.args.edit else self.commits[0].message, + self.edit if self.edit else self.commits[0].message, ) self.draw_arrow_between_commits(self.commits[0].hexsha, "abcdef") self.recenter_frame() @@ -67,3 +68,17 @@ def construct(self): self.reset_head_branch("abcdef") self.fadeout() self.show_outro() + + +def cherrypick( + commit: str = typer.Argument( + ..., + help="The ref (branch/tag), or commit ID to simulate cherry-pick onto active branch", + ), + edit: str = typer.Option( + default=None, + 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/git_sim_commit.py b/git_sim/git_sim_commit.py index 52d6adb..7dd7b00 100644 --- a/git_sim/git_sim_commit.py +++ b/git_sim/git_sim_commit.py @@ -1,17 +1,21 @@ import sys -from argparse import Namespace import git import manim as m +import typer +from animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand -class GitSimCommit(GitSimBaseCommand): - def __init__(self, args: Namespace): - super().__init__(args=args) - self.defaultNumCommits = 4 if not self.args.amend else 5 - self.numCommits = 4 if not self.args.amend else 5 +class Commit(GitSimBaseCommand): + def __init__(self, message: str, amend: bool): + super().__init__() + self.message = message + self.amend = amend + + self.defaultNumCommits = 4 if not self.amend else 5 + self.numCommits = 4 if not self.amend else 5 self.hide_first_tag = True try: @@ -19,7 +23,7 @@ def __init__(self, args: Namespace): except TypeError: pass - if self.args.amend and self.args.message == "New commit": + if self.amend and self.message == "New commit": print( "git-sim error: The --amend flag must be used with the -m flag to specify the amended commit message." ) @@ -27,31 +31,26 @@ def __init__(self, args: Namespace): def construct(self): print( - "Simulating: git " - + self.args.subcommand - + (" --amend" if self.args.amend else "") - + ' -m "' - + self.args.message - + '"' + f"Simulating: git commit {' --amend' if self.amend else ''} -m '{self.message}'" ) self.show_intro() self.get_commits() - if self.args.amend: + if self.amend: tree = self.repo.tree() amended = git.Commit.create_from_tree( self.repo, tree, - self.args.message, + self.message, ) self.commits[0] = amended self.parse_commits(self.commits[self.i]) self.center_frame_on_commit(self.commits[0]) - if not self.args.amend: - self.setup_and_draw_parent(self.commits[0], self.args.message) + if not self.amend: + self.setup_and_draw_parent(self.commits[0], self.message) else: self.draw_ref(self.commits[0], self.drawnCommitIds[amended.hexsha]) self.draw_ref( @@ -64,7 +63,7 @@ def construct(self): self.recenter_frame() self.scale_frame() - if not self.args.amend: + if not self.amend: self.reset_head_branch("abcdef") self.vsplit_frame() self.setup_and_draw_zones( @@ -84,7 +83,6 @@ def populate_zones( firstColumnArrowMap, secondColumnArrowMap, ): - for x in self.repo.index.diff(None): if "git-sim_media" not in x.a_path: firstColumnFileNames.add(x.a_path) @@ -96,3 +94,17 @@ def populate_zones( secondColumnArrowMap[y.a_path] = m.Arrow( stroke_width=3, color=self.fontColor ) + + +def commit( + message: str = typer.Option( + default="New commit", + 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", + ), +): + scene = Commit(message=message, amend=amend) + handle_animations(scene=scene) diff --git a/git_sim/git_sim_log.py b/git_sim/git_sim_log.py index 2c2c587..61904a7 100644 --- a/git_sim/git_sim_log.py +++ b/git_sim/git_sim_log.py @@ -1,21 +1,22 @@ -from argparse import Namespace +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 GitSimLog(GitSimBaseCommand): - def __init__(self, args: Namespace): - super().__init__(args=args) - self.numCommits = self.args.commits + 1 - self.defaultNumCommits = self.args.commits + 1 +class Log(GitSimBaseCommand): + def __init__(self, commits: int): + super().__init__() + self.numCommits = commits + 1 + self.defaultNumCommits = commits + 1 try: self.selected_branches.append(self.repo.active_branch.name) except TypeError: pass def construct(self): - print("Simulating: git " + self.args.subcommand) - + print(Settings.INFO_STRING + type(self).__name__) self.show_intro() self.get_commits() self.parse_commits(self.commits[0]) @@ -23,3 +24,15 @@ def construct(self): self.scale_frame() self.fadeout() self.show_outro() + + +def log( + commits: int = typer.Option( + default=Settings.commits, + help="The number of commits to display in the simulated log output", + min=1, + max=12, + ), +): + scene = Log(commits=commits) + handle_animations(scene=scene) diff --git a/git_sim/git_sim_merge.py b/git_sim/git_sim_merge.py index 121ce0d..12470f5 100644 --- a/git_sim/git_sim_merge.py +++ b/git_sim/git_sim_merge.py @@ -4,27 +4,32 @@ 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 GitSimMerge(GitSimBaseCommand): - def __init__(self, args: Namespace): - super().__init__(args=args) +class Merge(GitSimBaseCommand): + def __init__(self, branch: str, no_ff: bool): + super().__init__() + self.branch = branch + self.no_ff = no_ff try: - git.repo.fun.rev_parse(self.repo, self.args.branch[0]) + git.repo.fun.rev_parse(self.repo, self.branch) except git.exc.BadName: print( "git-sim error: '" - + self.args.branch[0] + + self.branch + "' is not a valid Git ref or identifier." ) sys.exit(1) self.ff = False - if self.args.branch[0] in [branch.name for branch in self.repo.heads]: - self.selected_branches.append(self.args.branch[0]) + 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) @@ -32,14 +37,14 @@ def __init__(self, args: Namespace): pass def construct(self): - print("Simulating: git " + self.args.subcommand + " " + self.args.branch[0]) + print(Settings.INFO_STRING + "merge " + self.branch) if self.repo.active_branch.name in self.repo.git.branch( - "--contains", self.args.branch[0] + "--contains", self.branch ): print( "git-sim error: Branch '" - + self.args.branch[0] + + self.branch + "' is already included in the history of active branch '" + self.repo.active_branch.name + "'." @@ -49,27 +54,27 @@ def construct(self): self.show_intro() self.get_commits() self.orig_commits = self.commits - self.get_commits(start=self.args.branch[0]) + self.get_commits(start=self.branch) # Use forward slash to determine if supplied branch arg is local or remote tracking branch - if not self.is_remote_tracking_branch(self.args.branch[0]): - if self.args.branch[0] in self.repo.git.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.args.branch[0] in self.repo.git.branch( + 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.args.branch[0]) + 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.args.no_ff: + 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" @@ -80,9 +85,7 @@ def construct(self): if "HEAD" in self.drawnRefs: self.reset_head_branch(reset_head_to, shift=shift) else: - self.draw_ref( - self.commits[0], commitId if self.args.no_ff else self.topref - ) + self.draw_ref(self.commits[0], commitId if self.no_ff else self.topref) self.draw_ref( self.commits[0], self.drawnRefs["HEAD"], @@ -94,7 +97,7 @@ def construct(self): self.get_commits() self.parse_commits(self.commits[0]) self.i = 0 - self.get_commits(start=self.args.branch[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.setup_and_draw_parent( @@ -112,3 +115,18 @@ 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/git_sim_rebase.py b/git_sim/git_sim_rebase.py index f6abf61..af5eb61 100644 --- a/git_sim/git_sim_rebase.py +++ b/git_sim/git_sim_rebase.py @@ -1,29 +1,32 @@ 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 GitSimRebase(GitSimBaseCommand): - def __init__(self, args: Namespace): - super().__init__(args=args) +class Rebase(GitSimBaseCommand): + def __init__(self, branch: str): + super().__init__() + self.branch = branch try: - git.repo.fun.rev_parse(self.repo, self.args.branch[0]) + git.repo.fun.rev_parse(self.repo, self.branch) except git.exc.BadName: print( "git-sim error: '" - + self.args.branch[0] + + self.branch + "' is not a valid Git ref or identifier." ) sys.exit(1) - if self.args.branch[0] in [branch.name for branch in self.repo.heads]: - self.selected_branches.append(self.args.branch[0]) + 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) @@ -31,26 +34,26 @@ def __init__(self, args: Namespace): pass def construct(self): - print("Simulating: git " + self.args.subcommand + " " + self.args.branch[0]) + print(Settings.INFO_STRING + "rebase " + self.branch) - if self.args.branch[0] in self.repo.git.branch( + if self.branch in self.repo.git.branch( "--contains", self.repo.active_branch.name ): print( "git-sim error: Branch '" + self.repo.active_branch.name + "' is already included in the history of active branch '" - + self.args.branch[0] + + self.branch + "'." ) sys.exit(1) if self.repo.active_branch.name in self.repo.git.branch( - "--contains", self.args.branch[0] + "--contains", self.branch ): print( "git-sim error: Branch '" - + self.args.branch[0] + + self.branch + "' is already based on active branch '" + self.repo.active_branch.name + "'." @@ -58,7 +61,7 @@ def construct(self): sys.exit(1) self.show_intro() - self.get_commits(start=self.args.branch[0]) + self.get_commits(start=self.branch) self.parse_commits(self.commits[0]) self.orig_commits = self.commits self.i = 0 @@ -66,7 +69,7 @@ def construct(self): reached_base = False for commit in self.commits: - if commit != "dark" and self.args.branch[0] in self.repo.git.branch( + if commit != "dark" and self.branch in self.repo.git.branch( "--contains", commit ): reached_base = True @@ -79,7 +82,7 @@ def construct(self): to_rebase = [] i = 0 current = self.commits[i] - while self.args.branch[0] not in self.repo.git.branch("--contains", current): + while self.branch not in self.repo.git.branch("--contains", current): to_rebase.append(current) i += 1 if i >= len(self.commits): @@ -113,7 +116,7 @@ def setup_and_draw_parent( circle.height = 1 circle.next_to( self.drawnCommits[child], - m.LEFT if self.args.reverse else m.RIGHT, + m.LEFT if Settings.reverse else m.RIGHT, buff=1.5, ) circle.shift(shift) @@ -152,13 +155,13 @@ def setup_and_draw_parent( ).next_to(circle, m.DOWN) self.toFadeOut.add(message) - if self.args.animate: + if Settings.animate: self.play( self.camera.frame.animate.move_to(circle.get_center()), m.Create(circle), m.AddTextLetterByLetter(commitId), m.AddTextLetterByLetter(message), - run_time=1 / self.args.speed, + run_time=1 / Settings.speed, ) else: self.camera.frame.move_to(circle.get_center()) @@ -168,10 +171,20 @@ def setup_and_draw_parent( self.toFadeOut.add(circle) if draw_arrow: - if self.args.animate: - self.play(m.Create(arrow), run_time=1 / self.args.speed) + if Settings.animate: + self.play(m.Create(arrow), run_time=1 / Settings.speed) else: self.add(arrow) 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/git_sim_reset.py b/git_sim/git_sim_reset.py index 97eaba2..79f0e58 100644 --- a/git_sim/git_sim_reset.py +++ b/git_sim/git_sim_reset.py @@ -1,29 +1,39 @@ import sys -from argparse import Namespace +from enum import Enum 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 -class GitSimReset(GitSimBaseCommand): - def __init__(self, args: Namespace): - super().__init__(args=args) +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 + ): + super().__init__() + self.commit = commit + self.mode = mode try: - self.resetTo = git.repo.fun.rev_parse(self.repo, self.args.commit) + self.resetTo = git.repo.fun.rev_parse(self.repo, self.commit) except git.exc.BadName: print( - "git-sim error: '" - + self.args.commit - + "' is not a valid Git ref or identifier." + f"git-sim error: '{self.commit}' is not a valid Git ref or identifier." ) sys.exit(1) - self.commitsSinceResetTo = list( - self.repo.iter_commits(self.args.commit + "...HEAD") - ) + self.commitsSinceResetTo = list(self.repo.iter_commits(self.commit + "...HEAD")) self.hide_first_tag = True try: @@ -31,20 +41,16 @@ def __init__(self, args: Namespace): except TypeError: pass - if self.args.hard: - self.args.mode = "hard" - if self.args.mixed: - self.args.mode = "mixed" - if self.args.soft: - self.args.mode = "soft" + if hard: + self.mode = ResetMode.HARD + if mixed: + self.mode = ResetMode.MIXED + if soft: + self.mode = ResetMode.SOFT def construct(self): print( - "Simulating: git " - + self.args.subcommand - + (" --" + self.args.mode if self.args.mode != "default" else "") - + " " - + self.args.commit + f"{Settings.INFO_STRING} reset {' --' + self.mode.value if self.mode != ResetMode.DEFAULT else ''} {self.commit}", ) self.show_intro() @@ -113,27 +119,53 @@ def populate_zones( if commit.hexsha == self.resetTo.hexsha: break for filename in commit.stats.files: - if self.args.mode == "soft": + if self.mode == ResetMode.SOFT: thirdColumnFileNames.add(filename) - elif self.args.mode == "mixed" or self.args.mode == "default": + elif self.mode in (ResetMode.MIXED, ResetMode.DEFAULT): secondColumnFileNames.add(filename) - elif self.args.mode == "hard": + elif self.mode == ResetMode.HARD: firstColumnFileNames.add(filename) for x in self.repo.index.diff(None): if "git-sim_media" not in x.a_path: - if self.args.mode == "soft": + if self.mode == ResetMode.SOFT: secondColumnFileNames.add(x.a_path) - elif self.args.mode == "mixed" or self.args.mode == "default": + elif self.mode in (ResetMode.MIXED, ResetMode.DEFAULT): secondColumnFileNames.add(x.a_path) - elif self.args.mode == "hard": + elif self.mode == ResetMode.HARD: firstColumnFileNames.add(x.a_path) for y in self.repo.index.diff("HEAD"): if "git-sim_media" not in y.a_path: - if self.args.mode == "soft": + if self.mode == ResetMode.SOFT: thirdColumnFileNames.add(y.a_path) - elif self.args.mode == "mixed" or self.args.mode == "default": + elif self.mode in (ResetMode.MIXED, ResetMode.DEFAULT): secondColumnFileNames.add(y.a_path) - elif self.args.mode == "hard": + 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", + ), +): + scene = Reset(commit=commit, mode=mode, soft=soft, mixed=mixed, hard=hard) + handle_animations(scene=scene) diff --git a/git_sim/git_sim_restore.py b/git_sim/git_sim_restore.py index 532fc6b..8efb0eb 100644 --- a/git_sim/git_sim_restore.py +++ b/git_sim/git_sim_restore.py @@ -1,36 +1,33 @@ import sys -from argparse import Namespace 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 -class GitSimRestore(GitSimBaseCommand): - def __init__(self, args: Namespace): - super().__init__(args=args) +class Restore(GitSimBaseCommand): + def __init__(self, files: list[str]): + super().__init__() self.hide_first_tag = True + self.files = files try: self.selected_branches.append(self.repo.active_branch.name) except TypeError: pass - for name in self.args.name: - if name not in [x.a_path for x in self.repo.index.diff(None)] + [ + for file in self.files: + if file not in [x.a_path for x in self.repo.index.diff(None)] + [ y.a_path for y in self.repo.index.diff("HEAD") ]: - print( - "git-sim error: No modified or staged file with name: '" - + name - + "'" - ) + print(f"git-sim error: No modified or staged file with name: '{file}'") sys.exit() def construct(self): - print( - "Simulating: git " + self.args.subcommand + " " + " ".join(self.args.name) - ) + print(Settings.INFO_STRING + "restore " + " ".join(self.files)) self.show_intro() self.get_commits() @@ -50,12 +47,11 @@ def populate_zones( firstColumnArrowMap, secondColumnArrowMap, ): - for x in self.repo.index.diff(None): if "git-sim_media" not in x.a_path: secondColumnFileNames.add(x.a_path) - for name in self.args.name: - if name == x.a_path: + for file in self.files: + if file == x.a_path: thirdColumnFileNames.add(x.a_path) secondColumnArrowMap[x.a_path] = m.Arrow( stroke_width=3, color=self.fontColor @@ -64,9 +60,19 @@ def populate_zones( for y in self.repo.index.diff("HEAD"): if "git-sim_media" not in y.a_path: firstColumnFileNames.add(y.a_path) - for name in self.args.name: - if name == y.a_path: + for file in self.files: + if file == y.a_path: secondColumnFileNames.add(y.a_path) 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", + ) +): + scene = Restore(files=files) + handle_animations(scene=scene) diff --git a/git_sim/git_sim_revert.py b/git_sim/git_sim_revert.py index bc1ea00..8ccc465 100644 --- a/git_sim/git_sim_revert.py +++ b/git_sim/git_sim_revert.py @@ -1,23 +1,26 @@ 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 GitSimRevert(GitSimBaseCommand): - def __init__(self, args: Namespace): - super().__init__(args=args) +class Revert(GitSimBaseCommand): + def __init__(self, commit: str): + super().__init__() + self.commit = commit try: - self.revert = git.repo.fun.rev_parse(self.repo, self.args.commit) + self.revert = git.repo.fun.rev_parse(self.repo, self.commit) except git.exc.BadName: print( "git-sim error: '" - + self.args.commit + + self.commit + "' is not a valid Git ref or identifier." ) sys.exit(1) @@ -33,7 +36,7 @@ def __init__(self, args: Namespace): pass def construct(self): - print("Simulating: git " + self.args.subcommand + " " + self.args.commit) + print(Settings.INFO_STRING + "revert " + self.commit) self.show_intro() self.get_commits() @@ -91,7 +94,7 @@ def setup_and_draw_revert_commit(self): circle.height = 1 circle.next_to( self.drawnCommits[self.commits[0].hexsha], - m.LEFT if self.args.reverse else m.RIGHT, + m.LEFT if Settings.reverse else m.RIGHT, buff=1.5, ) @@ -118,13 +121,13 @@ def setup_and_draw_revert_commit(self): ).next_to(circle, m.DOWN) self.toFadeOut.add(message) - if self.args.animate: + if Settings.animate: self.play( self.camera.frame.animate.move_to(circle.get_center()), m.Create(circle), m.AddTextLetterByLetter(commitId), m.AddTextLetterByLetter(message), - run_time=1 / self.args.speed, + run_time=1 / Settings.speed, ) else: self.camera.frame.move_to(circle.get_center()) @@ -133,8 +136,8 @@ def setup_and_draw_revert_commit(self): self.drawnCommits["abcdef"] = circle self.toFadeOut.add(circle) - if self.args.animate: - self.play(m.Create(arrow), run_time=1 / self.args.speed) + if Settings.animate: + self.play(m.Create(arrow), run_time=1 / Settings.speed) else: self.add(arrow) @@ -150,3 +153,13 @@ 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", + ) +): + scene = Revert(commit=commit) + handle_animations(scene=scene) diff --git a/git_sim/git_sim_stash.py b/git_sim/git_sim_stash.py index 3997512..065b50e 100644 --- a/git_sim/git_sim_stash.py +++ b/git_sim/git_sim_stash.py @@ -1,43 +1,37 @@ 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 -class GitSimStash(GitSimBaseCommand): - def __init__(self, args: Namespace): - super().__init__(args=args) +class Stash(GitSimBaseCommand): + def __init__(self, files: list[str]): + super().__init__() self.hide_first_tag = True + self.files = files try: self.selected_branches.append(self.repo.active_branch.name) except TypeError: pass - for name in self.args.name: - if name not in [x.a_path for x in self.repo.index.diff(None)] + [ + for file in self.files: + if file not in [x.a_path for x in self.repo.index.diff(None)] + [ y.a_path for y in self.repo.index.diff("HEAD") ]: - print( - "git-sim error: No modified or staged file with name: '" - + name - + "'" - ) + print(f"git-sim error: No modified or staged file with name: '{file}'") sys.exit() - if not self.args.name: - self.args.name = [x.a_path for x in self.repo.index.diff(None)] + [ + if not self.files: + self.files = [x.a_path for x in self.repo.index.diff(None)] + [ y.a_path for y in self.repo.index.diff("HEAD") ] def construct(self): - print( - "Simulating: git " + self.args.subcommand + " " + " ".join(self.args.name) - ) + print("Simulating: git stash " + " ".join(self.files)) self.show_intro() self.get_commits() @@ -61,11 +55,10 @@ def populate_zones( firstColumnArrowMap, secondColumnArrowMap, ): - for x in self.repo.index.diff(None): firstColumnFileNames.add(x.a_path) - for name in self.args.name: - if name == x.a_path: + for file in self.files: + if file == x.a_path: thirdColumnFileNames.add(x.a_path) firstColumnArrowMap[x.a_path] = m.Arrow( stroke_width=3, color=self.fontColor @@ -73,9 +66,19 @@ def populate_zones( for y in self.repo.index.diff("HEAD"): secondColumnFileNames.add(y.a_path) - for name in self.args.name: - if name == y.a_path: + for file in self.files: + if file == y.a_path: thirdColumnFileNames.add(y.a_path) secondColumnArrowMap[y.a_path] = m.Arrow( stroke_width=3, color=self.fontColor ) + + +def stash( + files: list[str] = typer.Argument( + default=None, + help="The name of the file to stash changes for", + ) +): + scene = Stash(files=files) + handle_animations(scene=scene) diff --git a/git_sim/git_sim_status.py b/git_sim/git_sim_status.py index 4a55e95..6d379a4 100644 --- a/git_sim/git_sim_status.py +++ b/git_sim/git_sim_status.py @@ -1,22 +1,17 @@ -from argparse import Namespace - +from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand +from git_sim.settings import Settings -class GitSimStatus(GitSimBaseCommand): - def __init__(self, args: Namespace): - super().__init__(args=args) - self.hide_first_tag = True - self.allow_no_commits = True - +class Status(GitSimBaseCommand): + def __init__(self): + super().__init__() try: self.selected_branches.append(self.repo.active_branch.name) except TypeError: pass def construct(self): - print("Simulating: git " + self.args.subcommand) - self.show_intro() self.get_commits() self.parse_commits(self.commits[0]) @@ -26,3 +21,11 @@ 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/git_sim_tag.py b/git_sim/git_sim_tag.py index af748ac..fdd1c38 100644 --- a/git_sim/git_sim_tag.py +++ b/git_sim/git_sim_tag.py @@ -1,16 +1,18 @@ -from argparse import Namespace - 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 -class GitSimTag(GitSimBaseCommand): - def __init__(self, args: Namespace): - super().__init__(args=args) +class Tag(GitSimBaseCommand): + def __init__(self, name: str): + super().__init__() + self.name = name def construct(self): - print("Simulating: git " + self.args.subcommand + " " + self.args.name) + print(f"{Settings.INFO_STRING} tag {self.name}") self.show_intro() self.get_commits() @@ -19,7 +21,7 @@ def construct(self): self.scale_frame() tagText = m.Text( - self.args.name, + self.name, font="Monospace", font_size=20, color=self.fontColor, @@ -37,8 +39,8 @@ def construct(self): fulltag = m.VGroup(tagRec, tagText) - if self.args.animate: - self.play(m.Create(fulltag), run_time=1 / self.args.speed) + if Settings.animate: + self.play(m.Create(fulltag), run_time=1 / Settings.speed) else: self.add(fulltag) @@ -46,3 +48,13 @@ def construct(self): 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) diff --git a/git_sim/log.py b/git_sim/log.py deleted file mode 100644 index 61904a7..0000000 --- a/git_sim/log.py +++ /dev/null @@ -1,38 +0,0 @@ -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 Log(GitSimBaseCommand): - def __init__(self, commits: int): - super().__init__() - self.numCommits = commits + 1 - self.defaultNumCommits = commits + 1 - try: - self.selected_branches.append(self.repo.active_branch.name) - except TypeError: - pass - - def construct(self): - print(Settings.INFO_STRING + type(self).__name__) - self.show_intro() - self.get_commits() - self.parse_commits(self.commits[0]) - self.recenter_frame() - self.scale_frame() - self.fadeout() - self.show_outro() - - -def log( - commits: int = typer.Option( - default=Settings.commits, - help="The number of commits to display in the simulated log output", - min=1, - max=12, - ), -): - scene = Log(commits=commits) - handle_animations(scene=scene) diff --git a/git_sim/merge.py b/git_sim/merge.py deleted file mode 100644 index 12470f5..0000000 --- a/git_sim/merge.py +++ /dev/null @@ -1,132 +0,0 @@ -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 Merge(GitSimBaseCommand): - def __init__(self, branch: str, no_ff: bool): - super().__init__() - self.branch = branch - self.no_ff = no_ff - - 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): - print(Settings.INFO_STRING + "merge " + 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) - - # Use forward slash to determine if supplied branch arg is local or remote tracking 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.setup_and_draw_parent( - self.orig_commits[0], - "Merge commit", - shift=2 * m.DOWN, - draw_arrow=False, - color=m.GRAY, - ) - self.draw_arrow_between_commits("abcdef", self.commits[0].hexsha) - self.draw_arrow_between_commits("abcdef", self.orig_commits[0].hexsha) - self.recenter_frame() - self.scale_frame() - self.reset_head_branch("abcdef") - - 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/rebase.py b/git_sim/rebase.py deleted file mode 100644 index af5eb61..0000000 --- a/git_sim/rebase.py +++ /dev/null @@ -1,190 +0,0 @@ -import sys - -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 Rebase(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) - - 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): - print(Settings.INFO_STRING + "rebase " + self.branch) - - if self.branch in self.repo.git.branch( - "--contains", self.repo.active_branch.name - ): - print( - "git-sim error: Branch '" - + self.repo.active_branch.name - + "' is already included in the history of active branch '" - + self.branch - + "'." - ) - sys.exit(1) - - if self.repo.active_branch.name in self.repo.git.branch( - "--contains", self.branch - ): - print( - "git-sim error: Branch '" - + self.branch - + "' is already based on active branch '" - + self.repo.active_branch.name - + "'." - ) - sys.exit(1) - - self.show_intro() - self.get_commits(start=self.branch) - self.parse_commits(self.commits[0]) - self.orig_commits = self.commits - self.i = 0 - self.get_commits() - - reached_base = False - for commit in self.commits: - if commit != "dark" and self.branch in self.repo.git.branch( - "--contains", commit - ): - reached_base = True - - self.parse_commits( - self.commits[0], shift=4 * m.DOWN, dots=False if reached_base else True - ) - self.center_frame_on_commit(self.orig_commits[0]) - - to_rebase = [] - i = 0 - current = self.commits[i] - while self.branch not in self.repo.git.branch("--contains", current): - to_rebase.append(current) - i += 1 - if i >= len(self.commits): - break - current = self.commits[i] - - parent = self.orig_commits[0].hexsha - - for j, tr in enumerate(reversed(to_rebase)): - if not reached_base and j == 0: - message = "..." - else: - message = tr.message - parent = self.setup_and_draw_parent(parent, message) - self.draw_arrow_between_commits(tr.hexsha, parent) - - self.recenter_frame() - self.scale_frame() - self.reset_head_branch(parent) - self.fadeout() - self.show_outro() - - def setup_and_draw_parent( - self, - child, - commitMessage="New commit", - shift=numpy.array([0.0, 0.0, 0.0]), - draw_arrow=True, - ): - circle = m.Circle(stroke_color=m.RED, fill_color=m.RED, fill_opacity=0.25) - circle.height = 1 - circle.next_to( - self.drawnCommits[child], - m.LEFT if Settings.reverse else m.RIGHT, - buff=1.5, - ) - circle.shift(shift) - - start = circle.get_center() - end = self.drawnCommits[child].get_center() - arrow = m.Arrow(start, end, color=self.fontColor) - length = numpy.linalg.norm(start - end) - (1.5 if start[1] == end[1] else 3) - arrow.set_length(length) - - sha = "".join( - chr(ord(letter) + 1) - if ( - (chr(ord(letter) + 1).isalpha() and letter < "f") - or chr(ord(letter) + 1).isdigit() - ) - else letter - for letter in child[:6] - ) - commitId = m.Text( - sha if commitMessage != "..." else "...", - font="Monospace", - font_size=20, - color=self.fontColor, - ).next_to(circle, m.UP) - self.toFadeOut.add(commitId) - - commitMessage = commitMessage[:40].replace("\n", " ") - message = m.Text( - "\n".join( - commitMessage[j : j + 20] for j in range(0, len(commitMessage), 20) - )[:100], - font="Monospace", - font_size=14, - color=self.fontColor, - ).next_to(circle, m.DOWN) - self.toFadeOut.add(message) - - if Settings.animate: - self.play( - self.camera.frame.animate.move_to(circle.get_center()), - m.Create(circle), - m.AddTextLetterByLetter(commitId), - m.AddTextLetterByLetter(message), - run_time=1 / Settings.speed, - ) - else: - self.camera.frame.move_to(circle.get_center()) - self.add(circle, commitId, message) - - self.drawnCommits[sha] = circle - self.toFadeOut.add(circle) - - if draw_arrow: - if Settings.animate: - self.play(m.Create(arrow), run_time=1 / Settings.speed) - else: - self.add(arrow) - 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 deleted file mode 100644 index 79f0e58..0000000 --- a/git_sim/reset.py +++ /dev/null @@ -1,171 +0,0 @@ -import sys -from enum import Enum - -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 - - -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 - ): - super().__init__() - self.commit = commit - self.mode = mode - - try: - self.resetTo = git.repo.fun.rev_parse(self.repo, self.commit) - except git.exc.BadName: - print( - f"git-sim error: '{self.commit}' is not a valid Git ref or identifier." - ) - sys.exit(1) - - self.commitsSinceResetTo = list(self.repo.iter_commits(self.commit + "...HEAD")) - self.hide_first_tag = True - - try: - self.selected_branches.append(self.repo.active_branch.name) - except TypeError: - pass - - if hard: - self.mode = ResetMode.HARD - if mixed: - self.mode = ResetMode.MIXED - if soft: - self.mode = ResetMode.SOFT - - def construct(self): - print( - f"{Settings.INFO_STRING} reset {' --' + self.mode.value if self.mode != ResetMode.DEFAULT else ''} {self.commit}", - ) - - self.show_intro() - self.get_commits() - self.parse_commits(self.commits[self.i]) - self.recenter_frame() - self.scale_frame() - self.reset_head_branch(self.resetTo.hexsha) - self.vsplit_frame() - self.setup_and_draw_zones(first_column_name="Changes deleted from") - self.fadeout() - self.show_outro() - - def build_commit_id_and_message(self, commit, dots=False): - hide_refs = False - if commit == "dark": - commitId = m.Text("", font="Monospace", font_size=20, color=self.fontColor) - commitMessage = "" - elif self.i == 3 and self.resetTo.hexsha not in [ - commit.hexsha for commit in self.get_nondark_commits() - ]: - commitId = m.Text( - "...", font="Monospace", font_size=20, color=self.fontColor - ) - commitMessage = "..." - hide_refs = True - elif self.i == 4 and self.resetTo.hexsha not in [ - commit.hexsha for commit in self.get_nondark_commits() - ]: - commitId = m.Text( - self.resetTo.hexsha[:6], - font="Monospace", - font_size=20, - color=self.fontColor, - ) - commitMessage = self.resetTo.message.split("\n")[0][:40].replace("\n", " ") - commit = self.resetTo - hide_refs = True - else: - commitId = m.Text( - commit.hexsha[:6], - font="Monospace", - font_size=20, - color=self.fontColor, - ) - commitMessage = commit.message.split("\n")[0][:40].replace("\n", " ") - - if ( - commit != "dark" - and commit.hexsha == self.resetTo.hexsha - and commit.hexsha != self.repo.head.commit.hexsha - ): - hide_refs = True - - return commitId, commitMessage, commit, hide_refs - - def populate_zones( - self, - firstColumnFileNames, - secondColumnFileNames, - thirdColumnFileNames, - firstColumnArrowMap={}, - secondColumnArrowMap={}, - ): - for commit in self.commitsSinceResetTo: - if commit.hexsha == self.resetTo.hexsha: - break - for filename in commit.stats.files: - if self.mode == ResetMode.SOFT: - thirdColumnFileNames.add(filename) - elif self.mode in (ResetMode.MIXED, ResetMode.DEFAULT): - secondColumnFileNames.add(filename) - elif self.mode == ResetMode.HARD: - firstColumnFileNames.add(filename) - - for x in self.repo.index.diff(None): - if "git-sim_media" not in x.a_path: - if self.mode == ResetMode.SOFT: - secondColumnFileNames.add(x.a_path) - elif self.mode in (ResetMode.MIXED, ResetMode.DEFAULT): - secondColumnFileNames.add(x.a_path) - elif self.mode == ResetMode.HARD: - firstColumnFileNames.add(x.a_path) - - for y in self.repo.index.diff("HEAD"): - if "git-sim_media" not in y.a_path: - if self.mode == ResetMode.SOFT: - thirdColumnFileNames.add(y.a_path) - elif self.mode in (ResetMode.MIXED, ResetMode.DEFAULT): - 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", - ), -): - 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 deleted file mode 100644 index 8efb0eb..0000000 --- a/git_sim/restore.py +++ /dev/null @@ -1,78 +0,0 @@ -import sys - -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 - - -class Restore(GitSimBaseCommand): - def __init__(self, files: list[str]): - super().__init__() - self.hide_first_tag = True - self.files = files - - try: - self.selected_branches.append(self.repo.active_branch.name) - except TypeError: - pass - - for file in self.files: - if file not in [x.a_path for x in self.repo.index.diff(None)] + [ - y.a_path for y in self.repo.index.diff("HEAD") - ]: - print(f"git-sim error: No modified or staged file with name: '{file}'") - sys.exit() - - def construct(self): - print(Settings.INFO_STRING + "restore " + " ".join(self.files)) - - self.show_intro() - self.get_commits() - self.parse_commits(self.commits[0]) - self.recenter_frame() - self.scale_frame() - self.vsplit_frame() - self.setup_and_draw_zones(reverse=True) - self.fadeout() - self.show_outro() - - def populate_zones( - self, - firstColumnFileNames, - secondColumnFileNames, - thirdColumnFileNames, - firstColumnArrowMap, - secondColumnArrowMap, - ): - for x in self.repo.index.diff(None): - if "git-sim_media" not in x.a_path: - secondColumnFileNames.add(x.a_path) - for file in self.files: - if file == x.a_path: - thirdColumnFileNames.add(x.a_path) - secondColumnArrowMap[x.a_path] = m.Arrow( - stroke_width=3, color=self.fontColor - ) - - for y in self.repo.index.diff("HEAD"): - if "git-sim_media" not in y.a_path: - firstColumnFileNames.add(y.a_path) - for file in self.files: - if file == y.a_path: - secondColumnFileNames.add(y.a_path) - 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", - ) -): - scene = Restore(files=files) - handle_animations(scene=scene) diff --git a/git_sim/revert.py b/git_sim/revert.py deleted file mode 100644 index 8ccc465..0000000 --- a/git_sim/revert.py +++ /dev/null @@ -1,165 +0,0 @@ -import sys - -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 Revert(GitSimBaseCommand): - def __init__(self, commit: str): - super().__init__() - self.commit = commit - - try: - self.revert = git.repo.fun.rev_parse(self.repo, self.commit) - except git.exc.BadName: - print( - "git-sim error: '" - + self.commit - + "' is not a valid Git ref or identifier." - ) - sys.exit(1) - - self.defaultNumCommits = 4 - self.numCommits = 4 - self.hide_first_tag = True - self.zone_title_offset += 0.1 - - try: - self.selected_branches.append(self.repo.active_branch.name) - except TypeError: - pass - - def construct(self): - print(Settings.INFO_STRING + "revert " + self.commit) - - self.show_intro() - self.get_commits() - self.parse_commits(self.commits[self.i]) - self.center_frame_on_commit(self.commits[0]) - self.setup_and_draw_revert_commit() - self.recenter_frame() - self.scale_frame() - self.reset_head_branch("abcdef") - self.vsplit_frame() - self.setup_and_draw_zones( - first_column_name="----", - second_column_name="Changes reverted from", - third_column_name="----", - ) - self.fadeout() - self.show_outro() - - def build_commit_id_and_message(self, commit, dots=False): - hide_refs = False - if commit == "dark": - commitId = m.Text("", font="Monospace", font_size=20, color=self.fontColor) - commitMessage = "" - elif self.i == 2 and self.revert.hexsha not in [ - commit.hexsha for commit in self.commits - ]: - commitId = m.Text( - "...", font="Monospace", font_size=20, color=self.fontColor - ) - commitMessage = "..." - hide_refs = True - elif self.i == 3 and self.revert.hexsha not in [ - commit.hexsha for commit in self.commits - ]: - commitId = m.Text( - self.revert.hexsha[:6], - font="Monospace", - font_size=20, - color=self.fontColor, - ) - commitMessage = self.revert.message.split("\n")[0][:40].replace("\n", " ") - hide_refs = True - else: - commitId = m.Text( - commit.hexsha[:6], - font="Monospace", - font_size=20, - color=self.fontColor, - ) - commitMessage = commit.message.split("\n")[0][:40].replace("\n", " ") - return commitId, commitMessage, commit, hide_refs - - def setup_and_draw_revert_commit(self): - circle = m.Circle(stroke_color=m.RED, fill_color=m.RED, fill_opacity=0.25) - circle.height = 1 - circle.next_to( - self.drawnCommits[self.commits[0].hexsha], - m.LEFT if Settings.reverse else m.RIGHT, - buff=1.5, - ) - - start = circle.get_center() - end = self.drawnCommits[self.commits[0].hexsha].get_center() - arrow = m.Arrow(start, end, color=self.fontColor) - length = numpy.linalg.norm(start - end) - (1.5 if start[1] == end[1] else 3) - arrow.set_length(length) - - commitId = m.Text( - "abcdef", font="Monospace", font_size=20, color=self.fontColor - ).next_to(circle, m.UP) - self.toFadeOut.add(commitId) - - commitMessage = "Revert " + self.revert.hexsha[0:6] - commitMessage = commitMessage[:40].replace("\n", " ") - message = m.Text( - "\n".join( - commitMessage[j : j + 20] for j in range(0, len(commitMessage), 20) - )[:100], - font="Monospace", - font_size=14, - color=self.fontColor, - ).next_to(circle, m.DOWN) - self.toFadeOut.add(message) - - if Settings.animate: - self.play( - self.camera.frame.animate.move_to(circle.get_center()), - m.Create(circle), - m.AddTextLetterByLetter(commitId), - m.AddTextLetterByLetter(message), - run_time=1 / Settings.speed, - ) - else: - self.camera.frame.move_to(circle.get_center()) - self.add(circle, commitId, message) - - self.drawnCommits["abcdef"] = circle - self.toFadeOut.add(circle) - - if Settings.animate: - self.play(m.Create(arrow), run_time=1 / Settings.speed) - else: - self.add(arrow) - - self.toFadeOut.add(arrow) - - def populate_zones( - self, - firstColumnFileNames, - secondColumnFileNames, - thirdColumnFileNames, - firstColumnArrowMap={}, - secondColumnArrowMap={}, - ): - 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", - ) -): - scene = Revert(commit=commit) - handle_animations(scene=scene) diff --git a/git_sim/stash.py b/git_sim/stash.py deleted file mode 100644 index 065b50e..0000000 --- a/git_sim/stash.py +++ /dev/null @@ -1,84 +0,0 @@ -import sys - -import manim as m -import typer - -from git_sim.animations import handle_animations -from git_sim.git_sim_base_command import GitSimBaseCommand - - -class Stash(GitSimBaseCommand): - def __init__(self, files: list[str]): - super().__init__() - self.hide_first_tag = True - self.files = files - - try: - self.selected_branches.append(self.repo.active_branch.name) - except TypeError: - pass - - for file in self.files: - if file not in [x.a_path for x in self.repo.index.diff(None)] + [ - y.a_path for y in self.repo.index.diff("HEAD") - ]: - print(f"git-sim error: No modified or staged file with name: '{file}'") - sys.exit() - - if not self.files: - self.files = [x.a_path for x in self.repo.index.diff(None)] + [ - y.a_path for y in self.repo.index.diff("HEAD") - ] - - def construct(self): - print("Simulating: git stash " + " ".join(self.files)) - - self.show_intro() - self.get_commits() - self.parse_commits(self.commits[0]) - self.recenter_frame() - self.scale_frame() - self.vsplit_frame() - self.setup_and_draw_zones( - first_column_name="Working directory", - second_column_name="Staging area", - third_column_name="Stashed changes", - ) - self.fadeout() - self.show_outro() - - def populate_zones( - self, - firstColumnFileNames, - secondColumnFileNames, - thirdColumnFileNames, - firstColumnArrowMap, - secondColumnArrowMap, - ): - for x in self.repo.index.diff(None): - firstColumnFileNames.add(x.a_path) - for file in self.files: - if file == x.a_path: - thirdColumnFileNames.add(x.a_path) - firstColumnArrowMap[x.a_path] = m.Arrow( - stroke_width=3, color=self.fontColor - ) - - for y in self.repo.index.diff("HEAD"): - secondColumnFileNames.add(y.a_path) - for file in self.files: - if file == y.a_path: - thirdColumnFileNames.add(y.a_path) - secondColumnArrowMap[y.a_path] = m.Arrow( - stroke_width=3, color=self.fontColor - ) - - -def stash( - files: list[str] = typer.Argument( - default=None, - help="The name of the file to stash changes for", - ) -): - scene = Stash(files=files) - handle_animations(scene=scene) diff --git a/git_sim/status.py b/git_sim/status.py deleted file mode 100644 index 6d379a4..0000000 --- a/git_sim/status.py +++ /dev/null @@ -1,31 +0,0 @@ -from git_sim.animations import handle_animations -from git_sim.git_sim_base_command import GitSimBaseCommand -from git_sim.settings import Settings - - -class Status(GitSimBaseCommand): - def __init__(self): - super().__init__() - try: - self.selected_branches.append(self.repo.active_branch.name) - except TypeError: - pass - - def construct(self): - self.show_intro() - self.get_commits() - self.parse_commits(self.commits[0]) - self.recenter_frame() - self.scale_frame() - self.vsplit_frame() - 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/tag.py b/git_sim/tag.py deleted file mode 100644 index fdd1c38..0000000 --- a/git_sim/tag.py +++ /dev/null @@ -1,60 +0,0 @@ -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 - - -class Tag(GitSimBaseCommand): - def __init__(self, name: str): - super().__init__() - self.name = name - - def construct(self): - print(f"{Settings.INFO_STRING} tag {self.name}") - - self.show_intro() - self.get_commits() - self.parse_commits(self.commits[0]) - self.recenter_frame() - self.scale_frame() - - tagText = m.Text( - self.name, - font="Monospace", - font_size=20, - color=self.fontColor, - ) - tagRec = m.Rectangle( - color=m.YELLOW, - fill_color=m.YELLOW, - fill_opacity=0.25, - height=0.4, - width=tagText.width + 0.25, - ) - - tagRec.next_to(self.topref, m.UP) - tagText.move_to(tagRec.get_center()) - - fulltag = m.VGroup(tagRec, tagText) - - if Settings.animate: - self.play(m.Create(fulltag), run_time=1 / Settings.speed) - else: - self.add(fulltag) - - self.toFadeOut.add(tagRec, tagText) - - 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 7a747b64265265d2c55560ef34f63a3f013c3534 Mon Sep 17 00:00:00 2001 From: manu Date: Thu, 9 Feb 2023 09:34:46 +0100 Subject: [PATCH 27/27] update deps in setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 05fb3d5..109edfe 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ "gitpython", "manim", "opencv-python-headless", + "typer", ], keywords="git sim simulation simulate git-simulate git-simulation git-sim manim animation gitanimation image video dryrun dry-run", project_urls={