use gix_diff::{ Rewrites, blob::DiffLineStats, rewrites, rewrites::{ Copies, CopySource, tracker::{ ChangeKind, visit::{Source, SourceKind}, }, }, tree::visit::Relation,};use gix_object::tree::EntryKind;use pretty_assertions::assert_eq; use crate::{ hex_to_id, rewrites::{Change, NULL_ID},}; #[test]fn rename_by_id() -> crate::Result { // Limits are only applied when doing rewrite-checks for limit in [0, 1] { let rewrites = Rewrites { copies: None, percentage: None, limit, track_empty: false, }; let mut track = util::new_tracker(rewrites); assert!( track.try_push_change(Change::modification(), "a".into()).is_some(), "modifications play no role in rename tracking" ); assert!( track.try_push_change(Change::deletion(), "b".into()).is_none(), "recorded for later matching" ); assert!( track.try_push_change(Change::addition(), "c".into()).is_none(), "recorded for later matching" ); let mut called = false; let out = util::assert_emit(&mut track, |dst, src| { assert!(!called, "only one rename pair is expected"); called = true; assert_eq!( src.unwrap(), Source { entry_mode: EntryKind::Blob.into(), id: *NULL_ID, kind: SourceKind::Rename, location: "b".into(), change: &Change::deletion(), diff: None, } ); assert_eq!(dst.location, "c"); std::ops::ControlFlow::Continue(()) }); assert_eq!( out, rewrites::Outcome { options: rewrites, ..Default::default() }, "no similarity check was performed, it was all matched by id" ); } Ok(())} #[test]fn gitlinks_are_renamed_only_by_id() { let id = hex_to_id( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ); let other_id = hex_to_id( "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", ); let gitlink = |id, kind| Change { id, kind, mode: EntryKind::Commit.into(), relation: None, }; let mut tracker = util::new_tracker(Default::default()); assert!( tracker .try_push_change(gitlink(id, ChangeKind::Deletion), "old".into()) .is_none() ); assert!( tracker .try_push_change(gitlink(id, ChangeKind::Addition), "new".into()) .is_none() ); let mut matched = false; util::assert_emit(&mut tracker, |destination, source| { assert_eq!(destination.location, "new"); assert_eq!(source.expect("equal gitlink IDs form an exact rename").location, "old"); matched = true; std::ops::ControlFlow::Continue(()) }); assert!(matched, "the exact gitlink rename was emitted"); let mut tracker = util::new_tracker(Default::default()); assert!( tracker .try_push_change(gitlink(id, ChangeKind::Deletion), "old".into()) .is_none() ); assert!( tracker .try_push_change(gitlink(other_id, ChangeKind::Addition), "new".into()) .is_none() ); let mut unmatched = 0; util::assert_emit(&mut tracker, |_destination, source| { assert!(source.is_none(), "gitlink IDs are never compared by blob similarity"); unmatched += 1; std::ops::ControlFlow::Continue(()) }); assert_eq!(unmatched, 2, "both unrelated gitlink changes remain unmatched");} #[test]fn copy_by_similarity_reports_limit_if_encountered() -> crate::Result { let rewrites = Rewrites { copies: Some(Copies { source: CopySource::FromSetOfModifiedFiles, percentage: Some(0.5), }), percentage: None, limit: 1, track_empty: false, }; let mut track = util::new_tracker(rewrites); let odb = util::add_retained_blobs( &mut track, [ (Change::modification(), "a", "a\n"), (Change::addition(), "a-cpy-1", "a"), (Change::addition(), "a-cpy-2", "a"), (Change::modification(), "d", "ab"), ], )?; let mut calls = 0; let out = util::assert_emit_with_objects( &mut track, |dst, src| { assert!(src.is_none()); match calls { 0 => assert_eq!(dst.location, "a"), 1 => assert_eq!(dst.location, "a-cpy-1"), 2 => assert_eq!(dst.location, "a-cpy-2"), 3 => assert_eq!(dst.location, "d"), _ => panic!("too many emissions"), } calls += 1; std::ops::ControlFlow::Continue(()) }, odb, ); assert_eq!( out, rewrites::Outcome { options: rewrites, num_similarity_checks_skipped_for_copy_tracking_due_to_limit: 4, ..Default::default() }, "no similarity check was performed at all - all or nothing" ); Ok(())} #[test]fn copy_by_id() -> crate::Result { // Limits are only applied when doing rewrite-checks for limit in [0, 1] { let rewrites = Rewrites { copies: Some(Copies { source: CopySource::FromSetOfModifiedFiles, percentage: None, }), percentage: None, limit, track_empty: false, }; let mut track = util::new_tracker(rewrites); let odb = util::add_retained_blobs( &mut track, [ (Change::modification(), "a", "a"), (Change::addition(), "a-cpy-1", "a"), (Change::addition(), "a-cpy-2", "a"), (Change::modification(), "d", "a"), ], )?; let mut calls = 0; let out = util::assert_emit_with_objects( &mut track, |dst, src| { let id = hex_to_id( "2e65efe2a145dda7ee51d1741299f848e5bf752e", "eb337bcee2061c5313c9a1392116b6c76039e9e30d71467ae359b36277e17dc7", ); let source_a = Source { entry_mode: EntryKind::Blob.into(), id, kind: SourceKind::Copy, location: "a".into(), change: &Change { id, ..Change::modification() }, diff: None, }; match calls { 0 => { assert_eq!(src.unwrap(), source_a); assert_eq!( dst.location, "a-cpy-1", "it just finds the first possible match in order, ignoring other candidates" ); } 1 => { assert_eq!(src.unwrap(), source_a, "copy-sources can be used multiple times"); assert_eq!(dst.location, "a-cpy-2"); } 2 => { assert!(src.is_none()); assert_eq!(dst.location, "d"); } _ => panic!("too many emissions"), } calls += 1; std::ops::ControlFlow::Continue(()) }, odb, ); assert_eq!( out, rewrites::Outcome { options: rewrites, ..Default::default() }, "no similarity check was performed, it was all matched by id" ); } Ok(())} #[test]fn copy_by_id_search_in_all_sources() -> crate::Result { // Limits are only applied when doing rewrite-checks for limit in [0, 1] { let rewrites = Rewrites { copies: Some(Copies { source: CopySource::FromSetOfModifiedFilesAndAllSources, percentage: None, }), percentage: None, limit, track_empty: false, }; let mut track = util::new_tracker(rewrites); let odb = util::add_retained_blobs( &mut track, [ (Change::addition(), "a-cpy-1", "a"), (Change::addition(), "a-cpy-2", "a"), ], )?; let content_id = hex_to_id( "2e65efe2a145dda7ee51d1741299f848e5bf752e", "eb337bcee2061c5313c9a1392116b6c76039e9e30d71467ae359b36277e17dc7", ); let mut calls = 0; let out = util::assert_emit_with_objects_and_sources( &mut track, |dst, src| { let source_a = Source { entry_mode: EntryKind::Blob.into(), id: content_id, kind: SourceKind::Copy, location: "a-src".into(), change: &Change { id: content_id, ..Change::modification() }, diff: None, }; match calls { 0 => { assert_eq!(src.unwrap(), source_a); assert_eq!( dst.location, "a-cpy-1", "it just finds the first possible match in order, ignoring other candidates" ); } 1 => { assert_eq!(src.unwrap(), source_a, "copy-sources can be used multiple times"); assert_eq!(dst.location, "a-cpy-2"); } 2 => { assert!(src.is_none()); assert_eq!(dst.location, "d"); } _ => panic!("too many emissions"), } calls += 1; std::ops::ControlFlow::Continue(()) }, odb, [( { let mut c = Change::modification(); c.id = content_id; c }, "a-src", )], ); assert_eq!( out, rewrites::Outcome { options: rewrites, ..Default::default() }, "no similarity check was performed, it was all matched by id" ); } Ok(())} #[test]fn copy_by_50_percent_similarity() -> crate::Result { let rewrites = Rewrites { copies: Some(Copies { source: CopySource::FromSetOfModifiedFiles, percentage: Some(0.5), }), percentage: None, limit: 0, track_empty: false, }; let mut track = util::new_tracker(rewrites); let odb = util::add_retained_blobs( &mut track, [ (Change::modification(), "a", "a\n"), (Change::addition(), "a-cpy-1", "a\nb"), (Change::addition(), "a-cpy-2", "a\nc"), (Change::modification(), "d", "a"), ], )?; let mut calls = 0; let out = util::assert_emit_with_objects( &mut track, |dst, src| { let id = hex_to_id( "78981922613b2afb6025042ff6bd878ac1994e85", "f8625e43f9e04f24291f77cdbe4c71b3c2a3b0003f60419b3ed06a058d766c8b", ); let source_a = Source { entry_mode: EntryKind::Blob.into(), id, kind: SourceKind::Copy, location: "a".into(), change: &Change { id, ..Change::modification() }, diff: Some(DiffLineStats { removals: 0, insertions: 1, before: 1, after: 2, similarity: 0.6666667, }), }; match calls { 0 => { assert_eq!( src.unwrap(), source_a, "it finds the first possible source, no candidates" ); assert_eq!(dst.location, "a-cpy-1"); } 1 => { assert_eq!(src.unwrap(), source_a, "the same source can be reused as well"); assert_eq!(dst.location, "a-cpy-2"); } 2 => { assert!(src.is_none()); assert_eq!(dst.location, "d"); } _ => panic!("too many emissions"), } calls += 1; std::ops::ControlFlow::Continue(()) }, odb, ); assert_eq!( out, rewrites::Outcome { options: rewrites, num_similarity_checks: 4, ..Default::default() }, "no similarity check was performed, it was all matched by id" ); Ok(())} #[test]fn copy_by_id_in_additions_only() -> crate::Result { let rewrites = Rewrites { copies: Some(Copies { source: CopySource::FromSetOfModifiedFiles, percentage: None, }), percentage: None, limit: 0, track_empty: false, }; let mut track = util::new_tracker(rewrites); let odb = util::add_retained_blobs( &mut track, [ (Change::modification(), "a", "a"), (Change::modification(), "a-cpy-1", "a"), ], )?; let mut calls = 0; let out = util::assert_emit_with_objects( &mut track, |dst, src| { match calls { 0 => { assert!(src.is_none()); assert_eq!(dst.location, "a"); } 1 => { assert!(src.is_none()); assert_eq!( dst.location, "a-cpy-1", "copy detection is only done for additions, not within modifications" ); } _ => panic!("too many emissions"), } calls += 1; std::ops::ControlFlow::Continue(()) }, odb, ); assert_eq!( out, rewrites::Outcome { options: rewrites, ..Default::default() }, "no similarity check was performed, it was all matched by id" ); Ok(())} #[test]fn rename_by_similarity_reports_limit_if_encountered() -> crate::Result { let rewrites = Rewrites { copies: None, percentage: Some(0.5), limit: 1, track_empty: false, }; let mut track = util::new_tracker(rewrites); let odb = util::add_retained_blobs( &mut track, [ (Change::deletion(), "a", "first\nsecond\n"), (Change::addition(), "b", "firt\nsecond\n"), (Change::addition(), "c", "second\nunrelated\n"), ], )?; Lines 1–500 of 1261. Load more
use gix_diff::{ Rewrites, blob::DiffLineStats, rewrites, rewrites::{ Copies, CopySource, tracker::{ ChangeKind, visit::{Source, SourceKind}, }, }, tree::visit::Relation, }; use gix_object::tree::EntryKind; use pretty_assertions::assert_eq; use crate::{ hex_to_id, rewrites::{Change, NULL_ID}, }; #[test] fn rename_by_id() -> crate::Result { // Limits are only applied when doing rewrite-checks for limit in [0, 1] { let rewrites = Rewrites { copies: None, percentage: None, limit, track_empty: false, }; let mut track = util::new_tracker(rewrites); assert!( track.try_push_change(Change::modification(), "a".into()).is_some(), "modifications play no role in rename tracking" ); assert!( track.try_push_change(Change::deletion(), "b".into()).is_none(), "recorded for later matching" ); assert!( track.try_push_change(Change::addition(), "c".into()).is_none(), "recorded for later matching" ); let mut called = false; let out = util::assert_emit(&mut track, |dst, src| { assert!(!called, "only one rename pair is expected"); called = true; assert_eq!( src.unwrap(), Source { entry_mode: EntryKind::Blob.into(), id: *NULL_ID, kind: SourceKind::Rename, location: "b".into(), change: &Change::deletion(), diff: None, } ); assert_eq!(dst.location, "c"); std::ops::ControlFlow::Continue(()) }); assert_eq!( out, rewrites::Outcome { options: rewrites, ..Default::default() }, "no similarity check was performed, it was all matched by id" ); } Ok(()) } #[test] fn gitlinks_are_renamed_only_by_id() { let id = hex_to_id( "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ); let other_id = hex_to_id( "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", ); let gitlink = |id, kind| Change { id, kind, mode: EntryKind::Commit.into(), relation: None, }; let mut tracker = util::new_tracker(Default::default()); assert!( tracker .try_push_change(gitlink(id, ChangeKind::Deletion), "old".into()) .is_none() ); assert!( tracker .try_push_change(gitlink(id, ChangeKind::Addition), "new".into()) .is_none() ); let mut matched = false; util::assert_emit(&mut tracker, |destination, source| { assert_eq!(destination.location, "new"); assert_eq!(source.expect("equal gitlink IDs form an exact rename").location, "old"); matched = true; std::ops::ControlFlow::Continue(()) }); assert!(matched, "the exact gitlink rename was emitted"); let mut tracker = util::new_tracker(Default::default()); assert!( tracker .try_push_change(gitlink(id, ChangeKind::Deletion), "old".into()) .is_none() ); assert!( tracker .try_push_change(gitlink(other_id, ChangeKind::Addition), "new".into()) .is_none() ); let mut unmatched = 0; util::assert_emit(&mut tracker, |_destination, source| { assert!(source.is_none(), "gitlink IDs are never compared by blob similarity"); unmatched += 1; std::ops::ControlFlow::Continue(()) }); assert_eq!(unmatched, 2, "both unrelated gitlink changes remain unmatched"); } #[test] fn copy_by_similarity_reports_limit_if_encountered() -> crate::Result { let rewrites = Rewrites { copies: Some(Copies { source: CopySource::FromSetOfModifiedFiles, percentage: Some(0.5), }), percentage: None, limit: 1, track_empty: false, }; let mut track = util::new_tracker(rewrites); let odb = util::add_retained_blobs( &mut track, [ (Change::modification(), "a", "a\n"), (Change::addition(), "a-cpy-1", "a"), (Change::addition(), "a-cpy-2", "a"), (Change::modification(), "d", "ab"), ], )?; let mut calls = 0; let out = util::assert_emit_with_objects( &mut track, |dst, src| { assert!(src.is_none()); match calls { 0 => assert_eq!(dst.location, "a"), 1 => assert_eq!(dst.location, "a-cpy-1"), 2 => assert_eq!(dst.location, "a-cpy-2"), 3 => assert_eq!(dst.location, "d"), _ => panic!("too many emissions"), } calls += 1; std::ops::ControlFlow::Continue(()) }, odb, ); assert_eq!( out, rewrites::Outcome { options: rewrites, num_similarity_checks_skipped_for_copy_tracking_due_to_limit: 4, ..Default::default() }, "no similarity check was performed at all - all or nothing" ); Ok(()) } #[test] fn copy_by_id() -> crate::Result { // Limits are only applied when doing rewrite-checks for limit in [0, 1] { let rewrites = Rewrites { copies: Some(Copies { source: CopySource::FromSetOfModifiedFiles, percentage: None, }), percentage: None, limit, track_empty: false, }; let mut track = util::new_tracker(rewrites); let odb = util::add_retained_blobs( &mut track, [ (Change::modification(), "a", "a"), (Change::addition(), "a-cpy-1", "a"), (Change::addition(), "a-cpy-2", "a"), (Change::modification(), "d", "a"), ], )?; let mut calls = 0; let out = util::assert_emit_with_objects( &mut track, |dst, src| { let id = hex_to_id( "2e65efe2a145dda7ee51d1741299f848e5bf752e", "eb337bcee2061c5313c9a1392116b6c76039e9e30d71467ae359b36277e17dc7", ); let source_a = Source { entry_mode: EntryKind::Blob.into(), id, kind: SourceKind::Copy, location: "a".into(), change: &Change { id, ..Change::modification() }, diff: None, }; match calls { 0 => { assert_eq!(src.unwrap(), source_a); assert_eq!( dst.location, "a-cpy-1", "it just finds the first possible match in order, ignoring other candidates" ); } 1 => { assert_eq!(src.unwrap(), source_a, "copy-sources can be used multiple times"); assert_eq!(dst.location, "a-cpy-2"); } 2 => { assert!(src.is_none()); assert_eq!(dst.location, "d"); } _ => panic!("too many emissions"), } calls += 1; std::ops::ControlFlow::Continue(()) }, odb, ); assert_eq!( out, rewrites::Outcome { options: rewrites, ..Default::default() }, "no similarity check was performed, it was all matched by id" ); } Ok(()) } #[test] fn copy_by_id_search_in_all_sources() -> crate::Result { // Limits are only applied when doing rewrite-checks for limit in [0, 1] { let rewrites = Rewrites { copies: Some(Copies { source: CopySource::FromSetOfModifiedFilesAndAllSources, percentage: None, }), percentage: None, limit, track_empty: false, }; let mut track = util::new_tracker(rewrites); let odb = util::add_retained_blobs( &mut track, [ (Change::addition(), "a-cpy-1", "a"), (Change::addition(), "a-cpy-2", "a"), ], )?; let content_id = hex_to_id( "2e65efe2a145dda7ee51d1741299f848e5bf752e", "eb337bcee2061c5313c9a1392116b6c76039e9e30d71467ae359b36277e17dc7", ); let mut calls = 0; let out = util::assert_emit_with_objects_and_sources( &mut track, |dst, src| { let source_a = Source { entry_mode: EntryKind::Blob.into(), id: content_id, kind: SourceKind::Copy, location: "a-src".into(), change: &Change { id: content_id, ..Change::modification() }, diff: None, }; match calls { 0 => { assert_eq!(src.unwrap(), source_a); assert_eq!( dst.location, "a-cpy-1", "it just finds the first possible match in order, ignoring other candidates" ); } 1 => { assert_eq!(src.unwrap(), source_a, "copy-sources can be used multiple times"); assert_eq!(dst.location, "a-cpy-2"); } 2 => { assert!(src.is_none()); assert_eq!(dst.location, "d"); } _ => panic!("too many emissions"), } calls += 1; std::ops::ControlFlow::Continue(()) }, odb, [( { let mut c = Change::modification(); c.id = content_id; c }, "a-src", )], ); assert_eq!( out, rewrites::Outcome { options: rewrites, ..Default::default() }, "no similarity check was performed, it was all matched by id" ); } Ok(()) } #[test] fn copy_by_50_percent_similarity() -> crate::Result { let rewrites = Rewrites { copies: Some(Copies { source: CopySource::FromSetOfModifiedFiles, percentage: Some(0.5), }), percentage: None, limit: 0, track_empty: false, }; let mut track = util::new_tracker(rewrites); let odb = util::add_retained_blobs( &mut track, [ (Change::modification(), "a", "a\n"), (Change::addition(), "a-cpy-1", "a\nb"), (Change::addition(), "a-cpy-2", "a\nc"), (Change::modification(), "d", "a"), ], )?; let mut calls = 0; let out = util::assert_emit_with_objects( &mut track, |dst, src| { let id = hex_to_id( "78981922613b2afb6025042ff6bd878ac1994e85", "f8625e43f9e04f24291f77cdbe4c71b3c2a3b0003f60419b3ed06a058d766c8b", ); let source_a = Source { entry_mode: EntryKind::Blob.into(), id, kind: SourceKind::Copy, location: "a".into(), change: &Change { id, ..Change::modification() }, diff: Some(DiffLineStats { removals: 0, insertions: 1, before: 1, after: 2, similarity: 0.6666667, }), }; match calls { 0 => { assert_eq!( src.unwrap(), source_a, "it finds the first possible source, no candidates" ); assert_eq!(dst.location, "a-cpy-1"); } 1 => { assert_eq!(src.unwrap(), source_a, "the same source can be reused as well"); assert_eq!(dst.location, "a-cpy-2"); } 2 => { assert!(src.is_none()); assert_eq!(dst.location, "d"); } _ => panic!("too many emissions"), } calls += 1; std::ops::ControlFlow::Continue(()) }, odb, ); assert_eq!( out, rewrites::Outcome { options: rewrites, num_similarity_checks: 4, ..Default::default() }, "no similarity check was performed, it was all matched by id" ); Ok(()) } #[test] fn copy_by_id_in_additions_only() -> crate::Result { let rewrites = Rewrites { copies: Some(Copies { source: CopySource::FromSetOfModifiedFiles, percentage: None, }), percentage: None, limit: 0, track_empty: false, }; let mut track = util::new_tracker(rewrites); let odb = util::add_retained_blobs( &mut track, [ (Change::modification(), "a", "a"), (Change::modification(), "a-cpy-1", "a"), ], )?; let mut calls = 0; let out = util::assert_emit_with_objects( &mut track, |dst, src| { match calls { 0 => { assert!(src.is_none()); assert_eq!(dst.location, "a"); } 1 => { assert!(src.is_none()); assert_eq!( dst.location, "a-cpy-1", "copy detection is only done for additions, not within modifications" ); } _ => panic!("too many emissions"), } calls += 1; std::ops::ControlFlow::Continue(()) }, odb, ); assert_eq!( out, rewrites::Outcome { options: rewrites, ..Default::default() }, "no similarity check was performed, it was all matched by id" ); Ok(()) } #[test] fn rename_by_similarity_reports_limit_if_encountered() -> crate::Result { let rewrites = Rewrites { copies: None, percentage: Some(0.5), limit: 1, track_empty: false, }; let mut track = util::new_tracker(rewrites); let odb = util::add_retained_blobs( &mut track, [ (Change::deletion(), "a", "first\nsecond\n"), (Change::addition(), "b", "firt\nsecond\n"), (Change::addition(), "c", "second\nunrelated\n"), ], )?;