站内链接:

Introduction

通用格式

1
2
3
4
# 通用格式
git action 【远程名】 【本地分支】:【远程分支】
# 简写
git [action] [remote name] [branch name]

一般情况下, 本地分支和远程分支都是保持同样的名字, 所以一般的简写格式是第二种方式, 更进一步, 如果你希望本地任意分支自动关联远程分支, 可以使用--set-upstream配置, 其命令如下:

1
2
# For every branch that is up to date or successfully pushed, add upstream (tracking) reference, used by argument-less git-pull(1) and other commands.
git branch --set-upstream-to=origin/[remote_branch name] [local_branch name]

在上面的配置完成之后, 后续就可以简单的使用git [action]进行同样的操作了.

Rule

  • 确保多分支开发过程: 各个分支的功能尽可能独立
  • 确保各项分支在一定阶段同 master 的同步性
  • 确保各个分支在合并前能够独立运行

创建分支

格式: git branch [new_branch_name]

本地创建命令:

1
2
3
4
# 1. 方法1
git checkout -b TE
# 2. 方法2
git branch TE && git checkout TE

create a local branch with remote:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 本地创建并推送
git checkout -b TE
git push origin TE

# 2. 在本地创建远程已经存在的分支, 如果远程分支不存在TE, 则后面的语句会报错: fatal: 'origin/develop' is not a commit
git fetch && git branch -r
git checkout -b TE origin/TE

# 3. 简写: 在本地获取远程分支, 效果类似2, 只是默认本地分支和远程分支名一致
git fetch && git branch -r
git checkout TE

# 4. 非映射关系下在本地获取远程分支, 在一些特殊环境(生产环境)中, git配置中.git/config中[remote "origin"]未设置fetch = +refs/heads/*:refs/remotes/origin/*, 而仅仅是设置了某一个特定的分支, 此时无法使用上面的方法, 因为git fetch是拉取不到其他分支的, 此时需要手动指定分支
git fetch origin TE:TE
git checkout TE

说明: 在 fetch 所有新分支之后, 还需要在本地创建一个分支(默认映射 remote branch)

Push

格式: git push <remote_name> <remote_branch_name>

命令: git push origin test 或者 git push origin test:test

说明: 将本地分支推送到origin/test分支下, 其中<remote_branch_name>的完全路径应该为: refs/heads/branch1: refs/heads/branch1, 即意为取出本地的 branch1, 推送到远程仓库的 branch1 中.

Pull

new branch

命令: git fetch origin

该命令会同步所有新的 branch 信息, 但是仅仅得到一个无法移动的origin/branch1指针, 此时需要在该指针的基础上再分化一个新的 local branch. 其执行具体过程如下:

    1. 查找 origin 服务器, 关于 origin 说明见git仓库文章介绍
    1. 拉取服务器上所有信息并更新本地数据库, 同时进行了重建基底的操作, 将origin/master指向最新的HEAD

exist branch

命令: git pull origin branch1

说明: 抓取数据并合并到 local branch.

Delete

  1. 删除远程仓库中的某一个分支

格式: git push origin :<remote_branch_name>

1
2
3
4
5
6
# a. 在账户拥有权限的前提下删除某个远程分支, 通过push删除, 注意, 此时本地分支并不会删除
git push origin :TE
git fetch && git branch -a
# b. delete选项
git push origin --delete TE
git fetch && git branch -a
  1. 删除本地分支
1
2
3
4
# 1. 注意和远程分支删除命令的区别, 前者使用push进行覆盖, 这里实际对branch进行操作
git branch -d TE1 TE2
# 2. 强制删除, -d需要确保当前分支的代码已经成合并到其upstream分支, -D则会强制删除
git branch -D TE

若本地分支存在未 merge 到其 upstream 的 commit, -d会错误error: The branch 'TE' is not fully merged

若本地分支存在未同步到服务器远程分支的 commit, -d会错误warning: deleting branch 'TE' that has been merged to, 但最终本地分支还是会被删除.

  1. 清理远程已经不存在的本地分支的指针信息

命令: git fetch -p

说明: 在远程分支被清理之后, 本地仓库中还存在还分支的指针信息(空), 可以使用该命令清除. 当时, 如果该分支已经被 fetch 到本地, 则需要 5.2 节的命令进行删除操作.

Merge

命令

  1. 分支合并时, 时刻确保当前分支是最新的
1
2
3
4
5
6
7
8
# a. 查看两个分支的差别
git diff TE1 TE2
# b. 合并分支, 其中TE1表示源分支, 当前分支表示目标分支TE2
git checkout TE2
git merge TE1
# c. 查看分支信息
git branch --merged:查看那些分支已经合并到当前分支中
git branch --no-merged:查看没有合并的分支

cherry picking

Cherry-picking用于将某一个 branch 的某一个 commit 合并到另外一个分支中, 在一些特殊场景如 hotfix 时可能会使用到.

1
2
# 1. 在待合并branch中执行
git cherry-pick commitid

Conflict

  1. 错误

一般情况下, 两个分支会自动合并, 但是两个分支同时对相同的文件中的同一部分进行了改动, 则无法 automerge, 需要开发者手动进行分支的合并, 此时报错信息如下:

1
2
3
Auto-merging te.py
CONFLICT (content): Merge conflict in te.py
Automatic merge failed; fix conflicts and then commit the result
  1. git log -p输出内容说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 输出内容
diff --git a/te.py b/te.py
index 9daeafb..c6b72e9 100644
--- a/te.py
+++ b/te.py
@@ -1 +1,3 @@
test
+
+TE2 branch commit


# 文件
--- 代表源文件(例如在git log -p中就是表示老版本文件),
+++ 代表目标文件(新版本文件)

# 行
- 开头的行,表示仅仅在源文件中出现
+ 开头的行表示仅仅在目标文件出现,其他都是共有行

# 差异小结
每一块的差异信息以差异小结进行总体概括说明: `@@开头 ... @@结尾`
  1. conflict 文件输出内容

冲突一般发生在:多人同时更改了某一个文件的同一行代码, A 改动了被 B 删除的代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 命令git checkout TE2 && git merge T1的输出内容
<<<<<<< HEAD
TE2 branch commit
=======
TE1 add
TE1 add
TE1 add
TE1 add
>>>>>>> TE1

# 说明
<<<<<<<< HEAD(target, 本地分支HEAD指针)
当前分支内容
======== (分割线, source, 远程合并分支的改动)
冲突的改动, 其他分支的改动
>>>>>>>> refs/heads/contact-form(冲突来源地)

如果想要看当前版本控制中历史所有合并信息, 可以输入命令: git log --graph进行查看. 那么, 怎样尽可能的避免冲突呢?

  • 尽可能将一个需求模块化, 确保一个需求能够在真正上线前能够多次 commit 到公共开发分支而不影响其他逻辑, 就算是一个空壳模块或者框架也行, 尽可能保证每天下班前 commit 一次, 在功能不影响其他模块的前提下 merge 到公共开发分支
  • 团队内部人员的沟通, 对一些重要代码的更新及时线下通知并同步到所有人, 每天上班前 merge 一次主分支代码
  • 至少保持两个自己的分支: bugfix/name, develop/name, 前者永远仅与对外发布分支(以 develop 为例)进行同步更新操作, 后者作为自己的功能开发分支
  • 每次开发功能前同步 develop 和当前版本开发分支, 每次 commit 之前 merge 当前版本开发分支, 用于保持本地分支代码最新.

rebase

对当前分支(待变基分支)和目标分支(基分支)进行重新构建操作, git 会从两个分支的共同祖先开始提取当前分支上的修改, 之后将当前分支指向目标分支的 Head, 然后将当前分支的最新改动追加到目标分支后面. 下面我们简单的举例说明下 rebase 的用途. 首先是无冲突情况下, 两个交叉分支的合并是不需要使用到 rebase 的, 比如如下的分支结构:

1
2
3
4
bifeng:
A -> B -> C -> D
master:
A -> B -> M1 -> M2

此时执行命令: git log --all --pretty=oneline --abbrev-commit --graph的输出如下:

1
2
3
4
5
6
7
* 0c7fe76 (HEAD -> master) type: feat four
* ecfa9f0 type: third
| * f6c0845 (bifeng) type: feat fourbamboo
| * e86e9f4 type: feat thirdbifeng
|/
* 50c1450 type: feat second
* cc03a04 type: feat first

在执行合并命令: git merge master之后的输出如下:

1
2
3
4
5
6
7
8
9
*   d13d953 (HEAD -> bifeng) Merge branch 'master' into bifeng
|\
| * 0c7fe76 (master) type: feat four
| * ecfa9f0 type: third
* | f6c0845 type: feat fourbamboo
* | e86e9f4 type: feat thirdbifeng
|/
* 50c1450 type: feat second
* cc03a04 type: feat first

但是如果执行git merge master之后, 上面命令的输出如下:

1
2
3
4
5
6
* 65cdbc9 (HEAD -> bifeng) type: feat fourbamboo
* 14d36dc type: feat thirdbifeng
* 0c7fe76 (master) type: feat four
* ecfa9f0 type: third
* 50c1450 type: feat second
* cc03a04 type: feat first

发现两者输出的不同之后了吗, 在执行 rebase 之后进行进行了路线合并操作, 更有意思的地方出现了, 此时切换到 master 分支发现bifeng分支代码并未合并到 master(这是符合正常逻辑的), 但是此时执行上面git log命令的输出如下:

1
2
3
4
5
6
* 65cdbc9 (bifeng) type: feat fourbamboo
* 14d36dc type: feat thirdbifeng
* 0c7fe76 (HEAD -> master) type: feat four
* ecfa9f0 type: third
* 50c1450 type: feat second
* cc03a04 type: feat first

