Skip to content

Commit 1418a4a

Browse files
committed
feat(repo): only suggest remote delete if branch exists
1 parent 4982b98 commit 1418a4a

File tree

5 files changed

+64
-15
lines changed

5 files changed

+64
-15
lines changed

src/helpers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function logError(prefix: string, error: unknown): void {
2+
if (error instanceof Error) {
3+
console.warn(`${prefix}: ${error.message}`);
4+
} else {
5+
console.warn(`${prefix}: ${String(error)}`);
6+
}
7+
}

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import process from "node:process";
22
import { getConfig, getDefaultTargetBranch, getMergedBranches, isDetachedHead, isGitRepo } from "./repo.js";
33
import { outputMergedBranches } from "./output.js";
4+
import { logError } from "./helpers.js";
45

56
function main(): void {
67
if (!isGitRepo()) {
@@ -21,7 +22,7 @@ function main(): void {
2122
const mergedBranches = getMergedBranches(targetBranch);
2223
outputMergedBranches(mergedBranches, targetBranch, getConfig());
2324
} catch (error) {
24-
console.error("Error executing 'git-merged-branches' command`:", error);
25+
logError("Error executing 'git-merged-branches' command", error);
2526
process.exit(1);
2627
}
2728
}

src/output.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { GitMergedConfig } from "./repo.js";
1+
import { fetchRemoteBranches, GitMergedConfig } from "./repo.js";
22
import { isValidURL } from "./validate.js";
33

44
function formatSingleBranch(
@@ -35,13 +35,17 @@ export function formatTaskBranches(branches: string[], { issueUrlFormat, issueUr
3535
}
3636

3737
export function outputMergedBranches(branches: string[], targetBranch: string, config: GitMergedConfig): void {
38-
if (branches.length) {
39-
console.info(`Branches merged into '${targetBranch}':`)
40-
console.info(formatTaskBranches(branches, config).join("\n"));
41-
42-
console.info("\nRun the following to delete branches locally and remotely:");
43-
console.info(`git branch --delete ${branches.join(" ")} && git push origin --delete ${branches.join(" ")}`);
44-
} else {
45-
console.info(`No branches merged into '${targetBranch}'.`);
38+
if (!branches.length) {
39+
return console.info(`No branches merged into '${targetBranch}'.`);
4640
}
41+
42+
console.info(`Branches merged into '${targetBranch}':`)
43+
console.info(formatTaskBranches(branches, config).join("\n"));
44+
45+
const remoteBranches = fetchRemoteBranches("origin");
46+
const remoteMerged = branches.filter(branch => remoteBranches.includes(branch));
47+
48+
console.info("\nRun the following to delete branches:");
49+
console.info(`locally:\n git branch --delete ${branches.join(" ")}`);
50+
if (remoteMerged.length) { console.info(`remotely:\n git push origin --delete ${remoteMerged.join(" ")}`); }
4751
}

src/repo.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { execSync } from "node:child_process";
22
import { readFileSync } from "node:fs";
33
import { join } from "node:path";
4+
import { logError } from "./helpers";
45

56
export function isGitRepo(): boolean {
67
try {
@@ -49,6 +50,20 @@ export function getMergedBranches(targetBranch: string): string[] {
4950
}, []);
5051
}
5152

53+
export function fetchRemoteBranches(remote = "origin"): string[] {
54+
try {
55+
const output = execSync(`git ls-remote --heads ${remote}`, { encoding: "utf-8" });
56+
return output
57+
.split("\n")
58+
.map(line => line.split("\t")[1])
59+
.filter(ref => ref?.startsWith("refs/heads/"))
60+
.map(ref => ref.replace("refs/heads/", ""));
61+
} catch (error) {
62+
logError(`Could not fetch remote branches from '${remote}'`, error);
63+
return [];
64+
}
65+
}
66+
5267
export interface GitMergedConfig {
5368
issueUrlFormat?: string;
5469
issueUrlPrefix?: string[];
@@ -59,7 +74,8 @@ export function getConfig(): GitMergedConfig {
5974
const pkgPath = join(process.cwd(), "package.json");
6075
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
6176
return pkg["git-merged-branches"] || {};
62-
} catch {
77+
} catch (error) {
78+
logError("Could not read package.json", error);
6379
return {};
6480
}
6581
}

src/tests/output.test.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, it, expect, vi, beforeEach } from "vitest";
22
import { formatTaskBranches, outputMergedBranches } from "../output";
33
import { GitMergedConfig } from "../repo";
4+
import * as repoMethods from "../repo";
45

56
const DEFAULT_CONFIG: GitMergedConfig = {
67
issueUrlFormat: "https://test-instance.org/browse/{{prefix}}{{id}}",
@@ -72,7 +73,7 @@ describe("outputMergedBranches", () => {
7273
warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
7374
});
7475

75-
it("should log the correct branches when there are merged branches", () => {
76+
it("should log the correct branches when there are local merged branches", () => {
7677
const branches = ["feat/TOKEN-800_new-feature", "fix/TOKEN-123_some-fix"];
7778

7879
outputMergedBranches(branches, "master", DEFAULT_CONFIG);
@@ -84,13 +85,33 @@ describe("outputMergedBranches", () => {
8485
expect(infoSpy).toHaveBeenNthCalledWith(2, branchOutput.join("\n"));
8586

8687
const localDelete = `git branch --delete ${branches.join(" ")}`;
87-
const remoteDelete = `git push origin --delete ${branches.join(" ")}`;
88-
expect(infoSpy).toHaveBeenNthCalledWith(3, "\nRun the following to delete branches locally and remotely:");
89-
expect(infoSpy).toHaveBeenNthCalledWith(4, `${localDelete} && ${remoteDelete}`);
88+
expect(infoSpy).toHaveBeenNthCalledWith(3, "\nRun the following to delete branches:");
89+
expect(infoSpy).toHaveBeenNthCalledWith(4, `locally:\n ${localDelete}`);
9090
expect(infoSpy).toHaveBeenCalledTimes(4);
9191
expect(warnSpy).not.toHaveBeenCalled();
9292
});
9393

94+
it("should log the correct branches when there are remote merged branches", () => {
95+
const branches = ["feat/TOKEN-800_new-feature", "fix/TOKEN-123_some-fix"];
96+
vi.spyOn(repoMethods, "fetchRemoteBranches").mockReturnValue(branches);
97+
98+
outputMergedBranches(branches, "master", DEFAULT_CONFIG);
99+
expect(infoSpy).toHaveBeenNthCalledWith(1, "Branches merged into 'master':");
100+
const branchOutput = [
101+
"feat/TOKEN-800_new-feature <https://test-instance.org/browse/TOKEN-800>",
102+
"fix/TOKEN-123_some-fix <https://test-instance.org/browse/TOKEN-123>"
103+
]
104+
expect(infoSpy).toHaveBeenNthCalledWith(2, branchOutput.join("\n"));
105+
106+
const localDelete = `git branch --delete ${branches.join(" ")}`;
107+
const remoteDelete = `git push origin --delete ${branches.join(" ")}`;
108+
expect(infoSpy).toHaveBeenNthCalledWith(3, "\nRun the following to delete branches:");
109+
expect(infoSpy).toHaveBeenNthCalledWith(4, `locally:\n ${localDelete}`);
110+
expect(infoSpy).toHaveBeenNthCalledWith(5, `remotely:\n ${remoteDelete}`);
111+
expect(infoSpy).toHaveBeenCalledTimes(5);
112+
expect(warnSpy).not.toHaveBeenCalled();
113+
});
114+
94115
it("should log a message when no branches are merged", () => {
95116
outputMergedBranches([], "master", DEFAULT_CONFIG);
96117
expect(infoSpy).toHaveBeenCalledWith("No branches merged into 'master'.");

0 commit comments

Comments
 (0)