为什么要版本管理?
很明显,你做一个项目是需要很多迭代的,如果某一次迭代后,功能出现重大问题,你排查不了,但是恢复却很方便,有没有这种情况,是吧,这就很有用。最简单的版本管理系统就是拷贝,加上v1,v2什么,如果想要知道时间,就在后面加上时间就好了。
那么如何进行协同工作?毕竟你这里有我这里也有,用谁的?那么统一管理吧!放到一个服务器上,就是如此。这就是统一管理,你别管人家是如何节省空间磁盘的,这时候自己的电脑上就当前版本,可以下载任何版本,就是如此。但这个便捷也是一个缺陷,一旦中央服务器挂机,所有文件就丢了,宁错一千,不能放过一个,分布式应运而生,相对于耗费的磁盘相比,它不会当机,这就是它最大优点。
而github,实际上就是披着羊皮的狼,它是中央服务器,对吧,不是,偏主服务器,自己的磁盘里也爆满(虽然有gitingnore,npm的方法)。。。大致就是如此了,master分支也是,如果每个人都在自己的电脑融master,你就必须保证每个电脑都是可以访问的,但只要偏主服务器的存在,一切就都迎刃而解了(或者说只解决了电脑直连的问题)。git每次存储都是完整镜像而不是快照。为了提高性能。
再者说几乎所有的操作都在本地上进行了,不用联网,这也算是弊中有利吧。
而用于团队合作方面,反正你总得用一款版本管理工具,就是如此。
但为什么会方便管理者融合分支呢?没有什么方便不方便的。。。
然后是重要的文件的三种状态:已提交(committed),已修改(modified)和已暂存(staged)。
已提交表示该文件已经被安全地保存在本地数据库中了;
已修改表示修改了某个文件,但还没有提交保存;
已暂存表示把已修改的文件放在下次提交时要保存的清单中。
由此我们看到 Git 管理项目时,文件流转的三个工作区域:Git 的工作目录,暂存区域,以及本地仓库。
.git就是用来保存数据库的地方。
基本的 Git 工作流程如下:
- 在工作目录中修改某些文件。
- 对修改后的文件进行快照,然后保存到暂存区域。
- 提交更新,将保存在暂存区域的文件快照永久转储到 Git 目录中。
所谓的暂存区域只不过是个简单的文件,一般都放在 Git 目录中。有时候人们会把这个文件叫做索引文件,不过标准说法还是叫暂存区域。
所以,我们可以从文件所处的位置来判断状态:如果是 Git 目录中保存着的特定版本文件,就属于已提交状态;如果作了修改并已放入暂存区域,就属于已暂存状态;如果自上次取出后,作了修改但还没有放到暂存区域,就是已修改状态。为什么会有暂存呢?有什么意义呢?
接下来还有重要的分支,合并等知识,现在就足够了。
不用登陆,设置一次用一辈子
gitbash不支持CTRL+v
gitbash用的是linux命令
创建一个版本库非常简单,首先,选择一个合适的地方,创建一个空目录
然后,转到这个目录,执行
$ git init才对
把文件添加到版本库
第一步,用命令git add告诉Git,把文件添加到仓库:
$ git add readme.txt
必须要在仓库目录下才行
可以add多次
第二步,用命令git commit告诉Git,把文件提交到仓库:
$ git commit -m "wrote a readme file"
[master (root-commit) eaadf4e] wrote a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.txt
简单解释一下git commit命令,-m后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。
git status命令可以让我们时刻掌握仓库当前的状态
git diff顾名思义就是查看difference,显示的格式正是Unix通用的diff格式
用git log命令查看历史记录
在Git中,用HEAD表示当前版本,也就是最新的提交1094adb...(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。
使用git reset命令回退到上一个版本
指定回到未来的某个版本,需要其版本号,一部分就成,然后用git reset就成
Git提供了一个命令git reflog用来记录你的每一次命令
git checkout -- file可以丢弃工作区的修改不管有没有放到暂存区
命令git reset HEAD <file>可以把暂存区的修改撤销掉(unstage),重新放回工作区
命令git rm删除文件,并git commit先手动删除文件,然后使用git rm <file>和git add<file>效果是一样的。
另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:
$ git checkout -- test.txt
git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
删除有两种方式: 1、工作区内"rm 文件名",然后"git rm 文件名",然后"git commit -m '备注'" 2、直接使用"git rm 文件名",然后"git commit -m '备注'" 实验结果: 1、工作区内"rm 文件名" 只是删除工作区内容,暂存区内容还是在的,在"git rm 文件名"操作之前可通过"git checkout --文件名"从暂存区进行恢复。 2、"git rm 文件名"是既删除工作区内容也删除暂存区内容的,所以在"git commit -m '备注'"操作之前可以通过"git checkout HEAD -- 文件名"从版本库进行恢复,当然你要是直接用git reset HEAD^也行但是可能会影响你其他修改但是未提交的其他内容的
$ git remote add origin git@github.com:账户名/远程仓库名.git
一般把远程库与本地库取名一致才好
若出现错误,先执行$ git remote rm origin,再执行上面语句就好
本地和远程的文件应该合并后才能上传本地的新文件,但是新建的项目还没有文件,空的,就不能往下拉了,直接把本地修改的文件上传即可
1、先拉下来,会自动合并的(不用操心)
git pull origin master
2、再上传
git push -u origin master
成功解决问题
refusing to merge unrelated histories
是两个不同的项目,要把两个不同的项目合并,git需要添加一句代码,在git pull,这句代码是在git 2.9.2版本发生的,最新的版本需要添加--allow-unrelated-histories
必须要建立一个没有被初始化的库才行,也就是没有readme.md的文件
先创建远程库,再从远程库克隆,这时要选一个合适的路径了 ,是在此路径下新建立一个文件夹,并把内容都放在里面
git push -u origin master
git clone git@github.com:heroisuseless/helloworld.git
git pull origin master
HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支。
一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:
首先,我们创建dev分支,然后切换到dev分支:
$ git checkout -b dev
Switched to a new branch 'dev'
然后,用git branch命令查看当前分支:
$ git branch
* dev
master
切换回master分支后,再查看一个readme.txt文件,刚才添加的内容不见了!因为那个提交是在dev分支上,而master分支此刻的提交点并没有变:
现在,我们把dev分支的工作成果合并到master分支上:
$ git merge dev
Updating d46f35e..b17d20eFast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)
git merge命令用于合并指定分支到当前分支
合并完成后,就可以放心地删除dev分支了:
$ git branch -d devDeleted branch dev (was b17d20e).
删除后,查看branch,就只剩下master分支了:
$ git branch
* master
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
这个只是针对不同文件而言,对于在不同分支上修改同一文件后,就必须手动修改文件改正,某一分支中的文件,使两个文件相同,这样才会解决冲突继续提交
分支管理策略
阅读: 473794
通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
下面我们实战一下--no-ff方式的git merge:
首先,仍然创建并切换dev分支:
$ git checkout -b devSwitched to a new branch 'dev'
修改readme.txt文件,并提交一个新的commit:
$ git add readme.txt
$ git commit -m "add merge"
[dev f52c633] add merge
1 file changed, 1 insertion(+)
现在,我们切换回master:
$ git checkout masterSwitched to branch 'master'
准备合并dev分支,请注意--no-ff参数,表示禁用Fast forward:
$ git merge --no-ff -m "merge with no-ff" devMerge made by the 'recursive' strategy.
readme.txt | 1 +
1 file changed, 1 insertion(+)
因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。
合并后,我们用git log看看分支历史:
$ git log --graph --pretty=oneline --abbrev-commit
* e1e9c68 (HEAD -> master) merge with no-ff
|\
| * f52c633 (dev) add merge
|/
* cf810e4 conflict fixed
...
可以看到,不使用Fast forward模式,merge后就像这样:
分支策略
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
下面我们实战一下--no-ff方式的git merge:
首先,仍然创建并切换dev分支:
$ git checkout -b devSwitched to a new branch 'dev'
修改readme.txt文件,并提交一个新的commit:
$ git add readme.txt
$ git commit -m "add merge"
[dev f52c633] add merge
1 file changed, 1 insertion(+)
现在,我们切换回master:
$ git checkout masterSwitched to branch 'master'
准备合并dev分支,请注意--no-ff参数,表示禁用Fast forward:
$ git merge --no-ff -m "merge with no-ff" devMerge made by the 'recursive' strategy.
readme.txt | 1 +
1 file changed, 1 insertion(+)
因为本次合并要创建一个新的commit,
Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:
$ git stash
Saved working directory and index state WIP on dev: f52c633 add merge
用于在dev上进行工作时,创建了一个bug分支,这样可以把dev场景保存起来
首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支,但肯定不会在master分支上进行工作的:
$ git checkout masterSwitched to branch 'master'Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)
$ git checkout -b issue-101Switched to a new branch 'issue-101'
bug分支实际上是一个超前分支
一是用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;
另一种方式是用git stash pop,恢复的同时把stash内容也删了
如果要丢弃一个没有被合并过的分支,可以通过git branch -D <name>强行删除。
要查看远程库的信息,用git remote或者,用git remote -v显示更详细的信息
$ git remote
origin
远程仓库的默认名称是origin
推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:
$ git push origin master
如果要推送其他分支,比如dev,就改成:
$ git push origin dev
如果你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送:
· 这就是rebase操作的特点:把分叉的提交历史“整理”成一条直线,看上去更直观。缺点是本地的分叉提交已经被修改过了。
rebase操作可以把本地未push的分叉提交历史整理成直线;
·
·
rebase的目的是使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比。
· 发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
$ git branch
* dev
master$ git checkout masterSwitched to branch 'master'
然后,敲命令git tag <name>就可以打一个新标签:
$ git tag v1.0
可以用命令git tag查看所有标签:
$ git tag
v1.0
$ git tag v0.9 f52c633
标签不是按时间顺序列出,而是按字母排序的。可以用git show <tagname>查看标签信息
还可以创建带有说明的标签,用-a指定标签名,-m指定说明文字:
$ git tag -a v0.1 -m "version 0.1 released" 1094adb
用命令git show <tagname>可以看到说明文字
如果标签打错了,也可以删除:
$ git tag -d v0.1Deleted tag 'v0.1' (was f15b0dd)
因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。
如果要推送某个标签到远程,使用命令git push origin <tagname>:
$ git push origin v1.0Total 0 (delta 0), reused 0 (delta 0)To github.com:michaelliao/learngit.git
* [new tag] v1.0 -> v1.0
或者,一次性推送全部尚未推送到远程的本地标签:
$ git push origin --tagsTotal 0 (delta 0), reused 0 (delta 0)To github.com:michaelliao/learngit.git
* [new tag] v0.9 -> v0.9
如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:
$ git tag -d v0.9Deleted tag 'v0.9' (was f52c633)
然后,从远程删除。删除命令也是push,但是格式如下:
$ git push origin :refs/tags/v0.9To github.com:michaelliao/learngit.git
- [deleted] v0.9
在Git工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。如果你确实想添加该文件,可以用-f强制添加到Git
甚至还有人丧心病狂地把lg配置成了:
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"