Repository: web3infra-foundation/mega (language: rust)

Issue:
[Moon UI] Hide Clone Button in Non-Repository Directories
Currently, the clone button is displayed at every directory level, but attempting to clone from non-repository directories results in a 404 error. The clone button should be hidden in any directory that is not a repository to prevent confusion and improve user experience.


Discussion / hints:

Fix patch:
diff --git a/ceres/src/api_service/mono_api_service.rs b/ceres/src/api_service/mono_api_service.rs
index 889c9db1..d1f1ef03 100644
--- a/ceres/src/api_service/mono_api_service.rs
+++ b/ceres/src/api_service/mono_api_service.rs
@@ -207,7 +207,6 @@ impl MonoApiService {
             vec![MergeStatus::Closed, MergeStatus::Merged]
         } else {
             vec![MergeStatus::Open, MergeStatus::Closed, MergeStatus::Merged]
-            // return Err(MegaError::with_message("Invalid status name"));
         };
         let storage = self.context.services.mono_storage.clone();
         let mr_list = storage.get_mr_by_status(status).await.unwrap();
@@ -301,7 +300,7 @@ impl MonoApiService {
 
                 // add conversation
                 storage
-                    .add_mr_conversation(&mr.mr_link, 0, ConvType::Merged)
+                    .add_mr_conversation(&mr.mr_link, 0, ConvType::Merged, None)
                     .await
                     .unwrap();
                 if mr.path != "/" {
diff --git a/ceres/src/model/mr.rs b/ceres/src/model/mr.rs
index c02228fa..f441b257 100644
--- a/ceres/src/model/mr.rs
+++ b/ceres/src/model/mr.rs
@@ -40,6 +40,7 @@ pub struct MRDetail {
 pub struct MRConversion {
     pub user_id: i64,
     pub conv_type: String,
+    pub comment: Option<String>,
     pub created_at: i64,
     pub updated_at: i64,
 }
@@ -49,7 +50,7 @@ impl From<mega_mr::Model> for MRDetail {
         Self {
             id: value.id,
             mr_link: value.mr_link,
-            title: String::new(),
+            title: value.title,
             status: value.status.to_string(),
             open_timestamp: value.created_at.and_utc().timestamp(),
             merge_timestamp: value.merge_date.map(|dt| dt.and_utc().timestamp()),
@@ -63,6 +64,7 @@ impl From<mega_mr_conv::Model> for MRConversion {
         Self {
             user_id: value.user_id,
             conv_type: value.conv_type.to_string(),
+            comment: value.comment,
             created_at: value.created_at.and_utc().timestamp(),
             updated_at: value.updated_at.and_utc().timestamp(),
         }
diff --git a/ceres/src/pack/monorepo.rs b/ceres/src/pack/monorepo.rs
index 61946046..db1d4194 100644
--- a/ceres/src/pack/monorepo.rs
+++ b/ceres/src/pack/monorepo.rs
@@ -15,10 +15,10 @@ use rand::{distributions::Alphanumeric, thread_rng, Rng};
 use tokio::sync::mpsc;
 use tokio_stream::wrappers::ReceiverStream;
 
-use callisto::raw_blob;
+use callisto::{db_enums::ConvType, raw_blob};
 use common::{errors::MegaError, utils::MEGA_BRANCH_NAME};
 use jupiter::{context::Context, storage::mono_storage::MonoStorage};
-use mercury::internal::pack::encode::PackEncoder;
+use mercury::internal::{object::ObjectTrait, pack::encode::PackEncoder};
 use mercury::{
     errors::GitError,
     hash::SHA1,
@@ -123,30 +123,33 @@ impl PackHandler for MonoRepo {
     async fn handle_receiver(&self, receiver: Receiver<Entry>) -> Result<(), GitError> {
         let storage = self.context.services.mono_storage.clone();
         let path_str = self.path.to_str().unwrap();
-        match storage.get_open_mr_by_path(path_str).await.unwrap() {
-            Some(mr) => {
-                let mut mr = mr.into();
-                self.handle_existing_mr(&mut mr, &storage, receiver).await
-            }
-            None => {
-                let mr_link: String = thread_rng()
-                    .sample_iter(&Alphanumeric)
-                    .take(8)
-                    .map(char::from)
-                    .collect();
-                let mr = MergeRequest {
-                    path: path_str.to_owned(),
-                    from_hash: self.from_hash.clone(),
-                    to_hash: self.to_hash.clone(),
-                    mr_link: mr_link.to_uppercase(),
-                    ..Default::default()
-                };
-                let unpack_res = self.save_entry(receiver).await;
-                if unpack_res.is_ok() {
+
+        let unpack_res = self.save_entry(receiver).await;
+        match unpack_res {
+            Ok(title) => match storage.get_open_mr_by_path(path_str).await.unwrap() {
+                Some(mr) => {
+                    let mut mr = mr.into();
+                    self.handle_existing_mr(&mut mr, &storage).await
+                }
+                None => {
+                    let mr_link: String = thread_rng()
+                        .sample_iter(&Alphanumeric)
+                        .take(8)
+                        .map(char::from)
+                        .collect();
+                    let mr = MergeRequest {
+                        path: path_str.to_owned(),
+                        from_hash: self.from_hash.clone(),
+                        to_hash: self.to_hash.clone(),
+                        mr_link: mr_link.to_uppercase(),
+                        title,
+                        ..Default::default()
+                    };
                     storage.save_mr(mr.clone().into()).await.unwrap();
+                    Ok(())
                 }
-                unpack_res
-            }
+            },
+            Err(err) => Err(err),
         }
     }
 
@@ -332,38 +335,25 @@ impl MonoRepo {
         &self,
         mr: &mut MergeRequest,
         storage: &MonoStorage,
-        receiver: Receiver<Entry>,
     ) -> Result<(), GitError> {
         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);
                 mr.to_hash = self.to_hash.clone();
                 storage
-                    .add_mr_comment(&mr.mr_link, 0, Some(comment))
+                    .add_mr_conversation(&mr.mr_link, 0, ConvType::Comment, Some(comment))
                     .await
                     .unwrap();
-
-                let unpack_res = self.save_entry(receiver).await;
-                if unpack_res.is_err() {
-                    mr.close();
-                    storage
-                        .add_mr_comment(
-                            &mr.mr_link,
-                            0,
-                            Some("Mega closed MR due to multi commit detected".to_string()),
-                        )
-                        .await
-                        .unwrap();
-                }
             } else {
                 tracing::info!("repeat commit with mr: {}, do nothing", mr.id);
             }
         } else {
             mr.close();
             storage
-                .add_mr_comment(
+                .add_mr_conversation(
                     &mr.mr_link,
                     0,
+                    ConvType::Comment,
                     Some("Mega closed MR due to conflict".to_string()),
                 )
                 .await
@@ -382,15 +372,18 @@ impl MonoRepo {
         )
     }
 
-    async fn save_entry(&self, receiver: Receiver<Entry>) -> Result<(), GitError> {
+    async fn save_entry(&self, receiver: Receiver<Entry>) -> Result<String, 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();
         for entry in receiver {
             if current_commit_id.is_empty() {
                 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();
                 }
             } else {
                 if entry.obj_type == ObjectType::Commit {
@@ -415,6 +408,6 @@ impl MonoRepo {
             .save_entry(&current_commit_id, entry_list)
             .await
             .unwrap();
-        Ok(())
+        Ok(mr_title)
     }
 }
diff --git a/ceres/src/protocol/mr.rs b/ceres/src/protocol/mr.rs
index 7c74f7ed..cd8b611e 100644
--- a/ceres/src/protocol/mr.rs
+++ b/ceres/src/protocol/mr.rs
@@ -7,6 +7,7 @@ use common::utils::generate_id;
 pub struct MergeRequest {
     pub id: i64,
     pub mr_link: String,
+    pub title: String,
     pub status: MergeStatus,
     pub merge_date: Option<NaiveDateTime>,
     pub path: String,
@@ -19,6 +20,7 @@ impl Default for MergeRequest {
         Self {
             id: generate_id(),
             mr_link: String::new(),
+            title: String::new(),
             status: MergeStatus::Open,
             merge_date: None,
             path: String::new(),
@@ -44,6 +46,7 @@ impl From<MergeRequest> for mega_mr::Model {
         Self {
             id: value.id,
             mr_link: value.mr_link,
+            title: value.title,
             status: value.status,
             merge_date: value.merge_date,
             path: value.path,
@@ -60,6 +63,7 @@ impl From<mega_mr::Model> for MergeRequest {
         Self {
             id: value.id,
             mr_link: value.mr_link,
+            title: value.title,
             status: value.status,
             merge_date: value.merge_date,
             path: value.path,
diff --git a/docker/start-moon.sh b/docker/start-moon.sh
index 6bb3a597..75b8029b 100755
--- a/docker/start-moon.sh
+++ b/docker/start-moon.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-# user must set the MEGA_HOST,MEGA_INTERNAL_HOST
+# user must set the MEGA_HOST, MEGA_INTERNAL_HOST
 if [ -z "$MEGA_HOST" ]; then 
   echo "MEGA_HOST is not set"
   exit 1
diff --git a/jupiter/callisto/src/lib.rs b/jupiter/callisto/src/lib.rs
index 1250d6e9..3884c3c7 100644
--- a/jupiter/callisto/src/lib.rs
+++ b/jupiter/callisto/src/lib.rs
@@ -18,7 +18,6 @@ pub mod mega_blob;
 pub mod mega_commit;
 pub mod mega_issue;
 pub mod mega_mr;
-pub mod mega_mr_comment;
 pub mod mega_mr_conv;
 pub mod mega_refs;
 pub mod mega_tag;
diff --git a/jupiter/callisto/src/mega_mr.rs b/jupiter/callisto/src/mega_mr.rs
index 419c807c..570b053e 100644
--- a/jupiter/callisto/src/mega_mr.rs
+++ b/jupiter/callisto/src/mega_mr.rs
@@ -10,6 +10,7 @@ pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
     pub id: i64,
     pub mr_link: String,
+    pub title: String,
     pub merge_date: Option<DateTime>,
     pub status: MergeStatus,
     #[sea_orm(column_type = "Text")]
diff --git a/jupiter/callisto/src/mega_mr_comment.rs b/jupiter/callisto/src/mega_mr_comment.rs
deleted file mode 100644
index c63f714f..00000000
--- a/jupiter/callisto/src/mega_mr_comment.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0
-
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
-#[sea_orm(table_name = "mega_mr_comment")]
-pub struct Model {
-    #[sea_orm(primary_key, auto_increment = false)]
-    pub id: i64,
-    pub conv_id: i64,
-    #[sea_orm(column_type = "Text", nullable)]
-    pub comment: Option<String>,
-    pub edited: bool,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {}
-
-impl ActiveModelBehavior for ActiveModel {}
diff --git a/jupiter/callisto/src/mega_mr_conv.rs b/jupiter/callisto/src/mega_mr_conv.rs
index 63b0b406..1f564804 100644
--- a/jupiter/callisto/src/mega_mr_conv.rs
+++ b/jupiter/callisto/src/mega_mr_conv.rs
@@ -12,6 +12,8 @@ pub struct Model {
     pub mr_link: String,
     pub user_id: i64,
     pub conv_type: ConvType,
+    #[sea_orm(column_type = "Text", nullable)]
+    pub comment: Option<String>,
     pub created_at: DateTime,
     pub updated_at: DateTime,
 }
diff --git a/jupiter/callisto/src/prelude.rs b/jupiter/callisto/src/prelude.rs
index c29eb639..4f479117 100644
--- a/jupiter/callisto/src/prelude.rs
+++ b/jupiter/callisto/src/prelude.rs
@@ -15,7 +15,6 @@ pub use crate::mega_blob::Entity as MegaBlob;
 pub use crate::mega_commit::Entity as MegaCommit;
 pub use crate::mega_issue::Entity as MegaIssue;
 pub use crate::mega_mr::Entity as MegaMr;
-pub use crate::mega_mr_comment::Entity as MegaMrComment;
 pub use crate::mega_mr_conv::Entity as MegaMrConv;
 pub use crate::mega_refs::Entity as MegaRefs;
 pub use crate::mega_tag::Entity as MegaTag;
diff --git a/jupiter/src/storage/init.rs b/jupiter/src/storage/init.rs
index a04dd7be..2a96fcca 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<DbE
             let backend = txn.get_database_backend();
 
             // `include_str!` will expand the file while compiling, so `.sql` is not needed after that
-            const SETUP_SQL: &str = include_str!("../../../sql/sqlite/sqlite_20240912_init.sql");
+            const SETUP_SQL: &str = include_str!("../../../sql/sqlite/sqlite_20240923_init.sql");
             txn.execute(Statement::from_string(backend, SETUP_SQL)).await?;
             Ok(())
         })
diff --git a/jupiter/src/storage/mono_storage.rs b/jupiter/src/storage/mono_storage.rs
index 6d1d94e8..dfe66730 100644
--- a/jupiter/src/storage/mono_storage.rs
+++ b/jupiter/src/storage/mono_storage.rs
@@ -9,7 +9,7 @@ use sea_orm::{
 
 use callisto::db_enums::{ConvType, MergeStatus};
 use callisto::{
-    mega_blob, mega_commit, mega_mr, mega_mr_comment, mega_mr_conv, mega_refs, mega_tag, mega_tree,
+    mega_blob, mega_commit, mega_mr, mega_mr_conv, mega_refs, mega_tag, mega_tree,
     raw_blob,
 };
 use common::errors::MegaError;
@@ -178,12 +178,14 @@ impl MonoStorage {
         mr_link: &str,
         user_id: i64,
         conv_type: ConvType,
+        comment: Option<String>,
     ) -> Result<i64, MegaError> {
         let conversation = mega_mr_conv::Model {
             id: generate_id(),
             mr_link: mr_link.to_owned(),
             user_id,
             conv_type,
+            comment,
             created_at: chrono::Utc::now().naive_utc(),
             updated_at: chrono::Utc::now().naive_utc(),
         };
@@ -192,27 +194,6 @@ impl MonoStorage {
         Ok(res.id)
     }
 
-    pub async fn add_mr_comment(
-        &self,
-        mr_link: &str,
-        user_id: i64,
-        comment: Option<String>,
-    ) -> Result<(), MegaError> {
-        let conv_id = self
-            .add_mr_conversation(mr_link, user_id, ConvType::Comment)
-            .await
-            .unwrap();
-        let comment = mega_mr_comment::Model {
-            id: generate_id(),
-            conv_id,
-            comment,
-            edited: false,
-        };
-        let comment = comment.into_active_model();
-        comment.insert(self.get_connection()).await.unwrap();
-        Ok(())
-    }
-
     pub async fn save_entry(
         &self,
         commit_id: &str,
diff --git a/mercury/src/internal/pack/decode.rs b/mercury/src/internal/pack/decode.rs
index c1f7e696..bd7e3c71 100644
--- a/mercury/src/internal/pack/decode.rs
+++ b/mercury/src/internal/pack/decode.rs
@@ -492,7 +492,7 @@ impl Pack {
         // DO NOT use thread::spawn, because it will block tokio runtime (if single-threaded runtime, like in tests)
         tokio::task::spawn_blocking(move || {
             self.decode(&mut reader, move |entry, _| {
-                sender.send(entry).unwrap();
+                if sender.send(entry).is_ok() {}
             }).unwrap();
             self
         }).await.unwrap()
diff --git a/mono/src/api/api_router.rs b/mono/src/api/api_router.rs
index 8f8fbc54..a2b01e09 100644
--- a/mono/src/api/api_router.rs
+++ b/mono/src/api/api_router.rs
@@ -1,3 +1,5 @@
+use std::path::PathBuf;
+
 use axum::{
     body::Body,
     extract::{Path, Query, State},
@@ -5,6 +7,7 @@ use axum::{
     routing::{get, post},
     Json, Router,
 };
+use http::StatusCode;
 
 use ceres::{
     api_service::ApiHandler,
@@ -15,7 +18,6 @@ use ceres::{
     },
 };
 use common::{errors::ProtocolError, model::CommonResult};
-use http::StatusCode;
 use taurus::event::api_request::{ApiRequestEvent, ApiType};
 
 use crate::api::error::ApiError;
@@ -29,11 +31,11 @@ pub fn routers() -> Router<MonoApiServiceState> {
         .route("/create-file", post(create_file))
         .route("/latest-commit", get(get_latest_commit))
         .route("/tree/commit-info", get(get_tree_commit_info))
+        .route("/tree/path-can-clone", get(path_can_be_cloned))
         .route("/tree", get(get_tree_info))
         .route("/blob", get(get_blob_string))
         .route("/file/blob/:object_id", get(get_blob_file))
-        .route("/file/tree", get(get_tree_file))
-        .route("/path-can-clone", get(path_can_be_cloned));
+        .route("/file/tree", get(get_tree_file));
     Router::new()
         .merge(router)
         .merge(mr_router::routers())
@@ -179,6 +181,20 @@ async fn path_can_be_cloned(
     Query(query): Query<BlobContentQuery>,
     state: State<MonoApiServiceState>,
 ) -> Result<Json<CommonResult<bool>>, ApiError> {
-    let res = state.api_handler(query.path.clone().into()).await.is_ok();
+    let path: PathBuf = query.path.clone().into();
+    let import_dir = state.context.config.monorepo.import_dir.clone();
+    let res = if path.starts_with(&import_dir) {
+        state
+            .context
+            .services
+            .git_db_storage
+            .find_git_repo_exact_match(path.to_str().unwrap())
+            .await
+            .unwrap()
+            .is_some()
+    } else {
+        // any path under monorepo can be cloned
+        true
+    };
     Ok(Json(CommonResult::success(Some(res))))
 }
diff --git a/moon/src/app/(dashboard)/mr/[id]/page.tsx b/moon/src/app/(dashboard)/mr/[id]/page.tsx
index 975e3d51..5dabba8c 100644
--- a/moon/src/app/(dashboard)/mr/[id]/page.tsx
+++ b/moon/src/app/(dashboard)/mr/[id]/page.tsx
@@ -1,27 +1,135 @@
 'use client'
-import MergeDetail from "@/components/MergeDetail";
 import { useEffect, useState } from "react";
+import { Card, Button, List, Tabs, TabsProps, Space, Timeline } from 'antd/lib';
+import { useRouter } from 'next/navigation';
+import { CommentOutlined, MergeOutlined } from '@ant-design/icons';
+import { formatDistance, fromUnixTime } from 'date-fns';
 
+interface MRDetail {
+    status: string,
+    conversions: Conversation[],
+    title: string,
+}
+interface Conversation {
+    user_id: number,
+    conv_type: String,
+    comment: String,
+    created_at: number,
+}
+
+export default function MRDetailPage({ params }: { params: { id: string } }) {
+    const [mrDetail, setMrDetail] = useState<MRDetail>(
+        {
+            status: "", 
+            conversions: [], 
+            title: "",
+          }
+    );
+    const router = useRouter();
+    const [filedata, setFileData] = useState([]);
+    const [loadings, setLoadings] = useState<boolean[]>([]);
 
-export default function MRDetailPage( { params }: { params: { id: string } }) {
-    const [mrDetail, setMrDetail] = useState([]);
     useEffect(() => {
-        const fetchData = async () => {
+        const fetchFileList = async () => {
+            set_to_loading(2)
             try {
-                const res = await fetch(`/api/mr/${params.id}/detail`);
-                const response = await res.json();
-                const data = response.data.data;
-                setMrDetail(data);
-            } catch (error) {
-                console.error('Error fetching data:', error);
+                const detail = await fetch(`/api/mr/${params.id}/detail`);
+                const detail_json = await detail.json();
+                setMrDetail(detail_json.data.data);
+                const res = await fetch(`/api/mr/${params.id}/files`);
+                const result = await res.json();
+                setFileData(result.data.data);
+            } finally {
+                cancel_loading(2)
             }
         };
-        fetchData();
+        fetchFileList();
     }, [params.id]);
 
+    const set_to_loading = (index: number) => {
+        setLoadings((prevLoadings) => {
+            const newLoadings = [...prevLoadings];
+            newLoadings[index] = true;
+            return newLoadings;
+        });
+    }
+
+    const cancel_loading = (index: number) => {
+        setLoadings((prevLoadings) => {
+            const newLoadings = [...prevLoadings];
+            newLoadings[index] = false;
+            return newLoadings;
+        });
+    }
+
+    const approve_mr = async (index: number, mr_link: string) => {
+        set_to_loading(index);
+        const res = await fetch(`/api/mr/${mr_link}/merge`, {
+            method: 'POST',
+        });
+        if (res) {
+            cancel_loading(index);
+        }
+    };
+
+    let conv_items = mrDetail?.conversions.map(conv => {
+        let icon;
+        let children;
+        switch (conv.conv_type) {
+            case "Comment": icon = <CommentOutlined />; children = conv.comment; break;
+            case "Merged": icon = <MergeOutlined />; children = "Merged via the queue into main " + formatDistance(fromUnixTime(conv.created_at), new Date(), { addSuffix: true }); break;
+            // default: icon = <CommentOutlined />; children = conv.comment;
+        };
+
+        const element = {
+            dot: icon,
+            // color: 'red',
+            children: children
+        }
+        return element
+    });
+
+    const tab_items: TabsProps['items'] = [
+        {
+            key: '1',
+            label: 'Conversation',
+            children:
+                <Space style={{ width: '100%' }}>
+                    <Timeline items={conv_items} />
+                </Space>
+        },
+        {
+            key: '2',
+            label: 'Files Changed',
+            children: <Space style={{ width: '100%' }}>
+                <List
+                    // style={{ width: '100%' }}
+                    header={<div>Change File List</div>}
+                    bordered
+                    dataSource={filedata}
+                    loading={loadings[2]}
+                    renderItem={(item) => (
+                        <List.Item>
+                            {item}
+                        </List.Item>
+                    )}
+                />
+            </Space>
+        }
+    ];
+
     return (
-        <div>
-            <MergeDetail mrDetail={mrDetail}/>
-        </div>
+        <Card title={mrDetail.title + " #" + params.id}>
+            {mrDetail && mrDetail.status === "open" &&
+                <Button
+                    type="primary"
+                    loading={loadings[1]}
+                    onClick={() => approve_mr(1, params.id)}
+                >
+                    Merge MR
+                </Button>
+            }
+            <Tabs defaultActiveKey="1" items={tab_items} />
+        </Card>
     )
 }
diff --git a/moon/src/app/(dashboard)/tree/[...path]/page.tsx b/moon/src/app/(dashboard)/tree/[...path]/page.tsx
index 52d7a514..cad0831b 100644
--- a/moon/src/app/(dashboard)/tree/[...path]/page.tsx
+++ b/moon/src/app/(dashboard)/tree/[...path]/page.tsx
@@ -10,6 +10,8 @@ import { Flex, Layout } from "antd/lib";
 export default function Page({ params }: { params: { path: string[] } }) {
     const [directory, setDirectory] = useState([]);
     const [readmeContent, setReadmeContent] = useState("");
+    const [cloneBtn, setCloneBtn] = useState(true);
+    const [endpoint, setEndPoint] = useState("");
     let path = '/' + params.path.join('/');
     useEffect(() => {
         const fetchData = async () => {
@@ -18,6 +20,10 @@ export default function Page({ params }: { params: { path: string[] } }) {
                 setDirectory(directory);
                 let readmeContent = await getReadmeContent(path, directory);
                 setReadmeContent(readmeContent);
+                let shown_clone_btn = await pathCanClone(path);
+                setCloneBtn(shown_clone_btn);
+                let endpoint =  await getEndpoint();
+                setEndPoint(endpoint);
             } catch (error) {
                 console.error('Error fetching data:', error);
             }
@@ -52,9 +58,12 @@ export default function Page({ params }: { params: { path: string[] } }) {
         <Flex gap="middle" wrap>
             <Layout style={breadStyle}>
                 <Bread path={params.path} />
-                <Flex justify={'flex-end'} >
-                    <CloneTabs/>
-                </Flex>
+                {
+                    cloneBtn &&
+                    <Flex justify={'flex-end'} >
+                        <CloneTabs endpoint= {endpoint}/>
+                    </Flex>
+                }
             </Layout>
             <Layout style={treeStyle}>
                 <RepoTree directory={directory} />
@@ -85,3 +94,18 @@ async function getReadmeContent(pathname, directory) {
     }
     return readmeContent
 }
+
+async function pathCanClone(pathname: string) {
+    const res = await fetch(`/api/tree/path-can-clone?path=${pathname}`);
+    const response = await res.json();
+    const shown_clone_btn = response.data.data;
+    return shown_clone_btn
+}
+
+
+async function getEndpoint() {
+    const res = await fetch(`/host`);
+    const response = await res.json();
+    const data = response.endpoint;
+    return data
+}
\ No newline at end of file
diff --git a/moon/src/app/api/tree/path-can-clone/route.ts b/moon/src/app/api/tree/path-can-clone/route.ts
new file mode 100644
index 00000000..d58531e6
--- /dev/null
+++ b/moon/src/app/api/tree/path-can-clone/route.ts
@@ -0,0 +1,16 @@
+import { type NextRequest } from 'next/server'
+export const revalidate = 0
+export const dynamic = 'force-dynamic' // defaults to auto
+
+export async function GET(request: NextRequest) {
+    const endpoint = process.env.MEGA_INTERNAL_HOST;
+
+    const searchParams = request.nextUrl.searchParams
+    const path = searchParams.get('path')
+
+    const res = await fetch(`${endpoint}/api/v1/tree/path-can-clone?path=${path}`, {
+    })
+    const data = await res.json()
+
+    return Response.json({ data })
+}
\ No newline at end of file
diff --git a/moon/src/app/host/route.ts b/moon/src/app/host/route.ts
new file mode 100644
index 00000000..5e1c8dc2
--- /dev/null
+++ b/moon/src/app/host/route.ts
@@ -0,0 +1,5 @@
+
+export async function GET() {
+    const endpoint = process.env.MEGA_HOST;
+    return Response.json({ endpoint })
+}
\ No newline at end of file
diff --git a/moon/src/components/CloneTabs.tsx b/moon/src/components/CloneTabs.tsx
index 754a5cc6..5bdf353c 100644
--- a/moon/src/components/CloneTabs.tsx
+++ b/moon/src/components/CloneTabs.tsx
@@ -1,14 +1,11 @@
 import React, { useEffect, useState } from 'react';
 import { Tabs, TabsProps, Button, Space, Popover, Input } from 'antd';
-import {
-    CodeBracketIcon,
-} from '@heroicons/react/16/solid'
 import copy from 'copy-to-clipboard';
 import { CopyOutlined, CheckOutlined, DownloadOutlined } from '@ant-design/icons';
 import { usePathname } from 'next/navigation';
 
 
-const CloneTabs: React.FC = () => {
+const CloneTabs = ({ endpoint }) => {
     const pathname = usePathname();
     const [text, setText] = useState<string>(pathname);
     const [copied, setCopied] = useState<boolean>(false);
@@ -19,15 +16,15 @@ const CloneTabs: React.FC = () => {
     };
 
     useEffect(() => {
-        if (typeof window !== 'undefined') {
-            const domain = window.location.origin;
+        if (endpoint) {
+            const url = new URL(endpoint);
             if (active_tab === '1') {
-                setText(`${domain}${pathname.replace('/tree', '')}.git`);
+                setText(`${url.href}${pathname.replace('/tree/', '')}.git`);
             } else {
-                setText(`ssh://git@${window.location.hostname}:${pathname.replace('/tree', '')}.git`);
+                setText(`ssh://git@${url.host}${pathname.replace('/tree', '')}.git`);
             }
         }
-    }, [pathname, active_tab]);
+    }, [pathname, active_tab, endpoint]);
 
 
 
diff --git a/moon/src/components/MergeDetail.tsx b/moon/src/components/MergeDetail.tsx
deleted file mode 100644
index f2bf2ae5..00000000
--- a/moon/src/components/MergeDetail.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-'use client'
-import React, { useEffect, useState } from 'react';
-import { Card, Button, List, Typography } from 'antd/lib';
-import { useRouter } from 'next/navigation';
-
-const MRDetailPage = ({ mrDetail }) => {
-    const router = useRouter();
-    const [filedata, setFileData] = useState([]);
-    const [loadings, setLoadings] = useState<boolean[]>([]);
-    const [error, setError] = useState(null);
-
-    useEffect(() => {
-        const fetchFileList = async () => {
-            set_to_loading(2)
-            try {
-                const res = await fetch(`/api/mr/${mrDetail.mr_link}/files`);
-                const result = await res.json();
-                setFileData(result.data.data);
-            } catch (err) {
-                setError(err);
-            } finally {
-                cancel_loading(2)
-            }
-        };
-        if (!mrDetail || !mrDetail.mr_link) {
-            return;
-        }
-        fetchFileList();
-    }, [mrDetail]);
-
-
-    const set_to_loading = (index: number) => {
-        setLoadings((prevLoadings) => {
-            const newLoadings = [...prevLoadings];
-            newLoadings[index] = true;
-            return newLoadings;
-        });
-    }
-
-    const cancel_loading = (index: number) => {
-        setLoadings((prevLoadings) => {
-            const newLoadings = [...prevLoadings];
-            newLoadings[index] = false;
-            return newLoadings;
-        });
-    }
-
-    const approve_mr = async (index: number, mr_link: string) => {
-        set_to_loading(index);
-        const res = await fetch(`/api/mr/${mr_link}/merge`,{
-            method: 'POST',
-        });
-        if (res) {
-            cancel_loading(index);
-        }
-        
-        if (res.ok) {
-            router.refresh();
-        }
-    };
-
-
-    return (
-        <Card title="Merge Request Detail Page">
-            {mrDetail.status === "open" &&
-                <Button
-                    type="primary"
-                    loading={loadings[1]}
-                    onClick={() => approve_mr(1, mrDetail.mr_link)}
-                >
-                    Merge MR
-                </Button>
-            }
-
-            <Card
-                style={{ marginTop: 16 }}
-                type="inner"
-                title={mrDetail.mr_link}
-                extra={<a href="#">More</a>}
-            >
-                <List
-                    style={{ width: '30%' }}
-                    header={<div>Change File List</div>}
-                    bordered
-                    dataSource={filedata}
-                    loading = {loadings[2]}
-                    renderItem={(item) => (
-                        <List.Item>
-                            {item}
-                        </List.Item>
-                    )}
-                />
-            </Card>
-        </Card>
-    )
-}
-
-export default MRDetailPage;
\ No newline at end of file
diff --git a/sql/postgres/pg_20240912__init.sql b/sql/postgres/pg_20240923__init.sql
similarity index 97%
rename from sql/postgres/pg_20240912__init.sql
rename to sql/postgres/pg_20240923__init.sql
index 13875622..e03c2d67 100644
--- a/sql/postgres/pg_20240912__init.sql
+++ b/sql/postgres/pg_20240923__init.sql
@@ -42,6 +42,7 @@ CREATE TABLE IF NOT EXISTS "mega_tag" (
 CREATE TABLE IF NOT EXISTS "mega_mr" (
   "id" BIGINT PRIMARY KEY,
   "mr_link" VARCHAR(40) NOT NULL,
+  "title" VARCHAR(80) NOT NULL,
   "merge_date" TIMESTAMP,
   "status" VARCHAR(20) NOT NULL,
   "path" TEXT NOT NULL,
@@ -57,20 +58,13 @@ CREATE TABLE IF NOT EXISTS "mega_mr_conv" (
   "mr_link" VARCHAR(20) NOT NULL,
   "user_id" BIGINT NOT NULL,
   "conv_type"  VARCHAR(20) NOT NULL,
+  "comment" TEXT,
   "created_at" TIMESTAMP NOT NULL,
   "updated_at" TIMESTAMP NOT NULL
 );
 CREATE INDEX "idx_conversation" ON "mega_mr_conv" ("mr_link");
 
 
-CREATE TABLE IF NOT EXISTS "mega_mr_comment" (
-  "id" BIGINT PRIMARY KEY,
-  "conv_id" BIGINT NOT NULL,
-  "comment" TEXT,
-  "edited" BOOLEAN NOT NULL
-);
-CREATE INDEX "idx_comment_id" ON "mega_mr_comment" ("conv_id");
-
 CREATE TABLE IF NOT EXISTS "mega_issue" (
   "id" BIGINT PRIMARY KEY,
   "number" BIGINT NOT NULL,
diff --git a/sql/sqlite/sqlite_20240912_init.sql b/sql/sqlite/sqlite_20240923_init.sql
similarity index 97%
rename from sql/sqlite/sqlite_20240912_init.sql
rename to sql/sqlite/sqlite_20240923_init.sql
index 8e0e30e4..29fa615e 100644
--- a/sql/sqlite/sqlite_20240912_init.sql
+++ b/sql/sqlite/sqlite_20240923_init.sql
@@ -42,6 +42,7 @@ CREATE TABLE IF NOT EXISTS "mega_tag" (
 CREATE TABLE IF NOT EXISTS "mega_mr" (
   "id" INTEGER PRIMARY KEY,
   "mr_link" TEXT NOT NULL,
+  "title" TEXT NOT NULL,
   "merge_date" TEXT,
   "status" TEXT NOT NULL,
   "path" TEXT NOT NULL,
@@ -57,20 +58,12 @@ CREATE TABLE IF NOT EXISTS "mega_mr_conv" (
   "mr_link" INTEGER NOT NULL,
   "user_id" INTEGER NOT NULL,
   "conv_type" TEXT NOT NULL,
+  "comment" TEXT,
   "created_at" TEXT NOT NULL,
   "updated_at" TEXT NOT NULL
 );
 CREATE INDEX "idx_conversation" ON "mega_mr_conv" ("mr_link");
 
-
-CREATE TABLE IF NOT EXISTS "mega_mr_comment" (
-  "id" INTEGER PRIMARY KEY,
-  "conv_id" INTEGER NOT NULL,
-  "comment" TEXT,
-  "edited" INTEGER NOT NULL
-);
-CREATE INDEX "idx_comment_id" ON "mega_mr_comment" ("conv_id");
-
 CREATE TABLE IF NOT EXISTS "mega_issue" (
   "id" INTEGER PRIMARY KEY,
   "number" INTEGER NOT NULL,


Test patch: