Git 分支

查看、创建、切换、删除分支

查看分支

1
2
3
4
5
6
7
git branch
# 查看所有分支以及最后一次提交
git branch -v
# 查看已经合并到当前分支的分支
git branch --merged
# 查看还未合并到当前分支的分支
git branch --no-merged

创建新分支

1
2
3
git branch <newbranchname>
# eg.
git branch dev

在当前提交对象上创建一个新的分支 dev,但并不会自动切换过去。

切换分支

1
2
3
4
5
git checkout <branchname>
# or
git switch <branchname>
# eg.
git checkout dev

此时,HEAD 指针已经从旧的分支指向了该分支 dev

创建并切换到新分支

1
2
3
git checkout -b <newbranchname>
# or
git switch -c <newbranchname>

删除分支

1
git branch -d <branchname>

强制删除。指定分支没有完全合并时,-d 参数会失效,此时需要强制删除:

1
git branch -D <branchname>

在不同的分支修改

此时,在新的分支 dev 上执行某些操作并提交并不影响原来的旧分支 master

1
2
vim newFile.txt
git commit -a -m "Add newFile.txt"

切换回旧分支 master 进行修改提交后,两个分支已经有了分叉。

1
2
3
git checkout master
git rm oldFile.txt
git commit -a -m "Delete oldFile.txt"

此时可以通过 git log --oneline --graph 来查看。

合并

  1. 创建新功能分支 feature 进行开发,并提交

    1
    2
    3
    git checkout -b feature
    # some changes
    git commit -a -m "New features"
  2. 线上 master 分支有紧急 bug 需要修复。创建修复分支 hotfix 进行修复

    1
    2
    3
    4
    git checkout master
    git checkout -b hotfix
    # fix bugs
    git commit -a -m "Fix bugs"
  3. 将修复代码部署到线上,即将 hotfix 合并到 master

    1
    2
    git checkout master
    git merge hotfix

    在合并时,会出现“快进(fast-forward)”这个词。 由于你想要合并的分支 hotfix 所指向的提交是当前所在的提交 master 的直接后继, 因此 Git 会直接将指针向前移动。换句话说,当你试图合并两个分支时, 如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候, 只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。

  4. bug 已经被修复,遂删除 hotfix 分支

    1
    git branch -d hotfix
  5. 切换回 feature 分支进行开发

    1
    2
    3
    git checkout feature
    # some changes
    git commit -a -m "New features"
  6. 新功能开发完毕,需要合并到 master 分支

    现在的分支图看起来是这样的:

    这和之前合并 hotfix 分支的时候有一点不一样。 在这种情况下,你的开发历史从一个更早的地方开始分叉开来(diverged)。 因为,master 分支所在提交并不是 feature 分支所在提交的直接祖先,Git 不得不做一些额外的工作。 出现这种情况的时候,Git 会使用两个分支的末端所指的快照(C4C5)以及这两个分支的公共祖先(C2),做一个简单的三方合并。

    1
    2
    git checkout master
    git merge feature

    Git 将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。 这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。最终结果如图:

  7. 新功能开发完毕,删除 feature 分支

    1
    git branch -d feature
  8. 解决冲突

    如果在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们。此时 Git 做了合并,但是没有自动地创建一个新的合并提交。Git 会暂停下来,等待你去解决合并产生的冲突。可以通过 git status 命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件。

    Git 会在有冲突的文件中加入标准的冲突解决标记:

    1
    2
    3
    4
    5
    6
    7
    China
    <<<<<<< HEAD
    Tianjin
    =======
    Shanghai
    Beijing
    >>>>>>> dev

    上半部分表示 HEAD 所指分支 master 的内容,下半部分为 dev 分支内容,可以通过手动修改代码来解决冲突。

    解决之后手动将该文件添加到暂存区,确认无误后提交。

    1
    2
    git add <conflictFile>
    git commit

    也有一些 mergediff 可视化工具来帮助更好的解决冲突,如 p4mergebcopendiff 等。

远程分支

远程引用是对远程仓库的引用(指针),包括分支、标签等等。 可以通过 git ls-remote 来显式地获得远程引用的完整列表,一个更常见的做法是利用远程跟踪分支。远程跟踪分支是远程分支状态的引用,是无法移动的本地引用。在进行了网络通信之后,Git 会自动的更新远程跟踪分支。以 <remote>/<branch> 来命名,如 origin/master

