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 dszkng@outlook.com

这两项代表着是哪个用户提交的。
使用--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
user.email=dszkng@outlook.com
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
core.ignorecase=true
core.precomposeunicode=true
remote.origin.url=ssh://gogs@git.genecard.cn: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
dszkng@outlook.com

有时候会看到重复的变量名,那就说明它们来自不同的配置文件,不过最终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时:

image

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

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

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这里。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • Git 基础 基本原理 客户端并不是只提取最新版本的文件快照,而是把代码仓库完整的镜像下来。这样一来,任何一处协同...
    __silhouette阅读 15,855评论 5 147
  • Git 命令行学习笔记 Git 基础 基本原理 客户端并不是只提取最新版本的文件快照,而是把代码仓库完整的镜像下来...
    sunnyghx阅读 3,902评论 0 11
  • 1.git的安装 1.1 在Windows上安装Git msysgit是Windows版的Git,从https:/...
    落魂灬阅读 12,648评论 4 54
  • 远山 夕阳夕伴同相游 无限风光无限愁。 故地重走多感慨, 何日功成回故乡。 面对江面望洋叹, 滔滔江水永不还。 是...
    远山居士阅读 184评论 0 0
  • 2018年3月1日,星期四,伍哥读报时间: 1、【上海成立首批罕见病诊治中心和专科门诊】上海市卫生计生委28日宣布...
    邢五阅读 111评论 0 0