Git基础教程

本文最后更新于:2023年7月14日 上午

git基础命令介绍

创建一个版本库

​ 版本库也就是仓库,英文名repository,我们经常使用网页端git直接new一个版本库,这里我们介绍一下如何使用命令创建一个仓库。

  • 首先创建一个文件夹作为仓库文件夹,当然这里也可以使用存有内容的文件夹并非必须是一个空的文件夹,只做样例展示。
  • 使用git init初始化仓库
1
2
3
mkdir learngit
cd learngit
git init

image-20221209203219599

​ 如图,此时一个空的版本库就做好了,这个时候我们也可以看到文件夹内自动创了.git目录。

ok,我们现在来上传一个文件试试,readme.txt存有新添加内容。

image-20221209204247400

  • 首先将文件存储到git暂存区,就是告诉git我要把这个文件添加到仓库。git add可以添加多个文件或目录,git add .会将本目录下所有文件存储至暂存区。此时git已经记录了一条操作记录即创建readme.txt文件,我们可以使用git status查看文件操作记录。
1
2
git add readme.txt
git status

image-20221209205137932

  • 文件进入暂存区后,操作已被记录,但是还没有合并到仓库上,这个时候使用git commit就将操作合并到仓库上,-m表示添加注释,一般会写明本次提交都做了啥。
1
git commit -m "create readme.txt"

image-20221209205406728

当然git commit可以使用空的注释,这需要额外的参数:

1
git commit --allow-empty-message -m ""

从上述两个步骤中,我们可以大致看出git基本流程:git add命令实际上就是一个保存记录的过程,git版本控制也是由git add实现的,当我们保存了很多条操作记录时,就可以使用git commit保存操作记录。

查看操作记录

现在我们对readme.txt进行修改保存,再次查看git状态。

1
git status

image-20221209210929874

可以看到git已经检测到文件修改了,但是还没有实际进行保存。此时我们可以使用git diff readme.txt查看修改了什么。

1
git diff readme.txt 

image-20221209222251961

现在我们可以看到我们对文件进行的所有修改,我们可以使用git addgit commit进行提交。

个人的思考

从上述两个基本操作,我们可以大概的将git看作是一个历史记录保存器,我们对文件的每一个字符的修改都会保存为一个字符历史记录,但是这样保存的话,字符历史记录就会太多太复杂了,因此git让我们使用git add显示的指定保存时间点(即历史记录分界线),git后台会将所有上次保存点至当前保存点的所有字符历史记录合并为某一行的操作,这就出现了+-,这样字符历史记录就被合并为某个版本信息了,当然对同一个文件的多次git add最终会被合并为一次修改,如果没有进行git commit,某一次的git add是无法恢复的;我们使用git commit将保存的历史记录写入仓库,整个版本库操作就完成了。

撤销修改

当我们发现最近编写的内容出现问题时,我们想要删除工作区修改,如何进行操作呢?

在没有使用git时,我们常用的办法就是一行行手工判断是否是修改的,并重新改回去,现在我们有了git那就方便多了,git checkout -- <filename>可以将指定的文件恢复至上一次保存的状态,无论是添加到暂存区的状态还是提交到仓库的状态都可以进行恢复,当然以最新的一次状态为准,总之,就是让这个文件回到最近一次git commitgit add时的状态。从此处我们可以看出无论是git add还是git commit最好确保代码完善后再进行add或commit操作,否则版本回退可能会无法直接运行。

那如果我们想要撤销暂存区里的内容呢?

那就应该使用git restore --staged <filename>git reset HEAD把文件从暂存区移动到工作区,保留文件最后一次修改的内容

版本回退

如果我们想进一步回退本地仓库的版本呢?我们首先对文件进行再一次的修改并提交至版本库

1
2
git add readme.txt
git commit-m "版本回退测试"

此时,如果我们想回退到之前的版本怎么办呢?git提供了git log查看所有保存历史记录。

1
git log

可以看到git清楚的记录了每一次commit修改的内容和注释,HEAD表示我们当前处在的位置。

image-20221210205410004

ok,坐好了,我们要准备回退了

