文章目錄
- The Missing Semester of Your CS Education 學習筆記以及一些拓展知識
- 版本控制Git
- 筆記部分
- Git的基本工作原理
- Git 的核心工作原理:快照而非差異
- Git 的三大工作區域
- Git的核心對象
- Git的四個對象
- 對象之間的關系與工作流程:
- 對象的引用
- Git的安裝和基礎配置
- Git的本地操作(個人的版本控制)
- Git項目的創建
- 1. git init
- 2. git clone
- Git項目管理的核心操作
- 1. git status
- 2. git add
- 3. git commit
- 4. git rm
- 5. git mv
- 6. git restore
- 7. git reset
- 8. git stash
- Git的分支與合并
- 1. git branch
- 2. git checkout
- 3. git switch
- 4. git merge
- 5. git mergetool
- 6. git tag
- Git的歷史查看和修改
- 1. git log
- 2. git diff
- 3. git show
- 4. git blame
- 4. git rebase
- Git的遠程操作(團隊的版本協作)
- 管理遠程倉庫
- 1. git clone
- 2. git remote
- 同步操作
- 1. git fetch
- 2. git pull
- 3. git push
- 建立Github倉庫
- 建立私人的Git服務器
- Git的配置
- git config 命令
- gitignore 文件
- 習題部分
The Missing Semester of Your CS Education 學習筆記以及一些拓展知識
以下是使用The Missing Semester of Your CS Education的個人學習筆記,方便之后翻閱
版本控制Git
Git的入門學習:參看這篇文章,筆記也參考了這篇文章。
筆記部分
說到版本控制,其實這是實際做一個中大型項目所必須的。在Git之前,常用的版本控制器還有SVN等,但Git一經推出就迅速占領了市場,現在已經成為版本控制絕對的主流。
Git的基本工作原理
Git 的核心工作原理:快照而非差異
要理解Git,首先要明白它和許多舊的版本控制系統(如SVN)在核心思想上的根本區別。
-
其他系統 (如SVN): 將文件的版本歷史存儲為一系列的差異(Diffs)。它們記錄了文件從一個版本到下一個版本的具體變化。當你需要檢出某個版本時,系統會從初始文件開始,依次應用每一個差異補丁,最終得到你想要的版本。
-
Git: 將數據視為一系列的快照(Snapshots)。當你進行一次提交(commit)時,Git會獲取你項目中所有文件的狀態,并為該狀態制作一個“快照”,然后保存一個指向該快照的引用。為了效率,如果文件沒有被修改,Git不會重新存儲該文件,而只是保留一個指向上一個已存儲文件的鏈接。
Git 的三大工作區域
你的項目文件會存在于以下三個區域之一:
- 工作目錄 (Working Directory)
這是你電腦上實際看到和編輯的文件所在的文件夾。它是從Git倉庫(.git目錄)中提取出來的某個版本的項目文件。你可以隨心所欲地修改這里的文件。 - 暫存區 (Staging Area / Index)
這是一個位于.git目錄中的文件,它保存了你下一次要提交的內容的信息。你可以把它想象成一個“購物車”或者“草稿箱”。
使用 git add 命令,你可以把你工作目錄中的修改“放入”暫存區,表示你希望這些修改被包含在下一次的快照中。在Git的術語中暫存區被稱為Index,但一般我們還是叫做暫存區
- Git 倉庫 (Git Repository / .git directory)
這是Git用來保存項目元數據和對象數據庫的地方,也是Git最重要的部分。當你執行 git commit 命令時,Git會抓取暫存區里的內容,生成一個永久性的快照,并將其保存在Git倉庫中。
Git的基本最基本的工作流程
- 在工作目錄中修改文件。
- 使用 git add 將想要提交的修改添加到暫存區。
- 使用 git commit 將暫存區的內容生成快照并永久存入本地倉庫。
- (可選)使用 git push 將本地倉庫的更新同步到遠程倉庫。
Git的核心對象
剛剛說了Git的工作區以及在不同工作區中最最基本的工作流程,我們現在需要進一步了解Git是怎么在暫存區和Git倉庫中組織我們的代碼文件的。
Git的四個對象
Git的核心是一個內容可尋址(content-addressable)的文件系統。簡單來說,你存儲的任何內容,都會通過其內容的哈希值來索引和檢索。這些存儲的基本單元就是Git對象。它們都保存在Git倉庫的 .git/objects/ 目錄下。Git共有四種主要的對象類型:Blob、Tree、Commit 和 Tag。
- blob (Binary Large Object,數據對象):
Blob對象,或稱為“數據塊”,是用來存儲文件內容的。它是Git對象中最基礎、最簡單的一種。
工作原理:- 當使用 git add 一個文件時,Git會獲取該文件的內容(注意:僅僅是內容,不包括文件名、權限或時間戳等元數據)。
- Git會用zlib算法對文件內容進行壓縮。
- 然后,Git會在壓縮后的內容前加上一個頭部信息,格式為 blob 文件內容長度\0。例如,一個內容為 “hello” 的文件,其頭部就是 blob 5\0。
- 最后,Git對“頭部+壓縮內容”這個整體計算出一個40位的SHA-1哈希值。這個哈希值就是該Blob對象的ID。
- 因為哈希值是根據文件內容計算的,所以只要文件內容完全相同,無論文件名是什么,在Git倉庫中它們都指向同一個Blob對象。這極大地節省了存儲空間。
- tree(樹對象):
它用來表示一個目錄結構。一個 tree 對象里記錄了它所包含的文件和子目錄的信息,包括文件名、文件權限,以及指向對應 blob 或子 tree 對象的哈希值。它解決了Blob對象不存儲文件名的問題。
工作原理:- Tree對象存儲的是一個列表,每一行代表一個文件或子目錄。
- 列表的每一行包含:文件模式(權限)、對象類型(blob或tree)、對象的SHA-1哈希值、文件名。
- 它就像一個清單,將文件名和對應的Blob對象(文件內容)或另一個Tree對象(子目錄)關聯起來。
- 與Blob類似,Tree對象本身的內容(即這個清單列表)也會被壓縮并計算出其自身的SHA-1哈希值來作為ID。
- commit (提交對象):
是Git歷史記錄的核心。它將某個時間點的項目狀態(一個Tree對象)和一系列元數據打包在一起,形成一次歷史快照。
Commit對象的內容包含以下關鍵信息:- 一個頂層Tree對象的SHA-1哈希: 指向代表了本次提交時項目根目錄的Tree對象。
- 一個或多個父Commit對象的SHA-1哈希:
- 普通的提交只有一個父Commit。
- 第一個提交(root commit)沒有父Commit。
- 由git merge產生的合并提交,通常有兩個或更多父Commit。
- 作者(Author): 原始代碼的創作者信息和創作時間戳。
- 提交者(Committer): 將代碼提交到倉庫的人的信息和提交時間戳。(在多人協作的rebase等場景下,二者可能不同)。
- 提交信息(Commit Message): 你在 git commit -m “…” 中寫的描述文字。
每次你執行 git commit,Git 實際上是在創建一個完整的項目快照。它會創建一個指向頂層 tree 對象的 commit 對象。如果某個文件在兩次提交之間沒有變化,新的 tree 對象會直接復用指向舊的 blob 對象的指針。這使得創建快照非常高效,并且節省空間
- Tag 對象
Tag對象,或稱為“標簽對象”,用于給某個特定的Commit打上一個有意義的、固定的標簽,通常用于版本發布(如 v1.0)。Git中有兩種標簽:輕量標簽(lightweight)和附注標簽(annotated)。只有附注標簽才是真正的Tag對象。
工作原理 (附注標簽):- 輕量標簽只是一個指向某個Commit的指針(類似分支),它本身不創建對象。
- 附注標簽會創建一個獨立的Tag對象,其內容包含:
- 一個Commit對象的SHA-1哈希: 指明這個標簽打在了哪個提交上。
- 標簽名: 例如 v1.0。
- 標簽創建者信息和時間戳。
- 一個標簽信息: 類似于提交信息,可以對該版本進行詳細描述。
- (可選)GPG簽名,用于驗證
對象類型 | 存儲內容 | 指向的對象 | 核心作用 |
---|---|---|---|
Blob | 文件的原始二進制內容 | 無 | 存儲文件數據 |
Tree | 目錄清單(文件名、權限、對象哈希) | Blob對象 或 其他Tree對象 | 描述目錄結構 |
Commit | Tree哈希、父Commit哈希、元數據 | 一個Tree對象 和 父Commit對象 | 記錄歷史快照和版本沿襲關系 |
Tag | Commit哈希、標簽名、元數據 | 一個Commit對象 | 為特定歷史版本提供永久性命名 |
對象之間的關系與工作流程:
這四種對象通過哈希值鏈接在一起,構成了一個有向無環圖(DAG)。
一次典型的 git commit 流程背后發生了什么:
- 你修改了文件 a.txt 和 b.txt。
- git add a.txt b.txt:
- Git為 a.txt 的新內容創建一個Blob對象。
- Git為 b.txt 的新內容創建一個Blob對象。
- Git更新暫存區(Index),記錄文件名與對應Blob哈希的映射。
- git commit -m “My first commit”:
- Git根據暫存區內容創建一個頂層的Tree對象,這個Tree對象里包含了指向 a.txt 和 b.txt 對應Blob對象的記錄。如果項目有子目錄,還會遞歸地創建子Tree對象。
- Git創建一個Commit對象,這個Commit對象:
- 指向剛剛創建的頂層Tree對象。
- 指向當前分支所在的那個Commit作為父Commit。
- 記錄下你的作者/提交者信息和提交信息。
- 最后,Git將當前分支的指針(比如main)移動到這個新創建的Commit對象的哈希上。
對象的引用
如果Git只有40位的SHA-1哈希值,那它對人類來說幾乎是無法使用的。我們不可能記住 e475e5a20d326f20478a179c32cfe33a52144579 這樣的字符串。因此,Git提供了一套非常友好的機制,允許我們使用易于記憶的名稱來作為指向Git對象的指針。這些“命名的指針”在Git中被統稱為引用(References,簡稱 refs)
工作原理:
從根本上說,一個引用就是一個位于 .git/refs/ 目錄下的普通文本文件。這個文件里面只包含一行內容:一個40位的SHA-1哈希值,或者指向另一個引用的路徑。
Git中最常見、最重要的引用有三種:分支(Branches)、輕量標簽(Lightweight Tags),以及一些特殊的引用,如 HEAD。
-
分支 (Branches)
本質: 分支就是一個可以移動的、指向某個Commit對象的指針。它的核心設計理念就是“變化”。
工作原理: 當你在一個分支上工作并創建一個新的Commit時,這個分支的指針會自動向前移動,指向這個最新的Commit。
物理存儲: 它們存儲在 .git/refs/heads/ 目錄下。 -
輕量標簽(Lightweight Tags)
本質: 輕量標簽是一個通常固定不變的、指向某個特定Commit的指針。它主要用于標記項目歷史中重要的里程碑,比如版本發布(v1.0, v2.1.3等)。
工作原理:- 這是一種“純粹”的引用。它就是一個簡單的文件,直接存儲了一個Commit的SHA-1哈希值。
- 它僅僅是給某個Commit起了一個別名,不包含任何額外信息。
物理存儲: 它們存儲在 .git/refs/tags/ 目錄下。
-
特殊引用(其中最重要的就是HEAD)
本質: HEAD 是一個特殊的指針,它指向你當前所在的位置。
工作原理: 大多數情況下,HEAD 是一個符號引用 (Symbolic Reference, or symref)。它本身不是直接指向一個Commit哈希,而是指向另一個引用,通常是一個分支。
物理存儲: HEAD 是一個位于 .git 根目錄下的文件,即 .git/HEAD。
注意:
- 引用是別名: 引用系統是Git為了方便人類使用,給底層復雜的SHA-1哈希對象起別名的一套機制。
- 分支是為“變化”而設計的動態指針,而標簽(特別是附注標簽)是為“穩定”而設計的靜態標記。
Git的安裝和基礎配置
對于一般的Linux系統其實都會預裝Git,當然也可以使用對應的包管理器下載:
sudo apt install git
git --version #檢查是否安裝成功。
Git在安裝好之后一定要先進行下面的配置:
git config --global user.name "你的名字" #設置您的用戶名。
git config --global user.email "你的郵箱" #設置您的郵箱。
git config --global core.editor vim # 設置編輯器
git config --list --show-origin # 檢查一下配置信息
如果使用了 --global 選項,那么該命令只需要運行一次,因為之后無論你在該系統上做任何事情, Git 都會使用那些信息。 當你想針對特定項目使用不同的用戶名稱與郵件地址時,可以在那個項目目錄下運行沒有 --global 選項的命令來配置。(來自:ProGit中文版)
前兩項配置至關重要,因為每一次提交都會記錄作者信息
下面就是很多Git命令了,如果使用時發現問題,我們可以通過以下方式查看手冊:
git help <verb>
git <verb> --help
man git-<verb>
Git的本地操作(個人的版本控制)
Git項目的創建
1. git init
git init 命令用于將一個普通的目錄轉變為一個 Git 倉庫,讓 Git 可以開始對這個目錄下的文件進行版本控制。
基本使用方法:
1. 在現有項目目錄中初始化
# 進入你的項目目錄
cd my-project
# 執行初始化命令
git init2. 初始化并創建一個新目錄
# git init <新目錄名>
git init new-awesome-project
在一個已經初始化的倉庫中再次運行 git init 是安全的。它不會覆蓋你已有的配置和歷史,只會重新初始化模板等
常用參數:
- -b <分支名> 或 --initial-branch=<分支名>
(非常推薦使用) 這個參數用于指定初始分支的名稱。在 Git 的較新版本中,默認的初始分支名已經從 master 趨向于 main。使用此參數可以明確設置你想要的初始分支名,避免后續重命名的麻煩。
# 初始化一個新倉庫,并將其主分支命名為 main
git init -b main
- –bare
創建一個“裸倉庫”(Bare Repository)。裸倉庫沒有工作目錄,也就是說你看不到項目文件的實際樣子,整個目錄的內容就相當于 .git 目錄里的內容。
用途:裸倉庫主要用作團隊協作的服務器。開發者不會直接在裸倉庫上進行編輯和提交,而是將各自的本地倉庫的更改“推送”(push)到這個中央裸倉庫中,并從中“拉取”(pull)別人的更改。
# 通常裸倉庫的目錄名以 .git 結尾,以示區別
git init --bare my-central-repo.git
- -q 或 --quiet
靜默模式,只打印關鍵的錯誤和警告信息,不輸出“Initialized empty Git repository…”這樣的提示信息。在自動化腳本中比較常用。
2. git clone
git clone 命令用于從一個已經存在的遠程 Git 倉庫(例如在 GitHub, GitLab, Gitee 或你自己的服務器上)下載一份完整的副本到你的本地機器
主要作用:
- 在本地創建一個與遠程倉庫同名的目錄(除非你指定了新目錄名)。
- 將遠程倉庫的整個 .git 目錄(包含所有歷史記錄、分支和標簽)完整地復制下來。
- 自動創建一個指向原始倉庫地址的“遠程連接”,默認名為 origin。這使得你將來可以方便地使用 git pull 和 git push 與遠程倉庫同步。
- 自動檢出(checkout)遠程倉庫的默認分支(通常是 main 或 master)的最新版本到你的工作目錄,讓你立刻可以開始工作。
基本用法:
git clone <倉庫URL> [<本地新目錄名>]# 使用 HTTPS 協議克隆 (公開倉庫或需要輸入用戶名密碼的私有倉庫)
git clone https://github.com/torvalds/linux.git
# 使用 SSH 協議克隆 (需要配置 SSH Key 的私有倉庫,更安全便捷)
git clone git@github.com:torvalds/linux.git
# 克隆到指定的目錄
git clone https://github.com/torvalds/linux.git my-linux-kernel
常用參數:
- -b <分支名> 或 --branch <分支名>
如果你只關心某個特定的分支,而不是默認主分支,可以用這個參數直接克隆并檢出該分支。
# 克隆倉庫并直接切換到 develop 分支
git clone -b develop https://example.com/my-project.git
- –depth <深度>
執行“淺克隆”(Shallow Clone)。它只會下載最近的 <深度> 次提交歷史,而不是全部歷史。–depth 1 表示只下載最新的那一次提交。
用途:對于歷史非常龐大、文件非常多的項目,淺克隆可以極大地節省下載時間和磁盤空間。這在 CI/CD(持續集成/持續部署)環境中尤其有用,因為通常只需要最新的代碼來構建和測試。
# 只克隆最新一次提交,不包含任何歷史記錄
git clone --depth 1 https://github.com/large/repository.git
- –bare
和 git init --bare 類似,這會克隆一個裸倉庫。你得到的是一個不包含工作目錄的 .git 目錄的副本。
用途:用于創建遠程倉庫的鏡像,或者備份遠程倉庫。
git clone --bare https://github.com/my/project.git project.git.backup
–progress
在克隆過程中顯示詳細的進度條。在交互式終端中這通常是默認開啟的,但在腳本中可能需要顯式指定。
Git項目管理的核心操作
1. git status
這是一個經常使用的操作,它告訴你當前工作目錄和暫存區的狀態。這是你在進行任何操作前后都應該習慣性使用的命令,以確保你清楚地知道發生了什么。
使用方法:
git status
解讀輸出:
- On branch [branch-name]: 顯示你當前所在的分支。
- Changes to be committed: 這部分列出了已經使用 git add 添加到暫存區的文件。這些是你下次 git commit 時會提交的內容。它們是“已暫存”狀態。
- Changes not staged for commit: 這部分列出了已修改但沒有添加到暫存區的文件。Git 知道它們被改動了,但如果你現在提交,這些改動不會被包含進去。
- Untracked files: 這部分列出了新創建的、Git 從未進行過版本控制的文件。Git 只是發現了它們,但完全不關心它們的內容,除非你使用 git add 來跟蹤它們。
常用參數:
- -s 或 --short:以更緊湊的格式顯示狀態,非常適合快速概覽。
- -b 或 --branch:在輸出的頂部額外顯示分支的詳細信息,包括與遠程分支的同步狀態。
2. git add
git add 是連接工作目錄和 Git 倉庫的橋梁。它將你工作目錄中的更改(新文件或修改過的文件)添加到暫存區,為下一次提交做準備。
核心作用:
- 開始跟蹤一個新文件。
- 將已修改文件的當前內容快照放入暫存區。
- 將一個被刪除的文件標記為“已刪除”,并放入暫存區。
基本使用方法
# 添加一個文件
git add file1.txt
# 添加多個文件
git add file1.txt file2.js
# 遞歸添加文件夾和文件夾下的所有文件
git add src/
# 添加當前目錄下所有更改
git add .
常用參數:
-
-p 或 --patch
(非常強大和推薦) 進入交互式的“補丁”模式。Git 會逐一展示文件中每一處修改(hunk),然后詢問你是否要暫存這一塊修改。你可以輸入 y (yes), n (no), s (split, 將大塊修改拆分成更小的塊), q (quit) 等。
用途:當一個文件里包含了多個不相關的修改時,你可以用 -p 參數只暫存其中一部分,從而實現更原子化、更清晰的提交。 -
-u 或 --update
只暫存那些已經被 Git 跟蹤的文件的修改和刪除,不會暫存新創建的文件(untracked files)。 -
-A 或 --all
暫存所有更改,包括新文件、被修改的文件和被刪除的文件。在 Git 較新的版本中,git add . 的行為和 git add -A 基本一致。
3. git commit
它將暫存區中的所有內容創建成一個永久的快照,并保存在 Git 的歷史記錄中。每一次提交都是你項目歷史中的一個節點。創建一個新的提交對象,包含一個唯一的 SHA-1 哈希值、作者信息、時間戳和提交信息。將暫存區的內容保存到本地倉庫。
使用方法:
git commit
# 執行這條命令后,Git 會打開你配置的默認文本編輯器(通常是 Vim 或 Nano),讓你輸入詳細的提交信息。提交信息的第一行是摘要(subject),空一行后可以寫更詳細的正文(body)。保存并關閉編輯器后,提交就完成了。# git commit -m "你的提交信息"
git commit -m "Feat: Add user login functionality"
常用參數:
-
-m
如上所述,直接在命令行提供提交信息。 -
-a 或 --all
一個快捷方式,它會自動把所有已經跟蹤過的文件的修改暫存起來,然后進行提交。相當于 git add -u 和 git commit 的合并。
# 對于已跟蹤文件的修改,這條命令等同于:
# git add .
# git commit -m "message"
git commit -a -m "Fix: Correct a typo in the documentation"
注意:-a 參數不會添加未被跟蹤的新文件(untracked files)。你必須先用 git add 手動添加新文件。
- –amend
(非常有用) 修改上一次的提交。這并不會真的“修改”歷史(Git 的歷史是不可變的),而是用一個新的提交來替換掉上一次的提交。
用途:- 修改上一次的提交信息:比如你提交后發現信息里有錯別字。
git commit --amend # 這會打開編輯器讓你重新編輯上一次的提交信息
- 將當前暫存區的更改合并到上一次提交中:比如你剛提交完就發現漏掉了一個文件或一處修改。
# 修復/添加文件 git add forgotten-file.txt # 使用 --amend 合并到上一次提交 git commit --amend --no-edit # --no-edit 表示不修改提交信息,直接使用上一次的
- 修改上一次的提交信息:比如你提交后發現信息里有錯別字。
如果你的上一次提交已經被推送(push)到了遠程倉庫,絕對不要使用 --amend,因為它會修改你本地的歷史,導致與遠程歷史不一致,給團隊協作帶來大麻煩。只對未推送的本地提交使用 --amend。
4. git rm
用于從 Git 的跟蹤列表(暫存區)和工作目錄中刪除文件。它不僅僅是簡單地刪除文件,還會將“刪除”這個操作記錄到暫存區。
核心作用:
- 將文件從工作目錄中刪除。
- 將這次刪除操作添加到暫存區,以便在下次提交時將文件從 Git 倉庫中徹底移除。
使用方法:
# 刪除 a.txt 文件
git rm a.txt
效果等同于你手動執行 rm <file> 然后再 git add <file>。
常用參數:
- –cached
只從暫存區(版本庫)中刪除,保留工作目錄中的文件。git rm --cached my_config.json #執行后,my_config.json 仍然存在于你的工作目錄中,但 Git 不再跟蹤它的任何變化。 #git status 會顯示它是一個 “untracked file”。這個操作通常會配合 .gitignore 文件一起使用,在取消跟蹤后,將文件名添加到 .gitignore 中,以防未來被意外 add。
- -f 或 --force
強制刪除。如果一個文件在工作目錄中有修改,并且這些修改還沒有被提交,git rm 會拒絕刪除它以防數據丟失。使用 -f 可以強制執行刪除。
如果你已經手動用 rm 命令刪除了一個文件,git status 會提示 deleted: <file>。此時,你只需要執行 git add <file> 或 git rm <file> 就能將這個刪除操作暫存。
5. git mv
git mv是在 Git 的跟蹤下,安全地重命名一個文件/目錄,或將文件/目錄移動到新的位置,并自動將這個操作暫存起來。其實git mv也可以用三個命名合起來:
- 用 mv 命令移動或重命名文件。
- 執行 git rm 將舊路徑的文件從暫存區移除。
- 執行 git add 將新路徑的文件添加到暫存區。
使用方法:
# 1.重命名文件
# 語法: git mv <舊文件名> <新文件名>
git mv old-file.txt new-file.txt# 2. 移動文件
# 語法: git mv <文件路徑> <目標目錄>
git mv README.md docs/
常用參數
-
-f 或 --force
強制執行。如果目標路徑已經存在一個文件,git mv 默認會失敗以防止覆蓋。使用 -f 可以強制覆蓋目標文件。 -
-n 或 --dry-run
“演習”模式。它只會顯示將要執行的操作,但不會真的移動文件。這在你進行復雜操作前,想預覽一下結果時非常有用。 -
-k
在遇到錯誤時,跳過該錯誤并繼續處理其他文件。
6. git restore
git restore 是一個相對較新(在 Git 2.23 版本中引入)的命令,它的出現是為了將 git checkout 中恢復文件的功能分離出來,使其職責更單一、語義更清晰。restore 的核心任務就是撤銷工作目錄或暫存區中的更改。
核心作用
- 將工作目錄中的文件恢復到暫存區中的狀態,或 HEAD(最新一次提交)的狀態。
- 將暫存區中的文件“撤銷”,使其返回到 HEAD 的狀態(即 “unstage”)。0
使用方法:
# 1. 撤銷工作目錄中的修改
# 如果你修改了一個文件,但想放棄這些修改,讓它回到最近一次提交(或暫存)后的樣子。
# 假設你修改了 README.md,但想撤銷這些修改
git restore README.md# 2. 將文件從暫存區中移除(Unstage)
# 如果你用 git add 將一個文件添加到了暫存區,但后來決定這次提交不應包含它。
# 假設你已經執行了 git add README.md
# 現在想把它從暫存區拿出來
git restore --staged README.md
常用參數:
- –staged
指定操作目標是暫存區,用于“撤銷暫存”(Unstage)。 - –worktree
(這是默認行為)指定操作目標是工作目錄,用于“撤銷修改”。 - –source <commit>
指定從某次特定的提交來恢復文件,而不僅僅是 HEAD。
# 將 README.md 文件恢復到上上次提交(HEAD~2)時的狀態
git restore --source=HEAD~2 README.md
7. git reset
git reset 是一個用來操控提交歷史的命令,它的核心動作是移動當前分支的 HEAD 指針.將當前分支的 HEAD 指針重置到指定的提交,并根據模式選擇性地更新暫存區和工作目錄。
三種主要模式
reset 命令的威力與危險并存,關鍵在于理解它的三種主要模式:–soft、–mixed(默認)和 --hard。
- git reset --soft :溫柔重置
- 動作:只移動 HEAD 指針到 。
- 影響:
- 倉庫歷史:HEAD 移動了。從舊 HEAD 到新 HEAD 之間的所有提交被“撤銷”。
- 暫存區:不變。所有被“撤銷”的提交所包含的更改,現在全部處于已暫存狀態。
- 工作目錄:不變。你的代碼文件沒有任何變化。
- 典型場景:合并多個零碎的提交。你可以回退幾個版本,然后將所有暫存的更改一次性地進行一個干凈的提交。
# 撤銷最近兩次提交,并將所有更改放入暫存區git reset --soft HEAD~2
- git reset --mixed :混合重置(默認模式)
- 動作:移動 HEAD 指針,并且重置暫存區。
- 影響:
- 倉庫歷史:HEAD 移動了。
- 暫存區:被清空,并更新為 時的狀態。
- 工作目錄:不變。所有被“撤銷”的提交所包含的更改,現在全部處于未暫存狀態(在工作目錄中)。
- 典型場景:你想撤銷幾次提交,并且想重新組織這些更改(重新 add 并 commit)。
# 撤銷最近一次提交,并將更改保留在工作目錄中 git reset --mixed HEAD~1 # 因為是默認模式,所以可以簡寫為: git reset HEAD~1
- git reset --hard :硬核重置(危險!)
- 動作:移動 HEAD 指針,同時重置暫存區和工作目錄。
- 影響:
- 倉庫歷史:HEAD 移動了。
- 暫存區:被重置。
- 工作目錄:被重置!所有被“撤銷”的提交所包含的更改,以及你在工作目錄中所有未提交的更改,都將被永久刪除。
- 典型場景:你發現最近幾次提交完全是錯誤的,想徹底、干凈地回到某個歷史版本,丟棄這期間的所有工作。
# 徹底丟棄最近一次提交以及所有本地的未提交更改git reset --hard HEAD~1
reset 命令也可以作用于單個文件(git reset <file>),其效果等同于 git restore --staged <file>,即只用來撤銷暫存。這是它在 restore 命令出現之前的一個歷史用法。
8. git stash
git stash 是一個非常有用的工作流工具,它允許你臨時保存未提交的更改(包括已暫存和未暫存的),以便將工作目錄恢復到一個干凈的狀態。
例如:當你正在一個分支上開發某個功能,但突然需要切換到另一個分支去修復一個緊急 Bug 時,你的工作目錄可能還很“臟”(有未完成的修改)。此時你不想為了這個未完成的功能創建一個臨時的 commit。stash 就是為此而生。
使用方法:
- 儲藏更改
# 將所有已跟蹤文件的修改(包括暫存和未暫存的)保存起來
git stash
# 推薦的做法是加上說明信息,方便以后識別
git stash push -m "Refactoring user login form"
- 查看儲藏列表
Stash 是一個棧(Stack,后進先出),你可以儲藏多次。
git stash list#輸出會是這樣:
stash@{0}: On main: Refactoring user login form
stash@{1}: On main: WIP on feature/new-feature
stash@{0} 是最近一次儲藏。
- 應用儲藏
有兩種方式可以恢復儲藏的更改:
- git stash apply [stash名]:應用儲藏,但不從儲藏列表中刪除它。
# 應用最近一次儲藏
git stash apply
# 應用指定的儲藏
git stash apply stash@{1}
- git stash pop [stash名]:應用儲藏,并自動從儲藏列表中刪除它。這是最常用的方式。
git stash pop
- 查看儲藏內容
在應用之前,你可能想看看某個儲藏里到底改了什么。
# 以補丁(diff)的形式顯示最近一次儲藏的內容
git stash show -p
# 顯示指定儲藏的內容
git stash show -p stash@{1}
- 刪除儲藏
- 如果你用 apply 應用了儲藏,或者某個儲藏你不再需要了,可以手動刪除它。
git stash drop stash@{1}
- 清空所有儲藏(危險操作!)。
git stash clear
常用參數
- push -m “message”:儲藏并附帶說明。
- list: 查看列表。
- pop: 應用并刪除。
- apply: 應用但不刪除。
- drop: 刪除。
- clear: 清空。
- -u 或 --include-untracked:在儲藏時,一并儲藏未被跟蹤的新文件。默認情況下 stash 只處理已跟蹤的文件。
- -a 或 --all:儲藏所有文件,包括被 .gitignore 忽略的文件。
注意事項:
- 儲藏是本地的:Stash 存儲在你的本地倉庫的 .git 目錄中,它不會隨著 git push 被推送到遠程倉庫。它純粹是你個人的本地工具。
- 添加說明是個好習慣:當儲藏列表變長時,沒有說明信息的 WIP on … 會讓你很難記起每個儲藏是做什么的。堅持使用 git stash push -m “…”。
- 可能會有沖突:如果在儲藏后,你的分支又有了新的提交,那么在 apply 或 pop 儲藏時,可能會發生合并沖突。解決方法和普通的合并沖突一樣。
- 不是長期存儲方案:stash 設計初衷是用于臨時、短期的狀態保存。如果一項工作需要擱置較長時間,更規范的做法是為它創建一個分支并提交(即使是臨時的 commit)。分支比 stash 更穩健、更清晰。
- 定期清理:養成定期清理不再需要的 stash 的習慣,保持 git stash list 的整潔。
Git的分支與合并
關于Git的分支:
在 Git 中,一個分支本質上只是一個指向某個特定提交(commit)的、輕量級的、可移動的指針。當你創建一個新分支時,Git 只是創建了一個新的指針,它指向你當前所在的提交。這使得創建和切換分支的操作快如閃電。HEAD 是另一個特殊的指針,它指向你當前工作的本地分支。
1. git branch
branch操作是Git中分支操作的核心。
基本使用方法:
#1.列出分支
# 列出所有本地分支
git branch
#輸出結果中,當前所在的分支會以 * 標記,并通常會高亮顯示。#2.創建一個新分支
# 語法: git branch <新分支名>
git branch feature/payment-gateway
#重要:這條命令只創建新分支,不會自動切換到該分支。你仍然停留在當前分支。#3.刪除一個分支
# 語法: git branch -d <要刪除的分支名>
git branch -d feature/user-auth
# -d 是 --delete 的縮寫,它會進行安全檢查。如果要刪除的分支上的工作還沒有被合并到當前分支,Git 會阻止刪除并給出提示,防止你意外丟失工作成果。#4.重命名分支
# 語法: git branch -m <舊分支名> <新分支名>
git branch -m feature/payment-gateway feature/payment-integration
常用參數:
- -a 或 --all
列出所有分支,包括本地分支和遠程跟蹤分支(如 remotes/origin/main) - -d / --delete
安全刪除。只有當分支的工作被完全合并后才能刪除。 - -D
強制刪除。git branch -D 會忽略檢查,直接刪除分支。當你確定要丟棄某個功能分支上的所有工作時使用。 - -m / --move
重命名分支。 - -M
強制重命名,即使新分支名已經存在。 - -v 或 -vv / --verbose
顯示更詳細的信息。-v 會顯示每個分支最后一次提交的哈希值和提交信息。-vv (非常有用) 還會顯示與上游遠程分支的跟蹤關系(領先或落后多少次提交)。 - –merged / --no-merged
非常有用的過濾器,用于分支清理。
# 列出所有已經合并到當前分支的分支(這些通常是可以安全刪除的)
git branch --merged
# 列出所有尚未合并到當前分支的分支
git branch --no-merged
分支的約定命名規范:
為了保持項目清晰,建議采用一致的分支命名規范。例如:
- 新功能:feature/user-login、feature/shopping-cart
- Bug修復:bugfix/issue-123、bugfix/null-pointer-exception
- 發布:release/v1.2.0
- 緊急修復:hotfix/security-patch
注意:
- 請務必區分 git branch 和 git switch (或舊的 git checkout )。前者只創建指針,后者才是切換 HEAD 指針到目標分支上,讓你開始在新分支上工作。git switch -c (或 git checkout -b ) 是一個快捷方式,可以一步完成創建和切換。
- 遠程分支管理:git branch 主要管理的是你的本地分支。刪除一個本地分支 (git branch -d) 并不會影響到遠程倉庫中對應的分支。要刪除遠程分支,你需要使用 git push.
2. git checkout
git checkout 是一個功能非常強大的“多面手”命令,它的核心功能有兩個:分支操作和恢復文件。
checkout的分支操作正在被switch取代,恢復文件操作被restore命令取代
使用方法
# 1.分支操作
# 切換到一個已經存在的分支
git checkout develop
# 創建一個新分支并立即切換過去(等同于 git branch <name> + git checkout <name>)
git checkout -b new-feature-branch# 2.恢復工作目錄中的文件
# 丟棄工作目錄中對 a.txt 的修改,用暫存區或 HEAD 的版本覆蓋它
git checkout -- a.txt
注意命令中的 --,它是一個好習慣,用于分隔分支名和文件名,避免當文件名與分支名相同時產生歧義.
常用參數:
- -b <new-branch>
創建一個新分支,并切換到該分支。 - -B <new-branch>
如果 <new-branch> 不存在,則創建并切換(同 -b);如果已存在,則重置該分支到當前 commit,并切換過去。這是一個有風險的操作,慎用。 - –track
當本地沒有某個分支,而遠程 origin 有時,可以使用它來快捷地創建并跟蹤遠程分支。
# 本地沒有 feature 分支,但遠程 origin/feature 存在
# 這條命令會創建一個本地的 feature 分支,并使其跟蹤 origin/feature
git checkout --track origin/feature
3. git switch
git switch 是在 Git 2.23 版本中引入的新命令,旨在將 git checkout 的功能進行拆分,使其職責更單一。switch 專門負責分支的切換和創建,讓命令的意圖更加清晰。
核心作用
- 切換到另一個已存在的分支。
- 創建一個新分支并立即切換過去
使用方法
1.切換到一個已存在的分支
# 切換到名為 develop 的分支
git switch develop2.創建并切換到新分支
# 這是 git checkout -b 的現代等價命令。
# 創建一個名為 feature/new-login 的新分支,并立即切換到該分支
git switch -c feature/new-login3.切換回上一個分支
#一個非常方便的快捷方式,可以在兩個分支之間快速來回切換。
git switch -
常用參數
- -c 或 --create
創建一個新分支并切換過去。 - -C 或 --force-create
強制創建。如果同名分支已存在,它將被重置到當前 HEAD 的位置。這是一個有風險的操作。 - –detach
進入“分離 HEAD”狀態。此時 HEAD 直接指向一個具體的提交(commit),而不是一個分支。這允許你在不創建新分支的情況下進行實驗性提交。
# 直接切換到某個 commit,進入分離 HEAD 狀態
git switch --detach a1b2c3d
4. git merge
git merge 是用于將一個分支的更改集成(合并)到另一個分支的命令。這是團隊協作和功能開發的核心操作。
標準的合并流程如下:
- 首先,切換到你想要接納更改的目標分支(例如 main)。
git switch main
- 確保目標分支是最新的。
git pull origin main
- 執行 merge 命令,將源分支(例如 feature/new-login)合并進來。
git merge feature/new-login
合并的兩種類型
- 快進合并 (Fast-Forward)
如果你的目標分支 (main) 在源分支 (feature/new-login) 創建之后沒有任何新的提交,那么合并時 Git 只會簡單地將 main 分支的指針向前移動到 feature/new-login 的最新位置。這個過程不會產生新的“合并提交”,歷史記錄保持線性。 - 三方合并 (Three-Way Merge)
如果目標分支和源分支在分叉后各自都有了新的提交,Git 就無法進行快進合并。此時,它會執行“三方合并”:
a. 找到兩個分支的共同祖先。
b. 將兩個分支的更改與共同祖先進行比較。
c. 創建一個新的合并提交 (Merge Commit),這個提交有兩個父提交,分別指向原來的兩個分支頭。
常用參數:
- –no-ff
(非常推薦) 禁止快進式合并。即使可以快進,也強制創建一個新的合并提交。
為什么? 這可以保留分支的開發歷史。在 git log --graph 中,你能清晰地看到一個功能是從哪個分支合并過來的,這使得項目歷史更具可讀性。
git merge --no-ff feature/new-login
- –squash
“壓扁”合并。它會將來自分支的所有提交的更改內容都應用到當前分支的工作目錄和暫存區,但不會自動創建合并提交。你需要手動執行 git commit 來創建一個全新的、單一的提交。
用途:當一個功能分支上有很多零碎的開發過程提交時,使用 --squash 可以將它們合并成一個干凈的、有意義的提交,從而保持主分支的歷史整潔。 - –abort
(救命稻草) 中止合并。如果合并過程中出現了很多你不想處理的沖突,或者你覺得合并操作有誤,可以執行 git merge --abort。它會讓你安全地回到執行 merge 命令之前的狀態。 - –ff-only
只在可以快進的情況下才進行合并,否則就中止。
注意事項(合并沖突)
- 合并方向:務必搞清楚合并的方向。git merge B 是將 B 分支合并到你當前所在的分支。
- 合并沖突 (Merge Conflicts):如果兩個分支在同一個文件的同一個地方有不同的修改,Git 無法自動判斷該保留哪個。這時就會發生合并沖突。Git 會暫停合并,在沖突文件中用特殊標記(<<<<<<<, =======, >>>>>>>)標出沖突區域,等待你手動解決。
- 解決沖突后:手動解決完所有沖突文件后,你需要使用 git add <已解決的文件> 將它們標記為已解決,然后執行 git commit 來完成這次合并提交。
5. git mergetool
當手動解決文本標記的合并沖突感到困難或效率低下時,git mergetool 可以啟動一個外部的可視化合并工具來幫助你.
mergetool本身的使用需要進行配置,而且并不是Git的核心內容,這里先了略過
6. git tag
git tag 的核心作用是為 Git 倉庫歷史中的某一個特定的提交(commit)打上一個永久性的、有意義的標記。這個標記通常用于指代一個重要的時間點,最常見的場景就是項目發布。
常用方法
# 1. 列出標簽
# 列出所有本地標簽
git tag
# 使用通配符篩選標簽
git tag -l "v1.8.*"# 2. 創建標簽
# 創建附注標簽(推薦)
# 語法: git tag -a <標簽名> -m "附注信息"
git tag -a v1.0 -m "Release version 1.0"
# 創建輕量標簽
# 語法: git tag <標簽名>
git tag v1.0-light# 3. 為過去的提交打標簽
# 如果你忘記了打標簽,可以先用 git log 找到目標提交的哈希值,然后在創建標簽時指定它。
# 假設從 git log 得知目標 commit 哈希是 7f5d8b2
git tag -a v0.9 -m "Retroactively tagging version 0.9" 7f5d8b2# 4. 查看標簽信息
# 使用 git show 命令查看標簽的詳細信息
git show v1.0# 5. 刪除標簽
# 刪除一個本地標簽
git tag -d v1.0-light# 6. 檢出標簽
# 你可以像檢出分支一樣檢出某個標簽,以查看當時的代碼狀態。
git checkout v1.0
注意最后一個檢出標簽的操作:會使你的倉庫進入“分離 HEAD (Detached HEAD)”狀態。這意味著你現在不在任何分支上。你可以在這個狀態下查看代碼、進行編譯,但如果你在此基礎上做了新的提交,這些提交不屬于任何分支,一旦你切換到其他分支,就可能丟失。如果想要在這個標簽基礎上開發,正確的做法是基于該標簽創建一個新分支:
git checkout -b hotfix-for-v1.0 v1.0
這條命令會創建一個名為 hotfix-for-v1.0 的新分支,其起點就是 v1.0 標簽所在的位置,然后將你切換到這個新分支上。
Git的歷史查看和修改
1. git log
log命令讓你能夠查看項目從誕生至今的每一次提交記錄。
使用方法:
最基礎的用法就是直接輸入命令:
git log
# 或者
git log <content> # 查找與content有關的log
你會看到一個詳細的列表,每個條目包含:
- Commit Hash:一個唯一的 SHA-1 哈希值,是這次提交的身份證。
- Author:提交者的姓名和郵箱。
- Date:提交的日期和時間。
- Commit Message:提交時附帶的說明信息。
常用參數:
- –oneline
將每次提交壓縮到一行顯示,只包含 commit 短哈希和提交信息摘要。非常適合快速概覽。 - –graph
以 ASCII 字符繪制出分支與合并的拓撲圖。通常和 –oneline、–decorate(顯示分支和標簽名)一起使用,效果極佳。 - –pretty=format:“<格式字符串>”
終極自定義格式。你可以指定任何你想要的輸出格式。# 示例:短哈希 - 作者 - 相對時間 : 提交信息git log --pretty=format:"%h - %an, %ar : %s"
- -p 或 --patch
顯示每次提交所引入的具體代碼差異(補丁)。 - -n <數量> 或 -<數量>
只顯示最近的 <數量> 次提交。例如 git log -3。 - –author=“<作者名>”
只顯示指定作者的提交。 - –grep=“<關鍵詞>”
在提交信息中搜索包含 <關鍵詞> 的提交。 - –since=<日期> 和 --until=<日期>
按時間范圍篩選。例如 git log --since=“2 weeks ago”。 - <分支1>…<分支2>
顯示在 分支2 中存在、但在 分支1 中不存在的提交。非常適合查看一個功能分支相比主分支多了哪些提交。git log main..feature/new-login
2. git diff
git diff 用于比較 Git 倉庫中任意兩個“狀態”之間的差異。這些“狀態”可以是提交、分支、標簽,甚至是你的工作目錄。
常用方法:
1.比較工作目錄與暫存區
git diff
# 顯示所有已修改但還未 git add 到暫存區的更改。這是你即將要 add 的內容。2.比較暫存區與最新提交
git diff --staged # 或者 --cached
# 顯示已經 git add 到暫存區、但還未 git commit 的更改。這是你即將要 commit 的內容。3. 比較工作目錄與最新提交
git diff HEAD
# 顯示你工作目錄中所有未提交的更改(包括已暫存和未暫存的)。4.比較兩次提交
# 語法: git diff <提交1> <提交2>
git diff a1b2c3d e4f5g6h5.比較兩個分支
# 顯示 feature 分支相比于 main 分支有哪些不同
git diff main..feature
Diff的輸出格式
- — a/file.txt 和 +++ b/file.txt 分別代表變更前的“a”版本和變更后的“b”版本。
- @@ -l,s +l,s @@ 稱為“hunk”頭,表示差異所在的行號范圍。
- 以 - 開頭的行表示從“a”版本中刪除。
- 以 + 開頭的行表示在“b”版本中添加。
常用參數 - –stat
不顯示完整的代碼差異,而是顯示一個統計摘要,包含哪些文件被修改了,以及增刪了多少行。 - –name-only
最精簡的模式,只列出有差異的文件名。 - –color-words
以單詞為單位高亮顯示差異,而不是以行為單位。對于修改文檔或文章非常有用。
3. git show
git show 的核心作用是以一種對人類友好的方式,顯示各種類型的 Git 對象(commit、tag、blob、tree)的詳細信息。它最常見的用途是查看某一次提交(commit)的完整詳情。
可以把 git show 理解為 git log 和 git diff 的一個便捷組合:
- 它像 git log 一樣,能顯示某次提交的元數據(作者、日期、提交信息)。
- 它又像 git diff 一樣,能顯示該次提交所引入的具體代碼更改(補丁/patch)。
使用方法
- 查看單個提交(最常用)
- 查看最近一次提交 (HEAD)
如果你不提供任何參數,git show 默認顯示當前 HEAD 指針指向的提交。
git show
-
查看指定的提交
你可以使用任何可以指向一個 commit 的引用來查看,比如:- Commit 哈希:git show a1b2c3d
- 分支名:git show main (顯示 main 分支頂端的最新提交)
- 標簽名:git show v1.0
- 相對引用:git show HEAD~2 (顯示當前提交往前數第2次的提交)
-
解讀輸出內容
git show 的輸出通常分為兩部分:-
提交元數據:
commit a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0 (HEAD -> main, origin/main)
Author: Your Name you@example.com
Date: Thu Jul 25 06:20:00 2024 -0700
Feat: Add user authentication feature -
代碼差異 (Diff/Patch):
這部分顯示了這次提交相比于其父提交所做的具體更改。
diff --git a/src/login.js b/src/login.js
index e69de29…d00491f 100644
— a/src/login.js
+++ b/src/login.js
@@ -0,0 +1,5 @@
+function authenticate(user, pass) {
+ // TODO: Implement real authentication
+ return true;
+}
-
- 查看標簽 (Tag)
git show 也可以用來查看標簽對象的信息。
git show v1.2.0
- 如果 v1.2.0 是一個附注標簽 (annotated tag),git show 會首先顯示標簽自身的信息(打標者、日期、附注信息),然后顯示該標簽指向的提交的詳細信息。
- 如果它是一個輕量標簽 (lightweight tag),git show 的輸出將和直接 show 那個提交完全一樣。
- 查看某次提交中的文件內容
你可以用 git show 直接查看某個歷史版本中某個文件的全部內容,而不是它的變更。
# 語法: git show <commit>:<文件路徑>
# 查看上一次提交中 src/main.js 文件的完整內容
git show HEAD~1:src/main.js
# 查看 v1.0 版本時 pom.xml 文件的內容
git show v1.0:pom.xml
常用參數
git show 的很多參數都是用來控制其 diff 部分的輸出格式的,與 git diff 的參數類似。
-
格式化輸出
–pretty=<格式> 或 --format=<格式>:與 git log 的用法完全一樣,可以自定義提交元數據的顯示格式,而不顯示 diff。
–stat:不顯示完整的代碼差異,只顯示一個統計摘要,包含哪些文件被修改了以及增刪的行數。
–name-only:最精簡的模式,只列出在該次提交中被更改的文件名。 -
控制 Diff 輸出
–color-words:以單詞為單位高亮顯示差異,而不是以行為單位。對修改文檔或文章非常有用。
-w 或 --ignore-all-space:在比較時忽略所有空白字符的差異。 -
處理合并提交
對于合并提交(merge commit),它有兩個父提交,git show 默認會顯示一個合并后的“組合差異”。這有時會比較混亂。
-m 或 --first-parent:當遇到合并提交時,只顯示該提交與第一個父提交的差異。這通常是你合并進來的那個分支的差異。
4. git blame
git blame 是一個行級的代碼考古工具。它能逐行顯示一個文件,并清晰地標明每一行代碼最后是由誰、在哪一次提交中修改的。
使用方法:
最基礎的用法是指定一個文件名:
git blame <文件名>
每一行的輸出內容包括:
- Commit 短哈希:這次修改所在的提交ID。^ 開頭的哈希表示這是該文件的初始引入提交。
- 作者:最后修改該行的作者名。
- 時間戳:提交的時間。
- 行號:文件中的原始行號。
- 代碼內容:該行的實際代碼。
常用參數:
- -L <起始行>,<結束行>:只顯示指定行號范圍內的 blame 信息。當你只關心文件中一小部分代碼的歷史時非常有用。
# 只查看 main.js 文件第 4 到第 6 行的歷史
git blame -L 4,6 src/main.js
- -e 或 --show-email:在作者名旁邊顯示完整的電子郵件地址。
- -w:在追溯歷史時,忽略單純的空白(空格、Tab)修改。如果某次提交只是重新格式化了代碼,使用此參數可以追溯到上一次真正修改代碼邏輯的提交。
- -C:更進一步,檢測代碼是否是從同一提交中的其他文件移動或復制過來的。這對于追蹤重構操作很有幫助
4. git rebase
git rebase 是 Git 中很危險的命令之一。它的核心思想是重寫歷史,將一系列提交“變基”到另一個基礎之上,從而創造一個更線性的、更整潔的提交歷史。
rebase的使用比較危險,這里就先不做筆記了
Git的遠程操作(團隊的版本協作)
Git的遠程操作一般有兩種方式,一種是自己建立一個私人的Git服務器,第二種是使用Github(或其他)這種Git服務平臺。
管理遠程倉庫
1. git clone
用于復制項目,之前已經說過。
2. git remote
用于管理你本地倉庫配置的遠程倉庫“別名”及其對應的 URL。它本身不進行數據傳輸(如 push 或 pull),只負責管理連接信息。
核心作用:
- 查看已配置的遠程倉庫。
- 添加新的遠程倉庫連接。
- 重命名或刪除已有的遠程倉庫連接。
- 修改遠程倉庫的 URL。
常見用法
1.查看遠程連接
# 只列出遠程連接的名稱 (如 origin)
git remote
# 列出名稱及其對應的 URL,非常常用
git remote -v
# 利用remote show命令查看更多信息
git remote show origin2.添加遠程連接
一個非常經典的場景是,你 fork 了一個開源項目。你 clone 的是你自己的 fork(origin),但你還想跟蹤原始項目的更新。這時就可以將原始項目添加為一個新的遠程連接,通常命名為 upstream。
# 語法: git remote add <名稱> <URL>
git remote add upstream https://github.com/original-author/original-project.git
# 現在,git remote -v 就會顯示 origin 和 upstream 兩個遠程連接。你可以從 upstream 拉取更新,然后推送到你自己的 origin。3.修改遠程連接的 URL
當你需要將項目遷移到新的服務器,或者想從 HTTPS 切換到 SSH 時,這個命令非常有用。
# 語法: git remote set-url <名稱> <新URL>
git remote set-url origin git@github.com:my-user/my-fork.git4.重命名遠程連接
# 語法: git remote rename <舊名稱> <新名稱>
git remote rename origin upstream5.刪除遠程連接
# 語法: git remote remove <名稱>
git remote remove upstream
常用參數
- git remote 的主要“參數”實際上是它的子命令,如 add, remove, set-url 等。
- -v 或 --verbose: 在列出遠程連接時,顯示詳細的 URL 信息。
注意:
- 本地操作:git remote 的所有操作都只修改你本地倉庫的 .git/config 文件。它不會以任何方式聯系遠程服務器或影響遠程倉庫本身。它只是在管理你本地的“書簽”
- 遠程跟蹤分支:你配置的每一個遠程連接,都對應著一組“遠程跟蹤分支”(如 origin/main, upstream/develop)。當你執行 git fetch <遠程名> 時,Git 就是根據這個遠程連接的 URL 去獲取最新數據,并更新這些遠程跟蹤分支。
同步操作
1. git fetch
fetch 的作用非常單純:它只負責從遠程倉庫下載最新的數據(新的提交、分支、標簽)到你的本地倉庫
- 它會更新你的“遠程跟蹤分支”(如 origin/main)。這些分支就像是遠程倉庫在你本地的只讀鏡像或書簽,讓你知道遠程倉庫的狀態。
- 它完全不會修改你自己的本地工作分支(如 main),也不會影響你的工作目錄或你正在編輯的代碼。。
使用方法:
# 從名為 origin 的遠程倉庫獲取所有更新
git fetch origin# 獲取所有已配置的遠程倉庫的更新
git fetch --all# 獲取 origin 倉庫的數據,并清理本地不存在的遠程跟蹤分支
git fetch --prune origin
常用參數
- –all:從所有配置的遠程倉庫中獲取更新。
- –prune 或 -p:在獲取前,清理掉本地那些在遠程倉庫中已被刪除的、陳舊的遠程跟蹤分支。這是一個保持倉庫整潔的好習慣。
2. git pull
pull 是一個復合命令,它試圖簡化工作流程,但也可能帶來意外。pull 是兩個命令的快捷方式:git pull = git fetch + git merge。
- 它首先會執行 git fetch,從遠程下載最新的數據。
- 然后,它會立刻嘗試將遠程跟蹤分支(如 origin/main)合并到你當前所在的本地工作分支(如 main)。
使用方法
# 從 origin 拉取更新并合并到當前分支
# 假設當前在 main 分支,這條命令大致等同于:
# git fetch origin
# git merge origin/main
git pull origin
如果你本地分支設置了上游跟蹤關系(通常 clone 或 push -u 后會自動設置),可以直接簡化為:git pull
常用參數
- –rebase:一個非常重要的參數。使用 git pull --rebase 時,pull 會以 git fetch + git rebase 的方式工作,而不是 merge。這可以避免產生不必要的合并提交,保持提交歷史的線性整潔。這是許多團隊推薦的做法。
- –ff-only:只在可以“快進式”(Fast-forward)合并時才執行,否則就停止。這可以防止 pull 操作自動創建一個非預期的合并提交。
注意:
- 因為 pull 會自動嘗試合并,如果你本地有與遠程不一致的提交,pull 操作可能會立刻導致合并沖突。
3. git push
當你完成了本地的提交,就需要用 push 命令將你的工作分享給團隊,更新到遠程倉庫。
使用方法
1.基本推送
# 語法: git push <遠程倉庫名> <本地分支名>[:<遠程分支名>]
git push origin main
# 這條命令將你本地的 main 分支推送到 origin 遠程倉庫對應的 main 分支。
#如果遠程分支名與本地分支名相同,可以省略冒號后的部分。2.首次推送新分支
#當你第一次推送一個本地新創建的分支時,建議使用 -u 參數來設置“上游跟蹤關系”。
# -u 參數會自動設置跟蹤,以后該分支可以直接使用 git push
git push -u origin feature/new-login
設置后,Git 就知道你本地的 feature/new-login 分支對應的是遠程的 origin/feature/new-login 分支。3.刪除遠程分支
git push origin --delete <分支名>4.推送標簽
#標簽默認不會被 git push 推送,需要顯式操作。
# 推送所有本地標簽
git push --tags
# 推送單個標簽
git push origin <標簽名>
常用參數
- -u 或 --set-upstream:設置上游跟蹤關系。
- –force:(危險!) 強制推送。它會用你本地的分支狀態強行覆蓋遠程分支。這會銷毀遠程倉庫上別人可能已經提交的更改。絕對不要在共享分支(如 main, develop)上使用此命令,除非你百分之百確定你在做什么,并且已經和團隊溝通過。
- –force-with-lease:一個更安全的強制推送。在推送前,它會檢查遠程分支是否在你上次 fetch 之后又有新的提交。如果有,推送就會失敗。這可以防止你無意中覆蓋掉團隊成員在你不知情的情況下推送的工作。如果必須強制推送,請優先使用此命令。
注意事項
- 先 pull(或 fetch+merge)再 push:在推送你的更改之前,一定要先從遠程拉取最新的版本并與你的本地工作合并。這可以確保你的工作是基于最新版本進行的,并在本地解決完所有可能的沖突。這是一個至關重要的協作習慣。
- 被拒絕的推送 (Rejected Push):如果你 push 時看到 rejected 的錯誤,通常意味著遠程分支上有了你本地沒有的新提交。這正是需要你先 pull 或 fetch 的信號。切勿立即使用 --force!
建立Github倉庫
占個位
建立私人的Git服務器
占個位
Git的配置
git config 命令
Git配置的三個層級:
- System (系統級)
- 作用范圍:對操作系統上的所有用戶和他們的所有倉庫都生效。
- 配置文件位置:通常在 /etc/gitconfig (Linux)。
- 使用場景:由系統管理員設置,為服務器上的所有用戶提供統一的默認配置。個人用戶很少需要修改它。
- 命令參數:–system
- Global (全局級/用戶級)
- 作用范圍:對當前登錄的單個用戶的所有倉庫生效。
- 配置文件位置:通常在 ~/.gitconfig 或 ~/.config/git/config。
- 使用場景:這是最常用的配置級別,用于設置你個人的信息,如用戶名、郵箱,以及你希望在所有項目中通用的別名、編輯器等。
- 命令參數:–global
- Local (本地級/倉庫級)
- 作用范圍:只對當前所在的單個倉庫生效。
- 配置文件位置:在倉庫的 .git/config 文件中。
- 使用場景:用于設置特定項目的配置。例如,你在公司的項目需要使用公司郵箱,而在個人的開源項目中使用個人郵箱。這時就可以在公司倉庫內部使用 --local 配置來覆蓋全局設置。
- 命令參數:–local (在一個倉庫內部時,此為默認級別,可省略)
當同一個配置項在多個層級中都存在時,Git 會采用“就近原則”:Local (倉庫級) > Global (用戶級) > System (系統級)
config命令的常用方法:
- 查看配置
- 列出所有配置
這個命令會列出所有層級的配置,并顯示它們各自的來源文件,非常適合調試。
git config -l --show-origin
查看單個配置項
- 查看單個配置
Git 會按照 Local > Global > System 的順序查找并返回第一個找到的值。
git config user.name
git config user.email
- 設置配置
- 首次使用 Git 必須做的配置
每個提交都包含作者信息,所以這是使用 Git 前必須完成的配置。通常我們把它設置在 --global 級別。
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
- 設置特定倉庫的配置
假設你在一個工作項目中,需要使用工作郵箱。
# 首先進入你的項目倉庫
cd /path/to/work-project
# 設置本地配置 (可以省略 --local)
git config --local user.email "your.name@work-company.com"
現在,在這個倉庫里的所有提交都會使用你的工作郵箱,而其他倉庫則繼續使用全局配置的個人郵箱。
- 直接編輯配置文件
你可以直接用編輯器打開配置文件進行更復雜的操作,比如設置別名。
# 編輯全局配置文件
git config --global --edit
# 編輯本地倉庫配置文件
git config --local --edit
- 刪除配置項
# 語法: git config --[scope] --unset <配置項>
git config --global --unset user.name
一些有用的配置推薦:
除了基本的 user.name 和 user.email,以下是一些能極大提升效率的推薦配置。
- 設置默認編輯器
git config --global core.editor "vim" # 或者 "code --wait", "nano" 等
- 設置新倉庫的默認分支名
git config --global init.defaultBranch main
- 開啟彩色顯示
讓 git status, git diff 等命令的輸出更易讀。
git config --global color.ui auto
- 設置命令別名 (alias)
# 為常用命令設置短別名
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit# 創建一個自定義的、格式優美的 log 命令
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
設置后,你只需輸入 git st 就等同于 git status,輸入 git lg 就能看到漂亮的提交歷史。
- 配置 pull 的默認行為
避免產生不必要的合并提交,保持歷史線性。
git config --global pull.rebase true
注意:許多開發者會將他們的 ~/.gitconfig 文件(以及其他點文件,如 .bashrc, .vimrc)放在一個專門的 Git 倉庫(稱為 “dotfiles” 倉庫)里進行版本控制,這樣就可以輕松地在多臺機器之間同步他們的個性化配置
gitignore 文件
.gitignore 文件是一個純文本文件,它的核心作用是充當一個“忽略清單”或“黑名單”。你可以在這個文件中列出一些你希望 Git 完全忽略的文件或目錄的匹配模式。
我們需要忽略的文件通常包括:
- 編譯產生的文件:如 Java 的 .class、C/C++ 的 .o、.a、.so 以及可執行文件。這些文件可以由源代碼重新生成,體積大且沒有版本控制的必要。
- 依賴包和庫文件:如 Node.js 項目的 node_modules 目錄、Python 的虛擬環境目錄 venv、Java 的 Maven/Gradle 依賴包。這些依賴可以通過包管理工具(如 npm, pip, mvn)根據配置文件(如 package.json, requirements.txt, pom.xml)自動下載,提交它們會使倉庫變得異常臃腫。
- 日志和臨時文件:如*.log、*.tmp、*.swp (Vim交換文件) 等。
- 操作系統和IDE/編輯器的配置文件:如 macOS 的 .DS_Store、Windows 的 Thumbs.db、JetBrains IDE 的 .idea/ 目錄、VS Code 的 .vscode/ 目錄(除非團隊需要共享特定配置)。
- 敏感信息文件:(極其重要) 包含密碼、API密鑰、數據庫連接字符串、私鑰等敏感數據的文件,如 .env、credentials.json、settings.local.py。將這些文件提交到公共倉庫會導致嚴重的安全漏洞。
使用方法
-
創建文件
在你的項目根目錄下,創建一個名為 .gitignore 的文本文件。注意,它以點 . 開頭,在 Linux/macOS 下是一個隱藏文件。 -
添加規則
在 .gitignore 文件中,每行寫一個匹配模式。Git 會根據這些模式來判斷是否要忽略某個文件或目錄。
基本規則:
- 空行或以 # 開頭的行會被忽略,可以作為注釋使用。
- 可以直接寫文件名,如 debug.log,會忽略所有目錄下的 debug.log 文件。
- 可以使用標準的 glob 模式(類似 shell 的通配符):
- *:匹配零個或多個字符。例如,*.log 會忽略所有以 .log 結尾的文件。
- ?:匹配一個任意字符。例如,file?.txt 會忽略 file1.txt、fileA.txt 等。
- []:匹配方括號中的任意一個字符。例如,[ab].log 會匹配 a.log 和 b.log。
- 目錄:在模式后面加上斜杠 / 表示這是一個目錄。例如,node_modules/ 會忽略整個 node_modules 目錄。即使不加 /,Git 通常也能正確識別目錄,但加上 / 是更明確、更推薦的做法。
- 否定模式:在模式前加上感嘆號 ! 表示不要忽略。這可以用來對之前的忽略規則設置例外。
# 例如,你想忽略所有 .log 文件,但保留 important.log:
*.log
!important.log
- 路徑分隔符:
- 如果模式不包含斜杠 /,它會匹配任何路徑下的同名文件/目錄。例如,tmp 會匹配 ./tmp、src/tmp 等。
- 如果模式以斜杠 / 開頭,它只匹配相對于項目根目錄的路徑。例如,/debug.log 只會忽略項目根目錄下的 debug.log,而不會忽略 src/debug.log。
- 如果模式中間包含斜杠 /,它也會被看作是相對于項目根目錄的路徑。例如,logs/debug.log。
- 雙星號 :可以匹配任意多層目錄。例如,/logs 會匹配項目下任何深度的 logs 目錄。
注意事項:
- .gitignore 文件本身應該被提交:將 .gitignore 文件提交到倉庫中,這樣團隊中的每個成員都能共享同一套忽略規則,保證了協作的一致性。
- 如果文件已經被跟蹤了怎么辦?:這是一個非常常見的問題。如果你不小心把一個本應忽略的文件(如 config.local.js)提交到了倉庫,此時再把它加入 .gitignore 是無效的,因為 Git 已經開始跟蹤它了。
解決方法:你需要先從 Git 的跟蹤列表(暫存區)中移除它,然后再提交。
# 1. 從 Git 的跟蹤列表中移除文件,但保留本地的物理文件
git rm --cached config.local.js# 2. 將 config.local.js 添加到 .gitignore 文件中
echo "config.local.js" >> .gitignore# 3. 提交這次更改
git commit -m "Stop tracking config.local.js"
使用模板
為每一種語言或框架從頭編寫 .gitignore 文件是很繁瑣的。強烈推薦使用現成的模板。
- GitHub 的模板庫:github/gitignore 是一個非常全面的官方模板集合,包含了幾乎所有主流語言和框架的推薦配置。
- 在線生成工具:網站 toptal.com/developers/gitignore (原 gitignore.io) 可以讓你選擇你的技術棧(如 Node, Python, VSCode),然后自動為你生成一個非常完善的 .gitignore 文件。
習題部分
題目一:
# 克隆課程的github倉庫
git clone https://github.com/missing-semester-cn/missing-semester-cn.github.io.git# 查看最近一條README更改,這里檢測的是commit的說明內容
git log -n 1 README# 查看最近一條_config.yml更改,這里檢測的是commit的說明內容(這個命令不合題意)
git log -n 1 _config.yml# 查找collection行的修改,這里檢測的是內容
git blame _config.yml | grep collections# 輸出:
a88b4eac (Anish Athalye 2020-01-17 15:26:30 -0500 18) collections:# 顯示一下commit的說明內容(這里的--pretty是看了答案)
git show --pretty=format:"%s" a88b4eac | head -1
題目二:
這一題還挺有意義的,需要你學會如何刪除已經提交了的敏感數據,主要涉及git-filter-repo工具的使用。(讀題干給的鏈接)
第一種情況:本地上的commit還沒有上傳到github
# 建立一個秘密文件
echo "don't open" >> secret.txt# 上傳
git add secret.txt
git commit -m "add secret files"# 撤回(這次撤回將文件放回了緩沖區)
git reset --soft HEAD~1# 撤回緩沖區的內容
git restore --staged secret.txt
第二種情況:已經上傳到github了
git filter-repo --path secret.txt --invert-paths
答案上給的是這個(是filter-repo的前身filter-branch):
git filter-branch --force --index-filter\'git rm --cached --ignore-unmatch ./my_password' \--prune-empty --tag-name-filter cat -- --all
這是一個強大且危險的 Git 歷史重寫操作,其目的是從 Git 倉庫的所有分支和標簽的全部歷史記錄中,徹底刪除一個名為 my_password 的文件。
題目三:
關于slash之前已經說了
題目四:
git config --global alias.graph 'log --all --graph --decorate --oneline'
題目五:
git config --global core.excludesfile ~/.gitignore
echo ".DS_Store" >> ~/.gitignore
放在最后:其實Git里的命令非常非常多,常用的還有cat-file、bisect等等,一篇筆記是肯定寫不完,之后有機會再補充。