第一章 Git操作
版本控制
什么是版本控制
版本控制是一种记录一个或若干文件内容变化,以便将来查询特定版本修订情况的系统
为什么要版本控制
有了它你就可以将某个文件回溯到之前的状态。甚至将整个项目都会退到过去某个时间点的状态。
可以比较文件的变化细节,查出最后是谁修改了哪个地方,从而找出导致怪异问题出现的原因,又是谁在何时报告了某个功能缺陷等。
集中化的版本控制系统
集中化的版本控制系统诸如CVS、Subversion一级Perforce等,都有一个单一的集中管理的服务器。保存所有文件的修订版本,而协同工作的人们都通过客户端链接到这台服务器,取出最新的文件或者提交更新。
这种做法的好处在于每个人都可以在一定程度上看到项目中的其他人在做什么。管理员可以轻松掌握每个开发者的权限,并且管理一个集中化的版本控制系统,要远比在各个客户端上维护本地数据库轻松容易。svn每次存的都是和上一版本的差异,所需存储空间小,但回滚速度慢。
缺点是中央服务器的单点故障:宕机时员工写的代码得不到保障,炸了时整个项目的历史记录会丢失。
分布式的版本控制系统
例如git、bitkeeper等,客户端并不只是提取最新版本的文件快照,而是把代码仓库完整地镜像下来,这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库回复,因为每一次的提取操作,实际上都是一次针对代码仓库的完整备份。
更进一步,许多这类系统都可以指定和若干不同的远端代码仓库进行交互。藉此,你就可以在同一个项目中分别和不同工作小组的人相互协作。
分布式的版本控制系统在管理项目时,存放的不是项目版本与版本之间的差异,他存的是索引(所需磁盘空间很少,所以每个客户端都可以放下整个项目的历史记录。)
git每次存的都是项目的完整快照,所需硬盘空间相对大,git团队对代码做了压缩,最终保证所需空间比svn多不太多,但是回滚速度极快。
分布式的版本控制系统出现之后解决了集中式版本控制系统的缺陷:
- 断网的情况下,也可以进行开发(因为版本控制是在本地进行的)
- 使用github进行团队协作,哪怕github挂了,每个客户端保存的也都是整个完整的项目(包含历史记录)
git简史
git是目前世界上最先进的分布式版本控制系统。同生活中许多伟大事件一样,git诞生于一个极富纷争、大举创新的年代。linux内核开源项目有着为数众多的参与者,绝大多数的linux内核维护工作都花在了提交补丁和保存归档的繁琐事务上(1991-2002年间)。到了2002年,整个项目组开始启用分布式版本控制系统Bitkeeper来管理和维护代码。
到了2005年开发bitkeeper的商业公司同linux内核开源社区的合作关系结束,他们收回了免费使用bitkeeper的权利。这就迫使linux开源社区(尤其是Linux的缔造者 linus Torvalds)不得不吸取教训,只有开发一套属于自己的版本控制系统才不会受制于人,他们对新的系统制定了若干目标:
- 分支切换速度快
- 容量小(压缩)
- 简单的设计
- 完全分布式
- 对非线性开发模式的强力支持(允许上千个并行开发分支)
- 有能力高效的管理类似linux内核一样的超大规模项目(速度和数据量)
自诞生于2005年以来,git日益完善,在高度易用的同时,仍然保留着初期设想的目标。它速度飞快,及其适合管理大项目,还有着令人难以置信的非线性分支管理系统可以应付各种复杂的项目开发需求。
这里吐槽一句,以上对于身处墙内的我们来说,部分不适用。
git安装
git官网安装包
安装后获得git bash,已经自带了ssh客户端。
Bash是[Bourne shell](https://baike.baidu.com/item/Bourne shell)的后继兼容版本与开放源代码版本,它的名称来自[Bourne shell](https://baike.baidu.com/item/Bourne shell)(sh)的一个双关语(Bourne again / born again):Bourne-Again SHell。
Bash是一个命令处理器,通常运行于文本窗口中,并能执行用户直接输入的命令。Bash还能从文件中读取命令,这样的文件称为脚本。和其他Unix shell 一样,它支持文件名替换(通配符匹配)、管道、here文档、命令替换、变量,以及条件判断和循环遍历的结构控制语句。包括关键字、语法在内的基本特性全部是从sh借鉴过来的。其他特性,例如历史命令,是从csh和ksh借鉴而来。总的来说,Bash虽然是一个满足POSIX规范的shell,但有很多扩展。
在windows下打开cmd写的是dos命令,在linux终端写的命令是linux命令。
git bash写的就是linux命令
$ git --version
git version 2.26.2.windows.1
git初始化配置
一般在新的系统上,都需要先配置自己的git工作环境。配置工作只需一次,升级时会沿用上次的配置,可以手动更改配置。
git提供了一个叫做git config的命令来配置或者读取响应的工作环境变量,这些变量决定了git在各个环节的具体工作方式和行为。这些变量可以存放在三个不同的地方:
- /etc/gitconfig 文件,系统中对所有用户都普遍使用的配置。使用git config时用--system选项读写的就是这个文件
- ~/.gitconfig 文件,用户目录下的配置文件只适用于该用户。使用git config时用--global选项,读写的就是这个文件
- .git/config 文件,当前项目的git目录中的配置文件(也就是工作目录中的.git/config文件),这里配置的仅仅针对当前项目有效
每一个级别的配置都会覆盖上层的同样配置
用户信息
首先配置个人用户名和邮箱地址。每次git时都会引用这两条信息,说明是谁提交了更新,所以会随更新一起永久纳入历史记录
git config --global user.name "*******"
git config --global user.email abc@df.com
git config --list检查已有信息
git底层概念与底层命令
一些linux基础命令
clear
echo ‘contex’ > text.txt
touch
ps
grep
ll(ls)
find
rm
mv
cat
vim(set num)
初始化新仓库
命令:git init(在当前目录下自动创建隐藏文件 .git文件夹)
点进去
$ git init
Initialized empty Git repository in C:/Users/10269/.git/
1598622358231.png
hooks:目录包含客户端或服务端的钩子脚本(类似于回调函数)
info:包含一个全局性排除文件
logs:保存日志文件
objects:目录存储所有数据内容
refs:目录存储指向数据(分支)的提交对象的指针
config:文件包含项目特有的配置选项
description:用来显示对仓库的描述信息
HEAD:文件指示目前被检出的分支
index:文件保存暂存区信息
1.区域
工作区:沙箱环境,该区域随意更改更新
⬇
暂存区:暂时存储代码
⬇
版本库:永久存储每个版本
2.对象
git对象:
git的核心部分是一个键值对数据库。你可以向该数据库插入任意类型的内容,它会返回一个键值(hash),通过该键值可以在任意时刻再次检索该内容
1598968803651.png!
1598969161393.png-w就是write,不加则不保存文件只返回hash
git对象生成的键值对在git内部是一个blob类型
这里-t可以返回文件类型
[图片上传中...(1598970454892.png-547bf8-1599414783547-0)]
1598969759637.png这里在更改文件内容后,需要显式的再提交一次,然后才会发现git仓库里保存了修改后的文件,并且该文件包含第一次的文件内容-----即git仓库不是保存增量,而是全部保存。
对于问题1、2,解决方案是树对象
并且当前操作都只对本地数据库进行操作,没有涉及暂存区
树对象
树对象能解决文件名保存的问题,也允许我们将多个文件组织到一起(比如一个系统可能有很多个文件整体提交)。git以一种类似于unix文件系统的方式存储内容。所有内容均以树对象和数据对象(git对象)的形式存储,其中树对象对应了unix中的目录项,数据对象(git对象)大致对应文件内容。一个树对象包含了一条或多条记录(每条记录含有一个指向git对象或者子树对象的SHA-1指针,以及相应的模式、类型、文件名信息)。一个树对象也可以包含另一个树对象。
构建树对象
1598970454892.pnggit ls -file -s 查看当前暂存区的内容,暂存区又叫索引区
1598975188915.png
即,git数据库里会同时存在Git对象和tree对象,其中git对象理解为文件版本(更新时会添加一个新的git对象),tree对象是文件的索引(起别名)也是文件的组织形式(更新时会添加一个新的tree对象),也就是项目的版本
1598975793048.png
1599150133473.png
问题:
现在又三个树对象,执行了三次write tree,分别代表了我们想要跟踪的不同项目快照,然而问题在于------1、需要记住三个树对象的SHA-1哈希,并且不知道树节点是由谁在何时为何保存的。
提交对象
调用commit-tree命令创建一个提交对象,为此需要指定一个树对象的SHA-1值,以及该提交的父提交对象(如果有的话,第一次将暂存区做快照时无父对象)
1599150251557.png 1599150318607.png 1599150526424.png
git本地操作与高层命令
git操作基本流程
- 创建工作目录,对工作目录进行修改
- 执行 git add ./(生成git对象到版本库,然后将其放入暂存区)
- git hash-object -w 文件名(修改了多少个工作目录中的文件,此命令就执行多少次)
- git update-index ...
- git commit -m "注释内容"(将暂存区git生成tree对象放到版本库,然后包装成commit对象放到版本库)
- git write-tree
- git commit-tree
git高层命令
git init 初始化仓库
git add ./ 将修改添加到暂存区
git commit -m "注释" 将暂存区提交到版本库
git status
1599230741712.png
工作目录下的文件只有两种状态(git status):
- 已跟踪 该类文件指本来就被纳入版本控制管理的文件,在上次快照中有它们的记录。工作一段时间后,它们的状态可能是已提交、已修改和已暂存
-
未跟踪 其他文件属于未跟踪,既没有上次更新时的快照,也不在当前的暂存区
1599152055266.png
运行git add之后又修改的文件,需要重新运行git add进行最新版本的暂存
git status仅仅列出修改过的文件,但不给出修改过的地方
git diff可以给出修改过的文件和修改的地方(当前做的哪些文更还没有暂存,有哪些更新已经暂存准备下次提交?)
-
当前做的哪些更新还没暂存
命令 git diff(无参数)
-
有哪些更新已经暂存起来准备下次提交?
命令 git diff --cached 或者 git diff --staged
跳过使用暂存区
有时候会觉得暂存区很麻烦,可以使用git commit -a,此时git会把所有已经跟踪过的文件暂存起来一起提交。
跟踪过的文件才可以使用-a选项跳过暂存区
移除文件
要从git中删除某个文件,必须要从已跟踪文件清单中注册删除(即在暂存区中删除),然后提交。使用git rm完成此操作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中。
注意,删除操作不会影响已提交的文件。而从暂存区删除文件后,进行提交,会在版本库生成新的树对象和提交对象,所以删除操作本质上是一个新增操作。
文件改名
git mv file.from file.to
该操作等于下面三个操作之和:
- mv file.from file.to
- git rm file.from
- git add file.to
查看历史记录
git log
git log --pretty=online
git log --online
Git分支操作
几乎所有的版本控制系统都以某种形式支持分支。使用分支意味着你可以把工作从开发主线上分离开,以免影响主线的开发。在很多版本控制系统中,这是一个略微低效的锅成--常常需要完全创建一个源代码目录的副本。对于大型项目而言,这样的过程太耗费时间。
而git分支模型极其高效轻量。也正因为这一特性,git从众多版本控制系统中脱颖而出。
创建分支
git branch
为你创建一个可以移动的新指针。比如,创建一个testing分支:git branch testing。这会在当前所在的提交对象上创建一个指针。
git branch 新分支名 创建了一个新的分支,但并不会自动跳到新的分支中去
1599231118468.png
git branch不只可以创建与删除分支。如果不加任何参数运行该命令,会得到当前所有分支的列表。
git branch -v 可以查看每一个分支的最后一次提交
git branch name commitHash 新建一个分支并使分支指向对应的提交对象
git branch -merged 查看哪些分支已经合并到当前分支。在这个列表中分支名字钱没有*符号的分支通常可以使用git branch -d删除
git branch --no-merged 查看所有包含未合并工作的分支,尝试使用git branch -d命令删除在这个列表中的分支时会失败。
如果真的想要删除分支并丢掉那些工作,可使用-D选项强制删除
切换分支
git checkout testing
git checkout -b test 创建分支并切换过去
1599231506700.png
分支切换会改变工作区的文件
在切换分支时,一定要注意你工作目录中的文件会被改变。如果是切换到一个较旧的分支,你的工作目录会回复到该分支最后一次提交时的样子。如果git不能干净利落的完成这个任务,它将禁止切换分支
每次切换分支前,需要确保当前分支已提交(git ls-file -s查看暂存区)
删除分支
git branch -d name 删除分支
不能自己删除自己
查看分支合并历史
git log --online --decorate --graph --all
分支合并
git merge 分支名
快进合并:主分支位于新的分支之后,直接将主分支跳到新分支即可,因为没有分歧产生
1599238807155.png
master本来在c2,合并后直接跳到c4即可。
此时如果再合并c3,c3在master之后,有分歧(如c4解决的bug在c3并没有得到解决),不会fast-forward
典型合并:两条线上进行合并,可能产生代码冲突,此时应该手动修改冲突代码,然后git add ./
1599313348451.png
分支模式
长期分支
master分支上保留完全稳定的代码
然后在主分支上创建自己的开发分支,自己的开发分支上还有一些临时的特性分支,在开发出稳定代码后再合并到主分支。
分支的本质
git的分支本质上只是指向提交对象的可变指针。git的默认分支名是master。在多次提交操作后,你其实已经有了一个指向最后那个提交对象的master分支。它会在每次的提交操作中自动向前移动。
.git/refs目录下保存了分支及其对应的提交对象
撤销与重置
工作区:如何撤回自己在工作目录中的修改
git checkout -- filename(checkout命令会改变head指针、清空工作区和暂存区,因此说是后悔药,其实是重置该文件。新版本是restore)
暂存区: 如何撤回自己的暂存(Git reset HEAD filename或git restore --staged)
版本库: 如何撤回自己的提交(git commit --amend).这个命令会将暂存区中的文件提交。如果自上次提交以来你还没有做任何修改,那么快照不会改变,修改的只是提交信息。如果提交后发现忘记暂存某些需要的修改,可以使用该命令覆盖上一个提交。
reset
Git reset --soft HEAD~
加波浪号指上一个,这与改变head自身不同(checkout所做的),reset移动head指向的分支
它本质上是撤销了上一次的git commit命令(执行git commit命令时,git创建一个新提交,并移动head所指向的分支使其指向该提交)
当我们将它reset回HEAD~,其实就是把该分支移动回原来的位置,而不会改变索引和工作目录。现在可以更新索引并在此运行commit来完成git commit --amend所要做的事情了。
git reset 【--mixed】HEAD~
它依然会撤销上一次提交,但还会取消暂存所有的东西。于是,我们回滚到了所有git add和git commit的命令执行之前
git reset --hard HEAD~
撤销最后的提交、git add和git commit命令以及工作目录中的所有工作
和checkout的区别
- checkout只动head,--hard则动head而且带着分支一起走
- checkout对工作目录是安全的,--hard强制覆盖工作目录
路径reset
git存储
有时,当你在项目的一部分上已经工作一段时间以后,所有东西都进入了混乱的状态,而这时你想要切换到另一个分支做一点别的事情。问题是,你不想仅仅因为过会回到这一点而为做了一半的工作创建一次提交,针对这个问题的答案是 git stash命令
git stash命令会将未完成的修改保存到一个栈上,而你可以在任何时候重新应用这些改动(git stash apply)
git stash list查看存储
git stash apply stash@{2} 如果不指定一个存储,git则默认是指定最近的存储
git stash pop 来应用存储然后立即从栈上扔掉它
git stash drop加上要移除的存储的名字来移除他
数据恢复
在你使用git的时候,你可能会意外丢失一次提交。通常这是因为你强制删除了正在工作的分支,但是最后却发现你还需要这个分支;亦或者硬重置了一个分支,放弃了你想要的提交。如果这些事情已经发生,该如何找回你的提交呢?
例子:目前我们有五次提交(通过 git log --pretty=online查看)
但是我们将master分支硬重置到第三次提交,现在log只能看到三次提交。顶部的两次提交丢失了。
我们需要找出最后一次提交的SHA-1,然后增加一个指向他的分支。
最简单的方法是使用reflog或者log -g
然后创建一个新的分支指向他:git branch recover-branch commitHASH,现在这个名为recover-branch的分支就是master分支曾经指向的地方,之前丢失的两次提交可以到达了。
打tag
git可以给历史中的某一次提交打上标签,以示重要。比较有代表性的是人们会用这个功能发布结点(v1.0等)
列出标签
git tag
git tag --l ‘v1.85*’ 列出所有v1.85开头的标签(正则)
创建标签
git主要使用两种类型的标签:轻量标签和附注标签
轻量标签很像一个不会改变的分支,他只是一个特定提交的引用
git tag v1.4 commitHASH(不列commitHASH时给当前所在提交打tag)
附注标签是存储在git 数据库中的一个完整对象。它们是可以被校验的。其中包含打标签者名字、邮箱、日期还有标签信息。
git tag --a v1.4 [commitHASH] [--m] '备注'
查看特定标签
git show可以显示任意类型的对象(git对象、树对象、提交对象、tag对象)
远程标签
删除标签
git tag -d <tagname>
该命令不会从远程库中移除标签,所以必须用
git push <remote> :refs/tags/v1.4
更新远程库
检出标签
如果你想查看某个标签所指向的文件版本,可以使用git checkout命令。
git checkout tagname
虽然这会使得你的仓库处于分离头指针(detach HEAD)状态。在分离头指针状态下,如果你做了某些更改然后提交,标签不会发生变化,但你的新提交将不属于任何分支,且无法访问。除非访问确切的提交哈希。因此,如果你需要进行更改---比如说你正在修复旧版本的错误----这通常需要创建一个新的分支。
git checkout -b version2
配别名
给git命令配别名
1599234261557.pnggit特点
1.直接记录快照,而非差异比较
2.近乎所有操作都是本地执行
3.时刻保持数据完整性
4.多数操作仅添加数据
5.文件的三种状态(已提交、已修改、已暂存)
git工作流程
git add
git status
git commit
第二章 代码风格
关于js的语法检查之类的,跳过
npm i eslint -D
-
生成配置文件
npx eslint --init
-
检查js文件
npx eslint 目录名
eslint要结合git
husky:哈士奇,为git仓库去设置钩子程序
在仓库初始化完毕之后,再去安装哈士奇
在package.json文件中写配置
Git钩子是在Git仓库中特定事件发生时自动运行的脚本。可以定制一些钩子,这些钩子可以在特定的情况下被执行,分为Client端的钩子和Server端的钩子。Client端钩子被operation触发,比如commit,merge等,Server端钩子被网络动作触发。
第三章 远程仓库
忽略某些文件
一些自动生成的文件通常无需纳入git管理,也不希望他们总出现在未跟踪列表。我们可以创建一个名为.gitignore的文件,列出要忽略的文件模式。
*.[oa]
*~
第一行告诉git忽略所有以.o或.a结尾的文件,一般这类对象文件和存档文件都是编译过程中出现的,我们用不着跟踪它们的版本。
第二行告诉git忽略所有以波浪符结尾的文件,许多文本编译软件(例如Emacs)都用这样的文件名保存副本。
此外,可能还需要忽略log,tmp或者pid目录,以及一些自动生成的文档等。
.gitignore的格式规范
所有空行或者以注释符号#开头的行都会被git忽略
可以使用标准的glob模式匹配
*代表匹配任意个字符
?代表匹配任意一个字符
**代表匹配多级目录
匹配模式前跟反斜杠/,这个斜杠代表项目根目录
匹配模式最后跟反斜杠/说明要忽略的是目录
要忽略指定模式以外的文件或目录,可以在模式前加上感叹号!取反
什么是远程仓库
为了能够在任意git项目上团队协作,你需要知道如何管理自己的远程仓库。远程仓库指的是托管在因特网或者其他网络中的你的项目的版本库。
你可以有好几个远程仓库,通常这些仓库对你只读,有些则可以读写。
与他人协作涉及管理远程仓库以及根据需要推送或者拉取数据。
管理远程仓库包括了解如何添加远程仓库、移除无效的远程仓库、管理不同的远程分支并定义他们是否被跟踪等。
远程协作基本流程
github是最大的git版本库远程托管商。所以应该学会如何使用github,这比git简单的多。
- 管理员在github网站创建空的仓库
- 管理员本地创建项目并init
- 管理员为远程仓库配置别名和用户信息
- 管理员推送本地项目到远程仓库
- 成员克隆远程仓库到本地
- 管理员邀请成员加入团队
- 成员推送提交到远程仓库
- 管理员更新成员提交的内容
git remote add taobao https|ssh(远程仓库地址,github上复制过来) //本步骤给项目配别名
git remote -v
git config --list //查看用户信息
git config user.name ""
git config user.email ""
//此时要清理一下windows凭据,在凭据管理器里
git push [remote-name] [branch-name]
git clone url
被邀请获得权限后
git push origin master
git fetch 【remote name】更新成员提交的内容
切换到taobao/master(远程跟踪分支,是本地分支和远程分支的媒介)分支
切回主分支,合并这个远程跟踪分支
深入理解远程库
远程分支
远程跟踪分支
本地分支
远程跟踪分支
远程跟踪分支是远程分支状态的引用。它们是你不能移动的本地分支。当你做任何网络通信操作时,它们会自动移动。
它们以【remote/branch】形式命名。例如你想要看你最后一次与远程仓库origin通信时master分支的状态,你可以查看origin/master分支
当克隆一个仓库时,它通常会自动创建一个跟踪origin/master分支的master分支
假设你网络里有一个在git.ourcompany.com的git服务器,假如你从这里克隆,git 的clone命令会自动为你将其命名为origin,拉去他所有的数据,创建一个指向它的master分支的指针,并且在本地将其命名为origin/master。git也会给你一个与origin/master分支在指向同一个地方的本地master分支,这样你就有工作的基础
git push --set-upstream origin [远程分支名] 给它和当前本地分支绑定一个远程跟踪分支
git branch --vv查看当前
git pull <remote><branch>拉取数据
git branch -u <remote><branch>
git pull
推送其他分支
当你想要公开分享一个分支时,需要将其推送到有写入权限的远程仓库上。本地的分支并不会自动与远程仓库同步--你必须显式地推送要分享的分支。这样,你就可以把不愿意分享的内容放到私人分支上,而将需要和别人协作的内容推送到公开分支。
1599412796188.png
跟踪分支
一个本地分支怎么去跟踪一个远程跟踪分支:
-
当克隆的时候,会自动生成一个master本地分支(已经跟踪了对应的远程跟踪分支)
-
新建其他分支时,可以指定想要跟踪的远程跟踪分支
git checkout -b 本地分支名 远程跟踪分支名
git checkout --track 远程跟踪分支名
- 将一个已经存在的本地分支改成一个跟踪远程跟踪分支的分支
git branch -u 远程跟踪分支名
删除远程分支
git push origin --delete serverfix //删除远程分支
git remote prune origin --dry-run //列出仍在远程跟踪但是远程已被删除的无用分支
git remote prune origin //清除上面命令列出来的远程跟踪
pull request流程
如果你想要参与某个项目,但是并没有推送权限,这时可以对这个项目进行派生(fork)。派生的意思是指,github将在你的空间中创建一个完全属于你的项目副本,且你对其具有推送权限。通过这种方式,项目的管理者不再需要忙着把用户添加到贡献者列表并给予权限。
人们可以派生这个项目,将修改推送到派生出的项目副本中,并通过创建合并请求(pull request)来让他们的改动进入源版本库
基本流程:
- 从master分支创建一个新分支(自己fork版本)
- 提交一些修改来改进项目(自己fork项目)
- 将这个分支推送到github(自己fork 的项目)
- 创建一个合并请求
- 讨论,根据实际情况继续修改
- 项目的拥有者合并或关闭你的合并请求
每次发起新的pull request时,要去拉取最新的原仓库的代码,而不是自己fork的那个仓库
git remote add <shortname 源仓库> <url 源仓库>
git fetch 远程仓库名字
Git merge 对应的远程跟踪分支
解决冲突
留坑
ssh
github自己的协议,用于验证你是哪个用户
优点是不用每次输入用户名和密码
ssh-keygen -t rsa -C <your email> //生成公私钥
把公钥复制到github的个人设置里面,以后每次填私钥就能验证身份了
.ssh文件位置:C:\User\Administrator.ssh
ssh -T git@github.com //测试公私钥是否已经配对
网友评论