A terminal-based Git client built with Python and Textual. Designed to replace GUI tools like GitHub Desktop while adding features that are only practical in a terminal environment — primarily the ability to manage and operate on many repositories simultaneously.
- Multi-repo dashboard — all your repositories in one view with branch name, ahead/behind counts and dirty-state indicators updated in real time.
- Auto-detection — run
gitplex .from any parent directory and it will find every git repository in the immediate subdirectories automatically. - Interactive staging — stage and unstage files individually. Switch to hunk mode to stage only specific chunks of a file without touching the rest.
- Bulk operations — select any subset of repos with checkboxes, then pull, push or fetch all of them concurrently with a single keypress.
- Commit graph — visual
git log --graphoutput with colour-coded branches, plus a structured commit table with author, date and refs. - Commit history actions — select any commit in the Log table and cherry-pick, revert, reset (soft / mixed / hard) or amend it directly from the TUI. Destructive operations require an explicit confirmation.
- Commit dialog — write and apply commit messages without leaving the TUI.
- Branch management — list branches, switch between them, create new ones.
- Force push — push with
--force-with-leaseafter a rebase or reset; fails safely if the remote was updated by someone else in the meantime. - Stash — stash dirty changes on the current repository instantly.
- Create repositories — run
git initon a new empty directory or on an existing folder full of files, with an optional automatic initial commit. - Config persistence — repository list is saved in
~/.config/gitplex/config.jsonand restored between sessions. - Clone repositories — clone any remote repository by URL directly from the TUI, with automatic destination path suggestion.
- Remote management — add, update or remove remotes for the current repository without leaving the app.
- System git — uses the
gitbinary already on your system, inheriting your.gitconfig, SSH keys, GPG signing and credential helpers with no extra configuration.
- Python 3.11 or newer
- git (any reasonably recent version)
- Linux or macOS — Windows is not currently supported, but support may be added in the future
pip install gitplex
dnf copr enable firexrwt/gitplex
dnf install gitplex
git clone https://github.com/firexrwt/gitplex.git
cd gitplex
pip install .
gitplex open the TUI, load repos saved in config
gitplex . scan the current directory for git repos
gitplex ~/projects scan any directory for git repos
gitplex <url> clone a repo, add it to config, and open the TUI
gitplex <url> --dest <path> clone to a specific directory
gitplex --help print usage and key binding reference
gitplex --version print the version number
When a directory is passed, GitPlex scans its immediate children for .git
directories and adds every repository it finds to the config automatically.
Repositories that are already tracked are not duplicated.
When a URL is passed, GitPlex clones the repository into a subdirectory of the
current working directory (named after the repository), adds it to the config,
and opens the TUI. Use --dest to specify a different destination path.
+------------------+--------------------------------------------+
| REPOS | [ Diff ] [ Graph ] |
| | |
| my-app main 2 | @@ -14,7 +14,9 @@ |
| backend main | def process(data): |
| infra main 1 | - return data |
| | + result = transform(data) |
+------------------+ + return result |
| [ Unstaged ][ Staged ] |
| | |
| M src/main.py | |
| M src/utils.py | |
| ? scratch.txt | |
+------------------+--------------------------------------------+
The left column is split vertically: the top half lists repositories, the bottom half lists changed files for the selected repository. The right column shows either the diff for the selected file or the commit graph, switchable via tabs at the top.
| Key | Action |
|---|---|
r |
Refresh all repository statuses |
q |
Quit |
Tab |
Move focus to the next panel |
Shift+Tab |
Move focus to the previous panel |
| Key | Action |
|---|---|
n |
Create a new repository (git init, new or existing folder) |
a |
Add an existing local repository to the tracked list |
C |
Clone a remote repository by URL |
R |
Manage remotes for the current repo (add / set-url / remove) |
X |
Remove the currently selected repository from the tracked list |
These act on the repository currently selected in the repo list.
| Key | Action |
|---|---|
c |
Open the commit dialog (requires at least one staged file) |
p |
Pull (current repo) |
P |
Push (current repo) |
F |
Force push with --force-with-lease (requires confirmation) |
f |
Fetch --all --prune (current repo) |
b |
Open the branch dialog (switch or create branch) |
S |
Stash all dirty changes on the current repo |
Bulk operations run on every repository that has its checkbox ticked. If no checkboxes are ticked, the operation runs on all tracked repositories.
| Key | Action |
|---|---|
Ctrl+A |
Toggle selection of all repositories |
Ctrl+P |
Bulk pull (all selected repos) |
Ctrl+U |
Bulk push (all selected repos) |
Ctrl+F |
Bulk fetch (all selected repos) |
Bulk operations run concurrently (up to 4 at a time) and stream output to a progress dialog as each repository completes.
The file list shows either unstaged or staged files depending on which tab is
active. Press Space on a file to toggle its staged state.
| Key | Action |
|---|---|
Space |
Stage the highlighted file / unstage it |
A |
Stage all unstaged files at once |
U |
Unstage all staged files at once |
d |
Discard unstaged changes in the highlighted file |
The Log tab inside the Graph panel shows a structured commit table.
| Key | Action |
|---|---|
Enter |
Open the commit actions dialog for the highlighted commit |
Commit actions available per commit:
| Action | Description |
|---|---|
| Cherry-pick | Apply this commit on top of the current branch |
| Revert | Create a new commit that undoes the changes |
| Reset soft | Move HEAD here; keep changes staged |
| Reset mixed | Move HEAD here; keep changes unstaged |
| Reset hard | Move HEAD here and discard all changes (requires confirmation) |
| Amend | Edit the commit message (available only for the HEAD commit) |
| Key | Action |
|---|---|
h |
Toggle hunk mode (split the diff into individual hunks) |
s |
Stage the currently highlighted hunk (hunk mode only) |
j |
Scroll down |
k |
Scroll up |
g |
Scroll to the top |
G |
Scroll to the bottom |
When hunk mode is active the diff panel splits into two sections: a list of
hunks at the top and the content of the highlighted hunk below. Navigate the
hunk list with the arrow keys, press s to stage the selected hunk. After
staging, the diff reloads automatically so the line numbers remain correct for
any remaining hunks.
cd ~/projects # parent directory containing all your repos
gitplex . # GitPlex scans and adds every repo it finds
Once inside the TUI:
- Scroll the repo list to get an overview of what is dirty and what is ahead or behind remote.
- Press
Ctrl+Fto fetch all repos at once and update the ahead/behind counters. - Press
Ctrl+Pto pull everything that is behind. - Select a repo, browse changed files, stage what you want, write a commit
message with
c. - Push with
Por do a finalCtrl+Ufor everything.
- Select the repository in the left panel.
- Select the file in the Unstaged tab.
- Press
hin the diff panel to enter hunk mode. - Navigate to the hunk you want and press
s. - Switch to the Staged tab to confirm only that hunk moved across.
- Press
cto commit.
- On GitHub, create a new empty repository (no README, no .gitignore).
- In GitPlex, select your repository in the repo list.
- Press
Rto open the remotes dialog. - Enter name
originand the URL GitHub gave you (https://github.com/you/repo.gitor[email protected]:you/repo.git). - Press "Add".
- Back in the main view, press
Pto push. On the first push git may reject it if the remote and local histories diverge — in that case rungit push -u origin mainonce from the terminal; subsequent pushes will work from GitPlex normally.
- Press
Cfrom anywhere in the TUI. - Paste the remote URL. GitPlex suggests a destination path automatically based on the repository name under your home directory.
- Optionally change the destination path.
- Confirm. The clone runs in the background and the repository appears in the list when it finishes.
- Press
nanywhere in the TUI. - Enter the path to the new directory (it does not have to exist yet) or the path to an existing folder that is not yet a repository.
- Set a branch name if you want something other than
main. - Tick "Make initial commit" if the folder already contains files you want to track from the start, and optionally edit the commit message.
- Confirm. The repository is initialised and added to the tracked list immediately.
The config file lives at ~/.config/gitplex/config.json. It is created
automatically on first run and updated whenever you add or remove a repository.
You can edit it directly if needed — the format is plain JSON:
{
"repos": [
"/home/user/projects/my-app",
"/home/user/projects/backend",
"/home/user/projects/infra"
],
"max_log_entries": 80,
"auto_fetch_interval": 0
}| Field | Default | Description |
|---|---|---|
repos |
[] |
Absolute paths to tracked repositories |
max_log_entries |
80 |
Maximum commits shown in the graph and log table |
auto_fetch_interval |
0 |
Seconds between automatic fetches (0 = disabled) |
src/gitplex/
app.py main application class and all key binding handlers
app.tcss Textual CSS stylesheet
config.py config file read/write
main.py CLI entry point
git/
repo.py GitRepo wrapper around subprocess git calls
diff.py unified diff parser and Rich markup renderer
widgets/
repo_list.py repository panel (left top)
file_list.py staged / unstaged file panel (left bottom)
diff_view.py diff viewer with hunk mode (right)
graph_view.py commit graph and log table (right)
modals.py all modal dialogs
MIT