Linux 内核开源项目有着为数众多的参与者。 绝大多数的 Linux 内核维护工作都花在了提交补丁和保存归档的繁琐事务上(1991-2002年间)。 到 2002 年,整个项目组开始启用一个专有的分布式版本控制系统 BitKeeper 来管理和维护代码。
到了 2005 年,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了 Linux 内核社区免费使用 BitKeeper 的权力。 这就迫使 Linux 开源社区(特别是 Linux 的缔造者 Linus Torvalds)基于使用 BitKeeper 时的经验教训,开发出自己的版本系统。 他们对新的系统制订了若干目标:
自诞生于 2005 年以来,Git 日臻成熟完善,在高度易用的同时,仍然保留着初期设定的目标。 它的速度飞快,极其适合管理大项目,有着令人难以置信的非线性分支管理系统。
版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。
许多人习惯用复制整个项目目录的方式来保存不同的版本,或许还会改名加上备份时间以示区别。这么做唯一的好处就是简单,但是特别容易犯错。
为了解决这个问题,人们很久以前就开发了许多种本地版本控制系统,大多都是采用某种简单的数据库来记录文件的历次更新差异。
其中最流行的一种叫做 RCS,现今许多计算机系统上都还看得到它的踪影。 RCS 的工作原理是在硬盘上保存补丁集(补丁是指文件修订前后的变化);通过应用所有的补丁,可以重新计算出各个版本的文件内容。
如何让在不同系统上的开发者协同工作?
于是,集中化的版本控制系统(Centralized Version Control Systems,简称 CVCS)应运而生。 这类系统,诸如 CVS、Subversion 以及 Perforce 等,都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。 多年以来,这已成为版本控制系统的标准做法。
每个人都可以在一定程度上看到项目中的其他人正在做些什么。 而管理员也可以轻松掌控每个开发者的权限,并且管理一个 CVCS 要远比在各个客户端上维护本地数据库来得轻松容易。缺点是中央服务器的单点故障。 如果宕机一小时,那么在这一小时内,谁都无法提交更新,也就无法协同工作。 如果中心数据库所在的磁盘发生损坏,又没有做恰当备份,毫无疑问你将丢失所有数据——包括项目的整个变更历史,只剩下人们在各自机器上保留的单独快照。
本地版本控制系统也存在类似问题,只要整个项目的历史记录被保存在单一位置,就有丢失所有历史更新记录的风险。
分布式版本控制系统(Distributed Version Control System,简称 DVCS)在这类系统中,像 Git、Mercurial、Bazaar 以及 Darcs 等,客户端并不只提取最新版本的文件快照, 而是把代码仓库完整地镜像下来,包括完整的历史记录。
这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。
Git 和其它版本控制系统(包括 Subversion 和近似工具)的主要差别在于 Git 对待数据的方式。 从概念上来说,其它大部分系统以文件变更列表的方式存储信息,这类系统(CVS、Subversion、Perforce、Bazaar 等等) 将它们存储的信息看作是一组基本文件和每个文件随时间逐步累积的差异 (它们通常称作 基于差异(delta-based) 的版本控制)。
Git 更像是把数据看作是对小型文件系统的一系列快照。在 Git 中,每当你提交更新或保存项目状态时,它基本上就会对当时的全部文件创建一个快照并保存这个快照的索引。 为了效率,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个 快照流。
Git 中所有的数据在存储前都计算校验和,然后以校验和来引用。 这意味着不可能在 Git 不知情时更改任何文件内容或目录内容。 若你在传送过程中丢失信息或损坏文件,Git 就能发现。
Git 用以计算校验和的机制叫做 SHA-1 散列(hash,哈希)。 这是一个由 40 个十六进制字符(0-9 和 a-f)组成的字符串,基于 Git 中文件的内容或目录结构计算出来。 SHA-1 哈希看起来是这样:
24b9da6552252987aa493b52f8696cd6d3b00373
Git 中使用这种哈希值的情况很多,你将经常看到这种哈希值。 实际上,Git 数据库中保存的信息都是以文件内容的哈希值来索引,而不是文件名。
Git 有三种状态,你的文件可能处于其中之一:
这会让我们的 Git 项目拥有三个阶段:工作区、暂存区以及 Git 仓库。
工作区是对项目的某个版本独立提取出来的内容。 这些从 Git 仓库的压缩数据库中提取出来的文件,放在磁盘上供你使用或修改。
暂存区是一个文件,保存了下次将要提交的文件列表信息,一般在 Git 仓库目录中。 按照 Git 的术语叫做“索引”,不过一般说法还是叫“暂存区”。
Git 仓库目录是 Git 用来保存项目的元数据和对象数据库的地方。 这是 Git 中最重要的部分,从其它计算机克隆仓库时,复制的就是这里的数据。
在你开始使用 Git 前,需要将它安装在你的计算机上。 即便已经安装,最好将它升级到最新的版本。 你可以通过软件包或者其它安装程序来安装,或者下载源码编译安装。
安装方法有三种,如下所示:
我们以第一种方式进行安装,点击安装好的.exe
文件,除了修改存放地址外,都可默认选项(一直点击Next),直至最后一步,安装完毕。我们可以点击鼠标右击(也可以在开始/菜单中搜索),可以看到快捷菜单里面有一个图形界面的 Git GUI菜单和Git Bash命令菜单,如图所示:
我们点击Git Bash,会弹出 Git 命令窗口,你可以在该窗口进行 Git 操作。
有两种方式:通过yum安装、通过.gz
文件源码安装。
非常简单的安装方式,输入命令:
yum -y install git
等待安装
看到Complete,表示安装完成,执行命令,查看Git版本:
git --version
yum安装的Git在/usr/libexec/git-core
目录下,如图所示:
yum remove git
.gz
源码安装有人觉得从源码安装 Git 更实用,因为你能得到最新的版本。 二进制安装程序倾向于有一些滞后,当然近几年 Git 已经成熟,这个差异不再显著。
如果你想从源码安装 Git,需要安装 Git 依赖的库:autotools、curl、zlib、openssl、expat 和 libiconv,命令如下所示:
yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc perl-ExtUtils-MakeMaker
我们可以通过命令wget
下载,命令如下所示:
wget https://www.kernel.org/pub/software/scm/git/git-2.37.3.tar.gz
由于网速问题,我们先下载到本地,在上传到服务器
然后解压文件,命令如下所示:
tar -zxvf git-2.37.3.tar.gz
解压后,如图所示
进入文件
cd git-2.37.3/
执行编译命令
make prefix=/usr/local/git all
安装Git文件路径
make prefix=/usr/local/git install
vim /etc/profile
底部加上命令
export PATH=$PATH:/usr/local/git/bin
刷新环境变量
source /etc/profile
查看Git版本,命令如下所示:
$ git --version
安装完 Git 之后,要做的第一件事就是设置你的用户名和邮件地址。 这一点很重要,因为每一个 Git 提交都会使用这些信息,它们会写入到你的每一次提交中,不可更改:
$ git config --global user.name "your name"
$ git config --global user.email "your email"
配置完成后,我们也可以查看已有配置:
$ git config --list
$ git config user.name
除此之外,其它配置都可有可无,有兴趣的可以自己去了解(windows安装的git自带了很多配置)。
通常有两种获取 Git 项目仓库的方式:
将尚未进行版本控制的本地目录转换为 Git 仓库。
(1)初始化Git仓库
我们先进入需要作为 Git 仓库的目录(以简单的Maven项目为例),然后执行命令,进行初始化操作
$ git init
可能会出现Reinitialized existing 的错误。
执行删除git命令,即可。
$ rm -rf .git
这个时候,我们仅仅是做了一个初始化的操作,你的项目里的文件还没有被跟踪。
(2)将项目添加到存储库
先用命令告诉 Git 开始对这些文件进行跟踪(如果以后创建新文件都需要先进行此操作)。
$ git add 项目
然后将项目提交到存储库中(确保文件一定被跟踪后才能提交,每次提交通过-m
追加备注内容,内容可以为空)。
$ git commit -m "message"
现在,你已经得到了一个存在被追踪文件与初始提交的 Git 仓库。
从其它服务器 克隆 一个已存在的 Git 仓库。
以gitee为例,创建一个项目。
然后我们把它克隆到本地,点击黄色按钮,复制克隆地址或直接点击下载。
$ git clone https://访问地址/项目.git
注:中途会提示你输入用户名和密码。
如果你觉得麻烦,也可以再克隆时指定用户名密码,命令如下所示:
$ git clone https://用户名:密码@访问地址/项目.git
如果用户名是邮箱需要将@ 符号需要转义成 %40,避免冲突。
项目拉取好后,本地会生成一个.git
的隐藏文件,以win10为例,需要勾选隐藏项目,才能显示,如图
点击进入目录,有很多配置信息,我们打开config文件,里面包含了远程仓库地址等信息,再提交代码或拉取代码时,自动访问此项目远程地址,不需要追加地址。
我们可以通过命令查看当前项目的状态。
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean
这说明当前所在分支相当干净,没有出现任何处于未跟踪状态的新文件。
现在,让我们在项目下创建一个新的 README 文件。
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
(use "git add ..." to include in what will be committed)
README
nothing added to commit but untracked files present (use "git add" to track)
我们可以看到提示我们使用“git add
来包含将要提交的内容(跟踪)。
如果你觉得git status
命令有些繁琐,使用 git status -s
命令或 git status --short
命令,你将得到一种格式更为紧凑的输出。
$ git status -s
M README
MM Rakefile
A lib/git.rb
M lib/simplegit.rb
?? LICENSE.txt
??
标记。A
标记。M
标记。例如,上面的状态报告显示: README 文件在工作区已修改但尚未暂存,而 lib/simplegit.rb 文件已修改且已暂存。 Rakefile 文件已修改,暂存后又作了修改,因此该文件的修改中既有已暂存的部分,又有未暂存的部分。
Git 非常聪明,它会推断出究竟发生了什么。
$ git mv file_from file_to
运行 git mv
就相当于运行了下面三条命令:
$ mv README.md README
$ git rm README.md
$ git add README
在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史。
$ git log
按时间先后顺序列出所有的提交,最近的更新排在最上面。 正如你所看到的,这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明。
其中一个比较有用的选项是 -p
或 --patch
$ git log -p
它会显示每次提交所引入的差异该选项除了显示基本信息之外,还附带了每次提交的变化。 当进行代码审查,或者快速浏览某个搭档的提交所带来的变化的时候,这个参数就非常有用了。
你也可以为 git log
附带一系列的总结性选项。 比如你想看到每次提交的简略统计信息,可以使用 --stat
选项:
$ git log --stat
可以看到列出所有被修改过的文件、有多少文件被修改了以及被修改过的文件的哪些行被移除或是添加了。 在每次提交的最后还有一个总结。
如果你只想看到校验和的信息,可以使用git log --pretty=oneline
命令
Git 十分智能,你只需要提供 SHA-1 的前几个字符就可以获得对应的某一次的提交记录。
$ git show <SHA-1>
有可能想要撤消某些操作。注意,有些撤消操作是不可逆的。 这是在使用 Git 的过程中,会因为操作失误而导致之前的工作丢失的少有的几个地方之一。
例如,你新建文件或者修改文件,你想重暂存文件中恢复, git status
命令提示了你:
在 “Changes to be committed” 文字正下方,提示使用git restore --staged
来取消暂存(可能因为低版本提示略有不同,低版本可以使用git reset HEAD
当然高版本也是可以兼容低版本命令的 )。
如果你想把已经修改,但是未提交到暂存区的文件,恢复成最后一次提交的样子,git status
也告诉了你应该如何做。
在 “Changes to be committed” 文字正下方,提示使用git restore
来取消暂存(可能因为低版本提示略有不同,低版本可以使用git checkout --
,当然高版本也是可以兼容低版本命令的 ),你会发现与取消暂存文件的命令是一样的,只不过少了--staged
。
然后我们再来查看文件的状态,已经变回原状态了。
实际日常开发中,是一个团队的编码,所以为了方便测试、上线,你需要拉取服务器上的代码或将你的代码上传到远程仓库。
$ git pull <remoteURL> <branch>
如果直接执行git pull
命令,会直接拉取你当前分支下的代码。
$ git push <remoteURL> <branch>
如果直接执行git push
命令,会直接上传你当前分支下的代码。
Git 可以给仓库历史中的某一个提交打上标签,以示重要。
$ git tag <name>
$ git tag
有了标签,我们可以对过去的提交打标签,这样方便我们做一些提交操作。
假设我们使用标签发布到远程
$ git push origin <tag>
要删除掉你本地仓库上的标签,可以使用命令 git tag -d
使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线。 在很多版本控制系统中,这是一个略微低效的过程——常常需要完全创建一个源代码目录的副本。对于大项目来说,这样的过程会耗费很多时间。
$ git branch <branchName>
你仍然在 master 分支上。 因为 git branch
命令仅仅 创建 一个新分支,并不会自动切换到新分支中去。
我们可以通过命令查看当前项目有哪些分支
$ git branch
根据开发需求,你可以切换到不同的分支上(为了不让你当前分支的努力不白费,请再切换分支时,保证当前分支的干净,或许你可将你的代码暂存或提交)
$ git checkout <branchName>
第一次切换分支需要加上 -b
命令
$ git checkout -b <branchName>
那么,Git 又是怎么知道当前在哪一个分支上呢? 也很简单,它有一个名为 HEAD 的特殊指针。我们可以通过命令查看当前指针指向的位置。
$ git log --oneline --decorate
切换到新的分支后,当前指针指向新分支,master分支和新分支同时指向一个校验和。
当新分支提交代码后,新的分支将指向新的校验和。
如果你切换分支后,指针也会跟随分支进行变化
如果分支不需要了,可以删除分支
$ git branch -d <branchName>
日常开发中,使用分支进行代码编写或维护操作,测试正常没问题后,需要合并到master分支。
$ git merge <branchName>
注意:合并分支是指当前分支下合并其它分支操作,比如:将其它分支代码合并到master分支。
如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们。
假设:现在再master分支上修改代码后提交、然后再其它分支上对同一个代码修改并提交,执行合并操作,如下所示
我们打开有问题文件
HEAD表示所指的版本,=======
上半部分表示当前所在分支的代码,下半部分表示其它分支修改代码>>>>>>>
指向其它分支。你必须选择使用由 =======
分割的两部分中的一个,或者你也可以自行合并这些内容。
解决了所有文件里的冲突之后,对每个文件使用 git add
命令来将其标记为冲突已解决。 一旦暂存这些原本有冲突的文件,Git 就会将它们标记为冲突已解决。
当然会有很多图形化工具,方便你的操作,后续讲解。
前面讲述的分支操作,都是本地操作,如果你尝试通过本地分支,push
代码到远程仓库,就会报错,如图所示:
当你想要公开分享一个分支时,需要将其推送到有写入权限的远程仓库上。 本地的分支并不会自动与远程仓库同步——你必须显式地推送想要分享的分支。
然后本地使用命令,查看远程分支的记录。
$ git fetch
使用命令查看所有分支。
$ git branch -a
开发过程中,分支提交代码之前,为了防止代码被覆盖的情况,需要先拉取其它分支的代码,一般都是拉取已经合并的master分支代码
$ git pull origin <branch>
切换到远程分支上,执行git push
命令(只要文件进行add
命令暂存后,无论再那个分支上上传都可以,前提是你得有上传权限)
注:当你使用远程分支时,你本地分支的操作权限移交给了远程操作,本地master分支合并、删除分支操作,并不会影响远程仓库。
如果你需要合并到master就需要再Gitee上提交合并申请,就像这样:
如果你再当前分支已经工作了,此时新的需求让你切换当另一个分支进行工作,你不想为了还未完成的工作创建一次提交,最好的办法贮藏。
贮藏(stash)会处理工作目录的脏的状态——即跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上, 而你可以在任何时候重新应用这些改动(甚至在不同的分支上)。
比如:现在需要切换分支,但是当前分支下有更改的文件。
$ git stash
此时,你可以切换分支并在其他地方工作;你的修改被存储在栈上。
$ git stash list
$ git stash apply <name>
恢复完后,为了以后使用时不混淆,可以将此贮藏删除
$ git stash drop <name>
清理工作目录有一些常见的原因,比如说为了移除由合并或外部工具生成的东西, 或是为了运行一个干净的构建而移除之前构建的残留。
你需要谨慎地使用这个命令,因为它被设计为从工作目录中移除未被追踪的文件。 如果你改变主意了,你也不一定能找回来那些文件的内容。 一个更安全的选项是运行 git stash --all
来移除每一样东西并存放在栈中。
你可以使用 git clean
命令去除冗余文件或者清理工作目录
使用 git clean -f -d
命令来移除工作目录中所有未追踪的文件以及空的子目录。 -f
意味着“强制(force)”或“确定要移除。默认情况下,git clean
命令只会移除没有忽略的未跟踪文件。
为了更高效的开发工作,使用图形化软件来简化复杂的命令。有很多优秀的图形化工具:GitHub、TortoiseGit 等。有兴趣的可以去官网查看更多版本
IDEA基本上是大多数开发中经常使用的工具,下面讲解IDEA中使用Git。
左上角选择File→Settings打开设置窗口,找到Version Control下的Git选项
IDEA会自动检测出Git,点击Test按钮测试是否正确。
将项目clone下来后,引入到IDEA中。
如果你修改代码后,要发布到测试环境上去,选中文件后,右击选择Git->Commit Directory。
注:提交代码前,切记先拉取代码,防止冲突。
弹出你修改的文件展示框
点击Commit按钮,提交到本地仓库(点击下拉项,也可以选择Commit and Push选项提交并上传远程)。
如果提交本地后,可以通过Git->Repository->Push提交到远程仓库。
提交完成后我们可以查看自己的提交记录,也可查看别人的提交记录。
右击Git->Repository->Fetch可以更新日志和分支。
然后点击左下角Version Control(如果左下角没有,点左上角工具栏View->Tool Windows),可以看到记录。
防止冲突或解决异常,我们通常会拉取最新的代码来解决这些问题。
选择指定项目右击Git->Repository->Pull
弹出选择框,选择合并的分支。
如果有文件会告诉你修改了那些文件
没有文件,表明你本地已经是最新的
如果你再当前分支修改了代码,准备push到远程仓库时,出现了异常。
可以点击右边Accept Yours(接受你的)按钮或Accept Theirs(接受他的)按钮,完成覆盖;你也可以点击Merge按钮手动解决冲突。
如果你本地修改了某个文件,你在pull代码的时候,可能有重复的文件,这时候IDEA会提示你,保存后再操作
点击View them可以查看冲突的文件
解决方案有两种,一种是撤回到之前版本(如果你不小心改动了代码)。
另一种先Commit到本地仓库后再pull,这样保存你已经修改的代码。
IDEA可以灵活的切换分支。再工作栏VCS->Git->Branches查看有那些分支。
可以通过Git->Repository->Fetch拉取新的分支,新分支再Remote Branches列表下可以看到,Local Branches为本地分支,使用后的远程分支会纳入本地分支中。
我们也可以切换分支,选择你的项目,选择分支点击Checkout,即可切换分支。
除了IDEA外,Visual Studio Code简称VS Code,也是开发中常用到的软件。
不做过多的讲解,上传代码、拉取代码,解决冲突,这些功能大致差不多。
仓库管理系统:GitLab