1. 环境准备与安装配置这个标题下面记的其实是一堆 Git 和 GitHub 的自用笔记从 8 月 23 号那版开始我把它从碎片化的临时记录整理成了一份能随时照着操作的速查清单。Git 相关的东西平时用得越多越觉得有必要把自己踩过的坑写下来不然每次换台电脑、重装系统、或者隔几个月再碰一个老项目都要重新查一遍命令和配置很浪费时间。这篇就当是归档整理适合刚接触版本控制的新手也适合用过一段时间但没系统梳理过 Git 和 GitHub 常用操作的开发者。我最早在 Windows 上折腾 Git 的时候第一步就卡在“装完以后怎么没反应”。其实不是没反应是得打开 Git Bash 或者系统自带的 PowerShell在命令行里敲git --version才能确认装好了。如果你在PS F:\ai\deepseekharness git --version这样的提示符后面看到git version 2.23.0.windows.1不管是新版本还是旧版本都说明安装路径没问题了。1.1 Git 安装的正确姿势Windows 用户直接去 Git 官网下载安装包注意选对系统位数然后一路 Next 就行。但有几个选项值得停下来看一下别全部默认。第一个是“Select Components”默认会带上 Git Bash 和 Git GUI这两个建议全保留。Git Bash 是 Windows 下最省心的命令行环境很多教程里的命令在它里面跑起来不会出现路径分隔符或者编码的怪问题。第二个是“Adjusting your PATH environment”这里默认选“Git from the command line and also from 3rd-party software”就好意思是以后你在 Windows 自带的终端里也能直接用git命令。如果不小心选了“Use Git from Git Bash only”从 PowerShell 里敲git是会提示“无法将 git 识别为 cmdlet”的。第三个是“Configuring the line ending conversions”默认的Checkout Windows-style, commit Unix-style line endings对团队协作比较友好。它会自动把文件里的换行符在检出时转成 Windows 的 CRLF在提交时又转回 Linux 风的 LF。如果你和我一样经常要和 Mac、Linux 同事合作这个默认值能避免一堆“整个文件都被标记为改动”的尴尬。macOS 上装 Git 最简单的是用 Homebrewbrew install git一行命令搞定。Linux 发行版则直接用自带的包管理器比如 Debian/Ubuntu 系是sudo apt install gitFedora 系是sudo dnf install git。装完以后务必顺手把全局用户名和邮箱配好否则第一次git commit会直接报错告诉你缺少身份信息。命令很简单git config --global user.name 你的名字 git config --global user.email youexample.com我用的是 GitHub 注册邮箱这样每次提交记录里能关联到自己的账号头像。配完以后可以用git config --list看一眼所有配置。1.2 全局配置的细节与踩坑全局配置文件在 Windows 上位于当前用户目录下的.gitconfigLinux/macOS 上是~/.gitconfig。除了用户名邮箱还有几个配置我每次重装都要重新设置干脆记在这里。core.autocrlf这个参数前面提过如果在 Windows 上安装时选了默认选项它就是true。个人项目或者只在自己电脑上用的项目也可以把它设成false避免 Git 在你完全没改文件内容的情况下报告一大堆换行符差异。我自己的习惯是统一设为input这样提交进仓库的永远是 LF检出到工作区时不做转换在 Windows 上偶尔手动处理一下也行。还有中文文件名显示问题。旧版本 Git 在 Windows 上会把中文文件名显示成\346\265\213\350\257\225.txt这种八进制转义格式看着很痛苦。解决办法是设置git config --global core.quotepath false设置完以后中文文件名就正常显示了。编辑器和 diff 工具也可以在这里配置虽然现在 IDE 都有内置的 Git 面板但命令行里偶尔触发编辑器时还是希望它用自己顺手的工具。我喜欢把默认编辑器设成 VS Code用下面这一条就能搞定git config --global core.editor code --wait另外建议把默认分支名设成main新仓库初始化时就不用自己手动改分支名了git config --global init.defaultBranch main还可以加个简单的别名让自己的常用命令短一点。我平时最长敲的就是git log --oneline --graph --all全靠别名救场git config --global alias.lg log --oneline --graph --all之后敲git lg就能看到漂亮的提交历史图了。1.3 初始化仓库与第一次提交在开始一个全新项目时先建好目录然后进到目录里执行git init。这一步会在当前目录下生成一个隐藏的.git文件夹它记录仓库的全部版本元数据。我第一次用 Git 的时候犯过一个大意的错误在目录还没有任何文件时就直接git status看到“No commits yet”很正常但没有文件自然什么都提交不了。正确的顺序是git init git add . git commit -m Initial commit: 项目初始化git add .会把当前目录下所有未忽略的文件加入暂存区。如果你看到某文件被列出来了但不想提交就先别把它加进去。提交成功后再用git status确认工作区是干净的心理上会踏实很多。新手有时候搞不清楚“暂存区”和“工作区”的区别我习惯用一个比喻工作区是你电脑上真正看到的文件暂存区是提交前的待办清单版本库是已经封箱归档的存档。你改了文件相当于草稿打好了git add是把草稿放进待办箱git commit才是正式把这一刻的草稿封存进档案室。这部分做完基础的安装和初始化就结束了。后面所有更复杂的操作都是在这套基础上叠加的。2. 日常使用与核心命令Git 日常占用我时间最多的是三件事提交代码、分支管理、改提交历史。这三块用熟了日常开发基本就顺了。2.1 基础工作流add 到 commit 到 push我见过不少同事把git push当成“保存文件”来用总觉得文件改完必须马上推到远程才安心。其实保存文件永远靠git commitpush只是“把本地归档同步到远程”。所以合理的工作流是改代码用编辑器写完一个可运行的阶段。git status查看变更情况确认没有误改的文件。git add 具体文件只添加你自己这次要提交的改动。git commit -m 描述这次改了什么把暂存区存档。git push把本地的提交推送到远程分支。git add有几个常见用法我整理了一下git add -A把工作区所有变更包括新增、修改、删除的文件全部加入暂存区。git add .只把当前目录及子目录下的变更加入暂存区站在仓库根目录执行时和-A差不多。git add -p进入交互模式可以让你逐个 hunk 确认是否暂存适合一个文件里包含多处无关改动时用。提交信息我习惯遵循一个基本模板第一行用 50 个字符以内的动词短语说清楚“做了什么事”比如Fix: 修复注册接口空指针异常。需要说明背景时空一行再写正文用git commit -m 标题 -m 正文可以一次性完成。git commit --amend是热词里被问得特别多的命令它用来修改最近一次提交。最常见的场景是提交完才发现漏了一个文件或者提交信息写错了。操作方式git add 漏掉的文件.txt git commit --amend这样会把上一次提交和本次暂存的改动合并成一个新提交提交信息也可以顺手用-m改掉。比如把“Fix bug”改成“Fix: 修复登录超时问题”git commit --amend -m Fix: 修复登录超时问题一定要记住--amend是在改写提交历史不要对已经推送到远程并且别人正在使用的提交使用。否则你本地改完历史以后再想push就必须用git push --force这会直接覆盖远程对应分支的历史轻则让别人拉取时出现一堆冲突重则弄丢别人的提交。只在确认分支只有你自己在用且改动还没推远程时用它才是安全的。使用 --amend 之前最好先用 git log -1 看看当前 HEAD 是不是你想改的那条提交。如果已经忘了上次提交里有哪些文件加上 --stat 能显示出提交中包含的文件列表。2.2 分支管理与合并分支是 Git 最核心的设计我理解成“在同一个项目里开几个互不干扰的开发线”。主分支main是稳定版本所在的地方开发新功能前从它切出一个feature/xxx分支在那边随便折腾。常用分支命令git branch # 查看本地分支前面带 * 的是当前分支 git branch feature/login # 基于当前 HEAD 创建新分支 git checkout feature/login # 切到新分支 git switch feature/login # Git 2.23 以后推荐用 switch 代替 checkout 做切换 git merge feature/login # 把 feature/login 合并到当前分支合并时分成两种情况。第一种是快速前进式合并目标分支没有产生新的提交Git 直接把指针往前挪历史是一条直线。第二种是真正的三方合并两个分支都各自有新提交Git 会把共同祖先和双方最新的版本进行比较产生一个合并提交。多人协作时最容易遇到的是冲突。当你把别人分支合并进来时如果双方改了同一个文件的同一段代码Git 会暂停下来在文件里插入这样的标记 HEAD 你当前分支的内容 被合并分支的内容 feature/login解决冲突没有魔法就是人类来拍板把不需要的部分删掉保留两边想要的逻辑然后保存文件执行git add 冲突文件.txt git commit -m Merge: 解决登录模块冲突我强烈建议在合并之前先查一下分歧情况git log HEAD..other-branch能列出other-branch有而当前分支没有的提交git diff HEAD...other-branch能预览差异。避免稀里糊涂合进去一大堆意想不到的改动。还有一种经常被拿来和 merge 比较的操作是git rebase。rebase 会把当前分支的提交“摘下来”重新接到目标分支的最新提交后面让历史保持线性。它的好处是提交记录非常干净适合个人开发和 review 流程。但 rebase 同样会改写提交哈希已经推到远程的分支不要随便 rebase。如果不小心 rebase 出了乱子git rebase --abort可以退回 rebase 之前的状态这个保命命令一定要记住。2.3 撤销与返工reset、revert、stash撤销操作是 Git 新手最容易慌的地方。我得先明确一个原则Git 的撤销大部分都可以“撤销撤销”核心工具是git reflog。git reset --soft HEAD~1撤销最近一次 commit但保留暂存区相当于你回到了提交前刚刚 add 完的状态。git reset --mixed HEAD~1默认撤销 commit 和暂存但保留工作区文件相当于回到 add 之前。git reset --hard HEAD~1彻底撤销 commit、暂存和工作区改动改动会直接丢失慎用。git revert commit通过生成一个反向提交来抹除目标提交的影响适合已经推送到远程的公共提交。如果只是想丢弃工作区里某个文件的未提交改动git checkout -- 文件名这个操作会把你当前工作区的文件恢复成暂存区或 HEAD 里的版本所有本地未保存的修改都会消失。用之前哪怕犹豫一秒都行但别以为它会自动备份。临时切换分支时如果不想提交手头改到一半的文件可以git stash把工作区改动暂时收起来。之后用git stash list查看用git stash pop恢复到工作区。我自己的习惯是所有 stash 都尽量当天清掉不然攒多了自己也分不清哪个是哪个不如直接开个 WIP 提交来得直观。3. 远程仓库协作与认证远程仓库这一块是 Git 真正发挥价值的场景也是问题最多的地方。我自己最常被问到的是 SSH 认证失败和免密配置。3.1 远程地址管理与 clone 拉取从一个现成的远程仓库起步时第一个动作通常是git clone。比如在 GitHub 上找到一个项目复制仓库地址后执行git clone https://github.com/用户名/仓库名.gitclone 完成后仓库会自带一个叫origin的远程引用它表示“这个仓库最初是从哪来的”。如果你是从零搭建的项目要关联远程就用git remote add origin https://github.com/用户名/仓库名.git git push -u origin main-u的意思是设置上游分支以后直接敲git push或者git pull就能自动同步到origin/main。很多人问“怎么上传文件夹到 GitHub”听起来像是在找一个网页端的按钮实际上本地项目上传到 GitHub 的完整姿势就是上面这几条命令项目根目录git init、git add .、git commit然后在 GitHub 新建空仓库关联远程后git push。一次提交之后后面就是普通的日常更新流程了。远程地址有两种协议HTTPS 和 SSH。HTTPS 的好处是默认不需要额外生成密钥通过账号密码或个人访问令牌Personal Access TokenPAT认证缺点是推拉时都要输一次密码及凭据过期后出现认证失败。SSH 需要提前配置密钥但配好以后就可以长期免密操作。如果你的远程仓库地址写错了或者一开始用了 HTTPS 后来想换成 SSH可以直接修改 remotegit remote set-url origin gitgithub.com:用户名/仓库名.git git remote -v # 查看当前配置3.2 SSH 密钥配置与认证失败排查SSH 认证失败的典型场景是执行git push时提示ssh: connect to host github.com port 22: Operation timed out或者Permission denied (publickey)。前者是网络层面的问题后者是密钥没配对。正确配置 SSH 密钥的步骤生成密钥ssh-keygen -t ed25519 -C your_emailexample.com一路回车会在~/.ssh/下生成一对文件私钥id_ed25519和公钥id_ed25519.pub。私钥绝不能外泄公钥可以公开。把公钥内容复制出来。Windows 上可以用type C:\Users\你的用户名\.ssh\id_ed25519.pub登录 GitHub进入 Settings → SSH and GPG keys → New SSH key把公钥内容粘贴进去保存。验证连接ssh -T gitgithub.com能看到Hi 用户名! Youve successfully authenticated, but GitHub does not provide shell access.就说明认证成功了。如果密钥配置没问题但还是认证失败可能是以下原因使用了多个 SSH 密钥且没有在~/.ssh/config里告诉 SSH 用哪一把。我同时管理 GitHub 和公司 GitLab 时就在 config 里按主机区分了Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github Host gitlab.work HostName gitlab.work.example.com User git IdentityFile ~/.ssh/id_ed25519_work22 端口不通时GitHub 提供了通过ssh.github.com:443连接的方式。它也是官方支持的做法适合某些网络环境下 22 端口被限制的情况。配置方法是在~/.ssh/config里加一段Host github.com HostName ssh.github.com Port 443 User git私钥权限不对。Linux/macOS 上私钥文件权限太宽松SSH 会拒绝使用。执行chmod 600 ~/.ssh/id_ed25519修正。排查这类问题有个基本思路先ssh -T gitgithub.com -vvv加详细日志看卡在哪个环节重点看是否读取了你的私钥、是否报Permission denied。3.3 免密配置与凭据管理SSH 密钥配好后日常git push已经不需要输入密码。如果坚持用 HTTPS也可以实现免密Git 会借助系统的凭据管理器保存令牌。Windows 上常见的是 Git Credential Manager第一次 push 时弹出登录窗口输入账号和 PAT 后凭据会被安全地保存在 Windows 凭据管理器里。macOS 上则通过钥匙串访问保存。还有一种临时方案是把凭据存成明文git config --global credential.helper store这样第一次 push 输入密码后它会写入~/.git-credentials文件。我只能说这个方案对你的个人电脑是不太建议长期使用的一旦机器被他人使用明文凭据就暴露了。真要图省事优先选用系统自带的凭据管理器。GitHub 大概在几年前就全面收紧了密码认证普通账号使用 HTTPS 时已经不允许直接用密码 push必须用个人访问令牌。进 GitHub 的 Settings → Developer settings → Personal access tokens生成一个有repo权限的令牌把它当成密码用就行。令牌记得及时保存它只会完整显示一次。4. 进阶技巧与场景化应用当日常命令和认证都顺手以后会陆续遇到大文件、忽略规则、IDE 集成和自动化部署这类场景。下面是我在实际项目里用过的配置。4.1 Git LFS 管理大文件普通 Git 仓库里塞大文件会非常痛苦。仓库里的每个文件都会被完整保存到提交历史中一旦把一个几百 MB 的设计稿提交了就算后来把它删掉历史里还留着它clone 时会原样下载仓库体积会变得不可控。GitHub 对单文件超过 100MB 的提交还会直接拒绝。Git LFSLarge File Storage就是专门解决这个问题的。它不是把大文件本体存入仓库而是存一个指针真正的内容放在 LFS 对象存储里。使用步骤非常简单安装 Git LFS 扩展。在已经装好 Git 的机器上再执行git lfs install。在仓库里指定哪些文件要用 LFS 管理git lfs track *.psd git lfs track models/*.bin git add .gitattributes然后正常git add、git commit、git push即可大文件会自动走 LFS 通道。我提醒两点第一.gitattributes这个文件本身要提交到仓库否则别人 clone 后不知道哪些文件应该走 LFS第二如果别人没有安装 Git LFS 客户端克隆下来的大文件会显示成几行指针文本像version https://git-lfs.github.com/spec/v1这样的开头。所以团队使用 LFS 时一定要在项目文档里注明“需要安装 Git LFS”。Git LFS 比较适合游戏素材包、模型文件、音视频资源、大型数据集这类场景不适合用在普通文本代码上因为文本文件压缩率很高没必要拖进 LFS 增加复杂度。4.2 .gitignore 与文件的忽略管理一个仓库里不是所有文件都应该被 Git 跟踪。node_modules、构建产物、日志、临时文件、本地配置这些一旦被提交进去不仅让仓库变臃肿还会污染团队成员的工作区。规则很简单项目根目录新建一个.gitignore文件按行写忽略规则# 依赖目录 node_modules/ # 构建输出 dist/ build/ target/ # 日志 *.log # IDE .idea/ .vscode/ # 系统文件 .DS_Store Thumbs.db # 本地环境配置 .env.local模式规则和通配符是.gitignore的核心我用得最多的几个/开头表示只匹配仓库根目录下的路径/dist不会匹配src/dist。末尾的/表示匹配目录。*匹配任意字符**可以匹配多级目录比如a/**/b能匹配a/b、a/x/b。!开头表示重新包含排除项。比如先忽略*.log再写!important.log就可破例跟踪这个特殊文件。真正让我踩过坑的是.gitignore只能影响未跟踪的文件。如果一个文件已经被git add跟踪了之后你在.gitignore里加了它它还是会被继续跟踪。解决办法是先把它从暂存区/索引里移除git rm --cached 目标文件如果想“重新应用”所有新写入的忽略规则可以一次性把所有文件从索引里摘除再重新添加git rm -r --cached . git add . git commit -m chore: 应用新的 gitignore 规则这个操作不会影响本地文件内容只是通知 Git 从索引中移除不再需要跟踪的对象。我自己用它清理过几次被错误提交的配置文件实测下来很稳。4.3 IDE 集成与自动部署命令行是 Git 的地基不过日常写代码时我也离不开 IDE 的 Git 面板。IntelliJ IDEA 在新建项目时可以直接选择 “Get from VCS”粘贴 Git 仓库地址即可拉取VS Code 的源代码管理面板能直观显示改动文件、暂存变更、提交和推送。IDE 面板本质上还是在调用 Git 命令所以理解底层逻辑能帮你避免很多困惑。比如 IDEA 里点击 “Share Project on GitHub”它会自动执行git remote add origin并完成首次推送VS Code 里的“同步”按钮实际上是依次执行了git pull --rebase和git push。自动部署方面最常见的方案是把静态站点部署到 GitHub Pages。以 Hexo 为例本地执行hexo generate会生成一个静态文件目录你只需要把该目录内容推送到仓库的gh-pages分支GitHub Pages 就会自动托管对应站点。也有更自动化的方式在仓库根目录放一个 GitHub Actions 工作流文件.github/workflows/deploy.yml在 push 到主分支时自动构建并部署。Actions 的好处是部署动作发生在云端本地不需要保留任何构建脚本。不管用哪种方式我都建议先把分支模型理清楚主分支放源码发布分支放构建产物或者用 Actions 自动生成发布内容。否则很容易出现“主分支上堆着一堆构建产物、源码和 dist 混杂”的混乱局面。5. 疑难杂症速查这部分是按报错关键词整理的都是从实际场景里碰到过的可以直接当成排查手册。5.1 常见 Git 报错对照表报错信息原因解决方法fatal: not a git repository (or any of the parent directories): .git当前目录不在一个 Git 仓库内或者父目录没有.git先cd到仓库根目录执行pwd确认位置如果是刚才git init了检查是否真的在目标目录里执行了命令fatal: refusing to merge unrelated histories两个仓库没有共同提交历史强行合并造成如果确实要合并不同来源的历史用git merge --allow-unrelated-histories 分支名Permission denied (publickey)SSH 公钥没有上传或 SSH 请求了错误的密钥上传公钥到 GitHub配置~/.ssh/config指定 IdentityFilessh: connect to host github.com port 22: Operation timed out网络环境下 22 端口不可达试 443 端口方案在~/.ssh/config里将HostName ssh.github.com、Port 443remote: Support for password authentication was removed使用 HTTPS 且用密码认证改用个人访问令牌作为密码或切换为 SSH 认证error: Your local changes to the following files would be overwritten by merge本地工作区有未提交改动和要合并的内容冲突要么先git stash暂存改动要么先把改动提交到当前分支再尝试 pull/mergefatal: not a git repository是我见过最多的新手报错。它最常见的触发点是你在 GitHub 仓库的网页里看到了一个目录但本地只进入了一个子目录而不是仓库根目录就开始敲 Git 命令。还有一种是项目里某个目录被意外执行过git init变成了嵌套仓库这时要看git rev-parse --show-toplevel打印出来的仓库根路径是否符合预期。5.2 误操作后的数据恢复误删分支、误 reset、误 amend这些操作虽然吓人但绝大多数都能靠git reflog救回来。git reflog会记录 HEAD 指针每次移动的历史即使你把分支删了提交对象也不会立即消失因为 reflog 里还留着指向它们的记录。我手滑删过一个feature/payment分支吓得手心冒汗后来用两步就找回来了git reflog # 找到删除前分支指向的提交哈希比如 abc1234 git branch feature/payment abc1234执行完这条命令分支就带着原来的提交历史回来了。同样如果你git reset --hard HEAD~3之后后悔了想回到三个提交之前也可以先从git reflog里找到那个提交的哈希再用git reset --hard 哈希回去。我自己已经把 reflog 当成了“版本控制的后悔药”它比系统还原还好用因为它记录了 Git 内部每一次动作。git commit --amend之后想反悔也可以从 reflog 里找到 amend 之前的原始提交哈希用git reset --hard 哈希恢复。只要提交对象还在本地基本都能捞回来。5.3 工作区清理与临时修改处理工作区被搞乱是日常开发中很常见的状态。有时候是“改了一堆实验代码想回到干净状态重新来”有时候是“切换分支时本地改动和分支间有冲突”。丢弃某个文件的所有未提交改动git checkout -- 文件名丢掉所有已跟踪文件的本地改动git reset --hard HEAD注意reset --hard不会删除未跟踪的文件。要清理未跟踪的新文件或目录必须用git cleangit clean -fd-f是强制-d是删除目录。这个命令真的很危险它会直接删掉不再被 Git 跟踪的文件而且不会放到回收站。我的建议是不要直接在项目根目录执行先加-n参数预览会被删除的文件列表git clean -nfd确认没问题再真正执行。我就曾因为误以为某个目录已经被.gitignore忽略实际上它从未被跟踪执行git clean -fd后把好不容易生成的报告文件全删了。那次之后我养成了“先 dry-run后执行”的习惯这也是我在这篇笔记里反复强调的原因。如果只是临时想换个分支又不想丢失改到一半的内容优先考虑git stashgit stash git switch feature/other # 做完其他事后切回来 git switch - git stash popstash list可以查看所有保存的记录。如果 pop 时出现冲突是因为当前分支和目标 stash 基于的版本状态差异太大这时要像处理合并冲突一样手动解决。写在最后的个人体会整理这份自用笔记期间我最大的感受是Git 命令不能靠硬背要把它理解成“对象 引用”的结构心里有了模型遇到报错就不会慌。比如.git目录里存的是对象分支只是指向提交的指针HEAD是当前所在位置的指针。明白了这个底座reset、branch、checkout这些命令的本质都一目了然。另一个体会是所有破坏性操作都先留一手。执行reset --hard、clean -fd、push -f之前习惯性用reflog或者git status确认当前状态甚至可以先把当前提交哈希抄到便签里。绝大多数“代码消失了”的惨案都能靠这些记录挽回。这份笔记还在持续更新每次遇到新坑都会往里补一条。建议你也把它当成一本可以随时翻阅的手册来维护遇到一个记录一个慢慢形成属于自己的 Git 速查表。到那时候Git 就不是需要背的工具而是顺手得跟呼吸一样的习惯了。