git使用


git官网

https://git-scm.com/

安装

安装完成后,还需要最后一步设置,在命令行输入:

$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"

否则第一次执行commit会报错如下

$ git commit -m "first commit"
Author identity unknown

*** Please tell me who you are.

Run

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

fatal: unable to auto-detect email address (got '琳琳向@DESKTOP-IJEUIA5.(none)')

如果报错了,按照提示操作即可

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

配置

Git 提供了一个叫做 git config 的工具,专门用来配置或读取相应的工作环境变量。

linux

  • /etc/gitconfig 文件:系统中对所有用户都普遍适用的配置。若使用 git config 时用 --system 选项,读写的就是这个文件。
  • ~/.gitconfig 文件:用户目录下的配置文件只适用于该用户。若使用 git config 时用 --global 选项,读写的就是这个文件。
  • 当前项目根目录下的 .git/config 文件:这里的配置仅仅针对当前项目有效。若使用 git config 时用 --local或者不加 选项,读写的就是这个文件。
  • 每一个级别的配置都会覆盖上层的相同配置,所以 .git/config 里的配置会覆盖 /etc/gitconfig 中的同名变量。

windows

  • Git 会尝试找寻 /etc/gitconfig 文件,只不过看当初 Git 装在什么目录,就以此作为根目录来定位。
  • Git 会找寻用户主目录下的 .gitconfig 文件。主目录即 $HOME 变量指定的目录
  • 当前项目根目录下的 .git/config 文件:这里的配置仅仅针对当前项目有效。
  • 每一个级别的配置都会覆盖上层的相同配置,所以 .git/config 里的配置会覆盖 /etc/gitconfig 中的同名变量。

修改config

git config --global user.name "runoob"
git config --global user.email test@runoob.com

查看配置信息

这里查看的所有配置文件的信息,有时候会看到重复的变量名,那就说明它们来自不同的配置文件(比如 /etc/gitconfig 和 ~/.gitconfig),不过最终 Git 实际采用的是最后一个。

git config --list/

可以查看具体某一个元素的信息。

git config user.name

自定义git

Git还有很多可配置项。
1.让Git显示颜色,会让命令输出看起来更醒目:

$ git config --global color.ui true

这样,Git会适当地显示不同的颜色,比如git status命令:
在这里插入图片描述

忽略特殊文件

1.忽略的文件
Git工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。
不需要从头写.gitignore文件,GitHub已经为我们准备了各种配置文件,只需要组合一下就可以使用了。所有配置文件可以直接在线浏览:这里
2.把.gitignore也提交到Git,就完成了!当然检验.gitignore的标准是git status命令是不是说working directory clean
使用Windows的童鞋注意了,如果你在资源管理器里新建一个.gitignore文件,它会非常弱智地提示你必须输入文件名,但是在文本编辑器里“保存”或者“另存为”就可以把文件保存为.gitignore了。
3.有些时候,你想添加一个文件到Git,但发现添加不了,原因是这个文件被.gitignore忽略了:

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

4.如果你确实想添加该文件,可以用-f强制添加到Git:

$ git add -f App.class

5.或者你发现,可能是.gitignore写得有问题,需要找出来到底哪个规则写错了,可以用git check-ignore -v App.class命令检查:

$ git check-ignore -v App.class
.gitignore:3:*.class	App.class

Git会告诉我们,.gitignore的第3行规则忽略了该文件,于是我们就可以知道应该修订哪个规则。
6.还有些时候,当我们编写了规则排除了部分文件时:

# 排除所有.开头的隐藏文件:
.*
# 排除所有.class文件:
*.class

7.但是我们发现.*这个规则把.gitignore也排除了,并且App.class需要被添加到版本库,但是被*.class规则排除了。

虽然可以用git add -f强制添加进去,但有强迫症的童鞋还是希望不要破坏.gitignore规则,这个时候,可以添加两条例外规则:

# 排除所有.开头的隐藏文件:
.*
# 排除所有.class文件:
*.class

# 不排除.gitignore和App.class:
!.gitignore
!App.class

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

配置别名

概念

1.告诉Git,以后st就表示status:

$ git config --global alias.st status

