章節 ▾ 第二版

5.3 分散式 Git - 維護專案

維護專案

除了知道如何有效地為專案貢獻之外,你很可能還需要知道如何維護一個專案。這包括接受並應用透過 format-patch 生成並已傳送至你的補丁,或者整合你已新增到專案作為遠端倉庫的遠端分支中的更改。無論你是維護一個規範的倉庫,還是想透過驗證或批准補丁來提供幫助,你都需要知道如何以一種對其他貢獻者最清晰、對你自己長期可持續的方式來接受工作。

在主題分支上工作

當你想整合新工作時,通常最好在一個主題分支上進行嘗試——即一個專門用於嘗試這項新工作的臨時分支。這樣,你就可以輕鬆地單獨修改補丁,如果它不起作用,就暫時擱置,直到有時間回來處理。如果你建立一個基於你打算嘗試的工作主題的簡單分支名稱,例如 ruby_client 或其他描述性的名稱,那麼如果你暫時放棄它,稍後回來處理時,很容易記住它。Git 專案的維護者傾向於為這些分支新增名稱空間——例如 sc/ruby_client,其中 sc 是貢獻者姓名的縮寫。正如你記得的,你可以基於你的 master 分支建立分支,如下所示:

$ git branch sc/ruby_client master

或者,如果你想立即切換到它,可以使用 checkout -b 選項:

$ git checkout -b sc/ruby_client master

現在,你就可以將收到的貢獻工作新增到這個主題分支,並決定是否將其合併到你更長期的分支中。

應用電子郵件補丁

如果你透過電子郵件收到一個需要整合到你專案中的補丁,你需要在你的主題分支上應用該補丁進行評估。應用電子郵件補丁有兩種方法:使用 git applygit am

使用 apply 應用補丁

如果你從使用 git diff 或 Unix diff 命令的變體生成補丁的人那裡收到補丁(不推薦;請參閱下一節),你可以使用 git apply 命令來應用它。假設你將補丁儲存在 /tmp/patch-ruby-client.patch,你可以這樣應用補丁:

$ git apply /tmp/patch-ruby-client.patch

這會修改你工作目錄中的檔案。它幾乎等同於執行 patch -p1 命令來應用補丁,但它更謹慎,接受的模糊匹配比 patch 少。如果 git diff 格式描述了檔案新增、刪除和重新命名,它也能處理,而 patch 做不到。最後,git apply 是一個“全部應用或全部中止”的模型,要麼全部應用,要麼都不應用;而 patch 可以部分應用補丁檔案,使你的工作目錄處於一種奇怪的狀態。總的來說,git applypatch 更保守。它不會為你建立一個提交——執行之後,你必須手動暫存並提交引入的更改。

你也可以使用 git apply 來檢視補丁是否能幹淨地應用,然後再嘗試實際應用——你可以執行 git apply --check 加上補丁檔案:

$ git apply --check 0001-see-if-this-helps-the-gem.patch
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply

如果沒有輸出,則補丁應該可以乾淨地應用。此命令在檢查失敗時也會以非零狀態退出,所以如果你願意,可以在指令碼中使用它。

使用 am 應用補丁

如果貢獻者是 Git 使用者,並且足夠好地使用了 format-patch 命令來生成他們的補丁,那麼你的工作就更輕鬆了,因為補丁包含了作者資訊和你的提交資訊。如果可能,鼓勵你的貢獻者使用 format-patch 而不是 diff 來為你生成補丁。你只需要為遺留補丁等情況使用 git apply

要應用 format-patch 生成的補丁,你需要使用 git am(該命令命名為 am,因為它用於“從郵箱應用一系列補丁”)。技術上講,git am 被設計用來讀取 mbox 檔案,這是一個簡單的純文字格式,用於在一個文字檔案中儲存一個或多個電子郵件訊息。它看起來是這樣的:

From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] Add limit to log function

Limit log functionality to the first 20

這是你在上一節看到的 git format-patch 命令的輸出開頭;它也代表一個有效的 mbox 電子郵件格式。如果有人透過 git send-email 正確地向你傳送了補丁,並且你將其下載為 mbox 格式,那麼你就可以將 mbox 檔案指向 git am,它將開始應用它看到的所有補丁。如果你使用的郵件客戶端可以將多個電子郵件儲存為 mbox 格式,你可以將整個補丁系列儲存到一個檔案中,然後使用 git am 一次應用一個。

