Git 原理与使用

一、基本操作

1.1、Git安装

centos安装:

centos7.6为例:

cpp 复制代码
sudo yum -y install git

查看 Git 安装的版本:

cpp 复制代码
git --version

ubuntu安装:

ubuntu20.04为例:

cpp 复制代码
 sudo apt-get install git -y

查看 git 安装的版本:

cpp 复制代码
git --version

1.2、Git 基本操作

创建 Git 本地仓库

仓库是进⾏版本控制的⼀个⽂件⽬录,创建⼀个 Git 本地仓库对应的命令为 git init ,注意命令要在⽂件⽬录下执⾏,例如:

cpp 复制代码
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ pwd
/home/ketil/gitcode
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ git init
Initialized empty Git repository in /home/ketil/gitcode/.git/
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ ll -a
total 12
drwxrwxr-x 3 ketil ketil 4096 Oct  8 18:27 ./
drwxr-x--- 9 ketil ketil 4096 Oct  8 18:27 ../
drwxrwxr-x 8 ketil ketil 4096 Oct  8 18:27 .git/

当前⽬录下多了⼀个 .git 的隐藏⽂件, .git ⽬录是 Git 来跟踪管理仓库的。

cpp 复制代码
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ tree .git
.git/
├── branches
├── config
├── description
├── HEAD
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.sample
│   ├── fsmonitor-watchman.sample
│   ├── post-update.sample
│   ├── pre-applypatch.sample
│   ├── pre-commit.sample
│   ├── pre-merge-commit.sample
│   ├── prepare-commit-msg.sample
│   ├── pre-push.sample
│   ├── pre-rebase.sample
│   ├── pre-receive.sample
│   └── update.sample
├── info
│   └── exclude
├── objects
│   ├── info
│   └── pack
└── refs
 ├── heads
 └── tags
9 directories, 16 files

配置 Git

当安装 Git 后⾸先要做的事情是设置用户名称e-mail地址。配置命令为:

cpp 复制代码
git config [--global] user.name "Your Name"
git config [--global] user.email "email@example.com"
# 把 Your Name 改成你的昵称
# 把 email@example.com 改成邮箱的格式,只要格式正确即可。

其中 --global 是⼀个可选项。如果使⽤了该选项,表⽰这台机器上所有的 Git 仓库都会使⽤这个配置。要注意的是,执⾏命令时必须要在仓库⾥。

查看配置命令为:

cpp 复制代码
git config -l

删除对应的配置命令为:

cpp 复制代码
git config [--global] --unset user.name
git config [--global] --unset user.email

⼯作区、暂存区、版本库

  • ⼯作区:是在电脑上要写代码或⽂件的⽬录。
  • 暂存区:英⽂叫 stage 或 index。⼀般存放在 .git ⽬录下的 index ⽂件(.git/index)中,有时把暂存区有时也叫作索引(index)。
  • 版本库:⼜名仓库,英⽂名 repository 。⼯作区有⼀个隐藏⽬录 .git ,它不算⼯作区,⽽是 Git 的版本库。这个版本库⾥⾯的所有⽂件都可以被 Git 管理起来,每个⽂件的修改、删除,Git 都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以"还原"。

1、在创建 Git 版本库时,Git 会为我们⾃动创建⼀个唯⼀的 master 分⽀,以及指向 master 的⼀个指 针叫 HEAD。

2、当对⼯作区修改(或新增)的⽂件执⾏ git add 命令时,暂存区⽬录树的⽂件索引会被更新

3、当执⾏提交操作 git commit 时,master 分⽀会做相应的更新,可以简单理解为暂存区的⽬录 树才会被真正写到版本库中。

通过新建或粘贴进⽬录的⽂件,并不能称之为向仓库中新增⽂件,⽽只是 在⼯作区新增了⽂件。必须要通过使⽤ git add 和 git commit 命令才能将⽂件添加到仓库中 进⾏管理。

场景1-添加文件

在包含 .git 的⽬录下新建⼀个 ReadMe ⽂件,可以使⽤ git add 命令可以将⽂件添加到暂存 区:

  • 添加⼀个或多个⽂件到暂存区: git add [file1] [file2] ...
  • 添加当前⽬录下的所有⽂件改动到暂存区: git add .

再使⽤ git commit 命令将暂存区内容添加到本地仓库中:

  • 提交暂存区全部内容到本地仓库中: git commit -m "message"
  • 提交暂存区的指定⽂件到仓库区: git commit [file1] [file2] ... -m "message"

注意 git commit 后⾯的 -m 选项,要跟上描述本次提交的 message,由⽤⼾⾃⼰完成,这部分内 容绝对不能省略,并要好好描述,是⽤来记录提交细节,是给⼈看的。

例如:

cpp 复制代码
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ vim ReadMe
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ cat ReadMe
hello
hello
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ git add ReadMe
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ git commit -m "commit my first file"
[master (root-commit) c614289] commit my first file
 1 file changed, 2 insertions(+)
 create mode 100644 ReadMe

git commit 命令执⾏成功后会告诉我们,1个⽂件被改动(就是我们新添加的ReadMe⽂件),插 ⼊了两⾏内容(ReadMe有两⾏内容)。

还可以多次 add 不同的⽂件,⽽只 commit ⼀次便可以提交所有⽂件,是因为需要提交的⽂件是 通通被 add 到暂存区中,然后⼀次性 commit 暂存区的所有修改。如:

cpp 复制代码
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ touch file1 file2 file3
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ git add file1
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ git add file2
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ git add file3
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ git commit -m "add 3 files"
[master 23807c5] add 3 files
 3 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file1
 create mode 100644 file2
 create mode 100644 file3

截⾄⽬前为⽌,已经更够将代码直接提交⾄本地仓库了。可以使⽤ git log 命令,来查看下历史提交记录:

cpp 复制代码
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ git log
commit ef43bb1d5583e3397fef09f1d174afb835ed8199 (HEAD -> master)
Author: ketil <wangxinhao001124@foxmail.com>
Date:   Tue Oct 1 16:09:35 2024 +0800

    add 3 files

commit 6520e1d571be4e78256481becbb2f9f54b2bb7a4
Author: ketil <wangxinhao001124@foxmail.com>
Date:   Tue Oct 1 15:33:59 2024 +0800

    commit my first file

该命令显⽰从最近到最远的提交⽇志,并且可以看到 commit 时的⽇志消息。 如果嫌输出信息太多,看得眼花缭乱的,可以试试加上 --pretty=oneline 参数:

cpp 复制代码
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ git log --pretty=oneline
ef43bb1d5583e3397fef09f1d174afb835ed8199 (HEAD -> master) add 3 files
6520e1d571be4e78256481becbb2f9f54b2bb7a4 commit my first file

需要说明的是,这些⼀⼤串类似 ef43bb1d...ed8199 的是每次提交的 commit id (版本 号),Git 的 commit id 不是1,2,3......递增的数字,⽽是⼀个 SHA1 计算出来的⼀个⾮常⼤的数 字,⽤⼗六进制表⽰。

查看 .git ⽂件

cpp 复制代码
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ tree .git/
.git/
├── branches
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.sample
│   ├── fsmonitor-watchman.sample
│   ├── post-update.sample
│   ├── pre-applypatch.sample
│   ├── pre-commit.sample
│   ├── pre-merge-commit.sample
│   ├── prepare-commit-msg.sample
│   ├── pre-push.sample
│   ├── pre-rebase.sample
│   ├── pre-receive.sample
│   └── update.sample
├── index
├── info
│   └── exclude
├── logs
│   ├── HEAD
│   └── refs
│   └── heads
│   └── master
├── objects
│   ├── 23
│   │   └── 807c536969cd886c4fb624b997ca575756eed6
│   ├── 83
│   │   └── 0a8c9feefbdc098bbae2cdc25e5034ce1920d7
│   ├── 8f
│   │   └── add50161b6fafa53ce7e79d278dc490240c946
│   ├── 9c
│   │   └── 9e1f0f6bff3015df71a0963004476f5e6cfd54
│   ├── c6
│   │   └── 1428926f3853d4ec6dde904415b0e6c1dabcc6
│   ├── e6
│   │   └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
│   ├── info
│   └── pack
└── refs
 ├── heads
 │   └── master
 └── tags
18 directories, 27 files
  1. index 就是我们的暂存区,add 后的内容都是添加到这⾥的。

  2. HEAD 就是我们的默认指向 master 分⽀的指针:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ cat .git/HEAD
ref: refs/heads/master

⽽默认的 master 分⽀,其实就是:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ cat .git/refs/heads/master
23807c536969cd886c4fb624b997ca575756eed6

打印的 23807c536969cd886c4fb624b997ca575756eed6 是什么东西呢?保存的就是当前最新 的 commit id。

  1. objects 为 Git 的对象库,⾥⾯包含了创建的各种版本库对象及内容。当执⾏ git add 命令 时,暂存区的⽬录树被更新,同时⼯作区修改(或新增)的⽂件内容被写⼊到对象库中的⼀个新的对象中,就位于".git/objects" ⽬录下,让我们来看看这些对象有何⽤处:
cpp 复制代码
ketil@8-127-134-49:~/gitcode$ ls .git/objects/
23 83 8f 9c c6 e6 info pack

查找 object 时要将 commit id 分成2部分,其前2位是⽂件夹名称,后38位是⽂件名称。 找到这个⽂件之后,⼀般不能直接看到⾥⾯是什么,该类⽂件是经过 sha (安全哈希算法)加密过的⽂件,好在我们可以使⽤ git cat-file 命令来查看版本库对象的内容:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git cat-file -p
23807c536969cd886c4fb624b997ca575756eed6
tree 830a8c9feefbdc098bbae2cdc25e5034ce1920d7
parent c61428926f3853d4ec6dde904415b0e6c1dabcc6
author hyb91 <wangxinhao001124@foxmail.com> 1683343652 +0800
committer hyb91 <wangxinhao001124@foxmail.com> 1683343652 +0800
add 3 files
# 这就是最近⼀次的提交!

其中,还有⼀⾏ tree 830a8c9feefbdc098bbae2cdc25e5034ce1920d7 ,使⽤同样的⽅法,看看结果:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git cat-file -p
830a8c9feefbdc098bbae2cdc25e5034ce1920d7
100644 blob 9c9e1f0f6bff3015df71a0963004476f5e6cfd54 ReadMe
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file1
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file2
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file3

在看 ReadMe 对应的 9c9e1f0f6bff3015df71a0963004476f5e6cfd54 :

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git cat-file -p
9c9e1f0f6bff3015df71a0963004476f5e6cfd54
hello
hello
# 这是对ReadMe做的修改!!被git记录了下来!!

总结⼀下,在本地的 git 仓库中,有⼏个⽂件或者⽬录很特殊

  • index: 暂存区, git add 后会更新该内容。
  • HEAD: 默认指向 master 分⽀的⼀个指针。
  • refs/heads/master: ⽂件⾥保存当前 master 分⽀的最新 commit id 。
  • objects: 包含了创建的各种版本库对象及内容,可以简单理解为放了 git 维护的所有修改。

场景二-添加⽂件

们再展⽰⼀种添加⽂件的场景,能加深对⼯作区、暂存区、版本库的理解,⽰例如下:

cpp 复制代码
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ touch file4 #1. 新增file4⽂件
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ git add file4 #2. 将file4添加到暂存区
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ touch file5 #3. 新增file5⽂件
ketil@iZ7xvdqducv7olrw8ilvqoZ:~/gitcode$ git commit -m"add file" #4. 提交修改
[master 3d406c0] add file
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file4

提交后发现打印了 1 file changed, 0 insertions(+), 0 deletions(-) ,意思是只 有⼀个⽂件改变了。git add 是将⽂件添加到暂存区, git commit 是将暂存区的内容添加到本地仓库 中。由于我们并没有使⽤ git add file5 ,file5 就不在暂存区中维护,所以我们 commit 的时候 其实只是把已经在暂存区的 file4 提交了,⽽遗漏了⼯作区的 file5。如何提交 file5 呢?很简单,再次 add , commit 即可。

