Git Learning

0 初始化

每台计算机只需配置一次,升级时会保留配置信息。

Git 自带一个 git config 的工具帮助设置控制 Git 外观和行为的配置变量,这些变量存在于三个不同的位置:

  • /etc/gitconfig:包含系统上每一个用户的及他们仓库的通用配置。如果带有--system选项的 git config 时,会从此文件读写变量
  • ~/.gitconfig或~/.config/git/config:只针对当前用户。使用--global选项读写此文件
  • 当前仓库目录中的 config (.git/config):针对该仓库

每一级别覆盖上一级别

基本配置

1
2
3
4
5
6
7
8
9
10
11
12
# 配置用户名和邮箱
git config --global user.name "Sidney DING"
git config --global user.email "sidneyding183@gmail.com"
# 文本编辑器 根据喜好
git config --glonal core.editor vim
# 检查配置信息
git config --list
git config user.name
# 获取帮助
git help <verb>
git <verb> --help
man git-<verb>

1 基础

Git 使用基本流程

git1

1.1 获取 git 仓库

获取仓库的方法有两种

1.1.1 现有目录初始化

1
2
3
4
# 进入项目目录
cd <ProjectDir>
# 初始化 会创建一个.git子目录
git init

1.1.2 克隆现有仓库

1
2
3
4
5
6
# RepoUrl根据协议不同 地址类型也不近相同
git clone <RepoUrl>
# 会在当前目录下创建testRepo目录 并初始化出一个.git文件夹
git clone https://github.com/userName/testRepo
# 创建的目录变为MyRepo
git clone https://github.com/userName/testRepo MyRepo

1.2 忽略文件

1
2
3
$ cat .gitignore
*.[oa]
*~
  • 所有空行或以#开头的行都会被 git 忽略
  • 可以使用标准的 glob 模式匹配
  • 匹配模式可以以/开头防止递归
  • 匹配模式可以以/结尾指定目录
  • 要忽略指定模式以外的文件或目录,可以在模式前加!取反

glob 模式:shell 中简化的正则

  • *:匹配零个或任意多个字符
  • [abc]:匹配 a,b,c 之间任意一个字符
  • ?:匹配任意一个字符
  • [0-9]:表示匹配 0-9 之间人一个数字
  • a/**/z:匹配任意中间目录,例:a/b/z,/a/b/c/z...
  • more: https://github.com/github/gitignore

1.3 本地文件管理

git2

  • 工作区:未隐藏的部分
  • 版本库:.git目录
    • Index:.git/index,暂存区
    • HEAD:.git/HEAD,是一个指针,指向当前分支的版本
    • master:只是一个存放 40 位 sha-1 的文件而已
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 用暂存区的文件替换工作区的文件 撤销修改
git checkout -- <file>
git checkout .
# 将文件加到暂存区
git add <file>
# 可以查看文件的状态
git status
# 查看文件的详细状态
git diff
git diff --staged
# 撤销暂存区的修改
git reset HEAD <file>
# 删除暂存区的文件 工作区的文件不做修改
git rm --cached <file>
# 改名字 等价于三条命令 mv README.md README | git rm README.md | git add README
git mv README.md README
# 将暂存区的目录树写到版本库中HEAD指向的分支
git commit
# 修改commit信息
git commit --amend
# 撤销上一次提交 其实并不是撤销 其实保留了上一次提交 只是copy了上上次提交的状态
git revert -m 1 HEAD
git revert -m commitId
# 查看提交历史 由近及远
git log
git log -p
git log -3
git log --pretty=oneline
# 回退到前一个版本 1可以改为任意版本内的数字
git reset --hard HEAD~1
# 回到任意的提交版本 commitId不必写全 HEAD确实是一个指针
git reset --hard <commitId>
# 命令历史
git reflog

后悔药使用指南:

git checkout . 还没有 add

git reset

1.4 远程仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 显示所有的读写远程仓库
git remote -v
# 添加一个远程仓库
git remote add <shortname> <url>
# 从远程仓库获取数据
git fetch [remoteName]
# 抓取数据并尝试自动合并
git pull
# 推送到远程仓库
git push [remoteName] [branchName]
git push origin master
git push origin HEAD:refs/for/<branchName>
# 查看远程仓库信息
git remote show [remoteName]
# 重命名远程仓库
git remote rename <oldRemoteName> <newRemoteName>
git remote rm <remoteName>
# 清除本地远程分支 远程分支可能不存在了 但是本地还有
git remote prune origin

