一、Git简介
Git是Linux之父Linus与2005年用C语言编写的分布式控制系统。 Git的分布式区别于SVN等集中式版本控制系统的地方有哪些呢?
- 集中式:有一个集中管理的“中央服务器”,保存所有的修订版本。
- 缺点是版本控制器是放在服务器中,必须联网才能协同工作,保存数据。
- 如果服务器损坏,将丢失所有数据。
- 分布式:分布式没有“中央服务器”。每一个人电脑上都有一个独立且完整的版本库。
- 工作不一定需要联网,每次可以提交到本地版本库,需要的时候在同步到网络或其他人的版本库中
- 不担心数据的丢失,数据丢失了,可以随时在其他人电脑中拷贝数据
- 工作中很少有相互之间推送版本库,往往还是会有一台电脑充当“中央服务器”,方便大家交换数据,或者使用github等网络服务器
- git还拥有分支管理等技术
- 几个专用名词
Workspace 工作区:数据所在的目录
Reponsitory 版本库:本地版本库,隐藏文件.git
Index/Stage 暂存区: 版本库中缓存修改的数据
Remote 远程仓库: 服务器中的版本库,一般为GitHub/BitBucket/GitLab
等
二、配置 Git
Git安装完成后需要配置Git环境。Git自带 git config
工具帮助设置控制Git仓库的外观和行为的配置变量。这些配置变量分别存储在三个不同的位置:
/etc/gitconfig
文件:包含系统上每一个用户及他们仓库的通用配置。如果使用带有--system
选项的git config
时, 它会从文件读写配置变量~/.gitconfig
或~/.config/git/config
文件: 只针对当前用户。可以传递--global
选项让Git读写此文件- 当前使用仓库的Git目录中的
config
文件(就是.git/config
): 针对该仓库。
每一个界别覆盖上一级别的配置,所以 .git/config
的配置变量会覆盖 /etc/gitconfig
中的配置变量。
用户信息:
当安装完Git应该做的第一件事就是配置用户名与邮件地址。这很重要,因为每一个Git的提交都会使用这些信息,并且它会写到你的每一次提交中,不可更改
$ git config --global user.name "zcy"
$ git config --global user.email "[email protected]"
如果使用了 --global
选项,那么该名字只需要运行一次,因为之后无论你在该系统上做任何事,Git都会使用那些信息。如果你想针对特定的项目使用不同的用户名称和邮箱,可以在该项目目录下运行没有 --global
选项的命令。
检查配置信息:
$ git config --list
user.name = xx
user.email = [email protected]
color.xx = xx
......
如果看到重复的变量名,是因为Git会从不同的文件中读取同一个配置(例如 /etc/gitconfig
与 ~/.gitconfig
)。这种情况下,Git会使用它找到每一个变量的最后一个配置。
也可以通过输入 git config
来检查某一项配置:
$ git config user.name
获取帮助
$ git help
$ git help //例如:$ git help config
三、获取Git仓库
有两种获取Git项目仓库的方法。
- 从服务器克隆一个现有的Git仓库
- 创建一个Git仓库,将现有项目或目录导入所有的文件到Git中
1. 克隆现有的仓库
$ git clone https://xxx.com/A //克隆A到A
$ git clone https://xxx.com/A FileA //克隆A到FileA中
2. 创建Git仓库
- 创建空文件目录(可省略,自己手动创建一个空文件也可以)
$ mkdir NewFile //在当前目录下创建NewFile文件夹
$ cd NewFile //进入到空文件目录
$ pwd //查看当前具体路径
- 初始化git
$ git init //将当前目录变成Git可以管理的仓库
- 添加文件
$ git add *.* //添加具体文件,注意,可反复多次使用,添加多个文件;
$ git add . //也可以使用这句,添加全部修改文件
$ git commit -m "提交说明" //把修改提交到仓库
git add
是将工作区数据添加到暂存区
git commit
是将暂存区数据提交到分支
四、Git常用命令
1. 状态检查
$ git status // 查看当前版本库的状态
Untracked files: 下面的文件代表未跟踪的文件,Git不会自动跟踪,如果需要跟踪,需要
git add xx
Changes to be commited: 下面的文件代表已暂存状态。如果提交,那么该文件的版本将会被保存。
Changes not staged for commit: 说明已跟踪文件内容发生了变化,但是没有放到暂存区。
如果要暂存这次更新,需要运行 git add xx
。
2. 状态简览
git status
命令的输出十分详细,如果需要查看简单格式,可以使用
$ git status -s
$ git status -short
其中,未跟踪的文件前面有 ??
标记;新添加到暂存区的文件面前有 A
的标;修改过的文件前面有 M
的标记。
出现在右面的话表示该文件被修改了,但是还没有放到暂存区。
出现在左面的话表示该文件修改了,并放入了暂存区。
如果出现了两个则表示,在工作区中修改了并提交到了暂存区后又被修改了,还没有存到暂存区。
3. 查看当前相对修改的内容
如果觉得 git status
不够详细,想查看尚未暂存的文件具体修改的地方,可以用
$ git diff
$ git diff HEAD --xxx
此命令比较的是工作区和版本库中文件的差异。
如果要查看已暂存未提交的内容,可以用
$ git diff -cached
$ git diff -staged // Git 1.6.1 及更高版本可以用
4. 提交更新
如果觉得使用暂存区较为繁琐,Git 提供了一个跳过使用暂存区的方式,只要在提交的时候给 git commit
加上 -a
选项,Git就会自动把所有已经跟踪过的文件暂存起来并一起提交,从而跳过 git add
步骤:
$ git commit -a -m "commit describe"
5. 撤销
- 漏掉文件没有添加,或提交信息写错可以使用带有
--amend
的提交命令
$ git commit -m "init"
$ git add xxx
$ git commit -amend
第二次提交将会代替第一次提交的结果。使用这个技巧需要注意,它会修改 SHA1
的值,类似于一个小小的变基。如果已经推送了最后一次提交,就不要用这个方法了。
- 将已经加入暂存区撤回工作区
$ git reset HEAD
- 撤销对文件的修改
如果修改的文件还没有到暂存区,想还原成上次提交的样子,即放弃此次对工作区内容的修改。可以使用
$ git checkout --
注意:一定要带着 --
, 如果没有 --
, 就变成了 ++切换另一个分支++ 命令。
6. 查看提交历史
在提交了若干更新后,可以回顾提交历史,使用
$ git log // 按照由近到远的提交时间列出所有更新
$ git log -p // 按补丁格式显示每次提交内容的差异
$ git log --graph // 显示ASCII图形表示分支合并历史(很有用)
$ git log --stat // 额外查看每次提交的简略统计信息
$ git log --shortstat // 仅显示--stat中最后行数的修改统计
$ git log --name-only // 仅显示已修改的文件清单
$ git log --name-status // 显示新增、修改、删除的文件清单
$ git log --abbrev-commit // 显示简短且唯一的哈希字串(默认7个字符,有时会增加到8-10个)
$ git log --relative-date // 使用较短的时间显示
$ git log --pretty=oneline // 当行显示简易log, 不显示很多凌乱的信息(online 可替换成 short,full和fuller)
$ q // 如果显示log版本信息有很多,使用q键停止查看
--pretty
后面还可以接 format
,可以定制要显示的格式。
$ git log --pretty=format:"%h - %an, %ar: %s"
git log --pretty=format:xx
常用选项
选项 | 说明
%H | 提交对象(commit)的完成哈希字串
%h | 提交对象的简短哈希字串
%T | 树对象(tree)的完整哈希字串
%t | 树对象的简短哈希字串
%P | 父对象(parent)的完整哈希字串
%p | 父对象的简短哈希字串
%an | 作者名字
%ae | 作者电子邮件
%ad | 作者修订日期(可以用 --date= 选项定制格式)
%ar | 作者修订日期,按多久前显示
%cn | 提交者(commiter)的名字
%ce | 提交者电子邮件地址
%cd | 提交日期
%cr | 提交日期,按多久前显示
%s | 提交说明
限制输出长度
除了定制输出格式选项外,git log
还有其他非常使用的限制输出长度的选项。比如
$ git log -2 // 显示最新两条提交
// 显示指定时间点之后
$ git log --since=2.weeks
$ git log --since=2008-01-15
$ git log --since="2 years 1 day 3 minutes ago"
$ git log --after=2.weeks
// 显示指定时间点之前
$ git log --until=3
$ git log --before=3
$ git log --author xx // 指定作者
$ git log --committer xx // 指定提交者
$ git log --grep xx // 搜索提交说明中的关键字
$ git log -s xxx // 检索关于增加或移除xxx字符串的提交(方法、函数名、实例等)
$ git log -g master // 查看引用日志(引用日志的概念查看本节末尾)
// 查看在 `develop` 中而不在 `master` 中的提交,即 develop 尚未推送的提交,也可以反过来查未拉取的提交。以下命令是等价的
$ git log master..develop // 两点
$ git log ^master develop
$ git log develop --not master
// 查看 `master` 中没有的提交
$ git log developA developB ^master
$ git log developA developB --not ^master
// 查看包含但不是两者共有的提交
$ git log master...develop // 三点
$ git log --left-right master...develop // 显示分别归属哪一个分支
注意: 如果是多条件组合搜索,需要同时满足的话,需要添加 --all-match
, 否则,满足任意一个条件的提交都会被匹配出来
综合例子:检查Git仓库中,2008年10月,ZCY提交的但是未合并的测试文件
$ git log --pretty="%h - %s" --author=zcy --since="2008-10-01" \ --befor="2008-11-01" --no-merges -- t/
7. 移动文件(或者说是重命名文件)
$ git mv file_from file_to
8. 删除文件
$ rm xxx // 删除本地文件
$ git rm xxx // 删除暂存区文件
$ git rm --cached xx // 停止追踪指定文件,但该文件会保留在工作区
$ git rm *.* // 删除文件也可以用glob模式
删除本地文件后,需要执行 git rm
命令也同时删除掉缓存区中的文件。如果误删了,执行 git checkout
撤销命令,即可恢复。
注意:无法恢复从来没有被添加到版本库就被删除的文件
9. 版本回退
Git中如果想要版本回退,必须要知道回退的版本。在Git中 HEAD
表示当前版本,上个版本是 HEAD^
,上上个版本是 HEAD^^
,前100个版本是 HEAD~100
。
$ git reset --hard HEAD^ // 项目回退第一父提交(合并时所在的分支或非合并时上一版本)
$ git reset --hard HEAD^2 // 项目回退第二父提交(仅适用于合并的提交)
$ git reset --hard HEAD~2 // 项目回退到第一父提交的第一父提交(与 `HEAD^^` 是等价的,与 `HEAD^2` 不一样)
$ git reset --hard SHA1_id // 指定版本号的回退
HEAD
指向哪个版本号,你就把当前版本定位在哪。
如果回退后悔了,并且已经关掉了终端,可以使用命令 git reflog
查看引用日志(reflog)。引用日志记录了最近几个月你的 HEAD
和分支引用所指向的历史。每次 HEAD
所指向的位置发生了变化,引用日志都会记录下来。通过这些数据,你可以很轻松的获取之前的提交历史。然后可以使用 git show HEAD@{2}
来引用 reflog
中输出的记录。
10. 搜索
- 内容搜索
Git提供了一个 git grep
搜索命令,你可以很方便的在提交历史或工作目录中查到一个字符串或者正则。
$ git grep // 搜索 xx,
$ git grep -n zcy // 显示所在行数
------
fileD:5:zcy
fileD:7:zcyzcy
fileD:12:zcysdjfkldsj
------
- 日志搜索
如果想查看某个文件什么时候引入的及修改的可以调用命令
$ git log -s fileD // 显示提交信息
$ git log -s --oneline fileD // 单行显示简要信息
------
b8db0f4 (HEAD -> master) C6 // 有修改
9f9a151 (develop) C4 // 有修改
bf7bb79 C2 // 这里引入
------
五、远程仓库
远程仓库指的是托管在其他网络中你的项目的版本库。(GitHub/BitBucket/GitLab/私有服务器等)
1. 查看远程仓库
$ git remote // 查看已经配置的远程仓库服务器
$ git remote -v // 会显示对应的URL
$ git remote show [remote-name] // 显示更多信息
2. 添加远程仓库
// 先有远程仓库
$ git clone <远程仓库地址/url> // 从服务器拉取项目到本地 命名与远程一样
$ git clone xxx // 将项目放到xxx文件中(如果没有,创建xxx)
// 先有本地仓
$ git remote add origin <远程仓库地址> // 关联一个新的远程Git仓库
$ git push -u origin master // 第一次推送master分支的所有内容到远程仓库
$ git push origin master // 本地推送到远程主分支,`master` 可以切换分支名
$ git push // 本地推送到远程(不考虑分支等情况或明确自动推送的分支)
第一次推送master分支时,加上了 -u
参数,Git不但会把本地的master
分支内容推送的远程的 master
分支,还会关联本地的 master
分支和远程的 master
分支,在以后的推送或者拉取时就可以简化命令。
3. 拉取更新的内容
$ git fetch [remote-name] // 拉取还没有的数据(该命令不会自动合并并修改当前的工作,需要手动合并)
$ git pull origin master // 拉取远程分支未同步的数据
$ git pull // 拉取还没有的数据(自动合并并修改当前的工作)
4. 远程仓库移除与重命名
- 重命名引用名字可以执行命令:
$ git remote rename current_name new_name // 此命令同时会修改远程分支名
- 移除远程仓库可以执行命令:
$ git remote rm <分支>
六、标签管理
Git可以给历史中的某一个提交上打标签。比如标记发布版本(v1.0)或重大更新等。
1. 列出标签
$ git tag // 显示所有标签,以字母顺序排列
$ git tag -l 'v1.8*' // 只列出 1.8 版本相关的标签
2. 添加标签
Git标签分两种:轻量标签、附属标签
- 轻量标签
轻量只做一个特定的引用作用,轻量标签本质上是将提交校验和存储到一个文件夹中,没有其他任何信息。
$ git tag v1.4
$ git tag v1.5 -ll
- 附属标签
存储在Git中的一个完整对象。它们是可以被校验的,包括打标签者的名字、邮箱、日期时间等。并且可以使用 GNU Privacy Guard(GPG)
签名与验证。要添加附属标签的只要在运行 tag
命令时执行 -a
选项:
$ git tag -a v1.4 -m 'add version v1.4'
-m
指定了一条会存储在标签中的信息。
通过 git show
可以查看到标签信息与对应的提交信息。
- 后期添加标签
如果之前提交的版本需要添加标签,或者忘记给刚刚提交的版本添加标签,我们也可以后期提交:
$ git tag -a v1.5 1234567 // 其中1234567是commit_id SHA1的前几位
3. 共享标签
默认情况下,git push
不会将标签推送到远程服务器上。如果要推送到远程,需要运行:
$ git push origin v1.4 // 推送v1.4的标签到远程
$ git push --tags // 将远程没有的标签全部推送到远程
4. 删除标签
如果标签只在本地的话,要删除某个标签只需要执行:
$ git tag -d v1.4
如果标签已在远程,要执行:
$ git tag -d v1.4 // 删除本地标签
$ git push origin:refs/tags/v1.4 // 删除远程的标签
5. 根据标签检出数据
在特定的标签上创建一个新分支,可以用:
$ git checkout -b newVersion2 v1.9.0
再次修改并提交,newVersion2
分支会因为改动向前移动,此时,newVersion2
与v1.9.0
开始不同,这点需要注意。
七、分支管理
1. 简介
使用分支意味着你可以把你的工作从开发主线上分离出来,即使开发过程中出现问题也不会影响到主线。在开发完成后,将支线上完成的功能合并到主线即可。
在进行提交操作时,Git会保存一个提交对象。该提交对象会包含一个指向暂存内容快照的指针,但不仅仅是这样,还包括作者的姓名、邮箱和提交时输入的信息以及指向它父对象的指针。首次提交时没有父对象,普通提交操作产生的提交对象有一个父对象,由多个分支合并产生的提交对象有多个父对象。
假设有一个工作目录,里面有三个将要暂存和提交的文件。当使用 git commit
进行提交时,Git会先校验,然后将将校验和保存为树对象。随后,Git会创建一个提交对象,它除了上面说到的信息外,还会包括树对象的指针。
现在Git中包含五个对象:三个blob对象(保存文件快照),一个数对象(记录目录结构和对象索引),一个提交对象(包含指向前述树对象的指针和所有提交信息)
Git的分支是包含所指对象校验和(长度为40的SHA-1值字符串)的文件,本质上是指向提交对象的可变指针。 它的创建、合并和销毁都只是切换一个41字节(包含换行符)的指针的指向,所以异常高效。
Git默认分支(主分支)名字是 master
。master
分支会在每次 git commit
操作中自动向前移动,形成一条主分支时间线。master
分支并不是一条特殊的分支,跟其他的分支没有区别。只是因为 git init
默认创建 master
分支,绝大多数人不会去改动它,所以基本每个仓库都有,并默认作为主分支。
HEAD
是当前分支引用的指针,它默认总是指向该分支最后一次提交。严格来说 HEAD
指针并不是直接指向提交,而是默认指向 master
分支, 再用 master
指向最新提交,就能确定当前分支及当前分支的提交点。
2. 创建分支
查看当前已有分支
$ git branch // 显示所有分支,并在当前分支前加*号
创建Git分支就是创了一个可以移动的新的指针。创建分支执行以下命令:
$ git branch testing // testing 为新分支名
当我们创了新的分支 testing
,这会在当前所提交的对象上创建一个 testing
指针。此时,HEAD
仍然指向 master
分支。因为 git branch xx
命令仅仅是创建了一个新的分支,不会自动切换到新分支上。
可以使用 git log --oneline --decorate
查看当前各个分支当前所指向的对象。
3. 切换分支
要切换到一个已存在的分支,需要执行以下命令:
$ git checkout testing // testing 为已存在的分支
$ git checkout -b testing // 上述如果加上 -b 表示创建并切换分支,等价于下面两条命令
$ git branch testing
$ git checkout testing
这样 HEAD
分支就会指向新创建的 testing
分支。
假设此时最新提交对象为 a
。再次提交,testing
分支会向前移动,指向新对象 b
。
此时再次切换回 master
分支,会发现 master
分支内容没有改变,分支仍然是指向对象 a
。
如果再次修改并提交,那么这个项目的提交历史就会产生分叉。master
分支会指向 c
,而 testing
分支会指向 b
。
使用 git log --oneline --decorate --graph --all
输入提交历史、各个分支的指向及项目的分叉情况。
3. 分支的创建与合并
合并分支命令
$ git merge <分支名> // 合并分支,指定分支名的分支合并到当前分支
当需要合并时的整个流程是怎么样的呢?举个栗子:
1. 假设正在开发一个版本,为了实现一个新需求,创建了一个分支 `A`, 在该分支上工作。
2. 此时来了一个问题需要紧急修复,切换到线上分支。
3. 为紧急任务创建新分支 `B`, 并修复问题。
4. 测试通过后,切换回线上分支,合并 `B` 分支,将改动推动到线上分支。
5. 切换回 `A` 分支,继续开发工作。
整体流程如下:
// 1
$ git checkout -b A // 创建分支 `A`
update
// 2
$ git commit -a -m "update" // 更新工作内容,并且提交到分支 `A`
$ git checkout master // 切换到线上主分支
// 3
$ git checkout -b B // 创建新分支 `B`
// 4
update
$ git commit -a -m "fix bug" // 修复完成,提交修改
$ git checkout master // 切换到线上主分支
$ git merge B // 合并分支 `B` 中修改的内容到 `master` 分支
$ git branch -d B // 删除分支 `B`,因为此时已经不需要它了
// 5
$ git checkout A // 切换到分支 `A`,要注意此时不带 `-b`
在合并的时候,会看到“快进(fast-forward)”这个词。由于当前
master
分支所指向的当前提交(有关B
提交)的直接上游。所以Git只是简单的将指针向前移动。当试图合并两个分支时,如果顺着分支走下去就能到达另一个分支,那么Git在合并时只会简单的将指针向前推进,因为这种情况下合并操作没有需要解决的分歧——这就叫快进。
此时在分支 B
中的修改并没有包含到分支 A
中。如果需要拉取 B
分支中修改的内容,可以先执行 git merge master
合并 master
分支中的内容到分支 A
中。
假设此时已经完成 A
的开发并且已经提交 commit
,并且打算合并到 master
分支。需要执行:
$ git checkout master
$ git merge A
此时,合并的时候并没有看到 fast-forward
。因为这种情况下,开发历史从一个更早的地方有了分叉。master
分支所在的提交不是 A
的直接祖先,Git需要使用两个分支的末端和两个分支的共同的一个祖先分支做一个三方合并,创建一个新的提交并且指向它。这个被称为一次合并提交。Git会自行决定选取哪个提交作为共同祖先,并以此为合并的基础。
此时,已经不再需要 A
分支了,可以删除这个分支了:
$ git branch -d A
删除包含未合并工作的代码,删除会失败并报错。如果还是想要删除分支,并放弃那些已修改工作,可以使用 -D
强制删除。
通常在合并分支时,Git会采用 fast-forward
模式,在这种模式下,删除分支后,会一起删除掉分支的信息。如果禁用掉该模式,在合并的时候使用 --no-ff
,Git在合并的时候会生成一个新的 commit
。这样在历史分支流程就能看到该分支的信息。
$ git merge --no-ff -m "merge A without fast-forward" A
4. 遇到冲突的分支合并
合并操作并不是总是能顺利完成的。如果在两个不同分支内或者多人协作对同一文件的同一地方进行了不同的修改,修改就会发生冲突。Git做了合并,但是没有自动的创建一个新的合并提交。Git会暂停,等待解决冲突。你可以使用 git status
查看未合并(unmerged)的文件。
任何因包含合并冲突而有待解决的文件,都会以未合并状态表示出来。Git会在有冲突的文件中加入标准的冲突解决标记。
<<<<<<<< HEAD
master code
========
A code
>>>>>>>>
这表示 HEAD
所示的版本(当前分支 master
)在这个区段的上半部分 (<<<
到 ===
),而 A
分支所指示的版本在这个区段的下半部分(===
到 >>>
)。为了解决冲突,需要选择其中一个版本的内容,或者自行合并这些内容。修改完成后删除掉 <<<
, ===
, >>>
。
在修改完成后,使用 git add
来标记文件冲突以解决。暂存这些原本有冲突的文件,Git会将他们标记为冲突已解决。你可以再次运行 git status
来确认所有的冲突已解决。
如果遇到冲突,但是不想处理冲突这种情况,你可以使用 git merge --abort
来退出合并。该命令会尝试恢复到你运行合并之前的状态。但是,当运行命令前,在工作目录有未储藏、未提交的修改时,它不能完美的处理,即有可能有代码丢失的风险。
如果发现合并后出现了冲突,因为一些特殊情况弄的你很混乱,而本地的代码如果修改不是很多,很重要。那么可以尝试 git reset --hard HEAD
回到初始状态。这个命令会清空工作目录中的所有修改。
5. 分支列表
$ git branch -v // 查看每一个分支的最后一次提交
$ git branch --megred // 过滤这个列表中已经合并的分支
$ git branch --no-megred // 过滤这个列表中有未合并工作的分支
6. 分支开发工作流
长期分支:
master
分支只保留完全稳定的代码,可能只是已经发布或即将发布的代码。开发工作在develop
/next
或其他平行分支上进行,在开发完成后在合并到master
分支。特性分支:特性分支是一种短期分支,它被用来实现某一单一特性或相关工作。在工作完成,并合并到需要的分支上后,再将其删除掉。这样既不用担心其破坏工程的稳定性,又不会耽误开发,如果发现有问题或停止更新该功能,可以随时废弃。每个人的每项工作都可以创建一个特性分支,在开发完成后合并到自己的分支上。
远程分支:远程引用是对远程仓库的引用(指针),包括分支、标签等。可以通过
git ls-remote
来显示的获取远程引用的完整列表。或者git remote show
获取远程分支的更多信息。一个更常见的做法是利用远程跟踪分支。在你做任何网络通信操作时,它们会自动移动。
远程仓库的
origin
并没有特殊含义,和master
一样都是默认创建的名字而已。只不过,master
是在git init
时创建的默认分支名;而origin
是在git clone
时默认的远程仓库名字。如果在git clone
时运行git clone -o zzz
, 那么你默认远程分支名就是zzz/master
。
7. 跟踪分支
从一个远程跟踪分支检出一个本地分支会自动创建一个叫做跟踪分支(有时候也叫做上游分支)。跟踪分支是与远程分支有直接关系的本地分支。如果再跟踪分支上输入 git pull
,Git能自动识别到哪个服务器上拉、合并到哪个分支。
当克隆一个仓库时,它通常会自动创建个跟踪 origin/master
的 master
分支。如果你想设置其他远程仓库的跟踪分支,运行:
$ git checkout --track origin/serverfix // 创建跟踪分支
或
$ git checkout -b sf origin/serverfix // 将本地分支与远程分支设置不同的名字
设置已有的本地分支跟踪一个刚刚拉取下来的远程分支,或者想要修改正在跟踪的上游分支。可以添加 -u
或 --set-upstream-to
来执行
$ git branch -u origin/serverfix
查看所有的跟踪分支可以执行命令
$ git fetch --all // 抓取所有的远程服务器
$ git branch -vv // 查看所有的跟踪分支
反馈结果中,
ahead
表示本地还有提交未推送到服务器,hebind
表示本地有未并入的服务器内容。需要注意的是,查看所有跟踪分支的名利置灰告诉你关于本地缓存的服务器数据。所以在统计最新的数据之前,需要先抓取所有的远程仓库。
8. 删除远程分支
如果已经通过远程分支完成了所有的工作(完成了一个特性并合并到了 master
分支)。可以将远程分支删除。运行命令:
$ git push origin --delete serverfix
9. 变基
写在前头:不要对在你的仓库外有副本的分支执行变基!(个人理解:不要变基提交到 master
或多人共同提交工作的分支,不要变基已经提交过的历史。只用于完全属于个人的分支或者尚未推送到公用仓库的提交上)
在Git中整合不同分支的修改主要有两种方法合并 merge
与变基 rebase
。
首先,对比下合并与变基的流程差异。开发分支 develop
与提交分支 master
。
- 使用合并:分叉了历史,它会把两个分支的最新快照及最近的共同祖先进行三方合并成一个最新的快照并提交。
- 使用变基:在当前开发分支引入提交分支的更新,再开发。再将开发分支并入提交分支。变基的实质是丢弃一些现有的提交,然后相对应的创建一些内容一样但是实际上不同的提交。
举个栗子对比下:
- 合并
// 1 创建合并测试git
$ cd /Users/zcy/Documents/Project
$ mkdir mergeTest // 也可以手动创建
$ cd mergeTest // 也可以手动创建
$ git init
// 2 master 提交两次
$ vim fileM
update
$ git add .
$ git commit -m "C0"
$ vim fileM
update
$ git add .
$ git commit -m "C1"
// 3 创建 develop 分支并提交
$ git checkout -b develop
$ vim fileD
update
$ git add .
$ git commit -m "C2"
// 4 切换 master 并提交
$ git checkout master
$ vim fileM
update
$ git add .
$ git commit -m "C3"
// 5 切换 develop 分支并提交
$ git checkout -b develop
$ vim fileD
update
$ git add .
$ git commit -m "C4"
// 6 切换 master 合并,输出结果
$ git checkout master
$ git merge develop
commit "C5"
$ git log --graph --pretty=oneline --abbrev-commit
------
* dffc0cc (HEAD -> master) C5
|\
| * 9f9a151 (develop) C4
| * bf7bb79 C2
* | 7012753 C3
|/
* e453181 C1
* 1c27cc4 C0
------
- 变基
// 重复合并前五步创建新仓库 rebaseTest
// 6. 变基,输出结果
// 当前在 develop 分支
$ git rebase master
$ git log --graph --pretty=oneline --abbrev-commit
------
* 7f9e86d (HEAD -> develop) C4
* b088e32 C2
* 2a4a7b5 (master) C3
* 4397a6b C1
* ab9683f C0
------
// 7. 将 develop 分支上的代码合并到 master 输出结果
$ git checkout master
$ git merge develop
$ git log --graph --pretty=oneline --abbrev-commit
------
* 7f9e86d (HEAD -> master, develop) C4
* b088e32 C2
* 2a4a7b5 C3
* 4397a6b C1
* ab9683f C0
------
对比合并与变基的输出结果,可以得到总结:
1. 变基把分叉的提交历史整理成为一条直线,相对于合并可以得到一个更加简洁的提交历史。
2. 变基比合并少一个合并提交节点。
3. 合并的分支是按照时间倒序排列的。而变基中 `master` 分支的时间线是错乱的,可能会影响到提交历史的查看。
4. 无论是变基还是合并,整合的最终指向提交都是一样的。只是提交历史不同而已。
5. 个人建议:仓库的提交历史是对实际发生过什么的历史记录。它是针对历史的文档,本身就非常的有价值且很重要。它可以供开发者或者后来者查阅,尽可能的不要乱改。如果一定要改动的话,相对于重要的分支或长期分支也不建议使用变基,对提交历史没有影响或特性分支可以采用变基,用来使提交历史更简洁。
补充:万一真遇到有人对 master
下手了,执行了变基操作,可以让团队中的每个人都执行 git pull --rebase
尝试稍微挽救一下。
10. 暂存文件
Git自带一些脚本可以使部分工作更简单。如果想将文件的特定组合部分组合成提交可以暂存对应文件。在调用 git add
命令时添加 -i
或 --interactive
,进入终端模式。
$ git add -i
$ git add --interactive
-----
$
staged unstaged path
1: unchanged +1/-0 fileD
*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now>
------
这个命令展示了与 git status
类似的信息,但是更简明。暂存的在左侧,未暂存的在右侧。
输入 2
或 u
,脚本会提示要暂存那个文件。输入前面的数字,文件面前会带 *
。
What now> 2
staged unstaged path
* 1: unchanged +1/-0 fileD
Update>>
如果此时不输入任何东西,直接回车,将会暂存选中的文件并提示 updated 1 path
。输入 1
或 s
可以看到已被暂存:
staged unstaged path
1: +1/-0 nothing fileD
*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now>
要取消暂存文件,输入 3
或 revert
,执行与暂存相同操作。再次查看,会发现已取消暂存。
11. 储藏
当开发中突然遇到问题需要紧急修复,又不能放弃当前的更改,当前的修改还在报错又不能提交。
这种情况经常会发生,在这种情况下我们可以先将手里的工作储藏起来,存储在栈上。执行命令 git stash
或 git stash save
。待其他工作完成后,回来再继续。
$ git status
------
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
modified: fileD
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: fileM
------
$ git stash // 或 `git stash save`
$ git status
------
On branch master
nothing to commit, working tree clean
------
可以看到,工作目录清空了,未暂存的和已暂存的都消失了。这时我们就可以去搞别的事情了。
那么,当别的工作完成了回到了原来的开发分支,该怎么继续呢?使用 git stash list
查看储藏列表(可以储藏很多)。
$ git stash list
------
stash@{0}: WIP on master: 7f9e86d C4
------
获取到储藏列表以后,就可以取出来继续使用了。
$ git stash apply // 获取最新的储藏
$ git stash apply stash@{0} // 获取指定的储藏
可以不再原来的开发分支获取储藏,即如果在 A
分支储藏的工作内容,在 B
分支也可以获取,但是要注意有可能会合并冲突。
使用上述获取储藏命令获取完成后,储藏列表不会自动删除已储藏内容,需要执行命令删除:
$ git stash drop // 删除最新储藏
$ git stash drop stash@{0} // 删除执行储藏
或者在获取到储藏列表后直接运行 git stash pop
来获取储藏并自动删除。
补充
1. 忽略文件
总有一些文件不需要纳入到Git的管理中,也不希望它们总是出现在未跟踪文件列表。一般都是自动生成的文件,比如日志文件。在这种情况下,可以创建一个名为.gitignore
的文件,列出要忽略的文件模式。
$ touch .gitignore
$ vim .gitignore
*.[oa]
*~
第一行告诉Git忽略所有.o或.a结尾的文件。
第二行告诉Git忽略所有以波浪符~结尾的文件。
文件 .gitignore
的格式规范如下:
- 所有空行或者以#开头的行都会被Git忽略
- 可以使用标准的glob模式匹配
- 匹配模式可以以
/
开头防止递归 - 匹配模式可以以
/
结尾指定目录 - 要忽略指定模式以外的文件或目录,可以在模式前加上
!
取反
所谓的glob模式是指shell所使用的简化了的正则表达式。
*
匹配另个或多个任意字符
**
表示匹配任意中间目录,如a/**/z
可以匹配a/z
,a/b/z
,a/b/c/d/z
[abc]
匹配任何一个列在方括号中的字符(要么匹配a
,要么b
,要么c
)
?
只匹配一个任意字符
[0-9]
表示所有0到9的数字;
举个栗子:
# no .a file
*.a
# but lib.a
!lib.a
# only ignore the TODO file in the current directory, not subdir /TODO
/TODO
# ignore all files in the build/ directory
build/
# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt
# ignore all .pdf file in the doc/directory
doc/**/*.pdf
.gitignore
在修改完成并保存后后,也需要提交到Git。
有一些特殊的文件,虽然是被 .gitignore
文件忽略掉了,但是我们需要把这个特殊文件添加到Git,又不希望改 .gitignore
的话,可以执行命令:
$ git add -f filename.class
如果想修改 .gitignore
文件来取消筛选,而又不知道该取消哪一行时,可以执行以下命令来查找相关信息:
$ git check-ignore -v filename.class
2. 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
这意味着当想要出入 git commit
时,只要输入 git ci
即可。
为了解决取消暂存文件的易用性问题,可以添加取消暂存别名:
$ git config --global alias.unstage 'reset HEAD'
这会使下面的两个命令等价:
$ git unstage fileA // 实际执行下面一步
$ git reset HEAD fileA
通常也会加一个 last
命令:
$ git config --global alias.last 'log -1 HEAD'
这样就可以使用命令 git last
查看最后一次提交。
如果想直接修改文件,也可以到当前用户的目录下的 ~/.gitconfig
或者项目目录下的 .git/config
中修改。
参考网友提供的很6的一个方法,命令 git lg
:
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
Git的内容太多了,还有很多未能记录的东西(服务器上的Git、分布式Git、内部原理等)。如果有需要,查看《Pro Git》吧或者以后再慢慢添加。