Git

1. 什么是 Git

Git 是一个分布式版本控制系统,由 Linus Torvalds (也是 Linux 系统的创建者)编写。最主要的两个特点就是:

  • 版本管理:对每一次版本进行管理,可以对产品版本进行任意回滚,即每修改一次就保存一次,以防止丢失工作进度。
  • 协作开发:实际开发中一般都是多人协同开发,每个人负责不同的方面,为确保整体工作进度,这就需要一个工具来保证代码时刻是最新的,Git 就是这样的一个工具。

常用版本管理工具

  • VSS
  • CVS
  • SVN
  • GIT
  • BitKeeper

Tips:

所有版本控制系统都只能控制跟踪文本文件的改动,如:TXT 文件、代码程序、网页代码等,对于二进制文件只能知道其修改了,但是不能知道具体修改了什么。

另外最好使用 utf-8 通用编码格式。

2. Git 基本操作

2.1 安装

Linux 上安装 Git

sudo apt-get install git

Windows 上直接下载安装即可,记得添加系统环境变量。

2.2 创建版本库

所谓版本库即仓房文件/代码的仓库 repository ,可以理解为一个目录。

首页我们要选择一个合适的地方,创建一个空目录,切换到目录中,然后初始化 Git,这样就创建好了一个版本库。

$ cd E:
$ mkdir git_test
$ git init    # 初始化 Git

创建成功后,会看到多出了一个 .git 的目录,这是用于跟踪管理版本库,切勿删除。Linux 中看不到可以使用 ls -ah 查看(隐藏文件)

将文件添加到版本库

只有将文件存放在版本库中,Git 才能管理控制。

  1. 使用 git add 命令将文件添加到暂存区
$ $ vim test.txt
$ git add test.txt        # git add . 添加多个文件

# 添加一句:第一行代码,第一次修改

warning: LF will be replaced by CRLF in test.txt.
The file will have its original line endings in your working directory
  1. add 命令只能将文件添加到暂存区,并不是真正放到仓库中,还需使用 git commit 命令才能将文件真真添加到仓库中
# m 后面跟修改备注,以便后续查询
$ git commit -m '第一次修改'

[master d25c1c9] 第一次修改
 1 file changed, 1 insertion(+)
 create mode 100644 test.txt

第一次提交可能会需要输入邮箱和用户名(任意),这只是 Git 为了方便区分是谁提交或修改的文件。

2.3 版本回滚

现在我们来修改 test.txt,再添加一行:

$ vim test.txt

# 在第二行添加:第二行代码,第二次修改

提交之前可以用 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:   test.txt

no changes added to commit (use "git add" and/or "git commit -a")

可以看到 git 提示我们修改了 test.txt 文件,并且贴心地告诉我们使用 git add filegit commit 去提交。

提交后我们再使用 git status 查看会发现工作目录是干净的:

$ git status

On branch master
nothing to commit, working tree clean

版本控制

实际开发中,我们可能每天都要提交最新的代码,长久以往我们不断修改提交、提交修改。当有一天不小心把文件/代码改乱了,想回到之前某个的版本,那么该怎么办?放心,Git 早已帮你记录好每次提交的版本,你只需查看版本号就可回到相应到版本。这样也就不怕代码会丢失。

$ git log

commit 8e882ba384400d48d76345e3b3d4d7a30fabda49 (HEAD -> master)
Author: Hubery_Jun <9825xxx16@qq.com>
Date:   Fri May 3 22:55:30 2019 +0800

    第二次修改

commit d25c1c9b41eba07b4a50fdbc77e15f6313d003d7
Author: Hubery_Jun <9825xxx16@qq.com>
Date:   Fri May 3 22:44:27 2019 +0800

    第一次修改

commit a069466807ba3f87a0d68290614a2498770c2675
Author: Hubery_Jun <9825xxx16@qq.com>
Date:   Wed May 1 16:51:42 2019 +0800

# git diff 命令查看修改了什么内容

使用 git log 可以查看每次提交时的记录,版本号(最长的那串 md5 值),以及修改者与时间。

如果只想查看版本号和提交时的备注,可以用:

$ git log --pretty=oneline

8e882ba384400d48d76345e3b3d4d7a30fabda49 (HEAD -> master) 第二次修改
d25c1c9b41eba07b4a50fdbc77e15f6313d003d7 第一次修改

版本回退

# 回到上一个版本
$ git reset --hard HEAD^

# 回到指定版本,后面跟版本后
$ git reset --hard 8e882ba384400d48d76345e3b3d4d7a30fabda49

# 实际只需要前 6 位即可
$ git reset --hard 8e882b

现在我们将 text.text 回滚到上一个版本:

$ git reset --hard d25c1c

HEAD is now at d25c1c9 第一次修改

$ cat test.txt
第一行代码,第一次修改

再查看提交记录,发现只有第一次提交记录,我们已经成功回滚到了第一个版本:

$ git log

commit d25c1c9b41eba07b4a50fdbc77e15f6313d003d7 (HEAD -> master)
Author: Hubery_Jun <982562616@qq.com>
Date:   Fri May 3 22:44:27 2019 +0800

    第一次修改

等等,要是手贱回滚错误,后悔了又想回滚回去怎么办?如果没有关闭 Git Bash,还可以往上翻查看上一次的提交版本号,但要是不小心把 Git Bash 关闭掉了怎么办?

不用担心,Git 总是那么贴心,我们只需使用 git reflog 查看所有的历史提交记录,即使已经回滚过的版本,并且还提示你上一次回滚的版本:

$ git reflog

d25c1c9 (HEAD -> master) HEAD@{0}: reset: moving to d25c1c
8e882ba HEAD@{1}: commit: 第二次修改
d25c1c9 (HEAD -> master) HEAD@{2}: commit: 第一次修改

3. 工作区、暂存区和版本库

前面我们有提到过暂存区和版本库,然而没有实际解释过两者到底是什么。

  • 工作区:存放文件的目录,即起初我们创建的 git_test 目录,所有的要修改的文件以及版本库到在这里面。
  • 版本库:工作区中有一个隐藏目录 .git,它就是版本库,用来进行版本控制,里面包含暂存区和分支 branch 等。

多说无益,还是上图吧(stage:暂存区,master:默认的主分支):

前面我们说把一个文件提交到版本库分为两步(add、commit),现在从上图可以清晰地看出:

  • 第一步使用 git add file 只是将文件添加到暂存区 stage 中,并没有真正提交到版本库中
  • 第二步 git commit,才是真正的将文件提交到版本库中(实际是提交到 master 分支上,如果没有创建别的分支的话)

4. 撤销修改

实际开发中需要不断地修改提交,难免会出现错误,同样地 Git 也提供了后悔药,而且有两次:

  • 未添加到暂存去之前,即还在工作区
  • 已经添加到暂存区,但未提交到分支上

4.1 工作区撤销修改

首先我们修改 test.txt 文件,在最后添加一行:

$ vim test.txt

hj@DESKTOP-IHSAOT4 MINGW64 /e/git_test (master)
$ cat test.txt

第一行代码,第一次修改
第二行代码,第二次修改
撤销修改        # 新添加的

使用 git status 命令查看状态,发现 Git 提示可以使用 add 将文件修改添加到暂存区或使用 git checkout -- <file> 命令去撤销工作区的修改。

hj@DESKTOP-IHSAOT4 MINGW64 /e/git_test (master)
$ 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:   test.txt

no changes added to commit (use "git add" and/or "git commit -a")

这里我们选择撤销修改,再次查看状态发现工作区是干净的,修改也成功撤销:

hj@DESKTOP-IHSAOT4 MINGW64 /e/git_test (master)
$ git checkout -- test.txt

hj@DESKTOP-IHSAOT4 MINGW64 /e/git_test (master)
$ cat test.txt

第一行代码,第一次修改
第二行代码,第二次修改

4.2 暂存区撤销

要是万一不小心修改已经添加到暂存区了怎么办?不要方,Git 还有一招,当然也是最后一道防线了。

我们再修改 test.txt 文件,添加:

hj@DESKTOP-IHSAOT4 MINGW64 /e/git_test (master)
$ vim test.txt

hj@DESKTOP-IHSAOT4 MINGW64 /e/git_test (master)
$ cat test.txt
第一行代码,第一次修改
第二行代码,第二次修改
添加到暂存区,怎么撤销?    # 新添加

再查看其状态:

$ git status

On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   test.txt

Git 告诉我们可以使用 git reset HEAD <file> 回到不在暂存区的位置(unstage),即工作区的位置。

hj@DESKTOP-IHSAOT4 MINGW64 /e/git_test (master)
$ git reset HEAD test.txt

Unstaged changes after reset:
M       test.txt

# 再查看状态,发现和工作区撤销是一样的了

$ 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:   test.txt

no changes added to commit (use "git add" and/or "git commit -a")

hj@DESKTOP-IHSAOT4 MINGW64 /e/git_test (master)
$ git checkout -- test.txt

hj@DESKTOP-IHSAOT4 MINGW64 /e/git_test (master)
$ git status
On branch master
nothing to commit, working tree clean

4.3 总结

  • 撤销分为两种:工作区、暂存区撤销
  • 工作区撤销: git checkout -- 文件名
  • 暂存区撤销:git reset HEAD 文件名,只能撤销到工作区,还要再执行 git checkout -- 文件名 才能真正撤销

5. 删除操作

我们经常会删除一些没用的文件,或者一不小心删除错了某个文件,该怎么办?

  1. 首先我们新建一个 t2.txt 的新文件,在里面添加一行:
$ vim t2.txt
$ cat t2.txt
删除操作

# 然后再把 t2 添加提交到版本库中
$ git add .
warning: LF will be replaced by CRLF in t2.txt.
The file will have its original line endings in your working directory


$ git commit -m '新增一个 t2.txt 文件'
[master 4fbaaa8] 新增一个 t2.txt 文件
 1 file changed, 1 insertion(+)
 create mode 100644 t2.txt

  1. 现在我们使用 rm 命令来尝试删除 t2.txt
$ rm t2.txt

# 再查看它的状态
$ 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:    t2.txt

no changes added to commit (use "git add" and/or "git commit -a")

查看状态,发现 Git 提示我们有两个选择,要么就是继续使用 add/rm 命令再提交来完成删除操作,要么就是使用 git checkout -- file 来恢复删除,可见 Git 是多么的贴心。

  1. 这里我们选择继续删除:
$ git rm t2.txt
rm 't2.txt'

# 执行删除命令后,一定要再提交,否则无效
$ git commit -m '删除 t2.txt'
[master ccd5c05] 删除 t2.txt
 1 file changed, 1 deletion(-)
 delete mode 100644 t2.txt

# 再查看工作区,发现 t2.txt 已经被删除
$ ls
test.txt

总结

  • 使用 rm 命令删除的文件在未真正删除之前,也是可以还原的
  • git rm file 后一定要执行 commit 才能生效
  • git checkout 本质是将版本库中版本替换工作区版本,因此可以还原

6. 远程仓库 GitHub

Git 另一个功能就是 远程仓库,只要保证有一台机器有一个原始仓库,其他机器便可克隆这个原始版本库,理论上可以克隆无数份。

原始仓库所在机器与克隆的机器理论上可以同一台,但是若是这台机器挂了,那么两者都会挂掉,这样做好像没什么意义。实际开发中有两种方法:

  • 公司内部专门有一台机器用来充当原始仓库(服务器),其他人可以从上面克隆这个仓库
  • 还有一种就是免费好用的 GitHub ,它可以免费托管代码,但是它是公开的,当然你也可以花点钱把你的仓库变为私有。

6.1 将 Git 与 GitHub 建立通讯

GitHub 与 Git 之间是才用 SSH 加密通讯的,如果想克隆 GitHub 上面的代码,就需要每次都输入用户名和密码。当然还有一种方式可以不用输入密码,那就是 SSH Keys

SSH Keys

SSH Keys 就像一段暗号,通讯时只需要检查暗号是否正确即可,主要分为以下几步:

  1. 在目标机器创建 SSH Key,首先在用户主目录检查下是否有 .ssh 目录,再看看里面是否有 id_rsa 和 id_rsa.pub 两个文件,若有则忽略这一步:
# 打开 shell 或 Git Bash,输入以下命令,邮箱换成自己的,一路回车即可。成功后会提示 .ssh 目录的位置,Windows 一般在 /c/Users/用户名/.ssh

# id_rsa 是私匙切忌泄露,id_rsa.pub 是公匙,可以给别人看
$ ssh-keygent -t rsa -C 'youremail@example.com'

  1. 将公匙复制到 GitHub

可以建立多个 SSH key,家里和公司都可以提交。

6.2 创建远程仓库

打开 GitHub 首页,登录点击创建一个新的仓库:

创建成功后,就会出现一些界面,它会提示你怎样将本地仓库推送到远程仓库:

现在我们把本地 git_test 目录下的 test.txt 推到这个远程仓库中去:

$ git remote add origin git@github.com:hj1933/git_test.git

# 推送到远程仓库,origin 是远程仓库名,你也可以修改,推送成功后悔看到如下提示

$ git push -u origin master

