鸿蒙PC_Git-Bash-ohos适配全记录

鸿蒙PC开源移植:Git Bash原生适配全记录

欢迎加入开源鸿蒙PC社区: https://harmonypc.csdn.net/

欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper

摘要: 把 Git Bash 的操作习惯带到鸿蒙 PC,需要同时处理终端输入、Shell 解析和真实 Git 仓库读写。本文从一次 add → commit → log 操作出发,拆解 ArkTS、N-API、C++17 和 NetworkKit 的分工,说明索引与对象兼容、HTTPS 传输、桌面交互以及构建验证的实现过程,并结合已有模拟器运行记录说明当前成果与后续工作。

关键词: 鸿蒙PC、开源软件移植、Git Bash、ArkTS、Harmony NDK、原生Git

一、先确定要带到鸿蒙 PC 的是什么

使用 Git Bash 时,开发者通常不会刻意区分终端窗口、Shell 和 Git:打开窗口,进入项目目录,查看修改,提交代码,这些动作连在一起就构成了日常工作。但开始适配时,这三个部分必须拆开。窗口能显示字符,并不意味着命令已经被正确解释;命令能返回一段结果,也不意味着磁盘上的 Git 仓库发生了正确变化。

我希望保留的是这种连续的命令行工作方式:查看状态后只暂存需要的文件,提交之后立刻查询历史,必要时继续管理分支。应用因此采用 Git Bash 风格的深色终端和 MINGW64 提示符,仓库操作则接入鸿蒙侧的原生服务。MINGW64 在这里是界面风格的一部分,不能据此判断应用正在运行 Windows 的 MinGW 环境。

上游参考项目 Git for Windows 涉及 Git、MSYS2 和 mintty。当前工程的技术路线是原生适配终端交互和受支持的 Git 行为,没有直接搬运 Windows 可执行文件,也没有把完整 Bash、MSYS2 或 mintty 编译进 HAP。因此,准确的项目定位是"面向鸿蒙 PC 的 Git Bash 兼容终端与原生 Git 实现"。这个边界决定了后面的代码组织和验证方法。

工程托管在 AtomGit PC 社区:ohos_harmony-git-bash。本文代码分析对应社区仓库的现有实现;设备画面和实际运行结论来自仓库保存的 2026 年 9 月 5 日记录,文章修订日期不代表重新完成了一轮设备测试。

二、把终端输入接到真实仓库

2.1 为什么要把页面、Shell 和仓库服务分开

假设用户输入 git commit -m "first native commit"。页面需要接收键盘事件并保留回显,Shell 需要识别引号中的空格,Git 服务需要读取索引、生成提交并更新引用。如果把这些逻辑都塞进页面,终端样式改动就容易影响命令行为,测试一个提交还必须先启动整个应用。

项目把调用过程组织为:

text 复制代码
Index.ets:输入、回显、键盘、滚动
    ↓
GitShell.ets:命令解析、参数展开、内置命令、Git 分发
    ↓
GitRepositoryService:仓库操作接口
    ↓
NativeGitRepositoryService.ets:鸿蒙原生服务适配
    ↓ N-API
libgit_native.so:对象、索引、引用、工作区、传输协议
    ↓
设备上的真实仓库文件

这份结构可以直接映射到源码。阅读工程时,先看 Index.etsexecuteCommand(),再进入 GitShell.ets,最后跟到原生服务,就能知道一条输入在哪一层变成仓库操作。页面源码

2.2 在页面生命周期中接入原生后端

页面进入时会获取应用上下文,把原生仓库服务与应用文件目录交给 Shell。下面是 aboutToAppear() 中的实际接入片段,省略了后续欢迎信息和终端行初始化:

typescript 复制代码
const context = this.getUIContext().getHostContext();
let nativeMessage: string = '';
if (context !== undefined) {
  nativeMessage = this.shell.attachRepositoryService(
    new NativeGitRepositoryService(),
    context.filesDir
  );
}

