跳到主要内容

Git in Action

主要记录 git 在实际使用中碰到的问题,会逐步慢慢积累

高频问题与实践方案

新项目与远端同步

$ git init
$ git remote add origin git@e.coding.net:datamate/pujiangjiaoye.git # 添加远端仓库
$ git branch --set-upstream-to=origin/master # 设置分支对应关系
$ git push --set-upstream origin master # 设置push 对应的远程仓库,建立本地分支的上游
$ git remote -v # 查看分支

当我们尝试push的时候又会出现一个问题

error: 推送一些引用到 'git@e.coding.net:datamate/pujiangjiaoye.git' 失败
提示:更新被拒绝,因为您当前分支的最新提交落后于其对应的远程分支。
提示:再次推送前,先与远程变更合并(如 'git pull ...')。详见
提示:'git push --help' 中的 'Note about fast-forwards' 小节。

# 按照提示,尝试用git pull 合并,又失败
$ git pull
fatal: 拒绝合并无关的历史

原因是我们本地与远程的版本不一致,但是我们是新提交,怎么会出现这样的问题呢? 因为很多平台在我们创建项目的时候,自动帮我们创建了README.md文件,所以会有不一致的情况存在。git pull 失败的原因是,两个不一致的文件没有共同祖先的历史

解决方法也很简单

git pull origin master --allow-unrelated-histories 

修改上一次的提交

git 是分支管理的工具,我们的工作最好在一个 commit 中提交相应的代码,完成一个功能(如 bugfix 或者 feature_add )。但是有的时候,我们的提交经过 Code Review 之后,可能会被打回来。这样面对新的修改,不应该重复提交。

首先,我们可以先建立一个提交。面对第二个文件(这里我们给 a 新增了 Hello world again )

第一种选择,我们提交两个 commit

git add .
git commit -m "second commit"

第二种选择,我们直接修改上一次的修改(推荐)

工作流如下

git add .
git commit --amend # 代表直接在上一次的提交修改,这里会弹出上一次 commit 的信息。
git push -f # 由于我们只是改了本地,提交的时候使用 -f 强制推送一下

这里强烈建议,使用 --amend 的方法的时候,一定要检查 commit 的信息,确定不是把别人的给冲掉了。

服务器端强制同步远端 git 仓库

git fetch 
git reset --hard origin/master # 要强制同步的分支!--hard 是丢弃分支的修改
git pull
Already up-to-date.

为什么修改了 .gitignore 文件,忽略项还是不起作用

新建的文件在git中会有缓存,如果某些文件已经被纳入了版本管理中,就算是在.gitignore中已经声明了忽略路径也是不起作用的,这时候我们就应该先把本地缓存删除,然后再进行git的push

git清除本地缓存命令如下:

git rm -r --cached .
git add .

下面附上我的 .gitignore 文件

# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini

# MAC:
*.DS_Store

# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build

# My configurations:
*.log
*.log.*

如何使用 git merge

# 开发分支(dev)上的代码达到上线的标准后,要合并到 master 分支
git checkout dev
git pull
git checkout master
git pull
git merge dev
git push -u origin master

# 当master代码改动了,需要更新开发分支(dev)上的代码

git checkout master
git pull
git checkout dev # 这里默认dev 远端没有动
git merge master
git push -u origin dev

参考: git merge最简洁用法

推荐一个 git merge 比较好的教程: merge:合并 commits

管理多个git账号

介绍

Git共有三个级别的 config 文件,分别是 systemgloballocalglobal 的在$home\.gitconfig,local的在仓库目录下的.git\config。这三个级别都分别配置了用户信息,当git commit时,会依次从local、global、system里读取用户信息。因此,我们利用local的配置来设置不同的用户信息

生成公钥

# 查看git账号,会按顺序读取local,global的用户信息
git config user.name
git config user.email

# 若是在空仓库中使用如下命令会出错,我们在仓库中使用--local,提前运行git init
git config --local -l
fatal: --local 只能在一个仓库内使用

# 更改本地账号
git config --local/--global user.name "Your name"
git config --local/--global user.email "Your email"
# 生成公钥
ssh-keygen -t rsa -C "your_email@example.com" -f ./
-t rsa 约定加密类型
-C 添加评论
-f 保存秘钥的地址(默认是 ~/.ssh/id_rsa)

生成公钥的地方需要注意的是,生成的公钥会自动生成在~/.ssh/id_rsa中,若是你有多个账号的话,无疑会把原先的秘钥覆盖。可以在后续弹出的提示中约定key保存的地址

Enter file in which to save the key (/Users/you/.ssh/id_rsa): [Press enter]  // 推荐使用默认地址
Enter passphrase (empty for no passphrase): //此处点击 Enter 键即可,也可以填写密码,填写密码后每次使用 SSH 方式推送代码时都会要求输入密码,由于这个 Key 也不是用于军事目的,所以也无需设置密码。

