Git 基礎學習系列
- Git 基礎 —— 安裝 配置 別名 對象
- Git 基礎 —— 常用命令
- Git 基礎 —— 常見使用場景
- Git基礎 —— Github 的使用
git init
創建 Git 本地倉庫
遠端無倉庫,本地無倉庫,本地新建一個倉庫
git init git_learning
遠端有倉庫,本地無倉庫,拉取遠端倉庫到本地
git clone git@github.com:Michael728/michael-git.git
cd michael-git
# 提交一個 readme 文件
touch README.md
git add README.md
git commit -m "add README"
git push -u origin master
遠端有空倉庫,本地已有項目文件,關聯遠端倉庫
查看我們當前項目有哪些遠程倉庫可以執行如下命令:
git remote -v
如果發現沒有關聯遠端倉庫,可以這么做:
cd micahel-git
git init # 如果本地已經是一個 Git 倉庫,這行就跳過
git remote add origin git@github.com:Michael728/michael-git.git # 添加一個遠端主機,并命名為 origin
git push -u origin --all # --all 表示 push all branches,-u 選項指定了一個默認主機
git push -u origin --tags # --tags All refs under refs/tags are pushed
將本地的master
分支推送到origin
主機,同時指定origin
為默認主機,后面就可以不要再指定遠端主機名 origin
了,直接使用git push
。
遠端主機名可以定義為其他,比如 github
。通過 git remote add
命令,一個倉庫其實可以與多個遠端倉庫發生關聯的,這時候只要遠端主機名取不一樣的即可區別。為什么要給遠程倉庫取名字?因為我們可能一個項目有多個遠程倉庫,比如,Github一個,比如公司一個,這樣的話,提交的時候可以提交到不同的遠程倉庫就需要指定不同的倉庫名字了。
參考:
- git push 的 -u 參數具體適合含義?
- Git遠程操作詳解
git clone
下載一個遠程倉庫:
git clone [-b br_name] <git@github.com:Michael728/michael-git.git> [本地倉庫名]
克隆的時候,可以指定下載遠端的分支、自定義本地倉庫的名字。如果不加分支名參數,git clone
命令會默認自動設置本地 master
分支跟蹤克隆的遠程倉庫的 master
分支(其實是倉庫的默認分支,大部分倉庫默認分支是 master)。同時,默認遠端主機設置別名為 origin
。
git mv
文件重命名:
git mv <old filename> <new filename>
git branch
git branch -r
只顯示遠端分支,git branch -a
顯示本地分支和遠程分支
新建分支
新建 develop 分支,并切換到 develop 分支:
git branch develop
git checkout develop
# 新建并切換分支
git checkout -b develop
本地分支推送到遠端倉庫
本地分支推送到遠程服務器時,遠程分支自動創建,推送本地分支到遠程:
git push --set-upstream <remote_host_name> <local_branch_name>:<remote_branch_name>
<remote_host_name>
:遠程 Git 服務器名稱,一般為origin
<local_branch_name>
:本地分支名稱<remote_branch_name>
:遠程分支名稱--set-upstream
參數用來關聯本地分支和遠程分支
一般情況下,本地分支和遠程分支名稱相同,所以可簡化為:
git push --set-upstream <remote_host_name> <branch_name>
參考:
- Git創建遠程分支
- 阮一峰--Git遠程操作詳解
查看本地分支:
git branch # 查看本地分支
git branch -r # 查看遠端分支
git branh -av # 查看所有分支,信息詳細點
刪除分支:
刪除本地分支:
git branch -d develop
git branch -D develop # 強制刪除
刪除遠程分支:
git push origin :<remote_branch_name>
# 和如下命令等同
git push origin --delete <remote_branch_name>
git add
多個場景會用到這個命令:
- 可以用它開始跟蹤新文件
- 把已跟蹤的文件放到暫存區
- 還能用于合并時把有沖突的文件標記為已解決狀態,這個是在解決沖突時會用到的功能
常用命令:
git add -u
:將文件的修改、文件的刪除,添加到暫存區,用-u
有個好處,避免把工作區沒準備好的新文件直接加到暫存區了,用的較多;git add .
:將文件的修改,文件的新建,添加到暫存區,慎用;git add -A/--all
:將文件的修改,文件的刪除,文件的新建,添加到暫存區,慎用;
git add -A
相對于git add -u
命令的優點 : 可以提交所有被刪除、被替換、被修改和新增的文件到數據暫存區,而git add -u
只能操作跟蹤過的文件。
git diff
比較工作區和暫存區的差異
將工作區和暫存區所有文件進行比較:
git diff
只對某些文件和暫存區進行比較:
git diff -- README.md [filename ...]
比較暫存區和 HEAD 之間的差異
git diff --cached
# 或者
git diff --staged
比較的是工作區和HEAD之間的差異
git diff HEAD
比較兩個分支的差異
git diff master temp
只關心這兩個分支中某個文件的差異:
git diff master temp -- index.html
其實,分支名就是一個指針,就是一種引用,可以直接使用 commit id 比較:
$ git diff 622a8 7e7a -- index.html
注意了:git diff A B
比較的結果可以看做是 B-A
的差集,調換 A B
順序,正負號會有變化的。
git reset
暫存區文件的恢復
暫存區全部文件恢復成和 HEAD 一樣:
git reset HEAD
- reset 命令不加 --hard,則暫存區的內容恢復成HEAD對應的內容,工作區的變更繼續保留;
- 如果加了 --hard,則不管工作區還是暫存區,內容都變回HEAD對應的內容,危險的命令,會讓你在工作區的修改丟失;
git reset
有三個參數:
--soft
這個只是把 HEAD 指向的 commit 恢復到你指定的 commit,暫存區 工作區不變--hard
這個是 把 HEAD, 暫存區, 工作區 都修改為 你指定的 commit 的時候的文件狀態--mixed
這個是不加時候的默認參數,把 HEAD,暫存區 修改為 你指定的 commit 的時候的文件狀態,工作區保持不變
怎樣取消暫存區部分文件的修改?
git reset HEAD style.css
將工作區和暫存區保持一致
有時候修改了文件,已經保存到暫存區,之后又在工作區進行了修改,此時,發現工作的效果不如暫存區的效果好,想要將工作區和暫存區保持一致。
git checkout -- <file>...
其實,git status
都有友好的提示的:
- 如果想要變更工作區的內容,那么要想到和
checkout
命令相關; - 如果想要變更暫存區的內容,那么要想到和
reset
命令相關;
消除最近的幾次提交
丟棄一些 commit,直接HEAD 指向了你指定的某個 commit,同時,暫存區和工作區也恢復到哪個 commit 時的狀態:
git reset --hard <commitid>
有些 commit 會丟失,是條危險的命令,要慎用。但是當你明確了你的需求,需要將工作區暫存區提交記錄明確恢復到某個 commit 狀態時,可以執行這個命令。
git commit
「提交」操作。當你前面采用 add
命令將文件添加到暫存區跟蹤后,需要通過commit
將暫存區的內容提交到當前分支:
git commit -m "test"
當一些已被追蹤的文件修改后,常常需要git add file
,然后再git commit -m "xxxx"
,其實這兩個步驟可以合二為一:
git commit -am "test"
這么寫個人覺得挺好,可以有效避免有些懶人git add .
的方式,將一切文件都添加到了暫存區,導致最后多余文件提交入庫。
amend
修改最近一次 commit 的 message:
git commit --amend
修改完 message 信息之后,保存退出即可
git commit --amend
命令本質上是用新的 commit 應該是替代了上一次的提交,不只是修改 message。比如上一次提交時有幾個文件沒有 add 以及 commit,可以重新進行 add 之后再 commit --amend
提交。但這次提交之后,在分支的 git log 中,不會增加一次新的 commit(因為被替換了嘛),看著效果相當于在父 commit 的基礎上進行的修改。
修改歷史提交的 message 信息:
如圖,想要修改 be4c 這次提交的 message 信息,那么可以使用 git rebase
命令,因為 be4c
這次提交會被新的提交替換掉,所以,「變基」操作的「基」要選擇它的父提交,85807
:
git rebase -i 8580
-i
會進入交互模式,有一系列指令操作對應的 commit,不要用pick
命令,而是使用reword
命令操作add ref
那次 commit,然后保存,就進入修改 message 的窗口,修改完再保存,最后就會 OK 了。
$ git rebase -i 8580
[detached HEAD b2b5486] Add ref projectDate: Mon Jan 14 23:56:14 2019 +08001 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/master.
git log -3
查看最近的3次提交,變為這樣了,會發現,倒數第二次的 message 信息修改 OK 了,最新一次的 message 雖然沒變,但其實,commit id
都發生了變化,「替換」的概念要記得。
git rebase 工作的過程中,就是用了「分離頭指針」。rebase 意味著基于新 base 的 commit 來變更部分 commits。它處理的時候,把 HEAD 指向base 的 commit,此時如果該 commit 沒有對應branch,就處于分離頭指針的狀態,然后重新一個一個生成新的 commit,當rebase 創建完最后一個 commit 后,結束分離頭狀態,Git 讓變完基的分支名指向 HEAD
PS:對于團隊中公用的分支,例如發布分支等,禁用 rebase,因為這樣會破壞歷史的 commit 信息的,將來要溯源、基于構建歷史拉取補丁分支等就會帶來極大不便。
連續多個 commit 合并
目前 commit 還在本地,沒有 push 到團隊分支上,想要將網頁相關的 commit 合并成一個,就是從圖中 55a9
開始的6個 commit 合成一個:
git rebase -i 7e7a
$ git rebase -i 7e7a
[detached HEAD 1c102e6] Create a complete web pageDate: Mon Jan 14 23:46:26 2019 +08005 files changed, 20 insertions(+)create mode 100644 index.htmlcreate mode 100644 js/a.jscreate mode 100644 style.csscreate mode 100644 styles/a.csscreate mode 100755 大嘴猴-頭像-logo.jpg
Successfully rebased and updated refs/heads/master.
變基過程中有可能會遇到沖突的,只要解決沖突即可,解決沖突的時候,只需要先修改有沖突的文件的內容,然后執行 git add <file_with_conflict>
即可,不要再 git commit
,否則多出來 commit 的,然后接著 git base --continue
即可,參考簡書-git rebase解決合并沖突
歷史中不連續的 commit 合并
將歷史中和 readme 相關的 commit 合并,就是下圖中52af 7e7a 03be 這三個 commit。
git rebase -i 52af # 因為52af 是首個,沒有父親,因此需要補充一下
$ git status
interactive rebase in progress; onto 52af14b
Last command done (1 command done):pick 52af14b
Next commands to do (3 remaining commands):squash 7e7a518 modify readmesquash 03bef1e Modify readme to README file(use "git rebase --edit-todo" to view and edit)
You are currently rebasing branch 'master' on '52af14b'.(all conflicts fixed: run "git rebase --continue")nothing to commit, working tree clean# michael @ Michael-MBP in ~/Code/Git-Geek/git_learning on git:52af14b o [17:08:05]
$ git rebase --continue
[detached HEAD fe08624] Add readme.mdAuthor: Michael728 <michael@163.com>Date: Mon Jan 14 23:35:44 2019 +08001 file changed, 1 insertion(+)create mode 100644 README.md
Successfully rebased and updated refs/heads/master.
最終整理成了兩個 commit:
有意思的發現,有兩個 commit 是沒有祖先的:
如果將 temp 分支、js01 tag 刪掉,Git 會清理掉下面那個樹。
git pull
git pull命令的作用是,取回遠程主機某個分支的更新,再與本地的指定分支合并。它的完整格式稍稍有點復雜:
git pull <遠程主機名> <遠程分支名>:<本地分支名>
比如,取回 origin 主機的 next 分支,與本地的master分支合并:
git pull origin next:master
如果遠程分支是與當前分支合并,則冒號后面的部分可以省略:
git pull origin next
# 等價于下面兩個命令
git fetch origin
git merge origin/next
參考:
- 阮一峰-Git遠程操作詳解
git push
git push
命令用于將本地分支的更新,推送到遠程主機:
$ git push <遠程主機名> <本地分支名>:<遠程分支名>
如果省略遠程分支名,則表示將本地分支推送與之存在"追蹤關系"的遠程分支(通常兩者同名),如果該遠程分支不存在,則會被新建。
$ git push origin master
上面命令表示,將本地的 master 分支推送到 origin 主機的 master 分支。如果后者不存在,則會被新建。
不管是否存在對應的遠程分支,將本地的所有分支都推送到遠程主機,這時需要使用 --all
選項:
$ git push --all origin
git push
不會推送標簽(tag
),除非使用 --tags
選項:
$ git push origin --tags
可能會遇到 rejected
的 error,因為遠端包含了一些本地是沒有的變更,比如,創建遠端倉庫時,在遠端倉庫的 master 分支上新建了文件,比如 License,而本地是沒有這次提交的:
把遠端拉取下來:
git fetch github master
語法:
git fetch <遠端主機名> <遠端分支名>
non-fast-forward
表示,你本地 master 分支的演進不是基于遠端 master 分支進行的,二者是割裂的,經過 fetch 之后,通過 gitk --all
可以看到:
解決這個問題,可以通過 rebase 或者 merge 的方式解決,現在先采用 merge 的方式:
# 切到本地 master 分支
$ git merge github/master
fatal: refusing to merge unrelated histories
$ git merge github/master --allow-unrelated-histories
Merge made by the 'recursive' strategy.LICENSE | 21 +++++++++++++++++++++1 file changed, 21 insertions(+)create mode 100644 LICENSE
上面 fetch 和 merge 方式,和如下 pull 命令等效:
$ git pull github master --allow-unrelated-histories
現在合并之后,分支演進如下:
可以看到merge 這種方式新生成的 commit 有兩個父親。
現在重新啟動將本地的 master 分支 push 到遠端:
git push github master
git rm
從 Git 中移除某個文件,就必須從已跟蹤的文件清單中刪除(從暫存區域移除文件),然后提交。可以使用 git rm
命令完成此項工作,并連帶從工作目錄中刪除指定的文件,以后這個文件就不會出現在 Git 庫中了。
當我們先把某文件從 Git 庫中刪除(亦即從暫存區移除),但仍然希望保留在當前工作目錄中。比如當你忘記在.gitignore
文件中將一些文件忽略,但是卻不小心把大的日志文件添加到暫存區域時,這一做法很有用:
# --cached 將 README 文件從暫存區移除,但是工作區目錄仍然保留
git rm --cached README
git checkout
基于某分支創建新分支:
git checkout -b <new_branch_name> <base_branch_name>
這里的 base_branch_name
是指創建分支時的「基」。
- 當省略時,就是基于當前分支創建;
git checkout -b <new_branch_name>
- 當這個「基」是遠端分支名時,就實現了在本地基于遠端分支創建分支。
git branch -av # 查看本地+遠程分支列表
git checkout -b dev origin/dev
還可以可以在checkout
命令中使用 Hash 值作為起點創建分支:
git checkout -b <name_of_branch> <commit id>
除了有“切換”的意思,checkout
還有一個撤銷的作用。
舉個例子,假設我們在一個分支開發一個小功能,剛寫完一半,這時候需求變了,而且是大變化,之前寫的代碼完全用不了,好在你剛寫,甚至都沒有 git add
進暫存區,這個時候很簡單的一個操作就直接把原文件還原:
git checkout <filename>
參考:在git中checkout歷史版本
git log
查看版本演變歷史:
git log --pretty=oneline # 檢查提交日志,都在一行:<commit id> <message>
git log --oneline # 與上面命令等價
查看某人的提交:
git log --author=michael
- 一個常用的選項是
-p
,用來顯示每次提交的內容差異; - 可以加上
-2
或者-n2
來僅顯示最近兩次提交:
git log -n2 # 查看最近的2次提交
git log -p -2
git log -n1 --format=format:%h # 查看當前分支最新的 commit id 縮略值
列出最近兩周內的提交:
git log --since=2.weeks
圖形化查看分支演變:
# 加了 --all 表示查看所有分支的歷史,否則只能看到當前分支的演變歷史
git log --all --graph
# 只查看指定分支的演變歷史,比如 temp 分支,此時就不能使用參數 --all
git log --oneline --graph temp
- git log命令全解析,打log還能這么隨心所欲!
- git-scm 2.3 Git 基礎 - 查看提交歷史
遠程倉庫的使用
查看遠程倉庫
git remote -v
如果想查看遠程倉庫更多的信息,可以使用git remote show <remote-name>
命令。
遠程倉庫的移除與重命名:
git remote rename <old-remote-name> <new-remote-name>
git remote rename pb paul
如果因為一些原因要移除一個遠程倉庫,可以使用git remote rm <remote-name>
。
添加一個新的遠程 Git 倉庫,同時指定一個可以輕松引用的簡寫:
git remote add <remote_host_name> <url>
這里的remote_host_name
常常取名為origin
。所以,常見的origin
其實是一個你 Git 倉庫跟蹤的遠程倉庫的簡寫。
拉取遠端倉庫有但你本地沒有的信息:
git fetch <remote_host_name>
如果你使用clone
命令克隆了一個倉庫,命令會自動將其添加為遠程倉庫并默認以origin
為縮寫。
Tag
列出標簽
git tag # 列出所有標簽
git tag -l 'v1.8*' # 列出以 v1.8 開頭的所有標簽
創建標簽
Git使用兩種主要類型的標簽:
- 附注(annotated)標簽
- 輕量(ightweight)標簽
前者會包括一些注釋信息,來進一步解釋這個 tag 的作用,而后者就僅僅只是一個 tag 的名字
附注標簽
git tag -a v1.4 -m 'my version 1.4'
通過git show <tag-name>
命令可以看到標簽信息
輕量標簽
git tag v1.4
沒用-a
、-m
的參數,只需要提供標簽名字
刪除標簽
git tag -d <tagname>
補打標簽
假設忘記給項目打標簽,可以在之后加上:
基于某歷史節點的commit id
補打Tag
:
git tag -a v1.2 <commit id>
共享標簽
默認情況下,git push
命令并不會傳送標簽到遠程服務器上。在創建完標簽后你必須顯示地推送標簽到共享服務器上。這個過程就像共享遠程分支一樣,可以運行git push origin [tagname]
如果想要一次性推送很多標簽,也可以使用--tags
選項的git push
:
git push origin --tags
檢出標簽
git checkout -b <new_br> <tag_name>
參考:
- The Junior Git
- 6 Git 基礎 - 打標簽
最后
- Git使用教程筆記
- Pro Git 中文
- Git常用命令備忘
- 掘金-今年下半年,中日合拍的《Git游記》即將正式開機,我將...(上集)
- git術語解釋staging,index,cache
- pcottle/learnGitBranching 分支演示網站