From cbd990d3f0b300eb8530f7e63267616e349e7985 Mon Sep 17 00:00:00 2001 From: manu Date: Thu, 9 Feb 2023 23:41:47 +0100 Subject: [PATCH 01/25] Use Typer instead of argparse (#49) Refactor codebase to swap CLI argument handling from argparse to Typer - Migrate settings into new settings.py dataclass - Migrate global arguments into animations.py - Move subcommand arguments into respective modules - Class name simplification - Assorted code reformating and cleanup --- git_sim/__main__.py | 478 +++++++++----------------------- git_sim/animations.py | 92 ++++++ git_sim/git_sim_add.py | 41 +-- git_sim/git_sim_base_command.py | 131 +++++---- git_sim/git_sim_branch.py | 32 ++- git_sim/git_sim_cherrypick.py | 53 ++-- git_sim/git_sim_commit.py | 51 ++-- git_sim/git_sim_log.py | 29 +- git_sim/git_sim_merge.py | 59 ++-- git_sim/git_sim_rebase.py | 55 ++-- git_sim/git_sim_reset.py | 95 ++++--- git_sim/git_sim_restore.py | 45 +-- git_sim/git_sim_revert.py | 38 ++- git_sim/git_sim_stash.py | 50 ++-- git_sim/git_sim_status.py | 24 +- git_sim/git_sim_tag.py | 30 +- git_sim/settings.py | 41 +++ setup.py | 3 +- 18 files changed, 700 insertions(+), 647 deletions(-) create mode 100644 git_sim/animations.py create mode 100644 git_sim/settings.py diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 93291a4..775ade1 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -5,368 +5,138 @@ 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", - help="Custom title to display at the beginning of the animation", - type=str, - default="Git Sim, by initialcommit.com", - ) - parser.add_argument( - "--logo", +import typer + +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() + + +@app.callback(no_args_is_help=True) +def main( + animate: bool = typer.Option( + Settings.animate, + help="Animate the simulation and output as an mp4 video", + ), + auto_open: bool = typer.Option( + 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( + Settings.light_mode, + "--light-mode", + help="Enable light-mode with white background", + ), + logo: pathlib.Path = typer.Option( + Settings.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: bool = typer.Option( + Settings.low_quality, "--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", + ), + max_branches_per_commit: int = typer.Option( + Settings.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", + ), + max_tags_per_commit: int = typer.Option( + Settings.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", + ), + 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( + Settings.reverse, "--reverse", + "-r", 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." - ) + ), + 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( + Settings.video_format.value, + help="Output format for the animation files.", + case_sensitive=False, + ), +): + Settings.animate = animate + Settings.auto_open = auto_open + Settings.img_format = img_format + Settings.light_mode = light_mode + Settings.logo = logo + 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 + + +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__": - main() + app() diff --git a/git_sim/animations.py b/git_sim/animations.py new file mode 100644 index 0000000..ef07ca4 --- /dev/null +++ b/git_sim/animations.py @@ -0,0 +1,92 @@ +import datetime +import inspect +import os +import pathlib +import subprocess +import sys +import time + +import cv2 +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] + + config.media_dir = os.path.join( + os.path.expanduser(Settings.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 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: + image_file_name = ( + "git-sim-" + + inspect.stack()[1].function + + "_" + + t + + "." + + Settings.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 Settings.auto_open: + try: + 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." + ) diff --git a/git_sim/git_sim_add.py b/git_sim/git_sim_add.py index 0dc2e52..5c89bda 100644 --- a/git_sim/git_sim_add.py +++ b/git_sim/git_sim_add.py @@ -1,35 +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) - self.maxrefs = 2 +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() @@ -49,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 @@ -71,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 22bbda1..98be3e2 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -1,20 +1,21 @@ 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): +class GitSimBaseCommand(m.MovingCameraScene): + 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 +25,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 +51,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 +76,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 +108,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 +134,13 @@ 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 +148,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 +158,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 +174,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 +190,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 +231,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 +288,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 +340,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,17 +352,16 @@ 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: - try: if commit.hexsha == tag.commit.hexsha: tagText = m.Text( @@ -387,11 +383,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 +398,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 +444,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 +454,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 +566,7 @@ def setup_and_draw_zones( thirdColumnTitle, ) - if self.args.animate: + if Settings.animate: self.play( m.Create(horizontal), m.Create(horizontal2), @@ -663,19 +659,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 +703,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 +722,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]) @@ -742,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) @@ -761,7 +756,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 +766,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 +800,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 +817,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 +844,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 +860,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 +901,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 +915,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/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 aec397a..7dd7b00 100644 --- a/git_sim/git_sim_commit.py +++ b/git_sim/git_sim_commit.py @@ -1,18 +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.maxrefs = 2 - 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: @@ -20,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." ) @@ -28,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( @@ -65,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( @@ -85,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) @@ -97,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 914a9fe..12470f5 100644 --- a/git_sim/git_sim_merge.py +++ b/git_sim/git_sim_merge.py @@ -4,28 +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 - 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]) + 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) @@ -33,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 + "'." @@ -50,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" @@ -81,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"], @@ -95,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( @@ -113,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 4762772..79f0e58 100644 --- a/git_sim/git_sim_reset.py +++ b/git_sim/git_sim_reset.py @@ -1,30 +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.maxrefs = 2 + self.commitsSinceResetTo = list(self.repo.iter_commits(self.commit + "...HEAD")) self.hide_first_tag = True try: @@ -32,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() @@ -114,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 151d0e0..8efb0eb 100644 --- a/git_sim/git_sim_restore.py +++ b/git_sim/git_sim_restore.py @@ -1,37 +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) - self.maxrefs = 2 +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() @@ -51,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 @@ -65,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 460b88d..8ccc465 100644 --- a/git_sim/git_sim_revert.py +++ b/git_sim/git_sim_revert.py @@ -1,28 +1,30 @@ 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) - self.maxrefs = 2 self.defaultNumCommits = 4 self.numCommits = 4 self.hide_first_tag = True @@ -34,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() @@ -92,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, ) @@ -119,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()) @@ -134,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) @@ -151,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 445a3d0..065b50e 100644 --- a/git_sim/git_sim_stash.py +++ b/git_sim/git_sim_stash.py @@ -1,44 +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) - self.maxrefs = 2 +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() @@ -62,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 @@ -74,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 9d857ec..6d379a4 100644 --- a/git_sim/git_sim_status.py +++ b/git_sim/git_sim_status.py @@ -1,23 +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.maxrefs = 2 - 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]) @@ -27,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/settings.py b/git_sim/settings.py new file mode 100644 index 0000000..02566a7 --- /dev/null +++ b/git_sim/settings.py @@ -0,0 +1,41 @@ +import pathlib +from dataclasses import dataclass +from enum import Enum + + +class VideoFormat(str, Enum): + mp4 = "mp4" + webm = "webm" + + +class ImgFormat(str, Enum): + jpg = "jpg" + png = "png" + + +@dataclass +class Settings: + commits = 5 + subcommand: str + 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 " + # 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 + video_format: VideoFormat = VideoFormat.mp4 + img_format: ImgFormat = ImgFormat.jpg diff --git a/setup.py b/setup.py index 74d6f9d..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={ @@ -31,7 +32,7 @@ }, entry_points={ "console_scripts": [ - "git-sim=git_sim.__main__:main", + "git-sim=git_sim.__main__:app", ], }, include_package_data=True, From 77b22743a846b38008b8cdcc219c956ef946716c Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Thu, 9 Feb 2023 14:47:19 -0800 Subject: [PATCH 02/25] Fix import bug Signed-off-by: Jacob Stopak --- git_sim/git_sim_commit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_sim/git_sim_commit.py b/git_sim/git_sim_commit.py index 7dd7b00..b761884 100644 --- a/git_sim/git_sim_commit.py +++ b/git_sim/git_sim_commit.py @@ -3,7 +3,7 @@ import git import manim as m import typer -from animations import handle_animations +from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand From fc8be6468cc7eb5b58187560fc73f48bc497badc Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Thu, 9 Feb 2023 16:29:16 -0800 Subject: [PATCH 03/25] Make subcommand output text consistent Signed-off-by: Jacob Stopak --- git_sim/git_sim_add.py | 4 +++- git_sim/git_sim_base_command.py | 4 ++-- git_sim/git_sim_branch.py | 2 +- git_sim/git_sim_cherrypick.py | 8 ++++---- git_sim/git_sim_commit.py | 6 +++++- git_sim/git_sim_log.py | 2 +- git_sim/git_sim_merge.py | 4 +++- git_sim/git_sim_rebase.py | 2 +- git_sim/git_sim_reset.py | 2 +- git_sim/git_sim_restore.py | 4 +++- git_sim/git_sim_revert.py | 2 +- git_sim/git_sim_stash.py | 6 +++++- git_sim/git_sim_status.py | 1 + git_sim/git_sim_tag.py | 2 +- git_sim/settings.py | 2 +- 15 files changed, 33 insertions(+), 18 deletions(-) diff --git a/git_sim/git_sim_add.py b/git_sim/git_sim_add.py index 5c89bda..2989e7d 100644 --- a/git_sim/git_sim_add.py +++ b/git_sim/git_sim_add.py @@ -29,7 +29,9 @@ def __init__(self, files: list[str]): sys.exit() def construct(self): - print(Settings.INFO_STRING + "add " + " ".join(self.files)) + print( + f"{Settings.INFO_STRING} {type(self).__name__.lower()} {' '.join(self.files)}" + ) self.show_intro() self.get_commits() diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index 98be3e2..f9726b5 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -42,8 +42,8 @@ def init_repo(self): print("git-sim error: No Git repository found at current path.") sys.exit(1) - def execute(self): - print("Simulating: git " + Settings.subcommand) + def construct(self): + print(f"{Settings.INFO_STRING} {type(self).__name__.lower()}") self.show_intro() self.get_commits() self.fadeout() diff --git a/git_sim/git_sim_branch.py b/git_sim/git_sim_branch.py index 19cf467..646bb47 100644 --- a/git_sim/git_sim_branch.py +++ b/git_sim/git_sim_branch.py @@ -12,7 +12,7 @@ def __init__(self, name: str): self.name = name def construct(self): - print(f"{Settings.INFO_STRING} branch {self.name}") + print(f"{Settings.INFO_STRING} {type(self).__name__.lower()} {self.name}") self.show_intro() self.get_commits() diff --git a/git_sim/git_sim_cherrypick.py b/git_sim/git_sim_cherrypick.py index 8db3e41..28697f0 100644 --- a/git_sim/git_sim_cherrypick.py +++ b/git_sim/git_sim_cherrypick.py @@ -34,10 +34,10 @@ def __init__(self, commit: str, edit: str): pass def construct(self): - substring = "" - if self.edit: - substring = f' -e "{self.edit}"' - print(f"{Settings.INFO_STRING} cherrypick {self.commit}{substring}") + print( + f"{Settings.INFO_STRING} {type(self).__name__.lower()} {self.commit}" + + ((' -e "' + self.edit + '"') if self.edit else "") + ) if self.repo.active_branch.name in self.repo.git.branch( "--contains", self.commit diff --git a/git_sim/git_sim_commit.py b/git_sim/git_sim_commit.py index b761884..05087f6 100644 --- a/git_sim/git_sim_commit.py +++ b/git_sim/git_sim_commit.py @@ -4,6 +4,7 @@ import manim as m import typer from git_sim.animations import handle_animations +from git_sim.settings import Settings from git_sim.git_sim_base_command import GitSimBaseCommand @@ -31,7 +32,10 @@ def __init__(self, message: str, amend: bool): def construct(self): print( - f"Simulating: git commit {' --amend' if self.amend else ''} -m '{self.message}'" + f"{Settings.INFO_STRING} {type(self).__name__.lower()} {'--amend ' if self.amend else ''}" + + '-m "' + + self.message + + '"' ) self.show_intro() diff --git a/git_sim/git_sim_log.py b/git_sim/git_sim_log.py index 61904a7..46deaf1 100644 --- a/git_sim/git_sim_log.py +++ b/git_sim/git_sim_log.py @@ -16,7 +16,7 @@ def __init__(self, commits: int): pass def construct(self): - print(Settings.INFO_STRING + type(self).__name__) + print(f"{Settings.INFO_STRING} {type(self).__name__.lower()}") self.show_intro() self.get_commits() self.parse_commits(self.commits[0]) diff --git a/git_sim/git_sim_merge.py b/git_sim/git_sim_merge.py index 12470f5..3863dc6 100644 --- a/git_sim/git_sim_merge.py +++ b/git_sim/git_sim_merge.py @@ -37,7 +37,9 @@ def __init__(self, branch: str, no_ff: bool): pass def construct(self): - print(Settings.INFO_STRING + "merge " + self.branch) + print( + f"{Settings.INFO_STRING} {type(self).__name__.lower()} {self.branch} {'--no-ff' if self.no_ff else ''}" + ) if self.repo.active_branch.name in self.repo.git.branch( "--contains", self.branch diff --git a/git_sim/git_sim_rebase.py b/git_sim/git_sim_rebase.py index af5eb61..97e76da 100644 --- a/git_sim/git_sim_rebase.py +++ b/git_sim/git_sim_rebase.py @@ -34,7 +34,7 @@ def __init__(self, branch: str): pass def construct(self): - print(Settings.INFO_STRING + "rebase " + self.branch) + print(f"{Settings.INFO_STRING} {type(self).__name__.lower()} {self.branch}") if self.branch in self.repo.git.branch( "--contains", self.repo.active_branch.name diff --git a/git_sim/git_sim_reset.py b/git_sim/git_sim_reset.py index 79f0e58..c680ffb 100644 --- a/git_sim/git_sim_reset.py +++ b/git_sim/git_sim_reset.py @@ -50,7 +50,7 @@ def __init__( def construct(self): print( - f"{Settings.INFO_STRING} reset {' --' + self.mode.value if self.mode != ResetMode.DEFAULT else ''} {self.commit}", + f"{Settings.INFO_STRING} {type(self).__name__.lower()}{' --' + self.mode.value if self.mode != ResetMode.DEFAULT else ''} {self.commit}", ) self.show_intro() diff --git a/git_sim/git_sim_restore.py b/git_sim/git_sim_restore.py index 8efb0eb..44724e4 100644 --- a/git_sim/git_sim_restore.py +++ b/git_sim/git_sim_restore.py @@ -27,7 +27,9 @@ def __init__(self, files: list[str]): sys.exit() def construct(self): - print(Settings.INFO_STRING + "restore " + " ".join(self.files)) + print( + f"{Settings.INFO_STRING} {type(self).__name__.lower()} {' '.join(self.files)}" + ) self.show_intro() self.get_commits() diff --git a/git_sim/git_sim_revert.py b/git_sim/git_sim_revert.py index 8ccc465..dbeafcb 100644 --- a/git_sim/git_sim_revert.py +++ b/git_sim/git_sim_revert.py @@ -36,7 +36,7 @@ def __init__(self, commit: str): pass def construct(self): - print(Settings.INFO_STRING + "revert " + self.commit) + print(f"{Settings.INFO_STRING} {type(self).__name__.lower()} {self.commit}") self.show_intro() self.get_commits() diff --git a/git_sim/git_sim_stash.py b/git_sim/git_sim_stash.py index 065b50e..8ec2fdf 100644 --- a/git_sim/git_sim_stash.py +++ b/git_sim/git_sim_stash.py @@ -5,6 +5,7 @@ from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand +from git_sim.settings import Settings class Stash(GitSimBaseCommand): @@ -12,6 +13,7 @@ def __init__(self, files: list[str]): super().__init__() self.hide_first_tag = True self.files = files + self.no_files = True if not self.files else False try: self.selected_branches.append(self.repo.active_branch.name) @@ -31,7 +33,9 @@ def __init__(self, files: list[str]): ] def construct(self): - print("Simulating: git stash " + " ".join(self.files)) + print( + f"{Settings.INFO_STRING} {type(self).__name__.lower()} {' '.join(self.files) if not self.no_files else ''}" + ) self.show_intro() self.get_commits() diff --git a/git_sim/git_sim_status.py b/git_sim/git_sim_status.py index 6d379a4..17fc71e 100644 --- a/git_sim/git_sim_status.py +++ b/git_sim/git_sim_status.py @@ -12,6 +12,7 @@ def __init__(self): pass def construct(self): + print(f"{Settings.INFO_STRING} {type(self).__name__.lower()}") self.show_intro() self.get_commits() self.parse_commits(self.commits[0]) diff --git a/git_sim/git_sim_tag.py b/git_sim/git_sim_tag.py index fdd1c38..2d4dd6d 100644 --- a/git_sim/git_sim_tag.py +++ b/git_sim/git_sim_tag.py @@ -12,7 +12,7 @@ def __init__(self, name: str): self.name = name def construct(self): - print(f"{Settings.INFO_STRING} tag {self.name}") + print(f"{Settings.INFO_STRING} {type(self).__name__.lower()} {self.name}") self.show_intro() self.get_commits() diff --git a/git_sim/settings.py b/git_sim/settings.py index 02566a7..219aa4c 100644 --- a/git_sim/settings.py +++ b/git_sim/settings.py @@ -32,7 +32,7 @@ class Settings: allow_no_commits = False low_quality = False auto_open = True - INFO_STRING = "Simulating: git " + 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 cb262b103a5bb0f90c1d22f12efa7513efa68415 Mon Sep 17 00:00:00 2001 From: Averagess <45748163+Averagess@users.noreply.github.com> Date: Fri, 10 Feb 2023 02:35:48 +0200 Subject: [PATCH 04/25] Create Dockerfile (#50) Create dockerfile --- Dockerfile | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4a4cd4b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3 + +WORKDIR /usr/src/git-sim + +RUN apt update + +RUN apt -y install build-essential python3-dev libcairo2-dev libpango1.0-dev ffmpeg + +RUN pip3 install manim + +RUN pip3 install git-sim + +ENTRYPOINT [ "git-sim" ] \ No newline at end of file From 98c374cf7568a7f86cd9f2bbcfdf2b89ee9976f6 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Thu, 9 Feb 2023 16:46:20 -0800 Subject: [PATCH 05/25] Shorten subcommand module names to match class names Signed-off-by: Jacob Stopak --- git_sim/__main__.py | 52 +++++++++---------- git_sim/{git_sim_add.py => add.py} | 0 git_sim/{git_sim_branch.py => branch.py} | 0 .../{git_sim_cherrypick.py => cherrypick.py} | 0 git_sim/{git_sim_commit.py => commit.py} | 0 git_sim/{git_sim_log.py => log.py} | 0 git_sim/{git_sim_merge.py => merge.py} | 0 git_sim/{git_sim_rebase.py => rebase.py} | 0 git_sim/{git_sim_reset.py => reset.py} | 0 git_sim/{git_sim_restore.py => restore.py} | 0 git_sim/{git_sim_revert.py => revert.py} | 0 git_sim/{git_sim_stash.py => stash.py} | 0 git_sim/{git_sim_status.py => status.py} | 0 git_sim/{git_sim_tag.py => tag.py} | 0 14 files changed, 26 insertions(+), 26 deletions(-) rename git_sim/{git_sim_add.py => add.py} (100%) rename git_sim/{git_sim_branch.py => branch.py} (100%) rename git_sim/{git_sim_cherrypick.py => cherrypick.py} (100%) rename git_sim/{git_sim_commit.py => commit.py} (100%) rename git_sim/{git_sim_log.py => log.py} (100%) rename git_sim/{git_sim_merge.py => merge.py} (100%) rename git_sim/{git_sim_rebase.py => rebase.py} (100%) rename git_sim/{git_sim_reset.py => reset.py} (100%) rename git_sim/{git_sim_restore.py => restore.py} (100%) rename git_sim/{git_sim_revert.py => revert.py} (100%) rename git_sim/{git_sim_stash.py => stash.py} (100%) rename git_sim/{git_sim_status.py => status.py} (100%) rename git_sim/{git_sim_tag.py => tag.py} (100%) diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 775ade1..c3580bc 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -8,19 +8,19 @@ import typer -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 +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 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.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) +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__": diff --git a/git_sim/git_sim_add.py b/git_sim/add.py similarity index 100% rename from git_sim/git_sim_add.py rename to git_sim/add.py diff --git a/git_sim/git_sim_branch.py b/git_sim/branch.py similarity index 100% rename from git_sim/git_sim_branch.py rename to git_sim/branch.py diff --git a/git_sim/git_sim_cherrypick.py b/git_sim/cherrypick.py similarity index 100% rename from git_sim/git_sim_cherrypick.py rename to git_sim/cherrypick.py diff --git a/git_sim/git_sim_commit.py b/git_sim/commit.py similarity index 100% rename from git_sim/git_sim_commit.py rename to git_sim/commit.py diff --git a/git_sim/git_sim_log.py b/git_sim/log.py similarity index 100% rename from git_sim/git_sim_log.py rename to git_sim/log.py diff --git a/git_sim/git_sim_merge.py b/git_sim/merge.py similarity index 100% rename from git_sim/git_sim_merge.py rename to git_sim/merge.py diff --git a/git_sim/git_sim_rebase.py b/git_sim/rebase.py similarity index 100% rename from git_sim/git_sim_rebase.py rename to git_sim/rebase.py diff --git a/git_sim/git_sim_reset.py b/git_sim/reset.py similarity index 100% rename from git_sim/git_sim_reset.py rename to git_sim/reset.py diff --git a/git_sim/git_sim_restore.py b/git_sim/restore.py similarity index 100% rename from git_sim/git_sim_restore.py rename to git_sim/restore.py diff --git a/git_sim/git_sim_revert.py b/git_sim/revert.py similarity index 100% rename from git_sim/git_sim_revert.py rename to git_sim/revert.py diff --git a/git_sim/git_sim_stash.py b/git_sim/stash.py similarity index 100% rename from git_sim/git_sim_stash.py rename to git_sim/stash.py diff --git a/git_sim/git_sim_status.py b/git_sim/status.py similarity index 100% rename from git_sim/git_sim_status.py rename to git_sim/status.py diff --git a/git_sim/git_sim_tag.py b/git_sim/tag.py similarity index 100% rename from git_sim/git_sim_tag.py rename to git_sim/tag.py From ad81da9fe39e6d893eef65194060eaffaa300f00 Mon Sep 17 00:00:00 2001 From: Jacob Stopak <49353917+initialcommit-io@users.noreply.github.com> Date: Thu, 9 Feb 2023 17:42:28 -0800 Subject: [PATCH 06/25] Update README Add details for installing with Docker --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 7014a1d..4ace4c8 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Example: `$ git-sim merge ` - Animation only: Speed up or slow down animation speed as desired ## Quickstart +Optional: If you prefer using Docker, skip steps (1) and (2) here and jump to the [Docker installation](#docker-installation) section below, then come back here to step (3). 1) **Install Manim and its dependencies for your OS / environment:** - [Install Manim on Windows](https://docs.manim.community/en/stable/installation/windows.html) @@ -376,6 +377,33 @@ See **Quickstart** section for details on installing manim and other dependencie $ pip3 install git-sim ``` +## Docker installation + +1) Clone down the git-sim repository: + +```console +$ git clone https://github.com/initialcommit-com/git-sim.git +``` + +2) Browse into the `git-sim` folder and build the Docker image: + +```console +$ docker build -t git-sim . +``` + +3) Run git-sim commands as follows: + - Windows: `docker run --rm -v %cd%:/usr/src/git-sim git-sim [global options] [subcommand options]` + - MacOS / Linux: `docker run --rm -v $(pwd):/usr/src/git-sim git-sim [global options] [subcommand options]` + +Optional: On MacOS / Linux / or GitBash in Windows, create an alias for the long docker command so your can run it as a normal `git-sim` command. To do so add the following to your `.bashrc` or equivalent (then restart your terminal): + +
.bashrc
+```bash +git-sim() { docker run --rm -v $(pwd):/usr/src/git-sim git-sim "$@" } +``` + +This will enable you to run git-sim subcommands as [described above](#commands). + ## Learn More Learn more about this tool on the [git-sim project page](https://initialcommit.com/tools/git-sim). From 0f485265b2323a32a4eccd3034dfc6d5fc7cfbdf Mon Sep 17 00:00:00 2001 From: Jacob Stopak <49353917+initialcommit-io@users.noreply.github.com> Date: Thu, 9 Feb 2023 17:45:54 -0800 Subject: [PATCH 07/25] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4ace4c8..f92c99f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Example: `$ git-sim merge ` - Animation only: Speed up or slow down animation speed as desired ## Quickstart -Optional: If you prefer using Docker, skip steps (1) and (2) here and jump to the [Docker installation](#docker-installation) section below, then come back here to step (3). + \*Optional: If you prefer using Docker, skip steps (1) and (2) here and jump to the [Docker installation](#docker-installation) section below, then come back here to step (3).\* 1) **Install Manim and its dependencies for your OS / environment:** - [Install Manim on Windows](https://docs.manim.community/en/stable/installation/windows.html) @@ -395,9 +395,8 @@ $ docker build -t git-sim . - Windows: `docker run --rm -v %cd%:/usr/src/git-sim git-sim [global options] [subcommand options]` - MacOS / Linux: `docker run --rm -v $(pwd):/usr/src/git-sim git-sim [global options] [subcommand options]` -Optional: On MacOS / Linux / or GitBash in Windows, create an alias for the long docker command so your can run it as a normal `git-sim` command. To do so add the following to your `.bashrc` or equivalent (then restart your terminal): +Optional: On MacOS / Linux / or GitBash in Windows, create an alias for the long docker command so your can run it as a normal `git-sim` command. To do so add the following line to your `.bashrc` or equivalent, then restart your terminal: -
.bashrc
```bash git-sim() { docker run --rm -v $(pwd):/usr/src/git-sim git-sim "$@" } ``` From 1fd4920b0185cef07f202732470fbeba92a2828d Mon Sep 17 00:00:00 2001 From: Jacob Stopak <49353917+initialcommit-io@users.noreply.github.com> Date: Thu, 9 Feb 2023 17:48:19 -0800 Subject: [PATCH 08/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f92c99f..6edda2c 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Example: `$ git-sim merge ` - Animation only: Speed up or slow down animation speed as desired ## Quickstart - \*Optional: If you prefer using Docker, skip steps (1) and (2) here and jump to the [Docker installation](#docker-installation) section below, then come back here to step (3).\* +    \*Optional: If you prefer using Docker, skip steps (1) and (2) here and jump to the [Docker installation](#docker-installation) section below, then come back here to step (3).\* 1) **Install Manim and its dependencies for your OS / environment:** - [Install Manim on Windows](https://docs.manim.community/en/stable/installation/windows.html) From 786b7abc2985eabefce9fbf81dee2936f4fc15d1 Mon Sep 17 00:00:00 2001 From: Jacob Stopak <49353917+initialcommit-io@users.noreply.github.com> Date: Thu, 9 Feb 2023 17:50:56 -0800 Subject: [PATCH 09/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6edda2c..63e6752 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Example: `$ git-sim merge ` - Animation only: Speed up or slow down animation speed as desired ## Quickstart -    \*Optional: If you prefer using Docker, skip steps (1) and (2) here and jump to the [Docker installation](#docker-installation) section below, then come back here to step (3).\* +

Note: If you prefer using Docker, skip steps (1) and (2) here and jump to the [Docker installation](#docker-installation) section below, then come back here to step (3).

1) **Install Manim and its dependencies for your OS / environment:** - [Install Manim on Windows](https://docs.manim.community/en/stable/installation/windows.html) From ccd38d3ce8087bc0fc13629db474c034657b3fb3 Mon Sep 17 00:00:00 2001 From: Jacob Stopak <49353917+initialcommit-io@users.noreply.github.com> Date: Thu, 9 Feb 2023 17:52:16 -0800 Subject: [PATCH 10/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63e6752..293b05d 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Example: `$ git-sim merge ` - Animation only: Speed up or slow down animation speed as desired ## Quickstart -

Note: If you prefer using Docker, skip steps (1) and (2) here and jump to the [Docker installation](#docker-installation) section below, then come back here to step (3).

+Note: If you prefer using Docker, skip steps (1) and (2) here and jump to the [Docker installation](#docker-installation) section below, then come back here to step (3). 1) **Install Manim and its dependencies for your OS / environment:** - [Install Manim on Windows](https://docs.manim.community/en/stable/installation/windows.html) From f450f14195d60d0c0ad74b440ccc165e134e36c0 Mon Sep 17 00:00:00 2001 From: Jacob Stopak <49353917+initialcommit-io@users.noreply.github.com> Date: Thu, 9 Feb 2023 17:52:50 -0800 Subject: [PATCH 11/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 293b05d..ca106c7 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Example: `$ git-sim merge ` - Animation only: Speed up or slow down animation speed as desired ## Quickstart -Note: If you prefer using Docker, skip steps (1) and (2) here and jump to the [Docker installation](#docker-installation) section below, then come back here to step (3). +Note: If you prefer to install git-sim with Docker, skip steps (1) and (2) here and jump to the [Docker installation](#docker-installation) section below, then come back here to step (3). 1) **Install Manim and its dependencies for your OS / environment:** - [Install Manim on Windows](https://docs.manim.community/en/stable/installation/windows.html) From dfe3b83c558bf9e36434a97f3979f832f461feb2 Mon Sep 17 00:00:00 2001 From: manu Date: Fri, 10 Feb 2023 18:39:50 +0100 Subject: [PATCH 12/25] Update cherrypick subcommand name to cherry-pick (#51) --- git_sim/__main__.py | 2 +- git_sim/cherrypick.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/git_sim/__main__.py b/git_sim/__main__.py index c3580bc..96c119c 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -125,7 +125,7 @@ def main( app.command()(git_sim.add.add) app.command()(git_sim.branch.branch) -app.command()(git_sim.cherrypick.cherrypick) +app.command()(git_sim.cherrypick.cherry_pick) app.command()(git_sim.commit.commit) app.command()(git_sim.log.log) app.command()(git_sim.merge.merge) diff --git a/git_sim/cherrypick.py b/git_sim/cherrypick.py index 28697f0..332b9d6 100644 --- a/git_sim/cherrypick.py +++ b/git_sim/cherrypick.py @@ -70,7 +70,7 @@ def construct(self): self.show_outro() -def cherrypick( +def cherry_pick( commit: str = typer.Argument( ..., help="The ref (branch/tag), or commit ID to simulate cherry-pick onto active branch", From 855b7aeb0b9c1198ec14fb5fa84b6ba114c3afc7 Mon Sep 17 00:00:00 2001 From: manu Date: Fri, 10 Feb 2023 18:50:11 +0100 Subject: [PATCH 13/25] Inherit pydantic in settings to handle env vars (#52) - Add pydantic to BaseSettings - Remove obsolete env var handling --- git_sim/__main__.py | 70 ++++++++++---------- git_sim/add.py | 4 +- git_sim/animations.py | 26 +++----- git_sim/branch.py | 8 +-- git_sim/cherrypick.py | 4 +- git_sim/commit.py | 6 +- git_sim/git_sim_base_command.py | 112 ++++++++++++++++---------------- git_sim/log.py | 6 +- git_sim/merge.py | 4 +- git_sim/rebase.py | 14 ++-- git_sim/reset.py | 4 +- git_sim/restore.py | 4 +- git_sim/revert.py | 14 ++-- git_sim/settings.py | 46 +++++++------ git_sim/stash.py | 4 +- git_sim/status.py | 8 +-- git_sim/tag.py | 8 +-- setup.py | 1 + 18 files changed, 170 insertions(+), 173 deletions(-) diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 96c119c..e11943d 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -21,7 +21,7 @@ import git_sim.stash import git_sim.status import git_sim.tag -from git_sim.settings import ImgFormat, Settings, VideoFormat +from git_sim.settings import ImgFormat, VideoFormat, settings app = typer.Typer() @@ -29,98 +29,98 @@ @app.callback(no_args_is_help=True) def main( animate: bool = typer.Option( - Settings.animate, + settings.animate, help="Animate the simulation and output as an mp4 video", ), auto_open: bool = typer.Option( - 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, + settings.img_format, help="Output format for the image files.", ), light_mode: bool = typer.Option( - Settings.light_mode, + settings.light_mode, "--light-mode", help="Enable light-mode with white background", ), logo: pathlib.Path = typer.Option( - Settings.logo, + settings.logo, help="The path to a custom logo to use in the animation intro/outro", ), low_quality: bool = typer.Option( - Settings.low_quality, + settings.low_quality, "--low-quality", help="Render output video in low quality, useful for faster testing", ), max_branches_per_commit: int = typer.Option( - 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( - 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, + settings.media_dir, help="The path to output the animation data and video file", ), outro_bottom_text: str = typer.Option( - Settings.outro_bottom_text, + 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, + settings.outro_top_text, help="Custom text to display above the logo during the outro", ), reverse: bool = typer.Option( - Settings.reverse, + settings.reverse, "--reverse", "-r", help="Display commit history in the reverse direction", ), show_intro: bool = typer.Option( - Settings.show_intro, + settings.show_intro, help="Add an intro sequence with custom logo and title", ), show_outro: bool = typer.Option( - Settings.show_outro, + settings.show_outro, help="Add an outro sequence with custom logo and text", ), speed: float = typer.Option( - Settings.speed, + 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, + settings.title, help="Custom title to display at the beginning of the animation", ), video_format: VideoFormat = typer.Option( - Settings.video_format.value, + settings.video_format.value, help="Output format for the animation files.", case_sensitive=False, ), ): - Settings.animate = animate - Settings.auto_open = auto_open - Settings.img_format = img_format - Settings.light_mode = light_mode - Settings.logo = logo - 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.animate = animate + settings.auto_open = auto_open + settings.img_format = img_format + settings.light_mode = light_mode + settings.logo = logo + 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 app.command()(git_sim.add.add) diff --git a/git_sim/add.py b/git_sim/add.py index 2989e7d..9b888e5 100644 --- a/git_sim/add.py +++ b/git_sim/add.py @@ -6,7 +6,7 @@ from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand -from git_sim.settings import Settings +from git_sim.settings import settings class Add(GitSimBaseCommand): @@ -30,7 +30,7 @@ def __init__(self, files: list[str]): def construct(self): print( - f"{Settings.INFO_STRING} {type(self).__name__.lower()} {' '.join(self.files)}" + f"{settings.INFO_STRING} {type(self).__name__.lower()} {' '.join(self.files)}" ) self.show_intro() diff --git a/git_sim/animations.py b/git_sim/animations.py index ef07ca4..31b1551 100644 --- a/git_sim/animations.py +++ b/git_sim/animations.py @@ -11,7 +11,7 @@ from manim import WHITE, Scene, config from manim.utils.file_ops import open_file -from git_sim.settings import Settings +from git_sim.settings import settings def handle_animations(scene: Scene) -> None: @@ -25,22 +25,14 @@ def handle_animations(scene: Scene) -> None: ).working_tree_dir.split("\\")[-1] config.media_dir = os.path.join( - os.path.expanduser(Settings.media_dir), "git-sim_media" + os.path.expanduser(settings.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 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: + if settings.low_quality: config.quality = "low_quality" - if Settings.light_mode: + if settings.light_mode: config.background_color = WHITE t = datetime.datetime.fromtimestamp(time.time()).strftime("%m-%d-%y_%H-%M-%S") @@ -48,7 +40,7 @@ def handle_animations(scene: Scene) -> None: scene.render() - if Settings.video_format == "webm": + 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...") @@ -60,7 +52,7 @@ def handle_animations(scene: Scene) -> None: os.remove(scene.renderer.file_writer.movie_file_path) scene.renderer.file_writer.movie_file_path = webm_file_path - if not Settings.animate: + if not settings.animate: video = cv2.VideoCapture(str(scene.renderer.file_writer.movie_file_path)) success, image = video.read() if success: @@ -70,7 +62,7 @@ def handle_animations(scene: Scene) -> None: + "_" + t + "." - + Settings.img_format + + settings.img_format ) image_file_path = os.path.join( os.path.join(config.media_dir, "images"), image_file_name @@ -80,9 +72,9 @@ def handle_animations(scene: Scene) -> None: else: print("Output video location:", scene.renderer.file_writer.movie_file_path) - if Settings.auto_open: + if settings.auto_open: try: - if not Settings.animate: + if not settings.animate: open_file(image_file_path) else: open_file(scene.renderer.file_writer.movie_file_path) diff --git a/git_sim/branch.py b/git_sim/branch.py index 646bb47..58ac9c6 100644 --- a/git_sim/branch.py +++ b/git_sim/branch.py @@ -3,7 +3,7 @@ from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand -from git_sim.settings import Settings +from git_sim.settings import settings class Branch(GitSimBaseCommand): @@ -12,7 +12,7 @@ def __init__(self, name: str): self.name = name def construct(self): - print(f"{Settings.INFO_STRING} {type(self).__name__.lower()} {self.name}") + print(f"{settings.INFO_STRING} {type(self).__name__.lower()} {self.name}") self.show_intro() self.get_commits() @@ -39,8 +39,8 @@ def construct(self): fullbranch = m.VGroup(branchRec, branchText) - if Settings.animate: - self.play(m.Create(fullbranch), run_time=1 / Settings.speed) + if settings.animate: + self.play(m.Create(fullbranch), run_time=1 / settings.speed) else: self.add(fullbranch) diff --git a/git_sim/cherrypick.py b/git_sim/cherrypick.py index 332b9d6..28bea63 100644 --- a/git_sim/cherrypick.py +++ b/git_sim/cherrypick.py @@ -6,7 +6,7 @@ from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand -from git_sim.settings import Settings +from git_sim.settings import settings class CherryPick(GitSimBaseCommand): @@ -35,7 +35,7 @@ def __init__(self, commit: str, edit: str): def construct(self): print( - f"{Settings.INFO_STRING} {type(self).__name__.lower()} {self.commit}" + f"{settings.INFO_STRING} {type(self).__name__.lower()} {self.commit}" + ((' -e "' + self.edit + '"') if self.edit else "") ) diff --git a/git_sim/commit.py b/git_sim/commit.py index 05087f6..546554a 100644 --- a/git_sim/commit.py +++ b/git_sim/commit.py @@ -3,10 +3,10 @@ import git import manim as m import typer -from git_sim.animations import handle_animations -from git_sim.settings import Settings +from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand +from git_sim.settings import settings class Commit(GitSimBaseCommand): @@ -32,7 +32,7 @@ def __init__(self, message: str, amend: bool): def construct(self): print( - f"{Settings.INFO_STRING} {type(self).__name__.lower()} {'--amend ' if self.amend else ''}" + f"{settings.INFO_STRING } {type(self).__name__.lower()} {'--amend ' if self.amend else ''}" + '-m "' + self.message + '"' diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index f9726b5..35ce33a 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -7,7 +7,7 @@ from git.exc import GitCommandError, InvalidGitRepositoryError from git.repo import Repo -from git_sim.settings import Settings +from git_sim.settings import settings class GitSimBaseCommand(m.MovingCameraScene): @@ -15,7 +15,7 @@ def __init__(self): super().__init__() self.init_repo() - self.fontColor = m.BLACK if Settings.light_mode else m.WHITE + self.fontColor = m.BLACK if settings.light_mode else m.WHITE self.drawnCommits = {} self.drawnRefs = {} self.drawnCommitIds = {} @@ -26,13 +26,13 @@ def __init__(self): self.prevRef = None self.topref = None self.i = 0 - self.numCommits = Settings.commits - self.defaultNumCommits = Settings.commits + self.numCommits = settings.commits + self.defaultNumCommits = settings.commits self.selected_branches = [] self.stop = False self.zone_title_offset = 2.6 if platform.system() == "Windows" else 2.6 - self.logo = m.ImageMobject(Settings.logo) + self.logo = m.ImageMobject(settings.logo) self.logo.width = 3 def init_repo(self): @@ -43,7 +43,7 @@ def init_repo(self): sys.exit(1) def construct(self): - print(f"{Settings.INFO_STRING} {type(self).__name__.lower()}") + print(f"{settings.INFO_STRING} {type(self).__name__.lower()}") self.show_intro() self.get_commits() self.fadeout() @@ -51,7 +51,7 @@ def construct(self): def get_commits(self, start="HEAD"): if not self.numCommits: - if Settings.allow_no_commits: + if settings.allow_no_commits: self.numCommits = self.defaultNumCommits self.commits = ["dark"] * 5 self.zone_title_offset = 2 @@ -108,11 +108,11 @@ def parse_commits( self.i = 0 def show_intro(self): - if Settings.animate and Settings.show_intro: + if settings.animate and settings.show_intro: self.add(self.logo) initialCommitText = m.Text( - Settings.title, + settings.title, font="Monospace", font_size=36, color=self.fontColor, @@ -134,13 +134,13 @@ def show_intro(self): self.camera.frame.save_state() def show_outro(self): - if Settings.animate and Settings.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( - Settings.outro_top_text, + settings.outro_top_text, font="Monospace", font_size=36, color=self.fontColor, @@ -148,7 +148,7 @@ def show_outro(self): self.play(m.AddTextLetterByLetter(outroTopText)) outroBottomText = m.Text( - Settings.outro_bottom_text, + settings.outro_bottom_text, font="Monospace", font_size=36, color=self.fontColor, @@ -158,9 +158,9 @@ def show_outro(self): self.wait(3) def fadeout(self): - if Settings.animate: + if settings.animate: self.wait(3) - self.play(m.FadeOut(self.toFadeOut), run_time=1 / Settings.speed) + self.play(m.FadeOut(self.toFadeOut), run_time=1 / settings.speed) else: self.wait(0.1) @@ -174,7 +174,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 Settings.light_mode else m.BLACK + commitFill = m.WHITE if settings.light_mode else m.BLACK elif len(commit.parents) <= 1: commitFill = m.RED else: @@ -190,19 +190,19 @@ def draw_commit( if prevCircle: circle.next_to( - prevCircle, m.RIGHT if Settings.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 Settings.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 Settings.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() @@ -231,13 +231,13 @@ def draw_commit( color=self.fontColor, ).next_to(circle, m.DOWN) - if Settings.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 / Settings.speed, + run_time=1 / settings.speed, ) elif not self.stop: self.add(circle, commitId, message) @@ -288,8 +288,8 @@ def draw_head(self, commit, commitId): head = m.VGroup(headbox, headText) - if Settings.animate: - self.play(m.Create(head), run_time=1 / Settings.speed) + if settings.animate: + self.play(m.Create(head), run_time=1 / settings.speed) else: self.add(head) @@ -340,8 +340,8 @@ def draw_branch(self, commit): self.prevRef = fullbranch - if Settings.animate: - self.play(m.Create(fullbranch), run_time=1 / Settings.speed) + if settings.animate: + self.play(m.Create(fullbranch), run_time=1 / settings.speed) else: self.add(fullbranch) @@ -352,13 +352,13 @@ def draw_branch(self, commit): self.topref = self.prevRef x += 1 - if x >= Settings.max_branches_per_commit: + if x >= settings.max_branches_per_commit: return def draw_tag(self, commit): x = 0 - if Settings.hide_first_tag and self.i == 0: + if settings.hide_first_tag and self.i == 0: return for tag in self.repo.tags: @@ -383,11 +383,11 @@ def draw_tag(self, commit): self.prevRef = tagRec - if Settings.animate: + if settings.animate: self.play( m.Create(tagRec), m.Create(tagText), - run_time=1 / Settings.speed, + run_time=1 / settings.speed, ) else: self.add(tagRec, tagText) @@ -398,43 +398,43 @@ def draw_tag(self, commit): self.topref = self.prevRef x += 1 - if x >= Settings.max_tags_per_commit: + if x >= settings.max_tags_per_commit: return except ValueError: pass def draw_arrow(self, prevCircle, arrow): if prevCircle: - if Settings.animate: - self.play(m.Create(arrow), run_time=1 / Settings.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 Settings.animate: + if settings.animate: self.play( self.camera.frame.animate.move_to(self.toFadeOut.get_center()), - run_time=1 / Settings.speed, + run_time=1 / settings.speed, ) else: self.camera.frame.move_to(self.toFadeOut.get_center()) def scale_frame(self): - if Settings.animate: + if settings.animate: self.play( self.camera.frame.animate.scale_to_fit_width( self.toFadeOut.get_width() * 1.1 ), - run_time=1 / Settings.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 / Settings.speed, + run_time=1 / settings.speed, ) else: self.camera.frame.scale_to_fit_width(self.toFadeOut.get_width() * 1.1) @@ -444,7 +444,7 @@ def scale_frame(self): ) def vsplit_frame(self): - if Settings.animate: + if settings.animate: self.play( self.camera.frame.animate.scale_to_fit_height( self.camera.frame.get_height() * 2 @@ -454,7 +454,7 @@ def vsplit_frame(self): self.camera.frame.scale_to_fit_height(self.camera.frame.get_height() * 2) try: - if Settings.animate: + if settings.animate: self.play( self.toFadeOut.animate.align_to(self.camera.frame, m.UP).shift( m.DOWN * 0.75 @@ -566,7 +566,7 @@ def setup_and_draw_zones( thirdColumnTitle, ) - if Settings.animate: + if settings.animate: self.play( m.Create(horizontal), m.Create(horizontal2), @@ -659,19 +659,19 @@ def setup_and_draw_zones( thirdColumnFilesDict[f] = text if len(firstColumnFiles): - if Settings.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 Settings.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 Settings.animate: + if settings.animate: self.play(*[m.AddTextLetterByLetter(s) for s in thirdColumnFiles]) else: self.add(*[s for s in thirdColumnFiles]) @@ -703,7 +703,7 @@ def setup_and_draw_zones( 0, ), ) - if Settings.animate: + if settings.animate: self.play(m.Create(firstColumnArrowMap[filename])) else: self.add(firstColumnArrowMap[filename]) @@ -722,7 +722,7 @@ def setup_and_draw_zones( 0, ), ) - if Settings.animate: + if settings.animate: self.play(m.Create(secondColumnArrowMap[filename])) else: self.add(secondColumnArrowMap[filename]) @@ -756,7 +756,7 @@ def populate_zones( firstColumnFileNames.add(z) def center_frame_on_commit(self, commit): - if Settings.animate: + if settings.animate: self.play( self.camera.frame.animate.move_to( self.drawnCommits[commit.hexsha].get_center() @@ -766,7 +766,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 Settings.animate: + if settings.animate: self.play( self.drawnRefs["HEAD"].animate.move_to( ( @@ -800,7 +800,7 @@ def reset_head_branch(self, hexsha, shift=numpy.array([0.0, 0.0, 0.0])): ) def translate_frame(self, shift): - if Settings.animate: + if settings.animate: self.play(self.camera.frame.animate.shift(shift)) else: self.camera.frame.shift(shift) @@ -817,7 +817,7 @@ def setup_and_draw_parent( circle.height = 1 circle.next_to( self.drawnCommits[child.hexsha], - m.LEFT if Settings.reverse else m.RIGHT, + m.LEFT if settings.reverse else m.RIGHT, buff=1.5, ) circle.shift(shift) @@ -844,13 +844,13 @@ def setup_and_draw_parent( ).next_to(circle, m.DOWN) self.toFadeOut.add(message) - if Settings.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 / Settings.speed, + run_time=1 / settings.speed, ) else: self.camera.frame.move_to(circle.get_center()) @@ -860,8 +860,8 @@ def setup_and_draw_parent( self.toFadeOut.add(circle) if draw_arrow: - if Settings.animate: - self.play(m.Create(arrow), run_time=1 / Settings.speed) + if settings.animate: + self.play(m.Create(arrow), run_time=1 / settings.speed) else: self.add(arrow) self.toFadeOut.add(arrow) @@ -901,8 +901,8 @@ def draw_ref(self, commit, top, text="HEAD", color=m.BLUE): ref = m.VGroup(refbox, refText) - if Settings.animate: - self.play(m.Create(ref), run_time=1 / Settings.speed) + if settings.animate: + self.play(m.Create(ref), run_time=1 / settings.speed) else: self.add(ref) @@ -915,8 +915,8 @@ def draw_ref(self, commit, top, text="HEAD", color=m.BLUE): def draw_dark_ref(self): refRec = m.Rectangle( - color=m.WHITE if Settings.light_mode else m.BLACK, - fill_color=m.WHITE if Settings.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 index 46deaf1..a14c767 100644 --- a/git_sim/log.py +++ b/git_sim/log.py @@ -2,7 +2,7 @@ from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand -from git_sim.settings import Settings +from git_sim.settings import settings class Log(GitSimBaseCommand): @@ -16,7 +16,7 @@ def __init__(self, commits: int): pass def construct(self): - print(f"{Settings.INFO_STRING} {type(self).__name__.lower()}") + print(f"{settings.INFO_STRING} {type(self).__name__.lower()}") self.show_intro() self.get_commits() self.parse_commits(self.commits[0]) @@ -28,7 +28,7 @@ def construct(self): def log( commits: int = typer.Option( - default=Settings.commits, + default=settings.commits, help="The number of commits to display in the simulated log output", min=1, max=12, diff --git a/git_sim/merge.py b/git_sim/merge.py index 3863dc6..b0599b6 100644 --- a/git_sim/merge.py +++ b/git_sim/merge.py @@ -8,7 +8,7 @@ from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand -from git_sim.settings import Settings +from git_sim.settings import settings class Merge(GitSimBaseCommand): @@ -38,7 +38,7 @@ def __init__(self, branch: str, no_ff: bool): def construct(self): print( - f"{Settings.INFO_STRING} {type(self).__name__.lower()} {self.branch} {'--no-ff' if self.no_ff else ''}" + f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.branch} {'--no-ff' if self.no_ff else ''}" ) if self.repo.active_branch.name in self.repo.git.branch( diff --git a/git_sim/rebase.py b/git_sim/rebase.py index 97e76da..38f6e69 100644 --- a/git_sim/rebase.py +++ b/git_sim/rebase.py @@ -7,7 +7,7 @@ from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand -from git_sim.settings import Settings +from git_sim.settings import settings class Rebase(GitSimBaseCommand): @@ -34,7 +34,7 @@ def __init__(self, branch: str): pass def construct(self): - print(f"{Settings.INFO_STRING} {type(self).__name__.lower()} {self.branch}") + print(f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.branch}") if self.branch in self.repo.git.branch( "--contains", self.repo.active_branch.name @@ -116,7 +116,7 @@ def setup_and_draw_parent( circle.height = 1 circle.next_to( self.drawnCommits[child], - m.LEFT if Settings.reverse else m.RIGHT, + m.LEFT if settings.reverse else m.RIGHT, buff=1.5, ) circle.shift(shift) @@ -155,13 +155,13 @@ def setup_and_draw_parent( ).next_to(circle, m.DOWN) self.toFadeOut.add(message) - if Settings.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 / Settings.speed, + run_time=1 / settings.speed, ) else: self.camera.frame.move_to(circle.get_center()) @@ -171,8 +171,8 @@ def setup_and_draw_parent( self.toFadeOut.add(circle) if draw_arrow: - if Settings.animate: - self.play(m.Create(arrow), run_time=1 / Settings.speed) + if settings.animate: + self.play(m.Create(arrow), run_time=1 / settings.speed) else: self.add(arrow) self.toFadeOut.add(arrow) diff --git a/git_sim/reset.py b/git_sim/reset.py index c680ffb..2f1cceb 100644 --- a/git_sim/reset.py +++ b/git_sim/reset.py @@ -7,7 +7,7 @@ from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand -from git_sim.settings import Settings +from git_sim.settings import settings class ResetMode(Enum): @@ -50,7 +50,7 @@ def __init__( def construct(self): print( - f"{Settings.INFO_STRING} {type(self).__name__.lower()}{' --' + self.mode.value if self.mode != ResetMode.DEFAULT else ''} {self.commit}", + f"{settings.INFO_STRING } {type(self).__name__.lower()}{' --' + self.mode.value if self.mode != ResetMode.DEFAULT else ''} {self.commit}", ) self.show_intro() diff --git a/git_sim/restore.py b/git_sim/restore.py index 44724e4..30475e2 100644 --- a/git_sim/restore.py +++ b/git_sim/restore.py @@ -5,7 +5,7 @@ from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand -from git_sim.settings import Settings +from git_sim.settings import settings class Restore(GitSimBaseCommand): @@ -28,7 +28,7 @@ def __init__(self, files: list[str]): def construct(self): print( - f"{Settings.INFO_STRING} {type(self).__name__.lower()} {' '.join(self.files)}" + f"{settings.INFO_STRING } {type(self).__name__.lower()} {' '.join(self.files)}" ) self.show_intro() diff --git a/git_sim/revert.py b/git_sim/revert.py index dbeafcb..2835ecc 100644 --- a/git_sim/revert.py +++ b/git_sim/revert.py @@ -7,7 +7,7 @@ from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand -from git_sim.settings import Settings +from git_sim.settings import settings class Revert(GitSimBaseCommand): @@ -36,7 +36,7 @@ def __init__(self, commit: str): pass def construct(self): - print(f"{Settings.INFO_STRING} {type(self).__name__.lower()} {self.commit}") + print(f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.commit}") self.show_intro() self.get_commits() @@ -94,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 Settings.reverse else m.RIGHT, + m.LEFT if settings.reverse else m.RIGHT, buff=1.5, ) @@ -121,13 +121,13 @@ def setup_and_draw_revert_commit(self): ).next_to(circle, m.DOWN) self.toFadeOut.add(message) - if Settings.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 / Settings.speed, + run_time=1 / settings.speed, ) else: self.camera.frame.move_to(circle.get_center()) @@ -136,8 +136,8 @@ def setup_and_draw_revert_commit(self): self.drawnCommits["abcdef"] = circle self.toFadeOut.add(circle) - if Settings.animate: - self.play(m.Create(arrow), run_time=1 / Settings.speed) + if settings.animate: + self.play(m.Create(arrow), run_time=1 / settings.speed) else: self.add(arrow) diff --git a/git_sim/settings.py b/git_sim/settings.py index 219aa4c..4aad919 100644 --- a/git_sim/settings.py +++ b/git_sim/settings.py @@ -1,7 +1,8 @@ import pathlib -from dataclasses import dataclass from enum import Enum +from pydantic import BaseSettings + class VideoFormat(str, Enum): mp4 = "mp4" @@ -13,29 +14,32 @@ class ImgFormat(str, Enum): png = "png" -@dataclass -class Settings: - commits = 5 - subcommand: str - 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 +class Settings(BaseSettings): allow_no_commits = False - low_quality = False + animate = False auto_open = True + commits = 5 + files: list[pathlib.Path] | None = None + hide_first_tag = False + img_format: ImgFormat = ImgFormat.jpg INFO_STRING = "Simulating: git" - # os.path.join(str(pathlib.Path(__file__).parent.resolve()), "logo.png") + light_mode = False logo = pathlib.Path(__file__).parent.resolve() / "logo.png" + low_quality = False + max_branches_per_commit = 1 + max_tags_per_commit = 1 media_dir = pathlib.Path().cwd() - files: list[pathlib.Path] | None = None + outro_bottom_text = "Learn more at initialcommit.com" + outro_top_text = "Thanks for using Initial Commit!" + reverse = False + show_intro = False + show_outro = False + speed = 1.5 + title = "Git Sim, by initialcommit.com" video_format: VideoFormat = VideoFormat.mp4 - img_format: ImgFormat = ImgFormat.jpg + + class Config: + env_prefix = "git_sim" + + +settings = Settings() diff --git a/git_sim/stash.py b/git_sim/stash.py index 8ec2fdf..3af3832 100644 --- a/git_sim/stash.py +++ b/git_sim/stash.py @@ -5,7 +5,7 @@ from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand -from git_sim.settings import Settings +from git_sim.settings import settings class Stash(GitSimBaseCommand): @@ -34,7 +34,7 @@ def __init__(self, files: list[str]): def construct(self): print( - f"{Settings.INFO_STRING} {type(self).__name__.lower()} {' '.join(self.files) if not self.no_files else ''}" + f"{settings.INFO_STRING } {type(self).__name__.lower()} {' '.join(self.files) if not self.no_files else ''}" ) self.show_intro() diff --git a/git_sim/status.py b/git_sim/status.py index 17fc71e..f3b1c08 100644 --- a/git_sim/status.py +++ b/git_sim/status.py @@ -1,6 +1,6 @@ from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand -from git_sim.settings import Settings +from git_sim.settings import settings class Status(GitSimBaseCommand): @@ -12,7 +12,7 @@ def __init__(self): pass def construct(self): - print(f"{Settings.INFO_STRING} {type(self).__name__.lower()}") + print(f"{settings.INFO_STRING } {type(self).__name__.lower()}") self.show_intro() self.get_commits() self.parse_commits(self.commits[0]) @@ -25,8 +25,8 @@ def construct(self): def status(): - Settings.hide_first_tag = True - Settings.allow_no_commits = True + 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 index 2d4dd6d..fd26b5f 100644 --- a/git_sim/tag.py +++ b/git_sim/tag.py @@ -3,7 +3,7 @@ from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand -from git_sim.settings import Settings +from git_sim.settings import settings class Tag(GitSimBaseCommand): @@ -12,7 +12,7 @@ def __init__(self, name: str): self.name = name def construct(self): - print(f"{Settings.INFO_STRING} {type(self).__name__.lower()} {self.name}") + print(f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.name}") self.show_intro() self.get_commits() @@ -39,8 +39,8 @@ def construct(self): fulltag = m.VGroup(tagRec, tagText) - if Settings.animate: - self.play(m.Create(fulltag), run_time=1 / Settings.speed) + if settings.animate: + self.play(m.Create(fulltag), run_time=1 / settings.speed) else: self.add(fulltag) diff --git a/setup.py b/setup.py index 109edfe..774fb10 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ "manim", "opencv-python-headless", "typer", + "pydantic", ], keywords="git sim simulation simulate git-simulate git-simulation git-sim manim animation gitanimation image video dryrun dry-run", project_urls={ From c4ef8bff041813492d3eea29fc1ee9f842277ec5 Mon Sep 17 00:00:00 2001 From: manu Date: Fri, 10 Feb 2023 19:36:37 +0100 Subject: [PATCH 14/25] Fix shorthands (#53) - Add -m shorthand for commit --message - Add -e shorthand for cherry-pick --edit - Add -h shorthand for global and subcommand --help - Clean up unused imports --- git_sim/__main__.py | 8 +------- git_sim/cherrypick.py | 4 +++- git_sim/commit.py | 4 +++- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/git_sim/__main__.py b/git_sim/__main__.py index e11943d..743d861 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -1,10 +1,4 @@ -import argparse -import datetime -import os import pathlib -import subprocess -import sys -import time import typer @@ -23,7 +17,7 @@ import git_sim.tag from git_sim.settings import ImgFormat, VideoFormat, settings -app = typer.Typer() +app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]}) @app.callback(no_args_is_help=True) diff --git a/git_sim/cherrypick.py b/git_sim/cherrypick.py index 28bea63..bba540c 100644 --- a/git_sim/cherrypick.py +++ b/git_sim/cherrypick.py @@ -76,7 +76,9 @@ def cherry_pick( help="The ref (branch/tag), or commit ID to simulate cherry-pick onto active branch", ), edit: str = typer.Option( - default=None, + None, + "--edit", + "-e", help="Specify a new commit message for the cherry-picked commit", ), ): diff --git a/git_sim/commit.py b/git_sim/commit.py index 546554a..03f66bb 100644 --- a/git_sim/commit.py +++ b/git_sim/commit.py @@ -102,7 +102,9 @@ def populate_zones( def commit( message: str = typer.Option( - default="New commit", + "New commit", + "--message", + "-m", help="The commit message of the new commit", ), amend: bool = typer.Option( From e50608d7288f8fd98ae6bbb5837e8e937bab2abe Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Fri, 10 Feb 2023 10:44:40 -0800 Subject: [PATCH 15/25] Update cherry-pick output message to add hyphen Signed-off-by: Jacob Stopak --- git_sim/cherrypick.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_sim/cherrypick.py b/git_sim/cherrypick.py index bba540c..7cb6bd5 100644 --- a/git_sim/cherrypick.py +++ b/git_sim/cherrypick.py @@ -35,7 +35,7 @@ def __init__(self, commit: str, edit: str): def construct(self): print( - f"{settings.INFO_STRING} {type(self).__name__.lower()} {self.commit}" + f"{settings.INFO_STRING} cherry-pick {self.commit}" + ((' -e "' + self.edit + '"') if self.edit else "") ) From 14fe6eece2fca87fb2ea8727948495d7734859e8 Mon Sep 17 00:00:00 2001 From: manu Date: Fri, 10 Feb 2023 19:48:01 +0100 Subject: [PATCH 16/25] Add trailing underscore to env_prefix (#54) --- git_sim/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_sim/settings.py b/git_sim/settings.py index 4aad919..b7f7049 100644 --- a/git_sim/settings.py +++ b/git_sim/settings.py @@ -39,7 +39,7 @@ class Settings(BaseSettings): video_format: VideoFormat = VideoFormat.mp4 class Config: - env_prefix = "git_sim" + env_prefix = "git_sim_" settings = Settings() From 28e2f784bd79b7a01d9c95cceaf09fe1724e12f5 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sat, 11 Feb 2023 03:14:31 -0800 Subject: [PATCH 17/25] Add --stdout option to write image data directly to stdout while suppressing other output Signed-off-by: Jacob Stopak --- README.md | 3 ++- git_sim/__main__.py | 5 +++++ git_sim/add.py | 7 ++++--- git_sim/animations.py | 7 +++++-- git_sim/branch.py | 3 ++- git_sim/cherrypick.py | 9 +++++---- git_sim/commit.py | 13 +++++++------ git_sim/log.py | 3 ++- git_sim/merge.py | 7 ++++--- git_sim/rebase.py | 5 ++++- git_sim/reset.py | 7 ++++--- git_sim/restore.py | 7 ++++--- git_sim/revert.py | 5 ++++- git_sim/settings.py | 1 + git_sim/stash.py | 7 ++++--- git_sim/status.py | 3 ++- git_sim/tag.py | 3 ++- 17 files changed, 61 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index ca106c7..e4e5d5b 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,8 @@ The `[global options]` apply to the overarching `git-sim` simulation itself, inc `--animate`: Instead of outputting a static image, animate the Git command behavior in a .mp4 video. `--disable-auto-open, -d`: Disable the automatic opening of the image/video file after generation. `--reverse, -r`: Display commit history in the reverse direction. -`--img-format`: Output format for the image file, i.e. `jpg` or `png`. Default output format is `jpg`. +`--img-format`: Output format for the image file, i.e. `jpg` or `png`. Default output format is `jpg`. +`--stdout`: Write raw image data to stdout while suppressing all other program output. Animation-only global options (to be used in conjunction with `--animate`): diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 743d861..8d1f7e0 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -97,6 +97,10 @@ def main( help="Output format for the animation files.", case_sensitive=False, ), + stdout: bool = typer.Option( + settings.stdout, + help="Write raw image data to stdout while suppressing all other program output", + ), ): settings.animate = animate settings.auto_open = auto_open @@ -115,6 +119,7 @@ def main( settings.speed = speed settings.title = title settings.video_format = video_format + settings.stdout = stdout app.command()(git_sim.add.add) diff --git a/git_sim/add.py b/git_sim/add.py index 9b888e5..56d8f8c 100644 --- a/git_sim/add.py +++ b/git_sim/add.py @@ -29,9 +29,10 @@ def __init__(self, files: list[str]): sys.exit() def construct(self): - print( - f"{settings.INFO_STRING} {type(self).__name__.lower()} {' '.join(self.files)}" - ) + if not settings.stdout: + print( + f"{settings.INFO_STRING} {type(self).__name__.lower()} {' '.join(self.files)}" + ) self.show_intro() self.get_commits() diff --git a/git_sim/animations.py b/git_sim/animations.py index 31b1551..48b4835 100644 --- a/git_sim/animations.py +++ b/git_sim/animations.py @@ -68,11 +68,14 @@ def handle_animations(scene: Scene) -> None: os.path.join(config.media_dir, "images"), image_file_name ) cv2.imwrite(image_file_path, image) - print("Output image location:", image_file_path) + if not settings.stdout: + print("Output image location:", image_file_path) + if settings.stdout: + sys.stdout.buffer.write(cv2.imencode(".jpg", image)[1].tobytes()) else: print("Output video location:", scene.renderer.file_writer.movie_file_path) - if settings.auto_open: + if settings.auto_open and not settings.stdout: try: if not settings.animate: open_file(image_file_path) diff --git a/git_sim/branch.py b/git_sim/branch.py index 58ac9c6..f47b3c8 100644 --- a/git_sim/branch.py +++ b/git_sim/branch.py @@ -12,7 +12,8 @@ def __init__(self, name: str): self.name = name def construct(self): - print(f"{settings.INFO_STRING} {type(self).__name__.lower()} {self.name}") + if not settings.stdout: + print(f"{settings.INFO_STRING} {type(self).__name__.lower()} {self.name}") self.show_intro() self.get_commits() diff --git a/git_sim/cherrypick.py b/git_sim/cherrypick.py index 7cb6bd5..b5280a3 100644 --- a/git_sim/cherrypick.py +++ b/git_sim/cherrypick.py @@ -34,10 +34,11 @@ def __init__(self, commit: str, edit: str): pass def construct(self): - print( - f"{settings.INFO_STRING} cherry-pick {self.commit}" - + ((' -e "' + self.edit + '"') if self.edit else "") - ) + if not settings.stdout: + print( + f"{settings.INFO_STRING} cherry-pick {self.commit}" + + ((' -e "' + self.edit + '"') if self.edit else "") + ) if self.repo.active_branch.name in self.repo.git.branch( "--contains", self.commit diff --git a/git_sim/commit.py b/git_sim/commit.py index 03f66bb..e259498 100644 --- a/git_sim/commit.py +++ b/git_sim/commit.py @@ -31,12 +31,13 @@ def __init__(self, message: str, amend: bool): sys.exit(1) def construct(self): - print( - f"{settings.INFO_STRING } {type(self).__name__.lower()} {'--amend ' if self.amend else ''}" - + '-m "' - + self.message - + '"' - ) + if not settings.stdout: + print( + f"{settings.INFO_STRING } {type(self).__name__.lower()} {'--amend ' if self.amend else ''}" + + '-m "' + + self.message + + '"' + ) self.show_intro() self.get_commits() diff --git a/git_sim/log.py b/git_sim/log.py index a14c767..f9ecf46 100644 --- a/git_sim/log.py +++ b/git_sim/log.py @@ -16,7 +16,8 @@ def __init__(self, commits: int): pass def construct(self): - print(f"{settings.INFO_STRING} {type(self).__name__.lower()}") + if not settings.stdout: + print(f"{settings.INFO_STRING} {type(self).__name__.lower()}") self.show_intro() self.get_commits() self.parse_commits(self.commits[0]) diff --git a/git_sim/merge.py b/git_sim/merge.py index b0599b6..95fa898 100644 --- a/git_sim/merge.py +++ b/git_sim/merge.py @@ -37,9 +37,10 @@ def __init__(self, branch: str, no_ff: bool): pass def construct(self): - print( - f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.branch} {'--no-ff' if self.no_ff else ''}" - ) + if not settings.stdout: + print( + f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.branch} {'--no-ff' if self.no_ff else ''}" + ) if self.repo.active_branch.name in self.repo.git.branch( "--contains", self.branch diff --git a/git_sim/rebase.py b/git_sim/rebase.py index 38f6e69..e1d3f26 100644 --- a/git_sim/rebase.py +++ b/git_sim/rebase.py @@ -34,7 +34,10 @@ def __init__(self, branch: str): pass def construct(self): - print(f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.branch}") + if not settings.stdout: + print( + f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.branch}" + ) if self.branch in self.repo.git.branch( "--contains", self.repo.active_branch.name diff --git a/git_sim/reset.py b/git_sim/reset.py index 2f1cceb..aa746f3 100644 --- a/git_sim/reset.py +++ b/git_sim/reset.py @@ -49,9 +49,10 @@ def __init__( self.mode = ResetMode.SOFT def construct(self): - print( - f"{settings.INFO_STRING } {type(self).__name__.lower()}{' --' + self.mode.value if self.mode != ResetMode.DEFAULT else ''} {self.commit}", - ) + if not settings.stdout: + print( + f"{settings.INFO_STRING } {type(self).__name__.lower()}{' --' + self.mode.value if self.mode != ResetMode.DEFAULT else ''} {self.commit}", + ) self.show_intro() self.get_commits() diff --git a/git_sim/restore.py b/git_sim/restore.py index 30475e2..8ba5ef3 100644 --- a/git_sim/restore.py +++ b/git_sim/restore.py @@ -27,9 +27,10 @@ def __init__(self, files: list[str]): sys.exit() def construct(self): - print( - f"{settings.INFO_STRING } {type(self).__name__.lower()} {' '.join(self.files)}" - ) + if not settings.stdout: + print( + f"{settings.INFO_STRING } {type(self).__name__.lower()} {' '.join(self.files)}" + ) self.show_intro() self.get_commits() diff --git a/git_sim/revert.py b/git_sim/revert.py index 2835ecc..0b18c62 100644 --- a/git_sim/revert.py +++ b/git_sim/revert.py @@ -36,7 +36,10 @@ def __init__(self, commit: str): pass def construct(self): - print(f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.commit}") + if not settings.stdout: + print( + f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.commit}" + ) self.show_intro() self.get_commits() diff --git a/git_sim/settings.py b/git_sim/settings.py index b7f7049..dac7966 100644 --- a/git_sim/settings.py +++ b/git_sim/settings.py @@ -37,6 +37,7 @@ class Settings(BaseSettings): speed = 1.5 title = "Git Sim, by initialcommit.com" video_format: VideoFormat = VideoFormat.mp4 + stdout = False class Config: env_prefix = "git_sim_" diff --git a/git_sim/stash.py b/git_sim/stash.py index 3af3832..ee2eb4e 100644 --- a/git_sim/stash.py +++ b/git_sim/stash.py @@ -33,9 +33,10 @@ def __init__(self, files: list[str]): ] def construct(self): - print( - f"{settings.INFO_STRING } {type(self).__name__.lower()} {' '.join(self.files) if not self.no_files else ''}" - ) + if not settings.stdout: + print( + f"{settings.INFO_STRING } {type(self).__name__.lower()} {' '.join(self.files) if not self.no_files else ''}" + ) self.show_intro() self.get_commits() diff --git a/git_sim/status.py b/git_sim/status.py index f3b1c08..0aa4e8f 100644 --- a/git_sim/status.py +++ b/git_sim/status.py @@ -12,7 +12,8 @@ def __init__(self): pass def construct(self): - print(f"{settings.INFO_STRING } {type(self).__name__.lower()}") + if not settings.stdout: + print(f"{settings.INFO_STRING } {type(self).__name__.lower()}") self.show_intro() self.get_commits() self.parse_commits(self.commits[0]) diff --git a/git_sim/tag.py b/git_sim/tag.py index fd26b5f..cbfa058 100644 --- a/git_sim/tag.py +++ b/git_sim/tag.py @@ -12,7 +12,8 @@ def __init__(self, name: str): self.name = name def construct(self): - print(f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.name}") + if not settings.stdout: + print(f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.name}") self.show_intro() self.get_commits() From a1401f1d048b4decfbe253a975d9e0ec873ffc5b Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sat, 11 Feb 2023 22:22:19 -0800 Subject: [PATCH 18/25] Improve spacing of file paths in zones Signed-off-by: Jacob Stopak --- git_sim/git_sim_base_command.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index 35ce33a..3575e2f 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -468,7 +468,7 @@ def vsplit_frame(self): def setup_and_draw_zones( self, first_column_name="Untracked files", - second_column_name="Working directory modifications", + second_column_name="Working directory mods", third_column_name="Staging area", reverse=False, ): @@ -507,7 +507,7 @@ def setup_and_draw_zones( (self.camera.frame.get_left()[0], horizontal.get_start()[1], 0), dash_length=0.2, color=self.fontColor, - ).shift(m.RIGHT * 6.5) + ).shift(m.RIGHT * 8) vert2 = m.DashedLine( ( self.camera.frame.get_right()[0], @@ -517,7 +517,7 @@ def setup_and_draw_zones( (self.camera.frame.get_right()[0], horizontal.get_start()[1], 0), dash_length=0.2, color=self.fontColor, - ).shift(m.LEFT * 6.5) + ).shift(m.LEFT * 8) if reverse: first_column_name = "Staging area" @@ -530,8 +530,7 @@ def setup_and_draw_zones( font_size=28, color=self.fontColor, ) - .align_to(self.camera.frame, m.LEFT) - .shift(m.RIGHT * 0.65) + .move_to((vert1.get_center()[0] - 4, 0, 0)) .shift(m.UP * self.zone_title_offset) ) secondColumnTitle = ( @@ -551,8 +550,7 @@ def setup_and_draw_zones( font_size=28, color=self.fontColor, ) - .align_to(self.camera.frame, m.RIGHT) - .shift(m.LEFT * 1.65) + .move_to((vert2.get_center()[0] + 4, 0, 0)) .align_to(firstColumnTitle, m.UP) ) @@ -926,7 +924,7 @@ def draw_dark_ref(self): self.prevRef = refRec def trim_path(self, path): - return (path[:5] + "..." + path[-15:]) if len(path) > 20 else path + return (path[:15] + "..." + path[-15:]) if len(path) > 30 else path def get_remote_tracking_branches(self): remote_refs = [remote.refs for remote in self.repo.remotes] From 62080bf1953e245caddc11a5c077d16190d89f93 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sun, 12 Feb 2023 17:04:29 -0800 Subject: [PATCH 19/25] Refactor manim config into main() and always append repo name to media path Signed-off-by: Jacob Stopak --- git_sim/__main__.py | 33 +++++++++++++++++++++++++++++++-- git_sim/animations.py | 31 +++++-------------------------- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/git_sim/__main__.py b/git_sim/__main__.py index 8d1f7e0..17f970d 100644 --- a/git_sim/__main__.py +++ b/git_sim/__main__.py @@ -1,6 +1,10 @@ import pathlib - import typer +import os +import sys +import datetime +import time +import git import git_sim.add import git_sim.branch @@ -16,12 +20,14 @@ import git_sim.status import git_sim.tag from git_sim.settings import ImgFormat, VideoFormat, settings +from manim import config, WHITE app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]}) @app.callback(no_args_is_help=True) def main( + ctx: typer.Context, animate: bool = typer.Option( settings.animate, help="Animate the simulation and output as an mp4 video", @@ -110,7 +116,7 @@ def main( 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.media_dir = os.path.join(os.path.expanduser(media_dir), "git-sim_media") settings.outro_bottom_text = outro_bottom_text settings.outro_top_text = outro_top_text settings.reverse = reverse @@ -121,6 +127,29 @@ def main( settings.video_format = video_format settings.stdout = stdout + 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] + + settings.media_dir = os.path.join(settings.media_dir, repo_name) + + config.media_dir = settings.media_dir + config.verbosity = "ERROR" + + 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-" + ctx.invoked_subcommand + "_" + t + ".mp4" + app.command()(git_sim.add.add) app.command()(git_sim.branch.branch) diff --git a/git_sim/animations.py b/git_sim/animations.py index 48b4835..251ff91 100644 --- a/git_sim/animations.py +++ b/git_sim/animations.py @@ -1,43 +1,19 @@ import datetime import inspect import os -import pathlib import subprocess import sys import time import cv2 import git.repo -from manim import WHITE, Scene, config +from manim import WHITE, Scene 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] - - config.media_dir = os.path.join( - os.path.expanduser(settings.media_dir), "git-sim_media" - ) - config.verbosity = "ERROR" - - 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": @@ -56,6 +32,9 @@ def handle_animations(scene: Scene) -> None: 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 @@ -65,7 +44,7 @@ def handle_animations(scene: Scene) -> None: + settings.img_format ) image_file_path = os.path.join( - os.path.join(config.media_dir, "images"), image_file_name + os.path.join(settings.media_dir, "images"), image_file_name ) cv2.imwrite(image_file_path, image) if not settings.stdout: From d090f6b93423f58bf2f4be75f2f1e15f460e4a39 Mon Sep 17 00:00:00 2001 From: Jacob Stopak <49353917+initialcommit-io@users.noreply.github.com> Date: Sun, 12 Feb 2023 17:32:02 -0800 Subject: [PATCH 20/25] Update README.md --- README.md | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e4e5d5b..8a742ac 100644 --- a/README.md +++ b/README.md @@ -62,13 +62,35 @@ $ git-sim [global options] [subcommand options] 5) Simulated output will be created as a `.jpg` file. Output files are named using the subcommand executed combined with a timestamp, and by default are stored in a subdirectory called `git-sim_media/`. The location of this subdirectory is customizable using the command line flag `--media-dir=path/to/output`. Note that when the `--animate` global flag is used, render times will be much longer and a `.mp4` video output file will be produced. -6) See global help for list of global options/flags and subcommands: +6) For convenience, environment variables can be set for any global command-line option available in git-sim. All environment variables start with `git_sim_` followed by the name of the option. + +For example, the `--media-dir` option can be set as an environment variable like: + +```console +$ export git_sim_media_dir=~/Desktop +``` + +Similarly, the `--speed` option can be set like: + +```console +$ export git_sim_speed=2 +``` + +Or, more generally: + +```console +$ export git_sim_option_name=option_value +``` + +Explicitly specifying options at the command-line takes precedence over the corresponding environment variable values. + +7) See global help for list of global options/flags and subcommands: ```console $ git-sim -h ``` -7) See subcommand help for list of options/flags for a specific subcommand: +8) See subcommand help for list of options/flags for a specific subcommand: ```console $ git-sim -h @@ -90,7 +112,7 @@ The `[global options]` apply to the overarching `git-sim` simulation itself, inc `--light-mode`: Use a light mode color scheme instead of default dark mode. `--animate`: Instead of outputting a static image, animate the Git command behavior in a .mp4 video. -`--disable-auto-open, -d`: Disable the automatic opening of the image/video file after generation. +`-d`: Disable the automatic opening of the image/video file after generation. Useful to avoid errors in console mode with no GUI. `--reverse, -r`: Display commit history in the reverse direction. `--img-format`: Output format for the image file, i.e. `jpg` or `png`. Default output format is `jpg`. `--stdout`: Write raw image data to stdout while suppressing all other program output. @@ -363,7 +385,7 @@ Optionally, set the environment variable `git_sim_media_dir` to set a global def $ export git_sim_media_dir=path/to/media/directory $ git-sim status ``` -Note: `--media-dir` takes precedence over the environment variable. If you set the environment and still provide the argument, you'll find the media in the path provided by `--media-dir`. +Note: `--media-dir` takes precedence over the environment variable. If you set the environment variable and still provide the argument, you'll find the media in the path provided by `--media-dir`. Generate output video in low quality to speed up rendering time (useful for repeated testing, must include `--animate`): From c0e1e004c4f106a10f68b4c386ed2218a430f983 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sun, 12 Feb 2023 20:40:52 -0800 Subject: [PATCH 21/25] Fix compatibility issues with typing in python versions < 3.11 Signed-off-by: Jacob Stopak --- git_sim/add.py | 7 ++++--- git_sim/restore.py | 7 ++++--- git_sim/settings.py | 5 +++-- git_sim/stash.py | 7 ++++--- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/git_sim/add.py b/git_sim/add.py index 56d8f8c..0d15e80 100644 --- a/git_sim/add.py +++ b/git_sim/add.py @@ -1,16 +1,17 @@ import sys - import git import manim as m import typer +from typing import List + from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings class Add(GitSimBaseCommand): - def __init__(self, files: list[str]): + def __init__(self, files: List[str]): super().__init__() self.hide_first_tag = True self.allow_no_commits = True @@ -82,7 +83,7 @@ def populate_zones( def add( - files: list[str] = typer.Argument( + files: List[str] = typer.Argument( default=None, help="The names of one or more files to add to Git's staging area", ) diff --git a/git_sim/restore.py b/git_sim/restore.py index 8ba5ef3..d6226bd 100644 --- a/git_sim/restore.py +++ b/git_sim/restore.py @@ -1,15 +1,16 @@ import sys - import manim as m import typer +from typing import List + from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings class Restore(GitSimBaseCommand): - def __init__(self, files: list[str]): + def __init__(self, files: List[str]): super().__init__() self.hide_first_tag = True self.files = files @@ -72,7 +73,7 @@ def populate_zones( def restore( - files: list[str] = typer.Argument( + files: List[str] = typer.Argument( default=None, help="The names of one or more files to restore", ) diff --git a/git_sim/settings.py b/git_sim/settings.py index dac7966..8dff4d7 100644 --- a/git_sim/settings.py +++ b/git_sim/settings.py @@ -1,6 +1,7 @@ import pathlib -from enum import Enum +from enum import Enum +from typing import List, Union from pydantic import BaseSettings @@ -19,7 +20,7 @@ class Settings(BaseSettings): animate = False auto_open = True commits = 5 - files: list[pathlib.Path] | None = None + files: Union[List[pathlib.Path], None] = None hide_first_tag = False img_format: ImgFormat = ImgFormat.jpg INFO_STRING = "Simulating: git" diff --git a/git_sim/stash.py b/git_sim/stash.py index ee2eb4e..147b0b5 100644 --- a/git_sim/stash.py +++ b/git_sim/stash.py @@ -1,15 +1,16 @@ import sys - import manim as m import typer +from typing import List + from git_sim.animations import handle_animations from git_sim.git_sim_base_command import GitSimBaseCommand from git_sim.settings import settings class Stash(GitSimBaseCommand): - def __init__(self, files: list[str]): + def __init__(self, files: List[str]): super().__init__() self.hide_first_tag = True self.files = files @@ -80,7 +81,7 @@ def populate_zones( def stash( - files: list[str] = typer.Argument( + files: List[str] = typer.Argument( default=None, help="The name of the file to stash changes for", ) From e827d12dea32595bbd20968f277e11024d985404 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sun, 12 Feb 2023 20:51:09 -0800 Subject: [PATCH 22/25] Fix color of dotted lines Signed-off-by: Jacob Stopak --- git_sim/git_sim_base_command.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/git_sim/git_sim_base_command.py b/git_sim/git_sim_base_command.py index 3575e2f..5d1163f 100644 --- a/git_sim/git_sim_base_command.py +++ b/git_sim/git_sim_base_command.py @@ -870,7 +870,9 @@ def draw_arrow_between_commits(self, startsha, endsha): start = self.drawnCommits[startsha].get_center() end = self.drawnCommits[endsha].get_center() - arrow = DottedLine(start, end, color=self.fontColor).add_tip() + arrow = DottedLine( + start, end, color=self.fontColor, dot_kwargs={"color": self.fontColor} + ).add_tip() length = numpy.linalg.norm(start - end) - 1.65 arrow.set_length(length) self.draw_arrow(True, arrow) From f968e2de75be076db7e4bea32dd722ba912d7aa3 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sun, 12 Feb 2023 21:24:40 -0800 Subject: [PATCH 23/25] Update readme Signed-off-by: Jacob Stopak --- README.md | 3 ++- git_sim/settings.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8a742ac..3ce2e18 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Example: `$ git-sim merge ` - Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick` - Generate an animated video (.mp4) instead of a static image using the `--animate` flag (note: significant performance slowdown, it is recommended to use `--low-quality` to speed up testing and remove when ready to generate presentation-quality video) - Choose between dark mode (default) and light mode +- Specify output formats of either jpg, png, mp4, or webm - Animation only: Add custom branded intro/outro sequences if desired - Animation only: Speed up or slow down animation speed as desired @@ -120,7 +121,7 @@ The `[global options]` apply to the overarching `git-sim` simulation itself, inc Animation-only global options (to be used in conjunction with `--animate`): `--video-format`: Output format for the video file, i.e. `mp4` or `webm`. Default output format is `mp4`. -`--speed=n`: Set the multiple of animation speed of the output simulation, `n` can be an integer or float, default is 1. +`--speed=n`: Set the multiple of animation speed of the output simulation, `n` can be an integer or float, default is 1.5. `--low-quality`: Render the animation in low quality to speed up creation time, recommended for non-presentation use. `--show-intro`: Add an intro sequence with custom logo and title. `--show-outro`: Add an outro sequence with custom logo and text. diff --git a/git_sim/settings.py b/git_sim/settings.py index 8dff4d7..563b445 100644 --- a/git_sim/settings.py +++ b/git_sim/settings.py @@ -36,7 +36,7 @@ class Settings(BaseSettings): show_intro = False show_outro = False speed = 1.5 - title = "Git Sim, by initialcommit.com" + title = "Git-Sim, by initialcommit.com" video_format: VideoFormat = VideoFormat.mp4 stdout = False From cc8d7240f9242e92b9c7ecd205770008438ebe99 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sun, 12 Feb 2023 21:28:00 -0800 Subject: [PATCH 24/25] Update readme Signed-off-by: Jacob Stopak --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ce2e18..bc8ec35 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,13 @@ Similarly, the `--speed` option can be set like: $ export git_sim_speed=2 ``` -Or, more generally: +Boolean flags can be set like: + +```console +$ export git_sim_light_mode=true +``` + +In general: ```console $ export git_sim_option_name=option_value From b1e167fbc39ed7e7388815db7c4c78de9fb94a38 Mon Sep 17 00:00:00 2001 From: Jacob Stopak Date: Sun, 12 Feb 2023 21:37:18 -0800 Subject: [PATCH 25/25] Bump version to 0.2.3 Signed-off-by: Jacob Stopak --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 774fb10..f7de254 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="git-sim", - version="0.2.2", + version="0.2.3", author="Jacob Stopak", author_email="jacob@initialcommit.io", description="Simulate Git commands on your own repos by generating an image (default) or video visualization depicting the command's behavior.",