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 --version

git:调用 Git 程序。
--version:查看当前安装的 Git 版本。

如果没有安装,可以执行:

sudo apt update
sudo apt install git -y

sudo:以管理员权限执行命令。
apt:Ubuntu/Debian 系统的软件包管理工具。
update:更新本地软件包索引。
install:安装软件包。
-y:自动回答 yes,避免安装过程中反复确认。

安装后配置用户名和邮箱:

git config --global user.name "你的名字"
git config --global user.email "你的邮箱"

git config:查看或修改 Git 配置。
--global:设置当前用户级别的 Git 配置,对当前系统用户的所有仓库生效。
user.name:提交记录中显示的作者名称。
user.email:提交记录中显示的作者邮箱。

查看配置:

git config --global --list

--list:列出当前配置项。

注意:这里的用户名和邮箱会写进 commit 作者信息里,不等于 GitHub 登录账号,但建议邮箱与 GitHub 账号邮箱保持一致。


二、创建第一个本地 Git 仓库

先创建一个 C 项目目录:

mkdir git_c_demo
cd git_c_demo

mkdir:创建目录。
cd:切换当前目录。

创建一个简单的 main.c

cat > main.c <<'EOF'
#include <stdio.h>

int main(void)
{
    printf("hello git\n");
    return 0;
}
EOF

cat:在终端打印或写入文件内容。这里配合重定向创建文件。
>:把输出写入指定文件,会覆盖原文件。
<<'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
./main

make:根据 Makefile 中的规则执行构建。
./main:运行当前目录下的 main 可执行文件。./ 表示当前目录。

初始化 Git 仓库:

git init

git init:在当前目录创建一个新的 Git 仓库,本质是生成 .git 目录。

查看当前仓库状态:

git status

git status:查看当前分支、工作区、暂存区状态,包括未跟踪文件、已修改文件、待提交内容等。

这时你会看到 main.cMakefileuntracked files

untracked files 表示:

Git 看到了这些文件,
但还没有开始跟踪它们。

把文件加入暂存区:

git add main.c Makefile

git add:把工作区中的修改加入暂存区,等待下一次 commit。

提交到本地仓库:

git commit -m "初始化 C 项目"

git commit:把暂存区中的内容保存为一次提交。
-m:message,直接在命令行指定提交说明。

查看提交历史:

git log --oneline

git log:查看提交历史。
--oneline:每个 commit 只显示一行,便于快速查看。

到这里,最核心流程已经完成:

写代码
↓
git add
↓
git commit

三、理解 status、diff、log

日常开发中,下面三个命令使用频率非常高:

git status
git diff
git log

修改 main.c

sed -i 's/hello git/hello git and github/' main.c

sed:Linux 下的文本处理工具。
-i:直接修改文件内容。
s/旧内容/新内容/:替换文本。

查看工作区差异:

git diff

git diff:查看工作区和暂存区之间的差异。简单理解就是:你改了什么,但还没 git add

加入暂存区:

git add main.c

查看暂存区差异:

git diff --staged

--staged:查看暂存区和上一次提交之间的差异,也就是即将被 commit 的内容。

提交:

git commit -m "修改 hello 输出内容"

查看图形历史:

git log --oneline --graph --decorate --all

--graph:用 ASCII 图显示分支关系。
--decorate:显示分支名、HEAD、tag 等引用信息。
--all:显示所有本地分支和远端追踪分支的历史。

建议以后经常使用:

git log --oneline --graph --decorate --all

它能帮助你看清楚当前仓库的真实历史结构。


四、撤销错误操作:restore、reset、revert 的基本思想

Git 里“撤销”不是一个命令解决所有问题,而是要先判断你要撤销的是哪一层:

工作区修改?
暂存区修改?
已经 commit 的修改?

撤销工作区修改:

git restore main.c

git restore:恢复工作区文件内容。常用于撤销还没有加入暂存区的修改。

如果文件已经 git add 到暂存区,先撤出暂存区:

git restore --staged main.c

