git 学习(1) ----- git 本地仓库操作
最近在项目中使用git了,在实战中才知道,以前学习的git 知识只是皮毛,需要重新系统的学一下,读了一本叫 Learn Git in a Month of Lunches 的书籍,这本书通俗易懂,使我对git 有了全面的认识。
在平时,我们常常听到git 和github,它俩总是一起出现,总有一种必须一起使用的感觉。实际上,git和 github 是两个不同的概念, git是一个版本管理工具,而github则是一个网站,主要用于保存代码,分享代码。它们 之间没有必然的联系,可以使用其中的任意一个。如果我们只在自己电脑上开发项目,不用共享,只使用git进行版本管理就可以了,没有必要使用github. 如果我们想要分享项目,也可以只使用github, 在github上网站,我们可以直接把源代码拖动到网站,就像qq 邮箱拖动文件一样。当然说它们一点联系都没有,也不可能。git 和github 组合使用,通常用于大型的多人协作的项目。比如react, vue 等项目,多人协作,就要把代码放到一个大家都能找到的地方(gitHub)上,每个人在进行开发的时候,就要进行版本管理,和github 进行通信,那就要用到git命令。我们先学习git。
git是一个版本管理工具,那什么是版本?版本为什么要进行管理?版本,我想大家都听说过,webpack有1,2,3,4 四个大版本。为什么会有这么多的版本?因为每一个版本就是一个变化,bug 修复,增加功能啊。如webpack2, 直接支持import,进行treeshaking, webpack4 提供了零配置的功能。这么多版本肯定要进行管理。这可能有点抽象,想一想我们平常的业务开发也是一样。我们写了一段代码,实现了一个功能, 但有时觉得代码实现方式不太好,就进行重写,但是引入了bug, 就想重新回到原来的代码?但由于变动太大,代码回不去了, 这时就想,要是把以前的代码保存一份该多好。修改代码之前保存一下,如果修改失败,可以进行回退。每一次的代码修改都是一次变化,git 就是用来帮我们管理这些变化的
在windows上使用git, 相对比较麻烦,要先安装git, 百度一下git for window,或直接输入网址 https://gitforwindows.org/
点击Download, 下载下来是一个.exe文件。双击进行安装。安装则比较简单,就是一路next. 安装完成后,桌面上通常会有一个git快捷方式, 同时点击鼠标右键,也会有git bash 和git GUI 命令。
右键点击git bash 或双击快捷方式,就可以打开git bash的窗口,使用git 命令。
Git 是管理变化的,最基本的就是要告诉git 是谁做出了这个改变, 所以在使用git 之前要先配置user.name, user.email,就是用户名和邮箱
git config --globle user.name “sam”
git config --globle user.email “105778@qq.com”
配置之后,可以看看有没有配置成功. 使用git config user.name, git config user.email 可以看到你刚才配置的信息。其实还有一个git config --list 命令,可以看到所有的git 配置,配置成功后,就可以学习git 了。 我们新建一个项目(文件夹),git-learning来学习git, 输入mkdir git-learning && cd git-learning
现在输入git init, 它会在我们的文件夹中初始化一个git 仓库, 此后git就可以追踪整个文件夹的变化,帮助我们管理整个项目了。
提示初始化了一个空的git 仓库,也就是创建git 仓库成功。但你会发现文件夹中什么也没有发生,还是空空如也,哪有创建git 仓库? 这主要是因为,它创建的git 仓库是一个.git 文件夹,window 默认不显示点开头的文件夹。Win10下,在文件夹上面有一个“查看”标签,点击,从右侧数,倒数第二个面板(显示/隐藏)中有一个 “隐藏的项目”复选框,勾选即可,这时看到.git 文件夹。.git文件夹就是git版本库,整个文件夹中所有的变化都放到它这里。所以这个文件夹至关重要,千万不要动它,此时,我们的git-learning 文件夹也称之为工作区,就是我们工作的地方,可以增加,删除文件。
git 仓库创建成功,终于可以看看git 是怎么追踪变化的。touch index.html ,在git learning 文件夹中创建一个index.html 文件, 使用git status 可以看到工作区发生了哪些变化
Untracked files: index.html, index.html 没有被追踪,git 已经检测到工作区新增了一个文件index.html. 怎么才能被git 追踪? 它提到了一个命令git add <file> ... , file就是我们要让git 追踪的文件名,在这里是index.html git add index.html, 可以发现什么都没有发生,这时再查看一下状态:
Changes to be commited, 这个变化可以被提交,就是index.html可以提交到git 仓库了(说明 git add 命令执行成功)。这里要使用的命令是git commit, 输入git commit,打开了一个vim 窗口,让你输入提交的信息。提交一个变化,要说明其原因,为什么要提交,要不然提交它做什么,输入提交信息后,先按esc, 再输入 :wq 退出。但有时候,只想输入一个提交信息,比如新增index.html 文件, 没有必要打开编辑器。可以给 git commit 加一个参数 -m ,然后在-m 后面添加注释,注释用引号括起来,双引号添加单行注释,单引号可以添加多行注释。-m 就是告诉git, 不用打开编辑器了,我直接在这里输入提交信息了。
提交成功,再来查看一个状态 git status
没有什么要提交的了,工作树是干净了。
现在来简单回顾一下我们的操作,先创建一个index.html文件,然后git add 命令,让git 去追踪,最后git commit 提交变化,用git status 可以随时查看状态。你可能会有疑问,git add 把文件添加到什么地方了,git commit 提交,又把文件提交到什么地方了。这其实涉及到了git 的三个区:工作区, 暂存区, 版本区(git 仓库)。我们在工作区创建了一个index.html文件,然后调用 git add 命令, git add 命令就是把文件增加到暂存区,然后git commit , git commit 就是把文件提交到git 仓库。
在工作区和版本区中加了一层暂存区,作为过渡, 显得更为灵活. 比如我们开发一个功能,不可能一次完成, 需要多次修改才能完成. 这时我们不可能把未完成的代码,直接提交到版本库,但有了暂存区之后,我们可以提交到暂存区,暂存区同样可以保存我们的代码,暂存区又没有提交到版本区,它不会影响到成熟的版本区. 这样就同时保护了工作区和版本区。
我们把变化(改变的文件)提交到git仓库,git 就会把当前的变化保存起来,形成一个快照。git commit 就是告诉git 仓库 把当前状态保存一下。每提交一次,它就会保存一次,形成一条一条的历史快照,这样我们就可以版本来回切换。查看提交记录是用git log 命令。
这里还有一个git ls-files 命令,可以查看版本库的内容。
这时也可以看到git 仓库中有了index.html ,也表示我们提交成功了。
现在我们再对index.html 进行修改,看看git 又是怎么追踪变化的文件的? 用编辑打开index.html, 输入html 模版。然后再用git status 查看状态
Changes not staged for commit, stage就是缓存(暂存),变化没有提交到暂存区。他提供了后续的两个动作,一个是git add 提交到暂存区,一个是git checkout -- 放弃更改。先使用git add 提交到缓存区。不过,在提交到缓存区之前,我们最好要看一下文件做了哪些改变,是不是我们想要做出的改变,这里的命令是git diff
可以看到git diff 把所有的更改都显示出来了。difff --git a/index.html b/index.html 以 git 的方式显示两个文件a/index.html 和b/index.html 的不同。--- a/index.html --- 表示变动之前的文件。+++ b/index.html , +++表示变动之后文件。 @@ -0,0 +1, 14 @@,对变化的位置做了标识,以@@ 开头和@@ 结束的内容表示的就是变化的位置, 它会为两个部分-0,0 和+1,14. -表示的是变动之前的文件,0表示变动前的第0 行,后面的0 表示连续0行,连接起来读就是变动之前的文件,从第0行,连续0行有变化,那就是什么都没有。+表示变动后的文件,1表示变动后文件的第一行,14 表示连续14行, 变动后的文件从1行开始,连续14 行有变化。具体的主化内容,则是在它下面是显示。你可以看到第一行的开始都有一个加号+, 它表示变化后文件新增的行,有时还会有一个减号,表示变化前的文件中删除的行。有时什么都没有,表示的是没有变化。
这时会有一个疑问,变动前的文件指的是哪一个文件?它指的是暂存区的文件,也就是git diff 比较的是工作区和暂存区的文件的不同。现在把变动提效到暂存区,git add index.html, 再调用git diff 命令,可以发现什么都没有输出。因为提交之后,工作区和暂存区一致了,没有什么不同,这也证明了,它只是对比的工作区和暂存区文件的不同。
如果我们想对比 暂存区内的文件和版本区内的文件的不同,那要用 git diff --staged。git diff --staged 看一下
git diff master 对比工作区内的文件和版本区内的文件的不同, git commit -m "修改index.html" 提交版本区,自己试一下就可以了,这里就不写了。
这里,只是对单个文件进行了修改,如果我们对多个文件进行了修改,git diff 会把所有修改的文件都显示出来,我们只想看到一个文件的变化,可以指它指定一个文件名,git diff 文件名,比如git diff text.txt ,只看到text.txt文件的变化内容。
我们看到了变化后的内容,如是我们想要做的改变,那就要先git add 提交到暂存区,再git commit 提交到版本区。这里有一个快捷的命令,可以直接提交到版本区。 git commit -a -m "注释", -a 就是add 的简写。这个简写命令只对文件更改有效,因为更改的文件已经在 git 版本中被追踪了。如果是新建 一个文件,它没有被追踪,这个简写命令无效。git commit -a -m "修改index.html" 可以看到直接提交到仓库了。
现在我们可以添加并修改一个文件了,那多个文件呢?其实是一样的,不过有几个简捷的命令。现在我们添加新的文件,用touch index.js .style.css reset.css, 这时可以调用git status 来查看状态,不过,我想你已经知道结果了,那就是这三个文件,git没有追踪,并且你也知道下一步做什么,就是调用git add 命令添加到暂存区。但是按照以前,三个文件就要写三次git add 命令,git add index.js git add style.css , git add reset.css ,有点麻烦了。不过git 也想到了这个问题,所以git add 命令接受的参数还可以是文件夹名,我们调用git add . 试一试,. 表示的就中当前文件夹,连文件夹名都省略掉了,真是很人性化,它把当前文件夹中的文件全都上传到暂存区
最后git commit 一下,提交到git 仓库。
但有时候,修改文件,然后git add 和git commit 的时候,会漏掉文件。有一次,好多文件需要把双引号改成单引号,改了10个文件就提交了,再回头看时,少改了一个文件,需要重新提交,但又不想再形成一个提交记录,这时要用git commit --amend 。先把漏掉这个文件比如a.js 添加到 暂存区,然后 git commit --amend --no-edit, --no-edit 就是不修改上一次的提交信息,这样,这次的提交就包含了漏掉的a.js. 如果还提交信息都相改,那就使用-m git commit -m "new message " --amend.
这也引出了--amend的另一个用法,可以直接修改上一次提交的信息。有时候,提交信息中有错别字,git commit -m 'update message ' --amend. 一定要记住的是,--amend 只能修改上一次(最近一次)的提交,并且,它会代替上一次的提交,形成一个全新的提交。
git 删除文件的操作
1,如果我们想删除掉git 仓库中的文件,该怎么做呢?先删除掉本地(工作区)的文件, 然后再把变动提交到git 仓库。删除命令用rm, rm style.css 就可以删除掉style.css 文件。删除文件后,git add 和 git commit 就可以把仓库中的文件删除了。
没有问题,git ls-files 命令,就是把仓库中的文件显示出来,可以看到确实是删除了style.css文件。其实git 提供了一个git rm 命令,它把工作区和暂存区的文件一起删除,调用git rm 进入到暂存区阶段,只要commit 一下就可以把文件从仓库中删除了。
git 重命名文件的操作。
对于文件的重命名操作,可能有点不太好理解。在图形界面操作时,选中一个文件,然后右键重命名,输入新的文件名,操作的都是同一个文件。但对于命令行操作来说,它确不是这样的。对于命令行来说,重命名意味着,新建一个文件,然后把旧文件中内容都拷贝到新文件中,然后再把旧文件删除,所以它用的是mv 命令,就是 move(移动),把一个文件移动到另一个文件。mv index.js main.js 就是把index.js 改为main.js, git status 看一下结果
确实是删除了index.js文件和新建了一个main.js文件,这时调用git add 和 git commit 提交到仓库。和删除 一样,git 也提供了git mv 命令,一步到暂存区。git mv main.js index.js, 然后 git commit 就可以改回来了。
git 操作的回退
首先是暂存区的回退,比如,写了一段代码,提交到了暂存区,然后发现没有写注释,还有一些debugger 和console.log, 想把提交撤回,git reset HEAD 文件名 git reset HEAD main.js ,它会把暂存区中状态回退到git 仓库最近一次提交的状态,所以所有提交到暂存区的改动都会消失, 工作区的改动不会消失。
unstaged changes 也说明撤销成功了。这时可以修改代码重新提交。但如果代码都错了,那就要把改动全部删除,重新写,其实这里也可以使用git checkout -- 文件名,直接把工作区的内容回到版本区中状态,就相当于把所有变动删除。
其次是仓库版本区(git 仓库的回退)。把代码都提交到仓库了,突然发现需求理解错了,那就有可能回退好几个版本了。这时要借助git log, 实现版本回退
当我们git commit 的时候,git 会形成一个历史快照,使用git log 可以查看。git log 还提供了一系列的参数来实现不同维度的输出。
git log --oneline 以一行方式输出,更为简捷,可以看到我们提交了多少次的记录。
git log --patch, 以git diff 对比变化的方式,输出提交记录。
你发现只输出了两个完整的commit, 并且最下面还有一个:位置的光标在闪烁,这表示等待你继续输入命令,因为在git 输出时,它默认输出窗口的大小的内容,所以对于这种log的输出,它肯定很多,一个窗口的大小肯定输出不完。这时按空格键,可以发现,它 又输出了内容, 一直按空格,直到全部输出完为止,你可以看到一个end: 光标在闪烁。
表明输出结束。此时按q(或Q)进行结束。
git log --stat, 以文件变动的展示方式输出提交记录。
还是和上面一样,你要按 空格或Q 进行继续输出或终止输出。
git log --patch-with-stat 对以上两种方式进行了整合,可以自己输出看一看结果
git log 参数 文件名,只输出包含指定文件的提交记录. git log --oneline index.html. 以oneline的方式输出只包含 index.html 的提交记录。
除了使用命令行来查看提交记录时,我们也可以使用图形界面工具来查看,它显得更为直观。直接在命令行中输入gitk, 就可以打开图形界面工具
在图形界面的上半部分,就是提交记录,下部分对应的每一个提交记录的变动。在上半部分的每一条提交记录上进行点击,下半部分就会相应变化,右铡还有patch, tree 等选择,自己可能多点点看一看,是非常直观。当我们手动关闭图形界面工具后,命令行窗口又变成了可输入状态。
版本回退:在学习版本回退之前,要了解是什么能让git进行版本回退,那就是指针。在程序开发的过程中,我们会进行一次次的提交,从而形成一个提交历史。就像上面一样,我们有一个6条的提交历史。每一次的提交都会有一个指针,指向上一次的提交,就像链表一样,如下图所示。
这一系列的提交也形成了一个分支,你可能没有注意,git init 在初始化git 仓库的时候,已经创建了一个默认分支叫master,我们这一次一次的提交都提交到了master 分支上。
上图中最后的master 就是指的master 分支,它表示,我们一直在master分支上进行工作。同时master也是一个指针或引用,指向我们分支的最后一次的提交,
也就是说,分支名,它即指的是我们开发过程中的整条分支,同时也是一个指针,指向整条分支的最后一次提交。
好了,剩下最后一个HEAD指针要学习了,HEAD指针也是指的当前分支,默认指向当前分支的最后一次提交。
我们可以使用命令看一下,master 和HEAD 是不是指向最后一个提交,命令是git rev-parse master 和 git rev-parse HEAD
可以看到它们都指向了最后一次提交。不过,HEAD指针可以移动,从而实现版本的前进或回退。你可以把HEAD指针想像成听歌或看视频上的时候,进度条上的滑块,通过拖动滑块,我们可以快进或后退,从而实现想看哪里就看哪里。HEAD 指针也是如此,我们可以通过改变HEAD的位置,从而实现版本的回退或前进。常用的命令有3个 git reset, get checkout, git revert, 这三个命令接受的最基本的参数,就是commit ID. 我们提交的时候,它都会生成唯一的一个id,如上图中的 b695ce51ac10a45d995014c3546b4fe49c7a2f58
但是这三个命令的使用场景却大不相同,需要根据不同的场景使用不同的命令。
git reset: reset 是重置的意思,重置意味着重新来过,当我们重置一个表单的时候,就是我们刚刚添写的内容全部舍弃掉,重新添写。我们用git log 查看一下commit id
现在想回退到删除style 样式文件, 那就用 git reset d3be0631e6c50dce17d00226dd270c7070b7ee86, 这个id 有点长,其实只要输入前面的6-7位就可以了,因为我们使用git log --oneline 的时候,commit id 就是6位。再说,如果git 找不到这个id, 它会告诉我们找不到,所以也不用担心。
输出的结果有点奇怪,index.js reset.css 没有添加到缓存区,但是我们的文件夹或工作区并没有发生变化,还是只有两个文件index.html 和main.js,这是怎么回事?其实当使用git reset 进行回退的时候,它默认改变的是暂存区,把暂存区的状态回退到指定id 的版本区(或暂存区)的状态,工作区不会发生变化。我们gitk 看看指定的id的版本区都有哪些文件?
在命令行中输入gitk,
SHA1 ID 就是我们要回退到的id, 右下边可以看到这个提交所对应的版本区(仓库)的文件,有index.html, index.js reset.css三个文件,也就是说,当我们执行git reset d3be06时,暂存区回退到这种状态,有index.html, index.js reset.css文件。然后再和工作区进行比较,此时工作区只有index.html, main.js,所以对于git来说,相当于我们在工作区删除了index.js 和 reset.css 两个文件,所以它才会显示 unstaged change: index.js reset.css, 两个文件前面还有一个D,它就是Delete的意思,表示删除,也是说删除了两个文件,没有提交到暂存区。这时你可能想到了使用git diff 来查看一下暂存区和工作区的不同
和我们刚才说的一样,我们删除了index.js 和reset.css 没有提交到暂存区,由于工作区还有main.js, 所以显示main.js 没有被追踪。这时如果想,回退版本的时候,直接把工作区也替换了,和暂存区状态一样。 这样再给git reset 命令增加一个参数: --hard. 为了完成这个操作,我们要回到以前的版本,这时 git log 一下,我们的提交记录只剩下4条了,根本就没有回去的commit ID,
相当于,d3be063 后面的两个提交
b695ce5, 53767b8 都没有发生一样,这就是git reset 重置的作用,也就是说, 如果我们做的提交,我们不想要了,要把它扔掉,那就是git reset. 现实的场景就是,在开发新功能的时候,突然这个功能取消了,或者,开发了一段时间,代码写的太烂了,要重写,这时就用git reset.
那我们到底能不能从过去回到未来?那要使用git reflog,它会把所有的提交记录显示出来,找到对应的id, 然后git reset 这个id 就可以了。现在再回到刚才那个问题 ?如果git reset的时候,想要把工作区和缓存区都同步到指定版本的内容,使用git reset --hard d3be063,可以看到这时工作区也回退到了指定版本的状态。
git checkout 也可以接受一个commit id, 移动HEAD 指针。
可以看到工作目录改变了,变成了指定的 commit id 的状态。
checkout 的结果就是 Head 指针和master 指针的分离,如果这时再进行提交,以前提交的历史记录也会消失,最好不做任何变动,提交记录, 不过我们可以在这里创建分支。它的主要场景就是快速查看旧版本。可以使用git ckeck master 回到最新的版本。
git revert: revert 是撤销的意思,撤销那一次的修改,重新修改提交。但我们执行 git revert a69243e 出现以下弹窗
下面还有更多,滚动鼠标滚轮可以看到,它有几个选项,直接编辑(E), 退出Q 等,如果选择直接编辑,进入一处输入界面,这应该是一个vim 的界面,可以输入解释信息,为什么要执行撤消操作,输入完成后,按esc 键退出,然后再输入:wq 退出编辑器。这时再看一下我们的工作目录,可以看到它是回到了指定的状态。这时git log 你会出现,它会当成一次新的提交,我们原来的提交历史没有消失,这时一种安全的操作。
但是,如果我们只想回退到前一版本,在commit id 的方式就有点麻烦了,因为我们先要git log 找到这个id; 这时git 提供了简单的操作,用HEAD; HEAD^ 表示上一个版本, HEAD^^ 表示回退到上两个版本,当有几个版本的时候,这种操作就不方便了,这时提供了 HEAD ~num 的模式,num 表示要回退到第几个版本, HEAD ~5,表示回退到上5个版本。 git reset –hard HEAD^^, 或 git reset –hard HEAD ~2 也表示回退两个版本。 git checkout 和git revert 也是同样的用法。