Git 笔记系列(二)—— Git工作流程
时间 | 更新备注 |
---|---|
2018-02-28 | 新建文章 |
2018-06-07 | 添加白话Git内容 |
目录
- Git 笔记系列(一)—— Git简介
- Git 笔记系列(二)—— Git工作流程
- Git 笔记系列(三)—— Git常用命令-一览
- Git 笔记系列(四)—— Git常用命令-Checkout
- Git 笔记系列(五)—— Git常用命令-Branch
- Git 笔记系列(六)—— Git常用命令-Reset
- Git 笔记系列(七)—— Git常用命令-Rebase
- Git 笔记系列(八)—— Git常用命令-Stash等
- Git 笔记系列(九)—— Git常用命令-Fork
- Git 笔记系列(十)—— Git进阶
- Git 笔记系列(十一)—— Git常见问题
- Git 笔记系列(十二)—— Git进阶, 巧用Git工具
- Git 笔记系列(十三)—— Git进阶, Git的奇技赢巧
引言
上一篇简单介绍了
Git
后,这篇来看看使用Git
的工作流程吧。
① 创建版本库
第一步,通过git init
命令把这个目录变成Git
可以管理的仓库。
第二步,用命令git add
告诉Git
,把文件添加到仓库,进行变化跟踪:
$ git add readme.txt
第三步,把文件提交到仓库,-m
后面输入的是本次提交的说明,能从历史记录里方便地找到改动记录。
git commit -m "xxx"
② 添加远程库
$ git remote add origin https://github.com/xx/test.git
添加后,远程库的名字就是origin
,这是Git
默认远程库。
下一步,就可以把本地库的所有内容推送到远程库上:
$ git push -u origin master
把本地库的内容推送到远程,用git push
命令,实际上是把当前分支master
推送到远程。
由于远程库是空的,我们第一次推送master
分支时,加上了-u
参数,Git
不但会把本地的master
分支内容推送的远程新的master
分支,还会把本地的master
分支和远程的master
分支关联起来,在以后的推送或者拉取时就可以简化命令。
总结:从现在起,只要本地作了提交,就可以通过命令:git push origin master
把本地master
分支的最新修改推送至GitHub
,现在,你就拥有了真正的分布式版本库!
要关联一个远程库,使用命令git remote add origin git@server-name:path/repo-name.git
;
关联后,使用命令git push -u origin master
第一次推送master
分支的所有内容;
此后,每次本地提交后,只要有必要,就可以使用命令git push origin master
推送最新修改;
分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN
在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步,真是太方便了!
③ 从远程库克隆
上次我们讲了先有本地库,后有远程库的时候,如何关联远程库。
现在,假设我们从零开发,那么最好的方式是先创建远程库,然后,从远程库克隆。
要克隆一个仓库,首先必须知道仓库的地址,然后使用git clone
命令克隆。
Git
支持多种协议,包括https
,但通过ssh
支持的原生Git
协议速度最快。
④ 场景操作
在下图中,可以看到部分Git
命令是如何影响工作区和暂存区(stage
,亦称index
)的。
图中左侧为工作区,右侧为版本库。在版本库中标记为index
的区域是暂存区(stage
,亦称index
),标记为master
的是master
分支所代表的目录树。
-
HEAD
实际是指向指向当前所在的本地分支的一个“游标”。告诉Git
当前的工作区在哪一个分支上。 -
head
(小写)是commit
对象的引用,每个head
都有一个名字(分支名字或者标签名字等等),但是默认情况下,每个叫master
的repository
都会有一个head
, 一个repository
可以包含任意数量的head
。在任何时候,只要这个head
被选择成为current head
,那么这个head
就成了HEAD
,总是大写
图中的objects
标识的区域为Git
的对象库,实际位于.git/objects
目录下。
文件变动更改
在Git
中,我们用一个提交来保存更改,当对工作区修改(或新增)的文件执行git add
命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。
git add 跟踪文件变化
- git add -A 提交所有变化
- git add -u 提交被修改(modified)和被删除(deleted)文件,不包括新文件(new)
- git add . 提交新文件(new)和被修改(modified)文件,不包括被删除(deleted)文件
Git Flow
当你第一次checkout一个分支,HEAD就指向当前分支的最近一个commit。在HEAD中的文件集(实际上他们从技术上不是文件,他们是blobs(一团),但是为了讨论的方便我们就简化认为他们就是一些文件)和在index中的文件集是相同的,在working copy的文件集和HEAD,Index中的文件集是完全相同的。所有三者(HEAD,Index(Staging),Working Copy)都是相同的状态,Git很happy。
当你对一个文件执行一次修改,Git感知到了这个修改,并且说:“嘿,文件已经变更了!你的Working Copy不再和index,head相同!”,随后Git标记这个文件是修改过的。
然后,当你执行一个git add,它就stages the file in the index,并且GIT说:“嘿,OK,现在你的working copy和index区是相同的,但是他们和HEAD区是不同的!”
当你执行一个git commit, Git就创建一个新的commit,随后HEAD就指向这个新的commit,而index, working copy的状态和HEAD就又完全匹配相同了,Git又一次Happy了。
文件提交
当执行提交操作(git commit
)时,暂存区的目录树写到版本库(对象库)中,被提交的分支,比如master
分支,会做相应的更新。即master
最新指向的目录树就是提交时原暂存区的目录树。
当执行git reset HEAD
命令时,暂存区的目录树会被重写,被master
分支指向的目录树所替换,但是工作区不受影响。
1.要随时掌握工作区的状态,使用git status
命令。
2.如果git status
告诉你有文件被修改过,用git diff
可以查看修改内容。
删除文件
1.命令git rm
用于删除一个文件。
2.确实要从版本库中删除该文件,那就用命令git rm
删掉,并且git commit
:
$ git rm test.txt
rm 'test.txt'
$ git commit -m "remove test.txt"
[master d17efd8] remove test.txt
1 file changed, 1 deletion(-)
delete mode 100644 test.txt
撤销修改
场景1
当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令
$ git checkout -- file。
场景2
当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步:
-
用命令
git reset HEAD file
,取消暂存,就回到了场景1; -
按场景1操作:
$ git checkout -- file。
(git reset
命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD
时,表示最新的版本。)
场景3
已经提交了不合适的修改到版本库时,没有推送到远程库时,想要撤销本次提交,git reset HEAD^
git删除文件情况 | 恢复命令 |
---|---|
删除本地文件,但是未添加到暂存区; | git checkout --deletedFileName |
删除本地文件,并且把删除操作添加到了暂存区; | git reset HEAD deletedFileName, git co deletedFileName |
把暂存区的操作提交到了本地git库; |
git reset --hard ORIG_HEAD 强制回滚到未删除版本 |
把本地git库的删除记录推送到了远程服务器github。 | 强制回滚到未删除版本,然后强制推送git push -f
|
文件操作图示
image版本回退
-
HEAD
指向的版本就是当前版本,因此,Git
允许我们在版本的历史之间穿梭,使用命令
$ git reset --hard commit_id
- 穿梭前,用
git log
可以查看提交历史,以便确定要回退到哪个版本。 - 要重返未来,用
git reflog
查看命令历史,以便确定要回到未来的哪个版本。 - 使用
git diff
命令可以查看工作区和版本库里面最新版本的区别
$ git diff HEAD -- readme.txt
⑤白话Git
能不能讲人话?
好的,举个栗子,一次产品发布后,上了不少新功能。你和同事激动的讨论着。
隔壁产品小妹十分好奇,问你:『你们都在说的变基、Merge 都是做什么的?』你想了想,这么解释给她听:Merge 合并与冲突『你和你的男朋友生活非常幸福』你说:『两个人一起为共同生活而努力,我们来假设家庭生活是你们两个人的主线,每天工作、休息、吃爱吃的食物,做爱做的事情,像这样』
image白话分支
『而分支就是偶尔他和朋友看个球,你和闺蜜逛个街。主线之外,你们还有各自的支线任务。但是完成支线任务之后,你们两个还是可以去一起看个电影啊,像这样支线任务完成回到了主线任务,并且可能你去逛了街穿了美美的衣服,有了一次很棒的约会,支线任务也是为主线添砖加瓦的。』
image白话冲突
『那你们平常说的冲突呢?』『冲突是这样的。比如今天他下班去踢球,说好今晚约会,结果他一身臭汗的回来。你会不会不开心?』『生气啊,他都记不得今晚有约会。』『恩,这个时候冲突就产生了,冲突产生的时候,他的支线任务就很难合并到主线任务中。在我们使用 WebIDE 的时候,合并的时候会提示你「发现冲突」,并且弹出冲突列表,再进行逐行处理,协调主线任务和支线任务。比如他早点回来洗澡,你花点时间补妆。』『啊啊啊,这挺好,省的男朋友老问我为什么生气。笨死了。』『然后我们再来说说储藏。』Stash 储藏『储藏的话,是不是说工作做到一半,要存着接着做?』『某种程度上说是的,你做到一半要有其他事情的时候,就需要把这件事情存起来。比如男朋友要送你一个手工的礼物,像这样可爱的龙猫』
image白话stash
『哇,好萌。』『对的,但是又有了别的工作,可是他做到一半的时候可能是这样的,所以不想让你看到,不能放家里只能放在办公室。就把现在的工作存储下来。并不提交到主线任务。而且可以顺利恢复上次的进度。』
image白话rebase
『这么说就很好理解了,那你快告诉我变基是什么啊。是不是男朋友跟别的男孩子跑了。』『额,怎么会,你男朋友又不是程序员……』Rebase 变基『变基的基其实是基础的意思,简单来说就是把支线任务变成主线任务。』『哎?听起来和 Merge 有点像啊。』『是的,目的上都是把分支任务整合到主线任务上,但是还是有一些区别的,画个图你就了解了,第一张图是 Merge。』
image『第二张图是 Rebase,他会对比主线「工作」之后两个支线分支 A「追番」和 B「烧饭」的相似与不同,将不同之处「炉子上要炖汤」提取出来作为 B1,然后把「A+B1」即「追番(一起)」和「炖汤 ing」放在主线任务里。』
image白话reset
『哦?看起来变基是个好功能呢。』『是的, Merge 的线路更加流畅整洁,特别是支线任务复杂的时候。』Reset 重置『重置就是你和男朋友吵架了,他特别想用的功能……』『就是返回到不生气的状态是吗?』『是的是的。』
image白话Tag
Tag 标签『这就是给任务改名字吗?』『是的,常常会把任务改成更有意义的名字,比如这样。』
imageGit文件4种状态
image-
Untracked: 未跟踪, 此文件在文件夹中, 但并没有加入到git库, 不参与版本控制. 通过
git add
状态变为Staged
. -
Unmodify: 文件已经入库, 未修改, 即版本库中的文件快照内容与文件夹中完全一致. 这种类型的文件有两种去处, 如果它被修改, 而变为
Modified
. 如果使用git rm
移出版本库, 则成为Untracked
文件 -
Modified: 文件已修改, 仅仅是修改, 并没有进行其他的操作. 这个文件也有两个去处, 通过
git add
可进入暂存staged
状态, 使用git checkout
则丢弃修改过, 返回到unmodify
状态, 这个git checkout
即从库中取出文件, 覆盖当前修改 -
Staged: 暂存状态. 执行
git commit
则将修改同步到库中, 这时库中的文件和本地文件又变为一致, 文件为Unmodify
状态. 执行git reset HEAD filename
取消暂存, 文件状态为Modified
总结
-
git add files
把把文件修改添加到暂存区。 -
git commit
给暂存区域生成快照并提交。 -
git reset -- files
用来撤销最后一次git add files
,你也可以用git reset
撤销所有暂存区域文件。 -
git checkout -- files
把文件从暂存区域复制到工作目录,用来丢弃本地修改。暂存区的所有内容提交到当前分支 - Git是如何跟踪修改的:每次修改,如果不add到暂存区,那就不会加入到commit中。