summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2025-11-20 17:44:42 +0800
committer魏曹先生 <1992414357@qq.com>2025-11-20 17:44:42 +0800
commit733739294fac9aa1c99c6a1cbb8cb08c0c00ef5b (patch)
treeea27f1705eff56fe1f0884a376b9497379b57cef
parentec1e19edb02a03cd0abf50e7b7c041c14707ae39 (diff)
update: improve jv here and status output
- Enhance jv here with detailed file status and holder information - Improve jv status with better formatting and update timestamps - Reorganize table columns for better readability
-rw-r--r--src/bin/jv.rs500
1 files changed, 455 insertions, 45 deletions
diff --git a/src/bin/jv.rs b/src/bin/jv.rs
index 6d53eaa..0057cf9 100644
--- a/src/bin/jv.rs
+++ b/src/bin/jv.rs
@@ -17,32 +17,36 @@ use just_enough_vcs::{
proc_make_sheet_action,
},
virtual_file_actions::{
- CreateTaskResult, TrackFileActionArguments, TrackFileActionResult,
+ CreateTaskResult, NextVersion, SyncTaskResult, TrackFileActionArguments,
+ TrackFileActionResult, UpdateDescription, UpdateTaskResult, VerifyFailReason,
proc_track_file_action,
},
},
constants::{
- CLIENT_FILE_LATEST_INFO, CLIENT_FILE_WORKSPACE, CLIENT_FOLDER_WORKSPACE_ROOT_NAME,
+ CLIENT_FILE_WORKSPACE, CLIENT_FOLDER_WORKSPACE_ROOT_NAME, CLIENT_PATH_WORKSPACE_ROOT,
PORT, REF_SHEET_NAME,
},
current::{current_doc_dir, current_local_path},
data::{
local::{
LocalWorkspace, cached_sheet::CachedSheet, config::LocalConfig,
- file_status::AnalyzeResult, latest_info::LatestInfo,
- local_files::get_relative_paths, member_held::MemberHeld,
+ file_status::AnalyzeResult, latest_file_data::LatestFileData,
+ latest_info::LatestInfo, local_files::get_relative_paths,
},
member::{Member, MemberId},
user::UserDirectory,
+ vault::virtual_file::VirtualFileVersion,
},
docs::{ASCII_YIZI, document, documents},
},
};
use std::{
+ collections::{HashMap, HashSet},
env::{current_dir, set_current_dir},
net::SocketAddr,
path::PathBuf,
process::exit,
+ str::FromStr,
};
use clap::{Parser, Subcommand, arg, command};
@@ -59,7 +63,7 @@ use just_enough_vcs_cli::{
display::{SimpleTable, md, size_str},
env::current_locales,
fs::move_across_partitions,
- input::{confirm_hint, confirm_hint_or, input_with_editor},
+ input::{confirm_hint, confirm_hint_or, input_with_editor, show_in_pager},
socket_addr_helper,
sort::quick_sort_with_cmp,
},
@@ -419,7 +423,7 @@ struct MoveKeyToAccountArgs {
key_path: PathBuf,
}
-#[derive(Parser, Debug)]
+#[derive(Parser, Debug, Clone)]
struct TrackFileArgs {
/// Show help information
#[arg(short, long)]
@@ -428,13 +432,17 @@ struct TrackFileArgs {
/// Track files
track_files: Option<Vec<PathBuf>>,
- /// Commit - Editor mode
+ /// Commit - Description
#[arg(short, long)]
- work: bool,
+ desc: Option<String>,
- /// Commit - Text mode
+ /// Commit - Description
+ #[arg(short, long)]
+ next_version: Option<String>,
+
+ /// Commit - Editor mode
#[arg(short, long)]
- msg: bool,
+ work: bool,
}
#[derive(Parser, Debug)]
@@ -1015,6 +1023,16 @@ async fn jv_here(_args: HereArgs) {
return;
};
+ let Ok(latest_file_data_path) = LatestFileData::data_path(&local_cfg.current_account()) else {
+ eprintln!("{}", md(t!("jv.fail.read_cfg")));
+ return;
+ };
+
+ let Ok(latest_file_data) = LatestFileData::read_from(&latest_file_data_path).await else {
+ eprintln!("{}", md(t!("jv.fail.read_cfg")));
+ return;
+ };
+
// Print path information
let sheet_name = if let Some(sheet_name) = local_cfg.sheet_in_use() {
sheet_name.to_string()
@@ -1022,6 +1040,31 @@ async fn jv_here(_args: HereArgs) {
"".to_string()
};
+ // Read cached sheet
+ let Ok(cached_sheet) = CachedSheet::cached_sheet_data(&sheet_name).await else {
+ eprintln!("{}", md(t!("jv.fail.read_cfg")));
+ return;
+ };
+
+ let Some(local_workspace) = LocalWorkspace::init_current_dir(local_cfg.clone()) else {
+ eprintln!("{}", md(t!("jv.fail.workspace_not_found")).trim());
+ return;
+ };
+
+ let Ok(analyzed) = AnalyzeResult::analyze_local_status(&local_workspace).await else {
+ eprintln!("{}", md(t!("jv.fail.status.analyze")).trim());
+ return;
+ };
+
+ // Read local sheet
+ let Ok(local_sheet) = local_workspace
+ .local_sheet(&local_cfg.current_account(), &sheet_name)
+ .await
+ else {
+ eprintln!("{}", md(t!("jv.fail.read_cfg")));
+ return;
+ };
+
let path = match current_dir() {
Ok(path) => path,
Err(_) => {
@@ -1051,23 +1094,22 @@ async fn jv_here(_args: HereArgs) {
"{}",
t!(
"jv.success.here.path_info",
- upstream = local_cfg.upstream_addr().to_string().cyan(),
- account = local_cfg.current_account().green(),
+ upstream = local_cfg.upstream_addr().to_string(),
+ account = local_cfg.current_account(),
sheet_name = sheet_name.yellow(),
path = relative_path,
minutes = minutes
)
.trim()
);
- println!();
// Print file info
let mut table = SimpleTable::new(vec![
- t!("jv.success.here.items.name"),
- t!("jv.success.here.items.version"),
- t!("jv.success.here.items.hold"),
- t!("jv.success.here.items.size"),
t!("jv.success.here.items.editing"),
+ t!("jv.success.here.items.holder"),
+ t!("jv.success.here.items.size"),
+ t!("jv.success.here.items.version"),
+ t!("jv.success.here.items.name"),
]);
let mut dir_count = 0;
@@ -1090,30 +1132,138 @@ async fn jv_here(_args: HereArgs) {
let size = metadata.len();
let is_dir = file_type.is_dir();
- let version = "-";
- let hold = "-";
- let editing = "-";
+ let mut version = "-".to_string();
+ let mut hold = "-".to_string();
+ let mut editing = "-".to_string();
if is_dir {
+ // Directory
+ // Add directory count
dir_count += 1;
- table.push_item(vec![
- format!("{}/", file_name.cyan()),
- version.to_string(),
- hold.to_string(),
- size_str(size as usize),
- editing.to_string(),
- ]);
+
+ // Add directory item
+ table.insert_item(
+ 0,
+ vec![
+ editing.to_string(),
+ hold.to_string(),
+ "-".to_string(),
+ version.to_string(),
+ t!(
+ "jv.success.here.append_info.name",
+ name = format!("{}/", file_name.cyan())
+ )
+ .trim()
+ .to_string(),
+ ],
+ );
} else {
+ // File
+ // Add file count
file_count += 1;
+
+ // Get current path
+ let current_path = PathBuf::from_str(relative_path.as_ref())
+ .unwrap()
+ .join(file_name.clone());
+
+ // Get mapping
+ if let Some(mapping) = cached_sheet.mapping().get(&current_path) {
+ let mut is_file_held = false;
+ let mut is_version_match = false;
+
+ // Hold status
+ let id = mapping.id.clone();
+ if let Some(holder) = latest_file_data.file_holder(&id) {
+ if holder == &local_cfg.current_account() {
+ hold = t!("jv.success.here.append_info.holder.yourself")
+ .trim()
+ .to_string();
+ is_file_held = true;
+ } else {
+ let holder_text = t!(
+ "jv.success.here.append_info.holder.others",
+ holder = holder
+ )
+ .trim()
+ .truecolor(128, 128, 128);
+ hold = holder_text.to_string();
+ }
+ }
+
+ // Version status
+ if let Some(latest_version) = latest_file_data.file_version(&id) {
+ let local_version = local_sheet.mapping_data(&current_path);
+ if let Ok(local_mapping) = local_version {
+ let local_version = local_mapping.version_when_updated();
+ if latest_version == local_version {
+ version = t!(
+ "jv.success.here.append_info.version.match",
+ version = latest_version
+ )
+ .trim()
+ .to_string();
+ is_version_match = true;
+ } else {
+ version = t!(
+ "jv.success.here.append_info.version.unmatch",
+ remote_version = local_version,
+ )
+ .trim()
+ .red()
+ .to_string();
+ }
+ }
+ }
+
+ // Editing status
+ let modified = analyzed.modified.contains(&current_path);
+ if !is_file_held || !is_version_match {
+ if modified {
+ editing = t!(
+ "jv.success.here.append_info.editing.cant_edit_but_modified"
+ )
+ .trim()
+ .red()
+ .to_string();
+ } else {
+ editing = t!("jv.success.here.append_info.editing.cant_edit")
+ .trim()
+ .truecolor(128, 128, 128)
+ .to_string();
+ }
+ } else {
+ if modified {
+ editing = t!("jv.success.here.append_info.editing.modified")
+ .trim()
+ .cyan()
+ .to_string();
+ } else {
+ editing = t!("jv.success.here.append_info.editing.can_edit")
+ .trim()
+ .to_string();
+ }
+ }
+ }
+
table.push_item(vec![
- file_name,
- version.to_string(),
- hold.to_string(),
- size_str(size as usize),
editing.to_string(),
+ hold.to_string(),
+ t!(
+ "jv.success.here.append_info.size",
+ size = size_str(size as usize)
+ )
+ .trim()
+ .yellow()
+ .to_string(),
+ version.to_string(),
+ t!("jv.success.here.append_info.name", name = file_name)
+ .trim()
+ .to_string(),
]);
}
+ // Total Size
total_size += size;
}
}
@@ -1145,14 +1295,24 @@ async fn jv_status(_args: StatusArgs) {
return;
};
+ let Ok(latest_info) = LatestInfo::read_from(LatestInfo::latest_info_path(
+ &local_dir,
+ &local_cfg.current_account(),
+ ))
+ .await
+ else {
+ eprintln!("{}", md(t!("jv.fail.read_cfg")));
+ return;
+ };
+
let account = local_cfg.current_account();
- let Ok(member_held_path) = MemberHeld::held_file_path(&account) else {
+ let Ok(latest_file_data_path) = LatestFileData::data_path(&account) else {
eprintln!("{}", md(t!("jv.fail.read_cfg")));
return;
};
- let Ok(member_held) = MemberHeld::read_from(&member_held_path).await else {
+ let Ok(latest_file_data) = LatestFileData::read_from(&latest_file_data_path).await else {
eprintln!("{}", md(t!("jv.fail.read_cfg")));
return;
};
@@ -1212,6 +1372,7 @@ async fn jv_status(_args: StatusArgs) {
path = path.display().to_string()
)
.trim()
+ .red()
.to_string()
})
.collect();
@@ -1240,7 +1401,7 @@ async fn jv_status(_args: StatusArgs) {
let holder_match = {
if let Ok(mapping) = local_sheet.mapping_data(path) {
let vfid = mapping.mapping_vfid();
- match member_held.file_holder(vfid) {
+ match latest_file_data.file_holder(vfid) {
Some(holder) => holder == &account,
None => false,
}
@@ -1251,9 +1412,10 @@ async fn jv_status(_args: StatusArgs) {
let base_version_match = {
if let Ok(mapping) = local_sheet.mapping_data(path) {
+ let vfid = mapping.mapping_vfid();
let ver = mapping.version_when_updated();
- if let Some(cached) = cached_sheet.mapping().get(path) {
- ver == &cached.version
+ if let Some(latest_version) = latest_file_data.file_version(&vfid) {
+ ver == latest_version
} else {
true
}
@@ -1270,6 +1432,7 @@ async fn jv_status(_args: StatusArgs) {
reason = t!("jv.success.status.invalid_modified_reasons.not_holder")
)
.trim()
+ .red()
.to_string();
}
@@ -1281,6 +1444,7 @@ async fn jv_status(_args: StatusArgs) {
reason = t!("jv.success.status.invalid_modified_reasons.base_version_mismatch")
)
.trim()
+ .red()
.to_string();
}
@@ -1307,6 +1471,13 @@ async fn jv_status(_args: StatusArgs) {
sort_paths(&mut modified_items);
}
+ // Calculate duration since last update
+ let update_instant = latest_info.update_instant.unwrap_or(Instant::now());
+ let duration = Instant::now().duration_since(update_instant);
+ let h = duration.as_secs() / 3600;
+ let m = (duration.as_secs() % 3600) / 60;
+ let s = duration.as_secs() % 60;
+
println!(
"{}",
md(t!(
@@ -1345,13 +1516,16 @@ async fn jv_status(_args: StatusArgs) {
if modified_items.is_empty() {
"".to_string()
} else {
- modified_items.join("\n") + "\n"
+ modified_items.join("\n")
}
} else {
t!("jv.success.status.no_file_modifications")
.trim()
.to_string()
- }
+ },
+ h = h,
+ m = m,
+ s = s
))
.trim()
);
@@ -1709,7 +1883,12 @@ async fn jv_sheet_drop(args: SheetDropArgs) {
}
async fn jv_track(args: TrackFileArgs) {
- let Some(track_files) = args.track_files else {
+ let track_files = if let Some(files) = args.track_files.clone() {
+ files
+ .iter()
+ .map(|f| current_dir().unwrap().join(f))
+ .collect()
+ } else {
println!("{}", md(t!("jv.track")));
return;
};
@@ -1721,12 +1900,17 @@ async fn jv_track(args: TrackFileArgs) {
}
};
+ let Some(local_workspace) = LocalWorkspace::init_current_dir(local_config.clone()) else {
+ eprintln!("{}", md(t!("jv.fail.workspace_not_found")).trim());
+ return;
+ };
+
let Some(local_dir) = current_local_path() else {
eprintln!("{}", t!("jv.fail.workspace_not_found").trim());
return;
};
- let Some(files) = get_relative_paths(local_dir, track_files).await else {
+ let Some(files) = get_relative_paths(&local_dir, &track_files).await else {
eprintln!(
"{}",
md(t!("jv.fail.track.parse_fail", param = "track_files"))
@@ -1744,12 +1928,16 @@ async fn jv_track(args: TrackFileArgs) {
None => return,
};
+ let files = files.iter().cloned().collect();
+ let update_info = get_update_info(local_workspace, &files, args).await;
+
match proc_track_file_action(
&pool,
ctx,
TrackFileActionArguments {
- relative_pathes: files.iter().cloned().collect(),
- display_progressbar: true,
+ relative_pathes: files,
+ file_update_info: update_info,
+ print_infos: true,
},
)
.await
@@ -1799,13 +1987,235 @@ async fn jv_track(args: TrackFileArgs) {
)
}
},
- TrackFileActionResult::UpdateTaskFailed(update_task_result) => todo!(),
- TrackFileActionResult::SyncTaskFailed(sync_task_result) => todo!(),
+ TrackFileActionResult::UpdateTaskFailed(update_task_result) => match update_task_result
+ {
+ UpdateTaskResult::Success(_) => {} // Success is not handled here
+ UpdateTaskResult::VerifyFailed { path, reason } => match reason {
+ VerifyFailReason::SheetNotFound(sheet_name) => {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.result.track.update_failed.verify.sheet_not_found",
+ sheet_name = sheet_name
+ ))
+ )
+ }
+ VerifyFailReason::MappingNotFound => {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.result.track.update_failed.verify.mapping_not_found",
+ path = path.display()
+ ))
+ )
+ }
+ VerifyFailReason::VirtualFileNotFound(vfid) => {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.result.track.update_failed.verify.virtual_file_not_found",
+ vfid = vfid
+ ))
+ )
+ }
+ VerifyFailReason::VirtualFileReadFailed(vfid) => {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.result.track.update_failed.verify.virtual_file_read_failed",
+ vfid = vfid
+ ))
+ )
+ }
+ VerifyFailReason::NotHeld => {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.result.track.update_failed.verify.not_held",
+ path = path.display()
+ ))
+ )
+ }
+ VerifyFailReason::VersionDismatch(current_version, latest_version) => {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.result.track.update_failed.verify.version_dismatch",
+ version_current = current_version,
+ version_latest = latest_version
+ ))
+ )
+ }
+ VerifyFailReason::UpdateButNoDescription => {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.result.track.update_failed.verify.update_but_no_description"
+ ))
+ )
+ }
+ VerifyFailReason::VersionAlreadyExist(latest_version) => {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.result.track.update_failed.verify.version_already_exist",
+ path = path.display(),
+ version = latest_version
+ ))
+ )
+ }
+ },
+ },
+ TrackFileActionResult::SyncTaskFailed(sync_task_result) => match sync_task_result {
+ SyncTaskResult::Success(_) => {} // Success is not handled here
+ },
},
Err(e) => handle_err(e),
}
}
+async fn get_update_info(
+ workspace: LocalWorkspace,
+ files: &HashSet<PathBuf>,
+ args: TrackFileArgs,
+) -> HashMap<PathBuf, (NextVersion, UpdateDescription)> {
+ let mut result = HashMap::new();
+
+ if files.len() == 1 {
+ if let (Some(desc), Some(ver)) = (&args.desc, &args.next_version) {
+ if let Some(file) = files.iter().next() {
+ result.insert(file.clone(), (ver.clone(), desc.clone()));
+ return result;
+ }
+ }
+ }
+ if args.work {
+ return start_update_editor(workspace, files, &args).await;
+ }
+
+ result
+}
+
+async fn start_update_editor(
+ workspace: LocalWorkspace,
+ files: &HashSet<PathBuf>,
+ args: &TrackFileArgs,
+) -> HashMap<PathBuf, (NextVersion, UpdateDescription)> {
+ // Get files
+ let Ok(analyzed) = AnalyzeResult::analyze_local_status(&workspace).await else {
+ return HashMap::new();
+ };
+ // Has unsolved moves, skip
+ if analyzed.lost.len() > 0 || analyzed.moved.len() > 0 {
+ return HashMap::new();
+ }
+ // No modified, skip
+ if analyzed.modified.len() < 1 {
+ return HashMap::new();
+ }
+ // No sheet, skip
+ let Some(sheet) = workspace.config().lock().await.sheet_in_use().clone() else {
+ return HashMap::new();
+ };
+ // No cached sheet, skip
+ let Ok(cached_sheet) = CachedSheet::cached_sheet_data(&sheet).await else {
+ return HashMap::new();
+ };
+ let files: Vec<(PathBuf, VirtualFileVersion)> = files
+ .iter()
+ .filter_map(|file| {
+ if analyzed.modified.contains(file) {
+ if let Some(mapping_item) = cached_sheet.mapping().get(file) {
+ return Some((file.clone(), mapping_item.version.clone()));
+ }
+ }
+ None
+ })
+ .collect();
+
+ // Generate editor text
+ let mut table = SimpleTable::new_with_padding(
+ vec![
+ t!("editor.modified_line.header.file_path").trim(),
+ t!("editor.modified_line.header.old_version").trim(),
+ "",
+ t!("editor.modified_line.header.new_version").trim(),
+ ],
+ 2,
+ );
+ for item in files {
+ table.push_item(vec![
+ item.0.display().to_string(),
+ item.1.to_string(),
+ t!("editor.modified_line.content.arrow").trim().to_string(),
+ " ".to_string(),
+ ]);
+ }
+ let lines = table.to_string();
+
+ let str = t!(
+ "editor.update_editor",
+ modified_lines = lines,
+ description = args.desc.clone().unwrap_or_default()
+ );
+
+ let path = workspace
+ .local_path()
+ .join(CLIENT_PATH_WORKSPACE_ROOT)
+ .join(".UPDATE.md");
+ let result = input_with_editor(str, path, "#").await.unwrap_or_default();
+
+ let mut update_info = HashMap::new();
+
+ // Parse the result returned from the editor
+ let lines: Vec<&str> = result.lines().collect();
+ let mut i = 0;
+
+ // Find the separator line
+ let mut separator_index = None;
+ while i < lines.len() {
+ let line = lines[i].trim();
+ if line.chars().all(|c| c == '-') && line.len() >= 5 {
+ separator_index = Some(i);
+ break;
+ }
+ i += 1;
+ }
+
+ if let Some(sep_idx) = separator_index {
+ // Parse path and version information before the separator
+ for line in &lines[..sep_idx] {
+ let trimmed_line = line.trim();
+ if trimmed_line.is_empty() {
+ continue;
+ }
+
+ // Parse format: /directory/file.extension version -> new_version
+ if let Some(arrow_pos) = trimmed_line.find("->") {
+ let before_arrow = &trimmed_line[..arrow_pos].trim();
+ let after_arrow = &trimmed_line[arrow_pos + 2..].trim();
+
+ // Separate path and old version
+ if let Some(last_space) = before_arrow.rfind(' ') {
+ let path_str = &before_arrow[..last_space].trim();
+ let _old_version = &before_arrow[last_space + 1..].trim(); // Old version, needs parsing but not used
+ let new_version = after_arrow.trim();
+
+ if !path_str.is_empty() && !new_version.is_empty() {
+ let path = PathBuf::from(path_str);
+ // Get description (all content after the separator)
+ let description = lines[sep_idx + 1..].join("\n").trim().to_string();
+
+ update_info.insert(path, (new_version.to_string(), description));
+ }
+ }
+ }
+ }
+ }
+
+ update_info
+}
+
async fn jv_hold(_args: HoldFileArgs) {
todo!()
}
@@ -2168,7 +2578,7 @@ async fn jv_docs(args: DocsArgs) {
return;
};
let file = doc_dir.join("DOCS.MD");
- if let Err(e) = input_with_editor(document, file, "").await {
+ if let Err(e) = show_in_pager(document, file).await {
eprintln!(
"{}",
md(t!(