Skip to content

Commit 497edaf

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

File tree

3 files changed

+135
-2
lines changed

3 files changed

+135
-2
lines changed

README.md

Lines changed: 8 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`
29+
- Supported commands: `log`, `status`, `add`, `restore`, `commit`, `stash`, `branch`, `tag`, `reset`, `revert`, `merge`, `rebase`, `cherry-pick`, `switch`, `checkout`, `fetch`, `pull`
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", 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", along with corresponding options.
130130

131131
```console
132132
$ git-sim [global options] <subcommand> [subcommand options]
@@ -297,6 +297,12 @@ Usage: `git-sim fetch <remote> <branch>`
297297

298298
- Fetches the specified `<branch>` from the specified `<remote>` to the local repo
299299

300+
### git pull
301+
Usage: `git-sim pull [<remote> <branch>]`
302+
303+
- Pulls the specified `<branch>` from the specified `<remote>` to the local repo
304+
- If `<remote>` and `<branch>` are not specified, the active branch is pulled from the default remote
305+
300306
## Video animation examples
301307
```console
302308
$ git-sim --animate reset HEAD^

git_sim/__main__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import git_sim.switch
2323
import git_sim.checkout
2424
import git_sim.fetch
25+
import git_sim.pull
2526

2627
from git_sim.settings import ImgFormat, VideoFormat, settings
2728
from manim import config, WHITE
@@ -209,6 +210,7 @@ def main(
209210
app.command()(git_sim.switch.switch)
210211
app.command()(git_sim.checkout.checkout)
211212
app.command()(git_sim.fetch.fetch)
213+
app.command()(git_sim.pull.pull)
212214

213215

214216
if __name__ == "__main__":

git_sim/pull.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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 Pull(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+
43+
# Save remotes and create the local clone
44+
orig_remotes = self.repo.remotes
45+
self.repo = git.Repo.clone_from(git_root, new_dir, no_hardlinks=True)
46+
47+
# Reset the remotes in the local clone to the original remotes
48+
for r1 in orig_remotes:
49+
for r2 in self.repo.remotes:
50+
if r1.name == r2.name:
51+
r2.set_url(r1.url)
52+
53+
# Pull the remote into the local clone
54+
try:
55+
self.repo.git.pull(self.remote, self.branch)
56+
head_commit = self.get_commit()
57+
self.parse_commits(head_commit)
58+
self.recenter_frame()
59+
self.scale_frame()
60+
61+
# But if we get merge conflicts...
62+
except git.GitCommandError as e:
63+
if "CONFLICT" in e.stdout:
64+
# Restrict to default number of commits since we'll show the table/zones
65+
self.n = self.n_default
66+
67+
# Get list of conflicted filenames
68+
self.conflicted_files = re.findall(r"Merge conflict in (.+)", e.stdout)
69+
70+
head_commit = self.get_commit()
71+
self.parse_commits(head_commit)
72+
self.recenter_frame()
73+
self.scale_frame()
74+
75+
# Show the conflicted files names in the table/zones
76+
self.vsplit_frame()
77+
self.setup_and_draw_zones(
78+
first_column_name="----",
79+
second_column_name="Conflicted files",
80+
third_column_name="----",
81+
)
82+
else:
83+
print(f"git-sim error: git pull failed for unhandled reason: {e.stdout}")
84+
shutil.rmtree(new_dir, onerror=del_rw)
85+
sys.exit(1)
86+
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+
94+
# Delete the local clone
95+
shutil.rmtree(new_dir, onerror=del_rw)
96+
97+
# Override to display conflicted filenames
98+
def populate_zones(
99+
self,
100+
firstColumnFileNames,
101+
secondColumnFileNames,
102+
thirdColumnFileNames,
103+
firstColumnArrowMap={},
104+
secondColumnArrowMap={},
105+
thirdColumnArrowMap={},
106+
):
107+
for filename in self.conflicted_files:
108+
secondColumnFileNames.add(filename)
109+
110+
def del_rw(action, name, exc):
111+
os.chmod(name, stat.S_IWRITE)
112+
os.remove(name)
113+
114+
def pull(
115+
remote: str = typer.Argument(
116+
default=None,
117+
help="The name of the remote to pull from",
118+
),
119+
branch: str = typer.Argument(
120+
default=None,
121+
help="The name of the branch to pull",
122+
),
123+
):
124+
scene = Pull(remote=remote, branch=branch)
125+
handle_animations(scene=scene)

0 commit comments

Comments
 (0)