1
2
3
4
5
6
7
8
git reset [<mode>] [<commit>]
<mode>:
--soft 版本恢复,回退版本版本修改内容会存在暂存中,相当于只回退了git commit,没有回退git add,源文件内容不会被修改
--mixed 版本恢复,回退版本不存在暂存区中,相当于回退了git commit和git add,源文件内容不会被修改
--hard 版本恢复,彻底恢复,于回退了git commit和git add,并且源文件内容会恢复之前的版本,此时针对现版本的修改都会丢失。
默认使用--mixed
<commit>:
需要恢复到的版本id或者HEAD^(当前版本的上一个版本)、HEAD^^(当前版本的上两个版本)、HEAD~100(当前版本的上100个版本)

以下三种方式回退的示例:

1
git reset --soft HEAD^

此时版本记录恢复到当前版本的上一版,回退版本修改记录存储在暂存区中,但是原文件内容不发生变化。

image-20221210212343275

回到回退前的版本,我们使用git relog查看所有回退命令,可以找到我们回退前的版本id。

1
2
git reflog
git reset --hard 34724b2

image-20221210213505828

1
git reset --mixed HEAD^

此时版本记录恢复到当前版本的上一版,回退版本修改内容不会存储在暂存区中,原文件内容不发生变化。

image-20221210212633862

重新回退至回退前的版本

1
2
git reset --hard 34724b2
git reset --hard HEAD^

此时版本记录恢复到当前版本的上一版,回退版本修改内容不会存储在暂存区中,原文件内容发生变化,针对新版本的所有修改都会丢失。

文件删除

当我们不需要某个文件时,我们会使用rm进行删除,但实际上我们有两种选择,一是确实要从版本库中删除某一文件,那就是使用git rm进行删除,并且git commit

1
2
3
4
5
rm <filename>
git add
git commit
git rm
git commit -m "文件删除"

image-20221210223042741

我们可以看到rm命令虽然在本地进行了删除,但是并未存入暂存区;git rm删除本地文件并存入暂存区准备commitgit rmrm+git add等价。

添加远程仓库

接下来,我们在github上创建一个远程仓库。

image-20221211202131238

这样远程就有一个仓库了但是并未与本地仓库相互关联,我们可以使用git登陆自己的账号,使用``将本地仓库与远程仓库相互关联起来。

1
git remote add origin https://github.com/oh-nice/learngit.git

添加之后远程库的名字就是origin,这是git的默认叫法,也可以改成别的。下一步,就可以把本地库的所有内容推送到远程库上。

1
git push -u origin master

把本地库的内容推送到远程,用git push命令,实际上是把当前分支master推送到远程。

由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。

image-20221211203930493

从现在开始,只要本地作了提交,就可以通过命令:

1
git push origin master

把本地master分支的最新修改推送至GitHub。

删除远程仓库

如果添加的时候远程仓库写错了,或者想删除远程仓库,可以先使用git remote -v查看远程仓库信息,再使用git remote rm origin删除远程仓库,值得注意的是,这里的删除是将本地仓库与远程仓库的关联删除,并不是真正的将远程仓库直接删除,如果想要直接删除远程仓库,需要到github网页进行删除。

创建并合并分支

在git中,每次提交都会默认提交到master分支中,master就是主分支,而HEAD就是指向的当前分支,只有当前分支是主分支时,HEAD才指向master

个人思考:分支可以理解为更加高层面的历史记录分界线,区别于提交操作,分支具有多个平行提交继承于同一个提交的特性,个人看来这是分支具有的最大特色。

首先,我们先创建dev分支,然后切换到dev分支:

1
git checkout -b dev

-b 表示创建并切换,等价于

1
2
git branch dev
git chechout dev

使用git branch可查看当前分支。

image-20221212215614738

接下来,我们在dev分支下添加一些提交,并合并到master分支中。

1
2
3
4
5
git add readme.txt
git commit -m 'dev分支修改'
# 切换回master分支合并dev分支
git chechout master
git merge dev

image-20221212220441665

默认情况下git会进行快进模式的合并,即直接把master的指针指向dev,合并就完成了,此时就可以放心的删除dev分支了;

1
git branch -d dev

但是当masterdev分支出现冲突时,快进模式无法解决冲突的,比如masterdev有不同的提交记录,此时我们就需要手动解决冲突合并。

我们创建dev分支,并提交一次修改记录;

1
2
3
git checkout -b dev
git add readme.txt
git commit -m "dev提交记录"

之后我们切换回master分支,再次提交一次不同修改内容的记录。

1
2
3
git checkout master
git add readme.txt
git commit -m "master提交记录"

此时我们将分支合并

1
2
git merge dev
git branch -d dev

image-20221212221951269

会发现合并分支报错,要求我们解决冲突问题,此时我们打开冲突文件,可以看到git使用<<<<<<<=======>>>>>>>标识了两次提交记录。

image-20221212222147126

此时我们可以选择保存某一个分支,或者手动输入保存的内容从而解决冲突。

使用git log --graph能够查看分支合并图

1
git log --graph --pretty=oneline --abbrev-commit

image-20221212222759189

现场保存

现在设想一下如下场景:

我正在编写一个新的功能,此时主分支程序出现了bug,需要紧急修复,而我的新功能还未完成,如果此时使用git addgit commit保存的话,分支上就会出现一些功能不完善的提交,这对整个分支来说可能会造成混乱,因此最好能够暂时保存当前工作现场,而不是提交不完善的代码,幸好,git提供给我们stash功能,通过此功能我们可以将当前的工作现场储藏起来,等以后恢复现场继续工作。

保存工作区现场

1
git stash

image-20221213121510288

此时我们已经成功保存工作区现场,使用git stash list可以查看所有保存现场。

1
git stash list

image-20221213121620615

这是我们对主分支进行bug修复

1
2
3
4
5
6
git checkout -b bugFix
git add readme.txt
git commit -m "fix bug"
git checkout master
git merge bugFix
git branch -d bugFix

image-20221213122143615

然后回到我们的开发分支,恢复现场

1
git stash apply stash@{0}

之后需要使用git stash drop进行删除保存现场,也可以使用git stash pop恢复并删除现场,但是此时的dev分支并未应用bugFix的提交,我们可以使用git cherry-pick <commit>将某一特定提交复制到当前分支,之后便会重新进行合并操作。

1
git cherry-pick bcb32

注意:现场储藏功能似乎仅支持储藏工作区文件,暂存区内容会被清空。

多人协作

查看远程库信息

1
git remote -v

推送分支

指定推送的分支

1
2
git push origin master
git push origin dev

拉取分支

在本地创建和远程对应的分支

1
git checkout -b branch-name origin/branch-name

建立本地分支与远程分支的关联

1
git branch --set-upstream branch-name origin/branch-name

标签管理

标签实际上就是某个commit提交记录,因为提交记录的id过长,所以采用标签方便记录。

git tag默认是打在最新提交的commit上的,如果想要打在其他commit,只需要添加commitid即可

1
2
git tag <tagname>
git tag -a <tagname> -m "message"

标签删除

1
2
3
4
# 删除本地标签
git tag -d <tagname>
# 删除远程标签
git push origin :refs/tags/v0.9

提交标签至远程仓库

1
2
3
4
# 提交指定标签
git push origin <tagname>
# 提交全部标签
git push origin --tags

Git高级操作

Git处理换行符问题

​ 当我们在Windows和Linux系统中同时进行git开发时,可能会出现大量文件修改但是,实际上没有内容更改的情况,原因就是Windows和Linux的换行符不统一问题。

Unix系统里,每行结尾只有“<换行>”,即”n”;Windows系统里面,每行结尾是“<换行><回车>”

解决方法

方法一:

在项目的 .gitattributes 文件中添加以下内容:

1
2
3
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf

方法二:

1
git config --global core.autocrlf true

强制全局转换换行符,签出时将换行符转换成CRLF,签入时转换回 LF。

创建新的空白分支

在使用git时,有时候我们可能需要创建一个新的分支,用于存放本项目的其他文件,这个时候我们就需要一个提交完全空白的分支,但是git只提供了在当前分支基础上创建新分支的命令,需要达到我们的目的需要一些手法操作。

  • 使用 git checkout--orphan参数创建新分支

    1
    git checkout --orphan uHook

    该命令会创建一个名为uHook的分支,并且该分支下有前一个分支下的所有文件,但是当前分支不会指向任何以前的提交。

  • 我们不想提交任何内容,所以我们需要把当前内容全部删除,用git命令

    1
    git rm -rf .
  • 使用git commit提交新分支

    1
    git commit -am "new branch for uHook"

    如果没有任何文件提交的话,分支是看不到的,可以创建一个新文件后再次提交则新创建的branch就会显示出来。

  • 使用branch来查看分支是否创建成功。

    1
    git branch -a

Github Actions

Github Actions简介

​ Github Actions是持续集成和持续交付(CI/CD)的自动化平台。简而言之,Github Actions可以自动化编译、运行、测试提交的代码,同时也可以自动化的部署。

Github Actions基本概念

  • workflow

​ workflow是一组预先配置完成的自动化进程,主要包括自动运行的jobs。Workflows默认存储在项目库根路径下的.github/workflows目录中,每一个 workflow对应一个具体的.yml 文件(或者 .yaml)。

  • Runner

​ Runner是工作流运服务器的代称。Github官方提供Ubuntu、Windows、macOS三种系统运行工作流。当然Github也提供自搭建服务器的方式运行工作流。

  • jobs:

​ 本工作流中的所有任务,一个job是工作流中运行在同一环境(Runner)下的一组步骤,同一job中的步骤是按顺序执行的,也就是说步骤之间可以存在依赖关系。

​ 但是job之间默认不存在先后关系,job之间是并行运算的,因此运行环境也不同步。如果如要设置job之间的依赖关系的话,也可以使用jobs.<job_id>.needs进行配置。

  • Actions

​ Actions是Github Actions平台设置的一些常用基本操作,例如拉去和推送等操纵。Actions也是可以自定义和发布的。

YAML基本语法

由于Github Actions是使用YAML配置的,因此创建Github Actions前先介绍一下YAML基本语法。

YAML的配置文件后缀为.yml,如main.yml

基本语法

  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tap,只允许1空格
  • 缩进的空格数不重要,只要相同的层级的元素左对齐即可
  • #表示注释
  • - 开头的行表示构成一个数组

Github Actions的使用

在项目库根路径下的.github/workflows目录中创建.yml文件,该文件就是一个工作流的配置文件:

  • name

​ 可选。Workflow的名字,默认为工作流的名字,在Github Actions页面显示该name。

1
name: my-first-actions

image-20230223103500766

  • run-name

​ 可选。某一Workflow运行时显示的名称, 在某一Github Actions标签下,每次运行时会显示。

1
run-name: Mr.Robot is learning Github Actions

image-20230223103705607

  • on

​ 工作流触发时机,可以选择在代码库进行操作时触发,比如pullpushpull_request等等,详情请见官方文档,这里展示workflow_dispatch的用法,workflow_dispatch可以手动触发workflows并进行相应的配置,可以使用inputs表明可选选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
on: 
workflow_dispatch:
inputs:
options-1:
description: "测试选项一"
required: true
default: "白天"
type: choice
options:
- 白天
- 黑夜
options-2:
description: "测试选项二"
required: false
type: choice
options:
- 太阳
- 月亮
environment:
description: "环境选项"
type: environment
required: false

image-20230223104241690

  • jobs

​ jobs表示要执行的一项或多项任务,一个job就是一个任务,每个job之间默认是并行的,如果在job间存在前后顺序的话需要设置jobs.<job_id>.needs

1
2
3
4
5
6
7
8
9
10
jobs:
check-bats-version:
runs-on: ubuntu-latest
steps:
...
hello-world:
runs-on: windows-latest
needs: check-bats-version
steps:
...

image-20230223105059227

  • jobs.<job_id>.runs-on

    配置jobs运行虚拟机环境,可选择Ubuntu、Windows、macOS三种系统。

    • ubuntu-latest,ubuntu-20.04
    • windows-latest,windows-2019
    • macos-latest,macos-11,macos-10.15

    官方提供的配置感觉还不错,详情见官方文档

    image-20230223105420265

    1
    2
    3
    4
    5
    6
    7
    jobs:
    check-bats-version:
    runs-on: ubuntu-latest
    ...
    hello-world:
    runs-on: windows-latest
    ..
  • jobs.<job_id>.env

​ 使用env可以给该任务或者步骤部署环境

