1use std::cmp;
18use std::collections::HashMap;
19use std::collections::HashSet;
20use std::rc::Rc;
21use std::sync::Arc;
22
23use clap::ValueEnum;
24use itertools::Itertools as _;
25use jj_lib::backend;
26use jj_lib::backend::BackendResult;
27use jj_lib::backend::CommitId;
28use jj_lib::config::ConfigValue;
29use jj_lib::op_store::LocalRemoteRefTarget;
30use jj_lib::ref_name::RefName;
31use jj_lib::store::Store;
32use jj_lib::str_util::StringMatcher;
33
34use crate::commit_templater::CommitRef;
35
36#[derive(Clone, Debug)]
38pub struct RefListItem {
39 pub primary: Rc<CommitRef>,
41 pub tracked: Vec<Rc<CommitRef>>,
43}
44
45pub struct RefFilterPredicates {
47 pub name_matcher: StringMatcher,
49 pub remote_matcher: StringMatcher,
51 pub matched_local_targets: HashSet<CommitId>,
53 pub conflicted: bool,
55 pub include_local_only: bool,
57 pub include_synced_remotes: bool,
59 pub include_untracked_remotes: bool,
61}
62
63pub fn collect_items<'a>(
65 all_refs: impl IntoIterator<Item = (&'a RefName, LocalRemoteRefTarget<'a>)>,
66 predicates: &RefFilterPredicates,
67) -> Vec<RefListItem> {
68 let mut list_items = Vec::new();
69 let refs_to_list = all_refs
70 .into_iter()
71 .filter(|(name, targets)| {
72 predicates.name_matcher.is_match(name.as_str())
73 || targets
74 .local_target
75 .added_ids()
76 .any(|id| predicates.matched_local_targets.contains(id))
77 })
78 .filter(|(_, targets)| !predicates.conflicted || targets.local_target.has_conflict());
79 for (name, targets) in refs_to_list {
80 let LocalRemoteRefTarget {
81 local_target,
82 remote_refs,
83 } = targets;
84 let (mut tracked_remote_refs, untracked_remote_refs) = remote_refs
85 .iter()
86 .copied()
87 .filter(|(remote_name, _)| predicates.remote_matcher.is_match(remote_name.as_str()))
88 .partition::<Vec<_>, _>(|&(_, remote_ref)| remote_ref.is_tracked());
89 if !predicates.include_synced_remotes {
90 tracked_remote_refs.retain(|&(_, remote_ref)| remote_ref.target != *local_target);
91 }
92
93 if predicates.include_local_only && local_target.is_present()
94 || !tracked_remote_refs.is_empty()
95 {
96 let primary = CommitRef::local(
97 name,
98 local_target.clone(),
99 remote_refs.iter().map(|&(_, remote_ref)| remote_ref),
100 );
101 let tracked = tracked_remote_refs
102 .iter()
103 .map(|&(remote, remote_ref)| {
104 CommitRef::remote(name, remote, remote_ref.clone(), local_target)
105 })
106 .collect();
107 list_items.push(RefListItem { primary, tracked });
108 }
109
110 if predicates.include_untracked_remotes {
111 list_items.extend(untracked_remote_refs.iter().map(|&(remote, remote_ref)| {
112 RefListItem {
113 primary: CommitRef::remote_only(name, remote, remote_ref.target.clone()),
114 tracked: vec![],
115 }
116 }));
117 }
118 }
119
120 list_items
121}
122
123#[derive(Copy, Clone, PartialEq, Debug, ValueEnum)]
125pub enum SortKey {
126 Name,
127 #[value(name = "name-")]
128 NameDesc,
129 AuthorName,
130 #[value(name = "author-name-")]
131 AuthorNameDesc,
132 AuthorEmail,
133 #[value(name = "author-email-")]
134 AuthorEmailDesc,
135 AuthorDate,
136 #[value(name = "author-date-")]
137 AuthorDateDesc,
138 CommitterName,
139 #[value(name = "committer-name-")]
140 CommitterNameDesc,
141 CommitterEmail,
142 #[value(name = "committer-email-")]
143 CommitterEmailDesc,
144 CommitterDate,
145 #[value(name = "committer-date-")]
146 CommitterDateDesc,
147}
148
149impl SortKey {
150 fn is_commit_dependant(&self) -> bool {
151 match self {
152 Self::Name | Self::NameDesc => false,
153 Self::AuthorName
154 | Self::AuthorNameDesc
155 | Self::AuthorEmail
156 | Self::AuthorEmailDesc
157 | Self::AuthorDate
158 | Self::AuthorDateDesc
159 | Self::CommitterName
160 | Self::CommitterNameDesc
161 | Self::CommitterEmail
162 | Self::CommitterEmailDesc
163 | Self::CommitterDate
164 | Self::CommitterDateDesc => true,
165 }
166 }
167}
168
169pub fn parse_sort_keys(value: ConfigValue) -> Result<Vec<SortKey>, String> {
170 if let Some(array) = value.as_array() {
171 array
172 .iter()
173 .map(|item| {
174 item.as_str()
175 .ok_or("Expected sort key as a string".to_owned())
176 .and_then(|key| SortKey::from_str(key, false))
177 })
178 .try_collect()
179 } else {
180 Err("Expected an array of sort keys as strings".to_owned())
181 }
182}
183
184pub fn sort(
189 store: &Arc<Store>,
190 items: &mut [RefListItem],
191 sort_keys: &[SortKey],
192) -> BackendResult<()> {
193 let mut commits: HashMap<CommitId, Arc<backend::Commit>> = HashMap::new();
194 if sort_keys.iter().any(|key| key.is_commit_dependant()) {
195 commits = items
196 .iter()
197 .filter_map(|item| item.primary.target().added_ids().next())
198 .map(|commit_id| {
199 store
200 .get_commit(commit_id)
201 .map(|commit| (commit_id.clone(), commit.store_commit().clone()))
202 })
203 .try_collect()?;
204 }
205 sort_inner(items, sort_keys, &commits);
206 Ok(())
207}
208
209fn sort_inner(
210 items: &mut [RefListItem],
211 sort_keys: &[SortKey],
212 commits: &HashMap<CommitId, Arc<backend::Commit>>,
213) {
214 let to_commit = |item: &RefListItem| {
215 let id = item.primary.target().added_ids().next()?;
216 commits.get(id)
217 };
218
219 for sort_key in sort_keys
222 .iter()
223 .rev()
224 .skip_while(|key| *key == &SortKey::Name)
225 {
226 match sort_key {
227 SortKey::Name => {
228 items.sort_by_key(|item| {
229 (
230 item.primary.name().to_owned(),
231 item.primary.remote_name().map(|name| name.to_owned()),
232 )
233 });
234 }
235 SortKey::NameDesc => {
236 items.sort_by_key(|item| {
237 cmp::Reverse((
238 item.primary.name().to_owned(),
239 item.primary.remote_name().map(|name| name.to_owned()),
240 ))
241 });
242 }
243 SortKey::AuthorName => {
244 items.sort_by_key(|item| to_commit(item).map(|commit| commit.author.name.as_str()));
245 }
246 SortKey::AuthorNameDesc => {
247 items.sort_by_key(|item| {
248 cmp::Reverse(to_commit(item).map(|commit| commit.author.name.as_str()))
249 });
250 }
251 SortKey::AuthorEmail => {
252 items
253 .sort_by_key(|item| to_commit(item).map(|commit| commit.author.email.as_str()));
254 }
255 SortKey::AuthorEmailDesc => {
256 items.sort_by_key(|item| {
257 cmp::Reverse(to_commit(item).map(|commit| commit.author.email.as_str()))
258 });
259 }
260 SortKey::AuthorDate => {
261 items.sort_by_key(|item| to_commit(item).map(|commit| commit.author.timestamp));
262 }
263 SortKey::AuthorDateDesc => {
264 items.sort_by_key(|item| {
265 cmp::Reverse(to_commit(item).map(|commit| commit.author.timestamp))
266 });
267 }
268 SortKey::CommitterName => {
269 items.sort_by_key(|item| {
270 to_commit(item).map(|commit| commit.committer.name.as_str())
271 });
272 }
273 SortKey::CommitterNameDesc => {
274 items.sort_by_key(|item| {
275 cmp::Reverse(to_commit(item).map(|commit| commit.committer.name.as_str()))
276 });
277 }
278 SortKey::CommitterEmail => {
279 items.sort_by_key(|item| {
280 to_commit(item).map(|commit| commit.committer.email.as_str())
281 });
282 }
283 SortKey::CommitterEmailDesc => {
284 items.sort_by_key(|item| {
285 cmp::Reverse(to_commit(item).map(|commit| commit.committer.email.as_str()))
286 });
287 }
288 SortKey::CommitterDate => {
289 items.sort_by_key(|item| to_commit(item).map(|commit| commit.committer.timestamp));
290 }
291 SortKey::CommitterDateDesc => {
292 items.sort_by_key(|item| {
293 cmp::Reverse(to_commit(item).map(|commit| commit.committer.timestamp))
294 });
295 }
296 }
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use jj_lib::backend::ChangeId;
303 use jj_lib::backend::MillisSinceEpoch;
304 use jj_lib::backend::Signature;
305 use jj_lib::backend::Timestamp;
306 use jj_lib::backend::TreeId;
307 use jj_lib::merge::Merge;
308 use jj_lib::op_store::RefTarget;
309
310 use super::*;
311
312 fn make_backend_commit(author: Signature, committer: Signature) -> Arc<backend::Commit> {
313 Arc::new(backend::Commit {
314 parents: vec![],
315 predecessors: vec![],
316 root_tree: Merge::resolved(TreeId::new(vec![])),
317 conflict_labels: Merge::resolved(String::new()),
318 change_id: ChangeId::new(vec![]),
319 description: String::new(),
320 author,
321 committer,
322 secure_sig: None,
323 })
324 }
325
326 fn make_default_signature() -> Signature {
327 Signature {
328 name: "Test User".to_owned(),
329 email: "test.user@g.com".to_owned(),
330 timestamp: Timestamp {
331 timestamp: MillisSinceEpoch(0),
332 tz_offset: 0,
333 },
334 }
335 }
336
337 fn commit_id_generator() -> impl FnMut() -> CommitId {
338 let mut iter = (1_u128..).map(|n| CommitId::new(n.to_le_bytes().into()));
339 move || iter.next().unwrap()
340 }
341
342 fn commit_ts_generator() -> impl FnMut() -> Timestamp {
343 let mut iter = Some(1_i64).into_iter().chain(1_i64..).map(|ms| Timestamp {
345 timestamp: MillisSinceEpoch(ms),
346 tz_offset: 0,
347 });
348 move || iter.next().unwrap()
349 }
350
351 fn prepare_data_sort_and_snapshot(sort_keys: &[SortKey]) -> String {
354 let mut new_commit_id = commit_id_generator();
355 let mut new_timestamp = commit_ts_generator();
356 let names = ["bob", "alice", "eve", "bob", "bob"];
357 let emails = [
358 "bob@g.com",
359 "alice@g.com",
360 "eve@g.com",
361 "bob@g.com",
362 "bob@g.com",
363 ];
364 let bookmark_names = ["feature", "bug-fix", "chore", "bug-fix", "feature"];
365 let remote_names = [None, Some("upstream"), None, Some("origin"), Some("origin")];
366 let deleted = [false, false, false, false, true];
367 let mut bookmark_items: Vec<RefListItem> = Vec::new();
368 let mut commits: HashMap<CommitId, Arc<backend::Commit>> = HashMap::new();
369 for (&name, &email, bookmark_name, remote_name, &is_deleted) in
370 itertools::izip!(&names, &emails, &bookmark_names, &remote_names, &deleted)
371 {
372 let commit_id = new_commit_id();
373 let mut b_name = "foo";
374 let mut author = make_default_signature();
375 let mut committer = make_default_signature();
376
377 if sort_keys.contains(&SortKey::Name) || sort_keys.contains(&SortKey::NameDesc) {
378 b_name = bookmark_name;
379 }
380 if sort_keys.contains(&SortKey::AuthorName)
381 || sort_keys.contains(&SortKey::AuthorNameDesc)
382 {
383 author.name = String::from(name);
384 }
385 if sort_keys.contains(&SortKey::AuthorEmail)
386 || sort_keys.contains(&SortKey::AuthorEmailDesc)
387 {
388 author.email = String::from(email);
389 }
390 if sort_keys.contains(&SortKey::AuthorDate)
391 || sort_keys.contains(&SortKey::AuthorDateDesc)
392 {
393 author.timestamp = new_timestamp();
394 }
395 if sort_keys.contains(&SortKey::CommitterName)
396 || sort_keys.contains(&SortKey::CommitterNameDesc)
397 {
398 committer.name = String::from(name);
399 }
400 if sort_keys.contains(&SortKey::CommitterEmail)
401 || sort_keys.contains(&SortKey::CommitterEmailDesc)
402 {
403 committer.email = String::from(email);
404 }
405 if sort_keys.contains(&SortKey::CommitterDate)
406 || sort_keys.contains(&SortKey::CommitterDateDesc)
407 {
408 committer.timestamp = new_timestamp();
409 }
410
411 if let Some(remote_name) = remote_name {
412 if is_deleted {
413 bookmark_items.push(RefListItem {
414 primary: CommitRef::remote_only(b_name, *remote_name, RefTarget::absent()),
415 tracked: vec![CommitRef::local_only(
416 b_name,
417 RefTarget::normal(commit_id.clone()),
418 )],
419 });
420 } else {
421 bookmark_items.push(RefListItem {
422 primary: CommitRef::remote_only(
423 b_name,
424 *remote_name,
425 RefTarget::normal(commit_id.clone()),
426 ),
427 tracked: vec![],
428 });
429 }
430 } else {
431 bookmark_items.push(RefListItem {
432 primary: CommitRef::local_only(b_name, RefTarget::normal(commit_id.clone())),
433 tracked: vec![],
434 });
435 }
436
437 commits.insert(commit_id, make_backend_commit(author, committer));
438 }
439
440 bookmark_items.sort_by_key(|item| {
443 (
444 item.primary.name().to_owned(),
445 item.primary.remote_name().map(|name| name.to_owned()),
446 )
447 });
448
449 sort_and_snapshot(&mut bookmark_items, sort_keys, &commits)
450 }
451
452 fn sort_and_snapshot(
454 items: &mut [RefListItem],
455 sort_keys: &[SortKey],
456 commits: &HashMap<CommitId, Arc<backend::Commit>>,
457 ) -> String {
458 sort_inner(items, sort_keys, commits);
459
460 let to_commit = |item: &RefListItem| {
461 let id = item.primary.target().added_ids().next()?;
462 commits.get(id)
463 };
464
465 macro_rules! row_format {
466 ($($args:tt)*) => {
467 format!("{:<20}{:<16}{:<17}{:<14}{:<16}{:<17}{}", $($args)*)
468 }
469 }
470
471 let header = row_format!(
472 "Name",
473 "AuthorName",
474 "AuthorEmail",
475 "AuthorDate",
476 "CommitterName",
477 "CommitterEmail",
478 "CommitterDate"
479 );
480
481 let rows: Vec<String> = items
482 .iter()
483 .map(|item| {
484 let name = [Some(item.primary.name()), item.primary.remote_name()]
485 .iter()
486 .flatten()
487 .join("@");
488
489 let commit = to_commit(item);
490
491 let author_name = commit
492 .map(|c| c.author.name.clone())
493 .unwrap_or_else(|| String::from("-"));
494 let author_email = commit
495 .map(|c| c.author.email.clone())
496 .unwrap_or_else(|| String::from("-"));
497 let author_date = commit
498 .map(|c| c.author.timestamp.timestamp.0.to_string())
499 .unwrap_or_else(|| String::from("-"));
500
501 let committer_name = commit
502 .map(|c| c.committer.name.clone())
503 .unwrap_or_else(|| String::from("-"));
504 let committer_email = commit
505 .map(|c| c.committer.email.clone())
506 .unwrap_or_else(|| String::from("-"));
507 let committer_date = commit
508 .map(|c| c.committer.timestamp.timestamp.0.to_string())
509 .unwrap_or_else(|| String::from("-"));
510
511 row_format!(
512 name,
513 author_name,
514 author_email,
515 author_date,
516 committer_name,
517 committer_email,
518 committer_date
519 )
520 })
521 .collect();
522
523 let mut result = vec![header];
524 result.extend(rows);
525 result.join("\n")
526 }
527
528 #[test]
529 fn test_sort_by_name() {
530 insta::assert_snapshot!(
531 prepare_data_sort_and_snapshot(&[SortKey::Name]), @r"
532 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
533 bug-fix@origin Test User test.user@g.com 0 Test User test.user@g.com 0
534 bug-fix@upstream Test User test.user@g.com 0 Test User test.user@g.com 0
535 chore Test User test.user@g.com 0 Test User test.user@g.com 0
536 feature Test User test.user@g.com 0 Test User test.user@g.com 0
537 feature@origin - - - - - -
538 ");
539 }
540
541 #[test]
542 fn test_sort_by_name_desc() {
543 insta::assert_snapshot!(
544 prepare_data_sort_and_snapshot(&[SortKey::NameDesc]), @r"
545 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
546 feature@origin - - - - - -
547 feature Test User test.user@g.com 0 Test User test.user@g.com 0
548 chore Test User test.user@g.com 0 Test User test.user@g.com 0
549 bug-fix@upstream Test User test.user@g.com 0 Test User test.user@g.com 0
550 bug-fix@origin Test User test.user@g.com 0 Test User test.user@g.com 0
551 ");
552 }
553
554 #[test]
555 fn test_sort_by_author_name() {
556 insta::assert_snapshot!(
557 prepare_data_sort_and_snapshot(&[SortKey::AuthorName]), @r"
558 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
559 foo@origin - - - - - -
560 foo@upstream alice test.user@g.com 0 Test User test.user@g.com 0
561 foo bob test.user@g.com 0 Test User test.user@g.com 0
562 foo@origin bob test.user@g.com 0 Test User test.user@g.com 0
563 foo eve test.user@g.com 0 Test User test.user@g.com 0
564 ");
565 }
566
567 #[test]
568 fn test_sort_by_author_name_desc() {
569 insta::assert_snapshot!(
570 prepare_data_sort_and_snapshot(&[SortKey::AuthorNameDesc]), @r"
571 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
572 foo eve test.user@g.com 0 Test User test.user@g.com 0
573 foo bob test.user@g.com 0 Test User test.user@g.com 0
574 foo@origin bob test.user@g.com 0 Test User test.user@g.com 0
575 foo@upstream alice test.user@g.com 0 Test User test.user@g.com 0
576 foo@origin - - - - - -
577 ");
578 }
579
580 #[test]
581 fn test_sort_by_author_email() {
582 insta::assert_snapshot!(
583 prepare_data_sort_and_snapshot(&[SortKey::AuthorEmail]), @r"
584 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
585 foo@origin - - - - - -
586 foo@upstream Test User alice@g.com 0 Test User test.user@g.com 0
587 foo Test User bob@g.com 0 Test User test.user@g.com 0
588 foo@origin Test User bob@g.com 0 Test User test.user@g.com 0
589 foo Test User eve@g.com 0 Test User test.user@g.com 0
590 ");
591 }
592
593 #[test]
594 fn test_sort_by_author_email_desc() {
595 insta::assert_snapshot!(
596 prepare_data_sort_and_snapshot(&[SortKey::AuthorEmailDesc]), @r"
597 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
598 foo Test User eve@g.com 0 Test User test.user@g.com 0
599 foo Test User bob@g.com 0 Test User test.user@g.com 0
600 foo@origin Test User bob@g.com 0 Test User test.user@g.com 0
601 foo@upstream Test User alice@g.com 0 Test User test.user@g.com 0
602 foo@origin - - - - - -
603 ");
604 }
605
606 #[test]
607 fn test_sort_by_author_date() {
608 insta::assert_snapshot!(
609 prepare_data_sort_and_snapshot(&[SortKey::AuthorDate]), @r"
610 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
611 foo@origin - - - - - -
612 foo Test User test.user@g.com 1 Test User test.user@g.com 0
613 foo@upstream Test User test.user@g.com 1 Test User test.user@g.com 0
614 foo Test User test.user@g.com 2 Test User test.user@g.com 0
615 foo@origin Test User test.user@g.com 3 Test User test.user@g.com 0
616 ");
617 }
618
619 #[test]
620 fn test_sort_by_author_date_desc() {
621 insta::assert_snapshot!(
622 prepare_data_sort_and_snapshot(&[SortKey::AuthorDateDesc]), @r"
623 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
624 foo@origin Test User test.user@g.com 3 Test User test.user@g.com 0
625 foo Test User test.user@g.com 2 Test User test.user@g.com 0
626 foo Test User test.user@g.com 1 Test User test.user@g.com 0
627 foo@upstream Test User test.user@g.com 1 Test User test.user@g.com 0
628 foo@origin - - - - - -
629 ");
630 }
631
632 #[test]
633 fn test_sort_by_committer_name() {
634 insta::assert_snapshot!(
635 prepare_data_sort_and_snapshot(&[SortKey::CommitterName]), @r"
636 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
637 foo@origin - - - - - -
638 foo@upstream Test User test.user@g.com 0 alice test.user@g.com 0
639 foo Test User test.user@g.com 0 bob test.user@g.com 0
640 foo@origin Test User test.user@g.com 0 bob test.user@g.com 0
641 foo Test User test.user@g.com 0 eve test.user@g.com 0
642 ");
643 }
644
645 #[test]
646 fn test_sort_by_committer_name_desc() {
647 insta::assert_snapshot!(
648 prepare_data_sort_and_snapshot(&[SortKey::CommitterNameDesc]), @r"
649 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
650 foo Test User test.user@g.com 0 eve test.user@g.com 0
651 foo Test User test.user@g.com 0 bob test.user@g.com 0
652 foo@origin Test User test.user@g.com 0 bob test.user@g.com 0
653 foo@upstream Test User test.user@g.com 0 alice test.user@g.com 0
654 foo@origin - - - - - -
655 ");
656 }
657
658 #[test]
659 fn test_sort_by_committer_email() {
660 insta::assert_snapshot!(
661 prepare_data_sort_and_snapshot(&[SortKey::CommitterEmail]), @r"
662 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
663 foo@origin - - - - - -
664 foo@upstream Test User test.user@g.com 0 Test User alice@g.com 0
665 foo Test User test.user@g.com 0 Test User bob@g.com 0
666 foo@origin Test User test.user@g.com 0 Test User bob@g.com 0
667 foo Test User test.user@g.com 0 Test User eve@g.com 0
668 ");
669 }
670
671 #[test]
672 fn test_sort_by_committer_email_desc() {
673 insta::assert_snapshot!(
674 prepare_data_sort_and_snapshot(&[SortKey::CommitterEmailDesc]), @r"
675 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
676 foo Test User test.user@g.com 0 Test User eve@g.com 0
677 foo Test User test.user@g.com 0 Test User bob@g.com 0
678 foo@origin Test User test.user@g.com 0 Test User bob@g.com 0
679 foo@upstream Test User test.user@g.com 0 Test User alice@g.com 0
680 foo@origin - - - - - -
681 ");
682 }
683
684 #[test]
685 fn test_sort_by_committer_date() {
686 insta::assert_snapshot!(
687 prepare_data_sort_and_snapshot(&[SortKey::CommitterDate]), @r"
688 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
689 foo@origin - - - - - -
690 foo Test User test.user@g.com 0 Test User test.user@g.com 1
691 foo@upstream Test User test.user@g.com 0 Test User test.user@g.com 1
692 foo Test User test.user@g.com 0 Test User test.user@g.com 2
693 foo@origin Test User test.user@g.com 0 Test User test.user@g.com 3
694 ");
695 }
696
697 #[test]
698 fn test_sort_by_committer_date_desc() {
699 insta::assert_snapshot!(
700 prepare_data_sort_and_snapshot(&[SortKey::CommitterDateDesc]), @r"
701 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
702 foo@origin Test User test.user@g.com 0 Test User test.user@g.com 3
703 foo Test User test.user@g.com 0 Test User test.user@g.com 2
704 foo Test User test.user@g.com 0 Test User test.user@g.com 1
705 foo@upstream Test User test.user@g.com 0 Test User test.user@g.com 1
706 foo@origin - - - - - -
707 ");
708 }
709
710 #[test]
711 fn test_sort_by_author_date_desc_and_name() {
712 insta::assert_snapshot!(
713 prepare_data_sort_and_snapshot(&[SortKey::AuthorDateDesc, SortKey::Name]), @r"
714 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
715 bug-fix@origin Test User test.user@g.com 3 Test User test.user@g.com 0
716 chore Test User test.user@g.com 2 Test User test.user@g.com 0
717 bug-fix@upstream Test User test.user@g.com 1 Test User test.user@g.com 0
718 feature Test User test.user@g.com 1 Test User test.user@g.com 0
719 feature@origin - - - - - -
720 ");
721 }
722
723 #[test]
724 fn test_sort_by_committer_name_and_name_desc() {
725 insta::assert_snapshot!(
726 prepare_data_sort_and_snapshot(&[SortKey::CommitterName, SortKey::NameDesc]), @r"
727 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
728 feature@origin - - - - - -
729 bug-fix@upstream Test User test.user@g.com 0 alice test.user@g.com 0
730 feature Test User test.user@g.com 0 bob test.user@g.com 0
731 bug-fix@origin Test User test.user@g.com 0 bob test.user@g.com 0
732 chore Test User test.user@g.com 0 eve test.user@g.com 0
733 ");
734 }
735
736 #[test]
739 fn test_sort_by_name_and_committer_date() {
740 insta::assert_snapshot!(
741 prepare_data_sort_and_snapshot(&[SortKey::Name, SortKey::AuthorDate]), @r"
742 Name AuthorName AuthorEmail AuthorDate CommitterName CommitterEmail CommitterDate
743 bug-fix@origin Test User test.user@g.com 3 Test User test.user@g.com 0
744 bug-fix@upstream Test User test.user@g.com 1 Test User test.user@g.com 0
745 chore Test User test.user@g.com 2 Test User test.user@g.com 0
746 feature Test User test.user@g.com 1 Test User test.user@g.com 0
747 feature@origin - - - - - -
748 ");
749 }
750}