如何使用 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天前)中找到所有已更改的路径(即其中的某些文件已更新)分支。关于我应该使用哪一个(HEAD
或 develop
)来跟踪 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 一词的意思是 分支名称 ,例如 master
或 develop
.这些名称特定于这个存储库。这个存储库的克隆,在其他 Git 中,可能在其他计算机或某些 cloud-server 或其他任何地方,具有 自己的 分支名称,独立于您的拥有。
当您使用 git fetch
或 git 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)在进行新提交时知道要移动哪个 分支名称 的方式。其他两个分支名称——branch2
和 branch3
——没有改变。如果我们 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回到 H
,H
指向 G
,G
指向 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 fetch
让 my 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)
我想知道如何跟踪 Git 中的分支是否被推送。基本上,我想从开发分支(不是回购)中找到所有分支,并能够检查是否推送了这些分支中的任何一个(经过一些更改后)。
到目前为止,使用 GitPython, details in
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天前)中找到所有已更改的路径(即其中的某些文件已更新)分支。关于我应该使用哪一个(HEAD
或 develop
)来跟踪 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 一词的意思是 分支名称 ,例如 master
或 develop
.这些名称特定于这个存储库。这个存储库的克隆,在其他 Git 中,可能在其他计算机或某些 cloud-server 或其他任何地方,具有 自己的 分支名称,独立于您的拥有。
当您使用 git fetch
或 git 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)在进行新提交时知道要移动哪个 分支名称 的方式。其他两个分支名称——branch2
和 branch3
——没有改变。如果我们 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回到 H
,H
指向 G
,G
指向 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 fetch
让 my 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)