Compare commits
8 Commits
fix-vue-ty
...
git/job-qu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
370a418fb7 | ||
|
|
78879876c1 | ||
|
|
684a58fc84 | ||
|
|
9150346a43 | ||
|
|
425d4c73f3 | ||
|
|
00e93bfa11 | ||
|
|
9d8b5077b4 | ||
|
|
3072133e59 |
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -6406,6 +6406,7 @@ dependencies = [
|
||||
"git",
|
||||
"gpui",
|
||||
"ignore",
|
||||
"is_executable",
|
||||
"libc",
|
||||
"log",
|
||||
"notify 8.2.0",
|
||||
@@ -8436,6 +8437,15 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_executable"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
|
||||
@@ -250,6 +250,7 @@ impl PasswordProxy {
|
||||
.await
|
||||
.with_context(|| format!("creating askpass script at {askpass_script_path:?}"))?;
|
||||
make_file_executable(&askpass_script_path).await?;
|
||||
// todo(shell): There might be no powershell on the system
|
||||
#[cfg(target_os = "windows")]
|
||||
let askpass_helper = format!(
|
||||
"powershell.exe -ExecutionPolicy Bypass -File {}",
|
||||
|
||||
@@ -1850,7 +1850,7 @@ impl Editor {
|
||||
.cursor_pointer()
|
||||
.child("⋯")
|
||||
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
.on_click(move |_, _window, cx| {
|
||||
.on_click(move |_, _win, cx| {
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.unfold_ranges(
|
||||
@@ -6605,7 +6605,7 @@ impl Editor {
|
||||
.when(show_tooltip, |this| {
|
||||
this.tooltip({
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
move |_window, cx| {
|
||||
move |_win, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Toggle Code Actions",
|
||||
&ToggleCodeActions {
|
||||
@@ -7929,7 +7929,7 @@ impl Editor {
|
||||
|
||||
fn update_visible_edit_prediction(
|
||||
&mut self,
|
||||
_window: &mut Window,
|
||||
_win: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<()> {
|
||||
if DisableAiSettings::get_global(cx).disable_ai {
|
||||
@@ -8308,7 +8308,7 @@ impl Editor {
|
||||
this.entry(msg, None, {
|
||||
let weak_editor = weak_editor.clone();
|
||||
let breakpoint = breakpoint.clone();
|
||||
move |_window, cx| {
|
||||
move |_win, cx| {
|
||||
weak_editor
|
||||
.update(cx, |this, cx| {
|
||||
this.edit_breakpoint_at_anchor(
|
||||
@@ -25434,7 +25434,7 @@ fn render_diff_hunk_controls(
|
||||
Button::new(("restore", row as u64), "Restore")
|
||||
.tooltip({
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
move |_window, cx| {
|
||||
move |_win, cx| {
|
||||
Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
|
||||
}
|
||||
})
|
||||
@@ -25460,7 +25460,7 @@ fn render_diff_hunk_controls(
|
||||
// .disabled(!has_multiple_hunks)
|
||||
.tooltip({
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
move |_window, cx| {
|
||||
move |_win, cx| {
|
||||
Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
|
||||
}
|
||||
})
|
||||
@@ -25490,7 +25490,7 @@ fn render_diff_hunk_controls(
|
||||
// .disabled(!has_multiple_hunks)
|
||||
.tooltip({
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
move |_window, cx| {
|
||||
move |_win, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Previous Hunk",
|
||||
&GoToPreviousHunk,
|
||||
|
||||
@@ -33,6 +33,7 @@ tempfile.workspace = true
|
||||
text.workspace = true
|
||||
time.workspace = true
|
||||
util.workspace = true
|
||||
is_executable = "1.0.5"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
fsevent.workspace = true
|
||||
|
||||
@@ -138,6 +138,7 @@ impl GitRepository for FakeGitRepository {
|
||||
path: RepoPath,
|
||||
content: Option<String>,
|
||||
_env: Arc<HashMap<String, String>>,
|
||||
_is_executable: bool,
|
||||
) -> BoxFuture<'_, anyhow::Result<()>> {
|
||||
self.with_state_async(true, move |state| {
|
||||
if let Some(message) = &state.simulated_index_write_error_message {
|
||||
|
||||
@@ -32,6 +32,7 @@ use std::mem::MaybeUninit;
|
||||
use async_tar::Archive;
|
||||
use futures::{AsyncRead, Stream, StreamExt, future::BoxFuture};
|
||||
use git::repository::{GitRepository, RealGitRepository};
|
||||
use is_executable::IsExecutable;
|
||||
use rope::Rope;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smol::io::AsyncWriteExt;
|
||||
@@ -208,6 +209,7 @@ pub struct Metadata {
|
||||
pub is_dir: bool,
|
||||
pub len: u64,
|
||||
pub is_fifo: bool,
|
||||
pub is_executable: bool,
|
||||
}
|
||||
|
||||
/// Filesystem modification time. The purpose of this newtype is to discourage use of operations
|
||||
@@ -895,6 +897,12 @@ impl Fs for RealFs {
|
||||
#[cfg(unix)]
|
||||
let is_fifo = metadata.file_type().is_fifo();
|
||||
|
||||
let path_buf = path.to_path_buf();
|
||||
let is_executable = self
|
||||
.executor
|
||||
.spawn(async move { path_buf.is_executable() })
|
||||
.await;
|
||||
|
||||
Ok(Some(Metadata {
|
||||
inode,
|
||||
mtime: MTime(metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH)),
|
||||
@@ -902,6 +910,7 @@ impl Fs for RealFs {
|
||||
is_symlink,
|
||||
is_dir: metadata.file_type().is_dir(),
|
||||
is_fifo,
|
||||
is_executable,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -2602,6 +2611,7 @@ impl Fs for FakeFs {
|
||||
is_dir: false,
|
||||
is_symlink,
|
||||
is_fifo: false,
|
||||
is_executable: false,
|
||||
},
|
||||
FakeFsEntry::Dir {
|
||||
inode, mtime, len, ..
|
||||
@@ -2612,6 +2622,7 @@ impl Fs for FakeFs {
|
||||
is_dir: true,
|
||||
is_symlink,
|
||||
is_fifo: false,
|
||||
is_executable: false,
|
||||
},
|
||||
FakeFsEntry::Symlink { .. } => unreachable!(),
|
||||
}))
|
||||
|
||||
@@ -400,6 +400,7 @@ pub trait GitRepository: Send + Sync {
|
||||
path: RepoPath,
|
||||
content: Option<String>,
|
||||
env: Arc<HashMap<String, String>>,
|
||||
is_executable: bool,
|
||||
) -> BoxFuture<'_, anyhow::Result<()>>;
|
||||
|
||||
/// Returns the URL of the remote with the given name.
|
||||
@@ -987,12 +988,15 @@ impl GitRepository for RealGitRepository {
|
||||
path: RepoPath,
|
||||
content: Option<String>,
|
||||
env: Arc<HashMap<String, String>>,
|
||||
is_executable: bool,
|
||||
) -> BoxFuture<'_, anyhow::Result<()>> {
|
||||
let working_directory = self.working_directory();
|
||||
let git_binary_path = self.any_git_binary_path.clone();
|
||||
self.executor
|
||||
.spawn(async move {
|
||||
let working_directory = working_directory?;
|
||||
let mode = if is_executable { "100755" } else { "100644" };
|
||||
|
||||
if let Some(content) = content {
|
||||
let mut child = new_smol_command(&git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
@@ -1013,7 +1017,7 @@ impl GitRepository for RealGitRepository {
|
||||
let output = new_smol_command(&git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.envs(env.iter())
|
||||
.args(["update-index", "--add", "--cacheinfo", "100644", sha])
|
||||
.args(["update-index", "--add", "--cacheinfo", mode, sha])
|
||||
.arg(path.as_unix_str())
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
@@ -389,11 +389,12 @@ impl Platform for WindowsPlatform {
|
||||
#[allow(
|
||||
clippy::disallowed_methods,
|
||||
reason = "We are restarting ourselves, using std command thus is fine"
|
||||
)]
|
||||
let restart_process = util::command::new_std_command("powershell.exe")
|
||||
.arg("-command")
|
||||
.arg(script)
|
||||
.spawn();
|
||||
)] // todo(shell): There might be no powershell on the system
|
||||
let restart_process =
|
||||
util::command::new_std_command(util::shell::get_windows_system_shell())
|
||||
.arg("-command")
|
||||
.arg(script)
|
||||
.spawn();
|
||||
|
||||
match restart_process {
|
||||
Ok(_) => self.quit(),
|
||||
|
||||
@@ -111,8 +111,6 @@ pub struct OutlinePanel {
|
||||
selected_entry: SelectedEntry,
|
||||
active_item: Option<ActiveItem>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
updating_fs_entries: bool,
|
||||
updating_cached_entries: bool,
|
||||
new_entries_for_fs_update: HashSet<ExcerptId>,
|
||||
fs_entries_update_task: Task<()>,
|
||||
cached_entries_update_task: Task<()>,
|
||||
@@ -853,8 +851,6 @@ impl OutlinePanel {
|
||||
width: None,
|
||||
active_item: None,
|
||||
pending_serialization: Task::ready(None),
|
||||
updating_fs_entries: false,
|
||||
updating_cached_entries: false,
|
||||
new_entries_for_fs_update: HashSet::default(),
|
||||
preserve_selection_on_buffer_fold_toggles: HashSet::default(),
|
||||
pending_default_expansion_depth: None,
|
||||
@@ -2658,7 +2654,6 @@ impl OutlinePanel {
|
||||
let repo_snapshots = self.project.update(cx, |project, cx| {
|
||||
project.git_store().read(cx).repo_snapshots(cx)
|
||||
});
|
||||
self.updating_fs_entries = true;
|
||||
self.fs_entries_update_task = cx.spawn_in(window, async move |outline_panel, cx| {
|
||||
if let Some(debounce) = debounce {
|
||||
cx.background_executor().timer(debounce).await;
|
||||
@@ -3016,7 +3011,6 @@ impl OutlinePanel {
|
||||
|
||||
outline_panel
|
||||
.update_in(cx, |outline_panel, window, cx| {
|
||||
outline_panel.updating_fs_entries = false;
|
||||
outline_panel.new_entries_for_fs_update.clear();
|
||||
outline_panel.excerpts = new_excerpts;
|
||||
outline_panel.collapsed_entries = new_collapsed_entries;
|
||||
@@ -3579,7 +3573,6 @@ impl OutlinePanel {
|
||||
|
||||
let is_singleton = self.is_singleton_active(cx);
|
||||
let query = self.query(cx);
|
||||
self.updating_cached_entries = true;
|
||||
self.cached_entries_update_task = cx.spawn_in(window, async move |outline_panel, cx| {
|
||||
if let Some(debounce) = debounce {
|
||||
cx.background_executor().timer(debounce).await;
|
||||
@@ -3612,7 +3605,6 @@ impl OutlinePanel {
|
||||
}
|
||||
|
||||
outline_panel.autoscroll(cx);
|
||||
outline_panel.updating_cached_entries = false;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
@@ -4542,12 +4534,10 @@ impl OutlinePanel {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let contents = if self.cached_entries.is_empty() {
|
||||
let header = if self.updating_fs_entries || self.updating_cached_entries {
|
||||
None
|
||||
} else if query.is_some() {
|
||||
Some("No matches for query")
|
||||
let header = if query.is_some() {
|
||||
"No matches for query"
|
||||
} else {
|
||||
Some("No outlines available")
|
||||
"No outlines available"
|
||||
};
|
||||
|
||||
v_flex()
|
||||
@@ -4556,33 +4546,28 @@ impl OutlinePanel {
|
||||
.flex_1()
|
||||
.justify_center()
|
||||
.size_full()
|
||||
.when_some(header, |panel, header| {
|
||||
panel
|
||||
.child(h_flex().justify_center().child(Label::new(header)))
|
||||
.when_some(query.clone(), |panel, query| {
|
||||
panel.child(
|
||||
h_flex()
|
||||
.px_0p5()
|
||||
.justify_center()
|
||||
.bg(cx.theme().colors().element_selected.opacity(0.2))
|
||||
.child(Label::new(query)),
|
||||
)
|
||||
})
|
||||
.child(h_flex().justify_center().child({
|
||||
let keystroke = match self.position(window, cx) {
|
||||
DockPosition::Left => {
|
||||
window.keystroke_text_for(&workspace::ToggleLeftDock)
|
||||
}
|
||||
DockPosition::Bottom => {
|
||||
window.keystroke_text_for(&workspace::ToggleBottomDock)
|
||||
}
|
||||
DockPosition::Right => {
|
||||
window.keystroke_text_for(&workspace::ToggleRightDock)
|
||||
}
|
||||
};
|
||||
Label::new(format!("Toggle Panel With {keystroke}")).color(Color::Muted)
|
||||
}))
|
||||
.child(h_flex().justify_center().child(Label::new(header)))
|
||||
.when_some(query, |panel, query| {
|
||||
panel.child(
|
||||
h_flex()
|
||||
.px_0p5()
|
||||
.justify_center()
|
||||
.bg(cx.theme().colors().element_selected.opacity(0.2))
|
||||
.child(Label::new(query)),
|
||||
)
|
||||
})
|
||||
.child(h_flex().justify_center().child({
|
||||
let keystroke = match self.position(window, cx) {
|
||||
DockPosition::Left => window.keystroke_text_for(&workspace::ToggleLeftDock),
|
||||
DockPosition::Bottom => {
|
||||
window.keystroke_text_for(&workspace::ToggleBottomDock)
|
||||
}
|
||||
DockPosition::Right => {
|
||||
window.keystroke_text_for(&workspace::ToggleRightDock)
|
||||
}
|
||||
};
|
||||
Label::new(format!("Toggle Panel With {keystroke}")).color(Color::Muted)
|
||||
}))
|
||||
} else {
|
||||
let list_contents = {
|
||||
let items_len = self.cached_entries.len();
|
||||
|
||||
@@ -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>>,
|
||||
},
|
||||
@@ -342,7 +343,13 @@ pub struct GitJob {
|
||||
key: Option<GitJobKey>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
impl std::fmt::Debug for GitJob {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "GitJob {{ {:?} }}", self.key)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum GitJobKey {
|
||||
WriteIndex(Vec<RepoPath>),
|
||||
ReloadBufferDiffBases,
|
||||
@@ -4288,6 +4295,7 @@ impl Repository {
|
||||
RepositoryState::Local {
|
||||
backend,
|
||||
environment,
|
||||
..
|
||||
} => backend.run_hook(hook, environment.clone()).await,
|
||||
RepositoryState::Remote { project_id, client } => {
|
||||
client
|
||||
@@ -4580,6 +4588,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 +4597,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 +5180,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,29 +5212,36 @@ impl Repository {
|
||||
backend.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
let state = RepositoryState::Local {
|
||||
fs: fs_cloned,
|
||||
backend,
|
||||
environment: Arc::new(environment),
|
||||
};
|
||||
let mut jobs = VecDeque::new();
|
||||
loop {
|
||||
println!(" Current job queue: {:?}", jobs);
|
||||
while let Ok(Some(next_job)) = job_rx.try_next() {
|
||||
println!(" >>> pushing new job: {next_job:?}");
|
||||
jobs.push_back(next_job);
|
||||
}
|
||||
|
||||
if let Some(job) = jobs.pop_front() {
|
||||
println!(" >>> popping front: {job:?}");
|
||||
if let Some(current_key) = &job.key
|
||||
&& jobs
|
||||
.iter()
|
||||
.any(|other_job| other_job.key.as_ref() == Some(current_key))
|
||||
{
|
||||
println!(" >>> SKIPPING");
|
||||
continue;
|
||||
}
|
||||
println!(" >>> RUNNING!");
|
||||
(job.job)(state.clone(), cx).await;
|
||||
} else if let Some(job) = job_rx.next().await {
|
||||
println!(" >>> blocking for new job: {job:?}");
|
||||
jobs.push_back(job);
|
||||
} else {
|
||||
// TODO: as far as I understand, this prong will never be reached
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -5361,13 +5385,17 @@ impl Repository {
|
||||
updates_tx: Option<mpsc::UnboundedSender<DownstreamUpdate>>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.paths_needing_status_update.extend(paths);
|
||||
println!("paths_neeeding_status_update: {paths:?}");
|
||||
// self.paths_needing_status_update.extend(paths.clone());
|
||||
|
||||
let this = cx.weak_entity();
|
||||
let _ = self.send_keyed_job(
|
||||
let res = self.send_keyed_job(
|
||||
Some(GitJobKey::RefreshStatuses),
|
||||
None,
|
||||
|state, mut cx| async move {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.paths_needing_status_update.extend(paths);
|
||||
})?;
|
||||
let (prev_snapshot, mut changed_paths) = this.update(&mut cx, |this, _| {
|
||||
(
|
||||
this.snapshot.clone(),
|
||||
@@ -5439,6 +5467,21 @@ impl Repository {
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
cx.spawn(async move |this, cx| match res.await {
|
||||
Ok(res) => {
|
||||
let _ = res;
|
||||
}
|
||||
Err(Canceled) => {
|
||||
println!(
|
||||
"skipped!!! this.paths_needing_status_update.len() = {:?}",
|
||||
this.update(cx, |this, _cx| {
|
||||
this.paths_needing_status_update.iter().count()
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// currently running git command and when it started
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -79,29 +79,42 @@ pub fn get_default_system_shell() -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the default system shell, preferring git-bash on Windows.
|
||||
/// Get the default system shell, preferring bash on Windows.
|
||||
pub fn get_default_system_shell_preferring_bash() -> String {
|
||||
if cfg!(windows) {
|
||||
get_windows_git_bash().unwrap_or_else(|| get_windows_system_shell())
|
||||
get_windows_bash().unwrap_or_else(|| get_windows_system_shell())
|
||||
} else {
|
||||
"/bin/sh".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_windows_git_bash() -> Option<String> {
|
||||
static GIT_BASH: LazyLock<Option<String>> = LazyLock::new(|| {
|
||||
pub fn get_windows_bash() -> Option<String> {
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn find_bash_in_scoop() -> Option<PathBuf> {
|
||||
let bash_exe =
|
||||
PathBuf::from(std::env::var_os("USERPROFILE")?).join("scoop\\shims\\bash.exe");
|
||||
bash_exe.exists().then_some(bash_exe)
|
||||
}
|
||||
|
||||
fn find_bash_in_git() -> Option<PathBuf> {
|
||||
// /path/to/git/cmd/git.exe/../../bin/bash.exe
|
||||
let git = which::which("git").ok()?;
|
||||
let git_bash = git.parent()?.parent()?.join("bin").join("bash.exe");
|
||||
if git_bash.is_file() {
|
||||
log::info!("Found git-bash at {}", git_bash.display());
|
||||
Some(git_bash.to_string_lossy().to_string())
|
||||
} else {
|
||||
None
|
||||
git_bash.exists().then_some(git_bash)
|
||||
}
|
||||
|
||||
static BASH: LazyLock<Option<String>> = LazyLock::new(|| {
|
||||
let bash = find_bash_in_scoop()
|
||||
.or_else(|| find_bash_in_git())
|
||||
.map(|p| p.to_string_lossy().into_owned());
|
||||
if let Some(ref path) = bash {
|
||||
log::info!("Found bash at {}", path);
|
||||
}
|
||||
bash
|
||||
});
|
||||
|
||||
(*GIT_BASH).clone()
|
||||
(*BASH).clone()
|
||||
}
|
||||
|
||||
pub fn get_windows_system_shell() -> String {
|
||||
@@ -191,14 +204,22 @@ pub fn get_windows_system_shell() -> String {
|
||||
}
|
||||
|
||||
static SYSTEM_SHELL: LazyLock<String> = LazyLock::new(|| {
|
||||
find_pwsh_in_programfiles(false, false)
|
||||
.or_else(|| find_pwsh_in_programfiles(true, false))
|
||||
.or_else(|| find_pwsh_in_msix(false))
|
||||
.or_else(|| find_pwsh_in_programfiles(false, true))
|
||||
.or_else(|| find_pwsh_in_msix(true))
|
||||
.or_else(|| find_pwsh_in_programfiles(true, true))
|
||||
.or_else(find_pwsh_in_scoop)
|
||||
.map(|p| p.to_string_lossy().into_owned())
|
||||
let locations = [
|
||||
|| find_pwsh_in_programfiles(false, false),
|
||||
|| find_pwsh_in_programfiles(true, false),
|
||||
|| find_pwsh_in_msix(false),
|
||||
|| find_pwsh_in_programfiles(false, true),
|
||||
|| find_pwsh_in_msix(true),
|
||||
|| find_pwsh_in_programfiles(true, true),
|
||||
|| find_pwsh_in_scoop(),
|
||||
|| which::which_global("pwsh.exe").ok(),
|
||||
|| which::which_global("powershell.exe").ok(),
|
||||
];
|
||||
|
||||
locations
|
||||
.into_iter()
|
||||
.find_map(|f| f())
|
||||
.map(|p| p.to_string_lossy().trim().to_owned())
|
||||
.inspect(|shell| log::info!("Found powershell in: {}", shell))
|
||||
.unwrap_or_else(|| {
|
||||
log::warn!("Powershell not found, falling back to `cmd`");
|
||||
|
||||
@@ -593,9 +593,9 @@ pub mod simple_message_notification {
|
||||
|
||||
use gpui::{
|
||||
AnyElement, DismissEvent, EventEmitter, FocusHandle, Focusable, ParentElement, Render,
|
||||
SharedString, Styled,
|
||||
ScrollHandle, SharedString, Styled,
|
||||
};
|
||||
use ui::prelude::*;
|
||||
use ui::{WithScrollbar, prelude::*};
|
||||
|
||||
use crate::notifications::NotificationFrame;
|
||||
|
||||
@@ -617,6 +617,7 @@ pub mod simple_message_notification {
|
||||
show_close_button: bool,
|
||||
show_suppress_button: bool,
|
||||
title: Option<SharedString>,
|
||||
scroll_handle: ScrollHandle,
|
||||
}
|
||||
|
||||
impl Focusable for MessageNotification {
|
||||
@@ -661,6 +662,7 @@ pub mod simple_message_notification {
|
||||
show_suppress_button: true,
|
||||
title: None,
|
||||
focus_handle: cx.focus_handle(),
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -777,7 +779,18 @@ pub mod simple_message_notification {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
NotificationFrame::new()
|
||||
.with_title(self.title.clone())
|
||||
.with_content((self.build_content)(window, cx))
|
||||
.with_content(
|
||||
div()
|
||||
.child(
|
||||
div()
|
||||
.id("message-notification-content")
|
||||
.max_h(vh(0.6, window))
|
||||
.overflow_y_scroll()
|
||||
.track_scroll(&self.scroll_handle.clone())
|
||||
.child((self.build_content)(window, cx)),
|
||||
)
|
||||
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx),
|
||||
)
|
||||
.show_close_button(self.show_close_button)
|
||||
.show_suppress_button(self.show_suppress_button)
|
||||
.on_close(cx.listener(|_, suppress, _, cx| {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use anyhow::Result;
|
||||
use cloud_llm_client::predict_edits_v3::Event;
|
||||
use futures::AsyncReadExt as _;
|
||||
use gpui::{
|
||||
@@ -17,7 +17,6 @@ use std::{
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::{EditPrediction, EditPredictionId, EditPredictionInputs};
|
||||
|
||||
@@ -31,9 +30,7 @@ pub struct SweepAi {
|
||||
impl SweepAi {
|
||||
pub fn new(cx: &App) -> Self {
|
||||
SweepAi {
|
||||
api_token: std::env::var("SWEEP_AI_TOKEN")
|
||||
.context("No SWEEP_AI_TOKEN environment variable set")
|
||||
.log_err(),
|
||||
api_token: std::env::var("SWEEP_AI_TOKEN").ok(),
|
||||
debug_info: debug_info(cx),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user