Skip to content

Commit d17d5fe

Browse files
author
Naseschwarz
committed
Disable dotted range commit yanking
The algorithm for computing marked_consecutive assumes that commits are consecutive in the commit graph if they are consecutive in the linearized log used in` commitlist.rs`. That is not universally correct, as siblings may also be displayed consecutively in this list. For now, this just removes generating commit lists in dotted range notation.
1 parent 6a884d1 commit d17d5fe

File tree

4 files changed

+177
-23
lines changed

4 files changed

+177
-23
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424
* set the terminal title to `gitui ({repo_path})` [[@acuteenvy](https://github.com/acuteenvy)] ([#2462](https://github.com/gitui-org/gitui/issues/2462))
2525
* respect `.mailmap` [[@acuteenvy](https://github.com/acuteenvy)] ([#2406](https://github.com/gitui-org/gitui/issues/2406))
2626

27+
### Fixes
28+
* yanking commit ranges no longer generates incorrect dotted range notations, but lists each individual commit [[@naseschwarz](https://github.com/naseschwarz)] (https://github.com/gitui-org/gitui/issues/2576)
29+
2730
## [0.27.0] - 2024-01-14
2831

2932
**new: manage remotes**

asyncgit/src/sync/commits_info.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ impl CommitId {
4949
let commit_obj = repo.revparse_single(revision)?;
5050
Ok(commit_obj.id().into())
5151
}
52+
53+
/// Tries to convert a &str representation of a commit id into
54+
/// a `CommitId`
55+
pub fn from_str_unchecked(commit_id_str: &str) -> Result<Self> {
56+
match Oid::from_str(commit_id_str) {
57+
Err(e) => Err(crate::Error::Generic(format!(
58+
"Could not convert {}",
59+
e.message()
60+
))),
61+
Ok(v) => Ok(Self::new(v)),
62+
}
63+
}
5264
}
5365

5466
impl Display for CommitId {
@@ -73,7 +85,7 @@ impl From<Oid> for CommitId {
7385
}
7486

7587
///
76-
#[derive(Debug)]
88+
#[derive(Debug, Clone)]
7789
pub struct CommitInfo {
7890
///
7991
pub message: String,

src/components/commitlist.rs

Lines changed: 160 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,9 @@ impl CommitList {
132132
commits
133133
}
134134

135-
///
136-
pub fn copy_commit_hash(&self) -> Result<()> {
137-
let marked = self.marked.as_slice();
138-
let yank: Option<String> = match marked {
135+
/// Build string of marked or selected (if none are marked) commit ids
136+
fn concat_selected_commit_ids(&self) -> Option<String> {
137+
match self.marked.as_slice() {
139138
[] => self
140139
.items
141140
.iter()
@@ -144,24 +143,19 @@ impl CommitList {
144143
.saturating_sub(self.items.index_offset()),
145144
)
146145
.map(|e| e.id.to_string()),
147-
[(_idx, commit)] => Some(commit.to_string()),
148-
[first, .., last] => {
149-
let marked_consecutive =
150-
marked.windows(2).all(|w| w[0].0 + 1 == w[1].0);
151-
152-
let yank = if marked_consecutive {
153-
format!("{}^..{}", first.1, last.1)
154-
} else {
155-
marked
156-
.iter()
157-
.map(|(_idx, commit)| commit.to_string())
158-
.join(" ")
159-
};
160-
Some(yank)
161-
}
162-
};
146+
marked => Some(
147+
marked
148+
.iter()
149+
.map(|(_idx, commit)| commit.to_string())
150+
.join(" "),
151+
),
152+
}
153+
}
163154

164-
if let Some(yank) = yank {
155+
/// Copy currently marked or selected (if none are marked) commit ids
156+
/// to clipboard
157+
pub fn copy_commit_hash(&self) -> Result<()> {
158+
if let Some(yank) = self.concat_selected_commit_ids() {
165159
crate::clipboard::copy_string(&yank)?;
166160
self.queue.push(InternalEvent::ShowInfoMsg(
167161
strings::copy_success(&yank),
@@ -893,8 +887,36 @@ impl Component for CommitList {
893887

894888
#[cfg(test)]
895889
mod tests {
890+
use asyncgit::sync::CommitInfo;
891+
896892
use super::*;
897893

894+
impl Default for CommitList {
895+
fn default() -> Self {
896+
Self {
897+
title: String::from("").into_boxed_str(),
898+
selection: 0,
899+
highlighted_selection: Option::None,
900+
highlights: Option::None,
901+
tags: Option::None,
902+
items: ItemBatch::default(),
903+
commits: IndexSet::default(),
904+
marked: Vec::default(),
905+
scroll_top: Cell::default(),
906+
local_branches: BTreeMap::default(),
907+
remote_branches: BTreeMap::default(),
908+
theme: SharedTheme::default(),
909+
key_config: SharedKeyConfig::default(),
910+
scroll_state: (Instant::now(), 0.0),
911+
current_size: Cell::default(),
912+
repo: RepoPathRef::new(sync::RepoPath::Path(
913+
std::path::PathBuf::default(),
914+
)),
915+
queue: Queue::default(),
916+
}
917+
}
918+
}
919+
898920
#[test]
899921
fn test_string_width_align() {
900922
assert_eq!(string_width_align("123", 3), "123");
@@ -916,4 +938,121 @@ mod tests {
916938
"Jon Grythe Stødle "
917939
);
918940
}
941+
942+
/// Build a commit list with a few commits loaded
943+
fn build_commit_list_with_some_commits() -> CommitList {
944+
let mut items = ItemBatch::default();
945+
let basic_commit_info = CommitInfo {
946+
message: String::default(),
947+
time: 0,
948+
author: String::default(),
949+
id: CommitId::default(),
950+
};
951+
// This just creates a sequence of fake ordered ids
952+
// 0000000000000000000000000000000000000000
953+
// 0000000000000000000000000000000000000001
954+
// 0000000000000000000000000000000000000002
955+
// ...
956+
items.set_items(
957+
0,
958+
(0..10)
959+
.map(|idx| CommitInfo {
960+
id: CommitId::from_str_unchecked(&format!(
961+
"{:040}",
962+
idx
963+
))
964+
.unwrap(),
965+
..basic_commit_info.clone()
966+
})
967+
.collect(),
968+
None,
969+
);
970+
CommitList {
971+
items: items,
972+
selection: 4, // Randomly select one commit
973+
..Default::default()
974+
}
975+
}
976+
977+
/// Build a value for cl.marked based on indices into cl.items
978+
fn build_marked_from_indices(
979+
cl: &CommitList,
980+
marked_indices: &[usize],
981+
) -> Vec<(usize, CommitId)> {
982+
marked_indices
983+
.iter()
984+
.map(|idx| (*idx, cl.items.iter().nth(*idx).unwrap().id))
985+
.collect()
986+
}
987+
988+
#[test]
989+
fn test_copy_commit_list_empty() {
990+
assert_eq!(
991+
CommitList::default().concat_selected_commit_ids(),
992+
None
993+
);
994+
}
995+
996+
#[test]
997+
fn test_copy_commit_none_marked() {
998+
let cl = CommitList {
999+
selection: 4,
1000+
..build_commit_list_with_some_commits()
1001+
};
1002+
assert_eq!(
1003+
cl.concat_selected_commit_ids(),
1004+
Some(String::from(
1005+
"0000000000000000000000000000000000000004"
1006+
))
1007+
);
1008+
}
1009+
1010+
#[test]
1011+
fn test_copy_commit_one_marked() {
1012+
let cl = build_commit_list_with_some_commits();
1013+
let cl = CommitList {
1014+
marked: build_marked_from_indices(&cl, &vec![2]),
1015+
..cl
1016+
};
1017+
assert_eq!(
1018+
cl.concat_selected_commit_ids(),
1019+
Some(String::from(
1020+
"0000000000000000000000000000000000000002",
1021+
))
1022+
);
1023+
}
1024+
1025+
#[test]
1026+
fn test_copy_commit_range_marked() {
1027+
let cl = build_commit_list_with_some_commits();
1028+
let cl = CommitList {
1029+
marked: build_marked_from_indices(&cl, &vec![4, 5, 6, 7]),
1030+
..cl
1031+
};
1032+
assert_eq!(
1033+
cl.concat_selected_commit_ids(),
1034+
Some(String::from(concat!(
1035+
"0000000000000000000000000000000000000004 ",
1036+
"0000000000000000000000000000000000000005 ",
1037+
"0000000000000000000000000000000000000006 ",
1038+
"0000000000000000000000000000000000000007"
1039+
)))
1040+
);
1041+
}
1042+
1043+
#[test]
1044+
fn test_copy_commit_random_marked() {
1045+
let cl = build_commit_list_with_some_commits();
1046+
let cl = CommitList {
1047+
marked: build_marked_from_indices(&cl, &vec![4, 7]),
1048+
..cl
1049+
};
1050+
assert_eq!(
1051+
cl.concat_selected_commit_ids(),
1052+
Some(String::from(concat!(
1053+
"0000000000000000000000000000000000000004 ",
1054+
"0000000000000000000000000000000000000007"
1055+
)))
1056+
);
1057+
}
9191058
}

src/queue.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ pub enum InternalEvent {
160160
}
161161

162162
/// single threaded simple queue for components to communicate with each other
163-
#[derive(Clone)]
163+
#[derive(Clone, Default)]
164164
pub struct Queue {
165165
data: Rc<RefCell<VecDeque<InternalEvent>>>,
166166
}

0 commit comments

Comments
 (0)