记录干杯

​( ゜- ゜)つロ 干杯 ~
开往

学习 Git

Git 基本用法

根据 官方文档 的学习路线进行总结。

git help

获取详细的帮助。

$ git help stash
$ git merge --help
$ git merge -h    # 获取简明帮助

git init --bare

初始化一个 Git 仓库,两者的区别是:

$ cd /mnt/d/git/init-test
$ git init
Initialized empty Git repository in /mnt/d/git/init-test/.git/

git status

查看当前仓库的状态。

$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   modified.txt
        new file:   tobecommited.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   modified.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        untracked.txt

-s--short 参数可以简化输出:

$ git status -s
A  777.txt
AM 888.txt
M  modified.txt
?? untracked.txt

git diff --staged

比较文件差异,两者区别是:

$ git diff
diff --git a/modified.txt b/modified.txt
index e5aa983..151e604 100644
--- a/modified.txt
+++ b/modified.txt
@@ -1 +1,2 @@
-99912
\ No newline at end of file
+99912^M
+hgfdg
\ No newline at end of file

git add

开始跟踪一个文件或把文件添加到暂存区。

$ git add example.txt
$ git add .            # . 指当前目录
$ git add code/        # 跟踪指定目录下所有文件

使用场景如下:

git commit --amdend

直接输入命令 git commit 会打开文本编辑器编写 Commit 信息。不过也可以使用 git commit -m "message" 这种写法。

$ git commit -m "git test 5"
[master e2706a2]  On branch master  Your branch is ahead of 'origin/master' by 1 commit.    (use "git push" to publish
your local commits)
 7 files changed, 3 insertions(+)
 create mode 100644 777.txt
 create mode 100644 888.txt
 create mode 100644 "untracked - \345\211\257\346\234\254 (2).txt"
 create mode 100644 "untracked - \345\211\257\346\234\254.txt"
 create mode 100644 untracked.txt

git commit -a 对于已跟踪过的文件,可以「省略」git add 步骤,相当于同时执行 addcommit

$ git status -s
 M modified.txt
$ git commit -a -m "commit -a test"
[master 4d2a641] commit -a test
 1 file changed, 2 insertions(+), 1 deletion(-)

如果你在执行 commit 之后又改动了一些文件,但不想让这次改动污染提交历史,那么可以用 git commit --amend 命令把当前暂存区中的文件提交,并覆盖上一次提交。

$ git commit -m "one commit"
[master deef0a2] one commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename 222.md => 233.md (100%)
$ vim 000.txt
$ git add 000.txt
$ git log -2
* deef0a2 - (HEAD -> master) one commit (60 seconds ago) <liangfengning>
* 6b2ad04 - git rm test 2 (6 days ago) <liangfengning>
$ git commit --amend
[master 08a4f90] one commit
 Date: Fri Apr 3 18:31:23 2020 +0800
 2 files changed, 1 insertion(+)
 create mode 100644 000.txt
 rename 222.md => 233.md (100%)
$ git log -2    # 可以看到合并后只有一条记录
* 08a4f90 - (HEAD -> master) one commit (22 seconds ago) <liangfengning>
* 6b2ad04 - git rm test 2 (6 days ago) <liangfengning>

git rm

从已跟踪的文件中移除某个文件,并删除文件。

$ git rm readme.txt
rm 'readme.txt'
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    readme.txt
$ cat readme.txt
cat: readme.txt: No such file or directory

文件从 Git 和本地都删除了,如果只删除本地文件则不会把更改提交到暂存区:

$ rm modified.txt
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    readme.txt

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    modified.txt

如果要 git rm 已经修改但未加入暂存区(add)加入暂存区但未提交(commit) 的文件,则会出错,需要使用 git rm -f

$ vim commit.txt
$ git rm commit.txt
error: the following file has local modifications:
    commit.txt
(use --cached to keep the file, or -f to force removal)
$ git add commit.txt
$ git rm commit.txt
error: the following file has changes staged in the index:
    commit.txt
(use --cached to keep the file, or -f to force removal)
$ git rm -f commit.txt
rm 'commit.txt'

如果要把一个文件从 Git 中移除(不被跟踪),但不删除源文件,可以用 git rm --cache 命令:

$ git rm --cache 777.txt
rm '777.txt'
$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    777.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        777.txt

常用于忘记添加 .gitignore 的情况。

git mv

移动文件,也可以是重命名。

$ git mv 222.md 233.md
$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        renamed:    222.md -> 233.md

