#[cfg(test)]
mod test;
pub use libflorescence::to_record::{
determine_change, determine_file, PartialFile, Pick, PickSet, State,
};
use libflorescence::prelude::*;
use libflorescence::to_record::{
default_pick, default_pick_set, file_has_any_partial_change,
};
use libflorescence::{diff, file, repo};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Msg {
ToggleOverall,
ToggleFile {
path: file::Path,
},
ToggleChange {
path: file::Path,
diff_id: diff::IdHash,
},
}
pub fn update(state: &mut State, msg: Msg, changed_files: &repo::ChangedFiles) {
match msg {
Msg::ToggleOverall => {
state.overall = next_overall_pick(state, changed_files);
if let PickSet::Partial = state.overall {
state.files.iter_mut().for_each(|(file, pick)| {
if file_has_any_partial_change(file, &state.changes) {
*pick = PickSet::Partial;
}
});
}
}
Msg::ToggleFile { path } => {
{
let unset_file_pick = match state.overall {
PickSet::Include | PickSet::Exclude => state.overall,
PickSet::Partial => PickSet::Exclude,
};
for changed_file in changed_files.keys() {
if !state.files.contains_key(changed_file) {
state
.files
.insert(changed_file.clone(), unset_file_pick);
}
}
}
let pick = next_file_pick(&path, state);
state.files.insert(path, pick);
state.overall = determine_overall_pick(state, changed_files);
}
Msg::ToggleChange {
path: file,
diff_id: change,
} => {
{
let unset_file_pick = match state.overall {
PickSet::Include | PickSet::Exclude => state.overall,
PickSet::Partial => PickSet::Exclude,
};
for changed_file in changed_files.keys() {
if !state.files.contains_key(changed_file) {
state
.files
.insert(changed_file.clone(), unset_file_pick);
}
}
}
{
let unset_pick = match state
.files
.get(&file)
.copied()
.unwrap_or(state.overall)
{
PickSet::Include => Pick::Include,
PickSet::Exclude | PickSet::Partial => Pick::Exclude,
};
let PartialFile { changes } =
state.changes.entry(file.clone()).or_default();
for change in changed_files.get(&file).unwrap() {
let diff_id = diff::id_parts_hash(change);
changes.entry(diff_id).or_insert(unset_pick);
}
}
let pick = next_file_change_pick(&file, change, state);
let partial_file =
state.changes.entry(file.clone()).or_insert_with(default);
partial_file.changes.insert(change, pick);
let file_pick = determine_file_pick(&file, state, changed_files)
.unwrap_or_else(|| {
if changed_files.get(&file).unwrap().len() > 1 {
PickSet::Partial
} else {
match pick {
Pick::Include => PickSet::Include,
Pick::Exclude => PickSet::Exclude,
}
}
});
state.files.insert(file, file_pick);
state.overall = determine_overall_pick(state, changed_files);
}
}
}
pub fn next_overall_pick(
state: &State,
changed_files: &repo::ChangedFiles,
) -> PickSet {
let maybe_next = next_pick_set(state.overall);
if let PickSet::Partial = maybe_next
&& !any_partial_change(state, changed_files)
{
next_pick_set(maybe_next)
} else {
maybe_next
}
}
pub fn next_file_pick(file: &file::Path, state: &State) -> PickSet {
let current = (match state.overall {
pick @ (PickSet::Include | PickSet::Exclude) => Some(pick),
PickSet::Partial => None,
})
.or_else(|| state.files.get(file).copied())
.unwrap_or_else(|| default_pick_set(state));
let maybe_next = next_pick_set(current);
if let PickSet::Partial = maybe_next
&& !file_has_any_partial_change(file, &state.changes)
{
next_pick_set(maybe_next)
} else {
maybe_next
}
}
pub fn next_file_change_pick(
file: &file::Path,
diff_id: diff::IdHash,
state: &State,
) -> Pick {
let current = (match state.overall {
PickSet::Include => Some(Pick::Include),
PickSet::Exclude => Some(Pick::Exclude),
PickSet::Partial => None,
})
.or_else(|| {
state.files.get(file).and_then(|pick| match pick {
PickSet::Include => Some(Pick::Include),
PickSet::Exclude => Some(Pick::Exclude),
PickSet::Partial => None,
})
})
.or_else(|| {
state
.changes
.get(file)
.and_then(|PartialFile { changes }| changes.get(&diff_id))
.copied()
})
.unwrap_or_else(|| default_pick(state));
next_pick(current)
}
pub fn add_untracked_file_to_record(state: &mut State, path: file::Path) {
if matches!(state.overall, PickSet::Exclude | PickSet::Partial) {
state.files.insert(path, PickSet::Include);
state.overall = PickSet::Partial;
}
}
pub fn rm_untracked_file_to_record(
state: &mut State,
path: &file::Path,
changed_files: &repo::ChangedFiles,
) {
state.files.remove(path);
state.overall = determine_overall_pick(state, changed_files);
}
fn next_pick_set(pick: PickSet) -> PickSet {
match pick {
PickSet::Include => PickSet::Partial,
PickSet::Partial => PickSet::Exclude,
PickSet::Exclude => PickSet::Include,
}
}
fn next_pick(pick: Pick) -> Pick {
match pick {
Pick::Include => Pick::Exclude,
Pick::Exclude => Pick::Include,
}
}
fn any_partial_change(
state: &State,
changed_files: &repo::ChangedFiles,
) -> bool {
let files = changed_files.keys();
if state.files.len() == files.len() {
let (mut has_include, mut has_exclude) = (false, false);
state.files.iter().any(|(file, pick)| {
has_exclude |= matches!(pick, PickSet::Exclude);
has_include |= matches!(pick, PickSet::Include);
(has_include && has_exclude)
|| matches!(pick, PickSet::Partial)
|| file_has_any_partial_change(file, &state.changes)
})
} else {
state.files.iter().any(|(file, pick)| {
matches!(pick, PickSet::Exclude | PickSet::Partial)
|| file_has_any_partial_change(file, &state.changes)
})
}
}
fn determine_overall_pick(
state: &State,
changed_files: &repo::ChangedFiles,
) -> PickSet {
let files = changed_files.keys();
let mut pick = None;
if state.files.len() != files.len() {
pick = Some(default_pick_set(state));
}
for file_pick in state.files.values() {
if let Some(pick) = &mut pick {
*pick = match (*pick, *file_pick) {
(PickSet::Include, PickSet::Include) => PickSet::Include,
(PickSet::Exclude, PickSet::Exclude) => PickSet::Exclude,
(PickSet::Include, PickSet::Exclude)
| (PickSet::Include, PickSet::Partial)
| (PickSet::Exclude, PickSet::Include)
| (PickSet::Exclude, PickSet::Partial)
| (PickSet::Partial, PickSet::Include)
| (PickSet::Partial, PickSet::Exclude)
| (PickSet::Partial, PickSet::Partial) => PickSet::Partial,
};
if let PickSet::Partial = pick {
break;
}
} else {
pick = Some(*file_pick);
}
}
pick.unwrap_or(PickSet::Include)
}
fn determine_file_pick(
file: &file::Path,
state: &State,
changed_files: &repo::ChangedFiles,
) -> Option<PickSet> {
let changes = changed_files.get(file).unwrap();
let mut pick = None;
if let Some(file) = state.changes.get(file) {
if file.changes.len() != changes.len() {
return Some(PickSet::Partial);
}
for change_pick in file.changes.values() {
if let Some(pick) = &mut pick {
*pick = match (*pick, change_pick) {
(PickSet::Include, Pick::Include) => PickSet::Include,
(PickSet::Exclude, Pick::Exclude) => PickSet::Exclude,
(PickSet::Exclude, Pick::Include)
| (PickSet::Include, Pick::Exclude)
| (PickSet::Partial, Pick::Include)
| (PickSet::Partial, Pick::Exclude) => PickSet::Partial,
};
if let PickSet::Partial = pick {
break;
}
} else {
pick = Some(match change_pick {
Pick::Include => PickSet::Include,
Pick::Exclude => PickSet::Exclude,
});
}
}
}
pick
}