vlambda博客
学习文章列表

Git详解 | 生信人,你的代码质量合格么?

过年和 985 毕业程序员小哥哥交流 coding,小哥哥震惊于生信圈的代码质量尽如此令人咋舌。特此重新整理一篇我首发于 2018 年的 CSDN 推文

先放一个优秀的代码质量控制示例镇楼,我当年初学 scRNA-seq 数据分析原来是用 R Seurat,看了这种质量的开发,我选择相信并开始使用 python scanpy:

  优秀的代码质量控制的基础是 Git,Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。发明人就是大名鼎鼎的 Linus ,发明初衷为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。需要知道的是 Git 底层文件验证采用的是 SHA-1,(SHA-1)哈希算法可以被用来验证文件。哈希算法有如下特点:

  1. 不管输入数据的数据量有多大,输入同一个哈希算法,得到的加密结果长度固定
  2. 哈希算法确定,输入数据确定,输出数据能够保证不变
  3. 哈希算法确定,输入数据有变化,输出数据一定有变化,而且通常变化很大
  4. 哈希算法不可逆

如果你未接触过 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'
  • 如果你是误删该文件
    1. 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严格来说不是指向提交,而是指向mastermaster才是指向提交的,所以,HEAD指向的就是当前分支。

Git详解 | 生信人,你的代码质量合格么?

  当我们创建新的分支,例如dev时,Git 新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:

Git详解 | 生信人,你的代码质量合格么?

  从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:Git详解 | 生信人,你的代码质量合格么?

  假如我们在dev上的工作完成了,就可以把dev合并到master上。Git 怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:

Git详解 | 生信人,你的代码质量合格么?

  合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:Git详解 | 生信人,你的代码质量合格么?

$ 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详解 | 生信人,你的代码质量合格么?

  当合并分支遇到上图情况时,就需要手动解决冲突:

Git详解 | 生信人,你的代码质量合格么?

手动根据实际业务需求修改好之后,再次提交即可:

$ git add test.txt
$ git commit -m 'fix conflict' test.txt
Git详解 | 生信人,你的代码质量合格么?

标签管理

  我们经常会看到某某软件对应 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 区别

    1. clone 是本地没有 repository 时,将 完整的远程 repository 下载过来
    2. pull 是本地有 repository 时,将远程的 repository [ 某分支或全部]下载过来,并且与本地代码 merge,从而保证代码是最新的。
    3. 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 是目前最优秀的版本控制软件,想要熟练它,多做协作项目就好了,总有你意想不到的惊喜等着你!

参考

[1]

廖学锋的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/