相当于以下命令:

$ mv 222.md 233.md
$ git rm 222.md
$ git add 233.md

git log

查看 Git 的历史记录,最新的在最上面,中间的是提交说明。

$ git log
commit 6b2ad042395e6bd74bf4187677955383c79b017d (HEAD -> master)
Author: liangfengning <[email protected]m>
Date:   Sat Mar 28 19:39:15 2020 +0800

    git rm test 2

...

git log -p 可以查看每次提交的详细改动,git log -2 只展示最近两条提交:

$ git log -p -2
commit 6b2ad042395e6bd74bf4187677955383c79b017d (HEAD -> master)
Author: liangfengning <[email protected]m>
Date:   Sat Mar 28 19:39:15 2020 +0800

    git rm test 2

diff --git a/222.md b/222.md
new file mode 100644
index 0000000..ada8034
--- /dev/null
+++ b/222.md
@@ -0,0 +1 @@
+# Test for Git
...

git log --stat 展示相对简略的信息:

$ git log --stat
commit 6b2ad042395e6bd74bf4187677955383c79b017d (HEAD -> master)
Author: liangfengning <[email protected]m>
Date:   Sat Mar 28 19:39:15 2020 +0800

    git rm test 2

 222.md       | 1 +
 modified.txt | 2 --
 2 files changed, 1 insertion(+), 2 deletions(-)
...

git log --pretty=oneline 把每次提交放在一行里,--graph 类图形化展示:

$ git log --pretty=oneline --graph
6b2ad042395e6bd74bf4187677955383c79b017d (HEAD -> master) git rm test 2
e797abf049a3c355c93ec3645d11c3f7cbb04cc6 git rm test
4d2a64150f5c2718566b605fce49fafb4661f123 commit -a test
...
*   bc8811421a40e134e8e01219decedadbdc6ca4e0 (tag: v1.0) merge with no-ff
|\
| * e1de0db0c9552eff4877c1d115b82f34106417d7 add merge
|/
*   4a0cec3aaba4d993f0912325214126418bf6b622 conflict fixed
|\
| * fc2dae62967912c581ed617a4ed3ce6aadeac232 AND simple
* | 075ee45a491458df9e9d70bce7220e2944c3ac83 & simple
|/
* d049c3bb6c386a7c24e4a1f6129532a33713c258 branch test
...

更多用法见:Git - 查看提交历史

git reset

git reset HEAD <filename> 用于把暂存区的文件撤回未 add 状态。常见应用是把不应该 git add 的文件添加到暂存区,要撤回这个操作。

$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   1.txt
$git reset HEAD 1.txt
Unstaged changes after reset:
M       1.txt
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   1.txt

no changes added to commit (use "git add" and/or "git commit -a")

git checkout 撤销操作

git checkout --<filename> 把一个文件的改动撤回到上一次提交的状态。注意:这个操作无法撤销,源文件会被覆盖。

新版本的 git restore 可以替代这个命令。

# 使用 checkout
$ git status
On branch master

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   2.txt

no changes added to commit (use "git add" and/or "git commit -a")
$ git checkout -- 2.txt
$ git status
On branch master

nothing to commit, working tree clean
# 使用 restore
$ git status
On branch master

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   1.txt

no changes added to commit (use "git add" and/or "git commit -a")
$ git restore 1.txt
 git status
On branch master

nothing to commit, working tree clean

git tag

查看标签,使用 git tag -l "通配符" 可以进行筛选。

$ git  tag
v1.0
# 下面的例子来自官方文档,有改动
$ git tag -l "v1.8.5*"
v1.8.5
v1.8.5-rc0
v1.8.5.1

Git 支持两种标签:轻量标签(lightweight)与附注标签(annotated)。

轻量标签很像一个不会改变的分支——它只是某个特定提交的引用。

而附注标签是存储在 Git 数据库中的一个完整对象, 它们是可以被校验的,其中包含打标签者的名字、电子邮件地址、日期时间, 此外还有一个标签信息,并且可以使用 GNU Privacy Guard (GPG)签名并验证。 通常会建议创建附注标签,这样你可以拥有以上所有信息。但是如果你只是想用一个临时的标签, 或者因为某些原因不想要保存这些信息,那么也可以用轻量标签。

引用来源:Git - 打标签

使用 git tag -a 创建一个附注标签。