修改⽂件

Git 跟踪并管理的是修改,⽽⾮⽂件。

什么是修改?⽐如新增了⼀⾏,这就是⼀个修改,删除了⼀⾏,也是⼀个修改,更改了某些字符, 也是⼀个修改,删了⼀些⼜加了⼀些,也是⼀个修改,甚⾄创建⼀个新⽂件,也算⼀个修改。

将 ReadMe ⽂件进⾏⼀次修改,此时,仓库中的 ReadMe 和⼯作区的 ReadMe 是不同的,如何查看当前仓库的状态呢? git status 命令⽤于查看在上次提交之后是否有对⽂件进⾏再次修改。

cpp 复制代码
ketil@8.134.127.49:~/gitcode$ git status
On branch master
Changes not staged for commit:
 (use "git add <file>..." to update what will be committed)
 (use "git restore <file>..." to discard changes in working directory)
 modified: ReadMe
no changes added to commit (use "git add" and/or "git commit -a")

上⾯的结果显示,ReadMe 被修改过了,但还没有完成添加与提交。

⽬前,只知道⽂件被修改了,如果能知道具体哪些地⽅被修改了,就更好了。

cpp 复制代码
ketil@8.134.127.49:~/gitcode$ git diff ReadMe
diff --git a/ReadMe b/ReadMe
index 9c9e1f0..4a97140 100644
--- a/ReadMe
+++ b/ReadMe
@@ -1,2 +1,3 @@
 hello 
-hello 
+hello 
+hello world

git diff [file] 命令⽤来显⽰暂存区和⼯作区⽂件的差异,显⽰的格式正是Unix通⽤的diff格 式。也可以使⽤ git diff HEAD -- [file] 命令来查看版本库和⼯作区⽂件的区别。 知道了对 ReadMe 做了什么修改后,再把它提交到本地仓库就放⼼多了。

cpp 复制代码
ketil@8.134.127.49:~/gitcode$ git add ReadMe
ketil@8.134.127.49:~/gitcode$ git status
On branch master
Changes to be committed:
 (use "git restore --staged <file>..." to unstage)
 modified: ReadMe

git add 之后,就没有看到上⾯ no changes added to commit (use "git add" and/or "git commit -a") 的消息了。接下来继续 git commit 即可:

cpp 复制代码
ketil@8.134.127.49:~/gitcode$ git commit -m "add modify ReadMe file"
[master 94da695] add modify ReadMe file
 1 file changed, 2 insertions(+), 1 deletion(-)
ketil@8.134.127.49:~/gitcode$ git status
On branch master
nothing to commit, working tree clean

版本回退

之前也提到过,Git 能够管理⽂件的历史版本,这也是版本控制器重要的能⼒。如果有⼀天发现之前的⼯作做的出现了很⼤的问题,需要在某个特定的历史版本重新开始,这个时候,就需要版本 回退的功能了。

执⾏ git reset 命令⽤于回退版本,可以指定退回某⼀次提交的版本。要解释⼀下"回退"本质是 要将版本库中的内容进⾏回退,⼯作区或暂存区是否回退由命令参数决定:

cpp 复制代码
git reset 命令语法格式为: git reset [--soft | --mixed | --hard] [HEAD]
  • --mixed 为默认选项,使⽤时可以不⽤带该参数。该参数将暂存区的内容退回为指定提交版本内 容,⼯作区⽂件保持不变。
  • --soft参数对于⼯作区和暂存区的内容都不变,只是将版本库回退到某个指定版本。
  • --hard 参数将暂存区与⼯作区都退回到指定版本。切记⼯作区有未提交的代码时不要⽤这个命 令,因为⼯作区会回滚,没有提交的代码就再也找不回了,所以使⽤该参数前⼀定要慎重。
  • HEAD 说明:
    • 可直接写成 commit id,表⽰指定退回的版本
    • HEAD 表⽰当前版本
    • HEAD^ 上⼀个版本
    • HEAD^^ 上上⼀个版本
    • 以此类推...
  • 可以使⽤ 〜数字表⽰:
    • HEAD~0 表⽰当前版本
    • HEAD~1 上⼀个版本
    • HEAD^2 上上⼀个版本
    • 以此类推...

为了便于表述,⽅便测试回退功能,我们先做⼀些准备⼯作:更新3个版本的 ReadMe,并分别进⾏3 次提交,如下所⽰:

cpp 复制代码
# 第⼀次修改提交
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello
hello
hello world
hello version1
ketil@8-134-127-49:~/gitcode$ git add ReadMe
ketil@8-134-127-49:~/gitcode$ git commit -m"add version1"
[master cff9d1e] add version1
 1 file changed, 1 insertion(+)
# 第⼆次修改提交
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello
hello
hello world
hello version1
hello version2
ketil@8-134-127-49:~/gitcode$ git add ReadMe
ketil@8-134-127-49:~/gitcode$ git commit -m"add version2"
 1 file changed, 1 insertion(+)
# 第三次修改提交
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello
hello 
hello world
hello version1
hello version2
hello version3
ketil@8-134-127-49:~/gitcode$ git add ReadMe
ketil@8-134-127-49:~/gitcode$ git commit -m"add version3"
[master d95c13f] add version3
 1 file changed, 1 insertion(+)
# 查看历史提交记录
ketil@8-134-127-49:~/gitcode$ git log --pretty=oneline
d95c13ffc878a55a25a3d04e22abfc7d2e3e1383 (HEAD -> master) add version3
14c12c32464d6ead7159f5c24e786ce450c899dd add version2
cff9d1e019333318156f8c7d356a78c9e49a6e7b add version1
...

现在,如果我们在提交完 version3 后, 发现 version 3 编写错误,想回退到 version2,重新基于 version 2 开始编写。由于在这⾥希望的是将⼯作区的内容也回退到 version 2 版本,所以需要⽤到 --hard 参数,⽰例如下:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git log --pretty=oneline
d95c13ffc878a55a25a3d04e22abfc7d2e3e1383 (HEAD -> master) add version3
14c12c32464d6ead7159f5c24e786ce450c899dd add version2
cff9d1e019333318156f8c7d356a78c9e49a6e7b add version1
...
ketil@8-134-127-49:~/gitcode$ git reset --hard
14c12c32464d6ead7159f5c24e786ce450c899dd
HEAD is now at 14c12c3 add version2
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello
hello
hello world
hello version1
hello version2

我们惊奇的发现,此时 ReadMe ⽂件的内容,已经回退到 version2 了!,当前,我们再次⽤ git log 查看⼀下提交⽇志,发现 HEAD 指向了version2,如下所⽰:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git log --pretty=oneline
14c12c32464d6ead7159f5c24e786ce450c899dd (HEAD -> master) add version2
cff9d1e019333318156f8c7d356a78c9e49a6e7b add version1
...

到这⾥⼀般回退功能就演⽰完了,但现在如果我后悔了,想再回到 version 3 怎么办?可以继续使 ⽤ git reset 命令,回退到 version 3 版本,但我们必须要拿到 version 3 的 commit id 去指定回退的版本。

但看到了 git log 并不能打印出 version 3 的 commit id ,运⽓好的话可以从终端 上去找找之前的记录,运⽓不好的话 commit id 已经被搞丢了。

Git 还提供了⼀个 git reflog 命令能补救⼀下,该命令⽤来记录本地的每⼀次命令。

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git reflog
14c12c3 (HEAD -> master) HEAD@{0}: reset: moving to
14c12c32464d6ead7159f5c24e786ce450c899dd
d95c13f HEAD@{1}: commit: add version3
14c12c3 (HEAD -> master) HEAD@{2}: commit: add version2
cff9d1e HEAD@{3}: commit: add version1
94da695 HEAD@{4}: commit: add modify ReadMe file
23807c5 HEAD@{5}: commit: add 3 files
c614289 HEAD@{6}: commit (initial): commit my first file

这样,就可以很⽅便的找到所有操作记录了,但 d95c13f 这个是啥东西?这个是 version 3 的 commit id 的部分。没错,Git 版本回退的时候,也可以使⽤部分 commit id 来代表⽬标版 本。

⽰例如下:

cpp 复制代码
# 回退到v3
ketil@8-134-127-49:~/gitcode$ git reset --hard d95c13f
HEAD is now at d95c13f add version3
# 查看⼯作区
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello 
hello 
hello world
hello version1
hello version2
hello version3
# 查看log
ketil@8-134-127-49:~/gitcode$ git log --pretty=oneline
d95c13ffc878a55a25a3d04e22abfc7d2e3e1383 (HEAD -> master) add version3
14c12c32464d6ead7159f5c24e786ce450c899dd add version2
cff9d1e019333318156f8c7d356a78c9e49a6e7b add version1
94da6950d27e623c0368b22f1ffc4bff761b5b00 add modify ReadMe file
23807c536969cd886c4fb624b997ca575756eed6 add 3 files
c61428926f3853d4ec6dde904415b0e6c1dabcc6 commit my first file

Git 的版本回退速度⾮常快,因为 Git 在内部有个指向当前分⽀(此处是master)的 HEAD 指针, refs/heads/master ⽂件⾥保存当前 master 分⽀的最新 commit id 。当我们 在回退版本的时候,Git 仅仅是给 refs/heads/master 中存储⼀个特定的version,可以简单理解 成如下⽰意图:

撤销修改

情况一:对于⼯作区的代码,还没有 add

可以使⽤ git checkout -- [file] 命令让⼯作区的 ⽂件回到最近⼀次 add 或 commit 时的状态。 要注意 git checkout -- [file] 命令中的 -- 很重要,切记不要省略,⼀旦省略,该命令就变为其他意思了。⽰例如下:

cpp 复制代码
# 向ReadMe中新增⼀⾏代码
ketil@8-134-127-49:~/gitcode$ vim ReadMe
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello
hello git
hello world
hello version1
hello version2
hello version3
This piece of code is like shit #新增代码
# 恢复到上⼀次 add 或 commit
ketil@8-134-127-49:~/gitcode$ git checkout -- ReadMe
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello
hello git
hello world
hello version1
hello version2
hello version3
情况二:已经 add ,但没有 commit
cpp 复制代码
# 向ReadMe中新增⼀⾏代码
ketil@8-134-127-49:~/gitcode$ vim ReadMe
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello 
hello git
hello world
hello version1
hello version2
hello version3
This piece of code is like shit #新增代码
# add 存⼊暂存区
ketil@8-134-127-49:~/gitcode$ git add ReadMe
ketil@8-134-127-49:~/gitcode$ git status
On branch master
Changes to be committed:
 (use "git restore --staged <file>..." to unstage)
 modified: ReadMe

回忆⼀下 git reset 回退命令,该命令如果使⽤ --mixed 参数,可以将暂存区的内容退回为指定的版本内容,但⼯作区⽂件保持不变。那就可以回退下暂存区的内容了。

⽰例如下:

cpp 复制代码
# --mixed 是默认参数,使⽤时可以省略
ketil@8-134-127-49:~/gitcode$ git reset HEAD ReadMe
Unstaged changes after reset:
M ReadMe

⽤ git status 查看⼀下,发现现在暂存区是⼲净的,⼯作区有修改。

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git status
On branch master
Changes not staged for commit:
 (use "git add <file>..." to update what will be committed)
 (use "git restore <file>..." to discard changes in working directory)
 modified: ReadMe
no changes added to commit (use "git add" and/or "git commit -a")

还记得如何丢弃⼯作区的修改吗?

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git checkout -- ReadMe
ketil@8-134-127-49:~/gitcode$ git status
On branch master
nothing to commit, working tree clean
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello 
hello git
hello world
hello version1
hello version2
hello version3

恢复了

情况三:已经 add ,并且也 commit 了

可以 git reset --hard HEAD^ 回退到上⼀个版本。不过,这是有条件的,就是还没有把⾃⼰的本地版本库推送到远程。

cpp 复制代码
# 向ReadMe中新增⼀⾏代码
ketil@8-134-127-49:~/gitcode$ vim ReadMe
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello
hello git
hello world
hello version1
hello version2
hello version3
This piece of code is like shit #新增代码
# 提交
ketil@8-134-127-49:~/gitcode$ git add ReadMe
ketil@8-134-127-49:~/gitcode$ git commit -m"test quash"
[master 5f71ae1] test quash
 1 file changed, 1 insertion(+)
# 回退
ketil@8-134-127-49:~/gitcode$ git reset --hard HEAD^
HEAD is now at 144a3f8 add file
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello
hello git
hello world
hello version1
hello version2
hello version3

删除文件

在 Git 中,删除也是⼀个修改操作,如果要删除 file5 ⽂件,怎么做?

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ ls
file1 file2 file3 file4 file5 ReadMe
ketil@8-134-127-49:~/gitcode$ rm file5

但这样直接删除是没有⽤的,反⽽徒增烦恼, git status 命令会⽴刻告诉你哪些⽂件被删除了:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git status
On branch master
Changes not staged for commit:
 (use "git add/rm <file>..." to update what will be committed)
 (use "git restore <file>..." to discard changes in working directory)
 deleted: file5
no changes added to commit (use "git add" and/or "git commit -a")

此时,⼯作区和版本库就不⼀致了,要删⽂件,⽬前除了要删⼯作区的⽂件,还要清除版本库的⽂ 件。

⼀般⾛到这⾥,有两种可能:

  • 确实要从版本库中删除该⽂件
  • 不⼩⼼删错了

对第⼆种情况,很明显误删,需要使⽤ git 来进⾏恢复。

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git checkout -- file5
ketil@8-134-127-49:~/gitcode$ ls
file1 file2 file3 file4 file5 ReadMe

对于第⼀种情况,很明显是没有删完,我们只删除了⼯作区的⽂件。这时就需要使⽤ git rm 将⽂ 件从暂存区和⼯作区中删除,并且 commit :

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git rm file5
rm 'file5'
ketil@8-134-127-49:~/gitcode$ git status
On branch master
Changes to be committed:
 (use "git restore --staged <file>..." to unstage)
 deleted: file5

ketil@8-134-127-49:~/gitcode$ git commit -m"deleted file5"
[master 5476bde] deleted file5
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 file5
ketil@8-134-127-49:~/gitcode$ git status
On branch master
nothing to commit, working tree clean

现在,⽂件就从版本库中被删除了。

二、分支管理

2.1、理解分支

在版本回退⾥,每次提交,Git都把它们串成⼀条时间线,这条时间线就可以理解为是⼀ 个分⽀。截⽌到⽬前,只有⼀条时间线,在Git⾥,这个分⽀叫主分⽀,即 master 分⽀。

再来理解⼀下HEAD,HEAD 严格来说不是指向提交,⽽是指向master,master才是指向提交的,所 以,HEAD 指向的就是当前分⽀。

每次提交,master分⽀都会向前移动⼀步,这样,随着不断提交,master分⽀的线也越来越⻓,⽽ HEAD只要⼀直指向master分⽀即可指向当前分⽀。

通过查看当前的版本库,也能清晰的理出思路:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ cat .git/HEAD
ref: refs/heads/master
ketil@8-134-127-49:~/gitcode$ cat .git/refs/heads/master
5476bdeb12510f7cd72ac4766db7988925ebd302

2.2、创建分支

Git ⽀持查看或创建其他分⽀,在这⾥来创建第⼀个⾃⼰的分⽀ dev ,对应的命令为:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git branch #查看当前本地所有分⽀
* master
ketil@8-134-127-49:~/gitcode$ git branch dev #新建分⽀dev
ketil@8-134-127-49:~/gitcode$ git branch
 dev
* master

