Split conflicts into their own section (#24324)
Co-Authored-By: Mikayla <mikayla@zed.dev> Release Notes: - N/A
This commit is contained in:
parent
5d1c56829a
commit
0a70627f00
13 changed files with 171 additions and 42 deletions
|
@ -100,6 +100,7 @@ CREATE TABLE "worktree_repositories" (
|
||||||
"branch" VARCHAR,
|
"branch" VARCHAR,
|
||||||
"scan_id" INTEGER NOT NULL,
|
"scan_id" INTEGER NOT NULL,
|
||||||
"is_deleted" BOOL NOT NULL,
|
"is_deleted" BOOL NOT NULL,
|
||||||
|
"current_merge_conflicts" VARCHAR,
|
||||||
PRIMARY KEY(project_id, worktree_id, work_directory_id),
|
PRIMARY KEY(project_id, worktree_id, work_directory_id),
|
||||||
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
|
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE
|
FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE worktree_repositories
|
||||||
|
ADD COLUMN current_merge_conflicts VARCHAR NULL;
|
|
@ -333,6 +333,9 @@ impl Database {
|
||||||
scan_id: ActiveValue::set(update.scan_id as i64),
|
scan_id: ActiveValue::set(update.scan_id as i64),
|
||||||
branch: ActiveValue::set(repository.branch.clone()),
|
branch: ActiveValue::set(repository.branch.clone()),
|
||||||
is_deleted: ActiveValue::set(false),
|
is_deleted: ActiveValue::set(false),
|
||||||
|
current_merge_conflicts: ActiveValue::Set(Some(
|
||||||
|
serde_json::to_string(&repository.current_merge_conflicts).unwrap(),
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.on_conflict(
|
.on_conflict(
|
||||||
|
@ -769,6 +772,13 @@ impl Database {
|
||||||
updated_statuses.push(db_status_to_proto(status_entry)?);
|
updated_statuses.push(db_status_to_proto(status_entry)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let current_merge_conflicts = db_repository_entry
|
||||||
|
.current_merge_conflicts
|
||||||
|
.as_ref()
|
||||||
|
.map(|conflicts| serde_json::from_str(&conflicts))
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
worktree.repository_entries.insert(
|
worktree.repository_entries.insert(
|
||||||
db_repository_entry.work_directory_id as u64,
|
db_repository_entry.work_directory_id as u64,
|
||||||
proto::RepositoryEntry {
|
proto::RepositoryEntry {
|
||||||
|
@ -776,6 +786,7 @@ impl Database {
|
||||||
branch: db_repository_entry.branch,
|
branch: db_repository_entry.branch,
|
||||||
updated_statuses,
|
updated_statuses,
|
||||||
removed_statuses: Vec::new(),
|
removed_statuses: Vec::new(),
|
||||||
|
current_merge_conflicts,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -736,11 +736,19 @@ impl Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let current_merge_conflicts = db_repository
|
||||||
|
.current_merge_conflicts
|
||||||
|
.as_ref()
|
||||||
|
.map(|conflicts| serde_json::from_str(&conflicts))
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
worktree.updated_repositories.push(proto::RepositoryEntry {
|
worktree.updated_repositories.push(proto::RepositoryEntry {
|
||||||
work_directory_id: db_repository.work_directory_id as u64,
|
work_directory_id: db_repository.work_directory_id as u64,
|
||||||
branch: db_repository.branch,
|
branch: db_repository.branch,
|
||||||
updated_statuses,
|
updated_statuses,
|
||||||
removed_statuses,
|
removed_statuses,
|
||||||
|
current_merge_conflicts,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ pub struct Model {
|
||||||
pub scan_id: i64,
|
pub scan_id: i64,
|
||||||
pub branch: Option<String>,
|
pub branch: Option<String>,
|
||||||
pub is_deleted: bool,
|
pub is_deleted: bool,
|
||||||
|
// JSON array typed string
|
||||||
|
pub current_merge_conflicts: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
|
|
@ -46,6 +46,8 @@ pub trait GitRepository: Send + Sync {
|
||||||
/// Returns the SHA of the current HEAD.
|
/// Returns the SHA of the current HEAD.
|
||||||
fn head_sha(&self) -> Option<String>;
|
fn head_sha(&self) -> Option<String>;
|
||||||
|
|
||||||
|
fn merge_head_shas(&self) -> Vec<String>;
|
||||||
|
|
||||||
/// Returns the list of git statuses, sorted by path
|
/// Returns the list of git statuses, sorted by path
|
||||||
fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus>;
|
fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus>;
|
||||||
|
|
||||||
|
@ -162,6 +164,18 @@ impl GitRepository for RealGitRepository {
|
||||||
Some(self.repository.lock().head().ok()?.target()?.to_string())
|
Some(self.repository.lock().head().ok()?.target()?.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn merge_head_shas(&self) -> Vec<String> {
|
||||||
|
let mut shas = Vec::default();
|
||||||
|
self.repository
|
||||||
|
.lock()
|
||||||
|
.mergehead_foreach(|oid| {
|
||||||
|
shas.push(oid.to_string());
|
||||||
|
true
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
shas
|
||||||
|
}
|
||||||
|
|
||||||
fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus> {
|
fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus> {
|
||||||
let working_directory = self
|
let working_directory = self
|
||||||
.repository
|
.repository
|
||||||
|
@ -387,6 +401,10 @@ impl GitRepository for FakeGitRepository {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn merge_head_shas(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
fn dot_git_dir(&self) -> PathBuf {
|
fn dot_git_dir(&self) -> PathBuf {
|
||||||
let state = self.state.lock();
|
let state = self.state.lock();
|
||||||
state.dot_git_dir.clone()
|
state.dot_git_dir.clone()
|
||||||
|
|
|
@ -134,7 +134,11 @@ impl FileStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_changes(&self) -> bool {
|
pub fn has_changes(&self) -> bool {
|
||||||
self.is_modified() || self.is_created() || self.is_deleted() || self.is_untracked()
|
self.is_modified()
|
||||||
|
|| self.is_created()
|
||||||
|
|| self.is_deleted()
|
||||||
|
|| self.is_untracked()
|
||||||
|
|| self.is_conflicted()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_modified(self) -> bool {
|
pub fn is_modified(self) -> bool {
|
||||||
|
|
|
@ -76,30 +76,29 @@ struct SerializedGitPanel {
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
enum Section {
|
enum Section {
|
||||||
|
Conflict,
|
||||||
Tracked,
|
Tracked,
|
||||||
New,
|
New,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Section {
|
|
||||||
pub fn contains(&self, status: FileStatus) -> bool {
|
|
||||||
match self {
|
|
||||||
Section::Tracked => !status.is_created(),
|
|
||||||
Section::New => status.is_created(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
struct GitHeaderEntry {
|
struct GitHeaderEntry {
|
||||||
header: Section,
|
header: Section,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GitHeaderEntry {
|
impl GitHeaderEntry {
|
||||||
pub fn contains(&self, status_entry: &GitStatusEntry) -> bool {
|
pub fn contains(&self, status_entry: &GitStatusEntry, repo: &Repository) -> bool {
|
||||||
self.header.contains(status_entry.status)
|
let this = &self.header;
|
||||||
|
let status = status_entry.status;
|
||||||
|
match this {
|
||||||
|
Section::Conflict => repo.has_conflict(&status_entry.repo_path),
|
||||||
|
Section::Tracked => !status.is_created(),
|
||||||
|
Section::New => status.is_created(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn title(&self) -> &'static str {
|
pub fn title(&self) -> &'static str {
|
||||||
match self.header {
|
match self.header {
|
||||||
|
Section::Conflict => "Conflicts",
|
||||||
Section::Tracked => "Changed",
|
Section::Tracked => "Changed",
|
||||||
Section::New => "New",
|
Section::New => "New",
|
||||||
}
|
}
|
||||||
|
@ -160,6 +159,8 @@ pub struct GitPanel {
|
||||||
commit_task: Task<Result<()>>,
|
commit_task: Task<Result<()>>,
|
||||||
commit_pending: bool,
|
commit_pending: bool,
|
||||||
|
|
||||||
|
conflicted_staged_count: usize,
|
||||||
|
conflicted_count: usize,
|
||||||
tracked_staged_count: usize,
|
tracked_staged_count: usize,
|
||||||
tracked_count: usize,
|
tracked_count: usize,
|
||||||
new_staged_count: usize,
|
new_staged_count: usize,
|
||||||
|
@ -276,6 +277,8 @@ impl GitPanel {
|
||||||
commit_editor,
|
commit_editor,
|
||||||
project,
|
project,
|
||||||
workspace,
|
workspace,
|
||||||
|
conflicted_count: 0,
|
||||||
|
conflicted_staged_count: 0,
|
||||||
tracked_staged_count: 0,
|
tracked_staged_count: 0,
|
||||||
tracked_count: 0,
|
tracked_count: 0,
|
||||||
new_staged_count: 0,
|
new_staged_count: 0,
|
||||||
|
@ -577,12 +580,13 @@ impl GitPanel {
|
||||||
}
|
}
|
||||||
GitListEntry::Header(section) => {
|
GitListEntry::Header(section) => {
|
||||||
let goal_staged_state = !self.header_state(section.header).selected();
|
let goal_staged_state = !self.header_state(section.header).selected();
|
||||||
|
let repository = active_repository.read(cx);
|
||||||
let entries = self
|
let entries = self
|
||||||
.entries
|
.entries
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|entry| entry.status_entry())
|
.filter_map(|entry| entry.status_entry())
|
||||||
.filter(|status_entry| {
|
.filter(|status_entry| {
|
||||||
section.contains(&status_entry)
|
section.contains(&status_entry, repository)
|
||||||
&& status_entry.is_staged != Some(goal_staged_state)
|
&& status_entry.is_staged != Some(goal_staged_state)
|
||||||
})
|
})
|
||||||
.map(|status_entry| status_entry.repo_path.clone())
|
.map(|status_entry| status_entry.repo_path.clone())
|
||||||
|
@ -601,7 +605,8 @@ impl GitPanel {
|
||||||
});
|
});
|
||||||
let repo_paths = repo_paths.clone();
|
let repo_paths = repo_paths.clone();
|
||||||
let active_repository = active_repository.clone();
|
let active_repository = active_repository.clone();
|
||||||
self.update_counts();
|
let repository = active_repository.read(cx);
|
||||||
|
self.update_counts(repository);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
||||||
cx.spawn({
|
cx.spawn({
|
||||||
|
@ -740,8 +745,7 @@ impl GitPanel {
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|entry| entry.status_entry())
|
.filter_map(|entry| entry.status_entry())
|
||||||
.filter(|status_entry| {
|
.filter(|status_entry| {
|
||||||
Section::Tracked.contains(status_entry.status)
|
!status_entry.status.is_created() && !status_entry.is_staged.unwrap_or(false)
|
||||||
&& !status_entry.is_staged.unwrap_or(false)
|
|
||||||
})
|
})
|
||||||
.map(|status_entry| status_entry.repo_path.clone())
|
.map(|status_entry| status_entry.repo_path.clone())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
@ -909,6 +913,7 @@ impl GitPanel {
|
||||||
self.entries_by_path.clear();
|
self.entries_by_path.clear();
|
||||||
let mut changed_entries = Vec::new();
|
let mut changed_entries = Vec::new();
|
||||||
let mut new_entries = Vec::new();
|
let mut new_entries = Vec::new();
|
||||||
|
let mut conflict_entries = Vec::new();
|
||||||
|
|
||||||
let Some(repo) = self.active_repository.as_ref() else {
|
let Some(repo) = self.active_repository.as_ref() else {
|
||||||
// Just clear entries if no repository is active.
|
// Just clear entries if no repository is active.
|
||||||
|
@ -925,6 +930,7 @@ impl GitPanel {
|
||||||
let (depth, difference) =
|
let (depth, difference) =
|
||||||
Self::calculate_depth_and_difference(&entry.repo_path, &path_set);
|
Self::calculate_depth_and_difference(&entry.repo_path, &path_set);
|
||||||
|
|
||||||
|
let is_conflict = repo.has_conflict(&entry.repo_path);
|
||||||
let is_new = entry.status.is_created();
|
let is_new = entry.status.is_created();
|
||||||
let is_staged = entry.status.is_staged();
|
let is_staged = entry.status.is_staged();
|
||||||
|
|
||||||
|
@ -955,7 +961,9 @@ impl GitPanel {
|
||||||
is_staged,
|
is_staged,
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_new {
|
if is_conflict {
|
||||||
|
conflict_entries.push(entry);
|
||||||
|
} else if is_new {
|
||||||
new_entries.push(entry);
|
new_entries.push(entry);
|
||||||
} else {
|
} else {
|
||||||
changed_entries.push(entry);
|
changed_entries.push(entry);
|
||||||
|
@ -963,9 +971,21 @@ impl GitPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort entries by path to maintain consistent order
|
// Sort entries by path to maintain consistent order
|
||||||
|
conflict_entries.sort_by(|a, b| a.repo_path.cmp(&b.repo_path));
|
||||||
changed_entries.sort_by(|a, b| a.repo_path.cmp(&b.repo_path));
|
changed_entries.sort_by(|a, b| a.repo_path.cmp(&b.repo_path));
|
||||||
new_entries.sort_by(|a, b| a.repo_path.cmp(&b.repo_path));
|
new_entries.sort_by(|a, b| a.repo_path.cmp(&b.repo_path));
|
||||||
|
|
||||||
|
if conflict_entries.len() > 0 {
|
||||||
|
self.entries.push(GitListEntry::Header(GitHeaderEntry {
|
||||||
|
header: Section::Conflict,
|
||||||
|
}));
|
||||||
|
self.entries.extend(
|
||||||
|
conflict_entries
|
||||||
|
.into_iter()
|
||||||
|
.map(GitListEntry::GitStatusEntry),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if changed_entries.len() > 0 {
|
if changed_entries.len() > 0 {
|
||||||
self.entries.push(GitListEntry::Header(GitHeaderEntry {
|
self.entries.push(GitListEntry::Header(GitHeaderEntry {
|
||||||
header: Section::Tracked,
|
header: Section::Tracked,
|
||||||
|
@ -990,14 +1010,16 @@ impl GitPanel {
|
||||||
.insert(status_entry.repo_path.clone(), ix);
|
.insert(status_entry.repo_path.clone(), ix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.update_counts();
|
self.update_counts(repo);
|
||||||
|
|
||||||
self.select_first_entry_if_none(cx);
|
self.select_first_entry_if_none(cx);
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_counts(&mut self) {
|
fn update_counts(&mut self, repo: &Repository) {
|
||||||
|
self.conflicted_count = 0;
|
||||||
|
self.conflicted_staged_count = 0;
|
||||||
self.new_count = 0;
|
self.new_count = 0;
|
||||||
self.tracked_count = 0;
|
self.tracked_count = 0;
|
||||||
self.new_staged_count = 0;
|
self.new_staged_count = 0;
|
||||||
|
@ -1006,7 +1028,12 @@ impl GitPanel {
|
||||||
let Some(status_entry) = entry.status_entry() else {
|
let Some(status_entry) = entry.status_entry() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if status_entry.status.is_created() {
|
if repo.has_conflict(&status_entry.repo_path) {
|
||||||
|
self.conflicted_count += 1;
|
||||||
|
if self.entry_appears_staged(status_entry) != Some(false) {
|
||||||
|
self.conflicted_staged_count += 1;
|
||||||
|
}
|
||||||
|
} else if status_entry.status.is_created() {
|
||||||
self.new_count += 1;
|
self.new_count += 1;
|
||||||
if self.entry_is_staged(status_entry) != Some(false) {
|
if self.entry_is_staged(status_entry) != Some(false) {
|
||||||
self.new_staged_count += 1;
|
self.new_staged_count += 1;
|
||||||
|
@ -1041,6 +1068,7 @@ impl GitPanel {
|
||||||
let (staged_count, count) = match header_type {
|
let (staged_count, count) = match header_type {
|
||||||
Section::New => (self.new_staged_count, self.new_count),
|
Section::New => (self.new_staged_count, self.new_count),
|
||||||
Section::Tracked => (self.tracked_staged_count, self.tracked_count),
|
Section::Tracked => (self.tracked_staged_count, self.tracked_count),
|
||||||
|
Section::Conflict => (self.conflicted_staged_count, self.conflicted_count),
|
||||||
};
|
};
|
||||||
if staged_count == 0 {
|
if staged_count == 0 {
|
||||||
ToggleState::Unselected
|
ToggleState::Unselected
|
||||||
|
@ -1467,7 +1495,7 @@ impl GitPanel {
|
||||||
self.header_state(header.header)
|
self.header_state(header.header)
|
||||||
} else {
|
} else {
|
||||||
match header.header {
|
match header.header {
|
||||||
Section::Tracked => ToggleState::Selected,
|
Section::Tracked | Section::Conflict => ToggleState::Selected,
|
||||||
Section::New => ToggleState::Unselected,
|
Section::New => ToggleState::Unselected,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -46,8 +46,9 @@ struct DiffBuffer {
|
||||||
change_set: Entity<BufferChangeSet>,
|
change_set: Entity<BufferChangeSet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const CHANGED_NAMESPACE: &'static str = "0";
|
const CONFLICT_NAMESPACE: &'static str = "0";
|
||||||
const ADDED_NAMESPACE: &'static str = "1";
|
const TRACKED_NAMESPACE: &'static str = "1";
|
||||||
|
const NEW_NAMESPACE: &'static str = "2";
|
||||||
|
|
||||||
impl ProjectDiff {
|
impl ProjectDiff {
|
||||||
pub(crate) fn register(
|
pub(crate) fn register(
|
||||||
|
@ -174,19 +175,25 @@ impl ProjectDiff {
|
||||||
let Some(git_repo) = self.git_state.read(cx).active_repository() else {
|
let Some(git_repo) = self.git_state.read(cx).active_repository() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let repo = git_repo.read(cx);
|
||||||
|
|
||||||
let Some(path) = git_repo
|
let Some(abs_path) = repo
|
||||||
.read(cx)
|
|
||||||
.repo_path_to_project_path(&entry.repo_path)
|
.repo_path_to_project_path(&entry.repo_path)
|
||||||
.and_then(|project_path| self.project.read(cx).absolute_path(&project_path, cx))
|
.and_then(|project_path| self.project.read(cx).absolute_path(&project_path, cx))
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let path_key = if entry.status.is_created() {
|
|
||||||
PathKey::namespaced(ADDED_NAMESPACE, &path)
|
let namespace = if repo.has_conflict(&entry.repo_path) {
|
||||||
|
CONFLICT_NAMESPACE
|
||||||
|
} else if entry.status.is_created() {
|
||||||
|
NEW_NAMESPACE
|
||||||
} else {
|
} else {
|
||||||
PathKey::namespaced(CHANGED_NAMESPACE, &path)
|
TRACKED_NAMESPACE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let path_key = PathKey::namespaced(namespace, &abs_path);
|
||||||
|
|
||||||
self.scroll_to_path(path_key, window, cx)
|
self.scroll_to_path(path_key, window, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,12 +266,14 @@ impl ProjectDiff {
|
||||||
let Some(abs_path) = self.project.read(cx).absolute_path(&project_path, cx) else {
|
let Some(abs_path) = self.project.read(cx).absolute_path(&project_path, cx) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
// Craft some artificial paths so that created entries will appear last.
|
let namespace = if repo.has_conflict(&entry.repo_path) {
|
||||||
let path_key = if entry.status.is_created() {
|
CONFLICT_NAMESPACE
|
||||||
PathKey::namespaced(ADDED_NAMESPACE, &abs_path)
|
} else if entry.status.is_created() {
|
||||||
|
NEW_NAMESPACE
|
||||||
} else {
|
} else {
|
||||||
PathKey::namespaced(CHANGED_NAMESPACE, &abs_path)
|
TRACKED_NAMESPACE
|
||||||
};
|
};
|
||||||
|
let path_key = PathKey::namespaced(namespace, &abs_path);
|
||||||
|
|
||||||
previous_paths.remove(&path_key);
|
previous_paths.remove(&path_key);
|
||||||
let load_buffer = self
|
let load_buffer = self
|
||||||
|
|
|
@ -336,6 +336,12 @@ impl Repository {
|
||||||
self.repository_entry.status()
|
self.repository_entry.status()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_conflict(&self, path: &RepoPath) -> bool {
|
||||||
|
self.repository_entry
|
||||||
|
.current_merge_conflicts
|
||||||
|
.contains(&path)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn repo_path_to_project_path(&self, path: &RepoPath) -> Option<ProjectPath> {
|
pub fn repo_path_to_project_path(&self, path: &RepoPath) -> Option<ProjectPath> {
|
||||||
let path = self.repository_entry.unrelativize(path)?;
|
let path = self.repository_entry.unrelativize(path)?;
|
||||||
Some((self.worktree_id, path).into())
|
Some((self.worktree_id, path).into())
|
||||||
|
|
|
@ -1800,6 +1800,7 @@ message RepositoryEntry {
|
||||||
optional string branch = 2;
|
optional string branch = 2;
|
||||||
repeated StatusEntry updated_statuses = 3;
|
repeated StatusEntry updated_statuses = 3;
|
||||||
repeated string removed_statuses = 4;
|
repeated string removed_statuses = 4;
|
||||||
|
repeated string current_merge_conflicts = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message StatusEntry {
|
message StatusEntry {
|
||||||
|
|
|
@ -32,7 +32,7 @@ impl<'a, K> Default for MapKeyRef<'a, K> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct TreeSet<K>(TreeMap<K, ()>)
|
pub struct TreeSet<K>(TreeMap<K, ()>)
|
||||||
where
|
where
|
||||||
K: Clone + Ord;
|
K: Clone + Ord;
|
||||||
|
|
|
@ -178,7 +178,7 @@ pub struct Snapshot {
|
||||||
completed_scan_id: usize,
|
completed_scan_id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct RepositoryEntry {
|
pub struct RepositoryEntry {
|
||||||
/// The git status entries for this repository.
|
/// The git status entries for this repository.
|
||||||
/// Note that the paths on this repository are relative to the git work directory.
|
/// Note that the paths on this repository are relative to the git work directory.
|
||||||
|
@ -203,6 +203,7 @@ pub struct RepositoryEntry {
|
||||||
work_directory_id: ProjectEntryId,
|
work_directory_id: ProjectEntryId,
|
||||||
pub work_directory: WorkDirectory,
|
pub work_directory: WorkDirectory,
|
||||||
pub(crate) branch: Option<Arc<str>>,
|
pub(crate) branch: Option<Arc<str>>,
|
||||||
|
pub current_merge_conflicts: TreeSet<RepoPath>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for RepositoryEntry {
|
impl Deref for RepositoryEntry {
|
||||||
|
@ -256,6 +257,11 @@ impl RepositoryEntry {
|
||||||
.map(|entry| entry.to_proto())
|
.map(|entry| entry.to_proto())
|
||||||
.collect(),
|
.collect(),
|
||||||
removed_statuses: Default::default(),
|
removed_statuses: Default::default(),
|
||||||
|
current_merge_conflicts: self
|
||||||
|
.current_merge_conflicts
|
||||||
|
.iter()
|
||||||
|
.map(|repo_path| repo_path.to_proto())
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,6 +312,11 @@ impl RepositoryEntry {
|
||||||
branch: self.branch.as_ref().map(|branch| branch.to_string()),
|
branch: self.branch.as_ref().map(|branch| branch.to_string()),
|
||||||
updated_statuses,
|
updated_statuses,
|
||||||
removed_statuses,
|
removed_statuses,
|
||||||
|
current_merge_conflicts: self
|
||||||
|
.current_merge_conflicts
|
||||||
|
.iter()
|
||||||
|
.map(RepoPath::to_proto)
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -456,6 +467,7 @@ struct BackgroundScannerState {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LocalRepositoryEntry {
|
pub struct LocalRepositoryEntry {
|
||||||
|
pub(crate) work_directory_id: ProjectEntryId,
|
||||||
pub(crate) work_directory: WorkDirectory,
|
pub(crate) work_directory: WorkDirectory,
|
||||||
pub(crate) git_dir_scan_id: usize,
|
pub(crate) git_dir_scan_id: usize,
|
||||||
pub(crate) status_scan_id: usize,
|
pub(crate) status_scan_id: usize,
|
||||||
|
@ -465,6 +477,7 @@ pub struct LocalRepositoryEntry {
|
||||||
pub(crate) dot_git_dir_abs_path: Arc<Path>,
|
pub(crate) dot_git_dir_abs_path: Arc<Path>,
|
||||||
/// Absolute path to the .git file, if we're in a git worktree.
|
/// Absolute path to the .git file, if we're in a git worktree.
|
||||||
pub(crate) dot_git_worktree_abs_path: Option<Arc<Path>>,
|
pub(crate) dot_git_worktree_abs_path: Option<Arc<Path>>,
|
||||||
|
pub current_merge_head_shas: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl sum_tree::Item for LocalRepositoryEntry {
|
impl sum_tree::Item for LocalRepositoryEntry {
|
||||||
|
@ -2520,6 +2533,13 @@ impl Snapshot {
|
||||||
for repository in update.updated_repositories {
|
for repository in update.updated_repositories {
|
||||||
let work_directory_id = ProjectEntryId::from_proto(repository.work_directory_id);
|
let work_directory_id = ProjectEntryId::from_proto(repository.work_directory_id);
|
||||||
if let Some(work_dir_entry) = self.entry_for_id(work_directory_id) {
|
if let Some(work_dir_entry) = self.entry_for_id(work_directory_id) {
|
||||||
|
let conflicted_paths = TreeSet::from_ordered_entries(
|
||||||
|
repository
|
||||||
|
.current_merge_conflicts
|
||||||
|
.into_iter()
|
||||||
|
.map(|path| RepoPath(Path::new(&path).into())),
|
||||||
|
);
|
||||||
|
|
||||||
if self
|
if self
|
||||||
.repositories
|
.repositories
|
||||||
.contains(&PathKey(work_dir_entry.path.clone()), &())
|
.contains(&PathKey(work_dir_entry.path.clone()), &())
|
||||||
|
@ -2539,6 +2559,7 @@ impl Snapshot {
|
||||||
.update(&PathKey(work_dir_entry.path.clone()), &(), |repo| {
|
.update(&PathKey(work_dir_entry.path.clone()), &(), |repo| {
|
||||||
repo.branch = repository.branch.map(Into::into);
|
repo.branch = repository.branch.map(Into::into);
|
||||||
repo.statuses_by_path.edit(edits, &());
|
repo.statuses_by_path.edit(edits, &());
|
||||||
|
repo.current_merge_conflicts = conflicted_paths
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let statuses = SumTree::from_iter(
|
let statuses = SumTree::from_iter(
|
||||||
|
@ -2561,6 +2582,7 @@ impl Snapshot {
|
||||||
},
|
},
|
||||||
branch: repository.branch.map(Into::into),
|
branch: repository.branch.map(Into::into),
|
||||||
statuses_by_path: statuses,
|
statuses_by_path: statuses,
|
||||||
|
current_merge_conflicts: conflicted_paths,
|
||||||
},
|
},
|
||||||
&(),
|
&(),
|
||||||
);
|
);
|
||||||
|
@ -3363,17 +3385,20 @@ impl BackgroundScannerState {
|
||||||
work_directory: work_directory.clone(),
|
work_directory: work_directory.clone(),
|
||||||
branch: repository.branch_name().map(Into::into),
|
branch: repository.branch_name().map(Into::into),
|
||||||
statuses_by_path: Default::default(),
|
statuses_by_path: Default::default(),
|
||||||
|
current_merge_conflicts: Default::default(),
|
||||||
},
|
},
|
||||||
&(),
|
&(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let local_repository = LocalRepositoryEntry {
|
let local_repository = LocalRepositoryEntry {
|
||||||
|
work_directory_id: work_dir_id,
|
||||||
work_directory: work_directory.clone(),
|
work_directory: work_directory.clone(),
|
||||||
git_dir_scan_id: 0,
|
git_dir_scan_id: 0,
|
||||||
status_scan_id: 0,
|
status_scan_id: 0,
|
||||||
repo_ptr: repository.clone(),
|
repo_ptr: repository.clone(),
|
||||||
dot_git_dir_abs_path: actual_dot_git_dir_abs_path,
|
dot_git_dir_abs_path: actual_dot_git_dir_abs_path,
|
||||||
dot_git_worktree_abs_path,
|
dot_git_worktree_abs_path,
|
||||||
|
current_merge_head_shas: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.snapshot
|
self.snapshot
|
||||||
|
@ -5127,11 +5152,11 @@ impl BackgroundScanner {
|
||||||
.snapshot
|
.snapshot
|
||||||
.git_repositories
|
.git_repositories
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|(entry_id, repo)| {
|
.find_map(|(_, repo)| {
|
||||||
if repo.dot_git_dir_abs_path.as_ref() == &dot_git_dir
|
if repo.dot_git_dir_abs_path.as_ref() == &dot_git_dir
|
||||||
|| repo.dot_git_worktree_abs_path.as_deref() == Some(&dot_git_dir)
|
|| repo.dot_git_worktree_abs_path.as_deref() == Some(&dot_git_dir)
|
||||||
{
|
{
|
||||||
Some((*entry_id, repo.clone()))
|
Some(repo.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -5148,13 +5173,13 @@ impl BackgroundScanner {
|
||||||
None => continue,
|
None => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some((entry_id, local_repository)) => {
|
Some(local_repository) => {
|
||||||
if local_repository.git_dir_scan_id == scan_id {
|
if local_repository.git_dir_scan_id == scan_id {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let Some(work_dir) = state
|
let Some(work_dir) = state
|
||||||
.snapshot
|
.snapshot
|
||||||
.entry_for_id(entry_id)
|
.entry_for_id(local_repository.work_directory_id)
|
||||||
.map(|entry| entry.path.clone())
|
.map(|entry| entry.path.clone())
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
|
@ -5163,10 +5188,13 @@ impl BackgroundScanner {
|
||||||
let branch = local_repository.repo_ptr.branch_name();
|
let branch = local_repository.repo_ptr.branch_name();
|
||||||
local_repository.repo_ptr.reload_index();
|
local_repository.repo_ptr.reload_index();
|
||||||
|
|
||||||
state.snapshot.git_repositories.update(&entry_id, |entry| {
|
state.snapshot.git_repositories.update(
|
||||||
entry.git_dir_scan_id = scan_id;
|
&local_repository.work_directory_id,
|
||||||
entry.status_scan_id = scan_id;
|
|entry| {
|
||||||
});
|
entry.git_dir_scan_id = scan_id;
|
||||||
|
entry.status_scan_id = scan_id;
|
||||||
|
},
|
||||||
|
);
|
||||||
state.snapshot.snapshot.repositories.update(
|
state.snapshot.snapshot.repositories.update(
|
||||||
&PathKey(work_dir.clone()),
|
&PathKey(work_dir.clone()),
|
||||||
&(),
|
&(),
|
||||||
|
@ -5260,6 +5288,11 @@ impl BackgroundScanner {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let merge_head_shas = local_repository.repo().merge_head_shas();
|
||||||
|
if merge_head_shas != local_repository.current_merge_head_shas {
|
||||||
|
mem::take(&mut repository.current_merge_conflicts);
|
||||||
|
}
|
||||||
|
|
||||||
let mut new_entries_by_path = SumTree::new(&());
|
let mut new_entries_by_path = SumTree::new(&());
|
||||||
for (repo_path, status) in statuses.entries.iter() {
|
for (repo_path, status) in statuses.entries.iter() {
|
||||||
let project_path = repository.work_directory.unrelativize(repo_path);
|
let project_path = repository.work_directory.unrelativize(repo_path);
|
||||||
|
@ -5283,6 +5316,12 @@ impl BackgroundScanner {
|
||||||
.snapshot
|
.snapshot
|
||||||
.repositories
|
.repositories
|
||||||
.insert_or_replace(repository, &());
|
.insert_or_replace(repository, &());
|
||||||
|
state.snapshot.git_repositories.update(
|
||||||
|
&local_repository.work_directory_id,
|
||||||
|
|entry| {
|
||||||
|
entry.current_merge_head_shas = merge_head_shas;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
util::extend_sorted(
|
util::extend_sorted(
|
||||||
&mut state.changed_paths,
|
&mut state.changed_paths,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue