git版本控制管理
git 初始化
1 | git init |
可以使用Git将一个空/满的目录转换为git版本库
将文件添加到版本库中
1 | git add filename.html |
为了管理内容,必须 明确地把它放入到版本库中,这种有意识的步骤可以将重要文件与临时分离开,上面命令使用Git add file将File添加到版本库中。若目录中有了很多文件可以使用命令
1 | git add. |
这段命令可以让git把当前目录及子目录中的文件都添加到版本库中。执行上段命令只是暂存了这个文件,这是提交之间的中间步骤,git将add和commit两步分开,以避免频繁变化。
运行git status命令,显示出中间状态的filename.html
1 | git status |
这个命令显示新文件index.html将在下一次提交时添加到版本库里。
配置提交作者
1 | git config user.name "Jon Loeliget" |
配置作者名和作者邮箱,也可以使用
1 | GIT_AUTHOR_NAME |
来告诉作者的姓名和email地址
再次提交
因为在前段已经将文件index.html加入到版本库中,所以无需再次添加,直接使用
1 | git commit index.html |
进行命令的提交。
查看提交
1 | git log |
条目按照从新到老的顺序罗列出来。
1 | git show 提交码 |
可以查看特定提交的更加详细的信息。
1 | git show-brach --more=10 |
提供当前分支的简洁摘要,参数—more=10表示额外10个版本。
查看提交差异
为了查看index.html的两个版本之间的差异,使用两个提交的全ID名并且运行git diff
1 | git diff 提交码1 提交码2 |
版本库内文件的删除与重命名
当有一个不再需要的文件:poem.html,则有
1 | git rm poem.html //删除poem.html |
1 | mv foo.html bar.html |
需要先执行mv foo.html bar.html,以防止git rm 命令会把foo.html 文件系统中永久删除。
若使用
1 | git mv foo.html bar.html |
也可以达到上面一段代码的效果。
1 | git commit -m "moved foo to bar" |
进行代码提交。
配置文件
1 | .git/config //版本库特定的配置设置,可用--file选项修改 |
配置别名
1 | git config --global alias.show-graph 'log --graph --abbrev-commit --pretty=oneline' |
创建了名为show-graph的别名
基本的Git概念
版本库
Git版本库是一个简单的数据库,包含所有用来和管理项目的修订版本和历史信息。在版本库中维护主要的数据结构:对象库和索引。
git对象类型
git在对象库里的对象有4种类型,块、目录树、提交和标签。
块(blob):文件的每一个版本表示为一个块
目录树(tree):一个目录树对象代表一层目录信息。它记录blob标识符、路径名和一个目录里所有文件的一些元数据。
提交(commit):一个提交对象保存版本库里每一次变化的元数据,包括作者、提交者、提交日期和日志消息。
标签:一个标签对象分配一个任意的且人类可读的名字给一个特定对象,通常是一个提交对象。
索引
索引是一个临时的、动态的二进制文件,它描述整个版本库的目录结构。具体而言索引捕获项目在某个时刻的整体结构的一个版本。
对象库图示
这张图显示在一个版本库里添加了两个文件的初始提交后的状态。两个文件都在顶级目录中,同时分支master和标签V1.0都指向ID为1492的提交对象。
保留原来的两个文件不变,添加一个包含一个文件的新子目录。
文件和树
1 | git ls-files -s |
可以看到文件的关联,hello.txt与3b18e的blob
捕获索引状态并把它保存到一个树对象里。
有两个对象:3b18e5的“Hello world”对象和一个新的68aba6树对象
查看树对象,第一个数100644,是对象的文件属性的八进制表示,这里3b18e5是hello world 的blob的对象名,hello.txt是与该blob关联的名字。
执行git ls-file -s时,可以看到树对象已经捕获了索引的信息。
文件管理与索引
若项目处于版本控制系统的管理下,可以在工作目录里编辑,然后把修改提交给版本库来进行保管。git的工作原理之类似,它在工作目录和版本库之间加设一层索引(index)用来暂存(stage)收集或修改。
1 | git commit index.html //当添加或更改文件时,git允许把两步合成一步 |
1 | git rm index.html |
Git中的文件分类
已追踪的(Tracked)
已追踪的文件是指已经在版本库中的文件,或者是已暂存到索引中的文件,若想将新文件somefile添加为已追踪的文件,执行git add somfile.
被忽略的(Ignored)
被忽略的文件必须在版本库中被明确声明为不可见或忽略,即使他有可能出现你的工作目录中。
未追踪的(Untracked)
未追踪的文件是指那些不在前两类的文件.
使用git add
git add 命令将暂存一 个文件,若一个文件是未追踪的状态,使用git add会将文件的状态转化成已追踪的,若git add作用于一个目录,那么该目录下的文件和子目录会递归暂存起来。
第一条git status命令显示有两个未追踪的文件,并提醒要追踪一个文件,只需要使用Git add就可以,使用Git add之后,暂存和追踪data和.gitignore文件,并准备在下一次提交时加到版本库中。
1 | git ls-files //可以查看隐藏在对象模型下的东西,并可以找到暂存文件的SHA1值 |
使用git Commit 的一些注意事项
git commit的-a或-all选项会导致执行提交之前自动暂存所有未暂存的和未追踪的文件变化,包括从工作副本中删除已追踪的文件。
使用Git rm
git可以从索引或者同时从索引和工作目录中删除一个文件。从工作目录和索引中删除一个文件,并不会删除该文件在版本库中的历史记录。文件的任何版本,只要是提交到版本库的历史记录的一部分,就会留在对象库里并保存历史记录。
若引进一个不应该暂存的“意外”文件,看看怎么将其删除。
因为Git rm是一条对索引进行操作的命令,所以它对没有添加到版本库或索引中的文件是不起作用的,Git必须要先认识到该文件才行。下面为偶然的暂存oops文件
若要将一个文件由已暂存的转化成未暂存的可以使用
1 | git rm --cached |
1 | git rm --cached |
会删除索引中的文件并把它保留工作目录中,而
1 | git rm |
则会将文件从索引和工作目录中都删除。
1 | git ls-files --stage //查看文件驻存 |
使用git mv
若需要移动或重命名文件,可以对旧文件使用git rm命令,然后使用git add命令添加新文件,或者直接使用git mv命令。
1 | mv stuff newstuff //移动文件 |
Git会在索引中删除stuff的路径名,并添加newstuff的路径名,至于stuff的原始内容,则仍保存在对象库中,然后才会将它与newstuff重新关联。
若要检查某个文件的历史记录,可使用命令
1 | git log mydata |
若要让Git在日志中回溯并找到内容相关联的整个历史记录,使用命令
1 | git log --follow mydata |
.gitignore文件
一个.gitignore文件下可以包含一个文件名模型列表,指定哪些文件要忽略。.gitignore文件的格式如下
- 忽略空行,注释用#开头
- 字面置文件名匹配任何目录中的同名文件
- 目录名由末尾的反斜线(/)标记,能匹配同名的目录和子目录,但不匹配文件或符号链接。
- 包含shell通配符,如星号(*),这种模式可以扩展为shell通配模式。
- 起始的感叹号(!)会对该行其余部分的模式进行取反。
为了解决带多个.gitignore目录的层次结构问题,也为了允许命令行对忽略文件列表的增编,git按照下列从高到低的优先顺序:
- 在命令行上指定的模式。
- 从相同目录的.gitignore文件中读取的模式
- 上层中的模式,向上进行。即一级目录的模式会被二级目录的械覆盖。
- 来自.git/info/exclude的文件模式
- 来自配置变量core.excludedfile指定文件中的模式
1 | cd my_package |
Gitk中对象模型和文件的详细视图
工作目录包含file1和file2两个文件,分别包括内容”foo”和”bar”,初始状态如图5-1如示
除了工作目录下的file1和file2之外,master分支还有一个提交,它记录了跑file1和file2内容完全一样的”foo”和”bar”的树,此外,该索引记录两个SHA1的值a23bf和9d3a2,与那两个文件分别对应。工作目录、索引以及对象库都是同步一致的,没有什么是可脏的。
图5-2显示了在工作目录中对file1编辑后的变化,现在它的内容包括”quux”。索引和对象库中没有变化,但工作目录现在是脏的。
由于file2的内容未发生改变而且没有任何git add 暂存file2,因此索引继续指向原始blob对象。
此时,你已经在索引中暂存了file1文件,而且工作目录和索引是一致的。不过,就HEAD而言,索引是脏的,因为索引中的树跟在master分支的HEAD提交的树在对象库中是不一样的。
如图5-3所示,Git首先取出工作目录中file1版本,为它的内容计算一个SHA1的散列ID(bd71363),然后把那个ID保存在对象库中。接下来,GIt就会记录在索引中的file1路径名已更新为新的bd71363的SHA1值。
最后,当所有变更都暂存到索引后,一个提交将它们应用到版本库中,git commit的作用如图5-4所示。
如图5-4所示,提交启动了三个步骤。首先,虚拟树对象(即索引)在转换成一个真实的树对象后,会以SHA1命名,然后放到对象库中。其次,用你的日志消息创建一个新的提交对象,新的提交将会指向新创建的树对象以及前一个或父提交。最后,master分支的引用从最近一次提交移动到新创建的提交对象,成为新的master HEAD。
在图5-4中,提交启动了三个步骤。首先,虚拟树对象(即索引)在转换成一个真实的树对象后,会以SHA1命名,然后放到对象库中。其次,用你的日志消息创建一个新的提交对象。新的提交将会指向新创建的树对象以及前一个或父提交。最后,master分支的引用从最近一次提交移动到新创建的提交对象,成为新的master HEAD。
提交
识别提交
在GIt中,可以通过显式或隐式引用来指代每一个提交。唯一的40位十六进制SHA1提交ID是显式引用。而始终指向最新提交的HEAD则是隐式引用。这两种机制有各自的优势,需要根据上下文来进行选择。
绝对提交名
对于提交来说,散列ID是个绝对名,意味着它只能表示唯一确定的一个提交,无论提交处于版本库历史中的任何位置,哈希ID得了对应着相同的提交。
每个提交的散列ID都是全局唯一的,不仅是对某个版本库,而是对任意和所有版本库都是唯一的。
由于输入一个40位十六进制的SHA1数字是一项繁琐且容易出错的工作,因此GIt允许你使用版本库的对象库中唯一的前缀来缩短这个数字。
引用和符号引用
引用是一个SHA1的散列值,指向GIt对象库中的对象。虽然一个引用可以指向任何GIt对象,但是它通常指向提交对象。符号引用间接指向Git对象,它仍然只是一个引用。
本地特性分支名称、远程跟踪分支名称和标签名都是引用。
每个符号引用都有一个以ref/开始的明确全称,并且都分层存储在版本库的.git/refs/目录中。目录中基本上有三种不同的命名空间代表不同的引用:
- refs/heads/ref代表本地分支。
- refs/remotes/ref代表远程跟踪分支
- refs/tags/ref代表标签
Git自动维护几个用于特定条目的特殊符号引用,而这些引用可以在使用提交的任何地方使用。
HEAD始终指向当前分支的最近提交。当切换分支时,HEAD会更新为指向新分支的最近提交。
ORIG_HEAD,合并和复位的某些操作,会把调整为新值之前的先前版本的HEAD记录到ORIG_HEAD中,可以使用ORIG_HEAD来恢复和回滚到之前的状态或做一个比较。
FETCH_HEAD,当使用远程库时,git fetch命令将所有抓取分支的头记录到.git/FETCH_HEAD中。FETCH_HEAD是最近抓取的分支HEAD的简写,并且仅在刚刚抓取操作之后才有效。
MERGE_HEAD,当一个合并操作正在进行时,其它分支的头暂时记录在MERGE_HEAD中。
所有的符号引用可以命令git symbolic-ref进行管理。
相对提交机制
git提供一种机制来确定相对于另一个引用的提交,通常是分支的头。如master和master^ ,其中master^ 始终指的是在master分支中倒数第二个提交。还有如master^^ 、master~2 ,以及master~10^2 ~ 2^2 的名字。
在同一代提交中,插入符号^ 是用来选择不同的父提交的。给定一个提交C,C^1 是其第一个父提交,C^2 是其第二个父提交,C^3是其第三个父提交。
波浪线~用于返回父提交之前并选择上一代提交。给定一个提交C, C~ 1 是其中每一个父提交,C~ 2 是其第一个祖父提交, C~3 是第一个曾祖父提交。当在同一代中存在多个父提交时,紧跟其后的是第一个父提交的第一个父提交。
提交历史记录
查看旧提交
显示提交历史记录的主要命令是git log。
若提供一个提交名,那么日志将从该提交开始回溯输出 。
git可以指定提交记录的输出范围。
git log显示子在master~ 12到master~10 之间的所有提交,是主分支上之前10次和第11次的提交。例子中引用了两个格式选项—pretty=short 和—abbrev-commit。前者调整了每个提交的信息数量,后者则是简单的请求缩写散列ID。
另一个查看对象库中对象信息的命令是git show,可以使用它来查看某个提交。
1 | git show HEAD-2 |
或查看某个特定的blob对象信息
1 | git show origin/master:Makefile |
提交图
使用gitk来查看提交图
1 | cd public_html |
分支
分支名
- 可以使用斜杠(/)创建一个分层的命名方案。但是,该分支名不能以斜线结尾。
- 分支名不能以减号(—)开头。
- 以斜杠分割的组件不能以点(.)开头。
- 分支名的任何地方都不能包含两个连续的点(…)
- 此外,分支名不能包含以下内容:
- ——任何空格或其它空白字符
- ——在Git 中具有特殊含义的字符,包括波浪线(~)、插入符波浪线( ^),冒号(:)、问号(?)、星号(*)、左方括号([)。
使用分支
因为一个分支开始时的原始提交没有显式定义,所以这个提交可以通过从分叉出的新分支的源分支名使用算法找到。
1 | git merge-base original-brach new-brach |
合并是一个分支的补充。当合并时,把一个或多个分支的内容回到一个隐式的目标分支中。
在图7-1中,dev分支指向提交的头,Z。如果你想重建版本库在提交Z时的状态。那么从Z回到原始提交A的所有可达提交都是必需的。图7-1中的可达部分突出显示为粗线,涵盖除(S,G,H,I,J,K,L)之外每一次提交.
你的每一个分支名和分支上提交的内容一样,都放在你的本地版本库中.然而,当把版本库提供给他们用时,也可以发布或选择使用任意数量的分支和相关的可用提交.
创建分支
新的分支基于版本库中现有的提交.完全由你来决定并指定哪次提交作为新分支的开始.
一旦已经确定了从哪一个提交开始分支时,只需要使用git brach 命令.因此,为了解决问题报告#1138,要从当前分支的HEAD创建一个新的分支,可以使用:
1 | git brach prs/pr-1138 |
如果没有指定的starting-commit,就默认为当前分支上的最近提交.换言之,默认是在现在工作地方启动一个新的分支.
列出分支名
1 | git branch |
当前已检出到你的工作目录中的分支用星号标记。这个例子也显示了其它两个分支:bug/pr-1和dev
查看分支
1 | git show-branch |
git show-branch命令提供比git branch更详细的输出,按时间以递序的形式列出对一个或多个分支有贡献的提交。若没有选项列出特性分支,-r显示远程追踪分支,-a显示所有分支。
git show-branch 的输出被一排破折号分为两部分。分隔符上方的部分列出分支名,并且方括号括起来,每行一个。每个分支名跟着一行输出,前面用感叹号或星号(如果它是当前分支)标记。
输出的下半部分是一个表示每个分支中提交的矩阵。每个提交后面跟着该提交中日志消息的第一行。如果有一个加号(+)、星号()或减号(-)在分支的列中,对应的提交会在该分支中显示。加号表示提交在一个分支中,星号突出显示存在于活动分支的提交,减号表示*一个合并提交。
检出分支
工作目录一次只能反映一个分支。要在不同的分支上开始工作,要发出git checkout命令。给定一个分支名,git checkout会使该分支变成新的当前分支。它改变了工作树文件和目录结构来匹配给定分支的状态。
检出分支的一个简单例子
1 | git checkout bug/pr-1 |
有未提交的更改时进行检出
Git会排除本地工作树中数据的删除与修改。工作目录中的未被追踪的文件和目录始终会置之不管;Git 不会删除与修改它们。但是,如果一个文件的本地修改不同于新分支上的变更.Git 会发出如下错误消息,并拒绝检出目标分支。
即在检出分支时,若git在检出分支时,有文件已经置于被追踪的状态且已经修改并不同于新分支上的变更。则会发出错误消息。
1 | git diff newStuff |
合并变更到不同分支
1 | git checkout -m dev |
在这里Git已经修改了NewStuff文件,并成功检出dev分支。
虽然它看起来像合并得很干净且一切都没有问题,GIt已经简单地修改了文件并留下其中的合并冲突指示。还必须要解决如下存在的冲突。
1 | cat NewStuff |
如果GIt可以检出一个分支,并改变它,且在没有任何合并冲突的情况下清晰地合并本地修改,那么检出请求就成功了。
假设在开发版本库的master分支,并对NewStuff文件进行了一些修改。然后,发现我们所做的更改其实在另一分支,也许是因为修复了问题报告#1,所以应该提交到bug/pr-1分支。
设置如下。首先在master分支上。对某些文件进行更改,这里通过添加文件“some bug fix”到NewStuff文件表示。
这一切都应提交到bug/pr-1分支而不是master分支上。作为参考,这是NewStuff文件在bug/pr-1分支中下一步检出之前的样子。
1 | git show bug/pr-1:NewStuff |
为了将修改放入所需的分支中,只须试图对它进行检出 。
1 | git checkout bug/pr-1 |
1 | cat NewStuff |
Git能够正确地合并工作目录和目标分支的变化,并把它们放在新工作目录结构串。若想验证合并是否符合你的预期,可以使用git diff。
新添加的一行是正确的。
创建并检出新分支
命令
1 | git checkout -b bug/pr-3 |
除非一些问题阻止检出命令完成,命令:
1 | git checkout -b new-branch start-point |
与两个命令序列是完全等价的。
1 | git branch new-branch start-point |
1 | git checkout new-branch |
分离HEAD分支
git checkout会改变期望的分支的头部。然而,可以检出任何提交。在这种情况下,Git会自动创建一种匿名分支,称为分离的HEAD,下面的情况下,GIt会创建一个分离的HEAD分支。
- 检出的提交不是分支的头部。
- 检出一个追踪分支。
- 检出标签引用的提交。
- 启动一个git bisect的操作。
- 使用git submodule update命令。
在上述这些情况下,GIt会告诉你,已经移动到一个分离的HEAD。
若发现自己在一个分离的头部,然后决定在该点用新的提交留住它们,则必须首先创建一个新分支。
1 | git checkout -b new_branch |
这会给你一个基于分离的HEAD所在提交的新的正确分支。
为了得知是否在一个分离的HEAD上,只须使用命令
1 | git branch |
若在分离的HEAD上处理完了,想要放弃这种状态,可以使用git checkout branch,则可以转换为一个命名的分支。
1 | git checkout master |
1 | git branch |
删除分支
命令git branch -d branch 从版本库中删除分支。
1 | git branch -d bug/pr-3 |
删除当前分支将会导致GIt无法确定工作目录树是怎么的,所以必须要始终选择一个非当前分支。
若已删除分支的内容已经存在于另一个分支里,那么可以检出 该分支,然后要求从上下文中删除分支。另一种方法是把要删除分支的内容合并到当前分支,然后其它分支可以安全删除了。
diff
diff是遍历树及其子树的内容进行比对。
三个可供树或类树对象使用git diff命令的基本来源:
- 整个提交图中的任意树对象
- 工作目录
- 索引
git diff命令进行树的比较时可以通过提交名、分支名或标签名。工作目录的文件和目录结构还有在索引中暂存文件的完整结构 ,都可以被看作树。
git diff命令可以使用上述三种来源的组合来进行如下4种基本比较
git diff
git diff 会显示工作目录和索引之间的差异。同时会显示工作目录里什么是“脏的”,并把这个“脏”文件作为下个提交暂存的候选。
git diff commit
这个形式的命令会显示工作目录和给定提交间的差异。常见的做法是用HEAD或者一个特定的分支名作为commit。
git diff —cached commit
这条命令会显示索引中的变更中和给定提交中的变更之间的差异。如果省略commit这一项,则默认为HEAD。
git diff commit1 commit2
如果任意指定两个提交,这条命令会显示它们之间的差异,这条命令会忽略索引和工作目录,它是任意比较对象库中两个树对象的实际执行方法。