通过API查找GitHub存储库中最早的提交记录。

11

如何高效地确定GitHub存储库中的初始提交时间?存储库具有“created_at”属性,但对于包含导入历史记录的存储库,最早的提交可能要早得多。

当使用命令行时,可以使用以下类似的命令:

git rev-list --max-parents=0 HEAD

然而,我在GitHub API中没有看到相应的等效命令。


我认为没有办法在恒定数量的API请求中获得它 - 目前,没有等同于您列出的Git命令的东西。因此,您需要浏览提交列表以查找最后一页(例如使用二进制搜索),然后获取该页面上的最后一个提交。另外(我非常确定您已经意识到了这一点),请注意,最旧的提交(按时间戳)不需要是最后一个提交(没有父级) - 重写历史记录并手动设置时间戳将允许最旧的提交位于提交树中的其他位置。 - Ivan Zuzak
我发现了这个问题,并对存储库的created_at感兴趣,可以用一行代码提取:curl -s https://api.github.com/users/WDScholia | jq .created_at "2020-05-18T17:45:47Z" - Wolfgang Fahl
6个回答

9
使用 GraphQL API,有一种方法可以在特定分支中获取最早的提交(初始提交)。
首先获取最后一次提交并返回totalCountendCursor
{
  repository(name: "linux", owner: "torvalds") {
    ref(qualifiedName: "master") {
      target {
        ... on Commit {
          history(first: 1) {
            nodes {
              message
              committedDate
              authoredDate
              oid
              author {
                email
                name
              }
            }
            totalCount
            pageInfo {
              endCursor
            }
          }
        }
      }
    }
  }
}

它会针对光标和pageInfo对象返回类似于这样的内容:
"totalCount": 931886,
"pageInfo": {
  "endCursor": "b961f8dc8976c091180839f4483d67b7c2ca2578 0"
}

我没有关于游标字符串格式 b961f8dc8976c091180839f4483d67b7c2ca2578 0 的任何来源,但我已经测试了一些拥有超过1000次提交的其他仓库,似乎它的格式总是这样的:

<static hash> <incremented_number>

所以,如果totalCount大于1,您只需从totalCount中减去2,并获取最旧的提交(或首次提交,如果您喜欢):

{
  repository(name: "linux", owner: "torvalds") {
    ref(qualifiedName: "master") {
      target {
        ... on Commit {
          history(first: 1, after: "b961f8dc8976c091180839f4483d67b7c2ca2578 931884") {
            nodes {
              message
              committedDate
              authoredDate
              oid
              author {
                email
                name
              }
            }
            totalCount
            pageInfo {
              endCursor
            }
          }
        }
      }
    }
  }
}

它生成以下输出(由Linus Torvalds进行初始提交):

{
  "data": {
    "repository": {
      "ref": {
        "target": {
          "history": {
            "nodes": [
              {
                "message": "Linux-2.6.12-rc2\n\nInitial git repository build. I'm not bothering with the full history,\neven though we have it. We can create a separate \"historical\" git\narchive of that later if we want to, and in the meantime it's about\n3.2GB when imported into git - space that would just make the early\ngit days unnecessarily complicated, when we don't have a lot of good\ninfrastructure for it.\n\nLet it rip!",
                "committedDate": "2005-04-16T22:20:36Z",
                "authoredDate": "2005-04-16T22:20:36Z",
                "oid": "1da177e4c3f41524e886b7f1b8a0c1fc7321cac2",
                "author": {
                  "email": "torvalds@ppc970.osdl.org",
                  "name": "Linus Torvalds"
                }
              }
            ],
            "totalCount": 931886,
            "pageInfo": {
              "endCursor": "b961f8dc8976c091180839f4483d67b7c2ca2578 931885"
            }
          }
        }
      }
    }
  }
}

使用的简单实现来获取第一个提交,方法如下:

import requests

token = "YOUR_TOKEN"

name = "linux"
owner = "torvalds"
branch = "master"

query = """
query ($name: String!, $owner: String!, $branch: String!){
  repository(name: $name, owner: $owner) {
    ref(qualifiedName: $branch) {
      target {
        ... on Commit {
          history(first: 1, after: %s) {
            nodes {
              message
              committedDate
              authoredDate
              oid
              author {
                email
                name
              }
            }
            totalCount
            pageInfo {
              endCursor
            }
          }
        }
      }
    }
  }
}
"""

def getHistory(cursor):
    r = requests.post("https://api.github.com/graphql",
        headers = {
            "Authorization": f"Bearer {token}"
        },
        json = {
            "query": query % cursor,
            "variables": {
                "name": name,
                "owner": owner,
                "branch": branch
            }
        })
    return r.json()["data"]["repository"]["ref"]["target"]["history"]

