Git notes

git是一个分布式版本控制系统,不同类型的版本控制软件还有svn,mercurial,vss,SourceAnywhere等。 而Github是一个集成了git的服务。

不管是使用Github,Coding还是Gitlab,我们都需要熟悉Git这一版本管理工具。本文简单介绍Git的工作原理,并总结了一些常用的git指令,冲突解决等,以便自己查看。

基本介绍

(图片均来自网络)

image-20230810010331314

从上图可以看出git的组成和基本指令。git在本地的包括工作区,暂存区,以及本地仓库,stash。远端仓库就是Github,Coding,Gitlab等工具。

Git的存储方式

因为每一个版本之间可能有重叠,为了节省空间,git不是将每一个独立存放,而是以快照的形式存放。如下图所示:

Git分支

一般有两类分支:

  • Merge分支(通常为master分支):Merge分支是为了可以随时发布release而创建的分支,可以使用Jenkins之类的CI工具进行自动化编译以及测试。
  • Topic分支:用来进行功能开发的

HEAD指向的是现在使用中的分支的最后一次更新。通常默认指向master分支的最后一次更新。通过移动HEAD,就可以变更使用的分支。

我们经常需要根据一个提交去查找它的祖先提交,如查找 HEAD 的第三个祖先提交。 这时就可以用到Git 中的 ~^,其具体用法见文章

  • Git中~^的区别?

    在git中,我们其实可以通过^和~来定位某个具体的commit,而不用每次都去敲繁琐的hash值。

    • ^代表父提交,当一个提交有多个父提交时,可以通过在^后面跟上一个数字,表示第几个父提交,^相当于^1.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      $ git log --graph --oneline

      * f44239d D
      * 7a3fb3d C
      |\
      | * 07b920c B
      |/
      * 71bd2cf A
      ...

      这时候,我们是不能通过 ~ 去找到 07b920c (B) 这个提交的。 如果一个提交有多个父提交,那么 ~ 只会找第一个父提交。 那么我们应该怎么找到 07b920c (B) 呢? 答案是:HEAD~^2

      <rev>^<n> 用来表示一个提交的第 n 个父提交,如果不指定 n,那么默认为 1。 和 ~ 不同的是,HEAD^^^ 并不等价于 HEAD^3,而是等价与HEAD^1^1^1

    • ~<n>相当于连续的<n>^.

    • (<commit>|HEAD)~n = (<commit>|HEAD)^^^…(^的个数为n)

分支规范

git上的分支命名规范

A successful Git branching model:

分支合并

在 git 中合并分支有两种选择:merge 和 rebase。

merge

  • fast-forward:

  • non fast-forward:

rebase

4 以补丁的形式打到 3 上,形成了新的提交 4’。

rebase类似于打补丁。rebase的时候,修改冲突后的提交不是使用commit命令,而是执行rebase命令指定 –continue选项。若要取消rebase,指定 –abort选项。

但是,无论哪一种,都有可能产生冲突。

两种方法区别:

  • merge
    保持修改内容的历史记录,但是历史记录会很复杂。
  • rebase
    历史记录简单,是在原有提交的基础上将差异内容反映进去。
    因此,可能导致原本的提交内容无法正常运行。

Git合并及冲突解决

冲突发生一般由于对同一个文件的修改不一致时。当有冲突发生时,一般需要手动合并冲突(也可以借助软件,如smartgit)。冲突标示一般为:

1
2
3
4
5
<<<<<<<HEAD
本地仓库内容
=======
远程仓库内容
>>>>>>>

手动合并后需要并删除标示行。

