开始学习Git了,为了督促学习同时保证后续学习资料我使用随笔记录,因为考虑到后续学习,可能会稍微详细记录一些.
笔记到本次修改为止没有参考官方文档,依据的是第三方教程和自己的理解应该会有些错误,欢迎指正,谢谢.
如果有时间我将会为常见操作用例补上流程图.
第一个标题给出常用git命令.
待完善…
前置
Git常用命令
随着自己Git的使用而添加命令
在当前目录创建仓库:
git init
显示隐藏目录:ls -ah
将所有文件添加到版本库:git add .
将已经添加的文件提交到仓库:git commit -m "本次提交的说明"
查看当前仓库状态:git status
查看文件更改细节:git diff
查看看提交日志:git log
可选参数:(控制显示条数)-n
(简化显示内容)--pretty=oneline
退出当前操作:q
Git常见注意事项
没有消息就是最大的好消息(前提是你没有卡死),Git很多命令都没有返回消息(类似Linux)
版本控的制差异
直接参看图片更加直观
集中式
使用中心服务器,所有终端用户只能,从服务器交换文件.例如SVN
分布式
每个电脑都是一个版本库,可以相互交换文件,通常为了方便依然需要一个主机充当服务器.
创建版本库
Git文件的添加需要add
和commit
两步,commit
可以一次提交很多文件,add
可以多次添加不同的文件,相当于你慢慢的吧文件放到了车上,一次运走.
文件添加到仓库
使用git add
命令,例如git add readme.txt
,执行后如果没有任何消息就表示这个文件已经被添加到了仓库.
这个文件一定要在仓库的目录下,子目录也可以
文件提交到仓库
使用git commit -m "本次提交说明"
,例如git commit -m "这是我提交的基础文件"
产生如下返回消息1
2
3
4$ git commit -m "这是我提交的第一个文件"
[master (root-commit) f34a8b0] 这是我提交的第一个文件
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 readme.txt
这里,file changed
代表被改变的文件数;insertions
代表插入内容的行数
查看修改
修改一下readme.txt文件,输入git status
命令1
2
3
4
5
6
7
8
9$ git status
On branch master //在主分支
Changes not staged for commit: //更改未提交
(use "git add <file>..." to update what will be committed) //使用git add 准备提交
(use "git checkout -- <file>..." to discard changes in working directory) 使用git checkout 放弃更改
modified: readme.txt //修改: readme.txt
no changes added to commit (use "git add" and/or "git commit -a") //没有将更改添加到提交
大致意思就是readme.txt被修改过了,但是还没有准备提交修改.
可以看到,git status
可以查询当前哪些文件被修改过了,但是无法查看具体修改了哪些内容.
插看具体的修改内容使用git diff
:1
2
3
4
5
6
7
8
9$ git diff readme.txt
diff --git a/readme.txt b/readme.txt //a版本的readme文件(变动前),和b版本的readme文件(变动后)
index 46d49bf..9247db6 100644 //两个版本的git哈希值(46d49bf,9247db6) 100644第三个是对象的模式(普通文件,664权限)
--- 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.
查看后就可以使用git add
添加到仓库(同添加新文件),然后运行git status
查看当前仓库状态1
2
3
4
5
6$ git status
On branch master
Changes to be committed: //要提交的更改
(use "git reset HEAD <file>..." to unstage)
modified: readme.txt //包括的文件
返回消息列出了将要被提交的文件
提交1
2
3$ git commit -m "add distributed"
[master e475afc] add distributed
1 file changed, 1 insertion(+), 1 deletion(-) //一个文件改变,添加一行,删除一行
继续使用git status
查看仓库当前状态1
2
3$ git status
On branch master
nothing to commit, working tree clean //没有提交,很干净? (通常我不希望看到这种返回消息)
当前没有需要提交的修改,工作目录很干净
版本回退
查看版本
首先修改文件
然后提交1
2
3
4$ git add readme.txt
$ git commit -m "append GPL"
[master 1094adb] append GPL
1 file changed, 1 insertion(+), 1 deletion(-)
像这样,你不断对文件进行修改,然后不断提交修改到版本库里,就好比玩RPG游戏时,每通过一关就会自动把游戏状态存盘,如果某一关没过去,你还可以选择读取前一关的状态。有些时候,在打Boss之前,你会手动存盘,以便万一打Boss失败了,可以从最近的地方重新开始。Git也是一样,每当你觉得文件修改到一定程度的时候,就可以“保存一个快照”,这个快照在Git中被称为commit。一旦你把文件改乱了,或者误删了文件,还可以从最近的一个commit恢复,然后继续工作,而不是把几个月的工作成果全部丢失。
继续,使用git log
查看最近到最远的提交日志.可以用git log -n
来控制显示条数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18$ git log
commit 58e1bcb1947d683e0683ac1115900056c3f4cabd (HEAD -> master)
Author: e1se <elsewhere997@163.com>
Date: Thu Jul 19 11:33:44 2018 +0800
修改了两行
commit e0e571805168d270182a8ada5d712b838d07ce34
Author: e1se <elsewhere997@163.com>
Date: Wed Jul 18 22:54:21 2018 +0800
添加了一行
commit f34a8b0d286590117d0d08e041c68a549ce4a1db
Author: e1se <elsewhere997@163.com>
Date: Wed Jul 18 19:46:34 2018 +0800
这是我提交的第一个文件
从下往上,可以看到三次提交
我们可以在git log
后添加参数--pretty=oneline
只显示文件的版本号(哈希),和提交说明1
2
3
4$ git log --pretty=oneline
58e1bcb1947d683e0683ac1115900056c3f4cabd (HEAD -> master) 修改了两行
e0e571805168d270182a8ada5d712b838d07ce34 添加了一行
f34a8b0d286590117d0d08e041c68a549ce4a1db 这是我提交的第一个文件
版本退回
在Git中用HEAD
表示当前版本也就是当前的提交,HEAD^
代表上一个版本,HEAD^^
代表上上个版本,为了简化表达使用类似HEAD~10
的方式表示之前的版本数.
使用git reset
命令版本回退1
2$ git reset --hard HEAD^ //回退命令.
HEAD is now at e0e5718 添加了一行 //当前版本变为提交描述为"添加了一行"的版本,也就是回退了一个版本
使用cat readme.txt
查看文件1
2
3
4
5$ cat readme.txt
Git is a version control system.
Git is free software.
The line is a new line.
可以看到文件确实被还原了.
继续使用git log
查看当前版本看状态,可以看到”修改了两行”的那次提交的版本不见了.
如果命令行没有被关掉,我们可以在之前的记录中找到”修改了两行”那次提交版本的版本号(哈希值),
使用命令git reset --hard
,可以看到这个命令和回退是一样的,只是参数的值不一样,这里参数值是版本号1
2$ git reset --hard 58e1 //版本号的哈希值不需要输入全部,只要前几位就好确保这个值是唯一的Git就能找到对应的完整值
HEAD is now at 58e1bcb 修改了两行 //HEAD代表当前版本,这里当前版本变为 "修改了两行"的那一次提交
如果关闭了Git没有了版本号记录,又想要变成回退前的版本,我们使用git reflog
来查看之前的每一命令1
2
3
4
5
6$ git reflog
58e1bcb (HEAD -> master) HEAD@{0}: reset: moving to 58e1 //结构分为三个部分:版本号,HEAD值,操作描述
e0e5718 HEAD@{1}: reset: moving to HEAD^
58e1bcb (HEAD -> master) HEAD@{2}: commit: 修改了两行
e0e5718 HEAD@{3}: commit: 添加了一行
f34a8b0 HEAD@{4}: commit (initial): 这是我提交的第一个文件
假设我们现在是第二行(e0e5)的状态(回退了一个版本),我们可以在第三行(58e1)看到回退前的版本号
版本号是唯一标示,表示着每一次操作时的版本.
HEAD用来表示这个版本号是不是当前版本,HEAD后缀越大代表这个版本越老,上面的HEAD@{3}
同HEAD~3
的意思一样代表当前版本之前三个版本.
操作描述记录了本次操作的类型,以及操作时编写的描述信息(例如提交描述)
同样我们可以用git reflog -n
来控制显示条数
工作区和暂存区
工作区是我们建立的版本仓库中直接能看到的目录,工作区的隐藏目录.git
就是Git的版本库
我们的工作区修改存放在暂存区,add后更新同步暂存区(此时用Git diff命令会看到没有返回消息),同时工作区修改或者新增的文件内容被写入到对象库中一个新的对象中,而该对象的ID被记录在在暂存区的文件索引
中(跟新暂存区索引指向新创建的对像).
当执行git commit
操作时,暂存区的目录树
全部写到版本库(对象树中),master (这里我们只有master主分支)分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。
当执行 “git reset HEAD” 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。
这里我依然不是很明白,这个博客用版本仓库第一次add为例子降解了暂存区,参看此博客可能会更清晰一些 Git暂存区原理
到目前为止暂存区的概念我依然不是特别清晰.
撤销修改
修改后还没有add时
命令git checkout --file
撤销工作区的修改
当file修改后暂存区没有file的索引,回到和版本库一样的状态
当file add后(没有commit暂存区有索引),又对file做了修改,这时file会回到添加到暂存区后的状态.
总之就是让这个文件回到最近一次commit或者git add的状态,没有add,哪个状态新就回到哪个状态.
修改后且add后
使用git rest HEAD file
把暂存区的修改撤销,现在修改就只存在于工作区了,暂存区是感觉的.
修改后不仅add还commit了
直接使用版本回退git reset --hard HEAD^
让你的版本库变为上个版本.
删除文件
rm file
这个命令等同于在资源管理器中删除
删除后使用git status
查看删除的文件
接下来使用git rm
,并且git commit
从版本库删除掉该文件(小提示:先手动删除文件,然后使用git rm
在使用git rm
之前如果发现错删了,因为暂存区里还有,可以使用git checkout -file
恢复文件.
总结,删除有两种方式:
1、工作区内”rm 文件名”,然后”git rm 文件名”,然后”git commit -m ‘备注’”
2、直接使用”git rm 文件名”,然后”git commit -m ‘备注’”
实验结果:(摘抄自网友评论,我的实验结果和这个一致)
1、工作区内”rm 文件名” 只是删除工作区内容,暂存区内容还是在的,在”git rm 文件名”操作之前可通过”git checkout –文件名”从暂存区进行恢复。
2、”git rm 文件名”是既删除工作区内容也删除暂存区内容的,所以在”git commit -m ‘备注’”操作之前可以通过”git checkout HEAD – 文件名”从版本库进行恢复,提交后就是只能git reset HEAD^
恢复到上一个版本库然后使用git checkout -- file
从版本库恢复到工作区
远程仓库
备注
由于每个人机器的环境不一致,你可能会遇到某些问题,你需要使用搜索引擎浏览一些github入门使用帖子,初级阶段出现问题百度后基本都能解决.
这里以windows操作系统并用github作为远程仓库为例子(不包含详细步骤只给出关键操作).
添加远程仓库
创建远程仓库
以github为例子,创建好密钥并把公钥添加到github上,详情百度.
免费使用的github只允许创建公共仓库,即任何人都可以查看克隆.
在github上创建一个远程仓库,GitTest
不要勾选creat readme
创建一个空仓库不要初始化,不然会报错
本地控制远程主机
使用命令$ git remote add origin git@github.com:e1sewhere/GitTest.git
其中origin
是操作远程仓库主机的名字你可以随意修改,如果忘记了可以使用git remote
查看远程仓库的名称,或者使用git remote -v
显示详细信息1
2
3$ git remote -v
origin git@github.com:michaelliao/learngit.git (fetch)
origin git@github.com:michaelliao/learngit.git (push)
冒号后的内容是远程仓库的ssh地址,你可以直接输入,也可以在github本仓库页面中找到复制下来git remote show <主机名>
可以查看该主机的详细信息.git remote rm <主机名>
删除远程主机git remote rename <原主机名> <新主机名>
重命名远程主机
推送
git push -u GitTest master
master是你要推送的分支,这里是muster分支
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令为git push GitTest master
远程仓库克隆
同新建仓库一样,远程克隆也是一种创建仓库的方式.
首先使用GitHub打开一个仓库的位置,你也可以自己创建一个仓库用于实验,这里我使用了以前学习HTML时写的hello world页面.
我们使用git clone
命令将这个仓库克隆,将Github项目页面内Clone or download下的SSH地址作为clone的地址
然后在你需要创建仓库的根目录(不是仓库里)打开git终端.
输入git clone git@github.com:E1sewhere/EdenPage.git
等待数据传输完毕.1
2
3
4
5$ git clone git@github.com:E1sewhere/EdenPage.git //github支持ssh和http两种协议,你可以随意选择,这里使用ssh,想了解两者的区别请自行搜索.
Cloning into 'EdenPage'...
remote: Counting objects: 13, done.
remote: Total 13 (delta 0), reused 0 (delta 0), pack-reused 13
Receiving objects: 100% (13/13), 5.52 MiB | 197.00 KiB/s, done.
克隆版本库的时候,使用的远程主机被自动命名为origin
.如果想使用其他的主机名称,需要使用git clone
命令的-o
选项指定.
例如我将远程主机命名为JQuery
1
2
3$ git clone -o jQuery https://github.com/jquery/jquery.git
$ git remote
jQuery
上面的命令表示,克隆时,指定远程主机名称为jQuery
可以看到Eden这个仓库已经被创建了,里面有.git
和readme在内的一些文件.
分支
创建分支
首先你需要自行了解版本控制系统中分支的意义
我们创建一个名为dev
的分支,使用命令git branch dev
然后切换到dev
分支,使用命令git checkout dev
一上两个命令可以合并为git checkout -b dev
实现创建并切换分支的目的.
使用git branch
会查看所有分支,其中带有星号的分支代表当前分支.1
2
3$ git branch
* dev
master
现在我们已经切换到了 dev
分支,在工作空间对readme文件修改并提交(提交前记得add)
然后我们切换回master分支1
2
3$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'GitTest/master'.
切换完成后查看修改的文件发现刚才提交的内容不见了,在master
分支上这个修改并没有被提交!所有的修改都在刚才创建的dev
分支上
接下来我们把dev
合并到master分支上,使用命令
git merge dev`,意思是合并指定分支到当前分支上.
1 | $ git merge dev |
如果dev
分支当前的工作已经完成,并且已经成功合并,我们就可以删除这个分支了,使用命令git branch -d dev
1 | $ git branch -d dev |
分支合并冲突
我们创建一个新的分支feature1
并切换,然后在readme.txt
添加新的一行Creating a new branch is quick AND simple
在这个分支上提交.
然后切换到master分支
1 | $ git checkout master |
在master
分支上添加一行Creating a new branch is quick & simple
在master
上提交
1 | $ git add readme.txt |
我们对同一个文件的同一行做了修改,这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:
1 | $ git merge feature1 |
Git告诉我们,readme.txt
文件存在冲突,必须手动解决冲突后再提交。git status
也可以告诉我们冲突的文件:
1 | $ git status |
看不懂?,翻译一下就好了,我们直接查看readme.txt
,它的最下面有这样几行
1 | <<<<<<< HEAD |
Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,我们修改如下后保存:
1 | Creating a new branch is quick and simple |
再提交:
1 | $ git add readme.txt |
成功了,feature1
分支和master分支
的上次提交保持原样,本次提交后两个master分支变为刚才修改冲突后的内容了
使用git log
查看分支合并的情况
1 | $ git log --graph --pretty=oneline --abbrev-commit |
分支合并方式
分为快速合并(fast forward)和非快速合并(no-off)fast forward
模式下,合并时仅移动指针,这种模式下删除分支后会丢掉分支信息(Git会在条件达成的情况下优先使用快速模式),两条分支看起来就是线性的,他们共用一条直线--no-off
模式下Git会在merge
时生成一个新的commit
,形成一个新的节点,这样看起来两个分支在创建时分叉,合并时汇合.
现场存储与恢复
如果你现在手上的工作没有做完又需要新建一个新的分支去开发或者修复bug这时为了不遗失工作进度且不影响新的分支内容,我们就要把当前分支的工作存储并隐藏起来(否则你暂存区的文件会被带到新的分支去),等以后回到这个分支工作时再恢复你存储的内容.
使用命令git stash
存储并隐藏当前工作
1 | $ git stash |
这时使用git status
查看工作区是干净的.
假设我们要在master
上修复bug,那么就在master
上创建一个临时分支
1 | $ git checkout master //切换到master分支 |
修复/开发完成后就可以去合并分支了.
这是我们回到刚才工作中的dev
分支,用git status
查看工作区,提示工作区是干净的.
为了恢复刚才的工作,首先使用git stash list
查看保存的工作现场列表,列表就意味着你可以做多个stash
保存.
1 | $ git stash list |
恢复时有两种方式git stash apply
,这种方式恢复后stash
的内容不删除,需要时使用git stash drop
手动删除git stash pop
恢复的同时删除stash
内容
当stash列表内又多个时,使用git stash apply stash@[0]
来恢复指定的stash.
抓取分支
使用Git克隆远程仓库时,默认情况下,只能看到master分支.
如果你想克隆dev
分支并在上面开发,就必须创建远程仓库的dev
分支到本地,我们使用这个命令git checkout -b dev origin/dev
,origin
是远程仓库的名称dev
是你要克隆的远程仓库的分支名称.
当别人已经向origin/dev
分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送:
1 | $ cat env.txt |
推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送:
1 | $ git pull |
git pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接:
1 | $ git branch --set-upstream-to=origin/dev dev |
再pull:
1 | $ git pull |
这回git pull成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提交,再push:
1 | $ git commit -m "fix env conflict" |
标签管理
创建标签
切换到需要创建标签的分支上,使用命令git tag <name>
1 | $ git tag v1.0 |
使用命令git tag
查看单签分支的所有标签
标签默认是打在最新提交的commit 上的,有过你要给之前的提交打标签,需要知道提交的commit
1 | $ git log --pretty=oneline --abbrev-commit //列出所有id |
例如我们要对提交描述为add merge
的这次提交打上标签,直接使用命令git tag <name> <commit id>
1 | $ git tag v0.9 f52c633 |
再用命令git tag查看标签:
1 | $ git tag |
使用git tag
命令,标签不是按时间顺序列出,而是按字母排序的。可以用git show
1 | $ git show v0.9 |
使用参数可以创建带有说明的标签,使用命令git tag -a <name> -m "描述"
,-a
指定标签名称,-m
指定说明文字
1 | $ git tag -a v0.1 -m "version 0.1 released" 1094adb |
用命令git show
1 | $ git show v0.1 |
删除标签
使用命令git tag -d <name>
删除本地标签
1 | $ git tag -d v0.1 |
如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:
1 | $ git tag -d v0.9 |
然后,从远程删除,删除也是使用远程的推送的push
格式如下
1 | $ git push origin :refs/tags/v0.9 |
推送标签
如果要推送某个标签到远程,使用命令git push origin
1 | $ git push origin v1.0 |
使用命令$ git push <服务器名称> --tags
推送全部本地标签
1 | $ git push origin --tags |
Github基本介绍
fork
:在自己的账号下克隆一个bootstrap仓库,相当于自己的远程仓库有了这个仓库.
如果要参与一个开源项目,需要先fork这个项目然后,从自己的远程仓库克隆到本地仓库(不能直接从开源项目仓库克隆这会没有权限推送),当你对开源项目的文件修改后就可以在Github上向开源项目所有者发起一个pull request .对方决定是否接受.
依然保留的疑问
暂存区是否相当于一个临时版本库,存储了还没有commit的修改,我们提交到版本库的修改都是从暂存区提交的?
我们工作区的修改的内容add后是存储在git对象库中的,暂存区(索引)只是指向对象,并不实际存储文件?
封装Git命令
在win下直接用bat批处理封装几个命令.
给一个提交hexo源码的的例子
创建一个.bat
文件,复制如下代码
1 | :input |
使用这个脚本只需要修改下你的目录就好了,这两行:
1 | E: |
同时确保当前分支在 master
分支.
以后提交就只需要双击bat,输入提交描述就好了.
参考
Git diff–和++具体解释
Git工作区,暂存区,和版本库-xunye
Git常用命令
廖雪峰Git教程 //我是参考这个做的笔记,但不建议看这个,有不少错误.
除了上面的参考我还推荐阅读: