Basics
总体流程图
git clone
该命令很简单不详细说,这里主要说一下如何重命名仓库,对当前文件夹已经存在同名仓库的时候会很有用:
git clone git@github.com:muwenzi/repo.git new-repo-name
git fetch
用于远端仓库和本地仓库的同步,并不会进行本地仓库和工作区的同步(即合并 merge)。默认情况下,fetch 会更新本地仓库中所有 origin/分支(包括远端新的分支和现有分支的新 commit)。
git fetch
如果只想取回特定分支的更新,可以指定分支名。
git fetch <远程主机名> <分支名>
比如,取回 origin
主机的 master
分支。
git fetch origin master
方便的话可以直接
git fetch master
⚠️ fetch 的操作只会让本地仓库分支(默认 origin/开头的这些分支)与远端仓库保持同步,但并不会更新工作区的分支代码。
如果还需要让本地仓库和工作区保持同步,fetch 完之后还需要执行一下操作:
git merge origin/<分支名>
git branch
git fetch 所取回的更新,在本地主机上要用 远程主机名/分支名
的形式读取。比如 origin 主机的 master,就要用 origin/master
读取。
查看分支
git branch 命令的-r 选项,可以用来查看远程分支,-a 选项查看所有分支,无选项则查看本地分支。
git branch -r
origin/master
git branch -a
* master
remotes/origin/master
删除分支
方式一:安全删除,Git 会阻止你删除包含 未合并更改 的分支。
git branch -d <分支名>
方式二:强制删除,即使包含 未合并更改,如果你对那条分支看都不想看一眼立马删除的话。
git branch -D <分支名>
删除远程分支:
git push origin :<分支名>
新建分支
git branch <新分支名> // 基于当前所在分支新建分支
⚠️ 新建完后 不会 自动切换到那个分支去,其实就是新建了一个
指针
而已。推荐使用git checkout -b <新分支名>
的方式去建立新分支,会自动切换。
修改当前分支名
git branch -m <新分支名>
手动建立远程分支与本地分支的追踪关系
git clone 的时候 Git 会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。所有本地分支默认与远程主机的同名分支,建立追踪关系,也就是说,本地的 master 分支自动"追踪"origin/master 分支。 Git 也允许手动建立追踪关系
git branch --set-upstream master origin/next
上面命令指定 master 分支追踪 origin/next 分支。
git checkout
该 命令可以用于三种场景:
- 切换分支
- 创建分支
- 撤销修改
创建分支
取回远程主机的更新以后,可以在它的基础上,使用 git checkout 命令创建一个新的分支。
git fetch # 本地仓库和远端仓库先进行同步
git checkout <新分支名> origin/master # gcb <新分支名> origin/master
上面命令表示,在 origin/master
的基础上,创建一个新的 本地 分支(没有 origin/开头的分支)。当切换到远程某个分支,本地没有这个分支的时候就需要这样做。
⚠️ 如果没有写
origin/master
那么将会基于当前所在的分支进行创建。
如果要将新分支推送到远端,还需要执行以下命令:
# 推送并建立追踪当前分支至远端
git push --set-upstream origin <分支名> # gpsup
这时会在远端创建一个与本地同名的 <分支名>
,加-u
会自动追踪该分支,不然还要手动 --set-upstream
,其实这条命令就是去修改.git
文件夹下config
的配置。
如果远端有分支 <分支名>
通过一下操作可以将该分支拉取到本地,并与远端自动建立关联:
git fetch # gf
git checkout <分支名> # gco <分支名>
example: 基于当前本地分支创建一个新分支并推送到远端
撤销修改
修改文件后,使用 status 命令查看一下文件状态:
git status
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: /there/is/a/modified/file
对于未 add 进暂存区的文件,此时还在工作区,可以使用 git checkout -- <file>
快速撤销本地修改。
git checkout [some_dir|file.txt]
恢复所有本地未提交的更改(应在项目根目录中执行):
git checkout . // git checkout -- .
⚠️ "--" 可加可不加
那么,对于已 add 进暂存区的文件,如何撤销本地修改?还是先使用 status 命令查看一下文件状态:
git status
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: /there/is/a/modified/file
先取消暂存修改 Git 提示我们,可以使用 reset 命令取消暂存:
git reset /there/is/a/modified/file
取消暂存后,文件状态就回到了跟之前一样了:
git status
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: /there/is/a/modified/file
再撤销本地修改这时按提示使用 checkout
即可:
git checkout -- /there/is/a/modified/file
这时工作目录就干净了:
git status
nothing to commit, working directory clean
一步到位
那么有更便捷的、一步到位的办法吗?有,指定提交即可:
git status
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: /there/is/a/modified/file
git checkout HEAD -- /there/is/a/modified/file
git status
nothing to commit, working directory clean
checkout 与 reset 区别
命令 | 操作目标 | 描述 |
---|---|---|
checkout | 工作目录(working tree) | 用于撤销本地修改 |
reset | 暂存区(index) | 只用于覆盖暂存区 |
因此 git reset <paths>
等于 git add <paths>
的逆向操作。如果企图用 reset
命令覆盖工作目录,是会报错的。
git merge
使用 git merge 命令或者 git rebase 命令,在本地分支上合并远程分支。
git merge origin/master
# 或者
git rebase origin/master
上面命令表示在当前分支上,合并 origin/master。与本地的其他分支合并则不用加origin/
git pull
git pull 命令的作用是,取回远程主机某个分支的更新,再与本地的指定分支合并。它的完整格式稍稍有点复杂。如果当前分支与远程分支存在追踪关系,git pull 就可以省略远程分支名
git pull origin
上面命令表示,本地的当前分支自动与对应的 origin 主机"追踪分支"进行合并。如果当前分支只有一个追踪分支,连远程主机名都可以省略。
git pull
上面命令表示,当前分支自动与唯一一个追踪分支进行合并。这条命令也就等于
git fetch
git merge 当前分支
对于稍复杂的一点情况,比如远程有 master, dev, kimi 三个分支,本地的分支对应是 kimi。如果想将远程的 dev 分支最新的提交合并到本地的 kimi 分支中(在 kimi 分支发起 pull request 请求合并到 dev 分支时候会用到),需要执行以下命令:
git fetch
git merge origin/dev
等价于
git pull origin dev:kimi
因为是取回远程的 dev 分支再与当前本地所在的 kimi 分支进行合并可以省略:后面的命令
git pull origin dev
如果是取回远程的 kimi 分支再与当前本地所在的 kimi 分支进行合并,那就是最常用的场景了
git pull
PS: 如果远程主机删除了某个分支,默认情况下,git pull 不会在拉取远程分支的时候,删除对应的本地分支。这是为了防止,由于其他人操作了远程主机,导致 git pull 不知不觉删除了本地分支。但是,你可以改变这个行为,加上参数 -p 就会在本地删除远程已经删除的分支。
git pull -p
Git force pull to overwrite local files
git fetch --all
git reset --hard origin/<<分支名>>
git revert
只是删除某一个提交,后面的提交不影响
git revert <commit id>
# 撤销刚刚的提交
git revert HEAD
git reset
reset
是将指定的 commit id
之前的提交从本地仓库移除。假设 git reset
之前,工作区和暂存区都有内容:
模式 | 本地仓库 | 暂存区 | 工作区 | zsh git |
---|---|---|---|---|
soft | 移出指定 id 之前代码 | 1. 本地仓库移出代码 2. 当前暂存区代码 | 当前工作区代码 | |
mixed | 移出指定 id 之前代码 | 清空 | 1. 本地仓库移出代码 2. 之前暂存区代码 3. 当前工作区代码 | |
hard | 移出指定 id 之前代码 | 清空 | 清空 |
上一个区域代码进入下一个区域,代码有冲突的地方会以下一个区域为准。
- 如果
本地仓库移出代码
进入暂存区/工作区
,有冲突会以暂存区/工作区
为准。 - 如果
之前暂存区代码
进入工作区
,有冲突会以工作区
为准。
默认是
--mixed
模式。
这也解释了,为什么 git reset HEAD <file>
能将暂存区的代码回退到工作区。
HEAD
可以理解为一个 游标,一直指向当前我们所在版本库的地址,就是我们当前所在版本库的 头指针。- 我们也可以不使用
HEAD
,可以直接使用版本库的 hash 值。 HEAD
也可以省略,直接写成git reset <file>
。git reset HEAD <file>
,zsh-git 缩写grh
git reset --hard
的丢失的代码如何找回?
从代码库移除的代码
$ git reflog
b7057a9 HEAD@{0}: reset: moving to b7057a9
98abc5a HEAD@{1}: commit: more stuff added to foo
b7057a9 HEAD@{2}: commit (initial): initial commit
reflog
会记录所有 HEAD
的历史,也就是说当你做 reset
,checkout
等操作的时候,这些操作会被记录在reflog
中。
所以,我们要找回我们第二个 commit,只需要做如下操作:
git reset --hard 98abc5a
暂存区丢失的代码
据说 git fsck --lost-found
可以
git fsck --lost-found
用 git show
看一下回来的内容对不对
git show ce1b401ce1a3138e66603fa0b751c2eff974cc78
也可以到 .git/lost-found
目录下找找看有没有你丢失的文件。
详情请参考:《Git Community Book 中文版:找回丢失的对象》
工作区丢失的代码
cmd + z 或者到 IDEA 的 Local History 里找找看,也许还能恢复。git cherry-pick
合并某个分支上的单个 commit
git cherry-pick 可以选择某一个分支中的一个或几个 commit(s)来进行操作。例如,假设我们有个稳定版本的分支,叫 v2.0,另外还有个开发版本的分支 v3.0,我们不能直接把两个分支合并,这样会导致稳定版本混乱,但是又想增加一个 v3.0 中的功能到 v2.0 中,这里就可以使用 cherry-pick 了。
git fetch # gf cherry-pick之前要先进行fetch,拉取到最新的commit
git cherry-pick <commit id>
就是对已经存在的 commit 进行再次提交,然后合并到目前所在的分支。 注意:当执行完 cherry-pick 以后,将会 生成一个新的提交。这个新的提交的哈希值和原来的不同,但标识名一样。
合并某个分支上的一系列 commits
在一些特性情况下,合并单个 commit 并不够,你需要合并一系列相连的 commits。这种情况下就不要选择 cherry-pick 了,rebase 更适合。还以上例为例,假设你需要合并 feature 分支的 commit76cada
~62ecb3
到 master 分支。首先需要基于 feature 创建一个新的分支,并指明新分支的最后一个 commit:
git checkout -bnewbranch 62ecb3
然后,rebase 这个新分支的 commit 到 master(--ontomaster)。76cada^
指明你想从哪个特定的 commit 开始。
git rebase --ontomaster 76cada^
得到的结果就是 feature 分支的 commit 76cada
~62ecb3
都被合并到了 master 分支。
git rebase
git stash
用于保存和恢复工作进度
git stash
保存当前的工作进度。会分别对暂存区和工作区的状态进行保存
git stash list
显示进度列表。此命令显然暗示了 git stash 可以多次保存工作进度,并用在恢复时候进行选择
git stash pop stash@{1}
如果不使用任何参数,会恢复最新保存的工作进度,并将恢复的工作进度从存储的工作进度列表中清除。
如果提供<stash>
参数(来自 git stash list
显示的列表),则从该 <stash>
中恢复。恢复完毕也将从进度列表中删除 <stash>
。
选项--index 除了恢复工作区的文件外,还尝试恢复暂存区。
git stash apply stash@{1}
除了不删除恢复的进度之外,其余和 git stash pop
命令一样
git stash clear
删除所有存储的进度
git clean
git clean
命令用来从你的工作区中删除所有没有 tracked过的文件. git clean
经常和git reset --hard
一起结合使用. 记住 reset 只影响被 track 过的文件, 所以需要 clean 来删除没有 track 过的文件. 结合使用这两个命令能让你的工作区完全回到一个指定的<commit>
的状态.
QA
- 什么叫没有 tracked?
不考虑.gitignore
的话:你创建了些文件 或者 IDE 自动创建了些文件,但是你从来没有 add 它们,也没有 commit 它们,它们就是没有 untracked。
- 与
git checkout .
的区别?
二者都是针对在工作区没有git add
的情况,但git checkout .
是对修改文件而言,git clean
是对新文件而言。
git clean 参数
-n 显示 将要 删除的 文件 和 目录(只是一个演习和提醒)
-f 删除 文件
-df 删除 文件 和 目录 zsh缩写:gclean
git clean -n
是一次 clean 的演习, 告诉你哪些文件会被删除. 记住他不会真正的删除文件, 只是一个提醒.
git clean -f
删除当前目录下所有没有 track 过的文件. 他不会删除 .gitignore
文件里面指定的文件夹和文件, 不管这些文件有没有被 track 过.
git clean -f <path>
删除指定路径下的没有被 track 过的文件.
git clean -df # gclean
删除当前目录下没有被 track 过的文件和文件夹.
git clean -xf
删除当前目录下所有没有 track 过的文件. 不管他是否是.gitignore 文件里面指定的文件夹和文件
git clean -fdx
这将删除所有本地未跟踪的文件/夹,所以只有 git 跟踪的文件保留:警告: -x 也将删除所有被忽略的文件!
小结: git reset --hard
和 git clean -f
是一对好基友. 结合使用他们能让你的工作目录完全回退到最近一次 commit 的时候
git remote
查看远端仓库的地址
git remote -v # grv
修改远端仓库地址
# grset
git remote set-url 远端名称 git@github.com:muwenzi/Program-Blog.git
添加远端仓库
常用的就是 fork 仓库,用来区分 pull/push
的究竟是哪一个远端:
git remote add 远端名称 git@github.com:muwenzi/Program-Blog.git # gra
删除远端仓库
git remote remove 远端名称 git@github.com:muwenzi/Program-Blog.git
拉取上游仓库的代码
git pull 上游仓库名 分支名
清除远端冗余分支
git remote show origin
可以查看本地与远端的分支是否还关联或者已经过时(远端已经不存在了)
然后就会提示另一条命令
git remote prune origin
这是用来清除 本地仓库对应的远端 已经失效的。
如果要删除本地分支,可以进行过滤再批量删除:
git branch -D `git branch --list 'fix-'`
删除除 master 以外所有本地分支,执行前需要切换到 master 分支执行:
git branch | grep -v "master" | xargs git branch -D
git tag
打 tag
git tag -a v1.1 -m "注释"
推送 tag
git push 不会推送标签(tag),除非使用--tags 选项。
git push origin --tags
# or
git tag -l # 查看所有tag
git push origin v1.1
删除本地 tag
git tag -d v1.1.1
删除远程 tag
git push origin :v1.1.1
# 也可以这样
git push origin --delete tag v1.1.1
HEAD
HEAD~
是HEAD~1
的缩写,表示当前 commit 的第一个父 commit。HEAD~2
表示当前 commit 的第一个父 commit 的第一个父 commit。HEAD~3
表示当前 commit 的第一个父 commit 的第一个父 commit 的第一个父 commit,以此类推。
HEAD^
是HEAD^1
的缩写,也是表示当前 commit 的第一个父 commit。但HEAD^2
表示当前 commit 的 第二个 父 commit (当有分支 merge 操作的时候就会有两个父 commit)。
~1
和 ~2
类似树中的父子节点,^1
和 ^2
类似树中的兄弟节点,如下图所示:
理解记忆:
~1
: 第一个儿子~2
: 第一个儿子的第一个儿子,即“嫡长孙”^1
: 第一个儿子^2
: 第二个儿子
如下图所示:
Others:
^
and~
可以组合去写。HEAD
也可以换成任意一个commit id
。
git config
config 配置有:
- system
- global
- local(当前仓库)
三个 设置先从system->global->local
底层配置会覆盖顶层配置 分别使用 --system/global/local
可以定位到配置文件
查看系统 config
git config --system --list
查看当前用户(global)配置
git config --global --list
查看当前仓库配置信息
git config --local --list
--global
中存储了提交用户的 email 和用户名 如果需要手动设置则可以使用如下指令:
git config --global user.name "myname"
git config --global user.email "test@gmail.com"
git rm
有时候在项目开发过程中,把某些目录或文件加入.gitignore
后发现并未生效,原因是.gitignore
只能忽略那些原来没有被 track 的文件,如果某些文件已经被纳入了版本管理中,则修改.gitignore
是无效的。那么解决方法就是先把本地缓存删除(改变成未 track 状态),然后再提交:
git rm -r --cached .
git add .
git commit -m 'update .gitignore
.gitignore
的匹配规则:
.a # 忽略所有 .a 结尾的文件
!lib.a # 但 lib.a 除外
/TODO # 仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODO
build/ # 忽略 build/ 目录下的所有文件
doc/.txt # 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt
参考资料
- Git 远程操作详解(阮一峰)
- git-recipes
- 回滚错误的修改
- Git 合并特定 commits 到另一个分支
- git checkout 命令撤销修改
- 自定义 Git - 配置 Git
- git stash 用于保存和恢复工作进度
- git clean 小结
- How do I force “git pull” to overwrite local files?
- [译]git clean
- git-remote - Manage set of tracked repositories
- git 创建、删除分支和 tag
- Can you delete multiple branches in one command with Git?
- GIT 本地删除除 master 以外所有分支,作者:风匀坊
- Git docs: git-remote - Manage set of tracked repositories
- Git 初接触 (三) Git 的撤销操作 git reset HEAD --
<file>
,作者:上官二狗 - 找回 Git 中丢失的 Commit,作者:drybeans
- git reset 后代码丢失 代码未 commit
- 在 git 中找回丢失的 commit,作者:alsotang
- What's the difference between HEAD^ and HEAD~ in Git? 作者:dr01
- What's the difference between HEAD^ and HEAD~ in Git? 作者:Alex Janzik
- GIT-查看 config 配置信息 作者:Merray
- Git 忽略规则及.gitignore 规则不生效的解决办法 作者:Android_大船
- Git 中.gitignore 使用和.gitignore 无效的解决方法 作者:JohnnyB0Y