git-juggler 0.2.15__tar.gz → 0.2.17__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.
- {git_juggler-0.2.15 → git_juggler-0.2.17}/.gitignore +2 -1
- {git_juggler-0.2.15 → git_juggler-0.2.17}/PKG-INFO +32 -5
- {git_juggler-0.2.15 → git_juggler-0.2.17}/README.md +12 -4
- git_juggler-0.2.17/pyproject.toml +53 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/scripts/dev.sh +4 -10
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/agents/hook_events.py +45 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/api/ci.py +2 -2
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/api/config.py +22 -12
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/api/git.py +24 -2
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/api/repos.py +1 -1
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/api/terminal.py +1 -1
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/config.py +30 -2
- git_juggler-0.2.15/src/backend/git_juggler/frontend_dist/assets/index-CB9Jhv3C.js → git_juggler-0.2.17/src/backend/git_juggler/frontend_dist/assets/index-BOaJc15H.js +19 -16
- git_juggler-0.2.15/src/backend/git_juggler/frontend_dist/assets/index-0n6Gh9f_.css → git_juggler-0.2.17/src/backend/git_juggler/frontend_dist/assets/index-DRsp7rcC.css +1 -1
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/frontend_dist/index.html +2 -2
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/git/command.py +13 -2
- git_juggler-0.2.17/src/backend/git_juggler/git/conflicts.py +175 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/git/data.py +6 -1
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/git/status.py +5 -1
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/main.py +18 -2
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/repo_discovery.py +39 -20
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/schemas.py +39 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_ci_poll_route.py +1 -1
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_claude_sessions_data.py +80 -0
- git_juggler-0.2.17/src/backend/tests/test_git_conflicts.py +77 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_preferences.py +33 -1
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_repos.py +31 -1
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/App.css +93 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/App.tsx +43 -11
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/api/client.ts +10 -1
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/api/types.ts +33 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/CommandPalette/CommandPalette.tsx +1 -0
- git_juggler-0.2.17/src/frontend/src/components/Conflicts/ConflictsPanel.tsx +267 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/ContextMenu/BranchContextMenu.tsx +16 -1
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Menu/JenkinsSettings.tsx +33 -20
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Menu/RepoPathsSettings.tsx +69 -44
- git_juggler-0.2.17/src/frontend/src/components/inputs/TemplateInput.tsx +236 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/agentRings.ts +4 -3
- git_juggler-0.2.17/src/frontend/src/lib/jenkinsUrlTemplate.ts +80 -0
- git_juggler-0.2.17/src/frontend/src/lib/templateTokens.ts +62 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/state/graph.ts +4 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/state/repos.ts +4 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/state/storeCore.ts +70 -1
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/state/terminal.ts +1 -0
- git_juggler-0.2.17/src/frontend/src/styles/terminal.css +265 -0
- git_juggler-0.2.17/src/frontend/tests/templateTokens.test.ts +80 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/vite.config.ts +5 -2
- git_juggler-0.2.15/CLAUDE.md +0 -101
- git_juggler-0.2.15/pyproject.toml +0 -30
- git_juggler-0.2.15/src/frontend/src/styles/terminal.css +0 -64
- {git_juggler-0.2.15 → git_juggler-0.2.17}/.github/workflows/build-package.yml +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/LICENSE +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/notes.txt +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/resources/git-juggler-demo-1.gif +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/resources/git-juggler-demo-2.png +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/resources/git-juggler-demo-3.png +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/resources/git-juggler-demo-4.png +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/scripts/build-package.sh +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/__init__.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/agents/__init__.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/agents/hooks.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/agents/tracking/__init__.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/agents/tracking/activity_models.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/agents/tracking/claude_sessions.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/agents/tracking/claude_transcripts.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/agents/tracking/git_resolver.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/agents/tracking/opencode_sessions.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/agents/tracking/paths.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/agents/tracking/text.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/api/__init__.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/api/agents.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/app.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/ci/__init__.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/ci/github_actions.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/ci/http.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/ci/jenkins.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/ci/run_cache.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/dev_app.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/frontend_dist/agent-logos/claude.png +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/frontend_dist/agent-logos/opencode.webp +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/frontend_dist/ci-logos/jenkins.webp +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/frontend_dist/git-juggler.png +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/frontend_dist/logo.png +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/git/__init__.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/git/detail.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/git/diff.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/git/gitfiles.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/git/refs.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/git/utils.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/terminal.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/themes.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/ui/__init__.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/git_juggler/ui/folder_picker.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/conftest.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/hook_event_files.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_agent_hooks.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_agent_tracking.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_ci.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_ci_poll.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_commit_detail.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_file_diff.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_git_data.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_github_actions.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_jenkins.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_opencode_sessions.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_pick_folder.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_run_cache.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/backend/tests/test_themes.py +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/.gitignore +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/README.md +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/index.html +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/package-lock.json +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/package.json +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/public/agent-logos/claude.png +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/public/agent-logos/opencode.webp +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/public/ci-logos/jenkins.webp +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/public/git-juggler.png +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/public/logo.png +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Agents/AgentActivityPanel.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Agents/AgentHoverCard.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Agents/AgentStatusPopover.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Agents/agentFormat.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Badges/BranchBadge.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Badges/CiRunBadge.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Badges/StashBadge.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Badges/TagBadge.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Badges/ciStatus.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Branches/DeleteBranchModal.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/CIRunPopover.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Commits/CommitDetail.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Commits/CommitList.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Commits/CommitRow.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Commits/LineCounts.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Commits/UncommittedRow.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/ContextMenu/CommitContextMenu.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/ContextMenu/CreateBranchModal.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/ContextMenu/CreateTagModal.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/ContextMenu/RepoContextMenu.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/ContextMenu/TagContextMenu.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Diff/FileDiffModal.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Graph/GraphPanel.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Graph/branchColor.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Graph/computeColumns.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Graph/laneWidth.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/GraphToolbar/GraphToolbar.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Menu/AgentQuickPicks.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Menu/AgentsSettings.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Menu/CiPollField.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Menu/DirectoryBrowserModal.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Menu/GitHubSettings.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Menu/MainMenu.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Menu/ThemePicker.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Menu/ThemeQuickPicks.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Pipelines/PipelinesPanel.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Search/MultiSelect.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Search/SearchBox.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Sidebar/NewGroupModal.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Sidebar/RepoList.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Sidebar/Sidebar.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Sidebar/panelKit.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Tabs/TabsBar.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Tags/CreateTagDialog.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Tags/DeleteTagModal.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Terminal/TerminalPanel.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/Welcome/WelcomeWizard.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/icons.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/inputs/NumberField.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/inputs/TextField.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/inputs/ToggleField.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/components/inputs/styles.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/index.css +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/index.tsx +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/agentStatusPopoverData.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/appTheme.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/branchVisibility.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/bundledThemes.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/ciPoll.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/ciRunPopoverData.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/color.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/dismissOnOutsideClick.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/flip.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/formatDate.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/preferenceSender.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/scrollToCommit.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/sideBySide.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/stageWindow.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/stripAnsi.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/swallowNextClick.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/lib/vscodeTheme.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/state/agents.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/state/ci.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/state/overlayStack.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/state/overlays.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/state/preferenceSync.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/state/preferences.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/state/settings.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/state/store.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/state/tabs.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/state/themes.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/styles/ci.css +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/styles/commits.css +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/styles/graph.css +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/styles/menu.css +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/src/styles/sidebar.css +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/tests/agentFormat.test.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/tests/agentRings.test.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/tests/agentStatusPopoverData.test.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/tests/ciPoll.test.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/tests/ciRunPopoverData.test.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/tests/computeColumns.test.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/tests/preferenceSender.test.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/tests/sideBySide.test.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/tests/stageWindow.test.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/tests/vscodeTheme.test.ts +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/tsconfig.app.json +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/tsconfig.json +0 -0
- {git_juggler-0.2.15 → git_juggler-0.2.17}/src/frontend/tsconfig.node.json +0 -0
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: git-juggler
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.17
|
|
4
4
|
Summary: A local Git dashboard for people juggling agents and too many repos.
|
|
5
|
+
Project-URL: Homepage, https://github.com/franz38/git-juggler
|
|
6
|
+
Project-URL: Repository, https://github.com/franz38/git-juggler
|
|
7
|
+
Project-URL: Issues, https://github.com/franz38/git-juggler/issues
|
|
8
|
+
Project-URL: Releases, https://github.com/franz38/git-juggler/releases
|
|
5
9
|
License-Expression: MIT
|
|
6
10
|
License-File: LICENSE
|
|
11
|
+
Keywords: agents,dashboard,git,repositories,terminal
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Web Environment
|
|
14
|
+
Classifier: Framework :: FastAPI
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
19
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
7
26
|
Requires-Python: >=3.10
|
|
8
27
|
Requires-Dist: fastapi>=0.110
|
|
9
28
|
Requires-Dist: gitpython>=3.1
|
|
@@ -30,6 +49,14 @@ Watch agents work live, follow every repo’s git graph, and catch what changed
|
|
|
30
49
|
- Connects GitHub Actions and Jenkins runs to commits and active pipelines.
|
|
31
50
|
- VS Code-compatible themes.
|
|
32
51
|
|
|
52
|
+
## Privacy / local-first
|
|
53
|
+
|
|
54
|
+
- Runs locally and binds to `127.0.0.1` by default.
|
|
55
|
+
- No telemetry or analytics are built in, and none are planned.
|
|
56
|
+
- Repo scanning and Claude/OpenCode session tracking read local files on your machine.
|
|
57
|
+
- Git mutations run through visible terminal commands, so you can see the exact command and output.
|
|
58
|
+
- Network requests are only used for features you configure, such as CI integrations.
|
|
59
|
+
|
|
33
60
|
## How to install
|
|
34
61
|
|
|
35
62
|
git-juggler is published on PyPI. The recommended way to install it is with
|
|
@@ -46,7 +73,7 @@ to seed the initial search path; after that, search paths are managed in the app
|
|
|
46
73
|
from the welcome wizard or Settings.
|
|
47
74
|
|
|
48
75
|
- `--host HOST` changes the bind host from the default `127.0.0.1`.
|
|
49
|
-
- `--port PORT` changes the backend port from the default `
|
|
76
|
+
- `--port PORT` changes the backend port from the default `8788`.
|
|
50
77
|
- `--no-open` disables opening the app in your browser automatically.
|
|
51
78
|
|
|
52
79
|
If you don't have pipx yet:
|
|
@@ -85,7 +112,7 @@ Use the dev script to start both the backend and frontend dev servers:
|
|
|
85
112
|
```
|
|
86
113
|
|
|
87
114
|
- `path-to-scan` is optional. On first run, it seeds the folder whose immediate children are scanned for Git repos.
|
|
88
|
-
- `--port PORT` is optional. It changes the backend port from the default `
|
|
115
|
+
- `--port PORT` is optional. It changes the backend port from the default `8788`; the dev script passes the selected port to Vite automatically.
|
|
89
116
|
- `--no-open` disables opening the app in your browser automatically.
|
|
90
117
|
|
|
91
118
|
Or run them manually in two terminals:
|
|
@@ -99,7 +126,7 @@ python3 -m venv .venv
|
|
|
99
126
|
```
|
|
100
127
|
cd src/frontend
|
|
101
128
|
npm install
|
|
102
|
-
npm run dev # http://localhost:5173 (proxies /api and /ws to :
|
|
129
|
+
npm run dev # http://localhost:5173 (proxies /api and /ws to :8788)
|
|
103
130
|
```
|
|
104
131
|
|
|
105
132
|
Open http://localhost:5173.
|
|
@@ -113,7 +140,7 @@ cd src/frontend && npm install && npm run build
|
|
|
113
140
|
cd ../.. && .venv/bin/git-juggler <path-to-scan> [--no-open]
|
|
114
141
|
```
|
|
115
142
|
|
|
116
|
-
The backend serves the built frontend in this mode, so only port
|
|
143
|
+
The backend serves the built frontend in this mode, so only port 8788 is needed.
|
|
117
144
|
By default, it opens the app in your browser.
|
|
118
145
|
|
|
119
146
|
## Build a Python package
|
|
@@ -17,6 +17,14 @@ Watch agents work live, follow every repo’s git graph, and catch what changed
|
|
|
17
17
|
- Connects GitHub Actions and Jenkins runs to commits and active pipelines.
|
|
18
18
|
- VS Code-compatible themes.
|
|
19
19
|
|
|
20
|
+
## Privacy / local-first
|
|
21
|
+
|
|
22
|
+
- Runs locally and binds to `127.0.0.1` by default.
|
|
23
|
+
- No telemetry or analytics are built in, and none are planned.
|
|
24
|
+
- Repo scanning and Claude/OpenCode session tracking read local files on your machine.
|
|
25
|
+
- Git mutations run through visible terminal commands, so you can see the exact command and output.
|
|
26
|
+
- Network requests are only used for features you configure, such as CI integrations.
|
|
27
|
+
|
|
20
28
|
## How to install
|
|
21
29
|
|
|
22
30
|
git-juggler is published on PyPI. The recommended way to install it is with
|
|
@@ -33,7 +41,7 @@ to seed the initial search path; after that, search paths are managed in the app
|
|
|
33
41
|
from the welcome wizard or Settings.
|
|
34
42
|
|
|
35
43
|
- `--host HOST` changes the bind host from the default `127.0.0.1`.
|
|
36
|
-
- `--port PORT` changes the backend port from the default `
|
|
44
|
+
- `--port PORT` changes the backend port from the default `8788`.
|
|
37
45
|
- `--no-open` disables opening the app in your browser automatically.
|
|
38
46
|
|
|
39
47
|
If you don't have pipx yet:
|
|
@@ -72,7 +80,7 @@ Use the dev script to start both the backend and frontend dev servers:
|
|
|
72
80
|
```
|
|
73
81
|
|
|
74
82
|
- `path-to-scan` is optional. On first run, it seeds the folder whose immediate children are scanned for Git repos.
|
|
75
|
-
- `--port PORT` is optional. It changes the backend port from the default `
|
|
83
|
+
- `--port PORT` is optional. It changes the backend port from the default `8788`; the dev script passes the selected port to Vite automatically.
|
|
76
84
|
- `--no-open` disables opening the app in your browser automatically.
|
|
77
85
|
|
|
78
86
|
Or run them manually in two terminals:
|
|
@@ -86,7 +94,7 @@ python3 -m venv .venv
|
|
|
86
94
|
```
|
|
87
95
|
cd src/frontend
|
|
88
96
|
npm install
|
|
89
|
-
npm run dev # http://localhost:5173 (proxies /api and /ws to :
|
|
97
|
+
npm run dev # http://localhost:5173 (proxies /api and /ws to :8788)
|
|
90
98
|
```
|
|
91
99
|
|
|
92
100
|
Open http://localhost:5173.
|
|
@@ -100,7 +108,7 @@ cd src/frontend && npm install && npm run build
|
|
|
100
108
|
cd ../.. && .venv/bin/git-juggler <path-to-scan> [--no-open]
|
|
101
109
|
```
|
|
102
110
|
|
|
103
|
-
The backend serves the built frontend in this mode, so only port
|
|
111
|
+
The backend serves the built frontend in this mode, so only port 8788 is needed.
|
|
104
112
|
By default, it opens the app in your browser.
|
|
105
113
|
|
|
106
114
|
## Build a Python package
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "git-juggler"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "A local Git dashboard for people juggling agents and too many repos."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
keywords = ["agents", "dashboard", "git", "repositories", "terminal"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 4 - Beta",
|
|
11
|
+
"Environment :: Web Environment",
|
|
12
|
+
"Framework :: FastAPI",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Operating System :: MacOS",
|
|
16
|
+
"Operating System :: Microsoft :: Windows",
|
|
17
|
+
"Operating System :: POSIX :: Linux",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Topic :: Software Development :: Version Control :: Git",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"fastapi>=0.110",
|
|
27
|
+
"uvicorn[standard]>=0.29",
|
|
28
|
+
"gitpython>=3.1",
|
|
29
|
+
"pywinpty>=2.0; sys_platform == 'win32'",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
git-juggler = "git_juggler.main:cli"
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/franz38/git-juggler"
|
|
37
|
+
Repository = "https://github.com/franz38/git-juggler"
|
|
38
|
+
Issues = "https://github.com/franz38/git-juggler/issues"
|
|
39
|
+
Releases = "https://github.com/franz38/git-juggler/releases"
|
|
40
|
+
|
|
41
|
+
[build-system]
|
|
42
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
43
|
+
build-backend = "hatchling.build"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.version]
|
|
46
|
+
source = "vcs"
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.wheel]
|
|
49
|
+
packages = ["src/backend/git_juggler"]
|
|
50
|
+
artifacts = ["src/backend/git_juggler/frontend_dist/**"]
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.sdist]
|
|
53
|
+
artifacts = ["src/backend/git_juggler/frontend_dist/**"]
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Runs the backend and frontend dev servers together, both hot-reloading.
|
|
3
|
-
# See
|
|
3
|
+
# See README.md's "Dev mode" section for the equivalent manual steps.
|
|
4
4
|
set -euo pipefail
|
|
5
5
|
|
|
6
6
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
7
|
-
BACKEND_PORT=
|
|
7
|
+
BACKEND_PORT=8788
|
|
8
8
|
OPEN_BROWSER=1
|
|
9
9
|
|
|
10
10
|
usage() {
|
|
@@ -12,8 +12,7 @@ usage() {
|
|
|
12
12
|
echo " path-to-scan optional: directory whose first-level children get scanned for git repos." >&2
|
|
13
13
|
echo " Only used to seed the config on first run; scan paths are otherwise" >&2
|
|
14
14
|
echo " managed in the app (welcome wizard / Settings)." >&2
|
|
15
|
-
echo " --port PORT backend port (default:
|
|
16
|
-
echo " src/frontend/vite.config.ts's proxy target -- don't commit that change." >&2
|
|
15
|
+
echo " --port PORT backend port (default: 8788)." >&2
|
|
17
16
|
echo " --no-open don't open the app in a browser automatically." >&2
|
|
18
17
|
}
|
|
19
18
|
|
|
@@ -115,7 +114,7 @@ fi
|
|
|
115
114
|
BACKEND_PID=$!
|
|
116
115
|
|
|
117
116
|
echo "Starting frontend dev server..."
|
|
118
|
-
(cd "$ROOT_DIR/src/frontend" && npm run dev) &
|
|
117
|
+
(cd "$ROOT_DIR/src/frontend" && GIT_JUGGLER_BACKEND_URL="http://127.0.0.1:$BACKEND_PORT" npm run dev) &
|
|
119
118
|
FRONTEND_PID=$!
|
|
120
119
|
|
|
121
120
|
if [ "$OPEN_BROWSER" = "1" ]; then
|
|
@@ -125,11 +124,6 @@ fi
|
|
|
125
124
|
echo ""
|
|
126
125
|
echo "Backend: http://127.0.0.1:$BACKEND_PORT"
|
|
127
126
|
echo "Frontend: http://localhost:5173 <- open this"
|
|
128
|
-
if [ "$BACKEND_PORT" != "8000" ]; then
|
|
129
|
-
echo ""
|
|
130
|
-
echo "NOTE: backend is on a non-default port. Update the proxy target in" \
|
|
131
|
-
"src/frontend/vite.config.ts to http://127.0.0.1:$BACKEND_PORT (local-only change, do not commit)."
|
|
132
|
-
fi
|
|
133
127
|
echo ""
|
|
134
128
|
echo "Press Ctrl+C to stop both."
|
|
135
129
|
|
|
@@ -230,6 +230,8 @@ class AgentHookEventReader:
|
|
|
230
230
|
self._cached_signature: tuple | None = None
|
|
231
231
|
self._cached_scans: list[AgentRepositoryScan] = []
|
|
232
232
|
self._cached_valid_until = 0
|
|
233
|
+
self._last_active_heads: dict[tuple[tuple[str, str], str], GitWorktreeInfo] = {}
|
|
234
|
+
self._idle_heads: dict[tuple[tuple[str, str], str], GitWorktreeInfo] = {}
|
|
233
235
|
|
|
234
236
|
def recent_scans(self, now: int | None = None) -> list[AgentRepositoryScan]:
|
|
235
237
|
"""One scan per open agent session.
|
|
@@ -294,6 +296,7 @@ class AgentHookEventReader:
|
|
|
294
296
|
opencode_signature = db_signature(self.opencode_db_path)
|
|
295
297
|
scans: list[AgentRepositoryScan] = []
|
|
296
298
|
next_change: int | None = None
|
|
299
|
+
seen_head_keys: set[tuple[tuple[str, str], str]] = set()
|
|
297
300
|
for session_key, session_events in sessions.items():
|
|
298
301
|
# A session is open until its end hook fires (or its process dies),
|
|
299
302
|
# however long it has been quiet.
|
|
@@ -374,6 +377,8 @@ class AgentHookEventReader:
|
|
|
374
377
|
elif busy is True:
|
|
375
378
|
# Working right now: the most recently touched worktree is where.
|
|
376
379
|
max(activities.values(), key=lambda item: item.last_activity).state = "active"
|
|
380
|
+
for activity in activities.values():
|
|
381
|
+
self._pin_idle_head(session_key, activity, seen_head_keys)
|
|
377
382
|
details: SessionDetails | None = None
|
|
378
383
|
if card is not None and session_id:
|
|
379
384
|
details = self._session_details(card, session_id)
|
|
@@ -404,8 +409,48 @@ class AgentHookEventReader:
|
|
|
404
409
|
parent_provider=provider if parent_opencode_session is not None else None,
|
|
405
410
|
)
|
|
406
411
|
)
|
|
412
|
+
self._prune_head_state(seen_head_keys)
|
|
407
413
|
return scans, next_change
|
|
408
414
|
|
|
415
|
+
def _pin_idle_head(
|
|
416
|
+
self,
|
|
417
|
+
session_key: tuple[str, str],
|
|
418
|
+
activity: AgentWorktreeActivity,
|
|
419
|
+
seen: set[tuple[tuple[str, str], str]],
|
|
420
|
+
) -> None:
|
|
421
|
+
key = (session_key, activity.worktree_path)
|
|
422
|
+
seen.add(key)
|
|
423
|
+
current = self._worktree_info(activity)
|
|
424
|
+
if activity.state == "active":
|
|
425
|
+
self._idle_heads.pop(key, None)
|
|
426
|
+
self._last_active_heads[key] = current
|
|
427
|
+
return
|
|
428
|
+
|
|
429
|
+
anchor = self._idle_heads.get(key)
|
|
430
|
+
if anchor is None or anchor.repository_id != activity.repository_id:
|
|
431
|
+
anchor = self._last_active_heads.get(key)
|
|
432
|
+
if anchor is None or anchor.repository_id != activity.repository_id:
|
|
433
|
+
anchor = current
|
|
434
|
+
self._idle_heads[key] = anchor
|
|
435
|
+
activity.branch = anchor.branch
|
|
436
|
+
activity.commit = anchor.commit
|
|
437
|
+
|
|
438
|
+
def _prune_head_state(self, seen: set[tuple[tuple[str, str], str]]) -> None:
|
|
439
|
+
for cache in (self._last_active_heads, self._idle_heads):
|
|
440
|
+
for key in list(cache):
|
|
441
|
+
if key not in seen:
|
|
442
|
+
del cache[key]
|
|
443
|
+
|
|
444
|
+
@staticmethod
|
|
445
|
+
def _worktree_info(activity: AgentWorktreeActivity) -> GitWorktreeInfo:
|
|
446
|
+
return GitWorktreeInfo(
|
|
447
|
+
repository_id=activity.repository_id,
|
|
448
|
+
worktree_path=activity.worktree_path,
|
|
449
|
+
common_git_dir=activity.repository_id,
|
|
450
|
+
branch=activity.branch,
|
|
451
|
+
commit=activity.commit,
|
|
452
|
+
)
|
|
453
|
+
|
|
409
454
|
@staticmethod
|
|
410
455
|
def _since_last_prompt(events: list[HookEvent]) -> list[HookEvent]:
|
|
411
456
|
"""The session's events from its latest user prompt onward.
|
|
@@ -25,7 +25,7 @@ router = APIRouter()
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def _resolve_repo_path(repo_id: str) -> Path:
|
|
28
|
-
path = resolve_repo_path(config.load_repo_paths(), repo_id)
|
|
28
|
+
path = resolve_repo_path(config.load_repo_paths(), repo_id, config.load_individual_repo_paths())
|
|
29
29
|
if path is None:
|
|
30
30
|
raise HTTPException(status_code=404, detail="repo not found")
|
|
31
31
|
return path
|
|
@@ -82,7 +82,7 @@ def api_ci_stages(repo_id: str, provider: str, run_id: str) -> list[CiStage]:
|
|
|
82
82
|
@router.get("/api/ci/recent", response_model=list[ActivePipeline])
|
|
83
83
|
def api_ci_recent(limit: Annotated[int, Query(ge=1, le=50)] = 10) -> list[ActivePipeline]:
|
|
84
84
|
return get_recent_pipelines(
|
|
85
|
-
list_repos(config.load_repo_paths()),
|
|
85
|
+
list_repos(config.load_repo_paths(), config.load_individual_repo_paths()),
|
|
86
86
|
config.load_github_config(),
|
|
87
87
|
config.load_jenkins_config(),
|
|
88
88
|
limit,
|
|
@@ -25,6 +25,7 @@ def create_config_router(root_path: Path) -> APIRouter:
|
|
|
25
25
|
def _current_config() -> ConfigResponse:
|
|
26
26
|
return ConfigResponse(
|
|
27
27
|
repo_paths=[str(p) for p in config.load_repo_paths()],
|
|
28
|
+
individual_repo_paths=[str(p) for p in config.load_individual_repo_paths()],
|
|
28
29
|
pinned_repo_paths=config.load_pinned_repo_paths(),
|
|
29
30
|
repo_groups=config.load_repo_groups(),
|
|
30
31
|
excluded_paths=config.load_excluded_paths(),
|
|
@@ -32,27 +33,36 @@ def create_config_router(root_path: Path) -> APIRouter:
|
|
|
32
33
|
github=config.load_github_config(),
|
|
33
34
|
jenkins=config.load_jenkins_config(),
|
|
34
35
|
terminal_shell=terminal_shell,
|
|
36
|
+
terminal_helper_command=[sys.executable, "-m", "git_juggler.main"],
|
|
35
37
|
)
|
|
36
38
|
|
|
37
39
|
@router.get("/api/config", response_model=ConfigResponse)
|
|
38
40
|
def api_get_config() -> ConfigResponse:
|
|
39
41
|
return _current_config()
|
|
40
42
|
|
|
43
|
+
def _resolve_dirs(raw_paths: list[str], *, require_git: bool) -> list[Path]:
|
|
44
|
+
resolved: list[Path] = []
|
|
45
|
+
seen: set[str] = set()
|
|
46
|
+
for raw in raw_paths:
|
|
47
|
+
path = Path(raw).expanduser().resolve()
|
|
48
|
+
if not path.is_dir():
|
|
49
|
+
raise HTTPException(status_code=400, detail=f"not a directory: {raw}")
|
|
50
|
+
if require_git and not (path / ".git").exists():
|
|
51
|
+
raise HTTPException(status_code=400, detail=f"not a git repository: {raw}")
|
|
52
|
+
key = str(path)
|
|
53
|
+
if key in seen:
|
|
54
|
+
continue
|
|
55
|
+
seen.add(key)
|
|
56
|
+
resolved.append(path)
|
|
57
|
+
return resolved
|
|
58
|
+
|
|
41
59
|
@router.put("/api/config", response_model=ConfigResponse)
|
|
42
60
|
def api_update_config(body: ConfigUpdateRequest) -> ConfigResponse:
|
|
43
61
|
if body.repo_paths is not None:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if not path.is_dir():
|
|
49
|
-
raise HTTPException(status_code=400, detail=f"not a directory: {raw}")
|
|
50
|
-
key = str(path)
|
|
51
|
-
if key in seen:
|
|
52
|
-
continue
|
|
53
|
-
seen.add(key)
|
|
54
|
-
resolved.append(path)
|
|
55
|
-
config.save_repo_paths(resolved)
|
|
62
|
+
config.save_repo_paths(_resolve_dirs(body.repo_paths, require_git=False))
|
|
63
|
+
|
|
64
|
+
if body.individual_repo_paths is not None:
|
|
65
|
+
config.save_individual_repo_paths(_resolve_dirs(body.individual_repo_paths, require_git=True))
|
|
56
66
|
|
|
57
67
|
if body.pinned_repo_paths is not None:
|
|
58
68
|
deduped = list(dict.fromkeys(body.pinned_repo_paths))
|
|
@@ -5,18 +5,19 @@ from pathlib import Path
|
|
|
5
5
|
from fastapi import APIRouter, HTTPException, Query
|
|
6
6
|
|
|
7
7
|
from .. import config
|
|
8
|
+
from ..git.conflicts import create_resolution_token, get_conflict_file_content
|
|
8
9
|
from ..git.data import GRAPH_PAGE_SIZE, HistoryChangedError, get_graph, get_repo_status
|
|
9
10
|
from ..git.detail import get_commit_detail
|
|
10
11
|
from ..git.diff import get_commit_file_diff, get_working_file_diff
|
|
11
12
|
from ..repo_discovery import resolve_repo_path
|
|
12
|
-
from ..schemas import CommitDetail, FileDiff, GraphResponse, RepoStatusResponse
|
|
13
|
+
from ..schemas import CommitDetail, ConflictFileContent, ConflictResolutionTokenRequest, ConflictResolutionTokenResponse, FileDiff, GraphResponse, RepoStatusResponse
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
router = APIRouter()
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def _resolve_repo_path(repo_id: str) -> Path:
|
|
19
|
-
path = resolve_repo_path(config.load_repo_paths(), repo_id)
|
|
20
|
+
path = resolve_repo_path(config.load_repo_paths(), repo_id, config.load_individual_repo_paths())
|
|
20
21
|
if path is None:
|
|
21
22
|
raise HTTPException(status_code=404, detail="repo not found")
|
|
22
23
|
return path
|
|
@@ -75,3 +76,24 @@ def api_working_file_diff(
|
|
|
75
76
|
return get_working_file_diff(repo_path, path, old_path, full)
|
|
76
77
|
except Exception as exc: # noqa: BLE001 - surfaced as a 404 either way
|
|
77
78
|
raise HTTPException(status_code=404, detail="diff not found") from exc
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@router.get("/api/repos/{repo_id}/conflicts/content", response_model=ConflictFileContent)
|
|
82
|
+
def api_conflict_file_content(repo_id: str, path: str) -> ConflictFileContent:
|
|
83
|
+
repo_path = _resolve_repo_path(repo_id)
|
|
84
|
+
try:
|
|
85
|
+
return get_conflict_file_content(repo_path, path)
|
|
86
|
+
except Exception as exc: # noqa: BLE001 - surfaced as a 404 either way
|
|
87
|
+
raise HTTPException(status_code=404, detail="conflict file not found") from exc
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@router.post("/api/repos/{repo_id}/conflicts/resolution-token", response_model=ConflictResolutionTokenResponse)
|
|
91
|
+
def api_create_conflict_resolution_token(
|
|
92
|
+
repo_id: str, request: ConflictResolutionTokenRequest
|
|
93
|
+
) -> ConflictResolutionTokenResponse:
|
|
94
|
+
if request.repo_id != repo_id:
|
|
95
|
+
raise HTTPException(status_code=400, detail="repo mismatch")
|
|
96
|
+
_resolve_repo_path(repo_id)
|
|
97
|
+
return ConflictResolutionTokenResponse(
|
|
98
|
+
token=create_resolution_token(repo_id, request.path, request.content)
|
|
99
|
+
)
|
|
@@ -13,7 +13,7 @@ router = APIRouter()
|
|
|
13
13
|
|
|
14
14
|
@router.get("/api/repos", response_model=list[RepoSummary])
|
|
15
15
|
def api_list_repos() -> list[RepoSummary]:
|
|
16
|
-
return list_repos(config.load_repo_paths())
|
|
16
|
+
return list_repos(config.load_repo_paths(), config.load_individual_repo_paths())
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
@router.get("/api/repos/scan-progress", response_model=RepoScanProgress)
|
|
@@ -17,7 +17,7 @@ def create_terminal_router(root_path: Path) -> APIRouter:
|
|
|
17
17
|
repo_id = websocket.query_params.get("repo")
|
|
18
18
|
cwd = root_path
|
|
19
19
|
if repo_id:
|
|
20
|
-
resolved = resolve_repo_path(config.load_repo_paths(), repo_id)
|
|
20
|
+
resolved = resolve_repo_path(config.load_repo_paths(), repo_id, config.load_individual_repo_paths())
|
|
21
21
|
if resolved is None:
|
|
22
22
|
await websocket.close(code=1008)
|
|
23
23
|
return
|
|
@@ -5,11 +5,22 @@ from pathlib import Path
|
|
|
5
5
|
|
|
6
6
|
from pydantic import ValidationError
|
|
7
7
|
|
|
8
|
-
from .schemas import Preferences
|
|
8
|
+
from .schemas import JenkinsConfig, JenkinsRuleConfig, Preferences
|
|
9
9
|
|
|
10
10
|
CONFIG_DIR = Path.home() / ".config" / "git-juggler"
|
|
11
11
|
CONFIG_PATH = CONFIG_DIR / "config.json"
|
|
12
12
|
|
|
13
|
+
# A fresh install ships one example Jenkins rule so the settings show how
|
|
14
|
+
# pipeline URL placeholders work. It has no repos, so nothing is ever requested
|
|
15
|
+
# from the placeholder host until the user assigns some. The settings' "Add
|
|
16
|
+
# rule" starts from the same rule (lib/jenkinsUrlTemplate.ts in the frontend).
|
|
17
|
+
EXAMPLE_JENKINS_BASE_URL = "https://jenkins.example.com"
|
|
18
|
+
EXAMPLE_JENKINS_RULE = JenkinsRuleConfig(
|
|
19
|
+
id="example",
|
|
20
|
+
name="Example rule",
|
|
21
|
+
job_url=EXAMPLE_JENKINS_BASE_URL + "/job/{repo_name_url}/job/{branch_name_url}",
|
|
22
|
+
)
|
|
23
|
+
|
|
13
24
|
|
|
14
25
|
def _load_raw() -> dict:
|
|
15
26
|
if not CONFIG_PATH.exists():
|
|
@@ -41,6 +52,21 @@ def save_repo_paths(paths: list[Path]) -> None:
|
|
|
41
52
|
_save_raw(data)
|
|
42
53
|
|
|
43
54
|
|
|
55
|
+
def load_individual_repo_paths() -> list[Path]:
|
|
56
|
+
"""Repos the user added one by one, on top of whatever the search paths
|
|
57
|
+
(``repo_paths``) turn up."""
|
|
58
|
+
raw = _load_raw().get("individual_repo_paths", [])
|
|
59
|
+
if not isinstance(raw, list):
|
|
60
|
+
return []
|
|
61
|
+
return [Path(p) for p in raw if isinstance(p, str)]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def save_individual_repo_paths(paths: list[Path]) -> None:
|
|
65
|
+
data = _load_raw()
|
|
66
|
+
data["individual_repo_paths"] = [str(p) for p in paths]
|
|
67
|
+
_save_raw(data)
|
|
68
|
+
|
|
69
|
+
|
|
44
70
|
def load_pinned_repo_paths() -> list[str]:
|
|
45
71
|
raw = _load_raw().get("pinned_repo_paths", [])
|
|
46
72
|
if not isinstance(raw, list):
|
|
@@ -201,10 +227,12 @@ def save_jenkins_config(jenkins: dict | None) -> None:
|
|
|
201
227
|
|
|
202
228
|
def ensure_seeded(default_path: Path) -> None:
|
|
203
229
|
"""On first run (no config file yet), seed it with the CLI-provided path
|
|
204
|
-
so existing single-path usage keeps working without extra setup
|
|
230
|
+
so existing single-path usage keeps working without extra setup, plus the
|
|
231
|
+
example Jenkins rule."""
|
|
205
232
|
if CONFIG_PATH.exists():
|
|
206
233
|
return
|
|
207
234
|
save_repo_paths([default_path])
|
|
235
|
+
save_jenkins_config(JenkinsConfig(base_url=EXAMPLE_JENKINS_BASE_URL, rules=[EXAMPLE_JENKINS_RULE]).model_dump())
|
|
208
236
|
|
|
209
237
|
|
|
210
238
|
def reset_to_factory(default_path: Path) -> None:
|