git簡單介紹
git
是一個分布式版本控制軟件,最初由linus torvalds
創作,于2005年以gpl
發布。最初目的是為更好地管理linux
內核開發而設計。
git工作流程以及各個區域
- workspace:工作區
- staging/index:暫存區
- local repository:本地倉庫(可修改)
- /refs/remotes:遠程倉庫的引用(不可修改)
- remote:遠程倉庫
git文件狀態變化
git各種命令
git簡單命令
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
|
# 在當前目錄新建一個git倉庫 git init # 打開git倉庫圖形界面 gitk # 顯示所有變更信息 git status # 刪除所有untracked files git clean -fd # 下載遠程倉庫的所有更新 git fetch remote # 下載遠程倉庫的所有更新,并且merge git pull romote branch-name # 查看上次commit id git rev-parse head # 將指定分支合并到當前分支 git merge branch-name # 將最近的一次commit打包到patch文件中 git format -patch head ^ # 將patch文件 添加到本地倉庫 git am patch- file # 查看指定文件修改歷史 git blame file -name |
git常用命令
git clone
1
2
3
4
5
|
# 將遠程git倉庫克隆到本地 git clone url # 將遠程git倉庫克隆到本地 git clone -b branch url |
git stash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# 將修改過,未add到staging區的文件,暫時存儲起來 git stash # 恢復之前stash存儲的內容 git stash apply # 保存stash 并寫message git stash save "stash test" # 查看stash了哪些存儲 git stash list # 將stash@{1}存儲的內容還原到工作區 git stash apply stash@{1} # 刪除stash@{1}存儲的內容 git stash drop stash@{1} # 刪除所有緩存的stash git stash clear |
git config
1
2
3
4
5
6
7
8
9
|
# 配置git圖形界面編碼為utf-8 git config --global gui.encoding=utf-8 # 設置全局提交代碼的用戶名 git config --global user.name name # 設置全局提交代碼時的郵箱 git config --global user.email email # 設置當前項目提交代碼的用戶名 git config user.name name |
git remote
1
2
3
4
5
6
7
8
9
10
11
|
# 顯示所有遠程倉庫 git remote - v # 增加一個新的遠程倉庫 git remote add name url # 刪除指定遠程倉庫 git remote remove name # 獲取指定遠程倉庫的詳細信息 git remote show origin |
git add
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 添加所有的修改到staging區 git add . git add --all # 添加指定文件到staging區 git add file # 添加多個修改的文件到staging區 git add file1 file2 # 添加修改的目錄到staging區 git add dir # 添加所有src目錄下main開頭的所有文件到staging區 git add src /main * |
git commit
1
2
3
4
5
6
7
8
9
10
11
|
# 提交staging區的代碼到本地倉庫區 git commit -m "message" # 提交staging中在指定文件到本地倉庫區 git commit file1 file2 -m "message" # 使用新的一次commit,來覆蓋上一次commit git commit --amend -m "message" # 修改上次提交的用戶名和郵箱 git commit --amend --author= "name <email>" --no-edit |
git branch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
# 列出本地所有分支 git branch # 列出本地所有分支 并顯示最后一次提交的哈希值 git branch - v # 在-v 的基礎上 并且顯示上游分支的名字 git branch -vv # 列出上游所有分支 git branch -r # 新建一個分支,但依然停留在當前分支 git branch branch-name # 刪除分支 git branch -d branch-name # 設置分支上游 git branch -- set -upstream-to origin /master # 本地分支重命名 git branch -m old-branch new-branch |
git checkout
1
2
3
4
5
6
7
8
9
10
11
|
# 創建本地分支并關聯遠程分支 git checkout -b local -branch origin /remote-branch # 新建一個分支,且切換到新分支 git checkout -b branch-name # 切換到另一個分支 git checkout branch-name # 撤銷工作區文件的修改,跟上次commit一樣 git checkout commit- file |
git tag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 創建帶有說明的標簽 git tag -a v1.4 -m 'my version 1.4' # 打標簽 git tag tag-name # 查看所有標簽 git tag # 給指定commit打標簽 git tag tag-name commit- id # 刪除標簽 git tag -d tag-name |
git push
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
|
# 刪除遠程分支 git push origin :master # 刪除遠程標簽 git push origin --delete tag tag-name # 上傳本地倉庫到遠程分支 git push remote branch-name # 強行推送當前分支到遠程分支 git push remote branch-name --force # 推送所有分支到遠程倉庫 git push remote --all # 推送所有標簽 git push --tags # 推送指定標簽 git push origin tag-name # 刪除遠程標簽(需要先刪除本地標簽) git push origin :refs /tags/tag-name # 將本地dev分支push到遠程master分支 git push origin dev:master |
git reset
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# 將未commit的文件移出staging區 git reset head # 重置staging區與上次commit的一樣 git reset --hard # 重置commit代碼和遠程分支代碼一樣 git reset --hard origin /master # 回退到上個commit git reset --hard head ^ # 回退到前3次提交之前,以此類推,回退到n次提交之前 git reset --hard head ~3 回退到指定commit git reset --hard commit- id |
git diff
1
2
3
4
5
6
7
8
9
10
11
|
# 查看文件在工作區和暫存區區別 git diff file -name # 查看暫存區和本地倉庫區別 git diff --cached file -name # 查看文件和另一個分支的區別 git diff branch-name file -name # 查看兩次提交的區別 git diff commit- id commit- id |
git show
1
2
3
4
5
|
# 查看指定標簽的提交信息 git show tag-name # 查看具體的某次改動 git show commit- id |
git log
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# 指定文件夾 log git log --pretty= format : "%h %cn %s %cd" --author= "iisheng\|勝哥" -- date =short src # 查看指定用戶指定format 提交 git log --pretty= format : "%h %cn %s %cd" --author=iisheng -- date =short # 查看該文件的改動歷史 git log --pretty=oneline file # 圖形化查看歷史提交 git log --graph --pretty=oneline --abbrev-commit # 統計倉庫提交排名前5 git log --pretty= '%an' | sort | uniq -c | sort -k1 -n -r | head -n 5 # 查看指定用戶添加代碼行數,和刪除代碼行數 git log --author= "iisheng" --pretty=tformat: --numstat | awk '{ add += $1 ; subs += $2 } end { printf "added lines: %s removed lines : %s \n",add,subs }' |
git rebase
1
2
3
4
5
6
7
8
|
# 將指定分支合并到當前分支 git rebase branch-name # 執行commit id 將rebase 停留在指定commit 處 git rebase -i commit- id # 執行commit id 將rebase 停留在 項目首次commit處 git rebase -i --root |
git restore
1
2
3
4
5
|
# 恢復第一次add 的文件,同 git rm --cached git restore --staged file # 移除staging區的文件,同 git checkout git restore file |
git revert
1
2
3
4
5
6
7
8
|
# 撤銷前一次commit git revert head # 撤銷前前一次commit git revert head ^ # 撤銷指定某次commit git revert commit- id |
git騷操作
git命令不能自動補全?(mac版)
我見過有的人使用git
別名,反正因為有自動補全的存在,我從來沒用過git
別名。不過我的確將我的rm -rf
命令替換成了別的腳本了...
安裝bash-completion
brew install bash-completion
添加 bash-completion 到 ~/.bash_profile
:
1
2
3
|
if [ -f $(brew --prefix) /etc/bash_completion ]; then . $(brew --prefix) /etc/bash_completion fi |
shell
有不同種類,我這里使用的是bash
。
代碼沒寫完,突然要切換到別的分支怎么辦?
暫存未提交的代碼
1
|
git stash |
還原暫存的代碼
1
|
git stash apply |
怎么合并其他分支的指定commit?
使用cherry-pick
命令
1
|
git cherry-pick 指定commit- id |
本地臨時代碼不想提交,怎么一次性清空?
還原未commit
的本地更改的代碼
1
|
git reset --hard |
還原包含commit
的代碼,到跟遠程分支相同
1
|
git reset --hard origin /master |
已經提交的代碼,不需要了,怎么當做沒提交過?
還原到上次commit
1
|
git reset --hard head ^ |
還原到當前之前的幾次commit
1
|
git reset --hard head ~2 |
強制推送到遠程分支,確保沒有其他人在push
,不然可能會丟失代碼
1
|
git push origin develop --force |
歷史commit作者郵箱寫錯了,怎么一次性改過來?
使用git filter-branch
命令。
復制下面的腳本,替換相關變量
-
old_email
-
correct_name
-
correct_email
腳本如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#!/bin/sh git filter-branch -- env -filter ' old_email= "your-old-email@example.com" correct_name= "your correct name" correct_email= "your-correct-email@example.com" if [ "$git_committer_email" = "$old_email" ] then export git_committer_name= "$correct_name" export git_committer_email= "$correct_email" fi if [ "$git_author_email" = "$old_email" ] then export git_author_name= "$correct_name" export git_author_email= "$correct_email" fi ' --tag-name-filter cat -- --branches --tags |
強制推送替換
1
|
git push --force --tags origin 'refs/heads/*' |
不小心把不該提交的文件commit了,怎么永久刪除?
也是使用git filter-branch
命令。
1
2
3
|
git filter-branch --force --index-filter \ "git rm --cached --ignore-unmatch file-path-and-name" \ --prune-empty --tag-name-filter cat -- --all |
強制推送覆蓋遠程分支。
1
|
git push origin --force --all |
強制推送覆蓋遠程tag
。
1
|
git push origin --force --tags |
怎么保證團隊成員提交的代碼都是可運行的?
這里想說的是使用git hooks
,一般在項目目錄.git/hooks
,客戶端可以使用hooks
,控制團隊commit
提交規范,或者push
之前,自動編譯項目校驗項目可運行。服務端可以使用hooks
,控制push之后自動構建項目,merge
等自動觸發單元測試等。
git reset --hard
命令,執行錯了,能恢復嗎?
查看當前commit log
誤操作git reset --hard 8529cb7
執行git reflog
還原到之前的樣子
公司使用gitlab,平時還用github,多賬號ssh,如何配置?
編輯 ~/.ssh/config
文件 沒有就創建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
# github host github.com port 22 hostname github.com preferredauthentications publickey addkeystoagent yes identityfile ~/. ssh /github_id_rsa usekeychain yes user iisheng # gitlab host gitlab.iisheng.cn port 22 hostname gitlab.iisheng.cn preferredauthentications publickey addkeystoagent yes identityfile ~/. ssh /gitlab_id_rsa usekeychain yes user iisheng |
git commits歷史如何變得清爽起來?
多用git rebase
。
比如,開發分支是feature
,主干分支是master
。我們在進行代碼合并的時候,可以執行下面的命令。
1
2
3
4
5
|
# 切換當前分支到feature git checkout feature # 將當前分支代碼變基為基于master git rebase master |
然后我們再切換到master
分支,執行git merge feature
,就可以進行快進式合并了,commmits
歷史就不會有交叉了。后文我們會詳細講解。
git rebase
會更改commit歷史,請謹慎使用。
下面的圖是guava
項目的commit
記錄。
如何修改已經提交的commit信息?
原始git
提交記錄是這樣的
執行git rebase -i 070943d
,對指定commitid之前的提交,進行修改
修改后git
提交記錄變成了這樣
git rebase -i
非常實用,還可以將多個commit合并成一個等很多事情,務必要記下。
不小心執行了git stash clear
怎么辦?
1
|
git fsck --lost-found |
執行之后,可以找到相關丟失的commit-id
,然后merge
一下即可。
該命令上可以找回git add
之后被弄丟的文件。
啥?你沒執行過git add
代碼就丟了?別怕,一般編譯器有local history
趕緊去試試吧。
詳解git merge
我們執行git merge
命令的時候,經常會看到fast-forward
字樣,fast-forward
到底是個什么東西?
其實,git merge
一般有三種場景。
快進式合并
舉個栗子,假如初始存在master
和hotfix
分支是這樣的。
然后我們在hotfix
分支加了些代碼,分支變成這樣了。
這個時候,我們將hotfix
分支,merge
到master
,即執行git merge hotfix
。
由于的分支hotfix
所指向的提交c3
是c2
的直接后繼, 因此git
會直接將指針向前移動。換句話說,如果順著一個分支走下去能夠到達另一個分支,那么git
在合并兩者的時候, 只會簡單的將指針向前推進(指針右移),因為這種情況下的合并操作沒有需要解決的分歧——這就叫做 快進(fast-forward)。
三方合并
再舉個栗子,假如初始存在feature
和master
分支情況是這樣的。
然后我們在feature
分支加了些代碼,而master
分支也有人加了代碼,現在分支變成這樣了。
這個時候,我們將feature
分支,merge
到master
,即執行git merge feature
。
和之前將分支指針向前推進所不同的是,git
將此次三方合并的結果做了一個新的快照并且自動創建一個新的提交指向它。這個被稱作一次合并提交,它的特別之處在于他有不止一個父提交。
所以我們也知道了,為什么有的時候merge之后會產生新的commit,而有的時候沒有。
遇到沖突時的合并
如果在兩個分支分別對同一個文件做了改動,git
就沒法直接合并他們。當遇到沖突的時候,git
會自動停下來,等待我們解決沖突。就像這樣
1
2
3
4
|
$ git merge dev auto-merging 111.txt conflict (content): merge conflict in 111.txt automatic merge failed; fix conflicts and then commit the result. |
我們可以在合并沖突后的任意時刻使用git status
命令來查看那些因包含合并沖突而處于未合并unmerged
狀態的文件。
1
2
3
4
5
6
7
8
9
10
11
|
$ git status on branch master you have unmerged paths. (fix conflicts and run "git commit" ) (use "git merge --abort" to abort the merge) unmerged paths: (use "git add <file>..." to mark resolution) both modified: 111.txt no changes added to commit (use "git add" and /or "git commit -a" ) |
待解決沖突的文件git
會以未合并的狀態標識出來,出現沖突的文件會出現一些特殊的區段,看起來像下面的樣子。
1
2
3
4
5
|
<<<<<<< head 111aaa ======= 111b >>>>>>> dev |
<<<<<<<
后面的是當前分支的引用,我們的例子中,就代表master
分支。>>>>>>>
后面表示的是要合并到當前分支的分支,即dev
分支。=======
的上半部分,表示當前分支的代碼。下半部分表示dev
分支的代碼。
我們可以把上面的測試內容改成下面的樣子來解決沖突
1
|
111aaa |
在解決了所有文件里的沖突之后,對每個文件使用git add
命令來將其標記為沖突已解決。
解決沖突的過程中,每一步都可以執行git status
查看當前狀態,git
也會給出相應提示,進行下一步操作。當我們所有的文件都暫存之后時,執行git status
時,git
會給我們看起來像下面的這種提示
1
2
3
4
|
$ git status on branch master all conflicts fixed but you are still merging. (use "git commit" to conclude merge) |
然后,我們根據提示執行git commit
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
merge branch 'dev' # conflicts: # 111.txt # # it looks like you may be committing a merge. # if this is not correct, please remove the file # .git/merge_head # and try again. # please enter the commit message for your changes. lines starting # with '#' will be ignored, and an empty message aborts the commit. # # on branch master # all conflicts fixed but you are still merging. # |
然后,我們保存這次提交就完成了這次沖突合并。
詳解git rebase
rebase做了什么
舉個栗子。我們同樣用剛才merge
的場景。
如果不用rebase
,使用merge
是下面這樣的,合并分支的時候會產生一個合并提交,而且會有分支交叉的情況。
使用rebase
是下面這樣的。
然后,切換到master
分支,進行一次快進式合并。
變基實際上就是基于其他分支重塑當前分支。變基之后,當前分支就相當于是基于最新的其他分支新加了一些commit,這樣的話就可以進行快進式合并了。
rebase原理
它的原理是首先找到這兩個分支(即當前分支 dev
、變基操作的目標基底分支master
)的最近共同祖先 c2
,然后對比當前分支相對于該祖先的歷次提交,提取相應的修改并存為臨時文件, 然后將當前分支指向目標基底c3
, 最后以此將之前另存為臨時文件的修改依序應用,也就是在c3
后面添加c4'
、c5'
。
git對象與快照
提到git
,總有人會說快照,快照是個什么鬼?
實際上,git
是一個內容尋址文件系統,其核心部分是一個簡單的鍵值對數據庫。將git
中的對象,存儲在.git/objects
目錄下。
git
對象主要分為,數據對象(blob object)、樹對象(tree object)、提交對象(commit object)、標簽對象(tag object)。
數據對象
我們新建一個目錄,然后在該目錄下執行git init
初始化一個git
項目。
然后,查看.git/objects
目錄下都有什么。
1
2
3
4
|
$ find .git /objects .git /objects .git /objects/pack .git /objects/info |
接著,我們寫一個文件echo '1111' > 111.txt
,并執行git add
之后,再查看。
1
2
3
4
5
6
|
$ find .git /objects .git /objects .git /objects/5f .git /objects/5f/2f16bfff90e6620509c0cf442e7a3586dad8fb .git /objects/pack .git /objects/info |
我們發現.git/objects
目錄下,多了個文件和目錄。實際上,git
會將我們的文件數據外加一個頭部信息header
一起做sha-1
校驗運算而得到校驗和。然后,校驗和的前2個字符用于命名子目錄,余下的38個字符則用作文件名。
我們可以使用下面的命令,顯示在git
對象中存儲的內容。
1
2
|
$ git cat - file -p 5f2f16bfff90e6620509c0cf442e7a3586dad8fb 1111 |
這就是我們在上文寫入的文件內容。
上述類型的對象稱之為數據對象(blob object)。數據對象,僅保存了文件內容,而文件名字沒有被保存。
樹對象
數據對象大致對應unix
中的inodes
或文件內容,樹對象則對應了unix
中的目錄項。一個樹對象包含了一條或多條樹對象記錄(tree entry),每條記錄含有一個指向數據對象或者子樹對象的sha-1
指針,以及相應的模式、類型、文件名信息。
通常,git
根據某一時刻暫存區(即index
區域)所表示的狀態創建并記錄一個對應的樹對象。
當我們執行過git add
之后,暫存區就有內容了,我們可以通過git
底層命令,生成樹對象。
1
2
|
$ git write-tree b716c7b049ccd9048b0566a57cfd516c17c1e39f |
查看該樹對象的內容。
1
2
|
$ git cat - file -p b716c7b049ccd9048b0566a57cfd516c17c1e39f 100644 blob 5f2f16bfff90e6620509c0cf442e7a3586dad8fb 111.txt |
提交對象
數據對象保存了數據的內容,樹對象可以表示當前目錄的快照。但是,若想重用這些快照,必須記住樹對象的sha-1
哈希值。而且,我們也不知道是誰保存了這些快照,在什么時刻保存的,以及為什么保存這些快照。而以上這些,正是
提交對象(commit object)
能保存的基本信息。
我們對當前暫存區進行一次提交,git commit -m "first commit"
。
然后查看一下log
找到該次提交的commit
哈希值。
1
2
|
$ git log --oneline 5281f7e ( head -> master) first commit |
接著,我們查看一下該提交對象的內容。
1
2
3
4
5
6
|
$ git cat - file -p 5281f7e tree b716c7b049ccd9048b0566a57cfd516c17c1e39f author iisheng <***@gmail.com> 1596073568 +0800 committer iisheng <***@gmail.com> 1596073568 +0800 first commit |
提交對象的格式很簡單:它先指定一個頂層樹對象,代表當前項目快照;然后是可能存在的父提交(前面描述的提交對象并不存在任何父提交);之后是作者/提交者信息(依據你的user.name
和user.email
配置來設定,外加一個時間戳);留空一行,最后是提交注釋。
標簽對象
標簽對象(tag object) 非常類似于一個提交對象——它包含一個標簽創建者信息、一個日期、一段注釋信息,以及一個指針。主要的區別在于,標簽對象通常指向一個提交對象,而不是一個樹對象。它像是一個永不移動的分支引用——永遠指向同一個提交對象,只不過給這個提交對象加上一個更友好的名字罷了。
實際上git
中的各種對象都是類似的,只不過因為各種對象自身功能不同,存儲結構不同而已。
git引用-我從遠程拉的代碼不是最新的?
git
引用相當于是git
中特定哈希值的別名。一長串的哈希值不是很友好,但是起個別名,我們就可以像這樣git show master
、git log master
的去使用他們。
git
中的引用存儲在.git/refs
目錄下。我們可以執行find .git/refs/
查看當前git
項目中都存在哪些引用。
head引用
在.git
目錄下有一個名字叫做head
的文件,head
文件通常是一個符號引用(symbolic reference)指向目前所在的分支。所謂符號引用,表示它是一個指向其他引用的指針。
如果我們在工作區checkout
一個sha-1
值,head
引用也會指向這個包含git
對象的sha-1
值。
標簽引用
git
標簽分為,附注標簽和輕量標簽。輕量標簽,使用 git tag v1.0
即可創建。附注標簽需要使用-a
選項,即git tag -a v1.0 -m "my version 1.0"
這種。
輕量標簽就是一個固定的引用。附注標簽需要創建標簽對象,并記錄一個引用來指向該標簽對象。
遠程引用
不熟悉git
的同學,可能會犯這樣一個錯誤。其他同學讓他拉取一下遠程最新的master
分支代碼,他可能直接用ide
找到本地的遠程分支的引用,也就是origin/master
,直接checkout
一個本地分支。
其實,origin/master
只是遠程分支的一個引用,不一定跟遠程分支代碼同步,我們可以用git fetch
或者git pull
來讓origin/master
和遠程分支同步。
參考文獻:
[1]:https://git-scm.com/
到此這篇關于git科普文,git基本原理及各種騷操作(推薦)的文章就介紹到這了,更多相關git基本原理內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://www.cnblogs.com/iisheng/p/13425658.html