$ git tag -a v1.1 -m "tag test"
$ git show v1.1
tag v1.1
Tagger: liangfengning <[email protected]m>
Date:   Tue May 5 18:26:10 2020 +0800

tag test

commit 354b9e0c162c50cfbfc837f312c8cc75c7632fae (HEAD -> master, tag: v1.1, origin/master)
Merge: 380e12e fc5e06f
Author: liangfengning <[email protected]m>
Date:   Tue May 5 17:50:10 2020 +0800

    Merge branch 'test'

直接创建轻量标签。

$ git tag v1.1.1
$ git show v1.1.1
commit 354b9e0c162c50cfbfc837f312c8cc75c7632fae (HEAD -> master, tag: v1.1.1, tag: v1.1, origin/master)
Merge: 380e12e fc5e06f
Author: liangfengning <[email protected]m>
Date:   Tue May 5 17:50:10 2020 +0800

    Merge branch 'test'

也可以在某次提交上打标签。

$ git tag v0.1.1 d65b938
$ git show v0.1.1
commit d65b938fda4e87759d9ffe40e816e9582ca1ba9a (tag: v0.1.1)
Author: liangfengning <[email protected]m>
Date:   Sun Apr 19 00:48:02 2020 +0800
...

标签需要主动推送到远程服务器,不能使用全局的 push。

# 这样不行
$ git push origin master
Everything up-to-date

# 需要单独 push
$ git push origin v1.1
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 158 bytes | 158.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0)
remote: . Processing 1 references
remote: Processed 1 references in total
To https://dev.lifeni.life/gitea/liangfengning/git-test.git
 * [new tag]         v1.1 -> v1.1

使用 git push origin --tags 可以把所有标签都 push 到远程服务器。

$ git push origin --tags
Total 0 (delta 0), reused 0 (delta 0)
remote: .. Processing 2 references
remote: Processed 2 references in total
To https://dev.lifeni.life/gitea/liangfengning/git-test.git
 * [new tag]         v0.1.1 -> v0.1.1
 * [new tag]         v1.1.1 -> v1.1.1

git tag -dgit push origin --delete 可以分别删除本地和远程的 tag。

$ git tag -d v1.0
Deleted tag 'v1.0' (was bc88114)
$ git push origin --delete v0.1.1
Username for 'https://dev.lifeni.life': liangfengning
Password for 'https://[email protected]':
remote: . Processing 1 references
remote: Processed 1 references in total
To https://dev.lifeni.life/gitea/liangfengning/git-test.git
 - [deleted]         v0.1.1

git show

Shows one or more objects (blobs, trees, tags and commits).

$ git show v1.0
commit bc8811421a40e134e8e01219decedadbdc6ca4e0 (tag: v1.0)
Merge: 4a0cec3 e1de0db
Author: liangfengning <[email protected]m>
Date:   Sat Mar 21 18:57:49 2020 +0800

    merge with no-ff

$ git show 354b9e0
commit 354b9e0c162c50cfbfc837f312c8cc75c7632fae (origin/master, master)
Merge: 380e12e fc5e06f
Author: liangfengning <[email protected]m>
Date:   Tue May 5 17:50:10 2020 +0800

    Merge branch 'test'

Git 分支管理

推荐看官方文档的解释,因为有配图:Git - 分支简介

git branch

查看、创建分支。

$ git branch test
$ git branch dev
$ git log --oneline --decorate
d65b938 (HEAD -> master, origin/master, test, dev) msg
$ git branch
  dev
* master
  test

git branch -d 删除一个分支。

$ git branch -d temp
Deleted branch temp (was d65b938).

git branch -v 查看每个分支的最后一次提交。

$ git branch -v
  dev    9ea5bd7 merge test 1
  master 354b9e0 Merge branch 'test'
* test   fc5e06f fix conflicts

git branch --mergedgit branch --no-merged 分别筛选已经合并和尚未合并到当前分支的分支。

$ git branch --merged
  dev
* test
$ git branch --no-merged
  master

没有合并的分支无法直接删除:

$ git branch -d master
error: The branch 'master' is not fully merged.
If you are sure you want to delete it, run 'git branch -D master'.

git checkout 分支相关

这里的 git checkout 用于切换到一个已存在的分支。可以用 git switch 替代。

$ git checkout dev
Switched to branch 'dev'
$ git switch master
Switched to branch 'master'

git checkout -b 可以创建一个分支并切换过去。

$ git checkout -b temp
Switched to a new branch 'temp'

git merge --no-ff

合并分支。