#in the first request, cursor is null
history = getHistory("null")
totalCount = history["totalCount"]
if (totalCount > 1):
    cursor = history["pageInfo"]["endCursor"].split(" ")
    cursor[1] = str(totalCount - 2)
    history = getHistory(f"\"{' '.join(cursor)}\"")
    print(history["nodes"][0])
else:
    print("got oldest commit (initial commit)")
    print(history["nodes"][0])

你可以在中找到一个例子,该例子链接在此帖子中。


它还可以通过path参数(在history(...)解析器中)用于获取给定子目录或文件的第一次提交。https://docs.github.com/en/rest/commits/commits#list-commits--parameters。 - Alex Rintt

3

如果数据已被缓存(在GitHub的一侧),并且根据您的精度要求,这可以仅使用两个请求完成。

首先,通过使用until参数将GET请求发送到/repos/:owner/:repo/commits来检查创建时间之前是否有提交记录(如VonC的答案所建议的),并通过per_page参数限制返回的提交记录数为1。

如果创建时间之前有提交记录,则可以调用贡献者统计终点/repos/:owner/:repo/stats/contributors)。响应每位贡献者都有一个weeks列表,并且最古老的w值与最古老的提交记录发生在同一周。

如果需要精确的时间戳,则可以再次使用提交记录终点,并将untilsince设置为最古老的周值后7天。

请注意,统计终点可能会返回202,表示统计信息不可用,此时需要在几秒钟后重试。


2

建议是列出一个仓库的提交记录(详见GitHub api V3部分),使用until参数,设置为创建该仓库的时间(再加上一天,例如)。

GET /repos/:owner/:repo/commits

这样,您将列出在创建存储库时或之前创建的所有提交:这将限制列表,排除存储库创建后创建的所有提交。


我认为这个想法是他们不一定知道哪个提交可能是最旧的,正如伊万所指出的,最旧的提交可能确实有父提交。 - Ian Stapleton Cordasco
感谢您建议先使用创建日期作为上限来制作提交列表。我最终采用了这个作为初始过滤器,然后使用统计API来避免浏览所有提交(请查看我的回答,它已经在 https://github.com/mihaip/githop/commit/6cc162c91b25bd26da379da2c1656fff6c199a1a 中实现)。 - Mihai Parparita
@MihaiParparita 很好的实现,比我的答案更完整。+1 - VonC
1
@MihaiParparita 我猜你把你的仓库从 githop 改名为 retrogit 了? https://github.com/mihaip/retrogit/commit/6cc162c91b25bd26da379da2c1656fff6c199a1a - testworks
@testworks:没错,后来进行了重命名。 - Mihai Parparita

0
发布我的解决方案,因为其他所有的方法都对我无效。
以下脚本检索给定 REPO(“owner/repo”)的提交列表,必要时遍历到最后一页,并输出最后(最旧)提交的 JSON 对象。
    REPO="owner/repo"
    URL="https://api.github.com/repos/$REPO/commits"
    H=" -H \"Accept: application/vnd.github+json\" \
      -H \"X-GitHub-Api-Version: 2022-11-28\""
    
    response=$(curl -s -L --include $H $URL | awk 'NR > 1')
    
    # Split the output into header and json
    header=$(echo "$response" | awk 'BEGIN{RS="\r\n";ORS="\r\n"} /^[a-zA-Z0-9-]+:/')
    commits=$(echo "$response" | awk '!/^[a-zA-Z0-9-]+:/')
    
    # If paginated, get last page
    if [[ $header == *"link"* ]]; then
      # Extract the last page value
      link_line=$(echo "$header" | grep -i "^link:")
      last_page=$(echo "$link_line" | sed -n 's/.*page=\([0-9]\+\)[^0-9].*rel="last".*/\1/p')
    
      # Get last-page commits
      commits=$(curl -s -L $H $URL?page=$last_page)
    fi
    
    # Print first commit
    echo $commits | jq '.[-1].commit'

-2
这不是通过API实现的,而是在GitHub.com上:如果你有最新的提交SHA和提交计数,你可以构建URL来找到它:
https://github.com/USER/REPO/commits?after=LAST_COMMIT_SHA+COMMIT_COUNT_MINUS_2

# Example. Commit count in this case was 1573
https://github.com/sindresorhus/refined-github/commits/master
  ?after=a76ed868a84cd0078d8423999faaba7380b0df1b+1571

-2

1
这个问题涉及到GitHub API,您提供的示例URL是GitHub网站的。 - Mihai Parparita

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接