传入 context.filesDir,使应用可以先在自己的文件空间里建立本地工作流。用户打开其他仓库时,还需要处理文件选择器返回的 URI 和实际可访问范围。先让应用内仓库走通,再扩展授权目录,能够分别定位 Git 数据处理与系统文件访问的问题。

服务接口也为测试保留了入口:Shell 的参数解析可以使用可控的测试后端;真实索引和对象操作则在原生 fixture 中验证。项目保留的内存兼容行为用于 Shell 测试,不能拿它代替设备上的原生后端。

2.3 原生库具体包含什么

NativeGitRepositoryService.etslibgit_native.so 导入 inspectRepositorystageRepositorycommitRepositoryreadLog 等函数。N-API 层把 ArkTS 参数交给 C++,再把操作结果和仓库快照返回到应用。页面不需要知道二进制 index 的字段布局,C++ 也不负责终端行的颜色。

原生库的 CMake 配置明确列出了实现文件,以下为其中两个完整配置项:

cmake 复制代码
add_library(
  git_native
  SHARED
  git_repository.cpp
  git_transport.cpp
  napi_init.cpp
)

target_link_libraries(
  git_native
  PUBLIC
  libace_napi.z.so
  libnet_http.so
  z
)

工程另外启用了 C++17。三个源文件分别承接仓库处理、传输协议和 N-API 导出,zlib 用于对象压缩等操作。这条链路没有通过启动系统 git 子进程完成本地命令;主机测试使用系统 Git,是为了建立和核对测试仓库。CMake 配置 · 服务实现

三、一次 commit 背后,究竟写了哪些内容

3.1 首先区分工作区、索引和 HEAD

实现 Git 时,一个容易低估的问题是:文件"现在的内容"和"准备提交的内容"可能不同。用户暂存文件后还可以继续修改,提交应该使用索引中的版本。若直接扫描工作区生成提交,就会把暂存后的修改一起提交,违背开发者对 git add 的预期。

因此,状态查询需要比较三份数据:工作区文件、索引记录和 HEAD 指向提交中的树。工作区与索引之间的差异对应尚未暂存的改动;索引与 HEAD 树之间的差异对应待提交的改动。未跟踪文件和忽略规则还需要单独判断,status 不能只靠文件是否存在来实现。

项目通过 ReadIndex() 读取 index v2/v3/v4,并在写入时规范化为 v2。这个选择让写入路径相对集中,但也意味着不能声称保留所有可选扩展:split-index、untracked-cache 等扩展数据目前不会完整保留。适配已有仓库时,读兼容范围和写回行为必须分别说明。索引与仓库实现

3.2 add 的结果要进入 Git 对象库

对于文件内容,Git 使用 blob 对象保存数据。对象标识并非直接对文件原始内容计算哈希,还包含对象类型、长度和 NUL 分隔符。项目 WriteLooseObject() 中实际构造待压缩数据的表达式是:

cpp 复制代码
const std::string raw =
    type + " " + std::to_string(payload.size()) + '\0' + payload;

该函数先通过 HashObjectId() 得到对象 ID,然后把压缩后的内容放进 objects/<前两位>/<剩余部分>。暂存操作还要把路径、对象 ID、文件模式等信息写入索引。终端上的 git add 通常没有大段成功提示,真正有意义的结果保存在对象和索引中。

这也解释了为什么"命令没有报错"还不足以完成测试:如果对象头、长度或者索引写入有误,后面的 commit 可能读到错误内容,其他 Git 工具也可能无法识别生成的数据。

3.3 commit 从索引生成树,再更新引用

CommitRepository() 先检查提交消息,加载仓库上下文,读取索引,计算暂存差异,然后调用 BuildTreeFromIndex() 创建 tree 对象。如果当前没有待提交变化,就返回对应错误,避免生成一次无意的空提交。

有了 tree 后,函数组织 commit 对象的文本。下面是源码中的连续片段:

cpp 复制代码
const std::string timestamp = CurrentGitTimestamp();
std::string payload = "tree " + treeObjectId + "\n";
if (!context.headObjectId.empty()) {
  payload += "parent " + context.headObjectId + "\n";
}
payload += "author " + author + " " + timestamp + "\n";
payload += "committer " + author + " " + timestamp + "\n\n";
payload += message;
if (payload.back() != '\n') {
  payload.push_back('\n');
}

首次提交没有父提交;后续提交需要记录原来的 HEAD。生成 commit 对象之后,还要判断 HEAD 是指向本地分支的符号引用,还是 detached HEAD,再写入相应引用及 reflog。最后重新检查仓库,返回新的快照,让终端输出反映操作后的状态。

支持一个 commit 命令,实际依赖树构建、身份配置、时间格式、对象压缩、引用和日志等多个模块。项目中的专用 update-ref 事务路径另有预检与回滚处理;不能把那部分保证笼统套用到所有仓库写操作上。

3.4 为什么还要读取 packed 对象和 worktree

只在新建小仓库中测试,会遗漏真实使用场景。已有仓库可能经过对象打包,分支引用可能放在 packed-refs,关联 worktree 的 .git 可能是文件而非目录。若假定所有对象都以 loose 形式存在、所有 .git 都是目录,打开开发者原来的项目时就容易失败。

当前实现覆盖 loose/packed 对象、OFS_DELTA 和 REF_DELTA 解析,也处理 linked worktree 的 commondir。这些能力使"打开仓库"从演示目录扩展到更接近日常使用的仓库结构。大型 pack 目前仍会读入内存,流式读取和缓存属于后续优化方向,不能用小仓库测试推断大仓库性能。

四、终端兼容中,最容易被外观掩盖的细节

4.1 引号、展开和重定向都有先后关系

命令解析不能简单使用空格切分。git commit -m "first native commit" 中的消息是一个参数;echo '$HOME'echo "$HOME" 对变量展开的要求不同;echo HarmonyOS-PC > pgc.txt 还包含输出重定向。如果先丢掉引号信息,再处理变量和路径,就很难恢复用户原本的输入含义。

GitShell.ets 承担引号感知的解析、参数展开、命令替换、通配符和重定向处理。当前管道是受支持内置命令之间的内存数据传递,文件描述符重定向限制在 0、1、2。它可以把 printf 输出传给已实现的 Git stdin 命令,但没有因此获得任意外部程序执行能力。Shell 实现

heredoc 又增加了一层交互状态:输入 << 后,后续行应进入正文收集,直到遇到结束标记,而不是每行都当作新命令执行。页面必须与 Shell 的输入状态配合,才能继续使用同一个终端输入区。

4.2 同一个输入框承担多种会话

普通命令、标签消息编辑、HTTPS 凭据和 heredoc 都可能经过同一个输入框,但它们对历史记录、回显和空白字符的处理不同。例如凭据输入不应进入普通命令历史;消息编辑中的空白也不应被普通命令的 trim() 逻辑抹掉。

页面 executeCommand() 先检查 editorActive()credentialPromptActive(),再决定如何读取输入、记录历史和生成可复制的终端行。TerminalInputSession.ets 管理历史与补全,TerminalAnsi.ets 处理颜色片段和显示尺寸。这样的拆分让键盘行为可以单独测试,也便于排查"输入对了但显示不对"的问题。

Ctrl+C 同样结合上下文处理:网络操作进行时用于取消请求,heredoc 收集时用于取消当前输入。在没有 PTY 进程会话的前提下,不能把它描述为已经具备完整 Unix 进程信号行为。

4.3 一次实际发现的路径显示问题

9 月 5 日的运行记录中有一个具体问题:切换目录后,命令回显和 pwd 反映实际目录,但顶部路径和底部提示符可能仍停留在启动目录。记录提出,后续应把这些派生字段接入 ArkUI 状态更新。

这说明业务对象内部状态与页面响应式状态需要分别检查。定位时先用 pwd 确认工作目录,再观察提示符刷新,能够判断问题处于命令执行还是界面呈现。本文截图在启动目录执行,画面路径与操作一致;文章修订没有把这个已知问题描述成已经修复。原始运行记录

