Git最全知识汇总,妈妈再也不用担心我了

GIT版本

$ git version
git version 2.9.0.windows.1

1仓库

创建本地仓库有2种形式,可以直接采用 《1.2 直接克隆一个远端库至本地》简单明了。

1.1 本地创建空库,并为空库绑定至远端库

1.选定某个目录作为工程目录,进入该目录并执行

git init

目录下多个一个.git目录

2.绑定当前目录至远程仓库

git remote add origin git@gitlab.test.com.cn:100/git_tutorial.git

.git/config文件多个一个remote和branch参数

 [remote "origin"]
	url = git@gitlab.test.com.cn:100/git_tutorial.git
	fetch = +refs/heads/*:refs/remotes/origin/*
 [`branch` "master"]
  `remote` = origin
   merge = refs/heads/master

1.2 直接克隆一个远端库至本地

$ git clone ssh://git@gitlab.test.com.cn:9527/zhansan/my-test-project.git
Cloning into 'my-test-project'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.
Checking connectivity... done.

自动创建.git目录,并为.git/confg增加remote和branch参数

在这里插入图片描述
自动创建了一个master的本地分支。

1.3 查看仓库

当你从远程仓库克隆后,Git 会自动为你将此远程仓库命名为origin,并下载其中所有的数据,建立一个指向它的 master 分支的指针,在本地命名为 origin/master,但你无法在本地更改其数据。接着,Git 建立一个属于你自己的本地master 分支,始于 origin上 master 分支相同的位置,也就是说,Git自动把本地的master分支和远程的master分支对应起来,你可以就此开始工作

要查看远程库的信息,用git remote

$ git remote
origin

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

$ git remote -v
origin  git@gitlab.test.com.cn:100/git_tutorial.git (fetch)
origin  git@gitlab.test.com.cn:100/git_tutorial.git (push)

2. 分支操作

2.1 创建本地分支

git branch develop

2.2 查看本地分支

git branch

在这里插入图片描述
注:名称前面加* 号的是当前的分支

2.3 查看远程分支

git branch -a

在这里插入图片描述

2.4 切换分支

git checkout branch_name

2.5 删除本地分支

git branch -d branch_name

2.6 删除远程分支

git branch -r -d origin/branch-name    //删除本地分支,可以不删
git push origin :branch-name   //真正生效的参数

2.7 如果远程新建了一个分支,本地没有该分支

可以利用 git checkout --track origin/branch_name ,这时本地会新建一个分支名叫 branch_name ,会自动跟踪(或追踪)远程的同名分支 branch_name。

有个重要名词,“追踪分支”或“跟踪分支”,简单来说就是本地的某个分支和远端分支拥有绑定关系,已经建立一个连接,否则,git push简约命令 (后面不带<远程主机名> <本地分支名>:<远程分支名>)操作无法执行,详细可以参考《追踪分支概念》

远程新建了branch_1分支,而本地暂时还没有:
在这里插入图片描述
执行:

$ git co --track origin/branch_1
Branch branch_1 set up to track remote branch branch_1 from origin.
Switched to a new branch 'branch_1'

再次查看结果,本地已经有了branch_1:
在这里插入图片描述
疑问:当我没用--track 参数,但是也能成功,稍有区别是不能带origin,否则失败,有时间再研究下:

 git co branch_1  //注意不是origin/branch_1

2.8 如果本地新建了一个分支 branch_name,但是在远程没有

有个重要名词,“追踪分支”或“跟踪分支”,简单来说就是本地的某个分支和远端分支拥有绑定关系,已经建立一个连接,否则,git push简约命令 (后面不带<远程主机名> <本地分支名>:<远程分支名>)操作无法执行,详细可以参考《追踪分支概念》

本地新建一个分支branch_2,远程尚未有branch_2分支,执行push时报错:

$ git br branch_2       //本地新建一个分支branch_2      
$ git co branch_2             
Switched to branch 'branch_2'

$ git push         //推送
fatal: `The current branch branch_2 has no upstream branch`.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin branch_2

原因分析:
执行push命令的前提是拥有“追踪分支”关系,我们可以查看已经存在的“追踪分支”关系列表,包含master分支和branch_1分支,在.git/confg文件下有branch 参数,显然没有branch_2分支项:

[branch "master"]
	remote = origin
	merge = refs/heads/master
[branch "branch_1"]
	remote = origin
	merge = refs/heads/branch_1

解决方案:
git push --set-upstream origin branch_name
或简称 git push -u origin branch_name

这样就可以自动在远程创建一个 branch_name 分支,然后本地分支会 track 该分支。后面再对该分支使用 push 和 pull 就自动同步。

$ git push --set-upstream origin branch_2
Total 0 (delta 0), reused 0 (delta 0)
remote: Enforcing Policies...
remote: (refs/heads/branch_2) (000000) (5484a1)
remote: fatal: ..: '..' is outside repository
remote: 0
To ssh://git@gitlab.test.com.cn:9527/100/my-test-project.git
 * [new branch]      branch_2 -> branch_2
Branch branch_2 set up to track remote branch branch_2 from origin.

再次查看,发现远端多个一个remotes/origin/branch_2
在这里插入图片描述
并且.git/confg文件下有branch 参数:

[branch "branch_2"]
	remote = origin
	merge = refs/heads/branch_2

2.7和2.8为我们引入了 跟踪分支概念

3. 基本操作

在这里插入图片描述

3.1一个简单的流程

# 编辑y一个文件
vim GIT.md

# 查看状态
git status

# 添加文件至 staging区
git add GIT.md

# 提交至本地仓库
git commit -m 'new file GIT.md'

# 查看提交记录
git log

# 推送至远端库
git push origin master

如果提交全部文件,使用git add .

3.1.1推送分支

就是把该分支上的所有本地提交推送到远程库。通常使用git push命令,该命令配合git pull命令的使用即可实现本地仓库与远程仓库的数据同步。

推送模板:
$ git push <远程主机名> <本地分支名>:<远程分支名>

远程主机名 是啥 ?是个名称,映射远端的库;具体参见 Git 里面的 origin 到底代表啥意思?

注意,分支推送和拉取的写法模板是<来源地>:<目的地>,即永远从左往右拷贝

  • git push 把本地推送至远端,本地是左,远端是右,对应<本地分支>:<远程分支>
  • git pull 从远端拉取至本地,远端是左,本地是右,对应<远程分支>:<本地分支>

给大家普及一个概念,什么是最佳习惯?一般情况下,我们的本地分支和远程分支是存在绑定关系的,尽量让名称也一致,提交代码时,在某个本地分支A创建的提交记录a,只会推送到远程分支A上,不要跨分支提交,比如如果想把a推送到远程分支B上,是不可取的。

2.7和2.8为我们引入了 跟踪分支概念,跟踪分支是执行git push 和git pull的前提条件,一般情况下,标准用法很好理解,下面我们来看看简写或特殊语法,容易混淆

简写或特殊语法

例1 省略一个分支名

与推送的标准模板进行对比,发现可以省略一个分支名,并且中间的也一并省略掉。

$ git push origin master

上面命令表示,将本地master分支推送到远程主机originmaster分支。如果后者不存在,则会被新建。

上面命令是简写,本质上等价于:

$ git push origin master:master

即<本地分支>=<远程分支>时,只填写一个就行

注意:如果远程master分支不存在,会在远程新建一个master分支,但是本地master的和远程的master不会产生追踪关系,好多文章的描述是错误的。

我们来验证一下在远端自动生成分支:

$ git br local_1      # 本地创建一个新的分支

$ git br -a       # 查看本地和远程所有分支
 `branch_1`       # 发现local_1只在本地存在
* branch_2         # *号表示当前所在的分支
  local_1
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/branch_1
  remotes/origin/branch_2
  remotes/origin/master


$ git push origin local_1    # 推送,省略远程分支名
Total 0 (delta 0), reused 0 (delta 0)
remote: Enforcing Policies...
remote: (refs/heads/local_1) (000000) (5484a1)
remote: fatal: ..: '..' is outside repository
remote: 0
To ssh://git@gitlab.test.com.cn:9527/100/my-test-project.git
 * [new branch]      local_1 -> local_1

$ git br -a      # 再次查看本地和远程所有分支
  branch_1
* branch_2
  `local_1`      # 发现local_1在本地存在
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/branch_1
  remotes/origin/branch_2
  `remotes/origin/local_1`   # 远程也存在了
  remotes/origin/master

如何证明本地的local_1和远程的local_1这两个分支没有追踪关系呢,我们来看下.git/confg文件,发现没有local_1项!

注意:省略远程分支名时连冒号也省略,下面这种形式不伦不类,执行会报错,其实也好理解,我们记得原理是左侧覆盖右侧,那么右侧为空,表明没有目标,肯定不合法

git push origin local_1:    //带冒号的写法
fatal: remote part of refspec is `not a valid name` in local_1:

例2:省略本地分支名

如果省略本地分支名,则表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支,注意冒号必须存在,否则就是例1场景了。

$ git push origin :master

等同于:

$ git push origin --delete master

上面命令表示删除origin主机的master分支。


例3:存在追踪关系

如果当前分支与远程分支之间存在追踪关系,则本地分支和远程分支都可以省略。

$ git push origin

上面命令表示,将当前分支推送到origin主机的对应分支。

如果当前是develop分支,则等价于:

$ git push origin develop:develop

很好理解,很多架构都有“约定优于配置”的理念,如果当前分支与远程的建立了追踪关系,git push时,就可以把当前分支映射至标准模块内。


例4:只有一个主机存在追踪关系

如果当前分支只有一个主机存在追踪分支,那么主机名都可以省略。

$ git push

例5:多个主机存在追踪关系

如果当前分支与多个主机存在追踪关系,则可以使用-u选项指定一个默认主机,这样后面就可以不加任何参数使用git push

git push --set-upstream  origin master
git push -u origin master   //简写

上面命令将本地的master分支推送到origin主机,同时指定origin为默认主机,后面就可以不加任何参数使用git push了。

这里是 -u的第二种用法 ,2.8章节是第一种用法。


例6:推送时多个分支的默认处理方式

不带任何参数的git push,默认只推送当前分支,这叫做simple方式。此外,还有一种matching方式,会推送所有有对应的远程分支的本地分支。Git 2.0版本之前,默认采用matching方法,新版改为默认采用simple方式。

可以查看当前的类型:

[root]$  git config -l|grep  push.default
push.default=matching

如果要修改这个设置,可以采用git config命令。

$ git config --global push.default matching
或者
$ git config --global push.default simple

验证一下过程,假设当前存在2个追踪分支,并且只存在一个主机,发现只有branch_2推送至远程库中:

$ git co branch_1   //切到branch_1
$ vi branch_1.txt      
$ git add .
$ git ci -m 'branch_1分支产生一次提交'   产生一个commit


$ git co branch_2    //切至branch_2
$ vi branch_2.txt
$ git add .
$ git ci -m 'branch_2分支产生一次提交'   产生一个commit
$ git push   #  推送,省略了分支参数
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 336 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Enforcing Policies...
remote: (`refs/heads/branch_2`) (5484a1) (f7852f)        //`只有branch_2推送成功`
remote: fatal: ..: '..' is outside repository
remote: 0
To ssh://git@gitlab.test.com.cn:9527/100/my-test-project.git
   5484a17..f7852fa  `branch_2 -> branch_2`


我们修改为 git config --global push.default matching,果然2个不同的分支同时提交了:

重复之前的步骤
$ git push       //`推送`
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 701 bytes | 0 bytes/s, done.
Total 6 (delta 0), reused 0 (delta 0)
remote: Enforcing Policies...
remote: (`refs/heads/branch_1`) (9f1955) (d08738)
remote: fatal: ..: '..' is outside repository
remote: 0
remote: Enforcing Policies...
remote: (`refs/heads/branch_2`) (f7852f) (bd28e0)
remote: fatal: ..: '..' is outside repository
remote: 0
To ssh://git@gitlab.test.com.cn:9527/100/my-test-project.git
   9f19555..d087383  `branch_1 -> branch_1`     # 分支1提交
   f7852fa..bd28e01  `branch_2 -> branch_2`	   # 分支2提交

例7 推送全部分支

还有一种情况,就是不管是否存在对应的远程分支,将本地的所有分支都推送到远程主机,这时需要使用--all选项。
$ git push --all origin
上面命令表示,将所有本地分支都推送到origin主机。

没仔细研究过,我猜测左右是和例6相关,比如你的当前提交方式是simple,但是你又想临时的提交全部分支的改动,那么很麻烦的方式是修改提交方式为matching,提交后再改回来;这时,可以通过--all命令强制提交本地的,不用改来改去的。


例8 强制提交(可以直接跳过,一般不建议用)

如果远程主机的版本比本地版本更新,推送时Git会报错,要求先在本地做git pull合并差异,然后再推送到远程主机。这时,如果你一定要推送,可以使用--force选项。

$ git push --force origin

上面命令使用 --force选项,结果导致在远程主机产生一个”非直进式”的合并(non-fast-forward merge)。除非你很确定要这样做,否则应该尽量避免使用--force选项

3.1.2更新本地分支

git push是将本地分支的提交推送到远程分支,而要想从远程分支下载其他人的最新提交,则可以使用git fetch或者git pull命令。

git fetch

一旦远程主机的版本库有了更新(Git术语叫做commit),需要将这些更新取回本地,这时就要用到git fetch命令:

$ git fetch <远程主机名>

上面命令将某个远程主机的更新,全部取回本地。


默认情况下,git fetch取回所有分支(branch)的更新。如果只想取回特定分支的更新,可以指定分支名:

$ git fetch <远程主机名> <分支名>

比如,取回origin主机的master分支:

$ git fetch origin master

git fetch所取回的更新,在本地主机上要用"远程主机名/分支名"的形式读取。比如origin主机的master,就要用origin/master读取。

git merge

但是需要注意的是,git fetch只是将远程分支的更新取回本地,并不会与本地与之存在追踪关系的本地分支合并,如果要合并,需要用git merge 命令。

比如
$ git merge origin/master
表示将本地的master分支与取回的远程分支origin/master合并。

git pull

git pull命令的功能则相当于git fetch与git merge的合并,即取回远程主机某个分支的更新,再与本地的指定分支合并,模板为:

$ git pull <远程主机名> <远程分支名>:<本地分支名>


比如,取回origin主机的master分支,与本地的master分支合并,需要写成下面这样。
$ git pull origin master:master

如果远程分支是与当前分支合并,则冒号后面的部分可以省略。

$ git pull origin master

上面命令表示,取回origin/master分支,再与当前分支合并。


如果当前分支与远程分支存在追踪关系,git pull就可以省略远程分支名。

$ git pull origin

上面命令表示,本地的当前分支自动与对应的远程追踪分支进行合并。


如果当前分支只有一个追踪分支,连远程主机名都可以省略。

$ git pull

3.2 查看变更

# 查看本地变化,列出经git status显示为红色的文件的变化
git diff

# index变化,列出经git status显示为绿色的文件的变化
git diff --cached

# 查看某个提交的改动点,包含详细内容
git show COMMITID

# 2次提交改动点,包含详细内容 ,COMMITID1 提交时间在前,COMMITID2提交时间在后,注意不要反了
git diff COMMITID1 COMMITID2

#2次提交涉及的文件列表,不包含详细内容
git diff --stat  COMMITID1 COMMITID2

4. 撤销操作

4.1 撤销本地修改

回退 git status 红色文件

$ git status
 (use "git checkout -- <file>..." to discard changes in working directory)
   modified:   a.txt
   
$ git checkout  --a.txt

如果要撤销全部红色文件,使用点号,表示当前目录的全部文件:

$ git checkout .

4.2 回退暂存区

回退 git status 绿色文件,再通过git status查看会变为红色文件

$ git reset HEAD <file> 
或 $ git reset file    //也可省略HEAD

在这里插入图片描述

如果回退全部,可以用 点号:

git reset .

4.3 撤销提交

4.3.1彻底回退到某个版本,本地的源码也会变为上一个版本的内容;git status查看,内容为空:

git reset --hard PREVIOUS_COMMINTID

4.3.2 仅回退版本库(commit),到某个版本,改动点在暂存区 ,代码保留在绿色区域:

git reset --soft PREVIOUS_COMMINTID

比如在commit1之后产生commit2和commit3,当执行回退命令至commit1后,commit2和commit3的改动都会出现在暂存区:


$ vi b.txt     //修改b.txt
$ git add b.txt
$ git ci -m 'commit2'     //产生提交commit2
$ vi c.txt    //新增c.txt
$ git add c.txt 
$ git ci -m 'commit3'     //产生提交commit3
$ git reset --soft commit1ID    //回退至commit1

再次查看,commit2和commit3的改动都会出现在暂存区:
在这里插入图片描述

4.3.3 回退版本库(commit)和暂存区(绿色),改动点保存在工作区,代码变在红色区域

git reset --mixed PREVIOUS_COMMINTID

如果仍然按照4.3.2中的步骤,比如在commit1之后产生commit2和commit3,当执行回退命令(--mixed )至commit1后,commit2和commit3的改动都会出现在工作区,通过git status查看,代码为红色的:

在这里插入图片描述

5 查看提交日志

# get all commit logs
git log

# get 4 commit logs
git log -4

# get commit stat
git log --stat

# get commit detail
git log -p

# 修改排版,更容易查看提交的记录
git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) \
    %C(bold blue)<%an>%Creset' --abbrev-commit --

6. 分支–创建、删除与查看

#新建分支feature/bugfix
git branch feature/bugfix

#切至feature/bugfix分支,如果该分支不存在,则新建
git checkout -b feature/bugfix [start_point]

# 删除本地分支feature/bugfix
git branch -D feature/bugfix
# 删除远程分支
git push origin :feature/bugfix
# 查看本地分支列表
git branch 
# 查看全部分支列表,包含远程
git branch -a

7. 配置

#查看当前配置
git config -l 
或git config --list
# username and email,首次提交代码之前需要设置name和email
git config --global user.name "xx"
git config --global user.email xx@test.com.cn

# color
git config --global color.ui true

别名

git config --global alias.ci commit
git config --global alias.co checkout
git config --global alias.st status
git config --global alias.br branch
git config --global alias.df diff
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"

# get pretty logs
git lg

# list branchs
git br

忽略
仓库根目录新建.gitignore文件,将不需要加入到仓库的目录和文件配置在此文件中

$ cat .gitignore
cscope.*
*.o
*.obj
/build/
/target/
/tcfs/

8. cherry-pick

挑选出想要的某次提交,进行合并至当前分支

git cherry-pick COMMITID

9. reflog

查看HEAD的指针变化

git reflog

git reset --hard reflog_id

10. 暂存

#查看本地暂存列表 
git stash list 
#弹出最近放入的一条数据,类似栈原理,记录会从栈中删除
git stash  pop
#弹出指定放入的数据,类似栈原理,记录会从栈中删除
git stash  pop stash@{1}
#从栈中复制最近的一条记录,不会从栈中删除
git stash apply 
#从栈中复制指定的记录,不会从栈中删除
git stash apply stash@{1}
#丢弃某个记录
git stash drop xxx
# 存入当前工作区的改动 ,默认描述为commitID 和该次提交的msg 
  stash@{0}: WIP on master: 115f011 update name
git stash 
# 存入当前工作区的改动 ,并指定msg;message可以加单引号,也可以不加
git stash save message

注意:git stash 只能加入Modify类型的文件,而新增的文件不会放进去,比如修改了一个a.txt,新增了一个b.txt,那么执行命令后,b.txt仍在工作区

清除本地未追踪的文件

git clean -f -d

11. gerrit使用

refs/for/<branch_name>
git push时在原有的分支上拼接refs/for

12. 进阶知识

本工作流建议禁用使用pull指令,使用fetch和rebase指令替代

12.1 git rebase

git rebase负责git push之前,把本地commit和别人已经提交的commit进行合并,并把自己的commit置于顺序图root节点,保证提交记录没有分叉;只有1条主干节点
1)本地代码已经是最新代码,只需考虑是否合并本地的多次提交
修改文件,将本地修改提供到本地仓库

git commit -am ‘for feature’ //2f89fc6
git commit -am ‘for feature1’ //fa15458
git commit -am ‘for feature2’ //acdace4

把3次commit合并成一个提交

git rebase -i origin/master

# 编辑如下文档,只保留最上面的commit为pick,其它编辑为f
pick acdace4 test1
f    fa15458 test2
f    2f89fc6 test3

全部命令列表

# Commands:
# p, pick = use commit   
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message  
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit

2)本地代码不是最新代码,需要从远端更新代码,执行
git fetch ,建议禁用git pull

git fetch
git rebase origin/master

上面的2个命令等价于 git pull --rebase

期间可能出现冲突
it rebase --continue 修复完冲突后,继续合并冲突的流程
git rebase --abort 冲突解决不了,放弃此次操作,回退至冲突之前的代码

12.2 合并变动至上一次提交

一般发生改动后,通过git commit -m message 来产生新的提交;如果提交后,发现一个bug,需要合入上次提交,否则又要新产生一个commitId

git commit --amend 

13. gerrit中经常碰到的一些问题

1.Abandon产生no new changes

登录到gerrit网络,找到自己想要到丢弃的review,点击Abandon按钮
丢弃过review后再次review提交时,会报如下错误

modify a.txt
git add a.txt
git commit -m 'c1'  /产生一次提交
git push //推送至gerrit评审
abandon该次提交
git push      //突然又想保留这次提交了, 再次推送至gerrit评审
remote: Processing changes: refs: 1, done
To ssh://id@gerrit.test.com.cn:29418/xx/xxxx
 ! [remote rejected] HEAD -> refs/publish/master (no new changes)
  error: failed to push some refs to 'ssh://id@gerrit.test.com.cn:29418/xx/xxxx'

2.Abandon产生change closed

modify a.txt
git add a.txt
git commit -m 'c1'  /产生一次提交
git push //推送至gerrit评审
abandon该次提交
modify b.txt
git add b.txt
git ci --amend   //复用上次提交
git push      //再次推送至gerrit评审
 ! [remote rejected] HEAD -> refs/for/develop (change http://gerrit.test.com.cn/5394258 closed)
error: failed to push some refs to 'ssh://xx@gerrit.test.com.cn:100/xxx'

遇到上述问题后,需要删除原来提交中的Change-Id

git commit --amend
# 将提交中的下行Change-Id删除掉
Change-Id: If5774e6af6499aa04433f0e9c769a8f965f27fa7

3.abandon提交后,新的提交报依赖问题

注意:Abandon后,再次commit后,一定要将新的commit和abandon的commit做提交合作(git rebase -i origin/master)

modify a.txt
git add a.txt
git commit -m 'c1'  /产生一次提交
git push //推送至gerrit评审
abandon该次提交
modify b.txt
git add b.txt
git ci -m 'c2'   //产生新提交
git push      //再次推送至gerrit评审

结果是可以正常提交,但是评审界面发现c2依赖c1,导致流程审核失败






参考:git push 使用教程
Git本地分支和远程分支关联


版权声明:本文为m0_45406092原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。