gitlane 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. gitlane-0.1.0/LICENSE +21 -0
  2. gitlane-0.1.0/PKG-INFO +156 -0
  3. gitlane-0.1.0/README.md +126 -0
  4. gitlane-0.1.0/pyproject.toml +50 -0
  5. gitlane-0.1.0/setup.cfg +4 -0
  6. gitlane-0.1.0/src/gitlane/__init__.py +3 -0
  7. gitlane-0.1.0/src/gitlane/__main__.py +7 -0
  8. gitlane-0.1.0/src/gitlane/blobs.py +77 -0
  9. gitlane-0.1.0/src/gitlane/branches.py +267 -0
  10. gitlane-0.1.0/src/gitlane/cli.py +130 -0
  11. gitlane-0.1.0/src/gitlane/git_ops.py +523 -0
  12. gitlane-0.1.0/src/gitlane/graph.py +319 -0
  13. gitlane-0.1.0/src/gitlane/launcher.py +71 -0
  14. gitlane-0.1.0/src/gitlane/server.py +1039 -0
  15. gitlane-0.1.0/src/gitlane/stash.py +181 -0
  16. gitlane-0.1.0/src/gitlane/static/app.css +643 -0
  17. gitlane-0.1.0/src/gitlane/static/gitlane.svg +15 -0
  18. gitlane-0.1.0/src/gitlane/static/htmx.min.js +1 -0
  19. gitlane-0.1.0/src/gitlane/static/index.html +822 -0
  20. gitlane-0.1.0/src/gitlane/workspace.py +211 -0
  21. gitlane-0.1.0/src/gitlane.egg-info/PKG-INFO +156 -0
  22. gitlane-0.1.0/src/gitlane.egg-info/SOURCES.txt +36 -0
  23. gitlane-0.1.0/src/gitlane.egg-info/dependency_links.txt +1 -0
  24. gitlane-0.1.0/src/gitlane.egg-info/entry_points.txt +2 -0
  25. gitlane-0.1.0/src/gitlane.egg-info/requires.txt +10 -0
  26. gitlane-0.1.0/src/gitlane.egg-info/top_level.txt +1 -0
  27. gitlane-0.1.0/tests/test_blobs.py +90 -0
  28. gitlane-0.1.0/tests/test_branches.py +358 -0
  29. gitlane-0.1.0/tests/test_changes.py +262 -0
  30. gitlane-0.1.0/tests/test_cli.py +130 -0
  31. gitlane-0.1.0/tests/test_compat.py +55 -0
  32. gitlane-0.1.0/tests/test_e2e.py +716 -0
  33. gitlane-0.1.0/tests/test_graph.py +336 -0
  34. gitlane-0.1.0/tests/test_server.py +619 -0
  35. gitlane-0.1.0/tests/test_stash.py +195 -0
  36. gitlane-0.1.0/tests/test_status.py +144 -0
  37. gitlane-0.1.0/tests/test_sync.py +253 -0
  38. gitlane-0.1.0/tests/test_workspace.py +280 -0