但是,如果有人將透過 git format-patch 生成的補丁檔案上傳到票務系統或其他類似系統中,你可以將檔案本地儲存,然後將儲存在磁碟上的檔案傳遞給 git am 來應用它:

$ git am 0001-limit-log-function.patch
Applying: Add limit to log function

你可以看到它乾淨地應用了,並且自動為你建立了新的提交。作者資訊是從郵件的 FromDate 標頭中獲取的,提交資訊是從郵件的主題和正文(在補丁之前)中獲取的。例如,如果這個補丁是從上面的 mbox 示例中應用的,生成的提交可能看起來像這樣:

$ git log --pretty=fuller -1
commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0
Author:     Jessica Smith <jessica@example.com>
AuthorDate: Sun Apr 6 10:17:23 2008 -0700
Commit:     Scott Chacon <schacon@gmail.com>
CommitDate: Thu Apr 9 09:19:06 2009 -0700

   Add limit to log function

   Limit log functionality to the first 20

Commit 資訊表明了應用補丁的人和應用的時間。Author 資訊是最初建立補丁的個人和最初建立的時間。

但有可能補丁無法乾淨地應用。也許你的主分支已經與補丁構建所基於的分支相差太遠,或者補丁依賴於你尚未應用的另一個補丁。在這種情況下,git am 過程將失敗並詢問你想要做什麼:

$ git am 0001-see-if-this-helps-the-gem.patch
Applying: See if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Patch failed at 0001.
When you have resolved this problem run "git am --resolved".
If you would prefer to skip this patch, instead run "git am --skip".
To restore the original branch and stop patching run "git am --abort".

與有衝突的合併或 rebase 操作類似,此命令會在遇到問題的任何檔案中放入衝突標記。你可以透過類似的方式解決此問題——編輯檔案以解決衝突,暫存新檔案,然後執行 git am --resolved 繼續下一個補丁。

$ (fix the file)
$ git add ticgit.gemspec
$ git am --resolved
Applying: See if this helps the gem

如果你希望 Git 更智慧地嘗試解決衝突,你可以傳遞一個 -3 選項,它會使 Git 嘗試進行三向合併。此選項預設不開啟,因為它在你倉庫中不存在補丁所說的基礎提交時不起作用。如果你確實有這個提交——如果補丁是基於一個公開提交的——那麼 -3 選項通常在應用衝突補丁時更明智。

$ git am -3 0001-see-if-this-helps-the-gem.patch
Applying: See if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
No changes -- Patch already applied.

在這種情況下,沒有 -3 選項,補丁將被視為衝突。由於使用了 -3 選項,補丁乾淨地應用了。

如果你正在從 mbox 檔案應用多個補丁,你也可以在互動模式下執行 am 命令,它會在找到每個補丁時停止,並詢問你是否要應用它:

$ git am -3 -i mbox
Commit Body is:
--------------------------
See if this helps the gem
--------------------------
Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all

如果你儲存了多個補丁,這會很有用,因為你可以先檢視補丁,如果你不記得它是什麼,或者如果你已經做過了,就不要應用補丁。

當你的主題的所有補丁都已應用並提交到你的分支中後,你可以選擇是否以及如何將它們整合到更長期的分支中。

檢出遠端分支

如果你的貢獻來自一個 Git 使用者,他設定了自己的倉庫,推送了多項更改到其中,然後將倉庫的 URL 和更改所在的遠端分支名稱發給了你,你可以將其新增為遠端倉庫並在本地進行合併。

例如,如果 Jessica 發郵件告訴你,她的倉庫中的 ruby-client 分支有一個很棒的新功能,你可以透過新增遠端倉庫並本地檢出該分支來測試它:

$ git remote add jessica https://github.com/jessica/myproject.git
$ git fetch jessica
$ git checkout -b rubyclient jessica/ruby-client

