标签归档:Git

git基本原理详解

1.什么是git

这个去google一下可以搜出一大堆,git就是一个软件管理器,不同一般的是它是分布式的,不仅有一个中心的服务器控制最新版本代码,而且每个开发者自己还有个本地仓库,所以在开发过程中都是先将代码提交到本地仓库再推送到中心服务器上的,这样的好处就是每个人都依赖于中心服务器来实现交互,但又不会被中心服务器限制,就算中心服务器挂了,也能很容易的找到最新版本的代码,而且我自己的工作依然可以顺利进行,提交到本地仓库,当中心服务器修复之后,再将自己仓库的东西推送到中心服务器。当然它还有很多的不同点,以后再来比较git和svn。

2.自己的git仓库

我们要进行开发,提交代码和中心服务器进行交互,首先我们要有自己的一个开发基地,也就是我们自己的git仓库。拥有自己的git仓库的方式有两种,一就是在自己已有的目录里初始化自己的git仓库然后和中心服务器建立连接,更新最新代码到自己的git仓库。二就是将一个已经存在的项目克隆到自己的目录成为自己的git仓库。

执行git init命令,就会创建并初始化git仓库,这个时候在该目录下会产生一个.git的隐藏文件夹,而该目录就是你的工作目录,你的一切行为都是在这个目录里,而这个.git文件夹就是你的本地仓库,当你进行了一些文件操作之后,认为可以提交了那么首先你就是提交到本地仓库也就是这个.git中,然后再推送到中心服务器。或者直接克隆一个仓库到本地作为git仓库,也是一样的。当你进入到这个.git目录中,会发现里面还有很多的子目录和文件,有的是很重要的,这里说几个,config文件,这是你项目的配置文件,里面有中心服务器的信息和分支信息,HEAD文件指向当前的分支,index文件是暂存区的相关信息,logs目录中都是相关操作产生的日志,这个很重要,因为日志是我们操作的唯一证据,我们本地的版本控制也靠它,objects目录里面存储的就是所有 的数据,也就是快照,refs目录里是存储指向数据提交对象的指针。

乍看之下不知道说的什么,主要是里面的很多名字不知道是什么东西,下面再来看看一些名字概念,分支和提交对象以后再讲这个涉及版本的控制,我们先看最基本的。

3.git的分层结构

刚开始学习git的时候,总是一头雾水,又是什么工作目录,又是什么暂存区,又是什么本地仓库,又是什么远程仓库,还尼玛快照,看的我头晕眼花的。但是只要把这几个概念弄清楚,那么最基本的开发就不是问题了。先来看看我所理解的git的分层结构:

git的工作总共分四层,其中三层是在自己本地也就是前面说的git仓库,包括了工作目录,暂存区和本地仓库,工作目录就是我们执行命令git init时所在的地方,也就是我们执行一切文件操作的地方,暂存区和本地仓库都是在.git目录,因为它们只是用来存数据的。远程仓库在中心服务器,也就是我们做好工作之后推送到远程仓库,或者从远程仓库更新下来最新代码到我们的git仓库。git所存储的都是一系列的文件快照,然后git来跟踪这些文件快照,发现哪个文件快照有变化它就会提示你需要添加到暂存区或是提交到本地仓库来保证你的工作目录是干净的。

这个怎么理解呢,git中的文件有两种状态,一种是被跟踪的,也就是提交到本地仓库的文件,因为本地仓库要保管它们当然得跟踪他们,对它们负责,还有一种就是未被跟踪的。那么当我们添加新的文件时,它不是被跟踪的,因为本地仓库里面没有这个文件,它是外来的,本地仓库目前还不需要对他们负责。但是如果是对仓库已经存在的文件进行修改,那么这些文件就是被跟踪的文件,就可以通过git status查看他们的状态来进行相应的操作。当然我们也可以生成一个.gitignore文件,里面指定要忽略的文件类型,然后这些文件就不会被跟踪,不管怎么改变它们,git status都不会提示你需要做什么操作的。

所以当我们在工作目录中进行文件操作后,要先添加到暂存区,然后再将暂存区中刚添加的文件快照提交到本地仓库,然后再将本地仓库的最新版本文件快照推送到远程仓库。这个文件快照其实就是各个文件的在被添加到暂存区时的状态,就和照相一样的,留下每个不同时刻的快照,方便以后查询,而git存储的就是这些一系列的快照。说到这个快照就要说说git的对象了。

4.git的对象

从根本上讲,git是一套内容寻址的文件系统,它存储的也是key-value键值对,然后根据key值来查找value的,说到寻址就会想到指针吧,不错,git也是根据指针来寻址的,这些指针就存储在git的对象中。git一共有3种对象,commit对象,tree对象和blob对象。下面便是这3个对象:

这个blob对象对应的就是文件快照中那些发生变化的文件内容,而tree对象则记录了文件快照中各个目录和文件的结构关系,它指向了被跟踪的快照,commit对象则记录了每次提交到本地仓库的文件快照,从上图看出其中有两个指针,一个指向tree对象,一个则指向上一个commit对象。这个怎么理解呢,怎么还有上一个commit对象,在开发过程中,我们会提交很多次文件快照,那么第一次提交的内容会用一个commit来记录,这个commit没有指针指向上一个commit对象,因为没有上一个commit,它是第一个,当第二次提交时,又会有另外一个commit对象来记录,那么这次commit对象中就会有一个指针指向上一次提交后的commit对象,经过很多次提交后就会有很多的commit对象,它们组成了一个链表,当我们要恢复哪个版本的时候,只要找到这个commit对象就能恢复那个版本的文件不是吗。而我们所谓的HEAD对象其实就指向最近一个提交的commit对象,也就是最后一个commit对象。

5.git的基本操作

上面说了这么多东西,也该看看基本操作了,基本上的操作无非是文件的增删改和版本的提交更新回溯。当把上面的内容弄清楚之后,这些基本操作根本就是小草一碟:

上面列出了基本上能用到的所有命令,当然还没涉及到分支,这个概念后面再说,基本的用法google一下一大堆,这里不用多说,还有就是要经常用到git status这个命令,它可以指引你该干嘛干嘛,确定你的工作目录是否干净。干净的意思就是和暂存区还有本地仓库保持一致。还有一个命令也是经常用到的,那就是git log,这个命令会列出你的操作产生的日志,有很多的信息,还有提交后的commit对象的id,这个checkout版本的时候用处很大。

文章来源参考文档:
https://blog.csdn.net/xiaoputao0903/article/details/23912561?utm_source=tuicool&utm_medium=referral
http://www.open-open.com/lib/view/open1328070620202.html

Git 多平台换行符问题(LF or CRLF)

目前,在开发中,使用 Git 作为版本管理工具还是比较流行的,大量的开源项目都在往 Github 迁移。Windows 上有 Git bash 客户端,基于 MinGW,有很多 GNU 工具可用,体验还不错。
在做完工作后,我尝试 git add .,想着这块工作可以告一段落了,而事实是:
$ git add .
fatal: CRLF would be replaced by LF ...

一脸懵逼,Google 一下吧,看看是什么原因。发现,这已经是一个非常经典的问题了:
早就听说过这个问题,总算是亲自踩到这个坑里了。
文本文件所使用的换行符,在不同的系统平台上是不一样的。UNIX/Linux 使用的是 0x0A(LF),早期的 Mac OS 使用的是 0x0D(CR),后来的 OS X 在更换内核后与 UNIX 保持一致了。但 DOS/Windows 一直使用 0x0D0A(CRLF) 作为换行符。
跨平台协作开发是常有的,不统一的换行符确实对跨平台的文件交换带来了麻烦。最大的问题是,在不同平台上,换行符发生改变时,Git 会认为整个文件被修改,这就造成我们没法 diff,不能正确反映本次的修改。还好 Git 在设计时就考虑了这一点,其提供了一个 autocrlf 的配置项,用于在提交和检出时自动转换换行符,该配置有三个可选项:
true: 提交时转换为 LF,检出时转换为 CRLF
false: 提交检出均不转换
input: 提交时转换为LF,检出时不转换
用如下命令即可完成配置:
#提交时转换为LF,检出时转换为CRLF
git config --global core.autocrlf true

#提交时转换为LF,检出时不转换
git config --global core.autocrlf input

#提交检出均不转换
git config --global core.autocrlf false

在 Windows 上,系统默认配置一般为 true。如果文件编码是 UTF8 并且包含中文字符,那最好还是把 autocrlf 设置为 false。否则,就会遇到我所遇到的问题,Git 尝试将 CRLF 转化为 LF 时失败,导致无法 add 。
如果把 autocrlf 设置为 false 时,那另一个配置项 safecrlf 最好设置为 ture。该选项用于检查文件是否包含混合换行符,其有三个可选项:
true: 拒绝提交包含混合换行符的文件
false: 允许提交包含混合换行符的文件
warn: 提交包含混合换行符的文件时给出警告
配置方法:
#拒绝提交包含混合换行符的文件
git config --global core.safecrlf true
#允许提交包含混合换行符的文件
git config --global core.safecrlf false
#提交包含混合换行符的文件时给出警告
git config --global core.safecrlf warn

到此,还并未解决我遇到的问题。实际上,我们有两种办法解决。
一种是将配置项改为如下的形式:
$ git config --global core.autocrlf false
$ git config --global core.safecrlf false

这种方式是不推荐的,虽然代码能被提交,但是项目中的文件可能会包含两种格式的换行符。而且会有如上提到的问题,文件被视为整个被修改,无法 diff,之所以使用版本控制工具,最重要的原因之一就是其 diff 功能。

