Git 版本管理

安装-Git版本管理


Git官网安装说明

Linux 系统安装

# 如果你的 Linux 是 Ubuntu:
$ sudo apt-get install git-all

# 如果你的 Linux 是 Fedora:
$ sudo yum install git-all
 
 
  • 1
  • 2
  • 3
  • 4

如果是其他linux版本,可以直接通过源码安装。先从git官网下载源码,然后解压,依次输入:./config,make,sudo make install这几个命令安装


第一个版本管理库-Git版本管理


创建版本库 (init)
添加文件管理 (add)
提交改变 (commit)
流程图

版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。

创建版本库

#确定版本管理的文件,比如说Github-files
mkdir Github-files
cd ~/Github-files
 
 
  • 1
  • 2
#记录每一个施加修改的人. 这样人和修改能够对应上. 所以我们在 git 中添加用户名 user.name 和 用户 email user.email

git config --global user.name "bids"
git config --global user.email "bids@siat.ac.cn"
 
 
  • 1
  • 2
  • 3
#查看是否完成添加user.name 和 user.email
git config user.name
git confit user.email
 
 
  • 1
  • 2
bids
bids@siat.ac.cn
 
 
  • 1
#建立 git 的管理文件
cd ~/Github-files
git init
 
 
  • 1
  • 2
  • 3

瞬间Git就把仓库建好了,而且告诉你是一个空的仓库(empty Git repository),细心的读者可以发现当前目录下多了一个.git的目录,这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。

如果你没有看到.git目录,那是因为这个目录默认是隐藏的,用ls -ah命令就可以看见。

添加文件管理 (add)

cd ~/Github-files
ls -a
#. .. .git
 
 
  • 1
  • 2
  • 3
#建立一个新的1.py
touch 1.py
 
 
  • 1
  • 2
#用 status 来查看版本库的状态
git status

# 输出
On branch master    # 在 master 分支

Initial commit

Untracked files:    
  (use "git add <file>..." to include in what will be committed)

    1.py        # 1.py 文件没有被加入版本库 (unstaged)

nothing added to commit but untracked files present (use "git add" to track)
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

现在 1.py 并没有被放入版本库中 (unstaged), 所以我们要使用 add 把它添加进版本库 (staged):

$ git add 1.py

# 再次查看状态 status
$ git status

# 输出
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

    new file:   1.py    # 版本库已识别 1.py (staged)

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

如果想一次性添加文件夹中所有未被添加的文件, 可以使用这个:

$ git add .
 
 
  • 1

提交改变 (commit)

我们已经添加好了 1.py 文件, 最后一步就是提交这次的改变, 并在 -m 自定义这次改变的信息

$ git commit -m "create 1.py"

# 输出
[master (root-commit) 6bd231e] create 1.py
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 1.py
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

git commit命令,-m后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。

为什么Git添加文件需要add,commit一共两步呢?因为commit可以一次提交很多文件,所以你可以多次add不同的文件

$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."

 
 
  • 1
  • 2
  • 3
  • 4

流程图

整个上述过程可以被这张 git 官网上的流程图直观地表现:

这里写图片描述

小结

初始化一个Git仓库,使用git init命令。
添加文件到Git仓库,分两步:
第一步,使用命令git add ,注意,可反复多次使用,添加多个文件;
第二步,使用命令git commit,完成。


记录修改(log & diff)-Git版本管理


修改记录 log
查看修改部分 diff
    查看 unstaged
    查看 staged (–cached)
    查看 staged & unstaged (HEAD)

修改记录 log

cd ~/Github-files
#查看修改记录,git log命令显示从最近到最远的提交日志
git log
 
 
  • 1
  • 2
  • 3
sudo gedit 1.py
#添加 
a=1
 
 
  • 1
  • 2
  • 3
#git status命令可以让我们时刻掌握仓库当前的状态
git status

# 输出
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   1.py    # 这里显示有一个修改还没被提交

no changes added to commit (use "git add" and/or "git commit -a")
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

所以我们先把这次修改添加 (add) 到可被提交 (commit) 的状态, 然后再提交 (commit) 这次的修改:

$ git add 1.py
$ git commit -m "change 1"

# 输出
[master fb51216] change 1
 1 file changed, 1 insertion(+) # 提示文件有一处添加
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

再次查看 log, 现在我们就能看到 create 1.py 和 change 1 这两条修改信息了. 而且做出这两条 commit 的 ID, 修改的 Author, 修改 Date 也被显示在上面.