当创建新的分⽀后,Git 新建了⼀个指针叫 dev, * 表⽰当前 HEAD 指向的分⽀是 master 分⽀。另外,可以通过⽬录结构发现,新的 dev 分⽀:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ ls .git/refs/heads/
dev master
ketil@8-134-127-49:~/gitcode$ cat .git/refs/heads/*
5476bdeb12510f7cd72ac4766db7988925ebd302
5476bdeb12510f7cd72ac4766db7988925ebd302

发现⽬前 dev 和 master 指向同⼀个修改。并且也可以验证下 HEAD ⽬前是指向 master 的。

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ cat .git/HEAD
ref: refs/heads/master

⼀张图总结:

2.3、切换分支

那如何切换到 dev 分⽀下进⾏开发呢?使⽤ git checkout 命令即可完成切换,⽰例如下:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git checkout dev
Switched to branch 'dev'
ketil@8-134-127-49:~/gitcode$ git branch
* dev
 master
ketil@8-134-127-49:~/gitcode$ cat .git/HEAD
ref: refs/heads/dev

我们发现 HEAD 已经指向了 dev,就表⽰已经成功的切换到了 dev 上!

接下来,在 dev 分⽀下修改 ReadMe ⽂件,新增⼀⾏内容,并进⾏⼀次提交操作:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ vim ReadMe
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello
hello git
hello world
hello version1
hello version2
hello version3
write aaa for new branch
ketil@8-134-127-49:~/gitcode$ git add .
ketil@8-134-127-49:~/gitcode$ git commit -m"modify ReadMe"
[dev 3740dce] modify ReadMe
 1 file changed, 1 insertion(+)

现在,dev 分⽀的⼯作完成,可以切换回 master 分⽀:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git checkout master
Switched to branch 'master'
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello 
hello git
hello world
hello version1
hello version2
hello version3

切换回 master 分⽀后,发现ReadMe⽂件中新增的内容不⻅了?再切回 dev 看看:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git checkout dev
Switched to branch 'dev'
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello
hello git
hello world
hello version1
hello version2
hello version3
write aaa for new branch

在 dev 分⽀上,内容还在。为什么会出现这个现象呢?来看看 dev 分⽀和 master 分⽀指向,发 现两者指向的提交是不⼀样的:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ cat .git/refs/heads/dev
bdaf528ffbb8e05aee34d37685408f0e315e31a4
ketil@8-134-127-49:~/gitcode$ cat .git/refs/heads/master
5476bdeb12510f7cd72ac4766db7988925ebd302

看到这⾥就能明⽩了,因为我们是在dev分⽀上提交的,⽽master分⽀此刻的提交点并没有变,此时 的状态如图如下所⽰。

当切换到 master 分⽀之时,HEAD 就指向了 master,当然看不到提交了!

2.4、合并分支

为了在 master 主分⽀上能看到新的提交,就需要将 dev 分⽀合并到 master 分⽀,⽰例如下:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git branch
* dev
 master
ketil@8-134-127-49:~/gitcode$ git checkout master # 切换到 master 上进⾏合并
Switched to branch 'master'
ketil@8-134-127-49:~/gitcode$ git merge dev # 合并 dev 分⽀
Updating 16623e1..3740dce
Fast-forward
 ReadMe | 1 +
 1 file changed, 1 insertion(+)
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello 
hello git
hello world
hello version1
hello version2
hello version3
write aaa for new branch 

git merge 命令⽤于合并指定分⽀到当前分⽀。合并后,master 就能看到 dev 分⽀提交的内容了。此时的状态如下图所示。

Fast-forward 代表"快进模式",也就是直接把master指向dev的当前提交,所以合并速度⾮常快。 当然,也不是每次合并都能 Fast-forward,会说其他⽅式的合并。

2.5、删除分支

合并完成后, dev 分⽀就没⽤了, 那么dev分⽀就可以被删除掉,注意如果当前正处于某 分⽀下,就不能删除当前分⽀,如:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git branch
* dev
 master
ketil@8-134-127-49:~/gitcode$ git branch -d dev
error: Cannot delete branch 'dev' checked out at '/home/ketil/gitcode'

⽽可以在其他分⽀下删除当前分⽀,如:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git checkout master
Switched to branch 'master'
ketil@8-134-127-49:~/gitcode$ git branch -d dev
Deleted branch dev (was bdaf528).
ketil@8-134-127-49:~/gitcode$ git branch
* master

此时的状态如图下所示:

因为创建、合并和删除分⽀⾮常快,所以Git⿎励使⽤分⽀完成某个任务,合并后再删掉分⽀,这和 直接在master分⽀上⼯作效果是⼀样的,但过程更安全。

2.6、合并冲突

可是,在实际分⽀合并的时候,并不是想合并就能合并成功的,有时候可能会遇到代码冲突的问题。

为了演⽰这问题,创建⼀个新的分⽀ dev1 ,并切换⾄⽬标分⽀,可以使⽤ git checkout - b dev1 ⼀步完成创建并切换的动作,⽰例如下:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git checkout -b dev1
Switched to a new branch 'dev1'
ketil@8-134-127-49:~/gitcode$ git branch
* dev1
 master

在 dev1 分⽀下修改 ReadMe ⽂件,更改⽂件内容如下,并进⾏⼀次提交,如:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch # 将 aaa 该为 bbb
ketil@8-134-127-49:~/gitcode$ git add .
ketil@8-134-127-49:~/gitcode$ git commit -m"modify ReadMe"
[dev1 0854245] modify ReadMe
 1 file changed, 1 insertion(+), 1 deletion(-)

切换⾄ master 分⽀,观察 ReadMe ⽂件内容:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git checkout master
Switched to branch 'master'
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello 
hello git
hello world
hello version1
hello version2
hello version3
write aaa for new branch

发现,切回来之后,⽂件内容由变成了⽼的版本,这种现象很正常,此时在 master 分⽀上,我们对 ReadMe ⽂件再进⾏⼀次修改,并进⾏提交,如下:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git branch
 dev1
* master
ketil@8-134-127-49:~/gitcode$ vim ReadMe
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello
hello git
hello world
hello version1
hello version2
hello version3
write ccc for new branch
ketil@8-134-127-49:~/gitcode$ git add .
ketil@8-134-127-49:~/gitcode$ git commit -m"modify ReadMe"
[master c10f6d0] modify ReadMe
 1 file changed, 1 insertion(+), 1 deletion(-)

现在,master 和 dev1 分支各自都分别有新的提交,变成这样:

这种情况下,Git 只能试图把各⾃的修改合并起来,但这种合并就可能会有冲突,如下所⽰:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git merge dev1
Auto-merging ReadMe
CONFLICT (content): Merge conflict in ReadMe
Automatic merge failed; fix conflicts and then commit the result.
ketil@8-134-127-49:~/gitcode$ git status
On branch master
You have unmerged paths.
 (fix conflicts and run "git commit")
 (use "git merge --abort" to abort the merge)
Unmerged paths:
 (use "git add <file>..." to mark resolution)
 both modified: ReadMe
no changes added to commit (use "git add" and/or "git commit -a")

发现 ReadMe ⽂件有冲突后,可以直接查看⽂件内容,要说的是 Git 会⽤ <<<<<<<<<,========, >>>>>> 来标记出不同分⽀的冲突内容,如下所⽰:

cpp 复制代码
hyb@139-159-150-152:~/gitcode$ cat ReadMe
hello 
hello git
hello world
hello version1
hello version2
hello version3
<<<<<<< HEAD
write ccc for new branch
=======
write bbb for new branch
>>>>>>> dev1

此时必须要⼿动调整冲突代码,并需要再次提交修正后的结果!!(再次提交很重要,切勿忘 记)

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
ketil@8-134-127-49:~/gitcode$ git add .
ketil@8-134-127-49:~/gitcode$ git commit -m"merge ReadMe"
[master 2976afc] merge ReadMe

到这⾥冲突就解决完成,此时的状态变成了:

⽤带参数的 git log也可以看到分⽀的合并情况,具体⼤家可以⾃⾏搜索 git log 的⽤法:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git log --graph --pretty=oneline --abbrev-commit
* 2976afc (HEAD -> master) merge ReadMe
|\
| * c594fd1 (dev1) modify ReadMe
* | c10f6d0 modify ReadMe
|/

最后,不要忘记 dev1 分⽀使⽤完毕后就可以删除了:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git branch
* master
ketil@8-134-127-49:~/gitcode$ git branch -d dev1
Deleted branch dev1 (was c594fd1).

2.7、分支管理策略

通常合并分⽀时,如果可能,Git 会采⽤ Fast forward 模式。还记得如果采⽤ Fast forward 模式之后,形成的合并结果是什么?

在这种 Fast forward 模式下,删除分⽀后,查看分⽀历史时,会丢掉分⽀信息,看不出来最新提 交到底是 merge 进来的还是正常提交的。

但在合并冲突部分,我们也看到通过解决冲突问题,会再进⾏⼀次新的提交,得到的最终状态为:

那么这就不是 Fast forward 模式了,这样的好处是,从分⽀历史上就可以看出分⽀信息。例如我 们现在已经删除了在合并冲突部分创建的 dev1 分⽀,但依旧能看到 master 其实是由其他分⽀合并 得到:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git log --graph --pretty=oneline --abbrev-commit
* 2976afc (HEAD -> master) merge ReadMe
|\
| * c594fd1 modify ReadMe
* | c10f6d0 modify ReadMe
|/ 

Git ⽀持强制禁⽤ Fast forward 模式,那么就会在 merge 时⽣成⼀个新的 commit ,这样, 从分⽀历史上就可以看出分⽀信息。

下⾯实战⼀下 --no-ff ⽅式的 git merge 。⾸先,创建新的分⽀ dev2 ,并切换⾄新的分⽀:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git checkout -b dev2
Switched to a new branch 'dev2'

修改 ReadMe ⽂件,并提交⼀个新的 commit :

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello 
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d
ketil@8-134-127-49:~/gitcode$ git add .
ketil@8-134-127-49:~/gitcode$ git commit -m"modify ReadMe"
[dev2 41b082f] modify ReadMe
 1 file changed, 1 insertion(+)

切回 master 分⽀,开始合并:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git checkout master
Switched to branch 'master'
ketil@8-134-127-49:~/gitcode$ git merge --no-ff -m "merge with no-ff" dev2
Merge made by the 'recursive' strategy.
 ReadMe | 1 +
 1 file changed, 1 insertion(+)
ketil@8-134-127-49:~/gitcode$ cat ReadMe 
hello
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d

请注意 --no-ff 参数,表⽰禁⽤ Fast forward 模式。禁⽤ Fast forward 模式后合并会创建 ⼀个新的 commit ,所以加上 -m 参数,把描述写进去。

合并后,查看分⽀历史:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git log --graph --pretty=oneline --abbrev-commit
* 5bd16b4 (HEAD -> master) merge with no-ff
|\
| * 41b082f (dev2) modify ReadMe
|/ 

可以看到,不使⽤ Fast forward 模式,merge后就像这样:

所以在合并分⽀时,加上 --no-ff 参数就可以⽤普通模式合并,合并后的历史有分⽀,能看出来曾 经做过合并,⽽ fast forward 合并就看不出来曾经做过合并。

2.8、分支策略

在实际开发中,应该按照⼏个基本原则进⾏分⽀管理:

⾸先,master分⽀应该是⾮常稳定的,也就是仅⽤来发布新版本,平时不能在上⾯⼲活;

那在哪⼲活呢?⼲活都在dev分⽀上,也就是说,dev分⽀是不稳定的,到某个时候,⽐如1.0版本发布 时,再把dev分⽀合并到master上,在master分⽀发布1.0版本;

每个⼈都在dev分⽀上⼲活,每个⼈都有⾃⼰的分⽀,时不时地往dev分⽀上合并就 可以了。

2.9、bug分支

假如现在正在 dev2 分⽀上进⾏开发,开发到⼀半,突然发现 master 分⽀上⾯有 bug,需要 解决。在Git中,每个 bug 都可以通过⼀个新的临时分⽀来修复,修复后,合并分⽀,然后将临时分⽀ 删除。

可现在 dev2 的代码在⼯作区中开发了⼀半,还⽆法提交,怎么办?例如:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git branch
* dev2
 master
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello 
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d
i am coding ...
ketil@8-134-127-49:~/gitcode$ git status
On branch dev2
Changes not staged for commit:
 (use "git add <file>..." to update what will be committed)
 (use "git restore <file>..." to discard changes in working directory)
 modified: ReadMe
no changes added to commit (use "git add" and/or "git commit -a")

Git 提供了 git stash 命令,可以将当前的⼯作区信息进⾏储藏,被储藏的内容可以在将来某个时间恢复出来。

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git stash
Saved working directory and index state WIP on dev2: 41b082f modify ReadMe
hyb@139-159-150-152:~/gitcode$ git status
On branch dev2
nothing to commit, working tree clean

⽤ git status 查看⼯作区,就是⼲净的(除⾮有没有被 Git 管理的⽂件),因此可以放⼼地创建分 ⽀来修复bug。

储藏 dev2 ⼯作区之后,由于我们要基于master分⽀修复 bug,所以需要切回 master 分⽀,再新 建临时分⽀来修复 bug,⽰例如下:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git checkout master # 切回master
Switched to branch 'master'
ketil@8-134-127-49:~/gitcode$ git checkout -b fix_bug # 新建并切换到 fix_bug 分
⽀
Switched to a new branch 'fix_bug'
ketil@8-134-127-49:~/gitcode$ vim ReadMe
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello  
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d,e # 修复bug--忘记写e
ketil@8-134-127-49:~/gitcode$ git add ReadMe # 重新add,commit
ketil@8-134-127-49:~/gitcode$ git commit -m"fix bug"
[fix_bug 4bbc0c4] fix bug
 1 file changed, 1 insertion(+), 1 deletion(-)

修复完成后,切换到 master 分⽀,并完成合并,最后删除 fix_bug 分⽀:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git checkout master
Switched to branch 'master'
ketil@8-134-127-49:~/gitcode$ git merge --no-ff -m"merge fix_bug branch"
fix_bug
Merge made by the 'recursive' strategy.
 ReadMe | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello  
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d,e
ketil@8-134-127-49:~/gitcode$ git branch -d fix_bug
Deleted branch fix_bug (was 4bbc0c4).

⾄此,bug 的修复⼯作已经做完了,我们还要继续回到 dev2 分⽀进⾏开发。切换回 dev2 分⽀:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git checkout dev2
Switched to branch 'dev2'
ketil@8-134-127-49:~/gitcode$ git status
On branch dev2
nothing to commit, working tree clean

⼯作区是⼲净的,刚才的⼯作现场存到哪去了?⽤ git stash list 命令看看:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git stash list
stash@{0}: WIP on dev2: 41b082f modify ReadMe

⼯作现场还在,Git 把 stash 内容存在某个地⽅了,但是需要恢复⼀下,如何恢复现场呢?可以使 ⽤ git stash pop 命令,恢复的同时会把 stash 也删了,⽰例如下:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git stash pop
On branch dev2
Changes not staged for commit:
 (use "git add <file>..." to update what will be committed)
 (use "git restore <file>..." to discard changes in working directory)
 modified: ReadMe
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (4f873250b3503687b5efd26196776aee7e3724c2)

再次查看的时候,我们已经发现已经没有现场可以恢复了

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git stash list
ketil@8-134-127-49:~/gitcode$ 

另外,恢复现场也可以采⽤ git stash apply 恢复,但是恢复后,stash内容并不删除,需要 ⽤ git stash drop 来删除;

可以多次stash,恢复的时候,先⽤ git stash list 查看,然后恢复指定的stash,⽤命令 git stash apply stash@{0}。

恢复完代码之后我们便可以继续完成开发,开发完成后便可以进⾏提交,例如:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d
i am coding ... Done!
ketil@8-134-127-49:~/gitcode$ git add .
ketil@8-134-127-49:~/gitcode$ git commit -m"modify ReadMe"
[dev2 ed0916d] modify ReadMe
 1 file changed, 1 insertion(+)

但注意到了,修复 bug 的内容,并没有在 dev2 上显⽰。此时的状态图为:

Master 分⽀⽬前最新的提交,是要领先于新建 dev2 时基于的 master 分⽀的提交的,所以 在 dev2 中当然看不⻅修复 bug 的相关代码。

最终⽬的是要让 master 合并 dev2 分⽀的,那么正常情况下切回 master 分⽀直接合 并即可,但这样其实是有⼀定⻛险的。

是因为在合并分⽀时可能会有冲突,⽽代码冲突需要我们⼿动解决(在 master 上解决)。⽆法保证对于冲突问题可以正确地⼀次性解决掉,因为在实际的项⽬中,代码冲突不只⼀两⾏那么简单, 有可能⼏⼗上百⾏,甚⾄更多,解决的过程中难免⼿误出错,导致错误的代码被合并到 master 上。 此时的状态为:

解决这个问题的⼀个好的建议就是:最好在⾃⼰的分⽀上合并下 master ,再让 master 去合并 dev ,这样做的⽬的是有冲突可以在本地分⽀解决并进⾏测试,⽽不影响 master 。此时的状态 为:

对应的实操演⽰如下,要说明的是,以下演⽰的merge操作,没有使⽤ --no-ff ,但上述的图⽰是 禁⽤ Fast forward 了模式后得出的,主要是为了⽅便解释问题。

cpp 复制代码
# dev 合并 master
ketil@8-134-127-49:~/gitcode$ git branch
* dev2
 master
ketil@8-134-127-49:~/gitcode$ git merge master
Auto-merging ReadMe
CONFLICT (content): Merge conflict in ReadMe
Automatic merge failed; fix conflicts and then commit the result.
# 发⽣冲突
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello 
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
<<<<<<< HEAD
a,b,c,d
i am coding ... Done!
=======
a,b,c,d,e
>>>>>>> master
# 解决冲突并重新提交
ketil@8-134-127-49:~/gitcode$ vim ReadMe
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d,e
i am coding ... Done!
ketil@8-134-127-49:~/gitcode$ git add .
ketil@8-134-127-49:~/gitcode$ git commit -m"merge master"
[dev2 447d29f] merge master
ketil@8-134-127-49:~/gitcode$ git status
On branch dev2
nothing to commit, working tree clean
# 切回master
ketil@8-134-127-49:~/gitcode$ git checkout master
Switched to branch 'master'
# master 合并 dev2---⽆需解决冲突!!
ketil@8-134-127-49:~/gitcode$ git merge dev2
Updating 193421f..447d29f
Fast-forward
 ReadMe | 1 +
 1 file changed, 1 insertion(+)
ketil@8-134-127-49:~/gitcode$ git status
On branch master
nothing to commit, working tree clean
# 删除 dev2 分⽀
ketil@8-134-127-49:~/gitcode$ git branch -d dev2
Deleted branch dev2 (was 447d29f).

2.10、删除临时分支

软件开发中,总有⽆穷⽆尽的新的功能要不断添加进来。

添加⼀个新功能时,肯定不希望因为⼀些实验性质的代码,把主分⽀搞乱了,所以,每添加⼀个新 功能,最好新建⼀个分⽀,可以将其称之为 feature 分⽀,在上⾯开发,完成后,合并,最后,删除该 feature 分⽀。

可是,如果今天正在某个 feature 分⽀上开发了⼀半,被产品经理突然叫停,说是要停⽌新功 能的开发。虽然⽩⼲了,但是这个 feature 分⽀还是必须就地销毁,留着⽆⽤了。这时使⽤传统 的 git branch -d 命令删除分⽀的⽅法是不⾏的。演⽰如下:

cpp 复制代码
# 新增并切换到 dev3 分⽀
ketil@8-134-127-49:~/gitcode$ git checkout -b dev3
Switched to a new branch 'dev3'

# 开始开发新功能并提交
ketil@8-134-127-49:~/gitcode$ vim ReadMe
ketil@8-134-127-49:~/gitcode$ cat ReadMe
hello
hello git
hello world
hello version1
hello version2
hello version3
write bbb for new branch
a,b,c,d,e
i am coding ... Done!
i am writing new features ...
ketil@8-134-127-49:~/gitcode$ git add .
ketil@8-134-127-49:~/gitcode$ git commit -m"modify ReadMe for new features"
[dev3 cd2f149] modify ReadMe for new features
 1 file changed, 1 insertion(+)

# 此时新功能叫停

# 切回master准备删除dev3
ketil@8-134-127-49:~/gitcode$ git checkout master
Switched to branch 'master'

# 常规删除dev3分⽀时失败
ketil@8-134-127-49:~/gitcode$ git branch -d dev3
error: The branch 'dev3' is not fully merged.
If you are sure you want to delete it, run 'git branch -D dev3'.

直接使⽤传统的删除分⽀的⽅法不⾏,按照提⽰,有了如下⽅式:

cpp 复制代码
ketil@8-134-127-49:~/gitcode$ git branch -D dev3
Deleted branch dev3 (was cd2f149).
ketil@8-134-127-49:~/gitcode$ git branch
* master

小结

分⽀在实际中有什么⽤呢?假设准备开发⼀个新功能,但是需要两周才能完成,第⼀周写了50% 的代码,如果⽴刻提交,由于代码还没写完,不完整的代码库会导致别⼈不能⼲活了。如果等代码全部写完再⼀次提交,⼜存在丢失每天进度的巨⼤⻛险。

现在有了分⽀,就不⽤怕了。创建了⼀个属于⾃⼰的分⽀,别⼈看不到,还继续在原来的分⽀上 正常⼯作,⽽在⾃⼰的分⽀上⼲活,想提交就提交,直到开发完毕后,再⼀次性合并到原来的分⽀ 上,这样,既安全,⼜不影响别⼈⼯作。

并且 Git ⽆论创建、切换和删除分⽀,Git在1秒钟之内就能完成!⽆论版本库是1个⽂件还是1万个⽂件。

三、远程操作

3.1、理解分布式版本控制系统

⽬前所说的所有内容(⼯作区,暂存区,版本库等等),都是在本地!也就是在笔记本或者计算机上。⽽ Git 其实是分布式版本控制系统。

可以简单理解为,每个⼈的电脑上都是⼀个完整的版本库,这样⼯作的时候,就不需要联⽹了,因为版本库就在⾃⼰的电脑上。既然每个⼈电脑上都有⼀个完整的版本库,那多个⼈如何协作 呢?⽐⽅说在⾃⼰电脑上改了⽂件A,你的同事也在他的电脑上改了⽂件A,这时,你们俩之间只需把各⾃的修改推送给对⽅,就可以互相看到对方的修改。

分布式版本控制系统的安全性要⾼很多,因为每个⼈电脑⾥都有完整的版本库,某⼀个⼈的电脑坏掉 了不要紧,随便从其他⼈那⾥复制⼀个就可以了。

在实际使⽤分布式版本控制系统的时候,其实很少在两⼈之间的电脑上推送版本库的修改,因为可能 你们俩不在⼀个局域⽹内,两台电脑互相访问不了。也可能今天你的同事病了,他的电脑压根没有开 机。因此,分布式版本控制系统通常也有⼀台充当"中央服务器"的电脑,但这个服务器的作⽤仅仅 是⽤来⽅便"交换"⼤家的修改,没有它⼤家也⼀样⼲活,只是交换修改不⽅便⽽已。有了这个"中央服务器"的电脑,这样就不怕本地出现什么故障了(⽐如运⽓差,硬盘坏了,上⾯的所有东西全部 丢失,包括git的所有内容)。

3.2、远程仓库

Git 是分布式版本控制系统,同⼀个 Git 仓库,可以分布到不同的机器上。怎么分布呢?最早,肯定只有⼀台机器有⼀个原始版本库,此后,别的机器可以 "克隆" 这个原始版本库,⽽且每台机器的版本 库其实都是⼀样的,并没有主次之分。

实际情况往往是这样,找⼀台电脑充当服务器的⻆⾊,每天24⼩时开机,其他每个⼈都从这个"服务 器"仓库克隆⼀份到⾃⼰的电脑上,并且各⾃把各⾃的提交推送到服务器仓库⾥,也从服务器仓库中 拉取别⼈的提交。

完全可以⾃⼰搭建⼀台运⾏ Git 的服务器,不过现阶段,为了学 Git 先搭个服务器绝对是⼩题⼤作。好 在这个世界上有个叫 GitHub 的神奇的⽹站,从名字就可以看出,这个⽹站就是提供 Git 仓库托管服务的,所以,只要注册⼀个GitHub账号,就可以免费获得 Git 远程仓库。

3.3、克隆远程仓库

克隆/下载远端仓库到本地,需要使⽤ git clone 命令,后⾯跟上远端仓库的链接,远端仓库的链接可以从仓库中找到:选择"克隆/下载"获取远程仓库链接:

SSH 协议和 HTTPS 协议是 Git 最常使⽤的两种数据传输协议。SSH 协议使⽤了公钥加密和公钥登陆机制,体现了其实⽤性和安全性,使⽤此协议需要将我们的公钥放上服务器,由 Git 服务器进⾏管理。使 ⽤ HTTPS ⽅式时,没有要求,可以直接克隆下来。

  • 使⽤ HTTPS ⽅式:
cpp 复制代码
ketil@8-134-127-49:~$ git clone https://gitee.com/hyb91/git_teaching.git
Cloning into 'git_teaching'...
Username for 'https://gitee.com': hyb91
Password for 'https://hyb91@gitee.com':
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (4/4), 1.80 KiB | 1.80 MiB/s, done.

ketil@8-134-127-49:~$ ls
gitcode git_teaching
ketil@8-134-127-49:~$ ls git_teaching/
README.en.md README.md
  • 使⽤ SSH ⽅式:
cpp 复制代码
ketil@8-134-127-49:~$ git clone git@gitee.com:hyb91/git_teaching.git
Cloning into 'git_teaching'...
The authenticity of host 'gitee.com (212.64.63.215)' can't be established.
ECDSA key fingerprint is SHA256:FQGC9Kn/eye1W8icdBgrQp+KkGYoFgbVr17bmjey0Wc.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'gitee.com,212.64.63.215' (ECDSA) to the list of
known hosts.
git@gitee.com: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

使⽤ SSH ⽅式克隆仓库,由于没有添加公钥到远端库中,服务器拒绝了 clone 链接。需要设置⼀下:

第⼀步:创建SSH Key。在⽤⼾主⽬录下,看看有没有.ssh⽬录,如果有,再看看这个⽬录下有没有 id_rsa 和 id_rsa.pub 这两个⽂件,如果已经有了,可直接跳到下⼀步。如果没有,需要创建 SSH Key:

cpp 复制代码
# 注意要输⼊⾃⼰的邮箱,然后⼀路回⻋,使⽤默认值即可
ketil@8-134-127-49:~$ ssh-keygen -t rsa -C "wangxinhao001124@foxmail.com"
Generating public/private rsa key pair.
Enter file in which to save the key (/home/hyb/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/hyb/.ssh/id_rsa
Your public key has been saved in /home/hyb/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:sepKZUwKIXjJxYcot49tvOW+Aa+pEhdsRqVDFSmgQZo wangxinhao001124@foxmail.com
The key's randomart image is:
+---[RSA 3072]----+
|*+oB=+           |
|==Oo+ .          |
|E*+o .. .        |
| *o + o          |
| o *o + S        |
|. o ==..         |
| o ..++          |
|. ..+..          |
| ...+o+.         |
+----[SHA256]-----+

顺利的话,可以在⽤⼾主⽬录⾥找到 .ssh ⽬录,⾥⾯有 id_rsa 和 id_rsa.pub 两个⽂件,这两 个就是SSH Key的秘钥对, id_rsa 是私钥,不能泄露出去, id_rsa.pub 是公钥,可以放⼼地告 诉任何⼈。

cpp 复制代码
ketil@8-134-127-49:~$ ls -a .ssh/
. .. id_rsa id_rsa.pub known_hosts

第⼆步:添加⾃⼰的公钥到远端仓库。

点击 ssh公钥 选项,进⾏设置:

点击确认后,需要对你进⾏认证,输⼊账号密码即可。

cpp 复制代码
ketil@8-134-127-49:~$ git clone git@gitee.com:hyb91/git_teaching.git
Cloning into 'git_teaching'...
Warning: Permanently added the ECDSA host key for IP address '212.64.63.190'
to the list of known hosts.
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (4/4), done.

ketil@8-134-127-49:~$ ls
gitcode git_teaching
ketil@8-134-127-49:~$ ls git_teaching/
README.en.md README.md

done, 成功。如果有多个⼈协作开发,GitHub/Gitee 允许添加多个公钥,只要把每个⼈的电脑上的 Key 都添加到 GitHub/Gitee,就可以在每台电脑上往 GitHub/Gitee 上提交推送了。

当从远程仓库克隆后,实际上 Git 会⾃动把本地的 master 分⽀和远程的 master 分⽀对应起来, 并且,远程仓库的默认名称是 origin 。在本地我们可以使⽤ git remote 命令,来查看远程库的信息,如:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git remote
origin

或者,⽤ git remote -v 显⽰更详细的信息:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git remote -v
origin git@gitee.com:hyb91/git_teaching.git (fetch)
origin git@gitee.com:hyb91/git_teaching.git (push)

上⾯显⽰了可以抓取和推送的origin的地址。如果没有推送权限,就看不到 push 的地址。推送是什么意思呢?继续往下看。

3.4、向远程仓库推送

本地已经 clone 成功远程仓库后,我们便可以向仓库中提交内容,例如新增⼀个 file.txt ⽂件:

cpp 复制代码
# 新建⽂件
ketil@8-134-127-49:~/git_teaching$ ls
README.en.md README.md
ketil@8-134-127-49:~/git_teaching$ vim file.txt
ketil@8-134-127-49:~/git_teaching$ cat file.txt
hello git

# 提交⽂件
ketil@8-134-127-49:~/git_teaching$ git add .
ketil@8-134-127-49:~/git_teaching$ git commit -m"create file.txt"
[master 7ce3183] create file.txt
 1 file changed, 1 insertion(+)
 create mode 100644 file.txt

提交时要注意,如果之前设置过全局的 name 和 e-mail,这两项配置需要和 gitee 上配置的⽤⼾名和邮箱⼀致,否则会出错。或者从来没有设置过全局的 name 和 e-mail,那么第⼀次提交时也 会报错。这就需要重新配置下了,同样要注意需要和 gitee 上配置的⽤⼾名和邮箱⼀致。如何配置已讲过,在这⾥就不再赘述。

到这⾥已经将内容提交⾄本地仓库中,如何将本地仓库的内容推送⾄远程仓库呢,需要使⽤ git push 命令,该命令⽤于将本地的分⽀版本上传到远程并合并,命令格式如下:

cpp 复制代码
git push <远程主机名> <本地分⽀名>:<远程分⽀名>

# 如果本地分⽀名与远程分⽀名相同,则可以省略冒号:
git push <远程主机名> <本地分⽀名>

此时只要将本地的 master 分⽀推送到 origin 主机的 master 分⽀,则可以:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git push origin master
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 308 bytes | 308.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-6.4]
To gitee.com:hyb91/git_teaching.git
 c6ce3f0..7ce3183 master -> master

推送成功!这⾥由于使⽤的是 SSH 协议,是不⽤每⼀次推送都输⼊密码的,⽅便推送操作。如果使⽤的是 HTTPS 协议,有个⿇烦地⽅就是每次推送都必须输⼊⼝令。

3.5、拉取远程仓库

在 gitee 上点击 README.md ⽂件并在线修改它:

此时,远程仓库是要领先于本地仓库⼀个版本,为了使本地仓库保持最新的版本,我们需要拉取下远 端代码,并合并到本地。Git 提供了 git pull 命令,该命令⽤于从远程获取代码并合并本地的版 本。格式如下:

cpp 复制代码
git pull <远程主机名> <远程分⽀名>:<本地分⽀名>

# 如果远程分⽀是与当前分⽀合并,则冒号后⾯的部分可以省略。
git pull <远程主机名> <远程分⽀名>

使⽤⼀下:

cpp 复制代码
# 拉取远程分⽀,并与当前分⽀进⾏合并
ketil@8-134-127-49:~/git_teaching$ git pull origin master
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 1.02 KiB | 1.02 MiB/s, done.
From gitee.com:hyb91/git_teaching
 * branch master -> FETCH_HEAD
 7ce3183..60e6b0a master -> origin/master
Updating 7ce3183..60e6b0a
Fast-forward
 README.md | 2 ++
 1 file changed, 2 insertions(+)

ketil@8-134-127-49:~/git_teaching$ cat README.md
...
第⼀次修改内容

3.6、配置Git

忽略特殊⽂件

在⽇常开发中,有些⽂件不想或者不应该提交到远端,⽐如保存了数据库密码的配置⽂件,那怎 么让 Git 知道呢?在 Git ⼯作区的根⽬录下创建⼀个特殊的 .gitignore ⽂件,然后把要忽略的⽂件 名填进去,Git 就会⾃动忽略这些⽂件了。

不需要从头写 .gitignore ⽂件,gitee 在创建仓库时就可以为我们⽣成,不过需要主动勾选⼀ 下。

如果当时没有选择这个选择,在⼯作区创建⼀个也是可以的。⽆论哪种⽅式,最终都可以得到⼀个完 整的 .gitignore ⽂件,例如想忽略以 .so 和 .ini 结尾所有⽂件, .gitignore 的内容 如下:

cpp 复制代码
# 省略选择模本的内容
...

# My configurations:
*.ini
*.so

在 .gitignore ⽂件中也可以指定某个确定的⽂件。

最后⼀步就是把 .gitignore 也提交到远端,就完成了:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ vim .gitignore
ketil@8-134-127-49:~/git_teaching$ git add .
ketil@8-134-127-49:~/git_teaching$ git commit -m"add .gitignore"
[master 97811ab] add .gitignore
 1 file changed, 3 insertions(+)
 create mode 100644 .gitignore
ketil@8-134-127-49:~/git_teaching$ git push origin master
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 362 bytes | 362.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-6.4]
To gitee.com:hyb91/git_teaching.git
 60e6b0a..97811ab master -> master

接着就来验证⼀下.gitignore⽂件的能⼒,在⼯作区新增两个⽂件 a.so b.ini :

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ touch a.so b.ini
ketil@8-134-127-49:~/git_teaching$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

检验 .gitignore 的标准就是 git status 命令是不是说 working tree clean 。发现 Git 并没有提⽰在⼯作区中有⽂件新增,果然 .gitignore ⽣效了!

但有些时候,就是想添加⼀个⽂件到 Git,但由于这个⽂件被 .gitignore 忽略了,根本添加不了,那么可以⽤ -f 强制添加:

cpp 复制代码
$ git add -f [filename]

或者发现,可能是 .gitignore 写得有问题,需要找出来到底哪个规则写错了,⽐如说 a.so ⽂件 是要被添加的,可以⽤ git check-ignore 命令检查:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git check-ignore -v a.so
.gitignore:3:*.so     a.so

Git 会告诉我们, .gitignore 的第3⾏规则忽略了该⽂件,于是就可以知道应该修订哪个规则。

还有些时候,当编写了规则排除了部分⽂件时,例如:

cpp 复制代码
#  排除所有.开头的隐藏⽂件:
.*

但是发现 .* 这个规则把 .gitignore 也排除了。虽然可以⽤ git add -f 强制添加进去,这个时候,可以添加⼀条例外规则:

cpp 复制代码
# 排除所有.开头的隐藏⽂件:
.*

# 不排除.gitignore
!.gitignore

把指定⽂件排除在 .gitignore 规则外的写法就是 ! +⽂件名,所以,只需把例外⽂件添加进去即 可。

给命令配置别名

将 git status 简化为 git st ,对应的命令为:

cpp 复制代码
$ git config --global alias.st status

--global 参数是全局参数,也就是这些命令在这台电脑的所有Git仓库下都有⽤。如果不加,那只 针对当前的仓库起作⽤。

现在敲 git st 看看效果:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git st
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean

再来配置⼀个 git last ,让其显⽰最后⼀次提交信息:

cpp 复制代码
$ git config --global alias.last 'log -l'

这样,⽤ git last 就能显⽰最近⼀次的提交:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git last
commit 97811abd1d43774aeb54fee32bf4fc76b2b08170 (HEAD -> master,
origin/master, origin/HEAD)
Author: hyb91 <2689241679@qq.com>
Date: Fri May 12 17:27:06 2023 +0800

 add .gitignore

3.7、标签管理

理解标签

标签 tag ,可以简单的理解为是对某次 commit 的⼀个标识,相当于起了⼀个别名。例如,在项⽬ 发布某个版本的时候,针对最后⼀次 commit 起⼀个 v1.0 这样的标签来标识⾥程碑的意义。

这有什么⽤呢?相较于难以记住的 commit id , tag 很好的解决这个问题,因为 tag ⼀定要给⼀ 个让⼈容易记住,且有意义的名字。当我们需要回退到某个重要版本时,直接使⽤标签就能很快定位 到。

创建标签

在Git中打标签⾮常简单,⾸先,切换到需要打标签的分⽀上

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git branch
* master

然后,敲命令 git tag [name] 就可以打⼀个新标签:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git tag v1.0

可以⽤命令 git tag 查看所有标签:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git tag
v1.0

默认标签是打在最新提交的 commit 上的。那如何在指定的commit上打标签呢?⽅法是找到历史提 交的commit id,然后打上就可以了,⽰例如下:

cpp 复制代码
# 历史记录
ketil@8-134-127-49:~/git_teaching$ git log --pretty=oneline --abbrev-commit
97811ab (HEAD -> master, tag: v1.0, origin/master, origin/HEAD) add .gitignore
60e6b0a update README.md.
7ce3183 create file.txt
c6ce3f0 Initial commit

# 对 Initial commit 这次提交打标签
ketil@8-134-127-49:~/git_teaching$ git tag v0.9 c6ce3f0
ketil@8-134-127-49:~/git_teaching$ git tag
v0.9
v1.0

注意,标签不是按时间顺序列出,⽽是按字⺟排序的。

可以⽤ git show [tagname] 查看标签信息。

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git show v1.0
commit 97811abd1d43774aeb54fee32bf4fc76b2b08170 (HEAD -> master, tag: v1.0,
origin/master, origin/HEAD)
Author: hyb91 <2689241679@qq.com>
Date: Fri May 12 17:27:06 2023 +0800

 add .gitignore

diff --git a/.gitignore b/.gitignore
...

Git 还提供可以创建带有说明的标签,⽤-a指定标签名,-m指定说明⽂字,格式为:

cpp 复制代码
git tag -a [name] -m "XXX" [commit_id]

另外,打完标签之后,使⽤ tree .git 命令查看⼀下本地库有什么变化!

操作标签

如果标签打错了,也可以删除:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git tag
v0.9
v1.0
ketil@8-134-127-49:~/git_teaching$ git tag -d v0.9
Deleted tag 'v0.9' (was c6ce3f0)
ketil@8-134-127-49:~/git_teaching$ git tag
v1.0

因为创建的标签都只存储在本地,不会⾃动推送到远程。所以,打错的标签可以在本地安全删除。 如果要推送某个标签到远程,使⽤命令 git push origin <tagname>

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git tag
v1.0
ketil@8-134-127-49:~/git_teaching$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-6.4]
To gitee.com:hyb91/git_teaching.git
 * [new tag] v1.0 -> v1.0

此时,查看远端码云,看到了标签已经被更新!

当然,如果本地有很多标签,也可以⼀次性的全部推送到远端:

cpp 复制代码
git push origin --tags

如果标签已经推送到远程,要删除远程标签就⿇烦⼀点,先从本地删除:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git tag
v1.0
ketil@8-134-127-49:~/git_teaching$ git tag -d v1.0
Deleted tag 'v1.0' (was 97811ab)

然后,从远程删除。删除命令也是push,但是格式如下:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git push origin :refs/tags/v1.0
remote: Powered by GITEE.COM [GNK-6.4]
To gitee.com:hyb91/git_teaching.git
 - [deleted]     v1.0

在码云上查看删除成功

四、多人协作

4.1、多⼈协作⼀

⽬前,所完成的⼯作如下:

  • 基本完成 Git 的所有本地库的相关操作,git基本操作,分⽀理解,版本回退,冲突解决等等
  • 申请码云账号,将远端信息clone到本地,以及推送和拉取。

之前已经将项⽬ clone 到了指定⽬录,如:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ pwd
/home/hyb/git_teaching

在 windows 环境下,再 clone 同⼀个项⽬仓库,来模拟和你⼀起协作开发的另⼀名⼩伙伴。

注意,文章中是模拟了两个⽤⼾,实际开发中,每个⽤⼾都有⾃⼰的gitee/github账号,如果要多⼈进 ⾏协同开发,必须要将⽤⼾添加进开发者,⽤⼾才有权限进⾏代码提交。

到此,相当于有了两个⽤⼾,分别在 linux 和 windows 上针对于同项⽬进⾏协作开发,准备⼯作到此结束。

⽬前,仓库中只有⼀个 master 主分⽀,但在实际的项⽬开发中,在任何情况下其实都是不允许 直接在 master 分⽀上修改代码的,这是为了保证主分⽀的稳定。所以在开发新功能时,常常会新建其他分⽀,供开发时进⾏迭代使⽤。

接下来,就在 gitee 上新建 dev 远程分⽀供使⽤:

创建成功的远程分⽀是可以通过 Git 拉取到本地来,以实现完成本地开发⼯作。

接下来自己和另⼀名开发的⼩伙伴都将远程仓库进⾏⼀次拉取操作,并观察结果:

  • 对于自己要操作的是:
cpp 复制代码
ketil@8-134-124-49:~/git_teaching$ git pull
From gitee.com:hyb91/git_teaching
 * [new branch] dev -> origin/dev
Already up to date.

# 注:之前讲的 git branch 其实只能查看本地分⽀,要查看远程分⽀需要加上-r选项。
# 但前提是要pull⼀下拉取最新的远端仓库,才能看到最新的内容。
ketil@8-134-127-49:~/git_teaching$ git branch -r
 origin/HEAD -> origin/master
 origin/dev
 origin/master

ketil@8-134-127-49:~/git_teaching$ git checkout -b dev origin/dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.
Switched to a new branch 'dev'

拉取后便可以看到远程的 dev 分⽀,接着切换到 dev 分⽀供自己进⾏本地开发。要说明的是切换到的是本地的 dev 分⽀,根据⽰例中的操作,会将本地分⽀和远程分⽀的进⾏关系链接。

  • 对于小伙伴要操作的是:

现在,就可以一起在 dev 上完成开发。

⾸先,让在 dev 分⽀上进⾏⼀次开发,并 push 到远程。例如:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ vim file.txt
ketil@8-134-127-49:~/git_teaching$ cat file.txt
hello git
complete the first function!
ketil@8-134-127-49:~/git_teaching$ git add file.txt
ketil@8-134-127-49:~/git_teaching$ git commit -m "first function"
[dev 305f78a] first function
 1 file changed, 1 deletion(-)
ketil@8-134-127-49:~/git_teaching$ git push origin dev # 将dev分⽀推送到远端
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 287 bytes | 287.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-6.4]
To gitee.com:hyb91/git_teaching.git
 cc3be59..305f78a dev -> dev

⾄此,已经将代码成功推送⾄码云,接下来假如你的⼩伙伴要和你协同开发,碰巧也要对 file.txt ⽂件作修改,并试图推送。

推送失败,因为你的⼩伙伴的最新提交和你推送的提交有冲突,解决办法也很简单,Git已经提⽰ 我们,先⽤ git pull 把最新的提交从 origin/dev 抓下来,然后,在本地进⾏合并,并解决冲 突,再推送。

由此,两名开发者已经开始可以进⾏协同开发了,不断的 git pull/add/commit/push ,遇到了 冲突,就使⽤我们之前讲的冲突处理解决掉冲突。

对于你来说,要想看到⼩伙伴的代码,只需要 pull ⼀下即可,例如:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ cat file.txt
hello git
complete the first function!
ketil@8-134-127-49:~/git_teaching$ git pull
Updating 305f78a..72c5345
Fast-forward
 file.txt | 1 +
 1 file changed, 1 insertion(+)
ketil@8-134-127-49:~/git_teaching$ cat file.txt
hello git
complete the first function!
complete the second function!

最后不要忘记,虽然是在分⽀上进⾏多⼈协作开发,但最终的⽬的是要将开发后的代码合并到 master上去,让项⽬运⾏最新的代码。接下来就需要做这件事情了:

cpp 复制代码
# 切换⾄ master分⽀, pull ⼀下,保证本地的master是最新内容。
# 合并前这么做是⼀个好习惯
ketil@8-134-127-49:~/git_teaching$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
ketil@8-134-127-49:~/git_teaching$ git pull
Already up to date.

# 切换⾄ dev 分⽀, 合并 master 分⽀
# 这么做是因为如果有冲突,可以在dev分⽀上进⾏处理,⽽不是在在master上解决冲突。
# 这么做是⼀个好习惯
ketil@8-134-127-49:~/git_teaching$ git checkout dev
Switched to branch 'dev'
Your branch is up to date with 'origin/dev'.
ketil@8-134-127-49:~/git_teaching$ git merge master
Already up to date.

# 切换⾄ master 分⽀,合并 dev 分⽀
ketil@8-134-127-49:~/git_teaching$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
ketil@8-134-127-49:~/git_teaching$ git merge dev
Updating 7388a31..72c5345
Fast-forward
 file.txt | 2 ++
 1 file changed, 2 insertions(+)
ketil@8-134-127-49:~/git_teaching$ cat file.txt
hello git
complete the first function!
complete the second function!

# 将 master 分⽀推送⾄远端
ketil@8-134-127-49:~/git_teaching$ git status
On branch master
Your branch is ahead of 'origin/master' by 4 commits.
 (use "git push" to publish your local commits)
nothing to commit, working tree clean
ketil@8-134-127-49:~/git_teaching$ git push origin master
Total 0 (delta 0), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-6.4]
To gitee.com:hyb91/git_teaching.git
 7388a31..72c5345 master -> master
ketil@8-134-127-49:~/git_teaching$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean

此时,查看远端仓库,master已经是最新代码了。

总结⼀下,在同⼀分⽀下进⾏多⼈协作的⼯作模式通常是这样:

  • ⾸先,可以试图⽤ git push origin branch-name 推送⾃⼰的修改;
  • 如果推送失败,则因为远程分⽀⽐你的本地更新,需要先⽤ git pull 试图合并;
  • 如果合并有冲突,则解决冲突,并在本地提交;
  • 没有冲突或者解决掉冲突后,再⽤git push origin branch-name推送就能成功!
  • 功能开发完毕,将分⽀ merge 进 master,最后删除分⽀。

4.2、多人协作二

⼀般情况下,如果有多需求需要多⼈同时进⾏开发,是不会在⼀个分⽀上进⾏多⼈开发,⽽是⼀个需求或⼀个功能点就要创建⼀个 feature 分⽀。

现在同时有两个需求需要你和你的⼩伙伴进⾏开发,那么你们俩便可以各⾃创建⼀个分⽀来完成⾃⼰ 的⼯作。在上个部分我们已经了解了可以从码云上直接创建远程分⽀,其实在本地创建的分⽀也可以 通过推送的⽅式发送到远端。在这个部分我们就来⽤⼀下这种⽅式。

• 对于你来说,可以进⾏以下操作:

cpp 复制代码
# 新增本地分⽀ feature-1 并切换
ketil@8-134-127-49:~/git_teaching$ git branch
 dev
* master
ketil@8-134-127-49:~/git_teaching$ git checkout -b feature-1
Switched to a new branch 'feature-1'

# 新增需求内容-创建function1⽂件
ketil@8-134-127-49:~/git_teaching$ vim function1
ketil@8-134-127-49:~/git_teaching$ cat function1
Done!

# 将 feature-1 分⽀推送到远端
ketil@8-134-127-49:~/git_teaching$ git add function1
ketil@8-134-127-49:~/git_teaching$ git commit -m"add function1"
[feature-1 12ed0db] add function1
 1 file changed, 1 insertion(+)
 create mode 100644 function1
ketil@8-134-127-49:~/git_teaching$ git push origin feature-1
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 270 bytes | 270.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-6.4]
remote: Create a pull request for 'feature-1' on Gitee by visiting:
remote: https://gitee.com/hyb91/git_teaching/pull/new/hyb91:feature1...hyb91:master
To gitee.com:hyb91/git_teaching.git
 * [new branch] feature-1 -> feature-1
  • 对于⼩伙伴来说,可以进⾏以下操作:

此时,在本地,你看不⻅他新建的⽂档,他看不⻅你新建的⽂档。并且推送各⾃的分⽀时,并没有任 何冲突,你俩互不影响。

正常情况下,两人就可以在⾃⼰的分⽀上进⾏专业的开发了!

但你的小伙伴请假了,需求还没开发完,需要你帮他继续开发,于是他便把 feature-2 分⽀名告诉你了。这时你就需要在⾃⼰的机器上切换到 feature-2 分⽀帮忙继续开发,要做的操作如下:

cpp 复制代码
# 必须先拉取远端仓库内容
ketil@8-134-127-49:~/git_teaching$ git pull
...
From gitee.com:hyb91/git_teaching
    305f78a..72c5345 dev         -> origin/dev
    * [new branch]   feature-2   -> origin/feature-2
...

# 可以看到远程已经有了feature-2
ketil@8-134-127-49:~/git_teaching$ git branch -a
 dev
* feature-1
 master
 remotes/origin/HEAD -> origin/master
 remotes/origin/dev
 remotes/origin/feature-1
 remotes/origin/feature-2
 remotes/origin/master

# 切换到feature-2分⽀上,可以和远程的feature-2分⽀关联起来,
# 否则将来只使⽤ git push 推送内容会失败
ketil@8-134-127-49:~/git_teaching$ git checkout -b feature-2 origin/feature-2
Branch 'feature-2' set up to track remote branch 'feature-2' from 'origin'.
Switched to a new branch 'feature-2'

ketil@8-134-127-49:~/git_teaching$ ls
a.so b.ini file.txt function2 README.en.md README.md

切换成功后,便可以看⻅ feature-2 分⽀中的 function2 ⽂件了,接着就可以帮⼩伙伴进⾏开发:

cpp 复制代码
# 继续开发
ketil@8-134-127-49:~/git_teaching$ vim function2
ketil@8-134-127-49:~/git_teaching$ cat function2
Done!
Help done!

# 推送内容
ketil@8-134-127-49:~/git_teaching$ git add function2
ketil@8-134-127-49:~/git_teaching$ git commit -m"modify function2"
[feature-2 1079ae7] modify function2
 1 file changed, 2 insertions(+), 1 deletion(-)
ketil@8-134-127-49:~/git_teaching$ git push origin feature-2
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 262 bytes | 262.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-6.4]
To gitee.com:hyb91/git_teaching.git
 e1233f1..1079ae7 feature-2 -> feature-2

查看远程状态,推送成功了。

这时,你的⼩伙伴已经修养的差不多,可以继续进⾏⾃⼰的开发⼯作,那么他⾸先要获取到你帮他开 发的内容,然后接着你的代码继续开发。或者你已经帮他开发完了,那他也需要在⾃⼰的电脑上看看 你帮他写的代码。

Pull ⽆效的原因是⼩伙伴没有指定本地 feature-2 分⽀与远程 origin/feature-2 分⽀的链接,根据提 ⽰,设置 feature-2 和 origin/feature-2的链接即可:

⽬前,⼩伙伴的本地代码和远端保持严格⼀致。你和你的⼩伙伴可以继续在不同的分⽀下进⾏协同开发了。

各⾃功能开发完毕后,不要忘记我们需要将代码合并到master中才算真正意义上的开发完毕。 由于你的⼩伙伴率先开发完毕,于是开始 merge :

  1. 切换至master,pull一下,保证本地master是最新内容
cpp 复制代码
git chechout master
git pull
  1. 切换至 dev 分支,合并 master 分支
cpp 复制代码
git checkout feature-2
git merge master
  1. 切换至master分支,合并feature-2 分支
cpp 复制代码
git chechout master
git merge feature-2
  1. 将master分支推送至远端
cpp 复制代码
git status
git push origin master
git status

当你的⼩伙伴将其代码 merge 到 master 后,这是你也开发完成了,也需要进⾏ merge 到 master 操作,于是你:

cpp 复制代码
# 切换⾄ master分⽀, pull ⼀下,保证本地的master是最新内容。
# 合并前这么做是⼀个好习惯
ketil@8-134-127-49:~/git_teaching$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
ketil@8-134-127-49:~/git_teaching$ git pull
From gitee.com:hyb91/git_teaching
 72c5345..29006bd master -> origin/master
Updating 72c5345..29006bd
Fast-forward
 function2 | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 function2

# 切换⾄ feature-1 分⽀, 合并 master 分⽀
# 这么做是因为如果有冲突,可以在feature-1分⽀上进⾏处理,⽽不是在在master上解决冲突。
# 这么做是⼀个好习惯
ketil@8-134-127-49:~/git_teaching$ git checkout feature-1
Switched to branch 'feature-1'
Your branch is up to date with 'origin/feature-1'.
ketil@8-134-127-49:~/git_teaching$ git merge master
Merge made by the 'recursive' strategy.
 function2 | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 function2
ketil@8-134-127-49:~/git_teaching$ ls
a.so b.ini file.txt function1 function2 README.en.md README.md

# 1、由于feature-1分⽀已经merge进来了新内容,为了保证远程分⽀最新,所以最好push⼀下。
# 2、要 push 的另⼀个原因是因为在实际的开发中,master的merge操作⼀般不是由我们⾃⼰在本地
进⾏操作,
# 其他⼈员或某些平台merge时,操作的肯定是远程分⽀,所以就要保证远程分⽀的最新。
# 3、如果 merge 出现冲突,不要忘记需要commit才可以push!!
ketil@8-134-127-49:~/git_teaching$ git status
On branch feature-1
Your branch is ahead of 'origin/feature-1' by 4 commits.
 (use "git push" to publish your local commits)

nothing to commit, working tree clean
ketil@8-134-127-49:~/git_teaching$ git push origin feature-1
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 299 bytes | 299.00 KiB/s, done.
Total 2 (delta 1), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-6.4]
To gitee.com:hyb91/git_teaching.git
 ea75a35..4b4c3d4 feature-1 -> feature-1

# 切换⾄ master 分⽀,合并 feature-1 分⽀
ketil@8-134-127-49:~/git_teaching$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
ketil@8-134-127-49:~/git_teaching$ git merge feature-1
Updating 29006bd..4b4c3d4
Fast-forward
 function1 | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 function1
ketil@8-134-127-49:~/git_teaching$ ls
a.so b.ini file.txt function1 function2 README.en.md README.md

# 将 master 分⽀推送⾄远端
ketil@8-134-127-49:~/git_teaching$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
 (use "git push" to publish your local commits)

nothing to commit, working tree clean
ketil@8-134-127-49:~/git_teaching$ git push origin master
Total 0 (delta 0), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-6.4]
To gitee.com:hyb91/git_teaching.git
 29006bd..4b4c3d4 master -> master
ketil@8-134-127-49:~/git_teaching$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean

此时, feature-1 和 feature-2 分⽀对于来说就没⽤了, 那么可以直接在远程仓库中 将dev分⽀删除掉。

这就是多⼈协作的⼯作模式,⼀旦熟悉了,就⾮常简单。

4.3、远程分⽀删除后,本地 git branch -a 依然能看到的解决办法

当前已经删除了远程的⼏个分⽀,使⽤ git branch -a 命令可以查看所有本地分⽀和远程分⽀,但发现很多在远程仓库已经删除的分⽀在本地依然可以看到。例如:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git pull
Already up to date.
ketil@8-134-127-49:~/git_teaching$ git branch -a
 dev
 feature-1
 feature-2
* master
 remotes/origin/HEAD -> origin/master
 remotes/origin/dev
 remotes/origin/feature-1
 remotes/origin/feature-2
 remotes/origin/master

使⽤命令 git remote show origin ,可以查看remote地址,远程分⽀,还有本地分⽀与之相对应关系等信息。

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git remote show origin
* remote origin
 Fetch URL: git@gitee.com:hyb91/git_teaching.git
 Push URL: git@gitee.com:hyb91/git_teaching.git
 HEAD branch: master
 Remote branches:
 master tracked
 refs/remotes/origin/dev stale (use 'git remote prune' to remove)
 refs/remotes/origin/feature-1 stale (use 'git remote prune' to remove)
 refs/remotes/origin/feature-2 stale (use 'git remote prune' to remove)
 Local branches configured for 'git pull':
 dev merges with remote dev
 feature-1 merges with remote feature-1
 feature-2 merges with remote feature-2
 master merges with remote master
 Local ref configured for 'git push':
 master pushes to master (up to date)

此时可以看到那些远程仓库已经不存在的分⽀,根据提⽰,使⽤ git remote prune origin 命令:

cpp 复制代码
ketil@8-134-127-49:~/git_teaching$ git remote prune origin
Pruning origin
URL: git@gitee.com:hyb91/git_teaching.git
 * [pruned] origin/dev
 * [pruned] origin/feature-1
 * [pruned] origin/feature-2
ketil@8-134-127-49:~/git_teaching$ git branch -a
 dev
 feature-1
 feature-2
* master
 remotes/origin/HEAD -> origin/master
 remotes/origin/master

这样就删除了那些远程仓库不存在的分⽀。

五、企业开发模式

在传统的 IT 组织下,开发团队(Dev)和运维团队(Ops)之间诉求不同:

  • 开发团队(尤其是敏捷团队)追求变化
  • 运维团队追求稳定

双⽅往往存在利益的冲突。⽐如,精益和敏捷的团队把持续交付作为⽬标,⽽运维团队则为了线上的 稳定⽽强调变更控制。部⻔墙由此建⽴起来,这当然不利于 IT 价值的最⼤化。

为了弥合开发和运维之间的鸿沟,需要在⽂化、⼯具和实践⽅⾯的系列变⾰⸺DevOps正式登上舞台。

DevOps(Development和Operations的组合词)是⼀种重视"软件开发⼈员(Dev)"和"IT运维技 术⼈员(Ops)"之间沟通合作的⽂化、运动或惯例。透过⾃动化"软件交付"和"架构变更"的流 程,来使得构建、测试、发布软件能够更加地快捷、频繁和可靠。在DevOps的软件开发过程包含计 划、编码、构建、测试、预发布、发布、运维、监控,由此可⻅DevOps的强⼤。

⼀个软件的迭代,在开发⼈员看来,说⽩了就是对代码进⾏迭代,那么就需要对代码进⾏管理。如何管理的代码呢,那不就是 Git(分布式版本控制系统) !所以 Git 对于开发⼈员来说其重要性就不⾔⽽喻了。

系统开发环境

  1. **开发环境:**开发环境是程序猿们专⻔⽤于⽇常开发的服务器。为了开发调试⽅便,⼀般打开全部错 误报告和测试⼯具,是最基础的环境。
  2. 测试环境:⼀个程序在测试环境⼯作不正常,那么肯定不能把它发布到⽣产机上。该环境是开发环 境到⽣产环境的过渡环境。
  3. **预发布环境:**该环境是为避免因测试环境和线上环境的差异等带来的缺陷漏测⽽设⽴的⼀套环境。 其配置等基本和⽣产环境⼀致,⽬的是能让我们发正式环境时更有把握!所以预发布环境是你的产 品质量最后⼀道防线,因为下⼀步你的项⽬就要上线了。要注意预发布环境服务器不在线上集成服 务器范围之内,为单独的⼀些机器。
  4. **⽣产环境:**是指正式提供对外服务的线上环境,例如我们⽬前在移动端或PC端能访问到的APP都是 ⽣产环境。

这⼏个环境也可以说是系统开发的三个重要阶段:开发->测试->上线。⼀张图总结:

对于规模稍微⼤点的公司来说,可不⽌这么⼏个环境,⽐如项⽬正式上线前还存在仿真/灰度环境,再 ⽐如还存在多套测试环境,以满⾜不同版本上线前测试的需要。

⼀个项⽬的开始从设计开始,⽽⼀个项⽬的成功则从测试开始。⼀套良好的测试体系可以将系统中绝 ⼤部分的致命Bug 解决在系统上线之前。测试系统的完善和成熟也是衡量⼀个软件企业整体⽔平的重 要指标之⼀,测试往往被忽视,因为它对可以的隐性、对软件开发企业不产⽣直接的效益,但是它却 是软件质量的最终保障,乃⾄项⽬能否成功的重要因素!

Git 分支设计规范

环境有了概念后,那么对于开发⼈员来说,⼀般会针对不同的环境来设计分⽀,例如:

分支 名称 适用环境
master 主分支 生产环境
release 预发布分支 预发布/测试环境
develop 开发分支 开发环境
feature 紧急开发分支 本地
hotfix 紧急修复分支 本地

注:以上表格中的分⽀和环境的搭配仅是常⽤的⼀种,可视情况⽽定不同的策略。

master 分支

  • master 为主分⽀,该分⽀为只读且唯⼀分⽀。⽤于部署到正式发布环境,⼀般由合并 release 分⽀得到。
  • 主分⽀作为稳定的唯⼀代码库,任何情况下不允许直接在 master 分⽀上修改代码。
  • 产品的功能全部实现后,最终在master分⽀对外发布,另外所有在master分⽀的推送应该打标签 (tag)做记录,⽅便追溯。
  • master 分⽀不可删除。

release 分支

  • release 为预发布分⽀,基于本次上线所有的 feature 分⽀合并到 develop 分⽀之后,基 于 develop 分⽀创建。可以部署到测试或预发布集群。
  • 命名以 release/ 开头,建议的命名规则: release/version_publishtime。
  • release 分⽀主要⽤于提交给测试⼈员进⾏功能测试。发布提测阶段,会以 release 分⽀代码 为基准进⾏提测。
  • 如果在 release 分⽀测试出问题,需要回归验证 develop 分⽀看否存在此问题。
  • release 分⽀属于临时分⽀,产品上线后可选删除。

develop 分支

  • develop 为开发分⽀,基于master分⽀创建的只读且唯⼀分⽀,始终保持最新完成以及 bug 修 复后的代码。可部署到开发环境对应集群。
  • 可根据需求⼤⼩程度确定是由 feature 分⽀合并,还是直接在上⾯开发(⾮常不建议)。

feature 分支

  • feature 分⽀通常为新功能或新特性开发分⽀,以 develop 分⽀为基础创建 feature 分支
  • 命名以 feature/ 开头,建议的命名规则: feature/user_createtime_feature 。
  • 新特性或新功能开发完成后,开发⼈员需合到 develop 分⽀。
  • ⼀旦该需求发布上线,便将其删除。

hotfix 分支

  • hotfix 分⽀为线上 bug 修复分⽀或叫补丁分⽀,主要⽤于对线上的版本进⾏ bug 修复。当线上 出现紧急问题需要⻢上修复时,需要基于 master 分⽀创建 hotfix 分⽀。
  • 命名以 hotfix/ 开头,建议的命名规则: hotfix/user_createtime_hotfix
  • 当问题修复完成后,需要合并到 master 分⽀和 develop 分⽀并推送远程。⼀旦修复上线,便 将其删除。

其实,以上讲解的是企业级常⽤的⼀种 Git 分⽀设计规范:Git Flow 模型。但要说的是,该模型并不是适⽤于所有的团队、所有的环境和所有的⽂化。如果你采⽤了持续交付,你会想要⼀些能够 尽可能简化交付过程的东西。有些⼈喜欢基于主⼲的开发模式,喜欢使⽤特性标志。然⽽,从测试的⻆度来看,这些反⽽会把他吓⼀跳。

关键在于站在的团队或项⽬的⻆度思考:这种分⽀模型可以帮助你们解决哪些问题?它会带来哪些 问题?这种模式为哪种开发提供更好的⽀持?你们想要⿎励这种⾏为吗?你选择的分⽀模型最终都是为了让⼈们更容易地进⾏软件协作开发。因此,分⽀模型需要考虑到使⽤者的需求,⽽不是盲⽬听信 某些所谓的"成功的分⽀模型"。

所以对于不同公司,规范是会有些许差异,但万变不离其宗,是为了效率与稳定。

企业级项⽬管理实战

DevOps 研发平台

Gitee企业版免费版

修复测试环境 Bug

在 develop 测试出现了Bug,建议⼤家直接在 feature 分⽀上进⾏修复。 修复后的提测上线流程 与 新需求加⼊的流程⼀致。

修改预发布环境 Bug

在 release 测试出现了 Bug,⾸先要回归下 develop 分⽀是否同样存在这个问题。 如果存在,修复流程 与 修复测试环境 Bug流程⼀致。 如果不存在,这种可能性⽐较少,⼤部分是数据兼容问题,环境配置问题等。

修改正式环境 Bug

在 master 测试出现了Bug,⾸先要回归下 release 和 develop 分⽀是否同样存在这个问 题。

如果存在,修复流程 与 修复测试环境 Bug流程⼀致。

如果不存在,这种可能性也⽐较少,⼤部分是数据兼容问题,环境配置问题等。

紧急修复正式环境 Bug

需求在测试环节未测试出 Bug,上线运⾏⼀段时候后出现了 Bug,需要紧急修复的。

有的企业⾯对紧急修复时,⽀持不进⾏测试环境的验证,但还是建议验证下预发布环境。

可基于 master 创建 hotfix/xxx 分⽀,修复完毕后发布到 master 验证,验证完毕后,将 master 代码合并到 develop 分⽀,同时删掉 hotfix/xxx 分⽀。

拓展阅读

其他DevOps研发平台

腾讯coding

阿里云效

拓展实践

阿⾥⻜流flow分⽀模型,及项⽬版本管理实践:

https://blog.csdn.net/bbcckkl/article/details/111087267

相关推荐
我不是程序猿儿43 分钟前
【GIT】TortoiseGit的变基(Rebase)操作
git
yyycqupt8 小时前
git使用(一)
git
Kkooe11 小时前
GitLab|数据迁移
运维·服务器·git
Beekeeper&&P...12 小时前
git bash是什么,git是什么,git中的暂存区是什么,git中的本地仓库是什么,git中工作目录指的是什么
开发语言·git·bash
Stara051117 小时前
Git推送+拉去+uwsgi+Nginx服务器部署项目
git·python·mysql·nginx·gitee·github·uwsgi
lsswear17 小时前
GIT 操作
git
勋勋勋勋小勋勋17 小时前
git分支合并某一次提交
git
PandaCave18 小时前
git常用命令以及注意事项总结
git
算你狠 - ZGX1 天前
Git使用
git