好了,现在敲git st看看效果。
2.当然还有别的命令可以简写,很多人都用co表示checkout,ci表示commit,br表示branch:

$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch

3.配置一个git last,让其显示最后一次提交信息:

$ git config --global alias.last 'log -1'

这样,用git last就能显示最近一次的提交:
4.甚至还有人丧心病狂地把lg配置成了:

git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

来看看git lg的效果:
在这里插入图片描述

配置文件

配置Git的时候,加上–global是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。
1.配置文件放哪了?每个仓库的Git配置文件都放在.git/config文件中:

$ cat .git/config 
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
    precomposeunicode = true
[remote "origin"]
    url = git@github.com:michaelliao/learngit.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
    remote = origin
    merge = refs/heads/master
[alias]
    last = log -1

别名就在[alias]后面,要删除别名,直接把对应的行删掉即可。
2.而当前用户的Git配置文件放在用户主目录下的一个隐藏文件.gitconfig中:

$ cat .gitconfig
[alias]
    co = checkout
    ci = commit
    br = branch
    st = status
[user]
    name = Your Name
    email = your@email.com

搭建Git服务器

概念

搭建Git服务器需要准备一台运行Linux的机器,强烈推荐用Ubuntu或Debian,这样,通过几条简单的apt命令就可以完成安装。
1.安装git:

$ sudo apt-get install git

2.创建一个git用户,用来运行git服务:

$ sudo adduser git

3.创建证书登录:
收集所有需要登录的用户的公钥,就是他们自己的id_rsa.pub文件,把所有公钥导入到/home/git/.ssh/authorized_keys文件里,一行一个。
4.初始化Git仓库:
先选定一个目录作为Git仓库,假定是/srv/sample.git,在/srv目录下输入命令:

$ sudo git init --bare sample.git

Git就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的Git仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的Git仓库通常都以.git结尾。
5.把owner改为git用户:

$ sudo chown -R git:git sample.git

6.禁用shell登录:
出于安全考虑,第二步创建的git用户不允许登录shell,这可以通过编辑/etc/passwd文件完成。找到类似下面的一行:

git:x:1001:1001:,,,:/home/git:/bin/bash
改为:
git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell

这样,git用户可以正常通过ssh使用git,但无法登录shell,因为我们为git用户指定的git-shell每次一登录就自动退出。
7.克隆远程仓库:
现在,可以通过git clone命令克隆远程仓库了,在各自的电脑上运行:

$ git clone git@server:/srv/sample.git
Cloning into 'sample'...
warning: You appear to have cloned an empty repository.

管理公钥

1.如果团队很小,把每个人的公钥收集起来放到服务器的/home/git/.ssh/authorized_keys文件里就是可行的。
2.如果团队有几百号人,就没法这么玩了,这时,可以用Gitosis来管理公钥。

管理权限

Git支持钩子(hook),所以,可以在服务器端编写一系列脚本来控制提交等操作,达到权限控制的目的。Gitolite就是这个工具。

小结

  • 搭建Git服务器非常简单,通常10分钟即可完成;

  • 要方便管理公钥,用Gitosis;

  • 要像SVN那样变态地控制权限,用Gitolite。

Git 工作区、暂存区和版本库

基本概念

  • 工作区:就是你在电脑里能看到的目录。

  • 暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。

  • 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。
    图示:
    在这里插入图片描述

  • 图中左侧为工作区,右侧为版本库。在版本库中标记为 “index” 的区域是暂存区(stage/index),标记为 “master” 的是 master 分支所代表的目录树。

  • 图中我们可以看出此时 “HEAD” 实际是指向 master 分支的一个"游标"。所以图示的命令中出现 HEAD 的地方可以用 master 来替换。

  • 图中的 objects 标识的区域为 Git 的对象库,实际位于 “.git/objects” 目录下,里面包含了创建的各种对象及内容(修改的内容放在这里)。

  • 当对工作区修改(或新增)的文件执行 git add 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。

  • 当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。

创建版本库

创建库

1.创建一个目录并cd进去
2.通过git init命令把这个目录变成Git可以管理的仓库

git init

上传文件

1.用命令git add告诉Git,把文件添加到暂存区

git add readme.txt

2.用命令git commit告诉Git,把文件提交到版本库

