use crate::config::ConfigSet;
use super::{MODE_EXECUTABLE, MODE_REGULAR, MODE_SYMLINK};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WorktreeModePolicy {
pub trust_filemode: bool,
pub has_symlinks: bool,
}
impl Default for WorktreeModePolicy {
fn default() -> Self {
Self {
trust_filemode: true,
has_symlinks: true,
}
}
}
impl WorktreeModePolicy {
#[must_use]
pub fn from_config(config: &ConfigSet) -> Self {
let flag = |key: &str| config.get_bool(key).and_then(|r| r.ok()).unwrap_or(true);
Self {
trust_filemode: flag("core.filemode"),
has_symlinks: flag("core.symlinks"),
}
}
#[must_use]
pub fn index_mode_for_worktree(self, worktree_mode: u32, recorded_mode: Option<u32>) -> u32 {
let Some(recorded) = recorded_mode else {
return worktree_mode;
};
if !is_regular_file_mode(worktree_mode) {
return worktree_mode;
}
if !self.has_symlinks && recorded == MODE_SYMLINK {
return MODE_SYMLINK;
}
if !self.trust_filemode && is_regular_file_mode(recorded) {
return recorded;
}
worktree_mode
}
#[must_use]
pub fn worktree_mode_matches(self, worktree_mode: u32, recorded_mode: u32) -> bool {
self.index_mode_for_worktree(worktree_mode, Some(recorded_mode)) == recorded_mode
}
}
fn is_regular_file_mode(mode: u32) -> bool {
mode == MODE_REGULAR || mode == MODE_EXECUTABLE
}
#[cfg(test)]
mod tests {
use super::*;
use crate::index::{MODE_GITLINK, MODE_TREE};
const NO_SYMLINKS: WorktreeModePolicy = WorktreeModePolicy {
trust_filemode: true,
has_symlinks: false,
};
const NO_FILEMODE: WorktreeModePolicy = WorktreeModePolicy {
trust_filemode: false,
has_symlinks: true,
};
#[test]
fn default_trusts_worktree() {
let p = WorktreeModePolicy::default();
assert_eq!(
p.index_mode_for_worktree(MODE_REGULAR, Some(MODE_SYMLINK)),
MODE_REGULAR
);
assert_eq!(
p.index_mode_for_worktree(MODE_EXECUTABLE, Some(MODE_REGULAR)),
MODE_EXECUTABLE
);
assert!(!p.worktree_mode_matches(MODE_REGULAR, MODE_SYMLINK));
}
#[test]
fn no_symlinks_keeps_recorded_symlink_for_regular_file() {
for wt in [MODE_REGULAR, MODE_EXECUTABLE] {
assert_eq!(
NO_SYMLINKS.index_mode_for_worktree(wt, Some(MODE_SYMLINK)),
MODE_SYMLINK
);
assert!(NO_SYMLINKS.worktree_mode_matches(wt, MODE_SYMLINK));
}
assert_eq!(
NO_SYMLINKS.index_mode_for_worktree(MODE_EXECUTABLE, Some(MODE_REGULAR)),
MODE_EXECUTABLE
);
assert_eq!(
NO_SYMLINKS.index_mode_for_worktree(MODE_SYMLINK, Some(MODE_REGULAR)),
MODE_SYMLINK
);
}
#[test]
fn no_filemode_keeps_recorded_regular_mode() {
assert_eq!(
NO_FILEMODE.index_mode_for_worktree(MODE_EXECUTABLE, Some(MODE_REGULAR)),
MODE_REGULAR
);
assert_eq!(
NO_FILEMODE.index_mode_for_worktree(MODE_REGULAR, Some(MODE_EXECUTABLE)),
MODE_EXECUTABLE
);
assert_eq!(
NO_FILEMODE.index_mode_for_worktree(MODE_REGULAR, Some(MODE_SYMLINK)),
MODE_REGULAR
);
assert_eq!(
NO_FILEMODE.index_mode_for_worktree(MODE_REGULAR, Some(MODE_GITLINK)),
MODE_REGULAR
);
}
#[test]
fn untracked_and_non_regular_worktree_modes_pass_through() {
let none = WorktreeModePolicy {
trust_filemode: false,
has_symlinks: false,
};
assert_eq!(
none.index_mode_for_worktree(MODE_EXECUTABLE, None),
MODE_EXECUTABLE
);
assert_eq!(
none.index_mode_for_worktree(MODE_TREE, Some(MODE_SYMLINK)),
MODE_TREE
);
assert_eq!(
none.index_mode_for_worktree(MODE_GITLINK, Some(MODE_REGULAR)),
MODE_GITLINK
);
}
#[test]
fn from_config_reads_both_keys() {
let mut cfg = ConfigSet::new();
assert_eq!(
WorktreeModePolicy::from_config(&cfg),
WorktreeModePolicy::default()
);
cfg.add_command_override("core.symlinks", "false")
.expect("set symlinks");
cfg.add_command_override("core.filemode", "false")
.expect("set filemode");
assert_eq!(
WorktreeModePolicy::from_config(&cfg),
WorktreeModePolicy {
trust_filemode: false,
has_symlinks: false,
}
);
}
}