推送

1
2
3
git push <remote> <branch>
# eg.
git push origin dev

这是一种简写形式,完整的形式为:

1
git push origin refs/heads/dev:refs/heads/dev

或者简写为:

1
git push origin dev:dev

此命令在推送到不同的分支名时比较有用。

拉取

1
git fetch origin

当抓取到新的远程跟踪分支 feature 时,本地不会自动生成一份可编辑的副本,只有一个不可修改的远程分支指针。要么将该分支合并到本地分支,要么基于该分支创建一个新的本地分支:

1
git checkout -b feature origin/feature

git pull 命令则将拉取和合并整合到一起。

跟踪

从一个远程跟踪分支检出一个本地分支会自动创建所谓的“跟踪分支”。

  1. 自动跟踪。git clone 一个仓库时会自动创建一个跟踪到远程默认分支的本地分支。

  2. 创建跟踪分支

    1
    git checkout -b <branch> <remote>/<branch>
  3. 创建同名跟踪分支

    1
    2
    3
    git checkout --track <remote>/<branch>
    # 简化版。当本地该分支名不存在,且刚好只有一个名字与之匹配的远程分支时有效
    git checkout <branch>
  4. 设置已有本地分支的跟踪分支

    1
    git branch -u <remote>/<branch>
  5. 查看分支跟踪信息

    1
    git branch -vv

    该命令显示本地分支跟踪远程分支的信息,包括领先、落后、提交信息。

  6. 取消跟踪

    1
    git branch --unset-upstream <branch>

    不输入 <branch> 时默认为当前分支。

删除

1
2
3
git push <remote> -d <branch>
# eg.
git push origin -d feature

变基

整合分叉分支常用的方法是进行 merge 合并,它会将两个分支与它们的公共祖先进行比较并生成一次合并提交。

还有一种方法即为:可以提取在当前分支中引入的补丁和修改,然后在另一分支的基础上应用一次。 在 Git 中,这种操作就叫做 变基(rebase)。你可以使用 rebase 命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样。

例如,假设现在 topicmaster 分支有分叉,执行:

1
2
git checkout topic
git rebase master

现在,将 topic 分支上的修改应用到了 master 分支上,新的 topic 分支好像从 master 分支直接改动过来的一样(可通过 git log 查看)。实际上,可以通过 git rebase <basebranch> <topicbranch> 来避免分支的切换。

现在可以执行快进合并来更新 master 分支:

1
2
git checkout master
git merge topic

一般使用 rebase 的目的是为了确保在向远程分支推送时能保持提交历史的整洁。

所生成的重放也可以指定另外的分支进行应用:

1
git rebase --onto master server client

取出 client 分支,找出它从 server 分支分歧之后的补丁,然后把这些补丁在 master 分支上重放一遍,让 client 看起来像直接基于 master 修改一样。此番操作之后可以将 client 快进合并到 master

注:只对尚未推送或分享给别人的本地修改执行变基操作清理历史,从不对已推送至别处的提交执行变基操作。因为变基操作改变了历史,如果他人使用历史进行了修改,将带来比较麻烦的后果。

压缩提交历史

在向远程仓库推送自己的某些修改之前,可以在本地通过 rebase 来压缩多次提交,使得历史记录看起来更整洁。

1
2
3
4
5
# 压缩后 3 次提交
git rebase -i HEAD~3

# 压缩从 775cabd 开始的提交(不包括 775cabd )
git rebase -i 775cabd

此时 Git 会弹出文本编辑器,倒序显示最近三次的提交:

1
2
3
pick 177b35c 66
pick 805f778 Delete other files
pick da35738 Change readme.md

我们把最后两次的提交压缩至倒数第三次提交,将第二第三行动作改为 squash 或者 s

1
2
3
pick 177b35c 66
s 805f778 Delete other files
s da35738 Change readme.md

保存退出后,Git 开始压缩。中间若有冲突,我们需手动解决冲突并将文件添加至暂存区,并执行 git rebase --continue 命令。如果想放弃压缩,执行 git rebase --abort

当所有冲突都解决后,Git 会弹出文本编辑器要求输入新的提交的信息,保存退出后即为压缩成功。