Git 版本管理系统的使用

[TOC]

背景

以前协同修改文件的方法:

  1. 通过复制文件来备份不同版本,按照日期等命名规则来区分。

  2. 文件共享,大家都能编辑,容易被别人的修改覆盖,所以需要文件名加上编辑者的名字。

Git解决的问题

  • 以前方式比较麻烦、容易出错。

Git是一个分布式版本管理系统,是为了更好地管理Linux内核开发而创立的。

Git可以在任何时间点,把文档的状态作为更新记录保存起来。因此可以把编辑过的文档复原到以前的状态,也可以显示编辑前后的内容差异。

而且,编辑旧文件后,试图覆盖较新的文件的时候(即上传文件到服务器时),系统会发出警告,因此可以避免在无意中覆盖了他人的编辑内容。

管理历史记录的数据库(Repository)

数据库是记录文件或目录状态的地方,存储着内容修改的历史记录。在数据库的管理下,把文件和目录修改的历史记录放在对应的目录下。

本地数据库

为了方便用户个人使用,在自己的机器上配置的数据库。

远程数据库(共享数据库)

配有专用的服务器,为了多人共享而建立的数据库。
如果想要公开在本地数据库中修改的内容,把内容上传到远程数据库就可以了。另外,通过远程数据库还可以取得其他人修改的内容。

创建数据库的方法

有两种方法:

  • 创建全新的数据库。
  • 复制远程数据库。

修改记录的提交

若要把文件或目录的添加和变更保存到数据库,就需要进行提交。

执行提交后,数据库中会生成上次提交的状态与当前状态的差异记录(也被称为revision)。

系统会根据修改的内容和目录结构使用SHA1哈希函数计算出没有重复的40位16进制的英文及数字来给提交命名。指定这个命名,就可以在数据库中找到对应的提交。

工作树和索引

在Git管理下,我们实际操作的目录就是工作树

在数据库和工作树之间有索引,索引是为了向数据库提交作准备的区域。

https://ws1.sinaimg.cn/large/006tKfTcgy1ftn4p78kw4j30fv07a0tg.jpg

基础配置

三个配置文件

Git工作环境变量三个存放位置:

  • /etc/gitconfig:对系统所有用户都普遍适用的配置。
  • ~/.gitconfig:只适用当前用户的配置。
  • your-project/.git/config:只适用当前项目的配置。

重要配置1:用户信息

$ git config --global user.name "dszkng"
$ git config --global user.email [email protected]

这两项代表着是哪个用户提交的。
使用--global选项时说明配置的是当前用户主目录下这个配置文件。

重要配置2:文本编辑器

Git需要你输入一些额外消息的时候,会自动调用一个外部文本编辑器给你用。默认会使用操作系统指定的默认编辑器,一般可能会是Vi或者Vim。如果你有其他偏好,可以重新设置:

$ git config --global core.editor emacs

重要配置3:差异分析工具

在解决合并冲突时使用哪种差异分析工具?
比如要改用vimdiff的话:

$ git config --global merge.tool vimdiff

Git可以理解kdiff3tkdiffmeldxxdiffemergevimdiffgvimdiffecmerge,和 opendiff等合并工具的输出信息。当然,你也可以指定使用自己开发的工具。

查看配置信息

$ git config --list
credential.helper=osxkeychain
user.name=dszkng
[email protected]
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
core.ignorecase=true
core.precomposeunicode=true
remote.origin.url=ssh://[email protected]:2222/zhichun/zc-cms.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master

$ git config user.email
[email protected]

有时候会看到重复的变量名,那就说明它们来自不同的配置文件,不过最终Git实际采用的是最后一个。

基本使用

Git思想及基本工作原理

只有掌握了基本的原理,用起来才会知其所以然,游刃有余。

分支(branch)操作

使用场景

在开发软件时,可能有多人同时为同一个软件(并行)开发功能修复Bug,可能存在多个Release版本,并且需要对各个版本进行维护。

所幸,Git的分支功能可以支持同时进行多个功能的开发和版本管理。

什么是分支?

分支是为了将修改记录的整体流程分叉保存。分叉后的分支不受其他分支的影响,所以在同一个数据库里可以同时进行多个修改。

为了不受其他开发人员的影响,一般是在主分支上建立自己专用的分支。完成工作后,将自己分支上的修改合并到主分支。因为每一次提交的历史记录都会被保存,所以当发生问题时,定位和修改造成问题的提交就容易多了。

创建分支

$ git branch branch_name
  • 切换并创建分支
$ git checkout -b branch_name
  • 查看创建过的分支
$ git branch

切换分支

$ git checkout branch_name

在切换工作分支时需要进行checkout操作。Git会从工作树还原向目标分支提交的修改内容。checkout之后的提交记录将被追加到目标分支。

  • HEAD

HEAD指向的是现在使用中的分支的最后一次更新。通常默认指向master分支的最后一次更新。通过移动HEAD,就可以变更使用的分支。

ps:提交时使用~(tilde)^(caret)就可以指定某个提交的相对位置
比如对于:HEAD3 -> HEAD2 -> HEAD1 -> HEAD(master)。
HEAD1HEAD~1HEAD~^表示。
HEAD2HEAD~2HEAD~1^1表示。
HEAD3HEAD~3HEAD~1^2HEAD~2^1表示。

  • stash(暂存区)

stash是临时保存文件修改内容的区域。stash可以暂时保存工作树索引里还没提交的修改内容,等事后再取出暂存的修改,应用到原先的分支或其他的分支上。

ps:如果在还未提交的修改内容以及新添加的文件,留在索引区域工作树的情况下切换到其他的分支时,修改内容会从原来的分支移动到目标分支。

但是如果在checkout的目标分支中相同的文件也有修改,checkout会失败的。这时要么先提交修改内容,要么用stash暂时保存修改内容后再checkout

合并分支

$ git merge branch_name

将完成作业的Topic分支合并到Merge分支有两种方法,合并后分支的历史记录会有很大的差别。

比如bugfix分支是从master分支分叉出来的,bugfix合并到master时:

Git 版本管理系统的使用_第1张图片
image

  • merge
$ git merge branch_name
  1. 如果master分支状态没有被更改过,bugfix分支的历史记录包含master分支所有的历史记录,这时候合并是最简单的,这就是快进(fast-forward)合并
    Git 版本管理系统的使用_第2张图片
    image
  2. 如果master分支在分叉之后又有了更新,这种情况下,要把master分支的修改内容和bugfix分支的修改内容汇合起来。因此,合并两个修改会生成一个提交。这时,master分支的HEAD会移动到该提交上。
    Git 版本管理系统的使用_第3张图片
    image

ps:执行合并时,如果设定了non fast-forward选项,即使在能够fast-forward合并的情况下也会生成新的提交并合并

Git 版本管理系统的使用_第4张图片
image

  • rebase
$ git init
$ vim a.txt # 新增a.txt,内容:create file
$ git commit -a -m 'first commit'
$ git branch issue # 新建issue分支
$ vim a.txt # 修改文件制造冲突条件,增加内容:first edit
$ git commit -a -m 'update a.txt in master branch'
$ git checkout issue # 切换分支
$ vim a.txt # 修改文件,增加内容:second edit
$ git commit -a -m 'update a.txt in issue branch'
$ git rebase master # 合并master和issue,但是会冲突
$ vim a.txt # 修改冲突内容
$ git add . # 不需要commit
$ git rebase --continue # 继续修改冲突后的提交
$ git rebase --abort # 如果要取消rebase的话
$ git checkout master
$ git merge issue # 这时候执行合并操作,实际进行的是fast-forward合并

bugfix分支的历史记录会添加在master分支的后面。历史记录成一条线,相当整洁。
rebase操作可能会有冲突,需要修改各自产生冲突的部分。rebase之后,masterHEAD位置不变。因此,要合并master分支和bugfix分支,即是将masterHEAD移动到bugfixHEAD这里。

Git 版本管理系统的使用_第5张图片
image

image

psmergerebase都是合并历史记录,但是各自的特征不同。

  • merge
    保持修改内容的历史记录,但是历史记录会很复杂。
  • rebase
    历史记录简单,是在原有提交的基础上将差异内容反映进去
    因此,可能导致原本的提交内容无法正常运行。

删除分支

$ git branch -d branch_name

分支实践

两种分支

分支可以被任意创建,但是,要先确定运用规则才可以有效地利用分支。

  • Merge分支

Merge分支是为了可以随时发布release而创建的分支,它还能作为Topic分支的源分支使用。保持分支稳定的状态是很重要的。如果要进行更改,通常先创建Topic分支,而针对该分支,可以使用Jenkins之类的CI工具进行自动化编译以及测试。

通常,大家会将master分支当作Merge分支使用。

ps:在数据库初次提交后,Git会默认创建一个master分支,不修改分支情况下,每次修改提交都是在master分支上。

  • Topic分支

Topic分支是为了开发新功能修复Bug等任务而建立的分支。若要同时进行多个的任务,请创建多个的Topic分支。
Topic分支是从稳定的Merge分支创建的。完成作业后,要把Topic分支合并回Merge分支。

常用分支

  • 主分支
    • master:只负责管理发布的状态,在提交时使用标签记录发布版本号
    • develop:日常开发分支。
  • release分支
    • release-2018.9:从develop分叉出,为release作准备的分支,做最后的调整,再合并到develop分支。
    • release-2017.8
  • 特性分支(topic分支)
    • feature:日常新功能开发。
    • bugfix:从release分支分叉出,解决完bug问题,最后合并到release分支。
  • hotfix分支
    • hotfix-20180805:从master分支分叉出,解决完紧急bug问题,最后合并到develop分支。因为直接从develop分支创建可以发布的版本要花许多的时间,所以最好选择从master分支创建。
    • hotfix-20180802

远程分支操作

