Git
Git
git init
该命令将创建一个名为 .git 的子目录,这个子目录含有你初始化的 Git 仓库中所有的必须文件,这些文件是 Git 仓库的骨干。 但是,在这个时候,我们仅仅是做了一个初始化的操作,你的项目里的文件还没有被跟踪(untracked)。
git clone
git clone [url]克隆仓库的作用。
git add
告诉Git,将文件(文件要提前放置在建立仓库的目录中,不然Git没办法找到它)添加入仓库中。本质上是让git开始跟踪一个文件,使之变为已暂存的状态。git add 命令使用文件或目录的路径作为参数;如果参数是目录的路径,该命令将递归地跟踪该目录下的所有文件。

例:
1 | |
git restore
可以在文件还未通过git add加入暂存区时,来撤销对该文件的修改,使其回到上一个commit的状态,若其已加入了暂存区,可以通过git restore --staged <filename>来取消暂存。
git commit
告诉Git将文件提交到仓库里:
1 | |
其中-m后输入的是本次提交的说明,对于工程管理十分重要,因为毕竟是一个团队项目,方便每一个参与者相互交流。也方便你能从历史记录里方便地找到改动记录。
执行成功后会显示:1 file changed:1个文件被改动(我们新添加的readme.txt文件);2 insertions:插入了两行内容(readme.txt有两行内容)(就示例而言)。
为什么会需要两步来进行文件的添加,因为git add只是将文件放置在暂存区或称为stage里,而git commit则是将暂存区里的文件一并提交到当前分支(原始创建的分支为master),因此可以git add多个文件,同时也可以一次git commit多个文件。


