OXYGENHUB

Coverage / src/commitbox.ts 95.6%

86 of 90 lines run, 13 of 13 functions, 39 of 42 branches · run #26 on main · run missed

10×
// The commit box's logic (spec §5.1), kept free of the vscode module so it
20×
// can be tested: signing state from `oxy identity show`, the placeholder
30×
// that shows it, message history, and conflict marker detection.
41×
5
export interface SigningInfo {
6
  /** `Name <email>`, or empty without an identity. */
7
  who: string;
8
  /** Whether the secret key is in the key directory. */
9
  secret: boolean;
10
  /** `change`: every commit is signed; `seal`: changes are sealed at push. */
11
  mode: "change" | "seal";
12
}
131×
140×
/** Parse `oxy --plain identity show`; `null` when no identity is configured. */
154×
export function parseIdentityShow(text: string): SigningInfo | null {
161×
  const head = text.match(/^#\s*identity\s+(.+)$/m);
171×
  if (!head) {
181×
    return null;
191×
  }
201×
  const secretLine = text.match(/^\s*secret\s+(.+)$/m)?.[1] ?? "";
211×
  const signingLine = text.match(/^\s*signing\s+(.+)$/m)?.[1] ?? "";
221×
  return {
231×
    who: head[1].trim(),
241×
    secret: /available/.test(secretLine),
251×
    mode: /core\.sign\s*=\s*change/.test(signingLine) ? "change" : "seal",
261×
  };
273×
}
283×
293×
/** A short name for the placeholder: the name without the address. */
303×
function shortName(who: string): string {
313×
  return who.replace(/\s*<[^>]*>\s*$/, "").trim() || who;
325×
}
335×
345×
/** How the next commit will be authenticated, for the commit box. */
355×
export function signingLabel(info: SigningInfo | null | undefined): string {
365×
  if (info === undefined) {
371×
    return "";
381×
  }
395×
  if (info === null) {
401×
    return "⚠ unsigned: no identity (oxy identity create)";
411×
  }
425×
  if (!info.secret) {
431×
    return `⚠ unsigned: the key for ${shortName(info.who)} is missing`;
441×
  }
455×
  return info.mode === "change" ? `✔ signed as ${shortName(info.who)}` : `◈ sealed at push as ${shortName(info.who)}`;
465×
}
471×
481×
/** The commit box placeholder for the current mode. */
491×
export function placeholder(opts: { amend: boolean; resolution: boolean; signing: string }): string {
503×
  const what = opts.amend ? "Amend the last change: new message, or empty to keep it" : opts.resolution ? "Describe the resolution" : "Message";
513×
  const key = opts.amend ? "Ctrl+Enter to amend" : "Ctrl+Enter to commit";
523×
  return `${what} (${key})${opts.signing ? ` · ${opts.signing}` : ""}`;
533×
}
541×
551×
/** Recent commit messages, newest first, without duplicates. */
561×
export class MessageHistory {
571×
  static readonly LIMIT = 30;
581×
  private items: string[];
591×
601×
  constructor(items: readonly string[] = []) {
611×
    this.items = items.filter((m) => m.trim()).slice(0, MessageHistory.LIMIT);
621×
  }
631×
641×
  add(message: string): void {
6543×
    const m = message.trim();
6643×
    if (!m) {
671×
      return;
681×
    }
6943×
    this.items = [m, ...this.items.filter((x) => x !== m)].slice(0, MessageHistory.LIMIT);
7043×
  }
711×
721×
  get entries(): readonly string[] {
733×
    return this.items;
743×
  }
753×
}
763×
773×
/**
783×
 * Line numbers (1-based) that open or close a conflict: Oxygen's markers
793×
 * (`<<<<<<< oxy conflict …`) and Git's alike, since both begin with seven
803×
 * angle brackets.
813×
 */
823×
export function conflictMarkerLines(text: string): number[] {
833×
  const out: number[] = [];
843×
  text.split("\n").forEach((l, i) => {
8518×
    if (/^(<{7}|>{7})( |\r?$)/.test(l)) {
864×
      out.push(i + 1);
874×
    }
883×
  });
893×
  return out;
903×
}