Git简明教程

2014/02/11 Git

Git简明教程

Git是一个 分布式版本控制系统 ,而SVN、CVS等则为 集中式版本控制系统 。

分布式与集中式的区别

集中式版本控制系统的特点是需要远程服务器,如果无法连接远程服务器,我们所做的变更就无法提交。

而在分布式版本控制系统中,每个端都保存着仓库完整的信息,不用联网也可以查询到所有的变更历史、在本地进行commit、checkout等操作,仅当你需要将变更推送到远程仓库,才需要网络。

Git特性

  • 本地操作 大部分操作都在本地进行,不需要连网,本地保存着仓库的完整信息,如果远程仓库损坏,随时可以用本地仓库进行数据恢复。
  • 一个基于文件快照文件系统 Git与其他版本控制系统的其他不同之处在于Git对待数据的方式,Git就像一个小型的文件系统,在我们每次提交,对整个文件系统做一个快照。
  • 保证数据完整性 Git 中所有的数据在存储前使用哈希算法计算一个哈希值,只要内容一改变,哈希值就会发生,这样可以确保任何数据的改变都可以被Git察觉,进而保护数据的完整性。
  • 高效分支管理 一般的版本控制系统都支持分支管理,然而这些系统创建分支就是把整个系统复制一份而已,这对于大型项目来说,就很费时间了,而Git则完全不同,Git的分支是非常轻量级的,在Git中,分支的创建、删除、合并都非常快速,有时只是移动一个指针而已。

配置Git

刚开始使用Git时,需要配置一下Git环境,第一件事就是配置你的用户名和邮件地址,这里的用户名和邮箱地址会写入到你之后的每一次提交当中:

git config --global user.name "test"  
git config --global user.email "test@example.com"  

上面使用git config命令时,我们使用–global选项,表示该配置对所有的仓库都有效,实际上Git的配置有三个级别,对应的命令选项为–global,–system,–local,其中–local优先级最高,–system优化级最低。

  • –system 表示对系统上所有用户的所有仓库都有效,对应的修改会保存在/etc/gitconfig文件中,由于是系统级别的配置,因此需要管理员权限才能操作。
    git config --system user.name "test"  
    git config --system user.name "test@example.com"  
    
  • –global 表示对当前用户的所有仓库都有效,对应配置会保存在~/.gitcofnig或者~/.config/git/config文件中。
    git config --global user.name "test"  
    git config --global user.name "test@example.com"  
    
  • –local 对当前仓库有效,对应的配置会保存在项目的.git/config文件当中。
    git config --local user.name "test"  
    git config --local user.name "test@example.com"  
    

可以使用以下命令查看不同级别配置文件的位置及相关配置:

git config --list --show-origin  

Git基本操作流程

  • 生成或者克隆一个仓库
  • 在工作区中修改文件。
  • 将你想要下次提交的更改选择性地暂存,这样只会将更改的部分添加到暂存区。
  • 提交更新,找到暂存区的文件,将快照永久性存储到 Git 目录。
  • 将提交推送到远程仓库。

Git重要概念

  • 仓库(Repository)
  • 工作区(workspace)
  • 暂存区(Index,也称为索引)
  • 远程仓库(Remote)

怎么生成Git仓库

Git创建仓库有两种方式:

  • 在本地初始化
  • 克隆已经存在的远程仓库

本地初始化

git init demo  

执行完上面的命令之后,我们便在demo目录中初始化了一个仓库,这时候Git默认帮我们创建一个名称为master或者main的分支。

克隆已经存在的远程仓库

除了初始化空仓库之外,有时候我们需要参与一些已经进行的项目,这样的项目已经有存在的仓库了,这时候我们可以采用克隆的方式把远程仓库同步到我们本地目录,如:

git clone https://github.com/xxx/xxx  

默认情况下,会把远程版本库的名称作为克隆到本地目录的名称,我们也可以自定义本地目录的名称,如:

git clone https://github.com/xxx/xxx demo  

克隆后,默认也是进入一个master或者main的主分支,该分支会关联到我们克隆的远程服务器上的同名分支,我们也可以指定要克隆远程服务器的哪个分支:

git clone -b develop https://githbu.com/xxx/xxx demo  

区分工作区与仓库

经过上述步骤后,我们在demo目录中生成了一个仓库,这里有一点要注意的是,很多初学者往往把demo看作是仓库,其实demo目录下的.git才是仓库,而demo目录则是工作区。

工作区是我们操作文件的地方,我们添加、修改或删除文件都是在demo目录中进行的,而最终提交的数据是保存到.git目录中的,所以.git目录才是仓库,这个目录保留着整个仓库的所有信息。

区分工作区与暂存区

SVN或CVS的使用习惯是直接提交所修改的内容,而Git并不是这样的,在Git中,当我们在工作区对文件作出修改后,并不能直接提交,而是需要将文件添加到暂存区(Index或Stage),

将文件添加到暂存区

git add命令用于将文件添加到暂存区:

echo hello world >> README.md  
git add README.md  

除了一个一个地添加之外,git add 命令也支持将全部修改一次性添加到暂存区,如:

git add --all  
//或者  
git add .  

将文件从暂存区中撤消

对于添加到暂存区的文件,也可以执行撤消操作,比如我们想将上面添加到暂存区的文件撤回工作区,可以这样做:

git restore --staged README.md  

如何提交

把要提交的文件添加到暂存区后,如果想提交到仓库中,可使用git commit命令:

git commit  

执行上面的命令后,会弹出一个vim编辑器窗口,我们可以在这个窗口编辑提交说明,编辑完成后保存退出就完成了一次提交了。

当然,也可以在git commit命令跟上-m选项来附带说明文字,这样可以直接提交:

git commit -m '提交说明'  

虽然使用暂存区,可以让我们更好的挑选自己要提交的修改,但这样多一个步骤总是很烦人,所以Git支持在git add命令后跟上一个-a选项,这样可以直接跳过暂存区,直接将当前工作的修改提交到仓库中:

git commit -a -m "提交说明"   

不过,要注意的是,未跟踪的文件并不会直接被提交,还是需要添加到暂存区的。

Git仓库文件的状态

一个文件从创建到提交,经历了几个状态的变化,如下所示:

  • 刚创建的文件状态为Untracked,表示该文件还未被纳入Git的版本控制中。
  • git add命令会文件添加到暂存区,此时文件的状态为Staged。
  • 文件在暂存区被commit之后,状态则为Unmodified。
  • Unmodified状态的文件被修改后则状态变为Modified。
  • git rm命令会把文件从Unmodified变成Untracked。

远程仓库

到目前为止,我们的所有操作还是在本地进行的,当我们想把与仓库与别人共享,或者参与别人的项目时,这时候就需要连接远程仓库了。

查看远程仓库

如果想查看当前仓库关联到哪个远程仓库,可以使用下面的命令:

git remote -v  

如果我们使用git clone命令克隆一个远程版本库,Git会帮我们将该远程仓库命名为origin。

添加远程仓库

也可以自己添加当前仓库关联的远程仓库,比如下面我们添加一个名称为foo的仓库:

git remote add foo https://www.github.com/xxx/xxx  

向远程仓库推送 使用git push命令可以将本地的提交推送到远程仓库:

git push  

执行上面命令的话,会推送当前所有分支,推送到远程仓库中同名的分支下,当然我们也可以推送到指定的分支,比如我们指定推送到foo的develop分支上:

git push foo develop  

同步远程仓库

如果别人向远程版本库推送了变更,而我们想同步到本地的仓库,可以使用git fetch将远程仓库的最新变更抓取回来:

git fetch   

同样,也可以指定抓取哪个分支:

git fetch foo develop  

