grit_lib/index/
mode_policy.rs1use crate::config::ConfigSet;
4
5use super::{MODE_EXECUTABLE, MODE_REGULAR, MODE_SYMLINK};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct WorktreeModePolicy {
19 pub trust_filemode: bool,
21 pub has_symlinks: bool,
23}
24
25impl Default for WorktreeModePolicy {
26 fn default() -> Self {
27 Self {
28 trust_filemode: true,
29 has_symlinks: true,
30 }
31 }
32}
33
34impl WorktreeModePolicy {
35 #[must_use]
38 pub fn from_config(config: &ConfigSet) -> Self {
39 let flag = |key: &str| config.get_bool(key).and_then(|r| r.ok()).unwrap_or(true);
40 Self {
41 trust_filemode: flag("core.filemode"),
42 has_symlinks: flag("core.symlinks"),
43 }
44 }
45
46 #[must_use]
67 pub fn index_mode_for_worktree(self, worktree_mode: u32, recorded_mode: Option<u32>) -> u32 {
68 let Some(recorded) = recorded_mode else {
69 return worktree_mode;
70 };
71 if !is_regular_file_mode(worktree_mode) {
72 return worktree_mode;
73 }
74 if !self.has_symlinks && recorded == MODE_SYMLINK {
75 return MODE_SYMLINK;
76 }
77 if !self.trust_filemode && is_regular_file_mode(recorded) {
78 return recorded;
79 }
80 worktree_mode
81 }
82
83 #[must_use]
87 pub fn worktree_mode_matches(self, worktree_mode: u32, recorded_mode: u32) -> bool {
88 self.index_mode_for_worktree(worktree_mode, Some(recorded_mode)) == recorded_mode
89 }
90}
91
92fn is_regular_file_mode(mode: u32) -> bool {
93 mode == MODE_REGULAR || mode == MODE_EXECUTABLE
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use crate::index::{MODE_GITLINK, MODE_TREE};
100
101 const NO_SYMLINKS: WorktreeModePolicy = WorktreeModePolicy {
102 trust_filemode: true,
103 has_symlinks: false,
104 };
105 const NO_FILEMODE: WorktreeModePolicy = WorktreeModePolicy {
106 trust_filemode: false,
107 has_symlinks: true,
108 };
109
110 #[test]
111 fn default_trusts_worktree() {
112 let p = WorktreeModePolicy::default();
113 assert_eq!(
114 p.index_mode_for_worktree(MODE_REGULAR, Some(MODE_SYMLINK)),
115 MODE_REGULAR
116 );
117 assert_eq!(
118 p.index_mode_for_worktree(MODE_EXECUTABLE, Some(MODE_REGULAR)),
119 MODE_EXECUTABLE
120 );
121 assert!(!p.worktree_mode_matches(MODE_REGULAR, MODE_SYMLINK));
122 }
123
124 #[test]
125 fn no_symlinks_keeps_recorded_symlink_for_regular_file() {
126 for wt in [MODE_REGULAR, MODE_EXECUTABLE] {
127 assert_eq!(
128 NO_SYMLINKS.index_mode_for_worktree(wt, Some(MODE_SYMLINK)),
129 MODE_SYMLINK
130 );
131 assert!(NO_SYMLINKS.worktree_mode_matches(wt, MODE_SYMLINK));
132 }
133 assert_eq!(
135 NO_SYMLINKS.index_mode_for_worktree(MODE_EXECUTABLE, Some(MODE_REGULAR)),
136 MODE_EXECUTABLE
137 );
138 assert_eq!(
140 NO_SYMLINKS.index_mode_for_worktree(MODE_SYMLINK, Some(MODE_REGULAR)),
141 MODE_SYMLINK
142 );
143 }
144
145 #[test]
146 fn no_filemode_keeps_recorded_regular_mode() {
147 assert_eq!(
148 NO_FILEMODE.index_mode_for_worktree(MODE_EXECUTABLE, Some(MODE_REGULAR)),
149 MODE_REGULAR
150 );
151 assert_eq!(
152 NO_FILEMODE.index_mode_for_worktree(MODE_REGULAR, Some(MODE_EXECUTABLE)),
153 MODE_EXECUTABLE
154 );
155 assert_eq!(
157 NO_FILEMODE.index_mode_for_worktree(MODE_REGULAR, Some(MODE_SYMLINK)),
158 MODE_REGULAR
159 );
160 assert_eq!(
161 NO_FILEMODE.index_mode_for_worktree(MODE_REGULAR, Some(MODE_GITLINK)),
162 MODE_REGULAR
163 );
164 }
165
166 #[test]
167 fn untracked_and_non_regular_worktree_modes_pass_through() {
168 let none = WorktreeModePolicy {
169 trust_filemode: false,
170 has_symlinks: false,
171 };
172 assert_eq!(
173 none.index_mode_for_worktree(MODE_EXECUTABLE, None),
174 MODE_EXECUTABLE
175 );
176 assert_eq!(
177 none.index_mode_for_worktree(MODE_TREE, Some(MODE_SYMLINK)),
178 MODE_TREE
179 );
180 assert_eq!(
181 none.index_mode_for_worktree(MODE_GITLINK, Some(MODE_REGULAR)),
182 MODE_GITLINK
183 );
184 }
185
186 #[test]
187 fn from_config_reads_both_keys() {
188 let mut cfg = ConfigSet::new();
189 assert_eq!(
190 WorktreeModePolicy::from_config(&cfg),
191 WorktreeModePolicy::default()
192 );
193 cfg.add_command_override("core.symlinks", "false")
194 .expect("set symlinks");
195 cfg.add_command_override("core.filemode", "false")
196 .expect("set filemode");
197 assert_eq!(
198 WorktreeModePolicy::from_config(&cfg),
199 WorktreeModePolicy {
200 trust_filemode: false,
201 has_symlinks: false,
202 }
203 );
204 }
205}