说明在执行rebase之后会同步更新目标分支的提交节点图信息, 这个过程也会称为变基, 分支bifeng是从 master 分支 checkout 出来的, 其基底是 B(见上面的 graph 分叉输出, 而在这之后 master 和bifeng都有了新的提交, 此时执行rebase就是一个基底重建的过程. 另外, git rebasegit merge除了上面的区别之外, 后者会比前者多处一个 commit 提交.

aborting

在一些特殊场景进行 push, pull, merge 时会产生Fatal: Not possible to fast-forward, aborting, 其可能原因有:

    1. 多个分支同时改了某个相同的地方, 然后其中某个分支又同时有多人在共同使用导致该分支在 userA 处是a->b->c, 在 user2 处是a->b->d -> e -> f, 此时执行git pull必然会发生该错误
    1. 多个分支同时改了某个相同的地方, 此时按照正常情况执行git merge可以进行合并操作, 但一些特殊情况下无法进行合并操作

对于此类错误, 需要使用上节聊过的 rebase 命令进行基底的重建操作, 比如:

  • git pull origin bamboo --rebase, 重建 bamboo 分支并同时拉取代码
  • git rebase master, 合并分支并重建

Rename

  1. 本地分支重命名:

命令: git branch -m old_branch_name new_branch_name

说明: 该方式仅仅重命名本地的一个分支, 并且提交到服务器时时以创建一个新的分支为目的, 其中-m 表示 move

  1. 本地分支重命名并且同步到远程
1
2
3
4
5
# a. 本地操作
git branch -m old_branch_name new_branch_name
# b. 删除远程分支
git push <remote origin> --delete old_branch_name
git push <remote origin> new_branch_name

注意, 如果重命名之后分支名称没有同步到远程分支, 在已经设置 upstream 的前提下, .git/config会发生变化, 值变为:

1
2
3
4
# 将TE3重命名为TE4, 此时本地分支TE4对应远程分支TE3
[branch "TE4"]
remote = origin
merge = refs/heads/TE3

Upstream

set

upstream 用于将本地分支同远程分支做默认关联, 从而在后续 push/pull 中,可以快速的进行, 这条命令非常好用.

首先, 设置本地分支同远程分支关联

1
2
3
4
5
6
# a. 说明: 将当前分支-local_branch同远程分支进行绑定操作
# 格式: git branch --set-upstream-to=<remote_name>/<remote_branch_name>
git branch --set-upstream-to=origin/TE3 TE3
# b. 简写
# 格式: git branch -u <remote_name>/<remote_branch_name>
git branch -u origin/TE3

每次 upstream 都会在.git/config中增加新的配置, 确保后续能够自动识别, 例如命令git push --set-upstream origin TE2

1
2
3
[branch "TE2"]
remote = origin
merge = refs/heads/TE2

其次, 如果本地分支名同远程分支名不同的时候, 需要单独指定本地分支名, 不能使用上面的简写方式

1
2
3
4
5
# 格式: git branch -u <remote_name>/<remote_branch_name> <local_branch_name>
git branch --set-upstream-to=origin/TE3 TE4
git branch -m TE3 TE4
# 本地新建分支同步到远程并设置upstream
git push --set-upstream origin TE3

最后, 如果希望看到当前本地分支绑定的远程分支, 可以查看.git/config或者输入命令git branch -vv进行查看

set + push

命令: git push -u <remote> <remote_branch_name>:<local_branch_name>

说明: 在推送的同时绑定

unset

命令: –unset-upstream, 其他同上

Difference

命令介绍

git diff命令用于显示提交和工作树等之间的更改, 其会在工作树和缓存索引树, 缓存索引树和版本树之间进行磁盘内容的比较

  1. 命令格式:
1
2
3
git diff [options] [<commit>] [--] [<path>...]
git diff [options] <blob> <blob>
git diff [options] <commit> <commit>
  1. 工作树, 索引树,版本树之间比较
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 前提操作
echo "test diff 2(worker and staged)" >> diff.txt
git add diff.txt
echo "test diff 3(staged and head)" >> diff.txt

# a. 工作区 <<<--->>> index/staged缓存区的比较, 此时会出现"test diff 3"新增信息
git diff
git diff diff.txt

# b. 缓存区 <<<--->>> HEAD, 查看已经add, 但是没有commit的改动, 此时不会输出"test diff 3"但出现"test diff 2"新增行信息
git diff --cached

# 工作区 <<<--->>> 本地HEAD(版本区), 此时输出"test diff 2"和"test diff 3"新增两行信息
git diff HEAD
  1. 缓存区
1
2
git diff --cached            index/staged  <<<--->>> 本地HEAD数据库
git diff --cached filename index/staged中某一个特定文件和 HEAD 中的区别
  1. 版本区

注意, 版本区可以比较不同 commit 提交的区别, 这对于排查历史提交比较好用, 当然也可以使用git log来进行查看.

1
2
3
git diff master story        查看HEAD 中某两个版本的区别
git diff master:file1 story:file1
git diff <commit-id> <commit-id2>

命令关系图

diff 命令在工作区, 缓存区, 版本区的操作流程示意图如下:

stage-diff

更新细节的流程变化如下, 此时 diff 命令可以指定某个 commit 提交来判断文件变化.

workdiectory

参考: