@senad-d/branchme 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -1
- package/package.json +1 -1
- package/src/git.ts +17 -4
- package/src/tools/branchme-tools.ts +5 -4
- package/src/types.ts +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.3.
|
|
3
|
+
## 0.3.6 - Unreleased
|
|
4
|
+
|
|
5
|
+
- Resolved Sonar maintainability findings by using a set for integration-branch membership and non-mutating sorting for changed worktree paths.
|
|
6
|
+
|
|
7
|
+
- `create_branch`, `change_branch`, `pull_branch` and `push_branch` receipts carry the resulting full HEAD (`head`); `push_branch` also names the remote-tracking ref it updated (`remoteTrackingRef`) and its commit after the push (`remoteHead`), and the text result says whether it matches the local HEAD. A caller no longer needs `branch_status` after these calls to learn or prove the HEAD.
|
|
8
|
+
## 0.3.5 - Released
|
|
4
9
|
|
|
5
10
|
- `push_branch` never pushes onto a differently named integration branch: when the current branch's upstream is `main`, `master`, `trunk`, `develop`, or the origin default branch under another name (a feature branch created from `origin/main` with tracking), it publishes to the same-named branch on that remote with `--set-upstream`, reports `mode: "publish"`, and keeps the previous upstream in `upstream`. Previously such a branch was pushed straight onto the integration branch. Other differently named upstreams keep their explicit `HEAD:<upstream-ref>` push.
|
|
6
11
|
- Added optional `remove_worktree.discardChanges` (default `false`, unchanged behavior). When `true`, the user's explicit authorization, a worktree with staged, unstaged, untracked, or unmerged changes is removed with `git worktree remove --force` and the result lists every discarded path in `discardedPaths`; without it a dirty worktree is still refused, now naming the option. Locked, detached, main, current, prunable, missing, and foreign worktrees stay rejected, and the local branch is retained.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@senad-d/branchme",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pi extension for verified Git repository initialization, branch, worktree, integration, retirement, push, and pull request workflows.",
|
|
6
6
|
"license": "MIT",
|
package/src/git.ts
CHANGED
|
@@ -563,7 +563,7 @@ async function resolvePushTarget(
|
|
|
563
563
|
};
|
|
564
564
|
}
|
|
565
565
|
|
|
566
|
-
const INTEGRATION_BRANCH_NAMES = ["main", "master", "trunk", "develop"];
|
|
566
|
+
const INTEGRATION_BRANCH_NAMES = new Set(["main", "master", "trunk", "develop"]);
|
|
567
567
|
|
|
568
568
|
async function isIntegrationBranch(
|
|
569
569
|
pi: Pick<ExtensionAPI, "exec">,
|
|
@@ -573,7 +573,7 @@ async function isIntegrationBranch(
|
|
|
573
573
|
signal?: AbortSignal,
|
|
574
574
|
): Promise<boolean> {
|
|
575
575
|
const branch = remoteRef.slice("refs/heads/".length);
|
|
576
|
-
if (INTEGRATION_BRANCH_NAMES.
|
|
576
|
+
if (INTEGRATION_BRANCH_NAMES.has(branch)) return true;
|
|
577
577
|
return remote === "origin" && branch === await getOriginDefaultBranch(pi, ctx, signal);
|
|
578
578
|
}
|
|
579
579
|
|
|
@@ -1828,7 +1828,7 @@ function parseWorktreeRemovalStatus(output: string): WorktreeRemovalStatus {
|
|
|
1828
1828
|
return {
|
|
1829
1829
|
workingTree: parseWorkingTreeStatus(output).workingTree,
|
|
1830
1830
|
ignoredPaths: [...ignoredPaths].sort(compareIgnoredPaths),
|
|
1831
|
-
changedPaths: changedPaths.sort(compareIgnoredPaths),
|
|
1831
|
+
changedPaths: [...changedPaths].sort(compareIgnoredPaths),
|
|
1832
1832
|
};
|
|
1833
1833
|
}
|
|
1834
1834
|
|
|
@@ -2687,8 +2687,9 @@ export async function createLocalBranch(
|
|
|
2687
2687
|
signal,
|
|
2688
2688
|
timeout: GIT_MUTATION_TIMEOUT_MS,
|
|
2689
2689
|
});
|
|
2690
|
+
const head = await getVerifiedRefCommit(pi, rootCtx, "HEAD", "HEAD", signal);
|
|
2690
2691
|
|
|
2691
|
-
return { repoRoot, previousBranch: safeDetail(previousBranch), newBranch: safeDetail(branchName) };
|
|
2692
|
+
return { repoRoot, previousBranch: safeDetail(previousBranch), newBranch: safeDetail(branchName), head };
|
|
2692
2693
|
});
|
|
2693
2694
|
}
|
|
2694
2695
|
|
|
@@ -2726,6 +2727,7 @@ export async function changeExistingLocalBranch(
|
|
|
2726
2727
|
if (current.detached || current.currentBranch !== branchName) {
|
|
2727
2728
|
throw new Error(`git switch did not end on branch '${redactSecrets(branchName)}'.`);
|
|
2728
2729
|
}
|
|
2730
|
+
const head = await getVerifiedRefCommit(pi, rootCtx, "HEAD", "HEAD", signal);
|
|
2729
2731
|
|
|
2730
2732
|
return {
|
|
2731
2733
|
repoRoot,
|
|
@@ -2733,6 +2735,7 @@ export async function changeExistingLocalBranch(
|
|
|
2733
2735
|
previousDetached: previous.detached,
|
|
2734
2736
|
currentBranch: safeDetail(current.currentBranch),
|
|
2735
2737
|
hasChangesBeforeSwitch: false,
|
|
2738
|
+
head,
|
|
2736
2739
|
};
|
|
2737
2740
|
});
|
|
2738
2741
|
}
|
|
@@ -2882,6 +2885,7 @@ export async function pullCurrentBranch(
|
|
|
2882
2885
|
signal,
|
|
2883
2886
|
timeout: GIT_PULL_TIMEOUT_MS,
|
|
2884
2887
|
});
|
|
2888
|
+
const head = await getVerifiedRefCommit(pi, rootCtx, "HEAD", "HEAD", signal);
|
|
2885
2889
|
|
|
2886
2890
|
return {
|
|
2887
2891
|
repoRoot,
|
|
@@ -2890,6 +2894,7 @@ export async function pullCurrentBranch(
|
|
|
2890
2894
|
remote: safeDetail(target.remote),
|
|
2891
2895
|
remoteRef: safeDetail(target.remoteRef),
|
|
2892
2896
|
output: safeOutput(result.stdout || result.stderr),
|
|
2897
|
+
head,
|
|
2893
2898
|
};
|
|
2894
2899
|
});
|
|
2895
2900
|
}
|
|
@@ -2981,6 +2986,11 @@ export async function pushCurrentBranch(
|
|
|
2981
2986
|
signal,
|
|
2982
2987
|
timeout: GIT_PUSH_TIMEOUT_MS,
|
|
2983
2988
|
});
|
|
2989
|
+
// The push updated the remote-tracking ref of the pushed branch; reading it
|
|
2990
|
+
// back is the remote-visible HEAD the receipt proves, without another fetch.
|
|
2991
|
+
const remoteTrackingRef = `${target.remote}/${target.remoteRef.replace(/^refs\/heads\//u, "")}`;
|
|
2992
|
+
const head = await getVerifiedRefCommit(pi, rootCtx, "HEAD", "HEAD", signal);
|
|
2993
|
+
const remoteHead = await getRemoteTrackingRefCommit(pi, rootCtx, remoteTrackingRef, signal);
|
|
2984
2994
|
|
|
2985
2995
|
return {
|
|
2986
2996
|
repoRoot,
|
|
@@ -2991,6 +3001,9 @@ export async function pushCurrentBranch(
|
|
|
2991
3001
|
remoteRef: safeDetail(target.remoteRef),
|
|
2992
3002
|
refspec: safeDetail(target.refspec),
|
|
2993
3003
|
output: safeOutput(result.stdout || result.stderr),
|
|
3004
|
+
head,
|
|
3005
|
+
remoteTrackingRef: safeDetail(remoteTrackingRef),
|
|
3006
|
+
remoteHead,
|
|
2994
3007
|
};
|
|
2995
3008
|
});
|
|
2996
3009
|
}
|
|
@@ -245,7 +245,7 @@ export function formatBranchStatus(details: GitContextDetails): string {
|
|
|
245
245
|
|
|
246
246
|
export function formatChangeBranch(details: ChangeBranchDetails): string {
|
|
247
247
|
const previous = details.previousDetached ? "detached HEAD" : details.previousBranch ?? "unknown branch";
|
|
248
|
-
return `Changed branch from ${previous} to ${details.currentBranch}.`;
|
|
248
|
+
return `Changed branch from ${previous} to ${details.currentBranch} at ${details.head}.`;
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
export function formatPullRequest(details: PullRequestToolDetails, autofilledFields: PullRequestInputField[] = []): string {
|
|
@@ -641,7 +641,7 @@ export function registerBranchMeTools(pi: Pick<ExtensionAPI, "registerTool" | "e
|
|
|
641
641
|
content: [
|
|
642
642
|
{
|
|
643
643
|
type: "text",
|
|
644
|
-
text: `Created and checked out branch ${details.newBranch} from ${details.previousBranch}.`,
|
|
644
|
+
text: `Created and checked out branch ${details.newBranch} from ${details.previousBranch} at ${details.head}.`,
|
|
645
645
|
},
|
|
646
646
|
],
|
|
647
647
|
details,
|
|
@@ -735,7 +735,7 @@ export function registerBranchMeTools(pi: Pick<ExtensionAPI, "registerTool" | "e
|
|
|
735
735
|
async execute(_toolCallId, _params, signal, _onUpdate, ctx) {
|
|
736
736
|
const details = await pullCurrentBranch(pi, ctx, signal);
|
|
737
737
|
return {
|
|
738
|
-
content: [{ type: "text", text: `Pulled current branch ${details.currentBranch} with fast-forward-only semantics.` }],
|
|
738
|
+
content: [{ type: "text", text: `Pulled current branch ${details.currentBranch} with fast-forward-only semantics; HEAD ${details.head}.` }],
|
|
739
739
|
details,
|
|
740
740
|
};
|
|
741
741
|
},
|
|
@@ -846,8 +846,9 @@ export function registerBranchMeTools(pi: Pick<ExtensionAPI, "registerTool" | "e
|
|
|
846
846
|
async execute(_toolCallId, _params, signal, _onUpdate, ctx) {
|
|
847
847
|
const details = await pushCurrentBranch(pi, ctx, signal);
|
|
848
848
|
const action = details.mode === "publish" ? "Published" : "Pushed";
|
|
849
|
+
const remoteState = details.remoteHead === details.head ? "matches" : `is at ${details.remoteHead}`;
|
|
849
850
|
return {
|
|
850
|
-
content: [{ type: "text", text: `${action} current branch ${details.currentBranch}.` }],
|
|
851
|
+
content: [{ type: "text", text: `${action} current branch ${details.currentBranch} at ${details.head}; ${details.remoteTrackingRef} ${remoteState}.` }],
|
|
851
852
|
details,
|
|
852
853
|
};
|
|
853
854
|
},
|
package/src/types.ts
CHANGED
|
@@ -280,6 +280,8 @@ export interface CreateBranchDetails {
|
|
|
280
280
|
repoRoot: string;
|
|
281
281
|
previousBranch: string;
|
|
282
282
|
newBranch: string;
|
|
283
|
+
/** Full commit identity the new branch starts at (HEAD after the switch). */
|
|
284
|
+
head: string;
|
|
283
285
|
}
|
|
284
286
|
|
|
285
287
|
export interface ChangeBranchDetails {
|
|
@@ -288,6 +290,8 @@ export interface ChangeBranchDetails {
|
|
|
288
290
|
previousDetached: boolean;
|
|
289
291
|
currentBranch: string;
|
|
290
292
|
hasChangesBeforeSwitch: false;
|
|
293
|
+
/** Full commit identity of HEAD after the switch. */
|
|
294
|
+
head: string;
|
|
291
295
|
}
|
|
292
296
|
|
|
293
297
|
export interface FetchBranchDetails {
|
|
@@ -318,6 +322,8 @@ export interface PullBranchDetails {
|
|
|
318
322
|
remote: string;
|
|
319
323
|
remoteRef: string;
|
|
320
324
|
output: string;
|
|
325
|
+
/** Full commit identity of HEAD after the fast-forward. */
|
|
326
|
+
head: string;
|
|
321
327
|
}
|
|
322
328
|
|
|
323
329
|
export interface RebaseBranchDetails {
|
|
@@ -338,6 +344,12 @@ export interface PushBranchDetails {
|
|
|
338
344
|
remoteRef: string;
|
|
339
345
|
refspec: string;
|
|
340
346
|
output: string;
|
|
347
|
+
/** Full commit identity of the local HEAD that was pushed. */
|
|
348
|
+
head: string;
|
|
349
|
+
/** Remote-tracking ref updated by the push, for example origin/feature/x. */
|
|
350
|
+
remoteTrackingRef: string;
|
|
351
|
+
/** Full commit identity of that remote-tracking ref after the push: the remote-visible HEAD. */
|
|
352
|
+
remoteHead: string;
|
|
341
353
|
}
|
|
342
354
|
|
|
343
355
|
export type IntegrateBranchStatus = "already_integrated" | "fast_forward" | "merge_commit" | "conflict" | "conflict_kept";
|