##// END OF EJS Templates
rhg: Fall back to Python if verbose status is requested by config...
Simon Sapin -
r49344:47f2a82a default
parent child Browse files
Show More
@@ -1,527 +1,538 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 use crate::error::CommandError;
8 use crate::error::CommandError;
9 use crate::ui::Ui;
9 use crate::ui::Ui;
10 use crate::utils::path_utils::RelativizePaths;
10 use crate::utils::path_utils::RelativizePaths;
11 use clap::{Arg, SubCommand};
11 use clap::{Arg, SubCommand};
12 use format_bytes::format_bytes;
12 use format_bytes::format_bytes;
13 use hg;
13 use hg;
14 use hg::config::Config;
14 use hg::config::Config;
15 use hg::dirstate::has_exec_bit;
15 use hg::dirstate::has_exec_bit;
16 use hg::dirstate::status::StatusPath;
16 use hg::dirstate::status::StatusPath;
17 use hg::dirstate::TruncatedTimestamp;
17 use hg::dirstate::TruncatedTimestamp;
18 use hg::dirstate::RANGE_MASK_31BIT;
18 use hg::dirstate::RANGE_MASK_31BIT;
19 use hg::errors::{HgError, IoResultExt};
19 use hg::errors::{HgError, IoResultExt};
20 use hg::lock::LockError;
20 use hg::lock::LockError;
21 use hg::manifest::Manifest;
21 use hg::manifest::Manifest;
22 use hg::matchers::AlwaysMatcher;
22 use hg::matchers::AlwaysMatcher;
23 use hg::repo::Repo;
23 use hg::repo::Repo;
24 use hg::utils::files::get_bytes_from_os_string;
24 use hg::utils::files::get_bytes_from_os_string;
25 use hg::utils::files::get_bytes_from_path;
25 use hg::utils::files::get_bytes_from_path;
26 use hg::utils::files::get_path_from_bytes;
26 use hg::utils::files::get_path_from_bytes;
27 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
27 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
28 use hg::StatusOptions;
28 use hg::StatusOptions;
29 use log::info;
29 use log::info;
30 use std::io;
30 use std::io;
31 use std::path::PathBuf;
31 use std::path::PathBuf;
32
32
33 pub const HELP_TEXT: &str = "
33 pub const HELP_TEXT: &str = "
34 Show changed files in the working directory
34 Show changed files in the working directory
35
35
36 This is a pure Rust version of `hg status`.
36 This is a pure Rust version of `hg status`.
37
37
38 Some options might be missing, check the list below.
38 Some options might be missing, check the list below.
39 ";
39 ";
40
40
41 pub fn args() -> clap::App<'static, 'static> {
41 pub fn args() -> clap::App<'static, 'static> {
42 SubCommand::with_name("status")
42 SubCommand::with_name("status")
43 .alias("st")
43 .alias("st")
44 .about(HELP_TEXT)
44 .about(HELP_TEXT)
45 .arg(
45 .arg(
46 Arg::with_name("all")
46 Arg::with_name("all")
47 .help("show status of all files")
47 .help("show status of all files")
48 .short("-A")
48 .short("-A")
49 .long("--all"),
49 .long("--all"),
50 )
50 )
51 .arg(
51 .arg(
52 Arg::with_name("modified")
52 Arg::with_name("modified")
53 .help("show only modified files")
53 .help("show only modified files")
54 .short("-m")
54 .short("-m")
55 .long("--modified"),
55 .long("--modified"),
56 )
56 )
57 .arg(
57 .arg(
58 Arg::with_name("added")
58 Arg::with_name("added")
59 .help("show only added files")
59 .help("show only added files")
60 .short("-a")
60 .short("-a")
61 .long("--added"),
61 .long("--added"),
62 )
62 )
63 .arg(
63 .arg(
64 Arg::with_name("removed")
64 Arg::with_name("removed")
65 .help("show only removed files")
65 .help("show only removed files")
66 .short("-r")
66 .short("-r")
67 .long("--removed"),
67 .long("--removed"),
68 )
68 )
69 .arg(
69 .arg(
70 Arg::with_name("clean")
70 Arg::with_name("clean")
71 .help("show only clean files")
71 .help("show only clean files")
72 .short("-c")
72 .short("-c")
73 .long("--clean"),
73 .long("--clean"),
74 )
74 )
75 .arg(
75 .arg(
76 Arg::with_name("deleted")
76 Arg::with_name("deleted")
77 .help("show only deleted files")
77 .help("show only deleted files")
78 .short("-d")
78 .short("-d")
79 .long("--deleted"),
79 .long("--deleted"),
80 )
80 )
81 .arg(
81 .arg(
82 Arg::with_name("unknown")
82 Arg::with_name("unknown")
83 .help("show only unknown (not tracked) files")
83 .help("show only unknown (not tracked) files")
84 .short("-u")
84 .short("-u")
85 .long("--unknown"),
85 .long("--unknown"),
86 )
86 )
87 .arg(
87 .arg(
88 Arg::with_name("ignored")
88 Arg::with_name("ignored")
89 .help("show only ignored files")
89 .help("show only ignored files")
90 .short("-i")
90 .short("-i")
91 .long("--ignored"),
91 .long("--ignored"),
92 )
92 )
93 .arg(
93 .arg(
94 Arg::with_name("copies")
94 Arg::with_name("copies")
95 .help("show source of copied files (DEFAULT: ui.statuscopies)")
95 .help("show source of copied files (DEFAULT: ui.statuscopies)")
96 .short("-C")
96 .short("-C")
97 .long("--copies"),
97 .long("--copies"),
98 )
98 )
99 .arg(
99 .arg(
100 Arg::with_name("no-status")
100 Arg::with_name("no-status")
101 .help("hide status prefix")
101 .help("hide status prefix")
102 .short("-n")
102 .short("-n")
103 .long("--no-status"),
103 .long("--no-status"),
104 )
104 )
105 }
105 }
106
106
107 /// Pure data type allowing the caller to specify file states to display
107 /// Pure data type allowing the caller to specify file states to display
108 #[derive(Copy, Clone, Debug)]
108 #[derive(Copy, Clone, Debug)]
109 pub struct DisplayStates {
109 pub struct DisplayStates {
110 pub modified: bool,
110 pub modified: bool,
111 pub added: bool,
111 pub added: bool,
112 pub removed: bool,
112 pub removed: bool,
113 pub clean: bool,
113 pub clean: bool,
114 pub deleted: bool,
114 pub deleted: bool,
115 pub unknown: bool,
115 pub unknown: bool,
116 pub ignored: bool,
116 pub ignored: bool,
117 }
117 }
118
118
119 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
119 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
120 modified: true,
120 modified: true,
121 added: true,
121 added: true,
122 removed: true,
122 removed: true,
123 clean: false,
123 clean: false,
124 deleted: true,
124 deleted: true,
125 unknown: true,
125 unknown: true,
126 ignored: false,
126 ignored: false,
127 };
127 };
128
128
129 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
129 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
130 modified: true,
130 modified: true,
131 added: true,
131 added: true,
132 removed: true,
132 removed: true,
133 clean: true,
133 clean: true,
134 deleted: true,
134 deleted: true,
135 unknown: true,
135 unknown: true,
136 ignored: true,
136 ignored: true,
137 };
137 };
138
138
139 impl DisplayStates {
139 impl DisplayStates {
140 pub fn is_empty(&self) -> bool {
140 pub fn is_empty(&self) -> bool {
141 !(self.modified
141 !(self.modified
142 || self.added
142 || self.added
143 || self.removed
143 || self.removed
144 || self.clean
144 || self.clean
145 || self.deleted
145 || self.deleted
146 || self.unknown
146 || self.unknown
147 || self.ignored)
147 || self.ignored)
148 }
148 }
149 }
149 }
150
150
151 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
151 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
152 let status_enabled_default = false;
152 let status_enabled_default = false;
153 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
153 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
154 if !status_enabled.unwrap_or(status_enabled_default) {
154 if !status_enabled.unwrap_or(status_enabled_default) {
155 return Err(CommandError::unsupported(
155 return Err(CommandError::unsupported(
156 "status is experimental in rhg (enable it with 'rhg.status = true' \
156 "status is experimental in rhg (enable it with 'rhg.status = true' \
157 or enable fallback with 'rhg.on-unsupported = fallback')"
157 or enable fallback with 'rhg.on-unsupported = fallback')"
158 ));
158 ));
159 }
159 }
160
160
161 // TODO: lift these limitations
161 // TODO: lift these limitations
162 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
162 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
163 return Err(CommandError::unsupported(
163 return Err(CommandError::unsupported(
164 "ui.tweakdefaults is not yet supported with rhg status",
164 "ui.tweakdefaults is not yet supported with rhg status",
165 ));
165 ));
166 }
166 }
167 if invocation.config.get_bool(b"ui", b"statuscopies")? {
167 if invocation.config.get_bool(b"ui", b"statuscopies")? {
168 return Err(CommandError::unsupported(
168 return Err(CommandError::unsupported(
169 "ui.statuscopies is not yet supported with rhg status",
169 "ui.statuscopies is not yet supported with rhg status",
170 ));
170 ));
171 }
171 }
172 if invocation
172 if invocation
173 .config
173 .config
174 .get(b"commands", b"status.terse")
174 .get(b"commands", b"status.terse")
175 .is_some()
175 .is_some()
176 {
176 {
177 return Err(CommandError::unsupported(
177 return Err(CommandError::unsupported(
178 "status.terse is not yet supported with rhg status",
178 "status.terse is not yet supported with rhg status",
179 ));
179 ));
180 }
180 }
181
181
182 let ui = invocation.ui;
182 let ui = invocation.ui;
183 let config = invocation.config;
183 let config = invocation.config;
184 let args = invocation.subcommand_args;
184 let args = invocation.subcommand_args;
185
186 let verbose = !ui.plain()
187 && !args.is_present("print0")
188 && (config.get_bool(b"ui", b"verbose")?
189 || config.get_bool(b"commands", b"status.verbose")?);
190 if verbose {
191 return Err(CommandError::unsupported(
192 "verbose status is not supported yet",
193 ));
194 }
195
185 let all = args.is_present("all");
196 let all = args.is_present("all");
186 let display_states = if all {
197 let display_states = if all {
187 // TODO when implementing `--quiet`: it excludes clean files
198 // TODO when implementing `--quiet`: it excludes clean files
188 // from `--all`
199 // from `--all`
189 ALL_DISPLAY_STATES
200 ALL_DISPLAY_STATES
190 } else {
201 } else {
191 let requested = DisplayStates {
202 let requested = DisplayStates {
192 modified: args.is_present("modified"),
203 modified: args.is_present("modified"),
193 added: args.is_present("added"),
204 added: args.is_present("added"),
194 removed: args.is_present("removed"),
205 removed: args.is_present("removed"),
195 clean: args.is_present("clean"),
206 clean: args.is_present("clean"),
196 deleted: args.is_present("deleted"),
207 deleted: args.is_present("deleted"),
197 unknown: args.is_present("unknown"),
208 unknown: args.is_present("unknown"),
198 ignored: args.is_present("ignored"),
209 ignored: args.is_present("ignored"),
199 };
210 };
200 if requested.is_empty() {
211 if requested.is_empty() {
201 DEFAULT_DISPLAY_STATES
212 DEFAULT_DISPLAY_STATES
202 } else {
213 } else {
203 requested
214 requested
204 }
215 }
205 };
216 };
206 let no_status = args.is_present("no-status");
217 let no_status = args.is_present("no-status");
207 let list_copies = all
218 let list_copies = all
208 || args.is_present("copies")
219 || args.is_present("copies")
209 || config.get_bool(b"ui", b"statuscopies")?;
220 || config.get_bool(b"ui", b"statuscopies")?;
210
221
211 let repo = invocation.repo?;
222 let repo = invocation.repo?;
212
223
213 if repo.has_sparse() || repo.has_narrow() {
224 if repo.has_sparse() || repo.has_narrow() {
214 return Err(CommandError::unsupported(
225 return Err(CommandError::unsupported(
215 "rhg status is not supported for sparse checkouts or narrow clones yet"
226 "rhg status is not supported for sparse checkouts or narrow clones yet"
216 ));
227 ));
217 }
228 }
218
229
219 let mut dmap = repo.dirstate_map_mut()?;
230 let mut dmap = repo.dirstate_map_mut()?;
220
231
221 let options = StatusOptions {
232 let options = StatusOptions {
222 // we're currently supporting file systems with exec flags only
233 // we're currently supporting file systems with exec flags only
223 // anyway
234 // anyway
224 check_exec: true,
235 check_exec: true,
225 list_clean: display_states.clean,
236 list_clean: display_states.clean,
226 list_unknown: display_states.unknown,
237 list_unknown: display_states.unknown,
227 list_ignored: display_states.ignored,
238 list_ignored: display_states.ignored,
228 list_copies,
239 list_copies,
229 collect_traversed_dirs: false,
240 collect_traversed_dirs: false,
230 };
241 };
231 let (mut ds_status, pattern_warnings) = dmap.status(
242 let (mut ds_status, pattern_warnings) = dmap.status(
232 &AlwaysMatcher,
243 &AlwaysMatcher,
233 repo.working_directory_path().to_owned(),
244 repo.working_directory_path().to_owned(),
234 ignore_files(repo, config),
245 ignore_files(repo, config),
235 options,
246 options,
236 )?;
247 )?;
237 for warning in pattern_warnings {
248 for warning in pattern_warnings {
238 match warning {
249 match warning {
239 hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui
250 hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui
240 .write_stderr(&format_bytes!(
251 .write_stderr(&format_bytes!(
241 b"{}: ignoring invalid syntax '{}'\n",
252 b"{}: ignoring invalid syntax '{}'\n",
242 get_bytes_from_path(path),
253 get_bytes_from_path(path),
243 &*syntax
254 &*syntax
244 ))?,
255 ))?,
245 hg::PatternFileWarning::NoSuchFile(path) => {
256 hg::PatternFileWarning::NoSuchFile(path) => {
246 let path = if let Ok(relative) =
257 let path = if let Ok(relative) =
247 path.strip_prefix(repo.working_directory_path())
258 path.strip_prefix(repo.working_directory_path())
248 {
259 {
249 relative
260 relative
250 } else {
261 } else {
251 &*path
262 &*path
252 };
263 };
253 ui.write_stderr(&format_bytes!(
264 ui.write_stderr(&format_bytes!(
254 b"skipping unreadable pattern file '{}': \
265 b"skipping unreadable pattern file '{}': \
255 No such file or directory\n",
266 No such file or directory\n",
256 get_bytes_from_path(path),
267 get_bytes_from_path(path),
257 ))?
268 ))?
258 }
269 }
259 }
270 }
260 }
271 }
261
272
262 for (path, error) in ds_status.bad {
273 for (path, error) in ds_status.bad {
263 let error = match error {
274 let error = match error {
264 hg::BadMatch::OsError(code) => {
275 hg::BadMatch::OsError(code) => {
265 std::io::Error::from_raw_os_error(code).to_string()
276 std::io::Error::from_raw_os_error(code).to_string()
266 }
277 }
267 hg::BadMatch::BadType(ty) => {
278 hg::BadMatch::BadType(ty) => {
268 format!("unsupported file type (type is {})", ty)
279 format!("unsupported file type (type is {})", ty)
269 }
280 }
270 };
281 };
271 ui.write_stderr(&format_bytes!(
282 ui.write_stderr(&format_bytes!(
272 b"{}: {}\n",
283 b"{}: {}\n",
273 path.as_bytes(),
284 path.as_bytes(),
274 error.as_bytes()
285 error.as_bytes()
275 ))?
286 ))?
276 }
287 }
277 if !ds_status.unsure.is_empty() {
288 if !ds_status.unsure.is_empty() {
278 info!(
289 info!(
279 "Files to be rechecked by retrieval from filelog: {:?}",
290 "Files to be rechecked by retrieval from filelog: {:?}",
280 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
291 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
281 );
292 );
282 }
293 }
283 let mut fixup = Vec::new();
294 let mut fixup = Vec::new();
284 if !ds_status.unsure.is_empty()
295 if !ds_status.unsure.is_empty()
285 && (display_states.modified || display_states.clean)
296 && (display_states.modified || display_states.clean)
286 {
297 {
287 let p1 = repo.dirstate_parents()?.p1;
298 let p1 = repo.dirstate_parents()?.p1;
288 let manifest = repo.manifest_for_node(p1).map_err(|e| {
299 let manifest = repo.manifest_for_node(p1).map_err(|e| {
289 CommandError::from((e, &*format!("{:x}", p1.short())))
300 CommandError::from((e, &*format!("{:x}", p1.short())))
290 })?;
301 })?;
291 for to_check in ds_status.unsure {
302 for to_check in ds_status.unsure {
292 if unsure_is_modified(repo, &manifest, &to_check.path)? {
303 if unsure_is_modified(repo, &manifest, &to_check.path)? {
293 if display_states.modified {
304 if display_states.modified {
294 ds_status.modified.push(to_check);
305 ds_status.modified.push(to_check);
295 }
306 }
296 } else {
307 } else {
297 if display_states.clean {
308 if display_states.clean {
298 ds_status.clean.push(to_check.clone());
309 ds_status.clean.push(to_check.clone());
299 }
310 }
300 fixup.push(to_check.path.into_owned())
311 fixup.push(to_check.path.into_owned())
301 }
312 }
302 }
313 }
303 }
314 }
304 let relative_paths = (!ui.plain())
315 let relative_paths = (!ui.plain())
305 && config
316 && config
306 .get_option(b"commands", b"status.relative")?
317 .get_option(b"commands", b"status.relative")?
307 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
318 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
308 let output = DisplayStatusPaths {
319 let output = DisplayStatusPaths {
309 ui,
320 ui,
310 no_status,
321 no_status,
311 relativize: if relative_paths {
322 relativize: if relative_paths {
312 Some(RelativizePaths::new(repo)?)
323 Some(RelativizePaths::new(repo)?)
313 } else {
324 } else {
314 None
325 None
315 },
326 },
316 };
327 };
317 if display_states.modified {
328 if display_states.modified {
318 output.display(b"M", ds_status.modified)?;
329 output.display(b"M", ds_status.modified)?;
319 }
330 }
320 if display_states.added {
331 if display_states.added {
321 output.display(b"A", ds_status.added)?;
332 output.display(b"A", ds_status.added)?;
322 }
333 }
323 if display_states.removed {
334 if display_states.removed {
324 output.display(b"R", ds_status.removed)?;
335 output.display(b"R", ds_status.removed)?;
325 }
336 }
326 if display_states.deleted {
337 if display_states.deleted {
327 output.display(b"!", ds_status.deleted)?;
338 output.display(b"!", ds_status.deleted)?;
328 }
339 }
329 if display_states.unknown {
340 if display_states.unknown {
330 output.display(b"?", ds_status.unknown)?;
341 output.display(b"?", ds_status.unknown)?;
331 }
342 }
332 if display_states.ignored {
343 if display_states.ignored {
333 output.display(b"I", ds_status.ignored)?;
344 output.display(b"I", ds_status.ignored)?;
334 }
345 }
335 if display_states.clean {
346 if display_states.clean {
336 output.display(b"C", ds_status.clean)?;
347 output.display(b"C", ds_status.clean)?;
337 }
348 }
338
349
339 let mut dirstate_write_needed = ds_status.dirty;
350 let mut dirstate_write_needed = ds_status.dirty;
340 let filesystem_time_at_status_start =
351 let filesystem_time_at_status_start =
341 ds_status.filesystem_time_at_status_start;
352 ds_status.filesystem_time_at_status_start;
342
353
343 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
354 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
344 && !dirstate_write_needed
355 && !dirstate_write_needed
345 {
356 {
346 // Nothing to update
357 // Nothing to update
347 return Ok(());
358 return Ok(());
348 }
359 }
349
360
350 // Update the dirstate on disk if we can
361 // Update the dirstate on disk if we can
351 let with_lock_result =
362 let with_lock_result =
352 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
363 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
353 if let Some(mtime_boundary) = filesystem_time_at_status_start {
364 if let Some(mtime_boundary) = filesystem_time_at_status_start {
354 for hg_path in fixup {
365 for hg_path in fixup {
355 use std::os::unix::fs::MetadataExt;
366 use std::os::unix::fs::MetadataExt;
356 let fs_path = hg_path_to_path_buf(&hg_path)
367 let fs_path = hg_path_to_path_buf(&hg_path)
357 .expect("HgPath conversion");
368 .expect("HgPath conversion");
358 // Specifically do not reuse `fs_metadata` from
369 // Specifically do not reuse `fs_metadata` from
359 // `unsure_is_clean` which was needed before reading
370 // `unsure_is_clean` which was needed before reading
360 // contents. Here we access metadata again after reading
371 // contents. Here we access metadata again after reading
361 // content, in case it changed in the meantime.
372 // content, in case it changed in the meantime.
362 let fs_metadata = repo
373 let fs_metadata = repo
363 .working_directory_vfs()
374 .working_directory_vfs()
364 .symlink_metadata(&fs_path)?;
375 .symlink_metadata(&fs_path)?;
365 if let Some(mtime) =
376 if let Some(mtime) =
366 TruncatedTimestamp::for_reliable_mtime_of(
377 TruncatedTimestamp::for_reliable_mtime_of(
367 &fs_metadata,
378 &fs_metadata,
368 &mtime_boundary,
379 &mtime_boundary,
369 )
380 )
370 .when_reading_file(&fs_path)?
381 .when_reading_file(&fs_path)?
371 {
382 {
372 let mode = fs_metadata.mode();
383 let mode = fs_metadata.mode();
373 let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT;
384 let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT;
374 let mut entry = dmap
385 let mut entry = dmap
375 .get(&hg_path)?
386 .get(&hg_path)?
376 .expect("ambiguous file not in dirstate");
387 .expect("ambiguous file not in dirstate");
377 entry.set_clean(mode, size, mtime);
388 entry.set_clean(mode, size, mtime);
378 dmap.add_file(&hg_path, entry)?;
389 dmap.add_file(&hg_path, entry)?;
379 dirstate_write_needed = true
390 dirstate_write_needed = true
380 }
391 }
381 }
392 }
382 }
393 }
383 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
394 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
384 if dirstate_write_needed {
395 if dirstate_write_needed {
385 repo.write_dirstate()?
396 repo.write_dirstate()?
386 }
397 }
387 Ok(())
398 Ok(())
388 });
399 });
389 match with_lock_result {
400 match with_lock_result {
390 Ok(closure_result) => closure_result?,
401 Ok(closure_result) => closure_result?,
391 Err(LockError::AlreadyHeld) => {
402 Err(LockError::AlreadyHeld) => {
392 // Not updating the dirstate is not ideal but not critical:
403 // Not updating the dirstate is not ideal but not critical:
393 // don’t keep our caller waiting until some other Mercurial
404 // don’t keep our caller waiting until some other Mercurial
394 // process releases the lock.
405 // process releases the lock.
395 }
406 }
396 Err(LockError::Other(HgError::IoError { error, .. }))
407 Err(LockError::Other(HgError::IoError { error, .. }))
397 if error.kind() == io::ErrorKind::PermissionDenied =>
408 if error.kind() == io::ErrorKind::PermissionDenied =>
398 {
409 {
399 // `hg status` on a read-only repository is fine
410 // `hg status` on a read-only repository is fine
400 }
411 }
401 Err(LockError::Other(error)) => {
412 Err(LockError::Other(error)) => {
402 // Report other I/O errors
413 // Report other I/O errors
403 Err(error)?
414 Err(error)?
404 }
415 }
405 }
416 }
406 Ok(())
417 Ok(())
407 }
418 }
408
419
409 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
420 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
410 let mut ignore_files = Vec::new();
421 let mut ignore_files = Vec::new();
411 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
422 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
412 if repo_ignore.exists() {
423 if repo_ignore.exists() {
413 ignore_files.push(repo_ignore)
424 ignore_files.push(repo_ignore)
414 }
425 }
415 for (key, value) in config.iter_section(b"ui") {
426 for (key, value) in config.iter_section(b"ui") {
416 if key == b"ignore" || key.starts_with(b"ignore.") {
427 if key == b"ignore" || key.starts_with(b"ignore.") {
417 let path = get_path_from_bytes(value);
428 let path = get_path_from_bytes(value);
418 // TODO:Β expand "~/" and environment variable here, like Python
429 // TODO:Β expand "~/" and environment variable here, like Python
419 // does with `os.path.expanduser` and `os.path.expandvars`
430 // does with `os.path.expanduser` and `os.path.expandvars`
420
431
421 let joined = repo.working_directory_path().join(path);
432 let joined = repo.working_directory_path().join(path);
422 ignore_files.push(joined);
433 ignore_files.push(joined);
423 }
434 }
424 }
435 }
425 ignore_files
436 ignore_files
426 }
437 }
427
438
428 struct DisplayStatusPaths<'a> {
439 struct DisplayStatusPaths<'a> {
429 ui: &'a Ui,
440 ui: &'a Ui,
430 no_status: bool,
441 no_status: bool,
431 relativize: Option<RelativizePaths>,
442 relativize: Option<RelativizePaths>,
432 }
443 }
433
444
434 impl DisplayStatusPaths<'_> {
445 impl DisplayStatusPaths<'_> {
435 // Probably more elegant to use a Deref or Borrow trait rather than
446 // Probably more elegant to use a Deref or Borrow trait rather than
436 // harcode HgPathBuf, but probably not really useful at this point
447 // harcode HgPathBuf, but probably not really useful at this point
437 fn display(
448 fn display(
438 &self,
449 &self,
439 status_prefix: &[u8],
450 status_prefix: &[u8],
440 mut paths: Vec<StatusPath<'_>>,
451 mut paths: Vec<StatusPath<'_>>,
441 ) -> Result<(), CommandError> {
452 ) -> Result<(), CommandError> {
442 paths.sort_unstable();
453 paths.sort_unstable();
443 for StatusPath { path, copy_source } in paths {
454 for StatusPath { path, copy_source } in paths {
444 let relative;
455 let relative;
445 let path = if let Some(relativize) = &self.relativize {
456 let path = if let Some(relativize) = &self.relativize {
446 relative = relativize.relativize(&path);
457 relative = relativize.relativize(&path);
447 &*relative
458 &*relative
448 } else {
459 } else {
449 path.as_bytes()
460 path.as_bytes()
450 };
461 };
451 // TODO optim, probably lots of unneeded copies here, especially
462 // TODO optim, probably lots of unneeded copies here, especially
452 // if out stream is buffered
463 // if out stream is buffered
453 if self.no_status {
464 if self.no_status {
454 self.ui.write_stdout(&format_bytes!(b"{}\n", path))?
465 self.ui.write_stdout(&format_bytes!(b"{}\n", path))?
455 } else {
466 } else {
456 self.ui.write_stdout(&format_bytes!(
467 self.ui.write_stdout(&format_bytes!(
457 b"{} {}\n",
468 b"{} {}\n",
458 status_prefix,
469 status_prefix,
459 path
470 path
460 ))?
471 ))?
461 }
472 }
462 if let Some(source) = copy_source {
473 if let Some(source) = copy_source {
463 self.ui.write_stdout(&format_bytes!(
474 self.ui.write_stdout(&format_bytes!(
464 b" {}\n",
475 b" {}\n",
465 source.as_bytes()
476 source.as_bytes()
466 ))?
477 ))?
467 }
478 }
468 }
479 }
469 Ok(())
480 Ok(())
470 }
481 }
471 }
482 }
472
483
473 /// Check if a file is modified by comparing actual repo store and file system.
484 /// Check if a file is modified by comparing actual repo store and file system.
474 ///
485 ///
475 /// This meant to be used for those that the dirstate cannot resolve, due
486 /// This meant to be used for those that the dirstate cannot resolve, due
476 /// to time resolution limits.
487 /// to time resolution limits.
477 fn unsure_is_modified(
488 fn unsure_is_modified(
478 repo: &Repo,
489 repo: &Repo,
479 manifest: &Manifest,
490 manifest: &Manifest,
480 hg_path: &HgPath,
491 hg_path: &HgPath,
481 ) -> Result<bool, HgError> {
492 ) -> Result<bool, HgError> {
482 let vfs = repo.working_directory_vfs();
493 let vfs = repo.working_directory_vfs();
483 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
494 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
484 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
495 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
485 let is_symlink = fs_metadata.file_type().is_symlink();
496 let is_symlink = fs_metadata.file_type().is_symlink();
486 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
497 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
487 // dirstate
498 // dirstate
488 let fs_flags = if is_symlink {
499 let fs_flags = if is_symlink {
489 Some(b'l')
500 Some(b'l')
490 } else if has_exec_bit(&fs_metadata) {
501 } else if has_exec_bit(&fs_metadata) {
491 Some(b'x')
502 Some(b'x')
492 } else {
503 } else {
493 None
504 None
494 };
505 };
495
506
496 let entry = manifest
507 let entry = manifest
497 .find_by_path(hg_path)?
508 .find_by_path(hg_path)?
498 .expect("ambgious file not in p1");
509 .expect("ambgious file not in p1");
499 if entry.flags != fs_flags {
510 if entry.flags != fs_flags {
500 return Ok(true);
511 return Ok(true);
501 }
512 }
502 let filelog = repo.filelog(hg_path)?;
513 let filelog = repo.filelog(hg_path)?;
503 let fs_len = fs_metadata.len();
514 let fs_len = fs_metadata.len();
504 // TODO: check `fs_len` here like below, but based on
515 // TODO: check `fs_len` here like below, but based on
505 // `RevlogEntry::uncompressed_len` without decompressing the full filelog
516 // `RevlogEntry::uncompressed_len` without decompressing the full filelog
506 // contents where possible. This is only valid if the revlog data does not
517 // contents where possible. This is only valid if the revlog data does not
507 // contain metadata. See how Python’s `revlog.rawsize` calls
518 // contain metadata. See how Python’s `revlog.rawsize` calls
508 // `storageutil.filerevisioncopied`.
519 // `storageutil.filerevisioncopied`.
509 // (Maybe also check for content-modifying flags? See `revlog.size`.)
520 // (Maybe also check for content-modifying flags? See `revlog.size`.)
510 let filelog_entry =
521 let filelog_entry =
511 filelog.data_for_node(entry.node_id()?).map_err(|_| {
522 filelog.data_for_node(entry.node_id()?).map_err(|_| {
512 HgError::corrupted("filelog missing node from manifest")
523 HgError::corrupted("filelog missing node from manifest")
513 })?;
524 })?;
514 let contents_in_p1 = filelog_entry.data()?;
525 let contents_in_p1 = filelog_entry.data()?;
515 if contents_in_p1.len() as u64 != fs_len {
526 if contents_in_p1.len() as u64 != fs_len {
516 // No need to read the file contents:
527 // No need to read the file contents:
517 // it cannot be equal if it has a different length.
528 // it cannot be equal if it has a different length.
518 return Ok(true);
529 return Ok(true);
519 }
530 }
520
531
521 let fs_contents = if is_symlink {
532 let fs_contents = if is_symlink {
522 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
533 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
523 } else {
534 } else {
524 vfs.read(fs_path)?
535 vfs.read(fs_path)?
525 };
536 };
526 Ok(contents_in_p1 != &*fs_contents)
537 Ok(contents_in_p1 != &*fs_contents)
527 }
538 }
@@ -1,132 +1,132 b''
1 use format_bytes::format_bytes;
1 use format_bytes::format_bytes;
2 use std::borrow::Cow;
2 use std::borrow::Cow;
3 use std::env;
3 use std::env;
4 use std::io;
4 use std::io;
5 use std::io::{ErrorKind, Write};
5 use std::io::{ErrorKind, Write};
6
6
7 #[derive(Debug)]
7 #[derive(Debug)]
8 pub struct Ui {
8 pub struct Ui {
9 stdout: std::io::Stdout,
9 stdout: std::io::Stdout,
10 stderr: std::io::Stderr,
10 stderr: std::io::Stderr,
11 }
11 }
12
12
13 /// The kind of user interface error
13 /// The kind of user interface error
14 pub enum UiError {
14 pub enum UiError {
15 /// The standard output stream cannot be written to
15 /// The standard output stream cannot be written to
16 StdoutError(io::Error),
16 StdoutError(io::Error),
17 /// The standard error stream cannot be written to
17 /// The standard error stream cannot be written to
18 StderrError(io::Error),
18 StderrError(io::Error),
19 }
19 }
20
20
21 /// The commandline user interface
21 /// The commandline user interface
22 impl Ui {
22 impl Ui {
23 pub fn new() -> Self {
23 pub fn new() -> Self {
24 Ui {
24 Ui {
25 stdout: std::io::stdout(),
25 stdout: std::io::stdout(),
26 stderr: std::io::stderr(),
26 stderr: std::io::stderr(),
27 }
27 }
28 }
28 }
29
29
30 /// Returns a buffered handle on stdout for faster batch printing
30 /// Returns a buffered handle on stdout for faster batch printing
31 /// operations.
31 /// operations.
32 pub fn stdout_buffer(&self) -> StdoutBuffer<std::io::StdoutLock> {
32 pub fn stdout_buffer(&self) -> StdoutBuffer<std::io::StdoutLock> {
33 StdoutBuffer::new(self.stdout.lock())
33 StdoutBuffer::new(self.stdout.lock())
34 }
34 }
35
35
36 /// Write bytes to stdout
36 /// Write bytes to stdout
37 pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> {
37 pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> {
38 let mut stdout = self.stdout.lock();
38 let mut stdout = self.stdout.lock();
39
39
40 stdout.write_all(bytes).or_else(handle_stdout_error)?;
40 stdout.write_all(bytes).or_else(handle_stdout_error)?;
41
41
42 stdout.flush().or_else(handle_stdout_error)
42 stdout.flush().or_else(handle_stdout_error)
43 }
43 }
44
44
45 /// Write bytes to stderr
45 /// Write bytes to stderr
46 pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> {
46 pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> {
47 let mut stderr = self.stderr.lock();
47 let mut stderr = self.stderr.lock();
48
48
49 stderr.write_all(bytes).or_else(handle_stderr_error)?;
49 stderr.write_all(bytes).or_else(handle_stderr_error)?;
50
50
51 stderr.flush().or_else(handle_stderr_error)
51 stderr.flush().or_else(handle_stderr_error)
52 }
52 }
53
53
54 /// is plain mode active
54 /// Return whether plain mode is active.
55 ///
55 ///
56 /// Plain mode means that all configuration variables which affect
56 /// Plain mode means that all configuration variables which affect
57 /// the behavior and output of Mercurial should be
57 /// the behavior and output of Mercurial should be
58 /// ignored. Additionally, the output should be stable,
58 /// ignored. Additionally, the output should be stable,
59 /// reproducible and suitable for use in scripts or applications.
59 /// reproducible and suitable for use in scripts or applications.
60 ///
60 ///
61 /// The only way to trigger plain mode is by setting either the
61 /// The only way to trigger plain mode is by setting either the
62 /// `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
62 /// `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
63 ///
63 ///
64 /// The return value can either be
64 /// The return value can either be
65 /// - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
65 /// - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
66 /// - False if feature is disabled by default and not included in HGPLAIN
66 /// - False if feature is disabled by default and not included in HGPLAIN
67 /// - True otherwise
67 /// - True otherwise
68 pub fn plain(&self) -> bool {
68 pub fn plain(&self) -> bool {
69 // TODO: add support for HGPLAINEXCEPT
69 // TODO: add support for HGPLAINEXCEPT
70 env::var_os("HGPLAIN").is_some()
70 env::var_os("HGPLAIN").is_some()
71 }
71 }
72 }
72 }
73
73
74 /// A buffered stdout writer for faster batch printing operations.
74 /// A buffered stdout writer for faster batch printing operations.
75 pub struct StdoutBuffer<W: Write> {
75 pub struct StdoutBuffer<W: Write> {
76 buf: io::BufWriter<W>,
76 buf: io::BufWriter<W>,
77 }
77 }
78
78
79 impl<W: Write> StdoutBuffer<W> {
79 impl<W: Write> StdoutBuffer<W> {
80 pub fn new(writer: W) -> Self {
80 pub fn new(writer: W) -> Self {
81 let buf = io::BufWriter::new(writer);
81 let buf = io::BufWriter::new(writer);
82 Self { buf }
82 Self { buf }
83 }
83 }
84
84
85 /// Write bytes to stdout buffer
85 /// Write bytes to stdout buffer
86 pub fn write_all(&mut self, bytes: &[u8]) -> Result<(), UiError> {
86 pub fn write_all(&mut self, bytes: &[u8]) -> Result<(), UiError> {
87 self.buf.write_all(bytes).or_else(handle_stdout_error)
87 self.buf.write_all(bytes).or_else(handle_stdout_error)
88 }
88 }
89
89
90 /// Flush bytes to stdout
90 /// Flush bytes to stdout
91 pub fn flush(&mut self) -> Result<(), UiError> {
91 pub fn flush(&mut self) -> Result<(), UiError> {
92 self.buf.flush().or_else(handle_stdout_error)
92 self.buf.flush().or_else(handle_stdout_error)
93 }
93 }
94 }
94 }
95
95
96 /// Sometimes writing to stdout is not possible, try writing to stderr to
96 /// Sometimes writing to stdout is not possible, try writing to stderr to
97 /// signal that failure, otherwise just bail.
97 /// signal that failure, otherwise just bail.
98 fn handle_stdout_error(error: io::Error) -> Result<(), UiError> {
98 fn handle_stdout_error(error: io::Error) -> Result<(), UiError> {
99 if let ErrorKind::BrokenPipe = error.kind() {
99 if let ErrorKind::BrokenPipe = error.kind() {
100 // This makes `| head` work for example
100 // This makes `| head` work for example
101 return Ok(());
101 return Ok(());
102 }
102 }
103 let mut stderr = io::stderr();
103 let mut stderr = io::stderr();
104
104
105 stderr
105 stderr
106 .write_all(&format_bytes!(
106 .write_all(&format_bytes!(
107 b"abort: {}\n",
107 b"abort: {}\n",
108 error.to_string().as_bytes()
108 error.to_string().as_bytes()
109 ))
109 ))
110 .map_err(UiError::StderrError)?;
110 .map_err(UiError::StderrError)?;
111
111
112 stderr.flush().map_err(UiError::StderrError)?;
112 stderr.flush().map_err(UiError::StderrError)?;
113
113
114 Err(UiError::StdoutError(error))
114 Err(UiError::StdoutError(error))
115 }
115 }
116
116
117 /// Sometimes writing to stderr is not possible.
117 /// Sometimes writing to stderr is not possible.
118 fn handle_stderr_error(error: io::Error) -> Result<(), UiError> {
118 fn handle_stderr_error(error: io::Error) -> Result<(), UiError> {
119 // A broken pipe should not result in a error
119 // A broken pipe should not result in a error
120 // like with `| head` for example
120 // like with `| head` for example
121 if let ErrorKind::BrokenPipe = error.kind() {
121 if let ErrorKind::BrokenPipe = error.kind() {
122 return Ok(());
122 return Ok(());
123 }
123 }
124 Err(UiError::StdoutError(error))
124 Err(UiError::StdoutError(error))
125 }
125 }
126
126
127 /// Encode rust strings according to the user system.
127 /// Encode rust strings according to the user system.
128 pub fn utf8_to_local(s: &str) -> Cow<[u8]> {
128 pub fn utf8_to_local(s: &str) -> Cow<[u8]> {
129 // TODO encode for the user's system //
129 // TODO encode for the user's system //
130 let bytes = s.as_bytes();
130 let bytes = s.as_bytes();
131 Cow::Borrowed(bytes)
131 Cow::Borrowed(bytes)
132 }
132 }
@@ -1,426 +1,422 b''
1 TODO: fix rhg bugs that make this test fail when status is enabled
2 $ unset RHG_STATUS
3
4
5 $ hg init
1 $ hg init
6 $ cat << EOF > a
2 $ cat << EOF > a
7 > Small Mathematical Series.
3 > Small Mathematical Series.
8 > One
4 > One
9 > Two
5 > Two
10 > Three
6 > Three
11 > Four
7 > Four
12 > Five
8 > Five
13 > Hop we are done.
9 > Hop we are done.
14 > EOF
10 > EOF
15 $ hg add a
11 $ hg add a
16 $ hg commit -m ancestor
12 $ hg commit -m ancestor
17 $ cat << EOF > a
13 $ cat << EOF > a
18 > Small Mathematical Series.
14 > Small Mathematical Series.
19 > 1
15 > 1
20 > 2
16 > 2
21 > 3
17 > 3
22 > 4
18 > 4
23 > 5
19 > 5
24 > Hop we are done.
20 > Hop we are done.
25 > EOF
21 > EOF
26 $ hg commit -m branch1
22 $ hg commit -m branch1
27 $ hg co 0
23 $ hg co 0
28 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
24 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 $ cat << EOF > a
25 $ cat << EOF > a
30 > Small Mathematical Series.
26 > Small Mathematical Series.
31 > 1
27 > 1
32 > 2
28 > 2
33 > 3
29 > 3
34 > 6
30 > 6
35 > 8
31 > 8
36 > Hop we are done.
32 > Hop we are done.
37 > EOF
33 > EOF
38 $ hg commit -m branch2
34 $ hg commit -m branch2
39 created new head
35 created new head
40
36
41 $ hg merge 1
37 $ hg merge 1
42 merging a
38 merging a
43 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
39 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
44 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
40 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
45 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
41 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
46 [1]
42 [1]
47
43
48 $ hg id
44 $ hg id
49 618808747361+c0c68e4fe667+ tip
45 618808747361+c0c68e4fe667+ tip
50
46
51 $ echo "[commands]" >> $HGRCPATH
47 $ echo "[commands]" >> $HGRCPATH
52 $ echo "status.verbose=true" >> $HGRCPATH
48 $ echo "status.verbose=true" >> $HGRCPATH
53 $ hg status
49 $ hg status
54 M a
50 M a
55 ? a.orig
51 ? a.orig
56 # The repository is in an unfinished *merge* state.
52 # The repository is in an unfinished *merge* state.
57
53
58 # Unresolved merge conflicts:
54 # Unresolved merge conflicts:
59 #
55 #
60 # a
56 # a
61 #
57 #
62 # To mark files as resolved: hg resolve --mark FILE
58 # To mark files as resolved: hg resolve --mark FILE
63
59
64 # To continue: hg commit
60 # To continue: hg commit
65 # To abort: hg merge --abort
61 # To abort: hg merge --abort
66
62
67 $ hg status -Tjson
63 $ hg status -Tjson
68 [
64 [
69 {
65 {
70 "itemtype": "file",
66 "itemtype": "file",
71 "path": "a",
67 "path": "a",
72 "status": "M",
68 "status": "M",
73 "unresolved": true
69 "unresolved": true
74 },
70 },
75 {
71 {
76 "itemtype": "file",
72 "itemtype": "file",
77 "path": "a.orig",
73 "path": "a.orig",
78 "status": "?"
74 "status": "?"
79 },
75 },
80 {
76 {
81 "itemtype": "morestatus",
77 "itemtype": "morestatus",
82 "unfinished": "merge",
78 "unfinished": "merge",
83 "unfinishedmsg": "To continue: hg commit\nTo abort: hg merge --abort"
79 "unfinishedmsg": "To continue: hg commit\nTo abort: hg merge --abort"
84 }
80 }
85 ]
81 ]
86
82
87 $ hg status -0
83 $ hg status -0
88 M a\x00? a.orig\x00 (no-eol) (esc)
84 M a\x00? a.orig\x00 (no-eol) (esc)
89 $ cat a
85 $ cat a
90 Small Mathematical Series.
86 Small Mathematical Series.
91 1
87 1
92 2
88 2
93 3
89 3
94 <<<<<<< working copy: 618808747361 - test: branch2
90 <<<<<<< working copy: 618808747361 - test: branch2
95 6
91 6
96 8
92 8
97 =======
93 =======
98 4
94 4
99 5
95 5
100 >>>>>>> merge rev: c0c68e4fe667 - test: branch1
96 >>>>>>> merge rev: c0c68e4fe667 - test: branch1
101 Hop we are done.
97 Hop we are done.
102
98
103 $ hg status --config commands.status.verbose=0
99 $ hg status --config commands.status.verbose=0
104 M a
100 M a
105 ? a.orig
101 ? a.orig
106
102
107 Verify custom conflict markers
103 Verify custom conflict markers
108
104
109 $ hg up -q --clean .
105 $ hg up -q --clean .
110 $ cat <<EOF >> .hg/hgrc
106 $ cat <<EOF >> .hg/hgrc
111 > [command-templates]
107 > [command-templates]
112 > mergemarker = '{author} {rev}'
108 > mergemarker = '{author} {rev}'
113 > EOF
109 > EOF
114
110
115 $ hg merge 1
111 $ hg merge 1
116 merging a
112 merging a
117 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
113 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
118 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
114 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
119 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
115 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
120 [1]
116 [1]
121
117
122 $ cat a
118 $ cat a
123 Small Mathematical Series.
119 Small Mathematical Series.
124 1
120 1
125 2
121 2
126 3
122 3
127 <<<<<<< working copy: test 2
123 <<<<<<< working copy: test 2
128 6
124 6
129 8
125 8
130 =======
126 =======
131 4
127 4
132 5
128 5
133 >>>>>>> merge rev: test 1
129 >>>>>>> merge rev: test 1
134 Hop we are done.
130 Hop we are done.
135
131
136 Verify custom conflict markers with legacy config name
132 Verify custom conflict markers with legacy config name
137
133
138 $ hg up -q --clean .
134 $ hg up -q --clean .
139 $ cat <<EOF >> .hg/hgrc
135 $ cat <<EOF >> .hg/hgrc
140 > [ui]
136 > [ui]
141 > mergemarkertemplate = '{author} {rev}'
137 > mergemarkertemplate = '{author} {rev}'
142 > EOF
138 > EOF
143
139
144 $ hg merge 1
140 $ hg merge 1
145 merging a
141 merging a
146 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
142 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
147 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
143 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
148 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
144 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
149 [1]
145 [1]
150
146
151 $ cat a
147 $ cat a
152 Small Mathematical Series.
148 Small Mathematical Series.
153 1
149 1
154 2
150 2
155 3
151 3
156 <<<<<<< working copy: test 2
152 <<<<<<< working copy: test 2
157 6
153 6
158 8
154 8
159 =======
155 =======
160 4
156 4
161 5
157 5
162 >>>>>>> merge rev: test 1
158 >>>>>>> merge rev: test 1
163 Hop we are done.
159 Hop we are done.
164
160
165 Verify line splitting of custom conflict marker which causes multiple lines
161 Verify line splitting of custom conflict marker which causes multiple lines
166
162
167 $ hg up -q --clean .
163 $ hg up -q --clean .
168 $ cat >> .hg/hgrc <<EOF
164 $ cat >> .hg/hgrc <<EOF
169 > [command-templates]
165 > [command-templates]
170 > mergemarker={author} {rev}\nfoo\nbar\nbaz
166 > mergemarker={author} {rev}\nfoo\nbar\nbaz
171 > EOF
167 > EOF
172
168
173 $ hg -q merge 1
169 $ hg -q merge 1
174 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
170 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
175 [1]
171 [1]
176
172
177 $ cat a
173 $ cat a
178 Small Mathematical Series.
174 Small Mathematical Series.
179 1
175 1
180 2
176 2
181 3
177 3
182 <<<<<<< working copy: test 2
178 <<<<<<< working copy: test 2
183 6
179 6
184 8
180 8
185 =======
181 =======
186 4
182 4
187 5
183 5
188 >>>>>>> merge rev: test 1
184 >>>>>>> merge rev: test 1
189 Hop we are done.
185 Hop we are done.
190
186
191 Verify line trimming of custom conflict marker using multi-byte characters
187 Verify line trimming of custom conflict marker using multi-byte characters
192
188
193 $ hg up -q --clean .
189 $ hg up -q --clean .
194 $ "$PYTHON" <<EOF
190 $ "$PYTHON" <<EOF
195 > fp = open('logfile', 'wb')
191 > fp = open('logfile', 'wb')
196 > fp.write(b'12345678901234567890123456789012345678901234567890' +
192 > fp.write(b'12345678901234567890123456789012345678901234567890' +
197 > b'1234567890') # there are 5 more columns for 80 columns
193 > b'1234567890') # there are 5 more columns for 80 columns
198 >
194 >
199 > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes
195 > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes
200 > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8'))
196 > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8'))
201 >
197 >
202 > fp.close()
198 > fp.close()
203 > EOF
199 > EOF
204 $ hg add logfile
200 $ hg add logfile
205 $ hg --encoding utf-8 commit --logfile logfile
201 $ hg --encoding utf-8 commit --logfile logfile
206
202
207 $ cat >> .hg/hgrc <<EOF
203 $ cat >> .hg/hgrc <<EOF
208 > [command-templates]
204 > [command-templates]
209 > mergemarker={desc|firstline}
205 > mergemarker={desc|firstline}
210 > EOF
206 > EOF
211
207
212 $ hg -q --encoding utf-8 merge 1
208 $ hg -q --encoding utf-8 merge 1
213 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
209 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
214 [1]
210 [1]
215
211
216 $ cat a
212 $ cat a
217 Small Mathematical Series.
213 Small Mathematical Series.
218 1
214 1
219 2
215 2
220 3
216 3
221 <<<<<<< working copy: 1234567890123456789012345678901234567890123456789012345...
217 <<<<<<< working copy: 1234567890123456789012345678901234567890123456789012345...
222 6
218 6
223 8
219 8
224 =======
220 =======
225 4
221 4
226 5
222 5
227 >>>>>>> merge rev: branch1
223 >>>>>>> merge rev: branch1
228 Hop we are done.
224 Hop we are done.
229
225
230 Verify basic conflict markers
226 Verify basic conflict markers
231
227
232 $ hg up -q --clean 2
228 $ hg up -q --clean 2
233 $ printf "\n[ui]\nmergemarkers=basic\n" >> .hg/hgrc
229 $ printf "\n[ui]\nmergemarkers=basic\n" >> .hg/hgrc
234
230
235 $ hg merge 1
231 $ hg merge 1
236 merging a
232 merging a
237 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
233 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
238 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
234 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
239 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
235 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
240 [1]
236 [1]
241
237
242 $ cat a
238 $ cat a
243 Small Mathematical Series.
239 Small Mathematical Series.
244 1
240 1
245 2
241 2
246 3
242 3
247 <<<<<<< working copy
243 <<<<<<< working copy
248 6
244 6
249 8
245 8
250 =======
246 =======
251 4
247 4
252 5
248 5
253 >>>>>>> merge rev
249 >>>>>>> merge rev
254 Hop we are done.
250 Hop we are done.
255
251
256 internal:merge3
252 internal:merge3
257
253
258 $ hg up -q --clean .
254 $ hg up -q --clean .
259
255
260 $ hg merge 1 --tool internal:merge3
256 $ hg merge 1 --tool internal:merge3
261 merging a
257 merging a
262 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
258 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
263 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
259 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
264 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
260 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
265 [1]
261 [1]
266 $ cat a
262 $ cat a
267 Small Mathematical Series.
263 Small Mathematical Series.
268 <<<<<<< working copy
264 <<<<<<< working copy
269 1
265 1
270 2
266 2
271 3
267 3
272 6
268 6
273 8
269 8
274 ||||||| base
270 ||||||| base
275 One
271 One
276 Two
272 Two
277 Three
273 Three
278 Four
274 Four
279 Five
275 Five
280 =======
276 =======
281 1
277 1
282 2
278 2
283 3
279 3
284 4
280 4
285 5
281 5
286 >>>>>>> merge rev
282 >>>>>>> merge rev
287 Hop we are done.
283 Hop we are done.
288
284
289 internal:mergediff
285 internal:mergediff
290
286
291 $ hg co -C 1
287 $ hg co -C 1
292 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
288 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
293 $ cat << EOF > a
289 $ cat << EOF > a
294 > Small Mathematical Series.
290 > Small Mathematical Series.
295 > 1
291 > 1
296 > 2
292 > 2
297 > 3
293 > 3
298 > 4
294 > 4
299 > 4.5
295 > 4.5
300 > 5
296 > 5
301 > Hop we are done.
297 > Hop we are done.
302 > EOF
298 > EOF
303 $ hg co -m 2 -t internal:mergediff
299 $ hg co -m 2 -t internal:mergediff
304 merging a
300 merging a
305 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
301 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
306 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
302 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
307 use 'hg resolve' to retry unresolved file merges
303 use 'hg resolve' to retry unresolved file merges
308 [1]
304 [1]
309 $ cat a
305 $ cat a
310 Small Mathematical Series.
306 Small Mathematical Series.
311 1
307 1
312 2
308 2
313 3
309 3
314 <<<<<<<
310 <<<<<<<
315 ------- base
311 ------- base
316 +++++++ working copy
312 +++++++ working copy
317 4
313 4
318 +4.5
314 +4.5
319 5
315 5
320 ======= destination
316 ======= destination
321 6
317 6
322 8
318 8
323 >>>>>>>
319 >>>>>>>
324 Hop we are done.
320 Hop we are done.
325 Test the same thing as above but modify a bit more so we instead get the working
321 Test the same thing as above but modify a bit more so we instead get the working
326 copy in full and the diff from base to destination.
322 copy in full and the diff from base to destination.
327 $ hg co -C 1
323 $ hg co -C 1
328 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
324 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
329 $ cat << EOF > a
325 $ cat << EOF > a
330 > Small Mathematical Series.
326 > Small Mathematical Series.
331 > 1
327 > 1
332 > 2
328 > 2
333 > 3.5
329 > 3.5
334 > 4.5
330 > 4.5
335 > 5.5
331 > 5.5
336 > Hop we are done.
332 > Hop we are done.
337 > EOF
333 > EOF
338 $ hg co -m 2 -t internal:mergediff
334 $ hg co -m 2 -t internal:mergediff
339 merging a
335 merging a
340 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
336 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
341 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
337 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
342 use 'hg resolve' to retry unresolved file merges
338 use 'hg resolve' to retry unresolved file merges
343 [1]
339 [1]
344 $ cat a
340 $ cat a
345 Small Mathematical Series.
341 Small Mathematical Series.
346 1
342 1
347 2
343 2
348 <<<<<<<
344 <<<<<<<
349 ======= working copy
345 ======= working copy
350 3.5
346 3.5
351 4.5
347 4.5
352 5.5
348 5.5
353 ------- base
349 ------- base
354 +++++++ destination
350 +++++++ destination
355 3
351 3
356 -4
352 -4
357 -5
353 -5
358 +6
354 +6
359 +8
355 +8
360 >>>>>>>
356 >>>>>>>
361 Hop we are done.
357 Hop we are done.
362
358
363 Add some unconflicting changes on each head, to make sure we really
359 Add some unconflicting changes on each head, to make sure we really
364 are merging, unlike :local and :other
360 are merging, unlike :local and :other
365
361
366 $ hg up -C
362 $ hg up -C
367 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
363 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
368 updated to "e0693e20f496: 123456789012345678901234567890123456789012345678901234567890????"
364 updated to "e0693e20f496: 123456789012345678901234567890123456789012345678901234567890????"
369 1 other heads for branch "default"
365 1 other heads for branch "default"
370 $ printf "\n\nEnd of file\n" >> a
366 $ printf "\n\nEnd of file\n" >> a
371 $ hg ci -m "Add some stuff at the end"
367 $ hg ci -m "Add some stuff at the end"
372 $ hg up -r 1
368 $ hg up -r 1
373 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
369 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
374 $ printf "Start of file\n\n\n" > tmp
370 $ printf "Start of file\n\n\n" > tmp
375 $ cat a >> tmp
371 $ cat a >> tmp
376 $ mv tmp a
372 $ mv tmp a
377 $ hg ci -m "Add some stuff at the beginning"
373 $ hg ci -m "Add some stuff at the beginning"
378
374
379 Now test :merge-other and :merge-local
375 Now test :merge-other and :merge-local
380
376
381 $ hg merge
377 $ hg merge
382 merging a
378 merging a
383 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
379 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
384 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
380 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
385 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
381 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
386 [1]
382 [1]
387 $ hg resolve --tool :merge-other a
383 $ hg resolve --tool :merge-other a
388 merging a
384 merging a
389 (no more unresolved files)
385 (no more unresolved files)
390 $ cat a
386 $ cat a
391 Start of file
387 Start of file
392
388
393
389
394 Small Mathematical Series.
390 Small Mathematical Series.
395 1
391 1
396 2
392 2
397 3
393 3
398 6
394 6
399 8
395 8
400 Hop we are done.
396 Hop we are done.
401
397
402
398
403 End of file
399 End of file
404
400
405 $ hg up -C
401 $ hg up -C
406 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
402 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
407 updated to "18b51d585961: Add some stuff at the beginning"
403 updated to "18b51d585961: Add some stuff at the beginning"
408 1 other heads for branch "default"
404 1 other heads for branch "default"
409 $ hg merge --tool :merge-local
405 $ hg merge --tool :merge-local
410 merging a
406 merging a
411 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
407 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
412 (branch merge, don't forget to commit)
408 (branch merge, don't forget to commit)
413 $ cat a
409 $ cat a
414 Start of file
410 Start of file
415
411
416
412
417 Small Mathematical Series.
413 Small Mathematical Series.
418 1
414 1
419 2
415 2
420 3
416 3
421 4
417 4
422 5
418 5
423 Hop we are done.
419 Hop we are done.
424
420
425
421
426 End of file
422 End of file
General Comments 0
You need to be logged in to leave comments. Login now