如果她稍後再次傳送郵件給你另一個包含另一個很棒功能的 الآخر分支,你可以直接 fetchcheckout,因為你已經設定好了遠端倉庫。

如果你一直與某人合作,這種方式最有用。如果某人偶爾只貢獻一個補丁,那麼透過電子郵件接受它可能比要求每個人執行自己的伺服器並需要不斷新增和刪除遠端倉庫來獲取少量補丁更耗時。你也不太可能想要數百個遠端倉庫,每個倉庫只對應貢獻一兩個補丁的人。然而,指令碼和託管服務可能會使這更容易——這很大程度上取決於你的開發方式和你貢獻者如何開發。

這種方法的另一個優點是你還可以獲得提交的歷史記錄。雖然你可能會遇到合法的合併問題,但你知道他們的工作是基於你歷史記錄中的哪個位置;一個正確的三向合併是預設的,而不是需要提供 -3 並希望補丁是基於你可以訪問的公共提交生成的。

如果你不是一直與某人合作,但仍想以這種方式從他們那裡拉取,你可以將遠端倉庫的 URL 提供給 git pull 命令。這只是一次性拉取,不會將 URL 儲存為遠端引用。

$ git pull https://github.com/onetimeguy/project
From https://github.com/onetimeguy/project
 * branch            HEAD       -> FETCH_HEAD
Merge made by the 'recursive' strategy.

確定引入的內容

現在你有一個包含貢獻工作的主題分支。此時,你可以確定你想要對它做什麼。本節將回顧幾個命令,以便你可以瞭解如何使用它們來準確地審查如果你將此分支合併到你的主分支中會引入什麼內容。

通常,獲取對該分支中存在但不在你的 master 分支中的所有提交的審查會很有幫助。你可以透過在分支名稱前新增 --not 選項來排除 master 分支中的提交。這與我們之前使用的 master..contrib 格式的作用相同。例如,如果你的貢獻者傳送了兩個補丁,你建立了一個名為 contrib 的分支並將這些補丁應用在那裡,你可以執行這個:

$ git log contrib --not master
commit 5b6235bd297351589efc4d73316f0a68d484f118
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Oct 24 09:53:59 2008 -0700

    See if this helps the gem

commit 7482e0d16d04bea79d0dba8988cc78df655f16a0
Author: Scott Chacon <schacon@gmail.com>
Date:   Mon Oct 22 19:38:36 2008 -0700

    Update gemspec to hopefully work better

要檢視每個提交引入了什麼更改,請記住你可以將 -p 選項傳遞給 git log,它會在每個提交後面附加引入的 diff。

要檢視如果你將此主題分支與另一個分支合併會發生什麼情況的完整 diff,你可能需要使用一個奇怪的技巧來獲得正確的結果。你可能會想到執行這個:

$ git diff master

此命令會給你一個 diff,但這可能具有誤導性。如果你的 master 分支在你從它建立主題分支後向前推進了,那麼你會得到看似奇怪的結果。這是因為 Git 直接比較了你所在主題分支的最後一個提交和 master 分支的最後一個提交的快照。例如,如果你在 master 分支的檔案中添加了一行,直接比較快照會顯示主題分支將要刪除該行。

如果 master 是你主題分支的直接祖先,這不成問題;但如果兩個歷史記錄已經分叉,diff 將顯示你正在新增主題分支中的所有新內容,並刪除 master 分支中獨有的所有內容。

你真正想看到的是主題分支中新增的更改——如果你將此分支與 master 合併時將引入的工作。你可以透過讓 Git 比較你主題分支的最後一個提交與它與 master 分支的第一個共同祖先來做到這一點。

從技術上講,你可以透過明確找出共同祖先,然後在此基礎上執行你的 diff 來做到這一點:

$ git merge-base contrib master
36c7dba2c95e6bbb78dfa822519ecfec6e1ca649
$ git diff 36c7db

或者,更簡潔地說:

$ git diff $(git merge-base contrib master)

但是,這兩種方法都不太方便,所以 Git 提供了另一種實現相同功能的快捷方式:三點語法。在 git diff 命令的上下文中,你可以在另一個分支後加上三個句點,以對你所在分支的最後一個提交與其與另一個分支的共同祖先進行 diff

$ git diff master...contrib

此命令僅顯示自與 master 的共同祖先以來,你當前主題分支引入的工作。這是一個非常有用的語法,需要記住。

整合貢獻工作

當你的主題分支中的所有工作都準備好整合到更主線的​​分支時,問題是如何做到這一點。此外,你希望使用什麼樣的總體工作流來維護你的專案?你有多種選擇,我們將介紹其中一些。

合併工作流

一個基本的工作流是直接將所有工作合併到你的 master 分支。在這種情況下,你有一個包含基本穩定程式碼的 master 分支。當你有一個主題分支中的工作,你認為已經完成,或者其他人貢獻了你已經驗證過的工作,你就將其合併到你的 master 分支,刪除剛剛合併的主題分支,然後重複。

例如,如果我們有一個倉庫,其中有兩個名為 ruby_clientphp_client 的分支,其歷史記錄如下圖所示(帶有多個主題分支的歷史記錄),並且我們先合併 ruby_client,然後是 php_client,那麼你的歷史記錄最終會看起來像這樣(合併主題分支後的歷史記錄)。

History with several topic branches
圖 72. 帶有多個主題分支的歷史記錄
After a topic branch merge
圖 73. 合併主題分支後的歷史記錄

這可能是最簡單的工作流,但如果處理大型或更穩定的專案,並且你希望非常謹慎地引入內容,它可能會有問題。

如果你有一個更重要的專案,你可能希望使用一個兩階段的合併週期。在這種情況下,你有兩個長期執行的分支,masterdevelop,其中你確定 master 只在非常穩定的版本釋出時更新,並且所有新程式碼都整合到 develop 分支中。你會定期將這兩個分支推送到公共倉庫。每次有新的主題分支需要合併時(合併主題分支前),你將其合併到 develop合併主題分支後);然後,當你標記一個版本時,你將 master 快進到現在已穩定下來的 develop 分支所在的位置(專案釋出後)。

Before a topic branch merge
圖 74. 合併主題分支前
After a topic branch merge
圖 75. 合併主題分支後
After a project release
圖 76. 專案釋出後

這樣,當人們克隆你的專案倉庫時,他們可以選擇檢出 master 來構建最新的穩定版本並輕鬆保持更新,或者他們可以檢出 develop,它包含更前沿的內容。你也可以透過有一個 integrate 分支來擴充套件這個概念,所有工作都在其中合併。然後,當該分支的程式碼庫穩定並且透過測試時,你將其合併到 develop 分支;當它穩定一段時間後,你將 master 分支快進。

大型合併工作流

Git 專案有四個長期執行的分支:masternextseen(以前稱為 'pu' - 提議更新)用於新工作,以及 maint 用於維護回溯。當貢獻者引入新工作時,它們會以類似我們描述的方式收集到維護者倉庫的主題分支中(參見 管理複雜的並行貢獻主題分支系列)。此時,主題會被評估,以確定它們是否安全且準備好被使用,或者是否需要更多工作。如果它們是安全的,它們會被合併到 next,然後該分支會被推送,以便每個人都可以嘗試整合的所有主題。

Managing a complex series of parallel contributed topic branches
圖 77. 管理複雜的並行貢獻主題分支系列

如果主題仍需工作,它們將被合併到 seen。當確定它們完全穩定時,主題會被重新合併到 master。然後,nextseen 分支會從 master 重建。這意味著 master 幾乎總是向前推進,next 會偶爾 rebase,而 seen 會更頻繁地 rebase。

Merging contributed topic branches into long-term integration branches
圖 78. 將貢獻的主題分支合併到長期整合分支

當一個主題分支最終被合併到 master 後,它就會從倉庫中刪除。Git 專案還有一個 maint 分支,它從最後一個版本分叉出來,以在需要維護版本時提供回溯補丁。因此,當你克隆 Git 倉庫時,你有四個分支可以檢出,以評估專案在不同開發階段的情況,具體取決於你想要多前沿或者你想要如何貢獻;維護者有一個結構化的工作流來幫助他們審查新貢獻。Git 專案的工作流是專門化的。要清楚地理解這一點,你可以檢視 Git 維護者指南

