Skip to content

Architecture

Loky edited this page Oct 14, 2026 · 6 revisions

Architecture

core/ holds git and GitHub logic, ui/ holds presentation only. Modules talk through signals. The engine has no knowledge of the UI, and the UI never calls OS.execute().

Core flow

UI panel -> GitEngine (command API) -> GitRunner (OS.execute, threads, timeouts)
                |                              |
        command_completed(context) <-----------+
                |
        GitotResultRouter -> domain handlers -> panels (head_moved, files_changed, file_restored, status_parsed, branches_parsed)

A caller that only needs its own answer passes on_done to the command; command_completed still broadcasts to everyone.

Module Role
GitEngine Command API. Emits command_completed with a per-call context; run_fast / run_network and the helpers built on them (fetch, pull, init_repo...) take an optional on_done(exit_code, output). Delegates execution to GitRunner. Reads that repaint a panel (STATUS, LOG, BRANCHES, STASH_LIST, AHEAD_BEHIND) are latest-wins: the result of a request that a newer one superseded is not broadcast (its on_done still runs). Also owns repo setup: init_repo() and add_remote() (refuses a URL that fails GitRemote.is_valid_url(), the one URL rule).
GitRunner Process execution: worker threads, 120 s timeout, shell redirect, write lock (writes, push and pull; reads and the other network commands always run). No git-domain knowledge. Enforces SHELL_SAFE_PATTERN on network args and refuses project / log paths with characters the shell would expand.
GitSyncOrchestrator push -> create tag -> push tag. plan_push() returns NORMAL, FORCE_CONFIRM or BLOCKED_BEHIND from ahead/behind counts (no git call).
GitotRepoState Single source for the current branch: name, origin owner/repo and its web address, upstream / ahead / behind (changed signal). Written by the router, read by panels, the push flow and the log messages.
GitRemote, GitRef Static, no git call: remote URL rules (owner/repo, browsable address, the add_remote() whitelist) and tag / branch name rules.
GitIgnore Static: creates the default Godot .gitignore only when none exists.
GitStatusParser, GitDiffParser, GitLogParser, GitStashParser, GitBranchParser, GitLfsParser Static parsers. Custom \x1f-delimited formats. They return typed entries (GitStatusEntry, GitCommitEntry, GitStashEntry, GitBranchEntry, GitLfsFile), not dictionaries; diff line kinds are the GitDiffParser.LineType enum.
GitotResultRouter Dispatches each finished command: to GitotBranchResults or GitotSyncResults (each owns a COMMANDS list and a handle()), or to its own handlers (commit, stash, status, log, remote). The only place that reads command_completed; shared failure/hint texts live in GitotMessages.
GitLfs Facade for LFS state, track/untrack, pull, prune. Typed signals.
GithubAuth / GithubApi / GithubIssueParser PAT storage, authenticated REST wrapper, issue JSON normalization.
GitotSettings / GitotLogger Settings storage: KEY_* constants, DEFAULTS, and RANGES (clamp and spin-box bounds). Central log with capped history, levels Info / Success / Warning / Error / Fatal / Hint / git raw, and fail() as the one failure shape.

