Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Include detached state in git.status #695

Merged
merged 1 commit into from Dec 1, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 18 additions & 40 deletions src/lib/responses/StatusSummary.ts
@@ -1,48 +1,24 @@
import { FileStatusResult, StatusResult, StatusResultRenamed } from '../../../typings';
import { StatusResult } from '../../../typings';
import { append } from '../utils';
import { FileStatusSummary } from './FileStatusSummary';

/**
* The StatusSummary is returned as a response to getting `git().status()`
*/
type StatusLineParser = (result: StatusResult, file: string) => void;

export class StatusSummary implements StatusResult {
public not_added: string[] = [];
public conflicted: string[] = [];
public created: string[] = [];
public deleted: string[] = [];
public modified: string[] = [];
public renamed: StatusResultRenamed[] = [];

/**
* All files represented as an array of objects containing the `path` and status in `index` and
* in the `working_dir`.
*/
public files: FileStatusResult[] = [];
public staged: string[] = [];

/**
* Number of commits ahead of the tracked branch
*/
public not_added = [];
public conflicted = [];
public created = [];
public deleted = [];
public modified = [];
public renamed = [];
public files = [];
public staged = [];
public ahead = 0;

/**
*Number of commits behind the tracked branch
*/
public behind = 0;
public current = null;
public tracking = null;
public detached = false;

/**
* Name of the current branch
*/
public current: string | null = null;

/**
* Name of the branch being tracked
*/
public tracking: string | null = null;

/**
* Gets whether this StatusSummary represents a clean working branch.
*/
public isClean(): boolean {
return !this.files.length;
}
Expand Down Expand Up @@ -75,15 +51,15 @@ function renamedFile(line: string) {
};
}

