指令
Git Bash 使用 ssh 登陆
配置 Git Bash:
git config --global user.name <name>
git config --global user.email <email>
# 如果开了 Github 的 `Keep my email addresses private` 功能,
# 设置 email 时 要填 Github 生成的 email, 而不是注册 email.
# 还有一些其他可选设置
以下是我的一些配置:
$ git config --list
core.symlinks=false
core.autocrlf=true
core.fscache=true
color.diff=auto
color.status=auto
color.branch=auto
color.interactive=true
help.format=html
rebase.autosquash=true
http.sslbackend=openssl
http.sslcainfo=C:/Program Files/Git/mingw64/ssl/certs/ca-bundle.crt
credential.helper=manager
core.editor="C:\\Program Files\\Microsoft VS Code\\Code.exe" --wait
diff.astextplain.textconv=astextplain
filter.lfs.clean=git-lfs clean -- %f
filter.lfs.smudge=git-lfs smudge -- %f
filter.lfs.process=git-lfs filter-process
filter.lfs.required=true
filter.lfs.clean=git-lfs clean -- %f
filter.lfs.smudge=git-lfs smudge -- %f
filter.lfs.process=git-lfs filter-process
filter.lfs.required=true
user.name=username
user.email=fakenameg@users.noreply.github.com
core.quotepath=true
配置 ssh:
# 查看版本 (我的版本比较新, 老版本的指令略有不同)
$ ssh -V
OpenSSH_8.0p1, OpenSSL 1.1.1c 28 May 2019
# 新开一个 ssh 代理
$ ssh-agent -s
SSH_AUTH_SOCK=/tmp/ssh-zBSkGOjqZpYl/agent.1678; export SSH_AUTH_SOCK;
SSH_AGENT_PID=1679; export SSH_AGENT_PID;
echo Agent pid 1679;
# 确定一下这个 ssh 还没有密钥
$ ssh-add -l -E md5
The agent has no identities.
# 整一个新的密钥, 一路回车确定 (默认配置)
$ ssh-keygen -t rsa -b 4096 -C "随便一串字符"
# 新密钥的私钥和公钥储存位置 (id_rsa 和 id_rsa.pub)
$ ls ~/.ssh/
id_rsa id_rsa.pub known_hosts
# 添加到 ssh-agent
$ ssh-add ~/.ssh/id_rsa
Identity added: /c/Users/xie/.ssh/id_rsa (随便一串字符)
# 再次查看 ssh-agent 的密钥, 已经成功添加
$ ssh-add -l -E md5
4096 MD5:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx 随便一串字符 (RSA)
然后登陆 Github, 复制公钥:
clip < ~/.ssh/id_rsa.pub
登陆后 https://github.com/settings/keys
-> New SSH Key
, ‘Title’ 随意, 在 ‘Key’ 粘贴.
然后测试一下连接 (初次使用以下指令会多一段对话(如下), 输入 “yes”):
$ ssh -T git@github.com
The authenticity of host 'github.com (13.250.177.223)' can't be established.
RSA key fingerprint is SHA256:aF9zXis7VUy4CsdfSltbl6ZCksa34ChdVfA54S3xcA2.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'github.com,13.250.177.223' (RSA) to the list of known hosts.
Hi XieNaoban! You've successfully authenticated, but GitHub does not provide shell access.
提交三部曲
直接偷懒把当前时间作为本次提交版本的 “提交说明”, 每次都要思考 “提交说明” 怎么写真是太累了 (真实项目别这么干).
Bash:
# 获取当前时间作为 "提交说明"
readonly date=$(date +%Y-%m-%d\ %H:%M:%S)
git add . # 添加当前目录所有文件到暂存区
git commit -m "${date}" # 提交到本地仓库, 以时间作为 "提交说明"
git push origin master # origin 表示远程主机, master 为分支名称
PowerShell:
$date = Get-Date -Format 'yyyyMMdd-HH:mm:ss'
git add .
git commit -m "${date}"
git push origin master
分支操作
git branch # 查看分支
git branch <branch> # 创建分支
git checkout <branch> # 切换分支
git checkout -b <branch> # 创建并切换分支
# 推送到远程分支: 远程还没有该分支
git push origin <local_branch>:<remote_branch>
# 删除远程分支
git push origin --delete <remote_branch>
找不同
git status # 找出不同的文件、文件夹
git status -s # 找不同, 显示很精简规整, 就很舒服
git diff # 找出不同的每一行
分支合并
git checkout <branch_1> # 转到分支1
git merge <branch_2> # 合并分支2
git push origin <branch_1> # 推送到远程分支
从远程更新到本地
git pull <host> <remote_branch>:<local_branch>
git pull <host> <remote_branch> # 与当前本地分支合并
git pull # 简写, 合并本分支
# git pull origin tmp 相当于:
git fetch origin
git merge origin/tmp
# git pull 更快, 但 fetch 再 merge 可以方便查看 diff
git fetch origin master:tmp
git diff tmp
git merge tmp
撤销
# 撤销未 git add 的文件的修改
git checkout . # 全部撤销修改
git checkout <file> # 撤销文件修改
git reset --hard # 效果与 checkout 一样
# 撤销 git add 操作 (不修改文件本身)
git reset HEAD
踩坑
Git Bash 中文乱码
例如对于 git status
指令, 中文文件 “新建文本文档.txt” 会被显示为
\346\226\260\345\273\272\346\226\207\346\234\254\346\226\207\346\241\243.txt
解决方法如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lBbQ1PL7-1599531563745)(3.png)]
然后
# 解决 Windows Git Bash、Linux 下的中文转码问题
git config --global core.quotepath false
Powershell 中文乱码
例如对于 git log
指令, 中文的 Commit Message 会被显示为:
<E8><BF><87><E6><BB><A4><E5><8A><9F><E8><83><BD><E5><9F><BA><E6><9C><AC><E5><AE><8C><E6><88><90>
解决方法如下, 在 Powershell 输入:
git config --global core.quotepath false
git config --global gui.encoding utf-8
git config --global i18n.commit.encoding utf-8
git config --global i18n.logoutputencoding utf-8
$env:LESSCHARSET='utf-8' #该行可以不输入, 但可能之后要重启 Powershell
然后设置系统环境变量, 设置 LESSCHARSET
为 utf-8
.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vc0EFS2T-1599531563747)(4.png)]
Git Bash 命令提示符的 $
总是另起一行
命令前的那一串叫 “系统终端命令提示符 (Prompt Sign)” (就是那一串包含用户名, 工作目录和 $
符号的东西). Windows 下的 Git Bash 的命令提示符的 $
总是另起一行.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dgWcYyHu-1599531563749)(1.png)]
这玩意由 $PS1
控制. 输入 echo "$PS1"
, 可得:
# 我的PS1被 conda 修改过, 所以前面有个 "(base)"
(base)
xie@DESKTOP-BAD47V8 MINGW64 ~
$ echo "$PS1"
(base) \[\033]0;$TITLEPREFIX:$PWD\007\]\n\[\033[32m\]\u@\h \[\033[35m\]$MSYSTEM \[\033[33m\]\w\[\033[36m\]`__git_ps1`\[\033[0m\]\n$
修改它就能修改提示符样式.
而 Ubuntu 下的 $PS1
为:
xie@xie-vm:~$ echo "$PS1"
\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$
那么依葫芦画瓢改一改. 用 vim 或 code (vscode) 命令打开 ~/.bash_profile
, 添加一行:
export PS1="\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[32m\]\u@\h\[\033[00m\]:\[\033[33m\]\w\[\033[00m\]\$ "
保存重启 Git Bash即可.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x3D96avL-1599531563752)(2.png)]
使用 ssh 执行 git clone
报错
$ git clone git@github.com:xxx/xxx.git
Cloning into 'xxx'...
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
此时如果测试连接, 却显示正常:
$ ssh -T git@github.com
Hi XieNaoban! You've successfully authenticated, but GitHub does not provide shell access.
网上的解决方案大多是说 ssh 配置出了问题. 但我这里不是这个原因. 原因是勾选了 https://github.com/settings/emails
-> Keep my email addresses private
, 好像是我的隐私邮箱被系统更换了, 所以就挂了. 解决方案同下面的 Push 时报错 push declined due to email privacy restrictions
Push 时报错 push declined due to email privacy restrictions
从某天开始突然不能 push 了, 错误信息:
! [remote rejected] master -> master (push declined due to email privacy restrictions)
原因是勾选了 https://github.com/settings/emails
-> Keep my email addresses private
, 可能是因为它重置了隐私邮箱吧.
复制 Keep my email addresses private
下给的邮箱 (数字+名字@users.noreply.github.com),
git config --global user.email 12345678+XieNaoban@users.noreply.github.com
git commit --amend --reset-author # 会打开文本编辑器, 直接关闭即可
就好了.
Windows 下 Clone 的文件以 LF 为换行符而非 CRLF
设置:
git config --global core.autocrlf input # 提交时转换为 LF, 签出时不转换
# 或
git config --global core.autocrlf false # 提交签出均不转换
以下载 LF 的版本. 折腾完了改回去 (毕竟 Win 下还是默认开着好):
git config --global core.autocrlf true # 提交时转换为 LF, 签出时转换为 CRLF
解决方案: 直接干脆把文件统一成 LF, 不要转来转去了, 反正在 Win 下用 LF 大多数软件也支持.
Win 下 warning: LF will be replaced by CRLF
Windows 以 CRLF 为换行符, Linux 以 LF 为换行符, Git 仓库以 LF 为换行符. 从 Win 上 git clone
时, Git 会自动把仓库里所有的文件从 LF 转为 CRLF. 当提交时, 又会把 CRLF 转回 LF. 如果提交文件是纯 LF 文件, 会显示一个警告: warning: LF will be replaced by CRLF in xxx.
.
直接关了这个 warning:
git config --global core.safecrlf false # 默认是 'warn'
git reset
与 git revert
区别
git reset
回滚到某 commit, 后面的 commmit 都抛弃了. 而 git revert
创建一个新 commit, 中和之前的提交.
用 git reset
后 git push
会报错, 提示你当前本地版本落后于远程版本. 需要强制推送 (git push -f
).
保留空文件夹
在提交时, 空文件夹 (或者文件夹内所有文件均被gitignore忽略) 不会被提交, 所以 git clone
时将没有那个文件夹. 如果希望提交时保留空文件夹, 可以在该文件夹下新建一个空白文件, 并取名 .gitkeep
… 原理也很简单, 有了这个文件, 该文件夹就不是空的了… 所以其实取什么名字都行, 只不过 .gitkeep
比较约定俗成, 比如 IntelliJ 的文件目录视图貌似会自动忽略这个文件.
删除历史中的所有大文件
年少无知的我没写 .gitignore
, 把一堆巨大的 json 文件一并 add 到了历史里, 期间这些 json 还改动过好几次, 于是乎我的 .git
文件夹就理所当然地膨胀到了 100MB… 现在我很后悔, 发现这些 json 从一开始就不应该 add, 于是想把它们统统从历史中删除.
参考了不少博客, Github 官方也有文档: Removing sensitive data from a repository - GitHub Help.
先看一下Git历史数据占地面积:
git count-objects -v
得到:
count: 119
size: 43363
in-pack: 999
packs: 1
size-pack: 30787
prune-packable: 0
garbage: 0
size-garbage: 0
然后找出所有大文件, 用的大佬写好的 bash 脚本 (findBigFile.sh):
#!/bin/bash
#set -x
# Shows you the largest objects in your repo's pack file.
# Written for osx.
#
# @see http://stubbisms.wordpress.com/2009/07/10/git-script-to-show-largest-pack-objects-and-trim-your-waist-line/
# @author Antony Stubbs
# set the internal field spereator to line break, so that we can iterate easily over the verify-pack output
IFS=$'\n';
# list all objects including their size, sort by size, take top 10
objects=`git verify-pack -v .git/objects/pack/pack-*.idx | grep -v chain | sort -k3nr | head`
echo "All sizes are in kB. The pack column is the size of the object, compressed, inside the pack file."
output="size,pack,SHA,location"
for y in $objects
do
# extract the size in bytes
size=$((`echo $y | cut -f 5 -d ' '`/1024))
# extract the compressed size in bytes
compressedSize=$((`echo $y | cut -f 6 -d ' '`/1024))
# extract the SHA
sha=`echo $y | cut -f 1 -d ' '`
# find the objects location in the repository tree
other=`git rev-list --all --objects | grep $sha`
#lineBreak=`echo -e "\n"`
output="${output}\n${size},${compressedSize},${other}"
done
echo -e $output | column -t -s ', '
脚本运行结果大致如下:
size pack SHA location
60832 2900 a8b382af350184d5ead001b1e91c66cea62c54b7 path/to/your/file1
33288 4034 e7bba187e68f752e88664c49b95e5ba18e022dca path/to/your/file2
13804 572 c0c7383de6e9b28e97822a896199b11174ade578 path/to/your/file3
13751 571 2609b4429eb13a2a9885a27073b608e8bdecb3d0 path/to/your/file4
...
默认展示 10 条, 如果想要展示 233 条更多只要把脚本里的 head
改为 head -233
即可. 核心就是 objects
那句, 大佬注释写的挺完整, 在此不解释了.
然后就是最核心的删除语句:
git filter-branch --force --index-filter "git rm --cached --ignore-unmatch path/to/your/file" --prune-empty --tag-name-filter cat -- --all
这行指令原理应该是遍历你的所有 commit, 如果该 commit 有这个目录有这个文件, 就删除. 运行结果如下 (运行速度挺慢的):
WARNING: git-filter-branch has a glut of gotchas generating mangled history
rewrites. Hit Ctrl-C before proceeding to abort, then use an
alternative filtering tool such as 'git filter-repo'
(https://github.com/newren/git-filter-repo/) instead. See the
filter-branch manual page for more details; to squelch this warning,
set FILTER_BRANCH_SQUELCH_WARNING=1.
Proceeding with filter-branch...
Rewrite a361c1d183b59a5df22bc2b7d38eeadf45255675 (73/78) (44 seconds passed, remaining 3 predicted) rm 'path/to/your/file'
Rewrite 434e4dd7e434e2516dabf8987ab26ce0c45d2dc5 (75/78) (45 seconds passed, remaining 1 predicted) rm 'path/to/your/file'
Rewrite 50ce64408ebf9f9e96d2aa407c8f5b752460b66a (75/78) (45 seconds passed, remaining 1 predicted) rm 'path/to/your/file'
Rewrite 820ae95f38eac7e60eeeae40fe6d624c1c61a05a (77/78) (47 seconds passed, remaining 0 predicted) rm 'path/to/your/file'
Rewrite 1e2781e656495a06e325641e59eed36fd0df1f40 (77/78) (47 seconds passed, remaining 0 predicted)
Ref 'refs/heads/master' was rewritten
Ref 'refs/remotes/origin/master' was rewritten
WARNING: Ref 'refs/remotes/origin/WS' is unchanged
WARNING: Ref 'refs/remotes/origin/master' is unchanged
Ref 'refs/remotes/origin/xie' was rewritten
然后强制 push:
git push origin --force --all
有些教程到这就结束了, 有些还有后续 (清理、压缩 .git
等), 后续各不相同, 我综合整理了个自己的版本:
rm -rf .git/refs/original/
# 虽然物理上文件被删了, 但逻辑上可能还有没更新的引用, 删了以下的文件让 git 重新生成
git reflog expire --expire=now --all
# 管理所有的 reflog 信息, expire 表示修剪旧的 reflog 条目.
# --expire=now 表示修剪早于当前时间的条目;
# --all 表示处理所有引用的 reflog.
# 刚刚删掉的内容貌似就是在此重新生成 (应该吧).
git fsck --full --unreachable
# 验证数据库中对象的连接性和有效性.
# --full 啥都检查;
# --unreachable 如果有节点存在但不可达, 则输出.
# 这条指令貌似只是检测, 不修改什么东西.
git repack -A -d
# 把没打包的对象打包并删除冗余对象.
# -A 把引用的所有内容打到一个包里, -a 和 -A 好像是一个意思;
# -d 删除冗余包.
git gc --aggressive --prune=now
# 清理不必要的文件, 优化本地存储.
# --aggressive 更好地优化存储, 但会花费更多时间;
# --prune=now 修剪比当前时间更早的松散物体
其中 git repack -A -d
和 git gc --aggressive --prune=now
输出的 log 几乎一样, 可能只执行其中一个就行?
整理了个自动删除大文件的脚本 (事先找好要删的文件放在 rm_list
里):
#!/bin/bash
rm_list=('path/file1', 'path/file2', 'path/file3')
for f in ${rm_list[@]}
do
echo '---'
echo "removing '${f}'..."
git filter-branch --force --index-filter "git rm --cached --ignore-unmatch ${f}" --prune-empty --tag-name-filter cat -- --all
done
# clean up and reclaim space
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git fsck --full --unreachable
git repack -A -d
git gc --aggressive --prune=now
# force-push changes to overwrite remote repository
git push origin --force --all
删了 30 多个 json, 最后把 100MB 的 .git
清理到了 10MB (捂脸). 删除过程贼慢, 最后 push 时 “Writing objects” 也贼慢.
.gitignore
对已 track 文件不起作用
当一个文件已经被 commit 后才被发现这个文件是应该 ignore 的, 此时再把它添加入 .gitignore
的话, git 依然会追踪它. 修改该文件, 然后你会发现 git 仍然记录了这个文件的修改. 原因是 .gitignore
仅对新文件生效, 已被 track 的文件不受影响. 解决方法:
- 强制清除对所有文件的追踪 (只清除不想追踪的文件应该也行, 没试)
git rm -r -f --cache .
- 更新你的 .gitignore (或者在上述指令之前更新也没关系)
- 重新
add
、commit
.
给 Git SSH 上 Socks5 代理
如果是使用 http/https 给 git 上代理, 那方法很多, 比如给终端上代理或者直接给 git 设置代理:
git config --global http.proxy socks5://127.0.0.1:10808
git config --global https.proxy socks5://127.0.0.1:10808
使用完后:
git config --global --unset http.proxy
git config --global --unset https.proxy
但是我现在想用 ssh 进行 git clone (git clone git@github.com:xxx/xxx.git
), 针对 http/https 的代理对 ssh 是无效的. 所以要通过配置 ssh 自身来实现代理.
方法是打开 ~/.ssh/config
(Win下就是 个人文件夹\.ssh\config
), 如果没有就新建这个文件.
然后写入一行代理指令, Win 和 Linux 指令不同:
# Linux (如果无 nc 请先安装. 但是 Win 下压根没有 nc)
# -X: 指定协议, 5 就是 Socks5 (不写该参数默认也是 Socks5); -x 指定 address:port
ProxyCommand nc -X 5 -x 127.0.0.1:10808 %h %p
# Win (使用 Git 在 Win 下自己实现的工具)
ProxyCommand "C:\Program Files\Git\mingw64\bin\connect.exe" -S 127.0.0.1:10808 %h %p
以上指令效果是全局的, 局部的稍微麻烦一点, 要在指令里指定主机和用户, 懒得弄了.
当然, 直接在控制台像这样设置也行:
# -o 即 option 的意思
# 格式有两种, 语法略有不同而已, 本质一样的
ssh -o ProxyCommand="nc -X 5 -x 127.0.0.1:10808 %h %p"
ssh -o "ProxyCommand nc -X 5 -x 127.0.0.1:10808 %h %p"
# 以上是 Linux 的指令, Win 还是改成上面对应的指令
不过我对ssh 不太熟, 也没去试, 暂不知道 -o
是永久的还是仅限当前会话.