GIT Recipe与食用笔记
关于git的安装和环境配置较简单,笔记中不再赘述,本篇Recipe主要从Git初使用烹饪至Git的高级操作
身份认证
在使用git进行版本控制前,必须预先配置好身份认证模式,否则在使用git命令时会出现certificate verify error之类的身份认证错误。
Git的身份认证有Https和SSH两种。前者同时需要账号和密码,在每次提交版本时都需要输入一遍;后者可以认为是记住密码(SSH-Key),并将密钥保存在本地。
关于Git中SSH的认证机制:
通过SSH(Secure Shell)的命令会生成一个公钥-私钥对(可以理解为相互匹配的键值对),公钥需要添加至Git服务器,私钥则需保存在本地,提交文件时通过公私密钥匹配验证身份信息
配置SSH-Key的步骤记录如下
a) 创建(或选择)一个空文件夹作为保存SSH-Key的路径,在命令行中打开,运行以下代码以验证终端是否支持SSH相关操作
ssh -V
用于制作此Recipe的Windows终端显示
OpenSSH_for_Windows_8.1p1, LibreSSL 3.0.2
在Linux远程服务器(Ubuntu)上运行该命令显示
OpenSSH_7.6p1 Ubuntu-4ubuntu0.5, OpenSSL 1.0.2n 7 Dec 2017
b) 可以先配置Git中的用户信息
git config // 查看本机是否配置了个人信息
git config --global user.name "..." // 定义全局用户名
git config --global user.email "..." // 定义全局邮箱地址
如果需要设置局部用户,则将命令中的 –global 替换成 –local
之后可以通过运行
git config --list
查看当前配置信息
c) 可以先在Git Bash中进入ssh根目录
cd ~/.ssh
应当先检查现有的SSH密钥,运行
ls -al ~/.ssh
如果没有.pub文件,则运行如下命令生成SSH密钥
ssh-keygen -t rsa -C "UserX@example.com"
(如果以前创建过.pub文件可以直接按照之后的步骤添加,半角双引号内就是使用者的邮箱,应该最好用GitHub的注册邮箱)
运行后命令行会提示设置当前子目录下的私钥保存地址,打印fingerprint并生成randomart image展示在命令行中。在没有输入文件名称时,密钥默认保存为id_rsa和id_rsa.pub文件(运行ls可查看当前文件夹目录),id_rsa.pub存储的时公钥内容(.pub: public)。
d)
处理公钥:将id_rsa.pub添加到Git服务器的SSH中(GitHub头像框处Settings->SSH and GPG keys->New SSH key,将id_rsa.pub中的内容粘贴到Key文本框,再取个名字作为tittle,点击Add SSH key添加即可)。
处理私钥:
启动SSH-Agent,会返回agent的Pid
eval $(ssh-agent -s)
添加SSH密钥至SSH-Agent,运行
ssh-add ~/.ssh/id_rsa
其中id_rsa是之前设置的SSH名称,
运行后会提示输入passphrase,就是自己设置的密码;
显示Identity added后就是添加成功了
e) 还可以通过如下命令的运行来验证SSH链接是否成功
ssh -T git@github.com
到这里差不多就配置完了
关于ssh指令还有较多参数可以设置和管理注意事项,详细可以直接参考
博客1
git初级命令
初级之初级
git clone ...
在git clone后加上GitHub上(或huggingface, Bitbucket Cloud, ect.)拷贝来的http地址或ssh地址将repository获取至本地,在终端中打开该文件地址后,应当存在.git文件夹(如果设置显示隐藏文件的话),后续的版本建立在这个基础上
运行
git status
会显示当前分支(branch)以及当前改动过的文件信息,主要用于查看当前版本更改状态
运行
git log
可以查看当前分支下的提交(commit)记录等信息
git add <document>
<document>是文件路径,将更改的文件信息添加至暂存区(staging area),如果文件很多嫌麻烦可以一键添加,运行
git add --all
几个名词的简单理解
unstaging area,staging area,工作目录,本地仓库,远程仓库,分支,工作树
1) 在含有.git文件夹的目录下,就是我们的工作目录;对原始版本直接修改,就是在unstaging area中操作,但直接在unstaging
2) staging area 即暂存区,与unstaging相对,本地文件通过add操作后进入此区域,开始参与版本控制,但这个过程不会被git log日志记录,且此时git应该还是认为本地仓库和云端仓库一致
3) 本地仓库的内容变动需要通过commit实现,同时附带一段备注消息,此时会认为本地仓库发生了更改;commit即提交,以分支为操作单元
4) 关于分支(branch),当建立了一个分支指针后,我们的修改仅限于这个分支上,在在push后会可以同时查看不同分支的信息;这里用到指针应该只是避免重复申请空间,通过索引访问区别新旧版本上一点中的commit也仅限于操作当前分支。
5) 工作树(work tree),应该可以直接认为是当前文件夹目录下的二叉树结构
暂存区(staging area)撤销操作
暂存区用于区分当前工作目录和repository中的内容,将本地工作文件添加到云端暂存区防止当前分支被污染;若git add误添加文件,可通过
git reset HEAD
将file退回到unstage区,即从staging区清除该文件,这里的HEAD是一个指定状态
如下指令
git rm file_path
根据file_path访问到文件,同时删除暂存区和分支上的文件,同时工作区也不需要(将文件从工作区一并删除)
而
git rm --cached file_path
删除暂存区或分支上的文件,保留工作区的源文件,只是不希望该文件参与版本控制
/这里工作区指的是本地工作目录
commit与push基本操作
可以先运行
git log
查看当前工作区域中使用git的操作记录
可以先通过status查看工作区域状态,核对添加至暂存区的文件
以下两行运行命令(均需在含.git文件夹的目录中运行)
git commit -m "msg"
git push origin (NAME)
NAME是repository中主branch的名称,如果是新建了一个分支plans想要上传,则应运行
git push origin plans
git commit 将已经加入到staging area中的文件进行确认,同时添加备注信息"msg",仍然是在本地进行操作
使用commit之后再通过status查看状态,得到
Your branch is ahead of 'origin/main' by 1 commit.
commit是在当前分支下,将本地修改附加上备注信息后添加到本地仓库中;而通过git push会将索引存入本地仓库中的文件或更新同步(上传)至云端,使得本地仓库和云端仓库同步。
以上是在只有一个分支情况下的关于commit和push操作结果的描述,这可以加深对 第4)点 的理解:
在使用多分枝控制时,需要先选择分支,再进行add,commit等操作,这些修改只会更改当前分支中的内容
关于分支(branch)操作
先再简述一下分支,参考Atlassian Git Tutorial上的图,可以理解为分支是通过节点来控制版本的。建立在了解了git push的基础上,分支相当于在本地为仓库添加修改信息后push至云端但不污染云端repository,不同的用户可以创建不同的分支,项目的管理者可以筛选接受,选择合适的分支merge进如main成为新版本
创建分支
git branch [name]
在[name]处输入分支的名字,比如我可以新建一个名字为plans的分支(之后用plans举例)
选择分支
git checkout plans
即指定我接下来的操作在哪个分支上进行,各个分支是互不影响的
Atlassian Git Tutorial上介绍branch时讲到了指针机制,在使用时可以忽略;个人理解的话,一整个社区repository的容量让开发者不足以通过直接复制开辟新的存储空间区分版本,最好的办法就是通过存储指针索引进而同时查看不同版本的架构
在当前分支上进行commit会得到类似于
Your branch is ahead of 'origin/main' by 1 commit.
的回应
合并分支(将分支plans并入主线origin中)
git merge origin/plans
当确认了plans分支中的修改后便可以将其merge到主线中,分支更换依然使用checkout命令
如果之后不再使用plans这个分支,可以考虑删除之,运行
git branch -d plans
关于分支合并,对父指针Fa和子分支So,合并命令为
git merge Fa/So
branch的进阶操作
一些便捷指令
创建并切换分支
git checkout -b xxx yyy
创建分支xxx并指明是yyy的分支
修改当前分支内容并提交至本地仓库
git commit -a -m "amended"
分支管理
由于branch比较重要,就多写一些(大雾)
分支过多时需要进行管理,一些查看分支的操作如下
git branch // 查看本地分支,当前使用的分支会带*标记
git branch -r // 查看远程repository分支列表
git branch -a // 查看全部分支,远程以remotes/开头
git branch -v // 查看分支的最后一次提交
git branch --merged // 查看已合并到当前分支的分支
git branch --no-merged // 查看所有未合并工作分支
分支合并
上面提到的branch操作都是针对线性的分支结构,通过merge将子合并到父上即可,事实上分支合并中存在 快进合并 :当爷孙(相差多代)分支为A, B,两者间为单一线性路径时,实际合并时只是将A处指针移动至B,跳过中间分支。这之间指针存放的分支其实还保留着,所以A向后合并至B后其实还能够向前“回退”,只要AB之间的指针没有手动删除。
当对同一个分支节点建立了不同分支时,这样非线性路径的branch结构就产生了冲突。实际合并时会先通过快进合并对线性路径合并,之后使用三路合并算法解决分支冲突。
关于 冲突 可参考博客2
一些进阶烹饪方式
commit管理
commit是提交操作,在push之前,参与commit的内容只是参与本地仓库的版本控制
必要时,我们可以通过rebase指令合并提交
git rebase -i HEAD~n
可以合并最近的n次提交
在运行该命令后,命令行会自动进入如下vi编辑模式
绿色字样pick为可修改的命令(刚进入编辑模式时无法修改,按一下a可编辑),具体参数命令行中已经给出,部分记录如下
使用rebase合并commit
|
|
|
---|---|---|
pick | 保留commit内容 | p |
reward | 保留commit内容,但我要修改提交的注释 | r |
edit | 保留commit内容,但我要停下来修改(不止是注释) | e |
squash | 单纯将该commit与先前项合并 | s |
fixup | 将该commit与前一条合并,但不保留该条注释信息 | f |
exec | 执行shell命令 | x |
drop | 丢弃该条commit | d |
编辑完成后,按Esc键+:(半角)+x(即exec命令)+Enter即可保存并退出,执行命令
另外,如果还需要修改,可运行命令
git rebase --edit-todo
会有如下 hint
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5TigdlWD-1678625156736)(2.jpg)]
按照绿色字样提示操作,可重新进入之前的vi编辑模式
特别地,我们可能再提交时输错了备注,通过如下方式更改
运行
git commit --amend
此时命令行会进入上一条commit的信息界面(vi编辑),按一下a后可修改备注;
编辑完成后,按Esc后输入:(半角冒号),再键入wq+Enter即完成修改并退出vi编辑模式
使用revert撤销提交
通过revert撤销,会保留前后的commit和history,并将本次撤销作为一次新的提交
git revert HEAD # 撤销前一次commit
git revert HEAD^ # 撤销前前一次commit
git revert commit-id # 撤销指定id的commit
这里的commit-id要填长的Hash码
若想要撤销连续的commit,可以运行
git revert -n commit-id-A..commit-id-B
效果是:撤销A-B之间的commit使得版本回到A所在的位置(也就是撤销了A+1到B的提交,左开右闭)
错误撤销回退
如果使用revert错误撤销了commit,此时还是可以补救的,运行
git reflog
找到撤回的commit-id,通过
git reset --hard commit-id
恢复该条commit
既然用到了reset那也就再写一点,reset用法为
git reset [--soft | --mixed | --hard] [HEAD]
|
|
---|---|
mixed | 为默认值,可不带参数:不删除空间工作空间改动代码,撤销commit,并撤销add |
soft | 不删除工作空间改动代码,撤销 commit,不撤销 git add |
hard | 删除工作空间改动代码,撤销 commit,撤销 git add |
本地版本管理
当远程repository的拥有者更新了仓库,本地工作需要建立在新仓库的基础上,运行
git pull
使用默认参数同步云端仓库进度
具体参数如下
git pull <远程主机名> <远程分支名>:<本地分支名>
刚开始用git的时候永远是无脑push(×),因为都是在一个branch上做的而没有出现问题。
git push <远程主机名> <本地分支名>:<远程分支名>
注:这里半角冒号前后均无空格
值得注意的是,分支推送(git pull)顺序的写法是<来源地>:<目的地>,因此git pull是<远程分支>:<本地分支>,而git push是反过来<本地分支>:<远程分支>
origin就是远程主机,因此完整的git push的一种写法(从本地main到远程main)
git push origin main:main
版本比较git diff
命令
git diff
比较本地工作区和暂存区的差异,这是不加参数的默认情况,参数记录如下
git diff --cached # 比较暂存区与本地最新版本库(最近一次commit)
git diff HEAD # 比较工作区与本地最新版本库
git diff <commit-id> <path> # 比较指定工作区目录与指定id的commit的差异
git diff --cached <commit-id> <path> # 比较指定目录下暂存区与最新commit的内容
git diff <commit-id1> <commit-id2> # 比较两个commit内容
查找资料的过程中发现git diff还可用于制作补丁文件patch,干脆来记录一下补丁的打法
diff制作补丁patch
例如我在分支desargues下(我建立的主分支名字为desargues)创建了文件11.cpp,内容为
#include<bits/stdc++.h>
using namespace std;
加入暂存区并确认提交
git add 11.cpp
git commmit -m "std"
然后新建并切换到分支new
git checkout -b new
修改后的11.cpp为
#include<bits/stdc++.h>
using namespace ios;
同样先track
git add .
git commit -m "ios"
可以在git log中查看两次commit的short Hash码
使用diff比较两个commit的差异并将差异作为输出传入到.patch文件中
git diff b536cf48af9e5fa05fd581c05827948241e9e477 ea79e9ca874a4085f8a20ea55f735087b1659b78 > implement.patch
可以打开implement.patch文件,内容就是diff的输出,例如
注意,不要将补丁add或者commit,直接切换到原来的分支
git checkout desargues
运行
git apply --check implement.patch
先检查补丁是否可用,无输出即为可用;然后可以应用补丁
git apply implement.patch
参数省略
如果省略远程分支名,则表示将本地分支推送与之存在"追踪关系"的远程分支(通常两者同名),如果该远程分支不存在,则会被新建。
特别地,如果省略了本地分支名,会被认为是一个空分支,如
git push origin :main # 举例,慎用!!!
这等同于删除命令
git push origin --delete main
因此,没有掌握好push的用法,也是一件比较危险的事(大雾,虽然可以撤销)
更详细讲解在博客中有记录
.gitignore的书写
通过文件忽略可以保护信息,如果只是临时指定项目行为,可修改git项目中.git/info/exclude文件,将需要忽略的文件写入。另外,可以创建.gitignore文件实现针对全局的文件。手动建立.gitignore文件后可以放在工作目录的任意位置,然后运行
git config --global core.excludesfile ~/.gitignore
配置git;
之后按照忽略规则的正则语句在.gitignore中书写即可,这里的正则表达比较简单就不记录了,可以参考英文官方文档;需要注意的一点是,书写在.gitignore中的文件名似乎不区分大小写。
需要注意的是,对于已经被track(被追踪,即被纳入版本管理范围),通过修改.gitignore是不能将之忽略的,需要先清除本地track的索引缓存(即将所有文件改为未track的状态)再commit实现忽略
git rm -rf --cached .
git add .
git commit -m msg
详细中文讲解参考博客
特别地,命令
git add -f
可以track被.gitignore指定要忽略的文件:从这个角度看,add操作将某文件添加至暂存区其实就是开始使用git追踪它
remote管理远程仓库
命令
git remote
是针对远程repository的操作,可以用于查看当前工作目录下的项目在远程仓库中的信息
git remote -v
如在xlab-training-program中运行结果为
顺便提一嘴,这里的origin是远程仓库的别名
复制这里的link,运行
git remote show [link]
即可查看远程仓库信息,例如
另外,也可以用remote实现提交push的操作
git remote add [name] [link]
name是远程主机地址,前面记录过,可以用origin来指代;上述命令等效于
git push -u origin [branch-name]
这里branch-name是分支名称,比如可以填main或者前面示例分支plans
注:在搜资料的时候看到很多地方用了master,感觉弄不是很懂,去搜origin,master以及main的区别时在百度上发现了2020年GitHub使用main取代master表达的新闻 (乐)
这下就没啥问题了
关于remote的其他命令还有
git remote rm [name] # delete repository
git remote rename ori_name new_name # rename
关于fetch与pull
简单来讲,git fetch和git pull都能将远程repository中的更新同步到本地,可以认为
git fetch = git pull + git merge
要弄清这两者的区别,先熟悉几个概念:
1) FETCH_HEAD 是一个存储版本指针的文件,记录远程仓库已获取部分的末尾地址,位置为.git/FETCH_HEAD
2) commit-id 每个commit都有一个id编码,rebase处vi编辑模式 有id示例,该编码是一个可以唯一识别版本的序列号,如何查看commit-id?
在以上两个概念的基础上,使用fetch控制版本的过程为
git fetch origin plans:tmp
git diff tmp
git merge main/tmp
git branch -d tmp
先拉取远程版本信息的commit-id等内容到本地新建的临时分支tmp中,比较tmp与当前本地仓库版本信息的不同,diff将末尾分支索引保存到.git/FETCH_HEAD中,再将这个tmp并入本地的plans分支上;tmp应该用不到,可以考虑删掉
而如果使用pull的话,只需运行
git pull origin main:plans
这就是为什么说pull=fetch+merge,merge的是那个临时分支tmp
标签的使用
打标签的一个用途是记录当前版本的 里程碑意义 (bushi),参考博客,有【轻量标签】和【附注标签】两种用法,个人认为这篇博客记录的非常详细和完备,因此这里不再赘记(大雾)
提交转移
之前查资料的时候看到一个很有意思的命令cherry-pick,用于多branch下转移commit,这里需要用到commitHash
git cherry-pick <commitHash>
会将指定的提交应用到当前分支,commitHash是提交的Hash码(short)即前面提到的commit-id;运行命令后,当前分支下产生一个新的commit(内容一样Hash值不同),注意要预先切换好分支。
关于产生新提交的理解是,在当前branch尾部添加一个以指定Hash码commit为提交的新分支
另外,一次cherry-pick可以指定转移从A到B之间的多个提交Hash
git cherry-pick [Hash-A] [Hash-B]
这两个提交在时间上必须从早到晚,否则命令无法执行(不报错)
上一命令,是左开右闭的区间值(有点反常),如果要包括左边的值,命令修改为
git cherry-pick [Hash-A]^..[Hash-B]
以上cherry-pick的内容参考了博客
附注
想不出来这部分放哪儿
查看commit-id
运行
git rev-parse HEAD
将获取完整commit的Hash码,例如 3ba8bc5be876878bcd3d65bd04c562b390b80ada
运行
git rev-parse --short HEAD
返回简化的Hash码,即commit-id,与vi编辑模式中显示的commit内容前的码是同一个
关于产生新提交的理解是,在当前branch尾部添加一个以指定Hash码commit为提交的新分支
另外,一次cherry-pick可以指定转移从A到B之间的多个提交Hash
git cherry-pick [Hash-A] [Hash-B]
这两个提交在时间上必须从早到晚,否则命令无法执行(不报错)
上一命令,是左开右闭的区间值(有点反常),如果要包括左边的值,命令修改为
git cherry-pick [Hash-A]^..[Hash-B]
以上cherry-pick的内容参考了博客
附注
想不出来这部分放哪儿
查看commit-id
运行
git rev-parse HEAD
将获取完整commit的Hash码,例如 3ba8bc5be876878bcd3d65bd04c562b390b80ada
运行
git rev-parse --short HEAD
返回简化的Hash码,即commit-id,与vi编辑模式中显示的commit内容前的码是同一个