也可以在ssh-keygen中约定好

ssh-keygen -t rsa -C "your_email@example.com" -f ~/.ssh/coding 
-f 保存在yon下的.ssh/coding文件中

Your identification has been saved in /Users/ppsteven/.ssh/coding.
Your public key has been saved in /Users/ppsteven/.ssh/coding.pub.
The key fingerprint is:
SHA256:P6R/Fo70VkCyKsQsFXXX... XXX@outlook.com

.ssh 文件夹下也多了codingcoding.pub两个文件

coding是你的私钥,需要好好保管,在下面的配置文件中还需要使用,用以证明你自己的身份,coding.pub是公钥,我们需要上传到github,coding,码云等平台上去。

上传公钥

这里只要找到对应上传的地方上传即可,一般平台都有对应的教程,下面我们copy一下coding的操作。

mac 复制文件小技巧

cat coding.pub | pbcopy # 输出到剪切板

pbcopy : 表示复制剪切版 pbpaste :表示粘贴剪切版

配置config文件和添加私钥

若是拥有多个账号,需要在 .ssh 下配置多个账号的配置文件 config ,这个配置文件是用来作为路由使用。 我们需要检查是否存在 ~/.ssh/config 文件,不存在的话就创建如下文件。

# ~/.ssh/config

Host github # 别名,使用正则表达式
# connect to github by git@github.com
User git # 用户名
HostName github.com # 服务器地址
IdentityFile ~/.ssh/id_rsa # 私钥地址

Host coding
HostName e.coding.net
User git
IdentityFile ~/.ssh/coding

Host *.works.*
HostName devops.works
IdentityFile ~/.ssh/id_rsa

Host hostmem
HostName 195.85.35.15
IdentityFile ~/.ssh/id_rsa

Host *
User ppsteven # 默认用户
AddKeysToAgent yes # 自动添加到 ssh-agent
IdentityFile ~/.ssh/id_rsa
IdentityFile ~/.ssh/coding
ServerAliveInterval 60
ServerAliveCountMax 3

下面解释一下上述配置文件的含义

首先,每一项配置文件的含义都可以通过 man ssh_config 查看,也可以通过

进行在线查询。

  • Host 应填写正则表达式,是作为hosts的别称存在

    Host: 192.168.1.?  #  配置所有 192.168.1.[0-9] 的
    HostName: 192.168.1.3

    Host targaryen
    HostName 192.168.1.10
    User daenerys
    Port 7654
    IdentityFile ~/.ssh/targaryen.key

    Host: *
    User: root # 所用网站都使用的默认账号
  • HostName:真实的host地址

  • User:建立 ssh 连接所用的账号,一般如果是远程登录的话是 root , 用于 git 拉取代码的话,一般是 git

    连接服务器时,会将 User 和 HostName 进行拼接,如 git@github.com

  • IdentityFile:私钥存储的地址

  • AddKeysToAgent : 自动添加私钥到 ssh-agent 中去。

多账号管理

通过 ~/.ssh/config 文件对多个 git 账号进行管理,其本质是针对不同的 Host 使用不同的私钥进行验证,所以在使用时需要使用自定义的别名,让 ssh 能根据别名找到对应网站的私钥地址,这种方式和服务器路由匹配的原理类似。

git clone git@e.coding.net:datamate/demo.git # ssh 方式,此时并不会自动识别对应的 ~/.ssh/coding 的私钥地址
git clone coding:datamate/demo.git # 匹配到 coding 规则
git clone git@coding:datamate/demo.git # 匹配到 coding 规则

通过起别名的方式,根据 ~/.ssh/config 文件,让我们找到不同 Host 的私钥文件位置,那对于不起别名的方式,有没有方法可以做到让系统自动匹配到对应的私钥地址呢? 有,通过 ssh-agent

ssh-agent是什么

那么 ssh-agent 的作用是什么呢?ssh-agent 是一个用于存储私钥的临时性的 session 服务,当我们使用秘钥登录服务器时,ssh-agent 会启动一个进程在内存里保存这些私钥。之后每次登录时,ssh 客户端都会跟 ssh-agent 请求是否有目标主机的私钥;如果有,ssh 客户端便能直接登录目标主机。

$ ssh-add -D # 清空 ssh-agent 管理的秘钥
All identities removed.
$ ssh root@195.85.35.153 -i myserver # 登录服务器,并使用指定的私钥 myserver
$ ssh-add -l # 此时观察 ssh-agent 存储的秘钥,发现 ssh
2048 SHA256:6PVJJCR1H/3M977orF4EfJgk0d9bZoul538/MtzBXZY ppsteven@ppstevendeMacBook-Pro.local (RSA)
$ ssh root@195.85.35.153 # 此时,就算不指定 myserver 私钥,也能成功登录服务器,原因是ssh已经在ssh-agent中找到了存储的私钥。

