Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions asyncgit/src/sync/commit_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
sync::{get_stashes, repository::repo},
StatusItem, StatusItemType,
};
use git2::{Diff, Repository};
use git2::{Delta, Diff, Repository};
use scopetime::scope_time;
use std::collections::HashSet;

Expand Down Expand Up @@ -67,17 +67,28 @@ pub fn get_commit_files(
)?
};


fn path_from_delta(delta: git2::DiffDelta) -> String {
let path = if delta.status() == Delta::Deleted {
delta.old_file().path()
} else {
delta
.new_file()
.path()
.or_else(|| delta.old_file().path())
};
path
.map(|p| p.to_str().unwrap_or("").to_string())
.unwrap_or_default()
}

let res = diff
.deltas()
.map(|delta| {
let status = StatusItemType::from(delta.status());

StatusItem {
path: delta
.new_file()
.path()
.map(|p| p.to_str().unwrap_or("").to_string())
.unwrap_or_default(),
path: path_from_delta(delta),
status,
}
})
Expand Down
2 changes: 1 addition & 1 deletion asyncgit/src/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ pub use tags::{
delete_tag, get_tags, get_tags_with_metadata, CommitTags, Tag,
TagWithMetadata, Tags,
};
pub use tree::{tree_file_content, tree_files, TreeFile};
pub use tree::{file_content_at_commit, tree_file_content, tree_files, TreeFile};
pub use utils::{
get_head, get_head_tuple, repo_dir, repo_open_error,
stage_add_all, stage_add_file, stage_addremoved, Head,
Expand Down
34 changes: 34 additions & 0 deletions asyncgit/src/sync/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,40 @@ fn path_cmp(a: &Path, b: &Path) -> Ordering {
}
}


/// UTF-8 text content of `path` in `commit`, or in its first parent when `from_parent`.
pub fn file_content_at_commit(
repo_path: &RepoPath,
commit: CommitId,
path: &Path,
from_parent: bool,
) -> Result<String> {
scope_time!("file_content_at_commit");

let repo = repo(repo_path)?;
let commit = repo.find_commit(commit.into())?;
let object_id = if from_parent {
if commit.parent_count() == 0 {
return Err(Error::Generic(
"commit has no parent".into(),
));
}
commit.parent(0)?.id()
} else {
commit.id()
};
let commit = repo.find_commit(object_id)?;
let tree = commit.tree()?;
let entry = tree.get_path(path)?;
let blob = repo.find_blob(entry.id())?;

if blob.is_binary() {
return Err(Error::BinaryFile);
}

Ok(String::from_utf8_lossy(blob.content()).to_string())
}

