生成SSH
用SSH(Secure Shell)干什么?
SSH以非对称加密实现身份验证。身份验证有多种途径,例如其中一种方法是使用自动生成的公钥-私钥对来简单地加密网络连接,随后使用密码认证进行登录;另一种方法是人工生成一对公钥和私钥,通过生成的密钥进行认证,这样就可以在不输入密码的情况下登录。任何人都可以自行生成密钥。公钥需要放在待访问的电脑之中,而对应的私钥需要由用户自行保管。认证过程基于生成出来的私钥,但整个认证过程中私钥本身不会传输到网络中。
ssh-keygen命令:生成一对SSH密钥
$ ssh-key -t ed25519 -C "your comment"
Generating public/private ed25519 key pair.
随后命令行会询问你存储密钥的位置:
Enter file in which to save the key (/home/tsum/.ssh/id_ed25519):
直接回车,密钥将存放在默认位置~/username/.ssh/
文件夹下。
随后要求设置一个密码:Enter passphrase (empty for no passphrase):
连续敲两次回车,可以缺省不填。
常用Flags:
- -t 是Type的缩写,用于指定密钥类型。常见的有rsa、dsa、ed25519(都是加密算法)
- -C (comment)表示提供一个注释,用于区分这个密钥。
- -b 是bits的缩写, 用于指定密钥的长度。对于RSA密钥,最小要求768位,默认是2048位。4096指的是RSA密钥长度为4096位。DSA密钥必须恰好是1024位(FIPS 186-2 标准的要求)。
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
用rsa算法生成一对4096位的密钥,注释是:your_email@example.com
最终生成的密钥:
带有.pub后缀名的是公钥(可以理解为门锁),没有后缀的是私钥(理解为钥匙)。
ssh-add命令:将私钥身份添加到 ssh-agent 身份验证代理
- 开启 ssh-agent。默认情况下操作系统是不开启ssh-agent的。
$ eval "$(ssh-agent -s)"
Agent pid 470
- 把私钥添加到ssh-agent中。
$ ssh-add ~/.ssh/id_ed25519
Identity added: /home/tsum/.ssh/id_ed25519 (2207428258@qq.com)
也可以直接使用
$ ssh-add
Identity added: /home/tsum/.ssh/id_rsa (2207428258@qq.com)
Identity added: /home/tsum/.ssh/id_ed25519 (2207428258@qq.com)
直接添加所有的密钥
常用Flags:
-L
:查看ssh-agent中所有身份的公钥 (可以cd去.ssh/文件夹对应着看看)
$ ssh-add -L
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDqSJWKpZQYJdCX2R+VtpH08wmhs/7/fwfe6cQ2Usq1j9ybeaS4Ic9RazAdZo50IM1PNf9cRSAPt3H1HZlCu71ti2eqdl+FKiHBjD/acswianDhA59voQK48vUML/3sXunfI5G3l5LhbVC7Z8h1tF/MqzuBansN4C3SZGNglt6u9zIFDRRilCQ5tWWs33y5PwEprbkyQsma63VRYXh5XELU/+n6iRTAVIQUG9e5LJmbvkmCO5EIsjsxIo5I20vvAtiRDQT+nzvs0hwVs2qqnWuuiEd081qA8vbuPv2gvC5thl2v3lgVQBJYjQqYCC5rkVcWg/nP0/E3yhwYEJuyejWR5jC5vgeygEnqm3OF5lxdC22xVhISOGk+DnqHV7S6IYNkadA5QoyLcQ5qxRIaGZ7iWmi04qT2es/Syifw2GY0jX2j8IPA+OuoKqdCccLzjjrPfcFukjR/DUuWbPwnFUeRrT7agO2G4UwYjjQbskmHbPiaiI+KbCbntMpX65cbw/c= 2207428258@qq.com
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP6Gvxv4/xKlG99/F5ISmLFrmu7BjkpX5O35tDkXSCbz 2207428258@qq.com
-l
:列出ssh-agent 中所有身份的指纹(私钥)
$ ssh-add -l
3072 SHA256:S3GsFBitbn3rcMX8ky/qK+bIuPZtY6N0ftns0NTaE1s 2207428258@qq.com (RSA)
256 SHA256:tJJmHXRVP+NQGpMx/7EyYCo8DgxTxkuhtt4xs/FEJKw 2207428258@qq.com (ED25519)
所以ssh-agent就像一把钥匙串。登陆其他主机的时候,会自动将所有钥匙(私钥)往钥匙孔(公钥)里插一遍。
-d
:从ssh-agent 中删除密钥
$ ssh-add -d ~/.ssh/id_rsa.pub
Identity removed: /home/tsum/.ssh/id_rsa.pub (2207428258@qq.com)
-D
: 从ssh-agent中删除所有密钥
注意重启Ubuntu后也需要重新开启ssh-agent,并用ssh-add添加密钥。
将公钥添加到github账户中
首先查看公钥内容。
当然也可以在.ssh文件夹下直接看
复制到github上
测试是否连接成功
ssh -T git@github.com
The authenticity of host 'github.com (20.205.243.166)' can't be established.
ECDSA key fingerprint is SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'github.com,20.205.243.166' (ECDSA) to the list of known hosts.
Hi Tsumgo! You've successfully authenticated, but GitHub does not provide shell access.
第一次连接可能会提示警告:
The authenticity of host '``github.com`` (20.205.243.166)' can't be established.
原因是,.ssh/文件夹下缺少了一个known_hosts
文件。
如果当前主机为 SSH 客户端, ~/.ssh 目录下会有一个 known_hosts 文件,该文件用来记录连接过的远程主机。
此时选择yes即可,同时生成缺少的known_hosts文件
基本概念
Git是一个分布式的版本控制系统。
版本控制系统(VCSs)
是一类用于追踪源代码(或其他文件、文件夹)改动的工具。顾名思义,这些工具可以帮助我们管理代码的修改历史;不仅如此,它还可以让协作编码变得更方便。VCS通过一系列的快照(snapshot)将某个文件夹及其内容保存了起来,每个快照都包含了文件或文件夹的完整状态。同时它还维护了快照创建者的信息以及每个快照的相关信息等等。
什么是工作区(working directory)、缓存区(staging area)和提交(commits)
工作区:在资源管理器中可见的所有文件。
对于Git来说,这部分文件又分为3种:
- Untracked: 一些新建的文件。使用git add命令可以让Git跟踪他们,变为Tracked。
- Tracked:跟踪到的文件。已跟踪的文件即使改动后,依然处于Tracked状态。
- ignored:忽略的文件。
缓存区:保存更改的中间过程
在git add
命令后,文件会进入缓冲区,防止误操作。
提交:记录新版本到历史记录中。
在git commit
命令后,新的版本会被记录。用git checkout命令可以随时回到过去的版本中。
新建一个仓库(repository)
什么是git 仓库?
A Git repository is a virtual storage of your project. It allows you to save versions of your code, which you can access when needed.
Git init:初始化文件夹,作为一个Git仓库(repo)
首先,cd 至一个项目文件夹(我这里是TextEditor),这里用于存储整个项目所需要的代码。
使用以下命令初始化:
$ git init
Initialized empty Git repository in D:/Code/TextEditor/.git/
.git 文件夹内包括所有git操作需要的文件。具体可参考:https://blog.csdn.net/mayfla/article/details/78653396
当然,为了利用好开源共享性,我们可以直接拷贝网络上已有的开源仓库。
Git clone: 从远端下载仓库
用法:
git clone <repo URL>
由于 Git 支持不同的网络传输协议,因此这里的 repo URL 可以有很多不同的写法。
- http(s)://
HTTP协议。例如: https://github.com/alibaba/MNN.git
- ssh://
SSH协议。一个SSH协议地址通常长这样:
git@HOSTNAME:USERNAME/REPONAME.git
HOSTNAME 主机名
USERNAME 用户名
REPONAME 仓库名称
一个例子是:
git@github.com``:alibaba/MNN.git
- 本地路径(略)
例:
$ git clone https://github.com/alibaba/MNN.git
就可以在当前文件夹下得到MNN源码文件。
Saving Changes
当我写好了代码,如何让git记录我做出的更改?
在git的版本控制流程中,保存更改有两个步骤
下面以一个TextEditor项目为例。MainTest.c
是主程序,include/
与 libgraphics/
分别存储头文件及相应的.c文件
Git status:查看当前仓库的状态
单从它的功能就可以猜到,这将是我们使用git过程中使用最频繁的命令之一。
git告诉我们,在master
分支上,还没有任何提交记录。文件夹下有好多新文件没有被追踪到。
同时提醒我们,可以用git add <file>
命令,告诉git我将要提交哪些文件。
因此下一步:
Git add: 添加被更改的文件到缓冲区(staging area)
用法:
$ git add <file> -- 添加单个文件
$ git add <directory> -- 添加整个文件夹下的新文件、有改动的文件
如果我希望让仓库记录我所做的更改,首先使用:
$ git add MainTest.c include libgraphics
命令添加MainTest.c
include\
libgraphics\
到git的缓冲区中。
此处warning暂时不用关注。详情:https://blog.csdn.net/Babylonxun/article/details/126598477
注意,此时上述改动并没有被记录到仓库的历史记录中。这些文件被存放在了缓冲区。
我们可以再查看一次当前仓库的状态。
命令行提示我们,需要提交
(commit)之后,这些改动才能生效。
附:可以用git add -all
将所有的改动添加至缓冲区。
Git commit:记录并跟踪当前的改动。
$ git commit -m "add .c .h files to the repo"
-m
代表 message,用于描述此次提交的改动内容。
此时,我们成功完成了一次提交。这次改动会被记录到仓库的历史记录中,方便我们随时查看。
commit操作还有更多可选flags:
- 无参数:执行后会弹出文本编辑器,需要我们写本次的提交信息。
-a
:可以省去一步git add操作,直接将当前文件夹状态存储为一个快照。
但是请注意,新增文件不会在此次commit范围内。它们仍然需要git add之后再提交。
option -a/-all: Tell the command to automatically stage files that have been modified and deleted, but new files you have not told Git about are not affected.
-am
:综合-a
和-m
的两个特性。
例:
$ git commit -am "add a new file"
--amend
:追加更新上次commit。
$ git add hello.py
$ git commit --amend
此时会弹出文本编辑器,发现内容是上次编辑的commit message。因此我们的更改是基于上次快照的修改,并没有创建一个新的快照。
通过以上内容,不难发现:git add
与 git commit
通常是一起使用的。
例如:
$ git add .
$ git commit
为当前文件夹的所有文件创建快照。
$ git add hello.py
$ git commit
为某个新文件/修改后的文件创建快照。
在实际合作过程中,我们通常将每一个改动的文件分别提交。这样虽然增加了操作繁琐度,但是有利于日后在庞大的更新中定位Bug。
Git Diff:查看差异
我们在MainText.c
文件中做一些改动。
在命令行中输入git diff
从命令行给出的反馈可以看出,git diff 比较了工作区(working directory**)与上一次提交(commit)**的差别。
用git add
命令将它存储到缓冲区(staging area)。
$ git add MainTest.c
随后在工作区添加注释:
在命令行中输入git diff
不难发现,这里git diff
比较了工作区(working directory**)与缓冲区(staging area)**的差别。
我们首先解读上述内容。
diff --git a/MainTest.c b/MainTest.c
可以看出传给diff命令的两个文件。分别是 a/MainTest.c
和b/MainTest.c
分别是缓冲区(staging area)的文件、工作区(working directory)下的MainTest.c
。
index b68cee0..f1f0870 100644
这里是一个哈希值,用于代表git中的一个对象。用处不大。
- 图例
这部分表示:在下面的语句块中, '-'
表示a/MainTest.c
的改动,'+'
表示 b/MainTest.c
的改动。
- 比较语句块
绿色部分以'+'
开头,表示b/MainTest.c 中改动的内容
红色部分以'-'
开头,表示a/MainTest.c 中改动的内容。
为什么两次git diff
比较的内容不同?
当工作区有改动,缓冲区为空,**diff的对比是“工作区与最后一次commit提交的仓库**的共同文件”;
当工作区有改动,缓冲区不为空,**diff对比的是“工作区与缓冲区**的共同文件”。
其他用法
--stat
:显示简略信息。
在上述情况下,添加--stat
参数,会告诉我们有1个文件被修改,文件中有两处插入,一处删除。
--cached
or--staged
:显示缓冲区与最后一次commit(HEAD)之间的增删改。
在上述条件下继续实验,首先进行一次add
操作。
第一次git diff
:由于缓冲区不为空,比较工作区与缓冲区的文件差异。不输出。
第二次git diff --cached
:比较缓冲区与上一次commit(HEAD)的文件差异。有输出。
更多用法详见博客:https://blog.csdn.net/wq6ylg08/article/details/88798254
.gitignore文件
实际的项目中,我们不希望某些文件被跟踪到(例如.o文件,占空间)。
我们只需要写一个.gitignore
文件,就能实现这个需求。
我们需要用到类似正则表达式的通配符。
看下面例子很好懂。
Pattern | Example matches | Explanation* |
---|---|---|
**/logs | logs/debug.log logs/monday/foo.bar build/logs/debug.log | You can prepend a pattern with a double asterisk to match directories anywhere in the repository. |
**/logs/debug.log | logs/debug.log build/logs/debug.log but not logs/build/debug.log | You can also use a double asterisk to match files based on their name and the name of their parent directory. |
*.log | debug.log foo.log .log logs/debug.log | An asterisk is a wildcard that matches zero or more characters. |
*.log !important.log | debug.log trace.log but not important.log logs/important.log | Prepending an exclamation mark to a pattern negates it. If a file matches a pattern, but also matches a negating pattern defined later in the file, it will not be ignored. |
.log !important/.log trace.* | debug.log important/trace.log but not important/debug.log | Patterns defined after a negating pattern will re-ignore any previously negated files. |
/debug.log | debug.log but not logs/debug.log | Prepending a slash matches files only in the repository root. |
debug.log | debug.log logs/debug.log | By default, patterns match files in any directory |
debug?.log | debug0.log debugg.log but not debug10.log | A question mark matches exactly one character. |
debug[0-9].log | debug0.log debug1.log but not debug10.log | Square brackets can also be used to match a single character from a specified range. |
debug[01].log | debug0.log debug1.log but not debug2.log debug01.log | Square brackets match a single character form the specified set. |
debug[!01].log | debug2.log but not debug0.log debug1.log debug01.log | An exclamation mark can be used to match any character except one from the specified set. |
debug[a-z].log | debuga.log debugb.log but not debug1.log | Ranges can be numeric or alphabetic. |
logs | logs logs/debug.log logs/latest/foo.bar build/logs build/logs/debug.log | If you don’t append a slash, the pattern will match both files and the contents of directories with that name. In the example matches on the left, both directories and files named logs are ignored |
logs/ | logs/debug.log logs/latest/foo.bar build/logs/foo.bar build/logs/latest/debug.log | Appending a slash indicates the pattern is a directory. The entire contents of any directory in the repository matching that name – including all of its files and subdirectories – will be ignored |
logs/ !logs/important.log | logs/debug.log logs/important.log | Wait a minute! Shouldn’t logs/important.log be negated in the example on the left Nope! Due to a performance-related quirk in Git, you can not negate a file that is ignored due to a pattern matching a directory |
logs/**/debug.log | logs/debug.log logs/monday/debug.log logs/monday/pm/debug.log | A double asterisk matches zero or more directories. |
logs/*day/debug.log | logs/monday/debug.log logs/tuesday/debug.log but not logs/latest/debug.log | Wildcards can be used in directory names as well. |
logs/debug.log | logs/debug.log but not debug.log build/logs/debug.log | Patterns specifying a file in a particular directory are relative to the repository root. (You can prepend a slash if you like, but it doesn’t do anything special.) |
例如,我希望忽略文件夹中由Visual Studio自动生成的文件。
即此处.vs/ x64/ .sln .vcxproj
等等文件。
在.gitignore文件中保存如下内容:
可以发现资源管理器中相应文件变成灰色。
再用git status 查看当前工作区状态。
原来一长串的Untracked files不见了。 最后我们只需要将.gitignore 提交即可。
Undoing Changes
git revert
如果我们想要回退去修改版本二(有bug),但又不想删除版本三,可以使用revert命令。
Git revert会新建一个版本四,其中包含了修改后的版本二与版本三。
git reset
git reset的作用是:修改HEAD的位置,即将HEAD指向的位置改变为之前存在的某个版本,如下图所示,假设我们要回退到版本一:
适用场景: 如果想恢复到之前某个提交的版本,且那个版本之后提交的版本我们都不要了,就可以用这种方法。
Inspecting a repository
Git log:显示历史日志
在上面的项目中我们进行了两次commit。此处列出了commit message、修改人及其电子邮箱。
commit 31de595cf3461e6bf01ca984fd8881ce849c627a (HEAD -> master)
黄色部分为一串Hash值,git以此来区分不同的commits。
蓝色的HEAD是一个游标,表示当前工作区内展现的是master分支的内容。
这样子的日志显得很杂乱。我们有办法让日志更好看一点。
$ git log --all --graph --oneline --decorate
该命令使用有向无环图展现一个仓库的更新日志。与上面的命令比起来更加赏心悦目。
其他选项:
$ git log -n <limit> 只显示最新的 <limit> 次提交
$ git log --author="<pattern>" 查看特定作者的提交,使用正则表达式筛选
$ git log --grep="<pattern>" 用正则表达式筛选commit message
$ git log <file> 筛选包含特定文件的提交
Syncing
Git remote:与远程仓库联系
git remote
:列出远端。
git会为通过git clone
命令下载到本地的仓库建立与源仓库的联系。
MNN是从网上克隆到本地的仓库。
PS D:\Code\MNN> git remote -v
origin https://github.com/Tsumgo/MNN.git (fetch)
origin https://github.com/Tsumgo/MNN.git (push)
-v
表示verbose。git把源仓库的地址 https://github.com/Tsumgo/MNN.git 称为origin
。
git remote add <name> <url>
:添加一个远端。
我们在github上新建一个项目,并复制该项目的SSH地址。(不用HTTP协议,是因为SSH能免密码)
PS D:\Code\TextEditor> git remote add origin git@github.com:Tsumgo/TextEditor.git
PS D:\Code\TextEditor> git remote
origin
那么当前仓库就与一个叫origin的仓库建立了联系。
接下来我们将本地项目上传至远端仓库。
Git push:将对象传送至远端仓库
git push <remote> <local branch>:<remote branch>
$ git push origin master:master
在github上,我们就能看到本地文件在远端主机上更新了。
需要注意的是,每次在分支上**push**本地文件时应当首先拉去远程仓库的最新版本。
Git branch & git checkout
一个项目可以有多个分支同时开发。下面使用git branch
、git checkout
命令模拟对不同分支的管理。
git branch
$ git branch <branch> # 创建名为<branch>的分支
$ git branch -d <branch> # 删除<branch>分支。如果当前分支有未合并的更改则不会删除。
$ git branch -D <branch> # 强制删除<branch>分支。
$ git branch -m <branch> # 重命名当前分支
Git checkout: 切换分支
$ git checkout <commit> # 切换到其他分支
其中
建立两个分支
$ git branch cat # 创建cat 分支
$ git branch dog # 创建dog 分支
# 在文件中做一些修改。增加cat函数
$ git checkout cat 切换到cat分支
$ git add MainTest.c
$ git commit -m "add function cat"
$ git log --all --graph --oneline --decorate
我们可以看到此时仓库的每一个历史提交。
游标HEAD指向cat分支。同时我们在工作区查看MainTest.c,发现存储的确实是cat分支的内容。
而master、dog分支停留在上一个commit中,还未更改。使用
$ git checkout dog
切换到dog 分支。
并在工作区中查看MainTest.c
,已经回到了上一次提交的状态。
Git pull <remote>
:拉取远端仓库特定分支的最新版本。
Git pull (git fetch + git pull) 与远端仓库同步
我希望下载远端仓库的某些分支到本地进行开发,需要经历以上过程。
Git fetch
命令下载远端仓库的指定文件,但它们并没有进入本地仓库,只是在本地有个副本
Git merge
命令可以将远程仓库的副本加入到本地仓库中,其间需要程序员主动考虑合并细节。
Git pull
相当于上述两个步骤的合并。
$ git pull <remote> <remote branch>:<local branch> # 将远程主机的分支拉过来 与<local branch>合并。
$ git pull <remote> <remote branch> # 如果与当前分支合并,可以省略本地分支。
Work Flows
Feature Branch Workflow
- 首先,从主分支(master)创建一个新分支,命名为“feature/xyz”,其中“xyz”代表正在开发的特定功能。
- 接下来,在该分支上进行所有与特性相关的更改和提交。
- 当特性开发完成时,将其合并回主分支(master)。在合并之前,应该尽可能确保代码的稳定性,并通过测试确保它的质量。
如果需要对特性进行更改或修复,可以在feature分支上继续进行更改和提交,然后将其再次合并到主分支。
这种工作流程的好处在于,它允许开发人员独立地处理不同的功能开发,而不会影响其他特性或主分支的稳定性。它也使得跟踪特性开发进度变得更加容易,并且有助于团队成员更好地协作。
Forking Workflow
- 开发人员首先从公共代码库中fork一个副本到自己的GitHub账户下。
- 然后,在其个人副本上创建一个新的分支,以便在其中进行所需的更改和提交。
- 当对代码进行了更改并已准备好合并到主代码库时,开发人员将新分支推送到他们的个人副本,并创建一个“pull request”(PR)以请求将更改合并回主代码库。
- 在进行代码审查、测试和合并之前,其他团队成员或贡献者可以在PR中提出评论或建议修改。
- 一旦所有必要的更改都已审核并通过测试,开发人员就可以将更改合并回主代码库。
这种工作流程的优点在于,它允许开发人员在不影响公共代码库的情况下进行独立的开发工作,并且保持原始代码库的稳定性。它也使得跨多个开发团队协作变得更加容易,因为每个人都可以在自己的个人副本上工作,并通过pull request将更改合并回主代码库。