diff --git a/Cargo.toml b/Cargo.toml index c94b9a964..478c88c06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,12 +39,12 @@ anyhow = "1.0.93" serde = "1.0.215" serde_json = "1.0.132" tracing = "0.1.40" -tracing-subscriber = "0.3.18" +tracing-subscriber = "0.3.19" tracing-appender = "0.2" -thiserror = "2.0.3" +thiserror = "2.0.4" rand = "0.8.5" smallvec = "1.13.2" -tokio = "1.41.1" +tokio = "1.42" tokio-stream = "0.1.16" tokio-test = "0.4.4" clap = "4.5.21" diff --git a/ceres/Cargo.toml b/ceres/Cargo.toml index 7ffc71a80..4ce20a65a 100644 --- a/ceres/Cargo.toml +++ b/ceres/Cargo.toml @@ -17,7 +17,7 @@ callisto = { workspace = true } mercury = { workspace = true } anyhow = { workspace = true } -tokio = { workspace = true, features = ["net"] } +tokio = { workspace = true, features = ["net", "process"] } tokio-stream = { workspace = true } axum = { workspace = true } tracing = { workspace = true } diff --git a/ceres/src/api_service/mono_api_service.rs b/ceres/src/api_service/mono_api_service.rs index dcbc7c9e1..582595a3c 100644 --- a/ceres/src/api_service/mono_api_service.rs +++ b/ceres/src/api_service/mono_api_service.rs @@ -1,8 +1,10 @@ use std::collections::{HashMap, VecDeque}; use std::path::{Path, PathBuf}; use std::str::FromStr; +use std::{env, fs}; use axum::async_trait; +use tokio::process::Command; use callisto::db_enums::ConvType; use callisto::{mega_blob, mega_tree, raw_blob}; @@ -354,6 +356,80 @@ impl MonoApiService { .unwrap(); Ok(p_commit_id) } + + pub async fn content_diff(&self, mr_link: &str) -> Result { + let stg = self.context.mr_stg(); + if let Some(mr) = stg.get_mr(mr_link).await.unwrap() { + let base_path = self.context.config.base_dir.clone(); + env::set_current_dir(&base_path).unwrap(); + let clone_path = base_path.join(mr_link); + if fs::exists(&clone_path).unwrap() { + fs::remove_dir_all(&clone_path).unwrap(); + } + Command::new("mkdir") + .arg(mr_link) + .output() + .await + .expect("Failed to mkdir"); + // cd QB0X1X1K + env::set_current_dir(&clone_path).unwrap(); + // libra init + Command::new("libra") + .arg("init") + .output() + .await + .expect("Failed to execute libra init"); + // libra remote add origin http://localhost:8000/project + Command::new("libra") + .arg("remote") + .arg("add") + .arg("origin") + .arg(format!("http://localhost:8000{}", mr.path)) + .output() + .await + .expect("Failed to execute libra remote add"); + // libra fetch origin QB0X1X1K + Command::new("libra") + .arg("fetch") + .arg("origin") + .arg(mr_link) + .output() + .await + .expect("Failed to execute libra fetch"); + // libra branch QB0X1X1K origin/QB0X1X1K + Command::new("libra") + .arg("branch") + .arg(mr_link) + .arg(format!("origin/{}", mr_link)) + .output() + .await + .expect("Failed to execute libra branch"); + // libra switch QB0X1X1K + Command::new("libra") + .arg("switch") + .arg(mr_link) + .output() + .await + .expect("Failed to execute libra switch"); + // libra diff --old 14899d8b9c36334a640c2e17255a546b0b9df105 + let output = Command::new("libra") + .arg("diff") + .arg("--old") + .arg(mr.from_hash) + .output() + .await + .expect("Failed to execute libra diff"); + if output.status.success() { + return Ok(String::from_utf8_lossy(&output.stdout).to_string()); + } else { + tracing::error!( + "Command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + } + Ok(String::new()) + } } pub struct TreeComparation { diff --git a/ceres/src/pack/import_repo.rs b/ceres/src/pack/import_repo.rs index 95034db73..3868692bd 100644 --- a/ceres/src/pack/import_repo.rs +++ b/ceres/src/pack/import_repo.rs @@ -55,7 +55,7 @@ impl PackHandler for ImportRepo { self.find_head_hash(refs) } - async fn handle_receiver(&self, receiver: Receiver) -> Result { + async fn handle_receiver(&self, receiver: Receiver) -> Result, GitError> { let storage = self.context.services.git_db_storage.clone(); let mut entry_list = vec![]; let mut join_tasks = vec![]; @@ -65,25 +65,19 @@ impl PackHandler for ImportRepo { if entry_list.len() >= 10000 { let stg_clone = storage.clone(); let handle = tokio::spawn(async move { - stg_clone - .save_entry(repo_id, entry_list) - .await - .unwrap(); + stg_clone.save_entry(repo_id, entry_list).await.unwrap(); }); join_tasks.push(handle); entry_list = vec![]; } } join_all(join_tasks).await; - storage - .save_entry(repo_id, entry_list) - .await - .unwrap(); + storage.save_entry(repo_id, entry_list).await.unwrap(); self.attach_to_monorepo_parent().await.unwrap(); - Ok(String::new()) + Ok(None) } - async fn full_pack(&self) -> Result>, GitError> { + async fn full_pack(&self, _: Vec) -> Result>, GitError> { let pack_config = &self.context.config.pack; let (entry_tx, entry_rx) = mpsc::channel(pack_config.channel_message_size); let (stream_tx, stream_rx) = mpsc::channel(pack_config.channel_message_size); @@ -294,12 +288,16 @@ impl PackHandler for ImportRepo { .await } - async fn handle_mr(&self, _: &str) -> Result<(), GitError> { - // do nothing in import_repo - Ok(()) + async fn handle_mr(&self, _: &str) -> Result { + unreachable!() } - async fn update_refs(&self, refs: &RefCommand) -> Result<(), GitError> { + async fn update_refs( + &self, + _: Option, + _: Option, + refs: &RefCommand, + ) -> Result<(), GitError> { let storage = self.context.services.git_db_storage.clone(); match refs.command_type { CommandType::Create => { diff --git a/ceres/src/pack/mod.rs b/ceres/src/pack/mod.rs index 87d057ba3..bd0be5efd 100644 --- a/ceres/src/pack/mod.rs +++ b/ceres/src/pack/mod.rs @@ -19,7 +19,7 @@ use common::{ errors::{MegaError, ProtocolError}, utils::ZERO_ID, }; -use mercury::internal::pack::Pack; +use mercury::internal::{object::commit::Commit, pack::Pack}; use mercury::{ errors::GitError, internal::{ @@ -38,7 +38,7 @@ pub mod monorepo; pub trait PackHandler: Send + Sync { async fn head_hash(&self) -> (String, Vec); - async fn handle_receiver(&self, rx: Receiver) -> Result; + async fn handle_receiver(&self, rx: Receiver) -> Result, GitError>; /// Asynchronously retrieves the full pack data for the specified repository path. /// This function collects commits and nodes from the storage and packs them into @@ -48,7 +48,7 @@ pub trait PackHandler: Send + Sync { /// # Returns /// * `Result, GitError>` - The packed binary data as a vector of bytes. /// - async fn full_pack(&self) -> Result>, GitError>; + async fn full_pack(&self, want: Vec) -> Result>, GitError>; async fn incremental_pack( &self, @@ -63,9 +63,14 @@ pub trait PackHandler: Send + Sync { hashes: Vec, ) -> Result, MegaError>; - async fn handle_mr(&self, title: &str) -> Result<(), GitError>; + async fn handle_mr(&self, title: &str) -> Result; - async fn update_refs(&self, refs: &RefCommand) -> Result<(), GitError>; + async fn update_refs( + &self, + mr_link: Option, + commit: Option, + refs: &RefCommand, + ) -> Result<(), GitError>; async fn check_commit_exist(&self, hash: &str) -> bool; diff --git a/ceres/src/pack/monorepo.rs b/ceres/src/pack/monorepo.rs index fd057a608..e37acafa4 100644 --- a/ceres/src/pack/monorepo.rs +++ b/ceres/src/pack/monorepo.rs @@ -34,7 +34,6 @@ use crate::{ pack::PackHandler, protocol::{ import_refs::{RefCommand, Refs}, - mega_refs::MegaRefs, mr::MergeRequest, }, }; @@ -51,10 +50,10 @@ impl PackHandler for MonoRepo { async fn head_hash(&self) -> (String, Vec) { let storage = self.context.services.mono_storage.clone(); - let result = storage.get_ref(self.path.to_str().unwrap()).await.unwrap(); - let refs = if result.is_some() { - let mega_refs: MegaRefs = result.unwrap().into(); - vec![mega_refs.into()] + let result = storage.get_refs(self.path.to_str().unwrap()).await.unwrap(); + let refs = if !result.is_empty() { + let refs: Vec = result.into_iter().map(|x| x.into()).collect(); + refs } else { let target_path = self.path.clone(); let refs = storage.get_ref("/").await.unwrap().unwrap(); @@ -105,6 +104,7 @@ impl PackHandler for MonoRepo { storage .save_ref( self.path.to_str().unwrap(), + None, &c.id.to_plain_str(), &c.tree_id.to_plain_str(), ) @@ -122,18 +122,18 @@ impl PackHandler for MonoRepo { self.find_head_hash(refs) } - async fn handle_receiver(&self, receiver: Receiver) -> Result { + async fn handle_receiver(&self, receiver: Receiver) -> Result, GitError> { let storage = self.context.services.mono_storage.clone(); let mut entry_list = Vec::new(); let mut join_tasks = vec![]; let mut current_commit_id = String::new(); - let mut mr_title = String::new(); + let mut current_commit = None; for entry in receiver { - if current_commit_id.is_empty() { + if current_commit.is_none() { if entry.obj_type == ObjectType::Commit { current_commit_id = entry.hash.to_plain_str(); let commit = Commit::from_bytes(&entry.data, entry.hash).unwrap(); - mr_title = commit.format_message(); + current_commit = Some(commit); } } else { if entry.obj_type == ObjectType::Commit { @@ -158,14 +158,15 @@ impl PackHandler for MonoRepo { .save_entry(¤t_commit_id, entry_list) .await .unwrap(); - Ok(mr_title) + Ok(current_commit) } // monorepo full pack should follow the shallow clone command 'git clone --depth=1' - async fn full_pack(&self) -> Result>, GitError> { + async fn full_pack(&self, want: Vec) -> Result>, GitError> { let pack_config = &self.context.config.pack; let storage = self.context.services.mono_storage.clone(); let obj_num = AtomicUsize::new(0); + let mut trees = Vec::new(); let refs = storage .get_ref(self.path.to_str().unwrap()) @@ -184,18 +185,51 @@ impl PackHandler for MonoRepo { .unwrap() .unwrap() .into(); - self.traverse_for_count(tree.clone(), &HashSet::new(), &mut HashSet::new(), &obj_num) + trees.push(tree.clone()); + let mut exist_objs = HashSet::new(); + let mut counted_obj = HashSet::new(); + self.traverse_for_count(tree.clone(), &exist_objs, &mut counted_obj, &obj_num) .await; - obj_num.fetch_add(1, Ordering::SeqCst); + exist_objs.extend(counted_obj.clone()); + let (entry_tx, entry_rx) = mpsc::channel(pack_config.channel_message_size); let (stream_tx, stream_rx) = mpsc::channel(pack_config.channel_message_size); + let want_c = want.first().unwrap(); + if refs.ref_commit_hash != *want_c { + let refs = storage + .get_ref_by_commit(self.path.to_str().unwrap(), want_c) + .await + .unwrap() + .unwrap(); + let commit: Commit = storage + .get_commit_by_hash(&refs.ref_commit_hash) + .await + .unwrap() + .unwrap() + .into(); + let tree: Tree = storage + .get_tree_by_hash(&refs.ref_tree_hash) + .await + .unwrap() + .unwrap() + .into(); + trees.push(tree.clone()); + self.traverse_for_count(tree, &exist_objs, &mut counted_obj, &obj_num) + .await; + obj_num.fetch_add(1, Ordering::SeqCst); + entry_tx.send(commit.into()).await.unwrap(); + } + let encoder = PackEncoder::new(obj_num.into_inner(), 0, stream_tx); encoder.encode_async(entry_rx).await.unwrap(); - self.traverse(tree, &mut HashSet::new(), Some(&entry_tx)) - .await; + let mut send_exist = HashSet::new(); + for tree in trees { + self.traverse(tree, &mut send_exist, Some(&entry_tx)) + .await; + } entry_tx.send(commit.into()).await.unwrap(); drop(entry_tx); Ok(ReceiverStream::new(stream_rx)) @@ -318,7 +352,7 @@ impl PackHandler for MonoRepo { .await } - async fn handle_mr(&self, title: &str) -> Result<(), GitError> { + async fn handle_mr(&self, title: &str) -> Result { let storage = self.context.mr_stg(); let path_str = self.path.to_str().unwrap(); @@ -338,18 +372,40 @@ impl PackHandler for MonoRepo { path: path_str.to_owned(), from_hash: self.from_hash.clone(), to_hash: self.to_hash.clone(), - link, + link: link.clone(), title: title.to_string(), ..Default::default() }; storage.save_mr(mr.clone().into()).await.unwrap(); - Ok(()) + Ok(link) } } } - async fn update_refs(&self, _: &RefCommand) -> Result<(), GitError> { - //do nothing in monorepo because we use mr to handle refs update + async fn update_refs( + &self, + mr_link: Option, + commit: Option, + refs: &RefCommand, + ) -> Result<(), GitError> { + let ref_name = utils::mr_ref_name(&mr_link.unwrap()); + + let storage = self.context.services.mono_storage.clone(); + if let Some(mut mr_ref) = storage.get_mr_ref(&ref_name).await.unwrap() { + mr_ref.ref_commit_hash = refs.new_id.clone(); + mr_ref.ref_tree_hash = commit.unwrap().tree_id.to_plain_str(); + storage.update_ref(mr_ref).await.unwrap(); + } else { + storage + .save_ref( + self.path.to_str().unwrap(), + Some(ref_name), + &refs.new_id, + &commit.unwrap().tree_id.to_plain_str(), + ) + .await + .unwrap(); + } Ok(()) } @@ -373,7 +429,7 @@ impl MonoRepo { &self, mr: &mut MergeRequest, storage: &MrStorage, - ) -> Result<(), GitError> { + ) -> Result { if mr.from_hash == self.from_hash { if mr.to_hash != self.to_hash { let comment = self.comment_for_force_update(&mr.to_hash, &self.to_hash); @@ -399,7 +455,7 @@ impl MonoRepo { } storage.update_mr(mr.clone().into()).await.unwrap(); - Ok(()) + Ok(mr.link.clone()) } fn comment_for_force_update(&self, from: &str, to: &str) -> String { diff --git a/ceres/src/protocol/import_refs.rs b/ceres/src/protocol/import_refs.rs index d82015ecf..523cb2d14 100644 --- a/ceres/src/protocol/import_refs.rs +++ b/ceres/src/protocol/import_refs.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; -use callisto::{db_enums::RefType, import_refs}; -use common::utils::{generate_id, ZERO_ID}; +use callisto::{db_enums::RefType, import_refs, mega_refs}; +use common::utils::{generate_id, MEGA_BRANCH_NAME, ZERO_ID}; /// /// Represent the references(all branches and tags) in protocol transfer @@ -25,6 +25,18 @@ impl From for Refs { } } + +impl From for Refs { + fn from(value: mega_refs::Model) -> Self { + Self { + id: value.id, + ref_name: value.ref_name.clone(), + ref_hash: value.ref_commit_hash, + default_branch: value.ref_name == MEGA_BRANCH_NAME, + } + } +} + #[derive(Debug, Clone, PartialEq)] pub enum CommandType { Create, @@ -111,3 +123,17 @@ impl From for import_refs::Model { } } } + +impl From for mega_refs::Model { + fn from(value: RefCommand) -> Self { + mega_refs::Model { + id: generate_id(), + path: String::new(), + ref_name: value.ref_name, + ref_commit_hash: value.new_id, + ref_tree_hash: String::new(), + created_at: chrono::Utc::now().naive_utc(), + updated_at: chrono::Utc::now().naive_utc(), + } + } +} diff --git a/ceres/src/protocol/mega_refs.rs b/ceres/src/protocol/mega_refs.rs deleted file mode 100644 index cf8324fe5..000000000 --- a/ceres/src/protocol/mega_refs.rs +++ /dev/null @@ -1,50 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use callisto::mega_refs; - -use common::utils::MEGA_BRANCH_NAME; - -use crate::protocol::import_refs::Refs; - -#[derive(Clone, Serialize, Deserialize, Default)] -pub struct MegaRefs { - pub id: i64, - pub path: String, - pub ref_commit_hash: String, - pub ref_tree_hash: String, -} - -impl From for MegaRefs { - fn from(value: mega_refs::Model) -> Self { - Self { - id: value.id, - path: value.path, - ref_commit_hash: value.ref_commit_hash, - ref_tree_hash: value.ref_tree_hash, - } - } -} - -impl From for mega_refs::Model { - fn from(value: MegaRefs) -> Self { - Self { - id: value.id, - path: value.path, - ref_commit_hash: value.ref_commit_hash, - ref_tree_hash: value.ref_tree_hash, - created_at: chrono::Utc::now().naive_utc(), - updated_at: chrono::Utc::now().naive_utc(), - } - } -} - -impl From for Refs { - fn from(value: MegaRefs) -> Self { - Self { - id: value.id, - ref_hash: value.ref_commit_hash, - default_branch: true, - ref_name: MEGA_BRANCH_NAME.to_owned(), - } - } -} diff --git a/ceres/src/protocol/mod.rs b/ceres/src/protocol/mod.rs index 9299d6bac..58052b16d 100644 --- a/ceres/src/protocol/mod.rs +++ b/ceres/src/protocol/mod.rs @@ -16,7 +16,6 @@ pub mod smart; pub mod repo; pub mod import_refs; pub mod mr; -pub mod mega_refs; #[derive(Clone)] pub struct SmartProtocol { diff --git a/ceres/src/protocol/smart.rs b/ceres/src/protocol/smart.rs index bef7dda32..e0f167860 100644 --- a/ceres/src/protocol/smart.rs +++ b/ceres/src/protocol/smart.rs @@ -81,7 +81,7 @@ impl SmartProtocol { ref_list.push(pkt_line); } let pkt_line_stream = self.build_smart_reply(&ref_list, service_type.to_string()); - tracing::debug!("git_info_refs response: {:?}", pkt_line_stream); + tracing::debug!("git_info_refs, return: --------> {:?}", pkt_line_stream); Ok(pkt_line_stream) } @@ -142,7 +142,7 @@ impl SmartProtocol { let mut protocol_buf = BytesMut::new(); if have.is_empty() { - pack_data = pack_handler.full_pack().await.unwrap(); + pack_data = pack_handler.full_pack(want.clone()).await.unwrap(); add_pkt_line_string(&mut protocol_buf, String::from("NAK\n")); } else { if self.capabilities.contains(&Capability::MultiAckDetailed) { @@ -234,22 +234,31 @@ impl SmartProtocol { for command in &mut self.command_list { if command.ref_type == RefType::Tag { // just update if refs type is tag - pack_handler.update_refs(command).await.unwrap(); + pack_handler.update_refs(None, None, command).await.unwrap(); } else { // Updates can be unsuccessful for a number of reasons. // a.The reference can have changed since the reference discovery phase was originally sent, meaning someone pushed in the meantime. // b.The reference being pushed could be a non-fast-forward reference and the update hooks or configuration could be set to not allow that, etc. // c.Also, some references can be updated while others can be rejected. match unpack_result { - Ok(ref title) => { - if let Err(e) = pack_handler.handle_mr(title).await { - command.failed(e.to_string()); + Ok(ref commit) => { + if let Some(c) = commit { + let mr_title = c.format_message(); + if let Ok(mr_link) = pack_handler.handle_mr(&mr_title).await { + pack_handler + .update_refs(Some(mr_link), Some(c.clone()), command) + .await + .unwrap(); + } else if let Err(e) = pack_handler.handle_mr(&mr_title).await { + command.failed(e.to_string()); + } + } else { + if !default_exist { + command.default_branch = true; + default_exist = true; + } + pack_handler.update_refs(None, None, command).await.unwrap(); } - if !default_exist { - command.default_branch = true; - default_exist = true; - } - pack_handler.update_refs(command).await.unwrap(); } Err(ref err) => { command.failed(err.to_string()); diff --git a/common/src/utils.rs b/common/src/utils.rs index fc2453204..55982ebb9 100644 --- a/common/src/utils.rs +++ b/common/src/utils.rs @@ -40,3 +40,7 @@ pub fn generate_rich_text(content: &str) -> String { } serde_json::to_string_pretty(&data).expect("Failed to serialize JSON") } + +pub fn mr_ref_name(mr_link: &str) -> String { + format!("refs/heads/{}", mr_link) +} diff --git a/docker/README.md b/docker/README.md index fb58fcd6b..f2d957e09 100644 --- a/docker/README.md +++ b/docker/README.md @@ -28,25 +28,14 @@ docker buildx build -t mega-engine:0.1-pre-release -f ./docker/mega-engine-docke ```bash # Linux or MacOS -./init-volume.sh /mnt/data ./config.toml +sudo ./docker/init-volume.sh /mnt/data ./docker/config.toml # Windows # Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser # .\init-volume.ps1 -baseDir "D:\" -configFile ".\config.toml" ``` -[2] Start whole mono engine stack on local for testing -```bash -# create network -docker network create mono-network - -# run postgres -docker run --rm -it -d --name mono-pg --network mono-network -v /tmp/data/mono/pg-data:/var/lib/postgresql/data -p 5432:5432 mono-pg:0.1-pre-release -docker run --rm -it -d --name mono-engine --network mono-network -v /tmp/data/mono/mono-data:/opt/mega -p 8000:8000 mono-engine:0.1-pre-release -docker run --rm -it -d --name mono-ui --network mono-network -e MEGA_INTERNAL_HOST=http://mono-engine:8000 -e MEGA_HOST=http://localhost:8000 -p 3000:3000 mono-ui:0.1-pre-release -``` - -[3] Start whole mono engine stack on server with domain +[2] Start whole mono engine stack on server with domain ```bash # create network @@ -58,7 +47,7 @@ docker run --rm -it -d --name mono-engine --network mono-network --memory=8g -v docker run --rm -it -d --name mono-ui --network mono-network --memory=1g -e MEGA_INTERNAL_HOST=http://mono-engine:8000 -e MEGA_HOST=https://git.gitmono.com -p 3000:3000 mono-ui:0.1-pre-release ``` -[4] Nginx configuration for Mono +[3] Nginx configuration for Mono ```Nginx server { diff --git a/docker/mono-pg-dockerfile b/docker/mono-pg-dockerfile index fba17cc5b..874c18a81 100644 --- a/docker/mono-pg-dockerfile +++ b/docker/mono-pg-dockerfile @@ -17,6 +17,6 @@ EXPOSE 5432 # Add the database initialization script to the container # When the container starts, PostgreSQL will automatically execute all .sql files in the docker-entrypoint-initdb.d/ directory -COPY ./sql/postgres/pg_20241029__init.sql /docker-entrypoint-initdb.d/ +COPY ./sql/postgres/pg_20241204__init.sql /docker-entrypoint-initdb.d/ CMD ["postgres"] diff --git a/jupiter/callisto/src/mega_refs.rs b/jupiter/callisto/src/mega_refs.rs index 4476f3626..1ade9d10b 100644 --- a/jupiter/callisto/src/mega_refs.rs +++ b/jupiter/callisto/src/mega_refs.rs @@ -9,6 +9,8 @@ pub struct Model { pub id: i64, #[sea_orm(column_type = "Text", unique)] pub path: String, + #[sea_orm(column_type = "Text")] + pub ref_name: String, pub ref_commit_hash: String, pub ref_tree_hash: String, pub created_at: DateTime, diff --git a/jupiter/src/storage/init.rs b/jupiter/src/storage/init.rs index 48b1bae4d..1cd2ebfcc 100644 --- a/jupiter/src/storage/init.rs +++ b/jupiter/src/storage/init.rs @@ -51,7 +51,7 @@ async fn setup_sql(conn: &DatabaseConnection) -> Result<(), TransactionError, ref_commit_hash: &str, ref_tree_hash: &str, ) -> Result<(), MegaError> { let model = mega_refs::Model { id: generate_id(), path: path.to_owned(), + ref_name: ref_name.unwrap_or(MEGA_BRANCH_NAME.to_owned()), ref_commit_hash: ref_commit_hash.to_owned(), ref_tree_hash: ref_tree_hash.to_owned(), created_at: chrono::Utc::now().naive_utc(), @@ -82,14 +83,48 @@ impl MonoStorage { Ok(()) } - pub async fn get_ref(&self, path: &str) -> Result, MegaError> { + pub async fn get_refs(&self, path: &str) -> Result, MegaError> { let result = mega_refs::Entity::find() .filter(mega_refs::Column::Path.eq(path)) + .order_by_asc(mega_refs::Column::RefName) + .all(self.get_connection()) + .await?; + Ok(result) + } + + pub async fn get_ref( + &self, + path: &str, + ) -> Result, MegaError> { + let result = mega_refs::Entity::find() + .filter(mega_refs::Column::Path.eq(path)) + .filter(mega_refs::Column::RefName.eq(MEGA_BRANCH_NAME.to_owned())) + .one(self.get_connection()) + .await?; + Ok(result) + } + + pub async fn get_ref_by_commit( + &self, + path: &str, + commit: &str, + ) -> Result, MegaError> { + let result = mega_refs::Entity::find() + .filter(mega_refs::Column::Path.eq(path)) + .filter(mega_refs::Column::RefCommitHash.eq(commit)) .one(self.get_connection()) .await?; Ok(result) } + pub async fn get_mr_ref(&self, ref_name: &str) -> Result, MegaError> { + let res = mega_refs::Entity::find() + .filter(mega_refs::Column::RefName.eq(ref_name)) + .one(self.get_connection()) + .await?; + Ok(res) + } + pub async fn update_ref(&self, refs: mega_refs::Model) -> Result<(), MegaError> { let mut ref_data: mega_refs::ActiveModel = refs.into(); ref_data.reset(mega_refs::Column::RefCommitHash); diff --git a/jupiter/src/utils/converter.rs b/jupiter/src/utils/converter.rs index 736e3538f..61b9a68d6 100644 --- a/jupiter/src/utils/converter.rs +++ b/jupiter/src/utils/converter.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use callisto::{mega_blob, mega_refs, mega_tree, raw_blob}; use common::config::MonoConfig; -use common::utils::generate_id; +use common::utils::{generate_id, MEGA_BRANCH_NAME}; use mercury::hash::SHA1; use mercury::internal::object::blob::Blob; use mercury::internal::object::commit::Commit; @@ -106,6 +106,7 @@ impl MegaModelConverter { let mega_ref = mega_refs::Model { id: generate_id(), path: "/".to_owned(), + ref_name: MEGA_BRANCH_NAME.to_owned(), ref_commit_hash: commit.id.to_plain_str(), ref_tree_hash: commit.tree_id.to_plain_str(), created_at: chrono::Utc::now().naive_utc(), diff --git a/mono/src/api/mr/mr_router.rs b/mono/src/api/mr/mr_router.rs index 40156ecc2..fa64d1b85 100644 --- a/mono/src/api/mr/mr_router.rs +++ b/mono/src/api/mr/mr_router.rs @@ -28,6 +28,7 @@ pub fn routers() -> Router { .route("/mr/:link/close", post(close_mr)) .route("/mr/:link/reopen", post(reopen_mr)) .route("/mr/:link/files", get(get_mr_files)) + .route("/mr/:link/files-changed", get(get_mr_files_changed)) .route("/mr/:link/comment", post(save_comment)) .route("/mr/comment/:conv_id/delete", post(delete_comment)) } @@ -184,6 +185,18 @@ async fn get_mr_files( Ok(Json(res)) } +async fn get_mr_files_changed( + Path(link): Path, + state: State, +) -> Result>, ApiError> { + let res = state.monorepo().content_diff(&link).await; + let res = match res { + Ok(data) => CommonResult::success(Some(data)), + Err(err) => CommonResult::failed(&err.to_string()), + }; + Ok(Json(res)) +} + async fn save_comment( user: LoginUser, Path(link): Path, @@ -196,7 +209,12 @@ async fn save_comment( let res = if let Some(model) = state.mr_stg().get_mr(&link).await.unwrap() { state .mr_stg() - .add_mr_conversation(&model.link, user.user_id, ConvType::Comment, Some(json_string)) + .add_mr_conversation( + &model.link, + user.user_id, + ConvType::Comment, + Some(json_string), + ) .await .unwrap(); CommonResult::success(None) diff --git a/mono/src/git_protocol/http.rs b/mono/src/git_protocol/http.rs index 9b1b36345..7a3fdf2cc 100644 --- a/mono/src/git_protocol/http.rs +++ b/mono/src/git_protocol/http.rs @@ -130,13 +130,13 @@ pub async fn git_upload_pack( }) .await .unwrap(); - tracing::debug!("bytes from client: {:?}", upload_request); + tracing::debug!("Receive bytes: <-------- {:?}", upload_request); let (mut send_pack_data, protocol_buf) = pack_protocol .git_upload_pack(&mut upload_request.freeze()) .await?; let body_stream = async_stream::stream! { - tracing::info!("send ack/nak message buf: {:?}", &protocol_buf); + tracing::info!("send ack/nak message buf: --------> {:?}", &protocol_buf); yield Ok::<_, Infallible>(Bytes::copy_from_slice(&protocol_buf)); // send packdata with sideband64k while let Some(chunk) = send_pack_data.next().await { diff --git a/moon/package.json b/moon/package.json index 1978f7750..e14b6c819 100644 --- a/moon/package.json +++ b/moon/package.json @@ -9,20 +9,21 @@ "lint": "next lint" }, "dependencies": { - "@ant-design/icons": "^5.5.1", + "@ant-design/icons": "^5.5.2", "@ant-design/nextjs-registry": "^1.0.2", "@headlessui/react": "^2.2.0", "@headlessui/tailwindcss": "^0.2.1", "@heroicons/react": "^2.2.0", - "@lexical/react": "^0.20.0", + "@lexical/react": "^0.21.0", "@tailwindcss/forms": "^0.5.9", "clsx": "^2.1.1", "copy-to-clipboard": "^3.3.3", "date-fns": "^4.1.0", - "framer-motion": "^11.11.17", - "github-markdown-css": "^5.8.0", + "diff2html": "^3.4.48", + "framer-motion": "^11.13.1", + "github-markdown-css": "^5.8.1", "highlight.js": "^11.10.0", - "lexical": "^0.20.0", + "lexical": "^0.21.0", "next": "^15.0.3", "prism-react-renderer": "^2.4.0", "react": "^18.3.1", @@ -30,11 +31,11 @@ "react-markdown": "^9.0.1" }, "devDependencies": { - "@types/node": "^22.9.1", - "@types/react": "^18.3.12", + "@types/node": "^22.10.1", + "@types/react": "^18.3.13", "@types/react-dom": "^18.3.0", "autoprefixer": "^10.4.19", - "eslint": "^9.15.0", + "eslint": "^9.16.0", "eslint-config-next": "^15.0.3", "postcss": "^8.4.49", "typescript": "^5.6.3" diff --git a/moon/src/app/(dashboard)/application-layout.tsx b/moon/src/app/(dashboard)/application-layout.tsx index 9e36a20b2..7cbcca382 100644 --- a/moon/src/app/(dashboard)/application-layout.tsx +++ b/moon/src/app/(dashboard)/application-layout.tsx @@ -15,7 +15,6 @@ import { SidebarBody, SidebarFooter, SidebarHeader, - SidebarHeading, SidebarItem, SidebarLabel, SidebarSection, @@ -24,11 +23,8 @@ import { import { SidebarLayout } from '@/components/catalyst/sidebar-layout' import { ArrowRightStartOnRectangleIcon, - ChevronDownIcon, ChevronUpIcon, - Cog8ToothIcon, LightBulbIcon, - PlusIcon, ShieldCheckIcon, UserCircleIcon, KeyIcon, diff --git a/moon/src/app/(dashboard)/mr/[id]/page.tsx b/moon/src/app/(dashboard)/mr/[id]/page.tsx index 3f0343b25..f05e60ccc 100644 --- a/moon/src/app/(dashboard)/mr/[id]/page.tsx +++ b/moon/src/app/(dashboard)/mr/[id]/page.tsx @@ -7,6 +7,8 @@ import RichEditor from "@/components/rich-editor/RichEditor"; import MRComment from "@/components/MRComment"; import * as React from 'react' import { useRouter } from "next/navigation"; +import * as Diff2Html from 'diff2html'; +import 'diff2html/bundles/css/diff2html.min.css'; interface MRDetail { status: string, @@ -38,6 +40,7 @@ export default function MRDetailPage({ params }: { params: Params }) { const [filedata, setFileData] = useState([]); const [loadings, setLoadings] = useState([]); const router = useRouter(); + const [outputHtml, setOutputHtml] = useState(""); const checkLogin = async () => { const res = await fetch(`/api/auth`); @@ -61,10 +64,17 @@ export default function MRDetailPage({ params }: { params: Params }) { } }, [id]); + const get_diff_content = useCallback(async () => { + const detail = await fetch(`/api/mr/${id}/files-changed`); + const res = await detail.json(); + setOutputHtml(Diff2Html.html(res.data.data, { drawFileList: true, matching: 'lines' })); + }, []) + useEffect(() => { fetchDetail() fetchFileList(); checkLogin(); + get_diff_content() }, [id, fetchDetail, fetchFileList]); const set_to_loading = (index: number) => { @@ -178,7 +188,7 @@ export default function MRDetailPage({ params }: { params: Params }) { key: '2', label: 'Files Changed', children: - Change File List} bordered dataSource={filedata} @@ -188,6 +198,10 @@ export default function MRDetailPage({ params }: { params: Params }) { {item} )} + /> */} +
} diff --git a/moon/src/app/api/mr/[id]/files-changed/route.ts b/moon/src/app/api/mr/[id]/files-changed/route.ts new file mode 100644 index 000000000..245a9e716 --- /dev/null +++ b/moon/src/app/api/mr/[id]/files-changed/route.ts @@ -0,0 +1,14 @@ +export const dynamic = 'force-dynamic' // defaults to auto + +type Params = Promise<{ id: string }> + +export async function GET(request: Request, props: { params: Params }) { + const params = await props.params; + + const endpoint = process.env.MEGA_INTERNAL_HOST; + const res = await fetch(`${endpoint}/api/v1/mr/${params.id}/files-changed`, { + }) + const data = await res.json() + + return Response.json({ data }) +} \ No newline at end of file diff --git a/sql/postgres/pg_20241029__init.sql b/sql/postgres/pg_20241204__init.sql similarity index 99% rename from sql/postgres/pg_20241029__init.sql rename to sql/postgres/pg_20241204__init.sql index d8b5bc915..2f42bdf4e 100644 --- a/sql/postgres/pg_20241029__init.sql +++ b/sql/postgres/pg_20241204__init.sql @@ -80,11 +80,12 @@ CREATE INDEX "idx_issue" ON "mega_issue" ("link"); CREATE TABLE IF NOT EXISTS "mega_refs" ( "id" BIGINT PRIMARY KEY, "path" TEXT NOT NULL, + "ref_name" TEXT NOT NULL, "ref_commit_hash" VARCHAR(40) NOT NULL, "ref_tree_hash" VARCHAR(40) NOT NULL, "created_at" TIMESTAMP NOT NULL, "updated_at" TIMESTAMP NOT NULL, - CONSTRAINT uniq_mref_path UNIQUE (path) + CONSTRAINT uniq_mref_path UNIQUE (path, ref_name) ); CREATE TABLE IF NOT EXISTS "import_refs" ( "id" BIGINT PRIMARY KEY, diff --git a/sql/sqlite/sqlite_20241029_init.sql b/sql/sqlite/sqlite_20241204_init.sql similarity index 99% rename from sql/sqlite/sqlite_20241029_init.sql rename to sql/sqlite/sqlite_20241204_init.sql index c8793ea08..3bd0e58e4 100644 --- a/sql/sqlite/sqlite_20241029_init.sql +++ b/sql/sqlite/sqlite_20241204_init.sql @@ -78,11 +78,12 @@ CREATE TABLE IF NOT EXISTS "mega_issue" ( CREATE TABLE IF NOT EXISTS "mega_refs" ( "id" INTEGER PRIMARY KEY, "path" TEXT NOT NULL, + "ref_name" TEXT NOT NULL, "ref_commit_hash" TEXT NOT NULL, "ref_tree_hash" TEXT NOT NULL, "created_at" TEXT NOT NULL, "updated_at" TEXT NOT NULL, - CONSTRAINT uniq_mref_path UNIQUE (path) + CONSTRAINT uniq_mref_path UNIQUE (path, ref_name) ); CREATE TABLE IF NOT EXISTS "import_refs" ( "id" INTEGER PRIMARY KEY,