Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl Config {
pub fn new(path: &str) -> Result<Self, ConfigError> {
let builder = c::Config::builder()
.add_source(c::File::new(path, FileFormat::Toml))
.add_source(c::Environment::with_prefix("mega")); // e.g. MEGA_BASE_DIR == base_dir
.add_source(c::Environment::with_prefix("mega").prefix_separator("_").separator("__")); // e.g. MEGA_BASE_DIR == base_dir
// support ${} variable substitution
let config = variable_placeholder_substitute(builder);

Expand Down
2 changes: 1 addition & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ Config `confg.toml` file for the Mega project.
### Enhance
- You can use environment variables starting with `MEGA_` to override the configuration in `config.toml`.
- like `MEGA_BASE_DIR` to override `base_dir`. // with `env::set_var()`
- but it seems only not available for nested keys, like `log.log_path`.
- use separator `__` (2 \* `_`) for nested keys, like `MEGA_LOG__LOG_PATH` for `log.log_path`.
- Support `${}` syntax to reference other keys in the same file.
- like `log_path = "${base_dir}/logs"`, `${base_dir}` will be replaced by the value of `base_dir`
- or `key = "${xxx.yyy}/zzz"` (prefix `xxx.` can't be omitted)
Expand Down
2 changes: 1 addition & 1 deletion libra/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ lazy_static = { workspace = true }
lru-mem = "0.3.0"
mercury = { workspace = true }
once_cell = "1.19.0"
path_abs = "0.5.1"
path-absolutize = "3.1.1"
pathdiff = "0.2.1"
regex = { workspace = true }
reqwest = { workspace = true, features = ["stream", "json"] }
Expand Down
14 changes: 4 additions & 10 deletions libra/src/command/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ use crate::{
#[cfg(unix)]
use std::process::{Command, Stdio};

use crate::utils::path_ext::PathExt;

#[derive(Parser, Debug)]
pub struct DiffArgs {
#[clap(long, help = "Old commit, defaults is staged or HEAD")]
Expand Down Expand Up @@ -123,15 +125,7 @@ pub async fn execute(args: DiffArgs) {
.pathspec
.iter()
.map(|s| {
let path = {
let _path = PathBuf::from(s);
if _path.is_absolute() {
_path
} else {
std::env::current_dir().unwrap().join(_path) // proces path such as `./`
}
};
util::to_workdir_path(&path)
util::to_workdir_path(s)
})
.collect();

Expand Down Expand Up @@ -182,7 +176,7 @@ pub async fn diff(
// filter files, cross old and new files, and pathspec
for (new_file, new_hash) in new_blobs {
// if new_file did't start with any path in filter, skip it
if !filter.is_empty() && !filter.iter().any(|path| new_file.starts_with(path)) {
if !filter.is_empty() && !filter.iter().any(|path| new_file.sub_of(path)) {
continue;
}
match old_blobs.get(&new_file) {
Expand Down
13 changes: 12 additions & 1 deletion libra/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,21 @@ fn ask_username_password() -> (String, String) {
let mut username = String::new();
io::stdin().read_line(&mut username).unwrap();
username = username.trim().to_string();
tracing::debug!("username: {}", username);

print!("password: ");
io::stdout().flush().unwrap();
let password = read_password().unwrap(); // hide password
let password = if std::env::var("LIBRA_NO_HIDE_PASSWORD").is_ok() {
// for test
let mut password = String::new();
io::stdin().read_line(&mut password).unwrap();
password = password.trim().to_string();
tracing::debug!("password: {}", password);
password
} else {
// error in test environment: "No such device or address"
read_password().unwrap() // hide password
};
(username, password)
}

Expand Down
1 change: 0 additions & 1 deletion libra/src/command/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use std::collections::HashSet;
use std::path::PathBuf;

use colored::Colorize;
use path_abs::PathInfo;

use mercury::internal::object::commit::Commit;
use mercury::internal::object::tree::Tree;
Expand Down
8 changes: 6 additions & 2 deletions libra/src/internal/protocol/https_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,18 @@ pub struct BasicAuth {
pub(crate) username: String,
pub(crate) password: String,
}

static AUTH: Mutex<Option<BasicAuth>> = Mutex::new(None);
impl BasicAuth {
/// set username & password manually
pub async fn set_auth(auth: BasicAuth) {
AUTH.lock().unwrap().replace(auth);
}

/// send request with basic auth, retry 3 times
pub async fn send<Fut>(request_builder: impl Fn() -> Fut) -> Result<Response, reqwest::Error>
where
Fut: std::future::Future<Output=RequestBuilder>,
{
static AUTH: Mutex<Option<BasicAuth>> = Mutex::new(None);
const MAX_TRY: usize = 3;
let mut res;
let mut try_cnt = 0;
Expand Down
1 change: 0 additions & 1 deletion libra/src/utils/lfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use crate::utils::path_ext::PathExt;
use crate::utils::{path, util};
use lazy_static::lazy_static;
use mercury::internal::index::Index;
use path_abs::{PathInfo, PathOps};
use regex::Regex;
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE};
use ring::digest::{Context, SHA256};
Expand Down
3 changes: 3 additions & 0 deletions libra/src/utils/path_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ impl PathExt for PathBuf {
util::workdir_to_current(self)
}

/// Check if `self` is a sub path (child) of `parent`<br>
/// Simply convert to absolute path (to current dir) and call `starts_with`
/// - aka: "src/main.rs" is a sub path of "src/"
fn sub_of(&self, parent: &Path) -> bool {
util::is_sub_path(self, parent)
}
Expand Down
45 changes: 29 additions & 16 deletions libra/src/utils/util.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use path_abs::{PathAbs, PathInfo};
use std::collections::HashSet;
use std::io::Write;
use std::path::{Path, PathBuf};
use path_absolutize::*;
use std::{env, fs, io};
use indicatif::{ProgressBar, ProgressStyle};
use mercury::hash::SHA1;
Expand Down Expand Up @@ -99,8 +99,10 @@ where
P: AsRef<Path>,
B: AsRef<Path>,
{
let path_abs = PathAbs::new(path.as_ref()).unwrap(); // prefix: '\\?\' on Windows
let parent_abs = PathAbs::new(parent.as_ref()).unwrap();
// to absolute, just for clear redundant `..` `.` in the path
// may generate wrong intermediate path, but the final result is correct (after `starts_with`)
let path_abs = path.as_ref().absolutize().unwrap();
let parent_abs = parent.as_ref().absolutize().unwrap();
path_abs.starts_with(parent_abs)
}

Expand Down Expand Up @@ -140,22 +142,24 @@ where
}

/// `path` & `base` must be absolute or relative (to current dir)
/// <br> return "." if `path` == `base`
pub fn to_relative<P, B>(path: P, base: B) -> PathBuf
where
P: AsRef<Path>,
B: AsRef<Path>,
{
let path_abs = PathAbs::new(path.as_ref()).unwrap(); // prefix: '\\?\' on Windows
let base_abs = PathAbs::new(base.as_ref()).unwrap();
if cfg!(windows) {
assert_eq!(
// just little check
path_abs.to_str().unwrap().starts_with(r"\\?\"),
base_abs.to_str().unwrap().starts_with(r"\\?\")
)
}
// × crate `PathAbs` is NOT good enough
// 1. `PathAbs::new` can not handle `.` or `./`, all return ""
// 2. `PathAbs::new` generate prefix: '\\?\' on Windows
// So, we replace it with `path_absolutize` √
let path_abs = path.as_ref().absolutize().unwrap();
let base_abs = base.as_ref().absolutize().unwrap();
if let Some(rel_path) = pathdiff::diff_paths(path_abs, base_abs) {
rel_path
if rel_path.to_string_lossy() == "" {
PathBuf::from(".")
} else {
rel_path
}
} else {
panic!(
"fatal: path {:?} cannot convert to relative based on {:?}",
Expand Down Expand Up @@ -276,7 +280,7 @@ pub fn is_empty_dir(dir: &Path) -> bool {
}

pub fn is_cur_dir(dir: &Path) -> bool {
PathAbs::new(dir).unwrap() == PathAbs::new(cur_dir()).unwrap()
dir.absolutize().unwrap() == cur_dir().absolutize().unwrap()
}

/// transform path to string, use '/' as separator even on windows
Expand Down Expand Up @@ -358,12 +362,21 @@ mod test {
assert!(is_sub_path("src/main.rs", "src"));
assert!(is_sub_path("src/main.rs", "src/"));
assert!(is_sub_path("src/main.rs", "src/main.rs"));
assert!(is_sub_path("src/main.rs", "."));
}

#[test]
fn test_to_relative() {
assert_eq!(to_relative("src/main.rs", "src"), PathBuf::from("main.rs"));
assert_eq!(to_relative(".", "src"), PathBuf::from(".."));
}

#[tokio::test]
async fn test_to_workdir_path() {
test::setup_with_new_libra().await;
let workdir_path = to_workdir_path("src/main.rs");
assert_eq!(workdir_path, PathBuf::from("src/main.rs"));
assert_eq!(to_workdir_path("./src/abc/../main.rs"), PathBuf::from("src/main.rs"));
assert_eq!(to_workdir_path("."), PathBuf::from("."));
assert_eq!(to_workdir_path("./"), PathBuf::from("."));
assert_eq!(to_workdir_path(""), PathBuf::from("."));
}
}
1 change: 1 addition & 0 deletions mega/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ ctrlc = { workspace = true }
tempfile = { workspace = true }
serial_test = "3.1.1"
lazy_static = {workspace = true}
assert_cmd = "2.0.16"

[build-dependencies]
shadow-rs = { workspace = true }
61 changes: 32 additions & 29 deletions mega/tests/lfs_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,41 +46,36 @@ fn check_git_lfs() -> bool {
status.success()
}

fn run_cmd(program: &str, args: &[&str]) {
let output = Command::new(program)
.args(args)
.output()
.unwrap();

let status = output.status;
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if !status.success() {
panic!(
"Command failed: {} {}\nStatus: {}\nStdout: {}\nStderr: {}",
program,
args.join(" "),
status,
stdout,
stderr
);
} else {
println!(
"Command success: {} {}\nStatus: {}\nStdout: {}",
program,
args.join(" "),
status,
stdout,
);
fn run_cmd(program: &str, args: &[&str], stdin: Option<&str>, envs: Option<Vec<(&str, &str)>>) {
let mut cmd = assert_cmd::Command::new(program);
let mut cmd = cmd.args(args);
if let Some(stdin) = stdin {
cmd = cmd.write_stdin(stdin);
}
if let Some(envs) = envs {
cmd = cmd.envs(envs);
}
let assert = cmd.assert().success();
let output = assert.get_output();

println!("Command success: {} {}\nStatus: {}\nStdout: {}",
program,
args.join(" "),
output.status,
String::from_utf8_lossy(&output.stdout),
);
}

fn run_git_cmd(args: &[&str]) {
run_cmd("git", args);
run_cmd("git", args, None, None);
}

fn run_libra_cmd(args: &[&str]) {
run_cmd(LIBRA.to_str().unwrap(), args);
run_cmd(LIBRA.to_str().unwrap(), args, None, None);
}

fn run_libra_cmd_with_stdin(args: &[&str], stdin: Option<&str>, envs: Option<Vec<(&str, &str)>>) {
run_cmd(LIBRA.to_str().unwrap(), args, stdin, envs);
}

fn is_port_in_use(port: u16) -> bool {
Expand Down Expand Up @@ -185,7 +180,9 @@ fn libra_lfs_push(url: &str) -> io::Result<()> {
// try lock API
run_libra_cmd(&["lfs", "lock", "large_file.bin"]);
// push to mega server
run_libra_cmd(&["push", "mega", "master"]);
run_libra_cmd_with_stdin(&["push", "mega", "master"],
Some("mega\nmega"), // basic auth, can be overridden by env var
Some(vec![("LIBRA_NO_HIDE_PASSWORD", "1")]));

Ok(())
}
Expand Down Expand Up @@ -223,6 +220,7 @@ fn lfs_split_with_git() {
assert!(check_git_lfs(), "git lfs is not installed");

let mega_dir = TempDir::new().unwrap();
env::set_var("MEGA_authentication__enable_http_auth", "false"); // no need for git
// start mega server at background (new process)
let mut mega = run_mega_server(mega_dir.path());

Expand All @@ -242,13 +240,18 @@ fn lfs_split_with_libra() {
}

let mega_dir = TempDir::new().unwrap();
env::set_var("MEGA_authentication__enable_http_auth", "true");
env::set_var("MEGA_authentication__enable_test_user", "true");
env::set_var("MEGA_authentication__test_user_name", "mega");
env::set_var("MEGA_authentication__test_user_token", "mega");
// start mega server at background (new process)
let mut mega = run_mega_server(mega_dir.path());

let url = &format!("http://localhost:{}/third-part/lfs-libra.git", PORT);
libra_lfs_push(url).expect("(libra)Failed to push large file to mega server");
libra_lfs_clone(url).expect("(libra)Failed to clone large file from mega server");

env::set_var("MEGA_authentication__enable_http_auth", "false"); // avoid affecting other tests
mega.kill().expect("Failed to kill mega server");
thread::sleep(Duration::from_secs(1)); // wait for server to stop, avoiding affecting other tests
}