1.5 打标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 列出所有的标签
git tag
git tag -l 'v1.8*'
# 打附注标签
git tag -a v1.9 -m 'Version 1.9'
# 打轻量标签
git tag v1.9-lw
# 后期打标签
git tag -a v1.7 <commitId>
# 默认标签不会传送到远程仓库
git push origin [tagname]
git push origin --tags # 一次性传很多标签
# 删除tag
git tag -d <tagName>

1.5 别名

可以给命令起别名 简化操作

1
2
3
4
5
6
7
# 之后可以用 git co 代替 git checkout
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
# 使用git last 查看上一次提交
git config --global alias.last 'log -a HEAD'

1.7 other

1
2
# 修改author
git commit --amend --author="author name..."

2 分支

简单来说就是时间线,每条时间线上有很多时间点 (commit)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建分支 dev
git branch dev
# 切换到 dev 分支
git checkout dev
# 创建并切换到分支 dev
git checkout -b dev
# 查看当前分支
git branch
# 查看所有分支
git branch -a
# 合并制定分支到当前分支 fast-forward 代表只是向上游移动
git merge dev
# 删除分支
git branch -d dev

2.1 冲突

当合并分支不再是 fast-forward 模式时,Git 只是试图将各自修改的部分合并起来,但这种合并会有冲突

1
2
3
4
$ git merge feature1
Auto-merging testFile.txt
CONFLICT (content): Merge conflict in testFile.txt
Automatic merge failed; fix conflicts and then commit the result.

Git 提示我们需要手动解决冲突后再提交,通过git status也可以知道冲突的文件

在解决冲突时,可以使用meld fileName

1
2
3
4
5
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>>

<<<<<<<,=======,>>>>>>> 标记出不同分支的内容,需要手动去解决,比如手动修改后:

1
Creating a new branch is quick and simple.

这样就可以再进行提交了

通过git log --graph --pretty=oneline --abbrev-commit可以查看分支合并情况

中断合并git merge --abort

2.2 bug 分支

背景:当前正在dev进行工作,且进行到一半,还没法提交,预计还需要 1 天完成,此时来了一个 bug,这个 bug 必须在两小时内修复

Git 提供了stash功能,可以把当前工作现场存储起来,等以后恢复后继续工作

1
2
$ git stash
Saved working directory and index state WIP on dev: f52c633 and merge

此时使用git status可以看到工作区是干净的,可以放心创建分支来修复 bug。比如现在在 master 上创建分支修复 bug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
git checkout master
git checkout -b issue-101
# 进行一系列复杂操作 修复了bug 提交
git commit -m "fix bug 101"
# 切换回master分支
git checkout master
# 合并分支 --no-ff 表示禁用fast-forward模式
git merge --no-ff -m "merge bug fix 101" issue-101
# 删除bug分支
git branch -d issue-101
# 切回dev分支继续干活
git checkout dev
# 查看工作现场
git stash list
# 恢复工作现场同时删除工作现场
git stash pop

还可以使用git stash apply stash@{0}恢复,然后现场还在,需要通过git stash drop stash@{0}删除

注意:git stash 之前,一定要先用 git status 看一下,是否有 Untrackted files。如果有,需要先 add。因为 git stash 不能将 untrakcted file 压栈

没有 commit 切换分支是会报错的

工作区和暂存区是一个公开的平台,任何一个分支都会用到

2.3 Feature 分支

开发时,每添加一个新功能最好新建一个 feature 分支,因为这个功能的代码可能是实验性质的,不能把主分支搞乱。

新建 Feature 分支,开发,合并,删除 Feature 分支

开发到一半,觉得这个不行,可以使用git branch -D fearture进行强行删除

2.4 多人协作

首先,试图使用git push origin <branchName>推送自己的修改

如果推送失败,则因为远程分支比你新,需要先用git pull试图合并

若果有冲突,则解决冲突,并在本地提交

没有冲突或者解决冲突后,再用git push origin <branchName>推送就能成功

如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --setupstream-to=origin/branch-name branch-name建立本地分支与远程分支的关联

本地新建分支如果不推送到远程,对其他人就是不可见的,于是可以随便玩..

git pull = git fetch + git merge

2.5 变基 rebase

