OXYGENHUB
◆ main oxygen-vscode / src / commitbox.ts
3.0 KiB · last changed Commit box: amend mode, previous messages, signing indicator, and a warning before committing conflict markers; 0.9.0 1 month ago source blame history raw
1
// The commit box's logic (spec §5.1), kept free of the vscode module so it
2
// can be tested: signing state from `oxy identity show`, the placeholder
3
// that shows it, message history, and conflict marker detection.
4
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
}
13
14
/** Parse `oxy --plain identity show`; `null` when no identity is configured. */
15
export function parseIdentityShow(text: string): SigningInfo | null {
16
  const head = text.match(/^#\s*identity\s+(.+)$/m);
17
  if (!head) {
18
    return null;
19
  }
20
  const secretLine = text.match(/^\s*secret\s+(.+)$/m)?.[1] ?? "";
21
  const signingLine = text.match(/^\s*signing\s+(.+)$/m)?.[1] ?? "";
22
  return {
23
    who: head[1].trim(),
24
    secret: /available/.test(secretLine),
25
    mode: /core\.sign\s*=\s*change/.test(signingLine) ? "change" : "seal",
26
  };
27
}
28
29
/** A short name for the placeholder: the name without the address. */
30
function shortName(who: string): string {
31
  return who.replace(/\s*<[^>]*>\s*$/, "").trim() || who;
32
}
33
34
/** How the next commit will be authenticated, for the commit box. */
35
export function signingLabel(info: SigningInfo | null | undefined): string {
36
  if (info === undefined) {
37
    return "";
38
  }
39
  if (info === null) {
40
    return "⚠ unsigned: no identity (oxy identity create)";
41
  }
42
  if (!info.secret) {
43
    return `⚠ unsigned: the key for ${shortName(info.who)} is missing`;
44
  }
45
  return info.mode === "change" ? `✔ signed as ${shortName(info.who)}` : `◈ sealed at push as ${shortName(info.who)}`;
46
}
47
48
/** The commit box placeholder for the current mode. */
49
export function placeholder(opts: { amend: boolean; resolution: boolean; signing: string }): string {
50
  const what = opts.amend ? "Amend the last change: new message, or empty to keep it" : opts.resolution ? "Describe the resolution" : "Message";
51
  const key = opts.amend ? "Ctrl+Enter to amend" : "Ctrl+Enter to commit";
52
  return `${what} (${key})${opts.signing ? ` · ${opts.signing}` : ""}`;
53
}
54
55
/** Recent commit messages, newest first, without duplicates. */
56
export class MessageHistory {
57
  static readonly LIMIT = 30;
58
  private items: string[];
59
60
  constructor(items: readonly string[] = []) {
61
    this.items = items.filter((m) => m.trim()).slice(0, MessageHistory.LIMIT);
62
  }
63
64
  add(message: string): void {
65
    const m = message.trim();
66
    if (!m) {
67
      return;
68
    }
69
    this.items = [m, ...this.items.filter((x) => x !== m)].slice(0, MessageHistory.LIMIT);
70
  }
71
72
  get entries(): readonly string[] {
73
    return this.items;
74
  }
75
}
76
77
/**
78
 * Line numbers (1-based) that open or close a conflict: Oxygen's markers
79
 * (`<<<<<<< oxy conflict …`) and Git's alike, since both begin with seven
80
 * angle brackets.
81
 */
82
export function conflictMarkerLines(text: string): number[] {
83
  const out: number[] = [];
84
  text.split("\n").forEach((l, i) => {
85
    if (/^(<{7}|>{7})( |\r?$)/.test(l)) {
86
      out.push(i + 1);
87
    }
88
  });
89
  return out;
90
}