但是,执行git fetch后也只是把别人推送到远程仓库的变更拉回来而已,却并不会把变更合并到我们当前工作区,所以我们还需要自己手动执行合并命令:

git merge   

上面的命令没有指定合并哪个远程仓库分支,所以会默认合并当前分支所跟踪的远程分支,也可以指定合并哪个分支:

git merge foo develop  

如果觉得执行git fetch和git merge比较麻烦的话,也可以使用git pull命令,这个命令在抓取远程仓库数据后,会自动执行合并:

git pull foo develop  

Git分支

分支模型是Git的必杀技,也是Git最强大的地方,掌握Git分支的使用,也就是学到Git的精髓。

分支的本质就是一个引用(指针),是对提交(commit)的引用。 Git会使用SHA-1哈希算法给每次提交生成一个40位个哈希值(commit id),这个值是唯一的,只要我们修改文件再提交,产生的commit id都不一样。

每次的commit都包含一个指向前一个commit的指针,这些commit连起来就是一条我们提交的时间线了。 分支就是指向某个commit的指针,不同分叉代表不同的分支走向,不同分支也可以指向同一个commit。

Git仓库还有个叫HEAD的指针,HEAD指向当前工作区所在的分支,当我们切换分支时,HEAD就会指向我们所切换的分支,比如,我们把分支从master切换到develop,HEAD也随之改变。

创建分支

Git的分支管理功能命令为:git branch。

现在假设我们要在项目中开发一个新功能,按正常开发流程,我们需要基于master分支创建一个新的功能分支,在git branch命令后直接跟上分支名即可:

git branch feature/1

执行后,我们就创建了一个名为feature/1的分支。

切换分支

不过,在创建分支后,Git并没有帮我们切换到feature/1上,切换分支的命令为git checkout:

git checkout feature/1

git checkout也可以在分支不存在时,直接创建并切换,只要跟上-b选项即可:

git checkout -b feature/1

注意,git checkout命令跟上选项-b的话,如果要切换的分支已经存在的话,则会报错,此时要去掉-b选项。

切换到feature/1分支之后,新增一个文件并提交:

# 创建文件
echo "This is feature 1" > feature1.html
# 添加到暂存区
git add feature1.html
# 提交修改
git commit -m 'new file feature1.html'

查看分支

直接运行git branch命令可以查看当前仓库的分支:

 git branch
* feature/1
  master

输出结果中,有的分支名前面有*号,表示当前检出的分支,也是HEAD指针指向的分支。

跟上-v选项后,可以查看所有分支的最后一次提交的commit Id和提交时我们输入的说明文字:

git branch -v
* feature/1 69d618f new file feature1.html
  master    ac02f4c new file hello.html

–merged选项可以查看已经合并的分支:

git branch --merged

–no-merged选项可以查看未合并的分支:

git branch --no-merged

合并分支

在进行分支合并时,一般会出现三种情况:快进合并(Fast-forward)、正常分支合并和分支冲突;下面我们一一讨论。

快进合并

假设现在我们已经feature/1分支上完成了功能开发,那接下来就是将feature/1分支合并master上了,Git的分支合并命令为:git merge:

# 切换到master分支
$ git checkout master
# 将feature/1分支合并到master
$ git merge feature/1

Updating ac02f4c..69d618f
Fast-forward
 feature1.html | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 feature1.html

合并完成后,可以看到输出的信息有Fast-forward的提示文字,说明此次合并模式为:快进合并。因为feature/1分支是master分支的基础上创建的,两个分支之间没有出现分叉,因此Git在合并feature/1到master时,只需要将master指向feature/1分支指向的commit id即可,所以称为快进合并。

正常分支合并

为了更好模拟正常分支合并的情况,现在让我们回到feature/1分支未合并到master时的状态,然后再切到master分支,基于master分支创建feature/2分支 接着再将feature/1分支合并到master分上(快进合并),master分支和feature/2分支出现分叉,出现不同的走向。这时将feature/2合并到master,Git不会再采用快进合并的方式,而是将feature/2的提交与master的提交整合在一起,并产生一个新的提交

