Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed .beads/dolt-monitor.pid.lock
Empty file.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,12 +280,15 @@ dub bottom

### `dub info` and `dub branch info`

Show tracked metadata for a branch.
Show tracked metadata for a branch, optionally including the parent-relative diff.

```bash
# current branch
dub info

# current branch with parent-relative diff
dub info --diff

# explicit branch
dub info feat/auth-login

Expand Down
3 changes: 3 additions & 0 deletions apps/docs/content/docs/commands/log.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ For detailed metadata about a specific branch:
# Current branch
dub info

# Current branch with parent-relative diff
dub info --diff

# Explicit branch
dub info feat/auth-login
```
46 changes: 45 additions & 1 deletion packages/cli/src/commands/branch.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as fs from 'node:fs';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { createTestRepo, gitInRepo } from '../../test/helpers';
import { branchInfo, formatBranchInfo } from './branch';
import { branchInfo, branchInfoOutput, formatBranchInfo } from './branch';
import { create } from './create';
import { init } from './init';

Expand Down Expand Up @@ -100,4 +101,47 @@ describe('branch info', () => {
expect(untracked).toContain('Tracked: no');
expect(untracked).toContain('not tracked by DubStack');
});

it('includes a parent-relative diff when requested', async () => {
await create('feat/a', dir);
await create('feat/b', dir);
await gitInRepo(dir, ['checkout', 'feat/b']);

await fs.promises.writeFile(`${dir}/feature.txt`, 'hello from feat/b\n');
await gitInRepo(dir, ['add', 'feature.txt']);
await gitInRepo(dir, ['commit', '-m', 'feat: add branch diff fixture']);

const output = await branchInfoOutput(dir, undefined, { diff: true });
expect(output).toContain('Branch: feat/b');
expect(output).toContain('Diff vs feat/a:');
expect(output).toContain('diff --git a/feature.txt b/feature.txt');
});

it('shows an explicit marker when there are no changes relative to the parent', async () => {
await create('feat/a', dir);
await create('feat/b', dir);

const output = await branchInfoOutput(dir, undefined, { diff: true });
expect(output).toContain('Diff vs feat/a:');
expect(output).toContain('(no changes)');
});

it('explains that root branches do not have a parent-relative diff', async () => {
await create('feat/a', dir);
await gitInRepo(dir, ['checkout', 'main']);

const output = await branchInfoOutput(dir, 'main', { diff: true });
expect(output).toContain('Branch: main');
expect(output).toContain('Diff: unavailable for stack root branches.');
});

it('explains that untracked branches cannot show a dubstack diff', async () => {
await gitInRepo(dir, ['checkout', '-b', 'rogue']);

const output = await branchInfoOutput(dir, undefined, { diff: true });
expect(output).toContain('Branch: rogue');
expect(output).toContain(
'Diff: unavailable because this branch is not tracked by DubStack.',
);
});
});
44 changes: 43 additions & 1 deletion packages/cli/src/commands/branch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getCurrentBranch } from '../lib/git';
import { getCurrentBranch, getDiffBetween } from '../lib/git';
import { findStackForBranch, readState, type Stack } from '../lib/state';

export interface BranchInfoResult {
Expand Down Expand Up @@ -80,3 +80,45 @@ export function formatBranchInfo(info: BranchInfoResult): string {
`Children: ${childrenLabel}`,
].join('\n');
}

export interface BranchInfoOutputOptions {
diff?: boolean;
}

/**
* Formats branch info and optionally appends a parent-relative git diff.
*/
export async function branchInfoOutput(
cwd: string,
branchName?: string,
options: BranchInfoOutputOptions = {},
): Promise<string> {
const info = await branchInfo(cwd, branchName);
const summary = formatBranchInfo(info);

if (!options.diff) {
return summary;
}

if (!info.tracked) {
return [
summary,
'',
'Diff: unavailable because this branch is not tracked by DubStack.',
].join('\n');
}

if (!info.parent) {
return [summary, '', 'Diff: unavailable for stack root branches.'].join(
'\n',
);
}

const diff = await getDiffBetween(info.parent, info.currentBranch, cwd);
return [
summary,
'',
`Diff vs ${info.parent}:`,
diff.trim().length > 0 ? diff.trimEnd() : '(no changes)',
].join('\n');
}
23 changes: 14 additions & 9 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { createRequire } from 'node:module';
import chalk from 'chalk';
import { Command } from 'commander';
import { abortCommand } from './commands/abort';
import { branchInfo, formatBranchInfo } from './commands/branch';
import { branchInfoOutput } from './commands/branch';
import {
checkout,
interactiveCheckout,
Expand Down Expand Up @@ -76,6 +76,15 @@ const { version } = require('../package.json') as { version: string };

const program = new Command();

async function showInfo(
branch: string | undefined,
options: { diff?: boolean },
): Promise<void> {
console.log(
await branchInfoOutput(process.cwd(), branch, { diff: options.diff }),
);
}

program
.name('dub')
.description('Manage stacked diffs (dependent git branches) with ease')
Expand Down Expand Up @@ -329,20 +338,16 @@ program
new Command('info')
.description('Show tracked stack info for the current branch')
.argument('[branch]', 'Branch to inspect (defaults to current branch)')
.action(async (branch?: string) => {
const info = await branchInfo(process.cwd(), branch);
console.log(formatBranchInfo(info));
}),
.option('-d, --diff', 'Show the parent-relative git diff for the branch')
.action(showInfo),
);

program
.command('info')
.argument('[branch]', 'Branch to inspect (defaults to current branch)')
.option('-d, --diff', 'Show the parent-relative git diff for the branch')
.description('Show tracked stack info for a branch')
.action(async (branch?: string) => {
const info = await branchInfo(process.cwd(), branch);
console.log(formatBranchInfo(info));
});
.action(showInfo);

program
.command('track')
Expand Down