在我們做設定管理系統和gitlab系統整合的時候,有一個常見的場景,就是要獲取某個檔案的commitId,來記錄本次組態檔提交的版本。這個通過gitlabApi很容易實現:
GET /projects/:id/repository/files/:file_path?ref=:ref
其中:
通過此api就能很容易獲取到某個專案中的檔案commitId和其他一些相關資訊。檔案地址
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fmodels%2Fkey%2Erb?ref=master"
響應結果:
{
"file_name": "key.rb",
"file_path": "app/models/key.rb",
"size": 1476,
"encoding": "base64",
"content": "IyA9PSBTY2hlbWEgSW5mb3...",
"content_sha256": "4c294617b60715c1d218e61164a3abd4808a4284cbc30e6728a01ad9aada4481",
"ref": "master",
"blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83",
"commit_id": "d5a3ff139356ce33e37e73add446f16869741b50",
"last_commit_id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
"execute_filemode": false
}
但是如果現在需要獲取一個資料夾的commitId,如果還是用以上方法,file_path如果傳一個資料夾路徑,則該方法會報錯,顯然不適用。那麼我們如果要獲取一個資料夾的commitId,從而記錄本次資料夾(包括其中所有檔案)的提交版本,該如何實現呢?為此本人找遍了網上所有的資料,沒有一個明確的解決方案,甚至詢問了時下最流行的人工智慧chatGpt,得到的回答如下:
這裡多說一句題外話,不得不說,chatGpt還是非常強大的,他的回答可以說秒殺了我在網上找的90%的資料,寫得是有理有據,看上去很有道理的樣子,而且回答的邏輯也很嚴謹,如果我沒驗證一番,看這個回答,幾乎挑不出任何毛病。
但是遺憾的是,chatGpt答對了開頭,卻沒答對結尾。為啥這麼說呢?因為第一個獲取資料夾的api確實沒給錯,但是獲取到的資料夾返回物件返回的id,卻不是我期待已久的commitId,而是一個treeId,我們chatGpt給出來的資訊,似乎告訴我們必須要通過這個treeId來找到commitId,這才算是找到了這個資料夾的commitId。
按照chatGpt提供的第二個api,我們看到可以直接通過treeId獲取到commitId,於是我驗證了一番,發現這個api並不是這麼用的,為此我查閱了官方檔案的api之後,發現這個api根本沒有這個treeId的傳參,而且官方檔案也根本沒有通過treeId來找到commitId的相應api(我猜測可能是chatGpt的最新資料庫只更新到了2021年,所以他了解到的gitlabApi版本落後的緣故,但是他的回答確實已經給了我很大的參考價值,確實是個很棒的人工智慧)。
就此,開發陷入卡殼狀態,苦思冥想不得其姐,按道理來說不應該呀,gitlab頁面上明明能顯示出資料夾的commitId,gitlab卻沒有提供相應api能拿到這個commitId?這不科學,莫非是方向走錯了,其實並沒一個通過treeId獲取commitId的概念,是我被chatGpt誤導了?
這逼得我不得不研究了一下gitlab專案倉庫中檔案的commitId、資料夾commitId以及整個專案commitId的更新規律,看看他們到底是怎麼變化的,資料夾的commitId和檔案的commitId到底是不是同一回事,為此,我用一個gitlab的檔案倉庫的develop分支做了如下實驗:
先記錄下幾個值:
project的commitId:d1a982d706df99ac4e1c5c0c331b9f65c6d16e6d
project根目錄下資料夾abc的commitId:44ef733bb2fdd0b8d0661473351efd662b2e33b4
project根目錄下檔案hello.txt的commitId:621fe03c295f36c8e1941e43afcd82021daecb00
然後做如下操作:
此時,我們似乎隱隱約約得出了這樣一個結論,某資料夾內的任一檔案做了最新的一次修改,其所在資料夾也會更新為這最新的一次修改,但是與這個檔案同級的此資料夾下的其他檔案,commitId不會變化。
我們繼續實驗:
先記錄下幾個值:
project的commitId:a61557d137efb0ba2f578461bad10ec101270d88
project根目錄下資料夾abc的commitId:a61557d137efb0ba2f578461bad10ec101270d88
project根目錄下檔案README.md
(和abc資料夾同級)的commitId:f6815e7bb31f7cf34a9f774a4c4b1966f6ae33e2
到此,我們似乎真的可以得出一個肯定的結論:專案下任一檔案做了最新的一次修改(或是新增、刪除),生成了一個新的commitId值,這個值除了會更新檔案本身的commitId,還會更新其所在資料夾的commitId值,這個規律一直向上一級資料夾延伸,一直延伸到根目錄更新project的commitId,但其他檔案或非此檔案的父資料夾的commitId不會隨之變化。
換句話說,project的commitId會一直隨著他下面任一檔案或資料夾的最新的一次更新而變化(可以把project根目錄當做是這個專案所有檔案的父資料夾)。project下任意的子資料夾,也會隨著其資料夾下任一檔案或資料夾的最新一次更新而變化,並且也會引起最高層父資料夾,也就是project根目錄commitId的變化。
根據以上在gitlab倉庫的親身體驗,我們形成了一個概念,那就是資料夾本身的commitId其實就是這個資料夾下最新的一次檔案提交的commitId,那我們似乎有了一個新的思路:前面chatGpt提供的獲取資料夾commitId的解決方案,我們是不是可以這麼變通一下,第一個api獲取某路徑下的檔案樹,獲取到的檔案樹的集合,我們來過濾判斷此資料夾存不存在,如果存在,那麼第二個獲取commits的api的真是用法,可以傳路徑引數,查出此資料夾路徑下所有的commitId的提交集合,然後從這個集合中根據提交時間排序,找出最新的一次提交的commitId,即為這個資料夾的commitId。
程式碼實現如下(這裡為了呼叫gitlabApi的方便,我們在專案中引入了gitlab4j-api的jar包,實際就是對http請求gitlabApi的封裝):
override fun getFileInfoTest(
token: String,
domain: String,
projectId: String,
filePath: String,
dirName: String,
ref: String
) {
val host = if (domain.startsWith("http")) {
domain
} else {
"https://$domain"
}
val gitlab = GitLabApi(host, token)
//獲取指定檔案目錄下的檔案樹
val fileTree = gitlab.repositoryApi.getTree(projectId, filePath, ref)
//過濾目標資料夾,判斷資料夾在指定目錄存不存在
val dirExsit = fileTree?.filter { it.path == "$filePath/$dirName" && it.name == dirName }
if (CollectionUtil.isEmpty(dirExsit)) {
println("資料夾不存在")
}
//如果名稱不為空,說明此資料夾存在,則找出此資料夾下所有的commit提交記錄
val commits = gitlab.commitsApi.getCommits(projectId, ref, "$filePath/$dirName")
if (CollectionUtil.isEmpty(commits)) {
println("commits不存在")
}
//返回的提交記錄結果集已經按照提交時間從大到小排序,只需要拿出第一條記錄,
//即為此資料夾下所有檔案的最新一次提交,即為此資料夾的提交commitId
val commit = commits[0].toString()
println(commit)
}