дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
feat(vcs): Add a modal commit mode and fill in Git log, branch popup and settings (#144)
- Settings › Commit › "Use non-modal commit interface": when off, Local Changes, Shelf and Stash become tabs of the Git tool window and ⌘K opens a Commit dialog; switches live. - Git tool window: Worktrees tab; Show History and Show Git Log for a branch open their own closable tabs instead of changing the Log filters. - Log: toolbar and context menu for a commit's changed files, vertical toolbar, Tags node and hiding for the branches pane, Collapse / Expand Linear Branches as separate actions, and a Version Control › Log page. - Branch popup: collapsible groups, branches grouped by prefix, an options menu behind the gear, incoming / outgoing icons and "Pull into … Using Rebase / Merge"; New Branch on ⌥⌘N outside the editor. - Git main menu regrouped with Uncommitted Changes and Current File submenus; rebase / merge controls enabled only while one is in progress. - Settings: Version Control lists its pages; new options for clearing the initial commit message, showing Update options, confirming dropped commits, revealing created patches, large-file and file-name warnings, the Push dialog after Commit and Push, combined stashes and shelves, and removing applied files from the shelf.
Цей коміт міститься в:
1 джерело
66c36a7361
коміт
eb67595e72
54 files changed
+3425
-871
No files matched your search
@@ -2,7 +2,7 @@
|
||||
* Commit checks (Settings › Version Control › Commit / Git): before-commit
|
||||
* modifications (Reformat code, Optimize imports, Cleanup) applied through
|
||||
* the language servers, then the checks (Analyze code, Check TODO) and the Git
|
||||
* warnings (detached HEAD / rebase, CRLF line separators).
|
||||
* warnings (detached HEAD / rebase, file names, large files, CRLF line separators).
|
||||
*/
|
||||
import type { ReactNode } from "react";
|
||||
import type { Diagnostic } from "@/bindings";
|
||||
@@ -11,7 +11,7 @@ import { call, commands, isTauri, onIdeEvent } from "@/platform/ipc";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { workflowSettings } from "@/platform/workflowSettings";
|
||||
import { maybeEditorService, toolWindowService } from "@/workbench/contributions";
|
||||
import { baseName, relTo } from "./model";
|
||||
import { baseName, effectiveKind, relTo, stripSlash } from "./model";
|
||||
import { logConsole } from "./ops";
|
||||
import { currentRepo, useVcs } from "./store";
|
||||
import styles from "./common.module.css";
|
||||
@@ -180,6 +180,50 @@ export interface CommitProblem {
|
||||
details?: { label: string; run: () => void };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Git: names invalid on Windows, large files
|
||||
|
||||
const WINDOWS_RESERVED = new Set(
|
||||
["CON", "PRN", "AUX", "NUL"].concat([1, 2, 3, 4, 5, 6, 7, 8, 9].flatMap((n) => [`COM${n}`, `LPT${n}`])),
|
||||
);
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const WINDOWS_INVALID = /[<>:"\\|?*\u0000-\u001f]/;
|
||||
|
||||
/** The first path segment below `root` that Windows rejects (IDEA's GitFileNameCheckinHandler). */
|
||||
export function badFileName(
|
||||
paths: readonly string[],
|
||||
root: string,
|
||||
): { name: string; kind: "reserved" | "char" } | null {
|
||||
for (const p of paths) {
|
||||
for (const name of relTo(root, p).split("/")) {
|
||||
const dot = name.lastIndexOf(".");
|
||||
const stem = dot > 0 ? name.slice(0, dot) : name;
|
||||
if (WINDOWS_RESERVED.has(stem.toUpperCase())) return { name, kind: "reserved" };
|
||||
if (WINDOWS_INVALID.test(name)) return { name, kind: "char" };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Files bigger than `limit` bytes; sizes come from listing their folders. */
|
||||
async function largeFiles(paths: readonly string[], limit: number): Promise<Array<{ path: string; size: number }>> {
|
||||
const byDir = new Map<string, Set<string>>();
|
||||
for (const p of paths) {
|
||||
const dir = p.slice(0, p.lastIndexOf("/")) || "/";
|
||||
(byDir.get(dir) ?? byDir.set(dir, new Set()).get(dir)!).add(p);
|
||||
}
|
||||
const out: Array<{ path: string; size: number }> = [];
|
||||
for (const [dir, wanted] of byDir) {
|
||||
try {
|
||||
for (const e of await call(commands.fsReadDir(dir)))
|
||||
if (wanted.has(e.path) && e.kind === "file" && e.size > limit) out.push({ path: e.path, size: e.size });
|
||||
} catch {
|
||||
// unreadable folder
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface CommitCandidate {
|
||||
@@ -209,8 +253,36 @@ export async function runCommitChecks(files: readonly CommitCandidate[], amend:
|
||||
}
|
||||
|
||||
const existing = files.filter((f) => f.exists).map((f) => f.path);
|
||||
if (git.warnBadFileNames) {
|
||||
// New paths only: a name already in the repository is not this commit's problem.
|
||||
const kinds = new Map(useVcs.getState().status.map((f) => [stripSlash(f.path), effectiveKind(f)]));
|
||||
const added = existing.filter((p) => ["added", "untracked", "renamed"].includes(kinds.get(p) ?? ""));
|
||||
const bad = badFileName(added, useVcs.getState().root ?? "");
|
||||
if (bad)
|
||||
problems.push({
|
||||
title: "File names",
|
||||
text:
|
||||
bad.kind === "reserved"
|
||||
? tx`Some files use reserved names that are not allowed on Windows: '${bad.name}'. If you commit, it will not be possible to check out the Git repository on Windows.`
|
||||
: tx`Some of the file names contain characters that are not allowed on Windows: '${bad.name}'. If you commit, it might not be possible to check out the Git repository on Windows.`,
|
||||
});
|
||||
}
|
||||
if (!isTauri() || !existing.length) return problems;
|
||||
|
||||
if (git.warnLargeFiles) {
|
||||
const large = await largeFiles(existing, git.largeFileMb * 1024 * 1024);
|
||||
if (large.length) {
|
||||
const mb = Math.ceil(large.reduce((n, f) => n + f.size, 0) / (1024 * 1024));
|
||||
problems.push({
|
||||
title: "Large files",
|
||||
text:
|
||||
large.length === 1
|
||||
? tx`You are about to commit a large file into the repository: ${baseName(large[0]!.path)} (${mb} MB). It is not recommended to store large files in a Git repository. Consider using Git LFS.`
|
||||
: tx`You are about to commit ${large.length} large files into the repository with the total size of ${mb} MB. It is not recommended to store large files in a Git repository. Consider using Git LFS.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const mods: Modifications = {
|
||||
reformat: commit.reformat,
|
||||
optimizeImports: commit.optimizeImports,
|
||||
|
||||
Reference in new issue
Заблокувати користувача