Skip to content

Commit f7f1bea

Browse files
Merge branch 'push' into dev
2 parents c6bbb11 + 52f05f7 commit f7f1bea

File tree

4 files changed

+188
-5
lines changed

4 files changed

+188
-5
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/git_sim_base_command.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def parse_commits(
7878
i=0,
7979
prevCircle=None,
8080
shift=numpy.array([0.0, 0.0, 0.0]),
81+
make_branches_remote=False,
8182
):
8283
commit = commit or self.get_commit()
8384

@@ -91,7 +92,7 @@ def parse_commits(
9192
if commit != "dark":
9293
if not hide_refs and isNewCommit:
9394
self.draw_head(commit, i, commitId)
94-
self.draw_branch(commit, i)
95+
self.draw_branch(commit, i, make_branches_remote=make_branches_remote)
9596
self.draw_tag(commit, i)
9697
if (
9798
not isinstance(arrow, m.CurvedArrow)
@@ -348,7 +349,7 @@ def draw_head(self, commit, i, commitId):
348349
if i == 0 and self.first_parse:
349350
self.topref = self.prevRef
350351

351-
def draw_branch(self, commit, i):
352+
def draw_branch(self, commit, i, make_branches_remote=False):
352353
x = 0
353354

354355
remote_tracking_branches = self.get_remote_tracking_branches()
@@ -369,7 +370,7 @@ def draw_branch(self, commit, i):
369370
and commit.hexsha == remote_tracking_branches[branch]
370371
):
371372
branchText = m.Text(
372-
branch, font="Monospace", font_size=20, color=self.fontColor
373+
branch if not make_branches_remote else make_branches_remote + "/" + branch, font="Monospace", font_size=20, color=self.fontColor
373374
)
374375
branchRec = m.Rectangle(
375376
color=m.GREEN,
@@ -1137,6 +1138,13 @@ def color_by(self, offset=0):
11371138
elif settings.color_by == "branch":
11381139
pass
11391140

1141+
elif settings.color_by == "notlocal":
1142+
for commit_id in self.drawnCommits:
1143+
try:
1144+
self.orig_repo.commit(commit_id)
1145+
except ValueError:
1146+
self.drawnCommits[commit_id].set_color(m.GOLD)
1147+
11401148
def add_group_to_author_groups(self, author, group):
11411149
if author not in self.author_groups:
11421150
self.author_groups[author] = [group]

git_sim/push.py

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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+
settings.color_by = "notlocal"
26+
27+
if self.remote and self.remote not in self.repo.remotes:
28+
print("git-sim error: no remote with name '" + self.remote + "'")
29+
sys.exit(1)
30+
31+
def construct(self):
32+
if not settings.stdout and not settings.output_only_path and not settings.quiet:
33+
print(
34+
f"{settings.INFO_STRING } {type(self).__name__.lower()} {self.remote if self.remote else ''} {self.branch if self.branch else ''}"
35+
)
36+
37+
self.show_intro()
38+
39+
# Configure paths to make local clone to run networked commands in
40+
git_root = self.repo.git.rev_parse("--show-toplevel")
41+
repo_name = os.path.basename(self.repo.working_dir)
42+
new_dir = os.path.join(tempfile.gettempdir(), "git_sim", repo_name)
43+
new_dir2 = os.path.join(tempfile.gettempdir(), "git_sim", repo_name + "2")
44+
45+
# Save remotes
46+
orig_remotes = self.repo.remotes
47+
48+
# Create local clone of local repo
49+
self.repo = git.Repo.clone_from(git_root, new_dir, no_hardlinks=True)
50+
if self.remote:
51+
for r in orig_remotes:
52+
if self.remote == r.name:
53+
remote_url = r.url
54+
break
55+
else:
56+
remote_url = orig_remotes[0].url
57+
58+
# Create local clone of remote repo to simulate push to so we don't touch the real remote
59+
self.remote_repo = git.Repo.clone_from(remote_url, new_dir2, no_hardlinks=True, bare=True)
60+
61+
# Reset local clone remote to the local clone of remote repo
62+
if self.remote:
63+
for r in self.repo.remotes:
64+
if self.remote == r.name:
65+
r.set_url(new_dir2)
66+
else:
67+
self.repo.remotes[0].set_url(new_dir2)
68+
69+
# Push the local clone into the local clone of the remote repo
70+
push_result = 0
71+
try:
72+
self.repo.git.push(self.remote, self.branch)
73+
# If push fails...
74+
except git.GitCommandError as e:
75+
if "rejected" in e.stderr and "fetch first" in e.stderr:
76+
push_result = 1
77+
self.orig_repo = self.repo
78+
self.repo = self.remote_repo
79+
else:
80+
print(f"git-sim error: git push failed: {e.stderr}")
81+
82+
head_commit = self.get_commit()
83+
self.parse_commits(head_commit, make_branches_remote=(self.remote if self.remote else self.repo.remotes[0].name))
84+
self.recenter_frame()
85+
self.scale_frame()
86+
self.failed_push(push_result)
87+
self.color_by()
88+
self.fadeout()
89+
self.show_outro()
90+
91+
# Unlink the program from the filesystem
92+
self.repo.git.clear_cache()
93+
if self.orig_repo:
94+
self.orig_repo.git.clear_cache()
95+
96+
# Delete the local clones
97+
shutil.rmtree(new_dir, onerror=del_rw)
98+
shutil.rmtree(new_dir2, onerror=del_rw)
99+
100+
def failed_push(self, push_result):
101+
if push_result == 1:
102+
text1 = m.Text(
103+
f"'git push' failed since the remote repo has commits that don't exist locally.",
104+
font="Monospace",
105+
font_size=20,
106+
color=self.fontColor,
107+
weight=m.BOLD,
108+
)
109+
text1.move_to([self.camera.frame.get_center()[0], 5, 0])
110+
111+
text2 = m.Text(
112+
f"Run 'git pull' (or 'git-sim pull' to simulate first) and then try again.",
113+
font="Monospace",
114+
font_size=20,
115+
color=self.fontColor,
116+
weight=m.BOLD,
117+
)
118+
text2.move_to(text1.get_center()).shift(m.DOWN / 2)
119+
120+
text3 = m.Text(
121+
f"Gold commits exist in remote repo, but not locally (need to be pulled).",
122+
font="Monospace",
123+
font_size=20,
124+
color=m.GOLD,
125+
weight=m.BOLD,
126+
)
127+
text3.move_to(text2.get_center()).shift(m.DOWN / 2)
128+
129+
text4 = m.Text(
130+
f"Red commits exist in both local and remote repos.",
131+
font="Monospace",
132+
font_size=20,
133+
color=m.RED,
134+
weight=m.BOLD,
135+
)
136+
text4.move_to(text3.get_center()).shift(m.DOWN / 2)
137+
138+
self.toFadeOut.add(text1)
139+
self.toFadeOut.add(text2)
140+
self.toFadeOut.add(text3)
141+
self.toFadeOut.add(text4)
142+
self.recenter_frame()
143+
self.scale_frame()
144+
if settings.animate:
145+
self.play(m.AddTextLetterByLetter(text2), m.AddTextLetterByLetter(text2), m.AddTextLetterByLetter(text3), m.AddTextLetterByLetter(text4))
146+
else:
147+
self.add(text1, text2, text3, text4)
148+
149+
150+
151+
def del_rw(action, name, exc):
152+
os.chmod(name, stat.S_IWRITE)
153+
os.remove(name)
154+
155+
def push(
156+
remote: str = typer.Argument(
157+
default=None,
158+
help="The name of the remote to push to",
159+
),
160+
branch: str = typer.Argument(
161+
default=None,
162+
help="The name of the branch to push",
163+
),
164+
):
165+
scene = Push(remote=remote, branch=branch)
166+
handle_animations(scene=scene)

0 commit comments

Comments
 (0)