UPWS6J3BIHQKXSSWHD7CFLJOXWT3MRABFRVQ4T4NRYFALBAKJOOQC SWWE2R6MVBX5CNM6X3WLXZTSRTU53PBJL7WJSFVF77XBPXDX4COAC EC3TVL4X6VZZVLOKUN63LC73ADPHBHMZO7QMDXGX2ZPURVI4B4XQC KT5UYXGKEEXUHURNOYFVIG7WQ3Y3SJZMM2TP4OSW6NXSXQ5XXRHAC W7IUT3ZVMFH77IGKLAL7WX7IVVTGTY3FKEJ3WHMP3KI37B6NENLQC YBJRDOTCX3ZRDB5EVXJBR55FX3CADCSIGMYWNYVC2PD5W3GXR3DQC A5YBC77VWH2LXCZJOPZORQJI5ZYABSCHJWVX5HVNWPM5RABXESLQC 4WO3ZJM2RNYZCBPS7FGYAEBELYD57OSS7LEUYCWGZBCAY272SNQQC W4LFX7IHQ7SDX67ATSGWDB5IN6472ZJDBKY2XZ54SBJEYD5GAT5QC AMPZ2BXK4IGUZO3OPBRSJ6Z4GI5K4PRFMLUGTR6AP4FKKRWQG7LQC RPCIGCNSMPYGTMTUXJRIJG76L2WLRDHGJJZHE4DJ4UT5RPNZRNPAC OQ6HSAWHIRTAIIWMDGCTIOK47JDY7QVVAHLRDA2R5TTJKNSBFCWQC WI2BVQ6JOJBM4OC5KSZBMTDPBWESIR7GD72B5TLO7H2SY7QBDHJAC CALXOZXANFZ64NBZBTR2KYTZ6ZLLCJXNFAEALBB2EYAVDVJJ6X6AC VCNKFNUF7OWVSWC6I5D25KUZ3XZZICZ3LHWVPF2N5ZSP7LQ2JOUQC KQ3P3QRLRNBRI5R6NS43M7IKKZCHBKOY4ZF3WH3PLCKDKBTEERZAC FL2ULDJNRO3KPS24T2PEZWWNXAVFYC42SRVN3LDFVPIAFTDCOBGQC KWTBNTO3QUUE2YADF6SYW6G6ZOKYEWRJQKIWDGZXR33S3YNDVIZQC WAOGSCOJ5A372BZKHEYD2BCDBCENNVLFYW3INKUOOAZMDADDIFIQC WH57EHNML4OTGQQZBT2SG6SOFTBOD6OJPJYHJVGPH22CSSOE25AAC AZ5D2LQUSYVWVEP7ISFDSZTMZ65UEHZATILMDQ4TYLCKJH4Q3TIAC IFQPVMBD552DZ3B5HCM6W6MI2SB6576ZYJNU5KVA3O4YPZAUEFHAC LFEMJYYDO45ASMQSOJ3TNID7B5UZXDHB3NWFZJXWOAWNBS6GMDEAC 5O4FWCFP4ZPAS7WKSYPHN76ML3O2S4JUOYWOV2ETD4TF2H6KZ6AQC HPSOAD4RXHXU7TSVX2AD5ZDGHMS7HLYNRARWHJCXGGH5RWW7LH6QC 2SLTGWP6FTM7C7BMSYEI2EBD4YTVO2XCIVRPHBJH5XHVLLVR76TAC UF5NJKASGMZSZMBUKSUI67B2GIMQFX5SNNQEHHGUBNDBQ2QZZWSAC 6YZAVBWU6E5FYOI5JGEIPXGZLIKAW6LS2AOFIQWEE5DMOPPCD5PQC 23SFYK4Q5NKBPJG53PQNPWQH6UOUU2YKJEL7RLXYBRLJOJYV7AWQC use libpijul::HashMap;use crate::{diff, repo};#[derive(Clone, Copy, Debug, PartialEq, Eq)]pub enum Pick {Include,Exclude,}#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]pub enum PickSet {#[default]Include,Exclude,Partial,}#[derive(Clone, Debug, Default)]pub struct State {/// State of all filespub overall: PickSet,/// Map from file name to its changes state. This is preserved even when/// `overall` selection changes.pub files: HashMap<String, PickSet>,/// Map from file name to its individual change state. This is preserved/// even when `overall` and `files` selection changespub changes: HashMap<String, PartialFile>,}#[derive(Clone, Debug, Default)]pub struct PartialFile {/// Map from file name to its state// NOTE: When it's possible to select individual lines/chars to record,// then the value would be `PickSet` and the inner-most value of// selected ranges would be `Pick`pub changes: HashMap<diff::IdHash, Pick>,}/// Determine the default value for a file that is not explicitly set.pub fn default_pick_set(state: &State) -> PickSet {match default_pick(state) {Pick::Include => PickSet::Include,Pick::Exclude => PickSet::Exclude,}}/// Determine the default value for a change that is not explicitly set.pub fn default_pick(state: &State) -> Pick {let State {overall,files,changes: _,} = state;if matches!(overall, PickSet::Partial)|| files.values().any(|pick| matches!(pick, PickSet::Partial | PickSet::Exclude)){Pick::Exclude} else {Pick::Include}}pub fn determine_file(state: &State,file: &str,changed_files: &repo::ChangedFiles,) -> PickSet {match state.overall {PickSet::Include => PickSet::Include,PickSet::Exclude => PickSet::Exclude,PickSet::Partial => {if let Some(pick) = state.files.get(file) {*pick} else if file_has_any_partial_change(file, state, changed_files) {PickSet::Partial} else {default_pick_set(state)}}}}pub fn determine_change(file: &str,diff_id: diff::IdHash,state: &State,) -> Pick {match state.overall {PickSet::Include => return Pick::Include,PickSet::Exclude => return Pick::Exclude,PickSet::Partial => {}}if let Some(pick) = state.files.get(file) {match pick {PickSet::Include => return Pick::Include,PickSet::Exclude => return Pick::Exclude,PickSet::Partial => {}}}let explicit = state.changes.get(file).and_then(|file| file.changes.get(&diff_id)).copied();dbg!(&explicit);explicit.unwrap_or_else(|| default_pick(state))}pub fn file_has_any_partial_change(file: &str,state: &State,changed_files: &repo::ChangedFiles,) -> bool {let changes = changed_files.get(file).unwrap();let (mut has_include, mut has_exclude) = (false, false);state.changes.get(file).map(|file| {if file.changes.len() == changes.len() {// The number of of changes in selection is the same as number// of actual changes, we need to have both exclude and include// to be partialfile.changes.values().any(|pick| {has_exclude |= matches!(pick, Pick::Exclude);has_include |= matches!(pick, Pick::Include);has_include && has_exclude})} else {// There is the same number of changes in selection is not the// same as number of actual changes, we only need to find 1// exclusion to be partialfile.changes.values().any(|pick| matches!(pick, Pick::Exclude))}}).unwrap_or_default()}
// Filter changes using `to_record`match to_record.overall {to_record::PickSet::Exclude => {bail!("Nothing to record")}to_record::PickSet::Partial => {change.changes.retain(|hunk| {if let Ok((path, diff)) =pijul_change_to_diff(&diff, hunk, &repo.changes){match to_record::determine_file(&to_record,&path,&changed_files,) {to_record::PickSet::Exclude => {return false;}to_record::PickSet::Partial => {let diff_id = diff::id_parts_hash(&diff);match to_record::determine_change(&path, diff_id, &to_record,) {to_record::Pick::Exclude => {return false;}to_record::Pick::Include => {}}}to_record::PickSet::Include => {}}}true});}to_record::PickSet::Include => {}}
match change {BaseHunk::FileMove {del: _,add,path: old_path,} => match add {change::Atom::NewVertex(add) => {let FileMetadata {basename: new_path,metadata: _,..} = FileMetadata::read(&diff.contents[add.start.0.into()..add.end.0.into()],);changes.entry(new_path.to_string()).or_default().insert(ChangedFileDiff::Move {old_path: old_path.clone(),},);}change::Atom::EdgeMap(_edge_map) => todo!(),},BaseHunk::FileDel {del: _,contents,path,encoding,} => {let contents = if let Some(contents) = contents.as_ref() {let raw_contents = get_change_contents(repo_changes,contents,&diff.contents,)?;Some(try_decode_contents(raw_contents, encoding))} else {None};
let (path, diff) = pijul_change_to_diff(diff, change, repo_changes)?;changes.entry(path).or_default().insert(diff);}Ok(changes)}
changes.entry(path.clone()).or_default().insert(ChangedFileDiff::Del { contents });}BaseHunk::FileUndel {undel: _,contents: _,path,encoding: _,} => {changes.entry(path.clone()).or_default().insert(ChangedFileDiff::Undel);}BaseHunk::FileAdd {path,add_name: _,add_inode: _,contents: _,encoding: _,} => {changes.entry(path.clone()).or_default().insert(ChangedFileDiff::Add);}BaseHunk::SolveNameConflict { name: _, path } => {changes.entry(path.clone()).or_default().insert(ChangedFileDiff::SolveNameConflict);}BaseHunk::UnsolveNameConflict { name: _, path } => {changes.entry(path.clone()).or_default().insert(ChangedFileDiff::UnsolveNameConflict);}BaseHunk::Edit {local,change,encoding,} => {let line = local.line;let deleted = if let change::Atom::EdgeMap(map) = change {map.edges.first().map(|edge| edge.flag.is_deleted()).unwrap_or_default()} else {false};// TODO: This fn isn't public in upstream pijul, contriblet raw_contents =get_change_contents(repo_changes, change, &diff.contents)?;let contents = try_decode_contents(raw_contents, encoding);let diff = ChangedFileDiff::Edit {line,deleted,contents,};changes.entry(local.path.clone()).or_default().insert(diff);
// Get the file name and diff from a Pijul hunkfn pijul_change_to_diff(diff: &Diff,hunk: &DiffHunk,repo_changes: &FileSystem,) -> anyhow::Result<(String, ChangedFileDiff)> {let changed_file = match hunk {BaseHunk::FileMove {del: _,add,path: old_path,} => match add {change::Atom::NewVertex(add) => {let FileMetadata {basename: new_path,metadata: _,..} = FileMetadata::read(&diff.contents[add.start.0.into()..add.end.0.into()],);(new_path.to_string(),ChangedFileDiff::Move {old_path: old_path.clone(),},)
BaseHunk::Replacement {change,replacement,local,encoding,} => {let line = local.line;let raw_change_contents =get_change_contents(repo_changes, change, &diff.contents)?;let change_contents =try_decode_contents(raw_change_contents, encoding);let raw_replacement_contents = get_change_contents(
change::Atom::EdgeMap(_edge_map) => todo!(),},BaseHunk::FileDel {del: _,contents,path,encoding,} => {let contents = if let Some(contents) = contents.as_ref() {let raw_contents = get_change_contents(
let diff = ChangedFileDiff::Replacement {line,change_contents,replacement_contents,};changes.entry(local.path.clone()).or_default().insert(diff);}BaseHunk::SolveOrderConflict { change: _, local } => {changes.entry(local.path.clone()).or_default().insert(ChangedFileDiff::SolveOrderConflict);}BaseHunk::UnsolveOrderConflict { change: _, local } => {changes.entry(local.path.clone()).or_default().insert(ChangedFileDiff::UnsolveOrderConflict);}BaseHunk::ResurrectZombies {change: _,local,encoding: _,} => {changes.entry(local.path.clone()).or_default().insert(ChangedFileDiff::ResurrectZombines);}BaseHunk::AddRoot { name: _, inode: _ } => {changes.entry(ROOT_FILE.to_string()).or_default().insert(ChangedFileDiff::AddRoot);}BaseHunk::DelRoot { name: _, inode: _ } => {changes.entry(ROOT_FILE.to_string()).or_default().insert(ChangedFileDiff::DelRoot);}
(path.clone(), ChangedFileDiff::Del { contents })
}Ok(changes)
BaseHunk::FileUndel {undel: _,contents: _,path,encoding: _,} => (path.clone(), ChangedFileDiff::Undel),BaseHunk::FileAdd {path,add_name: _,add_inode: _,contents: _,encoding: _,} => (path.clone(), ChangedFileDiff::Add),BaseHunk::SolveNameConflict { name: _, path } => {(path.clone(), ChangedFileDiff::SolveNameConflict)}BaseHunk::UnsolveNameConflict { name: _, path } => {(path.clone(), ChangedFileDiff::UnsolveNameConflict)}BaseHunk::Edit {local,change,encoding,} => {let line = local.line;let deleted = if let change::Atom::EdgeMap(map) = change {map.edges.first().map(|edge| edge.flag.is_deleted()).unwrap_or_default()} else {false};// TODO: This fn isn't public in upstream pijul, contriblet raw_contents =get_change_contents(repo_changes, change, &diff.contents)?;let contents = try_decode_contents(raw_contents, encoding);let diff = ChangedFileDiff::Edit {line,deleted,contents,};(local.path.clone(), diff)}BaseHunk::Replacement {change,replacement,local,encoding,} => {let line = local.line;let raw_change_contents =get_change_contents(repo_changes, change, &diff.contents)?;let change_contents =try_decode_contents(raw_change_contents, encoding);let raw_replacement_contents =get_change_contents(repo_changes, replacement, &diff.contents)?;let replacement_contents =try_decode_contents(raw_replacement_contents, encoding);let diff = ChangedFileDiff::Replacement {line,change_contents,replacement_contents,};(local.path.clone(), diff)}BaseHunk::SolveOrderConflict { change: _, local } => {(local.path.clone(), ChangedFileDiff::SolveOrderConflict)}BaseHunk::UnsolveOrderConflict { change: _, local } => {(local.path.clone(), ChangedFileDiff::UnsolveOrderConflict)}BaseHunk::ResurrectZombies {change: _,local,encoding: _,} => (local.path.clone(), ChangedFileDiff::ResurrectZombines),BaseHunk::AddRoot { name: _, inode: _ } => {(ROOT_FILE.to_string(), ChangedFileDiff::AddRoot)}BaseHunk::DelRoot { name: _, inode: _ } => {(ROOT_FILE.to_string(), ChangedFileDiff::DelRoot)}};Ok(changed_file)
}#[derive(Clone, Copy, Debug, PartialEq, Eq)]pub enum Pick {Include,Exclude,}#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]pub enum PickSet {#[default]Include,Exclude,Partial,}#[derive(Clone, Debug, Default)]pub struct State {/// State of all filespub overall: PickSet,/// Map from file name to its changes state. This is preserved even when/// `overall` selection changes.pub files: HashMap<String, PickSet>,/// Map from file name to its individual change state. This is preserved/// even when `overall` and `files` selection changespub changes: HashMap<String, PartialFile>,}#[derive(Clone, Debug, Default)]pub struct PartialFile {/// Map from file name to its state// NOTE: When it's possible to select individual lines/chars to record,// then the value would be `PickSet` and the inner-most value of// selected ranges would be `Pick`pub changes: HashMap<diff::IdHash, Pick>,}pub fn determine_file(state: &State,file: &str,changed_files: &repo::ChangedFiles,) -> PickSet {match state.overall {PickSet::Include => PickSet::Include,PickSet::Exclude => PickSet::Exclude,PickSet::Partial => {if let Some(pick) = state.files.get(file) {*pick} else if file_has_any_partial_change(file, state, changed_files) {PickSet::Partial} else {default_pick_set(state)}}}}pub fn determine_change_pick(file: &str,diff_id: diff::IdHash,state: &State,) -> Pick {match state.overall {PickSet::Include => return Pick::Include,PickSet::Exclude => return Pick::Exclude,PickSet::Partial => {}}if let Some(pick) = state.files.get(file) {match pick {PickSet::Include => return Pick::Include,PickSet::Exclude => return Pick::Exclude,PickSet::Partial => {}}}let explicit = state.changes.get(file).and_then(|file| file.changes.get(&diff_id)).copied();dbg!(&explicit);explicit.unwrap_or_else(|| default_pick(state))
/// Determine the default value for a change that is not explicitly set.fn default_pick(state: &State) -> Pick {let State {overall,files,changes: _,} = state;if matches!(overall, PickSet::Partial)|| files.values().any(|pick| matches!(pick, PickSet::Partial | PickSet::Exclude)){Pick::Exclude} else {Pick::Include}}
fn file_has_any_partial_change(file: &str,state: &State,changed_files: &repo::ChangedFiles,) -> bool {let changes = changed_files.get(file).unwrap();let (mut has_include, mut has_exclude) = (false, false);state.changes.get(file).map(|file| {if file.changes.len() == changes.len() {// The number of of changes in selection is the same as number// of actual changes, we need to have both exclude and include// to be partialfile.changes.values().any(|pick| {has_exclude |= matches!(pick, Pick::Exclude);has_include |= matches!(pick, Pick::Include);has_include && has_exclude})} else {// There is the same number of changes in selection is not the// same as number of actual changes, we only need to find 1// exclusion to be partialfile.changes.values().any(|pick| matches!(pick, Pick::Exclude))}}).unwrap_or_default()}