$ git log

# 输出
commit fb51216b081e00db3996e14edf8ff080fab1980a
Author: bids <bids@siat.ac.cn>
Date:   Tue Nov 29 00:24:50 2017 +1100

    change 1

commit 13be9a7bf70c040544c6242a494206f240aac03c
Author: Morvan Zhou <mz@email.com>
Date:   Tue Nov 29 00:06:47 2016 +1100

    create 1.py
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

查看修改部分 diff

能看看具体修改了什么内容

git diff顾名思义就是查看difference,显示的格式正是Unix通用的diff格式.

如果删除一部分代码, 也会被记录上, 比如把 a = 1 改成 a = 2, 再添加一个 b = 1

a=2
b=1
 
 
  • 1

查看 unstaged

查看这次还没 add (unstaged) 的修改部分

$ git diff

# 输出
diff --git a/1.py b/1.py
index 1337a53..ff7c36c 100644
--- a/1.py
+++ b/1.py
@@ -1 +1,2 @@
-a = 1  # 删除了 a = 1
+a = 2  # 添加了 a = 2
+b = 1  # 添加了 b = 1
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

查看 staged --cached

已经 add 了这次修改, 文件变成了 “可提交状态” (staged), 我们可以在 diff 中添加参数 –cached 来查看修改:

$ git add .   # add 全部修改文件
$ git diff --cached

# 输出
diff --git a/1.py b/1.py
index 1337a53..ff7c36c 100644
--- a/1.py
+++ b/1.py
@@ -1 +1,2 @@
-a = 1
+a = 2
+b = 1
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

查看 staged & unstaged HEAD

还有种方法让我们可以查看 add 过 (staged) 和 没 add (unstaged) 的修改, 比如我们再修改一下 1.py 但不 add

a = 2
b = 1
c = b
 
 
  • 1
  • 2
  • 3

目前 a = 2 和 b = 1 已被 add, c = b 是新的修改, 还没被 add

# 对比三种不同 diff 形式
$ git diff HEAD     # staged & unstaged

@@ -1 +1,3 @@
-a = 1  # 已 staged
+a = 2  # 已 staged
+b = 1  # 已 staged
+c = b  # 还没 add 去 stage (unstaged)
-----------------------
$ git diff          # unstaged

@@ -1,2 +1,3 @@
 a = 2  # 注: 前面没有 +
 b = 1  # 注: 前面没有 +
+c = b  # 还没 add 去 stage (unstaged)
-----------------------
$ git diff --cached # staged

@@ -1 +1,2 @@
-a = 1  # 已 staged
+a = 2  # 已 staged
+b = 1  # 已 staged
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

保持这次修改, 全部 add 变成 staged 状态, 并 commit

$ git add .
$ git commit -m "change 2"

# 输出
[master 6cc6579] change 2
 1 file changed, 3 insertions(+), 1 deletion(-)

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

小结

要随时掌握工作区的状态,使用git status命令。
如果git status告诉你有文件被修改过,用git diff可以查看修改内容


回到从前 (reset)-Git版本管理


修改已 commit 的版本
回到过去 reset
    回到 add 之前
    回到 commit 之前

比如已经提交了 commit 却发现在这个 commit 中忘了附上另一个文件.

修改已 commit 的版本

最后一个 commit 是 change 2, 我们将要添加另外一个文件, 将这个修改也 commit 进 change 2. 所以我们复制 1.py 这个文件, 改名为 2.py. 并把 2.py 变成 staged, 然后使用 –amend 将这次改变合并到之前的 change 2 中.

$ git add 2.py
$ git commit --amend --no-edit   # "--no-edit": 不编辑, 直接合并到上一个 commit
$ git log --oneline    # "--oneline": 每个 commit 内容显示在一行

# 输出
904e1ba change 2    # 合并过的 change 2
c6762a1 change 1
13be9a7 create 1.py
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

回到过去 reset

回到 add 之前

$ git add 1.py
$ git status -s # "-s": status 的缩写模式
# 输出
M  1.py     # staged
-----------------------
$ git reset 1.py
# 输出
Unstaged changes after reset:
M   1.py
-----------------------
$ git status -s
# 输出
 M 1.py     # unstaged
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

回到 commit 之前

