afrag  
记录学习和成长的历程
日历
<2024年5月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
统计
  • 随笔 - 9
  • 文章 - 5
  • 评论 - 2
  • 引用 - 0

导航

常用链接

留言簿

随笔分类

随笔档案

文章档案

搜索

  •  

积分与排名

  • 积分 - 9914
  • 排名 - 2390

最新评论

阅读排行榜

评论排行榜

 

转发自 道法术器的博客文章——Git教程


1 Git是什么

Git是一个分布式的版本控制系统。

注意上面的”分布式的“这个限定词,这一点是Git和CVS,VSN等其他版本控制系统最大的分别。

集中式版本控制系统最大的毛病就是必须联网才能够工作,也就是各个客户端必须连接到中央仓库才能够工作。

分布式的版本控制系统中,不必有中央仓库,每个人的电脑上都有一个完整的版本库。我们可以在自己的电脑上修改、提交。如果两个人需要交换修改的代码,则需要将自己的修改推送给对方。

在实际的应用中,为了方便一个团队之间交换代码修改,我们还是有一个中央仓库。注意这个仓库是提供便利的,并不是没有它就不行(虽然会很不方便)。

这个教程的主要目的是让大家了解使用Git进行版本管理的一些基本操作和逻辑,让大家对Git的使用有一个基本的了解。如果想要详细了解每个命令的用法,或者要了解一些“高级”的命令,建议大家还是去看看Git的手册。

2. 创建本地仓库

关于Git的安装,这里就不讲了,网上有很多文章,大家根据自己的系统,找一篇文章照着做就可以了。这里主要讲解Git的一些概念和操作。

前面已经说过,Git是一个分布式的版本控制系统,每个人的电脑上都有一个完整的版本库,我们把这个版本库称为本地仓库。所有其他的仓库,无论是同事电脑上的,还是中央服务器的,我们都称为远程仓库。

创建本地仓库有两种方式,第一种是我们从零开始创建,第二种是从远程仓库克隆一份。

我们可以把版本仓库理解为一个目录,其中保存了我们所有让git管理的文件。Git能够跟踪这些文件的修改、删除,能够追踪这些文件的历史,并且可以还原。

2.1 从头开始创建本地仓库

从头开始创建本地仓库非常简单。

  1. 选择你想要创建版本仓库的地方,创建一个空的目录。
  2. 进入该目录,在命令行中执行git init命令,把这个目录变成Git可以管理的版本仓库。
  3. 把文件添加到Git仓库中,一共需要三步:
    • 把文件放到Git仓库的目录中,无论是新创建一个文件,还是从其他地方copy一个文件。也可以放在子目录中
    • 运行git add <filepath>,告诉Git把文件添加到仓库中。
    • 使用git commit命令,告诉Git把改变提交到仓库。

2.2 从远程仓库克隆

相比从头开始创建本地仓库,我们做得更多的是从远程仓库克隆一份。理论上来说远程仓库可以是任何一台电脑上的仓库,但是通常来说,我们会在github,或自己搭建的git服务器上创建中央仓库,团队中的所有成员都从该仓库上克隆,并在后续将自己的修改上传上去,或者从中央仓库读取其他同事的修改。因此我们这样只讲怎样中央仓库克隆到本地仓库。

  1. 创建本机的ssh-key,在类linux系统上,只需要运行ssh-keygen命令就可以了。
  2. 将ssh-key的公钥id_rsa.pub上传到github或者Git服务器上,具体的步骤要参考你们是要的Git服务器是哪一个。
  3. 执行命令git clone <版本库的网址> [本地目录名]来创建本地仓库。如果不指定目录,则会在当前目录下创建一个目录,名字与远程版本库的名字相同。

上面的命令中,版本库的网址中是可以带上协议、用户名等信息的。Git支持http(s),ssh,git,ftp(s),本地文件协议等不同的协议。

3 使用本地仓库进行版本管理

3.1 工作区和暂存区

为了能够更好的使用本地仓库进行版本管理,我们先了解一下工作区和暂存区的概念。

无论我们是从远程仓库克隆,还是使用git init命令新建一个本地仓库,最终在本地仓库的目录中,都有一个隐藏的子目录.git