git commit -m "wrote a readme file"

①git commit后,git log就会更新快照内容,包括提交ID和提交信息.(git log显示的就是版本库的commit信息).
②master(如果是这个分支)指针指向最新的提交快照.
③HEAD指针继续指向master.

注意:本地安装完git后第一次执行git commit会报错如下

Author identity unknown

*** Please tell me who you are.

Run

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

fatal: unable to auto-detect email address (got '琳琳向@DESKTOP-IJEUIA5.(none)')

按照提示操作即可:

git config --global user.email "you@example.com"
git config --global user.name "Your Name"

版本管理

1.git status查看工作区和缓冲区和版本库的差异

git status

2.git diff查看工作区和缓冲区和版本库的差异

git diff 后面可以跟文件名

版本回退

1.git log查看版本库的commit信息(快照信息).
–pretty=oneline 可以简化显示信息

git log
git log --pretty=oneline

2.从版本库(commit提交)回退版本.
①在Git中,用HEAD表示当前版本,也就是指向最新的commit提交(也就是指向master指针(如果是这个分支)).
②上一个版本就是HEAD^ ,上上一个版本就是HEAD^^,可以写成HEAD~100
③这个命令会用你指定的版本库ID(commit提交)覆盖缓冲区和工作区.

git reset --hard HEAD^

3.我们后悔了上面的reset怎么办?如果还记得commit ID就行
①这个时候git log已经看不到已经丢失的commit信息
②版本号没必要写全,前几位就可以了,Git会自动去找

git reset --hard 1094a   //可以重新用1094a覆盖缓冲区和工作区

版本回退原理
①Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是修改HEAD的指向(也修改了master的指向,HEAD还是指向master).
②HEAD默认指向的是master指针(如果是这个分支名),master指针指向最新的commit提交(快照).
4.我们后悔了上面的reset怎么办?且不记得commit怎么恢复丢失的数据?
①git reflog用来记录你的每一次命令.可以查看你需要恢复的那一次commit的ID.

git reflog
e475afc HEAD@{1}: reset: moving to HEAD^
1094adb (HEAD -> master) HEAD@{2}: commit: append GPL
e475afc HEAD@{3}: commit: add distributed
eaadf4e HEAD@{4}: commit (initial): wrote a readme file

管理和修改

查看又没文件和版本库的区别

git diff HEAD -- readme.txt

撤销修改

修改了工作区,但是没有添加到暂存区

丢弃工作区的修改(用缓存区保存的信息覆盖工作区的信息)

git checkout -- file

--很重要,没有--,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout命令

修改了工作区,且添加到了暂存区

暂存区的修改撤销掉(unstage),工作区保持不变(用版本库的信息覆盖暂存区)

git reset HEAD <file>
git reset HEAD readme.txt

如果想继续丢弃工作区的修改可以

git checkout -- file

修改已经添加到了版本库(commit),但是还没有push到远程库

参考版本回退。(用快照覆盖版本库,暂存区,工作区。快照后面的快照号会丢失)

git reset --hard HEAD^
或者
git reset --hard 1094a  //重新用1094a覆盖缓冲区和工作区

删除文件

手动删除文件

1.手动删除文件,比如用linux命令rm删除被git记录的文件
2.在git记录上删除文件.
git rm 将文件从暂存区和工作区中删除:

git rm <file>
git rm test.txt

3.git commit -m ""即可(不需要add,因为git rm已经删除工作区的信息了)

小提示:先手动删除文件,然后使用git rm 和git add效果是一样的。

用git命令删除文件

1.git rm 将文件从暂存区和工作区中删除

git rm <file>
git rm test.txt
如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f
git rm -f runoob.txt   //强行从暂存区和工作区中删除修改后的 runoob.txt 文件:
如果想把文件从暂存区域移除,但仍然希望保留在当前工作目录中,换句话说,使用 --cached 选项即可:
git rm --cached <file>

2.git commit -m ""即可(不需要add,因为git rm已经删除工作区的信息了)

手动删除文件,发现删除错了

丢弃工作区的修改(用缓存区保存的信息覆盖工作区的信息)

git checkout -- test.txt

远程仓库

这里介绍了如何给github配置你本地机器的SSH KEY