五、HTTPS 远程操作:网络请求只是其中一部分

本地工作流完成后,远程协作还需要处理 Git 协议。把仓库 URL 交给 HTTP 客户端,并不会自动得到一个可用的 Git 仓库。原生协议层要理解远程引用、能力协商和 pack 数据,系统网络层则负责实际请求、TLS 与连接控制。

项目在 NativeGitRepositoryService.ets 中使用 NetworkKit,二进制响应采用 ARRAY_BUFFERgit_transport.cpp 负责相关协议数据,仓库层安装 pack/index 并更新引用。以 fetch 为例,可以按以下顺序理解实现:

  1. 发现远程引用与服务端能力,确定需要获取的对象。
  2. 构造 upload-pack 请求,经 NetworkKit 收发二进制数据。
  3. 处理 side-band 进度、错误及 pack 内容,验证校验和并解析对象。
  4. 安装 pack 与索引,再更新远程跟踪引用及 FETCH_HEAD

把数据校验放在引用更新之前,是因为引用一旦指向缺失或损坏的对象,仓库就会进入不一致状态。测试需要覆盖协议错误和损坏数据,不能只比较下载字节数。传输协议实现

push 走 receive-pack,并处理 report-status。本地分支检查通过不代表服务器一定接受,服务端报告也参与成功判定。pull 当前只执行 fast-forward 更新,遇到分叉历史会拒绝继续,尚未实现三路合并、rebase 和冲突处理。这个边界会直接影响开发者能否采用某个协作流程,必须在介绍"支持 pull"时一起说明。

HTTPS 请求使用系统 CA,支持 http.sslCAInfo,并拒绝 http.sslVerify=false。凭据通过专门的存储与脱敏模块处理,AssetStoreKit 可用时尝试持久化,否则保留在进程内存。系统证书、代理、凭据重启恢复和真实可写远程仓库仍需要目标设备验证;本文本地提交截图不用于证明这些网络能力已经通过设备验收。

六、从构建成功到行为正确,要验证不同的层

6.1 先准备能复现的工程环境

工程使用 DevEco Studio 与 HarmonyOS 6.1.1(API 24)SDK,目标设备包含 2in1。原生主机测试还需要 Git、C++17 编译器和 zlib 开发环境。下载工程的命令在开发电脑终端执行:

bash 复制代码
git clone https://atomgit.com/OpenHarmonyPCDeveloper/ohos_harmony-git-bash.git
cd ohos_harmony-git-bash

完整验证脚本以 macOS DevEco 安装路径为默认值。在配置好该环境,或按编译说明设置 DEVECO_SDK_HOMEJAVA_HOMEHVIGOROHPM 后执行:

bash 复制代码
bash ./scripts/verify.sh

Windows 开发者可以在 DevEco Studio 中打开工程、同步依赖,再选择设备运行;不能因为安装了 Git Bash,就认为 macOS 默认路径、rsync 和主机 C++ 依赖已经可用。完整环境配置见 README.OpenHarmony_CN.md

此前构建曾遇到中文工程路径问题。现在验证脚本会检测路径,并在需要时复制到临时英文目录构建,再把 HAP 复制回来。这只解决脚本所覆盖的构建路径;直接在 IDE 中打开项目时,仍建议采用英文工作目录,把路径问题与业务代码问题分开处理。

6.2 主机 fixture 检查的是 Git 数据互通

scripts/verify.sh 先编译并运行原生 fixture,再执行 ArkTS 测试和 HAP 组装。原生测试中的一个关键做法,是调用系统 Git 创建仓库,再让本项目 C++ 服务读写,最后交回系统 Git 检查。

例如,现有测试调用 CommitRepository() 后,会用系统 Git 的 rev-parse HEAD 对比原生服务返回的提交 ID,并读取提交消息。这样可以发现"应用自己的写入和读取都用了同一种错误格式"而导致的假通过。索引、分支、工作区和打包对象也需要在真实 Git 数据上验证。原生测试源码

