diff --git a/config.public.json b/config.public.json index 96ca3f4..30f7998 100644 --- a/config.public.json +++ b/config.public.json @@ -40,6 +40,18 @@ "zimbatm" ] }, + "tag_paths": { + "6.topic: python": [ + "pkgs/top-level/python-packages.nix", + "pkgs/development/interpreters/python", + "pkgs/development/python-modules", + "doc/languages-frameworks/python.md" + ], + "6.topic: ruby": [ + "pkgs/development/interpreters/ruby", + "pkgs/development/ruby-modules" + ] + }, "checkout": { "root": "/var/lib/gc-of-borg/.nix-test-rs" }, diff --git a/ofborg/src/bin/mass-rebuilder.rs b/ofborg/src/bin/mass-rebuilder.rs index 79732d2..8f0a5a5 100644 --- a/ofborg/src/bin/mass-rebuilder.rs +++ b/ofborg/src/bin/mass-rebuilder.rs @@ -39,6 +39,7 @@ fn main() { cfg.acl(), cfg.runner.identity.clone(), events, + cfg.tag_paths.clone().unwrap(), ); channel.basic_prefetch(1).unwrap(); diff --git a/ofborg/src/checkout.rs b/ofborg/src/checkout.rs index b670c78..4f1bde9 100644 --- a/ofborg/src/checkout.rs +++ b/ofborg/src/checkout.rs @@ -148,7 +148,6 @@ impl CachedProjectCo { } } - pub fn commit_messages_from_head(&self, commit: &str) -> Result, Error> { let mut lock = self.lock()?; @@ -175,6 +174,33 @@ impl CachedProjectCo { )); } } + + pub fn files_changed_from_head(&self, commit: &str) -> Result, Error> { + let mut lock = self.lock()?; + + let result = Command::new("git") + .arg("diff") + .arg("--name-only") + .arg(format!("HEAD...{}", commit)) + .current_dir(self.clone_to()) + .output()?; + + lock.unlock(); + + if result.status.success() { + return Ok( + String::from_utf8_lossy(&result.stdout) + .lines() + .map(|l| l.to_owned()) + .collect(), + ); + } else { + return Err(Error::new( + ErrorKind::Other, + String::from_utf8_lossy(&result.stderr).to_lowercase(), + )); + } + } } impl clone::GitClonable for CachedProjectCo { @@ -264,7 +290,9 @@ mod tests { let working_co = project .clone_for("testing-commit-msgs".to_owned(), "123".to_owned()) .expect("clone should work"); - working_co.checkout_origin_ref(OsStr::new("master")); + working_co + .checkout_origin_ref(OsStr::new("master")) + .unwrap(); let expect: Vec = vec!["check out this cool PR".to_owned()]; @@ -275,4 +303,31 @@ mod tests { expect ); } + + #[test] + pub fn test_files_changed_list() { + let workingdir = TestScratch::new_dir("test-test-files-changed-list"); + + let bare = TestScratch::new_dir("bare-files-changed"); + let mk_co = TestScratch::new_dir("mk-files-changed"); + let hash = make_pr_repo(&bare.path(), &mk_co.path()); + + let cloner = cached_cloner(&workingdir.path()); + let project = cloner.project("commit-files-changed-list".to_owned(), bare.string()); + let working_co = project + .clone_for("testing-files-changed".to_owned(), "123".to_owned()) + .expect("clone should work"); + working_co + .checkout_origin_ref(OsStr::new("master")) + .unwrap(); + + let expect: Vec = vec!["default.nix".to_owned(), "hi another file".to_owned()]; + + assert_eq!( + working_co.files_changed_from_head(&hash).expect( + "fetching files changed should work", + ), + expect + ); + } } diff --git a/ofborg/src/config.rs b/ofborg/src/config.rs index a77a159..5381b81 100644 --- a/ofborg/src/config.rs +++ b/ofborg/src/config.rs @@ -7,6 +7,7 @@ use hyper::net::HttpsConnector; use hyper_native_tls::NativeTlsClient; use hubcaps::{Credentials, Github}; use nix::Nix; +use std::collections::HashMap; use ofborg::acl; @@ -20,6 +21,7 @@ pub struct Config { pub rabbitmq: RabbitMQConfig, pub github: Option, pub log_storage: Option, + pub tag_paths: Option>>, } #[derive(Serialize, Deserialize, Debug)] diff --git a/ofborg/src/tagger.rs b/ofborg/src/tagger.rs index efe5cc6..4672f06 100644 --- a/ofborg/src/tagger.rs +++ b/ofborg/src/tagger.rs @@ -1,5 +1,6 @@ use ofborg::tasks; use ofborg::outpathdiff::PackageArch; +use std::collections::HashMap; pub struct StdenvTagger { possible: Vec, @@ -154,3 +155,104 @@ impl RebuildTagger { } } + +pub struct PathsTagger { + possible: HashMap>, + selected: Vec, +} + +impl PathsTagger { + pub fn new(tags_and_criteria: HashMap>) -> PathsTagger { + PathsTagger { + possible: tags_and_criteria, + selected: vec![], + } + } + + pub fn path_changed(&mut self, path: &str) { + let mut tags_to_add: Vec = self.possible + .iter() + .filter(|&(ref tag, ref _paths)| !self.selected.contains(&tag)) + .filter(|&(ref _tag, ref paths)| { + paths.iter().any(|tp| path.contains(tp)) + }) + .map(|(tag, _paths)| tag.clone()) + .collect(); + self.selected.append(&mut tags_to_add); + self.selected.sort(); + } + + pub fn tags_to_add(&self) -> Vec { + self.selected.clone() + } + + pub fn tags_to_remove(&self) -> Vec { + let mut remove: Vec = self.possible.keys().map(|k| k.to_owned()).collect(); + remove.sort(); + for tag in &self.selected { + let pos = remove.binary_search(&tag).unwrap(); + remove.remove(pos); + } + + return remove; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn test_files_changed_list() { + let mut criteria: HashMap> = HashMap::new(); + criteria.insert( + "topic: python".to_owned(), + vec![ + "pkgs/top-level/python-packages.nix".to_owned(), + "bogus".to_owned(), + ], + ); + criteria.insert( + "topic: ruby".to_owned(), + vec![ + "pkgs/development/interpreters/ruby".to_owned(), + "bogus".to_owned(), + ], + ); + + { + let mut tagger = PathsTagger::new(criteria.clone()); + tagger.path_changed("default.nix"); + assert_eq!(tagger.tags_to_add().len(), 0); + assert_eq!( + tagger.tags_to_remove(), + vec!["topic: python".to_owned(), "topic: ruby".to_owned()] + ); + + + tagger.path_changed("pkgs/development/interpreters/ruby/default.nix"); + assert_eq!(tagger.tags_to_add(), vec!["topic: ruby".to_owned()]); + assert_eq!(tagger.tags_to_remove(), vec!["topic: python".to_owned()]); + + tagger.path_changed("pkgs/development/interpreters/ruby/foobar.nix"); + assert_eq!(tagger.tags_to_add(), vec!["topic: ruby".to_owned()]); + assert_eq!(tagger.tags_to_remove(), vec!["topic: python".to_owned()]); + + + tagger.path_changed("pkgs/top-level/python-packages.nix"); + assert_eq!( + tagger.tags_to_add(), + vec!["topic: python".to_owned(), "topic: ruby".to_owned()] + ); + } + + { + let mut tagger = PathsTagger::new(criteria.clone()); + tagger.path_changed("bogus"); + assert_eq!( + tagger.tags_to_add(), + vec!["topic: python".to_owned(), "topic: ruby".to_owned()] + ); + } + } +} diff --git a/ofborg/src/tasks/massrebuilder.rs b/ofborg/src/tasks/massrebuilder.rs index 53c0596..dfcfaee 100644 --- a/ofborg/src/tasks/massrebuilder.rs +++ b/ofborg/src/tasks/massrebuilder.rs @@ -15,7 +15,7 @@ use ofborg::nix::Nix; use ofborg::acl::ACL; use ofborg::stats; use ofborg::worker; -use ofborg::tagger::{StdenvTagger, RebuildTagger}; +use ofborg::tagger::{StdenvTagger, RebuildTagger, PathsTagger}; use ofborg::outpathdiff::{OutPaths, OutPathDiff}; use ofborg::evalchecker::EvalChecker; use ofborg::commitstatus::CommitStatus; @@ -30,6 +30,7 @@ pub struct MassRebuildWorker { acl: ACL, identity: String, events: E, + tag_paths: HashMap>, } impl MassRebuildWorker { @@ -40,6 +41,7 @@ impl MassRebuildWorker { acl: ACL, identity: String, events: E, + tag_paths: HashMap>, ) -> MassRebuildWorker { return MassRebuildWorker { cloner: cloner, @@ -48,12 +50,27 @@ impl MassRebuildWorker { acl: acl, identity: identity, events: events, + tag_paths: tag_paths }; } fn actions(&self) -> massrebuildjob::Actions { return massrebuildjob::Actions {}; } + + fn tag_from_paths(&self, issue: &hubcaps::issues::IssueRef, paths: Vec) { + let mut tagger = PathsTagger::new(self.tag_paths.clone()); + + for path in paths { + tagger.path_changed(&path); + } + + update_labels( + &issue, + tagger.tags_to_add(), + tagger.tags_to_remove(), + ); + } } impl worker::SimpleWorker for MassRebuildWorker { @@ -199,6 +216,11 @@ impl worker::SimpleWorker for MassRebuildWorker { vec!["".to_owned()], )); + self.tag_from_paths( + &issue, + co.files_changed_from_head(&job.pr.head_sha).unwrap_or(vec![]) + ); + overall_status.set_with_description("Merging PR", hubcaps::statuses::State::Pending); if let Err(_) = co.merge_commit(job.pr.head_sha.as_ref()) { diff --git a/ofborg/test-srcs/build-pr/hi another file b/ofborg/test-srcs/build-pr/hi another file new file mode 100644 index 0000000..e69de29