@senomas/pi-git-hat 0.2.2 → 0.2.3

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.
Files changed (2) hide show
  1. package/git-hat.ts +106 -1
  2. package/package.json +1 -1
package/git-hat.ts CHANGED
@@ -1141,9 +1141,114 @@ export default function (pi: ExtensionAPI) {
1141
1141
  },
1142
1142
  });
1143
1143
 
1144
+ // -- /hatr command: rebase onto most recent role-matched branch --
1145
+
1146
+ pi.registerCommand("hatr", {
1147
+ description: "Rebase current branch onto the most recent role-matched branch",
1148
+ handler: async (_args, ctx) => {
1149
+ // Edge case: detached HEAD or non-git directory
1150
+ const branch = await detectBranch();
1151
+ if (!branch) {
1152
+ ctx.ui.notify("Not on a branch (detached HEAD or not a git repo). Aborting.", "warning");
1153
+ return;
1154
+ }
1155
+ const currentBranch = branch;
1156
+
1157
+ // List all local branches
1158
+ let allBranches: string[] = [];
1159
+ try {
1160
+ const result = await pi.exec("git", ["branch", "--format", "%(refname:short)"]);
1161
+ allBranches = result.stdout.trim().split("\n").filter(Boolean);
1162
+ } catch {
1163
+ ctx.ui.notify("Failed to list git branches.", "error");
1164
+ return;
1165
+ }
1166
+
1167
+ // Filter to role-matched branches, excluding current
1168
+ const candidates: string[] = [];
1169
+ for (const b of allBranches) {
1170
+ if (b === currentBranch) continue;
1171
+ if (detectRole(b, config)) candidates.push(b);
1172
+ }
1173
+
1174
+ // Edge case: no other role-matched branches
1175
+ if (candidates.length === 0) {
1176
+ ctx.ui.notify("No other role-matched branches to rebase onto.", "info");
1177
+ return;
1178
+ }
1179
+
1180
+ // Get latest commit info for each candidate
1181
+ interface CandidateInfo {
1182
+ branch: string;
1183
+ timestamp: number;
1184
+ abbrev: string;
1185
+ subject: string;
1186
+ }
1187
+ const infos: CandidateInfo[] = [];
1188
+ for (const b of candidates) {
1189
+ try {
1190
+ const result = await pi.exec("git", [
1191
+ "log", "--format=%ct %h %s", "-1", b,
1192
+ ]);
1193
+ const line = result.stdout.trim();
1194
+ if (!line) continue;
1195
+ const space1 = line.indexOf(" ");
1196
+ const space2 = line.indexOf(" ", space1 + 1);
1197
+ if (space1 === -1 || space2 === -1) continue;
1198
+ const timestamp = parseInt(line.slice(0, space1), 10);
1199
+ const abbrev = line.slice(space1 + 1, space2);
1200
+ const subject = line.slice(space2 + 1);
1201
+ infos.push({ branch: b, timestamp, abbrev, subject });
1202
+ } catch {
1203
+ // skip branches we can't read
1204
+ }
1205
+ }
1206
+
1207
+ if (infos.length === 0) {
1208
+ ctx.ui.notify("Could not determine latest commits for candidates.", "error");
1209
+ return;
1210
+ }
1211
+
1212
+ // Pick the most recent
1213
+ infos.sort((a, b) => b.timestamp - a.timestamp);
1214
+ const target = infos[0];
1215
+
1216
+ // Show and confirm
1217
+ const confirmed = await ctx.ui.confirm(
1218
+ `Rebase ${currentBranch} onto ${target.branch}? (latest commit: ${target.abbrev} ${target.subject})`,
1219
+ );
1220
+ if (!confirmed) {
1221
+ ctx.ui.notify("Rebase cancelled.", "info");
1222
+ return;
1223
+ }
1224
+
1225
+ // Run the rebase
1226
+ try {
1227
+ const result = await pi.exec("git", ["rebase", target.branch]);
1228
+ if (result.code === 0) {
1229
+ ctx.ui.notify(
1230
+ `Rebase onto ${target.branch} succeeded. Updated commit tree:`,
1231
+ "info",
1232
+ );
1233
+ await showGitLog(ctx, 10);
1234
+ } else {
1235
+ ctx.ui.notify(
1236
+ `Rebase onto ${target.branch} failed:\n${result.stderr}\n\nResolve conflicts manually, then run \`git rebase --continue\`.`,
1237
+ "error",
1238
+ );
1239
+ }
1240
+ } catch (e) {
1241
+ ctx.ui.notify(
1242
+ `Rebase onto ${target.branch} failed: ${(e as Error).message}\n\nResolve conflicts manually, then run \`git rebase --continue\`.`,
1243
+ "error",
1244
+ );
1245
+ }
1246
+ },
1247
+ });
1248
+
1144
1249
  // -- Session lifecycle ----------------------------------------
1145
1250
 
1146
- pi.on("session_start", async (event, ctx) => {
1251
+ pi.on("session_start", async (event, ctx) => {"}]}
1147
1252
  cwdAbsolute = ctx.cwd;
1148
1253
 
1149
1254
  // Seed bundled roles/ files to project .pi/ before loading config
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@senomas/pi-git-hat",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Pi extension for role-based Git branch workflows — wear different hats by switching branches",
5
5
  "type": "module",
6
6
  "keywords": ["pi-package", "git", "workflow", "branching", "roles"],