1 Git 简介
Git 是一个开源的分布式版本控制系统,最初由 Linus Torvalds 为管理 Linux 内核而开发的开源软件,目前已应用在 Windows、Linux、MacOS 等操作系统上。
SVN 也是一种版本控制系统,但是其采用集中式管理方式,容易出现单点故障,容错性差。Git 采用分布式管理方式,没有中央服务器,每个人的电脑都是一个完整的版本库。
Git 常用命令流程图如下:
Git下载地址见→Git - Downloads
2 Git 配置
2.1 配置用户信息
1)查看用户信息
git config --global -l --show-origin
git config user.name
git config user.email
git config user.password
2)修改用户信息
git config --global user.name "xxx"
git config --global user.email "xxx@qq.com"
git config --global user.password "xxx"
2.2 配置 ssh key
使用 SSH 拉取代码时,需要配置 ssh key。
1)生成 ssh 密钥对
# 创建 ssh key(可以省略 -b 属性, rsa密钥长度建议至少2048位, 默认1024位不安全, 可能导致SSH密钥无法正常工作, gitcode服务器要求至少2048位)
ssh-keygen -o -t rsa -b 4096 -C "email@example.com"
说明:运行 ssh-keygen 命令后,将在本地用户目录下的 .ssh 目录下(如 C:\Users\81518\.ssh\ ),生成 id_rsa(私钥)和 id_rsa.pub(公钥),公钥需要配置在服务器上。公钥和私钥的作用如下:
- 在数据传输时,公钥用于加密,私钥用于解密。
- 在身份认证时,私钥用于签名,公钥用于验证。
2)查看公钥
cat ~/.ssh/id_rsa.pub
3)配置公钥
登录 gitcode、github 等官网,点击用户头像,依次选择 settings→SSH keys→New SSH key,将公钥粘贴并保存,如下。
4)测试 ssh 连通状态
# 测试SSH密钥是否能够正常工作
ssh -T git@gitcode.net
ssh -T git@github.com
2.3 配置代理
1)查看代理
# 查看代理
git config http.proxy
git config https.proxy
2)设置 http 拉代码的代理
如果通过 http 拉代码,可以设置代理如下。
# 设置代理
# http/https代理
git config --global https.proxy http://127.0.0.1:1080
git config --global https.proxy https://127.0.0.1:1080
# socks5代理(ss、ssr)
git config --global http.proxy socks5://127.0.0.1:1080
git config --global https.proxy socks5://127.0.0.1:1080
# 只对github使用代理, 其他仓库不走代理
# http/https代理
git config --global http.https://github.com.proxy http://127.0.0.1:1080
git config --global https.https://github.com.proxy https://127.0.0.1:1080
# socks5代理(ss、ssr)
git config --global http.https://github.com.proxy socks5://127.0.0.1:1080
git config --global https.https://github.com.proxy socks5://127.0.0.1:1080
3)取消代理
# 取消全局代理
git config --global --unset http.proxy
git config --global --unset https.proxy
# 取消github代理
git config --global --unset http.https://github.com.proxy
git config --global --unset https.https://github.com.proxy
4)设置 ssh 拉代码的代理
如果使用 git@ 拉代码,在 ~/.ssh/config 文件(没有可以新建一个)里添加以下内容。
Host github.com
User git
ProxyCommand connect -H 127.0.0.1:1080 %h %p
Host github.com
User git
ProxyCommand connect -S 127.0.0.1:1080 %h %p
2.4 其他配置
1)免密拉取代码或推送代码
执行以下命令后, 再拉代码或推送代码, 需要输一次密码, 以后就不需要输入了。
git config --global credential.helper store
2)绑定远程分支
git branch --set-upstream-to=origin/branch_name
# 如: git branch --set-upstream-to=origin/master
# 绑定后, 就可以通过git pull拉代码, 通过git push推送代码
# 而不必使用git pull origin master拉代码, 不必使用git push origin master推送代码
3 拉取代码
3.1 首次拉取代码
1)克隆代码
# 下载一个项目和它的整个代码历史
git clone [repository_url]
# 下载一个项目和它的整个代码历史, 并将项目根目录重命名为repository_name
git clone [repository_url] [repository_name]
# 下载指定分支的代码
git clone -b [branch_name] [repository_url]
# 下载项目及其所有依赖子模块(递归拉取)
git clone --recurse-submodules [repository_url]
2)拉取依赖模块
# 如果执行clone时忘记了添加--recurse-submodules, 进入项目根目录后, 执行以下命令可以继续拉所有依赖子模块(递归拉取)
git submodule update --init --recursive
3.2 非首次拉取代码
1)查看工作区是否有改动
# 同步代码前, 执行以下命令, 如果打印: Your branch is up to date, 说明本地没有修改, 同步代码不会有冲突, 可以放心拉代码了
git status
2) 拉取代码
# 下载远程仓库的所有变动
git fetch [remote]
# 取回远程仓库的变化,并与本地分支合并
git pull [remote] [branch]
4 提交代码
4.1 首次提交代码
此处“首次”是指远程仓库刚创建之后提交的第一笔代码。用户如果想将某个目录里面的文件提交到远程仓库,cd 到该目录下,再执行以下流程。
1)初始化本地仓库
git init
2)将本地仓库与远程仓库关联
git remote add origin [repository_url]
3)提交代码
# 将当前目录的所有文件添加到暂存区
git add .
# 提交暂存区所有文件到本地仓库
git commit -m [message]
# 将本地的master分支推送到origin主机, 同时指定origin为默认主机, 后面就可以不加任何参数使用git push了
git push -u origin master
4.2 非首次提交代码
1)查看工作区改动了哪些文件
# 提交代码前, 执行以下命令, 查看修改了哪些文件(不显示具体改动的内容, 只显示改动的文件名)
git status
2)查看工作区改动的内容
# 显示暂存区和工作区的差异(所有文件)
git diff
# 显示暂存区和工作区的差异(指定文件)
git diff [file]
3)工作区 → 暂存区
# 添加指定文件到暂存区
git add [file1] [file2] ...
# 将指定目录添加到暂存区
git add [dir]
# 将当前目录的所有文件添加到暂存区
git add .
4)暂存区 → 本地仓库
# 提交暂存区指定文件到本地仓库
git commit [file1] [file2] ... -m [message]
# 提交暂存区所有文件到本地仓库
git commit -m [message]
说明:message 为提交的日志说明。
5)本地仓库 → 远程仓库
# 上传本地指定分支到远程仓库
git push [remote] [branch]
# 强行推送当前分支到远程仓库,即使有冲突
git push [remote] --force
# 推送所有分支到远程仓库
git push [remote] --all
6)合并远程仓库代码
# 合并指定分支到当前分支
git merge [branch_name] --no-ff
说明:--no-ff 表示不使用“快进合并”(fast-forward merge),快进合并会直接将目标分支的更改合并到当前分支,而不创建新的合并提交。使用 --no-ff 选项会强制创建一个新的合并提交,这样可以保留更清晰的合并历史,更容易追溯分支的合并情况。
5 导出、导入 patch
patch 是 commit 的离线数据,用户 A 可以导出 patch 发给用户 B,用户 B 导入patch 就可以同步用户 A 的本地代码。
5.1 导出 patch
# 导出最近的 1 个提交, 生成 0001-Update.patch 文件
git format-patch -1
# 导出最近的 2 个提交, 生成 0001-Update.patch、0002-Update.patch 文件
git format-patch -2
# 导出最近的 1 个提交, 生成 xxx/0001-Update.patch 文件
git format-patch -1 -o xxx
说明:用户 A 在执行 git format-patch 前,需要执行 git commit 将数据提交到本地仓库(不要执行 git push 提交到远程仓库)。
5.2 导入 patch
# 导入 0001-Update.patch 差异数据
git am 0001-Update.patch
# 导入所有差异数据
git am *.patch
# 应用 patch 时冲突了, 解决冲突后, 使用以下命令继续应用 patch
git am --continue
6 解决冲突
1)工作区 ⇌ 临时空间
# 将工作区改动代码提至临时空间, 同时将暂存区的代码同步至工作区(清除本地修改)
git stash
# 拉取代码之后, 执行下述命令, 将临时空间的代码同步至本地
git stash pop
# git stash apply
2)撤销 modify(只改变工作区)
# 清除工作区中未暂存的修改(指定文件)
git checkout [file1] [file2] ...
# 清除工作区中未暂存的修改(所有文件)
git checkout .
# 清除工作区中未暂存的修改(指定文件, Git 2.23 版本新引入)
git restore [file1] [file2] ...
# 清除工作区中未暂存的修改(所有文件, Git 2.23 版本新引入)
git restore .
3)撤销 add(只改变暂存区)
# 撤销 add 的指定文件 (不改变工作区, Git 2.23 版本新引入)
git restore [file1] [file2] ... --staged
# 撤销 add 的所有文件 (不改变工作区, Git 2.23 版本新引入)
git restore . --staged
4)撤销 add 和 modify(改变暂存区和工作区)
# 撤销 add 和 modify (不能指定文件, 会撤销所有修改)
git reset --hard
5)撤销 commit(只改变本地仓库)
# 撤销 commit (只改变本地仓库, 不改变暂存区, Git 2.23 版本新引入)
git reset HEAD^ --soft
6)恢复历史版本
方法一:
# 后退 n 步,每步都后退到第 1 个提交上
git reset HEAD~n
# 后退 1 步,这步后退到第 n 个提交上
git reset HEAD^n
说明: 当省略 n 时,相当于 n 为 1,如:git reset HEAD~ 等效于 git reset HEAD~1,git reset HEAD^ 等效于 git reset HEAD^1
方法二:
# 查看历史版本信息(可以得到 commitId)
git log [--oneline]
# 根据 commitId 回退至指定版本
git reset commitId
说明:reset 只会回退本地仓库和暂存区的内容,不会回退工作区的内容,如果想回退工作区的内容,在 reset 命令的末尾加上 --hard;如果只想回退本地仓库的内容,不回退暂存区的内容,在 reset 命令的末尾加上 --soft。
7 检视代码
1)查看历史版本信息
# 显示当前分支的版本历史(包含提交用户、提交时间、commitId等信息)
git log [--oneline]
2)查看指定文件的修改情况
# 查看文件的相对路径
find . -name "Test.java"
# 显示指定文件是什么人在什么时间修改过
git blame [file]
8 分支管理
1)查看分支
# 列出所有本地分支
git branch
# 列出所有远程分支
git branch -r
# 列出所有本地分支和远程分支
git branch -a
2)新建分支
# 新建分支,但依然停留在当前分支
git branch [branch_name]
3)切换分支
# 新建一个分支,并切换到该分支
git checkout -b [branch_name]
# 创建一个名为local_master的新分支,并将其设置为跟踪remotes/origin/master
git checkout -b local_master remotes/origin/master
# 切换到指定分支,并更新工作区
git checkout [branch_name]
# 切换到上一个分支
git checkout -
4)重命名分支
# 重命名分支
git branch -m <oldbranch> <newbranch>
5)删除分支
# 删除分支
git branch -d [branch_name]
9 统计代码
1)统计所有开发者及其提交笔数
# -s: 显示姓名, -e: 显示邮箱, -n: 按提交数量排序
git shortlog -s -e -n
打印格式如下。
220 little_fat_sheep <xxx@qq.com>
15 zhyan8 <abc@qq.com>
2)统计用户今天提交的代码行数
git diff --author="little_fat_sheep" --shortstat "@{0 day ago}"
author 可以是用户名,也可以是邮箱,删除时,表示统计所有用户的。打印格式如下。
28 files changed, 544 insertions(+), 143 deletions(-)
3)统计用户近期提交代码行数
# 用户近1周提交代码量
git log --author="little_fat_sheep" --since="1 week ago" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "添加行数: %s\n删除行数: %s\n净增行数: %s\n", add, subs, loc }'
# 用户近半月提交代码量
git log --author="little_fat_sheep" --since="15 day ago" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "添加行数: %s\n删除行数: %s\n净增行数: %s\n", add, subs, loc }'
# 用户近1月提交代码量
git log --author="little_fat_sheep" --since="1 month ago" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "添加行数: %s\n删除行数: %s\n净增行数: %s\n", add, subs, loc }'
# 用户近1年提交代码量
git log --author="little_fat_sheep" --since="1 year ago" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "添加行数: %s\n删除行数: %s\n净增行数: %s\n", add, subs, loc }'
author 可以是用户名,也可以是邮箱,删除时,表示统计所有用户的。打印格式如下。
添加行数: 144943
删除行数: 259107
净增行数: -114164
4)统计用户提交的代码行数
git log --author="little_fat_sheep" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "添加行数: %s\n删除行数: %s\n净增行数: %s\n", add, subs, loc }'
author 可以是用户名,也可以是邮箱,删除时,表示统计所有用户的。打印格式如下。
添加行数: 1443700
删除行数: 883901
净增行数: 559799
5)统计每个文件的行数以及总行数
git ls-files | xargs wc -l
打印格式如下。
0 base/.gitignore
48 base/build.gradle
0 base/consumer-rules.pro
10 base/proguard-rules.pro
3 base/src/main/AndroidManifest.xml
...
213152 total
6)统计各种语言的添加、删除、净增行数
git log --author="little_fat_sheep" --pretty=tformat: --numstat | awk '
/\.java$/ { java_add += $1; java_sub += $2; java_net += $1 - $2 }
/\.kt$/ { kotlin_add += $1; kotlin_sub += $2; kotlin_net += $1 - $2 }
/\.js$|\.ts$/ { js_add += $1; js_sub += $2; js_net += $1 - $2 }
/\.c$|\.h$/ { c_add += $1; c_sub += $2; c_net += $1 - $2 }
/\.cpp$|\.hpp$|\.cc$|\.cxx$|\.hxx$/ { cpp_add += $1; cpp_sub += $2; cpp_net += $1 - $2 }
/\.cs$/ { cs_add += $1; cs_sub += $2; cs_net += $1 - $2 }
/\.m$|\.mm$/ { objc_add += $1; objc_sub += $2; objc_net += $1 - $2 }
/\.swift$/ { swift_add += $1; swift_sub += $2; swift_net += $1 - $2 }
/\.dart$/ { dart_add += $1; dart_sub += $2; dart_net += $1 - $2 }
/\.py$/ { python_add += $1; python_sub += $2; python_net += $1 - $2 }
/\.shader$|\.glsl$|\.vert$|\.frag$/ { shader_add += $1; shader_sub += $2; shader_net += $1 - $2 }
/\.xml$/ { xml_add += $1; xml_sub += $2; xml_net += $1 - $2 }
/\.json$/ { json_add += $1; json_sub += $2; json_net += $1 - $2 }
/\.css$/ { css_add += $1; css_sub += $2; css_net += $1 - $2 }
/\.html$|\.htm$/ { html_add += $1; html_sub += $2; html_net += $1 - $2 }
/\.sh$|\.bash$|\.zsh$|\.bat$/ { shell_add += $1; shell_sub += $2; shell_net += $1 - $2 }
END {
print "代码统计报告 - 作者: little_fat_sheep";
print "=======================================================";
print "语言类型 | 添加行数 | 删除行数 | 净增行数";
print "-------------------------------------------------------";
printf "%-13s | %11s | %11s | %11s\n", "Java", java_add, java_sub, java_net;
printf "%-13s | %11s | %11s | %11s\n", "Kotlin", kotlin_add, kotlin_sub, kotlin_net;
printf "%-13s | %11s | %11s | %11s\n", "JS/TS", js_add, js_sub, js_net;
printf "%-13s | %11s | %11s | %11s\n", "C", c_add, c_sub, c_net;
printf "%-13s | %11s | %11s | %11s\n", "C++", cpp_add, cpp_sub, cpp_net;
printf "%-13s | %11s | %11s | %11s\n", "C#", cs_add, cs_sub, cs_net;
printf "%-13s | %11s | %11s | %11s\n", "Objective-C", objc_add, objc_sub, objc_net;
printf "%-13s | %11s | %11s | %11s\n", "Swift", swift_add, swift_sub, swift_net;
printf "%-13s | %11s | %11s | %11s\n", "Dart", dart_add, dart_sub, dart_net;
printf "%-13s | %11s | %11s | %11s\n", "Python", python_add, python_sub, python_net;
printf "%-13s | %11s | %11s | %11s\n", "Shader", shader_add, shader_sub, shader_net;
printf "%-13s | %11s | %11s | %11s\n", "XML", xml_add, xml_sub, xml_net;
printf "%-13s | %11s | %11s | %11s\n", "JSON", json_add, json_sub, json_net;
printf "%-13s | %11s | %11s | %11s\n", "CSS", css_add, css_sub, css_net;
printf "%-13s | %11s | %11s | %11s\n", "HTML", html_add, html_sub, html_net;
printf "%-13s | %11s | %11s | %11s\n", "Shell", shell_add, shell_sub, shell_net;
print "=======================================================";
printf "%-11s | %11s | %11s | %11s\n", "总计",
java_add + kotlin_add + js_add + c_add + cpp_add + cs_add + objc_add + swift_add + dart_add + python_add + shader_add + xml_add + json_add + css_add + html_add + shell_add,
java_sub + kotlin_sub + js_sub + c_sub + cpp_sub + cs_sub + objc_sub + swift_sub + dart_sub + python_sub + shader_sub + xml_sub + json_sub + css_sub + html_sub + shell_sub,
java_net + kotlin_net + js_net + c_net + cpp_net + cs_net + objc_net + swift_net + dart_net + python_net + shader_net + xml_net + json_net + css_net + html_net + shell_net;
}'
author 可以是用户名,也可以是邮箱,删除时,表示统计所有用户的。打印格式如下。
代码统计报告 - 作者: little_fat_sheep
=======================================================
语言类型 | 添加行数 | 删除行数 | 净增行数
-------------------------------------------------------
Java | 35809 | 8864 | 26945
Kotlin | 24887 | 8216 | 16671
JS/TS | | |
C | 803 | 0 | 803
C++ | 791 | 0 | 791
C# | 8160 | 2684 | 5476
Objective-C | | |
Swift | | |
Dart | | |
Python | | |
Shader | 6797 | 3514 | 3283
XML | 9520 | 3515 | 6005
JSON | 8941 | 5067 | 3874
CSS | | |
HTML | | |
Shell | 529 | 14 | 515
=======================================================
总计 | 96237 | 31874 | 64363
10 gitignore
在项目根目录下配置 .gitignore 文件,可以忽略不需要更新的文件,如编译构建的中间文件、日志文件、第三方依赖包的缓存等。
Android 项目的 .gitignore 文件如下。
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
Unity 项目的 .gitignore 文件如下。
/Build
/Library
/Logs
/Temp
/UserSettings
/obj
/.idea
.vs
.vsconfig
Assembly-CSharp.csproj
Assembly-CSharp-Editor.csproj
Assembly-CSharp-firstpass.csproj
注意:.gitignore 文件需要在第一笔提交时提交上库,否则忽略的文件可能会失效,如:先提交了 build 文件,再提交 .gitignore 文件,虽然 .gitignore 中配置了忽略 build,后面 build 中有变动也会被提交上库的。当 .gitignore 中的文件(如 build)忽略失效时,可以先删除对应文件(如 build)并提交上库,之后再修改相关文件(如 build),就不会检测到代码更新。
11 Android Studio 中配置 Git
1)配置本地 git.exe
在【File】→【Settings】→【Version Control】→【Git】中选择本地安装的 Git,如下。
2)将本地项目导入到本地仓库
在【VCS】→【Import Into Version Control】→【Create Git Repository】中,选择当前项目的上级目录。
3)将本地仓库推送到远程服务器
在【VCS】→【Git】→【Remote】中,输入Name 和 URL,如下。