git 命令大全:运维日常常用命令速查表
日常用到的 git 命令不过三十来条:clone 拿到仓库,status、diff、log 看变化,add、commit 记录,switch、merge 管分支,pull、push 同步,restore、reset、revert 撤销。下面按场景列成速查表,每条一句说明,最后讲在服务器上用 Git 管理配置文件和部署脚本。
运维日常用到的 git 命令,按场景可以分成六组:初始化与克隆(git init、git clone),查看状态与历史(git status、git diff、git log),暂存与提交(git add、git commit),分支切换与合并(git switch、git merge),远程同步(git fetch、git pull、git push),撤销与回退(git restore、git reset、git revert、git stash)。下面每组一张速查表,每条命令一句说明,撤销类命令另外标出了风险。动手前先用 git --version 看一下版本:git switch 和 git restore 需要 Git 2.23 或更新的版本,CentOS 7 自带的是 1.8.3.1,没有这两个命令,表里同时给出了老版本的写法。
先分清四个位置:命令都在它们之间搬运内容
理解了 Git 的四个“位置”,大部分命令的作用就不用死记:
| 位置 | 是什么 | 进出的命令 |
|---|---|---|
| 工作区 | 你看到并直接编辑的文件 | 编辑文件即改变它;git restore 丢弃这里的修改 |
| 暂存区 | 下一次提交将要包含的内容 | git add 放进来;git restore --staged 拿出去 |
| 本地仓库 | .git 目录里的全部提交历史 |
git commit 把暂存区记成一个提交;git reset 移动分支的位置 |
| 远程仓库 | GitLab、Gitea 等服务器上的仓库 | git push 推上去;git fetch、git pull 拉下来 |
git status 会告诉你每个改动处在哪个位置。第一次在一台机器上使用 Git,先设置身份和几项常用配置:
git config --global user.name "zhangsan"
git config --global user.email "[email protected]"
git config --global init.defaultBranch main # 新仓库的默认分支名,Git 2.28 起支持
git config --global core.editor vim # 写提交说明时用的编辑器
git config --global pull.ff only # pull 只接受快进,避免意外生成合并提交
git config --list --show-origin # 查看全部配置以及各自来自哪个文件
不加 --global 时,配置只写进当前仓库的 .git/config,适合在服务器上给某个仓库单独设置提交身份。
初始化、克隆与查看:status、diff、log
初始化与克隆
| 命令 | 说明 |
|---|---|
git init |
把当前目录变成一个 Git 仓库 |
git init -b main |
初始化并指定分支名(Git 2.28 起) |
git clone https://git.example.com/ops/scripts.git |
克隆仓库到同名目录,后面再跟一个路径就克隆到指定目录 |
git clone -b v1.4.2 --depth 1 地址 目录 |
只取某个分支或标签的最新一次提交,在服务器上部署时省时间和磁盘 |
git remote -v |
查看远程仓库的名字和地址 |
查看状态、差异与历史
| 命令 | 说明 |
|---|---|
git status |
哪些文件改了、哪些已暂存、哪些是新文件,以及当前在哪个分支 |
git status -sb |
简短格式,每个文件一行,第一行显示与远程分支相比领先、落后几个提交 |
git diff |
工作区里还没暂存的改动 |
git diff --staged |
已暂存、即将提交的改动,提交前看一眼 |
git diff HEAD~3 -- nginx.conf |
某个文件和 3 个提交之前相比改了什么 |
git log --oneline --graph --decorate --all |
所有分支的提交历史,一行一个,带分支图 |
git log --stat -5 |
最近 5 个提交,以及每个提交改了哪些文件 |
git log -p -- conf.d/shop.conf |
某个文件每一次提交的具体改动 |
git log --since="2 weeks ago" --author=zhangsan |
按时间和作者筛选 |
git log -S 'client_max_body_size' |
找出增加或删除过这段文字的提交,定位某项配置是哪次改的 |
git show 提交号 |
某个提交的说明和完整改动 |
git show HEAD~1:conf.d/shop.conf |
查看文件在上一个提交时的内容,不影响工作区 |
git blame -L 10,20 conf.d/shop.conf |
第 10 到 20 行分别由哪个提交、谁、在什么时间改的 |
HEAD 表示当前所在的提交,HEAD~1 是它的上一个,HEAD~3 是往前数第三个。提交号不用写全,写前 7 位通常就够。
暂存、提交与切换分支
暂存与提交
| 命令 | 说明 |
|---|---|
git add 文件 |
把这个文件的改动放进暂存区 |
git add -A |
暂存所有改动,包括新增和删除的文件 |
git add -p |
逐块询问是否暂存,一次改了几件事、想分成几个提交时用 |
git commit -m "说明" |
把暂存区的内容记成一个提交 |
git commit -am "说明" |
自动暂存所有已跟踪文件的改动并提交,新文件不包括在内 |
git commit --amend |
修改上一个提交的说明,或补进漏掉的文件;已推送的提交不要这样改 |
git rm --cached 文件 |
不再跟踪该文件,但保留在磁盘上,通常配合 .gitignore 使用 |
git mv 旧路径 新路径 |
移动或重命名文件,并记录这个变化 |
切换分支:git switch 与 git checkout
| 命令(Git 2.23 起) | 老版本写法 | 说明 |
|---|---|---|
git branch |
同左 | 列出本地分支,当前分支前有 *;-a 连同远程分支一起列出,-vv 显示跟踪关系 |
git switch dev |
git checkout dev |
切换到 dev;本地没有、远程有同名分支时,自动创建并跟踪它 |
git switch -c hotfix |
git checkout -b hotfix |
新建分支并切换过去 |
git switch -c hotfix v1.4.2 |
git checkout -b hotfix v1.4.2 |
从某个标签或提交新建分支 |
git switch - |
git checkout - |
回到上一个分支 |
git switch --detach v1.4.2 |
git checkout v1.4.2 |
切到某个标签,处于“分离 HEAD”状态,部署指定版本时用 |
git merge dev |
同左 | 把 dev 合并进当前分支 |
git merge --no-ff dev |
同左 | 即使能快进也生成一个合并提交,保留分支的痕迹 |
git merge --abort |
同左 | 出现冲突时放弃这次合并,回到合并前 |
git rebase main |
同左 | 把当前分支的提交挪到 main 最新提交之后;别人也在用的分支不要 rebase |
git cherry-pick 提交号 |
同左 | 只把某一个提交复制到当前分支,常用于把修复补到发布分支 |
git branch -d hotfix |
同左 | 删除已合并的分支;没合并的要用 -D 强制删除 |
git branch -m 旧名 新名 |
同左 | 重命名分支 |
切换分支时,如果工作区里有未提交的修改、并且和目标分支冲突,Git 会拒绝切换并列出冲突的文件,先提交或用 git stash 收起来再切。合并出现冲突时,冲突的文件里会出现 <<<<<<<、=======、>>>>>>> 三种标记,手工改成最终想要的内容并删掉标记,git add 这些文件后执行 git commit 完成合并。
拉取与推送:和远程仓库同步
| 命令 | 说明 |
|---|---|
git fetch |
下载远程的新提交,不改动当前分支和工作区 |
git fetch --prune |
同时清理远程已删除的分支在本地留下的记录 |
git log --oneline HEAD..origin/main |
fetch 之后,查看远程比本地多出的提交 |
git pull |
fetch 加合并,把远程的新提交合进当前分支 |
git pull --ff-only |
只在能快进时更新,本地有分叉就报错停下;服务器上更新代码用它 |
git pull --rebase |
把本地还没推送的提交放到远程提交之后,历史保持一条直线 |
git push |
把当前分支推送到它跟踪的远程分支 |
git push -u origin hotfix |
第一次推送新分支并建立跟踪关系,之后直接 git push |
git push origin --delete hotfix |
删除远程分支 |
git tag -a v1.4.3 -m "说明" |
在当前提交上打一个附注标签,发布时用 |
git push origin v1.4.3 |
推送一个标签;git push --tags 推送全部标签 |
git push --force-with-lease |
改写历史后强制推送,远程被别人更新过时会拒绝;共用分支不要强推 |
git remote add origin 地址 |
给本地仓库添加远程仓库 |
git remote set-url origin 新地址 |
仓库迁移、改用 SSH 地址后修改远程地址 |
撤销、回退与 stash
撤销类命令最容易出事,表里多了一列“风险”,执行前先看:
| 想做的事 | 命令 | 老版本写法 | 风险 |
|---|---|---|---|
| 丢弃工作区里某个文件的修改 | git restore 文件 |
git checkout -- 文件 |
未提交的修改直接丢失,找不回来 |
| 把文件移出暂存区,修改保留 | git restore --staged 文件 |
git reset HEAD 文件 |
无 |
| 把某个文件恢复成两个提交前的样子 | git restore --source=HEAD~2 文件 |
git checkout HEAD~2 -- 文件(老写法会同时改暂存区) |
覆盖该文件当前的内容 |
| 撤销最近一次提交,改动留在暂存区 | git reset --soft HEAD~1 |
同左 | 无,适合说明写错或漏了文件 |
| 撤销最近一次提交,改动留在工作区 | git reset HEAD~1 |
同左 | 无 |
| 彻底回到某个提交,之后的一切都不要 | git reset --hard 提交号 |
同左 | 未提交的修改丢失;已提交的还能从 reflog 找回 |
| 撤销一个已经推送的提交 | git revert 提交号 |
同左 | 无:生成一个反向提交,不改写历史,共用分支用它 |
| 删除未跟踪的文件和目录 | git clean -n 预览,git clean -fd 执行 |
同左 | 删除后无法恢复,务必先看预览 |
| 找回 reset 掉的提交 | git reflog 找到提交号,再 git switch -c 新分支 提交号 |
git checkout -b 新分支 提交号 |
无 |
git reflog 记录了 HEAD 在本地移动过的每一个位置,reset --hard 掉的提交在一段时间内都能从这里找回来;但从来没有提交过的修改,Git 没有任何记录。
stash 用来临时收起手头的修改,比如在服务器上临时改过配置,现在要先拉取新版本:
| 命令 | 说明 |
|---|---|
git stash 或 git stash push -m "说明" |
收起已跟踪文件的修改,工作区恢复干净;push -m 需要 Git 2.13 起,老版本写 git stash save "说明" |
git stash -u |
连同未跟踪的新文件一起收起 |
git stash list |
列出所有收起的记录,最新的是 stash@{0} |
git stash show -p stash@{0} |
查看某条记录的具体改动 |
git stash pop |
恢复最近一条并从列表删除;有冲突时这条记录会保留 |
git stash apply stash@{1} |
恢复指定的一条,记录仍保留在列表里 |
git stash drop stash@{1} |
删除指定的一条 |
在服务器上用 Git 管理配置文件和部署脚本
给配置目录加上版本记录
配置文件放进 Git,“谁改了什么、改坏了怎么退回”就有了答案。以 nginx 为例:
cd /etc/nginx
git init
git config user.name "ops-web01"
git config user.email "[email protected]"
printf '%s\n' '*.key' '*.pem' 'ssl/' > .gitignore
git add -A && git commit -m "init: nginx config on web01"
# 每次改配置:改完先看差异,检查通过、加载成功后再提交
vim conf.d/shop.conf
git diff
nginx -t && systemctl reload nginx && git commit -am "shop.conf: client_max_body_size 50m"
# 新配置出了问题:把这个文件退回上一版,重新加载后把回退也记下来
git restore --source=HEAD~1 conf.d/shop.conf
nginx -t && systemctl reload nginx && git commit -am "revert shop.conf"
几点注意:
- 私钥和带密码的文件不进仓库。 先写好
.gitignore;已经提交过的,git rm --cached只能让以后不再跟踪,历史里依然有,应当更换这些密钥和密码。 - Git 不管权限和属主。 它只记录文件的可执行位,不记录属主、属组和其他权限位,也不记录空目录。从 Git 恢复 /etc 下的文件后,用
ls -l核对一下权限。 - 仓库要推到别处。 只在本机的仓库,服务器坏了它也跟着没了。加一个远程仓库定期
git push,就多了一份异地的配置历史。 - 整个 /etc 都想管起来,可以用 etckeeper(RHEL 系在 EPEL 软件源中):它把 /etc 放进 Git,并在用 apt、dnf 安装软件前后自动提交。
Git 记录的是“文件变成了什么样”,记录不了“谁登录了哪台服务器、敲了哪些命令”。两者配合才完整:Toplink DCIM 的 IPMI 远程管理里,经浏览器控制台发起的 SSH 会话会全程录像并记录命令级日志,配置出了问题,可以先用 git log 定位是哪次改动,再对照会话记录找到当时的操作过程。
用 Git 发布部署脚本或代码
服务器拉取仓库,用只读的部署密钥,并且只接受快进更新:
# 1. 生成本机专用的部署密钥,把 .pub 公钥添加到仓库的部署密钥(只读)
ssh-keygen -t ed25519 -f ~/.ssh/deploy_scripts -N '' -C "deploy@web01"
# 2. 在 ~/.ssh/config 里为仓库写一个别名,固定密钥和端口
Host git-ops
HostName git.example.com
Port 22022
User git
IdentityFile ~/.ssh/deploy_scripts
IdentitiesOnly yes
# 3. 克隆、日常更新、按标签发布与回滚
ssh -T git-ops # 测试密钥是否被仓库接受
git clone git-ops:ops/scripts.git /opt/scripts
cd /opt/scripts && git pull --ff-only # 日常更新
git fetch --tags && git switch --detach v1.4.3 # 发布指定版本
git switch --detach v1.4.2 # 有问题,切回上一个版本
代码目录如果就在网站根目录下,.git 目录可能被人直接下载(访问 https://站点/.git/config 试一下)。在 nginx 里加上 location ~ /\.git { deny all; },或者用 git archive 导出不含 .git 的文件再发布。
没有代码托管平台、只想“推送即部署”,可以在服务器上建一个裸仓库,用钩子把推送的内容检出到发布目录:
# 服务器上(发布目录 /var/www/site 要事先建好,并且 deploy 用户可写)
git init --bare /srv/git/site.git
printf '#!/bin/sh\ngit --work-tree=/var/www/site --git-dir=/srv/git/site.git checkout -f main\n' > /srv/git/site.git/hooks/post-receive
chmod +x /srv/git/site.git/hooks/post-receive
# 自己的电脑上
git remote add prod ssh://[email protected]:22022/srv/git/site.git
git push prod main
服务器上常见的 Git 报错
| 报错 | 原因 | 处理 |
|---|---|---|
fatal: detected dubious ownership in repository at '/opt/scripts' |
仓库目录属于另一个用户,例如用 deploy 克隆、用 root 执行;Git 2.35.2 起出于安全考虑拒绝操作 | 用属主的身份执行,如 sudo -u deploy git -C /opt/scripts pull --ff-only;确认可信后才用 git config --global --add safe.directory /opt/scripts 放行 |
fatal: Not possible to fast-forward, aborting. |
服务器上有本地提交,或者远程的历史被改写过 | git log --oneline origin/main..HEAD 看本地多了什么,确认无用后 git reset --hard origin/main |
fatal: Need to specify how to reconcile divergent branches. |
本地和远程分叉了,又没有设置 pull 的方式 | 设置 pull.ff only 或 pull.rebase true,再按上一行处理分叉 |
error: Your local changes to the following files would be overwritten by merge: |
有人直接在服务器上改了受 Git 管理的文件 | 先 git diff 看改了什么;要保留就 git stash,拉取后 git stash pop |
脚本执行报 /bin/bash^M: bad interpreter |
文件在 Windows 上编辑过,换行符是 CRLF | 仓库里加 .gitattributes,写入 *.sh text eol=lf,执行 git add --renormalize . 后提交 |
Permission denied (publickey). |
部署公钥没有添加到仓库,或者用错了密钥 | ssh -vT git-ops 查看实际提交的是哪把密钥 |
git diff 显示 old mode 100644 和 new mode 100755 |
文件的可执行位变了 | 确实需要可执行就提交;在 Windows 上提交脚本,用 git update-index --chmod=+x deploy.sh 设置可执行位 |
You are in 'detached HEAD' state. |
切到了标签或某个提交上 | 部署指定版本时属于正常;要在这里改代码,先 git switch -c 新分支名 |
报错信息里通常已经给出了下一步的建议命令(以 hint: 开头的几行),先读完再动手,尤其不要看到报错就习惯性地加 --force 或 reset --hard。
常见问题
git status 里的中文文件名显示成一串带反斜杠的数字,怎么办?
这是 Git 默认把非 ASCII 字符转义后显示。执行 git config --global core.quotepath false,git status、git diff 里就会直接显示中文;仍是乱码的话,再检查终端和系统的字符集是不是 UTF-8。
git push 每次都要输入账号密码,怎么免输?
推荐改用 SSH 地址:把公钥添加到代码平台的账号或仓库,再把远程地址换成 git@ 开头的写法。必须用 HTTPS 时可以配置 credential.helper,其中 store 会把凭据明文存进 ~/.git-credentials,多人共用的服务器上不要用;不少平台也已不接受账号密码,要改用访问令牌。
Git 能用来备份整个服务器吗?
不适合。Git 适合管理文本文件的变更历史,大文件和二进制文件会让仓库迅速膨胀,运行中的数据库文件直接提交也得不到一致的副本。配置文件和脚本用 Git 管理,数据和系统用专门的备份工具。