Rebase 和 Cherry-pick 工作流

其他維護者更喜歡將貢獻的工作 rebase 或 cherry-pick 到他們的 master 分支之上,而不是合併進去,以保持一個大致線性的歷史。當你有一個主題分支中的工作並已決定將其整合,你切換到該分支並執行 rebase 命令,以在你的當前 master(或 develop 等)分支之上重建更改。如果工作進展順利,你可以快進你的 master 分支,你最終會得到一個線性的專案歷史。

將引入的工作從一個分支移動到另一個分支的另一種方法是 cherry-pick。Git 中的 cherry-pick 就像單個提交的 rebase。它會獲取在提交中引入的補丁,並嘗試將其重新應用到你當前所在的分支上。如果你在一個主題分支上有多個提交,而你只想整合其中一個,或者你只有一個提交在主題分支上,並且你寧願 cherry-pick 它而不是執行 rebase,那麼這很有用。例如,假設你有一個專案,看起來像這樣:

Example history before a cherry-pick
圖 79. Cherry-pick 前的示例歷史

如果你想將提交 e43a6 拉到你的 master 分支,你可以執行:

$ git cherry-pick e43a6
Finished one cherry-pick.
[master]: created a0a41a9: "More friendly message when locking the index fails."
 3 files changed, 17 insertions(+), 3 deletions(-)

這會引入與 e43a6 中相同的更改,但你會得到一個新的提交 SHA-1 值,因為應用日期不同。現在你的歷史看起來像這樣:

History after cherry-picking a commit on a topic branch
圖 80. Cherry-pick 主題分支上的提交後的歷史

現在你可以刪除你的主題分支,並丟棄你不想拉取的提交。

Rerere

如果你進行了大量的合併和 rebase,或者你正在維護一個長期的主題分支,Git 有一個名為“rerere”的功能可以提供幫助。

Rerere 的意思是“重用已記錄的解決”——這是一種簡化手動衝突解決的方法。當 rerere 啟用時,Git 會儲存成功合併的前後影像集,如果它注意到一個衝突與你已經修復過的衝突完全相同,它就會直接使用上次的修復,而不會打擾你。

此功能包含兩個部分:一個配置設定和一個命令。配置設定是 rerere.enabled,將其新增到全域性配置中非常方便:

$ git config --global rerere.enabled true

現在,無論何時你進行一個解決了衝突的合併,解決方法都會被記錄在快取中,以備將來需要。

如果需要,你可以使用 git rerere 命令與 rerere 快取進行互動。當單獨呼叫它時,Git 會檢查其解析度資料庫,並嘗試匹配任何當前合併衝突並解決它們(儘管如果 rerere.enabled 設定為 true,這會自動完成)。還有子命令可以檢視將要記錄的內容,從快取中刪除特定解析度,以及清除整個快取。我們將在 Rerere 中更詳細地介紹 rerere。

標記你的釋出

當你決定釋出一個版本時,你可能希望分配一個標籤,以便將來可以重新建立該版本。你可以在 Git 基礎 中討論的那樣建立一個新標籤。如果你決定作為維護者簽署標籤,標記過程可能看起來像這樣:

$ git tag -s v1.5 -m 'my signed 1.5 tag'
You need a passphrase to unlock the secret key for
user: "Scott Chacon <schacon@gmail.com>"
1024-bit DSA key, ID F721C45A, created 2009-02-09

如果你確實簽署了你的標籤,你可能會遇到分發用於簽名標籤的公共 PGP 金鑰的問題。Git 專案的維護者透過將他們的公鑰作為 blob 包含在倉庫中,然後新增一個直接指向該內容的標籤來解決了這個問題。要做到這一點,你可以透過執行 gpg --list-keys 來確定你想要的金鑰:

$ gpg --list-keys
/Users/schacon/.gnupg/pubring.gpg
---------------------------------
pub   1024D/F721C45A 2009-02-09 [expires: 2010-02-09]
uid                  Scott Chacon <schacon@gmail.com>
sub   2048g/45D02282 2009-02-09 [expires: 2010-02-09]

然後,你可以透過匯出金鑰並將其透過 git hash-object 管道將其直接匯入 Git 資料庫,後者會將新的 blob 寫入 Git 並返回 blob 的 SHA-1 值:

