Git/GitHub 常用操作
前言
很多初学者刚接触 Git 时,会把 Git 理解成“上传代码的工具”。
这种理解不完全错,但很容易导致后面遇到分支、冲突、远端同步、push 被拒绝、PR 合并、rebase、stash、reflog 等问题时完全不知道 Git 在做什么。
更准确地说:
Git 是本地代码版本数据库。 GitHub 是远端 Git 仓库 + Web 页面 + 协作平台。Git 真正重要的不是某一条命令,而是它背后的模型:
工作区 working tree ↓ git add 暂存区 staging area / index ↓ git commit 本地仓库 local repository ↓ git push 远端仓库 remote repository,例如 GitHub本文面向有一定 Linux C/C++ 基础、但 Git/GitHub 体系还不熟悉的开发者,目标不是罗列命令,而是通过一条完整学习路线,建立可以支撑日常开发的 Git/GitHub 工作流。
本文约定:
- Linux 环境以 Ubuntu 24.04 为例。
- Windows 环境以 PowerShell 为例。
- 远端仓库以 GitHub 为例。
- 项目以一个简单 C 语言项目
git_c_demo为练习载体。 - 命令或参数第一次出现时会解释其功能和用途,后续重复出现时不再重复解释。
一、安装 Git 与基础配置
在 Linux 上先检查 Git 是否已经安装:
git--versiongit:调用 Git 程序。--version:查看当前安装的 Git 版本。
如果没有安装,可以执行:
sudoaptupdatesudoaptinstallgit-ysudo:以管理员权限执行命令。apt:Ubuntu/Debian 系统的软件包管理工具。update:更新本地软件包索引。install:安装软件包。-y:自动回答 yes,避免安装过程中反复确认。
安装后配置用户名和邮箱:
gitconfig--globaluser.name"你的名字"gitconfig--globaluser.email"你的邮箱"git config:查看或修改 Git 配置。--global:设置当前用户级别的 Git 配置,对当前系统用户的所有仓库生效。user.name:提交记录中显示的作者名称。user.email:提交记录中显示的作者邮箱。
查看配置:
gitconfig--global--list--list:列出当前配置项。
注意:这里的用户名和邮箱会写进 commit 作者信息里,不等于 GitHub 登录账号,但建议邮箱与 GitHub 账号邮箱保持一致。
二、创建第一个本地 Git 仓库
先创建一个 C 项目目录:
mkdirgit_c_democdgit_c_demomkdir:创建目录。cd:切换当前目录。
创建一个简单的main.c:
cat>main.c<<'EOF' #include <stdio.h> int main(void) { printf("hello git\n"); return 0; } EOFcat:在终端打印或写入文件内容。这里配合重定向创建文件。>:把输出写入指定文件,会覆盖原文件。<<'EOF' ... EOF:Here Document,用来一次性写入多行文本。
创建Makefile:
cat>Makefile<<'EOF' CC = gcc CFLAGS = -Wall -Wextra -g all: main main: main.c $(CC) $(CFLAGS) -o main main.c clean: rm -f main EOF测试编译:
make./mainmake:根据Makefile中的规则执行构建。./main:运行当前目录下的main可执行文件。./表示当前目录。
初始化 Git 仓库:
gitinitgit init:在当前目录创建一个新的 Git 仓库,本质是生成.git目录。
查看当前仓库状态:
gitstatusgit status:查看当前分支、工作区、暂存区状态,包括未跟踪文件、已修改文件、待提交内容等。
这时你会看到main.c和Makefile是untracked files。
untracked files表示:
Git 看到了这些文件, 但还没有开始跟踪它们。把文件加入暂存区:
gitaddmain.c Makefilegit add:把工作区中的修改加入暂存区。
查看暂存区的文件
gitls-files--cached等待下一次 commit,提交到本地仓库:
gitcommit-m"初始化 C 项目"git commit:把暂存区中的内容保存为一次提交。-m:message,直接在命令行指定提交说明。
查看提交历史:
gitlog--onelinegit log:查看提交历史。--oneline:每个 commit 只显示一行,便于快速查看。
到这里,最核心流程已经完成:
写代码 ↓ git add ↓ git commit三、理解 status、diff、log
日常开发中,下面三个命令使用频率非常高:
gitstatusgitdiffgitlog修改main.c:
sed-i's/hello git/hello git and github/'main.csed:Linux 下的文本处理工具。-i:直接修改文件内容。s/旧内容/新内容/:替换文本。
查看工作区差异:
gitdiffgit diff:查看工作区和暂存区之间的差异。简单理解就是:你改了什么,但还没git add。
加入暂存区:
gitaddmain.c查看暂存区差异:
gitdiff--staged--staged:查看暂存区和上一次提交之间的差异,也就是即将被 commit 的内容。
提交:
gitcommit-m"修改 hello 输出内容"查看图形历史:
gitlog--oneline--graph--decorate--all--graph:用 ASCII 图显示分支关系。--decorate:显示分支名、HEAD、tag 等引用信息。--all:显示所有本地分支和远端追踪分支的历史。
建议以后经常使用:
gitlog--oneline--graph--decorate--all它能帮助你看清楚当前仓库的真实历史结构。
四、撤销错误操作:restore、reset、revert 的基本思想
Git 里“撤销”不是一个命令解决所有问题,而是要先判断你要撤销的是哪一层:
工作区修改? 暂存区修改? 已经 commit 的修改?撤销工作区修改:
gitrestore main.cgit restore:恢复工作区文件内容。常用于撤销还没有加入暂存区的修改。
如果文件已经git add到暂存区,先撤出暂存区:
gitrestore--stagedmain.c--staged:只把文件从暂存区移回工作区,不删除实际修改。
如果 commit 已经提交,而且希望用一个新的提交来抵消它,可以使用:
gitrevert<commit_hash>git revert:创建一个新的 commit,用来反向撤销指定 commit 的修改。<commit_hash>:某次提交的编号。
revert的特点是安全,因为它不删除历史,而是新增一条“反向修改”。
日常建议:
未 add:git restore 已 add 未 commit:git restore --staged 已 commit 且已共享:优先 git revert五、分支:branch、switch、merge
分支是 Git 的核心能力之一。
查看当前分支:
gitbranchgit branch:查看、创建或删除本地分支。
创建并切换新分支:
gitswitch-cfeature/demogit switch:切换分支。-c:create,创建新分支并切换过去。feature/demo:分支名。
切换回 main:
gitswitch main在 feature 分支提交后,可以合并回 main:
gitmerge feature/demogit merge:把指定分支的修改合并到当前分支。
删除已合并分支:
gitbranch-dfeature/demo-d:delete,安全删除本地分支。如果 Git 认为该分支未合并,会拒绝删除。
分支的基本模型是:
main | A \ B feature/demo合并后可能变成:
A --- B main或者产生 merge commit:
A ---- M main \ / B --六、冲突:两个分支改了同一处代码怎么办?
冲突的本质是:
两个分支改了同一个位置, Git 不知道最终应该保留哪一个版本。例如:
<<<<<<<HEADprintf("hello from main branch\n");=======printf("hello from feature branch\n");>>>>>>>feature/demo含义是:
<<<<<<< HEAD 到 ======= 当前分支的内容。 ======= 到 >>>>>>> 要合并进来的分支内容。解决冲突的标准流程:
gitstatuscatmain.cnanomain.cgitaddmain.cgitcommitnano:Linux 下常见的终端文本编辑器。
不带-m的git commit:打开编辑器填写提交信息,常用于 merge commit。
解决冲突时必须删除:
<<<<<<< ======= >>>>>>>最终文件应该是干净、可编译的代码。
冲突不是错误,而是 Git 在告诉你:
我不能替你决定最终代码应该是什么, 需要你自己判断。七、GitHub:远端仓库与 SSH 连接
GitHub 可以理解成:
GitHub = 远端 Git 仓库 + Web 页面 + 协作工具它不仅能保存代码,还能支持:
README Issues Pull Request Actions Releases WikiLinux 开发中推荐使用 SSH 连接 GitHub。
检查 SSH 目录:
ls-al~/.sshls:列出目录内容。-a:显示隐藏文件。-l:使用长格式显示文件详细信息。~/.ssh:当前用户的 SSH 配置目录。
生成 SSH key:
ssh-keygen-ted25519-C"你的邮箱"ssh-keygen:生成 SSH 密钥。-t:指定密钥类型。ed25519:一种现代 SSH 密钥算法。-C:添加注释,通常写邮箱,方便识别密钥用途。
启动 ssh-agent:
eval"$(ssh-agent-s)"eval:执行字符串中的命令。ssh-agent -s:启动 SSH agent,并输出可被 shell 执行的环境变量设置。
添加私钥:
ssh-add ~/.ssh/id_ed25519ssh-add:把私钥加入 ssh-agent,避免每次连接都手动指定。
查看公钥:
cat~/.ssh/id_ed25519.pub只复制.pub公钥到 GitHub,不要复制没有.pub的私钥文件。
测试连接:
ssh-Tgit@github.comssh:通过 SSH 协议连接远程主机。-T:不分配远程终端,只测试认证连接。git@github.com:GitHub SSH 连接地址。
成功时通常会看到:
Hi 用户名! You've successfully authenticated...八、本地仓库连接 GitHub:remote、push、origin/main
如果本地已经有仓库,GitHub 上创建了空仓库,可以添加远端:
gitremoteaddorigin git@github.com:用户名/git_c_demo.gitgit remote:管理远端仓库。add:添加远端。origin:远端仓库别名,约定俗成常用这个名字。git@github.com:用户名/仓库名.git:SSH 格式的远端仓库地址。
查看远端配置:
gitremote-v-v:verbose,显示详细远端地址,包括 fetch 和 push 地址。
第一次推送 main:
gitpush-uorigin maingit push:把本地提交推送到远端仓库。-u:建立 upstream 追踪关系。origin:远端仓库名。main:要推送的本地分支名。
执行后建立关系:
本地 main ←→ 远端 origin/main这里要区分:
main: 本地分支。 origin/main: 本地记录的远端 origin 上 main 分支的状态。 它不是 GitHub 本身,而是本地保存的一份远端分支快照。如果远端地址写错,可以修改:
gitremote set-url origin git@github.com:用户名/git_c_demo.gitset-url:修改已有远端的 URL 地址。
九、clone:第二台电脑如何接入项目
如果 Windows 主机要接入同一个 GitHub 仓库,应使用:
git clone git@github.com:用户名/git_c_demo.gitgit clone:复制一个已有 Git 仓库到本地,包括代码、.git目录、提交历史、分支、远端配置等。
注意:
clone 不是下载源码。 clone 是复制整个 Git 仓库。所以 clone 后:
不需要 git init。 不需要 git remote add origin。因为 clone 已经自动完成了:
创建 .git 复制历史 设置 origin 检出默认分支 main 建立 upstreamWindows 上建议使用 PowerShell:
mkdir D:\GitStudy cd D:\GitStudy git clone git@github.com:用户名/git_c_demo.git cd git_c_demo git statusWindows 下查看文件:
dirdir:PowerShell/CMD 下查看当前目录文件列表。
查看文件内容:
Get-Contentmain.cGet-Content:PowerShell 下读取并打印文件内容。
打开文本文件:
notepad main.cnotepad:打开 Windows 记事本编辑文件。
十、双机协作:fetch 和 pull
典型环境是:
Ubuntu 24.04 虚拟机 │ │ git push ▼ GitHub ▲ │ git fetch / git pull Windows 主机一台机器 push 后,另一台机器不会自动变化。
在另一台机器上可以先执行:
git fetchgit fetch:从远端获取最新提交、分支、标签等信息,更新origin/main,但不修改当前本地分支和工作区文件。
fetch 后查看历史:
git log--oneline--graph--decorate--all你可能看到:
origin/main 已经前进 HEAD -> main 还停在旧提交如果要真正同步当前分支:
git pullgit pull:从远端拉取并合并到当前分支。简单理解:
git pull = git fetch + git merge更稳妥的习惯是:
git fetch git log--oneline--graph--decorate--all gitdiffmain origin/main git pullgit diff main origin/main:比较本地main和远端追踪分支origin/main的差异。
十一、push 被拒绝:non-fast-forward
双机协作或多人协作时,经常会遇到:
! [rejected] main -> main (fetch first) error: failed to push some refs Updates were rejected because the remote contains work that you do not have locally.意思是:
远端有你本地没有的提交。 GitHub 拒绝你的 push,防止你覆盖远端历史。安全处理流程:
git fetch git log--oneline--graph--decorate--all git pull git push如果 pull 过程中冲突,就按冲突流程解决:
git statusGet-Contentmain.c notepad main.c git add main.c git commit git push不要一看到 push 被拒绝就执行:
gitpush-f-f:force,强制推送。它可能覆盖远端已有提交,团队开发中非常危险。
十二、GitHub Pull Request 工作流
真实团队开发通常不直接往main推代码,而是:
main 保持稳定 feature 分支开发 Pull Request 合并标准流程:
gitswitchmain git pull gitswitch-c feature/pr-demo notepad main.c gitdiffgit add main.c git commit-m"演示 Pull Request 工作流"git push-u origin feature/pr-demo然后在 GitHub 网页上:
Pull requests ↓ New pull request ↓ base: main compare: feature/pr-demo ↓ Create pull request术语解释:
Pull Request: GitHub 上的合并请求,用来请求把某个分支的修改合并到目标分支。 base: 目标分支,通常是 main。 compare: 要合并进来的分支,通常是 feature 分支。PR 页面重点看:
Conversation: 讨论区。 Commits: 这个 PR 包含哪些提交。 Files changed: 这个 PR 修改了哪些文件和哪些行。 Checks: 自动检查结果,例如测试、编译、代码扫描。确认没问题后:
Merge pull request Confirm merge Delete branchPR 合并发生在 GitHub 上,但本地不会自动更新,所以还要:
gitswitchmain git pull git branch-d feature/pr-demo十三、PR 后续修改与 Code Review
真实开发中,PR 很少一次就合并。常见流程是:
创建 PR ↓ review 提意见 ↓ 本地继续改 ↓ commit + push 到同一个 feature 分支 ↓ 原 PR 自动更新Code Review 是合并前的代码检查和讨论过程。常见动作包括:
Comment: 只发表评论。 Approve: 批准 PR。 Request changes: 请求修改,暂时不建议合并。关键点:
PR 跟踪的是 compare 分支,不是某一次 commit。所以如果你已经创建了feature/review-demo的 PR,后续只需要继续在这个分支上修改:
git branch notepad main.c gitdiffgit add main.c git commit-m"根据 review 修改输出内容"git pushGitHub 上原来的 PR 会自动更新,不需要重新创建 PR。
十四、stash:临时保存做到一半的工作
日常开发中经常会出现:
代码写到一半,还不能 commit, 但突然需要切回 main 处理别的任务。这时使用 stash。
git stash push-m"临时保存当前修改"git stash:管理临时保存的修改。push:把当前未提交修改压入 stash 栈。这里的 push 不是推送到 GitHub。-m:message,给这次 stash 添加说明。
查看 stash 列表:
git stash listlist:列出当前仓库中的 stash 记录。
恢复 stash:
git stash apply stash@{0}apply:把指定 stash 的内容恢复到当前工作区,但保留 stash 记录。stash@{0}:最新的一条 stash 记录。
删除 stash:
git stash drop stash@{0}drop:删除指定 stash 记录。
更快捷的方式:
git stash poppop:恢复最新 stash,并在恢复成功后删除这条 stash。
如果新建了未跟踪文件,也想一起 stash:
git stash push-u-m"保存包括新文件的修改"-u:include untracked,把未跟踪文件也一起保存进 stash。
注意:
stash 是本地操作。 不会 push 到 GitHub。 Windows 的 stash,Ubuntu 看不到。十五、rebase:让 feature 分支基于最新 main
当你从main创建 feature 分支后,别人又往main合并了新代码,你的 feature 分支就落后了。
这时有两种方式:
merge main 到 feature rebase feature 到最新 main 后面基础 rebase 用法:
gitswitchmain git pull gitswitchfeature/rebase-demo git rebase maingit rebase:把当前分支上的提交重新应用到另一个分支后面。main:这里表示把当前 feature 分支的提交重新放到最新main后面。
rebase 前:
F feature / A --- M mainrebase 后:
A --- M --- F' feature | main注意:F会变成F',commit hash 可能变化。
commit hash:每个 commit 的唯一编号。提交内容、父提交、作者信息或时间变化时,hash 通常也会变化。
如果 rebase 过程中冲突:
git status notepad 冲突文件 git add 冲突文件 git rebase--continue--continue:解决冲突并git add后,继续执行被暂停的 rebase。
如果状态混乱:
git rebase--abort--abort:取消当前 rebase,恢复到 rebase 开始前的状态。
安全原则:
自己的本地 feature 分支可以 rebase。 main 分支不要随便 rebase。 已经共享给别人的分支不要随便 rebase。十六、.gitignore:已经被跟踪的文件为什么 ignore 不掉?
.gitignore的核心规则是:
.gitignore 只影响未跟踪文件。如果一个文件已经被git add/git commit过,它已经是 tracked file。之后即使写进.gitignore,Git 也会继续跟踪它。
判断文件是否被 Git 跟踪:
gitls-files local_config.txtgit ls-files:列出 Git 当前正在跟踪的文件。后面跟文件名时,用来判断该文件是否在跟踪列表中。
让 Git 停止跟踪文件,但保留本地文件:
gitrm--cached local_config.txtgit rm:让 Git 删除某个文件。--cached:只从 Git 的索引/跟踪列表中移除,不删除工作区真实文件。
如果是目录:
gitrm-r--cached build/-r:recursive,递归处理目录中的所有文件。
查看被忽略文件:
git status--ignored--ignored:让git status额外显示被.gitignore忽略的文件。
C/C++ 项目常见.gitignore:
# object files *.o *.out *.a *.so *.exe # executable main # build directories build/ cmake-build-*/ # debug/core/log core core.* *.log # editor .vscode/ .idea/ *.swp # local config local_config.txt通常应该提交:
.c / .h / .cpp / .hpp Makefile CMakeLists.txt README.md .gitignore 测试代码 必要脚本通常不应该提交:
.o 文件 可执行文件 build 目录 core dump 日志文件 本地配置 IDE 个人配置 临时测试文件十七、reflog:误操作后的救命记录
如果你 reset 错了,或者发现 commit “不见了”,先不要慌。
执行:
git refloggit reflog:查看本地 HEAD 或分支引用的移动记录,常用于找回 reset、rebase、切分支等操作后“看起来丢失”的提交。
git log和git reflog的区别:
git log: 查看当前分支能访问到的提交历史。 git reflog: 查看 HEAD 和分支指针最近移动过的位置。模拟回退:
git reset--hard HEAD~1git reset:移动当前分支指针,让当前分支回到指定 commit。--hard:让当前分支、暂存区、工作区都恢复到指定 commit,会丢弃未提交修改,危险。HEAD~1:当前 HEAD 的上一个提交。
如果 reflog 里看到:
abc1234 HEAD@{1}: commit: 添加 reflog 演示提交可以恢复:
git reset--hard abc1234abc1234:目标 commit hash,要换成你自己终端里看到的编号。
注意:
reflog 是本地记录。 不会 push 到 GitHub。 Windows 的 reflog,Ubuntu 看不到。如果你只是演示分支,想强制删除:
git branch-D feature/reflog-demo-D:强制删除本地分支,即使它没有合并到 main,也会删除。比-d危险。
十八、日常开发推荐流程
1. 开始一个新功能
gitswitchmain git pull gitswitch-c feature/功能名2. 开发并提交
git status gitdiffgit add 具体文件 gitdiff--staged git commit-m"清晰说明修改"3. 推送 feature 分支
git push-u origin feature/功能名4. GitHub 上创建 PR
Pull requests ↓ New pull request ↓ base: main compare: feature/功能名 ↓ Create pull request5. 根据 review 修改
gitdiffgit add 具体文件 git commit-m"根据 review 修改代码"git push6. PR 合并后本地收尾
gitswitchmain git pull git branch-d feature/功能名7. 另一台机器同步
gitswitch maingitpullgitstatus这就是日常开发最常用的 GitHub Flow。
十九、常见问题处理流程
1. push 被拒绝
git fetch git log--oneline--graph--decorate--all git pull git push不要直接git push -f。
2. 发生冲突
git statusGet-Content冲突文件 notepad 冲突文件 git add 冲突文件 git commit整理文件时删除:
<<<<<<< ======= >>>>>>>3. 代码写到一半要切任务
git stash push-u-m"说明当前临时工作"gitswitchmain git pull恢复:
gitswitchfeature/原来的分支 git stash list git stash apply stash@{0}git stash drop stash@{0}4. feature 分支落后 main
gitswitchmain git pull gitswitchfeature/功能名 git rebase main如果冲突:
git status notepad 冲突文件 git add 冲突文件 git rebase--continue5. .gitignore 不生效
gitls-files 文件名 gitrm--cached 文件名 git add.gitignore git commit-m"停止跟踪本地文件"6. commit 看起来丢了
git status git reflog git reset--hard <commit_hash>不确定时,不要继续乱操作,先保存git status、git log、git reflog的输出。
二十、危险命令清单
这些命令不是不能用,而是必须知道风险。
gitreset--hard危险点:
会丢弃未提交的工作区和暂存区修改。gitpush-f危险点:
会强制覆盖远端历史,可能删除别人已经 push 的提交。gitbranch-D分支名危险点:
会强制删除本地分支,即使它没有合并。gitrm文件名危险点:
会从 Git 跟踪中删除文件,也会删除工作区真实文件。相对安全版本是:
gitrm--cached文件名它只停止跟踪,保留本地文件。
总结
完成上述全部内容已经足够满足日常开发。上述内容总结如下:
1. 创建仓库。 2. 提交代码。 3. 查看修改。 4. 创建分支。 5. 合并分支。 6. 解决冲突。 7. 推送到 GitHub。 8. 克隆仓库。 9. 双机同步。 10. 处理 push 被拒绝。 11. 使用 Pull Request。 12. 根据 Code Review 继续修改。 13. 使用 stash 临时保存。 14. 使用 rebase 让 feature 基于最新 main。 15. 正确处理 .gitignore。 16. 使用 reflog 找回误操作后的提交。这些能力已经能支撑:
个人项目 课程项目 实验室代码管理 GitHub 开源项目基础协作 公司中普通 feature 分支开发进阶部分
下面这些内容在本文中未曾涉及,读者若有需要可以等真实遇到场景再学:
cherry-pick interactive rebase squash submodule Git LFS bisect worktree GitHub Actions Git 内部对象 blob / tree / commit / tag 复杂 release 流程对于刚接触 Git 的开发者,最重要的不是继续堆命令,而是把下面这条主线用熟:
main 拉新 feature 开发 commit 保存 push 远端 PR 合并 pull 同步 stash 临时切任务 rebase 跟上 main reflog 处理误操作 .gitignore 管理无关文件结语
Git 真正难的地方,不是记命令,而是理解每条命令在改变什么:
它改的是工作区? 它改的是暂存区? 它创建了 commit? 它移动了分支指针? 它同步了远端? 它重写了历史? 它只是本地操作,还是会影响 GitHub?使用 Git 的最重要的习惯:
提交前看 git status。 提交前看 git diff。 不要提交编译产物和本地配置。 新功能从 feature 分支开始。 合并用 PR。 出错先看状态,不要乱用危险命令。以上就是本文全部内容