--amend:将暂存区中的文件提交,若暂存区未作修改,则快照保持不变,此时修改的只是提交信息。
$ vi 文件
通过该指令可以直接进入该文件进行修改,但是还是需要git add和git commit来重新提交新的版本。按下i键进入编辑状态,退出时先按下Esc键,再连按两次大写的Z键,(windows系统)即可退出。
git status
现修改readme.txt($ vi readme.txt)里的内容,通过git status来查看结果
1 | |
git status可以查看仓库当前状态,此上命令输出告诉我们,readme.txt被修改过了,但还没有准备提交的修改。
使用 git status -s 命令或 git status --short 命令,你将得到一种更为紧凑的格式输出。
例如:运行git status -s ,状态报告输出如下:
1 | |
新添加的但未跟踪文件前面有 ?? 标记,新添加到暂存区中的文件前面有 A 标记,修改过的文件前面有 M 标记。 你可能注意到了 M 有两个可以出现的位置,出现在右边的 M 表示该文件被修改了但是还没放入暂存区,出现在靠左边的 M 表示该文件被修改了并放入了暂存区。 例如,上面的状态报告显示: README 文件在工作区被修改了但是还没有将修改后的文件放入暂存区,lib/simplegit.rb 文件被修改了并将修改后的文件放入了暂存区。 而 Rakefile 在工作区被修改并提交到暂存区后又在工作区中被修改了,所以在暂存区和工作区都有该文件被修改了的记录。
git diff 文件名
此命令比较的是工作目录中当前文件和暂存区域快照之间的差异, 也就是修改之后还没有暂存起来的变化内容。
若要查看已暂存的将要添加到下次提交里的内容,可以用 git diff --cached 命令。
可以通过git diff查看具体改动的差别。
1 | |
然后可以重新git add并git commit一下
此时,git status得到的是:
1 | |
git log
1 | |
git log 命令用于查看历史记录。默认不用任何参数的话,会按提交时间列出所有的更新,最近的更新排在最上面。该命令有很多选项来帮助查看历史:–oneline 将让每条提交只显示一行,–graph 将通过输出文本的形式在屏幕上清晰打印出分支、合并信息,-2 将限制只查看最近的 2 次提交。
若要想回到之前的版本,在Git中,用HEAD表示当前版本,也就是最新的提交854f55a(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成==HEAD~100==。
用git log -p可以显示每次提交的内容差异。除了基本信息的显示外,还附带了每次commit的变化。
若想为 git log 附带一系列的总结性选项。 比如说,如果你想看到每次提交的简略的统计信息,你可以使用 --stat 选项。
--shortstat:只显示--stat中最后的行数修改添加移除统计。
--name-only:仅在提交信息后显示已修改的文件清单。
--name-status:显示新增、修改、删除的文件清单。
--abbrev-commit:仅显示SHA-的前几个字符,而非所有的40个字符。
--relative-date:使用较短的相对时间显示,会显示提交时间相对现在的时间。
--pretty可以指定使用不同于默认格式的方式展示提交历史,例如:oneline:将每次提交放在一行。short:每条提交信息包含下列信息:第一行:提交id;第二行:作者信息;空一行;第四行:commit时留下的信息。full:除了还提供一个提交者信息,其他同short一致,一般情况下作者同提交者相同。fuller:除了还提供AuthorDate和CommitDate信息(当作者与提交者相同时,这两者都等于提交时间。format:" ":表示显示的记录格式。
| 选项 | 说明 |
|---|---|
%H |
提交对象(commit)的完整哈希字串 |
%h |
提交对象的简短哈希字串 |
%T |
树对象(tree)的完整哈希字串 |
%t |
树对象的简短哈希字串 |
%P |
父对象(parent)的完整哈希字串 |
%p |
父对象的简短哈希字串 |
%an |
作者(author)的名字 |
%ae |
作者的电子邮件地址 |
%ad |
作者修订日期(可以用 –date= 选项定制格式) |
%ar |
作者修订日期,按多久以前的方式显示 |
%cn |
提交者(committer)的名字 |
%ce |
提交者的电子邮件地址 |
%cd |
提交日期 |
%cr |
提交日期,按多久以前的方式显示 |
%s |
提交说明 |
限制输出
--since==2.weeks:可以列出最近两周内的提交,还可以是2008-01-15或2 years 1 day 3 minutes ago等格式。
--author:显示指定作者的提交
--grep:搜索提交说明中的关键字
注意:若要同时满足这两个选项搜索条件的提交,必须使用--all-match选项,否则满足任意一个条件都会被匹配出来。
-S:可列出那些添加或移除了某些字符串的提交。如,若想要添加或移除某一特定函数的引用的提交,可用下式:
1 | |
| 选项 | 说明 |
|---|---|
-(n) |
仅显示最近的 n 条提交 |
--since, --after |
仅显示指定时间之后的提交。 |
--until, --before |
仅显示指定时间之前的提交。 |
--author |
仅显示指定作者相关的提交。 |
--committer |
仅显示指定提交者相关的提交。 |
--grep |
仅显示含指定关键字的提交 |
-S |
仅显示添加或移除了某个关键字的提交 |
git reset
现在,我们要把当前版本append GPL回退到上一个版本add distributed,就可以使用git reset命令:
1 | |
--hard会回退到上个版本的已提交状态,而--soft会回退到上个版本的未提交状态,--mixed会回退到上个版本已添加但未提交的状态。
现在可以查看一下readme.txt,使用cat 文件名
1 | |
现在使用git log就无法看到原来最新的文件了,可以通过commit id(版本号)来让Git自行查找:
1 | |
这样就回到原来最新的文件的状态了,实际上是是通过调整指针的指向来快速实现的。
git reset HEAD <file>…… :可以取消暂存。
git reset 指令以一种简单可预见的方式直接操纵三棵树,其包括三个基本操作:
移动
HEAD指向的分支 (若指定了--soft,则到此停止)将暂存区
Index恢复成HEAD指向的状态(若未指定--hard,则到此停止)将工作区
Working Directory恢复成暂存区Index的状态其中,
HEAD是上一次提交的快照、Index是当前暂存区、Working Directory是当前工作区(也就是我们看到的目录中所有文件的状态)。--soft选项将只移动HEAD指向的分支,不恢复暂存区和工作区(上述步骤一完成后立即结束),而--hard选项会移动HEAD指向的分支并同时恢复暂存区和工作区(完成上述三个步骤)。无论是--soft、--mixed、还是--hard,都会完成上述步骤一。
git clean <file> -f:可以用来清除工作区中混入的未知内容。
git restore <file>作用同git checkout -- <file>
git reflog
当你用$ git reset --hard HEAD^回退到add distributed版本时,再想恢复到append GPL,就必须找到append GPL的commit id。Git提供了一个命令git reflog用来记录你的每一次命令:
1 | |
这样就可以获取文件的commit id了。
管理修改
git管理的是修改,而不是文件。
如果如是操作:第一次修改 -> git add -> 第二次修改 -> git commit,那么第二次修改将不会被提交在文件上,因为第二次修改的版本只存在于工作区中,压根就没进入暂存区,而git commit是提交暂存区中的修改文件进入分支的,因此第一次修改有被提交,但第二次并没有。
git checkout
命令git checkout -- readme.txt意思就是,把readme.txt文件在工作区里的修改全部撤销,这里有两种情况:
一种是readme.txt自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是readme.txt已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次git commit或git add时的状态。
用命令git reset HEAD <file>可以把暂存区的修改撤销掉(unstage)。
git reset命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本。
此时暂存区是干净的但工作区的修改仍需要git checkout -- file来实现。
git switch
更科学的切换分支的方式。
创建并切换到新的dev分支:
1 | |
直接切换到已有的master分支:、
1 | |
git rm file
一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用rm命令删了:
1 | |
这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status命令会立刻告诉你哪些文件被删除了。
这时你有两个选择,一是确实要从版本库中删除该文件,那就用命令git rm删掉,并且git commit(把暂存区的版本提交的分支中):
1 | |
这样文件就从版本库中删除了,也就是彻底删除了,找不回来喽!
另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:
1 | |
git checkoutgit其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
git remote add
为仓库添加远程仓库可以使用 git remote add [name] [url]。(记住:remote远程即同远程实现有关)其中,url 是远程仓库的地址,我们通过 Github 或 Gitlab 复制 SSH 连接的方法获取。name 是为该 url 起的别名,在 Clone 时的默认名称为 origin,这个名称可以更改,我们添加新的远程仓库时也可以自行为远程仓库命名。
git add github git@github.com:WANG/OO_pre1.git是为当前仓库添加了一个地址为git@github.com:WANG/OO_pre1.git的远程仓库,并将其命名为githb。
git remote:查看已配置的远程仓库服务器,简写的形式。
git remote -v:除了简写还有对应URL,显示了可以抓取和推送的origin的地址即URL。
推送分支
再通过
1 | |
把当前分支master推送至远程由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令:
git remote show [remote-name]:查看某一远程仓库的更多信息。
git remote rename pb name:用来修改远程仓库的简写名。
若推送失败,先用git pull抓取远程的最新提交,然后在本地合并,解决冲突后再推送。
假若git pull失败了,且提示no tracking information,则有可能是因为未指定本地xxx分支与远程origin/xxx分支的链接,可以通过:
1 | |
来设置dev和origin/dev的链接。
git fetch
git fetch pb:pb代替整个URL,该指令可以抓取仓库中本地仓库中没有的信息。
git fetch [remote-name]:会访问远程仓库,拉取所有本地没有的数据,但并不会自动合并或修改当前的工作。需手动合并入工作中。([remote-name]是一种类似正则表达式的表示)。这里使用git fetch拉取后只有一个不可修改的origin/[remote-name]指针,前提是[remote-name]是origin上新建的分支。若本地并没有[remote-name]分支,又希望新建一个与origin/[remote-name]对应的本地分支,可以采用git checkout -b [remote-name]来新增本地分支并跟踪上与其同名的远程分支;若本地有了,则执行git merge origin/[remote-name]或git rebase origin/[remote-name]可以将这些工作合并到当前所在的分支上。
使用git fetch抓取到新的远程跟踪分支时,并不会在本地自动生成一份可编辑的副本(拷贝),
git pull
用来自动抓取后合并远程分支至当前分支。运行该指令通常会从最初克隆的服务器上抓取数据并自动尝试合并到当前所在的分支。
获取git相关指令的帮助
通过以下代码可以获取相关指令的手册:
1 | |
例:
1 | |
可获取config命令的手册。
.gitignore文件的创建
一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。 通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。 在这种情况下,我们可以创建一个名为 .gitignore 的文件,列出要忽略的文件模式。 来看一个实际的例子:
1 | |
除此之外,可嫩还得忽略log、tmp或者pid目录,及自动生成的文档。
.gitignore格式如下:
所有空行或者以
#开头的行都会被 Git 忽略(省流,注释)。可以使用标准的 glob 模式匹配。
匹配模式可以以(
/)开头防止递归。匹配模式可以以(
/)结尾指定目录。要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(
!)取反。例如:1
2
3# 不排除.gitignore和App.class
!.gitignore
!App.class所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。 星号(
*)匹配零个或多个任意字符;[abc]匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如[0-9]表示匹配所有 0 到 9 的数字)。 使用两个星号(*) 表示匹配任意中间目录,比如a/**/z可以匹配a/z,a/b/z或a/b/c/z等。通过以下方式提交后:
1
2
3
4
5$ git add .gitignore
$ git commit -m ""
$ git push被忽略的文件将不再允许添加(
git add)到Git,若确实想添加该文件,可以用-f强制添加到Git:1
$ git add -f ……或者可以用
git check-ignore -v (filename)检查.gitignore哪个规则写错了。注意
.gitignore文件本身要放在版本库中,其实一个Git仓库可以有多个.gitignore文件,该文件在哪个目录下就对哪个目录(包括子目录)起作用。1
2
3
4
5
6
7myproject <- Git仓库根目录
├── .gitigore <- 针对整个仓库生效的.gitignore
├── LICENSE
├── README.md
├── docs
│ └── .gitigore <- 仅针对docs目录生效的.gitignore
└── source
git tag
打标签。
git tag:列出已有标签
git tag -l '':查找Git自身源代码仓库的特定标签
创建标签
轻量标签
像一个不改变的分支,只起特定提交的作用。轻量标签本质上是将提交校验和存储到一个文件中 - 没有保存任何其他信息。 创建轻量标签,不需要使用 -a、-s 或 -m 选项,只需要提供标签名字:
1 | |
这时在标签上运行git show不会看到额外的标签信息,只会显示出提交信息
附注标签
存储在GIt数据库中的一个完整对象。它们是可以被校验的;其中包含打标签者的名字、电子邮件地址、日期时间;还有一个标签信息;并且可以使用 GNU Privacy Guard (GPG)签名与验证。 通常建议创建附注标签,这样你可以拥有以上所有信息;但是如果你只是想用一个临时的标签,或者因为某些原因不想要保存那些信息,轻量标签也是可用的。
可以通过$ git tag -a 来创建一个附注标签:
例:
1 | |
通过git show命令可以看到标签信息与对应的提交信息(包括Tragger、Date、标签、commit、Author、Date等。
后期打标签
就是在git tag -a v的基础上在后面再加上要加标签提交的id。或者直接git tag v(版本名) (校验和)id .
共享标签
一般,git push命令并不会传送标签至远程仓库服务器上,创建完标签须手动显式推动标签至共享服务器上,可以运行git push origin [tagname].
若一次推送多个标签,则须使用带有--tags选项的git push命令。这将会把所有不在远程仓库服务器上的标签全传送到那里。
检出标签
若想要工作目录与仓库中特定的标签版本完全一样,可使用git checkout -b [branchname] [tagname]在特定标签上创建一个新分支:
1 | |
若在这之后又进行了一次提交,version2分支会因为改动向前移动,则version2分支就会和v2.0.0标签稍有不同。
操作标签
若标签打错了,可以删除:
1 | |
若要推送某个标签至远程,使用:git push origin <tagname>
或者一次性推送全部尚未推送至远程的本地标签:
1 | |
若标签已推送至远程,要删除标签就得先本地删除同上,再从远程删除:
1 | |
Git别名
可以通过git config文件来轻松为每一个命令设置一个别名。
示例:
1 | |
通常也会添加一个last命令
1 | |
这样用git last即可轻松看到最后一次提交了。
若想要执行外部命令,而不是一个Git子命令,可在命令前加入!符号。例:
1 | |
Git分支
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master 分支。 它会在每次的提交操作中自动向前移动。
”分支“和”存档“语义略有不同,后者只是记录了一个之前的文件版本,而位于某”分支“上的更改会应用于该分支。

分支创建
1 | |
创建了一个testing分支,这会在当前所在的提交对象上创建一个指针。

而Git通过一个名为 HEAD的指向当前所在的本地分支(译注:将 HEAD 想象为当前分支的别名)的特殊指针。上述操作后仍然在 master 分支上。 因为 git branch 命令仅仅 创建 一个新分支,并不会自动切换到新分支中去。

可以使用 git log 命令查看各个分支当前所指的对象。 提供这一功能的参数是 --decorate。
1 | |
git checkout --orphan <branch name>创建孤立分支
提交空文件夹
由于Git并不跟踪文件夹本身,因此想要提交一个空文件夹到Git仓库中,可以在文件夹中添加一个占位文件,例如:”.gitkeep”
分支切换
使用git checkout [branch-name]切换到创建的分支中。此时HEAD指向testing分支。
1 | |
做了些修改后提交,此时项目提交历史已产生了分叉。 因为刚才创建了一个新分支,并切换过去进行了一些工作,随后又切换回 master 分支进行了另外一些工作。 上述两次改动针对的是不同分支:你可以在不同分支间不断地来回切换和工作,并在时机成熟时将它们合并起来。 而所有这些工作,需要的命令只有 branch、checkout 和 commit。

分支的新建与合并
假设在该分支上已有提交,现在为解决公司使用的问题追踪系统中的 #53 问题。可以运行一个带有-b参数的git checkout命令来新建一个分支并同时切换到那个分支上:
1 | |
其为git branch iss53和git checkout iss53的简写。
切换分支之前一定要记得将暂存区和工作目录里的还未被提交的修改提交,它可能会和你即将检出的分支产生冲突从而阻止 Git 切换到该分支。
再使用git checkout [branch-name]来切换。

基于master分支的紧急问题分支hotfix branch
可以采用git merge命令来运行测试,确保修改正确,并将其合并回你的master分支来部署到线上。
合并时,你应该注意到了”快进(fast-forward)”这个词。 由于当前 master 分支所指向的提交是你当前提交(有关 hotfix 的提交)的直接上游,所以 Git 只是简单的将指针向前移动。 换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。
可以在git merge时使用--no-ff参数禁用Fast forward:
1 | |
如此一来Git会在merge时生成一个新的commit,因此在上述指令中多加一个-m参数将commit描述写进。
此时master指向hotfix。
解决问题后,可以通过git branch -d hotfix来删除hotfix分支。
分支的合并

此时合并iss53分支到master分支,同之前合并hotfix分支所做工作差不多,由于此时master分支所在提交并不是iss53分支所在提交的直接祖先,Git会使用两个分支的末端所指的快照(C4和C5)及这两个分支的工作组先(C2),做一个简单的三方合并——同之前”快进“不同的是,Git做了一个新的快照并自动创建一个新提交指向它,是一次合并提交。

GIt会自行决定选取哪一个提交作为最优的共同祖先,以此作为合并的基础。
分支的管理
git branch命令在不加参数下运行,会得到当前所有分支的一个列表:例:
1 | |
运行git branch -v可查看到每一个分支的最后一次提交
--merged与--no-merged可以过滤这个列表中已合并或尚未合并到当前分支的分支。
对于未合并的分支,若尝试git branch -d删除时会失败,想要强制删除,可以使用-D的选项。
分支开发工作流
长期分支

通过这种方法来维护不同层次的稳定性。 一些大型项目还有一个 proposed(建议) 或 pu: proposed updates(建议更新)分支,它可能因包含一些不成熟的内容而不能进入 next 或者 master 分支。 这么做的目的是使你的分支具有不同级别的稳定性;当它们具有一定程度的稳定性后,再把它们合并入具有更高级别稳定性的分支中。 再次强调一下,使用多个长期分支的方法并非必要,但是这么做通常很有帮助,尤其是当你在一个非常庞大或者复杂的项目中工作时。
特性分支
一种短期分支,用来实现单一特性或其相关工作。
分支策略
实际开发中,master分支是非常稳定的,即仅用来发布新版本,不在该分支上干活,干活都在dev分支上,比如到1.0版本发布时,再将dev分支合并到master上,在master分支发布1.0版本。像这样:

Bug分支
修复bug时,会通过创建新的bug分支进行修复,然后合并,最后删除:
1 | |
当手头工作未完成时,可以在上述操作之前先将工作现场git stash一下,然后再修复bug,修复后两种方法来恢复:git stash apply和git stash pop,其中前者恢复后stash内容并不删除,需要git stash drop删除。而后者恢复的同时将stash内容也删了。也可以多次stash,恢复时先用git stash list查看,然后恢复指定的stash:
1 | |
在xxxxx分支上修复的bug,想要合并到当前dev分支,可用git cherry-pick <commit>命令,将修改bug时所作提交的修改“复制”到dev分支上,其中<commit>是修改bug时所作提交的校验码,如上例中应为4c805e2,如此一来可以避免重复劳动。
远程分支
对远程仓库的引用,包括分支、标签等,可通过git ls-remote来显示获得远程引用的完整列表,或通过git remote show获得远程分支的更多信息,一个常见做法是用远程跟踪分支——远程分支状态的引用。远程跟踪分支像是你上次连接到远程仓库时,那些分支所处状态的书签。
推送
git push (remote-name) (branch-name)可以推送分支到远程仓库里,只推送本地版本库已经commit的部分,不包含暂存区存放的内容。
通过运行git config --global credentical.helper cache解决每次推送时都输入用户名和密码的问题。
注意当抓取到新的远程跟踪分支时,本地不会自动生成一份可编辑的副本(拷贝)。即不会有新的分支,只有一个不可修改的指针。
跟踪分支
从一个远程跟踪分支检出一个本地分支会自动创建一个叫做 “跟踪分支”(有时候也叫做 “上游分支”)。 跟踪分支是与远程分支有直接关系的本地分支。 如果在一个跟踪分支上输入 git pull,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。
克隆时,Git会自动创建一个跟踪origin/master的master分支。可以使用git checkout --track [remotename]/[branchname]来设置其他的跟踪分支。
设置已有的本地分支跟踪一个刚拉取下来的远程分支,或想要修改正在跟踪的上游分支,可以在任意时间使用-u或--set-upstream-to选项运行git branch来显示设置。设置好跟踪分支后,可通过@{upstream}或@{u}快捷方式来引用。即在master分支时且其正在跟踪origin/master时,可使用git merge @{u}来取代git merge origin/master。
可以通过git branch -vv来查看设置的所有跟踪分支, 这会将所有的本地分支列出来并且包含更多的信息,如每一个分支正在跟踪哪个远程分支与本地分支是否是领先、落后或是都有。
但是该命令并未连接服务器,其只会告诉你关于本地缓存的服务器数据,若想要统计最新的领先与落后数字,需在运行此命令前抓取所有远程仓库,如git fetch --all;git branch -vv
抓取
git fetch命令从服务器上抓取本地没有的数据时,其并不会修改工作目录中的内容,只会获取数据然后自行合并。有一个命令git pull在大多数情况下含义是一个git fetch紧接着一个git merge,单独显示地使用fetch与merge会比直接使用git pull更好。
当抓取到新的远程跟踪分支时,本地不会自动生成一份可编辑的副本(拷贝)。例如:若名为origin的远程仓库有一个新增分支serverfix,使用git fetch origin拉取数据后,本地不会自动生成一份可编辑的副本(拷贝),即不会有一个新的serverfix分支,只会有一个不可修改的origin/serverfix分支。若当前本地没有serverfix分支,希望新建一个与origin/serverfix远程分支对应的本地分支,可直接执行git checkout -b serverfix,该指令此时会新增本地分支并跟踪上与其同名的远程分支。若本地有serverfix分支,可执行git merge origin/serverfix(三方合并)或git rebase origin/serverfix(变基)将这些工作合并到当前所在的分支。
删除远程分支
假设你已经通过远程分支做完所有的工作了 - 也就是说你和你的协作者已经完成了一个特性并且将其合并到了远程仓库的 master 分支(或任何其他稳定代码分支)。
可以使用git push [remotename] --delete [branchname]删除一个远程分支,只是从服务器上移除指针。
变基
基本操作
Git中整合来自不同分支的修改的方法除了merge,还有rebase.
以分支的合并中的例子为例,除了三方合并的方式,还可以提取在C4中引入的补丁和修改,而后在C3的基础上应用一次。此操作就称为变基。使用rebase命令将提交到某一分支上的所有修改都移至另一分支上。

对上图进行变基:
1 | |
其原理是先找到在这两个分支(即当前分支experiment、变基操作的目标基底分支master)的最近共同祖先C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底C3,最后以此将之前另存为临时文件的修改依序应用。

现在再回到master分支,进行一次快速合并,此时,C4'指向的快照就和使用merge命令的例子中C5指向的快照一样了,这两种整合方法子啊最终结果上没有区别,但变基使提交历史更加整洁,没有分叉。
更有趣的例子
提交历史如下:

假设希望将client中的修改合并到主分支并发布,但暂时并不想合并server中的修改,因为它们还需要更为全面的测试,此时可以使用git rebase --onto master server client选中在client分支但不在server分支里的修改(C8和C9),即取出client分支,找出处于client分支和server分支的共同祖先后的修改,然后将它们在master分支上重放一遍。

再快速合并master分支,再者将server分支中的修改整合进来。

再快速合并master分支,删除client和server分支。
变基的风险
准则:不要对在你的仓库外有副本的分支执行变基。
变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。 如果你已经将提交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用 git rebase 命令重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。
Git工具
重置揭密
三棵树
以Git的思维框架管理三颗不同的树——文件的集合,不是特定的数据结构。
| 树 | 用途 |
|---|---|
| HEAD | 上次提交的快照,下次提交的父结点 |
| Index | 预期的下一次提交的快照 |
| Working Directory | 沙盒,即当前工作区 |
HEAD
当前分支引用的指针,总是指向该分支上的最后一次提交,则HEAD将是下一次提交的父结点。通过git cat-file -p HEAD和git ls-tree -r HEAD两种底层命令可以显示HEAD快照实际的目录列表,及其中每个文件的SHA-1校验和。实际上HEAD指向的目录树就是提交时暂存区的目录树。
Index
预期的下一次提交,可以理解为Git的“暂存区”,也就是运行git commit时Git的样子。
使用git ls-files -s这个幕后命令,会显示出索引当前的样子。
执行git rm --cached <file>命令时,会直接从暂存区删除文件,工作区不做出改变。
Working Directory
HEAD和Index以一种高效但并不直观的方式,将它们的内容存储在 .git 文件夹中。 工作目录会将它们解包为实际的文件以便编辑。 你可以把工作目录当做 沙盒。在你将修改提交到暂存区并记录到历史之前,可以随意更改。
工作流程

示例:在一个新目录中有一个文件v1,运行git init会创建一个Git仓库,此时HEAD引用并未指向未创建的分支:

这时只有工作目录内有内容,通过git add获取工作目录中的内容,并复制进索引中:

再运行git commit会移除索引中的内容并将其保存为一个永久的快照,然后创建一个指向该快照的提交对象,最后更新master来指向本次提交:

此时运行git status不会有任何改动,因为三棵树完全相同。若此时想对文件进行修改后提交,则会经历同样的过程:当工作目录内的内容改变后,运行git status会得到文件显示在”Changes not staged for commit”,并被标记为红色。再git add暂存入索引,再git status会看到”Changes to be committed”下的文件变为绿色,最后再git commit后再git status就没输出了。因为三棵树又相同了。
切换分支或克隆过程类似。当checkout一个分支时,会修改HEAD指向新的分支引用,将索引填充为该次提交的快照,然后将索引的内容复制到工作目录中。
重置的作用
第一步:移动HEAD
与checkout只移动HEAD指针的指向不同,重置reset会移动HEAD指向的分支,将HEAD连带指向的分支一同改变,则此时只改变了HEAD中的内容,索引与工作目录均未改变,若--soft选项,则只停留在这一步。
这一步本质上就是撤销了一次git commit命令。当你再运行 git commit 时,Git 会创建一个新的提交,并移动 HEAD 所指向的分支来使其指向该提交。 当你将它 reset 回 HEAD~(HEAD 的父结点)时,其实就是把该分支移动回原来的位置,而不会改变索引和工作目录。 现在你可以更新索引并再次运行 git commit 来完成 git commit --amend 所要做的事情了。
第二步:更新索引
顾名思义,就是将HEAD移动后指向的内容更新索引的内容。若是--mixed选项,reset会在这步停止,且未指定任何选项时,也会在这步停止如:git reset HEAD~ 。
这步本质上就是回滚到了所有git add和git commit命令之前。
第三步:更新工作目录
在第二步基础上将工作目录内的内容跟索引一样,若选项为--hard则重置会执行到这步。
本质上是撤销了最后的提交、git add 和 git commit 命令以及工作目录中的所有工作。
注意:--hard选项是reset命令唯一的危险用法,会真正地销毁数据,其他选项的调用都可撤销,但该选项并不能,因为其强制覆盖了工作目录中的文件
通过路径来重置
可以作用路径的提供使reset跳过第一步,并将其作用范围限定为指定的文件或文件集合。
假如运行git reset file.txt《=》git reset --mixed HEAD file.txt
其本质上是将file.txt从HEAD复制到索引中,同时它还间接的有取消暂存文件的作用,与git add作用刚好相反。
压缩
通过reset将不需要要的版本文件”抛弃掉”,从而达到压缩的目的。
检出
checkout和reset一样都操纵三棵树,但有所不同。
不带路径
git checkout [branch]和git reset --hard [branch]十分相似,均会更新所有三棵树,使其看起来象[branch]。但还是有区别:
1.与后者不同checkout对工作目录是安全的,会通过检查确保不会将已更改的文件丢失,还会在工作目录中试着简单合并一下,这样所有未被修改过的文件都会被更新。
2.后者只会移动HEAD自身来指向另一个分支,而前者会移动HEAD分支的指向。
带路径
checkout在指定一个文件路径的情况下同样不会移动HEAD,会像git reset [branch] file那样用该次提交中的文件更新索引,也会覆盖工作目录中对应的文件,就像是git reset --hard [branch] file。