添加远程仓库

添加远程库

1.关联本地库和远程库
已经有了本地库,也已经有了远程库

git remote add origin git@github.com:michaelliao/learngit.git  //把michaelliao替换成你自己的GitHub账户名

关联一个远程库时必须给远程库指定一个名字,origin是默认习惯命名

2.把本地库的所有内容推送到远程库上
把本地库的内容推送到远程,用git push命令,实际上是把当前分支master推送到远程。

git push -u origin master

由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。

3.从现在起,只要本地作了提交,就可以通过push命令推送
这里的操作要在git commit -m后面

git push origin master  //把本地master分支的最新修改推送至远程库

4.SSH警告
当你第一次使用Git的clone或者push命令连接GitHub时,会得到一个警告:

The authenticity of host 'github.com (xx.xx.xx.xx)' can't be established.
RSA key fingerprint is xx.xx.xx.xx.xx.
Are you sure you want to continue connecting (yes/no)?

这是因为Git使用SSH连接,而SSH连接在第一次验证GitHub服务器的Key时,需要你确认GitHub的Key的指纹信息是否真的来自GitHub的服务器,输入yes回车即可。
Git会输出一个警告,告诉你已经把GitHub的Key添加到本机的一个信任列表里了:

Warning: Permanently added 'github.com' (RSA) to the list of known hosts.

删除远程库

删除前,建议先用git remote -v查看远程库信息

$ git remote -v
origin  git@github.com:michaelliao/learn-git.git (fetch)
origin  git@github.com:michaelliao/learn-git.git (push)

解除和远程库的绑定关系

git remote rm <name>
git remote rm origin

此处的“删除”其实是解除了本地和远程的绑定关系,并不是物理上删除了远程库。远程库本身并没有任何改动。要真正删除远程库,需要登录到GitHub,在后台页面找到删除按钮再删除。

从远程库克隆

git clone克隆一个本地库

git clone git@github.com:xxxxxxx/gitskills.git
Cloning into 'gitskills'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 3
Receiving objects: 100% (3/3), done.
$ cd gitskills
$ ls
README.md

实际上,Git支持多种协议,默认的git@使用ssh,但也可以使用https等其他协议。(SSH速度最快)
使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https。

分支管理

分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。

现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。

创建与合并分支

概念

一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:
在这里插入图片描述
当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:
在这里插入图片描述
你看,Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化!
不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:
在这里插入图片描述
假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:
在这里插入图片描述
所以Git合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:
在这里插入图片描述

操作

1.首先,我们创建dev分支,然后切换到dev分支:

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

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

$ git branch dev
$ git checkout dev
Switched to branch 'dev'

2.然后,用git branch命令查看当前分支:

$ git branch
* dev
  master

3.我们就可以在dev分支上正常提交,比如对readme.txt做个修改,加上一行:

$ git add readme.txt 
$ git commit -m "branch test"
[dev b17d20e] branch test
 1 file changed, 1 insertion(+)

4.现在,dev分支的工作完成,我们就可以切换回master分支:

$ git checkout master
Switched to branch 'master'

5.切换回master分支后,再查看一个readme.txt文件,刚才添加的内容不见了!因为那个提交是在dev分支上,而master分支此刻的提交点并没有变:
在这里插入图片描述
6.现在,我们把dev分支的工作成果合并到master分支上:
git merge命令用于合并指定分支到当前分支。

$ git merge dev
Updating d46f35e..b17d20e
Fast-forward
 readme.txt | 1 +
 1 file changed, 1 insertion(+)

再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。

注意:注意上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。
7.合并完成后,就可以放心地删除dev分支了:

$ git branch -d dev
Deleted branch dev (was b17d20e).

8.因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。

switch

我们注意到切换分支使用git checkout ,而前面讲过的撤销修改则是git checkout – ,同一个命令,有两种作用,确实有点令人迷惑。
实际上,切换分支这个动作,用switch更科学。因此,最新版本的Git提供了新的git switch命令来切换分支:

创建并切换到新的dev分支,可以使用:

$ git switch -c dev

直接切换到已有的master分支,可以使用:

$ git switch master

小结

Git鼓励大量使用分支:

查看分支:git branch

