##// END OF EJS Templates
rhg: fix the bug where sparse config is interpreted as relglob instead of glob...
Arseniy Alekseyev -
r51714:28c0fcff stable
parent child Browse files
Show More
@@ -1,339 +1,339 b''
1 use std::{collections::HashSet, path::Path};
1 use std::{collections::HashSet, path::Path};
2
2
3 use format_bytes::{write_bytes, DisplayBytes};
3 use format_bytes::{write_bytes, DisplayBytes};
4
4
5 use crate::{
5 use crate::{
6 errors::HgError,
6 errors::HgError,
7 filepatterns::parse_pattern_file_contents,
7 filepatterns::parse_pattern_file_contents,
8 matchers::{
8 matchers::{
9 AlwaysMatcher, DifferenceMatcher, IncludeMatcher, Matcher,
9 AlwaysMatcher, DifferenceMatcher, IncludeMatcher, Matcher,
10 UnionMatcher,
10 UnionMatcher,
11 },
11 },
12 operations::cat,
12 operations::cat,
13 repo::Repo,
13 repo::Repo,
14 requirements::SPARSE_REQUIREMENT,
14 requirements::SPARSE_REQUIREMENT,
15 utils::{hg_path::HgPath, SliceExt},
15 utils::{hg_path::HgPath, SliceExt},
16 IgnorePattern, PatternError, PatternFileWarning, PatternSyntax, Revision,
16 IgnorePattern, PatternError, PatternFileWarning, PatternSyntax, Revision,
17 NULL_REVISION,
17 NULL_REVISION,
18 };
18 };
19
19
20 /// Command which is triggering the config read
20 /// Command which is triggering the config read
21 #[derive(Copy, Clone, Debug)]
21 #[derive(Copy, Clone, Debug)]
22 pub enum SparseConfigContext {
22 pub enum SparseConfigContext {
23 Sparse,
23 Sparse,
24 Narrow,
24 Narrow,
25 }
25 }
26
26
27 impl DisplayBytes for SparseConfigContext {
27 impl DisplayBytes for SparseConfigContext {
28 fn display_bytes(
28 fn display_bytes(
29 &self,
29 &self,
30 output: &mut dyn std::io::Write,
30 output: &mut dyn std::io::Write,
31 ) -> std::io::Result<()> {
31 ) -> std::io::Result<()> {
32 match self {
32 match self {
33 SparseConfigContext::Sparse => write_bytes!(output, b"sparse"),
33 SparseConfigContext::Sparse => write_bytes!(output, b"sparse"),
34 SparseConfigContext::Narrow => write_bytes!(output, b"narrow"),
34 SparseConfigContext::Narrow => write_bytes!(output, b"narrow"),
35 }
35 }
36 }
36 }
37 }
37 }
38
38
39 /// Possible warnings when reading sparse configuration
39 /// Possible warnings when reading sparse configuration
40 #[derive(Debug, derive_more::From)]
40 #[derive(Debug, derive_more::From)]
41 pub enum SparseWarning {
41 pub enum SparseWarning {
42 /// Warns about improper paths that start with "/"
42 /// Warns about improper paths that start with "/"
43 RootWarning {
43 RootWarning {
44 context: SparseConfigContext,
44 context: SparseConfigContext,
45 line: Vec<u8>,
45 line: Vec<u8>,
46 },
46 },
47 /// Warns about a profile missing from the given changelog revision
47 /// Warns about a profile missing from the given changelog revision
48 ProfileNotFound { profile: Vec<u8>, rev: Revision },
48 ProfileNotFound { profile: Vec<u8>, rev: Revision },
49 #[from]
49 #[from]
50 Pattern(PatternFileWarning),
50 Pattern(PatternFileWarning),
51 }
51 }
52
52
53 /// Parsed sparse config
53 /// Parsed sparse config
54 #[derive(Debug, Default)]
54 #[derive(Debug, Default)]
55 pub struct SparseConfig {
55 pub struct SparseConfig {
56 // Line-separated
56 // Line-separated
57 pub(crate) includes: Vec<u8>,
57 pub(crate) includes: Vec<u8>,
58 // Line-separated
58 // Line-separated
59 pub(crate) excludes: Vec<u8>,
59 pub(crate) excludes: Vec<u8>,
60 pub(crate) profiles: HashSet<Vec<u8>>,
60 pub(crate) profiles: HashSet<Vec<u8>>,
61 pub(crate) warnings: Vec<SparseWarning>,
61 pub(crate) warnings: Vec<SparseWarning>,
62 }
62 }
63
63
64 /// All possible errors when reading sparse/narrow config
64 /// All possible errors when reading sparse/narrow config
65 #[derive(Debug, derive_more::From)]
65 #[derive(Debug, derive_more::From)]
66 pub enum SparseConfigError {
66 pub enum SparseConfigError {
67 IncludesAfterExcludes {
67 IncludesAfterExcludes {
68 context: SparseConfigContext,
68 context: SparseConfigContext,
69 },
69 },
70 EntryOutsideSection {
70 EntryOutsideSection {
71 context: SparseConfigContext,
71 context: SparseConfigContext,
72 line: Vec<u8>,
72 line: Vec<u8>,
73 },
73 },
74 /// Narrow config does not support '%include' directives
74 /// Narrow config does not support '%include' directives
75 IncludesInNarrow,
75 IncludesInNarrow,
76 /// An invalid pattern prefix was given to the narrow spec. Includes the
76 /// An invalid pattern prefix was given to the narrow spec. Includes the
77 /// entire pattern for context.
77 /// entire pattern for context.
78 InvalidNarrowPrefix(Vec<u8>),
78 InvalidNarrowPrefix(Vec<u8>),
79 #[from]
79 #[from]
80 HgError(HgError),
80 HgError(HgError),
81 #[from]
81 #[from]
82 PatternError(PatternError),
82 PatternError(PatternError),
83 }
83 }
84
84
85 /// Parse sparse config file content.
85 /// Parse sparse config file content.
86 pub(crate) fn parse_config(
86 pub(crate) fn parse_config(
87 raw: &[u8],
87 raw: &[u8],
88 context: SparseConfigContext,
88 context: SparseConfigContext,
89 ) -> Result<SparseConfig, SparseConfigError> {
89 ) -> Result<SparseConfig, SparseConfigError> {
90 let mut includes = vec![];
90 let mut includes = vec![];
91 let mut excludes = vec![];
91 let mut excludes = vec![];
92 let mut profiles = HashSet::new();
92 let mut profiles = HashSet::new();
93 let mut warnings = vec![];
93 let mut warnings = vec![];
94
94
95 #[derive(PartialEq, Eq)]
95 #[derive(PartialEq, Eq)]
96 enum Current {
96 enum Current {
97 Includes,
97 Includes,
98 Excludes,
98 Excludes,
99 None,
99 None,
100 }
100 }
101
101
102 let mut current = Current::None;
102 let mut current = Current::None;
103 let mut in_section = false;
103 let mut in_section = false;
104
104
105 for line in raw.split(|c| *c == b'\n') {
105 for line in raw.split(|c| *c == b'\n') {
106 let line = line.trim();
106 let line = line.trim();
107 if line.is_empty() || line[0] == b'#' {
107 if line.is_empty() || line[0] == b'#' {
108 // empty or comment line, skip
108 // empty or comment line, skip
109 continue;
109 continue;
110 }
110 }
111 if line.starts_with(b"%include ") {
111 if line.starts_with(b"%include ") {
112 let profile = line[b"%include ".len()..].trim();
112 let profile = line[b"%include ".len()..].trim();
113 if !profile.is_empty() {
113 if !profile.is_empty() {
114 profiles.insert(profile.into());
114 profiles.insert(profile.into());
115 }
115 }
116 } else if line == b"[include]" {
116 } else if line == b"[include]" {
117 if in_section && current == Current::Includes {
117 if in_section && current == Current::Includes {
118 return Err(SparseConfigError::IncludesAfterExcludes {
118 return Err(SparseConfigError::IncludesAfterExcludes {
119 context,
119 context,
120 });
120 });
121 }
121 }
122 in_section = true;
122 in_section = true;
123 current = Current::Includes;
123 current = Current::Includes;
124 continue;
124 continue;
125 } else if line == b"[exclude]" {
125 } else if line == b"[exclude]" {
126 in_section = true;
126 in_section = true;
127 current = Current::Excludes;
127 current = Current::Excludes;
128 } else {
128 } else {
129 if current == Current::None {
129 if current == Current::None {
130 return Err(SparseConfigError::EntryOutsideSection {
130 return Err(SparseConfigError::EntryOutsideSection {
131 context,
131 context,
132 line: line.into(),
132 line: line.into(),
133 });
133 });
134 }
134 }
135 if line.trim().starts_with(b"/") {
135 if line.trim().starts_with(b"/") {
136 warnings.push(SparseWarning::RootWarning {
136 warnings.push(SparseWarning::RootWarning {
137 context,
137 context,
138 line: line.into(),
138 line: line.into(),
139 });
139 });
140 continue;
140 continue;
141 }
141 }
142 match current {
142 match current {
143 Current::Includes => {
143 Current::Includes => {
144 includes.push(b'\n');
144 includes.push(b'\n');
145 includes.extend(line.iter());
145 includes.extend(line.iter());
146 }
146 }
147 Current::Excludes => {
147 Current::Excludes => {
148 excludes.push(b'\n');
148 excludes.push(b'\n');
149 excludes.extend(line.iter());
149 excludes.extend(line.iter());
150 }
150 }
151 Current::None => unreachable!(),
151 Current::None => unreachable!(),
152 }
152 }
153 }
153 }
154 }
154 }
155
155
156 Ok(SparseConfig {
156 Ok(SparseConfig {
157 includes,
157 includes,
158 excludes,
158 excludes,
159 profiles,
159 profiles,
160 warnings,
160 warnings,
161 })
161 })
162 }
162 }
163
163
164 fn read_temporary_includes(
164 fn read_temporary_includes(
165 repo: &Repo,
165 repo: &Repo,
166 ) -> Result<Vec<Vec<u8>>, SparseConfigError> {
166 ) -> Result<Vec<Vec<u8>>, SparseConfigError> {
167 let raw = repo.hg_vfs().try_read("tempsparse")?.unwrap_or_default();
167 let raw = repo.hg_vfs().try_read("tempsparse")?.unwrap_or_default();
168 if raw.is_empty() {
168 if raw.is_empty() {
169 return Ok(vec![]);
169 return Ok(vec![]);
170 }
170 }
171 Ok(raw.split(|c| *c == b'\n').map(ToOwned::to_owned).collect())
171 Ok(raw.split(|c| *c == b'\n').map(ToOwned::to_owned).collect())
172 }
172 }
173
173
174 /// Obtain sparse checkout patterns for the given revision
174 /// Obtain sparse checkout patterns for the given revision
175 fn patterns_for_rev(
175 fn patterns_for_rev(
176 repo: &Repo,
176 repo: &Repo,
177 rev: Revision,
177 rev: Revision,
178 ) -> Result<Option<SparseConfig>, SparseConfigError> {
178 ) -> Result<Option<SparseConfig>, SparseConfigError> {
179 if !repo.has_sparse() {
179 if !repo.has_sparse() {
180 return Ok(None);
180 return Ok(None);
181 }
181 }
182 let raw = repo.hg_vfs().try_read("sparse")?.unwrap_or_default();
182 let raw = repo.hg_vfs().try_read("sparse")?.unwrap_or_default();
183
183
184 if raw.is_empty() {
184 if raw.is_empty() {
185 return Ok(None);
185 return Ok(None);
186 }
186 }
187
187
188 let mut config = parse_config(&raw, SparseConfigContext::Sparse)?;
188 let mut config = parse_config(&raw, SparseConfigContext::Sparse)?;
189
189
190 if !config.profiles.is_empty() {
190 if !config.profiles.is_empty() {
191 let mut profiles: Vec<Vec<u8>> = config.profiles.into_iter().collect();
191 let mut profiles: Vec<Vec<u8>> = config.profiles.into_iter().collect();
192 let mut visited = HashSet::new();
192 let mut visited = HashSet::new();
193
193
194 while let Some(profile) = profiles.pop() {
194 while let Some(profile) = profiles.pop() {
195 if visited.contains(&profile) {
195 if visited.contains(&profile) {
196 continue;
196 continue;
197 }
197 }
198 visited.insert(profile.to_owned());
198 visited.insert(profile.to_owned());
199
199
200 let output =
200 let output =
201 cat(repo, &rev.to_string(), vec![HgPath::new(&profile)])
201 cat(repo, &rev.to_string(), vec![HgPath::new(&profile)])
202 .map_err(|_| {
202 .map_err(|_| {
203 HgError::corrupted(
203 HgError::corrupted(
204 "dirstate points to non-existent parent node"
204 "dirstate points to non-existent parent node"
205 .to_string(),
205 .to_string(),
206 )
206 )
207 })?;
207 })?;
208 if output.results.is_empty() {
208 if output.results.is_empty() {
209 config.warnings.push(SparseWarning::ProfileNotFound {
209 config.warnings.push(SparseWarning::ProfileNotFound {
210 profile: profile.to_owned(),
210 profile: profile.to_owned(),
211 rev,
211 rev,
212 })
212 })
213 }
213 }
214
214
215 let subconfig = parse_config(
215 let subconfig = parse_config(
216 &output.results[0].1,
216 &output.results[0].1,
217 SparseConfigContext::Sparse,
217 SparseConfigContext::Sparse,
218 )?;
218 )?;
219 if !subconfig.includes.is_empty() {
219 if !subconfig.includes.is_empty() {
220 config.includes.push(b'\n');
220 config.includes.push(b'\n');
221 config.includes.extend(&subconfig.includes);
221 config.includes.extend(&subconfig.includes);
222 }
222 }
223 if !subconfig.includes.is_empty() {
223 if !subconfig.includes.is_empty() {
224 config.includes.push(b'\n');
224 config.includes.push(b'\n');
225 config.excludes.extend(&subconfig.excludes);
225 config.excludes.extend(&subconfig.excludes);
226 }
226 }
227 config.warnings.extend(subconfig.warnings.into_iter());
227 config.warnings.extend(subconfig.warnings.into_iter());
228 profiles.extend(subconfig.profiles.into_iter());
228 profiles.extend(subconfig.profiles.into_iter());
229 }
229 }
230
230
231 config.profiles = visited;
231 config.profiles = visited;
232 }
232 }
233
233
234 if !config.includes.is_empty() {
234 if !config.includes.is_empty() {
235 config.includes.extend(b"\n.hg*");
235 config.includes.extend(b"\n.hg*");
236 }
236 }
237
237
238 Ok(Some(config))
238 Ok(Some(config))
239 }
239 }
240
240
241 /// Obtain a matcher for sparse working directories.
241 /// Obtain a matcher for sparse working directories.
242 pub fn matcher(
242 pub fn matcher(
243 repo: &Repo,
243 repo: &Repo,
244 ) -> Result<(Box<dyn Matcher + Sync>, Vec<SparseWarning>), SparseConfigError> {
244 ) -> Result<(Box<dyn Matcher + Sync>, Vec<SparseWarning>), SparseConfigError> {
245 let mut warnings = vec![];
245 let mut warnings = vec![];
246 if !repo.requirements().contains(SPARSE_REQUIREMENT) {
246 if !repo.requirements().contains(SPARSE_REQUIREMENT) {
247 return Ok((Box::new(AlwaysMatcher), warnings));
247 return Ok((Box::new(AlwaysMatcher), warnings));
248 }
248 }
249
249
250 let parents = repo.dirstate_parents()?;
250 let parents = repo.dirstate_parents()?;
251 let mut revs = vec![];
251 let mut revs = vec![];
252 let p1_rev =
252 let p1_rev =
253 repo.changelog()?
253 repo.changelog()?
254 .rev_from_node(parents.p1.into())
254 .rev_from_node(parents.p1.into())
255 .map_err(|_| {
255 .map_err(|_| {
256 HgError::corrupted(
256 HgError::corrupted(
257 "dirstate points to non-existent parent node".to_string(),
257 "dirstate points to non-existent parent node".to_string(),
258 )
258 )
259 })?;
259 })?;
260 if p1_rev != NULL_REVISION {
260 if p1_rev != NULL_REVISION {
261 revs.push(p1_rev)
261 revs.push(p1_rev)
262 }
262 }
263 let p2_rev =
263 let p2_rev =
264 repo.changelog()?
264 repo.changelog()?
265 .rev_from_node(parents.p2.into())
265 .rev_from_node(parents.p2.into())
266 .map_err(|_| {
266 .map_err(|_| {
267 HgError::corrupted(
267 HgError::corrupted(
268 "dirstate points to non-existent parent node".to_string(),
268 "dirstate points to non-existent parent node".to_string(),
269 )
269 )
270 })?;
270 })?;
271 if p2_rev != NULL_REVISION {
271 if p2_rev != NULL_REVISION {
272 revs.push(p2_rev)
272 revs.push(p2_rev)
273 }
273 }
274 let mut matchers = vec![];
274 let mut matchers = vec![];
275
275
276 for rev in revs.iter() {
276 for rev in revs.iter() {
277 let config = patterns_for_rev(repo, *rev);
277 let config = patterns_for_rev(repo, *rev);
278 if let Ok(Some(config)) = config {
278 if let Ok(Some(config)) = config {
279 warnings.extend(config.warnings);
279 warnings.extend(config.warnings);
280 let mut m: Box<dyn Matcher + Sync> = Box::new(AlwaysMatcher);
280 let mut m: Box<dyn Matcher + Sync> = Box::new(AlwaysMatcher);
281 if !config.includes.is_empty() {
281 if !config.includes.is_empty() {
282 let (patterns, subwarnings) = parse_pattern_file_contents(
282 let (patterns, subwarnings) = parse_pattern_file_contents(
283 &config.includes,
283 &config.includes,
284 Path::new(""),
284 Path::new(""),
285 Some(b"relglob:".as_ref()),
285 Some(b"glob:".as_ref()),
286 false,
286 false,
287 )?;
287 )?;
288 warnings.extend(subwarnings.into_iter().map(From::from));
288 warnings.extend(subwarnings.into_iter().map(From::from));
289 m = Box::new(IncludeMatcher::new(patterns)?);
289 m = Box::new(IncludeMatcher::new(patterns)?);
290 }
290 }
291 if !config.excludes.is_empty() {
291 if !config.excludes.is_empty() {
292 let (patterns, subwarnings) = parse_pattern_file_contents(
292 let (patterns, subwarnings) = parse_pattern_file_contents(
293 &config.excludes,
293 &config.excludes,
294 Path::new(""),
294 Path::new(""),
295 Some(b"relglob:".as_ref()),
295 Some(b"glob:".as_ref()),
296 false,
296 false,
297 )?;
297 )?;
298 warnings.extend(subwarnings.into_iter().map(From::from));
298 warnings.extend(subwarnings.into_iter().map(From::from));
299 m = Box::new(DifferenceMatcher::new(
299 m = Box::new(DifferenceMatcher::new(
300 m,
300 m,
301 Box::new(IncludeMatcher::new(patterns)?),
301 Box::new(IncludeMatcher::new(patterns)?),
302 ));
302 ));
303 }
303 }
304 matchers.push(m);
304 matchers.push(m);
305 }
305 }
306 }
306 }
307 let result: Box<dyn Matcher + Sync> = match matchers.len() {
307 let result: Box<dyn Matcher + Sync> = match matchers.len() {
308 0 => Box::new(AlwaysMatcher),
308 0 => Box::new(AlwaysMatcher),
309 1 => matchers.pop().expect("1 is equal to 0"),
309 1 => matchers.pop().expect("1 is equal to 0"),
310 _ => Box::new(UnionMatcher::new(matchers)),
310 _ => Box::new(UnionMatcher::new(matchers)),
311 };
311 };
312
312
313 let matcher =
313 let matcher =
314 force_include_matcher(result, &read_temporary_includes(repo)?)?;
314 force_include_matcher(result, &read_temporary_includes(repo)?)?;
315 Ok((matcher, warnings))
315 Ok((matcher, warnings))
316 }
316 }
317
317
318 /// Returns a matcher that returns true for any of the forced includes before
318 /// Returns a matcher that returns true for any of the forced includes before
319 /// testing against the actual matcher
319 /// testing against the actual matcher
320 fn force_include_matcher(
320 fn force_include_matcher(
321 result: Box<dyn Matcher + Sync>,
321 result: Box<dyn Matcher + Sync>,
322 temp_includes: &[Vec<u8>],
322 temp_includes: &[Vec<u8>],
323 ) -> Result<Box<dyn Matcher + Sync>, PatternError> {
323 ) -> Result<Box<dyn Matcher + Sync>, PatternError> {
324 if temp_includes.is_empty() {
324 if temp_includes.is_empty() {
325 return Ok(result);
325 return Ok(result);
326 }
326 }
327 let forced_include_matcher = IncludeMatcher::new(
327 let forced_include_matcher = IncludeMatcher::new(
328 temp_includes
328 temp_includes
329 .iter()
329 .iter()
330 .map(|include| {
330 .map(|include| {
331 IgnorePattern::new(PatternSyntax::Path, include, Path::new(""))
331 IgnorePattern::new(PatternSyntax::Path, include, Path::new(""))
332 })
332 })
333 .collect(),
333 .collect(),
334 )?;
334 )?;
335 Ok(Box::new(UnionMatcher::new(vec![
335 Ok(Box::new(UnionMatcher::new(vec![
336 Box::new(forced_include_matcher),
336 Box::new(forced_include_matcher),
337 result,
337 result,
338 ])))
338 ])))
339 }
339 }
@@ -1,440 +1,463 b''
1 test sparse
1 test sparse
2
2
3 $ hg init myrepo
3 $ hg init myrepo
4 $ cd myrepo
4 $ cd myrepo
5 $ cat > .hg/hgrc <<EOF
5 $ cat > .hg/hgrc <<EOF
6 > [extensions]
6 > [extensions]
7 > sparse=
7 > sparse=
8 > strip=
8 > strip=
9 > EOF
9 > EOF
10
10
11 $ echo a > show
11 $ echo a > show
12 $ echo x > hide
12 $ echo x > hide
13 $ hg ci -Aqm 'initial'
13 $ hg ci -Aqm 'initial'
14
14
15 $ echo b > show
15 $ echo b > show
16 $ echo y > hide
16 $ echo y > hide
17 $ echo aa > show2
17 $ echo aa > show2
18 $ echo xx > hide2
18 $ echo xx > hide2
19 $ hg ci -Aqm 'two'
19 $ hg ci -Aqm 'two'
20
20
21 Verify basic --include
21 Verify basic --include
22
22
23 $ hg up -q 0
23 $ hg up -q 0
24
25 Test that sparse pattern by default is interpreted as "glob:", and is interpreted relative to the root.
26
27 $ hg debugsparse --reset
28 $ hg debugsparse -X 'foo*bar'
29 $ cat .hg/sparse
30 [exclude]
31 foo*bar
32
33 $ mk() { mkdir -p "$1"; touch "$1"/"$2"; }
34 $ mk 'foo' bar
35 $ mk 'foo-bar' x
36 $ mk 'unanchoredfoo-bar' x
37 $ mk 'foo*bar' x
38 $ mk 'dir/foo-bar' x
39 $ hg status --config rhg.on-unsupported=abort
40 ? dir/foo-bar/x
41 ? foo/bar
42 ? unanchoredfoo-bar/x
43 $ hg clean -a --no-confirm
44 $ rm -r foo*bar
45 $ hg debugsparse --reset
46
24 $ hg debugsparse --include 'hide'
47 $ hg debugsparse --include 'hide'
25 $ ls -A
48 $ ls -A
26 .hg
49 .hg
27 hide
50 hide
28
51
29 Absolute paths outside the repo should just be rejected
52 Absolute paths outside the repo should just be rejected
30
53
31 #if no-windows
54 #if no-windows
32 $ hg debugsparse --include /foo/bar
55 $ hg debugsparse --include /foo/bar
33 abort: paths cannot be absolute
56 abort: paths cannot be absolute
34 [255]
57 [255]
35 $ hg debugsparse --include '$TESTTMP/myrepo/hide'
58 $ hg debugsparse --include '$TESTTMP/myrepo/hide'
36
59
37 $ hg debugsparse --include '/root'
60 $ hg debugsparse --include '/root'
38 abort: paths cannot be absolute
61 abort: paths cannot be absolute
39 [255]
62 [255]
40 #else
63 #else
41 TODO: See if this can be made to fail the same way as on Unix
64 TODO: See if this can be made to fail the same way as on Unix
42 $ hg debugsparse --include /c/foo/bar
65 $ hg debugsparse --include /c/foo/bar
43 abort: paths cannot be absolute
66 abort: paths cannot be absolute
44 [255]
67 [255]
45 $ hg debugsparse --include '$TESTTMP/myrepo/hide'
68 $ hg debugsparse --include '$TESTTMP/myrepo/hide'
46
69
47 $ hg debugsparse --include '/c/root'
70 $ hg debugsparse --include '/c/root'
48 abort: paths cannot be absolute
71 abort: paths cannot be absolute
49 [255]
72 [255]
50 #endif
73 #endif
51
74
52 Paths should be treated as cwd-relative, not repo-root-relative
75 Paths should be treated as cwd-relative, not repo-root-relative
53 $ mkdir subdir && cd subdir
76 $ mkdir subdir && cd subdir
54 $ hg debugsparse --include path
77 $ hg debugsparse --include path
55 $ hg debugsparse
78 $ hg debugsparse
56 [include]
79 [include]
57 $TESTTMP/myrepo/hide
80 $TESTTMP/myrepo/hide
58 hide
81 hide
59 subdir/path
82 subdir/path
60
83
61 $ cd ..
84 $ cd ..
62 $ echo hello > subdir/file2.ext
85 $ echo hello > subdir/file2.ext
63 $ cd subdir
86 $ cd subdir
64 $ hg debugsparse --include '**.ext' # let us test globs
87 $ hg debugsparse --include '**.ext' # let us test globs
65 $ hg debugsparse --include 'path:abspath' # and a path: pattern
88 $ hg debugsparse --include 'path:abspath' # and a path: pattern
66 $ cd ..
89 $ cd ..
67 $ hg debugsparse
90 $ hg debugsparse
68 [include]
91 [include]
69 $TESTTMP/myrepo/hide
92 $TESTTMP/myrepo/hide
70 hide
93 hide
71 path:abspath
94 path:abspath
72 subdir/**.ext
95 subdir/**.ext
73 subdir/path
96 subdir/path
74
97
75 $ rm -rf subdir
98 $ rm -rf subdir
76
99
77 Verify commiting while sparse includes other files
100 Verify commiting while sparse includes other files
78
101
79 $ echo z > hide
102 $ echo z > hide
80 $ hg ci -Aqm 'edit hide'
103 $ hg ci -Aqm 'edit hide'
81 $ ls -A
104 $ ls -A
82 .hg
105 .hg
83 hide
106 hide
84 $ hg manifest
107 $ hg manifest
85 hide
108 hide
86 show
109 show
87
110
88 Verify --reset brings files back
111 Verify --reset brings files back
89
112
90 $ hg debugsparse --reset
113 $ hg debugsparse --reset
91 $ ls -A
114 $ ls -A
92 .hg
115 .hg
93 hide
116 hide
94 show
117 show
95 $ cat hide
118 $ cat hide
96 z
119 z
97 $ cat show
120 $ cat show
98 a
121 a
99
122
100 Verify 'hg debugsparse' default output
123 Verify 'hg debugsparse' default output
101
124
102 $ hg up -q null
125 $ hg up -q null
103 $ hg debugsparse --include 'show*'
126 $ hg debugsparse --include 'show*'
104
127
105 $ hg debugsparse
128 $ hg debugsparse
106 [include]
129 [include]
107 show*
130 show*
108
131
109 Verify update only writes included files
132 Verify update only writes included files
110
133
111 $ hg up -q 0
134 $ hg up -q 0
112 $ ls -A
135 $ ls -A
113 .hg
136 .hg
114 show
137 show
115
138
116 $ hg up -q 1
139 $ hg up -q 1
117 $ ls -A
140 $ ls -A
118 .hg
141 .hg
119 show
142 show
120 show2
143 show2
121
144
122 Verify status only shows included files
145 Verify status only shows included files
123
146
124 $ touch hide
147 $ touch hide
125 $ touch hide3
148 $ touch hide3
126 $ echo c > show
149 $ echo c > show
127 $ hg status
150 $ hg status
128 M show
151 M show
129
152
130 Adding an excluded file should fail
153 Adding an excluded file should fail
131
154
132 $ hg add hide3
155 $ hg add hide3
133 abort: cannot add 'hide3' - it is outside the sparse checkout
156 abort: cannot add 'hide3' - it is outside the sparse checkout
134 (include file with `hg debugsparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
157 (include file with `hg debugsparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
135 [255]
158 [255]
136
159
137 But adding a truly excluded file shouldn't count
160 But adding a truly excluded file shouldn't count
138
161
139 $ hg add hide3 -X hide3
162 $ hg add hide3 -X hide3
140
163
141 Verify deleting sparseness while a file has changes fails
164 Verify deleting sparseness while a file has changes fails
142
165
143 $ hg debugsparse --delete 'show*'
166 $ hg debugsparse --delete 'show*'
144 pending changes to 'hide'
167 pending changes to 'hide'
145 abort: cannot change sparseness due to pending changes (delete the files or use --force to bring them back dirty)
168 abort: cannot change sparseness due to pending changes (delete the files or use --force to bring them back dirty)
146 [255]
169 [255]
147
170
148 Verify deleting sparseness with --force brings back files
171 Verify deleting sparseness with --force brings back files
149
172
150 $ hg debugsparse -f --delete 'show*'
173 $ hg debugsparse -f --delete 'show*'
151 pending changes to 'hide'
174 pending changes to 'hide'
152 $ ls -A
175 $ ls -A
153 .hg
176 .hg
154 hide
177 hide
155 hide2
178 hide2
156 hide3
179 hide3
157 show
180 show
158 show2
181 show2
159 $ hg st
182 $ hg st
160 M hide
183 M hide
161 M show
184 M show
162 ? hide3
185 ? hide3
163
186
164 Verify editing sparseness fails if pending changes
187 Verify editing sparseness fails if pending changes
165
188
166 $ hg debugsparse --include 'show*'
189 $ hg debugsparse --include 'show*'
167 pending changes to 'hide'
190 pending changes to 'hide'
168 abort: could not update sparseness due to pending changes
191 abort: could not update sparseness due to pending changes
169 [255]
192 [255]
170
193
171 Verify adding sparseness hides files
194 Verify adding sparseness hides files
172
195
173 $ hg debugsparse -f --exclude 'hide*'
196 $ hg debugsparse -f --exclude 'hide*'
174 pending changes to 'hide'
197 pending changes to 'hide'
175 $ ls -A
198 $ ls -A
176 .hg
199 .hg
177 hide
200 hide
178 hide3
201 hide3
179 show
202 show
180 show2
203 show2
181 $ hg st
204 $ hg st
182 M show
205 M show
183
206
184 $ hg up -qC .
207 $ hg up -qC .
185 TODO: add an option to purge to also purge files outside the sparse config?
208 TODO: add an option to purge to also purge files outside the sparse config?
186 $ hg purge --all --config extensions.purge=
209 $ hg purge --all --config extensions.purge=
187 $ ls -A
210 $ ls -A
188 .hg
211 .hg
189 hide
212 hide
190 hide3
213 hide3
191 show
214 show
192 show2
215 show2
193 For now, manually remove the files
216 For now, manually remove the files
194 $ rm hide hide3
217 $ rm hide hide3
195
218
196 Verify rebase temporarily includes excluded files
219 Verify rebase temporarily includes excluded files
197
220
198 $ hg rebase -d 1 -r 2 --config extensions.rebase=
221 $ hg rebase -d 1 -r 2 --config extensions.rebase=
199 rebasing 2:b91df4f39e75 tip "edit hide"
222 rebasing 2:b91df4f39e75 tip "edit hide"
200 temporarily included 2 file(s) in the sparse checkout for merging
223 temporarily included 2 file(s) in the sparse checkout for merging
201 merging hide
224 merging hide
202 warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
225 warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
203 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
226 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
204 [240]
227 [240]
205
228
206 $ hg debugsparse
229 $ hg debugsparse
207 [exclude]
230 [exclude]
208 hide*
231 hide*
209
232
210 Temporarily Included Files (for merge/rebase):
233 Temporarily Included Files (for merge/rebase):
211 hide
234 hide
212
235
213 $ cat hide
236 $ cat hide
214 <<<<<<< dest: 39278f7c08a9 - test: two
237 <<<<<<< dest: 39278f7c08a9 - test: two
215 y
238 y
216 =======
239 =======
217 z
240 z
218 >>>>>>> source: b91df4f39e75 - test: edit hide
241 >>>>>>> source: b91df4f39e75 - test: edit hide
219
242
220 Verify aborting a rebase cleans up temporary files
243 Verify aborting a rebase cleans up temporary files
221
244
222 $ hg rebase --abort --config extensions.rebase=
245 $ hg rebase --abort --config extensions.rebase=
223 cleaned up 1 temporarily added file(s) from the sparse checkout
246 cleaned up 1 temporarily added file(s) from the sparse checkout
224 rebase aborted
247 rebase aborted
225 $ rm hide.orig
248 $ rm hide.orig
226
249
227 $ ls -A
250 $ ls -A
228 .hg
251 .hg
229 show
252 show
230 show2
253 show2
231
254
232 Verify merge fails if merging excluded files
255 Verify merge fails if merging excluded files
233
256
234 $ hg up -q 1
257 $ hg up -q 1
235 $ hg merge -r 2
258 $ hg merge -r 2
236 temporarily included 2 file(s) in the sparse checkout for merging
259 temporarily included 2 file(s) in the sparse checkout for merging
237 merging hide
260 merging hide
238 warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
261 warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
239 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
262 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
240 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
263 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
241 [1]
264 [1]
242 $ hg debugsparse
265 $ hg debugsparse
243 [exclude]
266 [exclude]
244 hide*
267 hide*
245
268
246 Temporarily Included Files (for merge/rebase):
269 Temporarily Included Files (for merge/rebase):
247 hide
270 hide
248
271
249 $ hg up -C .
272 $ hg up -C .
250 cleaned up 1 temporarily added file(s) from the sparse checkout
273 cleaned up 1 temporarily added file(s) from the sparse checkout
251 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
274 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
252 $ hg debugsparse
275 $ hg debugsparse
253 [exclude]
276 [exclude]
254 hide*
277 hide*
255
278
256
279
257 Multiple -I and -X can be passed at once
280 Multiple -I and -X can be passed at once
258
281
259 $ hg debugsparse --reset -I '*2' -X 'hide2'
282 $ hg debugsparse --reset -I '*2' -X 'hide2'
260 $ ls -A
283 $ ls -A
261 .hg
284 .hg
262 hide.orig
285 hide.orig
263 show2
286 show2
264 $ hg debugsparse --reset -X 'hide*'
287 $ hg debugsparse --reset -X 'hide*'
265
288
266 Verify strip -k resets dirstate correctly
289 Verify strip -k resets dirstate correctly
267
290
268 $ hg status
291 $ hg status
269 $ hg debugsparse
292 $ hg debugsparse
270 [exclude]
293 [exclude]
271 hide*
294 hide*
272
295
273 $ hg log -r . -T '{rev}\n' --stat
296 $ hg log -r . -T '{rev}\n' --stat
274 1
297 1
275 hide | 2 +-
298 hide | 2 +-
276 hide2 | 1 +
299 hide2 | 1 +
277 show | 2 +-
300 show | 2 +-
278 show2 | 1 +
301 show2 | 1 +
279 4 files changed, 4 insertions(+), 2 deletions(-)
302 4 files changed, 4 insertions(+), 2 deletions(-)
280
303
281 $ hg strip -r . -k
304 $ hg strip -r . -k
282 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/39278f7c08a9-ce59e002-backup.hg
305 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/39278f7c08a9-ce59e002-backup.hg
283 $ hg status
306 $ hg status
284 M show
307 M show
285 ? show2
308 ? show2
286
309
287 Verify rebase succeeds if all changed files are in sparse checkout
310 Verify rebase succeeds if all changed files are in sparse checkout
288
311
289 $ hg commit -Aqm "add show2"
312 $ hg commit -Aqm "add show2"
290 $ hg rebase -d 1 --config extensions.rebase=
313 $ hg rebase -d 1 --config extensions.rebase=
291 rebasing 2:bdde55290160 tip "add show2"
314 rebasing 2:bdde55290160 tip "add show2"
292 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/bdde55290160-216ed9c6-rebase.hg
315 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/bdde55290160-216ed9c6-rebase.hg
293
316
294 Verify log --sparse only shows commits that affect the sparse checkout
317 Verify log --sparse only shows commits that affect the sparse checkout
295
318
296 $ hg log -T '{rev} '
319 $ hg log -T '{rev} '
297 2 1 0 (no-eol)
320 2 1 0 (no-eol)
298 $ hg log --sparse -T '{rev} '
321 $ hg log --sparse -T '{rev} '
299 2 0 (no-eol)
322 2 0 (no-eol)
300
323
301 Test status on a file in a subdir
324 Test status on a file in a subdir
302
325
303 $ mkdir -p dir1/dir2
326 $ mkdir -p dir1/dir2
304 $ touch dir1/dir2/file
327 $ touch dir1/dir2/file
305 $ hg debugsparse -I dir1/dir2
328 $ hg debugsparse -I dir1/dir2
306 $ hg status
329 $ hg status
307 ? dir1/dir2/file
330 ? dir1/dir2/file
308
331
309 Mix files and subdirectories, both "glob:" and unprefixed
332 Mix files and subdirectories, both "glob:" and unprefixed
310
333
311 $ hg debugsparse --reset
334 $ hg debugsparse --reset
312 $ touch dir1/notshown
335 $ touch dir1/notshown
313 $ hg commit -A dir1/notshown -m "notshown"
336 $ hg commit -A dir1/notshown -m "notshown"
314 $ hg debugsparse --include 'dir1/dir2'
337 $ hg debugsparse --include 'dir1/dir2'
315 $ "$PYTHON" $TESTDIR/list-tree.py . | grep -E -v '\.[\/]\.hg'
338 $ "$PYTHON" $TESTDIR/list-tree.py . | grep -E -v '\.[\/]\.hg'
316 ./
339 ./
317 ./dir1/
340 ./dir1/
318 ./dir1/dir2/
341 ./dir1/dir2/
319 ./dir1/dir2/file
342 ./dir1/dir2/file
320 ./hide.orig
343 ./hide.orig
321 $ hg debugsparse --delete 'dir1/dir2'
344 $ hg debugsparse --delete 'dir1/dir2'
322 $ hg debugsparse --include 'glob:dir1/dir2'
345 $ hg debugsparse --include 'glob:dir1/dir2'
323 $ "$PYTHON" $TESTDIR/list-tree.py . | grep -E -v '\.[\/]\.hg'
346 $ "$PYTHON" $TESTDIR/list-tree.py . | grep -E -v '\.[\/]\.hg'
324 ./
347 ./
325 ./dir1/
348 ./dir1/
326 ./dir1/dir2/
349 ./dir1/dir2/
327 ./dir1/dir2/file
350 ./dir1/dir2/file
328 ./hide.orig
351 ./hide.orig
329
352
330 Test that add -s adds dirs to sparse profile
353 Test that add -s adds dirs to sparse profile
331
354
332 $ hg debugsparse --reset
355 $ hg debugsparse --reset
333 $ hg debugsparse --include empty
356 $ hg debugsparse --include empty
334 $ hg debugsparse
357 $ hg debugsparse
335 [include]
358 [include]
336 empty
359 empty
337
360
338
361
339 $ mkdir add
362 $ mkdir add
340 $ touch add/foo
363 $ touch add/foo
341 $ touch add/bar
364 $ touch add/bar
342 $ hg add add/foo
365 $ hg add add/foo
343 abort: cannot add 'add/foo' - it is outside the sparse checkout
366 abort: cannot add 'add/foo' - it is outside the sparse checkout
344 (include file with `hg debugsparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
367 (include file with `hg debugsparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
345 [255]
368 [255]
346 $ hg add -s add/foo
369 $ hg add -s add/foo
347 $ hg st
370 $ hg st
348 A add/foo
371 A add/foo
349 ? add/bar
372 ? add/bar
350 $ hg debugsparse
373 $ hg debugsparse
351 [include]
374 [include]
352 add
375 add
353 empty
376 empty
354
377
355 $ hg add -s add/*
378 $ hg add -s add/*
356 add/foo already tracked!
379 add/foo already tracked!
357 $ hg st
380 $ hg st
358 A add/bar
381 A add/bar
359 A add/foo
382 A add/foo
360 $ hg debugsparse
383 $ hg debugsparse
361 [include]
384 [include]
362 add
385 add
363 empty
386 empty
364
387
365
388
366 $ cd ..
389 $ cd ..
367
390
368 Test non-sparse repos work while sparse is loaded
391 Test non-sparse repos work while sparse is loaded
369 $ hg init sparserepo
392 $ hg init sparserepo
370 $ hg init nonsparserepo
393 $ hg init nonsparserepo
371 $ cd sparserepo
394 $ cd sparserepo
372 $ cat > .hg/hgrc <<EOF
395 $ cat > .hg/hgrc <<EOF
373 > [extensions]
396 > [extensions]
374 > sparse=
397 > sparse=
375 > EOF
398 > EOF
376 $ cd ../nonsparserepo
399 $ cd ../nonsparserepo
377 $ echo x > x && hg add x && hg commit -qAm x
400 $ echo x > x && hg add x && hg commit -qAm x
378 $ cd ../sparserepo
401 $ cd ../sparserepo
379 $ hg clone ../nonsparserepo ../nonsparserepo2
402 $ hg clone ../nonsparserepo ../nonsparserepo2
380 updating to branch default
403 updating to branch default
381 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
404 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
382
405
383 Test debugrebuilddirstate
406 Test debugrebuilddirstate
384 $ cd ../sparserepo
407 $ cd ../sparserepo
385 $ touch included
408 $ touch included
386 $ touch excluded
409 $ touch excluded
387 $ hg add included excluded
410 $ hg add included excluded
388 $ hg commit -m 'a commit' -q
411 $ hg commit -m 'a commit' -q
389 $ cp .hg/dirstate ../dirstateboth
412 $ cp .hg/dirstate ../dirstateboth
390 $ hg debugsparse -X excluded
413 $ hg debugsparse -X excluded
391 $ cp ../dirstateboth .hg/dirstate
414 $ cp ../dirstateboth .hg/dirstate
392 $ hg debugrebuilddirstate
415 $ hg debugrebuilddirstate
393 $ hg debugdirstate
416 $ hg debugdirstate
394 n 0 -1 unset included
417 n 0 -1 unset included
395
418
396 Test debugdirstate --minimal where file is in the parent manifest but not the
419 Test debugdirstate --minimal where file is in the parent manifest but not the
397 dirstate
420 dirstate
398 $ hg debugsparse -X included
421 $ hg debugsparse -X included
399 $ hg debugdirstate
422 $ hg debugdirstate
400 $ cp .hg/dirstate ../dirstateallexcluded
423 $ cp .hg/dirstate ../dirstateallexcluded
401 $ hg debugsparse --reset
424 $ hg debugsparse --reset
402 $ hg debugsparse -X excluded
425 $ hg debugsparse -X excluded
403 $ cp ../dirstateallexcluded .hg/dirstate
426 $ cp ../dirstateallexcluded .hg/dirstate
404 $ touch includedadded
427 $ touch includedadded
405 $ hg add includedadded
428 $ hg add includedadded
406 $ hg debugdirstate --no-dates
429 $ hg debugdirstate --no-dates
407 a 0 -1 unset includedadded
430 a 0 -1 unset includedadded
408 $ hg debugrebuilddirstate --minimal
431 $ hg debugrebuilddirstate --minimal
409 $ hg debugdirstate --no-dates
432 $ hg debugdirstate --no-dates
410 n 0 -1 unset included
433 n 0 -1 unset included
411 a 0 -1 * includedadded (glob)
434 a 0 -1 * includedadded (glob)
412
435
413 Test debugdirstate --minimal where a file is not in parent manifest
436 Test debugdirstate --minimal where a file is not in parent manifest
414 but in the dirstate. This should take into account excluded files in the
437 but in the dirstate. This should take into account excluded files in the
415 manifest
438 manifest
416 $ cp ../dirstateboth .hg/dirstate
439 $ cp ../dirstateboth .hg/dirstate
417 $ touch includedadded
440 $ touch includedadded
418 $ hg add includedadded
441 $ hg add includedadded
419 $ touch excludednomanifest
442 $ touch excludednomanifest
420 $ hg add excludednomanifest
443 $ hg add excludednomanifest
421 $ cp .hg/dirstate ../moreexcluded
444 $ cp .hg/dirstate ../moreexcluded
422 $ hg forget excludednomanifest
445 $ hg forget excludednomanifest
423 $ rm excludednomanifest
446 $ rm excludednomanifest
424 $ hg debugsparse -X excludednomanifest
447 $ hg debugsparse -X excludednomanifest
425 $ cp ../moreexcluded .hg/dirstate
448 $ cp ../moreexcluded .hg/dirstate
426 $ hg manifest
449 $ hg manifest
427 excluded
450 excluded
428 included
451 included
429 We have files in the dirstate that are included and excluded. Some are in the
452 We have files in the dirstate that are included and excluded. Some are in the
430 manifest and some are not.
453 manifest and some are not.
431 $ hg debugdirstate --no-dates
454 $ hg debugdirstate --no-dates
432 n * excluded (glob)
455 n * excluded (glob)
433 a * excludednomanifest (glob)
456 a * excludednomanifest (glob)
434 n * included (glob)
457 n * included (glob)
435 a * includedadded (glob)
458 a * includedadded (glob)
436 $ hg debugrebuilddirstate --minimal
459 $ hg debugrebuilddirstate --minimal
437 $ hg debugdirstate --no-dates
460 $ hg debugdirstate --no-dates
438 n * included (glob)
461 n * included (glob)
439 a * includedadded (glob)
462 a * includedadded (glob)
440
463
General Comments 0
You need to be logged in to leave comments. Login now