Git详解 | 生信人,你的代码质量合格么?
过年和 985 毕业程序员小哥哥交流 coding,小哥哥震惊于生信圈的代码质量尽如此令人咋舌。特此重新整理一篇我首发于 2018 年的 CSDN 推文
先放一个优秀的代码质量控制示例镇楼,我当年初学 scRNA-seq 数据分析原来是用 R Seurat,看了这种质量的开发,我选择相信并开始使用 python scanpy:
优秀的代码质量控制的基础是 Git,Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。发明人就是大名鼎鼎的 Linus ,发明初衷为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。需要知道的是 Git 底层文件验证采用的是 SHA-1,(SHA-1)哈希算法可以被用来验证文件。哈希算法有如下特点:
-
不管输入数据的数据量有多大,输入同一个哈希算法,得到的加密结果长度固定 -
哈希算法确定,输入数据确定,输出数据能够保证不变 -
哈希算法确定,输入数据有变化,输出数据一定有变化,而且通常变化很大 -
哈希算法不可逆
如果你未接触过 git,我推荐一个视频教程:https://www.bilibili.com/video/BV1pW411A7a5。
Git 常用代码流程
配置 git
$ git config --list # 显示当前的Git配置
$ git config -e [--global] # 编辑Git配置文件
# 设置提交代码时的用户信息,是否加上全局--global自行决定,一般是直接设置全局的。用户邮箱,需要和你远程仓库保持一致不然你的contribution是不会被记录在远程仓库的
$ git config [--global] user.name "[name]"
$ git config [--global] user.email "[email address]"
# 常用的给命令起别名
$ git config --global alias.st status
$ git config --global alias.last 'log -1' # 别名 显示最后一次Commit
$ git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
配置包括~/.gitconfig
(用户级别)和.git/config
(仓库级别),所有命令加上 --gloabal 就是指用户级别的配置。
创建代码库
$ mkdir project-name # 创建目录
$ cd project-name
$ git init [project-name] # 在当前目录新建一个Git代码库
$ git clone url # 下载项目的整个代码历史(包括各个分支提交记录等)
$ git remote add Aliases url # 为远程库取别名,用于push/pull,如 git pull origin master
$ git remote -v # 查看远程库信息
$ git remote rm Aliases
推送代码
$ git add [* | file | dir] # 将文件、文件夹添加到暂存区(Index)
$ git add -p # 添加每个变化前,都会要求确认,对于同一个文件的多处变化,可以实现分次提交
$ git commit [file1 | file2] -m 'message' # 提交暂存区到仓库区
$ git commit -a #提交工作区自上次commit之后的变化,直接到仓库区,慎用
$ git commit -v #提交时显示所有diff信息
$ git commit --amend -m 'message' # 使用一次新的commit,替代上一次提交;也在代码无变化时用来修改message信息
$ git push [-u] origin master # 提交更改到远程仓库
$ git pull origin master # 拉取远程更改到本地仓库默认自动合并
删除文件
删除文件也要慎重,现在举个例子,假设我在工作区 add、commit 了一个文件 a.txt
,
-
如果你确实想删除该文件 -
git status
-
git rm/add a.txt
-
git commit -m 'delete a.txt'
-
如果你是误删该文件 -
git checkout -- b.txt
# 其他操作
$ git rm [file1] [file2] ... # 删除工作区文件,并且将这次删除放入暂存区
$ git rm --cached [file] # 停止追踪指定文件,但该文件会保留在工作区
$ git mv [file-original] [file-renamed] # 改名文件,并且将这个改名放入暂存区
修改管理
$ git diff [file] # 默认是工作区文件和暂存区文件比较;
$ git diff HEAD [file] # 工作区和HEAD本地库比较
$ git checkout -- file # 把工作区的修改撤销
$ git reset HEAD file # 把暂存区的修改撤销掉(unstage)
$ git reset --hard HEAD^ # 把本地库的修改撤销掉(详细请看下面版本回退)
版本回退
$ git log --pretty=oneline # 查看(从当前往后的)历史版本,HEAD表示当前版本
$ git reset --hard HEAD^ # 后退一个版本,一个 ^ 表示一个版本
$ git reset --hard HEAD~1 # 后退一个版本
$ git reset --hard commit_id # 后退或前进到某一个版本
$ git reflog # 查看版本变迁
关于 git reset 的参数做以下说明:
-
--soft
-
本地库移动 HEAD 指针 -
--mixed
-
本地库移动 HEAD 指针 -
重置暂存区 -
--hard
-
本地库移动 HEAD 指针 -
重置暂存区 -
重置工作区
因此,当然一般建议使用--hard
分支管理
分支管理是 git 的一大特性,以下分支讲解来自廖学锋的 Git 教程[1]。在 Git 里,默认有个分支叫master
分支。HEAD
严格来说不是指向提交,而是指向master
,master
才是指向提交的,所以,HEAD
指向的就是当前分支。
当我们创建新的分支,例如dev
时,Git 新建了一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
上:
从现在开始,对工作区的修改和提交就是针对dev
分支了,比如新提交一次后,dev
指针往前移动一步,而master
指针不变:
假如我们在dev
上的工作完成了,就可以把dev
合并到master
上。Git 怎么合并呢?最简单的方法,就是直接把master
指向dev
的当前提交,就完成了合并:
合并完分支后,甚至可以删除dev
分支。删除dev
分支就是把dev
指针给删掉,删掉后,我们就剩下了一条master
分支:
$ git branch [-r|a| ] # 列出所有分支, r表示remote、a表示all,当前分支前会标星号
$ git branch [branch-name] # 新建一个分支,但依然停留在当前分支
$ git branch -d [branch-name] # 删除分支
$ git checkout -b [branch] # 新建一个分支,并转到新分支
$ git checkout [branch-name] # 切换分支
$ git checkout - # 切换到上一个分支
$ git merge [branch] # 合并指定分支到当前分支,Fast-forward模式
$ git merge --no-ff -m 'merge branch to current branch' [branch] # 禁用Fast-forward模式【推荐】
$ git checkout -b branch origin/branch # 创建分支并关联远程分支
$ git branch --set-upstream-to <branch-name> origin/<branch-name> # 如果创建的时候未关联,可以这样关联
解决冲突
当合并分支遇到上图情况时,就需要手动解决冲突:
手动根据实际业务需求修改好之后,再次提交即可:
$ git add test.txt
$ git commit -m 'fix conflict' test.txt
标签管理
我们经常会看到某某软件对应 v1.01,这就是 Tag,每个 Tag 对应于一个 commit,不可以移动,且创建的标签默认都只存储在本地,不会自动推送到远程。
# 想在master分支打一个标签
$ git checkout master
$ git tag [-m 'message'] v0.9
$ git log --pretty=oneline --abbrev-commit # 用这个命令查看历史commit_id
$ git tag v0.9 gt786b7
$ git tag # 查看当前分支有哪些Tag
$ git show <tagname> # 查看某个tag信息
$ git tag -d tag_name # 先删本地标签
$ git push origin :refs/tags/tag_name # 再删远程标签
$ git push origin <tagname> # 推送标签
$ git push origin --tags # 推送所有标签
Git 几个使用技巧
登录方式
登录方式有两种:账号登录、SSH 免密登录,账号登录就不用写了,这里主要说一下 SSH 登录:
ssh-keygen -t rsa -C "[email protected]"
会在~/.ssh 文件夹下生成id_rsa
(私钥,保存好)和id_rsa.pub
(公钥)两个文件,然后将公钥中的内容粘贴到下图位置:
关联远程库
关联远程库,主要是关系到工作流程,一种是你新加入一个项目,开始做一个已经有前辈在做的项目,这时候你要先去 git 服务器上拉取内容;还有一种情况是你做了一项工作一部分,要团队协作了,你将代码上传到 git 服务器,用 git 进行版本控制。
-
case1
新建项目,启用 git track 系统:
$ echo "# proj_name" >> README.md
$ git init
$ git add README.md
$ git commit -m "first commit"
$ git branch -M main
$ git remote add origin git_url
$ git push -u origin main
-
case2
拉取 github 上的项目:
# 先在git服务器上建立一个repository
$ git remote add origin project_url # 建立关联
$ git branch -M main
$ git push -u origin master # 推送到服务器,第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
团队协作
团队协作时的版本控制需要特别注意,因为你并不能更保证你现在的本地库有没有保持最新状态,可以按如下方式 push:
-
git push origin branch_name
-
如果上一步失败, git pull
-
如果 merged 有冲突,手动解决,本地提交 -
git push origin branch-name
分叉直线化
多分支的提交会使历史线有很多的分叉,非常难以观察,这时候可以用到git rebase
,关于这一点可以参考gitbook rebase[2]
.gitignore
在.gitignore 文件中可以指定忽略的文件或目录,这些文件或目录将不会被提交。当然也可以通过全局配置core.excludesfile
来设置过滤规则。推荐大家参考github-gitignore[3],里面收集了不少常用的配置规则。以下介绍几个相关命令:
-
git add -f [file | dir]
文件被忽略时,强行添加某文件 -
git check-ignore -v file
file 被莫名忽略,八成.ignore 文件写错了,check 一下,找到错误
.gitkeep
Git 默认情况下会忽略空的文件夹,如果想要追踪控制空文件夹,根据惯例会在空文件夹下放置.gitkeep
文件。Anyway,其实可以放置任何你想放置的文件。
注意事项
-
clone、pull、fetch 区别
-
clone 是本地没有 repository 时,将 完整的远程 repository 下载过来 -
pull 是本地有 repository 时,将远程的 repository [ 某分支或全部]下载过来,并且与本地代码 merge,从而保证代码是最新的。 -
fetch 是 pull 的一个子动作,pull 包括 fetch 和 merge 两个操作。
github 技巧
-
优雅的查看代码历史版本
假如你正在 github 上阅读一个文件:https://github.githistory.xyz/jefferyUstc/CaptchaIdentifier/blob/master/predict.py, 这时你想看这个文件的历史代码,你只需要把 URL 的域名 github.com
换成:githistory.xyz
或 github.githistory.xyz
或 github-history.netlify.com
-
删除 github commit 历史记录
如果你想删除 git 的 track 记录,可以使用以下的方式进行操作:
$ cd project_name # 进入项目
$ git checkout --orphan latest_branch # 创建"孤儿"分支并进入
$ git add -A;
$ git commit -am "first commit";
$ git branch -D master; # 删除master分支
$ git branch -m master; # 将孤儿分支重命名为master分支
$ git push -f origin master; # 强制push到远程库
写在篇后
Git 是目前最优秀的版本控制软件,想要熟练它,多做协作项目就好了,总有你意想不到的惊喜等着你!
参考
廖学锋的Git教程:https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/001375840038939c291467cc7c747b1810aab2fb8863508000
[2]gitbook rebase:http://gitbook.liuhui998.com/4_2.html
[3]github-gitignore:https://github.com/leisurelicht/gitignore
[4]DueCode:https://duecode.io/blog/code-quality-dashboard/