合并分支的一种方式

  • rebase 操作可以把本地未 push 的分叉提交历史整理成直线
  • 目的是为了我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比
  • 变基有风险!

我们经常会合并多笔 change 为一笔,就是我们常说的 squash 操作,git rebase -i HEAD ~3

3 Patch

  • 创建补丁

git diff > my.patch

git diff --no-prefix > my.patch

git diff master > my.patch

1
2
3
4
5
# 将最近的三次提交 变为patch文件 -s 表示加上当前用户签名
$ git format-patch -s HEAD~3..HEAD
0001-Fix-typo-help-to-help.patch
0002-Add-I18N-support.patch
0003-Translate-for-Chinese.patch
  • 通过邮件发送补丁
1
git send-email *.patch

git am应用来自邮箱的补丁,特别是被 mbox 格式化过的

  • 应用补丁
1
2
3
4
5
6
# 基于HEAD~3创建一个分支
git checkout -b branch1 HEAD~3
# 应用补丁
git apply *.patch
# 如果apply一个patch出错,报error: patch failed: drivers/usb/host/xhci.c:918之类的错误,那么是因为本地已经有这个patch了,所以不能够apply

  • cherry-pick
1
2
# 引入特性分支的某个提交
git cherry-pick commitId

4 Repo

repo 就是通过 Python 对 git 的一个封装,用于管理多个 Git 版本库

4.1 安装 repo

1
2
3
4
# 下载repo的引导脚本
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
# 将repo脚本设置为可执行
chmod a+x ~/bin/repo

4.2 初始化

1
2
3
4
5
6
7
8
mkdir work-dir
cd work-dir
# 初始化清单文件 就是一个.git文件
repo init -u <gitUrl>
repo init -u <gitUrl> -b <branchName>
# 表示优先从本地的mirror同步 提高下载速度
repo init -u <gitUrl> -b <branchName> --reference=<localServerMirror>

此时工作目录就会有个.repo文件夹,其中有个manifest.xml文件,这文件里面包含了 Android 各项目的信息

代码库本地镜像 repo init --mirror -u <gitUrl>

4.3 同步

1
2
3
4
5
6
7
8
9
10
# 同步代码到本地
repo sync -j6
# 其中
# -j 多任务 过多会起反作用
# -c 只下载当前代码
# -d 让工程回退到manifest制定的版本
# -f 如果某个工程同步失败 继续同步

# 也可以只同步感兴趣的代码
repo sync platform/build

4.4 常用命令

1
2
# 获取帮助
repo help <command>
  • repo init

  • repo sync

    相当于git clone或git update + git rebase origin/branch

  • repo start

    对git checkout -b的封装

    repo start <newBranchName> [--all | <project>...]

  • repo checkout

    对git checkout的封装

  • repo status

    1
    2
    project system/core/                            (*** NO BRANCH ***)
    -m fs_mgr/fs_mgr_fstab.cpp

每个小节的首行显示项目名称, 以及所在分支名称

之后显示该项目中文件变更状态。头两个字母显示变更状态, 后面显示文件名或者其 他变更信息

第一个字母表示暂存区的文件修改状态

  • - :没有改变
    • A :添加 (不在 HEAD 中, 在暂存区)
    • M :修改 (在 HEAD 中, 在暂存区, 内容不同)
    • D :删除 (在 HEAD 中, 不在暂存区)
    • R :重命名 (不在 HEAD 中, 在暂存区, 路径修改)
    • C :拷贝 (不在 HEAD 中, 在暂存区, 从其他文件拷贝)
    • T :文件状态改变 (在 HEAD 中, 在暂存区, 内容相同)
    • U :未合并, 需要冲突解决

第二个字母表示工作区文件的更改状态

  • - :新 / 未知 (不在暂存区, 在工作区)
    • m :修改 (在暂存区, 在工作区, 被修改)
    • d :删除 (在暂存区, 不在工作区)

两个表示状态的字母后面, 显示文件名信息。如果有文件重命名还会显示改变前后的 文件名以及文件的相似度

  • repo diff [<project>...]

    是对git diff命令的封装, 用以分别显示各个项目工作区下的文件差异

1562563963220

5 Gerrit

先加 ssh-key

Gerrit 下的 Git 服务器只允许用户向特殊的引用ref/for/<branchName>推送,这样才会创建一个 reviewId