@akiojin/gwt 2.0.0 → 2.0.3
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.
- package/dist/claude-history.d.ts +60 -0
- package/dist/claude-history.d.ts.map +1 -0
- package/dist/claude-history.js +606 -0
- package/dist/claude-history.js.map +1 -0
- package/dist/claude.d.ts +12 -0
- package/dist/claude.d.ts.map +1 -0
- package/dist/claude.js +222 -0
- package/dist/claude.js.map +1 -0
- package/dist/cli/ui/components/App.d.ts +22 -0
- package/dist/cli/ui/components/App.d.ts.map +1 -0
- package/dist/cli/ui/components/App.js +557 -0
- package/dist/cli/ui/components/App.js.map +1 -0
- package/dist/cli/ui/components/common/Confirm.d.ts +13 -0
- package/dist/cli/ui/components/common/Confirm.d.ts.map +1 -0
- package/dist/cli/ui/components/common/Confirm.js +20 -0
- package/dist/cli/ui/components/common/Confirm.js.map +1 -0
- package/dist/cli/ui/components/common/ErrorBoundary.d.ts +23 -0
- package/dist/cli/ui/components/common/ErrorBoundary.d.ts.map +1 -0
- package/dist/cli/ui/components/common/ErrorBoundary.js +37 -0
- package/dist/cli/ui/components/common/ErrorBoundary.js.map +1 -0
- package/dist/cli/ui/components/common/Input.d.ts +14 -0
- package/dist/cli/ui/components/common/Input.d.ts.map +1 -0
- package/dist/cli/ui/components/common/Input.js +14 -0
- package/dist/cli/ui/components/common/Input.js.map +1 -0
- package/dist/cli/ui/components/common/LoadingIndicator.d.ts +19 -0
- package/dist/cli/ui/components/common/LoadingIndicator.d.ts.map +1 -0
- package/dist/cli/ui/components/common/LoadingIndicator.js +61 -0
- package/dist/cli/ui/components/common/LoadingIndicator.js.map +1 -0
- package/dist/cli/ui/components/common/Select.d.ts +30 -0
- package/dist/cli/ui/components/common/Select.d.ts.map +1 -0
- package/dist/cli/ui/components/common/Select.js +140 -0
- package/dist/cli/ui/components/common/Select.js.map +1 -0
- package/dist/cli/ui/components/parts/Footer.d.ts +15 -0
- package/dist/cli/ui/components/parts/Footer.d.ts.map +1 -0
- package/dist/cli/ui/components/parts/Footer.js +20 -0
- package/dist/cli/ui/components/parts/Footer.js.map +1 -0
- package/dist/cli/ui/components/parts/Header.d.ts +29 -0
- package/dist/cli/ui/components/parts/Header.d.ts.map +1 -0
- package/dist/cli/ui/components/parts/Header.js +19 -0
- package/dist/cli/ui/components/parts/Header.js.map +1 -0
- package/dist/cli/ui/components/parts/MergeStatusList.d.ts +13 -0
- package/dist/cli/ui/components/parts/MergeStatusList.d.ts.map +1 -0
- package/dist/cli/ui/components/parts/MergeStatusList.js +52 -0
- package/dist/cli/ui/components/parts/MergeStatusList.js.map +1 -0
- package/dist/cli/ui/components/parts/ProgressBar.d.ts +13 -0
- package/dist/cli/ui/components/parts/ProgressBar.d.ts.map +1 -0
- package/dist/cli/ui/components/parts/ProgressBar.js +53 -0
- package/dist/cli/ui/components/parts/ProgressBar.js.map +1 -0
- package/dist/cli/ui/components/parts/ScrollableList.d.ts +12 -0
- package/dist/cli/ui/components/parts/ScrollableList.d.ts.map +1 -0
- package/dist/cli/ui/components/parts/ScrollableList.js +11 -0
- package/dist/cli/ui/components/parts/ScrollableList.js.map +1 -0
- package/dist/cli/ui/components/parts/Stats.d.ts +13 -0
- package/dist/cli/ui/components/parts/Stats.d.ts.map +1 -0
- package/dist/cli/ui/components/parts/Stats.js +42 -0
- package/dist/cli/ui/components/parts/Stats.js.map +1 -0
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts +20 -0
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts.map +1 -0
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.js +77 -0
- package/dist/cli/ui/components/screens/AIToolSelectorScreen.js.map +1 -0
- package/dist/cli/ui/components/screens/BatchMergeProgressScreen.d.ts +14 -0
- package/dist/cli/ui/components/screens/BatchMergeProgressScreen.d.ts.map +1 -0
- package/dist/cli/ui/components/screens/BatchMergeProgressScreen.js +41 -0
- package/dist/cli/ui/components/screens/BatchMergeProgressScreen.js.map +1 -0
- package/dist/cli/ui/components/screens/BatchMergeResultScreen.d.ts +14 -0
- package/dist/cli/ui/components/screens/BatchMergeResultScreen.d.ts.map +1 -0
- package/dist/cli/ui/components/screens/BatchMergeResultScreen.js +71 -0
- package/dist/cli/ui/components/screens/BatchMergeResultScreen.js.map +1 -0
- package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts +15 -0
- package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts.map +1 -0
- package/dist/cli/ui/components/screens/BranchCreatorScreen.js +142 -0
- package/dist/cli/ui/components/screens/BranchCreatorScreen.js.map +1 -0
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts +39 -0
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -0
- package/dist/cli/ui/components/screens/BranchListScreen.js +172 -0
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -0
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts +29 -0
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts.map +1 -0
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js +92 -0
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js.map +1 -0
- package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts +23 -0
- package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts.map +1 -0
- package/dist/cli/ui/components/screens/PRCleanupScreen.js +94 -0
- package/dist/cli/ui/components/screens/PRCleanupScreen.js.map +1 -0
- package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts +17 -0
- package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts.map +1 -0
- package/dist/cli/ui/components/screens/SessionSelectorScreen.js +55 -0
- package/dist/cli/ui/components/screens/SessionSelectorScreen.js.map +1 -0
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.d.ts +20 -0
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.d.ts.map +1 -0
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.js +65 -0
- package/dist/cli/ui/components/screens/WorktreeManagerScreen.js.map +1 -0
- package/dist/cli/ui/hooks/useBatchMerge.d.ts +17 -0
- package/dist/cli/ui/hooks/useBatchMerge.d.ts.map +1 -0
- package/dist/cli/ui/hooks/useBatchMerge.js +77 -0
- package/dist/cli/ui/hooks/useBatchMerge.js.map +1 -0
- package/dist/cli/ui/hooks/useGitData.d.ts +20 -0
- package/dist/cli/ui/hooks/useGitData.d.ts.map +1 -0
- package/dist/cli/ui/hooks/useGitData.js +116 -0
- package/dist/cli/ui/hooks/useGitData.js.map +1 -0
- package/dist/cli/ui/hooks/useScreenState.d.ts +12 -0
- package/dist/cli/ui/hooks/useScreenState.d.ts.map +1 -0
- package/dist/cli/ui/hooks/useScreenState.js +30 -0
- package/dist/cli/ui/hooks/useScreenState.js.map +1 -0
- package/dist/cli/ui/hooks/useTerminalSize.d.ts +9 -0
- package/dist/cli/ui/hooks/useTerminalSize.d.ts.map +1 -0
- package/dist/cli/ui/hooks/useTerminalSize.js +24 -0
- package/dist/cli/ui/hooks/useTerminalSize.js.map +1 -0
- package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts +21 -0
- package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts.map +1 -0
- package/dist/cli/ui/screens/BranchActionSelectorScreen.js +61 -0
- package/dist/cli/ui/screens/BranchActionSelectorScreen.js.map +1 -0
- package/dist/cli/ui/types.d.ts +245 -0
- package/dist/cli/ui/types.d.ts.map +1 -0
- package/dist/cli/ui/types.js +2 -0
- package/dist/cli/ui/types.js.map +1 -0
- package/dist/cli/ui/utils/baseBranch.d.ts +4 -0
- package/dist/cli/ui/utils/baseBranch.d.ts.map +1 -0
- package/dist/cli/ui/utils/baseBranch.js +17 -0
- package/dist/cli/ui/utils/baseBranch.js.map +1 -0
- package/dist/cli/ui/utils/branchFormatter.d.ts +13 -0
- package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -0
- package/dist/cli/ui/utils/branchFormatter.js +202 -0
- package/dist/cli/ui/utils/branchFormatter.js.map +1 -0
- package/dist/cli/ui/utils/statisticsCalculator.d.ts +9 -0
- package/dist/cli/ui/utils/statisticsCalculator.d.ts.map +1 -0
- package/dist/cli/ui/utils/statisticsCalculator.js +37 -0
- package/dist/cli/ui/utils/statisticsCalculator.js.map +1 -0
- package/dist/client/assets/index-CqaYsI0z.js +81 -0
- package/dist/client/assets/index-Dy3OxWpP.css +32 -0
- package/dist/client/index.html +13 -0
- package/dist/codex.d.ts +12 -0
- package/dist/codex.d.ts.map +1 -0
- package/dist/codex.js +106 -0
- package/dist/codex.js.map +1 -0
- package/dist/config/builtin-tools.d.ts +19 -0
- package/dist/config/builtin-tools.d.ts.map +1 -0
- package/dist/config/builtin-tools.js +40 -0
- package/dist/config/builtin-tools.js.map +1 -0
- package/dist/config/constants.d.ts +74 -0
- package/dist/config/constants.d.ts.map +1 -0
- package/dist/config/constants.js +90 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/config/env-history.d.ts +4 -0
- package/dist/config/env-history.d.ts.map +1 -0
- package/dist/config/env-history.js +34 -0
- package/dist/config/env-history.js.map +1 -0
- package/dist/config/index.d.ts +24 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +144 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/tools.d.ts +40 -0
- package/dist/config/tools.d.ts.map +1 -0
- package/dist/config/tools.js +238 -0
- package/dist/config/tools.js.map +1 -0
- package/dist/git.d.ts +145 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +871 -0
- package/dist/git.js.map +1 -0
- package/dist/github.d.ts +14 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js +123 -0
- package/dist/github.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +510 -0
- package/dist/index.js.map +1 -0
- package/dist/launcher.d.ts +30 -0
- package/dist/launcher.d.ts.map +1 -0
- package/dist/launcher.js +116 -0
- package/dist/launcher.js.map +1 -0
- package/dist/repositories/git.repository.d.ts +38 -0
- package/dist/repositories/git.repository.d.ts.map +1 -0
- package/dist/repositories/git.repository.js +117 -0
- package/dist/repositories/git.repository.js.map +1 -0
- package/dist/repositories/github.repository.d.ts +16 -0
- package/dist/repositories/github.repository.d.ts.map +1 -0
- package/dist/repositories/github.repository.js +61 -0
- package/dist/repositories/github.repository.js.map +1 -0
- package/dist/repositories/worktree.repository.d.ts +18 -0
- package/dist/repositories/worktree.repository.d.ts.map +1 -0
- package/dist/repositories/worktree.repository.js +57 -0
- package/dist/repositories/worktree.repository.js.map +1 -0
- package/dist/services/BatchMergeService.d.ts +45 -0
- package/dist/services/BatchMergeService.d.ts.map +1 -0
- package/dist/services/BatchMergeService.js +197 -0
- package/dist/services/BatchMergeService.js.map +1 -0
- package/dist/services/WorktreeOrchestrator.d.ts +37 -0
- package/dist/services/WorktreeOrchestrator.d.ts.map +1 -0
- package/dist/services/WorktreeOrchestrator.js +74 -0
- package/dist/services/WorktreeOrchestrator.js.map +1 -0
- package/dist/services/dependency-installer.d.ts +26 -0
- package/dist/services/dependency-installer.d.ts.map +1 -0
- package/dist/services/dependency-installer.js +139 -0
- package/dist/services/dependency-installer.js.map +1 -0
- package/dist/services/git.service.d.ts +27 -0
- package/dist/services/git.service.d.ts.map +1 -0
- package/dist/services/git.service.js +95 -0
- package/dist/services/git.service.js.map +1 -0
- package/dist/services/github.service.d.ts +14 -0
- package/dist/services/github.service.d.ts.map +1 -0
- package/dist/services/github.service.js +52 -0
- package/dist/services/github.service.js.map +1 -0
- package/dist/services/worktree.service.d.ts +20 -0
- package/dist/services/worktree.service.d.ts.map +1 -0
- package/dist/services/worktree.service.js +52 -0
- package/dist/services/worktree.service.js.map +1 -0
- package/dist/types/api.d.ts +211 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +8 -0
- package/dist/types/api.js.map +1 -0
- package/dist/types/tools.d.ts +201 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +8 -0
- package/dist/types/tools.js.map +1 -0
- package/dist/utils/spinner.d.ts +6 -0
- package/dist/utils/spinner.d.ts.map +1 -0
- package/dist/utils/spinner.js +47 -0
- package/dist/utils/spinner.js.map +1 -0
- package/dist/utils/terminal.d.ts +20 -0
- package/dist/utils/terminal.d.ts.map +1 -0
- package/dist/utils/terminal.js +232 -0
- package/dist/utils/terminal.js.map +1 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +48 -0
- package/dist/utils.js.map +1 -0
- package/dist/web/client/src/components/BranchGraph.d.ts +8 -0
- package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -0
- package/dist/web/client/src/components/BranchGraph.js +125 -0
- package/dist/web/client/src/components/BranchGraph.js.map +1 -0
- package/dist/web/client/src/components/EnvEditor.d.ts +20 -0
- package/dist/web/client/src/components/EnvEditor.d.ts.map +1 -0
- package/dist/web/client/src/components/EnvEditor.js +65 -0
- package/dist/web/client/src/components/EnvEditor.js.map +1 -0
- package/dist/web/client/src/components/Terminal.d.ts +15 -0
- package/dist/web/client/src/components/Terminal.d.ts.map +1 -0
- package/dist/web/client/src/components/Terminal.js +112 -0
- package/dist/web/client/src/components/Terminal.js.map +1 -0
- package/dist/web/client/src/hooks/useBranches.d.ts +16 -0
- package/dist/web/client/src/hooks/useBranches.d.ts.map +1 -0
- package/dist/web/client/src/hooks/useBranches.js +35 -0
- package/dist/web/client/src/hooks/useBranches.js.map +1 -0
- package/dist/web/client/src/hooks/useConfig.d.ts +13 -0
- package/dist/web/client/src/hooks/useConfig.d.ts.map +1 -0
- package/dist/web/client/src/hooks/useConfig.js +27 -0
- package/dist/web/client/src/hooks/useConfig.js.map +1 -0
- package/dist/web/client/src/hooks/useSessions.d.ts +21 -0
- package/dist/web/client/src/hooks/useSessions.d.ts.map +1 -0
- package/dist/web/client/src/hooks/useSessions.js +49 -0
- package/dist/web/client/src/hooks/useSessions.js.map +1 -0
- package/dist/web/client/src/hooks/useWorktrees.d.ts +17 -0
- package/dist/web/client/src/hooks/useWorktrees.d.ts.map +1 -0
- package/dist/web/client/src/hooks/useWorktrees.js +41 -0
- package/dist/web/client/src/hooks/useWorktrees.js.map +1 -0
- package/dist/web/client/src/lib/api.d.ts +82 -0
- package/dist/web/client/src/lib/api.d.ts.map +1 -0
- package/dist/web/client/src/lib/api.js +147 -0
- package/dist/web/client/src/lib/api.js.map +1 -0
- package/dist/web/client/src/lib/websocket.d.ts +52 -0
- package/dist/web/client/src/lib/websocket.d.ts.map +1 -0
- package/dist/web/client/src/lib/websocket.js +137 -0
- package/dist/web/client/src/lib/websocket.js.map +1 -0
- package/dist/web/client/src/main.d.ts +2 -0
- package/dist/web/client/src/main.d.ts.map +1 -0
- package/dist/web/client/src/main.js +23 -0
- package/dist/web/client/src/main.js.map +1 -0
- package/dist/web/client/src/pages/BranchDetailPage.d.ts +3 -0
- package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -0
- package/dist/web/client/src/pages/BranchDetailPage.js +519 -0
- package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -0
- package/dist/web/client/src/pages/BranchListPage.d.ts +3 -0
- package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -0
- package/dist/web/client/src/pages/BranchListPage.js +150 -0
- package/dist/web/client/src/pages/BranchListPage.js.map +1 -0
- package/dist/web/client/src/pages/ConfigManagementPage.d.ts +3 -0
- package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -0
- package/dist/web/client/src/pages/ConfigManagementPage.js +125 -0
- package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -0
- package/dist/web/client/src/router.d.ts +9 -0
- package/dist/web/client/src/router.d.ts.map +1 -0
- package/dist/web/client/src/router.js +27 -0
- package/dist/web/client/src/router.js.map +1 -0
- package/dist/web/client/vite.config.d.ts +3 -0
- package/dist/web/client/vite.config.d.ts.map +1 -0
- package/dist/web/client/vite.config.js +21 -0
- package/dist/web/client/vite.config.js.map +1 -0
- package/dist/web/server/env/importer.d.ts +3 -0
- package/dist/web/server/env/importer.d.ts.map +1 -0
- package/dist/web/server/env/importer.js +47 -0
- package/dist/web/server/env/importer.js.map +1 -0
- package/dist/web/server/index.d.ts +11 -0
- package/dist/web/server/index.d.ts.map +1 -0
- package/dist/web/server/index.js +62 -0
- package/dist/web/server/index.js.map +1 -0
- package/dist/web/server/pty/manager.d.ts +50 -0
- package/dist/web/server/pty/manager.d.ts.map +1 -0
- package/dist/web/server/pty/manager.js +143 -0
- package/dist/web/server/pty/manager.js.map +1 -0
- package/dist/web/server/routes/branches.d.ts +11 -0
- package/dist/web/server/routes/branches.d.ts.map +1 -0
- package/dist/web/server/routes/branches.js +97 -0
- package/dist/web/server/routes/branches.js.map +1 -0
- package/dist/web/server/routes/config.d.ts +6 -0
- package/dist/web/server/routes/config.d.ts.map +1 -0
- package/dist/web/server/routes/config.js +162 -0
- package/dist/web/server/routes/config.js.map +1 -0
- package/dist/web/server/routes/index.d.ts +13 -0
- package/dist/web/server/routes/index.d.ts.map +1 -0
- package/dist/web/server/routes/index.js +29 -0
- package/dist/web/server/routes/index.js.map +1 -0
- package/dist/web/server/routes/sessions.d.ts +12 -0
- package/dist/web/server/routes/sessions.d.ts.map +1 -0
- package/dist/web/server/routes/sessions.js +95 -0
- package/dist/web/server/routes/sessions.js.map +1 -0
- package/dist/web/server/routes/worktrees.d.ts +11 -0
- package/dist/web/server/routes/worktrees.d.ts.map +1 -0
- package/dist/web/server/routes/worktrees.js +80 -0
- package/dist/web/server/routes/worktrees.js.map +1 -0
- package/dist/web/server/services/branches.d.ts +17 -0
- package/dist/web/server/services/branches.d.ts.map +1 -0
- package/dist/web/server/services/branches.js +262 -0
- package/dist/web/server/services/branches.js.map +1 -0
- package/dist/web/server/services/worktrees.d.ts +24 -0
- package/dist/web/server/services/worktrees.d.ts.map +1 -0
- package/dist/web/server/services/worktrees.js +60 -0
- package/dist/web/server/services/worktrees.js.map +1 -0
- package/dist/web/server/websocket/handler.d.ts +41 -0
- package/dist/web/server/websocket/handler.d.ts.map +1 -0
- package/dist/web/server/websocket/handler.js +143 -0
- package/dist/web/server/websocket/handler.js.map +1 -0
- package/dist/worktree.d.ts +53 -0
- package/dist/worktree.d.ts.map +1 -0
- package/dist/worktree.js +489 -0
- package/dist/worktree.js.map +1 -0
- package/package.json +1 -1
package/dist/git.js
ADDED
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
import { execa } from "execa";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export class GitError extends Error {
|
|
4
|
+
cause;
|
|
5
|
+
constructor(message, cause) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.cause = cause;
|
|
8
|
+
this.name = "GitError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 現在のディレクトリがGitリポジトリかどうかを確認
|
|
13
|
+
* Worktree環境でも動作するように、.gitファイルの存在も確認します
|
|
14
|
+
* @returns {Promise<boolean>} Gitリポジトリの場合true
|
|
15
|
+
*/
|
|
16
|
+
export async function isGitRepository() {
|
|
17
|
+
try {
|
|
18
|
+
// まず.gitの存在を確認(ディレクトリまたはファイル)
|
|
19
|
+
const fs = await import("node:fs");
|
|
20
|
+
const gitPath = path.join(process.cwd(), ".git");
|
|
21
|
+
if (fs.existsSync(gitPath)) {
|
|
22
|
+
// .gitが存在する場合、Git環境として認識
|
|
23
|
+
if (process.env.DEBUG) {
|
|
24
|
+
const stats = fs.statSync(gitPath);
|
|
25
|
+
console.error(`[DEBUG] .git exists: ${gitPath} (${stats.isDirectory() ? "directory" : "file"})`);
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
// .gitが存在しない場合、git rev-parseで確認
|
|
30
|
+
const result = await execa("git", ["rev-parse", "--git-dir"]);
|
|
31
|
+
if (process.env.DEBUG) {
|
|
32
|
+
console.error(`[DEBUG] git rev-parse --git-dir: ${result.stdout}`);
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
// Debug: log the error for troubleshooting
|
|
38
|
+
if (process.env.DEBUG) {
|
|
39
|
+
console.error(`[DEBUG] git rev-parse --git-dir failed:`, error.message);
|
|
40
|
+
if (error.stderr) {
|
|
41
|
+
console.error(`[DEBUG] stderr:`, error.stderr);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Gitリポジトリのルートディレクトリを取得
|
|
49
|
+
* @returns {Promise<string>} リポジトリのルートパス
|
|
50
|
+
* @throws {GitError} リポジトリルートの取得に失敗した場合
|
|
51
|
+
*/
|
|
52
|
+
export async function getRepositoryRoot() {
|
|
53
|
+
try {
|
|
54
|
+
// git rev-parse --git-common-dirを使用してメインリポジトリの.gitディレクトリを取得
|
|
55
|
+
const { stdout: gitCommonDir } = await execa("git", [
|
|
56
|
+
"rev-parse",
|
|
57
|
+
"--git-common-dir",
|
|
58
|
+
]);
|
|
59
|
+
const gitDir = gitCommonDir.trim();
|
|
60
|
+
// .gitディレクトリの親ディレクトリがリポジトリルート
|
|
61
|
+
const path = await import("node:path");
|
|
62
|
+
const repoRoot = path.dirname(gitDir);
|
|
63
|
+
// 相対パスが返された場合(.gitなど)は、現在のディレクトリからの相対パスとして解決
|
|
64
|
+
if (!path.isAbsolute(repoRoot)) {
|
|
65
|
+
return path.resolve(repoRoot);
|
|
66
|
+
}
|
|
67
|
+
return repoRoot;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
throw new GitError("Failed to get repository root", error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 現在の作業ツリー(root branch か worktree かを問わず)のルートディレクトリを取得
|
|
75
|
+
* @returns {Promise<string>} カレントWorktreeのルートパス
|
|
76
|
+
* @throws {GitError} 取得に失敗した場合
|
|
77
|
+
*/
|
|
78
|
+
export async function getWorktreeRoot() {
|
|
79
|
+
try {
|
|
80
|
+
const { stdout } = await execa("git", ["rev-parse", "--show-toplevel"]);
|
|
81
|
+
return stdout.trim();
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
throw new GitError("Failed to get worktree root", error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export async function getCurrentBranch() {
|
|
88
|
+
try {
|
|
89
|
+
const { stdout } = await execa("git", ["branch", "--show-current"]);
|
|
90
|
+
return stdout.trim() || null;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function getBranchCommitTimestamps(refs) {
|
|
97
|
+
try {
|
|
98
|
+
const { stdout } = await execa("git", [
|
|
99
|
+
"for-each-ref",
|
|
100
|
+
"--format=%(refname:short)%00%(committerdate:unix)",
|
|
101
|
+
...refs,
|
|
102
|
+
]);
|
|
103
|
+
const map = new Map();
|
|
104
|
+
for (const line of stdout.split("\n")) {
|
|
105
|
+
if (!line)
|
|
106
|
+
continue;
|
|
107
|
+
const [ref, timestamp] = line.split("\0");
|
|
108
|
+
if (!ref || !timestamp)
|
|
109
|
+
continue;
|
|
110
|
+
if (ref.endsWith("/HEAD"))
|
|
111
|
+
continue;
|
|
112
|
+
const parsed = Number.parseInt(timestamp, 10);
|
|
113
|
+
if (Number.isNaN(parsed))
|
|
114
|
+
continue;
|
|
115
|
+
map.set(ref, parsed);
|
|
116
|
+
}
|
|
117
|
+
return map;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
throw new GitError("Failed to get branch commit timestamps", error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export async function getLocalBranches() {
|
|
124
|
+
try {
|
|
125
|
+
const commitMap = await getBranchCommitTimestamps(["refs/heads"]);
|
|
126
|
+
const { stdout } = await execa("git", [
|
|
127
|
+
"branch",
|
|
128
|
+
"--format=%(refname:short)",
|
|
129
|
+
]);
|
|
130
|
+
return stdout
|
|
131
|
+
.split("\n")
|
|
132
|
+
.filter((line) => line.trim())
|
|
133
|
+
.map((name) => {
|
|
134
|
+
const trimmed = name.trim();
|
|
135
|
+
const timestamp = commitMap.get(trimmed);
|
|
136
|
+
return {
|
|
137
|
+
name: trimmed,
|
|
138
|
+
type: "local",
|
|
139
|
+
branchType: getBranchType(trimmed),
|
|
140
|
+
isCurrent: false,
|
|
141
|
+
...(timestamp !== undefined
|
|
142
|
+
? { latestCommitTimestamp: timestamp }
|
|
143
|
+
: {}),
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
throw new GitError("Failed to get local branches", error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export async function getRemoteBranches() {
|
|
152
|
+
try {
|
|
153
|
+
const commitMap = await getBranchCommitTimestamps(["refs/remotes"]);
|
|
154
|
+
const { stdout } = await execa("git", [
|
|
155
|
+
"branch",
|
|
156
|
+
"-r",
|
|
157
|
+
"--format=%(refname:short)",
|
|
158
|
+
]);
|
|
159
|
+
return stdout
|
|
160
|
+
.split("\n")
|
|
161
|
+
.filter((line) => line.trim() && !line.includes("HEAD"))
|
|
162
|
+
.map((line) => {
|
|
163
|
+
const name = line.trim();
|
|
164
|
+
const branchName = name.replace(/^origin\//, "");
|
|
165
|
+
const timestamp = commitMap.get(name);
|
|
166
|
+
return {
|
|
167
|
+
name,
|
|
168
|
+
type: "remote",
|
|
169
|
+
branchType: getBranchType(branchName),
|
|
170
|
+
isCurrent: false,
|
|
171
|
+
...(timestamp !== undefined
|
|
172
|
+
? { latestCommitTimestamp: timestamp }
|
|
173
|
+
: {}),
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
throw new GitError("Failed to get remote branches", error);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* ローカルとリモートのすべてのブランチ情報を取得
|
|
183
|
+
* @returns {Promise<BranchInfo[]>} ブランチ情報の配列
|
|
184
|
+
*/
|
|
185
|
+
export async function getAllBranches() {
|
|
186
|
+
const [localBranches, remoteBranches, currentBranch] = await Promise.all([
|
|
187
|
+
getLocalBranches(),
|
|
188
|
+
getRemoteBranches(),
|
|
189
|
+
getCurrentBranch(),
|
|
190
|
+
]);
|
|
191
|
+
// 現在のブランチ情報を設定
|
|
192
|
+
if (currentBranch) {
|
|
193
|
+
localBranches.forEach((branch) => {
|
|
194
|
+
if (branch.name === currentBranch) {
|
|
195
|
+
branch.isCurrent = true;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return [...localBranches, ...remoteBranches];
|
|
200
|
+
}
|
|
201
|
+
export async function createBranch(branchName, baseBranch = "main") {
|
|
202
|
+
try {
|
|
203
|
+
await execa("git", ["checkout", "-b", branchName, baseBranch]);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
throw new GitError(`Failed to create branch ${branchName}`, error);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export async function branchExists(branchName) {
|
|
210
|
+
try {
|
|
211
|
+
await execa("git", [
|
|
212
|
+
"show-ref",
|
|
213
|
+
"--verify",
|
|
214
|
+
"--quiet",
|
|
215
|
+
`refs/heads/${branchName}`,
|
|
216
|
+
]);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
export async function deleteBranch(branchName, force = false) {
|
|
224
|
+
try {
|
|
225
|
+
const args = ["branch", force ? "-D" : "-d", branchName];
|
|
226
|
+
await execa("git", args);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
throw new GitError(`Failed to delete branch ${branchName}`, error);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async function getWorkdirStatus(worktreePath) {
|
|
233
|
+
try {
|
|
234
|
+
// ファイルシステムの存在確認のためにfs.existsSyncを使用
|
|
235
|
+
const fs = await import("node:fs");
|
|
236
|
+
if (!fs.existsSync(worktreePath)) {
|
|
237
|
+
// worktreeパスが存在しない場合はデフォルト値を返す
|
|
238
|
+
return {
|
|
239
|
+
hasChanges: false,
|
|
240
|
+
changedFilesCount: 0,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const { stdout } = await execa("git", ["status", "--porcelain"], {
|
|
244
|
+
cwd: worktreePath,
|
|
245
|
+
});
|
|
246
|
+
const lines = stdout.split("\n").filter((line) => line.trim());
|
|
247
|
+
return {
|
|
248
|
+
hasChanges: lines.length > 0,
|
|
249
|
+
changedFilesCount: lines.length,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
throw new GitError(`Failed to get worktree status for path: ${worktreePath}`, error);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
export async function hasUncommittedChanges(worktreePath) {
|
|
257
|
+
const status = await getWorkdirStatus(worktreePath);
|
|
258
|
+
return status.hasChanges;
|
|
259
|
+
}
|
|
260
|
+
export async function getChangedFilesCount(worktreePath) {
|
|
261
|
+
const status = await getWorkdirStatus(worktreePath);
|
|
262
|
+
return status.changedFilesCount;
|
|
263
|
+
}
|
|
264
|
+
export async function showStatus(worktreePath) {
|
|
265
|
+
try {
|
|
266
|
+
const { stdout } = await execa("git", ["status"], { cwd: worktreePath });
|
|
267
|
+
return stdout;
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
throw new GitError("Failed to show status", error);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
export async function stashChanges(worktreePath, message) {
|
|
274
|
+
try {
|
|
275
|
+
const args = message ? ["stash", "push", "-m", message] : ["stash"];
|
|
276
|
+
await execa("git", args, { cwd: worktreePath });
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
throw new GitError("Failed to stash changes", error);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
export async function discardAllChanges(worktreePath) {
|
|
283
|
+
try {
|
|
284
|
+
// Reset tracked files
|
|
285
|
+
await execa("git", ["reset", "--hard"], { cwd: worktreePath });
|
|
286
|
+
// Clean untracked files
|
|
287
|
+
await execa("git", ["clean", "-fd"], { cwd: worktreePath });
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
throw new GitError("Failed to discard changes", error);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
export async function commitChanges(worktreePath, message) {
|
|
294
|
+
try {
|
|
295
|
+
// Add all changes
|
|
296
|
+
await execa("git", ["add", "-A"], { cwd: worktreePath });
|
|
297
|
+
// Commit
|
|
298
|
+
await execa("git", ["commit", "-m", message], { cwd: worktreePath });
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
throw new GitError("Failed to commit changes", error);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function getBranchType(branchName) {
|
|
305
|
+
if (branchName === "main" || branchName === "master")
|
|
306
|
+
return "main";
|
|
307
|
+
if (branchName === "develop" || branchName === "dev")
|
|
308
|
+
return "develop";
|
|
309
|
+
if (branchName.startsWith("feature/"))
|
|
310
|
+
return "feature";
|
|
311
|
+
if (branchName.startsWith("hotfix/"))
|
|
312
|
+
return "hotfix";
|
|
313
|
+
if (branchName.startsWith("release/"))
|
|
314
|
+
return "release";
|
|
315
|
+
return "other";
|
|
316
|
+
}
|
|
317
|
+
async function hasUnpushedCommitsInternal(branch, options = {}) {
|
|
318
|
+
const { cwd } = options;
|
|
319
|
+
const execOptions = cwd ? { cwd } : undefined;
|
|
320
|
+
try {
|
|
321
|
+
const { stdout } = await execa("git", ["log", `origin/${branch}..${branch}`, "--oneline"], execOptions);
|
|
322
|
+
return stdout.trim().length > 0;
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
const candidates = [
|
|
326
|
+
`origin/${branch}`,
|
|
327
|
+
"origin/main",
|
|
328
|
+
"origin/master",
|
|
329
|
+
"origin/develop",
|
|
330
|
+
"origin/dev",
|
|
331
|
+
branch,
|
|
332
|
+
"main",
|
|
333
|
+
"master",
|
|
334
|
+
"develop",
|
|
335
|
+
"dev",
|
|
336
|
+
];
|
|
337
|
+
for (const candidate of candidates) {
|
|
338
|
+
try {
|
|
339
|
+
await execa("git", ["rev-parse", "--verify", candidate], execOptions);
|
|
340
|
+
// If we are checking the same branch again, we already know the remote ref is missing.
|
|
341
|
+
if (candidate === `origin/${branch}` || candidate === branch) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
await execa("git", ["merge-base", "--is-ancestor", branch, candidate], execOptions);
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
// Not merged into this candidate, try next one.
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
// Candidate ref does not exist. Try the next candidate.
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// Could not prove that the branch is merged anywhere safe, treat as unpushed commits.
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
export async function hasUnpushedCommits(worktreePath, branch) {
|
|
361
|
+
return hasUnpushedCommitsInternal(branch, { cwd: worktreePath });
|
|
362
|
+
}
|
|
363
|
+
export async function hasUnpushedCommitsInRepo(branch, repoRoot) {
|
|
364
|
+
return hasUnpushedCommitsInternal(branch, repoRoot ? { cwd: repoRoot } : {});
|
|
365
|
+
}
|
|
366
|
+
export async function branchHasUniqueCommitsComparedToBase(branch, baseBranch, repoRoot) {
|
|
367
|
+
const execOptions = repoRoot ? { cwd: repoRoot } : undefined;
|
|
368
|
+
try {
|
|
369
|
+
await execa("git", ["rev-parse", "--verify", branch], execOptions);
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
const normalizedBase = baseBranch.trim();
|
|
375
|
+
if (!normalizedBase) {
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
const candidates = new Set();
|
|
379
|
+
candidates.add(normalizedBase);
|
|
380
|
+
if (!normalizedBase.startsWith("origin/")) {
|
|
381
|
+
candidates.add(`origin/${normalizedBase}`);
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
const localEquivalent = normalizedBase.replace(/^origin\//, "");
|
|
385
|
+
if (localEquivalent) {
|
|
386
|
+
candidates.add(localEquivalent);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
for (const candidate of candidates) {
|
|
390
|
+
try {
|
|
391
|
+
await execa("git", ["rev-parse", "--verify", candidate], execOptions);
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
const { stdout } = await execa("git", ["log", `${candidate}..${branch}`, "--oneline"], execOptions);
|
|
398
|
+
if (stdout.trim().length > 0) {
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
// Comparison failed for this candidate, try next one.
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// If no valid base candidate was found, treat the branch as having unique commits.
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Get the latest commit message for a specific branch in a worktree
|
|
412
|
+
*/
|
|
413
|
+
export async function getLatestCommitMessage(worktreePath, branch) {
|
|
414
|
+
try {
|
|
415
|
+
const { stdout } = await execa("git", ["log", "-1", "--pretty=format:%s", branch], { cwd: worktreePath });
|
|
416
|
+
return stdout.trim() || null;
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Get the count of unpushed commits
|
|
424
|
+
*/
|
|
425
|
+
export async function getUnpushedCommitsCount(worktreePath, branch) {
|
|
426
|
+
try {
|
|
427
|
+
const { stdout } = await execa("git", ["rev-list", "--count", `origin/${branch}..${branch}`], { cwd: worktreePath });
|
|
428
|
+
return parseInt(stdout.trim(), 10) || 0;
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
return 0;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Get the count of uncommitted changes (staged + unstaged)
|
|
436
|
+
*/
|
|
437
|
+
export async function getUncommittedChangesCount(worktreePath) {
|
|
438
|
+
try {
|
|
439
|
+
const { stdout } = await execa("git", ["status", "--porcelain"], {
|
|
440
|
+
cwd: worktreePath,
|
|
441
|
+
});
|
|
442
|
+
return stdout
|
|
443
|
+
.trim()
|
|
444
|
+
.split("\n")
|
|
445
|
+
.filter((line) => line.trim()).length;
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
return 0;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Get enhanced session information for display
|
|
453
|
+
*/
|
|
454
|
+
export async function getEnhancedSessionInfo(worktreePath, branch) {
|
|
455
|
+
try {
|
|
456
|
+
const [hasUncommitted, uncommittedCount, hasUnpushed, unpushedCount, latestCommit,] = await Promise.all([
|
|
457
|
+
hasUncommittedChanges(worktreePath),
|
|
458
|
+
getUncommittedChangesCount(worktreePath),
|
|
459
|
+
hasUnpushedCommits(worktreePath, branch),
|
|
460
|
+
getUnpushedCommitsCount(worktreePath, branch),
|
|
461
|
+
getLatestCommitMessage(worktreePath, branch),
|
|
462
|
+
]);
|
|
463
|
+
// Determine branch type based on branch name
|
|
464
|
+
let branchType = "other";
|
|
465
|
+
const lowerBranch = branch.toLowerCase();
|
|
466
|
+
if (lowerBranch.startsWith("feature/") || lowerBranch.startsWith("feat/")) {
|
|
467
|
+
branchType = "feature";
|
|
468
|
+
}
|
|
469
|
+
else if (lowerBranch.startsWith("bugfix/") ||
|
|
470
|
+
lowerBranch.startsWith("bug/") ||
|
|
471
|
+
lowerBranch.startsWith("fix/")) {
|
|
472
|
+
branchType = "bugfix";
|
|
473
|
+
}
|
|
474
|
+
else if (lowerBranch.startsWith("hotfix/")) {
|
|
475
|
+
branchType = "hotfix";
|
|
476
|
+
}
|
|
477
|
+
else if (lowerBranch === "develop" || lowerBranch === "development") {
|
|
478
|
+
branchType = "develop";
|
|
479
|
+
}
|
|
480
|
+
else if (lowerBranch === "main") {
|
|
481
|
+
branchType = "main";
|
|
482
|
+
}
|
|
483
|
+
else if (lowerBranch === "master") {
|
|
484
|
+
branchType = "master";
|
|
485
|
+
}
|
|
486
|
+
return {
|
|
487
|
+
hasUncommittedChanges: hasUncommitted,
|
|
488
|
+
uncommittedChangesCount: uncommittedCount,
|
|
489
|
+
hasUnpushedCommits: hasUnpushed,
|
|
490
|
+
unpushedCommitsCount: unpushedCount,
|
|
491
|
+
latestCommitMessage: latestCommit,
|
|
492
|
+
branchType,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
// Return safe defaults if any operation fails
|
|
497
|
+
return {
|
|
498
|
+
hasUncommittedChanges: false,
|
|
499
|
+
uncommittedChangesCount: 0,
|
|
500
|
+
hasUnpushedCommits: false,
|
|
501
|
+
unpushedCommitsCount: 0,
|
|
502
|
+
latestCommitMessage: null,
|
|
503
|
+
branchType: "other",
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
export async function fetchAllRemotes(options) {
|
|
508
|
+
try {
|
|
509
|
+
const execOptions = options?.cwd ? { cwd: options.cwd } : undefined;
|
|
510
|
+
const args = ["fetch", "--all", "--prune"];
|
|
511
|
+
if (execOptions) {
|
|
512
|
+
await execa("git", args, execOptions);
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
await execa("git", args);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
catch (error) {
|
|
519
|
+
throw new GitError("Failed to fetch remote branches", error);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
export async function getCurrentVersion(repoRoot) {
|
|
523
|
+
try {
|
|
524
|
+
const packageJsonPath = path.join(repoRoot, "package.json");
|
|
525
|
+
const fs = await import("node:fs");
|
|
526
|
+
const packageJson = JSON.parse(await fs.promises.readFile(packageJsonPath, "utf-8"));
|
|
527
|
+
return packageJson.version || "0.0.0";
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
// package.jsonが存在しない場合はデフォルトバージョンを返す
|
|
531
|
+
return "0.0.0";
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
export function calculateNewVersion(currentVersion, versionBump) {
|
|
535
|
+
const versionParts = currentVersion.split(".");
|
|
536
|
+
const major = parseInt(versionParts[0] || "0", 10);
|
|
537
|
+
const minor = parseInt(versionParts[1] || "0", 10);
|
|
538
|
+
const patch = parseInt(versionParts[2] || "0", 10);
|
|
539
|
+
switch (versionBump) {
|
|
540
|
+
case "major":
|
|
541
|
+
return `${major + 1}.0.0`;
|
|
542
|
+
case "minor":
|
|
543
|
+
return `${major}.${minor + 1}.0`;
|
|
544
|
+
case "patch":
|
|
545
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
export async function executeNpmVersionInWorktree(worktreePath, newVersion) {
|
|
549
|
+
try {
|
|
550
|
+
// まずpackage.jsonが存在するか確認
|
|
551
|
+
const fs = await import("node:fs");
|
|
552
|
+
const packageJsonPath = path.join(worktreePath, "package.json");
|
|
553
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
554
|
+
// package.jsonが存在しない場合は作成
|
|
555
|
+
const packageJson = {
|
|
556
|
+
name: path.basename(worktreePath),
|
|
557
|
+
version: newVersion,
|
|
558
|
+
description: "",
|
|
559
|
+
main: "index.js",
|
|
560
|
+
scripts: {},
|
|
561
|
+
keywords: [],
|
|
562
|
+
author: "",
|
|
563
|
+
license: "ISC",
|
|
564
|
+
};
|
|
565
|
+
await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
|
|
566
|
+
// 新規作成したpackage.jsonをコミット
|
|
567
|
+
await execa("git", ["add", "package.json"], { cwd: worktreePath });
|
|
568
|
+
await execa("git", [
|
|
569
|
+
"commit",
|
|
570
|
+
"-m",
|
|
571
|
+
`chore: create package.json with version ${newVersion}`,
|
|
572
|
+
], { cwd: worktreePath });
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
// package.json の version を直接書き換え(外部PMに依存しない)
|
|
576
|
+
const content = await fs.promises.readFile(packageJsonPath, "utf-8");
|
|
577
|
+
const json = JSON.parse(content);
|
|
578
|
+
json.version = newVersion;
|
|
579
|
+
await fs.promises.writeFile(packageJsonPath, JSON.stringify(json, null, 2) + "\n");
|
|
580
|
+
// 変更をコミット
|
|
581
|
+
await execa("git", ["add", "package.json"], { cwd: worktreePath });
|
|
582
|
+
await execa("git", ["commit", "-m", `chore: bump version to ${newVersion}`], { cwd: worktreePath });
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
// エラーの詳細情報を含める
|
|
587
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
588
|
+
const errorDetails = error?.stderr ? ` (stderr: ${error.stderr})` : "";
|
|
589
|
+
const errorStdout = error?.stdout ? ` (stdout: ${error.stdout})` : "";
|
|
590
|
+
throw new GitError(`Failed to update version to ${newVersion} in worktree: ${errorMessage}${errorDetails}${errorStdout}`, error);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
export async function deleteRemoteBranch(branchName, remote = "origin") {
|
|
594
|
+
try {
|
|
595
|
+
await execa("git", ["push", remote, "--delete", branchName]);
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
throw new GitError(`Failed to delete remote branch ${remote}/${branchName}`, error);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
export async function getCurrentBranchName(worktreePath) {
|
|
602
|
+
try {
|
|
603
|
+
const { stdout } = await execa("git", ["branch", "--show-current"], {
|
|
604
|
+
cwd: worktreePath,
|
|
605
|
+
});
|
|
606
|
+
return stdout.trim();
|
|
607
|
+
}
|
|
608
|
+
catch (error) {
|
|
609
|
+
throw new GitError("Failed to get current branch name", error);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
export async function pushBranchToRemote(worktreePath, branchName, remote = "origin") {
|
|
613
|
+
try {
|
|
614
|
+
// Check if the remote branch exists
|
|
615
|
+
const remoteBranchExists = await checkRemoteBranchExists(branchName, remote, { cwd: worktreePath });
|
|
616
|
+
if (remoteBranchExists) {
|
|
617
|
+
// Push to existing remote branch
|
|
618
|
+
await execa("git", ["push", remote, branchName], { cwd: worktreePath });
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
// Push and set upstream for new remote branch
|
|
622
|
+
await execa("git", ["push", "--set-upstream", remote, branchName], {
|
|
623
|
+
cwd: worktreePath,
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
catch (error) {
|
|
628
|
+
throw new GitError(`Failed to push branch ${branchName} to ${remote}`, error);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
export async function checkRemoteBranchExists(branchName, remote = "origin", options) {
|
|
632
|
+
try {
|
|
633
|
+
const execOptions = options?.cwd ? { cwd: options.cwd } : undefined;
|
|
634
|
+
const args = [
|
|
635
|
+
"show-ref",
|
|
636
|
+
"--verify",
|
|
637
|
+
"--quiet",
|
|
638
|
+
`refs/remotes/${remote}/${branchName}`,
|
|
639
|
+
];
|
|
640
|
+
if (execOptions) {
|
|
641
|
+
await execa("git", args, execOptions);
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
await execa("git", args);
|
|
645
|
+
}
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
catch {
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* 現在のディレクトリがworktreeディレクトリかどうかを確認
|
|
654
|
+
* @returns {Promise<boolean>} worktreeディレクトリの場合true
|
|
655
|
+
*/
|
|
656
|
+
export async function isInWorktree() {
|
|
657
|
+
try {
|
|
658
|
+
// git rev-parse --show-toplevelとgit rev-parse --git-common-dirの結果を比較
|
|
659
|
+
const [toplevelResult, gitCommonDirResult] = await Promise.all([
|
|
660
|
+
execa("git", ["rev-parse", "--show-toplevel"]),
|
|
661
|
+
execa("git", ["rev-parse", "--git-common-dir"]),
|
|
662
|
+
]);
|
|
663
|
+
const toplevel = toplevelResult.stdout.trim();
|
|
664
|
+
const gitCommonDir = gitCommonDirResult.stdout.trim();
|
|
665
|
+
// gitCommonDirが絶対パスで、toplevelと異なる親ディレクトリを持つ場合はworktree
|
|
666
|
+
const path = await import("node:path");
|
|
667
|
+
if (path.isAbsolute(gitCommonDir)) {
|
|
668
|
+
const mainRepoRoot = path.dirname(gitCommonDir);
|
|
669
|
+
return toplevel !== mainRepoRoot;
|
|
670
|
+
}
|
|
671
|
+
// gitCommonDirが相対パス(.git)の場合はメインリポジトリ
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
catch {
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* .gitignoreファイルに指定されたエントリーが存在することを保証します
|
|
680
|
+
* エントリーが既に存在する場合は何もしません
|
|
681
|
+
* @param {string} repoRoot - リポジトリのルートディレクトリ
|
|
682
|
+
* @param {string} entry - 追加するエントリー(例: ".worktrees/")
|
|
683
|
+
* @throws {GitError} ファイルの読み書きに失敗した場合
|
|
684
|
+
*/
|
|
685
|
+
// ========================================
|
|
686
|
+
// Batch Merge Operations (SPEC-ee33ca26)
|
|
687
|
+
// ========================================
|
|
688
|
+
/**
|
|
689
|
+
* Merge from source branch to current branch in worktree
|
|
690
|
+
* @param worktreePath - Path to worktree directory
|
|
691
|
+
* @param sourceBranch - Source branch to merge from
|
|
692
|
+
* @param dryRun - If true, use --no-commit flag for dry-run mode
|
|
693
|
+
* @see specs/SPEC-ee33ca26/research.md - Decision 3: Dry-run implementation
|
|
694
|
+
*/
|
|
695
|
+
export async function mergeFromBranch(worktreePath, sourceBranch, dryRun = false) {
|
|
696
|
+
try {
|
|
697
|
+
const args = ["merge"];
|
|
698
|
+
if (dryRun) {
|
|
699
|
+
args.push("--no-commit");
|
|
700
|
+
}
|
|
701
|
+
args.push(sourceBranch);
|
|
702
|
+
await execa("git", args, { cwd: worktreePath });
|
|
703
|
+
}
|
|
704
|
+
catch (error) {
|
|
705
|
+
throw new GitError(`Failed to merge from ${sourceBranch} in ${worktreePath}`, error);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Check if there is a merge in progress in worktree
|
|
710
|
+
* @param worktreePath - Path to worktree directory
|
|
711
|
+
* @returns true if MERGE_HEAD exists (merge in progress)
|
|
712
|
+
* @see specs/SPEC-ee33ca26/research.md - Best practices: Git state confirmation
|
|
713
|
+
*/
|
|
714
|
+
export async function hasMergeConflict(worktreePath) {
|
|
715
|
+
try {
|
|
716
|
+
await execa("git", ["rev-parse", "--git-path", "MERGE_HEAD"], {
|
|
717
|
+
cwd: worktreePath,
|
|
718
|
+
});
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
catch {
|
|
722
|
+
return false;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Abort current merge operation in worktree
|
|
727
|
+
* @param worktreePath - Path to worktree directory
|
|
728
|
+
* @see specs/SPEC-ee33ca26/research.md - Decision 3: Dry-run rollback
|
|
729
|
+
*/
|
|
730
|
+
export async function abortMerge(worktreePath) {
|
|
731
|
+
try {
|
|
732
|
+
await execa("git", ["merge", "--abort"], { cwd: worktreePath });
|
|
733
|
+
}
|
|
734
|
+
catch (error) {
|
|
735
|
+
throw new GitError(`Failed to abort merge in ${worktreePath}`, error);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Get current merge status in worktree
|
|
740
|
+
* @param worktreePath - Path to worktree directory
|
|
741
|
+
* @returns Object with inProgress and hasConflict flags
|
|
742
|
+
* @see specs/SPEC-ee33ca26/research.md - Best practices: Git state confirmation
|
|
743
|
+
*/
|
|
744
|
+
export async function getMergeStatus(worktreePath) {
|
|
745
|
+
// Check if merge is in progress (MERGE_HEAD exists)
|
|
746
|
+
const inProgress = await hasMergeConflict(worktreePath);
|
|
747
|
+
// Check if there are conflicts (git status --porcelain shows UU)
|
|
748
|
+
let hasConflict = false;
|
|
749
|
+
if (inProgress) {
|
|
750
|
+
try {
|
|
751
|
+
const { stdout } = await execa("git", ["status", "--porcelain"], {
|
|
752
|
+
cwd: worktreePath,
|
|
753
|
+
});
|
|
754
|
+
// UU indicates unmerged paths (conflicts)
|
|
755
|
+
hasConflict = stdout.includes("UU ");
|
|
756
|
+
}
|
|
757
|
+
catch {
|
|
758
|
+
hasConflict = false;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return {
|
|
762
|
+
inProgress,
|
|
763
|
+
hasConflict,
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Reset worktree to HEAD (rollback all changes)
|
|
768
|
+
* Used for dry-run cleanup after git merge --no-commit
|
|
769
|
+
* @param worktreePath - Path to worktree directory
|
|
770
|
+
* @see specs/SPEC-ee33ca26/research.md - Dry-run implementation: --no-commit + rollback
|
|
771
|
+
*/
|
|
772
|
+
export async function resetToHead(worktreePath) {
|
|
773
|
+
try {
|
|
774
|
+
await execa("git", ["reset", "--hard", "HEAD"], {
|
|
775
|
+
cwd: worktreePath,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
catch (error) {
|
|
779
|
+
throw new GitError(`Failed to reset worktree to HEAD in ${worktreePath}`, error);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
export async function getBranchDivergenceStatuses(options) {
|
|
783
|
+
const cwd = options?.cwd;
|
|
784
|
+
const remote = options?.remote ?? "origin";
|
|
785
|
+
const execOptions = cwd ? { cwd } : undefined;
|
|
786
|
+
const branchFilter = options?.branches?.filter((name) => name.trim().length > 0);
|
|
787
|
+
const filterSet = branchFilter && branchFilter.length > 0 ? new Set(branchFilter) : null;
|
|
788
|
+
const branchArgs = ["branch", "--format=%(refname:short)"];
|
|
789
|
+
const { stdout: localBranchOutput } = execOptions
|
|
790
|
+
? await execa("git", branchArgs, execOptions)
|
|
791
|
+
: await execa("git", branchArgs);
|
|
792
|
+
const branchNames = localBranchOutput
|
|
793
|
+
.split("\n")
|
|
794
|
+
.map((name) => name.trim())
|
|
795
|
+
.filter(Boolean)
|
|
796
|
+
.filter((name) => !filterSet || filterSet.has(name));
|
|
797
|
+
if (filterSet && branchNames.length === 0) {
|
|
798
|
+
return [];
|
|
799
|
+
}
|
|
800
|
+
const results = [];
|
|
801
|
+
for (const branchName of branchNames) {
|
|
802
|
+
const remoteExists = await checkRemoteBranchExists(branchName, remote, cwd ? { cwd } : undefined);
|
|
803
|
+
if (!remoteExists) {
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
try {
|
|
807
|
+
const revListArgs = [
|
|
808
|
+
"rev-list",
|
|
809
|
+
"--left-right",
|
|
810
|
+
"--count",
|
|
811
|
+
`${remote}/${branchName}...${branchName}`,
|
|
812
|
+
];
|
|
813
|
+
const { stdout } = execOptions
|
|
814
|
+
? await execa("git", revListArgs, execOptions)
|
|
815
|
+
: await execa("git", revListArgs);
|
|
816
|
+
const [remoteAheadRaw, localAheadRaw] = stdout.trim().split(/\s+/);
|
|
817
|
+
const remoteAhead = Number.parseInt(remoteAheadRaw || "0", 10) || 0;
|
|
818
|
+
const localAhead = Number.parseInt(localAheadRaw || "0", 10) || 0;
|
|
819
|
+
results.push({ branch: branchName, remoteAhead, localAhead });
|
|
820
|
+
}
|
|
821
|
+
catch (error) {
|
|
822
|
+
throw new GitError(`Failed to inspect divergence for ${branchName}`, error);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return results;
|
|
826
|
+
}
|
|
827
|
+
export async function pullFastForward(worktreePath, remote = "origin") {
|
|
828
|
+
try {
|
|
829
|
+
await execa("git", ["pull", "--ff-only", remote], {
|
|
830
|
+
cwd: worktreePath,
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
catch (error) {
|
|
834
|
+
throw new GitError(`Failed to fast-forward pull in ${worktreePath}`, error);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
export async function ensureGitignoreEntry(repoRoot, entry) {
|
|
838
|
+
const fs = await import("node:fs/promises");
|
|
839
|
+
const gitignorePath = path.join(repoRoot, ".gitignore");
|
|
840
|
+
try {
|
|
841
|
+
// .gitignoreファイルを読み込む(存在しない場合は空文字列)
|
|
842
|
+
let content = "";
|
|
843
|
+
let eol = "\n";
|
|
844
|
+
try {
|
|
845
|
+
content = await fs.readFile(gitignorePath, "utf-8");
|
|
846
|
+
if (content.includes("\r\n")) {
|
|
847
|
+
eol = "\r\n";
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
catch (error) {
|
|
851
|
+
// ENOENTエラー(ファイルが存在しない)は無視
|
|
852
|
+
if (error.code !== "ENOENT") {
|
|
853
|
+
throw error;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
const normalizedEntry = entry.trim();
|
|
857
|
+
const normalizedLines = content.split(/\r?\n/).map((line) => line.trim());
|
|
858
|
+
if (normalizedLines.includes(normalizedEntry)) {
|
|
859
|
+
// 既に存在する場合は何もしない
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const needsSeparator = content.length > 0 && !content.endsWith("\n") && !content.endsWith("\r");
|
|
863
|
+
const separator = needsSeparator ? eol : "";
|
|
864
|
+
const newContent = `${content}${separator}${entry}${eol}`;
|
|
865
|
+
await fs.writeFile(gitignorePath, newContent, "utf-8");
|
|
866
|
+
}
|
|
867
|
+
catch (error) {
|
|
868
|
+
throw new GitError(`Failed to update .gitignore: ${error.message}`, error);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
//# sourceMappingURL=git.js.map
|