可能发生冲突的情况时:

  • 本地拉取 git pull = git fetch + git merge

    1. 当远端有更新,本地库没有有变化,拉取操作远端会覆盖本地库(远端版本高于本地)
    2. 当远端修改,本地库也有修改,拉取操作会产生冲突文件(远端版本和本地版本冲突)
    3. 当本地库修改,而远端没有变化,拉取不会产生变化(本地版本高于远端版本)
    4. 当本地库版本低于远端版本,则无法推送,必须先拉取在操作,否则报错
  • 远程推送

    1. 首先,可以试图用git push origin branch-name推送自己的修改;
    2. 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch –set-upstream branch-name origin/branch-name。
    3. 如果合并有冲突,则解决冲突,并在本地提交;
    4. 没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!
  • 分支合并

    1. 当资源分支版本高于目标分支时,合并,资源分支同名文件会覆盖目标分支;
    2. 当目标分支版本高于资源分支时,合并,提示Already up-to-date.(已经更新),目标分支内容不会变化;

Git常用指令

此处总结一些常用的Git指令,一以便查用。

初始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 第一次安裝git之後,先設定自己的名字與信箱,因為Git用名字與信箱來分辨貢獻者
git config --global user.name "First Last"
git config --global user.email "user@example.com"

# 初始化,使當前目錄成為git目錄
git init
# 加上專案的說明文件(非必要)
touch README.md
# 加入暫存區
git add README.md
# 提交到本地容器
git commit -m "initial commit"

# 將本地的repository上傳到遠端(如github)的repository
# 將遠端容器命名為origin
git remote add origin <URL>
# 從本地端的master branch上傳到origin的master branch
git push -u origin master

# 从远端仓库克隆到本地
git clone <URL>

远端数据库操作

1
2
3
4
5
git pull
# 取得远程数据库的最新历史记录。取得的提交会导入到没有名字的分支,这个分支可以从名为FETCH_HEAD的退出。
git fetch
git push
git clone <URL>

Git记录查看

1
2
3
# 显示git flow
# 指定--graph选项,能以文本形式显示更新记录的流程图。指定--oneline选项,能在一行中显示提交的信息。
$ git log --graph --oneline

Git分支(branch)

1
2
3
4
5
6
7
8
9
10
11
12
13
git branch # 列出本地分支(前方有星號的為目前所在分支)
git branch -r # 列出遠端分支
git branch -a # 列出所有分支(本地+遠端)
git show-branch # 列出本地分支所包含的(有差異的)commit
git branch [branchname] [commit] # 在指定commit上建立分支
git branch -d [branchname] # 刪除指定分支
git branch -m [old-name] [new-name] # 重新命名分支
git branch [new-branch] [old-branch] # 由old-branch複製出新分支

git checkout [branch] # 切換到指定分支
git checkout [commit] # 也可以切換到任何commit
# 若commit不在任何branch上,用 git branch 查看時,會顯示 (no branch)
git checkout -b [new-branch] # 建立並切換到該分支(從目前分支複製)

stash

还未提交的修改内容以及新添加的文件,留在索引区域或工作树的情况下切换到其他的分支时,修改内容会从原来的分支移动到目标分支。

但是如果在checkout的目标分支中相同的文件也有修改,checkout会失败的。这时要么先提交修改内容,要么用stash暂时保存修改内容后再checkout。

stash是临时保存文件修改内容的区域。stash可以暂时保存工作树和索引里还没提交的修改内容,您可以事后再取出暂存的修改,应用到原先的分支或其他的分支上。

使用時機:

  1. 你不得不修改一個緊急Bug,你可以先把目前工作目錄的變更丟到stash,這時候你的工作目錄和上次剛提交內容的狀況一樣,等到修完Bug後再把stash中剛剛做到一半的東西還原以繼續
  2. 今天的工作時間結束後,還有未完成的部分
  3. pull到一個不乾淨的樹而不用merge
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
git stash                # 將目前把目前工作區的修改丟到stash裡
git stash save [message] # 將目前把目前工作區的修改丟到stash裡
git stash pop # 取出最新放入的一筆修改,並從stash中移除(若有衝突時會保留)
git stash apply # 將最新投入的一筆修改套用在工作目錄上
git stash drop # 從stash中移除最新的一筆修改
git stash show [stashName] # 列出某一筆stash的修改內容
# 命名:最新的為 stash@{0}, 以此類推
git stash show -p [stashName] # 列出某一筆stash的詳細變更
# 命名:最新的為 stash@{0}, 以此類推
git stash list # 列出所有在stash的修改
git stash [file] # 將檔案丟進stash
git stash clear # 清空stash
git stash branch [branchName] # 用最新一筆stash新增branch
git stash -p # 挑選要暫存的修改
git stash --includev # 同時暫存尚未在版本控制中的檔案(也就是從來沒被add過的檔案)