本地仓库目录是我们的工作区,我们要管理的文件都在这个目录中。但是.git目录不是工作区,这个是版本库。我们不要手动去修改这个目录中的内容。

在这个版本库中,保存了很多的东西,其中最重要的是暂存区,以及分支。关于分支的概念,我们以后再解释。目前来说,我们也只有一个分支,也就是Git自动为我们创建的master分区。还有一个指向master分区的指针,叫做HEAD。

前面我们已经提到过,在添加一个文件到Git本地仓库中的时候,需要先执行git add命令,然后执行git commit命令。其中git add命令就是将变化的代码从工作区拷贝到暂存区。 git commit则是将代码从暂存区提交到版本库。一旦提交后,暂存区会被清空。

所以,我们其实可以执行多次git add之后,执行一次git commit,一次性的将多次修改的结果进行提交。

可以使用git status来查看有哪些修改没有add到暂存区,有哪些修改在暂存区中,没有提交到版本库。

3.2 提交版本和查看历史

我们修改了一个或多个文件之后,可以使用git addgit commit命令将修改提交到版本库。在使用git commit命令时,可以使用-m选项来添加备注。该命令会得到类似下面的返回:

1
2
3
$ git commit -m "Fixed the type mistake in readme.txt"
[master 0db896a] Fixed the type mistake in readme.txt
1 file changed, 1 insertion(+), 1 deletion(-)

这个命令返回的信息中,包含了提交的分支,本次提交自动生成的版本号(commit id),输入的备注,以及修改内容的统计信息。 注意这里的版本号可能不是完整的版本号,而只是版本号的前几位

我们可以看到,Git的版本号是一个用十六进制表示的随机数字,这是为了避免各自在本地数据库中提交时版本号的冲突。

Git会记录下我们所有的版本历史,可以使用git log命令来查看。

3.3 版本回退

版本回退是版本管理系统最基本的功能。如果不能回退,要这系统何用。

要回退,首先需要知道回退到哪个版本。可以使用git log命令来查看版本历史信息。然后使用git rest --hard <版本号>来进行回退。这里的版本号不需要输全,只需要输入前面几位,能够唯一确认一个版本就可以了。

使用git log的时候,我们看到的会是一堆密密麻麻的信息。如果我们使用图形化的Git工具,就能够很直观的看到Git是将所有的提交按照时间顺序串成了一条线,在这个时间线上每一个点就是一次提交,每一次提交都有很多的信息,比如提交人是谁,版本号是多少,备注信息是什么。

这下就体现了提交的时候使用备注的好处了,我们可以通过备注知道每一次修改的原因和内容,这样才知道需要回退到哪个版本。

如果我们从今天的版本回退到了昨天的版本,还能不能回到今天的版本呢?可以的,但是前提要是你还记得今天的版本的版本号。如果我们前面的窗口没有关闭,可以从git log命令的输出中找到今天的版本号。如果已经关闭了,则可以重新使用git reflog命令查看我们执行的每一条命令。这样就可以使用git rest恢复到今天的版本了。

3.4 管理修改

前面讲过了工作区和暂存区。因此Git相比其他的版本管理系统多了一层。这一层有什么作用呢?

我们在提交的时候,提交的是暂存区中的内容,而不是工作区中的内容。因此,我们在修改的时候,可以多次将中间代码添加到暂存区。我们既不需要产生大量的中间版本号和提交记录,也可以保证不会因为后续的修改弄丢了前面的代码。我们可以大胆的试错,发现不合适了,很容易回滚到前一个版本。

我们可以使用git checkout -- <file>来撤销工作区的修改。这个时候,如果暂存区中有还没有提交的修改,那么会使用暂存区中的内容覆盖工作区中的内容。如果暂存区中的修改都已经提交了,那么会用版本库中的内容覆盖工作区中的内容。 注意--不能省略,省略了就变成切换分支的命令了。

当然,删除文件也是一种修改。一般我们把文件从Git的目录中删除之后,Git会检测到,并且使用git status命令能够看到。这个时候,如果确实要删除文件,可以使用git rm <file>来告诉暂存区我们要删除这文件,然后用git commit命令提交修改,从版本库中删除文件。

如果是误删除的,可以使用git checkout -- <file>命令从版本库中恢复文件。

4 分支管理

