diff --git a/src/commands/branch/new.test.tsx b/src/commands/branch/new.test.tsx
index a519c1f..aebb851 100644
--- a/src/commands/branch/new.test.tsx
+++ b/src/commands/branch/new.test.tsx
@@ -18,6 +18,11 @@ const mocks = vi.hoisted(() => {
setTimeout(resolve, ARBITRARY_DELAY / 4)
);
},
+ currentBranch: async () => {
+ return new Promise((resolve) =>
+ setTimeout(() => resolve('root'), ARBITRARY_DELAY / 4)
+ );
+ },
createBranch: async () => {
return new Promise((resolve) =>
setTimeout(resolve, ARBITRARY_DELAY / 4)
diff --git a/src/commands/branch/new.tsx b/src/commands/branch/new.tsx
index fccae74..c9adbc0 100644
--- a/src/commands/branch/new.tsx
+++ b/src/commands/branch/new.tsx
@@ -10,17 +10,22 @@ import {
import { Loading } from '../../components/loading.js';
import { SelectRootBranch } from '../../components/select-root-branch.js';
import { Text } from 'ink';
+import { UntrackedBranch } from '../../components/untracked-branch.js';
import { safeBranchNameFromCommitMessage } from '../../utils/naming.js';
import { useGit } from '../../hooks/use-git.js';
import { useTree } from '../../hooks/use-tree.js';
const BranchNew = (props: CommandProps) => {
- const { rootBranchName } = useTree();
+ const { rootBranchName, isCurrentBranchTracked } = useTree();
if (!rootBranchName) {
return ;
}
+ if (!isCurrentBranchTracked) {
+ return ;
+ }
+
return ;
};
diff --git a/src/commands/changes/commit.test.tsx b/src/commands/changes/commit.test.tsx
index 39a378d..9a44757 100644
--- a/src/commands/changes/commit.test.tsx
+++ b/src/commands/changes/commit.test.tsx
@@ -12,15 +12,30 @@ const mocks = vi.hoisted(() => {
return {
createGitService: vi.fn(({}) => {
return {
+ checkout: async () => {
+ return new Promise((resolve) =>
+ setTimeout(resolve, ARBITRARY_DELAY / 4)
+ );
+ },
+ currentBranch: async () => {
+ return new Promise((resolve) =>
+ setTimeout(() => resolve('root'), ARBITRARY_DELAY / 4)
+ );
+ },
+ createBranch: async () => {
+ return new Promise((resolve) =>
+ setTimeout(resolve, ARBITRARY_DELAY / 4)
+ );
+ },
addAllFiles: async () => {
return new Promise((resolve) =>
- setTimeout(resolve, ARBITRARY_DELAY / 2)
+ setTimeout(resolve, ARBITRARY_DELAY / 4)
);
},
commit: async ({ message }: { message: string }) => {
console.log(message);
return new Promise((resolve) =>
- setTimeout(resolve, ARBITRARY_DELAY / 2)
+ setTimeout(resolve, ARBITRARY_DELAY / 4)
);
},
};
@@ -101,7 +116,7 @@ describe('correctly renders changes commit UI', () => {
const expected = render();
- await delay(ARBITRARY_DELAY / 2);
+ await delay(ARBITRARY_DELAY / 4);
expect(actual1.lastFrame()).to.equal(expected.lastFrame());
expect(actual2.lastFrame()).to.equal(expected.lastFrame());
});
diff --git a/src/components/untracked-branch.tsx b/src/components/untracked-branch.tsx
new file mode 100644
index 0000000..5aa8441
--- /dev/null
+++ b/src/components/untracked-branch.tsx
@@ -0,0 +1,31 @@
+import React, { useCallback } from 'react';
+import { Box, Text, useInput } from 'ink';
+import { useAsyncValue } from '../hooks/use-async-value.js';
+import { useGit } from '../hooks/use-git.js';
+
+const TRACK_BRANCH_COMMAND = 'gum branch track';
+
+export const UntrackedBranch = () => {
+ const git = useGit();
+
+ const getCurrentBranch = useCallback(async () => {
+ return await git.currentBranch();
+ }, [git.currentBranch]);
+
+ const { value: currentBranch } = useAsyncValue({
+ getValue: getCurrentBranch,
+ });
+
+ return (
+
+
+ Cannot perform this operation on untracked branch{' '}
+ {currentBranch}.
+
+
+ You can start tracking it with{' '}
+ {TRACK_BRANCH_COMMAND}.
+
+
+ );
+};
diff --git a/src/hooks/use-async-value.ts b/src/hooks/use-async-value.ts
index 47dd589..5e7cefe 100644
--- a/src/hooks/use-async-value.ts
+++ b/src/hooks/use-async-value.ts
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
type State = { type: 'LOADING' } | { type: 'COMPLETE'; value: T };
type Result = { value?: T };
+
export const useAsyncValue = ({
getValue,
}: {
diff --git a/src/hooks/use-tree.ts b/src/hooks/use-tree.ts
index abcdc33..e3038e8 100644
--- a/src/hooks/use-tree.ts
+++ b/src/hooks/use-tree.ts
@@ -1,13 +1,27 @@
import { Tree, TreeService, createTreeService } from '../services/tree.js';
-import { useMemo, useState } from 'react';
+import { useAsyncValue } from './use-async-value.js';
+import { useCallback, useMemo, useState } from 'react';
+import { useGit } from './use-git.js';
interface UseTreeResult extends TreeService {
currentTree: Tree;
rootBranchName: string | undefined;
+ isCurrentBranchTracked: boolean;
+ isLoading: boolean;
}
export const useTree = (): UseTreeResult => {
const [currentTree, setCurrentTree] = useState([]);
+ const git = useGit();
+
+ const getCurrentBranchTracked = useCallback(async () => {
+ const currentBranch = await git.currentBranch();
+ return Boolean(currentTree.find((b) => b.key === currentBranch));
+ }, [currentTree, git.currentBranch]);
+
+ const currentBranchTrackedResult = useAsyncValue({
+ getValue: getCurrentBranchTracked,
+ });
const service = useMemo(() => createTreeService({ setCurrentTree }), []);
@@ -21,5 +35,7 @@ export const useTree = (): UseTreeResult => {
currentTree,
...computed,
...service,
+ isCurrentBranchTracked: Boolean(currentBranchTrackedResult.value),
+ isLoading: !('value' in currentBranchTrackedResult),
};
};
diff --git a/src/services/git.ts b/src/services/git.ts
index de38dd7..e8a6d8a 100644
--- a/src/services/git.ts
+++ b/src/services/git.ts
@@ -10,6 +10,7 @@ export const DEFAULT_OPTIONS: Partial = {
export interface GitService {
_git: SimpleGit;
branchLocal: () => Promise>;
+ currentBranch: () => Promise;
checkout: (branch: string) => Promise>;
addAllFiles: () => Promise;
commit: (args: { message: string }) => Promise;
@@ -25,13 +26,17 @@ export const createGitService = ({
return {
_git: gitEngine,
// @ts-expect-error - being weird about the return type
- checkout: async (branch: string) => {
- return gitEngine.checkout(branch);
- },
- // @ts-expect-error - being weird about the return type
branchLocal: async () => {
return gitEngine.branchLocal();
},
+ currentBranch: async () => {
+ const { current } = await gitEngine.branchLocal();
+ return current;
+ },
+ // @ts-expect-error - being weird about the return type
+ checkout: async (branch: string) => {
+ return gitEngine.checkout(branch);
+ },
addAllFiles: async () => {
await gitEngine.add('.');
},