Git是一个版本控制的软件,为了让版本更新更加清晰明了,方便回退等等,最后的效果应该像下图。自己有做笔记的习惯,学习是参照廖雪峰大佬的博客。
1.Git简介
SVN和CSV都是集中式版本控制系统,需要一个中央的存储,每次修改都需要上传到这个存储中去,需要联网,而且修改量很大,那么上传很慢,对网络要求也高。
Git是分布式的版本管理系统,在每个人的计算机上都有一个完整的版本库,还有一个充当中央交换器的一个服务器,但是它不负责全部项目的存储,只是为了方便交换修改。
- 安装Git并且需要配置user name和user email,这是Git要求我们的标识符,这里的--global是表示对于所有的仓库,我们都使用这个用户。
- 创建仓库就在当前目录下运行一个git init,然后就会在这个目录下生成一个.git文件,它是一个隐藏文件,需要用ls -ah才能查看到
- 提交文件用分为add和commit两步,一次commit可以添加多个文件。-m是后面可以添加一些版本更新的注释。
注意:Git命令必须在Git仓库目录内执行(git init除外),在仓库目录外执行是没有意义的。git能够识别出文本文件的修改处,但是二进制文件是不行的,比如图片,就只能识别出大小的变化。
2.Git时光机
2.1 版本回退
我们要回退到以前的版本,或者是已经回退到以前的版本了,又想回到当前版本,就像哆啦A梦的时光机一样穿来穿去。
首先要打印出每次的修改日志git log,如果嫌输出的东西太多太乱,可以用git log --pretty=oneline
commit后面跟的一大串就是一个版本快照编号。使用
git reset <版本号>来跳转到指定版本,未来或者过去,但是git reset --hard HEAD^指向上一个版本
但是如果我回退后隔天才后悔,找不到了最新的版本号可以用
git reflog这个记录了每一次执行的命令
2.2工作区和暂存区
在工作区就是我们可以看到的文件,除了.git以外的文件,但是有个working tree,这里面存储的是我们已经修改过的文件,但是尚未add。stage像是一个暂存区,把多个修改过的文件通过add加入到这个stage,然后在一次性提交到master tree里去。master tree用了一个HEAD指针指向当前的版本。reset就是改变这个指针的位置。
2.3管理修改
我们知道了git保存的修改不是每次修改的文件,而是修改的地方。所以要通过一个例子来表现
我修改了readme后运行了add,然后再次修改了readme,然后直接commit,使用
git diff 查看了工作区和master区里面的区别。
发现了git确实跟踪到了文件的修改
如果我们在还未commit修改之前想要撤销掉操作,会发现其实git status已经给我们提供了返回的提示,如果修改尚未add,就直接git restore file,如果已经add那么就git restore --staged file,除此之外还有一个checkout命令可以使用,git checkout -- file,也可以回退工作区的尚未staged的命令,一定要加--,它有两种情况。
- 文件修改后,没有add,直接恢复
- 文件修改后,add后,又再次修改,就恢复到add后在staged那个版本。
删除文件也是一样的,直接在工作区删除一个文件时rm test.txt,它不会立刻生效git status查看一下,我们还需要提交到stage阶段就使用git rm file,然后再git commit
3.添加远程库
现在的情景是,你已经在本地创建了一个Git仓库后,又想在GitHub创建一个Git仓库,并且让这两个仓库进行远程同步,这样,GitHub上的仓库既可以作为备份,又可以让其他人通过该仓库来协作,真是一举多得。
首先,登陆GitHub,然后,在右上角找到“Create a new repo”按钮,创建一个新的仓库:
在Repository name填入learngit,其他保持默认设置,点击“Create repository”按钮,就成功地创建了一个新的Git仓库,在我们的本地上运行下面这个命令
$ git remote add origin git@github.com:michaelliao/learngit.git
然后把我们的本地代码push上去
删除远程库
如果添加的时候地址写错了,或者就是想删除远程库,可以用git remote rm 命令。使用前,建议先用git remote -v查看远程库信息:
$ git remote -v
origin git@github.com:michaelliao/learn-git.git (fetch)
origin git@github.com:michaelliao/learn-git.git (push)
然后,根据名字删除,比如删除origin:
$ git remote rm origin
此处的“删除”其实是解除了本地和远程的绑定关系,并不是物理上删除了远程库。远程库本身并没有任何改动。要真正删除远程库,需要登录到GitHub,在后台页面找到删除按钮再删除。
上次我们讲了先有本地库,后有远程库的时候,如何关联远程库。
现在,假设我们从零开发,那么最好的方式是先创建远程库,然后,从远程库克隆。
首先,登陆GitHub,创建一个新的仓库,名字叫gitskills:
github-init-repo
我们勾选Initialize this repository with a README,这样GitHub会自动为我们创建一个README.md文件。创建完毕后,可以看到README.md文件:
github-init-repo-2
现在,远程库已经准备好了,下一步是用命令git clone克隆一个本地库:
$ git clone git@github.com:michaelliao/gitskills.git
Cloning into 'gitskills'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 3
Receiving objects: 100% (3/3), done.
注意把Git库的地址换成你自己的,然后进入gitskills目录看看,已经有README.md文件了:
$ cd gitskills
$ ls
README.md
4.分支管理
不得不说廖雪峰大佬真的太厉害了,深入浅出的描述了分支到底是一个什么样的东西,我这里只是做一个copy备份,大家一定要去看原po。
分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
也就是说文件等等改动都是在分支上的,合并和修改的操作都只是针对一个指针而已,也许是记录在了文件里,但是每个版本时间节点都记录了改动地方的快照、
git的常用分支命令列表: Git鼓励大量使用分支:
- 查看分支:git branch
- 创建分支:git branch
- 切换分支:git checkout 或者git switch
- 创建+切换分支:git checkout -b 或者git switch -c
- 合并某分支到当前分支:git merge
- 删除分支:git branch -d
4.1实战部分
,首先,我们创建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分支上正常提交,比如对readme.txt做个修改,加上一行: Creating a new branch is quick. 然后提交:
$ git add readme.txt
$ git commit -m "branch test"
[dev b17d20e] branch test
1 file changed, 1 insertion(+)
现在,dev分支的工作完成,我们就可以切换回master分支:
$ git checkout master
Switched to branch 'master'
切换回master分支后,再查看一个readme.txt文件,刚才添加的内容不见了!因为那个提交是在dev分支上,而master分支此刻的提交点并没有变:
git-br-on-master
现在,我们把dev分支的工作成果合并到master分支上:
$ git merge dev
Updating d46f35e..b17d20e
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)
git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。
注意到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。
合并完成后,就可以放心地删除dev分支了:
$ git branch -d dev
Deleted branch dev (was b17d20e).
删除后,查看branch,就只剩下master分支了:
$ git branch
* master
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
4.2解决冲突
准备新的feature1分支,继续我们的新分支开发:
$ git switch -c feature1
Switched to a new branch 'feature1'
修改readme.txt最后一行,改为:
Creating a new branch is quick AND simple. 在feature1分支上提交:
$ git add readme.txt
$ git commit -m "AND simple"
[feature1 14096d0] AND simple
1 file changed, 1 insertion(+), 1 deletion(-)
切换到master分支:
$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
Git还会自动提示我们当前master分支比远程的master分支要超前1个提交。
在master分支上把readme.txt文件的最后一行改为:
Creating a new branch is quick & simple. 提交:
$ git add readme.txt
$ git commit -m "& simple"
[master 5dc6824] & simple
1 file changed, 1 insertion(+), 1 deletion(-)
现在,master分支和feature1分支各自都分别有新的提交,变成了这样:
这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:
$ git merge feature1
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.
果然冲突了!Git告诉我们,readme.txt文件存在冲突,必须手动解决冲突后再提交。git status也可以告诉我们冲突的文件:
$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
我们可以直接查看readme.txt的内容:
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1
Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,我们修改如下后保存:
Creating a new branch is quick and simple. 再提交:
$ git add readme.txt
$ git commit -m "conflict fixed"
[master cf810e4] conflict fixed
现在,master分支和feature1分支变成了下图所示:
用带参数的git log也可以看到分支的合并情况:
$ git log --graph --pretty=oneline --abbrev-commit
* cf810e4 (HEAD -> master) conflict fixed
|\
| * 14096d0 (feature1) AND simple
* | 5dc6824 & simple
|/
* b17d20e branch test
* d46f35e (origin/master) remove test.txt
* b84166e add test.txt
* 519219b git tracks changes
* e43a48b understand how stage works
* 1094adb append GPL
* e475afc add distributed
* eaadf4e wrote a readme file
最后,删除feature1分支:
$ git branch -d feature1
Deleted branch feature1 (was 14096d0).
4.3分支管理
通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
下面我们实战一下--no-ff方式的git merge:
首先,仍然创建并切换dev分支:
$ git switch -c dev
Switched 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 switch master
Switched to branch 'master'
准备合并dev分支,请注意--no-ff参数,表示禁用Fast forward:
$ git merge --no-ff -m "merge with no-ff" dev
Merge 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后就像这样:
如果一个分支还没有merge就要删掉,这时候git branch -d branch不管用了,应该用git branch -D branch
分支管理的原则: master分支必须是十分稳定的,修改应该在dev分支上,每个人都应该有自己的分支,只有测试的时候合并到dev分支上,最后在版本更新发布的时候才把dev合并到master上
4.4stash命令
软件开发中,bug就像家常便饭一样。有了bug就需要修复,在Git中,由于分支是如此的强大,所以,每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。
当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支issue-101来修复它,但是,等等,当前正在dev上进行的工作还没有提交:
$ git status
On branch dev
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: hello.py
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: readme.txt
并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?
幸好,Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:
$ git stash
Saved working directory and index state WIP on dev: f52c633 add merge
现在,用git status查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug。
首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支:
$ git checkout master
Switched 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-101
Switched to a new branch 'issue-101'
现在修复bug,需要把“Git is free software ...”改为“Git is a free software ...”,然后提交:
$ git add readme.txt
$ git commit -m "fix bug 101"
[issue-101 4c805e2] fix bug 101
1 file changed, 1 insertion(+), 1 deletion(-)
修复完成后,切换到master分支,并完成合并,最后删除issue-101分支:
$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)
$ git merge --no-ff -m "merged bug fix 101" issue-101
Merge made by the 'recursive' strategy.
readme.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
太棒了,原计划两个小时的bug修复只花了5分钟!现在,是时候接着回到dev分支干活了!
$ git switch dev
Switched to branch 'dev'
$ git status
On branch dev
nothing to commit, working tree clean
工作区是干净的,刚才的工作现场存到哪去了?用git stash list命令看看:
$ git stash list
stash@{0}: WIP on dev: f52c633 add merge
工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:
一是用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;
另一种方式是用git stash pop,恢复的同时把stash内容也删了(applt 和pop都是能够指定index的,即弹出栈里面第几个修改):
$ git stash pop
On branch dev
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: hello.py
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: readme.txt
Dropped refs/stash@{0} (5d677e2ee266f39ea296182fb2354265b91b3b2a)
再用git stash list查看,就看不到任何stash内容了:
$ git stash list
你可以多次stash,恢复的时候,先用git stash list查看,然后恢复指定的stash,用命令:
$ git stash apply stash@{0}
同样的bug,要在dev上修复,我们只需要把4c805e2 fix bug 101这个提交所做的修改“复制”到dev分支。注意:我们只想复制4c805e2 fix bug 101这个提交所做的修改,并不是把整个master分支merge过来。
为了方便操作,Git专门提供了一个cherry-pick命令,让我们能复制一个特定的提交到当前分支:
$ git branch
* dev
master
$ git cherry-pick 4c805e2
[master 1d4b803] fix bug 101
1 file changed, 1 insertion(+), 1 deletion(-)
Git自动给dev分支做了一次提交,注意这次提交的commit是1d4b803,它并不同于master的4c805e2,因为这两个commit只是改动相同,但确实是两个不同的commit。用git cherry-pick,我们就不需要在dev分支上手动再把修bug的过程重复一遍。
4.5多人协作
多人协作流程
- 首先,可以试图用git push origin 推送自己的修改;
- 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
- 如果合并有冲突,则解决冲突,并在本地提交;
- 没有冲突或者解决掉冲突后,再用git push origin 推送就能成功!
- 如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to origin/。
常用操作
- 查看远程库信息,使用git remote -v;
- 本地新建的分支如果不推送到远程,对其他人就是不可见的;
- 从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull抓取远程的新提交;
- 在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name,本地和远程分支的名称最好一致;
- 建立本地分支和远程分支的关联,使用git branch --set-upstream branch-name origin/branch-name;
- 从远程抓取分支,使用git pull,如果有冲突,要先处理冲突。
5.标签管理
命令git tag <tagname>用于新建一个标签,默认为HEAD,也可以指定一个commit id;
命令git tag -a <tagname> -m "blablabla..."可以指定标签信息;
命令git tag可以查看所有标签。
如果标签打错了,也可以删除:
$ git tag -d v0.1
Deleted tag 'v0.1' (was f15b0dd)
因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。
如果要推送某个标签到远程,使用命令git push origin :
$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
* [new tag] v1.0 -> v1.0
或者,一次性推送全部尚未推送到远程的本地标签:
$ git push origin --tags
Total 0 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
* [new tag] v0.9 -> v0.9
如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:
$ git tag -d v0.9
Deleted tag 'v0.9' (was f52c633)
然后,从远程删除。删除命令也是push,但是格式如下:
$ git push origin :refs/tags/v0.9
To github.com:michaelliao/learngit.git
- [deleted] v0.9
要看看是否真的从远程库删除了标签,可以登陆GitHub查看。
6.使用github
在github下面可以fork出一个自己的在线项目,和源项目一摸一样,然后这时候在clone就是从自己的fork的repository里面clone了