|
1 | 1 | use super::{Options, Outcome};
|
2 | 2 | use crate::branch::checkout::utils::merge_worktree_changes_into_destination_or_keep_snapshot;
|
| 3 | +use anyhow::Context; |
3 | 4 | use bstr::ByteSlice;
|
4 | 5 | use gitbutler_oxidize::ObjectIdExt;
|
5 | 6 | use gix::diff::rewrites::tracker::ChangeKind;
|
6 | 7 | use gix::objs::TreeRefIter;
|
7 | 8 | use gix::prelude::ObjectIdExt as _;
|
8 | 9 | use gix::refs::Target;
|
9 | 10 | use gix::refs::transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog};
|
| 11 | +use std::collections::BTreeSet; |
10 | 12 | use tracing::instrument;
|
11 | 13 |
|
12 | 14 | /// Given the `current_head_id^{tree}` for the tree that matches what `HEAD` points to, perform all file operations necessary
|
@@ -76,8 +78,42 @@ pub fn safe_checkout(
|
76 | 78 | let destination_tree = git2_repo
|
77 | 79 | .find_tree(destination_tree.id.to_git2())?
|
78 | 80 | .into_object();
|
| 81 | + let mut dirs_we_tried_to_delete = BTreeSet::new(); |
79 | 82 | for (kind, path_to_alter) in &changed_files {
|
80 |
| - if !matches!(kind, ChangeKind::Deletion) { |
| 83 | + if matches!(kind, ChangeKind::Deletion) { |
| 84 | + // By now we can assume that the destination tree contains all files that should be |
| 85 | + // in the worktree, along with the worktree changes we will touch. |
| 86 | + // Thus, it's safe to delete the files that should be deleted, before possibly recreating them. |
| 87 | + let path_to_delete = repo |
| 88 | + .workdir_path(path_to_alter) |
| 89 | + .context("non-bare repository")?; |
| 90 | + if let Err(err) = std::fs::remove_file(&path_to_delete) { |
| 91 | + if err.kind() == std::io::ErrorKind::NotFound |
| 92 | + || err.kind() == std::io::ErrorKind::PermissionDenied |
| 93 | + || err.kind() == std::io::ErrorKind::NotADirectory |
| 94 | + { |
| 95 | + continue; |
| 96 | + }; |
| 97 | + if err.kind() == std::io::ErrorKind::IsADirectory { |
| 98 | + std::fs::remove_dir_all(path_to_delete)?; |
| 99 | + } else { |
| 100 | + return Err(err.into()); |
| 101 | + } |
| 102 | + } else { |
| 103 | + for dir_to_delete in path_to_delete.ancestors().skip(1) { |
| 104 | + if !dirs_we_tried_to_delete.insert(dir_to_delete.to_owned()) { |
| 105 | + break; |
| 106 | + } |
| 107 | + if let Err(err) = std::fs::remove_dir(dir_to_delete) { |
| 108 | + if err.kind() == std::io::ErrorKind::DirectoryNotEmpty { |
| 109 | + break; |
| 110 | + } else { |
| 111 | + return Err(err.into()); |
| 112 | + } |
| 113 | + } |
| 114 | + } |
| 115 | + } |
| 116 | + } else { |
81 | 117 | opts.path(path_to_alter.as_bytes());
|
82 | 118 | }
|
83 | 119 | }
|
|
0 commit comments