前面我们提到了,从git log可以看到我们所有的提交的历史记录,并且按照先后顺序串成了一条线,这条线就是一个“分支”。只不过我们现在只有一个分支,就是创建本地仓库时,默认为我们创建一个master分支,通常称为主分支。
这个分支目前的情况如下面的示意图:

分支允许我们创建另外一条”路径“,同时管理两个版本的代码。例如,我们完成了1.0版本,进入2.0版本的开发。但是我们同时需要进行1.0版本的维护,修复bug。这个时候,我们就可以同时维护1.0分支和2.0分支。

4.1 创建与合并分支

分支就是提交记录组成的一个时间线,因此一个分支可以表示如下:

master分支示意图

分支master指向该分支中的最后一次提交。然后Git还会用HEAD指向master,表明当前的分支是master分支。每次提交,master都会向前移动一步,一直指向最新的提交,master分支的时间线也越来越长。

master分支示意图

如果我们需要创建一个新的分支,例如从当前最新提交创建一个dev分支,那么其实只是创建了一个名为dev的指针,指向最新的提交,同时将HEAD修改为指向dev。使用的命令是git branch dev 和 git checkout dev,得到的结果如下图所示:

master分支示意图

因此,在Git中创建分支非常的快,因为只是创建一个指针,然后修改做一个指针。
我们可以使用git branch命令来查看当前的分支,该命令会列出所有的分支,并在当前使用的分支前使用*来标注。

从现在开始,所有的提交操作都是针对dev分支了,因此,如果做了一次新的提交,dev会向前移动一步,但是master指针不变,如下图:

master分支示意图

这个时候,要合并分支也很容易,只需要先使用git checkout master命令切换到master分支,然后在master分支中执行git merge dev来讲dev合并到当前分支就可以了。实际Git要做的只是将master指针指向最新的提交就可以了(这种合并叫做fast-forward,快速向前。当然并不是所有的合并都这么简单,我们后面会讲到)。如下图所示:

master分支示意图

当一个分支完成了历史使命的时候,我们可以将其删除。例如,如果我们要删除dev分支,只需要执行git branch -d dev就可以了。删除后就只剩下master分支了。

4.2 解决分支之间的冲突

前面介绍的合并分支是最简单的一种情况,当然现实世界通常不会这么简单。现实中常见的情况时在两个分支上我们都有提交,如下图所示:

master分支示意图

这个时候,如果我们执行git merge dev命令,就无法执行fast-forward合并,Git会试图把各自的修改合并起来。如果在dev分支上和在master分支上没有修改相同的文件,Git能够自动进行合并。如果有修改相同的文件,Git就处理不了,会告诉我们有冲突,需要我们手动解决冲突之后再提交。 使用git status命令可以看到冲突的文件。

如果我们打开冲突的文件,会看到类似下面的内容:

1
2
3
4
5
6
......
<<<<<<< HEAD
HEAD中的内容(当前分支中的内容)
==========
dev分支中的内容。
>>>>>>> dev

对于每一个冲突,Git中会用<<<<<<<,=======,>>>>>>>来标记不同分支中的内容。我们需要将这一段修改为合适的内容,并再次提交。如下图所示:

master分支示意图

5 通过远程仓库合作

一个团队中有多个开发人员,他们一起协助来完成软件开发的工作。因此,不可能大家都只在自己的本地仓库中修修改改。通常来说,我们会配置一个”中央仓库“,大家都把自己的本地仓库中的代码要提交到中央仓库。

5.1 日常的工作流程

在软件开发过程中,我们首先是从中央仓库下载代码到本地仓库,这个操作在前面的第2.2节中已经描述过了。

有了本地仓库之后,日常的工作流程通常上:

  1. 从中央仓库拉取最新的代码。
  2. 根据开发任务,在工作区中修改和测试代码。
  3. 将代码提交到本地仓库。
  4. 重复2和3,直到任务开发和测试完成。
  5. 将代码推送到中央仓库。

5.2 从中央仓库拉取代码

从中央仓库拉取代码,使用的是git pull命令。 git pull的时候,会试图自动合并本地与远程之间的冲突,如果无法自动合并,则需要手动解决冲突。解决的方式和我们前面第4.2节中说过的方法一样。

