just playing with tangled
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at gvimdiff 157 lines 5.8 kB view raw
1// Copyright 2020 The Jujutsu Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15use std::io; 16use std::io::Write as _; 17 18use clap_complete::ArgValueCandidates; 19use clap_complete::ArgValueCompleter; 20use jj_lib::backend::BackendResult; 21use jj_lib::conflicts::materialize_merge_result; 22use jj_lib::conflicts::materialize_tree_value; 23use jj_lib::conflicts::MaterializedTreeValue; 24use jj_lib::fileset::FilePattern; 25use jj_lib::fileset::FilesetExpression; 26use jj_lib::merge::MergedTreeValue; 27use jj_lib::repo::Repo as _; 28use jj_lib::repo_path::RepoPath; 29use pollster::FutureExt as _; 30use tracing::instrument; 31 32use crate::cli_util::print_unmatched_explicit_paths; 33use crate::cli_util::CommandHelper; 34use crate::cli_util::RevisionArg; 35use crate::cli_util::WorkspaceCommandHelper; 36use crate::command_error::user_error; 37use crate::command_error::CommandError; 38use crate::complete; 39use crate::ui::Ui; 40 41/// Print contents of files in a revision 42/// 43/// If the given path is a directory, files in the directory will be visited 44/// recursively. 45#[derive(clap::Args, Clone, Debug)] 46pub(crate) struct FileShowArgs { 47 /// The revision to get the file contents from 48 #[arg( 49 long, short, 50 default_value = "@", 51 value_name = "REVSET", 52 add = ArgValueCandidates::new(complete::all_revisions), 53 )] 54 revision: RevisionArg, 55 /// Paths to print 56 #[arg( 57 required = true, 58 value_name = "FILESETS", 59 value_hint = clap::ValueHint::FilePath, 60 add = ArgValueCompleter::new(complete::all_revision_files), 61 )] 62 paths: Vec<String>, 63} 64 65#[instrument(skip_all)] 66pub(crate) fn cmd_file_show( 67 ui: &mut Ui, 68 command: &CommandHelper, 69 args: &FileShowArgs, 70) -> Result<(), CommandError> { 71 let workspace_command = command.workspace_helper(ui)?; 72 let commit = workspace_command.resolve_single_rev(ui, &args.revision)?; 73 let tree = commit.tree()?; 74 // TODO: No need to add special case for empty paths when switching to 75 // parse_union_filesets(). paths = [] should be "none()" if supported. 76 let fileset_expression = workspace_command.parse_file_patterns(ui, &args.paths)?; 77 78 // Try fast path for single file entry 79 if let Some(path) = get_single_path(&fileset_expression) { 80 let value = tree.path_value(path)?; 81 if value.is_absent() { 82 let ui_path = workspace_command.format_file_path(path); 83 return Err(user_error(format!("No such path: {ui_path}"))); 84 } 85 if !value.is_tree() { 86 ui.request_pager(); 87 write_tree_entries(ui, &workspace_command, [(path, Ok(value))])?; 88 return Ok(()); 89 } 90 } 91 92 let matcher = fileset_expression.to_matcher(); 93 ui.request_pager(); 94 write_tree_entries( 95 ui, 96 &workspace_command, 97 tree.entries_matching(matcher.as_ref()), 98 )?; 99 print_unmatched_explicit_paths(ui, &workspace_command, &fileset_expression, [&tree])?; 100 Ok(()) 101} 102 103fn get_single_path(expression: &FilesetExpression) -> Option<&RepoPath> { 104 match &expression { 105 FilesetExpression::Pattern(pattern) => match pattern { 106 // Not using pattern.as_path() because files-in:<path> shouldn't 107 // select the literal <path> itself. 108 FilePattern::FilePath(path) | FilePattern::PrefixPath(path) => Some(path), 109 FilePattern::FileGlob { .. } => None, 110 }, 111 _ => None, 112 } 113} 114 115fn write_tree_entries<P: AsRef<RepoPath>>( 116 ui: &Ui, 117 workspace_command: &WorkspaceCommandHelper, 118 entries: impl IntoIterator<Item = (P, BackendResult<MergedTreeValue>)>, 119) -> Result<(), CommandError> { 120 let repo = workspace_command.repo(); 121 for (path, result) in entries { 122 let value = result?; 123 let materialized = materialize_tree_value(repo.store(), path.as_ref(), value).block_on()?; 124 match materialized { 125 MaterializedTreeValue::Absent => panic!("absent values should be excluded"), 126 MaterializedTreeValue::AccessDenied(err) => { 127 let ui_path = workspace_command.format_file_path(path.as_ref()); 128 writeln!( 129 ui.warning_default(), 130 "Path '{ui_path}' exists but access is denied: {err}" 131 )?; 132 } 133 MaterializedTreeValue::File(mut file) => { 134 io::copy(&mut file.reader, &mut ui.stdout_formatter().as_mut())?; 135 } 136 MaterializedTreeValue::FileConflict { contents, .. } => { 137 materialize_merge_result( 138 &contents, 139 workspace_command.env().conflict_marker_style(), 140 &mut ui.stdout_formatter(), 141 )?; 142 } 143 MaterializedTreeValue::OtherConflict { id } => { 144 ui.stdout_formatter().write_all(id.describe().as_bytes())?; 145 } 146 MaterializedTreeValue::Symlink { .. } | MaterializedTreeValue::GitSubmodule(_) => { 147 let ui_path = workspace_command.format_file_path(path.as_ref()); 148 writeln!( 149 ui.warning_default(), 150 "Path '{ui_path}' exists but is not a file" 151 )?; 152 } 153 MaterializedTreeValue::Tree(_) => panic!("entries should not contain trees"), 154 } 155 } 156 Ok(()) 157}