/// will only work on utf8 content
pub fn tree_file_content(
repo_path: &RepoPath,
Expand Down
26 changes: 25 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ pub struct App {
// "Flags"
requires_redraw: Cell<bool>,
file_to_open: Option<String>,
pending_external_editor: Option<(String, Option<asyncgit::sync::CommitId>, bool)>,
}

pub struct Environment {
Expand Down Expand Up @@ -244,6 +245,7 @@ impl App {
key_config: env.key_config,
requires_redraw: Cell::new(false),
file_to_open: None,
pending_external_editor: None,
repo: env.repo,
repo_path_text,
popup_stack: PopupStack::default(),
Expand Down Expand Up @@ -372,10 +374,19 @@ impl App {
self.external_editor_popup.hide();
if matches!(polling_state, InputState::Paused) {
let result =
if let Some(path) = self.file_to_open.take() {
if let Some((path, commit, from_parent)) =
self.pending_external_editor.take()
{
ExternalEditorPopup::open_file_in_editor(
&self.repo.borrow(),
Path::new(&path),
commit.map(|c| (c, from_parent)),
)
} else if let Some(path) = self.file_to_open.take() {
ExternalEditorPopup::open_file_in_editor(
&self.repo.borrow(),
Path::new(&path),
None,
)
} else {
let changes =
Expand Down Expand Up @@ -816,11 +827,24 @@ impl App {
}
}
InternalEvent::OpenExternalEditor(path) => {
self.pending_external_editor = None;
self.input.set_polling(false);
self.external_editor_popup.show()?;
self.file_to_open = path;
flags.insert(NeedsUpdate::COMMANDS);
}
InternalEvent::OpenExternalEditorAtCommit {
path,
commit,
from_parent,
} => {
self.file_to_open = None;
self.pending_external_editor =
Some((path, Some(commit), from_parent));
self.input.set_polling(false);
self.external_editor_popup.show()?;
flags.insert(NeedsUpdate::COMMANDS);
}
InternalEvent::Push(branch, push_type, force, delete) => {
self.push_popup
.push(branch, push_type, force, delete)?;
Expand Down
33 changes: 32 additions & 1 deletion src/components/status_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ use crate::{
ui::{self, style::SharedTheme},
};
use anyhow::Result;
use asyncgit::{hash, sync::CommitId, StatusItem, StatusItemType};
use asyncgit::{
hash,
sync::{utils::repo_work_dir, CommitId, RepoPathRef},
StatusItem, StatusItemType,
};
use crossterm::event::Event;
use ratatui::{layout::Rect, text::Span, Frame};
use std::{borrow::Cow, cell::Cell, path::Path};
Expand All @@ -32,6 +36,7 @@ pub struct StatusTreeComponent {
focused: bool,
show_selection: bool,
queue: Queue,
repo: RepoPathRef,
theme: SharedTheme,
key_config: SharedKeyConfig,
scroll_top: Cell<usize>,
Expand All @@ -49,6 +54,7 @@ impl StatusTreeComponent {
focused: focus,
show_selection: focus,
queue: env.queue.clone(),
repo: env.repo.clone(),
theme: env.theme.clone(),
key_config: env.key_config.clone(),
scroll_top: Cell::new(0),
Expand Down Expand Up @@ -508,6 +514,31 @@ impl Component for StatusTreeComponent {
} else if key_match(e, self.key_config.keys.edit_file)
{
if let Some(status_item) = self.selection_file() {
if let Some(commit_id) = self.revision {
let from_parent = matches!(
status_item.status,
StatusItemType::Deleted
);
let missing = repo_work_dir(
&self.repo.borrow(),
)
.ok()
.map(|wd| {
std::path::Path::new(&wd)
.join(&status_item.path)
})
.is_none_or(|p| !p.exists());
if from_parent || missing {
self.queue.push(
InternalEvent::OpenExternalEditorAtCommit {
path: status_item.path,
commit: commit_id,
from_parent,
},
);
return Ok(EventState::Consumed);
}
}
self.queue.push(
InternalEvent::OpenExternalEditor(Some(
status_item.path,
Expand Down
1 change: 1 addition & 0 deletions src/popups/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ impl CommitPopup {
ExternalEditorPopup::open_file_in_editor(
&self.repo.borrow(),
&file_path,
None,
)?;

let mut message = String::new();
Expand Down
29 changes: 25 additions & 4 deletions src/popups/externaleditor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use crate::{
};
use anyhow::{anyhow, bail, Result};
use asyncgit::sync::{
get_config_string, utils::repo_work_dir, RepoPath,
file_content_at_commit, get_config_string, utils::repo_work_dir,
CommitId, RepoPath,
};
use crossterm::{
event::Event,
Expand All @@ -25,6 +26,7 @@ use ratatui::{
};
use scopeguard::defer;
use std::ffi::OsStr;
use std::fs;
use std::{env, io, path::Path, process::Command};

///
Expand All @@ -48,6 +50,7 @@ impl ExternalEditorPopup {
pub fn open_file_in_editor(
repo: &RepoPath,
path: &Path,
at_commit: Option<(CommitId, bool)>,
) -> Result<()> {
let work_dir = repo_work_dir(repo)?;

Expand All @@ -57,9 +60,27 @@ impl ExternalEditorPopup {
path.into()
};

if !path.exists() {
let editor_path = if path.exists() {
path
} else if let Some((commit, from_parent)) = at_commit {
let content = file_content_at_commit(
repo,
commit,
&path,
from_parent,
)?;
let file_name = path
.file_name()
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_else(|| String::from("file"));
let editor_path = Path::new(&work_dir).join(".git").join(
format!("gitui-edit-{commit}-{file_name}"),
);
fs::write(&editor_path, content.as_bytes())?;
editor_path
} else {
bail!("file not found: {path:?}");
}
};

io::stdout().execute(LeaveAlternateScreen)?;
defer! {
Expand Down Expand Up @@ -107,7 +128,7 @@ impl ExternalEditorPopup {
let mut args: Vec<&OsStr> =
remainder.map(OsStr::new).collect();

args.push(path.as_os_str());
args.push(editor_path.as_os_str());

Command::new(command.clone())
.current_dir(work_dir)
Expand Down
6 changes: 6 additions & 0 deletions src/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ pub enum InternalEvent {
///
OpenExternalEditor(Option<String>),
///
OpenExternalEditorAtCommit {
path: String,
commit: CommitId,
from_parent: bool,
},
///
Push(String, PushType, bool, bool),
///
Pull(String),
Expand Down