Git 的一些使用技巧
Git 是一个非常强大的版本控制工具,是程序员必须要掌握的技能。
这里记录常用的命令技巧和碰到的一些问题。
首先应该了解 Git 里面的几个概念。
- Workspace:工作区
- Index/Stage:暂存区(使用
git add
命令将工作区的改动添加到暂存区) - Repository:本地仓库(使用
git commit
命令将暂存区的改动提交到本地仓库) - HEAD:指向本地仓库的当前版本,上一个版本就是
HEAD^
,上上一个版本就是HEAD^^
,往上 10 个版本,可以 写成HEAD~10
。 - Remote:远程仓库
克隆仓库
git clone git@github.com:shipengqi/shipengqi.github.io.git <target dir> -b <branch>
将 shipengqi.github.io.git
克隆到 target dir
指定的文件夹(默认是远程仓库的名字),并切换到指定
分支 branch
(默认是 master 分支)。
未暂存的内容
把未暂存的内容添加到暂存区
# 提交所有改动
git add -A
# 提交被修改 (modified) 和被删除 (deleted) 文件,不包括新文件 (new)
git add -u
# 提交新文件 (new) 和被修改 (modified) 文件,不包括被删除 (deleted) 文件
git add .
# 添加指定文件到暂存区
git add [file1] [file2] ...
# 添加指定目录到暂存区,包括子目录
git add [dir]
# 添加每个变化前,都会要求确认
# 对于同一个文件的多处变化,可以实现分次提交
git add -p
把未暂存的内容移动到一个新分支
git checkout -b new-branch
把未暂存的内容移动到另一个已存在的分支
git stash
git checkout my-branch
git stash pop
放弃未暂存的修改
git checkout <file-name>
# 放弃所有修改
git checkout .
暂存的内容
把暂存的内容添加到上一次的提交
git commit --amend
取消暂存的内容
添加到暂存区的文件,但是还没有提交,如果想要撤销暂存的文件,可以使用 git reset HEAD <file1> <file2>...
的方式取消暂存。
git reset HEAD file2
# 或者
git restore --staged <file>...
# 同时删除工作区和暂存区中的文件
git rm [file1] [file2] ...
# 从暂存区删除文件, 但工作区不删除
git rm --cached [file]
这样 file2
文件又回到了之前已修改未暂存的状态。
编辑提交
修改提交信息
# 打开默认编辑器
git commit --amend --only
# 或者
git commit --amend --only -m 'xxxxxxx'
如果已经 push 了这次提交, 那么可以修改这次提交(commit)然后强推(force push), 但是不推荐。
编辑指定 commit 的提交信息
编辑指定 commit 的提交信息,可以先使用 rebase
来修改某一次的提交信息
如果当前在 main
分支:
git rebase -i main^^
# 使用 commit 生成的哈希值来定位
git rebase -i aa588cd
git rebase -i aa588cd72b95ab35ec6e15637fb1e110281b2200
main^^
表示当前 main
指向的 commit 之前倒数第 2 个 commit。main~2
也是一样的意思。^
和 ~{count}
都是表示把 commit 往回偏移。
执行上面的命令之后,会进入如下的编辑界面:
pick aa588cd display error
pick e10dddf regex draft
# Rebase aa588cd..e10dddf onto aa588cd (2 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
找到想要修改的 commit,将 pick
改为 edit
,然后 wq
保存退出,接着再运行:
git commit --amend -m 'changed commit mesasge'
修改提交里的用户名和邮箱
git commit --amend --author "New Authorname <work@example.com>"
改完信息后,还需要 git rebase --continue
,将基准从当前倒数第二位置移到最新一次提交。git log
去检查下状态。
commit 添加签名
git commit -m "xx" -s
如果你配置了 username 和 email,参数 -s
会自动在 commit 的信息中添加签名,如下:
commit d52618492b788425b7c86d25c5e37eb67fd8fba6
Author: shipengqi <xxx@gmail.com>
Date: Mon May 2 18:36:11 2022 +0800
regex draft
Signed-off-by: shipengqi <xxx@gmail.com>
从一个提交(commit)里移除一个文件
从一个提交(commit)里移除一个文件:
git checkout HEAD^ myfile
git add -A
git commit --amend
当你有一个开放的补丁(open patch),你往上面提交了一个不必要的文件,需要强推(force push)去更新这个远程补丁。
撤销某一次提交
修改已经提交到了当前分支,但是还没有 push 到远程仓库
git reset --hard HEAD^
# 回退到指定的版本
git reset --hard <commit id>
# 只撤销提交,但不改变代码
git reset HEAD^
git reset commit-id
已经 push 到远程仓库的提交
已经 push 到远程仓库的提交,使用 git revert
,回滚到指定的历史版本,再 git push
更新远程仓库。
# 撤销某个 commit 版本
git revert commit-id
对于已经 push 到远程仓库的提交,也可以使用 reset,然后执行 git push -f
强制推到远程仓库中去,但是可能导致冲突。
如果只是回退到上一个 commit,建议使用 revert,如果要回退到多个版本之前,还是要使用 reset。
revert 与 reset 的区别
- reset 是在正常的 commit 历史中,删除了指定的 commit,这时 HEAD 是向后移动了,而 revert 是在正常的 commit 历史中再 commit 一 次,HEAD 是一直向前的。
- 对于已经把代码已经 push 到远程仓库,reset 删除指定 commit 以后,
push
可能导致一大堆冲突.但是 revert 不会。 - revert 是撤销指定的某个 commit 版本,但是指定 commit 之后的版本,还会保留下来。reset 是将 HEAD 移动到了指定的 commit, 指定 commit 之后的版本都会被丢弃。
如果想恢复到之前某个提交的版本,且那个版本之后提交的版本都不要了,就用 reset。 如果想撤销之前的某一版本,但是又想保留该目标版本后面的版本,就用 revert。
revert Merge Commit
执行 git revert commitId
可能会报错:
error: commit xxxxxxxxxxx is a merge but no -m option was given.
这是因为指定的 commit 是一次 merge,需要 -m
参数指定要 revert 的这个 merge commit 中的哪一个。
比如:git revert HEAD~1 -m 1
会 revert 第一个 commit。
你也可以在 git log
找到你要 revert 的那个 commit。
意外的做了一次硬重置(hard reset),如何找回内容
当你用 git reset --hard HEAD^
回退到上个版本时,再想恢复,就必须找到要恢复版本的 commit id。可以通过 git reflog
找到
那次 commit。
选择你想要回到的提交(commit)的 commit id,再重置一次:
git reset --hard SHA1234
提交错分支怎么办
还没有提交代码
比如忘了创建分支,并且 master 的代码可能还不是最新的,但是已经直接在 master 分支上进行了修改。这种情况下可以先把代码暂存起来,然后把 master 分 支更新到最新,再创建并切换到新的分支,然后把暂存的代码恢复回来。
# 暂存代码
git stash
# 更新 master
git fetch
git merge origin/master
# 创建新的分支并切换过去
git checkout -b <name>
# 把暂存的代码恢复回来
git stash pop
然后就可以直接 commit 了。
已经提交
代码提交了,还没有 push,这个时候可以先把它撤回来
git reset HEAD^
这样就把上一次的提交恢复为未提交的状态了,如果确定当前所在的 master 分支代码已经是最新的,就可以直接 checkout 到新的分支,来进行提交。否则,就就参考第一种情况。
查看 commit 历史
# 显示当前分支的版本历史
git log
# 显示 commit 历史,以及每次 commit 发生变更的文件
git log --stat
# 搜索提交历史,根据关键词
git log -S [keyword]
# 显示某个 commit 之后的所有变动,每个 commit 占据一行
git log [tag] HEAD --pretty=format:%s
# 显示某个 commit 之后的所有变动,其"提交说明"必须符合搜索条件
git log [tag] HEAD --grep feature
# 显示某个文件的版本历史,包括文件改名
git log --follow [file]
git whatchanged [file]
# 显示指定文件相关的每一次 diff
git log -p [file]
# 显示过去 5 次提交
git log -5 --pretty --oneline
# 显示所有提交过的用户,按提交次数排序
git shortlog -sn
# 显示指定文件是什么人在什么时间修改过
git blame [file]
# 查看命令历史
git reflog
回退前,用 git log
可以查看提交历史,以便确定要回退到哪个版本。
回退后,用 git reflog
查看命令历史,以便确定要回到未来的哪个版本。
查看某次提交
# 显示某次提交的元数据和内容变化
git show [commit]
# 显示某次提交发生变化的文件
git show --name-only [commit]
# 显示某次提交时,某个文件的内容
git show [commit]:[filename]
Stash
stash
和 add
的区别:
git stash
的作用是把工作区(必须是工作区中已经被 git 追踪到的文件)和暂存区中的内容暂时存到一个栈上。而且这个堆是和分支不
相关的。切换分支后,依然可以看到并使用。
git add
命令将修改添加到暂存区。
暂存工作目录下的所有改动
git stash
暂存所有改动,包括 untracked 的文件(新建的文件)
git stash -u
暂存指定文件
# 暂存某一个文件
git stash push working-directory-path/filename.ext
# 暂存多个文件
git stash push working-directory-path/filename1.ext working-directory-path/filename2.ext
暂存时记录消息
git stash save <message>
# 或
git stash push -m <message>
使用某个指定暂存
# 查看 stash 记录
git stash list
# apply 某个 stash
git stash apply "stash@{n}"
n
是 stash 在栈中的位置,最上层的 stash 会是 0
。
使用最后一个 stash 的状态,并删除这个 stash
git stash pop
删除所有的 stash
git stash clear
仅从 stash 中拿出某个文件的修改
git checkout <stash@{n}> -- <file-path>
比较差异
# 显示暂存区和工作区的差异
git diff
# 显示本地仓库中任意两个 commit 之间的文件变动
git diff <commit-id> <commit-id>
# 显示暂存区和最近的 commit 的不同
git diff --cached
# 显示工作区与当前分支最新 commit 之间的差异
git diff HEAD
分支
# 切换分支
git checkout dev
# 切换并新建一个分支
git checkout -b newBranch
# 删除一个分支
git branch -d branch
# 列出所有本地分支
git branch
# 列出所有远程分支
git branch -r
# 列出所有本地分支和远程分支
git branch -a
# 删除远程分支
git push origin --delete [branch-name]
git branch -dr [remote/branch]
# 合并指定分支到当前分支
git merge [branch]
# 选择一个 commit,合并进当前分支
git cherry-pick [commit]
# 关联远程分支
git branch -u origin/mybranch
# 或者在 push 时加上 -u 参数
git push origin/mybranch -u
# 重命名本地分支
git branch -m <new-branch-name>
需要提交到一个新分支,但错误的提交到了 master
在 master 下创建一个新分支,不切换到新分支,仍在 master 下:
(master)$ git branch my-branch
把 master 分支重置到前一个提交:
(master)$ git reset --hard HEAD^
checkout 到刚才新建的分支继续工作:
(master)$ git checkout my-branch
从错误的分支拉取了内容,或把内容拉取到了错误的分支
使用 git reflog
找到在这次 pull 之前 HEAD 的指向。
(master)$ git reflog
ab7555f HEAD@{0}: pull origin wrong-branch: Fast-forward
c5bc55a HEAD@{1}: checkout: checkout message goes here
重置分支到你所需的提交:
git reset --hard c5bc55a
恢复误删除的分支
有些时候可能删除了还没有推到远程的分支,如何恢复?例,创建一个分支,并做一次提交:
(master)$ git checkout -b my-branch
(my-branch)$ git branch
(my-branch)$ touch foo.txt
(my-branch)$ ls
README.md foo.txt
(my-branch)$ git add .
(my-branch)$ git commit -m 'foo.txt added'
(my-branch)$ foo.txt added
1 files changed, 1 insertions(+)
create mode 100644 foo.txt
现在我们切回到主(master)分支,‘不小心的’删除 my-branch
分支
(my-branch)$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
(master)$ git branch -D my-branch
Deleted branch my-branch (was 4e3cd85).
(master)$ echo oh noes, deleted my branch!
oh noes, deleted my branch!
开始恢复删除的分支,先使用 reflog
命令, 它存储了仓库(repo)里面所有动作的历史。
(master)$ git reflog
69204cd HEAD@{0}: checkout: moving from my-branch to master
4e3cd85 HEAD@{1}: commit: foo.txt added
69204cd HEAD@{2}: checkout: moving from master to my-branch
可以看到一个删除分支的提交 hash(commit hash),开始恢复:
(master)$ git checkout -b my-branch-help
Switched to a new branch 'my-branch-help'
(my-branch-help)$ git reset --hard 4e3cd85
HEAD is now at 4e3cd85 foo.txt added
(my-branch-help)$ ls
README.md foo.txt
标签
# 查看标签
git tag
# 展示当前分支的最近的 tag
git describe --tags --abbrev=0
# 查看标签详细信息
git tag -ln
# 本地创建标签
git tag [version-number]
# 默认 tag 是打在最近的一次 commit 上,指定 commit 打 tag
git tag -a [version-number] -m "v1.0 发布(描述)" [commit-id]
# 推送标签到远程仓库,保证本地创建好了标签才可以推送标签到远程仓库:
git push origin [local-version-number]
# 一次性推送所有标签
git push origin --tags
# 删除本地标签
git tag -d [tag-name]
# 删除远程标签
git push origin --delete tag [tagname]
# 切回到某个标签
git checkout -b branch_name tag_name
恢复已删除标签
首先, 需要找到无法访问的标签(unreachable tag):
git fsck --unreachable | grep tag
得到这个标签(tag)的 hash,然后:
git update-ref refs/tags/<tag_name> <hash>
这时标签(tag)应该已经恢复了。
远程仓库
# 下载远程仓库的所有变动
git fetch
# 显示所有远程仓库
git remote -v
# 显示某个远程仓库的信息
git remote show [remote]
# 增加一个新的远程仓库,并命名
git remote add [shortname] [url]
# 修改远程仓库的 url
git remote set-url [remote] [url]
# 取回远程仓库的变化,并与本地分支合并
git pull [remote] [branch]
# 上传本地指定分支到远程仓库
git push [remote] [branch]
# 强行推送当前分支到远程仓库,即使有冲突
git push [remote] --force
# 推送所有分支到远程仓库
git push [remote] --all
同步远程仓库到自己 fork 的仓库
如果一个项目多人维护,每个人都 fork 了主仓库,并修改,提交 PR,那么如果在你提交自己的修改之前,主仓库 merge 了别人的 PR,
你 fork 的仓库的 commit 就会落后于主仓库,例如会有类似 This branch is 12 commit behind ITOM-Shared-Services:master.
的提示。这个时候
直接提交你的代码,创建 PR,如果 merge 你的 PR,可能就会有冲突,怎么解决?
- 切换在你本地的仓库
- 添加 remote
git remote add itom git@github.houston.softwaregrp.net:ITOM-Shared-Services/keel-service.git
上面的命令中 itom
是给这个 remote 命名,git@github.houston.softwaregrp.net:ITOM-Shared-Services/keel-service.git
是 remote 的地址。
- 验证
git remote -v
- 在每次提交前执行
git pull itom master
,可以把主仓库的最新 commits 拉去到本地。 - 提交代码
Rebase 和 Merge
rebase 和 merge 有什么区别
rebase
rebase 会把你当前分支的 commit 放到公共分支的最后面,所以叫变基。 例如,你从 master 拉了个 feature 分支出来,然后你提交了几个 commit,这个时候刚好有人把他开发的东西合并到 master 了,这个时 候 master 就比你拉分支的时候多了几个 commit,如果这个时候你 rebase master 的话,就会把你当前的几个 commit,放到那个 人 commit 的后面。
merge
merge 会把公共分支和你当前的 commit 合并在一起,形成一个新的 commit 提交。
撤销 rebase/merge
如果 merge 或 rebase 了一个错误的分支, 或者完成不了一个进行中的 rebase/merge。 Git 在进行危险操作的时候会把原始的 HEAD 保
存在一个叫 ORIG_HEAD
的变量里, 所以要把分支恢复到 rebase/merge 前的状态是很容易的。
git reset --hard ORIG_HEAD
合并冲突时如何撤销 merge
如果已经执行了 git merge
命令,但是发生了冲突,可以使用下面的命令撤销:
git merge --abort
配置
配置 http 和 socks 代理
git config --global https.proxy 'http://127.0.0.1:8001'
git config --global http.proxy 'http://127.0.0.1:8001'
git config --global socks.proxy "127.0.0.1:1080"
配置常用的命令别名
git config --global alias.st status
git config --global alias.br branch
git config --global alias.co checkout
git config --global alias.ci commit
如:
git config --global alias.st status
可以使用 git st
代替 git status
。
或者修改配置文件,Linux 下, Git 的配置文件储存在 ~/.gitconfig
。在 [alias]
部分添加快捷别名,如下:
[alias]
a = add
amend = commit --amend
c = commit
ca = commit --amend
ci = commit -a
co = checkout
d = diff
dc = diff --changed
ds = diff --staged
f = fetch
loll = log --graph --decorate --pretty=oneline --abbrev-commit
m = merge
one = log --pretty=oneline
outstanding = rebase -i @{u}
s = status
unpushed = log @{u}
wc = whatchanged
wip = rebase -i @{u}
zap = fetch -p
配置 git log 格式
原生的 git log
不太好用,一样可以配置:
git config --global alias.lg 'log --color --graph --pretty=format:"%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset" --abbrev-commit'
然后git lg
就成了下面的样子:
生成压缩包
git archive