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 才能管理控制。
- 使用
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
- 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 file
和 git 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. 删除操作
我们经常会删除一些没用的文件,或者一不小心删除错了某个文件,该怎么办?
- 首先我们新建一个
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
- 现在我们使用
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 是多么的贴心。
- 这里我们选择继续删除:
$ 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 就像一段暗号,通讯时只需要检查暗号是否正确即可,主要分为以下几步:
- 在目标机器创建 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'
- 将公匙复制到 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,从而并不影响整体进度。
创建第一个分支
- 使用
git checkout -b 分支名
快速创建一个新的分支:
# 创建一个新的分支 dev,并切换到 dev
$ git checkout -b dev # dev 为新的分支名
Switched to a new branch 'dev'
# 事实上上面这条命令是下面这两条命令的简写
$ git branch dev
$ git checkout dev
- 查看当前所在分支,并修改
test.txt
:
# 查看所有分支,带星号的为当前分支
$ git branch
* dev
master
# 可以看出分支 dev 下也有个 test.txt,它从 master 主分支 copy 了一份
$ ls
test.txt
# 修改 test.txt,在最后添加一行
$ vim test.txt
$ cat test.txt
第一行代码,第一次修改
第二行代码,第二次修改
分支测试
- 添加提交到版本库后,重新切换回 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
- 将
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
第一行代码,第一次修改
第二行代码,第二次修改
分支测试
- 删除分支,合并之后如果没有其他用,就可以删除
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 解决分支冲突
- 添加一个新的分支
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(+)
- 切换到
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 分支提交 # 新添加的
现在 master
和 feature1
分支上都有提交,整体流程变成这样:
- 提交后,尝试合并:
$ 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.
- 用
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
,并告诉我们两个分支上都做了修改。
- 重新打开
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
分支上去发布 - 另外每个开发人员都会有自己的独立分支,如:
rose
、lila
等等,时不时往dev
分支合并。
整体流程大致像如下所示:
7.4 bug 分支
所谓 bug
分支,一般是用于临时处理某个 bug
而创建的分支,bug 处理完毕后也会被删除。
假设当前正在 dev
分支上开发,目测还有一周时间才能完成。这时老板突然要你赶紧处理一个 bug(估计两小时就能完成),但是手头上的工作还没完成,如若提交就会影响其他人的进度,那么该怎么办呢?
不用方,Git 给我们提供了一个 隐藏功能,我们可以将当前工作临时隐藏起来,待处理完 bug 后再恢复即可。
下面我们来模拟一下:
- 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")
- 使用
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'
- 修改
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).
- 最后切换到
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 抓取分支
日常开发中,大家都会往 master
和 dev
分支上推送各自的修改,下面来模拟下多人协作开发,以及遇到的问题该怎么解决。
情景:公司有一个远程仓库 git_test
,现在 rose 和 bob 都把它克隆到自己的本地仓库中。
- 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.
- 一般克隆成功后,都只有一个
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
分支,发现刚才添加的内容一句推送上来了。
- 这是 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 是一个免费的代码托管平台,很多开源项目都托管在上面,如:Shadowsocks
、Bootstrap
等等。
如果想为开源项目共享一份自己的力量,那么可以 fork
一份到自己账号中,然后再 clone
到本地仓库即可修改提交了。若是直接从开源项目仓库中 clone,将无法推送修改,因为没有权限。
Pull Request
为开源项目做贡献,主要分为以下几步:
- 从开源项目仓库
Fork
到自己远程仓库中 - 从自己远程仓库
clone
到本地仓库 - 创建一个新的分支,修改或添加源码后,
pull
到自己远程仓库中 - 打开 GitHub,并切换到刚才创建的分支上,然后发起一个
Pull Request
,它的意思就是给开源项目发起一个合并代码的请求,若是作者同意则会合并,成功后则会以邮件形式通知你
9.2 忽略特殊文件 .gitignore
所谓 .gitignore
文件就是用来配置一些不想被其他用户看到的 特殊文件,将其忽略掉。
忽略文件的原则是:
- 忽略操作系统自动生成的文件,如缩略图等
- 忽略编译生成的中间文件、可执行文件,如
Python
文件的.pyc
文件 - 忽略一些带有敏感信息的配置文件,如一个项目的数据库账户、密码等
我们不需要从头写 .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
参考文章