|
| 1 | +use anyhow::Context; |
1 | 2 | use gix::bstr::{BString, ByteSlice};
|
| 3 | +use gix::diff::blob::intern::TokenSource; |
| 4 | +use gix::diff::blob::unified_diff::{ContextSize, NewlineSeparator}; |
| 5 | +use gix::diff::blob::UnifiedDiff; |
2 | 6 | use gix::objs::tree::EntryMode;
|
3 | 7 | use gix::odb::store::RefreshMode;
|
4 | 8 | use gix::prelude::ObjectIdExt;
|
@@ -111,3 +115,86 @@ fn typed_location(mut location: BString, mode: EntryMode) -> BString {
|
111 | 115 | }
|
112 | 116 | location
|
113 | 117 | }
|
| 118 | + |
| 119 | +pub fn file( |
| 120 | + mut repo: gix::Repository, |
| 121 | + out: &mut dyn std::io::Write, |
| 122 | + old_revspec: BString, |
| 123 | + new_revspec: BString, |
| 124 | +) -> Result<(), anyhow::Error> { |
| 125 | + repo.object_cache_size_if_unset(repo.compute_object_cache_size_for_tree_diffs(&**repo.index_or_empty()?)); |
| 126 | + repo.objects.refresh = RefreshMode::Never; |
| 127 | + |
| 128 | + let old_resolved_revspec = repo.rev_parse(old_revspec.as_bstr())?; |
| 129 | + let new_resolved_revspec = repo.rev_parse(new_revspec.as_bstr())?; |
| 130 | + |
| 131 | + let old_blob_id = old_resolved_revspec |
| 132 | + .single() |
| 133 | + .context(format!("rev-spec '{old_revspec}' must resolve to a single object"))?; |
| 134 | + let new_blob_id = new_resolved_revspec |
| 135 | + .single() |
| 136 | + .context(format!("rev-spec '{new_revspec}' must resolve to a single object"))?; |
| 137 | + |
| 138 | + let (old_path, _) = old_resolved_revspec |
| 139 | + .path_and_mode() |
| 140 | + .context(format!("rev-spec '{old_revspec}' must contain a path"))?; |
| 141 | + let (new_path, _) = new_resolved_revspec |
| 142 | + .path_and_mode() |
| 143 | + .context(format!("rev-spec '{new_revspec}' must contain a path"))?; |
| 144 | + |
| 145 | + let mut resource_cache = repo.diff_resource_cache( |
| 146 | + gix::diff::blob::pipeline::Mode::ToGitUnlessBinaryToTextIsPresent, |
| 147 | + Default::default(), |
| 148 | + )?; |
| 149 | + |
| 150 | + resource_cache.set_resource( |
| 151 | + old_blob_id.into(), |
| 152 | + gix::object::tree::EntryKind::Blob, |
| 153 | + old_path, |
| 154 | + gix::diff::blob::ResourceKind::OldOrSource, |
| 155 | + &repo.objects, |
| 156 | + )?; |
| 157 | + resource_cache.set_resource( |
| 158 | + new_blob_id.into(), |
| 159 | + gix::object::tree::EntryKind::Blob, |
| 160 | + new_path, |
| 161 | + gix::diff::blob::ResourceKind::NewOrDestination, |
| 162 | + &repo.objects, |
| 163 | + )?; |
| 164 | + |
| 165 | + let outcome = resource_cache.prepare_diff()?; |
| 166 | + |
| 167 | + use gix::diff::blob::platform::prepare_diff::Operation; |
| 168 | + |
| 169 | + let algorithm = match outcome.operation { |
| 170 | + Operation::InternalDiff { algorithm } => algorithm, |
| 171 | + Operation::ExternalCommand { .. } => { |
| 172 | + unreachable!("We disabled that") |
| 173 | + } |
| 174 | + Operation::SourceOrDestinationIsBinary => { |
| 175 | + anyhow::bail!("Source or destination is binary and we can't diff that") |
| 176 | + } |
| 177 | + }; |
| 178 | + |
| 179 | + let interner = gix::diff::blob::intern::InternedInput::new( |
| 180 | + tokens_for_diffing(outcome.old.data.as_slice().unwrap_or_default()), |
| 181 | + tokens_for_diffing(outcome.new.data.as_slice().unwrap_or_default()), |
| 182 | + ); |
| 183 | + |
| 184 | + let unified_diff = UnifiedDiff::new( |
| 185 | + &interner, |
| 186 | + String::new(), |
| 187 | + NewlineSeparator::AfterHeaderAndLine("\n"), |
| 188 | + ContextSize::symmetrical(3), |
| 189 | + ); |
| 190 | + |
| 191 | + let unified_diff = gix::diff::blob::diff(algorithm, &interner, unified_diff)?; |
| 192 | + |
| 193 | + out.write_all(unified_diff.as_bytes())?; |
| 194 | + |
| 195 | + Ok(()) |
| 196 | +} |
| 197 | + |
| 198 | +pub(crate) fn tokens_for_diffing(data: &[u8]) -> impl TokenSource<Token = &[u8]> { |
| 199 | + gix::diff::blob::sources::byte_lines(data) |
| 200 | +} |
0 commit comments