From be7e3a54e74c2aa9a4929e38cb06b0815a0d65e1 Mon Sep 17 00:00:00 2001 From: Christian Schilling Date: Sat, 27 Aug 2022 18:37:06 +0200 Subject: [PATCH] Add changed files to graphql api Change-Id: graphql-changed-files --- src/filter/tree.rs | 90 ++++++++++++++++++++++++++++ src/graphql.rs | 84 ++++++++++++++++++++++++++ tests/filter/graphql_changed_files.t | 73 ++++++++++++++++++++++ tests/proxy/graphql_schema.t | 76 +++++++++++++++++++++++ 4 files changed, 323 insertions(+) create mode 100644 tests/filter/graphql_changed_files.t diff --git a/src/filter/tree.rs b/src/filter/tree.rs index c8ef5aceb..db1e30e56 100644 --- a/src/filter/tree.rs +++ b/src/filter/tree.rs @@ -213,6 +213,96 @@ pub fn insert<'a>( } } +pub fn diff_paths( + repo: &git2::Repository, + input1: git2::Oid, + input2: git2::Oid, + root: &str, +) -> JoshResult> { + rs_tracing::trace_scoped!("diff_paths"); + if input1 == input2 { + return Ok(vec![]); + } + + if let (Ok(_), Ok(_)) = (repo.find_blob(input1), repo.find_blob(input2)) { + return Ok(vec![(root.to_string(), 0)]); + } + + if let (Ok(_), Err(_)) = (repo.find_blob(input1), repo.find_blob(input2)) { + return Ok(vec![(root.to_string(), -1)]); + } + + if let (Err(_), Ok(_)) = (repo.find_blob(input1), repo.find_blob(input2)) { + return Ok(vec![(root.to_string(), 1)]); + } + + let mut r = vec![]; + + if let (Ok(tree1), Ok(tree2)) = (repo.find_tree(input1), repo.find_tree(input2)) { + for entry in tree2.iter() { + let name = entry.name().ok_or_else(|| josh_error("no name"))?; + if let Some(e) = tree1.get_name(entry.name().ok_or_else(|| josh_error("no name"))?) { + r.append(&mut diff_paths( + repo, + e.id(), + entry.id(), + &format!("{}{}{}", root, if root.is_empty() { "" } else { "/" }, name), + )?); + } else { + r.append(&mut diff_paths( + repo, + git2::Oid::zero(), + entry.id(), + &format!("{}{}{}", root, if root.is_empty() { "" } else { "/" }, name), + )?); + } + } + + for entry in tree1.iter() { + let name = entry.name().ok_or_else(|| josh_error("no name"))?; + if let Some(_) = tree2.get_name(entry.name().ok_or_else(|| josh_error("no name"))?) { + } else { + r.append(&mut diff_paths( + repo, + entry.id(), + git2::Oid::zero(), + &format!("{}{}{}", root, if root.is_empty() { "" } else { "/" }, name), + )?); + } + } + + return Ok(r); + } + + if let Ok(tree2) = repo.find_tree(input2) { + for entry in tree2.iter() { + let name = entry.name().ok_or_else(|| josh_error("no name"))?; + r.append(&mut diff_paths( + repo, + git2::Oid::zero(), + entry.id(), + &format!("{}{}{}", root, if root.is_empty() { "" } else { "/" }, name), + )?); + } + return Ok(r); + } + + if let Ok(tree1) = repo.find_tree(input2) { + for entry in tree1.iter() { + let name = entry.name().ok_or_else(|| josh_error("no name"))?; + r.append(&mut diff_paths( + repo, + entry.id(), + git2::Oid::zero(), + &format!("{}{}{}", root, if root.is_empty() { "" } else { "/" }, name), + )?); + } + return Ok(r); + } + + Ok(r) +} + pub fn overlay( repo: &git2::Repository, input1: git2::Oid, diff --git a/src/graphql.rs b/src/graphql.rs index 6cebef739..86ed451a5 100644 --- a/src/graphql.rs +++ b/src/graphql.rs @@ -46,6 +46,22 @@ fn find_paths( Ok(ws) } +pub struct DiffPath { + a: Option, + b: Option, +} + +#[graphql_object(context = Context)] +impl DiffPath { + fn from(&self) -> FieldResult> { + Ok(self.a.clone()) + } + + fn to(&self) -> FieldResult> { + Ok(self.b.clone()) + } +} + impl Revision { fn files_or_dirs( &self, @@ -262,6 +278,74 @@ impl Revision { self.files_or_dirs(at, depth, context, git2::ObjectType::Tree) } + fn changed_files( + &self, + at: Option, + depth: Option, + context: &Context, + ) -> FieldResult>> { + let transaction = context.transaction.lock()?; + let commit = transaction.repo().find_commit(self.commit_id)?; + let filter_commit = transaction.repo().find_commit(filter::apply_to_commit( + self.filter, + &commit, + &transaction, + )?)?; + + let (parent_id, parent_tree_id) = filter_commit + .parents() + .next() + .map(|p| (p.id(), p.tree_id())) + .unwrap_or((git2::Oid::zero(), git2::Oid::zero())); + + let d = filter::tree::diff_paths( + transaction.repo(), + parent_tree_id, + filter_commit.tree_id(), + "", + )?; + + let df = d + .into_iter() + .map(|(path, n)| match n { + 1 => DiffPath { + a: None, + b: Some(Path { + path: std::path::Path::new(&path).to_owned(), + commit_id: self.commit_id, + filter: self.filter, + tree: filter_commit.tree_id(), + }), + }, + -1 => DiffPath { + a: Some(Path { + path: std::path::Path::new(&path).to_owned(), + commit_id: parent_id, + filter: self.filter, + tree: parent_tree_id, + }), + b: None, + }, + _ => DiffPath { + a: Some(Path { + path: std::path::Path::new(&path).to_owned(), + commit_id: parent_id, + filter: self.filter, + tree: parent_tree_id, + }), + b: Some(Path { + path: std::path::Path::new(&path).to_owned(), + commit_id: self.commit_id, + filter: self.filter, + tree: filter_commit.tree_id(), + }), + }, + }) + .collect(); + + return Ok(Some(df)); + } + fn file(&self, path: String, context: &Context) -> FieldResult> { let transaction = context.transaction.lock()?; let path = std::path::Path::new(&path).to_owned(); diff --git a/tests/filter/graphql_changed_files.t b/tests/filter/graphql_changed_files.t new file mode 100644 index 000000000..ab743065d --- /dev/null +++ b/tests/filter/graphql_changed_files.t @@ -0,0 +1,73 @@ + $ export TESTTMP=${PWD} + + $ cd ${TESTTMP} + $ git init -q repo 1> /dev/null + $ cd repo + + $ echo contents0 > file1 + $ git add . + $ git commit -m "add file1" 1> /dev/null + $ echo contents2 > file2 + $ git add . + $ git commit -m "add file2" 1> /dev/null + $ mkdir ws + $ cat > ws/workspace.josh < ::file2 + > EOF + $ git add . + $ git commit -m "add ws" 1> /dev/null + + $ cat > query < query { + > rev(at: "HEAD") { + > history(limit: 10) { + > summary + > changedFiles { + > from { path } + > to { path } + > } + > } + > } + > } + > EOF + + $ josh-filter -g "$(cat query)" + { + "rev": { + "history": [ + { + "summary": "add ws", + "changedFiles": [ + { + "from": null, + "to": { + "path": "ws/workspace.josh" + } + } + ] + }, + { + "summary": "add file2", + "changedFiles": [ + { + "from": null, + "to": { + "path": "file2" + } + } + ] + }, + { + "summary": "add file1", + "changedFiles": [ + { + "from": null, + "to": { + "path": "file1" + } + } + ] + } + ] + } + } diff --git a/tests/proxy/graphql_schema.t b/tests/proxy/graphql_schema.t index 4ef5507a3..9476b1297 100644 --- a/tests/proxy/graphql_schema.t +++ b/tests/proxy/graphql_schema.t @@ -75,6 +75,41 @@ "name": "Boolean", "possibleTypes": null }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "from", + "type": { + "kind": "OBJECT", + "name": "Path", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "to", + "type": { + "kind": "OBJECT", + "name": "Path", + "ofType": null + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "DiffPath", + "possibleTypes": null + }, { "description": null, "enumValues": null, @@ -503,6 +538,47 @@ } } }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "at", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "depth", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "changedFiles", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "DiffPath", + "ofType": null + } + } + } + }, { "args": [ {