文章目录
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
分支推送到远程主机origin
的master
分支。如果后者不存在,则会被新建。
上面命令是简写,本质上等价于:
$ 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,导致流程审核失败