Skip to content
This repository was archived by the owner on Jul 3, 2025. It is now read-only.
Merged

sync #14

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: build

on:
workflow_run:
workflows: ["CI"]
types:
- completed
branches: [master]
workflow_dispatch:

jobs:
build:
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python3 -
echo "$HOME/.local/bin" >> $GITHUB_PATH

- name: Configure Poetry
run: |
poetry config virtualenvs.create true
poetry config virtualenvs.in-project false

- name: Install dependencies
run: |
poetry install --with dev

- name: Check for compilation errors
run: |
poetry run python -c "from gitx.app import GitxApp; print('Import successful')"

- name: Verify app starts without errors
run: |
bash scripts/verify.sh
13 changes: 13 additions & 0 deletions scripts/verify.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
timeout 5s poetry run python -c "from gitx.app import GitxApp; app = GitxApp()" || true
exit_code=$?
if [ -n "$exit_code" ] && [ "$exit_code" -eq 124 ]; then
echo "App initialized successfully (timeout as expected)"
exit 0
elif [ -n "$exit_code" ] && [ "$exit_code" -eq 0 ]; then
echo "App initialized successfully"
exit 0
else
echo "App initialization failed with exit code: $exit_code"
exit 1
fi
158 changes: 151 additions & 7 deletions src/gitx/app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import Container, Grid
from textual.widgets import Header, Footer
from textual.widgets import Header, Footer, Static, Input, Tree
from textual.screen import Screen

from gitx.widgets.status_panel import StatusPanel
from gitx.widgets.file_tree import FileTree
Expand All @@ -26,9 +27,7 @@ class GitxApp(App):
Binding(key="p", action="push", description="Push"),
Binding(key="f", action="pull", description="Pull (fetch)"),
Binding(key="b", action="new_branch", description="New branch"),
Binding(key="?", action="toggle_help", description="Help"),
Binding(key="^p", action="palette", description="Command palette"),
]
Binding(key="r", action="refresh", description="Refresh"),

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -77,11 +76,156 @@ def on_mount(self) -> None:
main_panel = self.query_one(MainPanel)
main_panel.show_welcome()

def action_refresh(self) -> None:
"""Refresh all panels with the latest git data."""
self.query_one(StatusPanel).refresh_status()
self.query_one(FileTree).refresh_tree()
self.query_one(CommitLog).refresh_log()
self.query_one(BranchesPanel).refresh_branches()
self.notify("Refreshed all panels")

def action_toggle_theme(self) -> None:
"""Toggle dark mode."""
self.theme = (
"textual-dark" if self.theme == "textual-light" else "textual-light"
)
self.dark = not self.dark

def action_stage_file(self) -> None:
"""Stage the selected file."""
file_tree = self.query_one(FileTree)
tree = file_tree.query_one(Tree)

if tree.cursor_node and hasattr(tree.cursor_node, 'data') and tree.cursor_node.data:
file_path = tree.cursor_node.data.get("path")
if file_path:
if self.git.stage_file(file_path):
self.notify(f"Staged: {file_path}")
self.action_refresh()
else:
self.notify(f"Failed to stage: {file_path}", severity="error")

def action_unstage_file(self) -> None:
"""Unstage the selected file."""
file_tree = self.query_one(FileTree)
tree = file_tree.query_one(Tree)

if tree.cursor_node and hasattr(tree.cursor_node, 'data') and tree.cursor_node.data:
file_path = tree.cursor_node.data.get("path")
if file_path:
if self.git.unstage_file(file_path):
self.notify(f"Unstaged: {file_path}")
self.action_refresh()
else:
self.notify(f"Failed to unstage: {file_path}", severity="error")

def action_commit(self) -> None:
"""Commit staged changes."""
# Create a modal input dialog for commit message
from textual.widgets import Label # Add this import

class CommitScreen(Screen):
def compose(self) -> ComposeResult:
yield Container(
Label("[bold]Enter commit message:[/bold]"),
Input(id="commit-message", placeholder="Commit message..."),
Static("Press Enter to commit, Esc to cancel"),
id="commit-dialog"
)

def on_key(self, event):
if event.key == "escape":
self.app.pop_screen()
elif event.key == "enter":
commit_msg = self.query_one("#commit-message").value
if commit_msg.strip():
self.app.pop_screen()
if self.app.git.commit(commit_msg):
self.app.notify(f"Committed: {commit_msg}")
self.app.action_refresh()
else:
self.app.notify("Commit failed", severity="error")
else:
self.app.notify("Please enter a commit message", severity="warning")

self.push_screen(CommitScreen())

def action_push(self) -> None:
"""Push changes to remote."""
success, output = self.git.push()
if success:
self.notify("Successfully pushed to remote")
else:
self.notify(f"Push failed: {output}", severity="error")
self.action_refresh()

def action_pull(self) -> None:
"""Pull changes from remote."""
success, output = self.git.pull()
if success:
self.notify("Successfully pulled from remote")
else:
self.notify(f"Pull failed: {output}", severity="error")
self.action_refresh()

def action_new_branch(self) -> None:
"""Create a new branch."""
# Create a modal input dialog for branch name
from textual.widgets import Label # Add this import

class BranchScreen(Screen):
def compose(self) -> ComposeResult:
yield Container(
Label("[bold]Enter new branch name:[/bold]"),
Input(id="branch-name", placeholder="Branch name..."),
Static("Press Enter to create, Esc to cancel"),
id="branch-dialog"
)

def on_key(self, event):
if event.key == "escape":
self.app.pop_screen()
elif event.key == "enter":
branch_name = self.query_one("#branch-name").value
if branch_name.strip():
self.app.pop_screen()
if self.app.git.create_branch(branch_name):
self.app.notify(f"Created and switched to branch: {branch_name}")
self.app.action_refresh()
else:
self.app.notify(f"Failed to create branch: {branch_name}", severity="error")
else:
self.app.notify("Please enter a branch name", severity="warning")

self.push_screen(BranchScreen())

def action_toggle_help(self) -> None:
"""Toggle help screen."""
from textual.widgets import Label # Add this import

class HelpScreen(Screen):
def compose(self) -> ComposeResult:
yield Container(
Label("[bold]GitX Help[/bold]"),
Static("[bold]Keyboard Shortcuts:[/bold]"),
Static("q - Quit"),
Static("t - Toggle theme"),
Static("s - Stage selected file"),
Static("u - Unstage selected file"),
Static("c - Commit staged changes"),
Static("p - Push to remote"),
Static("f - Pull from remote"),
Static("b - Create new branch"),
Static("r - Refresh all panels"),
Static("? - Toggle this help screen"),
Static("^p - Command palette"),
Static(""),
Static("Press any key to close this help"),
id="help-dialog"
)

def on_key(self, event):
self.app.pop_screen()

self.push_screen(HelpScreen())


def action_stage_file(self) -> None:
"""Stage the selected file."""
Expand Down
Loading
Loading