function parser(indexX: PorcelainFileStatus, indexY: PorcelainFileStatus, handler: (result: StatusSummary, file: string) => void): [string, (result: StatusSummary, file: string) => unknown] {
function parser(indexX: PorcelainFileStatus, indexY: PorcelainFileStatus, handler: StatusLineParser): [string, StatusLineParser] {
return [`${indexX}${indexY}`, handler];
}

function conflicts(indexX: PorcelainFileStatus, ...indexY: PorcelainFileStatus[]) {
return indexY.map(y => parser(indexX, y, (result, file) => append(result.conflicted, file)));
}

const parsers: Map<string, (result: StatusSummary, file: string) => unknown> = new Map([
const parsers: Map<string, StatusLineParser> = new Map([
parser(PorcelainFileStatus.NONE, PorcelainFileStatus.ADDED, (result, file) => append(result.created, file)),
parser(PorcelainFileStatus.NONE, PorcelainFileStatus.DELETED, (result, file) => append(result.deleted, file)),
parser(PorcelainFileStatus.NONE, PorcelainFileStatus.MODIFIED, (result, file) => append(result.modified, file)),
Expand Down Expand Up @@ -134,6 +110,8 @@ const parsers: Map<string, (result: StatusSummary, file: string) => unknown> = n

regexResult = onEmptyBranchReg.exec(line);
result.current = regexResult && regexResult[1] || result.current;

result.detached = /\(no branch\)/.test(line);
}]
]);

Expand Down
25 changes: 24 additions & 1 deletion test/integration/status.spec.ts
@@ -1,4 +1,11 @@
import { createTestContext, newSimpleGit, setUpFilesAdded, setUpInit, SimpleGitTestContext } from '../__fixtures__';
import {
createTestContext,
like,
newSimpleGit,
setUpFilesAdded,
setUpInit,
SimpleGitTestContext
} from '../__fixtures__';

describe('status', () => {
let context: SimpleGitTestContext;
Expand Down Expand Up @@ -39,4 +46,20 @@ describe('status', () => {
expect(status.not_added).toEqual(['dirty-dir/dirty']);
});

it('detached head', async () => {
const git = newSimpleGit(context.root);
expect(await git.status()).toEqual(like({
detached: false,
current: expect.any(String),
}));

await git.raw('tag', 'v1');
await git.raw('checkout', 'v1');

expect(await git.status()).toEqual(like({
current: 'HEAD',
detached: true,
}))
})

});
34 changes: 18 additions & 16 deletions test/unit/status.spec.ts
Expand Up @@ -191,7 +191,7 @@ describe('status', () => {
## No commits yet on master
`);

expect(statusSummary).toEqual(expect.objectContaining({
expect(statusSummary).toEqual(like({
current: `master`
}))
});
Expand All @@ -204,21 +204,21 @@ A src/b.txt
R src/a.txt -> src/c.txt
`);

expect(statusSummary).toEqual(expect.objectContaining({
expect(statusSummary).toEqual(like({
created: ['src/b.txt'],
modified: ['other.txt'],
renamed: [{from: 'src/a.txt', to: 'src/c.txt'}]
}));
});

it('Handles renamed', () => {
expect(parseStatusSummary(' R src/file.js -> src/another-file.js')).toEqual(expect.objectContaining({
expect(parseStatusSummary(' R src/file.js -> src/another-file.js')).toEqual(like({
renamed: [{from: 'src/file.js', to: 'src/another-file.js'}],
}));
});

it('parses status - current, tracking and ahead', () => {
expect(parseStatusSummary('## master...origin/master [ahead 3]')).toEqual(expect.objectContaining({
expect(parseStatusSummary('## master...origin/master [ahead 3]')).toEqual(like({
current: 'master',
tracking: 'origin/master',
ahead: 3,
Expand All @@ -227,7 +227,8 @@ R src/a.txt -> src/c.txt
});

it('parses status - current, tracking and behind', () => {
expect(parseStatusSummary('## master...origin/master [behind 2]')).toEqual(expect.objectContaining({
expect(parseStatusSummary('## master...origin/master [behind 2]')).toEqual(like({
detached: false,
current: 'master',
tracking: 'origin/master',
ahead: 0,
Expand All @@ -236,7 +237,7 @@ R src/a.txt -> src/c.txt
});

it('parses status - current, tracking', () => {
expect(parseStatusSummary('## release/0.34.0...origin/release/0.34.0')).toEqual(expect.objectContaining({
expect(parseStatusSummary('## release/0.34.0...origin/release/0.34.0')).toEqual(like({
current: 'release/0.34.0',
tracking: 'origin/release/0.34.0',
ahead: 0,
Expand All @@ -245,7 +246,8 @@ R src/a.txt -> src/c.txt
});

it('parses status - HEAD no branch', () => {
expect(parseStatusSummary('## HEAD (no branch)')).toEqual(expect.objectContaining({
expect(parseStatusSummary('## HEAD (no branch)')).toEqual(like({
detached: true,
current: 'HEAD',
tracking: null,
ahead: 0,
Expand All @@ -254,22 +256,22 @@ R src/a.txt -> src/c.txt
});

it('parses status - with untracked', () => {
expect(parseStatusSummary('?? Not tracked File\nUU Conflicted\n D Removed')).toEqual(expect.objectContaining({
expect(parseStatusSummary('?? Not tracked File\nUU Conflicted\n D Removed')).toEqual(like({
not_added: ['Not tracked File'],
conflicted: ['Conflicted'],
deleted: ['Removed'],
}));
});

it('parses status - modified, added and added-changed', () => {
expect(parseStatusSummary(' M Modified\n A Added\nAM Changed')).toEqual(expect.objectContaining({
expect(parseStatusSummary(' M Modified\n A Added\nAM Changed')).toEqual(like({
modified: ['Modified', 'Changed'],
created: ['Added', 'Changed'],
}));
});

it('parses status', () => {
expect(parseStatusSummary(statusResponse('this_branch').stdOut)).toEqual(expect.objectContaining({
expect(parseStatusSummary(statusResponse('this_branch').stdOut)).toEqual(like({
current: 'this_branch',
tracking: null,
}));
Expand All @@ -283,7 +285,7 @@ R src/a.txt -> src/c.txt
const statusSummary = parseStatusSummary('\n');

expect(statusSummary.isClean()).toBe(true);
expect(statusSummary).toEqual(expect.objectContaining({
expect(statusSummary).toEqual(like({
created: [],
deleted: [],
modified: [],
Expand All @@ -300,7 +302,7 @@ R src/a.txt -> src/c.txt
A ccc
?? ddd
`);
expect(statusSummary).toEqual(expect.objectContaining({
expect(statusSummary).toEqual(like({
staged: ['bbb', 'ccc'],
modified: ['aaa', 'bbb'],
}));
Expand All @@ -313,7 +315,7 @@ R src/a.txt -> src/c.txt
M modified
M staged
`);
expect(statusSummary).toEqual(expect.objectContaining({
expect(statusSummary).toEqual(like({
staged: ['staged-modified', 'staged'],
modified: ['staged-modified', 'modified', 'staged'],
}));
Expand Down Expand Up @@ -344,7 +346,7 @@ R src/a.txt -> src/c.txt
MM src/git_ind_wd.js
M src/git_ind.js
`);
expect(statusSummary).toEqual(expect.objectContaining({
expect(statusSummary).toEqual(like({
files: [
{path: 'src/git_wd.js', index: ' ', working_dir: 'M'},
{path: 'src/git_ind_wd.js', index: 'M', working_dir: 'M'},
Expand All @@ -354,7 +356,7 @@ M src/git_ind.js
});

it('Report conflict when both sides have added the same file', () => {
expect(parseStatusSummary(`## master\nAA filename`)).toEqual(expect.objectContaining({
expect(parseStatusSummary(`## master\nAA filename`)).toEqual(like({
conflicted: ['filename'],
}));
});
Expand All @@ -370,7 +372,7 @@ M src/git_ind.js
AA test-foo.js
`);

expect(statusSummary).toEqual(expect.objectContaining({
expect(statusSummary).toEqual(like({
conflicted: ['package.json', 'src/git.js', 'src/index.js', 'src/newfile.js', 'test.js', 'test', 'test-foo.js']
}));
});
Expand Down
27 changes: 27 additions & 0 deletions typings/response.d.ts
Expand Up @@ -294,12 +294,39 @@ export interface StatusResult {
modified: string[];
renamed: StatusResultRenamed[];
staged: string[];

/**
* All files represented as an array of objects containing the `path` and status in `index` and
* in the `working_dir`.
*/
files: FileStatusResult[];

/**
* Number of commits ahead of the tracked branch
*/
ahead: number;

/**
*Number of commits behind the tracked branch
*/
behind: number;

/**
* Name of the current branch
*/
current: string | null;

/**
* Name of the branch being tracked
*/
tracking: string | null;

/**
* Detached status of the working copy, for more detail of what the working branch
* is detached from use `git.branch()`
*/
detached: boolean;

/**
* Gets whether this represents a clean working branch.
*/
Expand Down