这只是理论课

git是世界上最先进的版本控制系统,没有之一
实际上,学会熟练的使用git要比会写代码更重要
这篇教程里介绍的内容,基本已经覆盖了所有内核开发中的git使用场景

某些git操作方式可能比较“土”,欢迎指出更高明的操作方式

基本概念

  • commit=提交 (可以是名词也可以是动词)

基本环境

  • 注册一个代码托管网站(比如github)
  • 弄台有linux的电脑(比如ubuntu)

安装git

以下以ubuntu为例

sudo apt-get install -y git

配置git用户名和邮箱

git config --global user.name "你的名字"

git config --global user.email “邮箱@邮箱.com”

几个注意点

  • 这里的名字和邮箱决定了你在创建commit时的作者信息,也就是决定了在git各种托管平台(比如github)上你创建的commit显示谁的用户名和头像
  • 这里设置的用户名与使用git push时填写的用户名并没有关系,它们不一定要是一样的,这里的用户名和邮箱仅用来描述直接使用git commit创建提交时的作者信息(当然,你也可以为git commit指定一个作者,详见此处。这里配置的信息仅仅只是为git commit指定一个默认作者)
  • 你可以把上面的global改成local来为所在仓库指定默认作者,global代表针对所有仓库的全局变量
  • 另外,github只认邮箱,也就是说user.email决定了在github的网页上要显示谁的用户名和头像。当然user.name还是可以在本地git log中看到的

常用命令的介绍

git clone

这基本上是最为人所熟知的命令了,其作用是把一个远程仓库下载到本地
有人可能会问,它和直接下载tar.gz文件有啥区别?其实区别还是非常大的。

  • git clone得到的是一个git仓库,而解压tar.gz得到的仅仅只是一个文件
  • git仓库意味着它的根目录下存在.git文件夹
  • 只有在git仓库中,你才能自如的使用各种git命令

那么,我们该如何使用这个命令呢?
用法:

  • 使用git clone <仓库地址>来克隆一个仓库的默认分支
  • 使用git clone <地址> -b <分支名/TAG名>来克隆指定分支/tag
  • 使用git clone <地址> <目录名>来克隆到一个指定目录(当然还可以和上面的-b结合·一下)
  • 使用git clone <地址> --depth=1克隆最小仓库

实例:

  • git clone https://github.com/xzr467706992/android_kernel_zuk_msm8996
  • git clone https://github.com/xzr467706992/android_kernel_zuk_msm8996 -b kernel.lnx.4.4.r39-rel
  • git clone https://github.com/xzr467706992/android_kernel_zuk_msm8996 kernel_source

git add

先来理解有一种东西叫做缓冲区
缓冲区可以理解为下一次commit的内容
这个命令是用来往缓冲区中增加文件的
用法

  • git add <文件名/目录>,来把一个文件加入缓冲区
  • 也可以使用git add *来把所有修改都加入缓冲区(隐藏文件除外)

git commit