另一种办法是,手动将文件的换行符转化为 LF,这可以通过编辑器来完成,大部分编辑器都可以将文件的换行符风格设置为 unix 的形式。也可以使用 dos2unix 转换工具来完成,Windows 上 Git bash 客户端自带了该工具。其他系统上也可以安装该工具,例如 Ubuntu 上安装:
sudo apt-get install dos2unix
有了该工具,可以批量的把项目中的文件都转化一遍:
find . -type f xargs dos2unix
或者
find . -type f -exec dos2unix {} +
到此,问题解决。go home!
参考文档:http://kuanghy.github.io/2017/03/19/git-lf-or-crlf

忽略git已经被提交的文件

可能大家在开发过程中都遇到过这样的情况,因为本地开发环境的差异产生了一些新的文件,比如Idea产生的项目文件,和关联文件、log。对于没有经验的新手或者在疏忽的情况下也可能把这些文件直接推送到了远程代码仓库。而后我们可能才想起来要在.gitignore中加入这个避免被提交到远程仓库,加上之后会发现根本没用了。

.gitignore不生效是因为我们误解了其用途,该文件只能作用于 Untracked Files,也就是那些从来没有被 Git 记录过的文件(自添加以后,从未 add 及 commit 过的文件)。之所以你的规则不生效,是因为那些 .log 文件曾经被 Git 记录过,因此 .gitignore 对它们完全无效。
所以面对问题我们可以做以下操作:
1. 从 Git 的数据库中删除对于该文件的追踪;
2. 把对应的规则写入 .gitignore,让忽略真正生效;
3. 提交+推送。
只有这样做,所有的团队成员才会保持一致而不会有后遗症,也只有这样做,其他的团队成员根本不需要做额外的工作来维持对一个文件的改变忽略。

需要注意的是,git rm –cached 删除的是追踪状态,而不是物理文件;如果你真的是彻底不想要了,你也可以直接 rm+忽略+提交。

git submodule的使用及常见问题

开发过程中,经常会使用一些通用的代码或者组件,但是公共代码库的版本管理是个麻烦的事情,但是submodule的使用可以很好地解决这个问题。

1、添加submodule,命令如下
git submodule add 仓库地址 路径
其中,仓库地址是指子模块仓库地址,路径指将子模块放置在当前工程下的路径。
注意:路径不能以 / 结尾(会造成修改不生效)、不能是现有工程已有的目录(不能順利 Clone)
命令执行完成,会在当前工程根路径下生成一个名为“.gitmodules”的文件,其中记录了子模块的信息。添加完成以后,再将子模块所在的文件夹添加到工程中即可。

2、使用submodule:
正常clone项目
git clone https://github.com/aaaa/bbbb
这个时候进入clone出来的目录,submodule的子目录下面是空的,默认不会把submodule一同checkout出来。
接着需要执行两条命令
git submodule init
git submodule update
这个时候submodule才会正常Checkout出来。

3、删除submodule
submodule的删除稍微麻烦点:首先,要在“.gitmodules”文件中删除相应配置信息。然后,执行“git rm –cached ”命令将子模块所在的文件从git中删除。
下载的工程带有submodule
当使用git clone下来的工程中带有submodule时,初始的时候,submodule的内容并不会自动下载下来的,此时,只需执行如下命令:
git submodule update –init –recursive
即可将子模块内容下载下来后工程才不会缺少相应的文件。

4、更新submodule的坑
submodule项目和它的父项目本质上是2个独立的git仓库。只是父项目存储了它依赖的submodule项目的版本号信息而已。如果你的同事更新了submodule,然后更新了父项目中依赖的版本号。你需要在git pull之后,调用 git submodule update来更新submodule信息。
这儿的坑在于,如果你git pull之后,忘记了调用 git submodule update,那么你极有可能再次把旧的submodule依赖信息提交上去。对于那些习惯使用 git commit -a的人来说,这种危险会更大一些。所以建议大家:
1.git pull之后,立即执行git status, 如果发现submodule有修改,立即执行git submodule update
2.尽量不要使用 git commit -a, git add命令存在的意义就是让你对加入暂存区的文件做二次确认,而 git commit -a相当于跳过了这个确认过程。
更复杂一些,如果你的submodule又依赖了submodule,那么很可能你需要在git pull 和 git submodule update之后,再分别到每个submodule中再执行一次git submodule update,这里可以使用 git submodule foreach命令来实现: git submodule foreach git submodule update

5、修改submodule的坑
有些时候你需要对submodule做一些修改,很常见的做法就是切到submodule的目录,然后做修改,然后commit和push。
这里的坑在于,默认git submodule update并不会将submodule切到任何branch,所以,默认下submodule的HEAD是处于游离状态的(‘detached HEAD’ state)。所以在修改前,记得一定要用git checkout master将当前的submodule分支切换到master,然后才能做修改和提交。

如果你不慎忘记切换到master分支,又做了提交,可以用cherry-pick命令挽救。具体做法如下:
1.用 git checkout master 将HEAD从游离状态切换到 master 分支, 这时候,git会报Warning说有一个提交没有在branch上,记住这个提交的change-id(假如change-id为 aaaa)
2.用 git cherry-pick aaaa 来将刚刚的提交作用在master分支上
3.用 git push 将更新提交到远程版本库中