# 不管我们之前有没有做了一些 add 工作, 这一步让我们回到上一次的 commit
$ git reset --hard HEAD    
# 输出
HEAD is now at 904e1ba change 2
-----------------------
# 看看所有的log
$ git log --oneline
# 输出
904e1ba change 2
c6762a1 change 1
13be9a7 create 1.py
-----------------------
# 回到 c6762a1 change 1
# 方式1: "HEAD^"
$ git reset --hard HEAD^  

# 方式2: "commit id"
$ git reset --hard c6762a1
-----------------------
# 看看现在的 log
$ git log --oneline
# 输出
c6762a1 change 1
13be9a7 create 1.py
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

怎么 change 2 消失了!!! 还有办法挽救消失的 change 2 吗? 我们可以查看 $ git reflog 里面最近做的所有 HEAD 的改动, 并选择想要挽救的 commit id:

$ git reflog   #记录的是所有的log
# 输出
c6762a1 HEAD@{0}: reset: moving to c6762a1
904e1ba HEAD@{1}: commit (amend): change 2
0107760 HEAD@{2}: commit: change 2
c6762a1 HEAD@{3}: commit: change 1
13be9a7 HEAD@{4}: commit (initial): create 1.py
 
 
  • 1
  • 2
  • 3
  • 4

重复 reset 步骤就能回到 commit (amend): change 2 (id=904e1ba)这一步了:

$ git reset --hard 904e1ba
$ git log --oneline
# 输出
904e1ba change 2
c6762a1 change 1
13be9a7 create 1.py
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

小结

HEAD指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令git reset –hard commit_id。
穿梭前,用git log可以查看提交历史,以便确定要回退到哪个版本。
要重返未来,用git reflog查看命令历史,以便确定要回到未来的哪个版本。


回到从前 (checkout 针对单个文件)-Git版本管理


之前我们使用 reset 的时候是针对整个版本库, 回到版本库的某个过去. 不过如果我们只想回到某个文件的过去, 又该怎么办呢?

改写文件 checkout

我们仅仅要对 1.py 进行回到过去操作, 回到 c6762a1 change 1 这一个 commit. 使用 checkout + id c6762a1 + – + 文件目录 1.py, 我们就能将 1.py 的指针 HEAD 放在这个时刻 c6762a1:

$ git log --oneline
# 输出
904e1ba change 2
c6762a1 change 1
13be9a7 create 1.py
---------------------
$ git checkout c6762a1 -- 1.py
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这时 1.py 文件的内容就变成了:

a = 1
 
 
  • 1
  • 1

我们在 1.py 加上一行内容 # I went back to change 1 然后 add 并 commit 1.py:

$ git add 1.py
$ git commit -m "back to change 1 and add comment for 1.py"
$ git log --oneline

# 输出
47f167e back to change 1 and add comment for 1.py
904e1ba change 2
c6762a1 change 1
13be9a7 create 1.py
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可以看出, 不像 reset 时那样, 我们的 change 2 并没有消失, 但是 1.py 却已经回去了过去, 并改写了未来.

git checkout – file命令中的–很重要,没有–,就变成了“切换到另一个分支”的命令

删除文件

一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用rm命令删了

rm test.txt
 
 
  • 1
  • 1

这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status命令会立刻告诉你哪些文件被删除了:

$ git status
# On branch master
# Changes not staged for commit:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       deleted:    test.txt
#
no changes added to commit (use "git add" and/or "git commit -a")

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令git rm删掉,并且git commit

$ git rm test.txt
rm 'test.txt'
$ git commit -m "remove test.txt"
[master d17efd8] remove test.txt
 1 file changed, 1 deletion(-)
 delete mode 100644 test.txt
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

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

另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:

$ git checkout -- test.txt
 
 
  • 1

git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。

小结

命令git rm用于删除一个文件。如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。


分支(branch)-Git版本管理


分支 图例
建立 dev 分支
    使用 branch 创建
    使用 checkout 创建
在 dev 分支中修改
将 dev 的修改推送到 master

分支 图例

之前我们说编辑的所有改变都是在一条主分支 master 上进行的. 通常我们会把 master 当作最终的版本, 而开发新版本或者新属性的时候, 在另外一个分支上进行, 这样就能使开发和使用互不干扰了

这里写图片描述

我们之前的文件当中, 仅仅只有一条 master 分支, 我们可以通过 –graph 来观看分支

