| 1 | # Pull request file revisions |
| 2 | |
| 3 | On a host that keeps no viewed-file record of its own, this environment keeps the marks and asks |
| 4 | the host what version each marked file is at, so that a push can report a cleared file as changed. |
| 5 | That version travels through four layers, and a missing entry means something different at each |
| 6 | one. No type distinguishes those meanings and no single file states the whole path, which is what |
| 7 | this page is for. |
| 8 | |
| 9 | The failure is on record rather than hypothetical. The interface doc above |
| 10 | [GitLab's `getFileRevisions`](../../packages/source-control-gitlab/src/server/GitLabPullRequestCli.ts) once said |
| 11 | the opposite of the code beneath it, because it described a missing path the way the layer below |
| 12 | means it. Anything written against that reading would treat a version the host declined to give as |
| 13 | a file the change request deleted, which reports every file a reader has cleared as changed across |
| 14 | a project the token cannot see. |
| 15 | |
| 16 | ## An absence changes meaning at the provider boundary |
| 17 | |
| 18 | Below the boundary, in a patch parser or a response decoder, a missing path means this revision |
| 19 | does not carry that file. Above it, in |
| 20 | [`ProviderFileRevisions`](../../packages/source-control-core/src/server/PullRequestProvider.ts), a missing path |
| 21 | means the read could not say. A provider's `getFileRevisions` converts on the way up: a path its |
| 22 | host looked at and has no version for arrives as the empty string, and absence is kept for what |
| 23 | the provider never got to look at. Absence below the boundary does not survive it. |
| 24 | |
| 25 | What "never got to look at" is belongs to the host, and the contract cannot know any of them: |
| 26 | |
| 27 | - [Azure DevOps](../../packages/source-control-azure-devops/src/server/AzureDevOpsPullRequestProvider.ts) reads every |
| 28 | version off one iteration listing, so a change too long to follow to its end leaves the paths |
| 29 | past that point out. |
| 30 | - [Bitbucket](../../packages/source-control-bitbucket/src/server/BitbucketPullRequestApi.ts) reads them off the |
| 31 | pull request's own patch, the only place it states a file's version, so a patch cut short at the |
| 32 | byte ceiling leaves the paths past the cut out. |
| 33 | - [GitLab](../../packages/source-control-gitlab/src/server/GitLabPullRequestCli.ts) asks in batches, so a batch |
| 34 | it could not read leaves that batch's paths out. |
| 35 | |
| 36 | Those three facts stay with their providers. The contract states only that absence means the read |
| 37 | could not say, which is the half a provider must convert to. |
| 38 | |
| 39 | ## Three nulls, and the empty string is not one of them |
| 40 | |
| 41 | The empty string is an answer: the host looked, and the head has no version, which is where a |
| 42 | deleted file sits. It compares equal to a mark stamped with it, so a mark taken against a deletion |
| 43 | is cleared once and stays cleared. Everything below is the absence of an answer, and the three |
| 44 | forms are not interchangeable. |
| 45 | |
| 46 | A null map, from the service's read of the whole scope, costs those marks their staleness. They |
| 47 | still report as cleared; they stop noticing pushes. It happens when the provider offers no |
| 48 | `getFileRevisions` at all, and, on either path, when the call failed and was logged. A press that |
| 49 | gets no answer does not refuse the reader's tick: it stores the mark with no baseline, which is |
| 50 | the third form below. |
| 51 | |
| 52 | A path absent from an answered map is the per-path case, and it is the one that must not be read |
| 53 | as a deletion. [`HeldFileRevisions`](../../apps/server/src/pullRequest/pullRequestViewedFiles.ts) |
| 54 | records what it has asked as well as what it heard, so an absent path keeps the last version given |
| 55 | for it instead of losing one. |
| 56 | |
| 57 | A null on the stored mark is the third, and the only one this environment invents. |
| 58 | [`PullRequestFileViewedMark`](../../apps/server/src/persistence/PullRequestFilesViewed.ts) stores |
| 59 | `revision` as nullable so a press that got no answer for a path can store no baseline at all. |
| 60 | Storing the empty string instead would report the file as changed the moment the host turned out |
| 61 | to have a version after all. A mark with no baseline reads as cleared until the reader presses it |
| 62 | again. |
| 63 | |
| 64 | Both read-side nulls collapse into that third one on the write path: whether the map was null or |
| 65 | the path was only absent from it, the press stores a mark with no baseline. |