ArkTS 测试关注带引号参数、Shell 展开、历史补全、终端解析和凭据脱敏。HAP 组装与双 ABI 配置检查的是库能否进入目标工程。三类验证分别回答数据是否正确、应用逻辑是否正确、目标包能否构建,彼此不能替代。

七、把模拟器中的一次提交完整对应起来

7.1 环境和证据来源

仓库记录的运行环境是 OpenHarmony-6.1.1.125,API 24,设备类型 2in1,型号 emulator。9 月 5 日沿用了 9 月 4 日已通过完整验证的开发 HAP,运行记录保存了代码基线、HAP 哈希和截图信息。这是模拟器中的实际运行,不是实体鸿蒙 PC 的验收记录。

脚本生成的开发包位于:

text 复制代码
entry/build/default/outputs/default/entry-default-unsigned.hap

在 DevEco Studio 中选择 entry/default、连接设备并点击 Run;目标设备要求签名时,完成对应调试签名。构建产物存在和设备允许安装是两个步骤,不应把未签名包描述成对所有设备都能直接安装。

7.2 在应用终端逐行执行

本次使用应用自带的 demo-app 仓库,新增一个文件,并且只暂存这个文件。作者配置通过命令级 -c 传入,不修改全局身份:

bash 复制代码
echo HarmonyOS-PC > pgc.txt
git add pgc.txt
git -c user.name=PGC -c user.email=pgc@example.invalid commit -m Verify-native-Git-on-HarmonyOS
git status --short
git log --oneline -1

图1:2026年9月5日的完整桌面运行截图,保留应用窗口、鸿蒙桌面和任务栏;设备侧 screenCap 采集,文章未改绘运行结果。

画面中的 commit 返回 [main 389cf92],随后的 log --oneline -1 也读到 389cf92。两者一致,说明后一次查询读取到了刚写入的提交引用。复现时提交哈希会受到时间和仓库内容影响,应比较同一次运行中的两处结果,而不是要求得到截图中的固定哈希。

status 仍显示 README.mddocs/porting-notes.md 为未跟踪文件,这并不表示刚才的提交失败。这两个文件原本就在演示仓库中,本次只执行了 git add pgc.txt,所以它们没有进入提交。这个结果对应了前文工作区与索引分离的设计。

7.3 进一步检查"暂存后再修改"的语义

下面是供读者在测试仓库中执行的补充实验,不是上述截图已经展示的结果。请选用尚不存在的测试文件名:

bash 复制代码
echo staged-version > pgc-index-check.txt
git add pgc-index-check.txt
echo working-version > pgc-index-check.txt
git diff --cached
git diff

核对重点是:暂存差异中应出现 staged-version,工作区差异应体现它到 working-version 的变化。这个实验直接检查索引是否保留了暂存时的内容。执行时还应留意测试仓库原有改动,避免把其他文件的差异误认为本次结果。

八、遇到问题时,沿着哪条链路排查

现象 优先检查的位置 判断依据
页面能显示,原生仓库打不开 页面服务接入、N-API 库和设备 ABI 先确认原生服务初始化结果,再看路径;窗口出现不能证明 .so 已正确接入。
pwd 正确,顶部路径不更新 ArkUI 页面状态与派生显示字段 这是已有运行记录中的问题,先区分实际目录和显示目录。
有文件改动却提示无内容可提交 工作区与索引 核对是否对目标文件执行 add,以及暂存后是否继续修改。
小仓库正常,已有仓库读失败 index 版本、packed 对象、worktree 元数据 新建演示仓库未必覆盖这些存储形式。
工程在中文目录构建失败 工具链路径和临时目录逻辑 先在英文路径复现,避免误改业务代码。
HTTPS 请求失败或 push 被拒绝 URL、TLS、认证、服务端报告、分支关系 区分网络连接失败与 Git 服务端拒绝更新。

