Skip to content

feat(commit): only commit files modified by current session#179

Open
paaauldev wants to merge 6 commits intocoollabsio:mainfrom
paaauldev:feature/session-based-commit
Open

feat(commit): only commit files modified by current session#179
paaauldev wants to merge 6 commits intocoollabsio:mainfrom
paaauldev:feature/session-based-commit

Conversation

@paaauldev
Copy link
Copy Markdown

Summary

  • Add session-based commit: only commit files modified by current session instead of all pending changes
  • Add git_snapshot field to SessionMetadata to track the base commit when session starts
  • Add helper functions get_current_commit(), get_changed_files_since(), and stage_files() in git_status.rs
  • Update create_commit_with_ai command to accept session_id and filter files changed since session started
  • Pass sessionId from frontend useGitOperations hook

Test plan

  • Create two sessions in the same worktree
  • Session 1: modify file A
  • Session 2: modify file B
  • Commit from Session 1
  • Verify only file A is committed (not B)
  • Verify Session 2 changes remain uncommitted

🤖 Generated with Claude Code

When user clicks "Commit" or "Commit & Push", only the files
modified by the current session are committed, not all pending changes.

- Add git_snapshot field to SessionMetadata to track base commit
- Add get_current_commit(), get_changed_files_since(), and stage_files()
  functions in git_status.rs
- Update create_commit_with_ai command to accept session_id and filter
  files changed since session started
- Pass session_id from frontend useGitOperations hook

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 10, 2026 10:38
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to make AI-assisted commits session-scoped by committing only files changed during the active chat session (tracked via a git snapshot). It also includes additional backend changes related to Plane project/issue support.

Changes:

  • Add sessionId to create_commit_with_ai invocations and thread it through the backend dispatcher.
  • Introduce session-based git helpers (get_current_commit, get_changed_files_since, stage_files) and use them to stage changes for commits.
  • Add git_snapshot to SessionMetadata to store the session’s base commit.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/components/chat/hooks/useGitOperations.ts Passes sessionId to commit operations so the backend can scope staging/commits to the active session.
src-tauri/src/projects/git_status.rs Adds git helper functions to support session-based commit filtering and selective staging.
src-tauri/src/projects/commands.rs Updates create_commit_with_ai to optionally stage only session-changed files; extends project settings with Plane fields.
src-tauri/src/http_server/dispatch.rs Wires sessionId into the WebSocket command dispatcher; adds Plane issue command routes.
src-tauri/src/chat/types.rs Adds git_snapshot to session metadata for tracking the base commit of a session.