$ git log --oneline --graph #命令可以看到分支合并图
# 输出
* 47f167e back to change 1 and add comment for 1.py
* 904e1ba change 2
* c6762a1 change 1
* 13be9a7 create 1.py
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

建立 dev 分支

使用 branch 创建

$ git branch dev    # 建立 dev 分支
$ git branch        # 查看当前分支

# 输出
  dev       
* master    # * 代表了当前的 HEAD 所在的分支
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

当我们想把 HEAD 切换去 dev 分支的时候, 我们可以用到上次说的 checkout

$ git checkout dev   #切换分支

# 输出
Switched to branch 'dev'
--------------------------
$ git branch

# 输出
* dev       # 这时 HEAD 已经被切换至 dev 分支
  master
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

使用 checkout 创建并且切换分支

使用 checkout -b + 分支名, 就能直接创建和切换到新建的分支

$ git checkout -b  dev

# 输出
Switched to a new branch 'dev'
--------------------------
$ git branch

# 输出
* dev       # 这时 HEAD 已经被切换至 dev 分支
  master
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:

$ git branch dev
$ git checkout dev
Switched to branch 'dev'
 
 
  • 1
  • 2

查看所以的分支

git branch -a
 
 
  • 1

在 dev 分支中修改

dev 分支中的 1.py 和 2.py 和 master 中的文件是一模一样的. 因为当前的指针 HEAD 在 dev 分支上, 所以现在对文件夹中的文件进行修改将不会影响到 master 分支.

我们在 1.py 上加入这一行 # I was changed in dev branch, 然后再 commit:

$ git commit -am "change 3 in dev"  # "-am": add 所有改变 并直接 commit
 
 
git log --oneline
git checkout master
git log --oneline
 
 
  • 1
  • 2
  • 3

master 分支没有受到影响。

将 dev 的修改推送到 master

好了, 我们的开发板 dev 已经更新好了, 我们要将 dev 中的修改推送到 master 中, 大家就能使用到正式版中的新功能了.

首先我们要切换到 master, 再将 dev 推送过来.

$ git checkout master   # 切换至 master 才能把其他分支合并过来

$ git merge dev         # 将 dev merge 到 master 中
$ git log --oneline --graph

# 输出
* f9584f8 change 3 in dev
* 47f167e back to change 1 and add comment for 1.py
* 904e1ba change 2
* c6762a1 change 1
* 13be9a7 create 1.py
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
git log --oneline --graph
 
 
  • 1

要注意的是, 如果直接 git merge dev, git 会采用默认的 Fast forward 格式进行 merge, 这样 merge 的这次操作不会有 commit 信息. log 中也不会有分支的图案. 我们可以采取 –no-ff 这种方式保留 merge 的 commit 信息.

$ git merge --no-ff -m "keep merge info" dev         # 保留 merge 信息
$ git log --oneline --graph

# 输出
*   c60668f keep merge info
|\  
| * f9584f8 change 3 in dev         # 这里就能看出, 我们建立过一个分支
|/  
* 47f167e back to change 1 and add comment for 1.py
* 904e1ba change 2
* c6762a1 change 1
* 13be9a7 create 1.py
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

删除分支

合并完成后,就可以放心地删除dev分支了:

$ git branch -d dev
Deleted branch dev (was fec145a).
 
 
  • 1

删除后,查看branch,就只剩下master分支了:

$ git branch
* master
 
 
  • 1
  • 2

小结

Git鼓励大量使用分支:

查看分支:git branch
创建分支:git branch
切换分支:git checkout
创建+切换分支:git checkout -b
合并某分支到当前分支:git merge
删除分支:git branch -d
如果要丢弃一个没有被合并过的分支,可以通过git branch -D 强行删除


merge 分支冲突-Git版本管理


今天的情况是这样, 想象不仅有人在做开发版 dev 的更新, 还有人在修改 master 中的一些 bug. 当我们再 merge dev 的时候, 冲突就来了. 因为 git 不知道应该怎么处理 merge 时, 在 master 和 dev 的不同修改.

当创建了一个分支后, 我们同时对两个分支都进行了修改.

比如在:

master 中的 1.py 加上 # edited in master.
dev 中的 1.py 加上 # edited in dev.

在下面可以看出在 master 和 dev 中不同的 commit:

git checkout master
git log --oneline
# 这是 master 的 log
* 3d7796e change 4 in master # 这一条 commit 和 dev 的不一样
* 47f167e back to change 1 and add comment for 1.py
* 904e1ba change 2
* c6762a1 change 1
* 13be9a7 create 1.py
-----------------------------
git checkout dev
git log --oneline
# 这是 dev 的 log
* f7d2e3a change 3 in dev   # 这一条 commit 和 master 的不一样
* 47f167e back to change 1 and add comment for 1.py
* 904e1ba change 2
* c6762a1 change 1
* 13be9a7 create 1.py
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

当我们想要 merge dev 到 master 的时候:

$ git branch
  dev
* master
-------------------------
$ git merge dev

# 输出
Auto-merging 1.py
CONFLICT (content): Merge conflict in 1.py
Automatic merge failed; fix conflicts and then commit the result.
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

git 发现的我们的 1.py 在 master 和 dev 上的版本是不同的, 所以提示 merge 有冲突. 具体的冲突, git 已经帮我们标记出来, 我们打开 1.py 就能看到:

a = 1
# I went back to change 1
<<<<<<< HEAD
# edited in master
=======
# edited in dev
>>>>>>> dev
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

所以我们只要在 1.py 中手动合并一下两者的不同就 OK 啦. 我们将当前 HEAD (也就是master) 中的描述 和 dev 中的描述合并一下.

a = 1
# I went back to change 1

# edited in master and dev

 
 
  • 1
  • 2
  • 3
  • 4

然后再 commit 现在的文件, 冲突就解决啦.

$ git commit -am "solve conflict"

再来看看 master 的 log:

$ git log --oneline --graph

# 输出
*   7810065 solve conflict
|\  
| * f7d2e3a change 3 in dev
* | 3d7796e change 4 in master
|/  
* 47f167e back to change 1 and add comment for 1.py
* 904e1ba change 2
* c6762a1 change 1
* 13be9a7 create 1.py
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

rebase 分支冲突-Git版本管理


什么是 rebase

一个更高级的合并方式 rebase. 同样是合并 rebase 的做法和 merge 不一样.

假设共享的 branch 是 branch B, 而我在 branch A 上工作, 有一天我发现branch B已经有一些小更新, 我也想试试我的程序和这些小更新兼不兼容, 我也我想合并, 这时就可以用 rebase 来补充我的分支branch B的内容. 补充完以后, 和后面那张图的 merge 不同, 我还是继续在 C3 上工作, 不过此时的 C3 的本质却不一样了, 因为吸收了那些小更新. 所以我们用 C3’ 来代替.

1

这里写图片描述

这里写图片描述

这里写图片描述

可以看出 rebase 改变了 C3 的属性, C3 已经不是从 C1 衍生而来的了.

所以需要强调的是 !!! 只能在你自己的分支中使用 rebase, 和别人共享的部分是不能用 !!!. 如果你不小心弄错了. 没事, 我们还能用在 reset 这一节 提到的 reflog 恢复原来的样子.

使用 rebase

master 和 dev 分支中都有自己的独立修改.

git checkout master
git log --oneline
# 这是 master 的 log
* 3d7796e change 4 in master # 这一条 commit 和 dev 的不一样
* 47f167e back to change 1 and add comment for 1.py
* 904e1ba change 2
* c6762a1 change 1
* 13be9a7 create 1.py
-----------------------------
git checkout dev 
git log --oneline
# 这是 dev 的 log
* f7d2e3a change 3 in dev   # 这一条 commit 和 master 的不一样
* 47f167e back to change 1 and add comment for 1.py
* 904e1ba change 2
* c6762a1 change 1
* 13be9a7 create 1.py

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

当我们想要用 rebase 合并 dev 到 master 的时候:

$ git branch

# 输出
  dev
* master
-------------------------
$ git rebase dev 

# 输出
First, rewinding head to replay your work on top of it...
Applying: change 3 in dev
Using index info to reconstruct a base tree...
M   1.py
Falling back to patching base and 3-way merge...
Auto-merging 1.py
CONFLICT (content): Merge conflict in 1.py
error: Failed to merge in the changes.
Patch failed at 0001 change 3 in dev
The copy of the patch that failed is found in: .git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

git 发现的我们的 1.py 在 master 和 dev 上的版本是不同的, 所以提示 merge 有冲突. 具体的冲突, git 已经帮我们标记出来, 我们打开 1.py 就能看到:

a = 1
# I went back to change 1
<<<<<<< f7d2e3a047be4624e83c1265a0946e2e8790f79c
# edited in dev
=======
# edited in master
>>>>>>> change 4 in master
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这时 HEAD 并没有指向 master 或者 dev, 而是停在了 rebase 模式上:

$ git branch
* (no branch, rebasing master) # HEAD 在这
  dev
  master
 
 
  • 1
  • 2
  • 3

所以我们打开 1.py, 手动合并一下两者的不同.

a = 1
# I went back to change 1

# edited in master and dev
 
 
  • 1
  • 2
  • 3

然后执行 git add 和 git rebase –continue 就完成了 rebase 的操作了.

$ git add 1.py
$ git rebase --continue
 
 
  • 1
  • 2

再来看看 master 的 log:

$ git log --oneline --graph

# 输出
* c844cb1 change 4 in master    # 这条 commit 原本的id=3d7796e, 所以 master 的历史被修改
* f7d2e3a change 3 in dev       # rebase 过来的 dev commit
* 47f167e back to change 1 and add comment for 1.py
* 904e1ba change 2
* c6762a1 change 1
* 13be9a7 create 1.py
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

临时修改 (stash)-Git版本管理


暂存修改
做其它任务
恢复暂存

想想有天在开开心心地改进代码, 突然接到老板的一个电话说要改之前的一个程序. 怎么办? 虽然还需要很久时间才能改进完自己的代码, 可我有强迫症, 又不想把要改的程序和自己改进代码的部分一起 commit 了.

这时 stash 就是我的救星了. 用 stash 能先将我的那改进的部分放在一边分隔开来. 再另外单独处理老板的任务.

暂存修改

假设我们现在在 dev 分支上快乐地改代码:

$ git checkout dev
 
 
  • 1

在 dev 中的 1.py 中加上一行 # feel happy, 然后老板的电话来了, 可是我还没有改进完这些代码. 所以我就用 stash 将这些改变暂时放一边.

$ git status -s
# 输出
 M 1.py
------------------ 
$ git stash
# 输出
Saved working directory and index state WIP on dev: f7d2e3a change 3 in dev
HEAD is now at f7d2e3a change 3 in dev
-------------------
$ git status
# 输出
On branch dev
nothing to commit, working directory clean  # 干净得很
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

做其它任务

然后我们建立另一个 branch 用来完成老板的任务:

$ git checkout -b boss

# 输出
Switched to a new branch 'boss' # 创建并切换到 boss
 
 
  • 1
  • 2
  • 3

然后苦逼地完成着老板的任务, 比如添加 # lovely boss 去 1.py. 然后 commit, 完成老板的任务.

$ git commit -am "job from boss"
$ git checkout master
$ git merge --no-ff -m "merged boss job" boss
 
 
  • 1
  • 2
  • 3

merge 如果有冲突的话, 可以像上次那样 解决.

a = 1
# I went back to change 1
<<<<<<< HEAD

# edited in master and dev

=======
# edited in dev

# lovely boss
>>>>>>> boss
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

通过以下步骤来完成老板的任务, 并观看一下 master 的 log:

$ git commit -am "solve conflict"
$ git log --oneline --graph
*   1536bea solve conflict
|\  
| * 27ba884 job from boss
* | 2d1961f change 4 in master
|/  
* f7d2e3a change 3 in dev
* 47f167e back to change 1 and add comment for 1.py
* 904e1ba change 2
* c6762a1 change 1
* 13be9a7 create 1.py
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

恢复暂存

轻松了, 现在可以继续开心的在 dev 上刷代码了.

$ git checkout dev
$ git stash list    # 查看在 stash 中的缓存

# 输出
stash@{0}: WIP on dev: f7d2e3a change 3 in dev
 
 
  • 1
  • 2
  • 3
  • 4

上面说明在 dev 中, 我们的确有 stash 的工作. 现在可以通过 pop 来提取这个并继续工作了.

$ git stash pop

# 输出
On branch dev
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   1.py

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (23332b7edc105a579b09b127336240a45756a91c)
----------------------
$ git status -s
# 输出
 M 1.py     # 和最开始一样了
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

小结

修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;
当手头工作没有完成时,先把工作现场git stash一下,然后去修复bug,修复后,再git stash pop,回到工作现场。


多人协作-Git版本管理


master分支是主分支,因此要时刻与远程同步;
dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。

多人协作的工作模式通常是这样:

首先,可以试图用git push origin branch-name推送自己的修改;
如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
如果合并有冲突,则解决冲突,并在本地提交;
没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!

如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch –set-upstream branch-name origin/branch-name。

这就是多人协作的工作模式,一旦熟悉了,就非常简单。

小结

查看远程库信息,使用git remote -v;
本地新建的分支如果不推送到远程,对其他人就是不可见的;
从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull抓取远程的新提交;
在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name,本地和远程分支的名称最好一致;
建立本地分支和远程分支的关联,使用git branch --set-upstream branch-name origin/branch-name;
从远程抓取分支,使用git pull,如果有冲突,要先处理冲突。

标签管理-Git版本管理


Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针。

Git有commit,为什么还要引入tag?
“请把上周一的那个版本打包发布,commit号是6a5819e…”
“一串乱七八糟的数字不好找!”
如果换一个办法:
“请把上周一的那个版本打包发布,版本号是v1.2”
“好的,按照tag v1.2查找commit就行!”
tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。

在Git中打标签非常简单,首先,切换到需要打标签的分支上:

$ git branch
* dev
  master
$ git checkout master
Switched to branch 'master'
 
 
  • 1
  • 2
  • 3
  • 4

然后,敲命令git tag 就可以打一个新标签:

$ git tag v1.0
 
 
  • 1

默认标签是打在最新提交的commit上的。有时候,如果忘了打标签,比如,现在已经是周五了,但应该在周一打的标签没有打,怎么办?

方法是找到历史提交的commit id,然后打上就可以了:

$ git log --pretty=oneline --abbrev-commit
6a5819e merged bug fix 101
cc17032 fix bug 101
7825a50 merge with no-ff
6224937 add merge
59bc1cb conflict fixed
400b400 & simple
75a857c AND simple
fec145a branch test
d17efd8 remove test.txt
...
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

比方说要对add merge这次提交打标签,它对应的commit id是6224937,敲入命令:

$ git tag v0.9 6224937

 
 
  • 1
  • 2

再用命令git tag查看标签:

$ git tag
v0.9
v1.0
 
 
  • 1
  • 2
  • 3

小结

命令git tag <name>用于新建一个标签,默认为HEAD,也可以指定一个commit id;
git tag -a <tagname> -m "blablabla..."可以指定标签信息;
git tag -s <tagname> -m "blablabla..."可以用PGP签名标签;
命令git tag可以查看所有标签。

操作标签

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

$ git tag -d v0.1
Deleted tag 'v0.1' (was e078af9)
 
 
  • 1

因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。

如果要推送某个标签到远程,使用命令git push origin :

$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:michaelliao/learngit.git
 * [new tag]         v1.0 -> v1.0
 
 
  • 1
  • 2
  • 3

或者,一次性推送全部尚未推送到远程的本地标签:

$ git push origin --tags
Counting objects: 1, done.
Writing objects: 100% (1/1), 554 bytes, done.
Total 1 (delta 0), reused 0 (delta 0)
To git@github.com:michaelliao/learngit.git
 * [new tag]         v0.2 -> v0.2
 * [new tag]         v0.9 -> v0.9
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

$ git tag -d v0.9
Deleted tag 'v0.9' (was 6224937)
 
 
  • 1

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

$ git push origin :refs/tags/v0.9
To git@github.com:michaelliao/learngit.git
 - [deleted]         v0.9
 
 
  • 1
  • 2

Github-Git版本管理


建立 github 版本库
连接本地版本库
推送修改

建立 github 版本库

Create a new online repository

这里写图片描述

添加好了以后, 会出现下面的介绍, 你可以选择push an existing repository 的代码链接上你的本地版本库.

这里写图片描述

连接本地版本库

$ cd ~/Github-files
$ git remote add origin https://github.com/MorvanZhou/git-demo.git
$ git push -u origin master     # 推送本地 master 去 origin
$ git push -u origin dev        # 推送本地 dev  去 origin

 
 
  • 1
  • 2
  • 3
  • 4

推送修改

如果在本地再进行修改, 比如在 1.py 文件中加上 # happy github, 然后 commit 并推上去:

$ git commit -am "change 5"
$ git push -u origin master
 
 
  • 1
  • 2

参考-Git版本管理


Git版本管理
Git 版本管理视频
Git教程-廖雪峰
Github 的项目怎么引用另一个项目?
submodule 中文文档
submodule 实例教材
subtree用法参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值