UI

  • gitot_dock is the shell. Each fold (status tree, branches, shelf, history, tags, settings, console) is its own script; GitotEditorSync (editor refresh), GitotCommitFlow (commit / amend and the commit area's wording), GitotPushFlow (push; emits remote_missing when there is no origin), GitotWorkingDiff, GitotRepoInit (Initialize repository button) and GitotRemotePrompt (remote URL dialog) hold the use cases.
  • gitot.gd owns the editor-level registration of GitotFileTooltip (FileSystem dock tooltip). It is fed by the dock's status_parsed signal, so it needs no git call of its own.
  • Bottom panels: Diff viewer, LFS. Main-screen tab: Issues.
  • gitot_result_router routes results to handlers so the dock stays thin. Widgets that start a command (Fetch, Pull, Delete on origin, Commit, Initialize) free themselves in the on_done they pass, not in the dock.

Design rules

  • Signals up, calls down. Static typing everywhere.
  • UI scripts never call OS.execute(). Never build shell strings from unchecked input.
  • Stale-result guards (latest-wins) for async diffs.
  • Regular expressions go through GitotRegex.compiled(pattern): one compiled RegEx per pattern, built on first use.

Generated class docs are in docs/doctool. More internals: Implementation Details.

Directory tree

addons/gitot/
├── plugin.cfg										# Plugin manifest (name, version, entry script)
├── gitot.gd										# EditorPlugin entry point; lifecycle, wiring, main-screen tab registration
├── core/
│   ├── git_engine.gd								# Command API + command_completed signal (per-call context); delegates execution to GitRunner
│   ├── git_runner.gd								# Process execution: worker threads, timeouts, shell redirect, write lock (no git-domain knowledge)
│   ├── git_sync_orchestrator.gd					# push → create-tag → push-tag state machine; plan_push() picks normal / force-confirm / blocked
│   ├── git_branch_entry.gd							# Typed branch row (GitBranchParser output)
│   ├── git_status_entry.gd							# Typed status row (path, status)
│   ├── git_commit_entry.gd							# Typed history row
│   ├── git_stash_entry.gd							# Typed stash row
│   ├── git_lfs_file.gd								# Typed LFS file row (with_scope())
│   ├── git_ignore.gd								# Default Godot .gitignore, created only when missing
│   ├── git_branch_parser.gd						# Parses `git branch -a --format=...` (`GitBranchParser.FORMAT`) into local/remote branch entries
│   ├── git_status_parser.gd						# Parses `git status --porcelain=v2`
│   ├── git_diff_parser.gd							# Parses `git diff -U0/-U3` hunks + `--name-status` output
│   ├── git_log_parser.gd							# Parses `git log` (custom \x1f-delimited) into commit entries
│   ├── git_stash_parser.gd							# Parses `git stash list` (custom \x1f-delimited) into stash entries
│   ├── git_path.gd									# Decodes git's quoted (escaped) paths to UTF-8; to_file_list() decodes name-list results
│   ├── git_remote.gd								# Remote URL rules: owner/repo, browsable address, add_remote() whitelist
│   ├── git_ref.gd									# Tag and branch name rules
│   ├── gitot_regex.gd								# GitotRegex.compiled(pattern): one compiled RegEx per pattern
│   ├── git_format.gd								# UNIT_SEP, the field separator shared by the `--format` strings and their parsers
│   ├── git_lfs.gd									# GitLfs facade: state detection, track/untrack, pull, prune; typed signals
│   ├── git_lfs_parser.gd							# Parses `git lfs ls-files --json` + LFS patterns from .gitattributes; merge_scopes(), summarize() stats
│   ├── github_auth.gd								# PAT storage (user://gitot_auth.cfg, plaintext)
│   ├── github_api.gd								# Authenticated HTTPRequest wrapper for GitHub REST API
│   ├── github_issue_parser.gd						# Normalizes raw GitHub issue JSON (type/priority/labels/branch-name slug)
│   ├── gitot_repo_state.gd							# Ahead / behind / upstream state of HEAD, `changed` signal
│   ├── gitot_settings.gd							# GitotSettings; settings storage (user://gitot_settings.cfg)
│   └── gitot_logger.gd								# GitotLogger; centralized print wrapper, capped history + UI listener
└── ui/
    ├── gitot_dock.tscn / gitot_dock.gd				# Local Git dock UI shell (wiring, toolbar, refresh)
    ├── gitot_editor_sync.gd						# EditorFileSystem / open scene / script refresh after git changed files
    ├── gitot_commit_flow.gd						# Commit use case: commit / amend, amend confirmation, busy and amend wording of the commit area
    ├── gitot_push_flow.gd							# Push use case: plan, confirm, tag input validation, hand-off to the orchestrator
    ├── gitot_working_diff.gd						# Working-tree mode of the diff panel (gutter click / Refresh), stale-result guard
    ├── gitot_result_router.gd						# Routes command_completed results to domain handlers; emits files_changed / file_restored / status_parsed / origin_added
    ├── gitot_branch_results.gd						# Branch command results (list, switch, create, delete, current branch); emits head_moved / branches_parsed
    ├── gitot_sync_results.gd						# Fetch, ahead/behind, push, pull and tag-push results; emits head_moved after a pull
    ├── gitot_messages.gd							# Failure layout and network hint shared by the result handlers
    ├── gitot_repo_init.gd							# Initialize repository button and result
    ├── gitot_remote_prompt.gd						# Remote URL dialog for the first push
    ├── gitot_status_tree.gd						# Staged/unstaged file trees: population, status letters/icons, staging, bulk actions, size guard
    ├── gitot_file_tooltip.gd						# FileSystem dock hover tooltip: adds each file's git status (EditorResourceTooltipPlugin)
    ├── gitot_log_panel.gd							# Commit history fold: Tree population, filters, commit_selected signal
    ├── gitot_stash_panel.gd						# Shelf fold: stash list Tree, Stash/Pop/Drop, cap, drop confirm
    ├── gitot_ui.gd									# Shared UI helpers: icons (incl. white), title-bar buttons, busy state, sync symbols/counts/tooltips, push tooltip
    ├── gitot_commit_files_list.gd					# Changed-files ItemList for one commit; pure UI
    ├── gitot_commit_log_diff.gd					# Commit-mode flow (history → file list → diff); latest-wins gate
    ├── gitot_branch_panel.gd						# Branch list fold: Tree, switch/track on double-click, Fetch button, title-bar sync status
    ├── gitot_branch_creator.gd						# Inline create-branch row (+ toggle), confirmation gate
    ├── gitot_branch_deleter.gd						# Delete-branch / Delete-on-origin buttons and confirmations
    ├── gitot_status_panel.gd						# Compact repo-state summary (owner/repo link, branch, scope)
    ├── gitot_diff_gutter.gd						# Script editor gutter coloring + click-to-diff (hunk_clicked signal); teardown() hides the gutter
    ├── gitot_tag_panel.gd							# Tag-versioning UI: toggles, version-tag formatting, tag resolution
    ├── gitot_settings_panel.gd						# Settings panel UI; reads/writes GitotSettings directly
    ├── gitot_log_console.gd						# Live console view backfilling GitotLogger.get_history(); owns the Info menu (Reflog, Repo size, Copy/Clear log)
    ├── github_issue_list.gd						# Issue Tree: population, header-click sort, filter integration
    ├── github_issue_filter.gd						# Type/Priority/Label dropdowns + AND-match rule
    ├── github_panel.tscn / github_panel.gd			# GitHub Issues Tracker Board, main-screen tab, owns GithubApi
    ├── issue_detail.tscn / issue_detail.gd			# Selected-issue detail: header, actions, branch creation, body
    ├── gitot_lfs_panel.tscn / gitot_lfs_panel.gd	# "Gitot LFS" bottom panel: dashboard (state, stats, patterns, actions) + files tree, setup guide
    ├── gitot_diff_panel.tscn / gitot_diff_panel.gd		# Bottom-dock full diff viewer; show_raw_diff() = size cap + parse for both modes
    └── github_auth_dialog.tscn / github_auth_dialog.gd	# PAT entry modal, opened on first use or 401

Clone this wiki locally