基本命令

ssh-agent bash # 启动 ssh-agent 服务
ssh-add -D # 删除之前存储的key
ssh-add -l # 查看存储的key,这里应该是空的
ssh-add ~/.ssh/id_rsa # 添加私钥 id_rsa
ssh-add ~/.ssh/coding # 添加私钥 coding

如何添加 ssh-agent

手动添加

ssh-add ~/.ssh/coding

每次使用ssh时自动添加

需要配置 ~/.ssh/config

AddKeysToAgent yes # 自动添加私钥到 ssh-agent 中
ssh root@demo.com # 登录成功后会自动添加
ssh -T git@github.com # 测试成功后会自动添加

测试ssh是否设置成功

# ssh -T 后面的名字是上面的别名
$ ssh -T github
Hi PPsteven! You've successfully authenticated, but GitHub does not provide shell access.
(base)

$ ssh -T coding
Coding 提示: Hello ppsteven, You've connected to Coding.net via SSH. This is a personal key.
ppsteven,你好,你已经通过 SSH 协议认证 Coding.net 服务,这是一个个人公钥
(base)

# 不使用别名时

# 第一次测试的时候,需要指定私钥位置,之后私钥会自动加入 ssh-agent 中
$ ssh -T git@e.coding.net -i ~/.ssh/coding
CODING 提示: Hello ppsteven, You've connected to coding.net via SSH. This is a Personal Key.
ppsteven,你好,你已经通过 SSH 协议认证 coding.net 服务,这是一个个人公钥.
公钥指纹:42:c6:f4:f6:39:1f:d7:84:e0:cb:19:a3:ad:25:62:20

# 私钥先加入 ssh-agent 中,后测试
$ ssh-add ~/.ssh/coding
$ ssh -T git@e.coding.net
CODING 提示: Hello ppsteven, You've connected to coding.net via SSH. This is a Personal Key.
ppsteven,你好,你已经通过 SSH 协议认证 coding.net 服务,这是一个个人公钥.
公钥指纹:42:c6:f4:f6:39:1f:d7:84:e0:cb:19:a3:ad:25:62:20

自动添加秘钥的几种方法

为什么要自动添加 秘钥 ,原因是 ssh-agent 是将所有的秘钥存在内存中,这意味着当电脑重启或初次启动的时候,ssh-agent 中存储的私钥会被清空,我们还是需要做 一次 手动添加私钥的操作。

方案一:使用 keychain(Mac-弃用)

keychain 是 Mac 电脑上的钥匙串服务,作用是存储密码、秘钥,证书等信息。Win 和 Linux 也有对应的机制,没有研究。

首先,我们要保证 config 里面有这样两段代码,Mac OS 10.12.2 以上系统需要,不然的话,无法持久化的添加到钥匙串中。

# ~/.ssh/config

+ Host *
+ AddKeysToAgent yes # 把私钥添加到 ssh-agent 中
+ UseKeychain yes # Mac 上秘钥被持久化到"钥匙串"中,代表从钥匙串中使用保存的秘钥

此时,当你运行 ssh 后,私钥会自动添加到 ssh-agent钥匙串 中,下面在mac中的 钥匙串 找到了添加的私钥。

需要注意的是,Mac上使用 ssh-add -K /path/to/storekey 已经不能直接存储秘钥了,mac 只会存储带 passphrase 的秘钥,对于不带 passphrase 验证的秘钥,Mac的 KeyChain 不会自动保存。可以手动在 Mac 的钥匙串中加入,然后通过 ssh-add -A 添加钥匙串中的秘钥

方案二:添加到启动配置中去(通用)

直接将所有的秘钥添加到 ~/.ssh/config

Host *
IdentityFile path/to/key1
IdentityFile path/to/key2

对于 Centos 系统,由于 ssh-agent 不会自动启动,所以需要单独运行 eval "$(ssh-agent -s)",参考 Ubuntu、Mac、Centos下ssh-agent 对比

$ eval "$(ssh-agent -s)"
> Agent pid 59566
$ ssh-add ~/.ssh/id_rsa

下一个问题,应该把这一段代码添加到哪里。这里的建议是加到或者 .profile ,不建议直接添加到 ~/.bashrc~/.zshrc 中,因为这样每次打开一个 SHELL 就会开一个 ssh-agent 服务,最终会拖垮服务器。

更多的地方参考我的另一篇教程:linux 环境变量执行顺序

参考资料

如何添加多账号

下面是 google 出来的解答,质量很高,建议参考

How to manage multiple GitHub accounts on a single machine with SSH keys

自动添加ssh账号