如何列出当前正在验证(在VSTS中)的“拉取请求"中更改的所有文件? [英] How to list all the files changed in the Pull Request being currently validated (in VSTS)?

查看:95
本文介绍了如何列出当前正在验证(在VSTS中)的“拉取请求"中更改的所有文件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个PR验证版本,我想列出PR中更改的所有文件以及状态.

在每个构建中同步源. 因此,在合并PR更改之前,源位于版本A(通常为origin/master). 让我将PR合并提交指定为B.

我当前的实现调用git diff-tree --name-status -r -M A..B,但作为

并且确实,所讨论的PR有一个提交-b7d9617fc,其中81317ea59是PR合并提交(即它是B)

正如我其他帖子所显示的,git diff-tree --name-status -r -M b00bf1df0..81317ea59是不正确的.

我相信使用...也是不正确的,的确如此:git diff-tree --name-status -r -M b00bf1df0...81317ea59不返回任何内容.

解决方案

基本问题是该问题的格式不正确:

...列出PR中所有已更改的文件以及状态

实际上,拉取请求是以下形式的请求:请更改分支名称B,以便与其指向更改时指向的任何提交,而不是指向它所指向的任何提交提交C代替.您可以根据自己的喜好来执行此操作,但是可以通过自己的合并提交来完成.这种请求请求可能会也可能不会—很难说,因为请求请求不是Git本身的一部分,而是由各种Web服务提供的附加组件,并且每个Web服务都可以实现此实现,但是它们一样-包括以下形式的信息:顺便说一句,当我要求您进行更改时名称B到哈希ID C的值,哈希ID O.

暂时假设它提供所有三个项目:分支名称​​ B ,旧的提交哈希ID O 和请求的提交哈希ID C .我们还要声明 B 的实际哈希值当前是 A .也许 A = O ,也许不是. (如果 O 未包含在拉取请求中,那并不是致命的,但是将由您自行决定合适的替代方法,并定义其所有工作方式,依此类推.)

关于提交的知识

现在,关于提交哈希ID的第一件事是它指定一个特定的提交,并且每个提交都是所有文件的完整快照.这根本不是一组更改!这只是一种状态,就像宣布今天的外面的温度是68°F(20°C).就其本身而言,这并不能告诉您昨天或上周的情况.要将状态转换为更改,必须选择其他状态进行比较.如果昨天的温度为77˚F(25˚C),则变化为9˚F(5˚C).

我们对Git中的提交执行相同的操作:我们选择一个状态(例如 C )并将其与以前的状态进行比较.但是我们只是说 actual 的先前状态是 A ,而 recorded 的先前状态是 O .如果将 C O 进行比较,则得到的答案与将 C 进行比较的答案不同. A ,当然,除非 A = O .

但这不是关于提交的唯一内容.每个提交还记录零个或多个 parent 提交哈希ID(通常为1),第二个最常见的是合并提交2. (零位父母表示该提交是 root 提交;对于第一次提交,每个存储库中通常只有一个.三个或多个是章鱼合并.) C 的父母各自具有自己的权利,也有父母,依此类推.考虑到父母,我们真正拥有的可能是这样的:

...--D--E--F--O--A   <-- B
         \     \
          G--H--C   <-- (pull requested at C)

或者,它可能更简单:

...--D--E--F--A   <-- B
               \
                G--C   <-- (pull requested at C)

其中AO是相同的提交.甚至它可能是删除某些提交的请求,尽管某些Web服务不允许这样做,或者如果您使用它们的接受请求"接口则不采取任何措施,而我们只作画即可:

...--C--O--A   <-- B

(请求总计为请快退" B,以便提交AO消失).

如果需要另一个合并怎么办?

让我们再次看一下该图:

...--D--E--F--O--A   <-- B
         \     \
          G--H--C   <-- (pull requested at C)

至少在GitHub上,如果您使用默认类型的合并拉取请求",则GitHub要做的就是添加 new 提交-我们将其称为M-其快照是通过运行 new git merge(在GitHub上) 1 形成的,即使C本身是合并提交. M中的快照将是通过合并更改而产生的快照.最终产品将是:

...--D--E--F--O--A--M   <-- B
         \     \ __/
          G--H--C

但是GitHub还提供了两种其他方式来接受此请求,每种方式都有不同之处.如果您的存储库中尚未存在提交GHC,则 rebase and merge 选项将生成以下图形:

...--D--E--F--O--A--G'-H'   <-- B

同时 squash and merge 选项产生:

...--D--E--F--O--A--S   <-- B

如果GitHub已创建M,则S具有M会具有的内容.


1 GitHub进行合并的方式不是通过使用git merge,至少不是从字面上看,因为没有办法处理冲突.如果存在合并冲突,GitHub甚至不提供制作M的权限.但是效果是好像 GitHub运行了文字git merge,所以至少可以将其用作心理模型.


最简单的情况:A = O,具有快速前进的潜力

假设图形确实看起来像这样:

...--D--E--F--A   <-- B
               \
                G--C   <-- (pull requested at C)

,以便 B current 值与 B old 值相同>当时发出请求的人发出了请求.也就是说,我们有 A = O .如果您在GitHub上使用默认的合并"按钮,则会得到以下信息:

...--D--E--F--A------M   <-- B
               \    /
                G--C

,但是提交M内容将与提交C内容完全匹配,这在几种方面都有帮助.

如果使用合并按钮的 rebase and merge 模式,则会得到以下信息:

...--D--E--F--A--G'-C'   <-- B

其中G'C'提交是请求请求的GC提交的副本:快照匹配,甚至是父提交ID G'G匹配;哈希ID有所不同:GitHub已经复制了GC的副本,尽管没有必要进行复制. (我曾经希望GitHub根本不制作这些副本.其他服务可能没有.)副本放进去.

如果您使用 squash and merge 按钮,则会得到以下提示:

...--D--E--F--A--S   <-- B

其中S parent A,而S content 恰好与C的匹配.再次,提交GC本身不会进入存储库:仅进入C的内容(如"squash"中的新快照提交S).

定义您想要的东西

现在您知道pull请求实际上是对 move 分支名称的请求,从现在的位置(A)到从他们请求的提交派生的内容(),或者通过添加新的合并提交M,或者通过添加新的squash-merge(普通单亲非合并提交)S,或者通过复制其部分或全部提交来实现.

您是否应该接受他们的拉取请求,取决于您在GitHub上单击的按钮,或者您使用的是其他任何东西(Bitbucket,GitLab或其他),无论系统提供什么,实际上会发生什么.由您决定实际上会发生什么.

然后,知道实际会发生什么,由您来决定如何比较放入存储库中的提交-可能尚未实际存在的提交!提交您的存储库中已经 are 的内容.您想将C的内容与A的内容进行比较吗?假设O的哈希ID可用,您是否想将CO的内容进行比较?或者,您是否想仔细查看与它们的父代相比,每一个的新提交会做什么,即使这些提交实际上还不存在?

显然,最后一个是最困难的:但是这些新提交的产生遵循一个定义明确的过程,因此要找出它们将包含的内容,一旦它们存在,您可以自己创建自己如果您接受拉取请求,则您的Web服务将在明天以相同的方式创建存储库.需要注意的是,如果您这样做执行此操作(如果您预测他们明天将要执行的操作),然后同意您明天将单击该按钮,那么如果明天分支B指向该行会发生什么情况A以外的其他提交?

如果您选择了最后一个选项,即根据将发生的事情接受,您将需要围绕整个操作建立某种流程,以确保会发生什么事情事件与发生事件相同.也有很多方法可以实现这一点,但没有一种是完美的.

实现您想要的

一旦您定义了所需的内容,您仍然必须实现.对于GitHub而言,这相对简单. (我不确定您将对Bitbucket或其他系统执行什么操作.)首先使用git fetch将提交C的副本复制到自己的存储库克隆中,并且任何父提交都具有以下内容:缺乏.

让我们假设origin是GitHub存储库的名称,并且拉取请求为#123.然后:

git fetch origin   # update everything so that we have origin/B pointing to A, etc
git fetch origin refs/pull/123/head:refs/heads/pr123

您现在有了一个名为pr123的分支,其尖端提交为commit C,以及您的远程跟踪名称origin/B指向提交A.

现在很容易判断A是否是C的祖先,即,是否最简单的情况有效.这个特殊的测试是在bash中完成的(不确定PowerShell):

if git merge-base --is-ancestor refs/remotes/origin/B refs/heads/pr123; then
    echo "it's the simple case, yay"
else
    echo "it's the merge case, boo"
fi

在简单的情况下,无论您使用哪种方法来接受拉取请求,最终指向origin/B指向的最终提交的内容都将与现在指向的提交C的内容匹配由refs/heads/pr123. 2 编写.如果您确定要测试的更改总体摘要更改,即从AC,而无需查看任何中间提交-则将被修改的文件是按以下方式列出的文件:

git diff-tree -r --name-status [options] refs/remotes/origin/B refs/heads/pr123

但是,如果您还希望检查中间提交,则还有更多工作要做.

如果处于 merge 情况下,事情将变得更加复杂,因为-即使您只是将最终结果与单个提交进行比较-现在,您必须决定是否比较提交AO或其他任何内容,例如AC的当前当前合并基数,无论可能是什么提交.

如果要做要使用实际的合并基数,则此处的三点语法很有用.但是,由于旧版本Git中的小错误,我建议在此处运行git merge-base --all refs/heads/B refs/heads/pr123并收集其输出.这将列出一定数量的哈希ID,理想情况下仅列出一个:如果仅获得一个,则表示合并基础提交.如果获得多个,则它们是 all 个候选合并基础,默认的git merge -s recursive策略将首先合并所有合并基础,然后将所得提交用作合并的合并基础. /p>

(拒绝复杂的请求请求通常是最明智的选择,或者至少是最简单的做法,告诉编写它们的人说,他们必须重新处理他们的请求请求,这样就不需要任何特殊的/特别的合并. )


2 通常,如果远程跟踪名称origin/B和分支名称pr123指向所需的提交,则可以使用这些名称.但是,Git用来将名称转换为哈希ID的方法是一个六步过程,概述在四个处下降; 用作远程跟踪"名称是步骤 5 .实际上,这意味着如果同时具有分支 pr123 tag pr123,则名称pr123是指标签,而不是分支.对于将在没有人工监视警告消息的情况下运行的脚本,最好拼写全名:refs/remotes/origin/Brefs/heads/pr123.您甚至可以将其解析为哈希ID,然后再使用哈希ID,因为哈希ID永远不会改变.

I have a PR validation build that I want to list all the files changed in the PR along with the status.

The sources are synced on each build. So, before the PR changes are merged, the source is at revision A (typically origin/master). Let me designate PR merge commit as B.

My current implementation calls git diff-tree --name-status -r -M A..B, but as Why is the list of files for a range of commits different from the aggregation of the lists of files per commit in the same range? shows this is incorrect.

So, what is the correct way? Note, that the Pull Request may contain several commits with at least one merge commit - B. But there could be more merge commits there.

I am pretty sure there is a pure Git way to do it and there is no need to execute Restful API against the TFS server to inspect the Pull Request object.

EDIT 1

Let A = b00bf1df0 and B = 81317ea59.

Given (I am using Powershell):

C:\Dayforce\tip [master ≡]> git log --format="%h" b00bf1df0..81317ea59
81317ea59
b7d9617fc
C:\Dayforce\tip [master ≡]>

And indeed, the PR in question has one commit - b7d9617fc with 81317ea59 being the PR merge commit (i.e. it is B)

As my other post shows, git diff-tree --name-status -r -M b00bf1df0..81317ea59 is incorrect.

I believe using ... is incorrect too, indeed: git diff-tree --name-status -r -M b00bf1df0...81317ea59 returns nothing.

解决方案

The fundamental problem is that the question is ill-formed:

... list all the files changed in the PR along with the status

A pull request is, in effect, a request of the form: Please change branch name B so that instead of pointing to whatever commit it points to at the time you get around to changing it, it points to commit C instead. Do this however you like, but perhaps by making your own merge commit. This pull-request thing may or may not—it's hard to say since pull requests are not part of Git itself, but rather add-ons provided by various web services, and each web-service can implement this however they like—include information of the form: And by the way, when I asked you to change the value of name B to hash ID C, the value was hash ID O.

Let's assume for the moment that it provides all three items: a branch name B, an old commit hash ID O, and a requested commit hash ID C. Let's also state that B's actual hash value is currently A. Maybe A = O, and maybe not. (If O is not included in the pull request, that's not really fatal, but it will be up to you to find a suitable substitute, and define how it all works and so on.)

What to know about commits

Now, the first thing about a commit hash ID is that it designates one particular commit, and each commit is a complete snapshot of all files. It's not a set of changes at all! It's just a state, like declaring that the temperature outside today is 68˚F (20˚C). By itself, this tells you nothing about what it was yesterday or last week. To turn a state into a change, you must pick some other state to compare it to. If it was 77˚F (25˚C) yesterday, the change is 9˚F (5˚C).

We do the same thing with commits in Git: we pick one state—such as C—and compare it to some previous state. But we just said that the actual previous state is A, while the recorded previous state is O. If you compare C vs O, you'll get a different answer than if you compare C vs A, unless of course A = O.

But that's not the only thing about a commit. Each commit also records zero or more parent commit hash IDs—usually exactly 1, and the next most common is 2 for merge commits. (Zero parents means the commit is a root commit; usually there's just one of these per repository, for the very first commit. Three or more is an octopus merge.) The parents of C are each commits in their own right, which also have parents, and so on. Taking the parents into account, what we really have could be something like this:

...--D--E--F--O--A   <-- B
         \     \
          G--H--C   <-- (pull requested at C)

Or, it could be simpler:

...--D--E--F--A   <-- B
               \
                G--C   <-- (pull requested at C)

where A and O are the same commit. Or it could even be a request to remove some commit(s), though some web services would not allow this, or would take no action if you use their "accept request" interface(s), but let's just draw it:

...--C--O--A   <-- B

(with the request amounting to please "rewind" B so that commits A and O vanish).

What if another merge is called-for?

Let's look at this graph again:

...--D--E--F--O--A   <-- B
         \     \
          G--H--C   <-- (pull requested at C)

On GitHub, at least, if you use the default kind of "merge pull request", what GitHub will do is add a new commit—let's call it M—whose snapshot is formed by running a new git merge (on GitHub),1 even though C itself is a merge commit. The snapshot in M will be that produced by combining changes. The end product will be:

...--D--E--F--O--A--M   <-- B
         \     \ __/
          G--H--C

But GitHub also offers two other ways to accept this pull request, each of which does something different. If commits G, H, and C are not already in your repository, the rebase and merge option produces instead this graph:

...--D--E--F--O--A--G'-H'   <-- B

while the squash and merge option produces:

...--D--E--F--O--A--S   <-- B

where S has the contents that M would have, if GitHub had made M.


1The way GitHub makes a merge is not by using git merge, at least not literally, as there is no way to handle conflicts. GitHub won't even offer to make M if there would be merge conflicts. But the effect is as if GitHub ran a literal git merge, so you can use this as a mental model, at least.


The simplest case: A=O, potential for fast-forward

Suppose the graph really does look like this:

...--D--E--F--A   <-- B
               \
                G--C   <-- (pull requested at C)

so that the current value of B is the same as the old value of B at the time the person making the pull-request made it. That is, we have A = O. If you use the default "merge" button on GitHub, you will get this:

...--D--E--F--A------M   <-- B
               \    /
                G--C

but the contents of commit M will match the contents of commit C exactly, which helps in several ways.

If you use the rebase and merge mode of the merge button, you get this:

...--D--E--F--A--G'-C'   <-- B

where the G' and C' commits are copies of the pull-requester's G and C commits: the snapshots match, and even the parent commit ID of G' matches that of G; what's different is the hash IDs: GitHub has made copies of both G and C even though there's no point to making such copies. (I, for one, wish GitHub did not make these copies at all. It's possible that other services don't.) The original commits don't go in; the copies go in, instead.

If you use the squash and merge button, you get this:

...--D--E--F--A--S   <-- B

where the parent of S is A, while the content of S matches that of C exactly. Again, commits G and C themselves does not enter the repository: only C's content goes in (as the new snapshot in "squash" commit S).

Defining what you want

Now you know that the pull request is really a request to move the branch name, from where it is now (A) to something derived from the commit they've requested (C), perhaps by adding a new merge commit M, perhaps by adding a new squash-merge (ordinary single-parent non-merge commit) S, or perhaps by copying some or all of their commits.

