如何使用 GitPython 跟踪一段时间内的 Git 提交

How do I track Git commits for a time period using GitPython

我想知道如何跟踪 Git 中的分支是否被推送。基本上,我想从开发分支(不是回购)中找到所有分支,并能够检查是否推送了这些分支中的任何一个(经过一些更改后)。

到目前为止,使用 GitPython, details in , and 我可以得出以下结论:

  import git
  from git import Repo

  repo = Repo('directory_of_repo') #points to develop not master
  paths = set()
  for item in repo.head.commit.diff('develop@{8 days ago}'):
    if item.a_path.find('a_certain_dirctory') != -1:
        paths.add(item.a_path)

而 mg 存储库指向 develop。现在我不确定我应该使用 HEAD@{8 days ago} 还是 develop@{8 days ago}P.S。天数可以不同)。但是,不确定我应该使用 HEAD 还是 develop? 8天前的例子,使用HEAD,唯一路径数为86,使用develop,则为15.

我真正要找的是在develop中的某个时间段(例如8天前)中找到所有已更改的路径(即其中的某些文件已更新)分支。关于我应该使用哪一个(HEADdevelop)来跟踪 develop 上特定时间段的变化的任何指导?

I want to find all branches from a master branch (not repo)

这没有意义,因为分支不会从分支中分支出来。

... and be able to check if any of those branches are pushed (after some changes) or not.

这个问题可能有意义,如果稍微改写的话。

这两者的关键是了解什么是分支,什么不是。但在你这样做之前,你必须意识到并不是每个人都对 branch 这个词表示相同的意思。事实上,有人可以在同一句话中不止一次说 branch 这个词,表示两个或更多不同的东西。

