77. Git 使用
ssh key
1 | ssh-keygen -t ed25519 -C "comment" -f file_name -N '' -q |
选项说明
-t
选项(默认 rsa)生成的密钥类型(不带默认为 rsa), 类型有四种分别是 dsa | ecdsa | ed25519 | rsa
- dsa 因安全性不高, 基本已经不再使用.
- ecdsa 因技术原因等, 基本已经不再使用.
- ed25519 加解密速度快, 生成时间快, 且安全性更高.
- rsa 加解密速度慢, 生成时间慢, 安全性没有 ed25519 高. (用的人多兼容性好)
-b 选项
表示生成的密钥的大小(以字节:byte 为单位)
在生成时如果指定的 key size 太小也会有安全的问题, 建议指定 key size 为 2048 或更大.-C 选项
生成密钥的描述信息. (会追加到公钥.pub 内容的末尾)
-f 选项
文件名称, 或者文件路径.
-N 选项
指定新密码, ‘’ 表示密码为空.
-q 选项
静默模式, 直接生成密钥对, 不现实过程中产生的信息.
连接Github
在
terminal
输入ssh-keygen
进入
~/.ssh
id_rsa
是 私钥id_rsa.pub
是 公钥将公钥复制到
Github
下- 点击头像, 选择
Setting
- 选择
SSH and GPG keys
- 选择
New SSH key
- Title 可以自己写;
Key
中复制你的公钥 - 选择
Add SSH key
- 下面就可以访问了
- 点击头像, 选择
git简单使用
将git下载到本地
git clone <你的仓库地址>
cd 进入 对应的文件夹
git pull
更新你的仓库git status
以查看在你上次提交之后是否有修改该命令加了
-s
参数, 以获得简短的结果输出. 如果没加该参数会详细输出内容.git add
将该文件添加到缓存git add *
将所有更改的文件, 添加进暂存git add <文件名>
将对应的文件添加进暂存
git commit -m "备注"
使用 git add 命令将想要快照的内容写入缓存区, 而执行 git commit 将缓存区内容添加到仓库中.git push
将本地库中的最新信息发送给远程库.
Notes
若已经有文件:
- 创建完git连接之后, 先
git pull
一下.
commit
和push
的区别
git作为支持分布式版本管理的工具, 它管理的库(repository)分为本地库、远程库.
git commit操作的是本地库, git push操作的是远程库.
git commit是将本地修改过的文件提交到本地库中.
git push是将本地库中的最新信息发送给远程库.
那有人就会问, 为什么要分本地commit和服务器的push呢?
因为如果本地不commit的话, 修改的纪录可能会丢失. 而有些修改当前是不需要同步至服务器的, 所以什么时候同步过去由用户自己选择. 什么时候需要同步再push到服务器
设置vimdiff
1 | git config --global diff.tool nvimdiff |
gitdiff 在打开每一个文件的 diff 时都会进行输入确认, 设置
difftool.prompt
可以禁用这一行为.
给 difftool 起个别名 vd
此后使用 git difftool 时 Git 便会调用 vimdiff 逐一打开每个文件的 Diff. 查看完成一个文件的 diff 后使用 :qa
关闭该文件 diff.
这时 Git 会自动打开下一个文件的 diff. 如果中止本次 Diff 呢?首先需要让 Git 信任 difftool 的返回码:
1 | git config --global difftool.trustExitCode true |
然后让 vimdiff 返回 1::cq
(:help cquit
) 退出 Vim.
创建版本库
init
git init
命令把这个目录变成Git可以管理的仓库
也不一定必须在空目录下创建Git仓库, 选择一个已经有东西的目录也是可以的.
status
运行git status
命令查看仓库当前状态
git status
命令可以让我们时刻掌握仓库当前的状态
add
命令git add
告诉Git, 把文件添加到仓库
commit
命令git commit
告诉Git, 把文件提交到仓库
-m
后面输入的是本次提交的说明, 可以输入任意内容, 当然最好是有意义的, 这样你就能从历史记录里方便地找到改动记录.
diff
git diff
顾名思义就是查看difference, 显示的格式正是Unix通用的diff格式, 看具体修改了什么内容
时光穿梭机
版本回退
你不断对文件进行修改, 然后不断提交修改到版本库里, 就好比玩RPG游戏时, 每通过一关就会自动把游戏状态存盘, 如果某一关没过去, 你还可以选择读取前一关的状态. 有些时候, 在打Boss之前, 你会手动存盘, 以便万一打Boss失败了, 可以从最近的地方重新开始. Git也是一样, 每当你觉得文件修改到一定程度的时候, 就可以“保存一个快照”, 这个快照在Git中被称为commit
. 一旦你把文件改乱了, 或者误删了文件, 还可以从最近的一个commit
恢复, 然后继续工作, 而不是把几个月的工作成果全部丢失.
查看历史记录
在实际工作中, 我们脑子里怎么可能记得一个几千行的文件每次都改了什么内容, 不然要版本控制系统干什么. 版本控制系统肯定有某个命令可以告诉我们历史记录, 在Git中, 我们用git log
命令查看
1 | $ git log |
git log
命令显示从最近到最远的提交日志, 我们可以看到3次提交, 最近的一次是append GPL
, 上一次是add distributed
, 最早的一次是wrote a readme file
.
如果嫌输出信息太多, 看得眼花缭乱的, 可以试试加上--pretty=oneline
参数:
1 | $ git log --pretty=oneline |
需要友情提示的是, 你看到的一大串类似1094adb...
的是commit id
(版本号), 和SVN不一样, Git的commit id
不是1, 2, 3……递增的数字, 而是一个SHA1计算出来的一个非常大的数字, 用十六进制表示, 而且你看到的commit id
和我的肯定不一样, 以你自己的为准. 为什么commit id
需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统, 后面我们还要研究多人在同一个版本库里工作, 如果大家都用1, 2, 3……作为版本号, 那肯定就冲突了.
每提交一个新版本, 实际上Git就会把它们自动串成一条时间线. 如果使用可视化工具查看Git历史, 就可以更清楚地看到提交历史的时间线
版本回退
准备把readme.txt
回退到上一个版本, 也就是add distributed
的那个版本
首先, Git必须知道当前版本是哪个版本, 在Git中, 用HEAD
表示当前版本, 也就是最新的提交1094adb...
(注意我的提交ID和你的肯定不一样), 上一个版本就是HEAD^
, 上上一个版本就是HEAD^^
, 当然往上100个版本写100个^
比较容易数不过来, 所以写成HEAD~100
.
现在, 我们要把当前版本append GPL
回退到上一个版本add distributed
, 就可以使用git reset
命令:
1 | $ git reset --hard HEAD^ |
--hard
参数有啥意义?这个后面再讲, 现在你先放心使用.
还可以继续回退到上一个版本wrote a readme file
, 不过且慢, 让我们用git log
再看看现在版本库的状态:
1 | $ git log |
最新的那个版本append GPL
已经看不到了!好比你从21世纪坐时光穿梭机来到了19世纪, 想再回去已经回不去了, 肿么办?
办法其实还是有的, 只要上面的命令行窗口还没有被关掉, 你就可以顺着往上找啊找啊, 找到那个append GPL
的commit id
是1094adb...
, 于是就可以指定回到未来的某个版本:
1 | $ git reset --hard 1094a |
版本号没必要写全, 前几位就可以了, Git会自动去找. 当然也不能只写前一两位, 因为Git可能会找到多个版本号, 就无法确定是哪一个了.
再小心翼翼地看看readme.txt
的内容:
1 | $ cat readme.txt |
果然, 我胡汉三又回来了.
Git的版本回退速度非常快, 因为Git在内部有个指向当前版本的HEAD
指针, 当你回退版本的时候, Git仅仅是把HEAD从指向append GPL
:
1 | ┌────┐ |
改为指向add distributed
:
1 | ┌────┐ |
然后顺便把工作区的文件更新了. 所以你让HEAD
指向哪个版本号, 你就把当前版本定位在哪.
现在, 你回退到了某个版本, 关掉了电脑, 第二天早上就后悔了, 想恢复到新版本怎么办?找不到新版本的commit id
怎么办?
在Git中, 总是有后悔药可以吃的. 当你用$ git reset --hard HEAD^
回退到add distributed
版本时, 再想恢复到append GPL
, 就必须找到append GPL
的commit id. Git提供了一个命令git reflog
用来记录你的每一次命令:
1 | $ git reflog |
终于舒了口气, 从输出可知, append GPL
的commit id是1094adb
, 现在, 你又可以乘坐时光机回到未来了.
总结
HEAD
指向的版本就是当前版本, 因此, Git允许我们在版本的历史之间穿梭, 使用命令git reset --hard commit_id
.- 穿梭前, 用
git log
可以查看提交历史, 以便确定要回退到哪个版本. - 要重返未来, 用
git reflog
查看命令历史, 以便确定要回到未来的哪个版本.
工作区与暂存区
工作区(Working Directory)
就是你在电脑里能看到的目录, 比如我的learngit
文件夹就是一个工作区:
版本库(Repository)
工作区有一个隐藏目录.git
, 这个不算工作区, 而是Git的版本库.
Git的版本库里存了很多东西, 其中最重要的就是称为stage(或者叫index)的暂存区, 还有Git为我们自动创建的第一个分支master
, 以及指向master
的一个指针叫HEAD
.

分支和HEAD
的概念我们以后再讲.
前面讲了我们把文件往Git版本库里添加的时候, 是分两步执行的:
第一步是用git add
把文件添加进去, 实际上就是把文件修改添加到暂存区;
第二步是用git commit
提交更改, 实际上就是把暂存区的所有内容提交到当前分支.
因为我们创建Git版本库时, Git自动为我们创建了唯一一个master
分支, 所以, 现在, git commit
就是往master
分支上提交更改.
你可以简单理解为, 需要提交的文件修改通通放到暂存区, 然后, 一次性提交暂存区的所有修改.
实例
现在, 使用两次命令git add
, 把readme.txt
和LICENSE
都添加后, 用git status
再查看一下:
1 | $ git status |
所以, git add
命令实际上就是把要提交的所有修改放到暂存区(Stage), 然后, 执行git commit
就可以一次性把暂存区的所有修改提交到分支.

一旦提交后, 如果你又没有对工作区做任何修改, 那么工作区就是“干净”的:
1 | $ git status |
现在版本库变成了这样, 暂存区就没有任何内容了:

管理修改
什么是修改?比如你新增了一行, 这就是一个修改, 删除了一行, 也是一个修改, 更改了某些字符, 也是一个修改, 删了一些又加了一些, 也是一个修改, 甚至创建一个新文件, 也算一个修改.
现有操作过程:
第一次修改 -> git add
-> 第二次修改 -> git commit
Git管理的是修改, 当你用git add
命令后, 在工作区的第一次修改被放入暂存区, 准备提交, 但是, 在工作区的第二次修改并没有放入暂存区, 所以, git commit
只负责把暂存区的修改提交了, 也就是第一次的修改被提交了, 第二次的修改不会被提交.
git diff HEAD -- readme.txt
命令可以查看工作区和版本库里面最新版本的区别
你可以继续git add
再git commit
, 也可以别着急提交第一次修改, 先git add
第二次修改, 再git commit
, 就相当于把两次修改合并后一块提交了:
第一次修改 -> git add
-> 第二次修改 -> git add
-> git commit
撤销修改
add前撤销
在准备提交前, 发现了错误. 既然错误发现得很及时, 就可以很容易地纠正它. 你可以删掉最后一行, 手动把文件恢复到上一个版本的状态. 如果用git status
查看一下:
1 | $ git status |
你可以发现, Git会告诉你, git checkout -- file
可以丢弃工作区的修改:
1 | git checkout -- readme.txt |
命令git checkout -- readme.txt
意思就是, 把readme.txt
文件在工作区的修改全部撤销, 这里有两种情况:
一种是readme.txt
自修改后还没有被放到暂存区, 现在, 撤销修改就回到和版本库一模一样的状态;
一种是readme.txt
已经添加到暂存区后, 又作了修改, 现在, 撤销修改就回到添加到暂存区后的状态.
总之, 就是让这个文件回到最近一次git commit
或git add
时的状态.
git checkout -- file
命令中的--
很重要, 没有--
, 就变成了“切换到另一个分支”的命令, 我们在后面的分支管理中会再次遇到git checkout
命令.
add后撤销
将文件git add
到暂存区了
庆幸的是, 在commit
之前, 你发现了这个问题. 用git status
查看一下, 修改只是添加到了暂存区, 还没有提交:
1 | $ git status |
Git同样告诉我们, 用命令git reset HEAD <file>
可以把暂存区的修改撤销掉(unstage), 重新放回工作区:
1 | $ git reset HEAD readme.txt |
git reset
命令既可以回退版本, 也可以把暂存区的修改回退到工作区. 当我们用HEAD
时, 表示最新的版本.
再用git status
查看一下, 现在暂存区是干净的, 工作区有修改:
1 | $ git status |
comit后撤销
假设你不但改错了东西, 还从暂存区提交到了版本库, 怎么办呢?还记得版本回退一节吗?可以回退到上一个版本. 不过, 这是有条件的, 就是你还没有把自己的本地版本库推送到远程. 还记得Git是分布式版本控制系统吗?我们后面会讲到远程版本库, 一旦你把stupid boss
提交推送到远程版本库, 你就真的惨了……
小结
- 场景1:当你改乱了工作区某个文件的内容, 想直接丢弃工作区的修改时, 用命令
git checkout -- file
. - 场景2:当你不但改乱了工作区某个文件的内容, 还添加到了暂存区时, 想丢弃修改, 分两步, 第一步用命令
git reset HEAD <file>
, 就回到了场景1, 第二步按场景1操作. - 场景3:已经提交了不合适的修改到版本库时, 想要撤销本次提交, 参考版本回退一节, 不过前提是没有推送到远程库.
删除文件
case1: 删除文件
确实要从版本库中删除该文件, 那就用命令git rm
删掉, 并且git commit
:
1 | $ git rm test.txt |
case2: 误删文件
另一种情况是删错了, 因为版本库里还有呢, 所以可以很轻松地把误删的文件恢复到最新版本
1 | git checkout -- test.txt |
git checkout
其实是用版本库里的版本替换工作区的版本, 无论工作区是修改还是删除, 都可以“一键还原”.
远程仓库
添加远程仓库
创建GitHub仓库
create a new repo
连接仓库
1 | git remote add origin git@github.com:<github账户名>/<仓库名>.git |
添加后, 远程库的名字就是origin
, 这是Git默认的叫法, 也可以改成别的, 但是origin
这个名字一看就知道是远程库.
关联一个远程库时必须给远程库指定一个名字, origin
是默认习惯命名.
推送内容
就可以把本地库的所有内容推送到远程库上
1 | $ git push -u origin master |
把本地库的内容推送到远程, 用git push
命令, 实际上是把当前分支master
推送到远程.
由于远程库是空的, 我们第一次推送master
分支时, 加上了-u
参数, Git不但会把本地的master
分支内容推送的远程新的master
分支, 还会把本地的master
分支和远程的master
分支关联起来, 在以后的推送或者拉取时就可以简化命令.
推送成功后, 可以立刻在GitHub页面中看到远程库的内容已经和本地一模一样
从现在起, 只要本地作了提交, 就可以通过命令:
1 | git push origin master |
把本地master
分支的最新修改推送至GitHub, 现在, 你就拥有了真正的分布式版本库!
删除远程库
如果添加的时候地址写错了, 或者就是想删除远程库, 可以用git remote rm <name>
命令. 使用前, 建议先用git remote -v
查看远程库信息:
1 | $ git remote -v |
然后, 根据名字删除, 比如删除origin
:
1 | git remote rm origin |
此处的“删除”其实是解除了本地和远程的绑定关系, 并不是物理上删除了远程库. 远程库本身并没有任何改动. 要真正删除远程库, 需要登录到GitHub, 在后台页面找到删除按钮再删除.
分支管理
分支就是科幻电影里面的平行宇宙, 当你正在电脑前努力学习Git的时候, 另一个你正在另一个平行宇宙里努力学习SVN.
如果两个平行宇宙互不干扰, 那对现在的你也没啥影响. 不过, 在某个时间点, 两个平行宇宙合并了, 结果, 你既学会了Git又学会了SVN!

创建与合并分支
形象化描述
在版本回退里, 你已经知道, 每次提交, Git都把它们串成一条时间线, 这条时间线就是一个分支. 截止到目前, 只有一条时间线, 在Git里, 这个分支叫主分支, 即master
分支. HEAD
严格来说不是指向提交, 而是指向master
, master
才是指向提交的, 所以, HEAD
指向的就是当前分支.
一开始的时候, master
分支是一条线, Git用master
指向最新的提交, 再用HEAD
指向master
, 就能确定当前分支, 以及当前分支的提交点:
1 | HEAD |
每次提交, master
分支都会向前移动一步, 这样, 随着你不断提交, master
分支的线也越来越长.
当我们创建新的分支, 例如dev
时, Git新建了一个指针叫dev
, 指向master
相同的提交, 再把HEAD
指向dev
, 就表示当前分支在dev
上:
1 | master |
你看, Git创建一个分支很快, 因为除了增加一个dev
指针, 改改HEAD
的指向, 工作区的文件都没有任何变化!
不过, 从现在开始, 对工作区的修改和提交就是针对dev
分支了, 比如新提交一次后, dev
指针往前移动一步, 而master
指针不变:
1 | master |
假如我们在dev
上的工作完成了, 就可以把dev
合并到master
上. Git怎么合并呢?最简单的方法, 就是直接把master
指向dev
的当前提交, 就完成了合并:
1 | HEAD |
所以Git合并分支也很快!就改改指针, 工作区内容也不变!
合并完分支后, 甚至可以删除dev
分支. 删除dev
分支就是把dev
指针给删掉, 删掉后, 我们就剩下了一条master
分支:
1 | HEAD |
实战
创建并切换到dev分支
创建dev
分支, 然后切换到dev
分支:
1 | $ git checkout -b dev |
git checkout
命令加上-b
参数表示创建并切换, 相当于以下两条命令:
1 | $ git branch dev |
然后, 用git branch
命令查看当前分支:
1 | $ git branch |
git branch
命令会列出所有分支, 当前分支前面会标一个*
号.
然后, 我们就可以在dev
分支上正常提交, 比如对readme.txt
做个修改, 加上一行:
1 | Creating a new branch is quick. |
然后提交:
1 | $ git add readme.txt |
现在, dev
分支的工作完成, 我们就可以切换回master
分支:
1 | $ git checkout master |
切换回master
分支后, 再查看一个readme.txt
文件, 刚才添加的内容不见了!因为那个提交是在dev
分支上, 而master
分支此刻的提交点并没有变:

现在, 我们把dev
分支的工作成果合并到master
分支上:
1 | $ git merge dev |
git merge
命令用于合并指定分支到当前分支. 合并后, 再查看readme.txt
的内容, 就可以看到, 和dev
分支的最新提交是完全一样的.
注意到上面的Fast-forward
信息, Git告诉我们, 这次合并是“快进模式”, 也就是直接把master
指向dev
的当前提交, 所以合并速度非常快.
当然, 也不是每次合并都能Fast-forward
, 我们后面会讲其他方式的合并.
合并完成后, 就可以放心地删除dev
分支了:
1 | $ git branch -d dev |
删除后, 查看branch
, 就只剩下master
分支了:
1 | $ git branch |
因为创建、合并和删除分支非常快, 所以Git鼓励你使用分支完成某个任务, 合并后再删掉分支, 这和直接在master
分支上工作效果是一样的, 但过程更安全.
switch
我们注意到切换分支使用git checkout <branch>
, 而前面讲过的撤销修改则是git checkout -- <file>
, 同一个命令, 有两种作用, 确实有点令人迷惑.
实际上, 切换分支这个动作, 用switch
更科学. 因此, 最新版本的Git提供了新的git switch
命令来切换分支:
创建并切换到新的dev
分支, 可以使用:
1 | git switch -c dev |
直接切换到已有的master
分支, 可以使用:
1 | git switch master |
使用新的git switch
命令, 比git checkout
要更容易理解.
小结
- 查看分支:
git branch
- 创建分支:
git branch <name>
- 切换分支:
git checkout <name>
或者git switch <name>
- 创建+切换分支:
git checkout -b <name>
或者git switch -c <name>
- 合并某分支到当前分支:
git merge <name>
- 删除分支:
git branch -d <name>
解决冲突
准备新的feature1
分支, 继续我们的新分支开发:
1 | $ git switch -c feature1 |
修改readme.txt
最后一行, 改为:
1 | Creating a new branch is quick AND simple. |
在feature1
分支上提交:
1 | $ git add readme.txt |
切换到master
分支:
1 | $ git switch master |
Git还会自动提示我们当前master
分支比远程的master
分支要超前1个提交.
在master
分支上把readme.txt
文件的最后一行改为:
1 | Creating a new branch is quick & simple. |
提交:
1 | $ git add readme.txt |
现在, master
分支和feature1
分支各自都分别有新的提交, 变成了这样:
1 | HEAD |
这种情况下, Git无法执行“快速合并”, 只能试图把各自的修改合并起来, 但这种合并就可能会有冲突, 我们试试看:
1 | $ git merge feature1 |
果然冲突了!Git告诉我们, readme.txt
文件存在冲突, 必须手动解决冲突后再提交. git status
也可以告诉我们冲突的文件:
1 | $ git status |
我们可以直接查看readme.txt的内容:
1 | Git is a distributed version control system. |
Git用<<<<<<<
, =======
, >>>>>>>
标记出不同分支的内容, 我们修改如下后保存:
1 | Creating a new branch is quick and simple. |
再提交:
1 | $ git add readme.txt |
现在, master
分支和feature1
分支变成了下图所示:
1 | HEAD |
用带参数的git log
也可以看到分支的合并情况:
1 | $ git log --graph --pretty=oneline --abbrev-commit |
最后, 删除feature1
分支:
1 | $ git branch -d feature1 |
小结
当Git无法自动合并分支时, 就必须首先解决冲突. 解决冲突后, 再提交, 合并完成.
解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容, 再提交.
用 git log --graph
命令可以看到分支合并图.
分支管理策略
通常, 合并分支时, 如果可能, Git会用Fast forward
模式, 但这种模式下, 删除分支后, 会丢掉分支信息.
如果要强制禁用Fast forward
模式, Git就会在merge时生成一个新的commit, 这样, 从分支历史上就可以看出分支信息.
下面我们实战一下--no-ff
方式的git merge
:
首先, 仍然创建并切换dev
分支:
1 | $ git switch -c dev |
修改readme.txt文件, 并提交一个新的commit:
1 | $ git add readme.txt |
现在, 我们切换回master
:
1 | $ git switch master |
准备合并dev
分支, 请注意--no-ff
参数, 表示禁用Fast forward
:
1 | $ git merge --no-ff -m "merge with no-ff" dev |
因为本次合并要创建一个新的commit, 所以加上-m
参数, 把commit描述写进去.
合并后, 我们用git log
看看分支历史:
1 | $ git log --graph --pretty=oneline --abbrev-commit |
可以看到, 不使用Fast forward
模式, merge后就像这样:

分支策略
在实际开发中, 我们应该按照几个基本原则进行分支管理:
首先, master
分支应该是非常稳定的, 也就是仅用来发布新版本, 平时不能在上面干活;
那在哪干活呢?干活都在dev
分支上, 也就是说, dev
分支是不稳定的, 到某个时候, 比如1.0版本发布时, 再把dev
分支合并到master
上, 在master
分支发布1.0版本;
你和你的小伙伴们每个人都在dev
分支上干活, 每个人都有自己的分支, 时不时地往dev
分支上合并就可以了.
所以, 团队合作的分支看起来就像这样:

小结
Git分支十分强大, 在团队开发中应该充分应用.
合并分支时, 加上--no-ff
参数就可以用普通模式合并, 合并后的历史有分支, 能看出来曾经做过合并, 而fast forward
合并就看不出来曾经做过合并.
API的使用
获取最新Releases的版本号
代码
1 | wget -qO- -t1 -T2 "https://api.github.com/repos/ryanoasis/nerd-fonts/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g' |
可以搭配 xargs
进行自定义命令
代码解释
主字段
https://api.github.com/repos/ryanoasis/nerd-fonts/releases/latest
这里用的是 GitHub 的官方 API, 格式为https://api.github.com/repos/{项目名}/releases/latest
打开上述链接后, 可见包含下述字段的内容:
1
2
3
4
5
6"html_url": "https://github.com/ryanoasis/nerd-fonts/releases/tag/v3.1.1",
"id": 131468221,
"node_id": "RE_kwDOAaTAks4H1gu9",
"tag_name": "v3.1.1",
"target_commitish": "master",
"name": "v3.1.1",那么这里的
tag_name
就是我们所需要的东西啦wget 参数
1
wget -qO- -t1 -T2`, 在这里, 我们使用了 4 个参数, 分别是`q,O-,t1,T2
-q
: q 就是 quiet 的意思了, 没有该参数将会显示从请求到输出全过程的所有内容, 这肯定不是我们想要的.-O-
:-O
是指把文档写入文件中, 而-O-
是将内容写入标准输出, 而不保存为文件. (注:这里是大写英文字母 O (Out), 不是数字 0)-t1,-T2
: 前者是设定最大尝试链接次数为 1 次, 后者是设定响应超时的秒数为 2 秒, 两者可以防止失败后反复获取, 导致后续脚本无法执行.
筛选参数
grep "tag_name"
: grep 是 Linux 一个强大的文本搜索工具, 在本代码中输出 tag_name 所在行, 即输出"tag_name": "v3.1.1",
head -n 1
:head -n
用于显示输出的行数, 考虑到某些项目可能存在多个不同版本的 tag_name, 这里我们只要第一个.awk -F ":" '{print $2}'
: awk 主要用于文本分析, 在这里指定:
为分隔符, 将该行切分成多列, 并输出第二列. 于是我们得到了(空格)"v3.1.1",
sed 's/\"//g;s/,//g;s/ //g'
: 在这里 sed 用于数据查找替换, 如sed 's/要被取代的字串/新的字串/g'
, 因此本段命令可分为 3 个, 以分号分隔.s/\"//g
即将引号删除(反斜杠是为了防止引号被转义), 以此类推, 最终留下我们需要的内容:v3.1.1
.
忽略特殊文件
有些时候, 你必须把某些文件放到Git工作目录中, 但又不能提交它们, 比如保存了数据库密码的配置文件啦, 等等, 每次git status
都会显示Untracked files ...
, 有强迫症的童鞋心里肯定不爽.
好在Git考虑到了大家的感受, 这个问题解决起来也很简单, 在Git工作区的根目录下创建一个特殊的.gitignore
文件, 然后把要忽略的文件名填进去, Git就会自动忽略这些文件.
[!NOTE]
.gitignore
文件本身应该提交给Git管理, 这样可以确保所有人在同一项目下都使用相同的.gitignore
文件.
不需要从头写.gitignore
文件, GitHub已经为我们准备了各种配置文件, 只需要组合一下就可以使用了. 所有配置文件可以直接在线浏览:GitHub/gitignore
忽略文件的原则是:
- 忽略操作系统自动生成的文件, 比如缩略图等;
- 忽略编译生成的中间文件、可执行文件等, 也就是如果一个文件是通过另一个文件自动生成的, 那自动生成的文件就没必要放进版本库, 比如Java编译产生的
.class
文件; - 忽略你自己的带有敏感信息的配置文件, 比如存放口令的配置文件.
举个例子:
假设你在Windows下进行Python开发, Windows会自动在有图片的目录下生成隐藏的缩略图文件, 如果有自定义目录, 目录下就会有Desktop.ini
文件, 因此你需要忽略Windows自动生成的垃圾文件:
1 | # Windows: |
然后, 继续忽略Python编译产生的.pyc
、.pyo
、dist
等文件或目录:
1 | # Python: |
加上你自己定义的文件, 最终得到一个完整的.gitignore
文件, 内容如下:
1 | # Windows: |
最后一步就是把.gitignore
也提交到Git, 就完成了!当然检验.gitignore
的标准是git status
命令是不是说working directory clean
.
使用Windows的童鞋注意了, 如果你在资源管理器里新建一个.gitignore
文件, 它会非常弱智地提示你必须输入文件名, 但是在文本编辑器里“保存”或者“另存为”就可以把文件保存为.gitignore
了.
有些时候, 你想添加一个文件到Git, 但发现添加不了, 原因是这个文件被.gitignore
忽略了:
1 | $ git add App.class |
如果你确实想添加该文件, 可以用-f
强制添加到Git:
1 | git add -f App.class |
或者你发现, 可能是.gitignore
写得有问题, 需要找出来到底哪个规则写错了, 可以用git check-ignore
命令检查:
1 | $ git check-ignore -v App.class |
Git会告诉我们, .gitignore
的第3行规则忽略了该文件, 于是我们就可以知道应该修订哪个规则.
还有些时候, 当我们编写了规则排除了部分文件时:
1 | # 排除所有.开头的隐藏文件: |
但是我们发现.*
这个规则把.gitignore
也排除了, 并且App.class
需要被添加到版本库, 但是被*.class
规则排除了.
虽然可以用git add -f
强制添加进去, 但有强迫症的童鞋还是希望不要破坏.gitignore
规则, 这个时候, 可以添加两条例外规则:
1 | # 排除所有.开头的隐藏文件: |
把指定文件排除在.gitignore
规则外的写法就是!
+文件名, 所以, 只需把例外文件添加进去即可.
可以通过GitIgnore Online Generator在线生成.gitignore
文件并直接下载.
最后一个问题:.gitignore
文件放哪?答案是放Git仓库根目录下, 但其实一个Git仓库也可以有多个.gitignore
文件, .gitignore
文件放在哪个目录下, 就对哪个目录(包括子目录)起作用.
1 | myproject <- Git仓库根目录 |
配置多个 SSH Key
背景
同时使用两个 GitHub 帐号, 需要为两个帐号配置不同的 SSH Key:
- 帐号 A 用于公司;
- 帐号 B 用于个人
创建ssh-key
生成帐号 A 的 SSH Key, 并在帐号 A 的 GitHub 设置页面添加 SSH 公钥:
1 | ssh-keygen -t ed25519 -C "GitHub User A" -f ~/.ssh/github_user_a_ed25519 |
生成帐号 B 的 SSH-Key, 并在帐号 B 的 GitHub 设置页面添加 SSH 公钥:
1 | ssh-keygen -t ed25519 -C "GitHub User B" -f ~/.ssh/github_user_b_ed25519 |
配置ssh代理
创建好了上面的多个ssh key就可以开始管理他们了. 在终端中输入如下命令, 查询系统ssh key的代理:
1 | ssh-add -l |
如果系统已经设置了代理, 需要删除:
1 | ssh-add -D |
如果提示:
1 | Could not open a connection to your authentication agent. |
执行:
1 | exec ssh-agent bash |
接下来添加刚才创建的ssh key的私钥:
1 | # 第一个 |
添加公钥
其实就是将对应的.pub中的内容, 复制到对应平台的ssh key管理栏目中, 不同的平台, 位置不同, 可以去对应的个人中心的设置中查看, 很容易找到.
配置config
创建或者修改文件 ~/.ssh/config
, 添加如下内容:
1 | Host gt_a |
验证ssh-key
用 ssh 命令分别测试两个 SSH Key:
1 | $ ssh -T gt_a |
使用
拉取代码
将 git@github.com
替换为 SSH 配置文件中对应的 Host
, 如原仓库 SSH 链接为:
1 | git@github.com:owner/repo.git |
使用帐号 A 推拉仓库时, 需要将连接修改为:
1 | gt_a:owner/repo.git |
多个git账户的提交问题
我们大多数人都会使用第三方工具进行git提交, 比如source tree之类的, 这些工具在提交时, 如果不对对应的git仓库进行专门的配置, 会默认走git的全局配置, 也就是会用默认的全局配置的账户进行git提交. 一不小心, 就会用我们私人的账户, 进行了公司项目的git提交, 生成了对应的提交记录, 也有可能因为权限问题, 导致直接提交失败.
这时, 我们需要对不同的仓库, 进行对应的配置.
检查全局配置
在终端中, 分别输入如下命令, 可以检查目前电脑中的git的全局配置信息, 如果没有返回, 说明没有全局配置, 如果有, 就可以看到对应的默认的账户是那个了.
1
2git config --global user.name
git config --global user.email为了避免麻烦, 我们可以取消全局配置:
1
2git config --global --unset user.name
git config --global --unset user.email全局配置和局部配置
此时已经取消了电脑中默认的git全局配置信息, 此时进行git提交, 会报对应的找不到账户信息的错误.
我们可以cd到对应的git仓库的根目录下, 执行局部git配置命令. 比如~/github/DemoProject
是一个在github平台托管的本地git仓库的根目录, 我们可以执行如下命令:1
2
3cd ~/github/DemoProject
git config user.name
git config user.email如果返回均为空, 说明没有进行过局部配置, 可以分别配置github的账户名和邮箱:
1
2git config user.name "github账户名"
git config user.email "github@example.com"同理, 在不同的git仓库下, 可以分别配置不同平台的git账户名和git邮箱. 这虽然看起来麻烦, 不过, 只要设置完成, 之后只要不再更改对应的git仓库的路径, 就不需要再更换配置了.
而且, 即便我们没有取消默认的全局git配置, 在进行了局部配置后, 后者的优先级会更高. 执行:1
git config --list
可以查看查看当前仓库的具体配置信息, 在当前仓库目录下查看的配置是全局配置+当前项目的局部配置, 使用的时候会优先使用当前仓库的局部配置, 如果没有, 才会去读取全局配置.
克隆含有子模块的项目
当一个 git 项目包含子模块(submodule) 时, 直接克隆下来的子模块目录里面是空的.
有两种方法解决:
方法一
如果项目已经克隆到了本地, 执行下面的步骤:
初始化本地子模块配置文件
1
git submodule init
更新项目, 抓取子模块内容.
1
git submodule update
方法二
另外一种更简单的方法, 就是在执行 git clone
时加上 --recursive
参数. 它会自动初始化并更新每一个子模块. 例如:
1 | Copygit clone --recursive https://github.com/example/example.git |
其他
关于 git 子模块更多内容, 参见官方文档.
submodule 相关
查看子模块:
git submodule
更新子模块:
- 更新项目内子模块到最新版本:
git submodule update
- 更新子模块为远程项目的最新版本:
git submodule update --remote
- 更新项目内子模块到最新版本:
克隆包含子模块的项目
克隆父项目:
git clone https://github.com/demo.git assets
初始化子模块:
git submodule init
更新子模块:
git submodule update
递归克隆整个项目submodule:
git clone https://github.com/user/demo.git ./demo --recursive
> `--recursive` 表示递归地克隆 git_parent 依赖的所有子版本库.
递归更新整个项目submodule:
git submodule foreach git pull
删除子模块:
git rm --cached subModulesA rm -rf subModulesA