The authenticity of host 'github.com (13.229.188.59)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'github.com,13.229.188.59' (RSA) to the list of known hosts.
Enumerating objects: 19, done.
Counting objects: 100% (19/19), done.
Delta compression using up to 4 threads
Compressing objects: 100% (13/13), done.
Writing objects: 100% (19/19), 1.58 KiB | 202.00 KiB/s, done.
Total 19 (delta 4), reused 0 (delta 0)
remote: Resolving deltas: 100% (4/4), done.
To github.com:hj1933/git_test.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

推送成功后,打开 GitHub,找到刚才新建的 git_test 仓库,刷新下是不是发现多了一个 test.txt 的新文件?就是这么简单快捷。

6.3 从远程仓库克隆到本地

打开远程仓库,点击右边 Clone or download,复制方框中的链接。然后再本地找一个适合的位置,打开 Git Bash,输入:

# 记得将链接替换成自己仓库的链接

git clone git@github.com:hj1933/git_test.git

Git 还提供了 Https 协议的方式,相对 SSH 来说一点慢,而且每次都需要输入口令。但是对于有些公司来说只开放 http 端口,而无法使用 SSH 协议。

克隆成功后,会发现本地目录中多了一个 git_test 的仓库,这就和我们自己用命令在本地创建仓库是一样的,至此我们也就可以和远程仓库互相通信了。

7. 分支管理

分支就像小说中的分身术,本尊与分身可以同时学习新的东西,分身学会了本尊亦懂。

分支管理是 Git 的一个非常重要的功能,虽然其他的版本控制系统(如:SVN)也有分支管理,但是创建、合并或删除分支时,速度特别慢,而 Git 无论哪个操作都可以在 1s 内完成,简直完虐其他软件。

分支在实际开发中的作用

实际开发一般都是多人协同开发,每个人负责不同的功能模块,假如都在一个分支上开发。那么后面的开发人员必须等前面的开发人员开发完毕才能工作,这就会导致浪费大量时间。

而有了分支后,每个人都可以创建自己的分支,然后在自己分支上修改提交都不会影响主分区,待开发完毕再合并到主分支即可,这样就能保证了整体的工作进度。

7.1 创建分支

Git 把每个版本串成一条线,就好比时间线一样(一直往前走),这条线就是一个分支,默认情况下只有一个 主分支 master

其中 HEAD 是指针,要回滚到哪个版本或切换到哪个分支,它就会指向谁。严格意义上来说 HEAD 不是指向提交,而是指向分支,分支才指向提交。

另外因为 HEAD 是 C 语言中的指针,每次切换到其他分支时也就会特别快,这也就是 Git 为什么会比其他版本控制系统快的原因。

每一次提交,master 就会往前走一步,不断提交 master 分支也会越来越长。当我们创建新的分支时,HEAD 就会指向新的分支 dev

dev 分支上的任务完成后,就可以与 master 主分支合并了,HEAD 也会重新指向 master,从而并不影响整体进度。


创建第一个分支

  1. 使用 git checkout -b 分支名 快速创建一个新的分支:
# 创建一个新的分支 dev,并切换到 dev
$ git checkout -b dev   # dev 为新的分支名
Switched to a new branch 'dev'

# 事实上上面这条命令是下面这两条命令的简写
$ git branch dev
$ git checkout dev

  1. 查看当前所在分支,并修改 test.txt
# 查看所有分支,带星号的为当前分支
$ git branch
* dev
  master

# 可以看出分支 dev 下也有个 test.txt,它从 master 主分支 copy 了一份
$ ls
test.txt

# 修改 test.txt,在最后添加一行
$ vim test.txt

$ cat test.txt

第一行代码,第一次修改
第二行代码,第二次修改
分支测试

  1. 添加提交到版本库后,重新切换回 master 主分支:
$ git add .

$ git commit -m '第一次分支测试'

[dev 4fa1810] 第一次分支测试
 1 file changed, 1 insertion(+)

# 切换到 master 分支
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

# 查看当前分支
$ git branch
  dev
* master

  1. dev 分支合并到 master 分支:
# 合并分支
$ git merge dev
Updating ccd5c05..4fa1810
Fast-forward
 test.txt | 1 +
 1 file changed, 1 insertion(+)

# 查看 test.txt,发现在 dev 的修改同步到了 master 分支
$ cat test.txt
第一行代码,第一次修改
第二行代码,第二次修改
分支测试

  1. 删除分支,合并之后如果没有其他用,就可以删除 dev 分支了:
# 删除 dev 分支
$ git branch -d dev
Deleted branch dev (was 4fa1810).


$ git branch
* master