gitlane-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gustavo Leite
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
gitlane-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.4
2
+ Name: gitlane
3
+ Version: 0.1.0
4
+ Summary: A free, open-source Git client that runs in your browser.
5
+ Author: Gustavo Leite
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/gustavojoseleite/gitlane
8
+ Project-URL: Issues, https://github.com/gustavojoseleite/gitlane/issues
9
+ Project-URL: Changelog, https://github.com/gustavojoseleite/gitlane/blob/main/CHANGELOG.md
10
+ Keywords: git,git-client,gui,commit-graph,browser,version-control
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Topic :: Software Development :: Version Control :: Git
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: fastapi>=0.110
22
+ Requires-Dist: uvicorn>=0.29
23
+ Requires-Dist: GitPython>=3.1.40
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8; extra == "dev"
26
+ Requires-Dist: httpx>=0.27; extra == "dev"
27
+ Provides-Extra: e2e
28
+ Requires-Dist: playwright>=1.40; extra == "e2e"
29
+ Dynamic: license-file
30
+
31
+ # gitlane
32
+
33
+ **A free, open-source Git client that runs in your browser.** Commit, push, pull, see the
34
+ commit graph, manage branches and stashes — in a clean local web app, with one command.
35
+
36
+ <picture>
37
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/gustavojoseleite/gitlane/main/docs/screenshots/main-dark.png">
38
+ <img alt="gitlane showing a repository: changed files on the left, the commit graph in the middle, a commit with its diff on the right" src="https://raw.githubusercontent.com/gustavojoseleite/gitlane/main/docs/screenshots/main-light.png">
39
+ </picture>
40
+
41
+ ## Features
42
+
43
+ - **Changes** — see changed, staged and untracked files; stage, unstage or discard whole files; commit with a summary and description
44
+ - **Diffs** — for your working files and any commit, with **before/after previews for images**
45
+ - **Commit graph** — all branches with colored lanes, branch/tag labels and your stashes; fast on repositories with tens of thousands of commits
46
+ - **Right-click menus** — checkout, create a branch, cherry-pick, revert, reset (soft / mixed / hard), merge, delete branches (also on the remote)
47
+ - **Sync** — fetch, pull, push, and force push that always uses `--force-with-lease`
48
+ - **Stash** — save (including untracked files), apply, pop, drop, and look inside a stash
49
+ - **Merge conflicts** — see the conflicted files, then *abort* or *mark resolved and commit*
50
+ - **Tabs** — several repositories at once; recent repos and a folder browser to open more
51
+ - **Safe by default** — dangerous actions ask first; runs only on your computer (see [Security](#security))
52
+ - Light and dark theme (follows your system)
53
+
54
+ ![Right-click menu on a commit](https://raw.githubusercontent.com/gustavojoseleite/gitlane/main/docs/screenshots/menu.png)
55
+
56
+ ## Install
57
+
58
+ You need **Python 3.10+** and **Git 2.23+** (most systems already have both).
59
+
60
+ The recommended way is [pipx](https://pipx.pypa.io/) or [uv](https://docs.astral.sh/uv/). They
61
+ install gitlane in its own environment, so it never conflicts with your other Python projects:
62
+
63
+ ```bash
64
+ pipx install gitlane
65
+ # or
66
+ uv tool install gitlane
67
+ ```
68
+
69
+ Just want to try it, without installing anything? With uv:
70
+
71
+ ```bash
72
+ uvx gitlane
73
+ ```
74
+
75
+ <details>
76
+ <summary>Don't have pipx or uv yet?</summary>
77
+
78
+ | System | Install pipx |
79
+ |---|---|
80
+ | Ubuntu / Debian | `sudo apt install pipx && pipx ensurepath` |
81
+ | Fedora | `sudo dnf install pipx && pipx ensurepath` |
82
+ | macOS | `brew install pipx && pipx ensurepath` |
83
+ | Windows | `py -m pip install --user pipx` then `py -m pipx ensurepath` |
84
+
85
+ Open a new terminal afterwards so the `gitlane` command is found.
86
+ </details>
87
+
88
+ <details>
89
+ <summary>Install the latest development version from GitHub</summary>
90
+
91
+ ```bash
92
+ pipx install git+https://github.com/gustavojoseleite/gitlane
93
+ ```
94
+ </details>
95
+
96
+ **Update:** `pipx upgrade gitlane` (or `uv tool upgrade gitlane`)
97
+ **Uninstall:** `pipx uninstall gitlane` (or `uv tool uninstall gitlane`)
98
+
99
+ ## Usage
100
+
101
+ ```bash
102
+ gitlane # inside a repository: open it
103
+ gitlane ~/dev/my-project # open a specific repository
104
+ gitlane # anywhere else: reopen your tabs, or pick a repository
105
+ ```
106
+
107
+ gitlane starts a small server on your computer and opens it in your browser. Keep the terminal
108
+ open while you use it; press **Ctrl+C** to stop. Use **+** next to the tabs to open more
109
+ repositories. Open tabs and recent repos are remembered.
110
+
111
+ | Option | What it does |
112
+ |---|---|
113
+ | `--port 8000` | use a fixed port (default: a free one) |
114
+ | `--no-browser` | only print the link, don't open the browser |
115
+ | `--install-launcher` | add gitlane to your app menu (Linux) |
116
+ | `--remove-launcher` | remove it from the app menu |
117
+ | `--version` | show the version |
118
+
119
+ **Keyboard:** `↑` `↓` move through commits · `Enter` open · `R` refresh · `Ctrl+Enter` commit ·
120
+ `Esc` close menus and dialogs. Drag the dividers between panels to resize them (double-click to reset).
121
+
122
+ ### Push and pull: logging in
123
+
124
+ gitlane uses **your own Git setup** and never asks for or stores passwords. If `git push` works in
125
+ your terminal without typing a password, it works in gitlane too.
126
+
127
+ - **SSH remotes** (`git@github.com:you/repo.git`) — recommended. Set up an SSH key once:
128
+ [GitHub guide](https://docs.github.com/en/authentication/connecting-to-github-with-ssh).
129
+ - **HTTPS remotes** (`https://github.com/you/repo.git`) — need a credential helper, for example
130
+ `gh auth setup-git` ([GitHub CLI](https://cli.github.com/)) or
131
+ [Git Credential Manager](https://github.com/git-ecosystem/git-credential-manager).
132
+ Or switch the remote to SSH: `git remote set-url origin git@github.com:you/repo.git`
133
+
134
+ ## Security
135
+
136
+ gitlane is a local app, but a web server on your computer still needs care:
137
+
138
+ - It listens **only on `127.0.0.1`** — other computers can't reach it.
139
+ - Every start creates a **secret token** in the link; without it every request is refused.
140
+ The token is then kept in a cookie that other websites can't use.
141
+ - It rejects requests from other websites (Origin and Host checks, which also block DNS rebinding).
142
+ - Changes are only made with `POST` requests; dangerous actions ask you first.
143
+ - Image previews are served so that an SVG from a repository can never run scripts.
144
+
145
+ Found a security problem? Please open an issue without the details first, and we'll find a private way to talk.
146
+
147
+ ## Contributing
148
+
149
+ Bug reports, ideas and pull requests are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for how to
150
+ set up the project, run the tests and send a change.
151
+
152
+ ## License
153
+
154
+ [MIT](LICENSE) © 2026 Gustavo Leite — free to use, change and share, including for commercial work.
155
+
156
+ gitlane includes [htmx](https://htmx.org/) (Zero-Clause BSD license).
@@ -0,0 +1,126 @@
1
+ # gitlane
2
+
3
+ **A free, open-source Git client that runs in your browser.** Commit, push, pull, see the
4
+ commit graph, manage branches and stashes — in a clean local web app, with one command.
5
+
6
+ <picture>
7
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/gustavojoseleite/gitlane/main/docs/screenshots/main-dark.png">
8
+ <img alt="gitlane showing a repository: changed files on the left, the commit graph in the middle, a commit with its diff on the right" src="https://raw.githubusercontent.com/gustavojoseleite/gitlane/main/docs/screenshots/main-light.png">
9
+ </picture>
10
+
11
+ ## Features
12
+
13
+ - **Changes** — see changed, staged and untracked files; stage, unstage or discard whole files; commit with a summary and description
14
+ - **Diffs** — for your working files and any commit, with **before/after previews for images**
15
+ - **Commit graph** — all branches with colored lanes, branch/tag labels and your stashes; fast on repositories with tens of thousands of commits
16
+ - **Right-click menus** — checkout, create a branch, cherry-pick, revert, reset (soft / mixed / hard), merge, delete branches (also on the remote)
17
+ - **Sync** — fetch, pull, push, and force push that always uses `--force-with-lease`
18
+ - **Stash** — save (including untracked files), apply, pop, drop, and look inside a stash
19
+ - **Merge conflicts** — see the conflicted files, then *abort* or *mark resolved and commit*
20
+ - **Tabs** — several repositories at once; recent repos and a folder browser to open more
21
+ - **Safe by default** — dangerous actions ask first; runs only on your computer (see [Security](#security))
22
+ - Light and dark theme (follows your system)
23
+
24
+ ![Right-click menu on a commit](https://raw.githubusercontent.com/gustavojoseleite/gitlane/main/docs/screenshots/menu.png)
25
+
26
+ ## Install
27
+
28
+ You need **Python 3.10+** and **Git 2.23+** (most systems already have both).
29
+
30
+ The recommended way is [pipx](https://pipx.pypa.io/) or [uv](https://docs.astral.sh/uv/). They
31
+ install gitlane in its own environment, so it never conflicts with your other Python projects:
32
+
33
+ ```bash
34
+ pipx install gitlane
35
+ # or
36
+ uv tool install gitlane
37
+ ```
38
+
39
+ Just want to try it, without installing anything? With uv:
40
+
41
+ ```bash
42
+ uvx gitlane
43
+ ```
44
+
45
+ <details>
46
+ <summary>Don't have pipx or uv yet?</summary>
47
+
48
+ | System | Install pipx |
49
+ |---|---|
50
+ | Ubuntu / Debian | `sudo apt install pipx && pipx ensurepath` |
51
+ | Fedora | `sudo dnf install pipx && pipx ensurepath` |
52
+ | macOS | `brew install pipx && pipx ensurepath` |
53
+ | Windows | `py -m pip install --user pipx` then `py -m pipx ensurepath` |
54
+
55
+ Open a new terminal afterwards so the `gitlane` command is found.
56
+ </details>
57
+
58
+ <details>
59
+ <summary>Install the latest development version from GitHub</summary>
60
+
61
+ ```bash
62
+ pipx install git+https://github.com/gustavojoseleite/gitlane
63
+ ```
64
+ </details>
65
+
66
+ **Update:** `pipx upgrade gitlane` (or `uv tool upgrade gitlane`)
67
+ **Uninstall:** `pipx uninstall gitlane` (or `uv tool uninstall gitlane`)
68
+
69
+ ## Usage
70
+
71
+ ```bash
72
+ gitlane # inside a repository: open it
73
+ gitlane ~/dev/my-project # open a specific repository
74
+ gitlane # anywhere else: reopen your tabs, or pick a repository
75
+ ```
76
+
77
+ gitlane starts a small server on your computer and opens it in your browser. Keep the terminal
78
+ open while you use it; press **Ctrl+C** to stop. Use **+** next to the tabs to open more
79
+ repositories. Open tabs and recent repos are remembered.
80
+
81
+ | Option | What it does |
82
+ |---|---|
83
+ | `--port 8000` | use a fixed port (default: a free one) |
84
+ | `--no-browser` | only print the link, don't open the browser |
85
+ | `--install-launcher` | add gitlane to your app menu (Linux) |
86
+ | `--remove-launcher` | remove it from the app menu |
87
+ | `--version` | show the version |
88
+
89
+ **Keyboard:** `↑` `↓` move through commits · `Enter` open · `R` refresh · `Ctrl+Enter` commit ·
90
+ `Esc` close menus and dialogs. Drag the dividers between panels to resize them (double-click to reset).
91
+
92
+ ### Push and pull: logging in
93
+
94
+ gitlane uses **your own Git setup** and never asks for or stores passwords. If `git push` works in
95
+ your terminal without typing a password, it works in gitlane too.
96
+
97
+ - **SSH remotes** (`git@github.com:you/repo.git`) — recommended. Set up an SSH key once:
98
+ [GitHub guide](https://docs.github.com/en/authentication/connecting-to-github-with-ssh).
99
+ - **HTTPS remotes** (`https://github.com/you/repo.git`) — need a credential helper, for example
100
+ `gh auth setup-git` ([GitHub CLI](https://cli.github.com/)) or
101
+ [Git Credential Manager](https://github.com/git-ecosystem/git-credential-manager).
102
+ Or switch the remote to SSH: `git remote set-url origin git@github.com:you/repo.git`
103
+
104
+ ## Security
105
+
106
+ gitlane is a local app, but a web server on your computer still needs care:
107
+
108
+ - It listens **only on `127.0.0.1`** — other computers can't reach it.
109
+ - Every start creates a **secret token** in the link; without it every request is refused.
110
+ The token is then kept in a cookie that other websites can't use.
111
+ - It rejects requests from other websites (Origin and Host checks, which also block DNS rebinding).
112
+ - Changes are only made with `POST` requests; dangerous actions ask you first.
113
+ - Image previews are served so that an SVG from a repository can never run scripts.
114
+
115
+ Found a security problem? Please open an issue without the details first, and we'll find a private way to talk.
116
+
117
+ ## Contributing
118
+
119
+ Bug reports, ideas and pull requests are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for how to
120
+ set up the project, run the tests and send a change.
121
+
122
+ ## License
123
+
124
+ [MIT](LICENSE) © 2026 Gustavo Leite — free to use, change and share, including for commercial work.
125
+
126
+ gitlane includes [htmx](https://htmx.org/) (Zero-Clause BSD license).
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "gitlane"
7
+ version = "0.1.0"
8
+ description = "A free, open-source Git client that runs in your browser."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.10"
13
+ authors = [{ name = "Gustavo Leite" }]
14
+ keywords = ["git", "git-client", "gui", "commit-graph", "browser", "version-control"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Environment :: Web Environment",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3 :: Only",
22
+ "Topic :: Software Development :: Version Control :: Git",
23
+ ]
24
+ dependencies = [
25
+ "fastapi>=0.110",
26
+ "uvicorn>=0.29",
27
+ "GitPython>=3.1.40",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["pytest>=8", "httpx>=0.27"]
32
+ # Browser tests (tests/test_e2e.py) use your installed Chrome; skipped if missing.
33
+ e2e = ["playwright>=1.40"]
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/gustavojoseleite/gitlane"
37
+ Issues = "https://github.com/gustavojoseleite/gitlane/issues"
38
+ Changelog = "https://github.com/gustavojoseleite/gitlane/blob/main/CHANGELOG.md"
39
+
40
+ [project.scripts]
41
+ gitlane = "gitlane.cli:main"
42
+
43
+ [tool.setuptools.packages.find]
44
+ where = ["src"]
45
+
46
+ [tool.setuptools.package-data]
47
+ gitlane = ["static/*"]
48
+
49
+ [tool.pytest.ini_options]
50
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """gitlane: a free, open-source Git client that runs in your browser."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,7 @@
1
+ """Allow ``python -m gitlane`` (used by the app-menu launcher)."""
2
+
3
+ import sys
4
+
5
+ from .cli import main
6
+
7
+ sys.exit(main())
@@ -0,0 +1,77 @@
1
+ """Reading file contents at a given version, used for image previews."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path, PurePosixPath
6
+
7
+ import git
8
+
9
+ from .git_ops import GitError, check_paths
10
+ from .graph import resolve_commit
11
+
12
+ MAX_BLOB_BYTES = 20 * 1024 * 1024
13
+
14
+ IMAGE_TYPES = {
15
+ ".png": "image/png",
16
+ ".jpg": "image/jpeg",
17
+ ".jpeg": "image/jpeg",
18
+ ".gif": "image/gif",
19
+ ".webp": "image/webp",
20
+ ".avif": "image/avif",
21
+ ".bmp": "image/bmp",
22
+ ".ico": "image/x-icon",
23
+ ".svg": "image/svg+xml",
24
+ }
25
+
26
+ # Special revisions besides commit ids.
27
+ WORKTREE = "WORKTREE" # the file on disk
28
+ INDEX = "INDEX" # the staged version
29
+ HEAD = "HEAD" # the last commit
30
+
31
+
32
+ def image_type(path: str) -> str | None:
33
+ return IMAGE_TYPES.get(PurePosixPath(path).suffix.lower())
34
+
35
+
36
+ def _spec(repo: git.Repo, rev: str, path: str) -> str:
37
+ if rev == INDEX:
38
+ return f":0:{path}"
39
+ if rev == HEAD:
40
+ return f"HEAD:{path}"
41
+ return f"{resolve_commit(repo, rev)}:{path}"
42
+
43
+
44
+ def _worktree_file(repo: git.Repo, path: str) -> Path | None:
45
+ root = Path(repo.working_tree_dir).resolve()
46
+ f = (root / path).resolve()
47
+ if not f.is_relative_to(root) or not f.is_file(): # also blocks symlinks pointing outside
48
+ return None
49
+ return f
50
+
51
+
52
+ def blob_exists(repo: git.Repo, rev: str, path: str) -> bool:
53
+ (path,) = check_paths([path])
54
+ if rev == WORKTREE:
55
+ return _worktree_file(repo, path) is not None
56
+ code, _, _ = repo.git.cat_file("-e", _spec(repo, rev, path),
57
+ with_extended_output=True, with_exceptions=False)
58
+ return code == 0
59
+
60
+
61
+ def read_blob(repo: git.Repo, rev: str, path: str) -> bytes | None:
62
+ """Contents of ``path`` at ``rev``; None if the file does not exist there."""
63
+ (path,) = check_paths([path])
64
+ if rev == WORKTREE:
65
+ f = _worktree_file(repo, path)
66
+ if f is None:
67
+ return None
68
+ if f.stat().st_size > MAX_BLOB_BYTES:
69
+ raise GitError("File is too large to preview.")
70
+ return f.read_bytes()
71
+ spec = _spec(repo, rev, path)
72
+ code, size, _ = repo.git.cat_file("-s", spec, with_extended_output=True, with_exceptions=False)
73
+ if code != 0:
74
+ return None
75
+ if int(size) > MAX_BLOB_BYTES:
76
+ raise GitError("File is too large to preview.")
77
+ return repo.git.cat_file("blob", spec, stdout_as_string=False, strip_newline_in_stdout=False)