$ gpg -a --export F721C45A | git hash-object -w --stdin
659ef797d181633c87ec71ac3f9ba29fe5775b92

現在你已經將你的金鑰內容儲存在 Git 中了,你可以透過指定 hash-object 命令給你的新 SHA-1 值來建立一個直接指向它的標籤:

$ git tag -a maintainer-pgp-pub 659ef797d181633c87ec71ac3f9ba29fe5775b92

如果你執行 git push --tagsmaintainer-pgp-pub 標籤將與所有人共享。如果有人想驗證一個標籤,他們可以直接透過從資料庫中直接拉取 blob 並將其匯入 GPG 來匯入你的 PGP 金鑰:

$ git show maintainer-pgp-pub | gpg --import

他們可以使用該金鑰來驗證你所有的簽名標籤。另外,如果你在標籤訊息中包含說明,執行 git show <tag> 將允許你向終端使用者提供關於標籤驗證的更具體說明。

生成構建號

由於 Git 沒有像 'v123' 這樣的單調遞增的數字或等價物來與每個提交關聯,如果你想為提交提供一個人類可讀的名稱,你可以對該提交執行 git describe。作為響應,Git 會生成一個字串,包含該提交之前的最近標籤的名稱,然後是自該標籤以來的提交數量,最後是正在描述的提交的部分 SHA-1 值(字首為字母“g”,表示 Git):

$ git describe master
v1.6.2-rc1-20-g8c5b85c

這樣,你就可以匯出一個快照或構建,並給它一個人們可以理解的名稱。事實上,如果你從 Git 倉庫克隆的原始碼構建 Git,git --version 會給出類似這樣的結果。如果你正在描述一個你直接標記過的提交,它只會給出標籤名稱。

預設情況下,git describe 命令需要帶註釋的標籤(使用 -a-s 標誌建立的標籤);如果你想利用輕量級(非帶註釋)標籤,請向命令新增 --tags 選項。你也可以將此字串用作 git checkoutgit show 命令的目標,儘管它依賴於末尾的縮寫 SHA-1 值,所以它可能不會永遠有效。例如,Linux 核心最近從 8 個字元跳到了 10 個字元以確保 SHA-1 物件唯一性,因此舊的 git describe 輸出名稱失效了。

準備釋出

現在你想釋出一個構建。你想做的一件事是為你的程式碼的最新快照建立一個存檔,以便那些不幸不使用 Git 的人可以使用。執行此操作的命令是 git archive

$ git archive master --prefix='project/' | gzip > `git describe master`.tar.gz
$ ls *.tar.gz
v1.6.2-rc1-20-g8c5b85c.tar.gz

如果有人開啟那個 tarball,他們會得到一個名為 project 的目錄下的你的專案的最新快照。你也可以以類似的方式建立一個 zip 存檔,但透過將 --format=zip 選項傳遞給 git archive

$ git archive master --prefix='project/' --format=zip > `git describe master`.zip

現在你有一個漂亮的 tarball 和一個 zip 存檔,可以作為你的專案釋出,你可以將其上傳到你的網站或傳送給人們。

Shortlog

是時候將你的專案動態傳送給你想了解專案進展的人員郵件列表了。一種快速獲取自上次釋出或郵件以來新增到你專案中的內容的變更日誌的方式是使用 git shortlog 命令。它會彙總你給出的範圍內的所有提交;例如,以下命令會提供自上次釋出(假設上次釋出名為 v1.0.1)以來的所有提交的摘要:

$ git shortlog --no-merges master --not v1.0.1
Chris Wanstrath (6):
      Add support for annotated tags to Grit::Tag
      Add packed-refs annotated tag support.
      Add Grit::Commit#to_patch
      Update version and History.txt
      Remove stray `puts`
      Make ls_tree ignore nils

Tom Preston-Werner (4):
      fix dates in history
      dynamic version method
      Version bump to 1.0.2
      Regenerated gemspec for version 1.0.2

你將獲得自 v1.0.1 以來所有提交的乾淨摘要,按作者分組,你可以將其傳送到你的列表。