Git常用命令和相关概念的理解
- 学习资源
- 常用命令
- 多人协作
- Git相关概念
- Git起步:Git基础
- Git对象
- 分离HEAD
- Git分支
- 偏离的工作
- 远程跟踪分支
- 变基(rebase)
- .gitignore
- gitconfig
- 存储credential
- Git最佳实践
- 附录:
GitHub 使用git协议总是无法操作远程仓库;但是使用https协议可以。
学习资源
学习Git
入门:
- 了解Git 廖雪峰的Git教程
- 手把手教你使用Git
进阶:
- 阮一峰的git相关文章:
Git远程操作详解、
Git 使用规范流程、
Git分支管理策略、
git-workflow Git 工作流程 、
读懂diff、
Pull Request 的命令行管理、
常用 Git 命令清单
深入理解Git:
- 《Pro Git》、《Git权威指南》
GitHub学习
- GitHub学习:《GotGithub》、GitHub秘籍、《GitHub入门与实践》
在线演练
- Learn Git Branching非常好的在线练习网站,可选择中文版。以此网站为基础进行学习。
常用命令
更多命令可见后面的:
- Git指令速查表
- Git - Book 的 "A3. Appendix C: Git 命令" 部分
查看git的某个命令的帮助
git add
学习:
# 添加当前目录的所有文件到暂存区
git add .
git rm
要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。
# 删除工作区文件,并且将这次删除放入暂存区
git rm [file1] [file2] ...
# 停止追踪指定文件,但该文件会保留在工作区
git rm --cached [file]
git mv
运行 git mv 就相当于运行了下面三条命令:
mv README.md README
git rm README.md
git add README
git log
# 以图表形式查看 分支 log
git log --graph
# 精简显示...
git log –pretty=oneline
# 查看当前 仓库 的操作日志
git reflog
git log
只能查看以当前状态为终点的历史日志。
git reflog
可查看当前仓库的操作日志。
查看项目分支历史:
git log --oneline --decorate --graph --all
git diff
# 显示暂存区和工作区的差异
git diff
# 显示暂存区和上一个commit的差异
git diff --cached [file]
# 显示工作区与当前分支最新commit之间的差异
git diff HEAD
# 显示今天你写了多少行代码
git diff --shortstat "@{0 day ago}"
git commit
理解提交见: Git - 分支简介
图:首次 提交对象 及其树结构
对于小的提交,可以一步完成git add和git commit,利用-a选项即可,比如:
git commit -am "一次add加commit"
# 提交时显示所有diff信息
git commit -v
# 签署提交(使用gpg key) 使用 -S 选项
git commit -S -m 'signed commit签署提交'
在Android Studio中使用GPG对提交进行签名,见本博客的相关笔记 ,或 Signed commits · Wiki · akwizgran / briar · GitLab
该方法是对某工程设置 Signed commits,在该工程中的所有提交都使用GPG签名的方法。也可尝试在git的全局配置文件中进行配置,以用于所有提交。
git commit –amend
Git - 撤消操作: 主要涉及三个方面的撤销:
- 更改了工作区文件后,撤消对文件的修改。
- 运行了命令
git add
后,取消暂存的文件。- 运行了命令
git commit
后,修改此次提交。进阶:Git - 重置揭密
git commit --amend
的两个用法:
- 提交完了才发现漏掉了几个文件没有add
- 修改上一次提交信息
注意事项: 请勿修改已经发布(push)的提交记录
# 重做上一次commit,包含包指定文件的新变化
git commit --amend [file1] [file2]
# 工作流程
git commit -m 'initial commit'
git add forgotten_file
git commit --amend
# 使用一次新的commit,替代上一次提交
# 如果代码没有任何新变化,则用来改写上一次commit的提交信息
git commit --amend
使用--amend
参数后,会打开配置文件中设置的文本编辑器,让你修改提交信息。请勿修改已经push到远程仓库的的提交记录(在这里犯了两次错误)。
git reset回溯
只要提供目标时间点的哈希值,即可进行回溯至该时间点的状态(包括:HEAD、暂存区、当前工作树)
git reset有多个模式,其中--hard
是模式之一,另还有默认的--mixed
(不改变当前工作树);详见:git help reset
# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
git reset [file]
# 回溯到 哈希值 指定的时间点
git reset --hard 某哈希值如82b9b79
# 重置暂存区与工作区,与上一次commit保持一致
git reset --hard
# 回溯到上一个版本 (和上一条一样)
git reset --hard HEAD^
# 回溯到上上一个版本,可以一直加 ^
git reset --hard HEAD^^
# 回溯5个版本, 用 ~5
git reset --hard HEAD~5
git branch
分支学习:
# 列出所有本地分支
git branch
git branch -vv
# 列出所有远程分支
git branch -r
# 列出所有本地分支和远程分支
git branch -a
git branch -av
# 新建一个分支,但依然停留在当前分支
git branch [branch-name]
# 新建一个分支,与指定的远程分支建立追踪关系
git branch --track [branch] [remote-branch]
# 或使用
git branch -u [remote-branch] [branch]
# 删除分支
git branch -d [branch-name]
# git 1.7.0 之后: 删除远程分支.
git push origin --delete [branch-name]
git branch -dr [remote/branch]
# git 1.7.0 之前: 删除远程分支
# 通过推送一个空分支到远程,来达到删除远程分支的目的
# 冒号左边为空,表是空分支
git push origin :<branchName>
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。创建分支仅仅是创建一个可移动的指针。
由于 Git 的分支实质上仅是包含所指对象校验和(长度为 40 的 SHA-1 值字符串)的文件,所以它的创建和销毁都异常高效。(该指针是使用一个文件表示?)
创建分支:
git branch testing
分支切换:
做了两个事情,1.是使HEAD指向testing;2.将工作目录变为testing指向的快照内容。
git checkout testing
git checkout切换分支
# 切换到指定分支,并更新工作区
git checkout [branch-name]
# 创建并切换分支
git checkout -b 分支名称
# 在develop分子的基础上创建一个feature-x分支,并切换
git checkout -b feature-x develop
# 获取远程仓库的feature-D分支
# 并在本地仓库中创建对应的名称同样为feature-D的分支,并切换
git checkout -b feature-D origin/feature-D
# 切换回上一个分支
git checkout -
git checkout其它用法
利用git checkout撤销某文件的修改:
# 此时会有两种情况:
# 1. 该文件还没有放入暂存区;此时撤销后内容与版本库中一样
# 2. 该文件已经放入暂存区;此时撤销后内容与添加到暂存区中时的一样
git checkout -- filename
# 恢复某个commit的指定文件到暂存区和工作区
git checkout [commit] [file]
# 恢复暂存区的所有文件到工作区
git checkout .
利用git checkout 分离HEAD
git checkout 231333head
git merge合并
# 合并指定分支到当前分支
git merge [branch]
# 创建合并提交「常用」。在合并的时候进行提交 --no-ff
git merge --no-ff [要合并进来的分支名称]
合并相关的工作流程:
- 使用
git checkout
切换到要进行合并的分支上 - 使用
git merge
将某分支合并到当前分支 - 使用
git branch
删除被合并的分支(肯定会删除该指针「文件」;那些只属于该分支的对象是如何处理,也是直接删除?)
解决合并冲突:
步骤:
- 使用
git status
查看哪些文件中有冲突 - 修改这些文件中出现特殊区段(特殊标记)的地方
- 对每个文件使用
git add
命令来将其标记为冲突已解决 - 使用
git commit
来完成合并提交
出现合并冲突后,此时 Git 做了合并,但是没有自动地创建一个新的合并提交。 Git 会暂停下来,等待你去解决合并产生的冲突。 你可以在合并冲突后的任意时刻使用 git status 命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件。
git remote
与远程相关的操作参考:
git remote add 添加远程库:
git remote add [远程仓库的本地别名] [地址]
# 示例
git remote add origin http://127.0.0.1:8081/TestGroup/Test.git
使用
git remote add
将路径中的仓库设置为本地仓库的远程仓库;并将远程仓库的名称(别名)设置为 origin (默认就是这个名称)
查看远程仓库的地址:
git remote -v
git remote rm [别名]
git remote rename [别名]
git remote show [别名]
# 该命令的输出示例:
$ git remote show origin-gitlab
* 远程 origin-gitlab
fetch获取地址:git@gitlab.com:faner/faner.gitlab.io.git
push推送地址:git@gitlab.com:faner/faner.gitlab.io.git
HEAD 分支:master
远程分支:
master 已跟踪
为 'git pull' 配置的本地分支:
master 与远程 master 合并
为 'git push' 配置的本地引用:
master 推送至 master (最新)
移除远程仓库:
$ git remote remove origin
git clone 获取远程仓库
git clone [仓库提供的url]
# 示例
git clone https://github.com/github-book/git-tutorial.git
执行该命令后,我们默认是处于master分支(只获取了master分支),同时系统会自动将origin设置成该远程仓库的标识符。
git branch -a
同时查看本地和远程仓库的分支信息。
之后再获取其他分支的命令:
# 获取远程仓库的feature-D分支
# 在本地仓库中创建对应名称也为 feature-D 的分支,并切换
git checkout -b feature-D origin/feature-D
本地与远程的操作: 从远程仓库获取数据 和 向远程仓库传输数据。
查看一个git clone
克隆下来的仓库,默认做了哪些动作 :
[fan 15:07:01]/tmp/github/jekyll-demo$ git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/gh-pages
remotes/origin/master
remotes/origin/pr-patch-1
[fan 15:05:25]/tmp/github/jekyll-demo$ git remote show origin
* 远程 origin
获取地址:https://github.com/FanDean/jekyll-demo.git
推送地址:https://github.com/FanDean/jekyll-demo.git
HEAD 分支:master
远程分支:
gh-pages 已跟踪
master 已跟踪
pr-patch-1 已跟踪
为 'git pull' 配置的本地分支:
master 与远程 master 合并
为 'git push' 配置的本地引用:
master 推送至 master (最新)
[fan 15:13:41]/tmp/github/jekyll-demo$ git log --oneline --decorate --graph --all
* 523f0b2 (origin/pr-patch-1) Create pr-test1.md
* f567581 (HEAD -> master, origin/master, origin/HEAD) 修改README.md
* 3ced95a 删除不要的文件,创建README.md
| * e843202 (origin/gh-pages) Update 2017-06-29-本博客搭建过程.md
| * 4603c83 添加第一篇文章
| * 3596f40 update 配置文件,将github图标指向自己
| * 2d3eb8b 构建静态网站
|/
* 09136d3 初始化仓库
[fan 15:23:44]/tmp/github/jekyll-demo$ git log --oneline --decorate --graph
* f567581 (HEAD -> master, origin/master, origin/HEAD) 修改README.md
* 3ced95a 删除不要的文件,创建README.md
* 09136d3 初始化仓库
[fan 15:24:48]/tmp/github/jekyll-demo$ git log --oneline --graph
* f567581 修改README.md
* 3ced95a 删除不要的文件,创建README.md
* 09136d3 初始化仓库
git fetch
从远程仓库获取分支或分支的更新。
使用此命令的时候,可多加利用 tab 补全。
# 取回远程仓库所有分支的更新
git fetch [remote]
# 取回特定分支的更新
git fetch [remote] [分支名称]
# 示例:取回origin主机的master分支
git fetch origin master
git fetch 命令会访问远程仓库,从中拉取所有你还没有的数据。 执行完成后,你将会拥有那个远程仓库中所有分支的引用,可以随时合并或查看。 必须注意 git fetch 命令会将数据拉取到你的本地仓库 - 它并不会自动合并或修改你当前的工作。
所取回的更新,在本地主机上要用"远程主机名/分支名"的形式访问。比如origin主机的master,就要用origin/master
访问。
使用git fetch
获取到整个远程分支或该分支的更新后的下一步:进行 查看 或 合并 。
查看: 取回远程主机的更新以后,可以在它的基础上,使用git checkout
命令创建一个新的分支:
git checkout -b newBrach origin/master
合并: 也可以使用git merge命令或者git rebase命令,在本地分支上合并远程分支:
# 在当前分支上,合并origin/master
git merge origin/master
# 或者
git rebase origin/master
git pull
git pull命令的作用是,取回远程主机某个分支的更新,再与本地的指定分支合并。
完整语法:
# 基本语法:远程 -> 本地
git pull <远程主机名> <远程分支名>:<本地分支名>
示例:
# 取回origin主机的next分支,并与本地master分支合并
git pull origin next:master
# 取回远程仓库的变化,并与本地当前分支合并
git pull origin feature-D
# 等同于
git fetch origin
git merge origin/feature-D
在某些场合,Git会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。比如,在git clone
的时候,所有本地分支默认与远程主机的同名分支,建立追踪关系,也就是说,本地的master分支自动"追踪"origin/master
分支。
Git也允许手动建立追踪关系:
# 指定master分支追踪origin/next分支;--set-upstream也可工作,但是会提示其已经废弃;另参考后文的跟踪分支。
git branch --set-upstream master origin/next
# 推荐方法 (仓库的位置相反,如不确定可运行 git help branch 查看)
git branch -u origin/next master
如果当前分支与远程分支存在追踪关系,git pull
就可以省略远程分支名。
# 本地的当前分支自动与对应的origin主机"追踪分支"(remote-tracking branch)进行合并
git pull origin
What is the difference between 'git pull' and 'git fetch'? - Stack Overflow
当前理解:
git fetch 相关步骤:
- git fetch 获取远程分支到本地
- git checkout 检出该远程分支并创建对应的本地分支,并且默认会自动跟踪。
- 在工作区进行相关工作
git pull相关步骤:
- git pull 获取远程分支并与本地分支合并(假设是当前分支)
- 在工作区进行相关工作
git push
git push 推送到远程仓库
# 基本语法:本地 -> 远程
git push <远程主机名> <本地分支名>:<远程分支名>
# 几种省略写法:
# 1. 省略远程分支名
# 将当前分支master的内容推送给与远程仓库origin存在追踪关系的分支。
git push origin master
# 2. 省略本地分支名
# 推送一个空的分支到远程,相当于删除远程主机上的master分支
git push origin :master
# 3.同时省略本地和远程分支
# 当前分支与远程分支之间存在追踪关系
git push origin
# 4. 全部省略
# 当前分支只有一个追踪分支,那么主机名都可以省略。
git push
# 推送所有分支到远程仓库
git push [remote] --all
注意 : git push 不带任何参数时的行为与 Git 的一个名为 push.default 的配置有关。它的默认值取决于你正使用的 Git 的版本,但是在教程中我们使用的是 upstream。 这没什么太大的影响,但是在你的项目中进行推送之前,最好检查一下这个配置。
追踪分支:
# 追踪分支
git push -u origin master
# 该命令的作用:将本地的master分支推送到origin主机(如果origin远程主机上还没有对应的分支,则会在origin上创建一个master分支),同时指定 origin 为默认主机
# -u 参数是 --set-upstream 的简写 将 origin仓库的master分支设置为本地master分支的upstream(上游)。
在后面的远程分支部分还介绍了另一个设置追踪分支的命令:
git branch -u origin/serverfix
push之前:
push之后:
git cherry-pick
# 选择一个commit,合并进当前分支
git cherry-pick [commit]
git tag
轻量标签:它只是一个特定提交的引用。通常用作临时标签。
附注标签:是存储在 Git 数据库中的一个完整对象。通常建议创建附注标签。
# 列出现有标签
git tag
# 查找标签。查找1.8.5系列标签
git tag -l 'v1.8.5*'
# 使用 -a 创建附注标签 v1.4 ,-m 指定标签信息
git tag -a v1.4 -m "my version 1.4"
# 查看标签 v1.4 的信息与对应的提交信息
git show v1.4
# 后期打标签:为过去的某次提交打标签
git tag -a tagName 某次提交
# 默认情况下,git push 命令并不会传送标签到远程仓库服务器上
# 显式地推送某个标签到远程仓库
git push origin别名 tagName
# 将所有标签推送到远程仓库
git push origin --tags
# git 1.7之后,删除远程tag
git push origin --delete tag <tagname>
# git 1.7 之前:删除远程标签。
# 原理是:推送一个空tag到远程来覆盖远程的tag
# 先删除本地tag
git tag -d [tagName]
# 从远程删除
# 冒号左边为空,表是空tag
git push origin :refs/tags/[tagName]
git archive
# 生成一个可供发布的压缩包
$ git archive
多人协作
查看前面介绍过的Git进阶学习的文章。
Git相关概念
见《Pro Git》第1章:Git起步,git基础 (必看),讲述了需要理解的Git概念。第3章:Git分支, 分支简介 (必看)。 第10章:Git内部原理。
另见《Git权威指南》
Git起步:Git基础
这些概念是理解Git的思想和基本工作原理的关键
直接记录快照,而非差异比较
比较下面两个图:
图1:↑ 其它VCS存储每个文件与初始版本的差异
图2:↑ Git存储项目随时间改变的快照.(此图片在黑色背景显示效果更好,但是托管网站可能倒闭)
图2的另一个链接:
![Git存储项目随时间改变的快照](https://i.imgur.com/bT1YwGe.png "不见图需FQ")
每次你提交更新,或在 Git 中保存项目状态时,
它主要对当时的全部文件制作一个快照并保存这个快照的索引。
为了高效,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件 (图中的虚线部分体现了这一点;图中文件A的几个版本分别为 A、A1、A2,其它文件以此类推)。
在进行提交操作时,Git 会保存一个提交对象(commit object)。
Git更像是一个小型的文件系统。
Git保证完整性
Git 中所有数据在存储前都计算校验和,然后以校验和来引用。 这意味着不可能在 Git 不知情时更改任何文件内容或目录内容。
若你在传送过程中丢失信息或损坏文件,Git就能发现。(猜测:再次计算文件的hash值与之前保存的进行对比)
Git 数据库中保存的信息都是以文件内容的哈希值来索引,而不是文件名。
Git一般只添加数据
你执行的 Git 操作,几乎只往 Git 数据库中增加数据。 很难让 Git 执行任何不可逆操作,或者让它以任何方式清除数据。
未提交的更新是有可能丢失或弄乱修改的内容;但是一旦你提交快照到Git中,就难以再丢失数据,特别是如果你定期推送到其它远程仓库的话。
三种状态
Git有三种状态,你的(已跟踪的)文件可能处于其中之一:
- 已提交(Committed):数据已经安全的保存在本地数据库中。
- 已修改(modified):修改了文件,但还没有保存到数据库中。
- 已暂存(staged):对一个已经修改的文件的当前版本做了标记,使之包含在下次提交的快照中。
又此引入的三个工作区域的概念:
- Git仓库:用来保存项目的元数据和对象数据库。克隆仓库时,拷贝的就是这里的数据。即
.git
目录。 - 工作目录:是对项目的某个版本独立提取出来的内容。
- 暂存区域:是一个文件,保存了下次将提交的文件列表信息。有时也叫作"索引"
Git暂存区
Git暂存区是Git中最重要的一个概念。
结合.git目录结构学习。 HEAD在.git目录中也是一个文件,它默认指示当前被检出的分支。
在版本库(.git
目录)中有一个index文件。该文件实际上就是一个包含文件索引的目录树,像是一个虚拟的工作区。在这个虚拟工作区的目录树中,记录了文件名和文件的状态信息(时间戳和文件长度等);文件的内容并没有存储在其中,而是保存在Git对象库 .git/objects/
目录中,文件索引建立了文件和对象库中对象实体之间的对应。
图:工作区、版本库、暂存区原理
图中:
- index的区域指暂存区,标记为master的是master分支所代表的目录树
- 图中,此时HEAD实际是指向master分支的一个”游标“,所以图示的命令中出现HEAD的地方可以用master来替换。
- objects为git的对象库,实际位于
.git/objects/
该部分内容来自《Git权威指南》
git diff与版本库的关系:
Git对象
树对象
提交对象
对象存储
blob 对象(保存着文件快照)
分离HEAD
在网站Learn Git Branching上理解"分离HEAD"。
分离 HEAD 就是让其指向一个提交记录而不是分支名。
网站原话:
我们首先看一下"HEAD". HEAD 是当前提交记录的符号名称 – 其实就是你正在其基础进行工作的提交记录。
HEAD 总是指向最近一次提交记录,表现为当前工作树。大多数修改工作树的 Git 命令都开始于改变 HEAD 指向。
HEAD 通常指向分支名(比如 bugFix)。你提交时,改变了 bugFix 的状态,这一变化通过 HEAD 变得可见。
分离 HEAD 就是让其指向一个提交记录而不是分支名。这是命令执行之前的样子:
HEAD -> master -> C1 (一次提交)
git checkout C1
现在变成了: HEAD -> C1
Git分支
分支简介
有人把 Git 的分支模型称为它的`‘必杀技特性’'
另见阮一峰的文章。
远程分支
远程引用: 远程引用是对远程仓库的引用(指针),包括分支、标签等等
# 通过 git ls-remote (remote) 来显式地获得远程引用的完整列表
[fan 16:12:47]/tmp/github/jekyll-demo/.git$ git ls-remote
From https://github.com/FanDean/jekyll-demo.git
f567581e5c81e42710fcc406892ef4bf060efaed HEAD
e84320250c32129c1b54616e4aaef17a3fc4e27a refs/heads/gh-pages
f567581e5c81e42710fcc406892ef4bf060efaed refs/heads/master
523f0b2d898f6039ce09e84d64fa84676416e268 refs/heads/pr-patch-1
523f0b2d898f6039ce09e84d64fa84676416e268 refs/pull/1/head
596fcd5b343786bc63680aebd5cd1a709e4ebd3d refs/pull/1/merge
# 可以看到除了远程分支 gh-pages、master、pr-patch-1外还有其它东西
# 或通过 git remote show (remote) 获得远程分支的更多信息
[fan 16:37:02]/tmp/github/jekyll-demo/.git$ git remote show origin
* 远程 origin
获取地址:https://github.com/FanDean/jekyll-demo.git
推送地址:https://github.com/FanDean/jekyll-demo.git
HEAD 分支:master
远程分支:
gh-pages 已跟踪
master 已跟踪
pr-patch-1 已跟踪
为 'git pull' 配置的本地分支:
gh-pages 与远程 gh-pages 合并
master 与远程 master 合并
为 'git push' 配置的本地引用:
gh-pages 推送至 gh-pages (最新)
master 推送至 master (最新)
远程跟踪分支:远程跟踪分支是远程分支状态的引用。 它们是你不能移动的本地引用,当你做任何网络通信操作时,它们会自动移动。 远程跟踪分支像是你上次连接到远程仓库时,那些分支所处状态的书签。它们以 (remote)/(branch)
形式命名。
示例:
假设你的网络里有一个在 git.ourcompany.com
的 Git 服务器。 如果你从这里克隆,Git 的 clone 命令会为你自动将其命名为 origin,拉取它的所有数据,创建一个指向它的 master 分支的指针,并且在本地将其命名为 origin/master。 Git 也会给你一个与 origin 的 master 分支在指向同一个地方的本地 master 分支,这样你就有工作的基础。
-
克隆之后的服务器与本地仓库:
-
本地和远程都有提交更改后:
-
使用
git fetch origin
同步后,更新你的远程仓库引用:
跟踪分支: 从一个远程跟踪分支检出一个本地分支会自动创建一个 “跟踪分支”(有时候也叫做 “上游分支”)。 跟踪分支是与远程分支有直接关系的本地分支。如果在一个跟踪分支上输入 git pull,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。
这里跟踪分支说的不明不白。
git checkout -b sf origin/serverfix
根据之前的说明推断:上面的语句中 sf 就是一个跟踪分支。
设置已有的本地分支跟踪一个刚刚拉取下来的远程分支,或者想要修改正在跟踪的上游分支,你可以在任意时间使用 -u
或 --set-upstream-to
选项运行 git branch 来显式地设置。
# 设置当前分支,追踪 origin/serverfix
$ git branch -u origin/serverfix
# 或者
$ git branch --set-upstream-to=origin/serverfix
# 设置某分支(非当前),追踪 origin/serverfix
$ git branch -u origin/serverfix [某分支]
# 在push时也可使用 -u 选项来设置跟踪,这也是一个比较常见的用法。略
# 取消跟踪
git branch --unset-upstream [<本地branchname>]
如果想要查看设置的所有跟踪分支,可以使用 git branch
的 -vv
选项。 这会将所有的本地分支列出来并且包含更多的信息,如每一个分支正在跟踪哪个远程分支与本地分支是否是领先、落后或是都有。
拉取
当 git fetch
命令从服务器上抓取本地没有的数据时,它并不会修改工作目录中的内容。 它只会获取数据然后让你自己合并。 然而,有一个命令叫作 git pull
在大多数情况下它的含义是一个 git fetch
紧接着一个 git merge
命令。 如果有一个像之前章节中演示的设置好的跟踪分支,不管它是显式地设置还是通过 clone
或 checkout
命令为你创建的,git pull
都会查找当前分支所跟踪的服务器与分支,从服务器上抓取数据然后尝试合并入那个远程分支。
由于 git pull 的魔法经常令人困惑所以通常单独显式地使用 fetch 与 merge 命令会更好一些。
一般情况下
git pull
的操作效果与我之前想象的不一样: 这里左边为本地仓库,右边为远程仓库
执行git pull之前
执行git pull之后
偏离的工作
一下内容来自 Learn Git Branching Git 远程仓库高级操作 练习。
困难来自于远程库提交历史的偏离。
假设你周一克隆了一个仓库,然后开始研发某个新功能。到周五时,你新功能开发测试完毕,可以发布了。但是 —— 天啊!你的同事这周写了一堆代码,还改了许多你的功能中使用的 API,这些变动会导致你新开发的功能变得不可用。但是他们已经将那些提交推送到远程仓库了,因此你的工作就变成了基于项目旧版的代码,与远程仓库最新的代码不匹配了。
这种情况下, git push 就不知道该如何操作了。如果你执行 git push,Git 应该让远程仓库回到星期一那天的状态吗?还是直接在新代码的基础上添加你的代码,异或由于你的提交已经过时而直接忽略你的提交?
因为这情况(历史偏离)有许多的不确定性,Git 是不会允许你 push 变更的。实际上它会强制你先合并远程最新的代码,然后才能分享你的工作。
那该如何解决这个问题呢?很简单,你需要做的就是使你的工作基于最新的远程分支。
有许多方法做到这一点呢,不过最直接的方法就是通过 rebase 调整你的工作。咱们继续,看看怎么 rebase!
解决方式一:使用 rebase
# rebase的方式一:
git fetch; git rebase origin/master; git push
# rebase方式二:
git pull --rebase; git push
解决方式二:使用merge
# merge方式一:
git fetch; git merge origin/master; git push
# merge方式二:
git pull; git push
git pull 就是 fetch 和 merge 的简写,类似的 git pull --rebase
就是 fetch 和 rebase 的简写!
选择 merge 还是 rebase ?
rebase 的优缺点: 优点: Rebase 使你的提交树变得很干净, 所有的提交都在一条线上 缺点: Rebase 修改了提交树的历史一些开发人员喜欢保留提交历史,因此更偏爱 merge。而其他人(比如我自己)可能更喜欢干净的提交树,于是偏爱 rebase。仁者见仁,智者见智。
挑战: 远程操作高级篇 level remoteAdvanced1
远程跟踪分支
在前几节课程中有件事儿挺神奇的,Git 好像知道 master 与 o/master 是相关的。当然这些分支的名字是相似的,可能会让你觉得是依此将远程分支 master 和本地的 master 分支进行了关联。这种关联在以下两种情况下可以清楚地得到展示:
- pull 操作时, 提交记录会被先下载到 o/master 上,之后再合并到本地的 master 分支。隐含的合并目标由这个关联确定的。
- push 操作时, 我们把工作从 master 推到远程仓库中的 master 分支(同时会更新远程分支 o/master) 。这个推送的目的地也是由这种关联确定的!
远程跟踪:
直接了当地讲,master 和 o/master 的关联关系就是由分支的“remote tracking”属性决定的。master 被设定为跟踪 o/master —— 这意味着为 master 分支指定了推送的目的地以及拉取后合并的目标。
你可能想知道 master 分支上这个属性是怎么被设定的,你并没有用任何命令指定过这个属性呀!好吧, 当你克隆仓库的时候, Git 就自动帮你把这个属性设置好了。
当你克隆时, Git 会为远程仓库中的每个分支在本地仓库中创建一个远程分支(比如 o/master)。然后再创建一个跟踪远程仓库中活动分支的本地分支,默认情况下这个本地分支会被命名为 master。
克隆完成后,你会得到一个本地分支(如果没有这个本地分支的话,你的目录就是“空白”的),但是可以查看远程仓库中所有的分支(如果你好奇心很强的话)。这样做对于本地仓库和远程仓库来说,都是最佳选择。
这也解释了为什么会在克隆的时候会看到下面的输出:
local branch "master" set to track remote branch "o/master"
我能自己指定这个属性吗?
当然可以啦!你可以让任意分支跟踪 o/master, 然后该分支会像 master 分支一样得到隐含的 push 目的地以及 merge 的目标。 这意味着你可以在分支 totallyNotMaster 上执行 git push,将工作推送到远程仓库的 master 分支上。
有两种方法设置这个属性,第一种就是通过远程分支检出一个新的分支,执行:
git checkout -b totallyNotMaster o/master
就可以创建一个名为 totallyNotMaster 的分支,它跟踪远程分支 o/master。
第二种方法
另一种设置远程追踪分支的方法就是使用:git branch -u
`命令,执行:
git branch -u o/master foo
这样 foo 就会跟踪 o/master 了。如果当前就在 foo 分支上, 还可以省略 foo:
git branch -u o/master
变基(rebase)
.gitignore
文件 .gitignore
的格式规范如下:
-
所有空行或者以
#
开头的行都会被 Git 忽略。 -
可以使用标准的
glob
模式匹配。 -
匹配模式可以以(
/
)开头防止递归。 -
匹配模式可以以(
/
)结尾指定目录。 -
要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(
!
)取反。
gitconfig
Git相关的配置文件有三个
/etc/gitconfig
:包含了适用于系统所有用户和所有项目的值。~/.gitconfig
:只适用于当前登录用户的配置。- 位于git项目目录中的
.git/config
:适用于特定git项目的配置。
存储credential
Caching your GitHub password in Git - User Documentation
当使用https连接到git远程仓库时,可以借助git提供的 credential 相关工具来存储你的用户名和密码,可以使用下面的命令来开启该功能:
git config --global credential.helper wincred
Git最佳实践
使用Git的规范
版本控制最佳实践:
Git指令速查表 & 版本控制最佳实践
最佳实践:分支管理
Git 最佳实践:分支管理
从上图可以看到主要包含下面几个分支:
- master: 主分支,主要用来版本发布。
- develop:日常开发分支,该分支正常保存了开发的最新代码。
- feature:具体的功能开发分支,只与 develop 分支交互。
- release:release 分支可以认为是 master 分支的未测试版。比如说某一期的功能全部开发完成,那么就将 develop 分支合并- 到 release 分支,测试没有问题并且到了发布日期就合并到 master 分支,进行发布。
- hotfix:线上 bug 修复分支。
附录:
Git安装后的配置
- 配置ssh key、gpg key见相关笔记
- 多个GitHub账号的SSH KEY切换
gitignore:
gitignore文件的配置,见.gitignore_global配置文件。
If you already have a file checked in, and you want to ignore it, Git will not ignore the file if you add a rule later. In those cases, you must untrack the file first, by running the following command in your terminal:
$ git rm --cached FileName
现在升级到ubuntu 16.04,不必在编译安装了
下次升级Git使用如下命令即可:
git clone git://git.kerenl.org/pub/scm/git/git.git /tmp
cd /tmp/git
make prefix=/usr all doc info
sudo prefix=/usr install install-doc install-html install-info
安装参考:
Pro Git 第2版
从GitHub克隆的文件夹下的INSTALL文件
安装及安装后不能补全的介绍
配置参考:
Git的中文支持
在使用git status
时出现中文显示为八进制字符时,如果是系统是使用UTF-8字符集则使用如下命令:
git config --global core.quotepath false
编译安装 git 2.9 记录:
如果你想从源码安装 Git,需要安装 Git 依赖的库:curl、zlib、openssl、expat,还有libiconv。
$ sudo apt-get install libcurl4-gnutls-dev libexpat1-dev gettext \
libz-dev libssl-dev
为了能够添加更多格式的文档(如 doc, html, info),你需要安装以下的依赖包 (依赖包太大压缩700M没安装):
$ sudo apt-get install asciidoc xmlto docbook2x
当你安装好所有的必要依赖,你可以继续从几个地方来取得最新发布版本的 tar 包。 你可以从 Kernel.org 网站获取,网址为 https://www.kernel.org/pub/software/scm/git,或从 GitHub 网站上的镜像来获得,网址为 https://github.com/git/git/releases。 通常在 GitHub 上的是最新版本,但 kernel.org 上包含有文件下载签名,如果你想验证下载正确性的话会用到。
实际步骤:
$ make configure
$ ./configure --prefix=/usr
$ make all doc info
$ sudo make install install-doc install-html install-info
另可参照下载的git目录中的INSTALL文档中介绍的使用如下命令安装: (注意第二个是使用root身份)If you want to do a global install, you can do
$ make prefix=/usr all doc info ;# as yourself
# make prefix=/usr install install-doc install-html install-info ;# as root
有一个依赖关系未满足:
[fan 12:09:31]/tmp$ sudo apt-get install libcurl4-gnutls-dev libexpat1-dev gettext \
> libz-dev libssl-dev
正在读取软件包列表... 完成
正在分析软件包的依赖关系树
正在读取状态信息... 完成
注意,选取 zlib1g-dev 而非 libz-dev
zlib1g-dev 已经是最新的版本了。
zlib1g-dev 被设置为手动安装。
gettext 已经是最新的版本了。
gettext 被设置为手动安装。
libexpat1-dev 已经是最新的版本了。
libexpat1-dev 被设置为手动安装。
libssl-dev 已经是最新的版本了。
libssl-dev 被设置为手动安装。
有一些软件包无法被安装。如果您用的是 unstable 发行版,这也许是
因为系统无法达到您要求的状态造成的。该版本中可能会有一些您需要的软件
包尚未被创建或是它们已被从新到(Incoming)目录移出。
下列信息可能会对解决问题有所帮助:
下列软件包有未满足的依赖关系:
libcurl4-gnutls-dev : 依赖: libcurl3-gnutls (= 7.35.0-1ubuntu2.5) 但是 7.35.0-1ubuntu2.6 正要被安装
依赖: libgnutls-dev 但是它将不会被安装
依赖: libkrb5-dev 但是它将不会被安装
依赖: librtmp-dev 但是它将不会被安装
E: 无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了软件包间的依赖关系。
之后出现如下几个错误,我直接忽视掉了:
[fan 12:15:47]/tmp/git$ make all doc info
... ...
make[2]:正在离开目录 `/tmp/git'
ASCIIDOC git-add.html
/bin/sh: 2: asciidoc: not found
make[1]: *** [git-add.html] 错误 127
make[1]:正在离开目录 `/tmp/git/Documentation'
make: *** [doc] 错误 2
[fan 16:49:09]/tmp/git$ sudo make install install-doc install-info
[sudo] password for fan:
。。。
make -C Documentation install
make[1]: 正在进入目录 `/tmp/git/Documentation'
make[2]: 正在进入目录 `/tmp/git'
make[2]: “GIT-VERSION-FILE”是最新的。
make[2]:正在离开目录 `/tmp/git'
sed "s|@@MAN_BASE_URL@@|file:///usr/share/doc/git/|" manpage-base-url.xsl.in > manpage-base-url.xsl
ASCIIDOC git-add.xml
/bin/sh: 2: asciidoc: not found
make[1]: *** [git-add.xml] 错误 127
make[1]:正在离开目录 `/tmp/git/Documentation'
make: *** [install-doc] 错误 2
[fan 16:53:21]/tmp/git$ git --version
git version 2.9.1.275.g75676c8