赞
踩
这是一个学Git无法绕开的话题,也是面试的常见题,我猜很多人的回答都是百度上直接背的,有了解过SVN底层的实现原理吗?
SVN是一种集中式版本控制工具,SVN架构如图:
A、B、C三个开发者如果需要提交自己的代码到远程仓库,必须联网(上传),上传之后SVN仓库内部做了什么?
假设用户A提交代码,会将用户A改动过的A.java提交给SVN仓库,仓库中记录的仅仅是变化(增量),对于B.java,C.java等没有h执行操作的文件,则没有增量。
那么引申出两个问题
1、假设无法联网怎么办?
2、假设SVN仓库硬盘坏了怎么办?
Git是一个分布式的版本控制工具,其架构如图所示:
local computer1、local computer2本质上就是一个本地仓库,所以Git无需联网就可以在本地开发实现提交等操作,Server、local computer1、computer2的内容是一样的。并且与SVN不同的是local computer1和local computer2直接可以进行通信。
Git版本控制的底层原理:
对于修改过的文件,如图中的A.java,会在新版本中保存修改过后的文件副本,可以理解为是一个Snapshot(注意:并不是增量文件)。对于没有修改过的文件,则在新版本中保存的是旧版本的引用。
Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念。
工作区(Working Directory):就是你在电脑里能看到的目录。
版本库(Repository):工作区有一个隐藏目录.git
,这个不算工作区,而是Git的版本库。
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master
,以及指向master
的一个指针叫HEAD
。
我们把文件往Git版本库里添加的时候,是分两步执行的:
第一步是用git add
把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit
提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master
分支,所以,现在,git commit
就是往master
分支上提交更改。
你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
俗话说,实践出真知。现在,我们再练习一遍,先对readme.txt
做个修改,比如加上一行内容:
- Git is a distributed version control system.
- Git is free software distributed under the GPL.
- Git has a mutable index called stage.
然后,在工作区新增一个LICENSE
文本文件(内容随便写)。
先用git status
查看一下状态:
- $ git status
- On branch master
- 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
-
- Untracked files:
- (use "git add <file>..." to include in what will be committed)
-
- LICENSE
-
- no changes added to commit (use "git add" and/or "git commit -a")
Git非常清楚地告诉我们,readme.txt
被修改了,而LICENSE
还从来没有被添加过,所以它的状态是Untracked
。
现在,使用两次命令git add
,把readme.txt
和LICENSE
都添加后,用git status
再查看一下:
- $ git status
- On branch master
- Changes to be committed:
- (use "git reset HEAD <file>..." to unstage)
-
- new file: LICENSE
- modified: readme.txt
现在,暂存区的状态就变成这样了:
所以,git add
命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit
就可以一次性把暂存区的所有修改提交到分支。
- $ git commit -m "understand how stage works"
- [master e43a48b] understand how stage works
- 2 files changed, 2 insertions(+)
- create mode 100644 LICENSE
一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是“干净”的:
- $ git status
- On branch master
- nothing to commit, working tree clean
现在版本库变成了这样,暂存区就没有任何内容了:
本文第二章节以上部分参考自《工作区和暂存区——廖雪峰的官方网站》
git中包含四种状态:Untracked、Unmodified、modified [可以通过git status查看当前状态]
1、Untracked一般出现在新建文件时候,表示新建的文件处于未被追踪的状态
2、将新建文件add之后,此时新建文件处于暂存区(Stage),状态为Staged状态
3、当用户commit提交暂存区域的文件到分支时,所有文件将处于Unmodified状态,此时Stage(暂存区是完全干净的)
4、当用户对文件进行修改后,被修改的文件将处于Modified状态,等待add 【重复2-4步骤】
当然还包括我们上一章节所说的git add <file> 和 git commit -m '信息' 和 git status 命令,下面我们将一个个介绍。
因为我们会存在远端的分支,比如使用gitlab举例来说。
假设我们本地已有一个项目,但是GitLab/GitHub上没有,我现在想将该项目推送上去,该怎么做呢?
1、这是一个比较笨的方法,在远端建立一个项目,从远端仓库clone到本地,将本地代码拷贝到该项目,再分别执行三个命令add->commit->push。
2、这是一个比较明智的做法,步骤如下:
(1)将需要提交项目的目录的项目初始化,打开Git Bash,比如我要将我的目录/User/itcats_cn/git_test项目提交到Gitlab,就cd到git_test,输入git init命令,初始化该项目为git管理的项目,在执行完该命令后,输入ls -a,应该可以看到.git的隐藏文件。
(2)在本地git add <file> 和 git commit -m 'message'
(3)在Gitlab中创建项目,项目名需要与第(2)步骤中提交的文件<file>名称一致
(4)复制gitlab中的git协议的地址,如git@192.168.0.11:web/test.git
(5)执行命令 git remote add origin git@192.168.0.11:web/test.git 【含义是"add" 本地项目与远端建立联系】
(6)执行命令 git config --list 发现remote.origin.url=git@192.168.0.11:web/test.git,已经关联上了
输入git remote -v命令,返回的是一个origin集合,说明用户可以对远端的仓库执行fetch和push操作
- origin git@192.168.0.11:web/test.git (fetch)
- origin git@192.168.0.11:web/test.git (push)
从远程仓库获取最新到本地,不会自动merge,Git中从远程的分支获取最新的版本到本地方式如下:
方式一
(1)查看远程仓库
- $ git remote -v
- eoecn https://github.com/eoecn/android-app.git (fetch)
- eoecn https://github.com/eoecn/android-app.git (push)
- origin https://github.com/com360/android-app.git (fetch)
- origin https://github.com/com360/android-app.git (push)
- su@SUCHANGLI /e/eoe_client/android-app (master)
从上面的结果可以看出,远程仓库有两个,一个是eoecn,一个是origin
(2)从远程获取最新版本到本地
- $ git fetch origin master
- From https://github.com/com360/android-app
- * branch master -> FETCH_HEAD
- su@SUCHANGLI /e/eoe_client/android-app (master)
$ git fetch origin master 这句的意思是:从远程的origin仓库的master分支下载代码到本地的origin master
(3)比较本地的仓库和远程参考的区别
- $ git log -p master.. origin/master
- su@SUCHANGLI /e/eoe_client/android-app (master)
我的本地参考代码和远程代码相同,所以是Already up-to-date
以上的方式有点不好理解,大家可以使用下面的方式,并且很安全
方式二
(1)查看远程分支,和上面的第一步相同
(2)从远程获取最新版本到本地
- $ git fetch origin master:temp
- From https://github.com/com360/android-app
- * [new branch] master -> temp
- su@SUCHANGLI /e/eoe_client/android-app (master)
git fetch origin master:temp 这句命令的意思是:从远程的origin仓库的master分支下载到本地并新建一个分支temp
(3)比较本地的仓库和远程参考的区别
- $ git diff temp
- su@SUCHANGLI /e/eoe_client/android-app (master)
命令的意思是:比较master分支和temp分支的不同
由于我的没有区别就没有显示其他信息
(4)合并temp分支到master分支
- $ git merge temp
- Already up-to-date.
- su@SUCHANGLI /e/eoe_client/android-app (master)
由于没有区别,所以显示Already up-to-date.
合并的时候可能会出现冲突,有时间了再把如何处理冲突写一篇博客补充上。
(5)如果不想要temp分支了,可以删除此分支
- $ git branch -d temp
- Deleted branch temp (was d6d48cc).
- su@SUCHANGLI /e/eoe_client/android-app (master)
如果该分支没有合并到主分支会报错,可以用以下命令强制删除git branch -D <分支名>
总结:方式二更好理解,更安全,对于pull也可以更新代码到本地,相当于fetch+merge,多人写作的话不够安全。
git fetch章节参考自:《git fetch的简单用法》
git pull origin master
git add .
git commit -m 'update .gitignore'
git push origin master 或者 git push -u origin dev
可以通过git log命令查看历史的commit记录,通过选择复制commit后的SHA1 id进行指定的版本回退,如:
- localhost:git_test fatah$ git log
- commit 617866c48b75d62b8b33a29fc21eb6d32cff3eb1 (HEAD -> master)
- Author: itcats_cn <xxx@qq.com>
- Date: Tue Aug 6 23:00:27 2019 -0400
-
- Add new file
-
- commit 8ea66b2158d45ddd459fc6b538669be5b546bbb0
- Author: itcats_cn <xxx@qq.com>
- Date: Wed Aug 7 10:20:37 2019 +0800
-
- init2
目前版本为617866开头的commit id版本号,如果想回退版本,可以通过指定版本号进行回退
git reset --hear '8ea66b2158d45ddd459fc6b538669be5b546bbb0'
如果嫌输出信息太多,看得眼花缭乱的,可以试试加上--pretty=oneline
参数:
- localhost:git_test fatah$ git log --pretty=oneline
- 881667e679a88b557b1014d33e6a46a3c1d0a442 (HEAD -> master, origin/master) add hahaha
- 617866c48b75d62b8b33a29fc21eb6d32cff3eb1 Add new file
Git的commit id(版本号)和SVN不一样,Git的commit id
不是1,2,3……递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示,而且你看到的commit id
和我的肯定不一样,以你自己的为准。为什么commit id
需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了。
Git必须知道当前版本是哪个版本,在Git中,用HEAD
表示当前版本,上一个版本就是HEAD^
,上上一个版本就是HEAD^^
,当然往上100个版本写100个^
比较容易数不过来,所以写成HEAD~100。
3.6
checkout作用:
1、切换到新的分支: 一般来说master分支上都是最稳定的版本,我们日常开发的时候在分支开发,开发完毕之后再合并到master中,具体操作如下。
- $ git checkout -b dev-0807-work
- Switched to a new branch 'dev-0807-work'
-
- $ git checkout -b master
- Switched to a new branch 'master'
2、撤销更改 :只能撤销更改过的文件,不能撤销新增的文件或删除的文件
比如我们修改了3.txt的内容
- $ git status
- On branch master
- 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: 3.txt
-
- no changes added to commit (use "git add" and/or "git commit -a")
现在我想撤销3.txt的更改,操作如下:
- $ git checkout 3.txt
- Updated 1 path from the index
此时3.txt的内容就成功被撤销,这样不会影响之前的版本,恢复成非常"干净"的状态
3.7
merge一般我们开发环境都在分支上开发,分支开发完毕时候测试通过则发布,并且在master进行merge合并并发布,这样才能保证master上是最"clean"的状态,但是一般我们将分支合并到Master都是在图形化界面发起的,下面我将展示GitLab中的 Pull Request。
这种merge方法方便code-review,查看改动之处并让参与者、审阅者也可以方便在下面书写评论。
在确认没有任何问题后,再点击绿色的按钮"Merge',就可以将我们的工作分支合并到master之中。
合并之后,在本地机器执行 git fetch 命令,从远端拉取最新的代码[此时本机处于dev-08-07-work分支]
本机切换到master分支,执行命令 git checkout master
- localhost:git_test fatah$ git checkout master
- Switched to branch 'master'
- Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.
- (use "git pull" to update your local branch)
提示远端的master有两处commit,先pull到本机的orgin/master
- localhost:git_test fatah$ git pull origin master
- Enter passphrase for key '/Users/fatah/.ssh/id_rsa':
- From 192.168.1.103:web/test
- * branch master -> FETCH_HEAD
- Updating 881667e..b117d8f
- Fast-forward
- a.txt | 3 ++-
- 1 file changed, 2 insertions(+), 1 deletion(-)
整个过程我们发现,本地的master并没有push到远端仓库,而是使用分支push到远端仓库中,再使用Request Merge在远端将分支与Master进行合并,在code-view没有问题之后,在远端仓库执行Merge操作。由于本地在fetch最新代码的时候并不会直接合并,所以在本机切换为master分支的时候会提示pull一下,因为有两处commit,在pull成功之后本机master便拥有了最新的master代码。
在merge的时候如何解决冲突呢?
模拟场景:
(1)比如修改gitlab中的a.txt文件,本地也修改git管理目录下的a.txt文件
(2)本地进行add 和 commit操作
(3)执行git pull origin master时候,发现冲突文件,因为远端和本地两处修改位置一样,到底选用哪一行?
(4)删除冲突部分,其中<<<<HEAD到=====之间的内容为本地仓库内容,后面的内容为远端仓库的内容,两处冲突地方选用哪个可以自己权衡,再修改完之后,输入git status,发现a.txt变为了unmodified状态,重新执行add -> commit -> push操作,发现完成解决冲突。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。