JavaScript is disabled, refresh for a better experience. ambee/giterated

ambee/giterated

Git repository hosting, collaboration, and discovery for the Fediverse.

Experimental repository diff structure

erremilia - ⁨2⁩ years ago

parent: tbd commit: ⁨0ef533d

Showing ⁨⁨6⁩ changed files⁩ with ⁨⁨275⁩ insertions⁩ and ⁨⁨17⁩ deletions⁩

giterated-daemon/src/backend/git.rs

View file
@@ -9,7 +9,7 @@ use giterated_models::repository::{
9 9 RepositoryCommitBeforeRequest, RepositoryDiff, RepositoryDiffRequest, RepositoryFile,
10 10 RepositoryFileFromIdRequest, RepositoryFileInspectRequest, RepositoryIssue,
11 11 RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest, RepositoryIssuesRequest,
12 RepositoryObjectType, RepositoryTreeEntry, RepositoryVisibility, Visibility,
12 RepositoryObjectType, RepositoryTreeEntry, RepositoryVisibility, Visibility, RepositoryDiffFile, RepositoryDiffFileInfo, RepositoryDiffFileStatus, RepositoryDiffFileChunk, RepositoryDiffPatchRequest,
13 13 };
14 14 use giterated_models::settings::{AnySetting, Setting};
15 15 use giterated_models::user::{User, UserParseError};
@@ -740,8 +740,116 @@ impl RepositoryBackend for GitBackend {
740 740
741 741 // Should be safe to unwrap?
742 742 let stats = diff.stats().unwrap();
743 let mut files: Vec<RepositoryDiffFile> = vec![];
744
745 diff.deltas().enumerate().for_each(|(i, delta)| {
746 // Parse the old file info from the delta
747 let old_file_info = match delta.old_file().exists() {
748 true => Some(RepositoryDiffFileInfo {
749 id: delta.old_file().id().to_string(),
750 path: delta.old_file().path().unwrap().to_str().unwrap().to_string(),
751 size: delta.old_file().size(),
752 binary: delta.old_file().is_binary()
753 }),
754 false => None,
755 };
756 // Parse the new file info from the delta
757 let new_file_info = match delta.new_file().exists() {
758 true => Some(RepositoryDiffFileInfo {
759 id: delta.new_file().id().to_string(),
760 path: delta.new_file().path().unwrap().to_str().unwrap().to_string(),
761 size: delta.new_file().size(),
762 binary: delta.new_file().is_binary()
763 }),
764 false => None,
765 };
766
767 let mut chunks: Vec<RepositoryDiffFileChunk> = vec![];
768 if let Some(patch) = git2::Patch::from_diff(&diff, i).ok().flatten() {
769 for chunk_num in 0..patch.num_hunks() {
770 if let Ok((chunk, chunk_num_lines)) = patch.hunk(chunk_num) {
771 let mut lines: Vec<String> = vec![];
772 for line_num in 0..chunk_num_lines {
773 if let Ok(line) = patch.line_in_hunk(chunk_num, line_num) {
774 if let Ok(line_utf8) = String::from_utf8(line.content().to_vec()) {
775 lines.push(line_utf8);
776 }
777
778 break;
779 }
780
781 lines.push(String::new());
782 }
783
784 chunks.push(RepositoryDiffFileChunk {
785 old_start: chunk.old_start(),
786 old_lines: chunk.old_lines(),
787 new_start: chunk.new_start(),
788 new_lines: chunk.new_lines(),
789 lines,
790 });
791 }
792 }
793 };
794
795 let file = RepositoryDiffFile {
796 status: RepositoryDiffFileStatus::from(delta.status()),
797 old_file_info,
798 new_file_info,
799 chunks,
800 };
801
802 files.push(file);
803 });
804
805 Ok(RepositoryDiff {
806 new_commit: Commit::from(commit_new),
807 files_changed: stats.files_changed(),
808 insertions: stats.insertions(),
809 deletions: stats.deletions(),
810 files,
811 })
812 }
813
814 async fn repository_diff_patch(
815 &mut self,
816 requester: Option<&User>,
817 repository: &Repository,
818 request: &RepositoryDiffPatchRequest,
819 ) -> Result<String, Error> {
820 let git = self
821 .open_repository_and_check_permissions(&repository.owner, &repository.name, requester)
822 .await?;
823
824 // Parse the passed object ids
825 let oid_old = git2::Oid::from_str(request.old_id.as_str())
826 .map_err(|_| GitBackendError::InvalidObjectId(request.old_id.clone()))?;
827 let oid_new = git2::Oid::from_str(request.new_id.as_str())
828 .map_err(|_| GitBackendError::InvalidObjectId(request.new_id.clone()))?;
829
830 // Get the ids associates commits
831 let commit_old = git
832 .find_commit(oid_old)
833 .map_err(|_| GitBackendError::CommitNotFound(oid_old.to_string()))?;
834 let commit_new = git
835 .find_commit(oid_new)
836 .map_err(|_| GitBackendError::CommitNotFound(oid_new.to_string()))?;
837
838 // Get the commit trees
839 let tree_old = commit_old
840 .tree()
841 .map_err(|_| GitBackendError::TreeNotFound(oid_old.to_string()))?;
842 let tree_new = commit_new
843 .tree()
844 .map_err(|_| GitBackendError::TreeNotFound(oid_new.to_string()))?;
845
846 // Diff the two trees against each other
847 let diff = git
848 .diff_tree_to_tree(Some(&tree_old), Some(&tree_new), None)
849 .map_err(|_| {
850 GitBackendError::FailedDiffing(oid_old.to_string(), oid_new.to_string())
851 })?;
743 852
744 // Honestly not quite sure what is going on here, could not find documentation.
745 853 // Print the entire patch
746 854 let mut patch = String::new();
747 855
@@ -755,13 +863,7 @@ impl RepositoryBackend for GitBackend {
755 863 })
756 864 .unwrap();
757 865
758 Ok(RepositoryDiff {
759 new_commit: Commit::from(commit_new),
760 files_changed: stats.files_changed(),
761 insertions: stats.insertions(),
762 deletions: stats.deletions(),
763 patch,
764 })
866 Ok(patch)
765 867 }
766 868
767 869 async fn repository_commit_before(

giterated-daemon/src/backend/mod.rs

View file
@@ -19,7 +19,7 @@ use giterated_models::repository::{
19 19 Commit, IssueLabel, Repository, RepositoryCommitBeforeRequest, RepositoryDiff,
20 20 RepositoryDiffRequest, RepositoryFile, RepositoryFileFromIdRequest,
21 21 RepositoryFileInspectRequest, RepositoryIssue, RepositoryIssueLabelsRequest,
22 RepositoryIssuesCountRequest, RepositoryIssuesRequest, RepositorySummary, RepositoryTreeEntry,
22 RepositoryIssuesCountRequest, RepositoryIssuesRequest, RepositorySummary, RepositoryTreeEntry, RepositoryDiffPatchRequest,
23 23 };
24 24 use giterated_models::settings::AnySetting;
25 25 use giterated_models::user::User;
@@ -50,6 +50,12 @@ pub trait RepositoryBackend {
50 50 repository: &Repository,
51 51 request: &RepositoryDiffRequest,
52 52 ) -> Result<RepositoryDiff, Error>;
53 async fn repository_diff_patch(
54 &mut self,
55 requester: Option<&User>,
56 repository: &Repository,
57 request: &RepositoryDiffPatchRequest,
58 ) -> Result<String, Error>;
53 59 async fn repository_commit_before(
54 60 &mut self,
55 61 requester: Option<&User>,

giterated-daemon/src/database_backend/handler.rs

View file
@@ -8,7 +8,7 @@ use giterated_models::{
8 8 Commit, DefaultBranch, Description, LatestCommit, Repository,
9 9 RepositoryCommitBeforeRequest, RepositoryDiff, RepositoryDiffRequest, RepositoryFile,
10 10 RepositoryFileFromIdRequest, RepositoryFileInspectRequest, RepositoryInfoRequest,
11 RepositorySummary, RepositoryView, Visibility,
11 RepositorySummary, RepositoryView, Visibility, RepositoryDiffPatchRequest,
12 12 },
13 13 settings::{AnySetting, GetSetting, GetSettingError, SetSetting, SetSettingError},
14 14 user::{User, UserRepositoriesRequest},
@@ -194,13 +194,40 @@ pub fn repository_diff(
194 194 .unwrap();
195 195
196 196 let mut repository_backend = state.repository_backend.lock().await;
197 let file = repository_backend
197 let diff = repository_backend
198 198 .repository_diff(None, object.object(), &operation)
199 199 .await
200 200 .map_err(|err| OperationError::Internal(format!("{:?}", err)))?;
201 201 drop(repository_backend);
202 202
203 Ok(file)
203 Ok(diff)
204 }
205 .boxed()
206 }
207
208 pub fn repository_diff_patch(
209 object: &Repository,
210 operation: RepositoryDiffPatchRequest,
211 state: DatabaseBackend,
212 operation_state: StackOperationState,
213 backend: BackendWrapper,
214 ) -> BoxFuture<'static, Result<String, OperationError<RepositoryError>>> {
215 let object = object.clone();
216
217 async move {
218 let object = backend
219 .get_object::<Repository>(&object.to_string(), &operation_state)
220 .await
221 .unwrap();
222
223 let mut repository_backend = state.repository_backend.lock().await;
224 let patch = repository_backend
225 .repository_diff_patch(None, object.object(), &operation)
226 .await
227 .map_err(|err| OperationError::Internal(format!("{:?}", err)))?;
228 drop(repository_backend);
229
230 Ok(patch)
204 231 }
205 232 .boxed()
206 233 }