创建分支:git branch <name>

切换分支:git checkout <name>或者git switch <name>

创建+切换分支:git checkout -b <name>或者git switch -c <name>

合并某分支到当前分支:git merge <name>

删除分支:git branch -d <name>

解决分支冲突

1.假如我们再feature1分支上修改了文件readme.txt最后一行并add和commit了
2.切换到master分支,在master分支上把readme.txt文件的最后一行修改(内容和feature1不一样),并add和commit。不会报错
3.现在,master分支和feature1分支各自都分别有新的提交,变成了这样:
在这里插入图片描述
这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突
4.master执行merge命令

$ git merge feature1
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.

果然冲突了!Git告诉我们,readme.txt文件存在冲突,必须手动解决冲突后再提交。
5.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")

6.我们可以直接查看readme.txt的内容(vim打开就行)
文件内容如下

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 of files.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1

Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,我们修改后保存。修改就是把Git合并失败的文件手动编辑为我们希望的内容。
7.再提交(add和commit)

$ git add readme.txt 
$ git commit -m "conflict fixed"
[master cf810e4] conflict fixed

8.现在,master分支和feature1分支变成了下图所示:
在这里插入图片描述
9.用带参数的git log也可以看到分支的合并情况:
–graph命令可以看到分支合并图
–pretty=oneline 可以简化显示信息

$ git log --graph --pretty=oneline --abbrev-commit
*   cf810e4 (HEAD -> master) conflict fixed
|\  
| * 14096d0 (feature1) AND simple
* | 5dc6824 & simple
|/  
* b17d20e branch test
* d46f35e (origin/master) remove test.txt
* b84166e add test.txt
* 519219b git tracks changes
* e43a48b understand how stage works
* 1094adb append GPL
* e475afc add distributed
* eaadf4e wrote a readme file

10.最后,删除feature1分支:

$ git branch -d feature1
Deleted branch feature1 (was 14096d0).

分支管理策略

分支管理策略

1.合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
2.强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
3.实战一下–no-ff方式的git merge
–no-ff:禁用Fast forward模式
因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。

$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
 readme.txt | 1 +
 1 file changed, 1 insertion(+)

3.看看分支历史

$ git log --graph --pretty=oneline --abbrev-commit
*   e1e9c68 (HEAD -> master) merge with no-ff
|\  
| * f52c633 (dev) add merge
|/  
*   cf810e4 conflict fixed
...

4.不使用Fast forward模式,merge后就像这样:
在这里插入图片描述

分支策略

在实际开发中,我们应该按照几个基本原则进行分支管理:
1.master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
2.干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
3.你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
4.所以,团队合作的分支看起来就像这样:
在这里插入图片描述

小结

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

Bug分支

1.有了bug就需要修复,在Git中,由于分支是如此的强大,所以,每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。
2.当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支issue-101来修复它,但是,等等,当前正在dev上进行的工作还没有提交:
3.并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?
stash:可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作

$ git stash
Saved working directory and index state WIP on dev: f52c633 add merge

4.现在,用git status查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug。
5.首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支

$ git checkout master
Switched to branch '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'

6.修复bug并提交(add和commit)
7.修复完成后,切换到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(-)

8.现在,是时候接着回到dev分支干活了!(切换回dev)

$ git switch dev
Switched to branch 'dev'

$ git status
On branch dev
nothing to commit, working tree clean

9.工作区是干净的,刚才的工作现场存到哪去了?用git stash list命令看看:

$ git stash list
stash@{0}: WIP on dev: f52c633 add merge

10.Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:
一:用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;
二:用git stash pop,恢复的同时把stash内容也删了:

$ git stash pop
On branch dev
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   hello.py

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

Dropped refs/stash@{0} (5d677e2ee266f39ea296182fb2354265b91b3b2a)

再用git stash list查看,就看不到任何stash内容了:

$ git stash list

11.你可以多次stash,恢复的时候,先用git stash list查看,然后恢复指定的stash,用命令:

$ git stash apply stash@{0}

12.在master分支上修复了bug后,我们要想一想,dev分支是早期从master分支分出来的,所以,这个bug其实在当前dev分支上也存在。那怎么在dev分支上修复同样的bug?重复操作一次,提交不就行了?