总结

  • 创建并切换分支:git checkout -b 分支名
  • 查看所有分支:git branch,带 * 号的胃当前分支
  • 切换分支:git checkout 分支名
  • 合并分支:git merge 分支名
  • 删除分支:git branch -d 分支名
  • 合并到 master 分支时,一定要先切换到 master 分支

7.2 解决分支冲突

  1. 添加一个新的分支 feature1,并在最后一行添加:
$ git checkout -b feature1
Switched to a new branch 'feature1'


$ vim test.txt

$ cat test.txt
第一行代码,第一次修改
第二行代码,第二次修改
分支测试
添加一这行,用于新的分支 feature1     # 新添加的


$ git add .

# 提交到版本库
$ git commit -m '添加新分支 feature1'
[feature1 93426de] 添加新分支 feature1
 1 file changed, 1 insertion(+)

  1. 切换到 master 分支,并修改 test.txt
$ git checkout master

# Git 告诉我们当前 master 分支比远程的 master 分支提交一个分支
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)


# 打开 test.txt,在最后添加一行
$ vim test.txt


$ cat test.txt
第一行代码,第一次修改
第二行代码,第二次修改
分支测试
master 分支提交   # 新添加的

现在 masterfeature1 分支上都有提交,整体流程变成这样:

  1. 提交后,尝试合并:
$ git add .


$ git commit -m 'master 更新'
[master ae39a38] master 更新
 1 file changed, 1 insertion(+)

# 合并分支时发现有一个冲突:在 test.txt
$ git merge feature1
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt
Automatic merge failed; fix conflicts and then commit the result.

  1. git status 查看冲突的地方:
$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
  (use "git push" to publish your local commits)

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:   test.txt

no changes added to commit (use "git add" and/or "git commit -a")

Git 告诉我们当前 master 分支比远程 master 分支超出两个提交,冲突的文件为 test.txt,并告诉我们两个分支上都做了修改。

  1. 重新打开 test.txt 文件,找到冲突的地方:
$ cat test.txt
第一行代码,第一次修改
第二行代码,第二次修改
分支测试
<<<<<<< HEAD
master 分支提交
=======
添加一这行,用于新的分支 feature1
>>>>>>> feature1

可以看到我们在 master 和 feature1 分支上做的修改,我们删除冲突,再重新提交:

# 删除标记(提示内容),再重新提交
$ cat test.txt
第一行代码,第一次修改
第二行代码,第二次修改
分支测试
master 分支提交
添加一这行,用于新的分支 feature1


$ git add .

$ git commit -m '解决冲突'
[master 6fa9b73] 解决冲突

冲突解决后,分支也自然合并成功,再重新打开 test.txt,看看是否合并成功。上面步骤相当于将 feature1 与最新的 master 合并。

也可以使用 git log 查看合并情况:

$ git log --graph --pretty=oneline
*   6fa9b73c86d0182bc960c308e224566f0c07cdfe (HEAD -> master) 解决冲突
|
| * 93426de6f45737ce835fb85f912b1d50f1b4a137 (feature1) 添加新分支 feature1
* | ae39a38842ca7f216acffe0edaeadbdbae953873 master 更新
|/
* 4fa18106952d27cf15eeba018d739d37b3375345 第一次分支测试
* ccd5c0587a0cbf97755e70edcc7b0acc80d51aa2 (origin/master) 删除 t2.txt
* 4fbaaa813446fc4554c819e502aab904d8598a84 新增一个 t2.txt 文件
* 8e882ba384400d48d76345e3b3d4d7a30fabda49 第二次修改
* d25c1c9b41eba07b4a50fdbc77e15f6313d003d7 第一次修改

总结

  • 如果分支上有提交,master 分支上没有提交,那么合并时就不会有冲突
  • 若两者都有提交,合并时就会有冲突,可以使用 git status 查看冲突的文件,然后再修改解决冲突即可
  • 使用 git log --graph --pretty=oneline 命令可以查看分支合并情况

7.3 分支策略

实际开发中应该遵循基本原则进行分支管理:

  • master 分支是最稳定的分支,一般在上面开发,仅用于发布最新稳定版
  • 日常情况开发人员都会在 dev 分支上开发,在合适的时候再合并到 master 分支上去发布
  • 另外每个开发人员都会有自己的独立分支,如:roselila 等等,时不时往 dev 分支合并。

整体流程大致像如下所示:

7.4 bug 分支

所谓 bug 分支,一般是用于临时处理某个 bug 而创建的分支,bug 处理完毕后也会被删除。