--staged:只把文件从暂存区移回工作区,不删除实际修改。

如果 commit 已经提交,而且希望用一个新的提交来抵消它,可以使用:

git revert <commit_hash>

git revert:创建一个新的 commit,用来反向撤销指定 commit 的修改。
<commit_hash>:某次提交的编号。

revert 的特点是安全,因为它不删除历史,而是新增一条“反向修改”。

日常建议:

未 add:git restore
已 add 未 commit:git restore --staged
已 commit 且已共享:优先 git revert

五、分支:branch、switch、merge

分支是 Git 的核心能力之一。

查看当前分支:

git branch

git branch:查看、创建或删除本地分支。

创建并切换新分支:

git switch -c feature/demo

git switch:切换分支。
-c:create,创建新分支并切换过去。
feature/demo:分支名。

切换回 main:

git switch main

在 feature 分支提交后,可以合并回 main:

git merge feature/demo

git merge:把指定分支的修改合并到当前分支。

删除已合并分支:

git branch -d feature/demo

-d:delete,安全删除本地分支。如果 Git 认为该分支未合并,会拒绝删除。

分支的基本模型是:

main
  |
  A
   \
    B  feature/demo

合并后可能变成:

A --- B  main

或者产生 merge commit:

A ---- M  main
 \    /
  B --

六、冲突:两个分支改了同一处代码怎么办?

冲突的本质是:

两个分支改了同一个位置,
Git 不知道最终应该保留哪一个版本。

例如:

<<<<<<< HEAD
    printf("hello from main branch\n");
=======
    printf("hello from feature branch\n");
>>>>>>> feature/demo

含义是:

<<<<<<< HEAD 到 =======
当前分支的内容。

======= 到 >>>>>>>
要合并进来的分支内容。

解决冲突的标准流程:

git status
cat main.c
nano main.c
git add main.c
git commit

nano:Linux 下常见的终端文本编辑器。
不带 -mgit commit:打开编辑器填写提交信息,常用于 merge commit。

解决冲突时必须删除:

<<<<<<<
=======
>>>>>>>

最终文件应该是干净、可编译的代码。

冲突不是错误,而是 Git 在告诉你:

我不能替你决定最终代码应该是什么,
需要你自己判断。

七、GitHub:远端仓库与 SSH 连接

GitHub 可以理解成:

GitHub = 远端 Git 仓库 + Web 页面 + 协作工具

它不仅能保存代码,还能支持:

README
Issues
Pull Request
Actions
Releases
Wiki

Linux 开发中推荐使用 SSH 连接 GitHub。

检查 SSH 目录:

ls -al ~/.ssh

ls:列出目录内容。
-a:显示隐藏文件。
-l:使用长格式显示文件详细信息。
~/.ssh:当前用户的 SSH 配置目录。

生成 SSH key:

ssh-keygen -t ed25519 -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_ed25519

ssh-add:把私钥加入 ssh-agent,避免每次连接都手动指定。

查看公钥:

cat ~/.ssh/id_ed25519.pub

只复制 .pub 公钥到 GitHub,不要复制没有 .pub 的私钥文件。

测试连接:

ssh -T git@github.com

ssh:通过 SSH 协议连接远程主机。
-T:不分配远程终端,只测试认证连接。
git@github.com:GitHub SSH 连接地址。

成功时通常会看到:

Hi 用户名! You've successfully authenticated...

八、本地仓库连接 GitHub:remote、push、origin/main

如果本地已经有仓库,GitHub 上创建了空仓库,可以添加远端:

git remote add origin git@github.com:用户名/git_c_demo.git

git remote:管理远端仓库。
add:添加远端。
origin:远端仓库别名,约定俗成常用这个名字。
git@github.com:用户名/仓库名.git:SSH 格式的远端仓库地址。

查看远端配置:

git remote -v

-v:verbose,显示详细远端地址,包括 fetch 和 push 地址。

第一次推送 main:

git push -u origin main

