1. 命令行与图形界面到底该选谁:先搞清使用场景
刚上手版本控制的人,几乎都会在同一个岔路口停下来:一边是黑底白字的终端,git status、git commit敲下去,输出一屏信息看得心里发虚;另一边是各种图形客户端,鼠标点几下就能提交、推送、看差异,看着就亲民。我在带新人的时候经常被问“到底该学哪个”,其实这个问题本身问偏了——Git命令和GUI不是二选一的关系,它们覆盖的是两类不同的工作场景,谁也没法完整替代谁。真正靠谱的姿势是:命令行负责兜底和精细操作,GUI负责日常快节奏的提交与代码走查,两套手感都要有。
这篇内容我打算把两块讲透:一块是Git命令的安装、配置和高频用法,另一块是GUI界面的基本操作路径,中间穿插我自己踩过的坑。适合刚接触版本控制、正在纠结要不要装图形客户端的同学,也适合已经用了很久、但一直停留在“add、commit、push三板斧”的人。全文的命令都是可以直接复制跑的,界面的操作路径我会写到“点哪个菜单”这个粒度,尽量让你看完就能动手。
提示:下面所有命令的演示环境是 Windows 上的 Git Bash,Linux 和 macOS 的写法基本一致,只有安装环节和路径写法有差异,我会在相应位置标出来。
1.1 Git命令行的真实优势
命令行最大的价值不是“显得专业”,而是它能把Git的完整能力暴露出来。GUI工具本质上是对底层命令的封装,封装就意味着取舍:常用的操作给你做成按钮,不常用的就藏起来甚至不实现。举几个特别典型的例子,git rebase -i的交互式变基、git add -p的分块暂存、git reflog的历史回溯,这三样在绝大多数图形客户端里要么根本没有入口,要么做成了半残废的半成品。而这三样恰好是处理“提交历史乱了”“只想提交部分改动”“误删分支要救回来”这类事故的关键工具。
另一个优势是可脚本化和可复现。命令行敲过一遍的流程,可以原封不动写进脚本、写进文档、发给同事;GUI操作就很难用文字精确描述,“点那个按钮,然后选第二个选项”这种表述在跨版本、跨平台的时候经常失效。团队协作里拉齐流程,命令行是唯一靠谱的载体。
还有一点容易被忽略:报错信息。GUI遇到问题往往只弹一个“操作失败”的红框,或者给一段被截断的提示;命令行会把完整的错误堆栈给你,包括哪个文件冲突、哪个引用找不到、远端返回了什么。排查问题时,完整错误信息等于一半的解决方案。
1.2 GUI 覆盖不到的四类操作
我整理了一下自己日常遇到的情况,下面这四类操作基本必须回到命令行:第一类是历史重写,包括压缩提交、修改历史提交信息、调整提交顺序,这些都需要交互式编辑;第二类是精细暂存,一个文件里改了五处,只想提交其中两处,GUI很难做到按行选择;第三类是事故恢复,分支被误删、提交被reset掉了、合并搞砸了,要靠reflog找回丢失的引用;第四类是批量操作,比如一次性查看所有分支的领先落后情况、批量清理已合并分支。
注意:这不是说GUI工具做得不好,而是产品定位决定的。图形客户端的目标用户是“日常提交不想敲命令”的人群,把交互式变基这种需要多轮编辑的操作做成界面,成本高、收益低。
1.3 两套工具的分工对照表
| 操作类型 | 推荐方式 | 理由 |
|---|---|---|
| 日常提交、推送、拉取 | GUI | 鼠标点选文件直观,不容易漏文件 |
| 代码走查、逐行对比 | GUI | 差异高亮和并排对比体验更好 |
| 查看提交历史、分支图 | GUI | 图形化的分支拓扑一眼看懂 |
| 交互式变基、修改历史提交 | 命令行 | GUI 基本不实现 |
| 按代码块暂存 | 命令行 | git add -p是唯一顺手的方案 |
| 冲突解决 | 视情况 | 简单冲突用 GUI,复杂冲突回命令行 |
| 误操作恢复 | 命令行 | 依赖reflog,GUI 无入口 |
| 批量脚本化处理 | 命令行 | 可复现、可分享 |
这张表我建议你先存下来,等手上真的遇到对应场景时再回头看,比一开始就死记硬背有用得多。
2. Git安装与首次配置:把地基一次打牢
安装这一步看着简单,实际上后面遇到的很多“玄学问题”,根源都在这儿。我自己经历过两次比较典型的:一次是换行符配置选错,导致每次提交都提示整个文件被改写;另一次是中文路径显示成八进制转义,看日志完全不知道改了哪个文件。这两个问题都能在安装和首次配置阶段一次性规避掉。
2.1 Windows 上安装时那几个选项怎么选
Git for Windows 的安装向导会问一串问题,很多人一路“下一步”点过去,然后就埋下了雷。我把几个真正需要停一下的选项列出来。
默认编辑器。默认是 Vim,如果你没有 Vim 使用经验,第一次触发编辑场景(比如合并提交信息、交互式变基)时会直接卡死在里面,连怎么退出都不知道。两个建议:要么花十分钟记住 Vim 的基本操作(i进入编辑,Esc退出编辑,:wq保存退出,:q!不保存强退),要么在这里直接选 VS Code 或者 Notepad++。我个人选的是 VS Code,配合后面要讲的core.editor配置,体验最顺。
PATH 环境变量。这里有三个选项,我推荐选中间那个“Git from the command line and also from 3rd-party software”。选第一个只有 Git Bash 能用 git 命令,你在 CMD 或者 PowerShell 里敲git会提示找不到;选第三个会把一堆 Unix 工具也塞进系统 PATH,偶尔会和系统自带命令打架。
HTTPS 传输后端。直接选 OpenSSL 库,兼容性最好。除非你在企业内网有特殊证书要求,否则不用动。
换行符转换。这是最关键的选项,三个取值分别是:检出时转成 Windows 风格、提交时转成 Unix 风格(推荐 Windows 用户选这个);检出时保持原样、提交时转成 Unix 风格;以及完全不做任何转换。具体差异我在 2.3 节展开讲,先记住 Windows 上选第一个就对了。
终端模拟器。选 MinTTY,中文显示和配色都更舒服,用 Windows 自带控制台经常出现字符错位。
git pull的默认行为。这里建议选“仅快进”(fast-forward only)或者“变基”。选默认的“合并”会让你的提交历史里凭空多出一堆“Merge branch 'main' of ...”这种噪音提交,团队里看到这种提交都会皱眉头。
凭证助手。选 Git Credential Manager,配好之后第一次输入账号密码,后面就不用手动输了。这个东西本质上是一个凭据存储,把你的认证信息交给系统的凭据管理器保管,比每次手打密码安全也方便。
2.2 首次必做的六条全局配置
装完之后第一件事不是急着 clone 项目,而是先把身份配好,否则提交记录里的作者信息是自动生成的机器名,以后想改就得重写历史。
git config --global user.name "你的名字" git config --global user.email "you@example.com" git config --global init.defaultBranch main git config --global core.editor "code --wait" git config --global credential.helper manager git config --global --list逐条说一下为什么。user.name和user.email会写进每一条提交记录,是你在项目里的身份标识,邮箱建议用你注册代码托管平台时用的那个,这样提交记录能和账号关联起来,贡献统计才准确。init.defaultBranch main是让git init创建的默认分支叫main而不是master,现在主流平台的新仓库默认都是main,本地保持一致能省掉一堆改名操作。core.editor指定编辑器,code --wait里的--wait很关键——不加这个参数,VS Code 会立刻返回,Git 以为你编辑完了,提交信息就变成空的。
credential.helper manager是启用凭证管理,Windows 上一般安装时就自动配好了,但手动确认一遍没坏处。最后git config --global --list列出所有全局配置,检查有没有拼错的。
提示:
--global作用于当前用户的所有仓库,配置文件在~/.gitconfig。如果只想对某个仓库生效,用--local;想看某条配置到底来自哪个文件,用git config --list --show-origin,排查“为什么这条配置不生效”的时候特别好用。
2.3 换行符与中文路径:两个最容易埋雷的配置
换行符问题的根源在于历史遗留:Windows 用CRLF(回车加换行)表示一行结束,Linux 和 macOS 用LF。如果两边不做统一,会出现两种情况——要么每次提交都提示整个文件被修改,要么在 Windows 上编辑过的脚本传到 Linux 上跑不起来,报“解释器错误”。
core.autocrlf取值 | 检出时 | 提交时 | 适用场景 |
|---|---|---|---|
true | LF 转 CRLF | CRLF 转 LF | Windows 单人开发 |
input | 不转换 | CRLF 转 LF | Linux/macOS 开发 |
false | 不转换 | 不转换 | 全平台统一用 LF 的项目 |
# Windows git config --global core.autocrlf true # Linux / macOS git config --global core.autocrlf input中文路径的问题出在core.quotepath这个配置上,它默认是true,会把非 ASCII 字符转成八进制转义,于是git status里你看到的是\344\270\255\346\226\207.txt这种东西,完全没法看。关掉它就行:
git config --global core.quotepath false顺带再配两个体验优化项:git config --global color.ui auto让输出带颜色,git config --global pull.rebase true让git pull默认走变基而不是合并。这两条属于可选项,但配了之后日常体验会明显顺畅。
2.4 SSH 密钥配置与远程仓库绑定
用 HTTPS 方式访问远程仓库,每次推送都要走一遍认证(虽然凭证助手会帮你记住),切换到 SSH 方式之后就是完全无感的。生成密钥的命令:
ssh-keygen -t ed25519 -C "you@example.com" # 一路回车用默认路径,或者自己指定文件名 # 建议设置一个密码短语,多一层保护生成的公钥在~/.ssh/id_ed25519.pub,私钥是同目录下的id_ed25519,私钥绝对不能外发。查看公钥内容并复制:
cat ~/.ssh/id_ed25519.pub然后把这一整行(以ssh-ed25519开头,以你的邮箱结尾)粘贴到代码托管平台的“SSH 公钥”设置页面。不同平台的入口位置不一样,但基本都在账号设置的“安全设置”或者“SSH 密钥”分类下。
粘贴完之后测试连接。测试命令各平台不同,但思路是一样的:向平台的 SSH 服务发起一次连接,看它返回什么。返回里带上你的用户名,就说明配置成功了;如果提示权限被拒,八成是公钥没贴对或者贴的时候多了换行。
我踩过的一个坑是多账号场景。如果你同时用两个平台,又都用默认的id_ed25519,就可能出现密钥冲突。解决办法是在~/.ssh/config里给不同主机指定不同的密钥文件:
Host gitee.com HostName gitee.com User git IdentityFile ~/.ssh/id_ed25519_gitee Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github这样每个平台用各自的密钥,互不干扰。
2.5 配置结果的验证方式
配置完别急着走,跑一遍验证清单:
git config --global --list # 全局配置是否齐全 git --version # 版本号是否正常输出版本信息 ssh -T git@gitee.com # SSH 连接测试还有一个小测试方法,能快速验证身份配置对不对:随便建一个空目录,git init,建一个文件,git add之后git commit,然后git log看一下作者名和邮箱是不是你配的那个。这一步花不了一分钟,但能避免后面几十条提交记录全是错的。
3. 高频Git命令实操:按场景拆解而不是按字典背
我不太建议去背命令大全,那种列表看完就忘。更有效的方式是按照“我要做什么”来组织记忆。下面按实际工作场景拆开讲,每个场景给出命令和背后的逻辑。
3.1 仓库初始化与克隆
参与一个新项目,第一步是拿到代码。两种情形:本地从零开始,或者从远程拉取已有仓库。
# 情形一:从零开始 mkdir my-project && cd my-project git init git remote add origin <远程仓库地址> # 情形二:拉取已有仓库 git clone <远程仓库地址> # 只要某个分支 git clone -b 分支名 --single-branch <远程仓库地址>git init会在当前目录创建一个.git子目录,里面存放所有版本信息。这个目录不要手动改任何一个文件,改了大概率会把仓库搞坏。如果你的项目里有些文件不该被跟踪(编译产物、依赖目录、本地配置),在git init之后立刻建.gitignore,别等到提交完了才想起来。
git clone会自动做三件事:下载全部历史、创建本地默认分支、把远程地址记成origin。如果你的仓库特别大(比如有几年历史、包含大量二进制文件),可以用--depth 1做浅克隆,只拉最近一次提交,速度能快很多,代价是看不到完整历史,也不能直接推送到别的分支。
3.2 工作区、暂存区、提交:三步走的核心心智模型
Git 最容易让人困惑的就是“为什么我改了文件,提交的时候却说没有改动”。根源在于它有三个区域:工作区(你正在编辑的文件)、暂存区(准备提交的内容)、版本库(已经提交的历史)。改动必须先进入暂存区,才能被提交。
git status # 查看三个区域的状态 git status -s # 精简输出 git add 文件名 # 把单个文件加入暂存区 git add . # 把当前目录所有改动加入暂存区 git add -p # 分块选择要暂存的内容 git commit -m "提交信息" # 提交暂存区的内容 git diff # 工作区 vs 暂存区 git diff --staged # 暂存区 vs 版本库 git diff HEAD # 工作区 + 暂存区 vs 版本库git add -p这个命令值得单独说。它会把你的每一处改动拆成小块,逐块问你“要不要暂存这一块”,按y是暂存,n是跳过,s是拆分更小的块。这个功能在“改了三处但只想提交两处”的时候是救命的,GUI 里几乎找不到等价操作。
提交信息怎么写,我个人的习惯是分两段:第一行用一句话概括,控制在 50 字以内;空一行;后面的段落解释为什么这么改、有什么影响。很多人只写“修复bug”“更新代码”,半年后回头看完全不知道当时在干什么。git log是你的第二大脑,前提是你写得足够具体。
3.3 撤销操作的五种情形对照
“怎么撤销”是新手问得最多的问题,因为情形不同,命令完全不同,用错了可能直接把代码搞丢。我整理成一张表:
| 情形 | 命令 | 说明 |
|---|---|---|
| 工作区改了还没 add,想还原 | git restore 文件名 | 危险,本地改动直接丢失 |
| 已经 add,想撤出暂存区 | git restore --staged 文件名 | 保留工作区改动 |
| 刚提交完,想改提交信息 | git commit --amend | 只对最近一次提交有效 |
| 刚提交完,想撤掉但保留改动 | git reset --soft HEAD~1 | 改动回到暂存区 |
| 提交已经推到远程了 | git revert 提交号 | 生成一个反向提交,历史安全 |
git reset有三个常用参数,--soft、--mixed(默认)、--hard,区别在于把改动退回到哪个区域。--hard会连同工作区的改动一起丢掉,是唯一真正会丢代码的选项,敲之前务必确认。
注意:已经推送到共享分支的提交,不要用
git reset去改写历史,要用git revert。改写共享历史会让所有同事的本地仓库和远程对不上,他们下一次拉取时会遇到一堆冲突,这是在团队协作里非常不受欢迎的行为。
3.4 分支切换、合并与变基
分支是 Git 的核心能力,新版本命令用git switch和git restore替代了部分git checkout的功能,语义更清晰。
git branch # 查看本地分支 git branch -a # 包括远程分支 git switch -c 新分支名 # 创建并切换分支 git switch 已有分支名 # 切换分支 git merge 要被合入的分支 # 合并到当前分支 git merge --no-ff 分支名 # 强制生成合并提交,保留分支记录 git rebase 目标分支 # 变基 git branch -d 分支名 # 删除已合并分支 git branch -D 分支名 # 强制删除合并和变基的区别,用一个类比说清楚:合并就像两条小路汇合成一条,汇合点留下一个记录,历史是真实的但看着有点复杂;变基就像把你的几个提交一个个摘下来,重新接到目标分支的最新位置,历史变成一条直线,看着很干净,但代价是原来的提交被替换了,哈希值全变了。
我自己的原则是:已经推送到共享分支的提交,绝不 rebase;纯本地、准备合并前的分支,可以 rebase 整理一下。这个界限守住,基本不会出大事。
3.5 远程协作:fetch、pull、push 的正确姿势
git remote -v # 查看远程地址 git fetch origin # 拉取远程更新,不改动本地 git pull --rebase origin main # 拉取并变基到远程最新 git push -u origin main # 首次推送并建立追踪关系 git push # 之后直接推送 git push --force-with-lease # 安全强推fetch和pull的区别值得说清楚。fetch只把远程的更新下载到本地,不会自动合并到你的工作区,你可以先git log看看别人改了什么,再决定怎么合。pull等于fetch加一次合并或者变基。我个人的习惯是多用 fetch,少用 pull,因为fetch给了你一个观察窗口,不会在你毫无准备的时候把冲突甩到脸上。
--force-with-lease比--force安全的地方在于:它会先检查远程分支有没有别人推的新提交,如果有,就拒绝推送。--force则是无条件覆盖,别人刚推的东西可能就被你抹掉了。这两个命令在团队里都要慎用,能不用就不用。
3.6 后悔药工具箱:reflog、stash、cherry-pick、clean
git reflog是我最想推荐给所有人的命令。它记录了 HEAD 指针的每一次移动,包括你自己的提交、重置、切换、合并。当你不小心reset --hard掉了一段代码,或者删掉了一个分支,reflog是最后的希望:
git reflog # 找到操作前的提交号,然后 git branch 恢复的分支名 提交号 # 或者直接 git reset --hard 提交号git stash用来临时保存工作区的改动,比如你正在改一个功能,突然要切分支处理紧急问题:
git stash # 保存当前改动 git stash list # 查看保存列表 git stash pop # 恢复最近一次并删除记录 git stash apply stash@{n} # 恢复指定记录但保留git cherry-pick可以把别的分支上的某几个提交“摘”过来,用在“这个功能只在这一处需要”的场景。git clean -fd用来清理没有被跟踪的文件和目录,这个命令会永久删除文件,执行前一定要先用-n参数预览一下要删什么:
git clean -nd # 预览要删除的内容 git clean -fd # 确认后执行删除4. GUI基本操作:从 git gui 到图形客户端的落地用法
讲完命令再看界面,你会发现很多按钮其实是命令的包装。理解了这个对应关系,用起来就不会心里没底。
4.1 git gui 与 gitk 的启动和界面拆解
Git 自带两个图形工具,git gui负责提交和仓库操作,gitk负责查看历史。在任意仓库目录下打开终端:
git gui # 打开提交界面 gitk # 打开历史查看器git gui的界面分成四块。左侧上半部分是未暂存改动列表,显示哪些文件发生了变化;左侧下半部分是已暂存改动列表,也就是准备提交的内容;右侧上方是差异预览区,点哪个文件就看哪个文件的改动;右侧下方是提交信息输入框。操作路径就是:在未暂存列表里点一下文件名(图标变成绿色加号表示已暂存),确认右侧差异没问题,填好提交信息,点“提交”。
界面顶部的菜单栏里还有几个容易忽略但很实用的入口:“分支”菜单能创建、切换、删除分支;“远程”菜单里能做拉取和推送;“合并”菜单里可以选本地或者远程分支合入当前分支。这些菜单项和命令行的对应关系是:
| GUI 菜单项 | 等价命令 |
|---|---|
| 提交 - 提交 | git commit |
| 提交 - 修改上次提交 | git commit --amend |
| 分支 - 新建 | git switch -c |
| 合并 - 本地合并 | git merge |
| 远程 - 从远程获取 | git fetch |
| 远程 - 推送到远程 | git push |
| 工具 - 添加 | git add |
提示:
git gui默认的提交界面不带签字(GPG 签名)功能,如果你的团队要求所有提交必须签名,这个工具就不够用了,得换支持签名的客户端或者在命令行操作。
4.2 图形化提交、分支、合并的完整操作路径
以一次典型的日常开发为例,走一遍完整流程。先在本地切出新分支:打开git gui,菜单“分支 - 新建”,输入分支名,选中“签出”选项,确定。然后在编辑器里改代码,回到git gui的界面按F5刷新,改动过的文件会出现在左上角列表里。
下一步是暂存和提交。这里有个细节:git gui支持按代码块暂存。在差异预览区里,如果你把光标定位到某一段改动上,界面上会出现“暂存这个代码块”的选项,这意味着 GUI 也能做到部分暂存,只是操作比git add -p稍微慢一点。全部确认之后,填提交信息,点提交。
提交完成要推送:菜单“远程 - 推送到远程”,会弹出目标仓库和分支的选择框,确认分支名没问题,点“推送”。如果远程有别人推的新提交,推送会被拒绝,这时候先做一次“远程 - 从远程获取”,再回来推送。
合并的操作路径也类似:先切到目标分支(比如切回main),然后“合并 - 本地合并”,选择要合入的分支,确定。有冲突的话,git gui会提示哪些文件冲突了,但它的冲突解决界面非常简陋,只能告诉你文件在哪,具体怎么改还是得在编辑器里手动处理。
4.3 GUI 里的提交签名、blame 与仓库浏览器
gitk这个工具虽然界面老旧,但有几个功能做得相当扎实。打开之后,上半部分是提交列表,每一行是一条提交,显示提交信息、作者、日期;下半部分是选中提交的详细差异,一行行标红标绿;左侧有一列分支标签,能直观看到哪些提交属于哪个分支。
Blame(追责)功能是gitk里我认为最有价值的。选中一个文件,右键选“Blame”,它会逐行显示这一行代码是哪次提交、谁写的。排查“这行诡异的代码是谁加的”这种问题,用这个功能比在命令行敲git blame看滚动输出舒服太多,鼠标点一下某一行,还能跳到对应的提交查看完整改动。这是我认为 GUI 明显优于命令行的少数场景之一。
仓库浏览器(菜单“查看 - 新建视图”)可以按分支、按作者、按时间过滤提交,适合在代码评审前快速梳理“这周改了什么”。
4.4 图形客户端的共同坑点与补位方案
不管用哪个图形客户端,有几个坑是共通的,我列出来提醒一下。
第一,界面刷新有延迟。你在编辑器里改了文件,GUI 不会自动感知,需要手动刷新(多数是F5)。如果刷新了还是没显示,检查一下文件是不是被.gitignore忽略了,或者是不是在子目录里而界面只显示了当前目录。
第二,大仓库卡顿。提交历史几万条的仓库,图形界面在渲染分支拓扑时可能直接卡死。这时候关掉“显示所有分支”,只显示当前分支,能缓解不少。
第三,忘记推送。GUI 的提交和推送是两个独立动作,很多人提交完就以为完事了,结果代码还躺在本地。我的习惯是:提交之后立刻看一眼远程状态,确认本地分支和远程分支是对齐的。
第四,路径和编码问题。有些客户端对中文路径支持不好,会出现乱码或者找不到文件。遇到这种情况,先在命令行确认core.quotepath已经关掉,再检查客户端本身的编码设置。
第五,GUI 做不了的事要有兜底方案。交互式变基、复杂冲突、reflog恢复,这些必须回命令行。所以我的建议是:装 GUI 可以,但命令行的基本功不能丢,否则一旦 GUI 卡住,你会完全没有退路。
5. 常见报错与排查技巧实录
这一节是我这些年攒下来的排错经验,都是真实遇到过的问题。
5.1 报错速查表
| 报错信息 | 原因 | 处理方式 |
|---|---|---|
Permission denied (publickey) | SSH 公钥没配好 | 重新检查公钥是否粘贴完整,用测试连接验证 |
fatal: refusing to merge unrelated histories | 两个仓库历史没有共同祖先 | 确认无误后加--allow-unrelated-histories |
error: failed to push some refs | 远程有本地没有的提交 | 先git pull --rebase再推送 |
Your local changes would be overwritten | 本地有未提交改动,切换会被覆盖 | 先 commit 或 stash |
fatal: not a git repository | 当前目录不在仓库内 | 检查路径,或者先git init |
LF will be replaced by CRLF | 换行符配置触发的警告 | 属于提示,按 2.3 节统一配置即可 |
index.lock已存在 | 上一次操作异常中断 | 确认没有 Git 进程在跑,手动删掉该文件 |
| 文件名显示成八进制转义 | core.quotepath为true | 关掉这个配置 |
detached HEAD | 处于游离头指针状态 | 需要保留改动就新建分支,否则切回正常分支 |
remote: Repository not found | 地址错误或没有访问权限 | 检查地址拼写和账号权限 |
5.2 index.lock 与文件占用这类"玄学"问题
index.lock这个问题我遇到过好几次,症状是任何 Git 命令都报“另一个进程正在操作仓库”。根本原因是某次操作中途被打断(比如强制关闭了图形客户端、终端被强杀、磁盘满了),锁文件没被正常释放。
处理方式:先确认没有 Git 相关的进程在后台跑(任务管理器里看有没有 git.exe、git-gui.exe),确认干净之后,找到仓库.git目录下的index.lock文件删掉,问题就解决了。
注意:删
index.lock之前一定要确认没有正在进行的操作,否则可能把仓库状态搞成半完成态。如果是在做一次很大的操作(比如几百兆的合并),宁可多等几分钟。
另一个相关的现象是“文件被占用,无法切换分支”。这通常是编辑器或者编译进程锁住了文件,关掉对应程序再试就行。
5.3 .gitignore 失效、误提交大文件怎么补救
.gitignore只对没有被跟踪的文件生效。如果一个文件已经被git add过或者提交过,再往.gitignore里加它是没有用的,Git 依然会盯着它。解决办法是先把它从索引里移除(保留本地文件):
git rm --cached 文件名 git commit -m "停止跟踪该文件"误提交大文件的情况更麻烦一些。如果只是最近一次提交混进去了:
git rm --cached 大文件名 git commit --amend如果是好多次提交之前就进去了,那这个文件的每个版本都留在历史里,仓库体积不会减小。要彻底清理需要重写历史,这个操作风险较高,动手前务必备份整个仓库目录,并且通知所有协作者同步处理。我个人的建议是:大文件在第一次提交前就挡在.gitignore外面,事后补救的成本远高于事前预防。
5.4 我踩过的几个坑和一直保留的习惯
第一个坑是在错误的目录里执行git init。有一次我在用户主目录下顺手敲了一下,结果整个主目录变成了一个仓库,git status列出几千个文件。清理方式是把那个.git目录删掉。现在我养成的习惯是:敲任何 Git 命令之前,先看一眼终端提示符里的路径。
第二个坑是用git add .一把梭。这个命令会把当前目录下所有改动都加进去,包括你不小心生成的临时文件、日志、编译产物。现在我基本只用git add 具体文件名或者git add -p,强迫自己看一眼改了什么。
第三个坑是提交信息写得太随意。以前写过一堆“update”“fix”,过了三个月回头查一个功能的改动历史,翻了半天没找到。现在我的写法是:第一行写清楚做了什么,正文写清楚为什么这么做。花在这上面的时间,未来会加倍还给你。
第四个坑是在共享分支上用reset --hard。这个错误最严重,直接把同事推的提交抹掉了,后来是靠reflog和远程分支的记录一点点恢复的。从那之后我给自己定了一条死规矩:共享分支上,任何改写历史的操作都不做。
还有一个习惯是每天开工前先git fetch。花两秒钟,看看远程有没有新东西,心里有数。这个动作跟 GUI 里的“从远程获取”是一回事,养成之后能避免很多“推不上去”的尴尬。
GUI 方面,我的用法比较克制:主要用来看差异、看历史、做 blame,提交和推送还是更习惯命令行。这不是说 GUI 不好,而是我的工作流里涉及历史整理的部分比较多,命令行更顺手。如果你刚入门,反过来用 GUI 做日常操作、命令行做兜底,也是完全合理的路径。工具是拿来用的,手感顺、不出错,就是好方案。