Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions changelog.d/unreleased/3049.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
category: fixed
issues:
- 3049
affected:
- src/CodeIndex/Database/DbContext.cs
- tests/CodeIndex.Tests/CodeIndexExceptionTests.cs
---

## English

- **SQLite connection-function retry sleeps now honor cancellation (#3049)** — transient failures while registering SQLite connection functions now use token-aware retry backoff so cancelled CLI and MCP work can stop promptly.

## 日本語

- **SQLite connection function 登録の retry sleep が cancellation を尊重するようになりました (#3049)** — SQLite connection function 登録中の transient failure は token-aware な retry backoff を使うようになり、キャンセルされた CLI / MCP 処理が速やかに停止できます。
19 changes: 19 additions & 0 deletions changelog.d/unreleased/3050.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
category: fixed
issues:
- 3050
affected:
- src/CodeIndex/Database/DbContext.cs
- src/CodeIndex/Cli/IndexCommandRunner.cs
- src/CodeIndex/Cli/QueryCommandRunner.cs
- src/CodeIndex/Mcp/McpServer.cs
- tests/CodeIndex.Tests/CodeIndexExceptionTests.cs
---

## English

- **SQLite connection-open retries now honor caller cancellation (#3050)** — `DbContext` open retry backoff now observes cancellation tokens passed by `index`, `status`, and MCP DB-open paths, so cancelled operations no longer wait for retry sleeps to finish.

## 日本語

- **SQLite connection open の retry が呼び出し元の cancellation を尊重するようになりました (#3050)** — `DbContext` の open retry backoff は `index`、`status`、MCP の DB open 経路から渡された cancellation token を監視し、キャンセル済みの操作が retry sleep の終了まで待たなくなりました。
19 changes: 19 additions & 0 deletions changelog.d/unreleased/3059.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
category: fixed
issues:
- 3059
affected:
- src/CodeIndex/Cli/GitHelper.cs
- src/CodeIndex/Cli/IndexCommandRunner.DryRun.cs
- src/CodeIndex/Cli/IndexCommandRunner.Update.cs
- src/CodeIndex/Cli/IndexCommandRunner.UpdateTargets.cs
- tests/CodeIndex.Tests/GitHelperTests.cs
---

## English

- **Git helper process execution now honors caller cancellation (#3059)** — git-backed update target resolution now passes cancellation into `GitHelper`, which kills the child process tree and returns promptly when the caller cancels.

## 日本語

- **Git helper の process 実行が呼び出し元の cancellation を尊重するようになりました (#3059)** — git ベースの update target 解決は cancellation を `GitHelper` に渡し、呼び出し元がキャンセルした場合は子プロセスツリーを終了して速やかに戻ります。
145 changes: 112 additions & 33 deletions src/CodeIndex/Cli/GitHelper.cs

Large diffs are not rendered by default.

16 changes: 13 additions & 3 deletions src/CodeIndex/Cli/IndexCommandRunner.DryRun.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,18 +137,23 @@ private static bool TryResolveDryRunCandidates(
// Git更新モード: コミットまたはref間の変更ファイル。
var changedFiles = new HashSet<string>(StringComparer.Ordinal);
var relevantIgnoreFileChanged = false;
var repoRoot = GitHelper.TryGetRepositoryRoot(projectPath) ?? Path.GetFullPath(projectPath);
var repoRoot = GitHelper.TryGetRepositoryRoot(projectPath, cancellationToken) ?? Path.GetFullPath(projectPath);
try
{
foreach (var commit in options.Commits)
{
var changed = GitHelper.GetChangedFilesFromCommit(projectPath, commit);
var changed = GitHelper.GetChangedFilesFromCommit(projectPath, commit, cancellationToken);
var normalized = NormalizeCommitFileTargets(projectPath, repoRoot, changed, out var commitTouchedRelevantIgnoreFile);
relevantIgnoreFileChanged |= commitTouchedRelevantIgnoreFile;
foreach (var path in normalized)
changedFiles.Add(path);
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
exitCode = WriteDryRunInterrupted(options, jsonOptions);
return false;
}
catch (Exception ex)
{
exitCode = WriteCommandError(
Expand All @@ -164,12 +169,17 @@ private static bool TryResolveDryRunCandidates(
{
try
{
var changed = GitHelper.GetChangedFilesBetweenRefs(projectPath, options.ChangedBetweenRefs[0], options.ChangedBetweenRefs[1]);
var changed = GitHelper.GetChangedFilesBetweenRefs(projectPath, options.ChangedBetweenRefs[0], options.ChangedBetweenRefs[1], cancellationToken);
var normalized = NormalizeCommitFileTargets(projectPath, repoRoot, changed, out var rangeTouchedRelevantIgnoreFile);
relevantIgnoreFileChanged |= rangeTouchedRelevantIgnoreFile;
foreach (var path in normalized)
changedFiles.Add(path);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
exitCode = WriteDryRunInterrupted(options, jsonOptions);
return false;
}
catch { /* ignore git errors in dry-run */ }
}

Expand Down
17 changes: 14 additions & 3 deletions src/CodeIndex/Cli/IndexCommandRunner.FullScan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,18 @@ void ThrowIfDiscoveryCancelled()
throw new IndexInterruptedException(0, null);
}

var currentHeadForCheckpoint = GitHelper.TryGetHeadCommit(projectRoot);
string? currentHeadForCheckpoint;
try
{
ThrowIfDiscoveryCancelled();
currentHeadForCheckpoint = GitHelper.TryGetHeadCommit(projectRoot, cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
ConsoleUi.StopSpinner(spinnerCts);
throw new IndexInterruptedException(0, null);
}

var scanCheckpointPath = Path.Combine(projectRoot, ".cdidx", ScanCheckpointFileName);
var checkpointedDirectories = LoadScanCheckpoint(scanCheckpointPath, currentHeadForCheckpoint);
WriteFullScanJsonLiveness(options, "scanning files...");
Expand Down Expand Up @@ -1384,7 +1395,7 @@ void StopJsonHeartbeat()
// full scan が「直近 full scan からブランチが動いた」をきちんと検知できる。
// 非 git workspace で null になった場合はキーごとクリアされる。Issue #1508。
writer.SetMeta(DbContext.IndexedHeadCommitMetaKey, currentHeadCommit);
writer.SetMeta(DbContext.IndexedHeadCommitBranchMetaKey, GitHelper.TryGetHeadBranch(projectRoot));
writer.SetMeta(DbContext.IndexedHeadCommitBranchMetaKey, GitHelper.TryGetHeadBranch(projectRoot, cancellationToken));
writer.SetMeta(
DbContext.LastFullScanElapsedMsMetaKey,
stopwatch.ElapsedMilliseconds.ToString(System.Globalization.CultureInfo.InvariantCulture));
Expand All @@ -1395,7 +1406,7 @@ void StopJsonHeartbeat()
// reflects the true HEAD at the time of the most recent successful index.
// #1509: あらゆる成功 index の終端で更新する HEAD トリプル (SHA + branch + 時刻) も
// ここで stamp する。full scan / partial update を問わず最新の HEAD を保存する。
StampIndexedHeadMetadata(writer, projectRoot);
StampIndexedHeadMetadata(writer, projectRoot, cancellationToken);
if (options.MemoryTrace)
memorySamples.Add(CaptureMemorySample("finalize", stopwatch));
var memoryTimelineForStamp = BuildMemoryTimeline(memorySamples);
Expand Down
3 changes: 2 additions & 1 deletion src/CodeIndex/Cli/IndexCommandRunner.Update.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ private static int RunUpdateMode(
options,
spinnerFrames,
jsonOptions,
cancellationToken,
out var targetPaths,
out var relevantIgnoreFileChanged);
if (resolveTargetsExitCode != null)
Expand Down Expand Up @@ -986,7 +987,7 @@ void ThrowIfUpdateCancelled()
}
if (errors == 0)
{
StampIndexedHeadMetadata(writer, projectRoot);
StampIndexedHeadMetadata(writer, projectRoot, cancellationToken);
StampCommitScopedFreshHeadMetadata(writer, options, projectRoot, currentHeadCommit);
if (options.MemoryTrace)
memorySamples.Add(CaptureMemorySample("finalize", stopwatch));
Expand Down
17 changes: 13 additions & 4 deletions src/CodeIndex/Cli/IndexCommandRunner.UpdateTargets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public static partial class IndexCommandRunner
IndexCommandOptions options,
string[] spinnerFrames,
JsonSerializerOptions jsonOptions,
CancellationToken cancellationToken,
out HashSet<string> targetPaths,
out bool relevantIgnoreFileChanged)
{
Expand All @@ -22,16 +23,20 @@ public static partial class IndexCommandRunner
{
if (!options.Json)
spinnerCts = ConsoleUi.StartSpinner("Resolving changed files...", spinnerFrames);
var repoRoot = GitHelper.TryGetRepositoryRoot(projectRoot) ?? Path.GetFullPath(projectRoot);
var repoRoot = GitHelper.TryGetRepositoryRoot(projectRoot, cancellationToken) ?? Path.GetFullPath(projectRoot);
foreach (var commit in options.Commits)
{
var changedFiles = GitHelper.GetChangedFilesFromCommit(projectRoot, commit);
var changedFiles = GitHelper.GetChangedFilesFromCommit(projectRoot, commit, cancellationToken);
var normalized = NormalizeCommitFileTargets(projectRoot, repoRoot, changedFiles, out var commitTouchedRelevantIgnoreFile);
relevantIgnoreFileChanged |= commitTouchedRelevantIgnoreFile;
foreach (var f in normalized)
targetPaths.Add(f);
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (Exception ex)
{
return WriteCommandError(
Expand Down Expand Up @@ -62,13 +67,17 @@ public static partial class IndexCommandRunner
{
if (!options.Json)
spinnerCts = ConsoleUi.StartSpinner("Resolving changed files between refs...", spinnerFrames);
var repoRoot = GitHelper.TryGetRepositoryRoot(projectRoot) ?? Path.GetFullPath(projectRoot);
var changedFiles = GitHelper.GetChangedFilesBetweenRefs(projectRoot, options.ChangedBetweenRefs[0], options.ChangedBetweenRefs[1]);
var repoRoot = GitHelper.TryGetRepositoryRoot(projectRoot, cancellationToken) ?? Path.GetFullPath(projectRoot);
var changedFiles = GitHelper.GetChangedFilesBetweenRefs(projectRoot, options.ChangedBetweenRefs[0], options.ChangedBetweenRefs[1], cancellationToken);
var normalized = NormalizeCommitFileTargets(projectRoot, repoRoot, changedFiles, out var rangeTouchedRelevantIgnoreFile);
relevantIgnoreFileChanged |= rangeTouchedRelevantIgnoreFile;
foreach (var f in normalized)
targetPaths.Add(f);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (Exception ex)
{
return WriteCommandError(
Expand Down
37 changes: 27 additions & 10 deletions src/CodeIndex/Cli/IndexCommandRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,17 @@ internal static int Run(string[] indexArgs, JsonSerializerOptions jsonOptions, C
if (options.OptimizeOnly)
return RunOptimizeFtsForDb(resolvedDbPath, options.Json, jsonOptions, options.ProjectPath);

var ignoreCase = GitHelper.ResolveIgnoreCase(options.ProjectPath);
var ignoreRuleRoot = GitHelper.TryGetRepositoryRoot(options.ProjectPath) ?? Path.GetFullPath(options.ProjectPath!);
bool ignoreCase;
string ignoreRuleRoot;
try
{
ignoreCase = GitHelper.ResolveIgnoreCase(options.ProjectPath, indexCancellation.Token);
ignoreRuleRoot = GitHelper.TryGetRepositoryRoot(options.ProjectPath, indexCancellation.Token) ?? Path.GetFullPath(options.ProjectPath!);
}
catch (OperationCanceledException) when (indexCancellation.IsCancellationRequested)
{
return WriteInterruptedResult(options.Json, jsonOptions, filesProcessed: 0, filesTotal: null);
}

// --dry-run: scan files but do not write to database / --dry-run: ファイルスキャンのみでDBに書き込まない
if (options.DryRun)
Expand Down Expand Up @@ -184,7 +193,7 @@ internal static int Run(string[] indexArgs, JsonSerializerOptions jsonOptions, C

using (indexLock)
{
using var db = new DbContext(dbPath);
using var db = new DbContext(dbPath, indexCancellation.Token);
if (db.ReadOnlyFallback)
{
return WriteCommandError(
Expand Down Expand Up @@ -233,7 +242,7 @@ internal static int Run(string[] indexArgs, JsonSerializerOptions jsonOptions, C
// `--rebuild` が DB を消す前に取り出す。incremental 経路で HEAD 差分を検知し、`status`
// (no `--check`) でも worktree の HEAD 切替検出に利用する。
var priorIndexedHeadCommit = db.GetMetaString(DbContext.IndexedHeadCommitMetaKey);
var currentHeadCommit = GitHelper.TryGetHeadCommit(options.ProjectPath);
var currentHeadCommit = GitHelper.TryGetHeadCommit(options.ProjectPath, indexCancellation.Token);

// Don't demote readiness yet. A transient usage error in update-mode preflight
// (bad --commits hash, git unavailable, etc.) would permanently downgrade a healthy
Expand Down Expand Up @@ -649,25 +658,29 @@ private static bool TryProbeDryRunFile(FileIndexer indexer, string absolutePath,
// the index data itself is valid; the metadata stamp is best-effort. Issue #1509.
// #1509: 成功 index 末尾で HEAD / branch / timestamp を codeindex_meta に保存する。
// git 不在時は NULL stamp、stamp 自体の例外は warn せず無視(index 本体は成功)。
private static void StampIndexedHeadMetadata(DbWriter writer, string projectRoot)
private static void StampIndexedHeadMetadata(DbWriter writer, string projectRoot, CancellationToken cancellationToken)
{
try
{
var headSha = GitHelper.TryGetHeadCommit(projectRoot);
var headBranch = GitHelper.TryGetHeadBranch(projectRoot);
var headSha = GitHelper.TryGetHeadCommit(projectRoot, cancellationToken);
var headBranch = GitHelper.TryGetHeadBranch(projectRoot, cancellationToken);
var timestamp = headSha != null
? DateTime.UtcNow.ToString("o", System.Globalization.CultureInfo.InvariantCulture)
: null;
writer.SetMeta(DbContext.IndexedHeadShaMetaKey, headSha);
writer.SetMeta(DbContext.IndexedHeadBranchMetaKey, headBranch);
writer.SetMeta(DbContext.IndexedHeadTimestampMetaKey, timestamp);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch
{
// Best-effort metadata only; never fail an otherwise-successful index run.
// best-effort であり、stamp の失敗で index 全体を失敗扱いにしない。
}
StampWorkspacePathCaseSensitivity(writer, projectRoot);
StampWorkspacePathCaseSensitivity(writer, projectRoot, cancellationToken);
}

private static void StampCommitScopedFreshHeadMetadata(DbWriter writer, IndexCommandOptions options, string projectRoot, string? currentHeadCommit)
Expand Down Expand Up @@ -713,15 +726,19 @@ private static bool TryChangedBetweenCoversCurrentHead(IndexCommandOptions optio
// an unwritable git config / temp probe never blocks an otherwise-successful index.
// #1546: workspace FS の大小区別を実プローブして codeindex_meta に保存する。
// probe 失敗時は黙って null stamp にして index 本体は成功扱いのままとする。
private static void StampWorkspacePathCaseSensitivity(DbWriter writer, string projectRoot)
private static void StampWorkspacePathCaseSensitivity(DbWriter writer, string projectRoot, CancellationToken cancellationToken)
{
try
{
var ignoreCase = GitHelper.ResolveIgnoreCase(projectRoot);
var ignoreCase = GitHelper.ResolveIgnoreCase(projectRoot, cancellationToken);
PathCasing.SeedFromWorkspace(projectRoot, ignoreCase);
var caseSensitive = (!ignoreCase).ToString(System.Globalization.CultureInfo.InvariantCulture);
writer.SetMeta(DbContext.WorkspacePathCaseSensitiveMetaKey, caseSensitive);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch
{
// Best-effort metadata only; never fail an otherwise-successful index run.
Expand Down
8 changes: 6 additions & 2 deletions src/CodeIndex/Cli/IndexWatchRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ public static int Run(
{
return RunCore(baseOptions, jsonOptions, projectRoot, resolvedDbPath, cts.Token);
}
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
return CommandExitCodes.Success;
}
finally
{
Console.CancelKeyPress -= handler;
Expand All @@ -50,10 +54,10 @@ internal static int RunCore(
CancellationToken cancellationToken)
{
var debounce = TimeSpan.FromMilliseconds(baseOptions.WatchDebounceMs ?? DefaultDebounceMs);
var ignoreCase = GitHelper.ResolveIgnoreCase(projectRoot);
var ignoreCase = GitHelper.ResolveIgnoreCase(projectRoot, cancellationToken);
var batcher = new FileChangeBatcher(debounce, ignoreCase: ignoreCase);

var ignoreRuleRoot = GitHelper.TryGetRepositoryRoot(projectRoot) ?? Path.GetFullPath(projectRoot);
var ignoreRuleRoot = GitHelper.TryGetRepositoryRoot(projectRoot, cancellationToken) ?? Path.GetFullPath(projectRoot);
var fileIndexer = new FileIndexer(projectRoot, ignoreCase, ignoreRuleRoot);
var watchExitCode = CommandExitCodes.Success;

Expand Down
11 changes: 8 additions & 3 deletions src/CodeIndex/Cli/QueryCommandRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3951,7 +3951,7 @@ public static int RunStatus(
if (!options.CheckWorkspace)
return CommandExitCodes.Success;
return GetStatusCheckExitCode(checkFailures);
});
}, cancellationToken: cancellationToken);
}

private static JsonObject BuildEffectiveConfigJson(QueryCommandOptions options, string[] cmdArgs, string? appVersion)
Expand Down Expand Up @@ -7463,7 +7463,12 @@ private static bool IsEmptySymbolAnalysis(SymbolAnalysisResult analysis)
&& analysis.Callers.Count == 0
&& analysis.Callees.Count == 0;

private static int WithDb(QueryCommandOptions options, JsonSerializerOptions jsonOptions, Func<DbReader, int> action, Action<int>? afterProfile = null)
private static int WithDb(
QueryCommandOptions options,
JsonSerializerOptions jsonOptions,
Func<DbReader, int> action,
Action<int>? afterProfile = null,
CancellationToken cancellationToken = default)
{
var dbPath = options.DbPath;
if (s_batchReader == null)
Expand Down Expand Up @@ -7521,7 +7526,7 @@ private static int WithDb(QueryCommandOptions options, JsonSerializerOptions jso
}
else
{
db = new DbContext(dbPath);
db = new DbContext(dbPath, cancellationToken);
if (!db.TryValidateIsCodeIndexDb(out var validationReason))
return WriteInvalidCodeIndexDbError(dbPath, validationReason);
db.TryMigrateForRead();
Expand Down
Loading
Loading