Repository navigation
Implementation Details
How each feature works internally. For usage, see the feature pages (Commit and Sync, Branches, Stash Manager, Diff Viewer and Gutter, Git LFS, Issues Tracker). Structure: Architecture.
-
Failsafe Startup: verifies
gitis available before initializing; disables cleanly with a clear error if not. Thatgit --versionis the only git call that blocks the main thread (it reads no repository, remote or credential helper;OS.execute()has no timeout). Everything else, including the LFS version shown in the startup log, comes from async commands. -
Async Execution: all process execution lives in
GitRunner. Local commands run withOS.execute()(argument array, no shell) on a worker thread; writes, push and pull take the write lock (Commit shows "Working..." meanwhile), reads and the other network commands always run. Network commands run as a monitored process killed afternetwork_timeout_sec. The editor never waits on git. -
Non-ASCII Support: accented and non-Latin file names (
Lumière.mp3) and text (diffs, commit history, stash names, tag messages).GitPath.unquote()decodes git's quoted paths, so staging, the Size Guard, LFS lists and the commit file list work with any file name.GitRunner._fix_encoding()restores the UTF-8 text that Windows decodes with the ANSI code page. Tested on Windows-1252 only; Linux, macOS and other Windows code pages are untested. (read: Troubleshooting). -
Commit & Sync: Staged/Unstaged file trees parsed from
git status --porcelain=v2, with single and bulk stage/unstage. Status color & icon. Warning icon for conflicted file and large-file guard. Push targets-u origin HEAD. Amend toggle (CheckButton) runscommit --amend(keeps message with--no-editif left blank); blocked behind a confirm dialog if the tracked ahead-count shows the commit is already pushed. Before every push,GitSyncOrchestrator.plan_push(has_upstream, ahead, behind)decides from the router's ahead/behind counts (no git call):NORMAL,FORCE_CONFIRM(diverged: dialog with the counts, then--force-with-lease) orBLOCKED_BEHIND(only behind: refused, "Pull first", because a forced push would rewind origin).GitotUi.push_tooltip()uses the same inputs, so the tooltip never disagrees with the action. While a write runs,GitRunner.write_busy_changed->GitEngine.write_busy_changeddisables the commit / stage buttons ("Working..."). Cancelling either confirm dialog logs a message and changes nothing. -
Stash Manager (Shelf): Foldable list (name, branch, relative date, exact date on tooltip) built from
git stash list(\x1f-delimited format, parsed byGitStashParser). Header title showsShelf (count/cap). Stash / Pop / Drop buttons. An optional name field feedsstash push -u -m; left blank, git's ownWIP on <branch>: ...subject is used. The cap is themax_stashessetting (1-50, default 10): Stash is disabled when the shelf is full, existing entries are never deleted, and the list shows every entry, including ones made from a terminal. The selection is kept across refreshes by stash SHA (the top entry is selected by default, so Pop acts on the latest). Drop asks for confirmation and re-resolves the SHA to its currentstash@{n}, since the stack can shift while the dialog is open; git's output (which contains the dropped SHA) is logged, so a mistake can be undone withgit stash store <sha>. Before a pop (async: list first, then pop), the touched files are listed withgit stash show --name-only --include-untracked --no-renamesand only those are refreshed (update_file(), open scene reload, stale-script toast). A failed pop (conflict) refreshes nothing and logs the raw output. -
Local Branch Management: Foldable
Tree(name, sync, updated) built fromgit branch -a(GitBranchParser.FORMAT,\x1f-delimited, parsed byGitBranchParser). Sync uses%(upstream:trackshort)(↑ahead,↓behind,↑↓diverged,✓in sync), which is locale-independent. A button row (defined in the scene) under the title: Fetch,+, Delete, Delete on origin. The current branch's sync status (symbol + counts fromrev-list, never git's localized text) is a label in the title bar; row tooltips explain each state in plain words. Double-click switches.+toggles an inline name row (Enter or Create button), guarded by a confirmation unlessconfirm_create_branchis off. Delete runsgit branch -don the selected local, non-current branch, always after a confirmation. There is no Force, so git refuses unmerged branches. If anorigin/copy exists, the dialog and the log say it stays on origin. Delete on origin runs onlygit push origin --delete refs/heads/<name>(name whitelist^[A-Za-z0-9][A-Za-z0-9._/-]*$, own confirmation dialog, old tip hash logged to restore withgit push origin <sha>:refs/heads/<name>); server refusals (default / protected branch) are shown raw. On switch/create, only files git actually changed are refreshed.EditorFileSystem.update_file()for cache bookkeeping, plusEditorInterface.reload_scene_from_path()for any of those files currently open in a tab, avoiding the full-projectscan()noise. Godot has no API to reload or close an open script tab, so an editor toast flags any open script that changed on disk (close/reopen manually), and a separate toast warns when changed-file detection itself is unreliable (first switch after clone, or rapid successive switches). (read: Troubleshooting). -
Remote Branch Management:
git fetch originvia the fold's title-bar button, refreshing the branch list on success. Remote-tracking branches with no local counterpart appear in the list, and double-clicking one runsswitch -c <name> --track origin/<name>in one atomic op. -
Tags Versioning: Tag is push with commit. Settings to auto use Version (from project settings) for the tag's name (with auto
vprefix) and the commit message for the tag's message. -
Repo Status Panel: Show
owner/repo · branch (local/remote) · ↑ahead ↓behind. Reads GitotRepoState, so it updates live.owner/repois a[url]link in theRichTextLabel: the click opensGitotRepoState.repo_url(built byGitRemote.web_url(),""= plain text), never an address read from the text. Detached HEAD shown in red in place of a branch name. Ahead/behind only logs to console on actual change, not on redundant refreshes (e.g. focus-in polling) and not for the first read at startup. -
Commit History:
git logparsed via\x1f- delimited format string. Count-filtered dropdown (10/20/30, default reflects last selection). Relative date shown in-list, exact short date (YYYY-MM-DD HH-MM) on date hover tooltip. Auto-refreshes alongside status on the same trigger set (commit/push/pull/switch) and on manual Refresh Status fallback button. Selecting a row (click or arrow keys) opens that commit history in the Diff Viewer Panel. -
Color Diff Gutter: Modified and added lines are marked directly in the script editor's gutter, computed from
git diff -U0againstHEAD. Updates automatically on save (where Godot's save signal fires reliably), on Godot editor focus, and a manual fallback refresh diff gutter button. Clicking a marked line gutter opens the bottom-dock diff viewer, jumped to that line. On plugin exitteardown()hides the gutter and clears its markers in the open editors (it is not removed:CodeEdit.remove_gutter()raised an index error in 4.7). -
Diff Viewer Panel: Separate
git diff -U3againstHEAD, decoupled from the gutter's own-U0diff (no shared parser state, no regression risk to the gutter). Renders, as a read-onlyCodeEdit:@@ -old,count +new,count @@header per foldable hunk, basic syntax highlighting, dedicated +/- sign gutter, real source line numbers in their own gutter, color-coded background per line. File-path label at the top confirms which file is shown. Stale-result guard discards a diff response if the active script tab changed before it arrived. (Manual fallback refresh button). Commit history mode: selecting a commit history row lists the commit's changed files (git show --name-status --format= --no-renames, status-colored A/M/D); the selected file's diff (git show -U3 ... -- <path>) is fetched on demand, so large commits stay fast. A latest-wins gate keeps one request in flight and drops outdated results (fast arrow-key navigation always ends on the highlighted commit), and a single file diff is truncated at 200,000 characters for display. Clicking the commit header copy its hash (brief color flash confirms the copy). Clicking a gutter marker in a script returns the panel to working-tree mode. (read: Troubleshooting). -
Restore File: In commit-history mode, restores the selected file to its content at that commit (
git restore --source=<hash> -- <path>). A confirmation dialog warns that uncommitted changes to the file will be lost. Guarded against the no-file-changes merge-commit case (button no-ops if there's nothing to restore). If the file is open in the script editor, its compiled class reloads automatically, but the tab's visible text only refreshes on the next editor focus change. Tooltip and console log both call this out (read: Troubleshooting). -
Git LFS:
GitLfs(core/, facade: commands in, typed signals out) over the staticGitLfsParser. State is detected with a two-step async chain,git lfs versionthengit config --get filter.lfs.process, givingNO_BINARY/NOT_INITIALIZED/READY. The panel is a split: left dashboard + right files list.READYshows both. Any other state shows only the dashboard as a 4-step setup guide with the one button that fixes it (Get Git LFS link or Install LFS,git lfs install, global).-
Dashboard: state, Refresh, Download (
git lfs pull), Prune, Info (shows / hides the notes at the bottom, remembered inlfs_note_visible), stats, patterns. Stats come fromGitLfsParser.summarize()over the files list: counts and sizes on this computer / on GitHub, to upload, to download, missing, largest file. There is no LFS quota API, so on GitHub is an estimate (origin's tree as of the last fetch, current files only; GitHub also counts old versions). -
Files:
git lfs ls-files --json(this checkout, then@{u}for origin; merged by oid inGitLfsParser.merge_scopes()) into a 3-columnTree(file, size, status). Status uses the same words as the branch list:Local + Remote,Local(not pushed yet),Remote(only a pointer here),Missing. -
Patterns: read from the root
.gitattributes(notgit lfs track, which installs hooks as a side effect). Only the root file is read, so nested, global and.git/inforules are not seen. Track / Untrack rungit lfs track|untrack; the Patterns menu tracks a preset in one click (Textures, Art source, 3D, Audio, Video, Fonts, Godot binary). Godot text formats (gd gdshader gdshaderinc gdextension tscn tres import uid godot cfg) are refused, they must stay diffable and mergeable in Git. So are catch-all patterns (*,**,*.*,/): they would put every file in LFS. -
Download:
git lfs pull(fetch + checkout) is the repair path for missing or pointer files; it never overwrites modified files. Runs as a network command (network_timeout_sec), then rescans the filesystem. Prune:git lfs prunebehind a confirmation; git-lfs never removes objects that are not pushed. -
Size Guard: a file is exempt only if LFS is
READYand an LFS rule without/matches its file name (String.match(), case-sensitive). Any doubt keeps the guard on. - LFS state is detected asynchronously at startup (two quick git calls), so the Size Guard exemption
and the Track-with-LFS button work immediately. The same first call supplies
GitLfs.version, logged once.
-
Dashboard: state, Refresh, Download (
-
Large-file Guard: Size configurable in settings, 0 to disable. Exempts LFS-tracked files (see Git LFS above).
-
GitHub Issues Tracker Board: (opt-out in settings possible). Split container (columns: number/title/date/type/priority/labels, header-click sort) on the left,
IssueDetailon the right (metadata header, colored label pills, Markdown body in a read-onlyCodeEdit, Open in Browser button). Parser normalizes raw REST JSON (native issue Type, org "Priority" issue field, null-safe body/labels). Filter: Type/Priority/Label built from the fetched data, client-side AND filter (no extra requests). Paginated fetch (50 issues/page, capped at 8 pages = 400 issues), chained automatically and stopping at the first short page. Branch creation from an issue: auto-generated name (<type-prefix><number>-<title-slug>, e.g.feature/12-fix-login, editable), base-branch picker (defaults to current branch), confirmation dialog if the working tree is dirty. If a token expires or is revoked, Gitot detects this automatically (HTTP 401) and re-prompts for a new one. The header counts open /Bug/Highpriority issues from the parsed entries (GithubIssueParser.count_where()), no extra request. -
Gitot Settings Panel: settings in
user://gitot_settings.cfg, every key aGitotSettings.KEY_*constant: large_file_mb, network_timeout_sec, max_stashes, status_letters, confirm_push, confirm_create_branch, confirm_amend, auto_refresh_on_focus, github_issues_enabled, lfs_note_visible, echo_to_output (see Settings). -
Status Signs: every Staged/Unstaged row and the FileSystem dock tooltip show a file's git status from one table,
GitotStatusTree.STATUS_VISUALS(perGitStatusParser.FileStatus: letter, editor icon, color, tooltip text).GitStatusParser.parse()reads the porcelain v2XYpair:Xis the staged side,Ythe unstaged side.?isUNTRACKEDanduisCONFLICT(both unstaged only). Letters:Uuntracked,Aadded,Mmodified (also type change),Ddeleted,Cconflict.Umeans untracked here (git's ownUis "unmerged").GitStatusParser.ARGSuses--no-renames, so a rename is aDplus anA.-
Lists: each
Treehas one column, so hover and selection cover the whole row._apply_status_visuals()sets the cell. Letter mode writesM path. Icon mode keeps the plain path and a tinted icon (GitotUi.get_icon()+set_icon_modulate()). The cell color is the status color, and a tree cell has only one, so the letter and the path share it. The path is stored in the item metadata and read through_path_of(). The cell text is display-only, so Stage, Unstage, Open file and Track with LFS never parse it. A Size Guard row replaces the status icon with its warning icon (icon mode). -
Setting:
status_letters(default off = icons). Toggling it emitsstatus_display_changed. The dock answers withrefresh_files(), so the lists repaint from onegit status(row selection is cleared). -
FileSystem tooltip:
GitotFileTooltip(EditorResourceTooltipPlugin) is registered ingitot.gd._enter_tree()withFileSystemDock.add_resource_tooltip_plugin()and removed in_exit_tree(). It is fed by the same parse as the lists (GitotResultRouter.status_parsed->GitotDock.status_parsed->update()), which rebuilds apath -> {staged, unstaged}dictionary. A hover is one lookup: no git call, no cost per hover._make_tooltip_for_path()appends one coloredGit (staged|unstaged): <letter>label per side to Godot's own tooltip (base), never replacing it, so a file that is both staged and modified shows two lines. Unchanged files keep the default tooltip. Always on, no setting.
-
Lists: each
-
Project root: Gitot builds
res://paths from git's repo-relative paths, so the Godot project folder must be the repository root. At startupgit rev-parse --show-prefixis checked and a non-empty prefix logs a warning. -
Gitot Logger: Output routed through
GitotLogger(Styled with Godot's own console font), so they can be filter out of Godot output log with the "standard output message" filter. Labels and colors come from oneSTYLEStable, and[is escaped in every message (a path likeLevel[1].tscnis not read as BBCode). The console's Info menu runsgit reflog -n 20(GitEngine.REFLOG_COUNT) orgit count-objects -vHand prints the raw output; Copy log copies the console as plain text, Clear log empties it (and its history). -
Messages: a result line names its subject (branch,
owner/repo, tag, file), read fromGitotRepoState(HEAD/originuntil known). Every failure goes throughGitotLogger.fail(title, raw, hint): error line, git's raw message, then a[Gitot Hint]line (Level.HINT,GitotLogger.h()) with causes git is known to produce.Level.EXTREME([Gitot Fatal], alsoprinterr) is reserved for "Gitot cannot work" (git missing, dock without its engine). Per-call data travels in thecommand_completedcontext (PUSH{force},PUSH_TAG{tag}). -
Results:
command_completedbroadcasts every result toGitotResultRouter(one dispatch point: no command is pre-handled elsewhere). A caller that needs only its own answer passeson_done(exit_code, output, ...bound args): it runs after the broadcast and is skipped if its target was freed. Latest-wins consumers (GitotCommitLogDiff) keep one request in flight and bind the(hash, path)they asked for. Reads that repaint the UI (LATEST_WINS_COMMANDS: status, log, branches, stash list, ahead/behind) are stamped: a slower, older answer is not broadcast, so it cannot overwrite a newer one (itson_donestill runs). -
Refresh cost: the editor-focus refresh (
auto_refresh_on_focus) is skipped when the last full refresh is under 3 s old (FOCUS_REFRESH_COOLDOWN_MSEC); Refresh and post-command refreshes never wait. The Staged / Unstaged trees draw at most 500 rows each (MAX_ROWS) plus a Show all row; Stage All reads the parsed entries, not the rows. The status is parsed once (router) and shared viastatus_parsed; branches viabranches_parsed. -
Shell safety:
run_network()builds a shell string, so every argument must matchGitRunner.SHELL_SAFE_PATTERN([A-Za-z0-9._/@+:-]), otherwise nothing runs. Tag names are checked up front (create_tag) so a tag that could not be pushed is never created. The project root and the log file sit inside double quotes, where spaces are harmless, so they are checked for the few characters that still act there (%on Windows;$, backtick and\on Linux / macOS;"on both) and refused the same way. The exit status is a marker the shell appends to the log; only a marker that ends the log counts.