这些判断点把笼统的"鸿蒙上不能用"拆成可以定位的工程问题。页面、文件权限、Git 数据和网络协议有各自的证据,修复时应回到对应层验证。

九、T0 / T1 / T2 与下一步工作

本文用 T0 表示基础构建运行,T1 表示本地主要工作流,T2 表示远程与增强能力。详细参数范围保存在 工程能力说明路线图,这里按使用场景归纳:

阶段 当前成果 还需要完成的验证或实现
T0:构建和终端 已有开发构建与模拟器安装启动记录;ArkTS 终端连接原生服务,提供历史、补全和基础内置命令。 实体 PC 键盘、输入法、剪贴板和窗口体验;已知路径显示刷新问题。
T1:本地 Git 已实现对象、索引、分支、标签、引用、reflog 等操作;模拟器记录直接覆盖 add → commit → status → log 更多真实项目与授权目录回归;大型 pack 内存占用、长路径等验证;submodule 检出仍有限制。
T2:远程和 Shell 扩展 已实现 HTTPS 引用发现、fetch、clone、push 与仅快进 pull,以及受支持的管道、重定向和 heredoc。 真实远程认证、权限恢复与异常网络验证;SSH、完整 PTY、外部进程和三路合并尚未实现。

这次适配让我更明确地看到了命令行工具的验收重点:界面保留了使用习惯,真正决定工具价值的仍是输入能否落到正确的仓库数据。add 保存哪一版内容,commit 如何形成历史,log 能否重新读出它们,才是这一阶段最扎实的结果。

补充运行记录:2026 年 9 月 11 日在本机创建了独立的 MateBook Pro 2in1 模拟器实例(HarmonyOS 6.1.1 / API 24),安装了按当前源码构建的开发 HAP,并确认应用进入 PC 横向窗口。下面的截图只证明 PC 窗口启动和终端界面适配;由于该实例的系统输入法会在自动化输入时改写空格和重定向符号,本轮没有把未能可靠复现的差异命令截图作为证据,也没有据此新增 Git 行为结论。

图2:2026年9月11日,在 HarmonyOS 6.1.1 / API 24 MateBook Pro 2in1 模拟器中安装当前开发 HAP 后的启动画面。画面保留 PC 横向窗口和桌面,截图不代表实体鸿蒙 PC 真机。

接下来需要把主机测试覆盖继续延伸到实体鸿蒙 PC 上,优先核对文件访问、输入体验和远程异常恢复,再逐步补齐能力边界。这样每增加一项命令,读者都能在源码、测试或设备记录中找到对应依据。

参考资料与源码入口

相关推荐
是店小二呀2 小时前
鸿蒙PC开源移植:Docker桌面工作台与Linux运行时桥接
docker·鸿蒙pc
是店小二呀3 小时前
开源鸿蒙PC原生适配:Node版本管理器命令行移植
docker·鸿蒙pc
GitCode官方4 天前
开源鸿蒙跨平台直播|napigen 开源鸿蒙跨端实践:打通 Flutter 与 ArkTS
人工智能·鸿蒙pc·atomgit
GKxx19 天前
在 HarmonyOS 上给 QEMU 搭一个最小 aarch64 Linux guest(内核 + busybox initramfs)
linux·华为·qemu·harmonyos·鸿蒙·鸿蒙pc
特立独行的猫a1 个月前
Tauri v2的Rust应用 → HarmonyOS(鸿蒙 PC)移植30分钟速成指南
开发语言·rust·harmonyos·tauri·移植·鸿蒙pc
特立独行的猫a2 个月前
鸿蒙PC Node.js三方库移植的AI移植框架与社区贡献完整流程
华为·node.js·harmonyos·三方库移植·鸿蒙pc
特立独行的猫a2 个月前
Python三方库鸿蒙PC移植指南PPT
harmonyos·移植·鸿蒙pc·python三方库
特立独行的猫a2 个月前
Python的C/C++三方库移植到鸿蒙PC实战踩坑记
c语言·c++·python·harmonyos·三方库移植·鸿蒙pc