文章目录
0 前言
需要说几句:
1、全文内容主要来源于廖雪峰Git教程,内容进行了删减,加入少量自己的思考。个人觉得原链接有些用词过于掺入感情色彩,贬低其他版本控制系统,虽然可能事实如此,但是我不喜欢这种描述;
2、我学Git是为了用而不是成为专家,所以如原网站说明一样,这是一个简明教程;
3、为减少麻烦,内容大部分用【git】,少部分【Git】,基本没区别,当然代码中输入的是小写的git;
4、最好的使用方法是先看原链接内容,然后再看本文,按需所取即可;
5、最后,本文主要用于自身复习学习,欢迎转载,注意带上廖雪峰老师的原文链接。
1 简介
Git是一个分布式版本控制系统,是使用C语言写成的。
在Win10上的浏览器下载git安装程序,成功后打开git bash,界面与cmd命令行相似,请在命令行中输入以下内容,在输入以下内容之前注意:【“name”】、【“email”】分别对应自己将使用的git的【用户名】、【邮箱地址】,输入时不需要输入引号,直接用自己想输入的名称与邮箱替换即可。
$ git config --global user.name "name"
$ git config --global user.email "email"
版本库又名仓库,英文名repository,相当于一个目录,里面的所有文件都可以被Git管理起来。在【我的电脑】中复制某个自己常用的文件夹路径,如【D:】,在git bash中输入:
$ cd d: #进入本地磁盘D
$ mkdir learngit #新建文件夹
$ cd learngit #进入文件夹learngit
然后通过git init命令把这个目录变成Git可以管理的仓库:
$ git init
Initialized empty Git repository in C:/Users/xw/learngit/.git/
这样就建立好了一个新的空仓库。
注意:git只能跟踪文本文件的改动,比如TXT文件、程序代码等,而图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化。同样地,Microsoft的Word格式是二进制格式,git无法跟踪Word文件改动。
下面正式开始git的命令简介。
打开Win10的记事本,输入以下内容:
Git is a version control system.
Git is free software.
另存为readme.txt
文件于刚刚创建的learngit目录下。下面的命令将该文件添加add到暂存区(后面会详细提及,现在不需要管):
$ git add readme.txt
上述命令应该无输出,这证明添加成功。再输入下面代码将文件提交commit到仓库:
$ git commit -m "wrote a readme file"
[master (root-commit) 48fd8af] wrote a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.txt
git commit命令执行成功后输出的内容:1 file changed:1个文件被改动(新添加的readme.txt文件);2 insertions:插入了两行内容(readme.txt有两行内容)。
git commit命令-m后面输入的是本次提交的说明,可以输入任意有意义的内容以便于从历史记录里方便地找到改动记录。
可以多次add不同的文件,然后用commit一次提交很多文件。比如:
$ git add file1
$ git add file2 file3
$ git commit -m "add 3 files"
总结一下【将文件添加到git仓库】的步骤与命令:
1、使用git add <file1> <file2> <file3>...
,可添加多个文件;
2、使用git commit -m <message>
完成提交。
2 切换仓库版本
目前已经成功地添加并提交了一个readme.txt文件,下面修改该文件内容如下:
Git is a distributed version control system.
Git is free software.
运行git status命令看看结果:
$ 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.txt
no changes added to commit (use "git add" and/or "git commit -a")
git status让自己时刻掌握仓库当前的状态,上面的命令输出显示readme.txt被修改过了,但还没有提交修改。使用git diff查看difference:
$ git diff readme.txt
diff --git a/readme.txt b/readme.txt
index d8036c1..013b5bc 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
Git is free software.
\ No newline at end of file
可以从输出看出在第一行添加了一个distributed单词。使用下面代码提交修改后的文件:
$ git add readme.txt
$ git commit -m "add distributed"
[master aa52d31] add distributed
1 file changed, 1 insertion(+), 1 deletion(-)
用git status命令看看仓库的当前状态:
$ git status
On branch master
nothing to commit, working tree clean
总结一下:
1、使用git status掌握工作区的状态。
2、使用git diff查看文件被修改了的内容。
2.1 版本回退
再练习一次,修改readme.txt文件如下:
Git is a distributed version control system.
Git is free software distributed under the GPL.
提交:
$ git add readme.txt
$ git commit -m "append GPL"
[master 0e6bb5b] append GPL
1 file changed, 1 insertion(+), 1 deletion(-)
每当文件修改到一定程度的时候,就可以“保存一个快照”,这个快照在Git中被称为commit。一旦文件被改乱了,或者误删了文件,还可以从最近的一个commit恢复,然后继续工作,而不是把几个月的工作成果全部丢失。用git log命令查看文件被修改的历史记录:
$ git log
commit 0e6bb5b0dc3f4aa6c52a4cba58c48b4d178e8763 (HEAD -> master)
Author: xw <1501144231@qq.com>
Date: Mon Aug 16 17:24:52 2021 +0800
append GPL
commit aa52d31599efa8ade7d1d3e3b278120804804139
Author: xw <1501144231@qq.com>
Date: Mon Aug 16 17:14:08 2021 +0800
add distributed
commit 48fd8af1247094c1904f3f960ef58bff1c9e5485
Author: xw <1501144231@qq.com>
Date: Mon Aug 16 15:57:41 2021 +0800
wrote a readme file
git log命令显示从最近到最远的提交日志,从上述输出可以看到3次提交,最近的一次是append GPL,上一次是add distributed,最早的一次是wrote a readme file。在加上–pretty=oneline参数后,可以减少输出信息,避免看得眼花缭乱:
$ git log --pretty=oneline
0e6bb5b0dc3f4aa6c52a4cba58c48b4d178e8763 (HEAD -> master) append GPL
aa52d31599efa8ade7d1d3e3b278120804804139 add distributed
48fd8af1247094c1904f3f960ef58bff1c9e5485 wrote a readme file
类似0e6bb5b…的是commit id(版本号),而每提交一个新版本,实际上Git就会把它们自动串成一条时间线。如果使用可视化工具查看Git历史,就可以看到提交历史的时间线是顺序排列的。在Git中,用HEAD表示当前版本,上一个版本就是HEAD^
,上上一个版本就是HEAD^^
,往上n个版本可以写成HEAD~n
。把当前版本append GPL回退到上一个版本add distributed,就可以使用git reset命令:
$ git reset --hard HEAD^
HEAD is now at aa52d31 add distributed
–hard参数的意义在后面再讲,现在先使用。现在readme.txt的内容变成版本add distributed,自己可以打开该文件查看是否是这样。再用git log再看看现在版本库的状态:
$ git log
commit aa52d31599efa8ade7d1d3e3b278120804804139 (HEAD -> master)
Author: xw <1501144231@qq.com>
Date: Mon Aug 16 17:14:08 2021 +0800
add distributed
commit 48fd8af1247094c1904f3f960ef58bff1c9e5485
Author: xw <1501144231@qq.com>
Date: Mon Aug 16 15:57:41 2021 +0800
wrote a readme file
可以通过commit id指定回到某个版本:
$ git reset --hard 0e6bb5b
HEAD is now at 0e6bb5b append GPL
版本号没必要写全,前5-8位就可以了,Git会自动去找。现在回到原来的版本后readme.txt内容变为:
Git is a distributed version control system.
Git is free software distributed under the GPL.
即回到了原来的版本。Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当回退版本的时候,Git仅仅是把HEAD从指向append GPL:
改为指向add distributed:
然后顺便把工作区的文件更新了。
git reflog用来记录每一次命令的历史记录:
$ git reflog
0e6bb5b (HEAD -> master) HEAD@{0}: reset: moving to 0e6bb5b
aa52d31 HEAD@{1}: reset: moving to HEAD^
0e6bb5b (HEAD -> master) HEAD@{2}: commit: append GPL
aa52d31 HEAD@{3}: commit: add distributed
48fd8af HEAD@{4}: commit (initial): wrote a readme file
可以通过commit id指定回到任意某个版本了。下面总结一下:
1、HEAD指向的版本就是当前版本,使用命令git reset --hard commit_id在版本的历史之间穿梭。
2、git log可以查看提交历史,以便确定要回退到哪个版本。
3、要重返未来,用git reflog查看命令历史记录,以便确定要回到未来的哪个版本id。
2.2 工作区与暂存区
工作区(Working Directory)就是在电脑里能看到的目录,比如learngit文件夹就是一个工作区。在工作区有一个隐藏目录.git,是Git的版本库。版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有自动创建的第一个分支master,以及指向master的一个指针叫HEAD。
之前说的把文件往Git版本库里添加的时候,是分两步执行的:
1、第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
2、第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。
这是因为在创建Git版本库时,Git自动创建了唯一一个master分支,而git commit就是往master分支上提交更改。简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
git add命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit就可以一次性把暂存区的所有修改提交到分支。
2.3 管理修改
注意:Git跟踪并管理的是修改,而非文件。而修改有多种形式,如新增了一行,删除了一行,更改了某些字符,创建一个新文件,也算一个修改。
用git diff HEAD -- readme.txt
命令可以查看工作区和版本库里面最新版本的区别。每次修改,如果不用git add到暂存区,那就不会加入到commit中。
2.4 撤销修改
git checkout -- file
可以丢弃工作区的修改:
$ git checkout -- readme.txt
上面命令git checkout -- readme.txt
意思就是,把readme.txt文件在工作区的修改全部撤销,有两种情况:
1、readme.txt自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
2、readme.txt已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
git checkout -- file
命令中的--
很重要,没有--
,就变成了“切换到另一个分支”的命令,在【4 分支管理】中会再次遇到git checkout命令。
用命令git reset HEAD <file>
可以把暂存区的修改撤销掉(unstage),重新放回工作区。git reset命令既可以回退版本,也可以把暂存区的修改回退到工作区。用HEAD表示最新的版本。
假设改错了东西,还从暂存区提交到了版本库,则可以通过版本回退到上一个版本,这是有条件的,就是还未把自己的本地版本库推送到远程。
总结一下:
1、若改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout – file。
2、若已改乱工作区某个文件的内容,还将其添加到了暂存区时,想丢弃修改分两步:第一步用命令git reset HEAD <file>
,这样就回到了上面第1种情况,第二步按情况1操作,即使用命令git checkout – file。
3、若已经提交了不合适的修改到版本库时,想要撤销本次提交,参考【2.1 版本回退】一节,不过前提是没有推送到远程库。
2.5 删除文件
用命令git rm可以从版本库中删除文件,然后git commit:
$ git rm aa.txt
rm 'aa.txt'
$ git commit -m "remove aa.txt"
[master 5259c5e] remove aa.txt
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 aa.txt
现在文件aa.txt就从版本库中被删除了。可以通过git checkout把误删的文件恢复到最新版本:
$ git checkout -- aa.txt
git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。 注意,无法恢复那些从来没有被添加到版本库就被删除的文件。
总之,如果一个文件已经被提交到版本库,那就永远不用担心误删,但只能将文件恢复到最新版本,也就是说会丢失最近一次提交后修改的内容。
3 远程仓库
Git是一个分布式版本控制系统,同一个Git仓库可以分布到不同的机器上。GitHub网站提供Git仓库托管服务,注册GitHub账号就可以免费获得Git远程仓库。请先注册GitHub账号。本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以设置:
1、第1步:创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:
$ ssh-keygen -t rsa -C "email"
【“email”】对应自己将使用的git的【邮箱地址】,输入时不需要输入引号,直接用自己想输入的名称与邮箱替换即可,然后一路回车,使用默认值即可,无需设置密码。如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有id_rsa和id_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。
2、第2步:登陆GitHub,打开“Account settings”,“SSH Keys”页面,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容, 点“Add Key”应该可以看到已经添加的Key。设置结束。
GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。GitHub允许添加多个Key,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。
3.1 添加远程库
现在的情景已经在本地创建了一个Git仓库后,又想在GitHub创建一个Git仓库,并且让这两个仓库进行远程同步。首先,登陆GitHub,然后,在右上角找到“Create a new repo”按钮,创建一个新的仓库learngit:
GitHub上的这个learngit仓库还是空的,可以把一个已有的本地仓库与之关联,并把本地仓库的内容推送到GitHub仓库。现在在本地的learngit仓库下运行命令:
$ git remote add origin git@github.com:XueToWei/learngit.git
把上面的XueToWei替换成自己的GitHub账户名。远程库的名字就是origin,这是Git默认的叫法。下一步,就可以把本地库的所有内容推送到远程库上:
$ git push -u origin master
用git push把当前分支master推送到远程,由于远程库是空的,在第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送到远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。在推送成功后,可以立刻在GitHub页面中看到远程库的内容已经和本地一模一样:
从现在起,只要本地作了提交,就可以通过下面命令把本地master分支的最新修改推送至GitHub了:
$ git push origin master
如果添加的时候地址写错了,或者就是想删除远程库,可以用git remote rm <name>
命令。使用前,建议先用git remote -v查看远程库信息:
$ git remote -v
origin git@github.com:XueToWei/learngit.git (fetch)
origin git@github.com:XueToWei/learngit.git (push)
然后,根据名字删除,比如删除origin:
$ git remote rm origin
此处的“删除”其实是解除了本地和远程的绑定关系,并不是物理上删除了远程库。远程库本身并没有任何改动。要真正删除远程库,需要登录到GitHub,在后台页面找到删除按钮再删除。
总结一下:
1、要关联一个远程库,使用命令git remote add origin git@github.com:path/repo-name.git
;
2、关联一个远程库时必须给远程库指定一个名字,origin是默认命名;关联后,使用命令git push -u origin master第一次推送master分支的所有内容;此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改。
3、git在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,当有网络的时候,再把本地提交推送一下就完成了同步,很方便。
3.2 从远程库克隆
上一小节叙述的是如何将本地仓库与GitHub的远程库进行关联。本小节先创建远程库,再从远程库克隆到本地仓库。首先在GitHub上新建一个新的仓库gitskills,注意勾选Initialize this repository with a README,这样GitHub会自动创建一个README.md文件。创建完毕后,可以看到README.md文件:
现在用命令git clone克隆一个本地库(记住要把Git库的地址换成自己的名称):
$ git clone git@github.com:XueToWei/gitskills.git
Cloning into 'gitskills'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.
总之,在知道某个仓库的地址后可使用git clone命令克隆该仓库到本地。
4 分支管理
分支类似于平行宇宙,在创建属于自己的分支后,别人看不到,他们可以继续在原来的分支上正常工作,而自己在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
4.1 创建与合并分支
每次提交commit文件时,Git都把它们串成一条时间线,这条时间线就是一个分支。目前只有一条时间线——主分支,即master分支。master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支以及当前分支的提交点:
随着每次提交,master分支都会向前移动,master分支的线也越来越长。在创建新分支,如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:
Git创建分支很快——除了增加一个dev指针,改变HEAD的指向,工作区的文件都没有任何变化。从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:
在dev上的工作完成后可以把dev合并到master上——直接把master指向dev的当前提交,就完成了合并:
Git合并分支也很快,仅更改指针,工作区内容也不变。合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后就剩下了一条master分支:
下面是代码实际操作过程。首先创建dev分支,然后切换到dev分支:
$ git checkout -b dev
Switched to a new branch 'dev'
-b参数表示创建并切换,相当于以下两条命令:
$ git branch dev
$ git checkout dev
可以用git branch命令查看当前分支:
$ git branch
* dev
main
git branch命令会列出所有分支,当前分支前面会标一个*号。现在可以在dev分支上正常提交,比如对readme.txt做个修改,最后加上一行,使该文件内容为:
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes.
Creating a new branch is quick.
提交:
$ git add readme.txt
$ git commit -m "branch test"
[dev 1de0817] branch test
1 file changed, 1 insertion(+)
create mode 100644 readme.txt
现在切换回master分支:
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
切换回master分支后,再查看一个readme.txt文件,会发现刚才添加的内容不见了!因为那个提交是在dev分支上,而master分支此刻的提交点并没有变:
因此,下面需要把dev分支的工作成果合并到master分支上:
$ git merge dev
Updating 2d7feda..d5a315d
Fast-forward
readme.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到和dev分支的最新提交是完全一样的。输出的Fast-forward指合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。在合并完成后,就可以放心地删除dev分支了:
$ git branch -d dev
Deleted branch dev (was d5a315d).
现在查看branch:
$ git branch
* master
就只剩下master分支了。使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
最新版本的Git提供了新的git switch命令来切换分支,创建并切换到新的dev分支,可以使用:
$ git switch -c dev
Switched to a new branch 'dev'
切换到已有的master分支,可以使用:
$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
使用新的git switch命令,比git checkout要更容易理解,毕竟switch就是改变、切换的意思。总结一下,Git鼓励使用分支:
1、查看分支:git branch
2、创建分支:git branch <name>
3、切换分支:git checkout <name>
或者git switch <name>
4、创建+切换分支:git checkout -b <name>
或者git switch -c <name>
5、合并某分支到当前分支:git merge <name>
6、删除分支:git branch -d <name>
4.2 解决冲突
有时合并分支会出现问题。举个例子,现在先创建新分支:
$ git switch -c feature1
Switched to a new branch 'feature1'
修改readme.txt最后一行,改为:
Creating a new branch is quick AND simple.
在feature1分支上提交:
$ git add readme.txt
$ git commit -m "AND simple"
[feature1 0374275] AND simple
1 file changed, 1 insertion(+), 1 deletion(-)
切换到master分支:
$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
输出提示当前master分支比远程的master分支要超前1个提交。现在在master分支上把readme.txt文件的最后一行改为:
Creating a new branch is quick & simple.
提交:
$ git add readme.txt
$ git commit -m "& simple"
[master 12d7f53] & simple
1 file changed, 1 insertion(+), 1 deletion(-)
现在,master分支和feature1分支各自都分别有新的提交,变成这样:
这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突:
$ git merge feature1
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.
readme.txt文件存在冲突,必须手动解决冲突后再提交。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: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
使用文本编辑器打开该文件,内容为:
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1
用<<<<<<<,=======,>>>>>>>
标记出不同分支的内容,现在修改最后一行如下后保存:
Creating a new branch is quick and simple.
提交:
$ git add readme.txt
$ git commit -m "conflict fixed"
[master 796783e] conflict fixed
现在,master分支和feature1分支变成了:
用带参数的git log也可以看到分支的合并情况:
$ git log --graph --pretty=oneline --abbrev-commit
* 796783e (HEAD -> master) conflict fixed
|\
| * 0374275 (feature1) AND simple
* | 12d7f53 & simple
|/
* d5a315d (dev) branch test
* 2d7feda (origin/master) remove aa.txt
* e18a628 add aa.txt
* 10bcb9d git tracks changes
* 0e6bb5b append GPL
* aa52d31 add distributed
* 48fd8af wrote a readme file
最后,删除feature1分支:
$ git branch -d feature1
Deleted branch feature1 (was 0374275).
完成。
总之,当Git无法自动合并分支时,就必须首先解决冲突——把Git合并失败的文件手动编辑为自己希望的内容,再提交。可以用git log --graph命令可以看到分支合并图。
4.3 分支管理策略
通常Git会用Fast forward模式合并分支,但这种模式下,删除分支后,会丢掉分支信息。如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
首先,仍然创建并切换dev分支:
$ git switch -c dev
Switched to a new branch 'dev'
修改readme.txt文件,并提交一个新的commit:
$ git add readme.txt
$ git commit -m "add merge"
[dev 690f371] add merge
1 file changed, 2 insertions(+), 1 deletion(-)
切换回master:
$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 4 commits.
(use "git push" to publish your local commits)
合并dev分支,有–no-ff参数,表示禁用Fast forward:
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
readme.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。在合并后用git log看看分支历史:
$ git log --graph --pretty=oneline --abbrev-commit
* 4111dac (HEAD -> master) merge with no-ff
|\
| * 690f371 (dev) add merge
|/
* 796783e conflict fixed
|\
| * 0374275 AND simple
* | 12d7f53 & simple
|/
* d5a315d branch test
* 2d7feda (origin/master) remove aa.txt
* e18a628 add aa.txt
* 10bcb9d git tracks changes
* 0e6bb5b append GPL
* aa52d31 add distributed
* 48fd8af wrote a readme file
可以看到若不使用Fast forward模式,merge后就像这样:
在开发中应该按照以下原则进行分支管理:首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;而干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;团队每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
总之,Git分支十分强大,在团队开发中应该充分应用。合并分支时,加上–no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。
4.4 Bug分支
每个bug都可以通过一个新的临时分支来修复,修复后合并分支,然后将临时分支删除。
Git提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:
$ git stash
这样可以放心地创建分支来修复bug。首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支issue-101:
$ git checkout master
Already on 'master'
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)
$ git checkout -b issue-101
Switched to a new branch 'issue-101'
issue-101为新创建的分支名字。现在修复bug,需要把“Git is free software …”改为“Git is a free software …”,然后提交:
$ git add readme.txt
$ git commit -m "fix bug 101"
[issue-101 b6bd155] fix bug 101
1 file changed, 1 insertion(+), 1 deletion(-)
修复完成后,切换到master分支,并完成合并,最后删除issue-101分支:
$ git switch master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
(use "git push" to publish your local commits)
$ git merge --no-ff -m "merged bug fix 101" issue-101
Merge made by the 'recursive' strategy.
readme.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
解决完bug后现在可以接着回到dev分支干活了:
$ git switch dev
Switched to branch 'dev'
$ git status
On branch dev
nothing to commit, working tree clean
工作区是干净的,刚才的工作现场存到哪去了?用git stash list命令看看:
$ git stash list
stash@{0}: WIP on master: 2653f1d merged bug fix 101
工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:
1、一是用git stash apply恢复,但是恢复后,stash内容并不删除,还需要自己用git stash drop来删除;
2、另一种方式是用git stash pop,恢复的同时把stash内容也删了:
$ git stash pop
Auto-merging readme.txt
On branch dev
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.txt
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (0a8bdc7cc0010b56252c20b23eec6d5a3cf6850f)
再用git stash list查看,就看不到任何stash内容了:
$ git stash list
上述代码无输出。可以多次stash,先用git stash list查看,然后恢复指定的stash,用命令:
$ git stash apply stash@{0}
下面图片的原内容我未整理,我不清楚其中意义何在,以后用到再整理:
总结一下:
1、修复bug时,可通过创建新的bug分支进行修复,然后合并,最后删除该分支即可;
2、当手头工作没有完成时,先把工作现场git stash一下,然后去修复bug,修复后,再git stash pop,回到工作现场;
3、(本条是上面图片内容的总结,如上所述我不会也不懂这条)在master分支上修复的bug,想要合并到当前dev分支,可以用git cherry-pick <commit>
命令,把bug提交的修改“复制”到当前分支,避免重复劳动。
4.5 Feature分支
在添加一个新功能时,有时不希望因为一些实验性质的代码把主分支搞乱了,这时最好新建一个feature分支,在上面开发,完成后合并到master分支,最后删除该feature分支即可。
举个栗子,现在开发一个名为Vulcan的新功能,先创建分支:
$ git switch -c feature-vulcan
Switched to a new branch 'feature-vulcan'
新建一个文件vulcan.py,里面写个hello world吧,暂作为“新功能”:
print("HELLO WORLD!")
添加并提交:
$ git add vulcan.py
$ git commit -m "add feature vulcan"
[feature-vulcan 845d2ac] add feature vulcan
1 file changed, 1 insertion(+)
create mode 100644 vulcan.py
由于feature分支和bug分支是类似的,因此之后合并然后删除即可。
但是原文有这样一个情况(lll¬ω¬):因经费不足,新功能必须取消,虽然白干了,但是这个包含机密资料的分支还是必须就地销毁:
$ git branch -d feature-vulcan
error: The branch 'feature-vulcan' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature-vulcan'.
提醒feature-vulcan分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用大写的-D参数。那现在就强行删除吧:
$ git branch -D feature-vulcan
Deleted branch feature-vulcan (was 845d2ac).
成功删除,结束。总之,准备开发一个新功能feature时,最好新建一个分支;如果要丢弃一个没有被合并过的分支,可以通过git branch -D <name>
强行删除。
4.6 多人协作
从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,远程仓库的默认名称是origin。要查看远程库的信息,用git remote:
$ git remote
origin
用git remote -v显示更详细的信息:
$ git remote -v
origin git@github.com:XueToWei/learngit.git (fetch)
origin git@github.com:XueToWei/learngit.git (push)
输出显示了可以抓取(fetch)和推送(push)的origin的地址。
4.6.1 推送分支
推送分支指把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:
$ git push origin master
Enumerating objects: 35, done.
Counting objects: 100% (35/35), done.
Delta compression using up to 8 threads
Compressing objects: 100% (25/25), done.
Writing objects: 100% (33/33), 2.97 KiB | 759.00 KiB/s, done.
Total 33 (delta 10), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (10/10), done.
To github.com:XueToWei/learngit.git
2d7feda..2abb1e9 master -> master
如果要推送其他分支,比如dev,就改成:
$ git push origin dev
并不是一定要把本地分支往远程推送,需要注意:
1、master分支是主分支,因此要时刻与远程同步;
2、dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
3、bug分支只用于在本地修复bug,就没必要推到远程了;
4、feature分支是否推到远程,取决于自己是否和其他人合作在该分支上面开发。
4.6.2 抓取分支
多人协作时,大家都会往master和dev分支上推送各自的修改。现在其他人的另一台电脑的另一个目录下克隆(注意要把SSH Key添加到GitHub):
$ git clone git@github.com:XueToWei/learngit.git
Cloning into 'learngit'...
remote: Enumerating objects: 49, done.
remote: Counting objects: 100% (49/49), done.
remote: Compressing objects: 100% (24/24), done.
remote: Total 49 (delta 13), reused 48 (delta 12), pack-reused 0
Receiving objects: 100% (49/49), 4.19 KiB | 476.00 KiB/s, done.
Resolving deltas: 100% (13/13), done.
其他人从远程库clone时,默认只能看到本地的master分支。不信可以让其他人用git branch命令看看,运行后应该是:
$ git branch
* master
若其他人要在dev分支上开发,就必须创建远程origin的dev分支到本地,可以用下面这个命令创建本地dev分支(首先需要保证远程库中除了master还有dev分支,不注意这个会报错的):
$ git checkout -b dev origin/dev
若出现:
$ git checkout -b dev origin/dev
fatal: 'origin/dev' is not a commit and a branch 'dev' cannot be created from it
的话,在保证远程库中除了master还有dev分支后,输入:
$ git pull
后再输入:
$ git checkout -b dev origin/dev
Switched to a new branch 'dev'
Branch 'dev' set up to track remote branch 'dev' from 'origin'.
现在其他人就可以在dev上继续修改并把dev分支push到远程,可以试一试,让他新建一个文件env.txt,内容就设置为:
attack on titan
现在其他人可以在dev上继续修改,然后,时不时地把dev分支push到远程:
$ git add env.txt
$ git commit -m "add env"
[dev 738504a] add env
1 file changed, 1 insertion(+)
create mode 100644 env.txt
$ git push origin dev
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 2 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 310 bytes | 310.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:XueToWei/learngit.git
2abb1e9..738504a dev -> dev
结束。
如果其他人已经向origin/dev分支推送了他的提交,而碰巧自己也对同样的文件作了修改,并试图推送:
$ cat env.txt
attack on titan
$ git add env.txt
$ git commit -m "add new env"
[master 5f6ee94] add new env
1 file changed, 1 insertion(+)
create mode 100644 env.txt
$ git push origin dev
To github.com:XueToWei/learngit.git
! [rejected] dev -> dev (fetch first)
error: failed to push some refs to 'github.com:XueToWei/learngit.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.
推送失败,因为其他人的最新提交和自己试图推送的提交有冲突,解决办法为:先用git pull把最新的提交从origin/dev抓下来,然后在本地合并,需要自己解决内容冲突,再推送到远程库。具体做法为首先指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接:
$ git branch --set-upstream-to=origin/dev dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.
下图:
我在进行上述操作时出现了较大问题,我在查阅诸多资料和花费许多时间后解决了,未来看此文的自己或者其他人有可能遇到,希望到时候着重注意。需要提醒的是:
1、在add、commit文件时看好自己在那个分支,别乱了;
2、多用git status查看仓库状态;
3、在commit时想清楚提交描述,也就是-m后的内容。
最后再总结一下:
1、多人协作的工作模式通常是这样:
- 首先,可以试图用
git push origin <branch-name>
推送自己的修改; - 如果推送失败,则因为远程分支比自己的本地更(geng,四声)新一些,需要先用git pull试图合并;
- 如果合并有冲突,则解决冲突,并在本地提交;
- 没有冲突或者解决掉冲突后,再用
git push origin <branch-name>
推送就能成功; - 如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令
git branch --set-upstream-to=origin/<branch-name> <branch-name>
。 - 这就是多人协作的工作模式,一旦熟悉了,就非常简单。
2、从远程抓取分支,使用git pull,如果有冲突,要先处理冲突;
3、本地新建的分支如果不推送到远程,对其他人就是不可见的;
4、从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull抓取远程的新提交;
5、在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name,本地和远程分支的名称最好一致;
6、建立本地分支和远程分支的关联,使用git branch --set-upstream-to=origin/<branch-name> <branch-name>
;
7、查看远程库信息,使用git remote -v。
4.7 Rebase
多人在同一个分支上协作时很容易出现冲突,即使没有冲突,后面push的人也必须git pull后在本地合并,然后才能push成功。
Git有一种称为rebase的操作,可以使Git的提交历史变成一条干净的直线。感觉暂时不会用到,到时候再说,这是原链接的内容。
5 标签管理
在发布软件的一个版本时,通常会先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。Git的标签其实就是指向某个commit的指针,这跟分支很像,但是分支可以移动,标签不能移动。
5.1 创建标签
在Git中打标签需要首先切换到需要打标签的分支上,如切换到master分支:
$ git branch
* dev
master
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
输入命令git tag <name>
就可以打一个新标签:
$ git tag v1.0
使用git tag查看所有标签:
$ git tag
v1.0
标签默认打在最新提交的commit上,要在其他commit上打标签的方法是找到历史提交的commit id,然后打上就可以了,先查看commit id:
$ git log --pretty=oneline --abbrev-commit
769f8ac (HEAD -> master, tag: v1.0, origin/master) settle conflict 2
0374275 AND simple
d5a315d branch test
2d7feda remove aa.txt
e18a628 add aa.txt
10bcb9d git tracks changes
0e6bb5b append GPL
aa52d31 add distributed
48fd8af wrote a readme file
比方说要对倒数第三行的append GPL提交打标签,它对应的commit id是0e6bb5b,敲入命令:
$ git tag v0.9 0e6bb5b
用命令git tag查看标签:
$ git tag
v0.9
v1.0
注意标签不是按时间顺序列出,而是按字母排序的。可用git show <tagname>
查看标签信息:
$ git show v0.9
commit 0e6bb5b0dc3f4aa6c52a4cba58c48b4d178e8763 (tag: v0.9)
Author: xw <1501144231@qq.com>
Date: Mon Aug 16 17:24:52 2021 +0800
append GPL
diff --git a/readme.txt b/readme.txt
index 013b5bc..99e0e11 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
Git is a distributed version control system.
-Git is free software.
\ No newline at end of file
+Git is free software distributed under the GPL.
\ No newline at end of file
可以创建带有说明的标签,用-a指定标签名,-m指定说明文字。下面是对上面commit id输出的倒数第一行的wrote a readme file进行打标签:
$ git tag -a v0.1 -m "version 0.1 released" 48fd8af
标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。
总结一下:
1、git tag <tagname>
用于新建一个标签,默认为HEAD,也可以指定一个commit id;
2、git tag -a <tagname> -m "blablabla..."
可以指定标签信息;
3、git tag可以查看所有标签。
5.2 操作标签
删除标签使用:
$ git tag -d v0.1
Deleted tag 'v0.1' (was be3fb63)
因为创建的标签都只存储在本地,不会自动推送到远程,打错的标签可以在本地安全删除。如果要推送某个标签到远程,使用命令git push origin <tagname>
:
$ git push origin v0.9
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:XueToWei/learngit.git
* [new tag] v0.9 -> v0.9
一次性推送全部尚未推送到远程的本地标签的代码如下:
$ git push origin --tags
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:XueToWei/learngit.git
* [new tag] v1.0 -> v1.0
如果标签已经推送到远程,要删除远程标签需要先从本地删除:
$ git tag -d v0.9
Deleted tag 'v0.9' (was 0e6bb5b)
然后,从远程删除,命令也是push,格式如下:
$ git push origin :refs/tags/v0.9
To github.com:XueToWei/learngit.git
- [deleted] v0.9
可以登陆GitHub查看远程库是否删除了标签。
总结一下:
1、git push origin <tagname>
可以推送一个本地标签;
2、git push origin --tags
可以推送全部未推送过的本地标签;
3、git tag -d <tagname>
可以删除一个本地标签;
4、git push origin :refs/tags/<tagname>
可以删除一个远程标签。
6 结束语
GitHub是一个开源协作社区,通过GitHub,既可以让别人参与自己的开源项目,自己也可以参与别人的开源项目。
如果希望别人的开源项目的官方库能接受自己的修改,可以在GitHub上发起一个pull request,对方是否接受自己的pull request就不一定了。
国内也有一个Git托管服务——Gitee,我暂时不用,我更不需要自定义Git,所以拜拜啦。
END