这个命令的作用,是把缓冲区中的修改,作为一个新的commit(提交)
用法:

  • 直接git commit后,会自动打开文本编辑器(可能是nano也可能是vim),在编辑器中写上commit message(也就是这个提交的标题,介绍之类的),保存即可(nano是ctrl+x,然后按y。vim是esc+:wq)
  • 也可以使用git commit -m "我是提交的内容"直接创建commit message而不打开文本编辑器
  • 还可以使用git commit -a来省去git add的过程,直接把所有的修改创建为一个新提交(这里”所有的修改“是指git正在追踪的文件,也就是在git status中显示为”修改“的文件(而非下面的”新文件“)

git status

这个命令可以用来查看当前仓库的状态,包括被修改的文件以及未追踪的文件等

  • 直接git status即可

git diff

这个命令用来查看文件的修改内容

  • 直接git diff来查看当前已经被修改,但尚未加入缓冲区的文件的修改内容
  • 你也可以使用git diff <文件名>

git checkout

这个命令翻译过来应该叫做“检出”,没法一句话概括“检出”的含义,直接来看用法吧

  • 在你git add了一个文件后,你想反向add(从缓冲区删除),你可以使用git checkout <文件名>
  • 当你想要切换分支时,可以使用git checkout <分支名>来切换到另一个已经存在的分支
    (注意!这里的已存在分支不一定要是本地已存在的分支,可以是你的远程仓库中已存在的分支)
  • 当你想要创建新分支时,可以使用git checkout -b <新分支名>来切换到一个新分支
  • 当你想要把一个文件的内容,用它在另一个分支里的内容覆盖时,可以使用git checkout <分支名> <文件名>
  • 在git大冲突的解决中,也可以使用这个命令,参见此处

git log

这是最重要的命令之一,查看当前仓库的“提交历史记录”
使用方式

  • git log 打开日志界面,按Q退出
  • git log --oneline 打开简洁版历史界面,按Q退出
  • git log <本地分支名> 来查看某一分支的日志
  • git log <文件/目录名> 来查看某一文件/目录的日志

为什么说它重要呢?因为它能提供一个提交的sha1值,而这个值在下面几条命令中非常重要

$ git log
commit dcd1d7d69a782cd890829d0a821e192d591043a8
Author: LibXZR <[email protected]>
Date:   Mon Mar 23 19:43:29 2020 +0800

    mpm-of: Fix section mismatch in mpm_irq_domain_size
    
    * mpm_init_irq_domain want this but it can be freed up

dcd1d7d69a782cd890829d0a821e192d591043a8就是这条提交的sha1值

git reset

这个命令是用来改变头指针的位置的
至于什么是头指针
你可以理解为,把当前的仓库内容状态,与哪个提交进行比较,那个提交就是头指针
我这样解释你一定看不懂,那就上实例
先来看看我的仓库的状态

$ git log --oneline 
dcd1d7d69a78 mpm-of: Fix section mismatch in mpm_irq_domain_size
302b8865fa8e BACKPORT: crypto: arm64/aes-ce-cipher - move assembler code to .S file
71185ccf0f4c UPSTREAM: crypto: arm64/aes-ce-cipher: add non-SIMD generic fallback
94d6a9fcb24b UPSTREAM: crypto: arm64/aes-ce-cipher - match round key endianness with generic code
5731a305d948 BACKPORT: crypto: arm64/aes - add scalar implementation
26e9eeb2f08d Makefile: Pass optimization flags to assembler
f75c7ca6245b Makefile: Enable LLVM Polly optimizations if available
77f38364c3f0 Makefile: Optimize for kryo

( oneline下从sha1看起来可能短一点,但是不影响,这东西是一个一个字符匹配的,甚至再短一点也没关系)

然后我们执行git reset 77f38364c3f0来还原到Makefile: Optimize for kryo这一条
此处我们再看日志,就变成了

$ git log --oneline 
77f38364c3f0 Makefile: Optimize for kryo

没错,上面的东西都没了
但是,单纯的git reset <sha1>并不会改变仓库的文件内容
此时我们执行status,可以看到下述内容

$ git status 
位于分支 kernel.lnx.4.4.r39-rel
您的分支落后 'origin/kernel.lnx.4.4.r39-rel' 共 7 个提交,并且可以快进。
  (使用 "git pull" 来更新您的本地分支)

尚未暂存以备提交的变更:
  (使用 "git add/rm <文件>..." 更新要提交的内容)
  (使用 "git checkout -- <文件>..." 丢弃工作区的改动)

    修改:     Makefile
    修改:     arch/arm64/crypto/Kconfig
    修改:     arch/arm64/crypto/Makefile
    修改:     arch/arm64/crypto/aes-ce-ccm-core.S
    删除:     arch/arm64/crypto/aes-ce-cipher.c
    修改:     arch/arm64/crypto/aes-ce.S
    修改:     drivers/soc/qcom/mpm-of.c

未跟踪的文件:
  (使用 "git add <文件>..." 以包含要提交的内容)

    arch/arm64/crypto/aes-ce-core.S
    arch/arm64/crypto/aes-ce-glue.c
    arch/arm64/crypto/aes-cipher-core.S
    arch/arm64/crypto/aes-cipher-glue.c

修改尚未加入提交(使用 "git add" 和/或 "git commit -a")

也就是说,仓库里的文件还在原来的状态,只是它们所在的提交都消失了,因此那些文件都变成了尚未加入缓冲区的状态
此时我们再执行git reset --hard就能把上述修改和删除的文件还原到Makefile: Optimize for kryo时的状态,但是未追踪的文件依然不会改变,需要手动删一下

如果直接使用git reset --hard <sha1>会把仓库完整重置到指定状态,就是完全变回那个提交时仓库的状态(即使是当时未追踪的文件也会消失)

其实我不应该介绍git reset的,你需要掌握的,只有git reset --hard
因此,上面看不懂就算了,直接看下面的

  • 使用git reset --hard <sha1> 来把当前分支还原到指定提交时的状态(小心丢数据)

git revert

反转一个提交
至于什么是反转,就是把指定提交的内容倒着做一遍
比如上个提交加了文件
反转之后就是把那个文件删了
它和git reset --hard并不一样,那个是重置仓库的状态,这个是反转指定的内容并作为一个新的提交创建
不过 它们两个其实可以达到相同的目的 那就是干掉导致问题的提交
使用方法

  • git revert <sha1>来反转一个提交 (可能出现冲突,参见冲突的解决)

git cherry-pick

这个命令是用来为当前分支应用指定的提交的
使用方法

  • git cherry-pick <sha1> 把指定提交打到当前分支上
  • git cherry-pick <头>^..<尾> 把连续多个提交打到当前分支上(包含头尾)
  • 可能出现冲突 参见冲突的解决
  • 连续pick在冲突解决完后 执行git cherry-pick --continue即可继续pick

注意:

  • cherry-pick只能pick已经存在于本地的提交
  • 假如你在github上看上了一个提交
  • 请先使用下方的fetch命令获取那个仓库,然后再cherry-pick

git branch

这个就是查看已存在的分支的命令了

  • 你可以使用git branch来查看本地的所有分支
  • 你可以使用git branch -r查看远程服务器上的所有分支

当然你还可以用它来删除本地分支(创建分支时用checkout)

  • git branch -D <本地分支名> 使用前提时你当前不在这个被删除的分支上(先切换到别的分支)

git remote

这个命令和远程服务器相关

  • 使用git remote来查看本地已保存的全部远程服务器
  • 使用git remote get-url <远程服务器名称>来查看一个远程服务器的地址
  • 使用git remote add <服务器名称> <地址>来新增远程服务器
  • 使用git remote remove <名称>来删除远程服务器

实例

  • git remote add server https://github.com/xzr467706992/android_kernel_zuk_msm8996

git push

这个命令用来将本地修改推送到远程服务器
这里有点复杂,我已经尽量精简,只保留最常用的内容

  • 直接使用git push来推送修改到与当前分支绑定的远程分支
  • 使用git push --set-upstream <远程服务器名> <本地分支名>来创建上述绑定。注意!这个命令是将本地分支设为追踪同名远程分支,如果想追踪名字不同的远程分支,请使用git push --set-upstream <远程服务器名> <本地分支名>:<远程分支名>
  • 使用git push <远程服务器名> <本地分支名>:<远程分支名>来把指定的本地分支推送到指定的远程分支(是否绑定无所谓)
  • 可以加上-f参数来强制推送(上述所有情况都可以),比如git push -f。这适用于强制覆盖,就是在提交历史被修改/本地分支落后于远程分支等情况下,强制更新远程分支 (比如git reset --hard之后)
  • 删除服务器上的分支也是用这个命令,用法是git push --delete <远程服务器名> <远程分支名>

实例
git push --set-upstream origin kernel.lnx.4.4.r39-rel:test -f

git fetch

这个命令是在git仓库已经存在的情况下,想要在当前仓库中获取别的仓库/分支的内容时使用
如果git仓库不存在,请使用git clone
使用方式

  • git fetch <远程地址/远程服务器名称> 把指定的远程服务器的默认分支,下载到本地的一个叫做FETCH_HEAD的临时分支里(你可以这么理解)(一般临时使用远程仓库时才会这么用,比如merge/cherry-pick)
  • git fetch <远程地址/服务器名称> <远程分支名> 把指定远程服务器的指定分支下载到FETCH_HEAD中
  • git fetch <远程地址/服务器名称> <远程分支名>:<本地分支名> 把远程服务器的指定分支下载到本地的指定分支

使用实例

  • git fetch https://github.com/xzr467706992/android_kernel_zuk_msm8996 kernel.lnx.4.4.r39-rel:test

git merge

合并分支
说白了就是,两个分支里的所有提交,我都要!
常见的方式比如合并linux-stable、合并kernel/common或者是合并caf tag
使用方式

  • git merge <本地分支名/FETCH_HEAD>来合并一个分支(可能会有冲突,稍后再讲冲突的解决)

git pull

可以把这个命令理解为git fetch <绑定的远程服务器> <绑定的分支> && git merge FETCH_HEAD

git冲突的解决

先来造一个冲突

我们现在在a分支上
我们的a分支中有一个文件
这个文件是空白的
我们此时创建一个b分支 (因为是基于a分支分出去的 因此b分支中也会有那个空白的文件)
当你在a分支的文件中写下
779女装
在b分支的文件中的文件中写下
779快女装
全部commit掉
然后回到a分支
git merge b
我们可能就会得到这样一样结果

自动合并 <文件名>
冲突(内容):合并冲突于 <文件名>
自动合并失败,修正冲突然后提交修正的结果。

没错是冲突了
为什么会冲突?
上面我对merge的解释是
说白了就是,两个分支里的所有提交,我都要!
因此,现在git就迷惑了
779究竟是要女装还是快女装
如果全都要
那是要先女装
还是先快女装
如果只要一个
那到底要哪一个
于是它就让你人工解决

解决冲突

我们可以使用git status查看是哪些文件发生了冲突

此时我们打开那个冲突的文件,会看到这样一幅场景

<<<<<<< HEAD
779女装
=======
779快女装
>>>>>>> b

接下来打上注释

<<<<<<< HEAD //代表冲突开始的地方
779女装 //a分支的内容
======= //分隔符
779快女装 //b分支的内容(被合并的分支)
>>>>>>> b //收尾符

那么779要怎么女装就看你的解决方式了

解决好后
git add <文件名>git commit即可
注意:

  • 解决完毕后,git加入的那些分隔符不要保留,那些东西会导致愚蠢的编译错误
  • 解决冲突不一定是上下两个二选一,可能是两个都要,或者两个都不行要自己写一个,具体要结合代码上下文自己研究解决方式

有时候还会有其他的冲突方式
比如你合并的东西中有一个你不需要且在你的分支中已经被删除的驱动受到了修改
此时git status中的提示可能为“由我们删除”
这种情况下执行git rm <文件名>即可
当然如果你想保留它(虽然没卵用)
git add <文件>即可

遇到需要完全手动修改的巨大冲突,使用checkout还原文件然后再手动修改

关于authorship

定义

authorship说白了就是一个提交的原作者信息,这是一个在开源社区中非常被看重的东西。

如何保留

  • 使用git cherry-pick而不使用手动修改+git commit
  • 如果非要手动修改不可,请使用git commit --author= 参见此处

写不动了 就到这儿吧
有新东西再补充