git merge feature/2

解决冲突

如果合并分支时出现冲突,处理方式也不一样的,现在我们假设feature/2分支与feature/1都修改了home.html文件的同一行,且feature/1已合并到master了,情况与上面的正常分支合并类似,不同的是feature/1与feature/2都改了同一个文件,并产生冲突

此时再将feature/2合并到master,Git会提示我们合并产生冲突了,需要手动解决:

$ git checkout master
$ git merge feature/2
Auto-merging hello.html
CONFLICT (content): Merge conflict in hello.html
Automatic merge failed; fix conflicts and then commit the result.

冲突的内容会同时出在文件里,此时如果我们用git status查看工作区状态,Git会显示both modified文本来提示该文件被两个分支改过:

$ git status
On branch master
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:   hello.html

no changes added to commit (use "git add" and/or "git commit -a")

打开文件,可以发现冲突的代码已被«««<和=======隔开了,有HEAD标签表示是当前分支master的修改(是从feature修改后合并过来的),=======下面的则是我们要合并的分支的代码。

Hello Git
<<<<<<< HEAD
This commit is from feature/1
=======
the commit is from feature/2
>>>>>>> feature/2

我们要自己决定冲突内容的去留,修改之后保存文件再commit即可,产生一个新的commit,此时master指向最新的commit。

$ git add .
$ git commit -m '合并冲突文件'

删除分支

对于已经合并到master的分支,可以删除了,删除分支同样用git branch命令,跟上-d选项与分支名即可:

$ git branch -d feature/1

对于还未合并的分支,使用-d选项无法删除,Git会提示该分支未合并,如果坚持要强制删除的话,使用-D选项进行强制删除:

$ git branch -D feature/2

查看提交历史

当我们在仓库中多次提交,创建多个分支后,你可能需要对目前整个仓库分支与提交点的关系进行梳理,也就是查看我们的提交历史:

 git log

上面命令未跟参数的情况下,会按顺序输出我们的提交日志,跟上-p或–patch选项,可以查看每次提交的差异,后面的数字表示要显示的日志数量:

git log -p -3

远程分支

上面的所有操作,都是在本地完成的,仓库也是存储在我们本地,如果团队的其他小伙伴想参与这个项目,该怎么办呢?

最好的办法就是把我们的仓库推送到一个团队内共享的远程仓库,具体要怎么做呢?

远程仓库设置

首先当然需要有一个远程仓库,假设我们在Github创建一个了仓库,其地址为:https://github.com/test/test,Git关于远程仓库设置的命令为:git remote。

git remote origin https://github.com/test/test

这里我们将远程仓库名称为设置为origin,这个名字并没有什么特殊含义,你也可以设置其他名称,而且,一个仓库可以设置多个远程仓库,所以除了Github,你也可以再设置一个存储在Gitee的仓库。

有了远程仓库,就可以将本地的提交推送到远程仓库了,使用git push命令:

git push -u origin master 

这样,我们就将本地master分支同步推送到远程仓库的master分支上了

基于远程仓库的协作过程

基于共享远程仓库进行协作开发了,具体来说就是将团队中的人把自己的提交推送到远程仓库,或者同步其他人推送到远程仓库的提交。

推送与同步

在Git中,git push命令用于将分支推送到远程分支。

我们在前面已经使用git push命令了,这个命令的本质是将当前分支的所有commit发给远程仓库的指定分支,远程仓库收到commit之后,将commit合并到指定的分支当中去。

git push的用法如下:

# 未指定推送分支,如果没有设置当前本地分支跟踪的远程分支,则无法推送
$ git push
# 跟上-u选项后,会自动设置当前本地分支跟踪该远程分支,之后就可以直接使用`git push`而不需要指定分支了。
$ git push -u origin master