同样的bug,要在dev上修复,我们只需要把4c805e2 fix bug 101(4c805e2 是commit号,fix bug 101是commit时候填的信息)这个提交所做的修改“复制”到dev分支。
注意:我们只想复制4c805e2 fix bug 101这个提交所做的修改,并不是把整个master分支merge过来。

为了方便操作,Git专门提供了一个cherry-pick命令(在dev分支执行),让我们能复制一个特定的提交到当前分支

$ git branch
* dev
  master
$ git cherry-pick 4c805e2
[master 1d4b803] fix bug 101
 1 file changed, 1 insertion(+), 1 deletion(-)

13.Git自动给dev分支做了一次提交,注意这次提交的commit是1d4b803,它并不同于master的4c805e2,因为这两个commit只是改动相同,但确实是两个不同的commit。

14.既然可以在master分支上修复bug后,在dev分支上可以“重放”这个修复过程,那么直接在dev分支上修复bug,然后在master分支上“重放”行不行?当然可以,不过你仍然需要git stash命令保存现场,才能从dev分支切换到master分支。

Feature分支

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

概念

如下场景,你在一个新的分支完成了某一项工作,当你要将这个工作分支合入主分支的时候,你接到命令停止合入并要求销毁分支.
1.直接删除分支

$ 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'.

销毁失败。Git友情提醒,feature-vulcan分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用大写的-D参数。。
2.强行删除

$ git branch -D feature-vulcan
Deleted branch feature-vulcan (was 287773e).

小结

开发一个新feature,最好新建一个分支;

如果要丢弃一个没有被合并过的分支,可以通过git branch -D <name>强行删除。

多人协作

多人协作

当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。
1.要查看远程库的信息,用git remote

$ git remote
origin

2.或者,用git remote -v显示更详细的信息:

$ git remote -v
origin  git@github.com:michaelliao/learngit.git (fetch)
origin  git@github.com:michaelliao/learngit.git (push)

上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。

推送分支

推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:
1.推送分支

$ git push origin master

2.如果要推送其他分支,比如dev,就改成:

$ git push origin dev

3.但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?

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

抓取分支

1.当你的小伙伴从远程库clone时,默认情况下,你的小伙伴只能看到本地的master分支。不信可以用git branch命令看看:

$ git branch
* master

2.现在,你的小伙伴要在dev分支上开发,就必须创建远程origin的dev分支到本地,于是他用这个命令创建本地dev分支:

$ git checkout -b dev origin/dev

现在,他就可以在dev上继续修改,然后,时不时地把dev分支push到远程:
3.你的小伙伴已经向origin/dev分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送:

$ git add env.txt

$ git commit -m "add new env"
[dev 7bd91f1] add new env
 1 file changed, 1 insertion(+)
 create mode 100644 env.txt

$ git push origin dev
To github.com:michaelliao/learngit.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to 'git@github.com:michaelliao/learngit.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

4.推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送:

$ git pull
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

5.git pull也失败了,原因是没有指定本地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'.

6.再pull:

$ git pull
Auto-merging env.txt
CONFLICT (add/add): Merge conflict in env.txt
Automatic merge failed; fix conflicts and then commit the result.

7.这回git pull成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提交,再push:

$ git commit -m "fix env conflict"
[dev 57c53ab] fix env conflict

$ git push origin dev
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 621 bytes | 621.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
   7a5e5dd..57c53ab  dev -> dev

8.总结
因此,多人协作的工作模式通常是这样:

  1. 首先,可以试图用git push origin 推送自己的修改;

  2. 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;

  3. 如果合并有冲突,则解决冲突,并在本地提交;

  4. 没有冲突或者解决掉冲突后,再用git push origin 推送就能成功!

  5. 如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <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,如果有冲突,要先处理冲突。

Rebase

概念

1.我们经常需要git pullgit push,会导致我们的log很混乱

$ git log --graph --pretty=oneline --abbrev-commit
* 582d922 (HEAD -> master) add author
* 8875536 add comment
* d1be385 (origin/master) init hello
*   e5e69f1 Merge branch 'dev'
|\  
| *   57c53ab (origin/dev, dev) fix env conflict
| |\  
| | * 7a5e5dd add env
| * | 7bd91f1 add new env
...