假设当前正在 dev 分支上开发,目测还有一周时间才能完成。这时老板突然要你赶紧处理一个 bug(估计两小时就能完成),但是手头上的工作还没完成,如若提交就会影响其他人的进度,那么该怎么办呢?

不用方,Git 给我们提供了一个 隐藏功能,我们可以将当前工作临时隐藏起来,待处理完 bug 后再恢复即可。


下面我们来模拟一下:

  1. rose 正在 dev 分支上开发一个功能,刚写了 func() 函数:
$ cat test.txt
第一行代码,第一次修改
第二行代码,第二次修改
分支测试
master 分支提交
添加一这行,用于新的分支 feature1
def func():           #  这个函数刚写的
      print('正常的开发!')


# 接到 Boss 电话要赶紧在两小时内修复一个小 bug,但是现在手头上的代码才写了一部分,没有提交
$ git status
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:   test.txt

no changes added to commit (use "git add" and/or "git commit -a")

  1. 使用 git stash 命令将当前工作隐藏起来,并切换回到 master 分支上,创建一个新的 bug 分支 bug-001
# 将当前工作隐藏起来
$ git stash
Saved working directory and index state WIP on dev: 3cc1284 巴拉巴拉

# 再查看状态,发现已经是干净的了
$ git status
On branch dev
nothing to commit, working tree clean

# 切换回 master 分支上
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 7 commits.
  (use "git push" to publish your local commits)

# 创建一个新的 bug 分支,bug-001
$ git checkout -b bug-001
Switched to a new branch 'bug-001'

  1. 修改 test.txt,在最后一行添加一个 哈哈哈哈
$ vim test.txt

$ cat test.txt
第一行代码,第一次修改
第二行代码,第二次修改
分支测试
master 分支提交
添加一这行,用于新的分支 feature1,哈哈哈哈

# 然后再添加提交,并切换 master 分支,合并
$ git add .

$ git commit -m 'bug-001 解决'
[bug-001 706dcb2] bug-001 解决
 1 file changed, 1 insertion(+), 1 deletion(-)

# 切换到 master 分支
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 7 commits.
  (use "git push" to publish your local commits)

# 合并 bug-001
$ git merge bug-001
Updating 3cc1284..706dcb2
Fast-forward
 test.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

$ cat test.txt
第一行代码,第一次修改
第二行代码,第二次修改
分支测试
master 分支提交
添加一这行,用于新的分支 feature1,哈哈哈哈

# 删除 bug-001
$ git branch -d bug-001
Deleted branch bug-001 (was 706dcb2).

  1. 最后切换到 dev 分支,重新开始工作:
# 切换到 dev
$ git checkout dev
Switched to branch 'dev'

# 查看当前所在分支
$ git branch
* dev
  feature1
  master

# 发现工作区是干净的
$ git status
On branch dev
nothing to commit, working tree clean

# 用 git stash list 命令查看之前被隐藏的工作现场
$ git stash list
stash@{0}: WIP on dev: 3cc1284 巴拉巴拉

# 恢复之前的 “工作现场”(stash 内容)
$ git stash apply
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:   test.txt

no changes added to commit (use "git add" and/or "git commit -a")


# 删除 stash 内容
$ git stash drop
Dropped refs/stash@{0} (81ebdd4a9e65e1b4fef243e4f61e593d40eca482)

# 再查看 stash,已经没有了
$ git stash list

恢复 stash 内容时,会有两种选择:

  • git stash apply 恢复 stash 内容,但是并不会删除,还需要 git stash drop 删除
  • git stash pop 恢复的同时也将 stash 删除掉。
  • stash 内容可以理解为隐藏工作现场的信息,可删除

总结

  • 当要处理某个 bug,手头上工作又还没完成时,可以使用 git stash 将当前工作隐藏起来
  • 解决 bug,最好创建一个 bug 分支,完成后即可删除
  • 恢复之前被隐藏的 stash 内容,可用 git stash apply,查看被隐藏的内容可用 git stash list,删除 git stash drop,恢复并删除 git stash pop

8. 多人协作

当踩远程仓库克隆岛本地仓库后,本地的 master 分支就和远程 master 分支相关联,远程仓库的默认名称为 origin

查看远程仓库信息:

# 查看远程仓库名字
$ git remote
origin

# 查看更详细信息,显示抓取 fetch,和推送的地址
$ git remote -v
origin  git@github.com:hj1933/git_test.git (fetch)
origin  git@github.com:hj1933/git_test.git (push)

8.1 推送分支

如何将本地仓库代码推送到远程仓库,需要指定远程仓库名字和要推送的分支。