Comment on lines +849 to +855
pub fn get_changed_files_since(repo_path: &str, base_commit: &str) -> Result<Vec<String>, String> {
let output = silent_command("git")
.args(["diff", "--name-only", &format!("{base_commit}..HEAD")])
.current_dir(repo_path)
.output()
.map_err(|e| format!("Failed to run git diff: {e}"))?;

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_changed_files_since() uses git diff --name-only {base_commit}..HEAD, which only compares commits and will be empty when the session only has working tree (uncommitted) changes. This will cause the session-based commit path to miss the intended files and fall back to staging everything. Use a diff that compares the working tree to the base snapshot (and include untracked files separately) so uncommitted changes are captured.

Copilot uses AI. Check for mistakes.
Comment on lines +870 to +887
/// Stage specific files for commit (instead of stage_all)
pub fn stage_files(repo_path: &str, files: &[String]) -> Result<(), String> {
if files.is_empty() {
return Ok(());
}

let mut cmd = silent_command("git");
cmd.arg("add").args(files);

let output = cmd
.current_dir(repo_path)
.output()
.map_err(|e| format!("Failed to stage files: {e}"))?;

if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("Failed to stage files: {stderr}"));
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stage_files() runs git add without a -- separator and without -A/--all. Filenames starting with - could be interpreted as options, and deletions won't be staged via plain git add <path> (deleted paths can error or be skipped). Prefer git add -A -- <files...> (or equivalent) to safely handle arbitrary paths and deletions.

Copilot uses AI. Check for mistakes.
Comment on lines +5840 to +5843
Ok(_) => {
// No files changed since snapshot - try to stage all anyway (might be untracked files)
stage_all_changes(&worktree_path)?;
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When get_changed_files_since() returns an empty list, this falls back to stage_all_changes(), which can stage and commit unrelated changes from other sessions (the exact behavior this PR is trying to prevent). Instead of staging all, consider returning a clear error like "No changes in this session" or staging only session-relevant files (e.g., diff vs snapshot + untracked).

Copilot uses AI. Check for mistakes.
Comment on lines +3867 to +3870
plane_api_key: Option<String>,
plane_url: Option<String>,
plane_workspace_id: Option<String>,
plane_project_id: Option<String>,
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding these new plane_* parameters changes the update_project_settings function signature. The HTTP/WebSocket dispatcher calls update_project_settings(...) positionally and must be updated to pass the additional arguments (even if they’re None), otherwise this will not compile.

Copilot uses AI. Check for mistakes.
Comment on lines +2167 to +2174
// =====================================================================
// Plane Issues
// =====================================================================
"list_plane_workspaces" => {
let project_id: String = field(&args, "projectId", "project_id")?;
let result = crate::projects::list_plane_workspaces(app.clone(), project_id).await?;
to_value(result)
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces Plane settings and a large set of list_/search_/get_plane_* dispatcher commands, but the PR description only discusses session-based commits. If the Plane work is intentional, please update the PR description/scope (or split into a separate PR) to keep review and rollback risk manageable.

Copilot uses AI. Check for mistakes.
Comment on lines +5810 to 5813
// 2. Check for uncommitted changes
let status = get_git_status(&worktree_path)?;
if status.trim().is_empty() {
if push {
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commit message prompt uses status from git status --short before session-based staging, so it can include unrelated workspace changes (e.g., from another session) even if only some files are staged/committed. This can mislead the AI into mentioning files that won't be part of the commit; consider generating a status scoped to staged changes (or to the session file set) after staging.

Copilot uses AI. Check for mistakes.
Comment on lines +5777 to +5799
// 1. Get git snapshot for session-based filtering
let git_snapshot = if let Some(ref sid) = session_id {
// Try to load session metadata to get existing snapshot
if let Ok(Some(metadata)) = crate::chat::storage::load_metadata(&app, sid) {
if metadata.git_snapshot.is_some() {
metadata.git_snapshot.clone()
} else {
// No snapshot exists yet - capture current commit and save it
let current_commit = get_current_commit(&worktree_path).ok();
if current_commit.is_some() {
// Save snapshot to session metadata
let _ = crate::chat::storage::with_metadata_mut(
&app,
sid,
&metadata.worktree_id,
&metadata.name,
metadata.order,
|m| {
m.git_snapshot = current_commit.clone();
Ok(())
},
);
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code captures and persists git_snapshot lazily when creating the first commit, which doesn't match the stated goal of "base commit when session starts" and can produce a wrong snapshot if HEAD moved between session start and first commit attempt. It would be more reliable to set git_snapshot when the session is created/opened, and handle/save errors from with_metadata_mut instead of discarding them with let _ = ....

Copilot uses AI. Check for mistakes.
Comment on lines 274 to 278
field_opt(&args, "customProfileName", "custom_profile_name")?;
let reasoning_effort: Option<String> =
field_opt(&args, "reasoningEffort", "reasoning_effort")?;
let session_id: Option<String> = from_field_opt(&args, "sessionId")?;
let result = crate::projects::create_commit_with_ai(
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from_field_opt(&args, "sessionId") only accepts the camelCase key. Most other optional args in this dispatcher use field_opt() to support both camelCase and snake_case; using field_opt(&args, "sessionId", "session_id") here would keep behavior consistent and avoid breaking clients that send session_id.

Copilot uses AI. Check for mistakes.
Comment on lines +1056 to +1059
/// Git commit hash when session started (for filtering commit to session-specific changes)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub git_snapshot: Option<String>,

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SessionMetadata::new() (and any other struct literals) must be updated to initialize the new git_snapshot field. As written, adding this non-Default struct field will cause a compile error due to a missing field in the struct literal initialization.

Copilot uses AI. Check for mistakes.
@BowgartField
Copy link
Copy Markdown
Contributor

I don't think all of this is needed.
AI already know what has been modified.
And jean is showing at the end of chat modified files.

@paaauldev
Copy link
Copy Markdown
Author

I don't think all of this is needed. AI already know what has been modified. And jean is showing at the end of chat modified files.

In windows at least is commiting all the files, not just the files from each session

paaauldev and others added 2 commits March 11, 2026 18:08
- Add include_co_author preference (disabled by default)
- Add toggle in Settings > General preferences
- Modify git commit functions to add Co-Authored-By when enabled

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ScDor
Copy link
Copy Markdown

ScDor commented Mar 14, 2026

Not just on windows, on Linux as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants