Skip to content

Commit b70bf99

Browse files
Add push subcommand
Signed-off-by: Jacob Stopak <jacob@initialcommit.io>
1 parent 497edaf commit b70bf99

File tree

3 files changed

+141
-2
lines changed

3 files changed

+141
-2
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Example: `$ git-sim merge <branch>`
2626

2727
## Features
2828
- Run a one-liner git-sim command in the terminal to generate a custom Git command visualization (.jpg) from your repo
29-
- Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick`, `switch`, `checkout`, `fetch`, `pull`
29+
- Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick`, `switch`, `checkout`, `fetch`, `pull`, `push`
3030
- 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)
3131
- Color commits by parameter, such as author the `--color-by=author` option
3232
- Choose between dark mode (default) and light mode
@@ -126,7 +126,7 @@ $ git-sim <subcommand> -h
126126
* [Manim (Community version)](https://www.manim.community/)
127127

128128
## Commands
129-
Basic usage is similar to Git itself - `git-sim` takes a familiar set of subcommands including "log", "status", "add", "restore", "commit", "stash", "branch", "tag", "reset", "revert", "merge", "rebase", "cherry-pick", "switch", "checkout", "fetch", "pull", along with corresponding options.
129+
Basic usage is similar to Git itself - `git-sim` takes a familiar set of subcommands including "log", "status", "add", "restore", "commit", "stash", "branch", "tag", "reset", "revert", "merge", "rebase", "cherry-pick", "switch", "checkout", "fetch", "pull", "push" along with corresponding options.
130130

131131
```console
132132
$ git-sim [global options] <subcommand> [subcommand options]
@@ -302,6 +302,13 @@ Usage: `git-sim pull [<remote> <branch>]`
302302

303303
- Pulls the specified `<branch>` from the specified `<remote>` to the local repo
304304
- If `<remote>` and `<branch>` are not specified, the active branch is pulled from the default remote
305+
- If merge conflicts occur, they are displayed in a table
306+
307+
### git push
308+
Usage: `git-sim push [<remote> <branch>]`
309+
310+
- Pushes the specified `<branch>` to the specified `<remote>`
311+
- If `<remote>` and `<branch>` are not specified, the active branch is pushed to the default remote
305312

306313
## Video animation examples
307314
```console

git_sim/__main__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import git_sim.checkout
2424
import git_sim.fetch
2525
import git_sim.pull
26+
import git_sim.push
2627

2728
from git_sim.settings import ImgFormat, VideoFormat, settings
2829
from manim import config, WHITE
@@ -211,6 +212,7 @@ def main(
211212
app.command()(git_sim.checkout.checkout)
212213
app.command()(git_sim.fetch.fetch)
213214
app.command()(git_sim.pull.pull)
215+
app.command()(git_sim.push.push)
214216

215217

216218
if __name__ == "__main__":

git_sim/push.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import sys
2+
import os
3+
from argparse import Namespace
4+
5+
import git
6+
import manim as m
7+
import numpy
8+
import typer
9+
import tempfile
10+
import shutil
11+
import stat
12+
import re
13+
14+
from git_sim.animations import handle_animations
15+
from git_sim.git_sim_base_command import GitSimBaseCommand
16+
from git_sim.settings import settings
17+
18+
19+
class Push(GitSimBaseCommand):
20+
def __init__(self, remote: str = None, branch: str = None):
21+
super().__init__()
22+
self.remote = remote
23+
self.branch = branch
24+
settings.max_branches_per_commit = 2
25+
26+
if self.remote and self.remote not in self.repo.remotes:
27+
print("git-sim error: no remote with name '" + self.remote + "'")
28+
sys.exit(1)
29+
30+
def construct(self):
31+
if not settings.stdout and not settings.output_only_path and not settings.quiet:
32+
print(
33+
f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.remote if self.remote else ''} {self.branch if self.branch else ''}"
34+
)
35+
36+
self.show_intro()
37+
38+
# Configure paths to make local clone to run networked commands in
39+
git_root = self.repo.git.rev_parse("--show-toplevel")
40+
repo_name = os.path.basename(self.repo.working_dir)
41+
new_dir = os.path.join(tempfile.gettempdir(), "git_sim", repo_name)
42+
new_dir2 = os.path.join(tempfile.gettempdir(), "git_sim", repo_name + "2")
43+
44+
# Save remotes
45+
orig_remotes = self.repo.remotes
46+
47+
# Create local clone of local repo
48+
self.repo = git.Repo.clone_from(git_root, new_dir, no_hardlinks=True)
49+
if self.remote:
50+
for r in orig_remotes:
51+
if self.remote == r.name:
52+
remote_url = r.url
53+
break
54+
else:
55+
remote_url = orig_remotes[0].url
56+
57+
# Create local clone of remote repo to simulate push to so we don't touch the real remote
58+
self.remote_repo = git.Repo.clone_from(remote_url, new_dir2, no_hardlinks=True, bare=True)
59+
60+
# Reset local clone remote to the local clone of remote repo
61+
if self.remote:
62+
for r in self.repo.remotes:
63+
if self.remote == r.name:
64+
r.set_url(new_dir2)
65+
else:
66+
self.repo.remotes[0].set_url(new_dir2)
67+
68+
# Push the local clone into the local clone of the remote repo
69+
try:
70+
self.repo.git.push(self.remote, self.branch)
71+
head_commit = self.get_commit()
72+
self.parse_commits(head_commit)
73+
self.recenter_frame()
74+
self.scale_frame()
75+
76+
# But if we get merge conflicts...
77+
except git.GitCommandError as e:
78+
if "CONFLICT" in e.stdout:
79+
# Restrict to default number of commits since we'll show the table/zones
80+
self.n = self.n_default
81+
82+
# Get list of conflicted filenames
83+
self.conflicted_files = re.findall(r"Merge conflict in (.+)", e.stdout)
84+
85+
head_commit = self.get_commit()
86+
self.parse_commits(head_commit)
87+
self.recenter_frame()
88+
self.scale_frame()
89+
90+
# Show the conflicted files names in the table/zones
91+
self.vsplit_frame()
92+
self.setup_and_draw_zones(
93+
first_column_name="----",
94+
second_column_name="Conflicted files",
95+
third_column_name="----",
96+
)
97+
else:
98+
print(f"git-sim error: git push failed: {e}")
99+
self.repo.git.clear_cache()
100+
shutil.rmtree(new_dir, onerror=del_rw)
101+
shutil.rmtree(new_dir2, onerror=del_rw)
102+
sys.exit(1)
103+
104+
self.color_by()
105+
self.fadeout()
106+
self.show_outro()
107+
108+
# Unlink the program from the filesystem
109+
self.repo.git.clear_cache()
110+
111+
# Delete the local clones
112+
shutil.rmtree(new_dir, onerror=del_rw)
113+
shutil.rmtree(new_dir2, onerror=del_rw)
114+
115+
def del_rw(action, name, exc):
116+
os.chmod(name, stat.S_IWRITE)
117+
os.remove(name)
118+
119+
def push(
120+
remote: str = typer.Argument(
121+
default=None,
122+
help="The name of the remote to push to",
123+
),
124+
branch: str = typer.Argument(
125+
default=None,
126+
help="The name of the branch to push",
127+
),
128+
):
129+
scene = Push(remote=remote, branch=branch)
130+
handle_animations(scene=scene)

0 commit comments

Comments
 (0)