gitlabApi如何獲取專案資料夾的commitId

2023-02-16 12:00:45

  在我們做設定管理系統和gitlab系統整合的時候,有一個常見的場景,就是要獲取某個檔案的commitId,來記錄本次組態檔提交的版本。這個通過gitlabApi很容易實現:

GET /projects/:id/repository/files/:file_path?ref=:ref

其中:

  • id:是專案id
  • file_path:檔案路徑,注意,這裡的file_path需要指定到檔案,否則會報錯,例如:abc/file.txt
  • ref:分支名稱,例如:develop

通過此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
然後做如下操作:

  1. 修改abc資料夾下的檔案hello.txt,此時,此檔案的commitId更新為:a61557d137efb0ba2f578461bad10ec101270d88
  2. 此時,觀察abc資料夾的commitId,我們發現,他的commitId也變為了:a61557d137efb0ba2f578461bad10ec101270d88
  3. 此時,再觀察此project專案的commitId,我們發現,他的commitId也變為了:a61557d137efb0ba2f578461bad10ec101270d88
  4. 此時,在觀察abc資料夾中其他檔案以及和abc資料夾同級的其他檔案,他們的commitId還是保持原樣未變。

  此時,我們似乎隱隱約約得出了這樣一個結論,某資料夾內的任一檔案做了最新的一次修改,其所在資料夾也會更新為這最新的一次修改,但是與這個檔案同級的此資料夾下的其他檔案,commitId不會變化。
我們繼續實驗:
先記錄下幾個值:
project的commitId:a61557d137efb0ba2f578461bad10ec101270d88
project根目錄下資料夾abc的commitId:a61557d137efb0ba2f578461bad10ec101270d88
project根目錄下檔案README.md
(和abc資料夾同級)的commitId:f6815e7bb31f7cf34a9f774a4c4b1966f6ae33e2

  1. 修改project根目錄下檔案README.md,其commitId變為:7eb3a4f553196ea21867b97ab60c3e94163b8b25
  2. 觀察project的commitId,竟然也變成了:7eb3a4f553196ea21867b97ab60c3e94163b8b25
  3. 再次觀察abc資料夾的commitId,還是之前的:a61557d137efb0ba2f578461bad10ec101270d88,並沒有變化。

  到此,我們似乎真的可以得出一個肯定的結論:專案下任一檔案做了最新的一次修改(或是新增、刪除),生成了一個新的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)
    }