# 推送 master 分支,也可以是其他分支,如 dev、feature 等
$ git push origin master
Enumerating objects: 32, done.
Counting objects: 100% (32/32), done.
Delta compression using up to 4 threads
Compressing objects: 100% (30/30), done.
Writing objects: 100% (30/30), 2.47 KiB | 252.00 KiB/s, done.
Total 30 (delta 21), reused 0 (delta 0)
remote: Resolving deltas: 100% (21/21), completed with 1 local object.
To github.com:hj1933/git_test.git
   ccd5c05..6446ce4  master -> master

不是所有分支都需要网远程推送:

  • master 分支为主分支,要与远程同步
  • dev 分支是开发分支,开发人员在上面工作,也要与远程同步
  • bug 分支只用于本地修复 bug,无需推送

8.2 抓取分支

日常开发中,大家都会往 masterdev 分支上推送各自的修改,下面来模拟下多人协作开发,以及遇到的问题该怎么解决。

情景:公司有一个远程仓库 git_test,现在 rose 和 bob 都把它克隆到自己的本地仓库中。

  1. rose 和 bob 分别将远程仓库克隆岛本地
$ git clone git@github.com:hj1933/git_test.git
Cloning into 'git_test'...
remote: Enumerating objects: 49, done.
remote: Counting objects: 100% (49/49), done.
remote: Compressing objects: 100% (19/19), done.
remote: Total 49 (delta 26), reused 47 (delta 24), pack-reused 0
Receiving objects: 100% (49/49), done.
Resolving deltas: 100% (26/26), done.

  1. 一般克隆成功后,都只有一个 master分支,没有别的分支。然后自己再创建一个 dev 分支,然后时不时把 dev 分支 。

bob 在本地创建了一个 dev 分支,在 test.txt最后添加了一句 另一个小伙伴开发,然后添加提交并推送到远程:
push 到远程。

$ cd git_test

$ git checkout -b dev
Switched to a new branch 'dev'

$ vim test.txt

$ cat test.txt
第一行代码,第一次修改
第二行代码,第二次修改
分支测试
master 分支提交
添加一这行,用于新的分支 feature1,哈哈哈哈
def func():
        print('正常的开发!')
另一个小伙伴开发      # bob 添加

$ git add .


$ git commit -m '由另一个小伙伴提交'[dev 30e4c14] 由另一个小伙伴提交
 1 file changed, 1 insertion(+)

# 推送到远程
 $ git push origin dev
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 343 bytes | 171.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas:   0% (0/2)remote: Resolving deltas:  50% (1/2)remote: Resolving deltas: 100% (2/2)remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
remote:
remote: Create a pull request for 'dev' on GitHub by visiting:
remote:      https://github.com/hj1933/git_test/pull/new/dev
remote:
To github.com:hj1933/git_test.git
 * [new branch]      dev -> dev

推送成功后,打开 GitHub,选择 dev 分支,发现刚才添加的内容一句推送上来了。

  1. 这是 rose 也对 test.txt 文件做修改,在最后添加一句 自己开发,并且也要推送到远程:
$ vim test.txt

$ cat test.txt
第一行代码,第一次修改
第二行代码,第二次修改
分支测试
master 分支提交
添加一这行,用于新的分支 feature1
def func():
        print('正常的开发!')
自己开发

$ git add .

$ git commit -m '自己提交,多人协作'
[dev ac8a516] 自己提交,多人协作
 2 files changed, 2 insertions(+)
 create mode 100644 .gitignore

现在尝试把 dev 分支的修改推送到远程:

$ git push origin dev
To github.com:hj1933/git_test.git
 ! [rejected]        dev -> dev (fetch first)
error: failed to push some refs to 'git@github.com:hj1933/git_test.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

发现推送失败,这是因为 bob 提交了最新的代码,与你的有冲突。解决办法就是把远程仓库抓取最新的代码,再重新提交:

# 抓取远程最新代码
$ git pull
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 2), reused 3 (delta 2), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:hj1933/git_test
 * [new branch]      dev        -> origin/dev
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.

    git pull <remote> <branch>

If you wish to set tracking information for this branch you can do so with:

    git branch --set-upstream-to=origin/<branch> dev

git pull 也失败了,但是 Git 提示我们要设置本地 dev 分支与远程 dev 分支的链接(在错误的最后又提示):

$ git branch --set-upstream-to=origin/dev dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.

# 再重新抓取最新代码,抓取成功
$ git pull
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt
Automatic merge failed; fix conflicts and then commit the result.