1
2
3
4
5
6
7
8
9
10
11
jobs:
enc:
name: "zhangsan"
check-bats-version:
runs-on: ubuntu-latest
steps:
env:
age: 22
run: |
echo $name
echo $age
  • jobs.<job_id>.if

    控制job是否启用,if可以添加表达式以便自主控制是否执行本项任务,表达式可以使用Github上下文

  • jobs.<job_id>.steps

​ 某一job的所有步骤,同一job中的steps都是在同一环境下的执行的,每一个step是一条独立的命令或执行脚本,顺序执行具有依赖关系。

  • jobs.<job_id>.steps.uses:

​ 使用一些官方或者第三方的actions来执行,例如最经常使用的actions/checkout@v2,他们克隆我们repo的代码,之后工作流就可以使用repo的文件了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
hello-world:
runs-on: windows-latest
needs: check-bats-version
steps:
# 克隆仓库
- name: Check out repo's default branch
uses: actions/checkout@v3
- name: Enable Developer Command Prompt
uses: ilammy/[email protected]
- name: Compile and run some Code
shell: cmd
run: |
cl.exe HelloWorld.cpp
HelloWorld.exe
- name: Upload a Build Artifact
uses: actions/[email protected]
with:
name: "HelloWorld"
path: "./HelloWorld.exe"

image-20230224200521018

Github Actions的功能实在过于丰富,目前仅仅便于理解,详细使用教程请看官方文档

测试样例

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
name: my-first-actions
run-name: Mr.Robot is learning Github Actions
# 设置actions执行时机,此处为手动触发
on:
workflow_dispatch:
inputs:
options-1:
description: "测试选项一"
required: true
default: "白天"
type: choice
options:
- 白天
- 黑夜
options-2:
description: "测试选项二"
required: false
type: choice
options:
- 太阳
- 月亮
environment:
description: "环境选项"
type: environment
required: false

# action的job
jobs:
# 一个jobs任务,名称为build
check-bats-version:
# 运行基础环境,设置为ubuntu
runs-on: ubuntu-latest
if: github.workflow == 'my-first-actions'
env:
name: "zhangsan"
# jobs任务的步骤
steps:
- name: Print env
env:
age: 22
run: |
echo $name
echo $age
- name: Check out repo's default branch
uses: actions/checkout@v3
- name: Print inputs
run: |
echo "测试选项一: $OPTIONS1"
echo "测试选项二: $OPTIONS2"
echo "environment: $ENVIRONMENT"
env:
OPTIONS1: ${{inputs.options-1}}
OPTIONS2: ${{inputs.options-2}}
ENVIRONMENT: ${{inputs.environment}}
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '14'
- name: npm install
run: npm install -g bats
- name: npm install
run: bats -v > output.txt
- name: Upload a Build Artifact
uses: actions/[email protected]
with:
name: "测试文件名"
path: "./output.txt"
hello-world:
runs-on: windows-latest
needs: check-bats-version
steps:
# 克隆仓库
- name: Check out repo's default branch
uses: actions/checkout@v3
- name: Enable Developer Command Prompt
uses: ilammy/[email protected]
- name: Compile and run some Code
shell: cmd
run: |
cl.exe HelloWorld.cpp
HelloWorld.exe
- name: Upload a Build Artifact
uses: actions/[email protected]
with:
name: "HelloWorld"
path: "./HelloWorld.exe"


参考链接

YAML 入门教程

GitHubActions详解

Github高级用法

Github引用外部仓库

例如在unraider仓库中引用patchelf可以使用以下命令,该命令会克隆外部仓库同时自动生成 .gitmodules文件,git commit即可获得引用外部仓库形式

1
git submodule add https://github.com/NixOS/patchelf.git patchelf 

注意:Git clone 的时候需要加上--recursive,否则克隆下来的 TARGET_FOLDER 是空文件夹:

1
git clone --recursive git://github.com/[YOUR_USERNAME]/[YOUR_REPO_NAME].git

如果没加--recursive,克隆后只需要初始化子模块即可:

1
git submodule update --init --recursive

Git基础教程
https://genioco.github.io/2022/12/09/Guide/Git基础教程/
作者
BadWolf
发布于
2022年12月9日
许可协议