$ git branch
  dev
* master
  test
$ git merge dev
Updating d65b938..9ea5bd7
Fast-forward
 1.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

如果遇到了冲突:

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

合并后的 log 是这样的:

*   fc5e06f - (HEAD -> test) fix conflicts (2 minutes ago) <liangfengning>
|\
| * 9ea5bd7 - (dev) merge test 1 (7 minutes ago) <liangfengning>
* | 7756594 - merge test 2 (6 minutes ago) <liangfengning>
|/

fast-forward:当要进行合并的两个分支是直接后继的关系的时候,就会采用这个方式,只是移动指针。详情可以查看:git merge —no-ff 是什么意思 - SegmentFault 思否

git rebase

变基。在合并两个分支的时候,可以采用变基,也就是把要并入的分支修改后放到当前分支的后面,而不是产生一个合并的 commit,最后产生的 log 图形是一条直线。详见:Git - 变基,这里不再介绍。

写法和 git merge 类似:

$ git rebase master

Git 远程仓库

git clone

从远程项目克隆,支持 https:// http:// git:// ssh:// 等协议。

$ git clone https://github.com/user/example/git
$ git clone user@server:path/to/repo.git

git remote

查看远程仓库服务器。

$ git remote
origin
$ git remote -v
origin  https://dev.lifeni.life/gitea/liangfengning/git-test.git (fetch)
origin  https://dev.lifeni.life/gitea/liangfengning/git-test.git (push)

git remote add 可以添加一个远程仓库。

$ git remote add fff https://github.com/liangfengning/git-test.git
$ git remote -v
fff     https://github.com/liangfengning/git-test.git (fetch)
fff     https://github.com/liangfengning/git-test.git (push)
origin  https://dev.lifeni.life/gitea/liangfengning/git-test.git (fetch)
origin  https://dev.lifeni.life/gitea/liangfengning/git-test.git (push)

git remote show 查看远程仓库的信息。

$ git remote show origin
* remote origin
  Fetch URL: https://dev.lifeni.life/gitea/liangfengning/git-test.git
  Push  URL: https://dev.lifeni.life/gitea/liangfengning/git-test.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local ref configured for 'git push':
    master pushes to master (up to date)

git remote renamegit remote remove 可以重命名和移除一个远程仓库,重命名时所有的引用会自动改变。下面的例子把 origin 移除,并把另一个仓库重命名为 origin

$ git remote remove origin
$ git remote rename fff origin
$ git remote -v
origin  https://github.com/liangfengning/git-test.gitt (fetch)
origin  https://github.com/liangfengning/git-test.git (push)

git fetch

访问远程仓库并获取本地没有的数据。

git fetchgit pull 的区别是:前者只是拉取数据,不会合并(merge),而后者会自动执行合并。

git push

推送某个分支到远程服务器。

$ git push origin master
Username for 'https://dev.lifeni.life': liangfengning
Password for 'https://[email protected]':
Enumerating objects: 19, done.
Counting objects: 100% (19/19), done.
Delta compression using up to 8 threads
Compressing objects: 100% (12/12), done.
Writing objects: 100% (17/17), 1.33 KiB | 227.00 KiB/s, done.
Total 17 (delta 6), reused 0 (delta 0)
remote: . Processing 1 references
remote: Processed 1 references in total
To https://dev.lifeni.life/gitea/liangfengning/git-test.git
   e2706a2..d65b938  master -> master

只有当你有所克隆服务器的写入权限,并且之前没有人推送过时,这条命令才能生效。 当你和其他人在同一时间克隆,他们先推送到上游然后你再推送到上游,你的推送就会毫无疑问地被拒绝。 你必须先抓取他们的工作并将其合并进你的工作后才能推送。

引用来源:Git - 远程仓库的使用

.gitignore

忽略文件,即不会跟踪文件。

文件 .gitignore 的格式规范如下:

  • 所有空行或者以 # 开头的行都会被 Git 忽略。
  • 可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中。
  • 匹配模式可以以(/)开头防止递归。
  • 匹配模式可以以(/)结尾指定目录。
  • 要忽略指定模式以外的文件或目录,可以在模式前加上叹号(!)取反。

所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。 星号(*)匹配零个或多个任意字符;[abc] 匹配任何一个列在方括号中的字符 (这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c); 问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。 使用两个星号(**)表示匹配任意中间目录,比如 a/**/z 可以匹配 a/za/b/za/b/c/z 等。