还有一种情况,是从中央仓库抓取一个新的分支。我们使用git clone命令从中央仓库克隆的时候,获取的是master分支。这个时候,中央仓库就是我们的远程仓库,默认的名称是origin。我们可以使用git remote -v来查看远程仓库的详细信息。

如果要从中央仓库获取一个新的分支,比如dev分支,则需要先执行git checkout -b <本地分支名称> <远程库名称>/<远程分支名称> 命令来获取新的分支。

5.3 推送分支到中央仓库

我们在本地做的修改,需要上传到中央仓库,使用的是git push <远程库名称> <分支名称>命令。这个过程被称为推送。

如果从上次下载代码到本次推送之间,远程仓库上的代码没有变化,那么本次推送就没有问题。但是通常来说,在团队合作的时候,在这段时间内会有其他人推送了新的代码到中央仓库。这个时候使用git push命令就会报错,并且会提示我们使用git pull获取最新的代码,合并代码后(无论是自动还是手动)再推送。

6 其他

6.1 标签管理

在Git中,每一次提交都有一个版本号,但是这个版本号对人类很不友好。没有任何含义,并且又长又难记。我们在日常的交流中无法使用这个版本号。

这种情况下,引入了tag的功能。Tag就是一个标签,我们可以在重要的提交版本上添加一个tag,这个tag其实就是指向这一次提交的版本。我们可以给tag赋予有意义的名称,例如V1.0这样的,这样方便我们日常的交流,和将来的查找。

在Git中打标签非常简单,执行git tag <tagname>就可以了。(我们暂时不讨论分支,后续的分支管理中在讨论。)

执行上面的命令的时候,默认是将标签打在分支的最后一次提交上。如果要将标签打在其他的提交上,可以使用git tag <tagname> <版本号>的方式来执行命令。

可以使用git tag命令来查看所有的标签,使用git show <tagname>来查看指定标签的信息。

我们创建的标签都是在本地,如果需要将标签推送到远程仓库,则可以使用git push <远程仓库名> <标签名>命令来推送。也可以一次性的将所有标签推送到远程,只需要使用命令git push <远程仓库名> --tags命令。

标签是不能移动和修改的,但是可以删除。因此,如果标签打错了,修改的方式就是删除错误的标签,重新创建正确的标签。git tag -d <name>就是用来删除指定名称的tag的命令。

如果标签已经被推送到了远程仓库,则需要先删除本地标签,然后使用如下形式的push命令来删除远程仓库中的标签。

git push <远程仓库名> :refs/tags/<标签名>

6.2 git stash操作

有些时候,我们在工作的中途,会接到一些紧急的任务,比如生产环境的bug修复。这个时候我们手头的工作才做了一半,既不能提交,也不能丢弃。但是我们又需要切换到另外的分支中来处理紧急任务,该怎么办?
这个时候就可以用到git stash操作了。

git stash命令会将工作区的修改“隐藏”起来。如下例所示:

1
2
3
$git stash
Saved working directory and index state WIP on master: 3793459 add new line in readme.txt
HEAD is now at 3793459 add new line in readme.txt

这个命令会将工作区中的修改创建一个存根,并保存到堆栈中。我们可以使用git stash list命令来查看堆栈中的存根。这个时候我们再用git status命令来查看的话,就会发现在工作区中没有新的修改。这个时候我们就可以切换分支,完成紧急任务。

当紧急任务完成之后,我们在切换回原来的分支,这个时候可以使用git stash pop ,可以从堆栈中弹出修改并应用到工作区,这样我们就可以继续以前的工作了。

6.3 git配置

  • 配置用户信息: 使用命令git config --global user.name="<用户名>"来配置用户的名字。使用命令git config --global user.name="<邮箱地址>"来配置用户的email地址。这两个命令配置的是本机的全局配置。

  • 忽略特殊的文件:在有些时候,我们希望不要提交工作区中的某些文件,比如java编译时生成的class文件等。我们可以将不希望提交的文件的名称放在Git工作区的根目录下的.gitignore文件中,Git就会忽略这些文件。该文件中可以使用通配符。

posted on 2020-07-23 08:15 afrag 阅读(62) 评论(0)  编辑  收藏

只有注册用户登录后才能发表评论。


网站导航:
 
 
Copyright © afrag Powered by: 博客园 模板提供:沪江博客