git: Use correct file mode when staging (#41900)
Closes #28667 Release Notes: - Fixed git not preserving file mode when committing. Now if an input file is executable it will be preserved when committed with Zed. --------- Signed-off-by: 11happy <soni5happy@gmail.com> Signed-off-by: 11happy <bhuminjaysoni@gmail.com> Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
This commit is contained in:
co-authored by
Jakub Konka
parent
00e93bfa11
commit
425d4c73f3
@@ -301,6 +301,7 @@ impl std::ops::Deref for Repository {
|
||||
#[derive(Clone)]
|
||||
pub enum RepositoryState {
|
||||
Local {
|
||||
fs: Arc<dyn Fs>,
|
||||
backend: Arc<dyn GitRepository>,
|
||||
environment: Arc<HashMap<String, String>>,
|
||||
},
|
||||
@@ -4288,6 +4289,7 @@ impl Repository {
|
||||
RepositoryState::Local {
|
||||
backend,
|
||||
environment,
|
||||
..
|
||||
} => backend.run_hook(hook, environment.clone()).await,
|
||||
RepositoryState::Remote { project_id, client } => {
|
||||
client
|
||||
@@ -4580,6 +4582,7 @@ impl Repository {
|
||||
let id = self.id;
|
||||
let this = cx.weak_entity();
|
||||
let git_store = self.git_store.clone();
|
||||
let abs_path = self.snapshot.repo_path_to_abs_path(&path);
|
||||
self.send_keyed_job(
|
||||
Some(GitJobKey::WriteIndex(vec![path.clone()])),
|
||||
None,
|
||||
@@ -4588,14 +4591,21 @@ impl Repository {
|
||||
"start updating index text for buffer {}",
|
||||
path.as_unix_str()
|
||||
);
|
||||
|
||||
match git_repo {
|
||||
RepositoryState::Local {
|
||||
fs,
|
||||
backend,
|
||||
environment,
|
||||
..
|
||||
} => {
|
||||
let executable = match fs.metadata(&abs_path).await {
|
||||
Ok(Some(meta)) => meta.is_executable,
|
||||
Ok(None) => false,
|
||||
Err(_err) => false,
|
||||
};
|
||||
backend
|
||||
.set_index_text(path.clone(), content, environment.clone())
|
||||
.set_index_text(path.clone(), content, environment.clone(), executable)
|
||||
.await?;
|
||||
}
|
||||
RepositoryState::Remote { project_id, client } => {
|
||||
@@ -5164,6 +5174,7 @@ impl Repository {
|
||||
cx: &mut Context<Self>,
|
||||
) -> mpsc::UnboundedSender<GitJob> {
|
||||
let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
|
||||
let fs_cloned = fs.clone();
|
||||
|
||||
cx.spawn(async move |_, cx| {
|
||||
let environment = project_environment
|
||||
@@ -5195,8 +5206,8 @@ impl Repository {
|
||||
backend.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
let state = RepositoryState::Local {
|
||||
fs: fs_cloned,
|
||||
backend,
|
||||
environment: Arc::new(environment),
|
||||
};
|
||||
|
||||
@@ -8174,6 +8174,91 @@ async fn test_single_file_diffs(cx: &mut gpui::TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Should we test this on Windows also?
|
||||
#[gpui::test]
|
||||
#[cfg(not(windows))]
|
||||
async fn test_staging_hunk_preserve_executable_permission(cx: &mut gpui::TestAppContext) {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
init_test(cx);
|
||||
cx.executor().allow_parking();
|
||||
let committed_contents = "bar\n";
|
||||
let file_contents = "baz\n";
|
||||
let root = TempTree::new(json!({
|
||||
"project": {
|
||||
"foo": committed_contents
|
||||
},
|
||||
}));
|
||||
|
||||
let work_dir = root.path().join("project");
|
||||
let file_path = work_dir.join("foo");
|
||||
let repo = git_init(work_dir.as_path());
|
||||
let mut perms = std::fs::metadata(&file_path).unwrap().permissions();
|
||||
perms.set_mode(0o755);
|
||||
std::fs::set_permissions(&file_path, perms).unwrap();
|
||||
git_add("foo", &repo);
|
||||
git_commit("Initial commit", &repo);
|
||||
std::fs::write(&file_path, file_contents).unwrap();
|
||||
|
||||
let project = Project::test(
|
||||
Arc::new(RealFs::new(None, cx.executor())),
|
||||
[root.path()],
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer(file_path.as_path(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
|
||||
|
||||
let uncommitted_diff = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_uncommitted_diff(buffer.clone(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
uncommitted_diff.update(cx, |diff, cx| {
|
||||
let hunks = diff.hunks(&snapshot, cx).collect::<Vec<_>>();
|
||||
diff.stage_or_unstage_hunks(true, &hunks, &snapshot, true, cx);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let output = smol::process::Command::new("git")
|
||||
.current_dir(&work_dir)
|
||||
.args(["diff", "--staged"])
|
||||
.output()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let staged_diff = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
assert!(
|
||||
!staged_diff.contains("new mode 100644"),
|
||||
"Staging should not change file mode from 755 to 644.\ngit diff --staged:\n{}",
|
||||
staged_diff
|
||||
);
|
||||
|
||||
let output = smol::process::Command::new("git")
|
||||
.current_dir(&work_dir)
|
||||
.args(["ls-files", "-s"])
|
||||
.output()
|
||||
.await
|
||||
.unwrap();
|
||||
let index_contents = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
assert!(
|
||||
index_contents.contains("100755"),
|
||||
"Index should show file as executable (100755).\ngit ls-files -s:\n{}",
|
||||
index_contents
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_repository_and_path_for_project_path(
|
||||
background_executor: BackgroundExecutor,
|
||||
|
||||
Reference in New Issue
Block a user