引用来源:Git - 记录每次更新到仓库

Github 上有整理好的 .gitignore 文件:github/gitignore: A collection of useful .gitignore templates

常见的情况及解决方案

合并冲突

  1. 两个分支修改同一个文件,在 merge 时发生冲突:

     # 修改文件
     $ git checkout dev
     $ vim 1.txt
     $ git add *
     $ git commit -m "merge test 1"
     $ git switch test
     $ vim 1.txt
     $ git add *
     $ git commit -m "merge test 2"
     $ git merge dev
     Auto-merging 1.txt
     CONFLICT (content): Merge conflict in 1.txt
     Automatic merge failed; fix conflicts and then commit the result.
  2. 查看冲突情况:

     $ cat 1.txt
     <<<<<<< HEAD
     1236677
     555
     =======
     12345
     >>>>>>> dev
     ghfhfg
     jhg
  3. 手动解决冲突并 add 和 commit:

     $ vim 1.txt
     $ git add *
     $ git status
     On branch test
     All conflicts fixed but you are still merging.
       (use "git commit" to conclude merge)
    
     Changes to be committed:
             modified:   1.txt
    
     $ git commit -m "fix conflicts"
  4. 此时 test 分支上的冲突已经被我们手动解决,而 dev 分支上的文件没有改动。

搭建 Git 服务器

安装

这里选择用 Docker 和 Gitea 搭建 Git 服务器,文档:Installation with Docker - Docs

安装 Docker Compose 的方法见:Install Docker Compose | Docker Documentation

使用的 docker-compose.yml 文件如下,将其放进 /home

version: '2'

networks:
  gitea:
    external: false

services:
  server:
    image: gitea/gitea:latest
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - DB_TYPE=mysql
      - DB_HOST=db:3306
      - DB_NAME=gitea
      - DB_USER=gitea
      - DB_PASSWD=gitea
    restart: always
    networks:
      - gitea
    volumes:
      - ./gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - '10080:3000'
      - '10022:22'
    depends_on:
      - db

  db:
    image: mysql:5.7
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=gitea
      - MYSQL_USER=gitea
      - MYSQL_PASSWORD=gitea
      - MYSQL_DATABASE=gitea
    networks:
      - gitea
    volumes:
      - ./mysql:/var/lib/mysql

/home 下输入以下命令安装:

docker-compose up -d

安装完成后,在客户端浏览器输入 [ip]:10080 进入管理页面,创建账号,按页面要求配置即可。

获取 SSH Key

在用户目录下的 .ssh 文件夹下执行命令 ssh-keygen,选项全部默认即可(三次回车):

# Ubuntu 下:
$ cd /home/ubuntu/.ssh
$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/c/Users/xxx/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_rsa
Your public key has been saved in id_rsa.pub
...
# Windows 下建议用 WSL 或者 Git Bash:
$ cd /mnt/c/user/xxx/.ssh
$ ssh-keygen
...

之后把目录下的 id_rsa.pub 文件内容复制到 Gitea 设置页面的 SSH 页即可。

问题解决

  1. 用 Nginx 把网站反代部署在二级目录,但除了首页均 404。

    即,访问 example.com/gitea,Nginx 把请求转发到 localhost:3000。由于网页中使用绝对路径,如 /img/logo.png,实际请求时会指向 example.com/img/logo.png 而不是example.com/gitea/img/logo.png,因此产生 404 错误。

    解决方法:使用:反向代理 - Docs 中的第二条。

    Nginx 配置好转发后,在 Gitea 的配置文件 /home/gitea/gitea/conf/app.ini 里修改:

     [server]
     ...
     ROOT_URL = http://localhost:3000/gitea/
     ...

    之后重启 Nginx 和 Gitea。

  2. 配置好 SSH Key 之后,仍然需要输入密码,而且不知道密码是什么。

    **解决方法:**在 git@[ip] 之前加上 ssh://

     # Error
     $ git clone git@[ip]:username/example.git
     Cloning into 'git-test'...
     git@[ip]'s password:
    
     # Success
     $ git clone ssh://git@[ip]:username/example.git

    或者参考 Cloning via ssh asks for password · Issue #9267 · go-gitea/gitea 里的第二种方法。

参考资料

  1. Git - Book
  2. github - git init 和 git init —bare 的区别? - SegmentFault 思否
  3. Cloning via ssh asks for password · Issue #9267 · go-gitea/gitea
网站正在重新设计中,部分功能和内容还没完成。