fetch

  • 四种用法
$ git fetch # 更新git remote中所有的远程repo所有branch的最新commit_id,将其记录到.git/FETCH_HEAD文件中。

$ git fetch remote_repo # 只更新名称为remote_repo的远程repo上的所有branch的最新commit_id,将其记录。 

$ git fetch remote_repo remote_branch_name # 只更新名称为remote_repo的远程repo上的remote_branch_name分支。

$ git fetch remote_repo remote_branch_name:local_branch_name # 同上,并在本地创建local_branch_name分支来保存远端分支的所有数据。

psFETCH_HEAD是一个版本链接,记录在本地的.git/FETCH_HEAD文件中,指向目前已经从远程仓库取下来的分支的末端版本。

pull

  • 运行过程
    首先,基于本地的FETCH_HEAD记录,比对本地的FETCH_HEAD记录与远程仓库的版本号,然后git fetch获得当前指向的远程分支的后续版本的数据,然后再利用git merge将其与本地的当前分支合并。

push

标签(tag)操作

使用场景

通常在发版本时会给版本库打个tag,这个tag就是这次版本的快照,指向某个commit的指针,类似于branch,但是tag不能像branch一样可以移动。

好处是查找以前某个版本时不用记0348411ee60d4951aa53afb50bc7d6b1c6d5cdfc这种长串的commit_id,通过tag来查找更清晰明了。

两种标签

  • 轻标签:只添加名称。
  • 注解标签:添加名称、注解和签名。

打标签

$ git tag tag_name # 不带注解
$ git tag -a tag_name # 调用编辑器来写注解
$ git tag -am "注解" tag_name # 简单字符串注解
  • commit的历史记录打上标签:
$ git tag v0.1 0348411

查看标签

$ git tag
$ git tag -n # 查看标签和注解信息
  • 列出的tag是按名称排序的,也可以查看某个tag的详细信息:
$ git show tag_name

删除标签

  • 本地删除:
$ git tag -d tag_name
  • 删除远程标签:
$ git tag -d v1.0 # 先本地删除
$ git push origin :refs/tags/v1.0 # 远程删除

推送标签

创建的标签都是只存储在本地的,不会自动推送到远程。所以打错的标签可以在本地安全删除。

  • 推送某个标签到远程:
$ git push origin tag_name
  • 一次性推送全部尚未推送到远程的本地标签
$ git push origin --tags

已提交(committed)操作

修改最后一次提交

使用选项:git commit --amend

$ git log
$ vim xxx # wq
$ git add xxx
$ git commit --amend # 本次修改并入最后一次提交,进入编辑,修改提交说明

撤销/反悔(revert)某次提交

$ git revert HEAD # 撤销前一次commit
$ git revert HEAD^ # 撤销前前一次commit
$ git revert commit_id

ps:第三种方式撤销,在没有和后面提交相冲突的情况下是直接成功的。
比如说:那次提交是新增了一个文件,后面的每次提交都没有修改这个文件,这时候撤销那次提交,直接做相反的操作把新增的文件删了,比较顺利。

回退/重置(reset)到某次提交

$ git reset --hard # 最后一次commit
$ git reset --hard HEAD~~ # 回退到前前一次commit
$ git reset --hard ORIG_HEAD # 回退到某次commit

psgit revertgit reset的区别:

  • git revert是用一次新的commit来回滚之前的commit,而git reset是直接删除指定commit之后的所有commit

  • 在回滚这一操作上看,效果差不多。但是在日后继续merge以前的老版本时有区别。因为git revert是用一次逆向的commit中和之前的提交,因此日后合并老的branch时,导致这部分改变不会再次出现,但是git reset是之间把某些commit在某个branch上删除,因而和老的branch再次merge时,这些被回滚的commit应该还会被引入。

  • git reset是把HEAD向后移动了一下,而git revertHEAD继续前进,只是新的commit的内容和要revert的内容正好相反,能够抵消要被revert的内容。

导入提交

  • 使用场景:
    branch1开发时进行了多次提交,这时切换到branch2,想把之前branch1分支提交的commitcopy过来,怎么办?
    做法:首先切换到branch1分支,然后查看提交历史记录,也可以用sourceTree查看,也可以用命令git log

  • 用法:

$ git checkout branch1
$ git log # 找到想要操作的commit
$ git checkout branch2 # 将commit导入branch2
$ git cherry-pick commit_id # 单个commit
$ git cherry-pick commit_id1..commit_id2 # commit_id1和commit_id2之间的commit

汇合(合并)提交

  • 用法:
$ git rebase -i HEAD~~

自动打开编辑器,HEAD - HEAD~~commit的都被列出来了,将最后一个pick改成squash,保存,进入注释说明编辑,再保存退出,完成合并(两个提交合并成了一个)。

修改提交

参考资料

Git Community Book 中文版
廖雪峰的官方网站 - Git教程
猴子都能懂的GIT入门
Git Book

你可能感兴趣的:(Git 版本管理系统的使用)