Git 基本使用方法

1.1 Git 目录介绍

项目初始化

1
2
3
4
5
6
7
8
mkdir git_study # 随便创建一个目录
cd git_study # 切换到该目录下
git init # 初始化 之后会得到一个 .git 的目录

# 环境为 windows 10
# 使用 tree 查看 .git 目录结构,失败了:
tree .git
tree .git bash: tree: command not found

windows下的 git 没有 tree 命令,默认调用的是cmdtree命令,而cmd 提供的 tree 命令比较特殊,并不是常见的 .exe 结尾文件,而是 .com 结尾的文件。去下载tree-1.5.2.2-bin.zip,解压,然后将 tree-1.5.2.2-bin\bin\tree.exe 文件复制到git Bash 的安装目录下 Git\usr\bin 下,然后就能使用tree命令了:

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
$ tree .git
.git
|-- HEAD # 当前指向的分支
|-- config # 配置文件
|-- description
|-- hooks
| |-- applypatch-msg.sample
| |-- commit-msg.sample
| |-- fsmonitor-watchman.sample
| |-- post-update.sample
| |-- pre-applypatch.sample
| |-- pre-commit.sample
| |-- pre-merge-commit.sample
| |-- pre-push.sample
| |-- pre-rebase.sample
| |-- pre-receive.sample
| |-- prepare-commit-msg.sample
| |-- push-to-checkout.sample
| `-- update.sample
|-- info
| `-- exclude
|-- objects
| |-- info
| `-- pack
`-- refs
|-- heads
`-- tags

1.1.1 Git Config

不同级别的 Git 配置:system > global > local (按照级别高到低的顺序)。

每个级别的配置可能重复,但是低级别的配置会覆盖高级别的配置。

1.1.2 常见 Git 配置

  • 用户名配置
1
2
3
4
git config --global user.name "yourname"
git config --global user.email youremail@example.com
# 查看用户名 后面不加参数就行
git config --global user.name # 查看邮箱同理
  • Instead of 配置
1
2
# eg:  将 ssh 协议转换为 http 协议
git config --global url.git@github.com:.insteadOf https://github.com/
  • Git 命令别名配置
    1
    2
    # eg:
    git config --global alias.cin "commit --amend --no-edit"

1.2 Git Remote

  • 查看 Remote

    1
    2
    # 查看已添加的远程仓库
    git remote -V
  • 添加 & 删除 Remote 仓库

1
2
3
4
5
git remote add repoName repoAddr # 添加    repoName 远程仓库名  repoAddr 远程仓库地址
git remote remove repoName # 删除
# eg:
git remote add origin_ssh git@github.com:git/git.git # 添加 git@github.com:git/git.git(该地址为 ssh 协议,下一行的是 http 协议) ,命名为 origin_ssh ,以下同理
git remote add origin_http https://github.com/git/git.git

演示如下:

1
2
3
4
5
$ git remote add test_origin git@github.com:this/is/a_test.git  # 添加   该地址是随便写的...
$ git remote -v # 查看

test_origin git@github.com:this/is/a_test.git (fetch)
test_origin git@github.com:this/is/a_test.git (push)

添加后,这个地址将同时用于pull和push操作,除非你明确地配置了不同的URL用于pull和push,上面的fetch类似于pull,具体在后面会讲到

只能修改push 的 url

1
git remote set-url --push repoName url

演示如下:

1
$ git remote set-url --push test_origin https://github.com/another/repos/url.git # 设置 test_origin仓库的 push url 为 https://github.com/another/repos/url.git

结果:

1
2
3
$ git remote -v
test_origin git@github.com:this/is/a_test.git (fetch)
test_origin git@github.com:another/repos/url.git (push) # 修改后的地址

也可以通过cat .git/config 来查看远程仓库的配置,经过上述操作,然后可以看到以下三个远程仓库信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true

[remote "test_origin"]
url = git@github.com:this/is/a_test.git
fetch = +refs/heads/*:refs/remotes/test_origin/*
pushurl = https://github.com/another/repos/url.git

1.3 Git Add

通过git add命令将文件添加到缓冲区。

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
Ace@MacBook-Air-15 MINGW64 /d/git_study (main)
$ touch readme.md

$ vim readme.md # 写入内容: Hello, I am a readme file.


Ace@MacBook-Air-15 MINGW64 /d/git_study (main)
$ git status
On branch main

No commits yet

Untracked files: # 表示有未加入缓冲区的文件
(use "git add <file>..." to include in what will be committed)
readme.md

nothing added to commit but untracked files present (use "git add" to track)

Ace@MacBook-Air-15 MINGW64 /d/git_study (main)
$ git add . # 加入缓冲区

Ace@MacBook-Air-15 MINGW64 /d/git_study (main)
$ git status
On branch main

No commits yet

Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: readme.md

查看.git目录结构,发现objects增加了一些内容


  • before git add


  • after git add
    (增加的是文件的id)

通过文件的id查看文件内容:

1
2
$ git cat-file -p 477d2427d8b7d6e7a02f705da6479569ed7eb825 # id
Hello, I am a readme file.

1.4 Git Commit

通过git commit命令提交。

1
git commit -m "description" # -m 用来加注释 

提交上面创建的 readme.md,之后会发现object目录下多了两个文件(45和a2开头的id):

after committed readme

下面会讲到

1.5 object

object(对象)是Git的基本数据单元,用于存储和管理项目的内容。

Git中有四种主要类型的对象,它们分别是:

  • Blob 存储文件的内容
  • Tree 存储同一次提交的所有文件的目录信息,每次不同的提交都会创建不同的tree对象。
  • Commit 存储提交信息,一个 Commit 可以对应唯一版本的代码,即每次提交都会有一个commit。
  • Tag (后面会再次提到)

关联如下图:

objects

  1. 通过 Commit 寻找到 Tree 信息,每个 Commit 都会存储对应的 Tree ID.
  2. 通过 Tree 存储的信息,获取到对应的目录树信息。
  3. 从 Tree 中获得 Blob 的 ID, 通过 Blob ID 获取对应的文件内容。

trace from commit to tree and blob
最后一个是commit,通过commit中的tree的ID可以查看tree的内容,tree中有blob的ID,可以用来查看源文件的内容。

1.6 refs

1
git chechout -b branchName # 创建新分支并自动切换到新分支 branchName 表示分支名
  • refs 文件存储的內容

创建myTestBranch分支之后,refs目录下多出了一个文件,且它们的内容都是一样的,即refs 的内容就是对应的 Commit ID,因此把 refs 当做指针,指向对应的 Commit 来表示当前 refs 对应的版本。

refs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Ace@MacBook-Air-15 MINGW64 /d/git_study (main)
$ cat .git/refs/heads/main
087260f549a0bb3c0f78544dd8583f52c7c1988f

Ace@MacBook-Air-15 MINGW64 /d/git_study (main)
$ git checkout -b myTestBranch
fatal: a branch named 'myTestBranch' already exists

Ace@MacBook-Air-15 MINGW64 /d/git_study (main)
$ git switch myTestBranch # 切换分支
Switched to branch 'myTestBranch'

Ace@MacBook-Air-15 MINGW64 /d/git_study (myTestBranch)
$ cat .git/refs/heads/myTestBranch
8b5e69c2b7fe66863d9c3e4bbcbd081218ca9b53

不同种类的 refs

  • refs/heads前缀表示的是分支,除此之外还有其他种类的 refs, 比如 refs/tags 前缀表示的是标签。

  • head git checkout -b BranchName # 创建一个新分支,其中 -b 表示切换分支,分支一般用于开发阶段,是可以不断添加 Commit 进行迭代的。

  • tag 标签一般表示的是一个稳定版本,指向的 Commit 一般不会变更,可通过 git tag 命令生成 tag.

创建 tag v0.0.1

image.png

1.7 追溯历史版本

  • 获取当前版本代码:通过 refs 指向的 Commit 可以获取唯一的代码版本。

  • 获取历史版本代码:Commit 里面会存有 parent commit 字段,通过 commit 的串联获取历史版本代码。

先用git log查看当前版本的 commit ID,然后查看commit,找到 parent 字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Ace@MacBook-Air-15 MINGW64 /d/git_study (myTestBranch)
$ git log
commit 8b5e69c2b7fe66863d9c3e4bbcbd081218ca9b53 (HEAD -> myTestBranch)
Date: Wed Aug 30 01:24:41 2023 +0800

now i updated test.txt

commit a20a750ede4652ba5979fedd3994f9ddf090a9a7
Author: Kjasn < >
Date: Wed Aug 30 01:02:39 2023 +0800

i add a readme

Ace@MacBook-Air-15 MINGW64 /d/git_study (myTestBranch)
$ git cat-file -p 8b5e69c2b7fe66863d9c3e4bbcbd081218ca9b53 # 通过commit id 查看
tree b362f2c28c76ee6619c05216c50497547a1d64ae
parent a20a750ede4652ba5979fedd3994f9ddf090a9a7
author Kjasn < > 1693329881 +0800
committer Kjasn < > 1693332399 +0800

now i updated test.txt

1.8 修改历史版本

  1. git commit –amend
    通过这个命令可以修改最近的一次 commit 信息,修改之后 commit id 会变,tree ID 和 parent 字段不变。

原来的commit对象仍然存在,但是没有 refs 指针指向它,变成了 悬空的commit

演示:

修改最近一个commit,commit ID 更新了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Ace@MacBook-Air-15 MINGW64 /d/git_study (myTestBranch)
$ git commit --amend
[myTestBranch 191970a7e6] now i updated test.txt this is another change to test.txt~ # 对test.txt 修改后的内容
Date: Wed Aug 30 01:24:41 2023 +0800
2 files changed, 2 insertions(+)
create mode 160000 git_test
create mode 100644 test1.txt

Ace@MacBook-Air-15 MINGW64 /d/git_study (myTestBranch)
$ git log
commit 191970a7e6cf9a463ebc6ca32b522ced441bc082 (HEAD -> myTestBranch)
Author: Kjasn < >
Date: Wed Aug 30 01:24:41 2023 +0800 # 时间仍然是第一次提交的时间,修改后不会更新

now i updated test.txt
this is another change to test.txt~

commit a20a750ede4652ba5979fedd3994f9ddf090a9a7
Author: Kjasn < >
Date: Wed Aug 30 01:02:39 2023 +0800

i add a readme

与修改之前的commit对比,tree和parent没变:

前:

1
2
3
4
5
6
7
8
Ace@MacBook-Air-15 MINGW64 /d/git_study (myTestBranch)
$ git cat-file -p 087260f549a0bb3c0f78544dd8583f52c7c1988f
tree b362f2c28c76ee6619c05216c50497547a1d64ae
parent a20a750ede4652ba5979fedd3994f9ddf090a9a7
author Kjasn < > 1693329881 +0800
committer Kjasn < > 1693329881 +0800

then i commit test.txt

后:

1
2
3
4
5
6
7
8
9
Ace@MacBook-Air-15 MINGW64 /d/git_study (myTestBranch)
$ git cat-file -p 191970a7e6cf9a463ebc6ca32b522ced441bc082
tree 96153ce3c7dea5dc537babc5c17102565b7066dc
parent a20a750ede4652ba5979fedd3994f9ddf090a9a7
author Kjasn < > 1693329881 +0800
committer Kjasn < > 1696726013 +0800

now i updated test.txt
this is another change to test.txt~
  1. git rebase
    通过 git rebase -i HEAD-3 可以实现对最近三个 commit 的修改:
    1. 合并 commit
    2. 修改具体的 commit message
    3. 删除某个 commit
  2. git filter –branch
    该命令可以指定删除所有提交中的某个文件或者全局修改邮箱地址等操作

1.9 完整的 Git 视图

完整的 Git 视图

1.10 Git Clone & Pull & Fetch

Clone
拉取完整的仓库到本地目录,可以指定分支,深度。
Fetch
将远端某些分支最新代码拉取到本地,不会执行 merge 操作,
会修改 refs/remote 内的分支信息,如果需要和本地代码合并需要手动操作。
Pull
拉取远端某分支,并和本地代码进行合并,操作等同于 git fetch + git merge,
也可以通过 git pull –rebase 完成 git fetch + git rebase 操作。
可能存在冲突,需要解决冲突。

1.11 Git Push

Push 是将本地代码同步至远端的方式。

常用命令:

1
git push repoName branchName   # 仓库名 分支名

冲突问题:
1.如果本地的 commit 记录和远端的 commit 历史不一致,则会产生冲突,比如 git commit –amend or git
rebase 都有可能导致这个问题。
2. 如果该分支就自己一个人使用,或者团队内确认过可以修改历史则可以通过 git push origin master -f 来完成强制推送,一般不推荐主干分支进行该操作,正常都应该解决冲突后再进行推送。

推送规则限制:
可以通过保护分支,来配置一些保护规则,防止误操作,或者一些不合规的操作出现,导致代码丢失。


2.1 分支管理工作流

分支管理工作流有:Git Flow ,Github Flow 和 Gitlab Flow (不过多说明)

以下以Github 工作流作为演示。


  • 在GitHub新建一个仓库用于学习测试,然后复制仓库地址(http,ssh)都可以。
1
2
3
$ git clone https://github.com/kjasn/git_test.git # 我用的是 http 协议的仓库地址
Cloning into 'git_test'...
warning: You appear to have cloned an empty repository. # 因为新建的是一个空仓库,什么也没加
  • 克隆的仓库存放在当前目录下的 git_test 目录下,切换到仓库目录下,随便创建一个文件用来演示提交。
    eg:
1
2
3
4
Ace@MacBook-Air-15 MINGW64 /d/git_study/git_test (test)
$ vim readme.md # 创建并写入内容
$ cat readme.md
Hello, Kjasn! I am rd.
  • add,commit并push
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Ace@MacBook-Air-15 MINGW64 /d/git_study/git_test (test)
$ git add .
warning: in the working copy of 'readme.md', LF will be replaced by CRLF the next time Git touches it

Ace@MacBook-Air-15 MINGW64 /d/git_study/git_test (test)
$ git commit -m "add readmeme~"
[test 9471dd3] add readmeme~
2 files changed, 1 insertion(+), 8 deletions(-)
delete mode 100644 readme # 删除了原先的readme...
create mode 100644 readme.md

Ace@MacBook-Air-15 MINGW64 /d/git_study/git_test (test)
$ git push origin test
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Writing objects: 100% (3/3), 261 bytes | 261.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:kjasn/git_test.git
2adb4c4..9471dd3 test -> test

然后在Github仓库页面刷新后可以看到新添加的readme文件。

新建一个Feature分支:

push之后,自动创建了一个向main分支合并的pull request

创建feature分支

在Feature分支更新readme文件然后push(图中链接)。访问链接可以查看提交的代码变更,是否与main分支有冲突,解决冲突后可以选择是否与main分支合并

选择是否合并

设置main分支保护,以防错误的提交代码影响到main分支:

设置main分支保护

演示更新提交,然后直接push,失败:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git push origin main
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 12 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 298 bytes | 298.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote: error: GH006: Protected branch update failed for refs/heads/main.
remote: error: Changes must be made through a pull request.
To github.com:kjasn/git_test.git
! [remote rejected] main -> main (protected branch hook declined) # 不允许直接push
error: failed to push some refs to 'github.com:kjasn/git_test.git'

2.2 代码合并

  • Fast-Forward

不会产生一个 merge 节点,合并后保持一个线性历史,如果 target 分支有了更新,则需要通过 rebase 操作更新source branch 后才可以合入。图示:

Fast-Forward

  • Three-Way Merge

三方合并,会产生一个新的 merge 节点。图示:

Three-Way Merge

  • 演示:

Fast-Forward

  • 不会生成merge结点
    fast forward merge

Three-Way Merge

  • 生成一个merge结点

three way merge

2.3 选择合适的工作流

  • 选择原则

没有最好的,只有最合适的

  • 针对小型团队合作,推荐使用 Github 工作流即可
  1. 尽量保证少量多次,最好不要一次性提交上千行代码
  2. 提交 Pull Request 后最少需要保证有 CR 后再合入
  3. 主干分支尽量保持整洁,使用 fast-forward 合入方式,合入前进行 rebase
  • 大型团队合作,根据自己的需要指定不同的工作流,不需要局限在某种流程中。

写在最后

参考:
git 入门教程之 git bash 竟然不支持 tree 命令 - 雪之梦技术驿站 - 博客园 (cnblogs.com)


Git 基本使用方法
https://kjasn.github.io/2023/08/30/Git-基本使用方法/
作者
Kjasn
发布于
2023年8月30日
许可协议