Git
理论
区域组成
Git 本地有三个区域:
- 工作区(workspace):就是你在电脑里能看到的目录。
- 暂存区(staging area):一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
- 版本库/本地仓库(local repository):工作目录有一个隐藏目录
.git
,这个不算工作区,而是 Git 的版本库。
如果在加上远程的 git 仓库(remote repository),就可以分为四个工作区域。文件在这四个区域之间的转换关系如下:
- 当对工作目录被修改(或新增)的文件执行
git add
命令时,暂存区的目录树被更新,同时这些文件的内容被写入到对象库中的一个新的对象中,而该对象的 ID 被记录在暂存区的文件索引中。 - 当执行提交操作
git commit
时,暂存区的目录树写到版本库(对象库)中,当前分支会做相应的更新。 - 当执行
git reset HEAD
命令时,暂存区的目录树会被重写,被当前分支指向的目录树所替换,但是工作区不受影响。 - 当执行
git rm --cached <file>
命令时,会直接从暂存区删除文件,工作区则不做出改变。 - 当执行
git checkout .
或者git checkout -- <file>
命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区中的改动。 - 当执行
git checkout HEAD .
或者git checkout HEAD <file>
命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。
HEAD
HEAD 是 Git 中用来引用当前快照的指针,指向当前分支的最后一次提交。
切换分支的原理就是将 HEAD 指向某一个分支的最后一次提交。
HEAD 指的是 .git/HEAD
文件,它存储着当前分支的名字:
1 | ref: refs/heads/master |
由此,我们可以得知当前所处于 master 分支。如果我们继续往下走,打开 refs/heads/master
文件:
1 | 7e136f508b982790db5686482075c60ee3ee4fed |
这是 master 分支上最新提交的 commit id(版本号)。
我们在执行 git log
时会看到如下信息:
1 | commit c15da881cddfec59648795fa77e37f2d18e69fcb |
这里的 “c15da88” 或 “c15da881cddfec59648795fa77e37f2d18e69fcb” 就是每次 commit 的 commit id,同样也是 git 命令中确定 commit 版本号的依据,即 [HEAD]
的值。
创建仓库
初始化仓库
(1)使用当前目录作为 Git 仓库,我们只需使它初始化:
1 | git init |
该命令执行完后会在当前目录生成一个 .git 目录。
(2)使用指定目录作为 Git 仓库:
1 | git init xxx |
初始化后,会在 xxx 目录下会出现一个名为 .git 的目录,所有 Git 需要的数据和资源都存放在这个目录中。
克隆仓库
1 | git clone https://gitee.com/ilenceszd/practice.git |
执行该命令后,会在当前目录下创建一个名为 practice 的目录。
如果要自己定义要新建的项目目录名称,可以在上面的命令末尾指定新的名字:
1 | git clone https://gitee.com/ilenceszd/practice.git myRepo |
基本操作
(1)添加远程版本库
1 | git remote add [shortname] [url] |
shortname 是本地仓库为远程仓库起的别名,其指向远程仓库,如:
1 | git remote add origin https://gitee.com/ilenceszd/practice.git |
(2)添加文件到暂存区
1 | 添加目录下所有文件 |
(3)将暂存区内容添加到本地仓库中
1 | git commit -m [message] |
message 可以是一些备注信息。
如果想在修改文件后不执行 git add
命令,直接来提交,可以执行以下命令:
1 | git commit -am "xxx" |
(4)提交到远程仓库
1 | git push <远程主机名> <本地分支名>:<远程分支名> |
如果本地分支名与远程分支名相同,则可以省略冒号,如:
1 | git push origin master |
如果本地版本与远程版本有差异,但又要强制推送可以使用 –force 参数:
1 | git push --force origin master |
分支管理
查看分支
1 | git branch |
创建分支
1 | git branch [branchname] |
切换分支
1 | git checkout [branchname] |
当你切换分支的时候,Git 会用该分支最后提交(commit)的快照替换你的工作目录的内容。
创建新分支并立即切换到该分支下
1 | git checkout -b [branchname] |
删除分支
1 | git branch -d [branchname] |
合并分支
1 | git merge [branchname] |
将分支 branchname 合并到当前分支上。
查看提交历史
Git 提交历史一般常用两个命令:
git log
- 查看历史提交记录。git blame <file>
- 以列表形式查看指定文件的历史修改记录。
查看历史提交记录:
1 | git log |
查看历史记录的简洁的版本:
1 | git log --oneline |
查看历史中什么时候出现了分支、合并:
1 | git log --graph |
查看历史中 commit 是和哪一个分支或标签关联的:
1 | git log --decorate |
逆向显示所有日志:
1 | git log --reverse |
查找指定用户的提交日志:
1 | git log --author=Silence |
文件状态
- Untracked: 未跟踪。此文件在文件夹中,但没有加入 git 库,不参与版本控制,通过
git add
状态变为 Staged. - Unmodify: 文件已经入库,未修改。也就是版本库中的文件快照内容与文件夹中完全一致。这种类型的文件有两种去处,如果它被修改,而变为 Modified。如果使用
git rm
移出版本库,则变成 Untracked。 - Modified:文件已修改。仅仅是修改,并没有进行其他的操作,这个文件也有两个去处,通过
git add
可进入暂存 Staged 状态。使用git checkout
则丢弃修改过的内容返回到 Unmodify 状态。 - Staged: 暂存状态。执行
git commit
则将修改同步到库中,这时库中的文件和本地文件又变为一致,文件为 Unmodify 状态。执行git reset HEAD filename
取消暂存,文件状态为 Modified。
查看文件状态:
1 | git status [filename] |
标签
当我们的项目开发到一定的阶段,需要发布一个版本,这时,我们就可以对最后一次 commit 打一个标签。
当我们对某一次 commit 打上标签之后,我们后面继续开发,想找到该次 commit 时,通过查找该标签就很容易找到这次提交的版本。
如果我们没有打标签,就只能查找 commit 提交时的哈希值来返回到指定的位置了。
所以标签的作用,是方便我们查阅某次 commit 的,比如我们发布一个新的版本时。
可以说,标签就是某次 commit 的版本号的别名。
本地仓库的标签
创建标签
1 | git tag [tagName] |
如果我们忘了给某个提交打标签,又将它发布了,我们可以给它追加标签:
1 | git tag -a [tagName] [HEAD] |
查看标签
1 | 查看所有标签 |
删除标签
1 | git tag -d [tagName] |
远程仓库的标签
推送标签到远程仓库
我们在向远程仓库 push 时,不仅可以根据分支,也可以根据标签。
推送某个分支的时候,标签并不会被推送到远程仓库,所以我们必须显式的推送标签到远程仓库。
1 | 推送某个标签到远程仓库 |
删除远程仓库的标签
同样,在删除本地的标签后,要想删除远程仓库的标签,也必须使用的显式的命令。
1 | 删除远程仓库中的某个标签 |
拉取远程仓库的标签
1 | git pull [remote] [tagName] |
其他常用命令
查看本地 git 配置
1 | 查看全部 config |
操作远程仓库
1 | 显示所有远程仓库 |
git checkout
该指令主要用于切换分支,其原理为将 HEAD 指针更新为指向特定分支的最近的一次 commit。
那么,我们将 HEAD 指针指向历史 commit 呢?
1 | git checkout [HEAD] |
这会进入 HEAD 游离状态,在该状态下,可以进行任意提交,因为当你切换回分支时这些提交会被清除。
由于执行该操作后,工作区的文件会被更新,因此如果我们觉得该 commit 下的文件对我们有用,可以新建分支将其保留:
1 | git switch -c [new-branch-name] |
这也是该命令的主要应用场景。
此外,还可以实现用暂存区全部或指定的文件替换工作区的文件:
1 | git checkout . |
这个操作很危险,会清除工作区中未添加到暂存区中的改动。
git clone、git fetch 和 git pull
用法
1 | git clone [远程仓库 url] [本地目录名] |
区别
git clone
下载的是整个远程库,本地无需 git init
,且下载的 .git
文件夹里存放着与远程仓库一模一样的版本库记录。git fetch
下载的是远程库的一个分支。git pull
是 git fetch
和 git merge
的组合操作。
git reset
语法:
1 | git reset [--soft | --mixed | --hard] [HEAD] |
–mixed
该参数是默认参数,用于重置暂存区的文件与上一次的提交(commit)保持一致,工作区文件内容保持不变。
也就是说,它作用的周期为 git add
之后 git commit
之前,它会将此次向暂存区添加的文件清除。
–soft
该参数用于回退到某个版本,其作用周期为 git commit
之后 git push
之前。
比如我们在执行 git commit
之后,想要撤回此次提交,就可以使用该参数。那么在下一次提交并 git push
后,远程仓库的提交记录中没有上次的提交记录。
注意:回退版本之后的暂存区的文件依然与上一次提交(撤回的提交)保持一致。
–hard
该参数会撤销工作区中所有未提交的修改内容,将暂存区与工作区都回到上一次版本,并删除上一次版本之后的所有信息提交。
其作用周期为任何时候。
git revert
语法:
1 | git revert -n [HEAD] |
该命令用于“反做”某一个版本,以达到撤销该版本的修改的目的。
比如我们 commit 了三个版本(版本一、版本二、 版本三),现在想要撤销版本二,但又不想影响撤销版本三的提交,就可以用该命令来反做版本二,生成新的版本四,这个版本四里会保留版本三的东西,但撤销了版本二的东西。
反做之后需执行 git commit
提交。
git cherry-pick
该指令可以将一个分支上的修改更新得到其它分支上。
语法:
1 | git cherry-pick [HEAD] |
例如在分支 first 和 second 上都有文件 xxx.txt。
现在修改 first 分支上 xxx.txt 文件中某一位置的内容,提交后记录 commit id 为 a78252f。
然后切换分支到 second,执行
1 | git cherry-pick a78252f |
就可以将修改的那一部分内容同步过来。
它还支持一次转移多个提交:
1 | git cherry-pick [HEAD1] [HEAD2] [HEAD3] |
–edit
自定义提交信息。
–no-commit
只更新工作区和暂存区,不自动提交。
如果操作过程中发生代码冲突,cherry-pick 会停下来,让用户决定如何操作。
–continue
解决代码冲突后,要先将修改的文件添加到暂存区,然后执行:
1 | git cherry-pick --continue |
来让 cherry-pick 过程继续执行下去。
–abort
发生冲突后,放弃合并,使工作区回到操作前的样子。