Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 51 additions & 10 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
name: Unsigned desktop packages
on:
workflow_dispatch:
inputs:
verify_package_run:
description: Optional existing package run for a Windows verification-only retry
required: false
default: ''
type: string
permissions:
contents: read
jobs:
package:
if: inputs.verify_package_run == ''
strategy:
fail-fast: false
matrix:
os: [macos-15, windows-2025]
runs-on: ${{ matrix.os }}
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
Expand All @@ -20,6 +28,10 @@ jobs:
node-version: 24.19.0
cache: pnpm
- uses: dtolnay/rust-toolchain@1.99.0
- uses: Swatinem/rust-cache@v2
with:
workspaces: apps/desktop/src-tauri
cache-on-failure: true
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
- name: macOS full tests including HTTP transport
Expand All @@ -35,20 +47,49 @@ jobs:
- run: node scripts/build-desktop.mjs --prepare-only
- run: cargo test --locked --manifest-path apps/desktop/src-tauri/Cargo.toml
- run: node scripts/build-desktop.mjs
- run: pnpm package:desktop
- if: runner.os == 'macOS'
run: node scripts/verify-installed.mjs --app "apps/desktop/src-tauri/target/release/bundle/macos/Git View.app" --output installed-verification.json
run: node scripts/verify-macos-package.mjs
- if: runner.os == 'Windows'
shell: pwsh
run: |
New-Item -ItemType Directory -Force dist/desktop-portable
Copy-Item apps/desktop/src-tauri/target/release/git-view-desktop.exe dist/desktop-portable/
Copy-Item apps/desktop/src-tauri/resources/* dist/desktop-portable/ -Recurse
node scripts/verify-installed.mjs --app dist/desktop-portable --output installed-verification.json
run: ./scripts/verify-windows-package.ps1
- uses: actions/upload-artifact@v4
if: always()
with:
name: unsigned-desktop-${{ matrix.os }}
path: |
apps/desktop/src-tauri/target/release/bundle/
dist/desktop-portable/
installed-verification.json
path: dist/installers/
if-no-files-found: error
verify_windows:
if: inputs.verify_package_run != ''
runs-on: windows-2025
timeout-minutes: 10
permissions:
contents: read
actions: read
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.7.0
- uses: actions/setup-node@v4
with:
node-version: 24.19.0
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Download the existing installer without rebuilding
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
PACKAGE_RUN: ${{ inputs.verify_package_run }}
run: |
if ($env:PACKAGE_RUN -notmatch '^\d+$') { throw 'Package run must be a numeric GitHub Actions run ID' }
gh run download $env:PACKAGE_RUN --name unsigned-desktop-windows-2025 --dir dist/installers
if ($LASTEXITCODE -ne 0) { throw 'Could not download the installer' }
- shell: pwsh
run: ./scripts/verify-windows-package.ps1
- uses: actions/upload-artifact@v4
if: always()
with:
name: windows-installer-verification
path: dist/installers/
if-no-files-found: error
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Git View 是一个本地 Git 图形工具,用于查看文件差异、浏览提交历史,以及比较分支和提交。支持整文件暂存、提交和本地分支操作,写入前需要预览确认。

支持中文和英文界面,通过本机 Git 读取仓库,无需连接远程服务。目前为桌面预览版,主要支持 macOS Apple silicon。
支持中文和英文界面,通过本机 Git 读取仓库,无需连接远程服务。目前为桌面预览版,提供 macOS Apple silicon 和 Windows x64 安装包;Windows 暂时只开放查看。

![提交历史与文件差异,浏览器中的演示仓库](docs/verification/screenshots/history-simplification-browser.png)

Expand All @@ -22,7 +22,14 @@ Git View 是一个本地 Git 图形工具,用于查看文件差异、浏览提

## 安装

当前需要从源码构建。以下步骤适用于 macOS Apple silicon。
在 [GitHub Releases](https://github.com/XSY-28/git-view/releases/latest) 下载当前版本的安装包和 `.sha256` 校验文件,无需登录 GitHub。两种包都自带 Node 运行时,使用时只需系统 Git,无需另装 Node、pnpm 或 Rust。

- **macOS Apple silicon**:下载 `macos-arm64.dmg`,打开后先退出 Git View,再双击 **Install Git View.command**。安装位置固定为 `~/Applications/Git View.app`,旧版本保留为 ZIP;完成后弹出磁盘映像。不要再复制一份到 `/Applications`。
- **Windows x64**:下载 `windows-x64-setup.exe`,运行安装向导,再从开始菜单打开。需要 Git for Windows 与 WebView2;缺少 WebView2 时安装程序会下载运行时。Windows 仅支持查看,不开放暂存、提交和分支写入。

当前包尚未代码签名或 Apple 公证,系统可能提示无法验证发布者。确认下载来源后,使用系统提供的“仍要运行”或“隐私与安全性 → 仍要打开”入口,不要关闭系统安全机制。完整步骤与校验、卸载说明见[安装包说明](docs/INSTALL.md)。

### 从源码构建(macOS)

构建需要 Git、Node.js **24.19.0**、pnpm **11.7.0**、Rust 和 Xcode Command Line Tools。CI 使用 Rust 1.99.0。

Expand Down Expand Up @@ -66,7 +73,9 @@ node scripts/create-demo.mjs

## 更新

更新本地源码后,先退出 Git View,再在项目根目录运行:
安装包用户退出 Git View 后,下载新版本并重新安装即可。macOS 会将旧版本备份为 ZIP。

从源码更新的 macOS 用户退出 Git View 后,在项目根目录运行:

```sh
pnpm update:desktop
Expand All @@ -77,7 +86,7 @@ open "$HOME/Applications/Git View.app"

## 支持范围

macOS Apple silicon 已有桌面实机验证。Windows 暂时仅开放查看,源码与桌面宿主由 CI 检查,安装和原生界面尚未实机验证。
macOS Apple silicon 已有桌面实机验证。Windows 暂时仅开放查看,已通过 Windows CI 中的实际安装、原生窗口差异与历史走查、正常退出和卸载验证;Windows 10/11 实体电脑尚未复测。详见[安装包验收记录](docs/verification/2026-10-07-desktop-installers.md)。

- 暂不提供 push、merge、rebase 或历史重写。远程跟踪引用来自本地仓库,不会自动获取远端更新。
- 暂存按整个文件操作,不支持选择差异行或片段。写入限常规仓库和 index;冲突、进行中的 Git 操作、submodule、特殊 index 或外部内容过滤器会被拒绝。暂不支持 `text`、`eol`、`ident`、`working-tree-encoding` 和 autocrlf 转换。
Expand Down
6 changes: 6 additions & 0 deletions apps/desktop/src-tauri/src/sidecar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ impl Sidecar {
use std::os::unix::process::CommandExt;
command.process_group(0);
}
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
// The GUI host has no console; keep its piped background runtime quiet.
command.creation_flags(windows_sys::Win32::System::Threading::CREATE_NO_WINDOW);
}
let mut child = command
.spawn()
.map_err(|_| "Bundled query process could not start")?;
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
"icon": ["icons/icon.png", "icons/icon.icns", "icons/icon.ico"],
"targets": ["app"],
"resources": { "resources/runtime/": "runtime/", "resources/app/": "app/" },
"macOS": { "minimumSystemVersion": "12.0" }
"macOS": { "minimumSystemVersion": "13.5" }
}
}
11 changes: 10 additions & 1 deletion apps/desktop/src-tauri/tauri.windows.conf.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
{
"bundle": { "targets": ["nsis"] }
"bundle": {
"targets": ["nsis"],
"windows": {
"nsis": {
"installMode": "currentUser",
"languages": ["English", "SimpChinese"],
"displayLanguageSelector": true
}
}
}
}
40 changes: 40 additions & 0 deletions docs/INSTALL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 安装 Git View 预览包

在 [GitHub Releases](https://github.com/XSY-28/git-view/releases/latest) 下载对应平台的安装包与同版本 `.sha256` 文件,无需登录 GitHub。开发构建也可从 [打包工作流](https://github.com/XSY-28/git-view/actions/workflows/package.yml) 的成功运行中下载 Artifacts ZIP(需要登录 GitHub)。当前目标平台为 macOS 13.5 及以上的 Apple silicon(arm64)和 Windows x64,尚不提供 Intel Mac、Windows ARM 或 Linux 安装包。macOS 下限由包内 [Node 24 的平台要求](https://github.com/nodejs/node/blob/v24.19.0/BUILDING.md#platform-list)决定。

## macOS Apple silicon

1. 确认系统 Git 可用,在终端执行 `git --version`。如果系统提示安装 Command Line Tools,按提示完成;使用安装包不需要 Node、pnpm、Rust 或完整 Xcode。
2. 下载 `Git-View_0.2.0_macos-arm64.dmg`。可在下载目录执行 `shasum -a 256 -c Git-View_0.2.0_macos-arm64.dmg.sha256` 核对文件。
3. 退出正在运行的 Git View,打开 DMG,双击 `Install Git View.command`。安装无需管理员权限,固定安装到 `~/Applications/Git View.app`,完成后自动打开应用。
4. 安装完成后弹出 DMG。不要把另一份 Git View.app 放在 `/Applications`,也不要保留解压的备份应用;这样能避免多个同名搜索入口。

安装程序从磁盘映像中解压到临时目录,用包内 Node 启动项目现有安装脚本。它校验应用身份和资源哈希、备份旧安装、检查运行状态、安装到固定路径、回收临时应用、清理注册并刷新搜索服务。磁盘映像中只保留压缩的应用,不会出现可被 Spotlight 注册的第二份 `.app`。

已有版本备份到 `~/Library/Application Support/Git View/backups`,格式为 ZIP。安装中发生错误时保留现有恢复机制;错误会显示在终端中。注册与搜索进程检查不能证明 Spotlight 面板结果正确,需要重新搜索确认。安装程序不会自动停止你正在使用的应用。

当前包未做 Apple Developer ID 签名或公证。系统拦截时,先确认下载来源,再用“系统设置 → 隐私与安全性 → 仍要打开”。安装命令和应用可能分别触发检查;不要通过关闭 Gatekeeper 绕过。

卸载时先退出应用,再删除 `~/Applications/Git View.app`。若也想删除设置和备份,另行删除 `~/.local/share/git-view` 和上述备份目录;不要删除你自己的 Git 仓库。

## Windows x64

1. 安装 [Git for Windows](https://gitforwindows.org/),确保新开的终端中 `git --version` 能成功。
2. 下载 `Git-View_0.2.0_windows-x64-setup.exe`。用 PowerShell 执行 `Get-FileHash .\Git-View_0.2.0_windows-x64-setup.exe -Algorithm SHA256`,与同版本 `.sha256` 文件中的哈希比较。
3. 运行安装向导。应用安装到当前用户目录,不要求管理员权限;缺少 Microsoft WebView2 时安装程序会下载运行时,因此首次安装可能需要联网。
4. 从开始菜单打开 Git View,点击顶部打开仓库按钮选择本地仓库,或展开“手动输入路径”填入仓库的完整路径。

Windows 当前只支持查看,包括当前改动、历史、版本比较和历史调查。暂存、取消暂存、提交、创建和切换分支均由后台明确拒绝。浏览器 HTTP 备用入口暂不支持 Windows,请使用桌面应用。

当前 EXE 未做代码签名,Windows 可能显示未知发布者或 SmartScreen 提示。仅在核对 GitHub 下载来源和哈希后使用系统提供的“更多信息 → 仍要运行”入口,不要关闭 SmartScreen。若设备的组织策略禁止未知发布者,需要遵循该设备的策略。

卸载使用“设置 → 应用 → 已安装的应用 → Git View → 卸载”。应用设置默认在用户主目录的 `.local\share\git-view`,卸载不会自动删除你的仓库。

## 构建与验收边界

包构建工作流分别在 macOS ARM 与 Windows x64 runner 上执行类型检查、适用的真实 Git 测试和 Rust 宿主测试,再构建安装包。

- macOS:验证 DMG 完整性和挂载、执行包内安装程序 dry-run,并对从 DMG 解压出的内容验证内置 Node、中文/空格路径、仓库只读指纹和 stdio 退出。原生 GUI 在本机固定安装路径验收。
- Windows:运行真正的 NSIS 静默安装到包含中文和空格的目录,对安装结果做包内运行检查;启动安装后的 release 应用,用 WebView2 调试连接驱动真实窗口和 Tauri IPC,检查打开中文路径仓库、暂存/未暂存差异、历史差异、正常关闭与 Node 子进程回收,最后卸载。

这些检查的通过与失败以该版本对应的 GitHub Actions 和随包验证报告为准。Windows runner 是 CI 系统,不能替代所有 Windows 10/11 实体电脑上的验收;SmartScreen、Gatekeeper、人工安装向导和原生系统目录选择对话框也需要目标设备复测。当前安装包没有自动更新机制。
39 changes: 39 additions & 0 deletions docs/verification/2026-10-07-desktop-installers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 桌面安装包验收(2026-10-07)

本轮为桌面预览版 0.2.0 提供 macOS Apple silicon DMG 和 Windows x64 NSIS EXE。构建源码为 `727be42df4ed0fcecb69d38348d61dd1831e02ef`,安装步骤见[安装说明](../INSTALL.md)。安装包没有 Developer ID、公证或 Windows 代码签名。

## 已验证

| 项目 | 结果与证据 |
| --- | --- |
| 本机源码检查 | `pnpm check` 通过:232 个测试、32 个文件,以及类型检查和构建。 |
| 本机宿主检查 | `cargo test --locked` 的 4 个实际 sidecar 生命周期测试通过,包括正常退出、协议错误、崩溃重启和 Git 子进程回收。 |
| macOS 安装与恢复脚本 | `pnpm test:install-desktop` 的 27 个测试通过。 |
| 本地 macOS DMG | 完整性、挂载、包内安装程序真实 dry-run 及解压内容验证通过;实际安装到 `~/Applications/Git View.app`。 |
| 本机 macOS 原生窗口 | 暂存差异显示 `-version one / +version two staged`,工作区差异显示 `-version two staged / +version three working`,首次提交显示 `+version one`;正常退出后主进程与 Node 子进程均结束。 |
| macOS 清理 | 用既有安装脚本移除本轮原始构建副本;旧版本保存为 ZIP,注册唯一检查及搜索服务刷新通过。最近打开的仓库恢复为项目目录,测试临时仓库已移除。 |
| 云端 macOS 包 | [打包运行](https://github.com/XSY-28/git-view/actions/runs/37610447140)的 macOS job 成功。CI 包验证报告与本机同源码构建的 DMG 原生窗口走查是分别记录的证据。 |
| Windows 安装与窗口 | 真实 NSIS 安装到中文/空格目录、包内运行、打开中文仓库、暂存/工作区差异、历史、仓库只读指纹、后台 Node 无控制台、正常关闭与子进程回收、卸载全部通过,见同一打包运行的 Windows job 与随包两份验证报告。 |

本地 DMG 的 SHA256 为 `42708befe15e289747273e4bb92c694c6e33c0b6062e2eba4bc8398a00b6b49c`。安装后的原生可执行文件和资源哈希,与从该 DMG 解压验证的内容一致。本地验收报告保存在忽略的 `dist/installers/`,CI 的平台包与报告保存在对应工作流 Artifacts。

## Windows 问题与回归检查

最初的安装包可完成构建、NSIS 安装和包内 CLI / stdio 验证,但原生窗口测试未能接入 WebView2。进程诊断和桌面截图确认窗口、WebView2 与 Node 已运行;WebView2 命令行缺少测试请求的调试参数。

微软文档明确指出,[管理员宿主会忽略环境变量中的 WebView2 调试参数](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/webview-features-flags)。验证脚本在一次性 CI 机器中仅为 `git-view-desktop.exe` 设置对应 HKLM 参数,保留并在 `finally` 中恢复旧值。应用的正常启动没有调试端口配置。

截图同时暴露了后台 Node 额外分配控制台窗口的问题。[旧包回归运行](https://github.com/XSY-28/git-view/actions/runs/37610032540)在实际进程控制台检查中失败。修复在 Windows 宿主中以 `CREATE_NO_WINDOW` 启动后台 Node,并为 Node 发起的 Git 查询设置 `windowsHide`;保留原有管道通信和 Windows Job 退出清理机制。新包已通过同一进程检查及完整窗口走查。

Windows 安装包的 SHA256 为 `ea2cfd34635e5fc47a447bc62fa439822104cf31b6bc8b9da98b436ec0e8cb37`,下载到本机后已与随包校验文件核对。CLI 验证报告中的 `gui: unverified` 只表示 CLI 脚本不执行 GUI 检查;原生窗口的独立报告列出上述窗口检查全部通过。

![Windows 安装后的工作区差异](screenshots/desktop-installers-windows-changes.png)

![Windows 安装后的历史提交差异](screenshots/desktop-installers-windows-history.png)

## 验证边界

- Windows 仍只支持查看,暂存、取消暂存、提交与分支写入尚未开放。
- Windows CI 环境中的实际窗口验证不等于所有 Windows 10/11 实体电脑验收;人工安装向导、原生目录选择器与 SmartScreen 仍需目标设备复测。
- macOS 首次互联网下载后的 Gatekeeper 流程、不同 macOS 版本及 Spotlight 搜索面板尚未复测。注册唯一、搜索服务刷新和新 PID 不能替代搜索面板证据。
- 暂无 Intel Mac、Windows ARM 和 Linux 安装包,也没有自动更新机制。
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"inspect": "node dist/cli.mjs inspect --repo \"$PWD\" --json",
"stop": "node dist/cli.mjs shutdown --json",
"build:desktop": "node scripts/build-desktop.mjs",
"package:desktop": "node scripts/package-desktop.mjs",
"update:desktop": "node scripts/install-desktop.mjs --check-ready && pnpm build && pnpm build:desktop && pnpm install:desktop",
"install:desktop": "node scripts/install-desktop.mjs",
"repair:desktop-search": "node scripts/install-desktop.mjs --repair-search",
Expand Down
2 changes: 1 addition & 1 deletion packages/git-cli/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function runGit(cwd: string, args: string[], options: RunOptions = {}): P
'-c', 'i18n.logOutputEncoding=utf-8', '-c', 'protocol.allow=never',
];
return new Promise((resolve, reject) => {
const child = spawn('git', [...globalArgs, ...args], { cwd, env, shell: false, stdio: ['pipe', 'pipe', 'pipe'] });
const child = spawn('git', [...globalArgs, ...args], { cwd, env, shell: false, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] });
const stdout: Buffer[] = [];
const stderr: Buffer[] = [];
let bytes = 0;
Expand Down
7 changes: 6 additions & 1 deletion scripts/install-desktop.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,12 @@ async function validateBundle(path, run, expectedVersion) {
async function requireStopped(run, paths) {
const output = (await run('/bin/ps', ['-axo', 'pid=,comm='])).stdout;
for (const line of output.split('\n')) {
const command = line.trim().replace(/^\d+\s+/, '');
const entry = line.trim().match(/^(\d+)\s+(.+)$/);
if (!entry) continue;
const command = entry[2];
// A distribution installer runs this script with the incoming bundle's
// own Node. Exempt only this exact process, never another app or sidecar.
if (Number(entry[1]) === process.pid && command === process.execPath) continue;
if (command.endsWith('/Contents/MacOS/git-view-desktop') || paths.some(path => command.startsWith(`${path}/Contents/`))) {
throw new Error('Git View is running. Quit Git View, then rerun the desktop update/install command. No process was stopped.');
}
Expand Down
Loading
Loading