虽然从远程重新拉取最新的代码,但是冲突还是存在,需要手动去解决,这就和分支冲突解决方式一样:

# 查看状态,git 告诉我们两个(bob、rose)都修改了 test.txt
$ git status
On branch dev
Your branch and 'origin/dev' have diverged,
and have 1 and 3 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

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:   test.txt

no changes added to commit (use "git add" and/or "git commit -a")


$ cat test.txt
第一行代码,第一次修改
第二行代码,第二次修改
分支测试
master 分支提交
添加一这行,用于新的分支 feature1,哈哈哈哈
def func():
        print('正常的开发!')
<<<<<<< HEAD
自己开发
=======
另一个小伙伴开发
>>>>>>> 30e4c14cd4ebd80b83689d233e2f37a8bb7b3ccc

手动解决冲突后,添加提交再重新推送到远程:

$ git add .

$ git commit -m '多人协作冲突解决'
[dev 041befd] 多人协作冲突解决

# 推送到远程
$ git push origin dev
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 4 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (7/7), 699 bytes | 174.00 KiB/s, done.
Total 7 (delta 3), reused 0 (delta 0)
remote: Resolving deltas: 100% (3/3), completed with 1 local object.
To github.com:hj1933/git_test.git
   30e4c14..041befd  dev -> dev

8.3 多人协作模式

  • 先尝试往远程推送 git push origin 分支名
  • 若推送失败,而说明远程比本地更新,则需要重新从远程拉取代码 git pull,拉取时需要指定本地分支与远程分支的链接
  • 若合并时若有冲突则解决冲突,再重新提交并推送到远程,没有远程就能直接推送成功

8.4 总结

  • 查看远程仓库信息,git remote -v
  • 从远程克隆到本地只有一个 master 分支,没有其他分支
  • 从本地推送分支 git push origin 分支名,若推送失败则从远程拉取最新代码 git pull
  • 在本地创建于远程一直的分支 git checkout -b 分支名
  • 建立本地与远程分支的关联,git branch --set-upstream 分支名 origin、分支名
  • 拉取最新代码后,也要注意查看是否有冲突,解决冲突后再重新推送

9. 其他

9.1 使用 GitHub 要注意的事项

GitHub 是一个免费的代码托管平台,很多开源项目都托管在上面,如:ShadowsocksBootstrap 等等。

如果想为开源项目共享一份自己的力量,那么可以 fork 一份到自己账号中,然后再 clone 到本地仓库即可修改提交了。若是直接从开源项目仓库中 clone,将无法推送修改,因为没有权限


Pull Request

为开源项目做贡献,主要分为以下几步:

  1. 从开源项目仓库 Fork 到自己远程仓库中
  2. 从自己远程仓库 clone 到本地仓库
  3. 创建一个新的分支,修改或添加源码后,pull 到自己远程仓库中
  4. 打开 GitHub,并切换到刚才创建的分支上,然后发起一个 Pull Request,它的意思就是给开源项目发起一个合并代码的请求,若是作者同意则会合并,成功后则会以邮件形式通知你

9.2 忽略特殊文件 .gitignore

所谓 .gitignore 文件就是用来配置一些不想被其他用户看到的 特殊文件,将其忽略掉。

忽略文件的原则是:

  1. 忽略操作系统自动生成的文件,如缩略图等
  2. 忽略编译生成的中间文件、可执行文件,如 Python 文件的 .pyc 文件
  3. 忽略一些带有敏感信息的配置文件,如一个项目的数据库账户、密码等

我们不需要从头写 .gitignore 文件,Git 以及为我们配置好了各种忽略文件,我们只需再稍作修改即可 https://github.com/github/gitignore

Tips:

Windows 上创建一个 .gitignore 文件,需要在文本编辑器中已另存为方式才能创建,否则将要你输入文件名。


  • 在 Git 工作区的根目录下创建一个特殊的.gitignore 文件
  • .gitignore 文件创建后,也要提交到 Git 中
  • 检查 .gitignore 的标准是用 git status 是不是 working directory clean
  • 若要添加一个文件到 Git,发现添加失败,原因可能是被忽略了
$ git add t2.class

The following paths are ignored by one of your .gitignore files:
t2.class
Use -f if you really want to add them.

  • 强制添加 $ git add -f t2.class
  • 若发现 .gitignore 写的有问题,可以使用以下命令检查:
$ git check-ignore -v t2.class

# 告诉你 .gitignore 文件第三行忽略了该文件
.gitignore:3:*.class     t2.class

参考文章

原文地址:https://www.cnblogs.com/midworld/p/10847201.html