git push:把本地提交推送到远端仓库。
-u:建立 upstream 追踪关系。
origin:远端仓库名。
main:要推送的本地分支名。

执行后建立关系:

本地 main  ←→  远端 origin/main

这里要区分:

main:
本地分支。

origin/main:
本地记录的远端 origin 上 main 分支的状态。
它不是 GitHub 本身,而是本地保存的一份远端分支快照。

如果远端地址写错,可以修改:

git remote set-url origin git@github.com:用户名/git_c_demo.git

set-url:修改已有远端的 URL 地址。


九、clone:第二台电脑如何接入项目

如果 Windows 主机要接入同一个 GitHub 仓库,应使用:

git clone git@github.com:用户名/git_c_demo.git

git clone:复制一个已有 Git 仓库到本地,包括代码、.git 目录、提交历史、分支、远端配置等。

注意:

clone 不是下载源码。
clone 是复制整个 Git 仓库。

所以 clone 后:

不需要 git init。
不需要 git remote add origin。

因为 clone 已经自动完成了:

创建 .git
复制历史
设置 origin
检出默认分支 main
建立 upstream

Windows 上建议使用 PowerShell:

mkdir D:\GitStudy
cd D:\GitStudy
git clone git@github.com:用户名/git_c_demo.git
cd git_c_demo
git status

Windows 下查看文件:

dir

dir:PowerShell/CMD 下查看当前目录文件列表。

查看文件内容:

Get-Content main.c

Get-Content:PowerShell 下读取并打印文件内容。

打开文本文件:

notepad main.c

notepad:打开 Windows 记事本编辑文件。


十、双机协作:fetch 和 pull

典型环境是:

Ubuntu 24.04 虚拟机
        │
        │ git push
        ▼
GitHub
        ▲
        │ git fetch / git pull
Windows 主机

一台机器 push 后,另一台机器不会自动变化。

在另一台机器上可以先执行:

git fetch

git fetch:从远端获取最新提交、分支、标签等信息,更新 origin/main,但不修改当前本地分支和工作区文件。

fetch 后查看历史:

git log --oneline --graph --decorate --all

你可能看到:

origin/main 已经前进
HEAD -> main 还停在旧提交

如果要真正同步当前分支:

git pull

git pull:从远端拉取并合并到当前分支。简单理解:

git pull = git fetch + git merge

更稳妥的习惯是:

git fetch
git log --oneline --graph --decorate --all
git diff main origin/main
git pull

git 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 status
Get-Content main.c
notepad main.c
git add main.c
git commit
git push

不要一看到 push 被拒绝就执行:

git push -f

-f:force,强制推送。它可能覆盖远端已有提交,团队开发中非常危险。


十二、GitHub Pull Request 工作流

真实团队开发通常不直接往 main 推代码,而是:

main 保持稳定
feature 分支开发
Pull Request 合并

标准流程:

git switch main
git pull
git switch -c feature/pr-demo
notepad main.c
git diff
git 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 branch

PR 合并发生在 GitHub 上,但本地不会自动更新,所以还要:

git switch main
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
git diff
git add main.c
git commit -m "根据 review 修改输出内容"
git push

GitHub 上原来的 PR 会自动更新,不需要重新创建 PR。


十四、stash:临时保存做到一半的工作

日常开发中经常会出现:

代码写到一半,还不能 commit,
但突然需要切回 main 处理别的任务。

这时使用 stash。

git stash push -m "临时保存当前修改"

git stash:管理临时保存的修改。
push:把当前未提交修改压入 stash 栈。这里的 push 不是推送到 GitHub。
-m:message,给这次 stash 添加说明。

查看 stash 列表:

git stash list

list:列出当前仓库中的 stash 记录。

恢复 stash:

git stash apply stash@{0}

apply:把指定 stash 的内容恢复到当前工作区,但保留 stash 记录。
stash@{0}:最新的一条 stash 记录。

删除 stash:

git stash drop stash@{0}

drop:删除指定 stash 记录。

更快捷的方式:

git stash pop

pop:恢复最新 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 用法:

git switch main
git pull
git switch feature/rebase-demo
git rebase main

git rebase:把当前分支上的提交重新应用到另一个分支后面。
main:这里表示把当前 feature 分支的提交重新放到最新 main 后面。

rebase 前:

      F    feature
     /
A --- M    main

rebase 后:

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 跟踪:

git ls-files local_config.txt

git ls-files:列出 Git 当前正在跟踪的文件。后面跟文件名时,用来判断该文件是否在跟踪列表中。

让 Git 停止跟踪文件,但保留本地文件:

git rm --cached local_config.txt

git rm:让 Git 删除某个文件。
--cached:只从 Git 的索引/跟踪列表中移除,不删除工作区真实文件。

如果是目录:

git rm -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 reflog

git reflog:查看本地 HEAD 或分支引用的移动记录,常用于找回 reset、rebase、切分支等操作后“看起来丢失”的提交。

git loggit reflog 的区别:

git log:
查看当前分支能访问到的提交历史。

git reflog:
查看 HEAD 和分支指针最近移动过的位置。

模拟回退:

git reset --hard HEAD~1

git reset:移动当前分支指针,让当前分支回到指定 commit。
--hard:让当前分支、暂存区、工作区都恢复到指定 commit,会丢弃未提交修改,危险。
HEAD~1:当前 HEAD 的上一个提交。

如果 reflog 里看到:

abc1234 HEAD@{1}: commit: 添加 reflog 演示提交

可以恢复:

git reset --hard abc1234

abc1234:目标 commit hash,要换成你自己终端里看到的编号。

注意:

reflog 是本地记录。
不会 push 到 GitHub。
Windows 的 reflog,Ubuntu 看不到。

如果你只是演示分支,想强制删除:

git branch -D feature/reflog-demo

-D:强制删除本地分支,即使它没有合并到 main,也会删除。比 -d 危险。


十八、日常开发推荐流程

1. 开始一个新功能

git switch main
git pull
git switch -c feature/功能名

2. 开发并提交

git status
git diff
git add 具体文件
git diff --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 request

5. 根据 review 修改

git diff
git add 具体文件
git commit -m "根据 review 修改代码"
git push

6. PR 合并后本地收尾

git switch main
git pull
git branch -d feature/功能名

7. 另一台机器同步

git switch main
git pull
git status

这就是日常开发最常用的 GitHub Flow。


十九、常见问题处理流程

1. push 被拒绝

git fetch
git log --oneline --graph --decorate --all
git pull
git push

不要直接 git push -f


2. 发生冲突

git status
Get-Content 冲突文件
notepad 冲突文件
git add 冲突文件
git commit

整理文件时删除:

<<<<<<<
=======
>>>>>>>

3. 代码写到一半要切任务

git stash push -u -m "说明当前临时工作"
git switch main
git pull

恢复:

git switch feature/原来的分支
git stash list
git stash apply stash@{0}
git stash drop stash@{0}

4. feature 分支落后 main

git switch main
git pull
git switch feature/功能名
git rebase main

如果冲突:

git status
notepad 冲突文件
git add 冲突文件
git rebase --continue

5. .gitignore 不生效

git ls-files 文件名
git rm --cached 文件名
git add .gitignore
git commit -m "停止跟踪本地文件"

6. commit 看起来丢了

git status
git reflog
git reset --hard <commit_hash>

不确定时,不要继续乱操作,先保存 git statusgit loggit reflog 的输出。


二十、危险命令清单

这些命令不是不能用,而是必须知道风险。

git reset --hard

危险点:

会丢弃未提交的工作区和暂存区修改。
git push -f

危险点:

会强制覆盖远端历史,可能删除别人已经 push 的提交。
git branch -D 分支名

危险点:

会强制删除本地分支,即使它没有合并。
git rm 文件名

危险点:

会从 Git 跟踪中删除文件,也会删除工作区真实文件。

相对安全版本是:

git rm --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。
出错先看状态,不要乱用危险命令。

以上就是本文全部内容

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