目录
2.代码管理-Git
Git简介
①Git是一个开源的分布式版本控制系统;
②Git采用了分布式版本库的方式,不必服务器端软件支持;
客户端并不只提取最新版本的文件快照, 而是把代码仓库完整地镜像下来,包括完整的历史记录。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。
更进一步,许多这类系统都可以指定和若干不同的远端代码仓库进行交互。籍此,你就可以在同一个项目中,分别和不同工作小组的人相互协作。 你可以根据需要设定不同的协作流程,比如层次模型式的工作流,而这在以前的集中式系统中是无法实现的。
Git与其他区别
Git 和其它版本控制系统(包括 Subversion 和近似工具)的主要差别在于 Git 对待数据的方式。 从概念上来说,其它大部分系统以文件变更列表的方式存储信息,这类系统(CVS、Subversion、Perforce、Bazaar 等等) 将它们存储的信息看作是一组基本文件和每个文件随时间逐步累积的差异 (它们通常称作 基于差异(delta-based) 的版本控制)。
Git不按照以上方式对待或保存数据。反之,Git更像是把数据看作是对小型文件系统的一系列快照。 在Git中,每当你提交更新或保存项目状态时,它基本上就会对当时的全部文件创建一个快照并保存这个快照的索引。为了效率,如果文件没有修改,Git不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个快照流。
Git配置
①/etc/gitconfig 文件:系统中对所有用户都普遍适用的配置。若使用git config时用 --system选项,读写的就是这个文件;
②~/.gitconfig 文件:用户目录下的配置文件只适用于该用户。若使用git config时用--global 选项,读写的就是这个文件;
③当前项目的Git目录中的配置文件(也就是工作目录中的 .git/config 文件):这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置,所以.git/config里的配置会覆盖/etc/gitconfig中的同名变量;
Git工作流程
常见流程如下:
①克隆Git资源作为工作目录;
②在克隆的资源上添加或修改文件;
③如果其他人修改了,你可以更新资源;
④在提交前查看修改;
⑤提交修改;
⑥在修改完成后,如果发现错误,可以撤回提交并再次修改并提交;
Git工作区/暂存区/版本库
①工作区:就是你在电脑里能看到的目录;
②暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index);
③版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库;
1.图中左侧为工作区,右侧为版本库。在版本库中标记为 "index" 的区域是暂存区(stage/index),标记为 "master" 的是 master 分支所代表的目录树。
2.图中我们可以看出此时 "HEAD" 实际是指向 master 分支的一个"游标"。所以图示的命令中出现 HEAD 的地方可以用 master 来替换。
3.图中的 objects 标识的区域为 Git 的对象库,实际位于 ".git/objects" 目录下,里面包含了创建的各种对象及内容。
4.当对工作区修改(或新增)的文件执行 git add 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。
5.当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。
6.当执行 git reset HEAD 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。
7.当执行 git rm --cached <file> 命令时,会直接从暂存区删除文件,工作区则不做出改变。
8.当执行 git checkout . 或者 git checkout -- <file> 命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区中的改动。
9.当执行 git checkout HEAD . 或者 git checkout HEAD <file> 命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。
Git基本操作
workspace:工作区
staging area:暂存区/缓存区
local repository:版本库或本地仓库
remote repository:远程仓库
获取Git仓库
通常有两种获取Git项目仓库的方式:
①将尚未进行版本控制的本地目录转换为 Git 仓库;
②从其它服务器克隆 一个已存在的Git仓库;
在已存在目录中初始化仓库
git init
克隆现有的仓库
git clone <url>
git clone <url> <本地目录>
2.Git基础
https://git-scm.com/book/zh/v2/Git-%E5%9F%BA%E7%A1%80-%E8%8E%B7%E5%8F%96-Git-%E4%BB%93%E5%BA%93
2.1.获取Git仓库
通常有2种获取Git项目仓库的方式,这2种方式都会在你的本地机器上得到一个工作就绪的Git仓库:
①将尚未进行版本控制的本地目录转换为Git仓库;
②从其它服务器 克隆 一个已存在的Git仓库;
将尚未进行版本控制的本地目录转换为Git仓库
大致思路如下(以Windows操作系统为例): ①cd到本地目录的根目录下: cd /c/user/my_project ②执行git init: git init ③对该目录下的文件进行版本控制: git add *.c git add LICENSE git commit -m 'initial project version' |
git init命令将创建一个名为.git的子目录,这个子目录含有初始化的Git仓库中所有的必须文件,这些文件是Git仓库的骨干,但该命令仅是做了一个初始化的操作,项目里的文件还没有被跟踪;
要想跟踪这些文件,则需要通过git add命令来指定所需的文件来进行追踪,然后执行git commit;
从其它服务器 克隆 一个已存在的Git仓库
克隆仓库的命令是 git clone <url>: git clone https://github.com/libgit2/libgit2 git clone https://github.com/libgit2/libgit2 mylibgit |
git clone命令克隆的是该Git仓库服务器上的几乎所有数据,而不是仅仅复制完成你的工作所需要文件,执行git clone命令时,默认配置的远程 Git仓库中的每一个文件的每一个版本都将被拉取下来;
2.2.记录每次更新到仓库
文件状态
文件,通过git add添加跟踪/从服务器git clone后,文件无非就2个状态:
①已跟踪:指那些被纳入了版本控制的文件,在上一次快照中有它们的记录,在工作一段时间后, 它们的状态可能是未修改、已修改、已放入暂存区。简而言之,已跟踪的文件就是Git已经知道的文件
②未跟踪:工作目录中除已跟踪文件外的其它所有文件都属于未跟踪文件,它们既不存在于上次快照的记录中,也没有被放入暂存区。 初次克隆某个仓库的时候,工作目录中的所有文件都属于已跟踪文件,并处于未修改状态,因为Git刚刚检出了它们,而你尚未编辑过它们;
检查当前文件状态
git status |
nothing to commit, working directory clean:表示所有已跟踪文件在上次提交后都未被更改过;
Untracked files:未跟踪的文件意味着Git在之前的快照(提交)中没有这些文件;Git 不会自动将之纳入跟踪范围,除非你明明白白地告诉它“我需要跟踪该文件”;
跟踪新文件
git add 文件/目录 |
Changes to be committed:说明是已暂存状态;git add命令使用文件或目录的路径作为参数;如果参数是目录的路径,该命令将递归地跟踪该目录下的所有文件;
暂存已修改的文件
如果修改了一个名为CONTRIBUTING.md的已被跟踪的文件,然后运行git status命令,则出现Changes not staged for commit下面,则说明已跟踪文件的内容发生了变化,但还没有放到暂存区stage;
要暂存这次更新,需要运行git add命令更新到暂存区stage;
git add命令是个多功能命令,例如:可以用它开始跟踪新文件、把已跟踪的文件放到暂存区stage、合并时把有冲突的文件标记为已解决状态等;
注意:Git只是暂存了你运行git add命令时的版本,如果你现在提交commit的版本是你最后一次运行git add命令时的那个版本,而不是你运行git commit时工作目录中的当前版本,所以,运行了 git add之后又作修订的文件,需要重新运行git add把最新版本重新暂存起来;
忽略文件
一般我们总会有些文件无需纳入Git的管理,也不希望它们总出现在未跟踪文件列表,在这种情况下,我们可以创建一个名为 .gitignore 的文件,列出要忽略的文件的模式;
文件.gitignore 的格式规范如下:
①所有空行或者以 # 开头的行都会被 Git 忽略;
②可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中;
③匹配模式可以以(/)开头防止递归;
④匹配模式可以以(/)结尾指定目录;
⑤要忽略指定模式以外的文件或目录,可以在模式前加上叹号(!)取反;
查看已暂存和未暂存的修改
如果你现在面对如下2个问题,该怎么解决呢:
问题1:当前做的哪些更新尚未暂存到stage?(git diff)
问题2:有哪些更新已暂存并准备好下次提交?(git diff --staged)
这里可以使用git diff命令;
git diff命令比较的是工作目录中当前文件与暂存区域stage快照之间的差异,也就是修改之后还没有暂存起来的变化内容;
若要查看已暂存的将要添加到下次提交里的内容,可以用git diff --staged命令,这条命令将比对已暂存文件与最后一次提交的文件差异;
注意:git diff本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动,所以有时候你一下子暂存了所有更新过的文件,运行git diff后却什么也没有,就是这个原因;
提交更新
暂存区stage已经准备就绪,可以提交了,提交之前需再次确认还有什么已修改或新建的文件还没有执行git add,否则提交的时候不会记录这些尚未暂存的变化;
这些已修改但未暂存的文件只会保留在本地磁盘,所以每次准备提交前,先用git status看下所需要的文件是不是都已暂存起来了, 然后再运行提交命令git commit;
git commit -m选项,将提交信息与命令放在同一行;
git commit提交后,系统会告诉你,当前是在哪个分支(master)提交的,本次提交的完整SHA-1校验和是什么,以及在本次提交中,有多少文件修订过,多少行添加和删改过;
注意:git commit提交时记录的是放在暂存区域的快照。任何还未暂存文件的仍然保持已修改状态,可以在下次提交时纳入版本管理。每一次运行提交操作,都是对你项目作一次快照,以后可以回到这个状态,或者进行比较;
跳过使用暂存区域
git commit -a选项,Git会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过git add步骤;
移除文件
情况1:要从Git中移除某个文件,就必须要从已跟踪文件清单中移除,即从暂存区域移除,然后提交,这种要求可以用git rm命令完成,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了;
:如果只是简单地从工作目录中手工删除文件,运行git status 时在“Changes not staged for commit”部分还是能看到;
如果要删除之前修改过或已经放到暂存区的文件,则必须使用强制删除选项-f,这是一种安全特性,用于防止误删尚未添加到快照的数据,这样的数据不能被Git恢复;
情况2:我们想把文件从Git仓库中删除,即从暂存区域移除,但仍然希望保留在当前工作目录中,可以使用--cached选项;
移动文件/重命名文件
Git并不显式跟踪文件移动操作,如果在Git中重命名了某个文件,仓库中存储的元数据并不会体现出这是一次改名操作。不过Git会推断出究竟发生了什么;
要在Git中对文件改名,可以使用git mv命令;
2.3.查看提交历史
在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史,可以使用git log命令;
git log命令有很多选项,例如:-p或--patch显示每次提交所引入的差异、-2选项来只显示最近的两次提交等等;
具体可以查看官网说明:https://git-scm.com/book/zh/v2/Git-%E5%9F%BA%E7%A1%80-%E6%9F%A5%E7%9C%8B%E6%8F%90%E4%BA%A4%E5%8E%86%E5%8F%B2
2.4.撤消操作
修补提交
有时候我们提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了,此时,可以运行带有--amend 选项的提交命令来重新提交:git commit --amend
这个命令会将暂存区中的文件提交,如果自上次提交以来你还未做任何修改, 那么快照会保持不变,而你所修改的只是提交信息;
取消暂存的文件
使用git reset HEAD <file>...来取消暂存
撤消对文件的修改
如何将文件还原成上次提交时的样子,或者刚克隆完的样子,或者刚把它放入工作目录时的样子,可以使用:git checkout -- <file>;
注意:git checkout命令会把file文件在本地的任何修改都会抹除,这是因为Git会用最近提交的版本覆盖掉它,除非你确实清楚不想要对那个文件的本地修改了,否则请不要使用这个命令;
2.5.远程仓库的使用
远程仓库的使用
为了能在任意Git项目上协作,你需要知道如何管理自己的远程仓库;
远程仓库是指托管在因特网或其他网络中的你的项目的版本库。 你可以有好几个远程仓库,通常有些仓库对你只读,有些则可以读写;
与他人协作涉及管理远程仓库、根据需要推送/拉取数据,管理远程仓库包括了解如何添加远程仓库、移除无效的远程仓库、管理不同的远程分支并定义它们是否被跟踪等等;
查看远程仓库
如果想查看已经配置的远程仓库服务器,可以使用git remote 命令;
它会列出你指定的每一个远程服务器的简写;
如果你已经克隆了自己的仓库,那么至少应该能看到 origin,这是Git给你克隆的仓库服务器的默认名字;
选项 -v可以显示出需要读写远程仓库使用的Git保存的简写与其对应的URL;
添加远程仓库
运行git remote add <shortname> <url>添加一个新的远程Git 仓库,同时指定一个方便使用的简写;
后面可以在命令行中使用字符串shortname来代替整个URL;
从远程仓库中抓取与拉取
这里有2种方式:
①git fetch <remote>
这个命令会访问远程仓库,从中拉取所有你还没有的数据,执行完成后,你将会拥有那个远程仓库中所有分支的引用,可以随时合并或查看;
必须注意:git fetch命令只会将数据下载到你的本地仓库,它并不会自动合并或修改你当前的工作。当准备好时你必须手动将其合并入你的工作;
②git pull
如果当前分支设置了跟踪远程分支,那么可以用git pull命令来自动抓取后合并该远程分支到当前分支。默认情况下,git clone 命令会自动设置本地master分支跟踪克隆的远程仓库的maste分支(或其它名字的默认分支)。运行git pull通常会从最初克隆的服务器上抓取数据并自动尝试合并到当前所在的分支;
通俗来讲,git fetch只负责拉取,而合并则是交给开发者,git pull则是拉取 + 合并一起做了;
推送到远程仓库
当你想分享你的项目时,必须将其推送到远程仓库:git push <remote> <branch>;
只有当你有所克隆服务器的写入权限,并且之前没有人推送过时,这条命令才能生效。 当你和其他人在同一时间克隆,他们先推送到远程仓库然后你再推送到远程仓库,你的推送就会毫无疑问地被拒绝,你必须先抓取他们的工作并将其合并进你的工作后才能推送,即执行git pull/git fetch进行合并,然后再去git push;
查看某个远程仓库
如果想要查看某一个远程仓库的更多信息,可以使用:git remote show <remote>命令;
远程仓库的重命名与移除
可以运行git remote rename来修改一个远程仓库的简写名;
如果想要移除一个远程仓库可以使用:git remote remove或git remote rm;
注意:一旦使用这种方式删除了一个远程仓库,那么所有和这个远程仓库相关的远程跟踪分支以及配置信息也会一起被删除;
2.6.打标签
列出标签
git tag
创建标签
Git支持2种标签:
①轻量标签(lightweight):轻量标签很像一个不会改变的分支——它只是某个特定提交的引用;
②附注标签(annotated):附注标签是存储在Git数据库中的一个完整对象,它们是可以被校验的,其中包含打标签者的名字、电子邮件地址、日期时间, 此外还有一个标签信息,并且可以使用GNU Privacy Guard(GPG)签名并验证。通常会建议创建附注标签,这样你可以拥有以上所有信息。但是如果你只是想用一个临时的标签, 或者因为某些原因不想要保存这些信息,那么也可以用轻量标签;
附注标签
在运行tag命令时指定-a选项来创建附注标签;
-m选项指定了一条将会存储在标签中的信息。如果没有为附注标签指定一条信息,Git会启动编辑器要求你输入信息;
通过使用git show命令可以看到标签信息和与之对应的提交信息;
轻量标签
轻量标签本质上是将提交校验和存储到一个文件中—没有保存任何其他信息。创建轻量标签,不需要使用-a、-s或 -m选项,只需要在git tag提供标签名字;
如果在标签上运行git show,你不会看到额外的标签信息。命令只会显示出提交信息;
共享标签
默认情况下,git push命令并不会传送标签到远程仓库服务器上。 在创建完标签后你必须显式地推送标签到共享服务器上。 这个过程就像共享远程分支一样,可以运行git push <remote> <tagname>;
如果想要一次性推送很多标签,也可以使用带有--tags选项的 git push命令。这将会把所有不在远程仓库服务器上的标签全部传送到那里;
当其他人从仓库中克隆或拉取,他们也能得到你的那些标签;
注意:使用 git push <remote> --tags 推送标签并不会区分轻量标签和附注标签, 没有简单的选项能够让你只选择推送一种标签;
删除标签
一共有3种方法:
①git tag -d <tagname> + git push <remote> :refs/tags/<tagname>
②git push <remote> :refs/tags/<tagname>,这种方式是第一种的变体;
③git push origin --delete <tagname>
检出标签
如果你想查看某个标签所指向的文件版本,可以使用git checkout命令;
注意:该命令会使你的仓库处于“分离头指针(detached HEAD)”的状态,在“分离头指针”状态下,如果你做了某些更改然后提交它们,标签不会发生变化, 但你的新提交将不属于任何分支,并且将无法访问,除非通过确切的提交哈希才能访问。 因此,如果你需要进行更改,那么通常需要创建一个新分支;
2.7.Git别名
如果不想每次都输入完整的 Git 命令,可以通过 git config 文件来轻松地为每一个命令设置一个别名,例如:
git config --global alias.co checkout git config --global alias.br branch git config --global alias.ci commit git config --global alias.st status |
3.Git分支
3.1.分支简介
分支简介
Git保存的不是文件的变化或者差异,而是一系列不同时刻的快照,这里举个例子来加深理解:
假如D:/test/工作目录下,有3个新增文件
①当git add时
Git会为每一个文件分别计算校验和(通过SHA-1哈希算法来计算),然后会把当前版本的文件快照保存到Git仓库中(Git通过blob对象来保存快照),最后将校验和保存到暂存区,等待提交;
②当git commit时
Git首先计算每一个子目录的校验和,然后在Git仓库中把这些子目录校验和保存为树对象,随后Git会创建一个提交对象,该对象包含git add时的信息外,还包含指向了这个树对象的指针,这样,Git就可以在需要的时候重现此次保存的快照;
这样,通过git add + git commit操作,Git仓库一共产生了5个对象:
①3个blob对象来保存文件快照;
②1个树对象来记录目录结构和blob对象索引;
③1个提交对象来记录指向树对象的指针、所有提交信息,包括提交者姓名、邮件、提交备注等;
③修改之后再次git add
此次产生的提交对象会包含一个指向上次提交对象(父对象)的指针;
④master分支
Git分支,本质上仅仅是指向提交对象的可变指针。Git默认分支名字是master。在多次提交操作之后,你其实已经有一个指向最后那个提交对象的master分支。master分支会在每次提交时自动向前移动;
master分支并不是一个特殊分支,跟其它分支完全没有区别。 之所以几乎每一个仓库都有master分支,是因为git init命令默认创建它,并且大多数人都懒得去改动它;
个人总结:每次提交的时候,都会提交3类对象,1类是存储提交内容的对象,1类是存储目录树与目录中文件索引的对象,1类是整个提交对象,包含目录树、提交信息、提交者信息等;
而Git分支本质是一个可变指针,这个指针指向了提交对象,也就是说,在Git中,对象就一个,但分支可以是多个,只是不同的指针指向了同一个对象;
创建分支
Git分支只是一个可以移动的新指针,新建分支使用git branch 命令:git branch 分支名
该分支会在当前所在的提交对象上创建一个指针;
例如:
第一次提交对象的hash是98ca9,
第二次提交对象的hash是34ac2,
第三次提交对象的hash是f30ab,
然后master、testing分支指向了f30ab这个对象;
Git如何区分不同分支
Git有一个名为HEAD的特殊指针,HEAD指向当前所在的本地分支,git branch命令仅仅创建一个新分支,并不会自动切换到新分支中去;
可以简单地使用git log命令查看各个分支当前所指的对象。 提供这一功能的参数是 --decorate;
分支切换
要切换到一个已存在的分支,使用git checkout命令:git checkout 分支名称
这样HEAD就指向指定分支了;
当HEAD指向新分支后,再次git commit后,会是如下效果:
(还记得前面那句话吗:在多次提交操作之后,你其实已经有一个指向最后那个提交对象的master分支。master分支会在每次提交时自动向前移动;)
HEAD向前切换
当切换回master分支看看:
git checkout master时HEAD随之移动,该指令做了2件事:①使HEAD 指回master分支;②将工作目录恢复成master分支所指向的快照内容;
即现在做修改的话,项目将始于一个较旧的版本,从本质上来讲,这就是忽略testing分支所做的修改,以便于向另一个方向进行开发;
注意:git checkout分支切换会改变你工作目录中的文件;在切换分支时,一定要注意你工作目录里的文件会被改变。如果是切换到一个较旧的分支,你的工作目录会恢复到该分支最后一次提交时的样子。 如果Git不能干净利落地完成这个任务,它将禁止切换分支;
分叉
如果在master分支上进行git add/git commit后会产生分叉,因为刚才你创建了一个新分支testing,并切换过去进行了一些工作,随后又切换回master分支进行了另外一些工作。 上述两次改动针对的是不同分支:你可以在不同分支间不断地来回切换和工作,并在时机成熟时将它们合并起来。而所有这些工作,你需要的命令只有 branch、checkout和commit:
项目分叉历史
可以使用git log命令查看分叉历史,运行git log --oneline --decorate --graph --all会输出你的提交历史、各个分支的指向以及项目的分支分叉情况;
由于Git的分支实质上仅是包含所指对象校验和(长度为40的 SHA-1值字符串)的文件,所以它的创建和销毁都异常高效。创建一个新分支就相当于往一个文件中写入41个字节(40个字符和1个换行符);
3.2.分支的新建与合并
切换分支
git checkout切换分支之前,要注意当前你的工作目录和暂存区里那些还没有被提交的修改,它可能会和你即将检出的分支产生冲突从而阻止Git切换到该分支。 最好的方法是,在你切换分支之前,保持好一个干净的状态。 有一些方法可以绕过这个问题(即,暂存(stashing)和修补提交(commit amending));
git checkout切换分支时,Git会重置你的工作目录,使其看起来像回到了你在那个分支上最后一次提交的样子。Git会自动添加、删除、修改文件以确保此时你的工作目录和这个分支最后一次提交时的样子一模一样;
合并分支(fast-forward)
如果想把hotfix分支合并到master分支上,先git checkout目标分支master,然后再使用git merge hotfix命令来达到上述目的:
git checkout master
git merge hotfix
如果在执行命令过程中,Git出现Fast-forward字样,则代表了合并分支的一种场景:
由于你想要合并的分支hotfix所指向的提交C4是你所在的提交 C2的直接后继, 因此Git会直接将指针向前移动,即当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做“快进(fast-forward)”。
然后,应该删除hotfix分支,因为你已经不再需要它了,毕竟master分支已经指向了同一个位置。你可以使用带-d选项的git branch命令来删除分支:git branch -d hotfix
合并分支(一次合并提交)
同样,想把iss53合并到master分支,跟之前操作步骤一样,先git checkout master,然后git merge iss53:
git checkout master
git merge iss53
由于iss53分支与master分支并不是直接后继,因此面对这种情况的时候,Git会使用两个分支的末端所指的快照(C4 和 C5)以及这两个分支的公共祖先(C2),做一个简单的三方合并:
Git将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。这个被称作一次合并提交,它的特别之处在于他有不止一个父提交;
既然iss53已经合并进来了,就不再需要iss53分支了,现在你可以在任务追踪系统中关闭此项任务,并删除这个分支:git branch -d iss53
遇到冲突时的分支合并
如果在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git就没法合并它们,在合并它们的时候就会产生合并冲突;
此时Git做了合并,但是没有自动地创建一个新的合并提交;Git会暂停下来,等待你去解决合并产生的冲突;
任何因包含合并冲突而有待解决的文件,都会以未合并状态标识出来;Git会在有冲突的文件中加入标准的冲突解决标记,这样可以打开这些包含冲突的文件然后手动解决冲突;
在解决了所有文件里的冲突之后,对每个文件使用git add命令来将其标记为冲突已解决;一旦暂存这些原本有冲突的文件,Git 就会将它们标记为冲突已解决;
等你退出合并工具之后,Git会询问刚才的合并是否成功。如果你回答是,Git会暂存那些文件以表明冲突已解决,可以再次运行git status来确认所有的合并冲突都已被解决;
如果确定之前有冲突的的文件都已经暂存了,这时你可以输入 git commit来完成合并提交;
3.3.分支管理
git branch命令
①如果不加任何参数运行它,会得到当前所有分支的一个列表,如果分支前的*字符代表现在检出的哪一个分支,即当前HEAD指针所指向的分支;
②如果需要查看每一个分支的最后一次提交,可以运行git branch -v 命令;
③如果要查看哪些分支已经合并到当前分支,可以运行git branch --merged;
④git branch -d 删除指定分支;
⑤查看所有包含未合并工作的分支,可以运行git branch --no-merged;
3.4.分支开发工作流
因为Git使用简单的三方合并,所以就算在一段较长的时间内,反复把一个分支合并入另一个分支。也就是说,在整个项目开发周期的不同阶段,你可以同时拥有多个开放的分支;你可以定期地把某些主题分支合并入其他分支中;
许多使用 Git 的开发者都喜欢使用这种方式来工作,比如只在 master 分支上保留完全稳定的代码——有可能仅仅是已经发布或即将发布的代码。 他们还有一些名为develop或者next的平行分支,被用来做后续开发或者测试稳定性——这些分支不必保持绝对稳定,但是一旦达到稳定状态,它们就可以被合并入master分支了。 这样,在确保这些已完成的主题分支能够通过所有测试,并且不会引入更多 bug之后,就可以合并入主干分支中,等待下一次的发布;
3.5.远程分支
远程引用
远程引用是对远程仓库的引用,包括分支、标签等等。
可以通过git ls-remote <remote>来显式地获得远程引用的完整列表,或者通过git remote show <remote>获得远程分支的更多信息。 然而,一个更常见的做法是利用远程跟踪分支。
远程跟踪分支
远程跟踪分支是远程分支状态的引用。它们是你无法移动的本地引用。一旦你进行了网络通信, Git就会为你移动它们以精确反映远程仓库的状态。请将它们看做书签,这样可以提醒你该分支在远程仓库中的位置就是你最后一次连接到它们的位置。
它们以 <remote>/<branch> 的形式命名。
举个例子:当你从远程一个叫git.ourcompany.com 的Git服务器,执行clone命令的时候,Git默认会把git.ourcompany.com叫做origin,然后拉取其所有数据,然后顺带着创建一个指针来指向origin的master分支;
注意这里的origin/master,只是放在你的电脑上的,用来代指git.ourcompany.com的master分支,这只是你个人电脑上的代指,不是整个网络对git.ourcompany.com的master的代指;
在Git为git.ourcompany.com创建一个origin/master代指后,也会在本地创建一个指向git.ourcompany.com/master的本地master分支,然后你就可以进行自己的工作了;
克隆之后的服务器与本地仓库
如果你在本地的master分支做了一些工作,在同一段时间内有其他人推送提交到git.ourcompany.com并且更新了它的master分支,这就是说你们的提交历史已走向不同的方向。
只要你保持不与origin服务器连接(并拉取数据),你的 origin/master 指针就不会移动。
本地与远程的工作可以分叉
如果要与给定的远程仓库同步数据,运行git fetch <remote> 命令(在本例中为 git fetch origin)。 这个命令查找“origin”是哪一个服务器(在本例中,它是 git.ourcompany.com), 从中抓取本地没有的数据,并且更新本地数据库,移动origin/master指针到更新之后的位置。
git fetch更新你的远程跟踪分支
如果有多个远程仓库与远程分支的情况,假定另一个Git服务器为git.team1.ourcompany.com;
则同伙运行git remote add命令添加一个新的远程仓库引用到当前的项目,我们将这个远程仓库命名为teamone,将其作为完整URL 的缩写;
添加另一个远程仓库
运行git fetch teamone来抓取远程仓库teamone有而本地没有的数据,但是由于服务器git.team1.ourcompany.com上现有的数据是origin服务器上的一个子集,所以 Git并不会抓取数据而是会设置远程跟踪分支teamone/master指向teamone的master分支;
推送
当你想要公开分享一个分支时,需要将其推送到有写入权限的远程仓库上;
本地的分支并不会自动与远程仓库同步,开发者必须显式地推送想要分享的分支,这样,就可以把不愿意分享的内容放到私人分支上,而将需要和别人协作的内容推送到公开分支;
git push 远程服务器 本地分支名:远程分支名
注意
假如张三通过git push origin serverfix命令来把本地serverfix分支代码提交到origin服务器上的serverfix分支;
然后李四则可以从origin服务器通过git fetch origin更新serverfix分支,此时Git会在李四电脑本地生成一个远程分支 origin/serverfix,指向服务器的serverfix分支的引用;
但是,当李四抓取到新的远程跟踪分支serverfix时,本地不会自动生成一份可编辑的副本,即这种情况下,不会有一个新的serverfix分支,而只有一个不可以修改的origin/serverfix指针;
要想变为可以编辑,整体有下面2个方式:
①通过运行git merge origin/serverfix,将serverfix分支合并到当前正在开发中的maaster分支;
②通过git checkout -b serverfix origin/serverfix,在本地让Git跟踪远程origin/serverfix分支,这样会给一个用于工作的本地分支,并且起点位于origin/serverfix,这样就可以在这个分支上进行开发工作;
跟踪分支
远程跟踪分支是远程分支状态的引用。
当我们通过git checkout命令来从一个远程跟踪分支检出一个本地分支时,Git会在本地自动创建所谓的“跟踪分支”(它跟踪的分支叫做“上游分支”),跟踪分支是与远程分支有直接关系的本地分支,如果在一个跟踪分支上输入git pull,Git能自动地识别去哪个服务器上抓取、合并到哪个分支;
当通过git clone克隆一个仓库时,它通常会自动地创建一个跟踪origin/master的本地master分支,当然还可以设置同一个Git 服务器下的不同跟踪分支,或是不同Git服务器的跟踪分支,又或者不跟踪master分支,通过git checkout -b <本地分支名> <远程Git服务器>/<远程分支名>命令可以达到上述目标;
①如果想设置已有的本地分支跟踪一个刚刚拉取下来的远程分支,或者想要修改正在跟踪的上游分支, 可以在任意时间使用git branch的-u或--set-upstream-to选项来显式地设置;
②如果想要查看设置的所有跟踪分支,可以使用 git branch 的 -vv选项,需要重点注意的一点是git branch显示的数字来自于你从每个服务器上最后一次抓取的数据,这个命令并没有连接服务器,它只会告诉你关于本地缓存的服务器数据,如果想要统计最新的领先与落后数字,需要在运行此命令前抓取所有的远程仓库,例如:git fetch --all; git branch -vv
拉取
①git fetch:从服务器上抓取本地没有的数据时,该指令并不会修改工作目录中的内容;它只会获取数据然后让你自己合并;
②git pull:可以看做是git fetch + git merge命令的集合体;对于已经与远程分支做了关联的跟踪分支,不管它是显式地设置还是通过clone或checkout命令创建的,git pull都会查找当前分支所跟踪的服务器与分支,从服务器上抓取数据然后尝试合并入那个远程分支;
建议:单独显式地使用fetch与merge命令会更好一些;
删除远程分支
运行带有--delete选项的git push命令来删除一个远程分支,注意该命令做的只是从服务器上移除这个指针,Git服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的;
3.6.变基
变基
在Git中整合来自不同分支的修改主要有两种方法:merge以及rebase;
merge
面对这种情况,merge命令会把两个分支的最新快照(C3 和 C4)以及二者最近的共同祖先(C2)进行三方合并,合并的结果是生成一个新的快照(并提交),成果如下:
变基(rebase)
还有一种方法:可以提取在C4中引入的补丁和修改,然后在C3的基础上应用一次,这种操作就叫做变基(rebase);
可以使用rebase命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样;
它的原理是首先找到这两个分支的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件, 然后将当前分支指向目标基底C3, 最后以此将之前另存为临时文件的修改依序应用;
总结
这两种整合方法的最终结果没有任何区别,但是变基使得提交历史更加整洁。在查看一个经过变基的分支的历史记录时会发现,尽管实际的开发工作是并行的,但它们看上去就像是串行的一样,提交历史是一条直线没有分叉;
变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起;
扩展
在对两个分支进行变基时,所生成的“重放”并不一定要在目标分支上应用,你也可以指定另外的一个分支进行应用,可以使用 git rebase 命令的 --onto 选项来达到这种效果;
变基的风险
要用rebase得遵守一条准则:如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基;
举个例子:
①Git服务器master指向C1;
②张三git clone C1后,进行git commit得到C2、C3,然后再通过git rebase C1与C3得到C4;
③李四git clone C1后,进行git commit得到C5,然后再通过git rebase C4与C5得到了C6;
④张三通过git fetch C6,然后经过后续操作,得到了C7、C8;
⑤李四对C6进行回滚,得到了C5,此刻,远程服务器master是指向了C4’;
⑥当张三对C8进行git rebase时发现,Git服务器的master指向了C5,C5与C8共同的祖先是C1,也就是说从李四的C4-C5-C6-C4’的操作,而C4的操作又是C1-C2-C3-C4,也就是这些从C1-C4’的操作,都要在C8上再执行一次,特别是C2-C3操作,简直是将相同的内容又合并了一次,生成了一个新的提交;
其实,说白了,就是Git服务器进行了回滚操作,一旦回滚,导致Git服务器与本地master的节点差距过大,把以前的指令重复执行一遍之后,最后结尾来个回滚操作,这尼玛,谁受得了;
你需要做的就是检查你做了哪些修改,以及他们覆盖了哪些修改。
总结
总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史,从不对已推送至别处的提交执行变基操作,这样,你才能享受到两种方式带来的便利;