(相关:What exactly do we mean by "branch"?

在任何存储库中,真正重要的是 而不是 分支。 提交 才是最重要的。分支就是您 find 提交的方式。在这种特殊情况下,branch 一词的意思是 分支名称 ,例如 masterdevelop .这些名称特定于这个存储库。这个存储库的克隆,在其他 Git 中,可能在其他计算机或某些 cloud-server 或其他任何地方,具有 自己的 分支名称,独立于您的拥有。

当您使用 git fetchgit push 将两个 Git 存储库相互连接时,一个 Git 将提交发送到另一个 Git,这收到他们。接收方 Git 可以看到部分或全部发送方 Git 的名称(分支名称、标签名称和其他名称),但真正重要的是提交。但是,在发送或接收了一些提交后,我们面临着查找 提交的问题。

每个提交都有一个唯一的哈希 ID。这个哈希 ID 又大又丑,人类不可能记住。幸运的是,每次提交都会记住一组 previous 提交哈希 ID——通常只有一个。 Git 将此称为 parent 提交。 child 提交记住其 parent 的哈希 ID。当您进行新提交时,Git 会为新提交分配一个新的唯一哈希 ID,并将您 刚才使用的提交的哈希 ID 放入新 child 作为 child 的 parent 提交。 (现在您正在使用 child 提交。)

当然,parent 提交本身可能是之前某个提交的 child。所以 parent 记住了 它的 parent——你刚刚创建的 child 的 grandparent——并且 commit 记住 its parent,依此类推。结果是一个很长的 backwards-pointing 链,其中 last 提交可能是最有趣的:

... <-F <-G <-H

这里的H代表最后次提交的hash ID。因为 H 保存了它的 parent G 的哈希 ID,我们可以使用 H 来找到 G。同时,G持有其parentF的哈希ID,所以我们可以使用G查找F,等等。这些 backwards-pointing 箭头意味着我们只需要 Git 记住链中 last 提交的哈希 ID。

这就是分支名称的用途。它们持有链中 last 提交的哈希 ID:

...--F--G--H   <-- master, branch2, branch3

注意这里三个名字都标识commit H.

如果我们 git checkout master 然后进行新的提交,它会得到一些丑陋的大哈希 ID,我们将其称为 I。新提交 I 将指向现有提交 H 作为其 parent:

...--F--G--H
            \
             I

现在,因为我们选择 master 作为 git checkout 的分支,Git 将更新 name master保存新提交的哈希 ID I:

...--F--G--H   <-- branch2, branch3
            \
             I   <-- master (HEAD)

附加的 (HEAD) 是我们(和 Git)在进行新提交时知道要移动哪个 分支名称 的方式。其他两个分支名称——branch2branch3——没有改变。如果我们 git checkout branch3 我们得到:

...--F--G--H   <-- branch2, branch3 (HEAD)
            \
             I   <-- master

如果我们现在进行新的提交,我们将得到:

             J   <-- branch3 (HEAD)
            /
...--F--G--H   <-- branch2
            \
             I   <-- master

这几乎就是它的全部内容:分支名称只是指针,指向提交。

如果我们 Git 通过 Internet-phone 调用其他 Git,我们的 Git 可以告诉他们 Git: 嘿,我有提交I,你有吗?如果他们说没有,我们的Git可以给他们提交I. 宇宙中的所有 Git 都会同意提交 I 获得提交 I 的哈希 ID,并且没有其他提交获得此哈希 ID。所以他们只需要先交换哈希 ID:提交的实际内容——所有文件的快照——如果需要可以稍后再去(并且可以压缩到另一个 Git 真正需要的内容),并且只是哈希ID在这里很重要。

一旦我们给了他们I——如果他们需要的话也给了他们H,如果需要也给了他们G——他们可能有这样的东西:

...--F   <-- master
      \
       G--H--I

也就是说,他们有一个名字,master,指向他们现有的提交F,它与我们现有的F 因此是对相同文件的相同提交。现在,他们也有 G-H-I,有 I poi回到 HH 指向 GG 指向 F。但是他们没有 name 来找到提交 I.

因此,我们的 Git 向他们发送了提交 I(以及需要的任何早期提交),现在将向他们发送一个礼貌的请求:请,如果可以的话,更改您的名字 master 以指向提交 I. 由他们决定是否服从这个礼貌的请求。如果他们 服从,他们会将提交 I 的原始哈希 ID(无论是什么)填充到 他们的 名称 master.

所以:

... be able to check if any of those branches are pushed

这仍然不是一个明智的措辞。但是我们可以做的是打电话给他们——另一个Git——向上,询问他们他们名字master,并将其与 our name master 中的哈希 ID 进行比较。这些是相同的哈希 ID 吗?如果是这样,我们就同步了。如果没有,我们就没有。

我们究竟如何不同步,我们不知道。我们只知道我们是否同步。这可能就是您想要的问题。 (如果你想确切地知道我们是如何 out 同步的,如果我们不同步,那是一个更难的问题。)

因此,要回答这个新的不同问题,我们应该调用他们的 Git,让他们列出他们的分支名称和包含的原始哈希 ID,并将它们与 我们的 分支名称并包含原始哈希 ID。他们会匹配或不匹配;或者我们可能会有他们没有的分支名称,反之亦然。

不过,在你做任何这些之前,请考虑 git fetch 的最后一个特性(无论如何由 Git 实现:这可能会或可能不会在你的 Python 库中,具体取决于关于它模仿 Git 的精确程度,或者它是否直接使用 Git)。我可以使用 git fetchmy Git 连接到 your Git:

git remote add my-name-for-you <url-for-your-git>
git fetch my-name-for-you

当我这样做时,your Git 告诉 my Git 它的所有分支和标签名称.然后我的 Git 让我选择我喜欢的名字——默认是我喜欢所有的名字——它从你的每个分支名称中获取 last 提交,以及我还需要的任何早期提交,以便我拥有您的所有提交。然后,在 my Git 中,它会为每个分支名称创建或更新 remote-tracking 名称

  • 我的 my-name-for-you/master 持有您的 master 持有的哈希 ID;
  • 我的 my-name-for-you/develop 持有您的 develop 持有的哈希 ID;
  • ...等等,对于您拥有的每个分支名称。

所以我不用每次都重新给你的 Git 打电话,我可以只用我的 Git 的 内存 你的 Git的分支名称。

如果我Git的记忆已经过时了,我就运行 git fetch my-name-for-you。我的 Git 调用你的 Git,更新我对你名字的记忆,并获得你的所有提交。

如果我你提交——如果我运行git push my-name-for-you master——我会把提交发给你并询问你的Git 来设置 你的 master。你的 Git 要么服从,要么拒绝,然后告诉我一些拒绝的原因。如果您的 Git 服从,我的 Git 将更新我的 my-name-for-you/master 以记住您的 master 现在存储了我刚刚发送给您的相同哈希 ID。

因此,一般来说,您只需检查自己的 origin/* 名称的哈希 ID,而不是 连接到 其他 Git。名称 origin 是默认远程的默认名称,是在您通过克隆其他 Git 存储库首次创建 Git 存储库时创建的。如有必要,您可以在检查 origin/* 名称之前 运行 git fetch origin

(对于某些特殊的工具用途,有时使用 git ls-remote 代替 git fetch 可能更好。这会获取名称和哈希 ID——这是实际 git fetch—但只是打印出来并停止,而不是继续进行 git fetch 的其余工作。不利的一面是您最终可能需要 git fetch,但有利的一面是你得到一张当时准确的图片,而不用等待 git fetch 工作。这个时刻可能不会很长,取决于另一个 Git 的活跃程度。)

使用日志和自选项,我们可以通过 --name-only 选项从一次 (--since) 获取所有更改的文件。

  from git import Git
  from datetime import date
  import datetime as DT

  def _get_the_changed_components(self):
      g = Git(self.repo_directory) # repo directory points to `develop`
      today = date.today()
      since = today - DT.timedelta(self.time_period) #some times ago
      loginfo = g.log('--since={}'.format(since), '--pretty=tformat:', '--name-only')
      files = loginfo.split('\n')
      for file in files:
          self.paths.add(file)