git是一个很好理解的工具,托管代码,多人协作,版本控制
但是里面的设计不得不让人惊叹
目录
git为什么这么吊炸天的?因为Git跟踪并管理的是修改,而非文件。
版本管理以及git工作方式
版本简介
版本,我们知道每次commit都会有一种类似于快照的机制,一旦你把文件改乱了,或者误删了文件,还可以从最近的一个commit
恢复,然后继续工作,而不是把几个月的工作成果全部丢失。(git log [--pretty=oneline]查看commit历史)
不同于SVN,Git使用SHA1计算出来的一个非常大十六进制的数字表示版本号,这是Git是分布式的版本控制系统(SVN是集中式),如果像svn都使用1,2,3...,肯定会产生冲突
版本回退方法:
首先,Git必须知道当前版本是哪个版本,在Git中,用HEAD表示当前版本,也就是最新的提交1094adb...(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。
$ git reset --hard HEAD^
HEAD is now at e475afc 提交描述
(不同于svn,git版本回退非常快,它使用指针指向当前版本,reset只是将指针移动到对应位置)
改为指向add distributed
:
如果后悔版本后退并且已经关掉窗口,可以使用git reflog查看命令历史,找到版本号,git reset --hart 版本号(版本号不用全部输入)
git防止各种智障行为
然后解释Git工作方式
工作区(Working Directory)
就是我们从服务器拉下来的项目根目录
版本库(Repository)
工作区有一个.git目录(默认是隐藏的),里面最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master
,以及指向master
的一个指针叫HEAD
。(之前一直理解错了master)
因为我们拉取项目创建Git版本库时,Git自动为我们创建了唯一一个master
分支,所以,现在,git commit
就是往master
分支上提交更改。
对于git add
命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit
就可以一次性把暂存区的所有修改提交到分支(当前分支(没有创建则是git自动为我们创建的master),同样我们可以切换分支)。
git为什么这么吊炸天的?因为Git跟踪并管理的是修改,而非文件。
比如有下面一种流程:
第一次修改工作区某个文件 -> git add
-> 第二次修改 -> git commit
实际上,第二次修改并没有commit,就是因为git管理的修改(如果是文件,那么第二次也会提交成功)
当你用git add
命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以,git commit
只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。(git diff HEAD -- 文件名
命令可以查看工作区和版本库里面最新版本的区别)
这就是缓存区在git的地位
git撤销修改(撤销的是工作区文件的修改)
git checkout -- 文件名 //可以丢弃工作区的修改
//命令git checkout -- readme意思就是,把readme.txt文件在工作区的修改全部撤销,这里有两种情况: //一种是readme自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
//一种是readme已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
//总之,就是让这个文件回到最近一次git commit或git add时的状态。
命令中"--"挺重要的,不要忘记带上它
删除文件
不同于我们平常在文件管理器或者使用rm命令删除文件
如果你在该git工作区中删除一个文件,就会造成工作区和.git文件夹版本库不一致问题,这时候你有两个选择:
一是确实要从版本库中删除该文件,那就用删掉命令git rm
,并且不要忘记git commit
:
$git rm 文件名
$git commit -m "git删除文件演示"
这样,文件就从工作区和版本库里删除了
二是你手贱误删了,如果去电脑回收站恢复,好low!!! 这时候不要忘了Git跟踪管理的是修改,而不是文件.删除也是修改的一种,所以我们可以用刚才讲的撤销修改来恢复
$git checkout -- 文件名
你就会看到你上次add或commit的文件了
好吧,其实电脑回收站更好,因为它保证恢复的一定是你删除之前的内容,git不一定
git关联远程仓库(早该说了)
要关联一个远程库,使用命令git remote add origin git地址;
关联后,使用命令git push -u origin master第一次推送master分支的所有内容;
此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改;
分支管理
创建与合并分支
之前所说的所有操作我们都是在版本库中master分支上操作,因为这是我们创建本地仓库或者拉取项目时git自动为我们创建的.
同样,我们有时候需要创建另外一个分支,在不影响master分支情况下修改另一个分支,必要时合并
一开始的时候,master
分支中的每一个文件是一条时间线,Git用master
指向最新的提交,再用HEAD
指向master
,就能确定当前分支,以及当前分支的提交点:
不同于SVN需要复制一份新的文件
当我们创建git新的分支,例如dev
时,Git新建了一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
上:
所以,Git创建一个分支很快,因为除了增加一个dev
指针,改改HEAD
的指向,工作区的文件都没有任何变化!
不过,从现在开始,对工作区的修改和提交就是针对dev
分支了,比如新提交一次后,dev
指针往前移动一步,而master
指针不变:
假如我们在dev
上的工作完成了,就可以把dev
合并到master
上。Git怎么合并呢?最简单的方法,就是直接把master
指向dev
的当前提交,就完成了合并:
所以Git合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除dev
分支。删除dev
分支就是把dev
指针给删掉,删掉后,我们就剩下了一条master
分支:
这种适合我们不能提交只完成一半的代码,也想让Git替我们管理 以及正式版和开发版分开开发
具体命令:
首先,我们创建dev分支,然后切换到dev分支:
$ git checkout -b dev
Switched to a new branch 'dev'
git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
然后,用git branch命令查看当前分支:
$ git branch
* dev
master
git branch命令会列出所有分支,当前分支前面会标一个*号。
然后,我们就可以在dev分支上正常提交了
...................
等我们在dev分支完成工作之后想切回master分支:
$ git checkout master
Switched to branch 'master'
切换回master分支后,查看刚才文件,刚才添加的内容不见了!因为那个提交是在dev分支上,而master分支此刻的提交点并没有变:
git-br-on-master
现在,我们把dev分支的工作成果合并到master分支上:
$ git merge dev
Updating d46f35e..b17d20e
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)
Fast-forward表示快进合并,即直接将master指向dev
合并完成后,就可以放心地删除dev分支了:
$ git branch -d dev
Deleted branch dev (was b17d20e).
解决冲突
了解完分支之后,我们会发现一个问题,如果我们在修改dev分支同时,master也进行了修改,那合并的时候怎么办?
凉拌是不可能的
此时合并,git会提醒我们存在冲突,必须解决冲突才能提交,所以这时候工作区文件会用<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容,并没有提交到.git文件夹版本库中
打开文件解决冲突之后,add,commit文件,其中过程如下:
.
分支管理策略
前面说Fast-forward表示快进合并,这种模式会让我们删除分支后丢失分支信息
强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
我们只需要在merger时加上--no-ff即可
$git merger --no-ff -m "分支提交说明" 分支名
因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。
本次合并之后时间线:
可以看出分支信息已保留,可随时用git log查看
团队使用git的正确姿势:
按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
就像这样
BUG分支
分支功能很强大,现在有个问题,我们正在dev分支上工作,这时候我们需要做另外创建一个分支来修改一个临时BUG,咋办?
Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:
$ git stash
Saved working directory and index state WIP on dev: f52c633 add merge
这时git status发现工作空间是之前master,dev分支并未影响,这样我们就可以正常创建新的分支了
恢复现场:
修复完成后,切换到master分支,并完成合并,最后删除issue-101分支:
$git checkout master
$git merger --n-ff -m "普通合并描述" issue-101
这时切换回dev分支:
$git checkout dev
使用git status查看工作区间也是干净的 使用下面命令恢复:
$git stash list //查看stash列表
stash@{0}: WIP on dev: f52c633 add merge
$git stash apply //恢复stash,但是不删除
$git stash drop //删除stash
$git stash pop //恢复stash并删除
你可以多次stash,恢复的时候,先用git stash list查看,然后恢复指定的stash,用命令:
$ git stash apply stash@{0}
Feature分支
如果一个分支不想要了,也不能合并污染原分支:
1,存起来,说不定以后就用到了
2,尝试$git branch -d 分支名,提示删除失败
$git branch -D 分支名 //强制删除
多人协作
先了解推送分支
$git push 远程分支名 本地分支名
如果远程仓库有分支(git remote查看远程仓库分支情况),正常情况下git clone 之后git branch之后只会看到master分支
此时我们需要
创建远程origin
的dev
分支到本地,这个命令创建本地dev
分支:
$ git checkout -b dev origin/dev
多人协作的工作模式通常是这样:
-
首先,可以试图用
git push origin <branch-name>
推送自己的修改; -
如果推送失败,则因为远程分支比你的本地更新,需要先用
git pull
试图合并; -
如果合并有冲突,则解决冲突,并在本地提交;
-
没有冲突或者解决掉冲突后,再用
git push origin <branch-name>
推送就能成功!
如果git pull
提示no tracking information
,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>
。
参考地址:address