新增: Git 提交历史显示作者 + 提交详情含作者/时间
- 后端 git log format 加 %an 作者名(状态查询+分页查询) - 提交列表每行显示 hash/作者/主题/时间 - 提交详情头部补充作者显示 - 分支切换按钮占位(后续接入分支列表 IPC)
This commit is contained in:
@@ -266,6 +266,8 @@ struct GitRecentCommit {
|
|||||||
subject: String,
|
subject: String,
|
||||||
/// Unix 时间戳(秒),供前端格式化显示。
|
/// Unix 时间戳(秒),供前端格式化显示。
|
||||||
timestamp: i64,
|
timestamp: i64,
|
||||||
|
/// 作者名(git an)
|
||||||
|
author: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Git 状态返回结构(无 .git 时各字段空,前端据此显示"非 Git 仓库")。
|
/// Git 状态返回结构(无 .git 时各字段空,前端据此显示"非 Git 仓库")。
|
||||||
@@ -367,23 +369,25 @@ fn run_git_status(dir: &str) -> GitStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) 最近提交:`git log -50 --format="%h %ct %s"`(hash + unix 时间戳 + subject)
|
// 3) 最近提交:`git log -50 --format="%h %ct %an %s"`(hash + 时间戳 + 作者 + subject)
|
||||||
let mut recent_commits = Vec::new();
|
let mut recent_commits = Vec::new();
|
||||||
if let Some(out) = run_git_cmd(path, &["log", "-50", "--format=%h %ct %s"], timeout) {
|
if let Some(out) = run_git_cmd(path, &["log", "-50", "--format=%h %ct %an %s"], timeout) {
|
||||||
for line in out.lines() {
|
for line in out.lines() {
|
||||||
// format:"<hash> <unix_ts> <subject>"
|
// format:"<hash> <unix_ts> <author> <subject>"(作者不含空格)
|
||||||
let line = line.trim();
|
let line = line.trim();
|
||||||
if line.is_empty() { continue; }
|
if line.is_empty() { continue; }
|
||||||
let mut parts = line.splitn(3, ' ');
|
let mut parts = line.splitn(4, ' ');
|
||||||
let hash = parts.next().unwrap_or("").to_string();
|
let hash = parts.next().unwrap_or("").to_string();
|
||||||
let ts_str = parts.next().unwrap_or("0");
|
let ts_str = parts.next().unwrap_or("0");
|
||||||
let timestamp: i64 = ts_str.parse().unwrap_or(0);
|
let timestamp: i64 = ts_str.parse().unwrap_or(0);
|
||||||
|
let author = parts.next().unwrap_or("").to_string();
|
||||||
let subject = parts.next().unwrap_or("").to_string();
|
let subject = parts.next().unwrap_or("").to_string();
|
||||||
if !hash.is_empty() {
|
if !hash.is_empty() {
|
||||||
recent_commits.push(GitRecentCommit {
|
recent_commits.push(GitRecentCommit {
|
||||||
hash,
|
hash,
|
||||||
subject,
|
subject,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
author,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -922,7 +926,7 @@ pub async fn get_module_commits(
|
|||||||
"log",
|
"log",
|
||||||
&format!("--skip={}", skip),
|
&format!("--skip={}", skip),
|
||||||
&format!("-{}", fetch_plus),
|
&format!("-{}", fetch_plus),
|
||||||
"--format=%h %ct %s",
|
"--format=%h %ct %an %s",
|
||||||
])
|
])
|
||||||
.current_dir(&dir_for_git)
|
.current_dir(&dir_for_git)
|
||||||
.env("LANG", "en_US.UTF-8")
|
.env("LANG", "en_US.UTF-8")
|
||||||
@@ -937,16 +941,18 @@ pub async fn get_module_commits(
|
|||||||
let line = line.trim();
|
let line = line.trim();
|
||||||
if line.is_empty() { continue; }
|
if line.is_empty() { continue; }
|
||||||
if commits.len() >= fetch as usize { break; }
|
if commits.len() >= fetch as usize { break; }
|
||||||
let mut parts = line.splitn(3, ' ');
|
let mut parts = line.splitn(4, ' ');
|
||||||
let hash = parts.next().unwrap_or("").to_string();
|
let hash = parts.next().unwrap_or("").to_string();
|
||||||
let ts_str = parts.next().unwrap_or("0");
|
let ts_str = parts.next().unwrap_or("0");
|
||||||
let timestamp: i64 = ts_str.parse().unwrap_or(0);
|
let timestamp: i64 = ts_str.parse().unwrap_or(0);
|
||||||
|
let author = parts.next().unwrap_or("").to_string();
|
||||||
let subject = parts.next().unwrap_or("").to_string();
|
let subject = parts.next().unwrap_or("").to_string();
|
||||||
if !hash.is_empty() {
|
if !hash.is_empty() {
|
||||||
commits.push(serde_json::json!({
|
commits.push(serde_json::json!({
|
||||||
"hash": hash,
|
"hash": hash,
|
||||||
"subject": subject,
|
"subject": subject,
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
|
"author": author,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ export interface GitRecentCommit {
|
|||||||
subject: string
|
subject: string
|
||||||
/** Unix 时间戳(秒) */
|
/** Unix 时间戳(秒) */
|
||||||
timestamp: number
|
timestamp: number
|
||||||
|
/** 作者名 */
|
||||||
|
author: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** getModuleGitStatus 返回结构。 */
|
/** getModuleGitStatus 返回结构。 */
|
||||||
@@ -151,7 +153,7 @@ export const moduleApi = {
|
|||||||
|
|
||||||
/** 分页查询工程 Git 提交历史。返回 { commits, has_more }。 */
|
/** 分页查询工程 Git 提交历史。返回 { commits, has_more }。 */
|
||||||
getModuleCommits(moduleId: string, skip?: number, limit?: number): Promise<{
|
getModuleCommits(moduleId: string, skip?: number, limit?: number): Promise<{
|
||||||
commits: { hash: string; subject: string; timestamp: number }[]
|
commits: { hash: string; subject: string; timestamp: number; author: string }[]
|
||||||
has_more: boolean
|
has_more: boolean
|
||||||
}> {
|
}> {
|
||||||
return invoke('get_module_commits', { moduleId, skip: skip ?? 0, limit: limit ?? 50 })
|
return invoke('get_module_commits', { moduleId, skip: skip ?? 0, limit: limit ?? 50 })
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
<span class="branch-icon">⎇</span>
|
<span class="branch-icon">⎇</span>
|
||||||
<span class="branch-name">{{ branchName }}</span>
|
<span class="branch-name">{{ branchName }}</span>
|
||||||
<span v-if="!showHistory" class="branch-count">{{ gitStatus?.changed_files.length ?? 0 }} {{ $t('gitChanges.changedFiles') }}</span>
|
<span v-if="!showHistory" class="branch-count">{{ gitStatus?.changed_files.length ?? 0 }} {{ $t('gitChanges.changedFiles') }}</span>
|
||||||
|
<button v-if="!showHistory" class="branch-switch-btn" @click="onSwitchBranch" :title="$t('gitChanges.switchBranch')">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="loading && !gitStatus" class="gc-skeleton">
|
<div v-if="loading && !gitStatus" class="gc-skeleton">
|
||||||
@@ -54,6 +57,7 @@
|
|||||||
<div v-if="selectedCommit" class="gc-commit-detail">
|
<div v-if="selectedCommit" class="gc-commit-detail">
|
||||||
<div class="gc-commit-detail-header">
|
<div class="gc-commit-detail-header">
|
||||||
<span class="gc-commit-detail-hash">{{ selectedCommit.hash }}</span>
|
<span class="gc-commit-detail-hash">{{ selectedCommit.hash }}</span>
|
||||||
|
<span class="gc-commit-detail-author">{{ selectedCommit.author }}</span>
|
||||||
<span class="gc-commit-detail-msg">{{ selectedCommit.subject }}</span>
|
<span class="gc-commit-detail-msg">{{ selectedCommit.subject }}</span>
|
||||||
<span class="gc-commit-detail-time">{{ formatTime(selectedCommit.timestamp) }}</span>
|
<span class="gc-commit-detail-time">{{ formatTime(selectedCommit.timestamp) }}</span>
|
||||||
<button class="gc-detail-close" @click="selectedCommit = null; commitDetail = null">✕</button>
|
<button class="gc-detail-close" @click="selectedCommit = null; commitDetail = null">✕</button>
|
||||||
@@ -96,6 +100,7 @@
|
|||||||
@click="selectCommit(c)"
|
@click="selectCommit(c)"
|
||||||
>
|
>
|
||||||
<span class="gc-commit-hash">{{ c.hash.slice(0, 8) }}</span>
|
<span class="gc-commit-hash">{{ c.hash.slice(0, 8) }}</span>
|
||||||
|
<span class="gc-commit-author">{{ c.author }}</span>
|
||||||
<span class="gc-commit-msg">{{ c.subject }}</span>
|
<span class="gc-commit-msg">{{ c.subject }}</span>
|
||||||
<span class="gc-commit-time">{{ formatTime(c.timestamp) }}</span>
|
<span class="gc-commit-time">{{ formatTime(c.timestamp) }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -137,7 +142,7 @@ const diffContent = ref('')
|
|||||||
const diffLoading = ref(false)
|
const diffLoading = ref(false)
|
||||||
|
|
||||||
// 提交历史(分页)
|
// 提交历史(分页)
|
||||||
const commits = ref<{ hash: string; subject: string; timestamp: number }[]>([])
|
const commits = ref<{ hash: string; subject: string; timestamp: number; author: string }[]>([])
|
||||||
const totalCommits = ref(0)
|
const totalCommits = ref(0)
|
||||||
const hasMoreCommits = ref(false)
|
const hasMoreCommits = ref(false)
|
||||||
const loadingMore = ref(false)
|
const loadingMore = ref(false)
|
||||||
@@ -145,7 +150,7 @@ const commitSkip = ref(0)
|
|||||||
const COMMIT_PAGE_SIZE = 50
|
const COMMIT_PAGE_SIZE = 50
|
||||||
|
|
||||||
// 已选提交详情
|
// 已选提交详情
|
||||||
const selectedCommit = ref<{ hash: string; subject: string; timestamp: number } | null>(null)
|
const selectedCommit = ref<{ hash: string; subject: string; timestamp: number; author: string } | null>(null)
|
||||||
const commitDetail = ref<{ files: { status: string; path: string }[]; diff: string } | null>(null)
|
const commitDetail = ref<{ files: { status: string; path: string }[]; diff: string } | null>(null)
|
||||||
const commitDetailLoading = ref(false)
|
const commitDetailLoading = ref(false)
|
||||||
const commitSelectedFile = ref('')
|
const commitSelectedFile = ref('')
|
||||||
@@ -271,6 +276,11 @@ async function loadStatus() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 占位:分支切换后续接入(需拉取分支列表 + checkout IPC)。 */
|
||||||
|
function onSwitchBranch() {
|
||||||
|
// TODO: 弹出分支列表选择 + 调用 git checkout
|
||||||
|
}
|
||||||
|
|
||||||
async function loadMoreCommits() {
|
async function loadMoreCommits() {
|
||||||
if (loadingMore.value || !hasMoreCommits.value) return
|
if (loadingMore.value || !hasMoreCommits.value) return
|
||||||
loadingMore.value = true
|
loadingMore.value = true
|
||||||
@@ -311,7 +321,7 @@ async function selectFile(path: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 点击提交行 → 加载该提交的变更文件列表 */
|
/** 点击提交行 → 加载该提交的变更文件列表 */
|
||||||
async function selectCommit(c: { hash: string; subject: string; timestamp: number }) {
|
async function selectCommit(c: { hash: string; subject: string; timestamp: number; author: string }) {
|
||||||
selectedCommit.value = c
|
selectedCommit.value = c
|
||||||
commitDetail.value = null
|
commitDetail.value = null
|
||||||
commitSelectedFile.value = ''
|
commitSelectedFile.value = ''
|
||||||
@@ -370,6 +380,21 @@ watch(() => props.moduleId, loadStatus, { immediate: true })
|
|||||||
.branch-icon { font-size: 16px; }
|
.branch-icon { font-size: 16px; }
|
||||||
.branch-name { font-weight: 600; color: var(--df-accent); }
|
.branch-name { font-weight: 600; color: var(--df-accent); }
|
||||||
.branch-count { margin-left: auto; font-size: 12px; color: var(--df-text-dim); }
|
.branch-count { margin-left: auto; font-size: 12px; color: var(--df-text-dim); }
|
||||||
|
.branch-switch-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--df-text-dim);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.branch-switch-btn:hover {
|
||||||
|
color: var(--df-text);
|
||||||
|
background: rgba(255,255,255,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
/* Tabs */
|
/* Tabs */
|
||||||
.gc-tabs { display: flex; border-bottom: 0.5px solid var(--df-border); flex-shrink: 0; }
|
.gc-tabs { display: flex; border-bottom: 0.5px solid var(--df-border); flex-shrink: 0; }
|
||||||
@@ -424,6 +449,12 @@ watch(() => props.moduleId, loadStatus, { immediate: true })
|
|||||||
font-size: 11px; color: var(--df-accent); flex-shrink: 0;
|
font-size: 11px; color: var(--df-accent); flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.gc-commit-msg { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
|
.gc-commit-msg { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
|
||||||
|
.gc-commit-author {
|
||||||
|
font-size: 11px; color: var(--df-text-dim);
|
||||||
|
flex-shrink: 0;
|
||||||
|
max-width: 100px;
|
||||||
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||||
|
}
|
||||||
.gc-commit-time { font-size: 11px; color: var(--df-text-dim); flex-shrink: 0; }
|
.gc-commit-time { font-size: 11px; color: var(--df-text-dim); flex-shrink: 0; }
|
||||||
|
|
||||||
/* 提交详情 */
|
/* 提交详情 */
|
||||||
@@ -438,6 +469,10 @@ watch(() => props.moduleId, loadStatus, { immediate: true })
|
|||||||
font-size: 11px; color: var(--df-accent);
|
font-size: 11px; color: var(--df-accent);
|
||||||
}
|
}
|
||||||
.gc-commit-detail-msg { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
|
.gc-commit-detail-msg { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
|
||||||
|
.gc-commit-detail-author {
|
||||||
|
font-size: 11px; color: var(--df-text-dim);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
.gc-commit-detail-time { font-size: 11px; color: var(--df-text-dim); flex-shrink: 0; }
|
.gc-commit-detail-time { font-size: 11px; color: var(--df-text-dim); flex-shrink: 0; }
|
||||||
.gc-detail-close {
|
.gc-detail-close {
|
||||||
background: none; border: none; color: var(--df-text-dim);
|
background: none; border: none; color: var(--df-text-dim);
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ export default {
|
|||||||
notGitRepo: 'Not a git repository',
|
notGitRepo: 'Not a git repository',
|
||||||
detached: 'HEAD detached',
|
detached: 'HEAD detached',
|
||||||
loadMore: 'Load more',
|
loadMore: 'Load more',
|
||||||
|
switchBranch: 'Switch branch',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ export default {
|
|||||||
notGitRepo: '非 Git 仓库',
|
notGitRepo: '非 Git 仓库',
|
||||||
detached: 'HEAD 分离',
|
detached: 'HEAD 分离',
|
||||||
loadMore: '加载更多',
|
loadMore: '加载更多',
|
||||||
|
switchBranch: '切换分支',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user