Git合併(merge)

1
2
3
4
git merge [branch/commit] 
# 將指定的版本合併到目前所在的分支
# 也就是說,只有目前的分支會被影響!
# 该命令将指定分支导入到HEAD指定的分支。

產生衝突時:先用git diff/status查看,修改后再提交

Git比較

1
2
3
4
5
6
7
8
9
git diff [commit] # 比較指定版本和當前目錄的差別(不包含staging area)(預設為HEAD)
git diff # 比較目前版本與工作目錄的不同([commit]=HEAD)
git diff --cached/--staged [commit] # 比較暫存區(staging area)與指定版本(預設是HEAD)的不同
git diff [commit] [commit] # 比較兩個版本的差別
git diff [...] [filename] # 比較特定檔案差異
git diff -S[string] # 只列出變更中,有包含string字串的差異
git diff --stat # 差異資訊(只顯示檔名和行數)
git diff -w # 不將空白視為變更
git diff [version1]:[file1] [version2]:[file2] # 比較兩個版本的指定檔案

Git标签

Git可以使用2种标签:轻标签和注解标签。打上的标签是固定的,不能像分支那样可以移动位置。

  • 轻标签
    • 添加名称
  • 注解标签
    • 添加名称
    • 添加注解
    • 添加签名

一般情况下,发布标签是采用注解标签来添加注解或签名的。轻标签是为了在本地暂时使用或一次性使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 添加轻标签
$ git tag <tagname>
# 显示标签列表
$ git tag
# 显示包含标签资料的历史记录
$ git log --decorate

# 添加注解标签
$ git tag -a <tagname>
# 显示标签的列表和注解
$ git tag -n

# 删除标签
$ git tag -d <tagname>

改写提交

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
# e.g.
$ git reset HEAD^ # 回退所有内容到上一个版本
$ git reset HEAD^ hello.php # 回退 hello.php 文件的版本到上一个版本
$ git reset 052e # 回退到指定版本

# 修改最近的提交
$ git commit --amend
# 取消过去的提交(否定而非删除)
$ git revert HEAD
# 遗弃提交(删除)
$ git reset --hard HEAD~~
# 还原到resset之前的状态
$ git reset --hard ORIG_HEAD
# 提取提交:从其他分支复制指定的提交,然后导入到现在的分支。
$ git cherry-pick [commit]

# 汇合提交:两个提交就合并成一个提交
$ git rebase -i HEAD~~
# 指令会打开文本编辑器,将第二行的“pick”改成“squash”,然后保存并退出。然后执行:
$ git commit --amend
$ git rebase --continue

# 把分支上的所有提交都汇合到master上
$ git checkout master
$ git merge --squash [branchName]

模式说明:

模式名称 HEAD的位置 索引 工作树
soft 修改 不修改 不修改
mixed 修改 修改 不修改
hard 修改 修改 修改

git reset –hard 操作后的数据恢复

第三方平台使用

添加SSH Key到远端服务器

  1. 打开terminal,输入命令ls -al ~/.ssh,检查是否显示有id_rsa.pub或者id_dsa.pub存在,如果存在请直接跳至第3步。
  2. 在bash中输入ssh-keygen -t rsa -C ”yourEmail@example.com
  3. 打开id_rsa.pub文件,并且复制全部内容
  4. 打开第三方平台账户,添加SSH Keys

就此,就可以使用ssh链接免密码上传本地仓库到远端仓库,或反之。

使用pycharm开发代码上传到GitLab和GitHub

References

浅析 Git 思想和工作原理

图解Git

Git常用指令

使用原理视角看 Git

GitLab 多人协同合作开发流程

Git &Gitlab协同流程

猴子都能懂的git入门(讲得蛮清晰也很完整的一个教程)