Move git utilities into a dedicated crate (#15564)

- create `codex-git-utils` and move the shared git helpers into it with
file moves preserved for diff readability
- move the `GitInfo` helpers out of `core` so stacked rollout work can
depend on the shared crate without carrying its own git info module

---------

Co-authored-by: Ahmed Ibrahim <219906144+aibrahim-oai@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Ahmed Ibrahim
2026-03-24 13:26:23 -07:00
committed by GitHub
Unverified
parent fc97092f75
commit 0f957a93cd
68 changed files with 206 additions and 134 deletions
+26 -15
View File
@@ -380,7 +380,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@@ -391,7 +391,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@@ -1444,6 +1444,7 @@ dependencies = [
"codex-features",
"codex-feedback",
"codex-file-search",
"codex-git-utils",
"codex-login",
"codex-otel",
"codex-protocol",
@@ -1509,6 +1510,7 @@ dependencies = [
"anyhow",
"clap",
"codex-experimental-api-macros",
"codex-git-utils",
"codex-protocol",
"codex-utils-absolute-path",
"codex-utils-cargo-bin",
@@ -1643,7 +1645,7 @@ dependencies = [
"clap",
"codex-connectors",
"codex-core",
"codex-git",
"codex-git-utils",
"codex-utils-cargo-bin",
"codex-utils-cli",
"pretty_assertions",
@@ -1768,6 +1770,7 @@ dependencies = [
"codex-client",
"codex-cloud-tasks-client",
"codex-core",
"codex-git-utils",
"codex-login",
"codex-tui",
"codex-utils-cli",
@@ -1794,7 +1797,7 @@ dependencies = [
"async-trait",
"chrono",
"codex-backend-client",
"codex-git",
"codex-git-utils",
"diffy",
"serde",
"serde_json",
@@ -1879,7 +1882,7 @@ dependencies = [
"codex-execpolicy",
"codex-features",
"codex-file-search",
"codex-git",
"codex-git-utils",
"codex-hooks",
"codex-login",
"codex-network-proxy",
@@ -1993,6 +1996,7 @@ dependencies = [
"codex-cloud-requirements",
"codex-core",
"codex-feedback",
"codex-git-utils",
"codex-otel",
"codex-protocol",
"codex-utils-absolute-path",
@@ -2133,10 +2137,12 @@ dependencies = [
]
[[package]]
name = "codex-git"
name = "codex-git-utils"
version = "0.0.0"
dependencies = [
"assert_matches",
"codex-utils-absolute-path",
"futures",
"once_cell",
"pretty_assertions",
"regex",
@@ -2144,6 +2150,7 @@ dependencies = [
"serde",
"tempfile",
"thiserror 2.0.18",
"tokio",
"ts-rs",
"walkdir",
]
@@ -2389,7 +2396,7 @@ version = "0.0.0"
dependencies = [
"anyhow",
"codex-execpolicy",
"codex-git",
"codex-git-utils",
"codex-utils-absolute-path",
"codex-utils-image",
"icu_decimal",
@@ -2484,6 +2491,7 @@ dependencies = [
"age",
"anyhow",
"base64 0.22.1",
"codex-git-utils",
"codex-keyring-store",
"keyring",
"pretty_assertions",
@@ -2554,6 +2562,7 @@ dependencies = [
"anyhow",
"chrono",
"clap",
"codex-git-utils",
"codex-protocol",
"dirs",
"log",
@@ -2620,6 +2629,7 @@ dependencies = [
"codex-features",
"codex-feedback",
"codex-file-search",
"codex-git-utils",
"codex-login",
"codex-otel",
"codex-protocol",
@@ -2713,6 +2723,7 @@ dependencies = [
"codex-features",
"codex-feedback",
"codex-file-search",
"codex-git-utils",
"codex-login",
"codex-otel",
"codex-protocol",
@@ -3836,7 +3847,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users 0.5.2",
"windows-sys 0.59.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -4081,7 +4092,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -5526,7 +5537,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -6289,7 +6300,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -6933,7 +6944,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
dependencies = [
"libc",
"windows-sys 0.45.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -8355,7 +8366,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.11.0",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -9788,7 +9799,7 @@ dependencies = [
"getrandom 0.3.4",
"once_cell",
"rustix 1.1.3",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -11234,7 +11245,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.61.2",
]
[[package]]
+2 -2
View File
@@ -50,7 +50,7 @@ members = [
"v8-poc",
"utils/absolute-path",
"utils/cargo-bin",
"utils/git",
"git-utils",
"utils/cache",
"utils/image",
"utils/json-to-toml",
@@ -117,7 +117,7 @@ codex-experimental-api-macros = { path = "codex-experimental-api-macros" }
codex-feedback = { path = "feedback" }
codex-features = { path = "features" }
codex-file-search = { path = "file-search" }
codex-git = { path = "utils/git" }
codex-git-utils = { path = "git-utils" }
codex-hooks = { path = "hooks" }
codex-keyring-store = { path = "keyring-store" }
codex-linux-sandbox = { path = "linux-sandbox" }
+2 -1
View File
@@ -14,8 +14,9 @@ workspace = true
[dependencies]
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive"] }
codex-protocol = { workspace = true }
codex-experimental-api-macros = { workspace = true }
codex-git-utils = { workspace = true }
codex-protocol = { workspace = true }
codex-utils-absolute-path = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }
+1
View File
@@ -4,6 +4,7 @@ mod jsonrpc_lite;
mod protocol;
mod schema_fixtures;
pub use codex_git_utils::GitSha;
pub use experimental_api::*;
pub use export::GenerateTsOptions;
pub use export::generate_internal_json_schema;
@@ -14,16 +14,6 @@ use serde::Serialize;
use strum_macros::Display;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, TS)]
#[ts(type = "string")]
pub struct GitSha(pub String);
impl GitSha {
pub fn new(sha: &str) -> Self {
Self(sha.to_string())
}
}
/// Authentication mode for OpenAI-backed providers.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
@@ -1,6 +1,7 @@
use std::collections::HashMap;
use std::path::PathBuf;
use codex_git_utils::GitSha;
use codex_protocol::ThreadId;
use codex_protocol::config_types::ForcedLoginMethod;
use codex_protocol::config_types::ReasoningSummary;
@@ -21,7 +22,6 @@ use serde::Serialize;
use ts_rs::TS;
use crate::protocol::common::AuthMode;
use crate::protocol::common::GitSha;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
+1
View File
@@ -34,6 +34,7 @@ codex-cloud-requirements = { workspace = true }
codex-core = { workspace = true }
codex-exec-server = { workspace = true }
codex-features = { workspace = true }
codex-git-utils = { workspace = true }
codex-otel = { workspace = true }
codex-shell-command = { workspace = true }
codex-utils-cli = { workspace = true }
@@ -213,7 +213,6 @@ use codex_core::find_archived_thread_path_by_id_str;
use codex_core::find_thread_name_by_id;
use codex_core::find_thread_names_by_ids;
use codex_core::find_thread_path_by_id_str;
use codex_core::git_info::git_diff_to_remote;
use codex_core::mcp::auth::discover_supported_scopes;
use codex_core::mcp::auth::resolve_oauth_scopes;
use codex_core::mcp::collect_mcp_snapshot;
@@ -243,6 +242,7 @@ use codex_features::FEATURES;
use codex_features::Feature;
use codex_features::Stage;
use codex_feedback::CodexFeedback;
use codex_git_utils::git_diff_to_remote;
use codex_login::ServerOptions as LoginServerOptions;
use codex_login::ShutdownHandle;
use codex_login::auth::login_with_chatgpt_auth_tokens;
@@ -8224,7 +8224,7 @@ fn extract_conversation_summary(
fn map_git_info(git_info: &CoreGitInfo) -> ConversationGitInfo {
ConversationGitInfo {
sha: git_info.commit_hash.clone(),
sha: git_info.commit_hash.as_ref().map(|sha| sha.0.clone()),
branch: git_info.branch.clone(),
origin_url: git_info.repository_url.clone(),
}
@@ -23,6 +23,7 @@ use codex_app_server_protocol::TurnStartParams;
use codex_app_server_protocol::TurnStartResponse;
use codex_app_server_protocol::UserInput;
use codex_core::ARCHIVED_SESSIONS_SUBDIR;
use codex_git_utils::GitSha;
use codex_protocol::ThreadId;
use codex_protocol::protocol::GitInfo as CoreGitInfo;
use codex_protocol::protocol::RolloutItem;
@@ -959,7 +960,7 @@ async fn thread_list_includes_git_info() -> Result<()> {
create_minimal_config(codex_home.path())?;
let git_info = CoreGitInfo {
commit_hash: Some("abc123".to_string()),
commit_hash: Some(GitSha::new("abc123")),
branch: Some("main".to_string()),
repository_url: Some("https://example.com/repo.git".to_string()),
};
@@ -20,6 +20,7 @@ use codex_app_server_protocol::ThreadStartResponse;
use codex_app_server_protocol::ThreadStatus;
use codex_core::ARCHIVED_SESSIONS_SUBDIR;
use codex_core::state_db::reconcile_rollout;
use codex_git_utils::GitSha;
use codex_protocol::ThreadId;
use codex_protocol::protocol::GitInfo as RolloutGitInfo;
use codex_state::StateRuntime;
@@ -378,7 +379,7 @@ async fn thread_metadata_update_can_clear_stored_git_fields() -> Result<()> {
"Thread preview",
Some("mock_provider"),
Some(RolloutGitInfo {
commit_hash: Some("abc123".to_string()),
commit_hash: Some(GitSha::new("abc123")),
branch: Some("feature/sidebar-pr".to_string()),
repository_url: Some("git@example.com:openai/codex.git".to_string()),
}),
+1 -1
View File
@@ -17,7 +17,7 @@ codex-utils-cargo-bin = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["full"] }
codex-git = { workspace = true }
codex-git-utils = { workspace = true }
[dev-dependencies]
pretty_assertions = { workspace = true }
+4 -2
View File
@@ -2,6 +2,8 @@ use std::path::PathBuf;
use clap::Parser;
use codex_core::config::Config;
use codex_git_utils::ApplyGitRequest;
use codex_git_utils::apply_git_patch;
use codex_utils_cli::CliConfigOverrides;
use crate::chatgpt_token::init_chatgpt_token_from_auth;
@@ -57,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::ApplyGitRequest {
let req = ApplyGitRequest {
cwd,
diff: diff.to_string(),
revert: false,
preflight: false,
};
let res = codex_git::apply_git_patch(&req)?;
let res = apply_git_patch(&req)?;
if res.exit_code != 0 {
anyhow::bail!(
"Git apply failed (applied={}, skipped={}, conflicts={})\nstdout:\n{}\nstderr:\n{}",
+1 -1
View File
@@ -25,4 +25,4 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2.0.17"
codex-backend-client = { path = "../backend-client", optional = true }
codex-git = { workspace = true }
codex-git-utils = { workspace = true }
+4 -2
View File
@@ -16,6 +16,8 @@ use chrono::Utc;
use codex_backend_client as backend;
use codex_backend_client::CodeTaskDetailsResponseExt;
use codex_git_utils::ApplyGitRequest;
use codex_git_utils::apply_git_patch;
#[derive(Clone)]
pub struct HttpClient {
@@ -459,13 +461,13 @@ mod api {
});
}
let req = codex_git::ApplyGitRequest {
let req = ApplyGitRequest {
cwd: std::env::current_dir().unwrap_or_else(|_| std::env::temp_dir()),
diff: diff.clone(),
revert: false,
preflight,
};
let r = codex_git::apply_git_patch(&req)
let r = apply_git_patch(&req)
.map_err(|e| CloudTaskError::Io(format!("git apply failed to run: {e}")))?;
let status = if r.exit_code == 0 {
+1 -1
View File
@@ -27,4 +27,4 @@ pub use mock::MockClient;
#[cfg(feature = "online")]
pub use http::HttpClient;
// Reusable apply engine now lives in the shared crate `codex-git`.
// Reusable apply engine now lives in the shared crate `codex-git-utils`.
+1
View File
@@ -22,6 +22,7 @@ codex-cloud-tasks-client = { path = "../cloud-tasks-client", features = [
] }
codex-client = { workspace = true }
codex-core = { path = "../core" }
codex-git-utils = { workspace = true }
codex-login = { path = "../login" }
codex-tui = { path = "../tui" }
codex-utils-cli = { workspace = true }
+4 -2
View File
@@ -10,6 +10,8 @@ pub use cli::Cli;
use anyhow::anyhow;
use chrono::Utc;
use codex_cloud_tasks_client::TaskStatus;
use codex_git_utils::current_branch_name;
use codex_git_utils::default_branch_name;
use owo_colors::OwoColorize;
use owo_colors::Stream;
use std::cmp::Ordering;
@@ -119,11 +121,11 @@ struct RealGitInfo;
#[async_trait::async_trait]
impl GitInfoProvider for RealGitInfo {
async fn default_branch_name(&self, path: &std::path::Path) -> Option<String> {
codex_core::git_info::default_branch_name(path).await
default_branch_name(path).await
}
async fn current_branch_name(&self, path: &std::path::Path) -> Option<String> {
codex_core::git_info::current_branch_name(path).await
current_branch_name(path).await
}
}
+1 -1
View File
@@ -41,7 +41,7 @@ codex-shell-command = { workspace = true }
codex-skills = { workspace = true }
codex-execpolicy = { workspace = true }
codex-file-search = { workspace = true }
codex-git = { workspace = true }
codex-git-utils = { workspace = true }
codex-hooks = { workspace = true }
codex-network-proxy = { workspace = true }
codex-otel = { workspace = true }
+2 -2
View File
@@ -1,9 +1,9 @@
use crate::AuthManager;
use crate::config::Config;
use crate::default_client::create_client;
use crate::git_info::collect_git_info;
use crate::git_info::get_git_repo_root;
use crate::plugins::PluginTelemetryMetadata;
use codex_git_utils::collect_git_info;
use codex_git_utils::get_git_repo_root;
use codex_protocol::protocol::SkillScope;
use serde::Serialize;
use sha1::Digest;
+1 -1
View File
@@ -235,7 +235,6 @@ pub(crate) struct PreviousTurnSettings {
use crate::exec_policy::ExecPolicyUpdateError;
use crate::feedback_tags;
use crate::git_info::get_git_repo_root;
use crate::guardian::GuardianReviewSessionManager;
use crate::hook_runtime::PendingInputHookDisposition;
use crate::hook_runtime::inspect_pending_input;
@@ -350,6 +349,7 @@ use crate::unified_exec::UnifiedExecProcessManager;
use crate::util::backoff;
use crate::windows_sandbox::WindowsSandboxLevelExt;
use codex_async_utils::OrCancelExt;
use codex_git_utils::get_git_repo_root;
use codex_otel::SessionTelemetry;
use codex_otel::TelemetryAuthMode;
use codex_otel::metrics::names::THREAD_STARTED_METRIC;
+2 -2
View File
@@ -39,7 +39,6 @@ use crate::config_loader::McpServerRequirement;
use crate::config_loader::ResidencyRequirement;
use crate::config_loader::Sourced;
use crate::config_loader::load_config_layers_state;
use crate::git_info::resolve_root_git_project_for_trust;
use crate::memories::memory_root;
use crate::model_provider_info::LEGACY_OLLAMA_CHAT_PROVIDER_ID;
use crate::model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
@@ -66,6 +65,7 @@ use codex_features::FeatureConfigSource;
use codex_features::FeatureOverrides;
use codex_features::Features;
use codex_features::FeaturesToml;
use codex_git_utils::resolve_root_git_project_for_trust;
use codex_protocol::config_types::AltScreenMode;
use codex_protocol::config_types::ForcedLoginMethod;
use codex_protocol::config_types::Personality;
@@ -134,7 +134,7 @@ pub use service::ConfigService;
pub use service::ConfigServiceError;
pub use types::ApprovalsReviewer;
pub use codex_git::GhostSnapshotConfig;
pub use codex_git_utils::GhostSnapshotConfig;
/// Maximum number of bytes of the documentation that will be embedded. Larger
/// files are *silently truncated* to this size so we do not take up too much of
+1 -1
View File
@@ -7,10 +7,10 @@ mod tests;
use crate::config::ConfigToml;
use crate::config_loader::layer_io::LoadedConfigLayers;
use crate::git_info::resolve_root_git_project_for_trust;
use codex_app_server_protocol::ConfigLayerSource;
use codex_config::CONFIG_TOML_FILE;
use codex_config::ConfigRequirementsWithSources;
use codex_git_utils::resolve_root_git_project_for_trust;
use codex_protocol::config_types::SandboxMode;
use codex_protocol::config_types::TrustLevel;
use codex_protocol::protocol::AskForApproval;
@@ -3,7 +3,7 @@ use crate::truncate;
use crate::truncate::TruncationPolicy;
use base64::Engine;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use codex_git::GhostCommit;
use codex_git_utils::GhostCommit;
use codex_protocol::AgentPath;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::models::BaseInstructions;
+10 -4
View File
@@ -1,9 +1,15 @@
use super::*;
use codex_git_utils::GitInfo;
use codex_git_utils::GitSha;
use codex_git_utils::collect_git_info;
use codex_git_utils::get_has_changes;
use codex_git_utils::git_diff_to_remote;
use codex_git_utils::recent_commits;
use codex_git_utils::resolve_root_git_project_for_trust;
use core_test_support::skip_if_sandbox;
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
use tokio::process::Command;
// Helper function to create a test git repository
async fn create_test_git_repo(temp_dir: &TempDir) -> PathBuf {
@@ -191,7 +197,7 @@ async fn test_collect_git_info_git_repository() {
// Should have commit hash
assert!(git_info.commit_hash.is_some());
let commit_hash = git_info.commit_hash.unwrap();
let commit_hash = git_info.commit_hash.unwrap().0;
assert_eq!(commit_hash.len(), 40); // SHA-1 hash should be 40 characters
assert!(commit_hash.chars().all(|c| c.is_ascii_hexdigit()));
@@ -558,7 +564,7 @@ async fn test_get_git_working_tree_state_unpushed_commit() {
#[test]
fn test_git_info_serialization() {
let git_info = GitInfo {
commit_hash: Some("abc123def456".to_string()),
commit_hash: Some(GitSha::new("abc123def456")),
branch: Some("main".to_string()),
repository_url: Some("https://github.com/example/repo.git".to_string()),
};
+2 -1
View File
@@ -41,7 +41,8 @@ mod exec_policy;
pub mod external_agent_config;
pub mod file_watcher;
mod flags;
pub mod git_info;
#[cfg(test)]
mod git_info_tests;
mod guardian;
mod hook_runtime;
pub mod instructions;
+1 -1
View File
@@ -2,9 +2,9 @@ use super::PluginManifestInterface;
use super::load_plugin_manifest;
use super::store::PluginId;
use super::store::PluginIdError;
use crate::git_info::get_git_repo_root;
use codex_app_server_protocol::PluginAuthPolicy;
use codex_app_server_protocol::PluginInstallPolicy;
use codex_git_utils::get_git_repo_root;
use codex_protocol::protocol::Product;
use codex_utils_absolute_path::AbsolutePathBuf;
use dirs::home_dir;
+1 -1
View File
@@ -1,10 +1,10 @@
use crate::codex::Session;
use crate::compact::content_items_to_text;
use crate::event_mapping::is_contextual_user_message_content;
use crate::git_info::resolve_root_git_project_for_trust;
use crate::truncate::TruncationPolicy;
use crate::truncate::truncate_text;
use chrono::Utc;
use codex_git_utils::resolve_root_git_project_for_trust;
use codex_protocol::models::ResponseItem;
use codex_state::SortKey;
use codex_state::ThreadMetadata;
+1 -1
View File
@@ -1,4 +1,4 @@
use codex_git::merge_base_with_head;
use codex_git_utils::merge_base_with_head;
use codex_protocol::protocol::ReviewRequest;
use codex_protocol::protocol::ReviewTarget;
use std::path::Path;
+1 -1
View File
@@ -1039,7 +1039,7 @@ async fn read_head_summary(path: &Path, head_limit: usize) -> io::Result<HeadTai
summary.git_sha = session_meta_line
.git
.as_ref()
.and_then(|git| git.commit_hash.clone());
.and_then(|git| git.commit_hash.as_ref().map(|sha| sha.0.clone()));
summary.git_origin_url = session_meta_line
.git
.as_ref()
+1 -1
View File
@@ -55,7 +55,7 @@ pub(crate) fn builder_from_session_meta(
builder.sandbox_policy = SandboxPolicy::new_read_only_policy();
builder.approval_mode = AskForApproval::OnRequest;
if let Some(git) = session_meta.git.as_ref() {
builder.git_sha = git.commit_hash.clone();
builder.git_sha = git.commit_hash.as_ref().map(|sha| sha.0.clone());
builder.git_branch = git.branch.clone();
builder.git_origin_url = git.repository_url.clone();
}
+1 -1
View File
@@ -245,7 +245,7 @@ async fn backfill_sessions_preserves_existing_git_branch_and_fills_missing_git_f
"2026-01-27T12:34:56Z",
thread_uuid,
Some(GitInfo {
commit_hash: Some("rollout-sha".to_string()),
commit_hash: Some(codex_git_utils::GitSha::new("rollout-sha")),
branch: Some("rollout-branch".to_string()),
repository_url: Some("git@example.com:openai/codex.git".to_string()),
}),
+7 -2
View File
@@ -41,13 +41,14 @@ use super::policy::EventPersistenceMode;
use super::policy::is_persisted_response_item;
use crate::config::Config;
use crate::default_client::originator;
use crate::git_info::collect_git_info;
use crate::path_utils;
use crate::state_db;
use crate::state_db::StateDbHandle;
use crate::truncate::TruncationPolicy;
use crate::truncate::truncate_text;
use codex_git_utils::collect_git_info;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::GitInfo as ProtocolGitInfo;
use codex_protocol::protocol::InitialHistory;
use codex_protocol::protocol::ResumedHistory;
use codex_protocol::protocol::RolloutItem;
@@ -846,7 +847,11 @@ async fn write_session_meta(
default_provider: &str,
generate_memories: bool,
) -> std::io::Result<()> {
let git_info = collect_git_info(cwd).await;
let git_info = collect_git_info(cwd).await.map(|info| ProtocolGitInfo {
commit_hash: info.commit_hash,
branch: info.branch,
repository_url: info.repository_url,
});
let session_meta_line = SessionMetaLine {
meta: session_meta,
git: git_info,
+4 -4
View File
@@ -5,10 +5,10 @@ use crate::state::TaskKind;
use crate::tasks::SessionTask;
use crate::tasks::SessionTaskContext;
use async_trait::async_trait;
use codex_git::CreateGhostCommitOptions;
use codex_git::GhostSnapshotReport;
use codex_git::GitToolingError;
use codex_git::create_ghost_commit_with_report;
use codex_git_utils::CreateGhostCommitOptions;
use codex_git_utils::GhostSnapshotReport;
use codex_git_utils::GitToolingError;
use codex_git_utils::create_ghost_commit_with_report;
use codex_protocol::models::ResponseItem;
use codex_protocol::user_input::UserInput;
use codex_utils_readiness::Readiness;
@@ -1,5 +1,5 @@
use super::*;
use codex_git::LargeUntrackedDir;
use codex_git_utils::LargeUntrackedDir;
use pretty_assertions::assert_eq;
use std::path::PathBuf;
+2 -2
View File
@@ -8,8 +8,8 @@ use crate::state::TaskKind;
use crate::tasks::SessionTask;
use crate::tasks::SessionTaskContext;
use async_trait::async_trait;
use codex_git::RestoreGhostCommitOptions;
use codex_git::restore_ghost_commit_with_options;
use codex_git_utils::RestoreGhostCommitOptions;
use codex_git_utils::restore_ghost_commit_with_options;
use codex_protocol::models::ResponseItem;
use codex_protocol::user_input::UserInput;
use tokio_util::sync::CancellationToken;
+8 -6
View File
@@ -8,11 +8,11 @@ use std::sync::RwLock;
use serde::Serialize;
use tokio::task::JoinHandle;
use crate::git_info::get_git_remote_urls_assume_git_repo;
use crate::git_info::get_git_repo_root;
use crate::git_info::get_has_changes;
use crate::git_info::get_head_commit_hash;
use crate::sandbox_tags::sandbox_tag;
use codex_git_utils::get_git_remote_urls_assume_git_repo;
use codex_git_utils::get_git_repo_root;
use codex_git_utils::get_has_changes;
use codex_git_utils::get_head_commit_hash;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::protocol::SandboxPolicy;
@@ -94,11 +94,12 @@ fn build_turn_metadata_bag(
pub async fn build_turn_metadata_header(cwd: &Path, sandbox: Option<&str>) -> Option<String> {
let repo_root = get_git_repo_root(cwd).map(|root| root.to_string_lossy().into_owned());
let (latest_git_commit_hash, associated_remote_urls, has_changes) = tokio::join!(
let (head_commit_hash, associated_remote_urls, has_changes) = tokio::join!(
get_head_commit_hash(cwd),
get_git_remote_urls_assume_git_repo(cwd),
get_has_changes(cwd),
);
let latest_git_commit_hash = head_commit_hash.map(|sha| sha.0);
if latest_git_commit_hash.is_none()
&& associated_remote_urls.is_none()
&& has_changes.is_none()
@@ -231,11 +232,12 @@ impl TurnMetadataState {
}
async fn fetch_workspace_git_metadata(&self) -> WorkspaceGitMetadata {
let (latest_git_commit_hash, associated_remote_urls, has_changes) = tokio::join!(
let (head_commit_hash, associated_remote_urls, has_changes) = tokio::join!(
get_head_commit_hash(&self.cwd),
get_git_remote_urls_assume_git_repo(&self.cwd),
get_has_changes(&self.cwd),
);
let latest_git_commit_hash = head_commit_hash.map(|sha| sha.0);
WorkspaceGitMetadata {
associated_remote_urls,
+3 -2
View File
@@ -1,5 +1,6 @@
use assert_cmd::Command as AssertCommand;
use codex_core::auth::CODEX_API_KEY_ENV_VAR;
use codex_git_utils::collect_git_info;
use codex_protocol::protocol::GitInfo;
use codex_utils_cargo_bin::find_resource;
use core_test_support::fs_wait;
@@ -596,7 +597,7 @@ async fn integration_git_info_unit_test() {
.unwrap();
// 3. Test git info collection directly
let git_info = codex_core::git_info::collect_git_info(&git_repo).await;
let git_info = collect_git_info(&git_repo).await;
// 4. Verify git info is present and contains expected data
assert!(git_info.is_some(), "Git info should be collected");
@@ -608,7 +609,7 @@ async fn integration_git_info_unit_test() {
git_info.commit_hash.is_some(),
"Git info should contain commit_hash"
);
let commit_hash = git_info.commit_hash.as_ref().unwrap();
let commit_hash = &git_info.commit_hash.as_ref().unwrap().0;
assert_eq!(commit_hash.len(), 40, "Commit hash should be 40 characters");
assert!(
commit_hash.chars().all(|c| c.is_ascii_hexdigit()),
+1
View File
@@ -29,6 +29,7 @@ codex-app-server-protocol = { workspace = true }
codex-cloud-requirements = { workspace = true }
codex-core = { workspace = true }
codex-feedback = { workspace = true }
codex-git-utils = { workspace = true }
codex-otel = { workspace = true }
codex-protocol = { workspace = true }
codex-utils-absolute-path = { workspace = true }
+1 -1
View File
@@ -64,9 +64,9 @@ use codex_core::config_loader::ConfigLoadError;
use codex_core::config_loader::LoaderOverrides;
use codex_core::config_loader::format_config_error_with_source;
use codex_core::format_exec_policy_error_with_source;
use codex_core::git_info::get_git_repo_root;
use codex_core::path_utils;
use codex_feedback::CodexFeedback;
use codex_git_utils::get_git_repo_root;
use codex_otel::set_parent_from_context;
use codex_otel::traceparent_context_from_env;
use codex_protocol::config_types::SandboxMode;
@@ -1,6 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "git",
crate_name = "codex_git",
name = "git-utils",
crate_name = "codex_git_utils",
)
@@ -1,5 +1,5 @@
[package]
name = "codex-git"
name = "codex-git-utils"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -9,12 +9,15 @@ readme = "README.md"
workspace = true
[dependencies]
codex-utils-absolute-path = { workspace = true }
futures = { workspace = true, features = ["alloc"] }
once_cell = { workspace = true }
regex = "1"
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }
tempfile = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["macros", "process", "rt", "time"] }
ts-rs = { workspace = true, features = [
"uuid-impl",
"serde-json-impl",
@@ -1,4 +1,4 @@
# codex-git
# codex-git-utils
Helpers for interacting with git, including patch application and worktree
snapshot utilities.
@@ -6,7 +6,7 @@ snapshot utilities.
```rust,no_run
use std::path::Path;
use codex_git::{
use codex_git_utils::{
apply_git_patch, create_ghost_commit, restore_ghost_commit, ApplyGitRequest,
CreateGhostCommitOptions,
};
@@ -4,15 +4,17 @@ use std::ffi::OsStr;
use std::path::Path;
use std::path::PathBuf;
use crate::util::resolve_path;
use codex_app_server_protocol::GitSha;
use codex_protocol::protocol::GitInfo;
use codex_utils_absolute_path::AbsolutePathBuf;
use futures::future::join_all;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use tokio::process::Command;
use tokio::time::Duration as TokioDuration;
use tokio::time::timeout;
use ts_rs::TS;
use crate::GitSha;
/// Return `true` if the project folder specified by the `Config` is inside a
/// Git repository.
@@ -38,6 +40,19 @@ pub fn get_git_repo_root(base_dir: &Path) -> Option<PathBuf> {
/// Timeout for git commands to prevent freezing on large repositories
const GIT_COMMAND_TIMEOUT: TokioDuration = TokioDuration::from_secs(5);
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)]
pub struct GitInfo {
/// Current commit hash (SHA)
#[serde(skip_serializing_if = "Option::is_none")]
pub commit_hash: Option<GitSha>,
/// Current branch name
#[serde(skip_serializing_if = "Option::is_none")]
pub branch: Option<String>,
/// Repository URL (if available from remote)
#[serde(skip_serializing_if = "Option::is_none")]
pub repository_url: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct GitDiffToRemote {
pub sha: GitSha,
@@ -77,7 +92,7 @@ pub async fn collect_git_info(cwd: &Path) -> Option<GitInfo> {
&& output.status.success()
&& let Ok(hash) = String::from_utf8(output.stdout)
{
git_info.commit_hash = Some(hash.trim().to_string());
git_info.commit_hash = Some(GitSha::new(hash.trim()));
}
// Process branch name
@@ -127,7 +142,7 @@ pub async fn get_git_remote_urls_assume_git_repo(cwd: &Path) -> Option<BTreeMap<
}
/// Return the current HEAD commit hash without checking whether `cwd` is in a git repo.
pub async fn get_head_commit_hash(cwd: &Path) -> Option<String> {
pub async fn get_head_commit_hash(cwd: &Path) -> Option<GitSha> {
let output = run_git_command_with_timeout(&["rev-parse", "HEAD"], cwd).await?;
if !output.status.success() {
return None;
@@ -138,7 +153,7 @@ pub async fn get_head_commit_hash(cwd: &Path) -> Option<String> {
if hash.is_empty() {
None
} else {
Some(hash.to_string())
Some(GitSha::new(hash))
}
}
@@ -616,7 +631,11 @@ pub fn resolve_root_git_project_for_trust(cwd: &Path) -> Option<PathBuf> {
return None;
}
let git_dir_path = canonicalize_or_raw(resolve_path(&repo_root, &PathBuf::from(git_dir_rel)));
let git_dir_path = canonicalize_or_raw(
AbsolutePathBuf::resolve_path_against_base(git_dir_rel, &repo_root)
.ok()?
.into_path_buf(),
);
let worktrees_dir = git_dir_path.parent()?;
if worktrees_dir.file_name() != Some(OsStr::new("worktrees")) {
return None;
@@ -689,7 +708,3 @@ pub async fn current_branch_name(cwd: &Path) -> Option<String> {
.map(|s| s.trim().to_string())
.filter(|name| !name.is_empty())
}
#[cfg(test)]
#[path = "git_info_tests.rs"]
mod tests;
@@ -5,6 +5,7 @@ mod apply;
mod branch;
mod errors;
mod ghost_commits;
mod info;
mod operations;
mod platform;
@@ -28,6 +29,21 @@ pub use ghost_commits::create_ghost_commit_with_report;
pub use ghost_commits::restore_ghost_commit;
pub use ghost_commits::restore_ghost_commit_with_options;
pub use ghost_commits::restore_to_commit;
pub use info::CommitLogEntry;
pub use info::GitDiffToRemote;
pub use info::GitInfo;
pub use info::collect_git_info;
pub use info::current_branch_name;
pub use info::default_branch_name;
pub use info::get_git_remote_urls;
pub use info::get_git_remote_urls_assume_git_repo;
pub use info::get_git_repo_root;
pub use info::get_has_changes;
pub use info::get_head_commit_hash;
pub use info::git_diff_to_remote;
pub use info::local_git_branches;
pub use info::recent_commits;
pub use info::resolve_root_git_project_for_trust;
pub use platform::create_symlink;
use schemars::JsonSchema;
use serde::Deserialize;
@@ -36,6 +52,17 @@ use ts_rs::TS;
type CommitID = String;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, TS)]
#[serde(transparent)]
#[ts(type = "string")]
pub struct GitSha(pub String);
impl GitSha {
pub fn new(sha: &str) -> Self {
Self(sha.to_string())
}
}
/// Details of a ghost commit created from a repository state.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, TS)]
pub struct GhostCommit {
+1 -1
View File
@@ -13,7 +13,7 @@ workspace = true
[dependencies]
codex-execpolicy = { workspace = true }
codex-git = { workspace = true }
codex-git-utils = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-utils-image = { workspace = true }
icu_decimal = { workspace = true }
+1 -1
View File
@@ -23,7 +23,7 @@ use crate::protocol::SandboxPolicy;
use crate::protocol::WritableRoot;
use crate::user_input::UserInput;
use codex_execpolicy::Policy;
use codex_git::GhostCommit;
use codex_git_utils::GhostCommit;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_image::error::ImageProcessingError;
use schemars::JsonSchema;
+2 -1
View File
@@ -49,6 +49,7 @@ use crate::request_permissions::RequestPermissionsEvent;
use crate::request_permissions::RequestPermissionsResponse;
use crate::request_user_input::RequestUserInputResponse;
use crate::user_input::UserInput;
use codex_git_utils::GitSha;
use codex_utils_absolute_path::AbsolutePathBuf;
use schemars::JsonSchema;
use serde::Deserialize;
@@ -2639,7 +2640,7 @@ pub struct RolloutLine {
pub struct GitInfo {
/// Current commit hash (SHA)
#[serde(skip_serializing_if = "Option::is_none")]
pub commit_hash: Option<String>,
pub commit_hash: Option<GitSha>,
/// Current branch name
#[serde(skip_serializing_if = "Option::is_none")]
pub branch: Option<String>,
+1
View File
@@ -11,6 +11,7 @@ workspace = true
age = { workspace = true }
anyhow = { workspace = true }
base64 = { workspace = true }
codex-git-utils = { workspace = true }
codex-keyring-store = { workspace = true }
rand = { workspace = true }
regex = { workspace = true }
+1 -16
View File
@@ -4,6 +4,7 @@ use std::path::PathBuf;
use std::sync::Arc;
use anyhow::Result;
use codex_git_utils::get_git_repo_root;
use codex_keyring_store::DefaultKeyringStore;
use codex_keyring_store::KeyringStore;
use schemars::JsonSchema;
@@ -161,22 +162,6 @@ pub fn environment_id_from_cwd(cwd: &Path) -> String {
format!("cwd-{short}")
}
fn get_git_repo_root(base_dir: &Path) -> Option<PathBuf> {
let mut dir = base_dir.to_path_buf();
loop {
if dir.join(".git").exists() {
return Some(dir);
}
if !dir.pop() {
break;
}
}
None
}
pub(crate) fn compute_keyring_account(codex_home: &Path) -> String {
let canonical = codex_home
.canonicalize()
+1
View File
@@ -22,6 +22,7 @@ tracing-subscriber = { workspace = true }
uuid = { workspace = true }
[dev-dependencies]
codex-git-utils = { workspace = true }
pretty_assertions = { workspace = true }
[lints]
+1 -1
View File
@@ -61,7 +61,7 @@ fn apply_session_meta_from_item(metadata: &mut ThreadMetadata, meta_line: &Sessi
metadata.cwd = meta_line.meta.cwd.clone();
}
if let Some(git) = meta_line.git.as_ref() {
metadata.git_sha = git.commit_hash.clone();
metadata.git_sha = git.commit_hash.as_ref().map(|sha| sha.0.clone());
metadata.git_branch = git.branch.clone();
metadata.git_origin_url = git.repository_url.clone();
}
+1 -1
View File
@@ -1089,7 +1089,7 @@ mod tests {
memory_mode: None,
},
git: Some(GitInfo {
commit_hash: Some("rollout-sha".to_string()),
commit_hash: Some(codex_git_utils::GitSha::new("rollout-sha")),
branch: Some("rollout-branch".to_string()),
repository_url: Some("git@example.com:openai/codex.git".to_string()),
}),
+1
View File
@@ -40,6 +40,7 @@ codex-core = { workspace = true }
codex-features = { workspace = true }
codex-feedback = { workspace = true }
codex-file-search = { workspace = true }
codex-git-utils = { workspace = true }
codex-login = { workspace = true }
codex-otel = { workspace = true }
codex-protocol = { workspace = true }
+8 -5
View File
@@ -69,9 +69,6 @@ use codex_core::config::types::Notifications;
use codex_core::config::types::WindowsSandboxModeToml;
use codex_core::config_loader::ConfigLayerStackOrdering;
use codex_core::find_thread_name_by_id;
use codex_core::git_info::current_branch_name;
use codex_core::git_info::get_git_repo_root;
use codex_core::git_info::local_git_branches;
use codex_core::mcp::McpManager;
use codex_core::models_manager::manager::ModelsManager;
use codex_core::plugins::PluginsManager;
@@ -81,6 +78,12 @@ use codex_core::skills::model::SkillMetadata;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_features::FEATURES;
use codex_features::Feature;
#[cfg(test)]
use codex_git_utils::CommitLogEntry;
use codex_git_utils::current_branch_name;
use codex_git_utils::get_git_repo_root;
use codex_git_utils::local_git_branches;
use codex_git_utils::recent_commits;
use codex_otel::RuntimeMetricsSummary;
use codex_otel::SessionTelemetry;
use codex_protocol::ThreadId;
@@ -9246,7 +9249,7 @@ impl ChatWidget {
}
pub(crate) async fn show_review_commit_picker(&mut self, cwd: &Path) {
let commits = codex_core::git_info::recent_commits(cwd, /*limit*/ 100).await;
let commits = recent_commits(cwd, /*limit*/ 100).await;
let mut items: Vec<SelectionItem> = Vec::with_capacity(commits.len());
for entry in commits {
@@ -9648,7 +9651,7 @@ async fn fetch_rate_limits(base_url: String, auth: CodexAuth) -> Vec<RateLimitSn
#[cfg(test)]
pub(crate) fn show_review_commit_picker_with_entries(
chat: &mut ChatWidget,
entries: Vec<codex_core::git_info::CommitLogEntry>,
entries: Vec<CommitLogEntry>,
) {
let mut items: Vec<SelectionItem> = Vec::with_capacity(entries.len());
for entry in entries {
+3 -2
View File
@@ -51,6 +51,7 @@ use codex_core::plugins::OPENAI_CURATED_MARKETPLACE_NAME;
use codex_core::skills::model::SkillMetadata;
use codex_features::FEATURES;
use codex_features::Feature;
use codex_git_utils::CommitLogEntry;
use codex_otel::RuntimeMetricsSummary;
use codex_otel::SessionTelemetry;
use codex_protocol::ThreadId;
@@ -6725,12 +6726,12 @@ async fn review_commit_picker_shows_subjects_without_timestamps() {
// Show commit picker with synthetic entries.
let entries = vec![
codex_core::git_info::CommitLogEntry {
CommitLogEntry {
sha: "1111111deadbeef".to_string(),
timestamp: 0,
subject: "Add new feature X".to_string(),
},
codex_core::git_info::CommitLogEntry {
CommitLogEntry {
sha: "2222222cafebabe".to_string(),
timestamp: 0,
subject: "Fix bug Y".to_string(),
+1 -1
View File
@@ -92,7 +92,7 @@ use crate::terminal_palette::default_bg;
use crate::terminal_palette::indexed_color;
use crate::terminal_palette::rgb_color;
use crate::terminal_palette::stdout_color_level;
use codex_core::git_info::get_git_repo_root;
use codex_git_utils::get_git_repo_root;
use codex_protocol::protocol::FileChange;
use codex_terminal_detection::TerminalName;
use codex_terminal_detection::terminal_info;
@@ -1,7 +1,7 @@
use std::path::PathBuf;
use codex_core::config::set_project_trust_level;
use codex_core::git_info::resolve_root_git_project_for_trust;
use codex_git_utils::resolve_root_git_project_for_trust;
use codex_protocol::config_types::TrustLevel;
use crossterm::event::KeyCode;
use crossterm::event::KeyEvent;
+1
View File
@@ -44,6 +44,7 @@ codex-core = { workspace = true }
codex-features = { workspace = true }
codex-feedback = { workspace = true }
codex-file-search = { workspace = true }
codex-git-utils = { workspace = true }
codex-login = { workspace = true }
codex-otel = { workspace = true }
codex-protocol = { workspace = true }
+8 -5
View File
@@ -89,9 +89,6 @@ use codex_core::config::types::Notifications;
use codex_core::config::types::WindowsSandboxModeToml;
use codex_core::config_loader::ConfigLayerStackOrdering;
use codex_core::find_thread_name_by_id;
use codex_core::git_info::current_branch_name;
use codex_core::git_info::get_git_repo_root;
use codex_core::git_info::local_git_branches;
use codex_core::plugins::PluginsManager;
use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME;
use codex_core::skills::model::SkillMetadata;
@@ -99,6 +96,12 @@ use codex_core::skills::model::SkillMetadata;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_features::FEATURES;
use codex_features::Feature;
#[cfg(test)]
use codex_git_utils::CommitLogEntry;
use codex_git_utils::current_branch_name;
use codex_git_utils::get_git_repo_root;
use codex_git_utils::local_git_branches;
use codex_git_utils::recent_commits;
use codex_otel::RuntimeMetricsSummary;
use codex_otel::SessionTelemetry;
use codex_protocol::ThreadId;
@@ -10414,7 +10417,7 @@ impl ChatWidget {
}
pub(crate) async fn show_review_commit_picker(&mut self, cwd: &Path) {
let commits = codex_core::git_info::recent_commits(cwd, /*limit*/ 100).await;
let commits = recent_commits(cwd, /*limit*/ 100).await;
let mut items: Vec<SelectionItem> = Vec::with_capacity(commits.len());
for entry in commits {
@@ -10796,7 +10799,7 @@ fn hook_event_label(event_name: codex_protocol::protocol::HookEventName) -> &'st
#[cfg(test)]
pub(crate) fn show_review_commit_picker_with_entries(
chat: &mut ChatWidget,
entries: Vec<codex_core::git_info::CommitLogEntry>,
entries: Vec<CommitLogEntry>,
) {
let mut items: Vec<SelectionItem> = Vec::with_capacity(entries.len());
for entry in entries {
@@ -74,6 +74,7 @@ use codex_core::plugins::OPENAI_CURATED_MARKETPLACE_NAME;
use codex_core::skills::model::SkillMetadata;
use codex_features::FEATURES;
use codex_features::Feature;
use codex_git_utils::CommitLogEntry;
use codex_otel::RuntimeMetricsSummary;
use codex_otel::SessionTelemetry;
use codex_protocol::ThreadId;
@@ -7322,12 +7323,12 @@ async fn review_commit_picker_shows_subjects_without_timestamps() {
// Show commit picker with synthetic entries.
let entries = vec![
codex_core::git_info::CommitLogEntry {
CommitLogEntry {
sha: "1111111deadbeef".to_string(),
timestamp: 0,
subject: "Add new feature X".to_string(),
},
codex_core::git_info::CommitLogEntry {
CommitLogEntry {
sha: "2222222cafebabe".to_string(),
timestamp: 0,
subject: "Fix bug Y".to_string(),
+1 -1
View File
@@ -92,7 +92,7 @@ use crate::terminal_palette::default_bg;
use crate::terminal_palette::indexed_color;
use crate::terminal_palette::rgb_color;
use crate::terminal_palette::stdout_color_level;
use codex_core::git_info::get_git_repo_root;
use codex_git_utils::get_git_repo_root;
use codex_protocol::protocol::FileChange;
use codex_terminal_detection::TerminalName;
use codex_terminal_detection::terminal_info;
@@ -1,7 +1,7 @@
use std::path::PathBuf;
use codex_core::config::set_project_trust_level;
use codex_core::git_info::resolve_root_git_project_for_trust;
use codex_git_utils::resolve_root_git_project_for_trust;
use codex_protocol::config_types::TrustLevel;
use crossterm::event::KeyCode;
use crossterm::event::KeyEvent;