From 625cf339110f4d33221d9de2504abbe0c68a7794 Mon Sep 17 00:00:00 2001 From: Qihang Cai Date: Tue, 10 Sep 2024 22:43:50 +0800 Subject: [PATCH 1/6] Optimize feedback for unsupported `Submodule` Signed-off-by: Qihang Cai --- libra/src/command/push.rs | 2 +- libra/src/utils/object_ext.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libra/src/command/push.rs b/libra/src/command/push.rs index 5d4e59cd5..d53e2c137 100644 --- a/libra/src/command/push.rs +++ b/libra/src/command/push.rs @@ -301,7 +301,7 @@ fn diff_tree_objs(old_tree: Option<&SHA1>, new_tree: &SHA1) -> HashSet { } _ => { // TODO: submodule (TreeItemMode: Commit) if item.mode == TreeItemMode::Commit { // (160000)| Gitlink (Submodule) - eprintln!("fatal: submodule not supported"); + eprintln!("{}", "Warning: Submodule is not supported yet".red()); } let blob = Blob::load(&item.id); objs.insert(blob.into()); diff --git a/libra/src/utils/object_ext.rs b/libra/src/utils/object_ext.rs index 6e56f01b5..53e4576b7 100644 --- a/libra/src/utils/object_ext.rs +++ b/libra/src/utils/object_ext.rs @@ -1,7 +1,7 @@ use std::fs; use std::io::{BufReader, Read}; use std::path::{Path, PathBuf}; - +use colored::Colorize; use mercury::hash::SHA1; use mercury::internal::object::blob::Blob; use mercury::internal::object::commit::Commit; @@ -38,6 +38,9 @@ impl TreeExt for Tree { let mut items = Vec::new(); for item in self.tree_items.iter() { if item.mode != TreeItemMode::Tree { // Not Tree, maybe Blob, link, etc. + if item.mode == TreeItemMode::Commit { // submodule + eprintln!("{}", "Warning: Submodule is not supported yet".red()); + } items.push((PathBuf::from(item.name.clone()), item.id)); } else { let sub_tree = Tree::load(&item.id); From 0320a0997fff29fd2b53835660e1a6e3a69fd462 Mon Sep 17 00:00:00 2001 From: Qihang Cai Date: Tue, 10 Sep 2024 22:45:33 +0800 Subject: [PATCH 2/6] improve `clone`: clean up repo directory once panic (failed) Signed-off-by: Qihang Cai --- libra/Cargo.toml | 1 + libra/src/command/clone.rs | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/libra/Cargo.toml b/libra/Cargo.toml index 660bfdd88..cc31245de 100644 --- a/libra/Cargo.toml +++ b/libra/Cargo.toml @@ -45,6 +45,7 @@ serde_json = { workspace = true } async_static = "0.1.3" once_cell = "1.19.0" byte-unit = "5.1.4" +scopeguard = "1.2.0" [target.'cfg(unix)'.dependencies] # only on Unix pager = "0.16.0" diff --git a/libra/src/command/clone.rs b/libra/src/command/clone.rs index eac27e983..bd8f47989 100644 --- a/libra/src/command/clone.rs +++ b/libra/src/command/clone.rs @@ -1,13 +1,14 @@ use std::path::PathBuf; use std::{env, fs}; - +use std::cell::Cell; use crate::command; use crate::command::restore::RestoreArgs; use crate::internal::branch::Branch; use crate::internal::config::{Config, RemoteConfig}; use crate::internal::head::Head; use clap::Parser; - +use colored::Colorize; +use scopeguard::defer; use crate::utils::path_ext::PathExt; use crate::utils::util; @@ -59,6 +60,15 @@ pub async fn execute(args: CloneArgs) { println!("Cloning into '{}'", repo_name); } + let is_success = Cell::new(false); + // clean up the directory if panic + defer! { + if !is_success.get() { + fs::remove_dir_all(&local_path).unwrap(); + eprintln!("{}", "fatal: clone failed, delete repo directory automatically".red()); + } + } + // CAUTION: change [current_dir] to the repo directory env::set_current_dir(&local_path).unwrap(); command::init::execute().await; @@ -72,6 +82,8 @@ pub async fn execute(args: CloneArgs) { /* setup */ setup(remote_repo.clone()).await; + + is_success.set(true); } async fn setup(remote_repo: String) { From d1efbf45442fd0e9a72d81f5888398adaca0708a Mon Sep 17 00:00:00 2001 From: Qihang Cai Date: Tue, 10 Sep 2024 22:48:56 +0800 Subject: [PATCH 3/6] improve `fetch` (HCI): add speed rate & total amount Signed-off-by: Qihang Cai --- libra/src/command/fetch.rs | 8 +++++++- libra/src/command/lfs.rs | 4 +--- libra/src/utils/util.rs | 9 +++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/libra/src/command/fetch.rs b/libra/src/command/fetch.rs index 96ae56ed9..ea58d1a77 100644 --- a/libra/src/command/fetch.rs +++ b/libra/src/command/fetch.rs @@ -1,7 +1,7 @@ use std::io; use std::vec; use std::{collections::HashSet, fs, io::Write}; - +use std::time::Instant; use ceres::protocol::ServiceType::UploadPack; use clap::Parser; use indicatif::ProgressBar; @@ -22,6 +22,7 @@ use crate::{ }, utils::{self, path_ext::PathExt}, }; +use crate::utils::util; #[derive(Parser, Debug)] pub struct FetchArgs { @@ -105,6 +106,7 @@ pub async fn fetch_repository(remote_config: &RemoteConfig) { let mut pack_data = Vec::new(); let mut reach_pack = false; let bar = ProgressBar::new_spinner(); + let time = Instant::now(); loop { let (len, data) = read_pkt_line(&mut reader).await.unwrap(); if len == 0 { @@ -115,6 +117,10 @@ pub async fn fetch_repository(remote_config: &RemoteConfig) { tracing::debug!("Receiving PACK data..."); } if reach_pack { // 2.PACK data + let bytes_per_sec = pack_data.len() as f64 / time.elapsed().as_secs_f64(); + let total = util::auto_unit_bytes(pack_data.len() as u64); + let bps = util::auto_unit_bytes(bytes_per_sec as u64); + bar.set_message(format!("Receiving objects: {total:.2} | {bps:.2}/s")); bar.tick(); // Side-Band Capability, should be enabled if Server Support let code = data[0]; diff --git a/libra/src/command/lfs.rs b/libra/src/command/lfs.rs index ab4eb197a..e0ad914b3 100644 --- a/libra/src/command/lfs.rs +++ b/libra/src/command/lfs.rs @@ -3,7 +3,6 @@ use std::fs::{File, OpenOptions}; use std::io; use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write}; use std::path::Path; -use byte_unit::UnitType; use reqwest::StatusCode; use ceres::lfs::lfs_structs::LockListQuery; use mercury::internal::index::Index; @@ -185,8 +184,7 @@ pub async fn execute(cmd: LfsCmds) { let _type = if is_pointer || !path_abs.exists() { "-" } else { "*" }; let oid = if long { oid } else { oid[..10].to_owned() }; let tail = if size { - let byte = byte_unit::Byte::from(lfs_size); - let byte = byte.get_appropriate_unit(UnitType::Decimal); + let byte = util::auto_unit_bytes(lfs_size); format!(" ({byte:.2})") } else { "".to_string() diff --git a/libra/src/utils/util.rs b/libra/src/utils/util.rs index f520b8928..50aea8d82 100644 --- a/libra/src/utils/util.rs +++ b/libra/src/utils/util.rs @@ -318,6 +318,15 @@ pub fn get_repo_name_from_url(mut url: &str) -> Option<&str> { Some(&url[repo_start..repo_end]) } +/// Find the appropriate unit and value for Bytes. +/// ### Examples +/// - 1024 bytes -> 1 KiB +/// - 1024 * 1024 bytes -> 1 MiB +pub fn auto_unit_bytes(bytes: u64) -> byte_unit::AdjustedByte { + let bytes = byte_unit::Byte::from(bytes); + bytes.get_appropriate_unit(byte_unit::UnitType::Binary) +} + #[cfg(test)] mod test { use super::*; From dd43697d729284b7ab992cf01455ec8ccabea570 Mon Sep 17 00:00:00 2001 From: Qihang Cai Date: Tue, 10 Sep 2024 22:53:41 +0800 Subject: [PATCH 4/6] improve `LFS API`: better feedback if current branch has no remote Signed-off-by: Qihang Cai --- libra/src/command/push.rs | 5 ----- libra/src/internal/config.rs | 21 ++++++++++++++------- libra/src/internal/protocol/lfs_client.rs | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/libra/src/command/push.rs b/libra/src/command/push.rs index d53e2c137..2527d2eba 100644 --- a/libra/src/command/push.rs +++ b/libra/src/command/push.rs @@ -66,11 +66,6 @@ pub async fn execute(args: PushArgs) { } }; let repo_url = Config::get_remote_url(&repository).await; - if repo_url.is_none() { - eprintln!("fatal: remote '{}' not found, please use 'libra remote add'", repository); - return; - } - let repo_url = repo_url.unwrap(); let branch = args.refspec.unwrap_or(branch); let commit_hash = Branch::find_branch(&branch, None).await.unwrap().commit.to_plain_str(); diff --git a/libra/src/internal/config.rs b/libra/src/internal/config.rs index 287445951..a7b3a128d 100644 --- a/libra/src/internal/config.rs +++ b/libra/src/internal/config.rs @@ -63,22 +63,29 @@ impl Config { } /// Get remote repo name of current branch - pub async fn get_current_remote() -> Option { + pub async fn get_current_remote() -> Result, ()> { match Head::current().await { Head::Branch(name) => { - Config::get_remote(&name).await + Ok(Config::get_remote(&name).await) + }, + Head::Detached(_) => { + eprintln!("fatal: HEAD is detached, cannot get remote"); + Err(()) }, - Head::Detached(_) => None, } } - pub async fn get_remote_url(remote: &str) -> Option { - Config::get("remote", Some(remote), "url").await + pub async fn get_remote_url(remote: &str) -> String { + match Config::get("remote", Some(remote), "url").await { + Some(url) => url, + None => panic!("fatal: No URL configured for remote '{}'.", remote), + } } + /// return `None` if no remote is set pub async fn get_current_remote_url() -> Option { - match Config::get_current_remote().await { - Some(remote) => Config::get_remote_url(&remote).await, + match Config::get_current_remote().await.unwrap() { + Some(remote) => Some(Config::get_remote_url(&remote).await), None => None, } } diff --git a/libra/src/internal/protocol/lfs_client.rs b/libra/src/internal/protocol/lfs_client.rs index ddf1cdd4f..8efc97910 100644 --- a/libra/src/internal/protocol/lfs_client.rs +++ b/libra/src/internal/protocol/lfs_client.rs @@ -57,7 +57,7 @@ impl LFSClient { let url = Config::get_current_remote_url().await; match url { Some(url) => LFSClient::from_url(&Url::parse(&url).unwrap()), - None => panic!("fatal: current remote url not found"), + None => panic!("fatal: no remote set for current branch, use `libra branch --set-upstream-to /`"), } } From 2d8fe7c07a9234b0e60e77efe2df0e73f7b19cf1 Mon Sep 17 00:00:00 2001 From: Qihang Cai Date: Tue, 10 Sep 2024 23:16:53 +0800 Subject: [PATCH 5/6] Better error handling for `LFS` uploading & downloading Signed-off-by: Qihang Cai --- libra/src/internal/protocol/lfs_client.rs | 16 ++++++++++------ libra/src/utils/lfs.rs | 7 +++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/libra/src/internal/protocol/lfs_client.rs b/libra/src/internal/protocol/lfs_client.rs index 8efc97910..5d6c65e30 100644 --- a/libra/src/internal/protocol/lfs_client.rs +++ b/libra/src/internal/protocol/lfs_client.rs @@ -152,24 +152,24 @@ impl LFSClient { // TODO: parallel upload for obj in resp.objects { - self.upload_object(obj).await; + self.upload_object(obj).await?; } println!("LFS objects push completed."); Ok(()) } /// upload (PUT) one LFS file to remote server - async fn upload_object(&self, object: Representation) { + async fn upload_object(&self, object: Representation) -> Result<(), ()> { if let Some(err) = object.error { eprintln!("fatal: LFS upload failed. Code: {}, Message: {}", err.code, err.message); - return; + return Err(()); } if let Some(actions) = object.actions { let upload_link = actions.get("upload"); if upload_link.is_none() { eprintln!("fatal: LFS upload failed. No upload action found"); - return; + return Err(()); } let link = upload_link.unwrap(); @@ -188,12 +188,13 @@ impl LFSClient { .unwrap(); if !resp.status().is_success() { eprintln!("fatal: LFS upload failed. Status: {}, Message: {}", resp.status(), resp.text().await.unwrap()); - return; + return Err(()); } println!("Uploaded."); } else { tracing::debug!("LFS file {} already exists on remote server", object.oid); } + Ok(()) } /// download (GET) one LFS file from remote server @@ -262,7 +263,10 @@ impl LFSClient { if checksum == oid { println!("Downloaded."); } else { - eprintln!("fatal: LFS download failed. Checksum mismatch: {} != {}", checksum, oid); + eprintln!("fatal: LFS download failed. Checksum mismatch: {} != {}. Fallback to pointer file.", checksum, oid); + let pointer = lfs::format_pointer_string(oid, size); + file.set_len(0).await.unwrap(); // clear + file.write_all(pointer.as_bytes()).await.unwrap(); } } diff --git a/libra/src/utils/lfs.rs b/libra/src/utils/lfs.rs index 6d341ba39..41164085a 100644 --- a/libra/src/utils/lfs.rs +++ b/libra/src/utils/lfs.rs @@ -59,11 +59,14 @@ pub fn generate_pointer_file(path: impl AsRef) -> (String, String) { // calc file hash without type let oid = calc_lfs_file_hash(path).unwrap(); - let pointer = format!("version {}\noid {}:{}\nsize {}\n", - LFS_VERSION, LFS_HASH_ALGO, oid, path.metadata().unwrap().len()); + let pointer = format_pointer_string(&oid, path.metadata().unwrap().len()); (pointer, oid) } +pub fn format_pointer_string(oid: &str, size: u64) -> String { + format!("version {}\noid {}:{}\nsize {}\n", LFS_VERSION, LFS_HASH_ALGO, oid, size) +} + /// Generate LFS Server Url from repo Url. /// By default, Git LFS will append `.git/info/lfs` to the end of a Git remote url to build the LFS server URL. /// [doc: server-discovery](https://github.com/git-lfs/git-lfs/blob/main/docs/api/server-discovery.md) From c98c7b957185db8c18a305cf588242ee8ec2adf1 Mon Sep 17 00:00:00 2001 From: Qihang Cai Date: Wed, 11 Sep 2024 18:28:59 +0800 Subject: [PATCH 6/6] Add integration test for `libra` in `mega`. Make mega server from thread to child process to control lifetime. Set `lfs.url` temporarily. Make tests run serially. Signed-off-by: Qihang Cai --- .github/workflows/base.yml | 4 + mega/Cargo.toml | 2 + mega/tests/lfs_test.rs | 156 ++++++++++++++++++++++++++++++------- 3 files changed, 136 insertions(+), 26 deletions(-) diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index 3d25c1d1d..418d37611 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -82,6 +82,10 @@ jobs: git config --global user.email "mega@github.com" git config --global user.name "Mega" git config --global lfs.url http://localhost:8000 + - uses: actions-rs/cargo@v1 + with: + command: build + args: --bin mega --bin libra - uses: actions-rs/cargo@v1 with: command: test diff --git a/mega/Cargo.toml b/mega/Cargo.toml index 292416237..11cb7adfc 100644 --- a/mega/Cargo.toml +++ b/mega/Cargo.toml @@ -43,6 +43,8 @@ bytes = { workspace = true } go-defer = { workspace = true } git2 = { workspace = true } tempfile = { workspace = true } +serial_test = "3.1.1" +lazy_static = {workspace = true} [build-dependencies] shadow-rs = { workspace = true } diff --git a/mega/tests/lfs_test.rs b/mega/tests/lfs_test.rs index 9e9d72d37..48b5ade82 100644 --- a/mega/tests/lfs_test.rs +++ b/mega/tests/lfs_test.rs @@ -1,17 +1,41 @@ mod common; /// integration tests for the mega module -use std::process::Command; +use std::process::{Child, Command}; use std::{env, fs, io, thread}; use std::io::Write; use std::net::TcpStream; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::time::Duration; use rand::Rng; +use serial_test::serial; use tempfile::TempDir; +use lazy_static::lazy_static; const PORT: u16 = 8000; // mega server port const LARGE_FILE_SIZE_MB: usize = 60; + +lazy_static! { + static ref LFS_URL: String = format!("http://localhost:{}", PORT); + + static ref TARGET: String = { + // mega/mega, absolute + let mut manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); // Get env at compile time + manifest.pop(); // remove "mega" from path + manifest.join("target").to_str().unwrap().to_string() + }; + + static ref LIBRA: PathBuf = { + let path = format!("{}/debug/libra", TARGET.as_str()); + PathBuf::from(path) + }; + + static ref MEGA: PathBuf = { + let path = format!("{}/debug/mega", TARGET.as_str()); + PathBuf::from(path) + }; +} + /// check if git lfs is installed fn check_git_lfs() -> bool { let status = Command::new("git") @@ -22,19 +46,19 @@ fn check_git_lfs() -> bool { status.success() } -fn run_git_cmd(args: &[&str]) { - let output = Command::new("git") +fn run_cmd(program: &str, args: &[&str]) { + let output = Command::new(program) .args(args) .output() .unwrap(); let status = output.status; - // assert!(status.success(), "Git command failed: git {}", args.join(" ")); if !status.success() { let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); panic!( - "Git command failed: git {}\nStatus: {}\nStdout: {}\nStderr: {}", + "Command failed: {} {}\nStatus: {}\nStdout: {}\nStderr: {}", + program, args.join(" "), status, stdout, @@ -43,16 +67,34 @@ fn run_git_cmd(args: &[&str]) { } } +fn run_git_cmd(args: &[&str]) { + run_cmd("git", args); +} + +fn run_libra_cmd(args: &[&str]) { + run_cmd(LIBRA.to_str().unwrap(), args); +} + fn is_port_in_use(port: u16) -> bool { TcpStream::connect_timeout(&format!("127.0.0.1:{}", port).parse().unwrap(), Duration::from_millis(1000)) .is_ok() } -fn run_mega_server() { - thread::spawn(|| { - let args = vec!["service", "multi", "http"]; - mega::cli::parse(Some(args)).expect("Failed to start mega service"); - }); +/// Run mega server in a new process +fn run_mega_server(data_dir: &Path) -> Child { + if !MEGA.exists() { + panic!("mega binary not found in \"target/debug/\", skip lfs test"); + } + + // env var can be shared between parent and child process + env::set_var("MEGA_BASE_DIR", data_dir); + + let server = Command::new(MEGA.to_str().unwrap()) + .args(["service", "multi", "http"]) + .current_dir(env!("CARGO_MANIFEST_DIR")) + .spawn() + .expect("Failed to start mega server"); + // loop check until port to be ready let mut i = 0; while !is_port_in_use(PORT) && (i < 15) { @@ -61,6 +103,9 @@ fn run_mega_server() { } assert!(is_port_in_use(PORT), "mega server not started"); println!("mega server started in {} secs", i); + thread::sleep(Duration::from_secs(1)); + + server } fn generate_large_file(path: &str, size_mb: usize) -> io::Result<()> { @@ -78,7 +123,7 @@ fn generate_large_file(path: &str, size_mb: usize) -> io::Result<()> { Ok(()) } -fn lfs_push(url: &str) -> io::Result<()> { +fn git_lfs_push(url: &str) -> io::Result<()> { let temp_dir = TempDir::new()?; let repo_path = temp_dir.path().to_str().unwrap(); println!("repo_path: {}", repo_path); @@ -90,27 +135,57 @@ fn lfs_push(url: &str) -> io::Result<()> { // track Large file run_git_cmd(&["lfs", "track", "*.bin"]); - // create large file generate_large_file("large_file.bin", LARGE_FILE_SIZE_MB)?; - // add & commit run_git_cmd(&["add", "."]); run_git_cmd(&["commit", "-m", "add large file"]); - // push to mega server run_git_cmd(&["remote", "add", "mega", url]); + + // set lfs.url + run_git_cmd(&["config", "lfs.url", &LFS_URL]); + // push to mega server run_git_cmd(&["push", "--all", "mega"]); Ok(()) } -fn lfs_clone(url: &str) -> io::Result<()> { +fn libra_lfs_push(url: &str) -> io::Result<()> { + let temp_dir = TempDir::new()?; + let repo_path = temp_dir.path().to_str().unwrap(); + println!("repo_path: {}", repo_path); + + env::set_current_dir(repo_path)?; + + // libra init + run_libra_cmd(&["init"]); + // track Large file + run_libra_cmd(&["lfs", "track", "*.bin"]); + // create large file + generate_large_file("large_file.bin", LARGE_FILE_SIZE_MB)?; + // add & commit + run_libra_cmd(&["add", "."]); + run_libra_cmd(&["commit", "-m", "add large file"]); + // add remote + run_libra_cmd(&["remote", "add", "mega", url]); + // branch --set-upstream-to=origin/master + run_libra_cmd(&["branch", "--set-upstream-to=mega/master"]); + // try lock API + run_libra_cmd(&["lfs", "lock", "large_file.bin"]); + // push to mega server + run_libra_cmd(&["push", "mega", "master"]); + + Ok(()) +} + +fn git_lfs_clone(url: &str) -> io::Result<()> { let temp_dir = TempDir::new()?; println!("clone temp_dir: {:?}", temp_dir.path()); env::set_current_dir(temp_dir.path())?; // git clone url - run_git_cmd(&["clone", url]); + // `--config`: temporary set lfs.url + run_git_cmd(&["clone", url, "--config", &("lfs.url=".to_owned() + &LFS_URL)]); let file = Path::new("lfs/large_file.bin"); assert!(file.exists(), "Failed to clone large file"); @@ -118,20 +193,49 @@ fn lfs_clone(url: &str) -> io::Result<()> { Ok(()) } +fn libra_lfs_clone(url: &str) -> io::Result<()> { + let temp_dir = TempDir::new()?; + println!("clone temp_dir: {:?}", temp_dir.path()); + env::set_current_dir(temp_dir.path())?; + // libra clone url + run_libra_cmd(&["clone", url]); + + let file = Path::new("lfs-libra/large_file.bin"); + assert!(file.exists(), "Failed to clone large file"); + assert_eq!(file.metadata()?.len(), LARGE_FILE_SIZE_MB as u64 * 1024 * 1024); + Ok(()) +} + #[test] +#[serial] fn lfs_split_with_git() { assert!(check_git_lfs(), "git lfs is not installed"); let mega_dir = TempDir::new().unwrap(); - env::set_var("MEGA_BASE_DIR", mega_dir.path()); - // start mega server at background - run_mega_server(); - - // MonoRepo (mega)'s lfs.url is not compatible with git-lfs - let lfs_url = format!("http://localhost:{}", PORT); - run_git_cmd(&["config", "--global", "lfs.url", &lfs_url]); + // start mega server at background (new process) + let mut mega = run_mega_server(mega_dir.path()); let url = &format!("http://localhost:{}/third-part/lfs.git", PORT); - lfs_push(url).expect("Failed to push large file to mega server"); - lfs_clone(url).expect("Failed to clone large file from mega server"); + git_lfs_push(url).expect("Failed to push large file to mega server"); + git_lfs_clone(url).expect("Failed to clone large file from mega server"); + + mega.kill().expect("Failed to kill mega server"); +} + +#[test] +#[serial] +fn lfs_split_with_libra() { + if !LIBRA.exists() { + panic!("libra binary not found in \"target/debug/\", skip lfs test"); + } + + let mega_dir = TempDir::new().unwrap(); + // 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"); + + mega.kill().expect("Failed to kill mega server"); } \ No newline at end of file