有时候我们的推送并不一定成功的,这是为什么呢?如果我们推送的远程分支上有比我们本地更新的提交,那么此时就无法推送成功,需要将远程分支的最新提交同步到我们本地,然后再推送。

Git同步抓取远程分支提交的命令为:git fetch:

git fetch origin master

调用git fetch之后,仅仅只是把远程分支同步到本地远程分支(每个远程分支在我们本地都有一个引用)而已,还没合并到本地的分支,因此还要再调用合并命令:

 git merge origin master

如果觉得每次同步都要执两条命令,那么就用git pull命令吧,这个命令相当于同时执行git fetch和git merge。

 git pull origin master

从上面的演示可以看出,当我们通过git fetch命令把远程分支同步到本地origin/master分支后,接下来的步骤与本地分支合并类似,就是把origin/master合并到master而已,这个合并的过程一样会出现快进合并、正常分支合并、分支冲突这三种我们之前已经讨论的情况,解决的方法也是一样的。

存储工作区文件(stash)

有什么办法可以切换到master时,不让功能分支的文件出现master分支的工作目录下,且不用将未完成的代码提交功能分支呢?

git stash就派上用场,git stash命令可以把当前工作的文件暂时存储到某个独立的地方,把工作区清理干净,避免未完成的修改对临时变更产生影响:

git stash

注意,git stash与git add命令把文件添加暂存区完全不同。

当小A完成了主线上bug的修复后,要再回到功能分支上开发,执行下面的命令就可以把之前存储的文件调出来,继续功能开发:

git stash pop

撤消操作

在使用Git的时候,难免需要撤消之前的操作,大概有以下几种情况:

文件回到提交状态

假如我们本地临时调试修改了某个已提交的文件,那么这个文件就变成已修改的状态,但我们并不想提交这个修改,因此调试完成后,就得把文件恢复到改动之前,git restore命令就派上用场了:

git restore 文件名

从暂存区撤回文件

我们知道git add命令可以帮我们把修改的文件添加到暂存区,如果发现添加错了,怎么办?同样是使用git restore命令,但需要跟上–staged选顶:

git restore --staged 文件名

修改提交

如果暂存区的文件已经提交了,却发现有代码写错了或者对提交说明不满意,想修改,但又不再提交,而是想要修改刚刚的提交,可以吗?

可以,把要改的文件改好之前添加到暂存区,然后重新执行git commit,但要跟上–amind选项,表示要修正上一个提交:

git commit --amend 

本地回退到某个版本

如果我们不仅仅只是修改刚刚的提交,而是要抛弃整个提交,回到上个提次,也是可以做到的,命令如下:

git reset --hard HEAD^

HEAD表示指向当前分支最新的commit,HEAD^则表示相对于当前commit的上一个commit:

git reset命令不仅可以回到上一个commit,实际上,这个命令后面参数为commit id,因此可以使用这个命令回到任意一个commit:

$ git reset --hard a23s93

所以,如果我们回到之前的提交点后,如果知道记住了后面的提交点的哈希值,也可以调用git reset命令回到那个点。

远程仓库回退

如果某个commit已经被我们push到远程仓库了,但却发现有代码写错了,想撤消,可以做到吗?这要分两种情况来分析:

第一种情况:

如果是你一个人维护的仓库或者团队中没人基于你推送的分支上开发,也就是说你的撤消推送到这个分支的操作不会影响其他人,那么在本地回退到某个commit之后,再执行git push –force 强制推送到远程仓库即可。

$ git reset --hard HEAD^
# -f 或 --force
$ git push --force

第二种情况:

如果你要回退的分支上已经有团队中其他人的最新提交,那千万不要在本地回滚后强制推送,因为很可能把其他人的提交给覆盖掉,这时候就只能正常修改,然后再提交版本了。

Search

    微信好友

    博士的沙漏

    Table of Contents