chore: merge git crates (#5909)

Merge `git-apply` and `git-tooling` into `utils/`
This commit is contained in:
jif-oai
2025-10-29 12:11:44 +00:00
committed by GitHub
parent 89591e4246
commit fa92cd92fa
23 changed files with 90 additions and 80 deletions

21
codex-rs/Cargo.lock generated
View File

@@ -950,7 +950,7 @@ dependencies = [
"clap",
"codex-common",
"codex-core",
"codex-git-apply",
"codex-git",
"serde",
"serde_json",
"tempfile",
@@ -1027,7 +1027,7 @@ dependencies = [
"async-trait",
"chrono",
"codex-backend-client",
"codex-git-apply",
"codex-git",
"diffy",
"serde",
"serde_json",
@@ -1063,7 +1063,7 @@ dependencies = [
"codex-apply-patch",
"codex-async-utils",
"codex-file-search",
"codex-git-tooling",
"codex-git",
"codex-keyring-store",
"codex-otel",
"codex-protocol",
@@ -1202,20 +1202,13 @@ dependencies = [
]
[[package]]
name = "codex-git-apply"
version = "0.0.0"
dependencies = [
"once_cell",
"regex",
"tempfile",
]
[[package]]
name = "codex-git-tooling"
name = "codex-git"
version = "0.0.0"
dependencies = [
"assert_matches",
"once_cell",
"pretty_assertions",
"regex",
"schemars 0.8.22",
"serde",
"tempfile",
@@ -1346,7 +1339,7 @@ version = "0.0.0"
dependencies = [
"anyhow",
"base64",
"codex-git-tooling",
"codex-git",
"codex-utils-image",
"icu_decimal",
"icu_locale_core",

View File

@@ -18,7 +18,6 @@ members = [
"execpolicy",
"keyring-store",
"file-search",
"git-tooling",
"linux-sandbox",
"login",
"mcp-server",
@@ -32,7 +31,7 @@ members = [
"stdio-to-uds",
"otel",
"tui",
"git-apply",
"utils/git",
"utils/cache",
"utils/image",
"utils/json-to-toml",
@@ -67,7 +66,7 @@ codex-core = { path = "core" }
codex-exec = { path = "exec" }
codex-feedback = { path = "feedback" }
codex-file-search = { path = "file-search" }
codex-git-tooling = { path = "git-tooling" }
codex-git = { path = "utils/git" }
codex-keyring-store = { path = "keyring-store" }
codex-linux-sandbox = { path = "linux-sandbox" }
codex-login = { path = "login" }

View File

@@ -14,7 +14,7 @@ codex-core = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["full"] }
codex-git-apply = { path = "../git-apply" }
codex-git = { workspace = true }
[dev-dependencies]
tempfile = { workspace = true }

View File

@@ -59,13 +59,13 @@ pub async fn apply_diff_from_task(
async fn apply_diff(diff: &str, cwd: Option<PathBuf>) -> anyhow::Result<()> {
let cwd = cwd.unwrap_or(std::env::current_dir().unwrap_or_else(|_| std::env::temp_dir()));
let req = codex_git_apply::ApplyGitRequest {
let req = codex_git::ApplyGitRequest {
cwd,
diff: diff.to_string(),
revert: false,
preflight: false,
};
let res = codex_git_apply::apply_git_patch(&req)?;
let res = codex_git::apply_git_patch(&req)?;
if res.exit_code != 0 {
anyhow::bail!(
"Git apply failed (applied={}, skipped={}, conflicts={})\nstdout:\n{}\nstderr:\n{}",

View File

@@ -24,4 +24,4 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2.0.12"
codex-backend-client = { path = "../backend-client", optional = true }
codex-git-apply = { path = "../git-apply" }
codex-git = { workspace = true }

View File

@@ -362,13 +362,13 @@ mod api {
});
}
let req = codex_git_apply::ApplyGitRequest {
let req = codex_git::ApplyGitRequest {
cwd: std::env::current_dir().unwrap_or_else(|_| std::env::temp_dir()),
diff: diff.clone(),
revert: false,
preflight,
};
let r = codex_git_apply::apply_git_patch(&req)
let r = codex_git::apply_git_patch(&req)
.map_err(|e| CloudTaskError::Io(format!("git apply failed to run: {e}")))?;
let status = if r.exit_code == 0 {

View File

@@ -26,4 +26,4 @@ pub use mock::MockClient;
#[cfg(feature = "online")]
pub use http::HttpClient;
// Reusable apply engine now lives in the shared crate `codex-git-apply`.
// Reusable apply engine now lives in the shared crate `codex-git`.

View File

@@ -23,7 +23,7 @@ codex-app-server-protocol = { workspace = true }
codex-apply-patch = { workspace = true }
codex-async-utils = { workspace = true }
codex-file-search = { workspace = true }
codex-git-tooling = { workspace = true }
codex-git = { workspace = true }
codex-keyring-store = { workspace = true }
codex-otel = { workspace = true, features = ["otel"] }
codex-protocol = { workspace = true }

View File

@@ -530,7 +530,7 @@ fn is_api_message(message: &ResponseItem) -> bool {
#[cfg(test)]
mod tests {
use super::*;
use codex_git_tooling::GhostCommit;
use codex_git::GhostCommit;
use codex_protocol::models::ContentItem;
use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::LocalShellAction;

View File

@@ -3,9 +3,9 @@ use crate::state::TaskKind;
use crate::tasks::SessionTask;
use crate::tasks::SessionTaskContext;
use async_trait::async_trait;
use codex_git_tooling::CreateGhostCommitOptions;
use codex_git_tooling::GitToolingError;
use codex_git_tooling::create_ghost_commit;
use codex_git::CreateGhostCommitOptions;
use codex_git::GitToolingError;
use codex_git::create_ghost_commit;
use codex_protocol::models::ResponseItem;
use codex_protocol::user_input::UserInput;
use codex_utils_readiness::Readiness;

View File

@@ -8,7 +8,7 @@ use crate::state::TaskKind;
use crate::tasks::SessionTask;
use crate::tasks::SessionTaskContext;
use async_trait::async_trait;
use codex_git_tooling::restore_ghost_commit;
use codex_git::restore_ghost_commit;
use codex_protocol::models::ResponseItem;
use codex_protocol::user_input::UserInput;
use tokio_util::sync::CancellationToken;

View File

@@ -1,17 +0,0 @@
[package]
name = "codex-git-apply"
version = { workspace = true }
edition = "2024"
[lib]
name = "codex_git_apply"
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
once_cell = "1"
regex = "1"
tempfile = "3"

View File

@@ -1,20 +0,0 @@
# codex-git-tooling
Helpers for interacting with git.
```rust,no_run
use std::path::Path;
use codex_git_tooling::{create_ghost_commit, restore_ghost_commit, CreateGhostCommitOptions};
let repo = Path::new("/path/to/repo");
// Capture the current working tree as an unreferenced commit.
let ghost = create_ghost_commit(&CreateGhostCommitOptions::new(repo))?;
// Later, undo back to that state.
restore_ghost_commit(repo, &ghost)?;
```
Pass a custom message with `.message("…")` or force-include ignored files with
`.force_include(["ignored.log".into()])`.

View File

@@ -11,7 +11,7 @@ path = "src/lib.rs"
workspace = true
[dependencies]
codex-git-tooling = { workspace = true }
codex-git = { workspace = true }
base64 = { workspace = true }
codex-utils-image = { workspace = true }

View File

@@ -11,7 +11,7 @@ use serde::ser::Serializer;
use ts_rs::TS;
use crate::user_input::UserInput;
use codex_git_tooling::GhostCommit;
use codex_git::GhostCommit;
use codex_utils_image::error::ImageProcessingError;
use schemars::JsonSchema;

View File

@@ -1,27 +1,25 @@
[package]
name = "codex-git-tooling"
name = "codex-git"
version.workspace = true
edition.workspace = true
readme = "README.md"
[lib]
name = "codex_git_tooling"
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
tempfile = { workspace = true }
thiserror = { workspace = true }
walkdir = { workspace = true }
once_cell = "1"
regex = "1"
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }
tempfile = { workspace = true }
thiserror = { workspace = true }
ts-rs = { workspace = true, features = [
"uuid-impl",
"serde-json-impl",
"no-serde-warnings",
] }
[lints]
workspace = true
walkdir = { workspace = true }
[dev-dependencies]
assert_matches = { workspace = true }

View File

@@ -0,0 +1,33 @@
# codex-git
Helpers for interacting with git, including patch application and worktree
snapshot utilities.
```rust,no_run
use std::path::Path;
use codex_git::{
apply_git_patch, create_ghost_commit, restore_ghost_commit, ApplyGitRequest,
CreateGhostCommitOptions,
};
let repo = Path::new("/path/to/repo");
// Apply a patch (omitted here) to the repository.
let request = ApplyGitRequest {
cwd: repo.to_path_buf(),
diff: String::from("...diff contents..."),
revert: false,
preflight: false,
};
let result = apply_git_patch(&request)?;
// Capture the current working tree as an unreferenced commit.
let ghost = create_ghost_commit(&CreateGhostCommitOptions::new(repo))?;
// Later, undo back to that state.
restore_ghost_commit(repo, &ghost)?;
```
Pass a custom message with `.message("…")` or force-include ignored files with
`.force_include(["ignored.log".into()])`.

View File

@@ -1,3 +1,11 @@
//! Helpers for applying unified diffs using the system `git` binary.
//!
//! The entry point is [`apply_git_patch`], which writes a diff to a temporary
//! file, shells out to `git apply` with the right flags, and then parses the
//! commands output into structured details. Callers can opt into dry-run
//! mode via [`ApplyGitRequest::preflight`] and inspect the resulting paths to
//! learn what would change before applying for real.
use once_cell::sync::Lazy;
use regex::Regex;
use std::ffi::OsStr;
@@ -5,6 +13,7 @@ use std::io;
use std::path::Path;
use std::path::PathBuf;
/// Parameters for invoking [`apply_git_patch`].
#[derive(Debug, Clone)]
pub struct ApplyGitRequest {
pub cwd: PathBuf,
@@ -13,6 +22,7 @@ pub struct ApplyGitRequest {
pub preflight: bool,
}
/// Result of running [`apply_git_patch`], including paths gleaned from stdout/stderr.
#[derive(Debug, Clone)]
pub struct ApplyGitResult {
pub exit_code: i32,
@@ -24,6 +34,10 @@ pub struct ApplyGitResult {
pub cmd_for_log: String,
}
/// Apply a unified diff to the target repository by shelling out to `git apply`.
///
/// When [`ApplyGitRequest::preflight`] is `true`, this behaves like `git apply --check` and
/// leaves the working tree untouched while still parsing the command output for diagnostics.
pub fn apply_git_patch(req: &ApplyGitRequest) -> io::Result<ApplyGitResult> {
let git_root = resolve_git_root(&req.cwd)?;
@@ -176,6 +190,7 @@ fn render_command_for_log(cwd: &Path, git_cfg: &[String], args: &[String]) -> St
)
}
/// Collect every path referenced by the diff headers inside `diff --git` sections.
pub fn extract_paths_from_patch(diff_text: &str) -> Vec<String> {
static RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?m)^diff --git a/(.*?) b/(.*)$")
@@ -199,6 +214,7 @@ pub fn extract_paths_from_patch(diff_text: &str) -> Vec<String> {
set.into_iter().collect()
}
/// Stage only the files that actually exist on disk for the given diff.
pub fn stage_paths(git_root: &Path, diff: &str) -> io::Result<()> {
let paths = extract_paths_from_patch(diff);
let mut existing: Vec<String> = Vec::new();
@@ -225,6 +241,7 @@ pub fn stage_paths(git_root: &Path, diff: &str) -> io::Result<()> {
// ============ Parser ported from VS Code (TS) ============
/// Parse `git apply` output into applied/skipped/conflicted path groupings.
pub fn parse_git_apply_output(
stdout: &str,
stderr: &str,

View File

@@ -1,11 +1,18 @@
use std::fmt;
use std::path::PathBuf;
mod apply;
mod errors;
mod ghost_commits;
mod operations;
mod platform;
pub use apply::ApplyGitRequest;
pub use apply::ApplyGitResult;
pub use apply::apply_git_patch;
pub use apply::extract_paths_from_patch;
pub use apply::parse_git_apply_output;
pub use apply::stage_paths;
pub use errors::GitToolingError;
pub use ghost_commits::CreateGhostCommitOptions;
pub use ghost_commits::create_ghost_commit;

View File

@@ -34,4 +34,4 @@ pub fn create_symlink(
}
#[cfg(not(any(unix, windows)))]
compile_error!("codex-git-tooling symlink support is only implemented for Unix and Windows");
compile_error!("codex-git symlink support is only implemented for Unix and Windows");