What will actually happen, should you accept their pull request, depends on the button you click on GitHub, or if you're using something else (Bitbucket? GitLab? whatever it may be), whatever that system provides. It's up to you to figure out what will actually happen.

Then, knowing what will actually happen, it's up to you yet again to figure out how to compare the commits that will go into your repository—commits that may not actually exist yet!—to the commits that already are in your repository. Do you want to compare the contents of C to those of A? Do you want to compare C to the contents of O, assuming the hash ID of O is available? Or do you want to look carefully at what each new commit would do compared to its parent(s), even if none of those commits actually exist yet?

The last is, obviously, the hardest: but the production of these new commits follows a well-defined process, so to find out what they will contain, once they do exist, you can simply create them yourself, in your own repository, in the same way your web service will create them tomorrow, should you accept the pull request. The caveat here is that if you do do this—if you project what they will do tomorrow—and then agree that you will click the button tomorrow, what happens if, tomorrow, branch B points to some commit other than A?

If you do choose this last option, of accepting based on what would happen, you will need to build some sort of process around the whole operation to make sure that what would happen remains the same as what does happen. There are many ways to achieve that as well, none of which are perfect.

Achieving what you want

Once you have defined what you want, you still have to achieve it. This is relatively straightforward for GitHub. (I'm not sure what you would do for Bitbucket or other systems.) You will start by using git fetch to obtain, into your own clone of your repository, copies of commit C and any parent commits it has that you lack.

Let's assume that origin is the name of the GitHub repository, and that the pull request is #123. Then:

git fetch origin   # update everything so that we have origin/B pointing to A, etc
git fetch origin refs/pull/123/head:refs/heads/pr123

You now have a branch named pr123 whose tip commit is commit C, along with your remote-tracking name origin/B pointing to commit A.

It's now easy to tell whether A is an ancestor of C, i.e., whether the simplest case is in effect. This particular test is done this way in bash (not sure about PowerShell):

if git merge-base --is-ancestor refs/remotes/origin/B refs/heads/pr123; then
    echo "it's the simple case, yay"
else
    echo "it's the merge case, boo"
fi

In the simple case, regardless of which method you use to accept the pull request, the contents of final commit to which origin/B points in the end will match the contents of commit C, as pointed-to right now by refs/heads/pr123.2 If you've decided that the change you want to test is the overall summary change—that is, from A to C, without looking at any intermediate commits—then the files that will be modified are those listed by:

git diff-tree -r --name-status [options] refs/remotes/origin/B refs/heads/pr123

But if you wish to inspect intermediate commits as well, you have more work to do.

If you're in a merge case, things become even more complex, since—even if you're just going to compare the end-result to a single commit—you must now decide whether to compare to commit A, O, or something else entirely, such as the actual current merge base of A and C, whatever commit that may be.

If you do want to use the actual merge base, this is where the three-dot syntax can be useful. However, due to minor bugs in older versions of Git, I'd advise running git merge-base --all refs/heads/B refs/heads/pr123 here and collecting its output. This will list some number of hash IDs, ideally just one: if you get just one, that's the merge base commit. If you get more than one, they are all candidate merge bases, and the default git merge -s recursive strategy would first merge all the merge bases, then use the resulting commit as the merge base for the merges.

(It's often wisest, or at least simplest, to just reject complicated pull requests, telling whoever authored them that they must rework their pull request such that it does not require any special / fancy merging.)


2Typically, if you have remote-tracking name origin/B and branch-name pr123 pointing to the desired commits, you can just use those names. However, the method that Git uses to turn a name into a hash ID is a six-step process, outlined in the gitrevisions documentation, and "use as branch name" is way down at step four, after "use as tag name"; "use as remote-tracking" name is step five. What this means in practice is that if you have both branch pr123 and tag pr123, the name pr123 refers to the tag, rather than the branch. For scripts, which will run with no human monitoring warning messages, it's wise to spell out the full names: refs/remotes/origin/B and refs/heads/pr123. You can even resolve them once, to hash IDs, and then use the hash IDs everywhere, since the hash IDs never change.

这篇关于如何列出当前正在验证(在VSTS中)的“拉取请求"中更改的所有文件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