feat: 项目 logo + 侧栏拖动排序 + 合并修复(主检出干净时 in-place)
- 项目 logo:仓库内文件(logo/icon/favicon 等多路径)→git remote 头像→自定义(URL/相对路径)→ 首字母徽章兜底;GET /api/projects/:id/logo(文件流/302),配置面板加 Logo 输入 - 侧栏项目拖动排序:projects.sort_order + POST /api/projects/reorder,乐观更新 - 合并修复:默认分支正被主检出占用时,若工作区干净则直接在主检出 in-place 合并 (用户手动合并的等价操作,安全);脏工作区拒绝并提示提交/暂存或仅通过 - schema: projects.logo / sort_order(ensureColumn 平滑迁移) 测试 75/75(merge 用例改为干净→成功/脏→拒绝) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+23
-7
@@ -111,19 +111,35 @@ test('mergeBranch:分支不存在(已删)→ 明确错误', async (t) => {
|
||||
assert.match(r.error ?? '', /分支不存在/);
|
||||
});
|
||||
|
||||
test('mergeBranch:defaultBranch 正被工作区检出 → 失败且不动用户检出', async (t) => {
|
||||
const repo = setup(t); // main 仍在主工作区检出
|
||||
gitSync(repo, ['branch', 'maestro/tsk_co']); // 分支存在但 main 被占用
|
||||
test('mergeBranch:main 被主检出占用且工作区干净 → 直接在主检出里合并成功', async (t) => {
|
||||
const repo = setup(t); // main 在主工作区检出,干净
|
||||
gitSync(repo, ['checkout', '-b', 'maestro/tsk_co']);
|
||||
commitFile(repo, 'c.txt', 'C\n', 'maestro(tsk_co): add c');
|
||||
gitSync(repo, ['checkout', 'main']); // 回到 main(仍被主检出占用)
|
||||
const mainBefore = gitSync(repo, ['rev-parse', 'main']).trim();
|
||||
|
||||
const r = await mergeBranch(repo, 'maestro/tsk_co', 'main', 'tsk_co');
|
||||
assert.equal(r.ok, false);
|
||||
assert.match(r.error ?? '', /检出/, `应提示 defaultBranch 被检出:${r.error}`);
|
||||
assert.equal(gitSync(repo, ['rev-parse', 'main']).trim(), mainBefore);
|
||||
assert.equal(gitSync(repo, ['rev-parse', '--abbrev-ref', 'HEAD']).trim(), 'main', '用户检出不应被切走');
|
||||
assert.equal(r.ok, true, r.error);
|
||||
assert.notEqual(gitSync(repo, ['rev-parse', 'main']).trim(), mainBefore, 'main 应前进');
|
||||
assert.equal(gitSync(repo, ['rev-parse', '--abbrev-ref', 'HEAD']).trim(), 'main', '仍在 main');
|
||||
assert.equal(gitSync(repo, ['show', 'main:c.txt']), 'C\n', '合并内容已进工作区');
|
||||
assertMergeWorktreeCleaned(repo, 'tsk_co');
|
||||
});
|
||||
|
||||
test('mergeBranch:main 被主检出占用但工作区有未提交改动 → 拒绝,提示提交/暂存', async (t) => {
|
||||
const repo = setup(t);
|
||||
gitSync(repo, ['checkout', '-b', 'maestro/tsk_dirty']);
|
||||
commitFile(repo, 'd.txt', 'D\n', 'maestro(tsk_dirty): add d');
|
||||
gitSync(repo, ['checkout', 'main']);
|
||||
writeFileSync(join(repo, 'README.md'), '# demo\nlocal edit\n'); // 制造脏工作区
|
||||
const mainBefore = gitSync(repo, ['rev-parse', 'main']).trim();
|
||||
|
||||
const r = await mergeBranch(repo, 'maestro/tsk_dirty', 'main', 'tsk_dirty');
|
||||
assert.equal(r.ok, false);
|
||||
assert.match(r.error ?? '', /未提交改动/, `应提示未提交改动:${r.error}`);
|
||||
assert.equal(gitSync(repo, ['rev-parse', 'main']).trim(), mainBefore, 'main 无损');
|
||||
});
|
||||
|
||||
test('mergeBranch:重复合并(分支已在 main 里)→ ok,不新建提交', async (t) => {
|
||||
const repo = setup(t);
|
||||
gitSync(repo, ['checkout', '-b', 'maestro/tsk_re']);
|
||||
|
||||
Reference in New Issue
Block a user