giterated-daemon/src/database_backend/mod.rs

View file
@@ -19,7 +19,7 @@ use crate::backend::{RepositoryBackend, UserBackend};
19 19 use self::handler::{
20 20 repository_commit_before, repository_diff, repository_file_from_id, repository_get_setting,
21 21 repository_get_value, repository_info, repository_set_setting, user_get_repositories,
22 user_get_setting, user_get_value, user_set_setting,
22 user_get_setting, user_get_value, user_set_setting, repository_diff_patch,
23 23 };
24 24
25 25 #[derive(Clone, Debug)]
@@ -79,6 +79,7 @@ impl DatabaseBackend {
79 79 .insert(repository_info)
80 80 .insert(repository_file_from_id)
81 81 .insert(repository_diff)
82 .insert(repository_diff_patch)
82 83 .insert(repository_commit_before)
83 84 .insert(repository_get_value)
84 85 .insert(repository_get_setting)
@@ -117,7 +118,7 @@ mod test {
117 118 use giterated_models::repository::{
118 119 Commit, Description, Repository, RepositoryCommitBeforeRequest, RepositoryDiff,
119 120 RepositoryDiffRequest, RepositoryFile, RepositoryFileFromIdRequest,
120 RepositoryFileInspectRequest, RepositorySummary, RepositoryTreeEntry,
121 RepositoryFileInspectRequest, RepositorySummary, RepositoryTreeEntry, RepositoryDiffPatchRequest,
121 122 };
122 123 use giterated_models::settings::AnySetting;
123 124 use giterated_models::user::{DisplayName, User};
@@ -221,6 +222,14 @@ mod test {
221 222 ) -> Result<RepositoryDiff, Error> {
222 223 todo!()
223 224 }
225 async fn repository_diff_patch(
226 &mut self,
227 _requester: Option<&User>,
228 _repository: &Repository,
229 _request: &RepositoryDiffPatchRequest,
230 ) -> Result<String, Error> {
231 todo!()
232 }
224 233 async fn repository_commit_before(
225 234 &mut self,
226 235 _requester: Option<&User>,

giterated-models/src/repository/mod.rs

View file
@@ -157,8 +157,89 @@ pub struct RepositoryDiff {
157 157 pub insertions: usize,
158 158 /// Total number of deletions
159 159 pub deletions: usize,
160 /// Preferably unified patch, probably a git patch.
161 pub patch: String,
160 /// List of changed files
161 pub files: Vec<RepositoryDiffFile>,
162 }
163
164 /// Represents the type of change made to a [`RepositoryDiffFile`]
165 #[derive(Clone, Debug, Serialize, Deserialize)]
166 pub enum RepositoryDiffFileStatus {
167 /// No changes
168 Unmodified,
169 /// Content changed between old and new
170 Modified,
171 /// Renamed between old and new
172 Renamed,
173 /// Copied from another old entry
174 Copied,
175 /// Ignored item in workdir
176 Ignored,
177 /// Untracked item in workdir
178 Untracked,
179 /// Type of file changed between old and new
180 Typechange,
181 /// File is unreadable
182 Unreadable,
183 /// File in the index is conflicted
184 Conflicted,
185 }
186
187 impl From<git2::Delta> for RepositoryDiffFileStatus {
188 fn from(status: git2::Delta) -> Self {
189 match status {
190 git2::Delta::Unmodified => Self::Unmodified,
191 git2::Delta::Modified => Self::Modified,
192 git2::Delta::Renamed => Self::Renamed,
193 git2::Delta::Copied => Self::Copied,
194 git2::Delta::Ignored => Self::Ignored,
195 git2::Delta::Untracked => Self::Untracked,
196 git2::Delta::Typechange => Self::Typechange,
197 git2::Delta::Unreadable => Self::Unreadable,
198 git2::Delta::Conflicted => Self::Conflicted,
199 _ => unreachable!(),
200 }
201 }
202 }
203
204 /// Represents a single file of a diff
205 #[derive(Clone, Debug, Serialize, Deserialize)]
206 pub struct RepositoryDiffFile {
207 /// The type of change made to this file
208 pub status: RepositoryDiffFileStatus,
209 /// "From" side of the diff, can be nonexistent if file for example got added for the first time
210 pub old_file_info: Option<RepositoryDiffFileInfo>,
211 /// "To" side of the diff, can be nonexistent if file got removed
212 pub new_file_info: Option<RepositoryDiffFileInfo>,
213 /// Individual chunks of changes in this file
214 pub chunks: Vec<RepositoryDiffFileChunk>,
215 }
216
217 /// Represents one side of a file diff
218 #[derive(Clone, Debug, Serialize, Deserialize)]
219 pub struct RepositoryDiffFileInfo {
220 /// ID of the file
221 pub id: String,
222 /// Path of the entry relative to the working directory of the repository
223 pub path: String,
224 /// Size in bytes
225 pub size: u64,
226 /// If the file is binary or not
227 pub binary: bool,
228 }
229
230 /// Represents a single chunk of a file diff
231 #[derive(Clone, Debug, Serialize, Deserialize)]
232 pub struct RepositoryDiffFileChunk {
233 /// Starting line number of the old file
234 pub old_start: u32,
235 /// Number of lines in "from" side of this chunk
236 pub old_lines: u32,
237 /// Starting line number of the new file
238 pub new_start: u32,
239 /// Number of lines in "to" side of this chunk
240 pub new_lines: u32,
241 /// Lines of the chunk
242 pub lines: Vec<String>,
162 243 }
163 244
164 245 #[derive(Debug, Clone, Serialize, Deserialize)]

giterated-models/src/repository/operations.rs

View file
@@ -70,6 +70,26 @@ impl GiteratedOperation<Repository> for RepositoryDiffRequest {
70 70 type Failure = RepositoryError;
71 71 }
72 72
73 /// A request to get the difference between two repository trees as a unified git patch.
74 ///
75 /// # Authentication
76 /// - Instance Authentication
77 /// - Validate request against the `issued_for` public key
78 /// - Validate User token against the user's instance's public key
79 /// # Authorization
80 /// - User Authorization
81 /// - Potential User permissions checks
82 #[derive(Clone, Debug, Serialize, Deserialize)]
83 pub struct RepositoryDiffPatchRequest {
84 pub old_id: String,
85 pub new_id: String,
86 }
87
88 impl GiteratedOperation<Repository> for RepositoryDiffPatchRequest {
89 type Success = String;
90 type Failure = RepositoryError;
91 }
92
73 93 /// A request to get the commit before the one with the passed id
74 94 ///
75 95 /// # Authentication
@@ -197,6 +217,19 @@ impl<S: Clone + Send + Sync, B: ObjectBackend<S> + std::fmt::Debug> Object<'_, S
197 217 .await
198 218 }
199 219
220 pub async fn diff_patch(
221 &mut self,
222 old_id: String,
223 new_id: String,
224 operation_state: &S,
225 ) -> Result<String, OperationError<RepositoryError>> {
226 self.request::<RepositoryDiffPatchRequest>(
227 RepositoryDiffPatchRequest { old_id, new_id },
228 operation_state,
229 )
230 .await
231 }
232
200 233 pub async fn commit_before(
201 234 &mut self,
202 235 id: String,