Skip to content

Commit f3c0d30

Browse files
committed
Merge branch 'release/0.2.3'
2 parents de3d1f6 + ca09e5d commit f3c0d30

File tree

9 files changed

+84
-20
lines changed

9 files changed

+84
-20
lines changed

.release-it.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
{
2-
"$schema": "https://unpkg.com/release-it@18/schema/release-it.json",
2+
"$schema": "https://unpkg.com/release-it/schema/release-it.json",
33
"git": {
44
"commitMessage": "chore(release): ${version}",
55
"tagName": "${version}",
66
"requireCleanWorkingDir": true
77
},
88
"npm": {
9-
"publish": true,
9+
"publish": false,
1010
"skipChecks": false
1111
},
1212
"github": {
1313
"release": true,
14+
"draft": true,
1415
"releaseName": "${version}"
1516
},
1617
"plugins": {
@@ -21,6 +22,7 @@
2122
},
2223
"hooks": {
2324
"before:init": ["npm run lint:all", "npm run test"],
24-
"after:bump": "npm run build"
25+
"after:bump": "npm run build",
26+
"after:release": "npm publish"
2527
}
2628
}

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [0.2.3](https://github.com/VChet/git-merged-branches/compare/0.2.2...0.2.3) (2025-05-30)
4+
5+
### Features
6+
7+
* **repo:** only suggest remote delete if branch exists ([1418a4a](https://github.com/VChet/git-merged-branches/commit/1418a4a2cd6f9c2a03ef13529563c7fc30aa5109))
8+
39
## [0.2.2](https://github.com/VChet/git-merged-branches/compare/0.2.1...0.2.2) (2025-05-29)
410

511
### Features

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
[![npm version](https://img.shields.io/npm/v/git-merged-branches)](https://www.npmjs.com/package/git-merged-branches)
44
[![build](https://github.com/VChet/git-merged-branches/actions/workflows/build.yml/badge.svg)](https://github.com/VChet/git-merged-branches/actions/workflows/build.yml)
5+
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/VChet/git-merged-branches)
56

67
**git-merged-branches is a command-line utility to view branches merged into a selected base branch (e.g., master or main).**
78

@@ -66,6 +67,10 @@ TOKEN-800_new-feature <https://your-jira-instance.net/browse/TOKEN-800>
6667

6768
If the configuration is invalid, warnings will be shown and the utility will skip formatting URLs.
6869

70+
## Documentation
71+
72+
- [DeepWiki](https://deepwiki.com/VChet/git-merged-branches)
73+
6974
## Development
7075

7176
To contribute or test locally:

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "git-merged-branches",
33
"description": "CLI tool to list all Git branches merged into a base branch with issue link formatting",
44
"type": "module",
5-
"version": "0.2.2",
5+
"version": "0.2.3",
66
"license": "MIT",
77
"author": {
88
"name": "VChet",
@@ -32,7 +32,9 @@
3232
"lint:js:fix": "npm run lint:js -- --fix",
3333
"lint:all": "npm run lint:ts && npm run lint:js",
3434
"test": "vitest --run",
35-
"release": "dotenv release-it"
35+
"release:patch": "dotenv release-it patch",
36+
"release:minor": "dotenv release-it minor",
37+
"release:major": "dotenv release-it major"
3638
},
3739
"devDependencies": {
3840
"@release-it/conventional-changelog": "^10.0.1",

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)