注意到Git用(HEAD -> master)和(origin/master)标识出当前分支的HEAD和远程origin的位置分别是582d922 add author和d1be385 init hello,本地分支比远程分支快两个提交。
2.现在我们尝试推送本地分支:

$ git push origin master
To github.com:michaelliao/learngit.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'git@github.com:michaelliao/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.

3.很不幸,失败了,这说明有人先于我们推送了远程分支。按照经验,先pull一下:

$ git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:michaelliao/learngit
   d1be385..f005ed4  master     -> origin/master
 * [new tag]         v1.0       -> v1.0
Auto-merging hello.py
Merge made by the 'recursive' strategy.
 hello.py | 1 +
 1 file changed, 1 insertion(+)

4.再用git status看看状态:

$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

加上刚才合并的提交,现在我们本地分支比远程分支超前3个提交。
5.用git log看看:

$ git log --graph --pretty=oneline --abbrev-commit
*   e0ea545 (HEAD -> master) Merge branch 'master' of github.com:michaelliao/learngit
|\  
| * f005ed4 (origin/master) set exit=1
* | 582d922 add author
* | 8875536 add comment
|/  
* d1be385 init hello
...

对强迫症童鞋来说,现在事情有点不对头,提交历史分叉了。如果现在把本地分支push到远程,有没有问题?
有!
什么问题?
不好看!
6.这个时候,rebase就派上了用场。我们输入命令git rebase试试:

$ git rebase
First, rewinding head to replay your work on top of it...
Applying: add comment
Using index info to reconstruct a base tree...
M	hello.py
Falling back to patching base and 3-way merge...
Auto-merging hello.py
Applying: add author
Using index info to reconstruct a base tree...
M	hello.py
Falling back to patching base and 3-way merge...
Auto-merging hello.py

7.再用git log看看:

$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master) add author
* 3611cfe add comment
* f005ed4 (origin/master) set exit=1
* d1be385 init hello
...

原本分叉的提交现在变成一条直线了!这种神奇的操作是怎么实现的?其实原理非常简单。我们注意观察,发现Git把我们本地的提交“挪动”了位置,放到了f005ed4 (origin/master) set exit=1之后,这样,整个提交历史就成了一条直线。rebase操作前后,最终的提交内容是一致的,但是,我们本地的commit修改内容已经变化了,它们的修改不再基于d1be385 init hello,而是基于f005ed4 (origin/master) set exit=1,但最后的提交7e61ed4内容是一致的。
8.最后,通过push操作把本地分支推送到远程:

Mac:~/learngit michael$ git push origin master
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 576 bytes | 576.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 1 local object.
To github.com:michaelliao/learngit.git
   f005ed4..7e61ed4  master -> master

9.再用git log看看效果:

$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master, origin/master) add author
* 3611cfe add comment
* f005ed4 set exit=1
* d1be385 init hello
...

远程分支的提交历史也是一条直线。

小结

  • rebase操作可以把本地未push的分叉提交历史整理成直线;
  • rebase的目的是使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比。

标签管理

发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。

Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。

创建标签

概念

1.切换到需要打标签的分支上:

$ git branch
* dev
  master
$ git checkout master
Switched to branch 'master'

2.敲命令git tag <name>就可以打一个新标签:

$ git tag v1.0

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

$ git tag
v1.0

3.默认标签是打在最新提交的commit上的。有时候,如果忘了打标签,比如,现在已经是周五了,但应该在周一打的标签没有打,怎么办?
方法是找到历史提交的commit id,然后打上就可以了:

$ git log --pretty=oneline --abbrev-commit
12a631b (HEAD -> master, tag: v1.0, origin/master) merged bug fix 101
4c805e2 fix bug 101
e1e9c68 merge with no-ff
f52c633 add merge
cf810e4 conflict fixed
5dc6824 & simple
14096d0 AND simple
b17d20e branch test
d46f35e remove test.txt
b84166e add test.txt
519219b git tracks changes
e43a48b understand how stage works
1094adb append GPL
e475afc add distributed
eaadf4e wrote a readme file

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

$ git tag v0.9 f52c633

再用命令git tag查看标签:

$ git tag
v0.9
v1.0

5.标签不是按时间顺序列出,而是按字母排序的。可以用git show <tagname>查看标签信息:

$ git show v0.9
commit f52c63349bc3c1593499807e5c8e972b82c8f286 (tag: v0.9)
Author: Michael Liao <askxuefeng@gmail.com>
Date:   Fri May 18 21:56:54 2018 +0800

    add merge

diff --git a/readme.txt b/readme.txt
...

可以看到,v0.9确实打在add merge这次提交上。
6.还可以创建带有说明的标签,用-a指定标签名,-m指定说明文字:

$ git tag -a v0.1 -m "version 0.1 released" 1094adb

用命令git show <tagname>可以看到说明文字:

$ git show v0.1
tag v0.1
Tagger: Michael Liao <askxuefeng@gmail.com>
Date:   Fri May 18 22:48:43 2018 +0800

version 0.1 released

commit 1094adb7b9b3807259d8cb349e7df1d4d6477073 (tag: v0.1)
Author: Michael Liao <askxuefeng@gmail.com>
Date:   Fri May 18 21:06:15 2018 +0800

    append GPL

diff --git a/readme.txt b/readme.txt
...

7.注意:标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。

小结

  • 命令git tag <tagname>用于新建一个标签,默认为HEAD,也可以指定一个commit id;

  • 命令git tag -a <tagname> -m "blablabla..."可以指定标签信息;

  • 命令git tag可以查看所有标签。

操作标签

概念

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

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

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

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

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

$ git push origin --tags
Total 0 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
 * [new tag]         v0.9 -> v0.9

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

$ git tag -d v0.9
Deleted tag 'v0.9' (was f52c633)

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

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

要看看是否真的从远程库删除了标签,可以登陆GitHub查看。
5.要切换到已有标签快照

git checkout tag_name 

结果

$ git checkout 复杂密码
Note: switching to '复杂密码'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 2c5e9f9 新增接口logsystem_get_sample_application

6.从tag切回分支

git branch -a
git checkout  分支名

小结

  • 命令git push origin <tagname>可以推送一个本地标签;

  • 命令git push origin --tags可以推送全部未推送过的本地标签;

  • 命令git tag -d <tagname>可以删除一个本地标签;

  • 命令git push origin :refs/tags/<tagname>可以删除一个远程标签。

小结

实用技巧

help

1.git config不加参数回车会在终端界面显示help。其他命令同理
2.git config --help会在浏览器打开help文档.其他命令同理

git add命令里的’.‘和’-u’的作用

概念

我通常是通过git add 的形式把我们 添加到索引库中, 可以是 文件也可以是 目录
git不仅能判断出 中,修改(不包括已删除)的文件,还能判断出新添的文件,并把它们的信息添加到索引库中。

git add .

1.添加当前目录和子目录下相应(新增、更新、删除)文件。并且会根据.gitignore做过滤。

git add -u

仅监控已经被add的文件(即tracked file)
1.添加当前目录和子目录下相应(更新、删除)文件。
2.git add -u [<path>]: 把 中所有tracked文件中被修改过或已删除文件的信息添加到索引库。 它不会处理untracted的文件
3.省略 表示.,即当前目录。
4. 仅监控已经被add的文件(即tracked file),他会将被修改的文件提交到暂存区。add -u 不会提交新文件(untracked file)。(git add --update的缩写)

git add -A

1.将文件的修改,文件的删除,文件的新建,添加到暂存区。
2.是上面两个功能的合集(git add --all的缩写),提交所有包括被删除的文件信息,被替换的文件信息、被修改的文件以及新增的文件到暂存区其中删除。修改以及新增文件到缓存区和git add .和git add -u是相同的,同时具有他们的功能,还具有替换的文件的功能

git add *

1.将当前目录下所有文件添加到暂存区,包括文件的修改,文件的新建
2.此命令类似于 “git add .”,区别在于上面几个命令都会根据.gitignore做过滤,但是git add * 会忽略.gitignore把任何文件都加入(不包含删除掉的文件)

输入命令后,会显示一些提示信息或者操作指示

$ 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自动提示我们当前master分支比远程的master分支要超前1个提交。

Git Cheat Sheet

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值