概述
此篇博文意在让新手快速上手 Git,满足工作中的基本需求,而非梳理细节。后续会再开一个系列,来探讨 Git 细节问题。
一、Git 的安装
这部分网站上资料非常多,根据自己的系统版本查找资料并下载安装包安装即可。
二、初次使用 Git 之前的配置
1. 配置用户名和邮箱
git config --global user.name "user_name" git config --global user.email "email_address"
2. 查看配置
git config --list
三、理论基础
1. 为什么要掌握 Git 命令
可能有很多小伙伴习惯了 Windows 的操作模式,对命令行相对抵触。而且市面上确实有诸如 TortoiseGit、SourceTree 等 Git 图形化工 具,使用体验也还尚可。但正所谓“Git 的设计让使用者觉得自己比想象中的笨”,Git 拥有太多强大的功能,而图形化工具中只封 装了其中的一部分,还是命令行的功能全面。其次,很多老鸟回望来路,都认为 Git 的学习确实需要自底向上的过程。掌握了命令 行,使用图形化工具如探囊取物。反之则不知其所以然,倘若后面遇到问题,就要抓瞎咯。
2. 这么多差异版本,是如何保存的
- SVN 记录原理
SVN 记录的是每一次版本变动的内容。换句话说,SVN 只保存差异。
- Git 记录原理
Git 记录的则是将每个版本独立保存。譬如说 File1 有 5 个版本,那就有 5 个对应的拷贝。这种方式看似更浪费空间,但在分支管理上带来了很多便利。
3. 工作区、暂存区和 Git 仓库
- 工作区:就是我们存放代码的地方,看得见摸得着。
- 暂存区:实际上是一个文件,其中保存我们的改动。
- Git仓库:最终存放我们所有版本数据的位置。HEAD 指针指向的就是我们最新提交的版本。
4. Git 的工作流程
-
在工作目录中增删、修改文件;
-
将需要进行版本管理的文件放入暂存区;
-
将暂存区的文件提交到 Git 仓库。
5. Git 管理的文件状态
-
已修改(modified);
-
已暂存(staged);
-
已提交(committed)。
四、建仓、拉代码、添加到暂存区、提交
1. 建仓
- 建立全新项目(空白目录)
在新建的空目录中执行以下命令,即可建仓:
git init
建仓之后,可以看到在空白目录中新增了一个 .git 目录。这个目录就是用来跟踪版本迭代的。
- 已有项目代码建仓
进入项目代码根目录后,执行 git init 即可。
2. 从服务器拉代码
-
默认方式
git clone 仓库地址
-
拉取指定分支
git clone -b branch_name 仓库地址
我们可以通过增加 -b branch_name 选项来指定 clone 的分支
- 拉取所有分支
3. 将文件添加到暂存区
-
新建一个 Readme.txt,内含部分描述信息(这是在工作区发生的)。然后将其添加到暂存区:
git add Readme.txt
-
如果我们修改了很多文件,逐一 add 过于繁琐。此时可以通过如下命令,把工作区的修改一并加入暂存区:
git add -u
4. 将暂存区中的修改提交到 Git 仓库
-
输入以下命令,将修改提交到 Git 仓库:
git commit -m "log"
-
我们还可以将略过添加暂存区的步骤,直接将文件提交到 Git 仓库:
git commit -am "log"
五、查看工作状态和历史提交
1. 查看状态
我们可以使用以下命令来查看当前的状态:
git status
2. 更新暂存区状态
某个文件已经加入暂存区,但我们又在工作区对其进行了修改,此时可以再次执行 git add,更新暂存区中的状态。
3. 还原工作区修改
如果我们在工作区修改了某个文件,但这些修改不想要了,那么可以执行以下命令,来还原工作区中的文件:
git checkout -- file_name
这里要注意,如果暂存区中有此文件,则会用暂存区的版本来覆盖工作区。如果暂存区中没有此文件,则会以 Git 仓库中的版本来覆盖工作区中的版本。如果想还原成 Git 仓库的版本,则可以先用 :
git reset HEAD file_name
将其从暂存区中移出,然后再使用 :
git checkout -- file_name
从而使工作区中的文件还原为 Git 仓库中的状态。
4. 查看历史提交
我们可以使用 git log 来查看历史提交。commit ID 是根据 sha1 算出来的。为什么不像 SVN 那样,从 1 开始排序?因为 Git 是分布式的,这样会引发冲突。
5. git log 的常用选项
-
--decorate:显示每个commit的引用(如:分支、tag等);
-
--oneline:单行显示(简化版 log)
-
--graph:以图形化方式查看 commit
-
--all:查看所有分支的 commit
六、回到过去
1. reset 命令——用 Git 仓库中的版本覆盖暂存区
-
用 Git 仓库中 HEAD 所指版本覆盖暂存区(指定文件)
git reset HEAD file_name
-
用 Git 仓库中 HEAD 所指版本覆盖暂存区(所有文件)
git reset HEAD
如何使用 Git 仓库中的其他版本覆盖暂存区
首先,假设我们在工作区的所有修改都 add 并且 commit 了,可以用下图来表示当前状态:
接下来,使用 reset 命令来回滚快照:
git reset HEAD~
~ 表示HEAD所指版本的前一个版本,~~ 等以此类推。如果 ~ 太多不便表示,可以在 ~ 后加数字来指定
回滚之后的状态如下:
注意,reset 命令实际包含了两个动作:
- 移动 HEAD 的指向,可以将其指向之前的快照(HEAD 指针的位置发生了改变,所以看 log 时,看不到最后一次提交的信息。要用 git reflog 才能看到所有 log)。
- 用 HEAD 移动后指向的快照覆盖暂存区。
2. reset 命令的选项
-
--mixed
git reset --mixed HEAD~
--mixed 是默认选项,不写也会自动加上
移动 HEAD 的指向,可以将其指向之前的快照。并用 HEAD 移动后指向的快照覆盖暂存区。
-
--soft
git reset --soft HEAD~
使用 --soft 选项,只移动 HEAD 的指向,使其指向之前的快照,但并不覆盖暂存区中的内容。
这种用法通常用于撤销错误的 commit。
-
--hard
git reset --hard HEAD~6
移动 HEAD 的指向,使其指向之前的快照。并用 HEAD 移动后指向的快照来覆盖暂存区中的内容。此外,还会将暂存区中的文件还原到工作区(也是 HEAD 所指的 Git 仓库版本的状态)。
3. 使用commit ID 来执行 reset
git reset commit_ID
数 HEAD 毕竟麻烦,我们也可以使用commit ID 来指定 reset 到哪个版本(不用全部 ID,足够识别就行了)。
4. 回滚个别文件
git reset commit_ID file_name
回滚个别文件时,HEAD 指针就不移动了。
5. reset 还可以往前滚
git reset commit_ID
视情况使用 --hard
七、版本对比
1. 比较工作区和暂存区的差异
git diff
2. 比较两个历史commit
git diff commit_ID1 commit_ID2
3. 比较工作区和 Git 仓库中的commit
git diff commit_ID
commit ID 用 HEAD 这种方式也可以
4. 比较暂存区和 Git 仓库快照
git diff --cached/--staged [commit ID]
#如果不指定快照ID,则默认和仓库中最新commit比较
5. diff 功能概览图
八、常用的小技巧
1. 修改最后一次提交
-
情景一:代码已经 commit 到仓库,突然想起还有两个文件没有 add。
-
情景二:代码已经 commit 到仓库,突然想起 log 写得不全面。
执行带 --amend 选项的 commit 命令,Git 就会“更正”最近的一次提交。
git commit --amend 在接下来的界面中可修改 log
git commit --amend -m "new log" 直接提交新 log
2. 删除文件
git rm file_name
此命令删除的只是工作区和暂存区的文件,也就是取消跟踪,下次提交时,不纳入版本控制。已经提交到 Git 仓库的文件是不会删除的,需要移动 HEAD 指针来切掉。
如果误删除,可以使用以下命令来恢复:
git checkout -- file_name
- 工作区和暂存区有差异
如果某个文件在工作区和暂存区的内容不一致,git rm 执行时会报错。我们可以用:
git rm -f file_name
这会同时把工作区和暂存区的文件删除。
如果需要只删除暂存区的文件,保存工作区的文件,则可以执行 :
git rm --cached file_name
3. 重命名文件
git mv old_name new_name
九、分支与分支管
1. 什么是分支?为什么要有分支?
2. 创建分支
git branch branch_name
创建 branch_name 分支
git checkout -b branch_name
创建 branch_name 分支,并切换到此分支
3. 切换分支
git checkout branch_name
4. 查看分支
-
查看本地分支
git branch -v
-
查看远程分支
git branch -r
-
查看本地分支和远程分支
git branch -a
5. 合并分支
git merge branch_name
将 branch_name 分支合并到当前分支。
如果 merge 时提示 conflict,则需要手动解决冲突。Git 会在冲突的文件中加入一些提示。
6. 删除分支
git branch -d branch_name
删除分支后再查看 log,会发现虽然这些分支没有了,但在这些分支上提交的Commit ID依然存在。
7. 匿名分支
git checkout commit_ID
此时是分离头指针状态。由于我们使用了 checkout 命令,但并未创建新的分支,所以 Git 创建了一个匿名分支。既然是匿名分支,如果我们切换到其他分支,匿名分支中的所有操作都会被丢弃。所以我们可以用匿名分支来做些实验,反正没有什么影响。如果想保留匿名分支的操作,可以根据 Git 提示来操作,为其新建一个正式的分支。
8.本地分支与远程分支关联
git branch --set-upstream-to=origin/<remote_branch> local_branch
使用git在本地新建一个分支后,需要做远程分支关联。如果没有关联,git会在下面的操作中提示你显示的添加关联。
关联目的是在执行git pull, git push操作时就不需要指定对应的远程分支,你只要没有显示指定,git pull的时候,就会提示你。
十、将本地修改推到服务器端
前面说过,Git 是分布式的 VCS 工具。我们本地有自己的仓库,服务器端有服务器端的仓库。为了确保其他人 clone 的代码中能包含我们的修改,就要把我们本地的修改推到服务器端。
我们通过 git push 命令,将本地修改推送到服务器端:
git push <远程主机名> <本地分支名>:<远程分支名>
通常用 origin 来表示远程主机名。
1. 省略远程分支名
表示将本地分支推送到与之存在追踪关系的远程分支(通常同名)。如果该远程分支不存在,则会新建。
-
origin: 远程主机名
-
master: 本地分支名
git push origin master
2. 省略本地分支名
表示删除远程分支。这一操作等同于推送一个空的本地分支到远程分支。
-
origin : 远程主机名
-
refs/for/master: 远程分支名
git push origin :dev
3. 同时省略本地分支名和远程分支名
如果当前分支与远程分支存在追踪关系,则本地分支和远程分支都可以省略,将当前分支推送到origin主机的对应分支。
git push origin
4. 同时省略远程主机名、本地分支名和远程分支名
如果当前分支只有一个远程分支,那么主机名都可以省略:
git push
5. push提示reject的处理方式
如果我们本地的代码不是服务器上最新版本,push 代码时会被拒绝。此时我们要先更新本地代码,再push到服务器。
git reset --hard
将本地代码恢复到最新commit状态
git pull --rebase
将服务器更新合并到本地
十一、更新本地代码
前文说到,Git 是分布式的 VCS,很多人都会向服务器端 push 修改。接下来看看,我们如何将服务器上的更新同步到本地。
1. 本地只有 master 分支,且未做修改
此时的本地代码处于 clean 状态,所以可以直接使用 git pull 来更新。
2. 本地只有 master 分支,且有修改
- 修改需要保留
此时要先将修改 push 到服务器端,然后再使用 git pull 来更新。
- 修改无需保留
由于修改无需保留,我们可以使用 git reset --hard 将代码恢复到 clean 状态,然后再使用
git pull 来更新。
3. 本地在自有分支上修改
由于我们在自己的分支(假定分支名为 mybranch)上修改,master 处于 clean 状态。此时要分四步处理:
-
切换到 master 分支:
git checkout master
-
更新 master 分支:
git pull
-
切换到 mybranch 分支:
git checkout mybranch
-
把 master 分支合并到 mybranch 分支:
git merge master