1# Pull request file revisions
2
3On a host that keeps no viewed-file record of its own, this environment keeps the marks and asks
4the host what version each marked file is at, so that a push can report a cleared file as changed.
5That version travels through four layers, and a missing entry means something different at each
6one. No type distinguishes those meanings and no single file states the whole path, which is what
7this page is for.
8
9The 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
11the opposite of the code beneath it, because it described a missing path the way the layer below
12means it. Anything written against that reading would treat a version the host declined to give as
13a file the change request deleted, which reports every file a reader has cleared as changed across
14a project the token cannot see.
15
16## An absence changes meaning at the provider boundary
17
18Below the boundary, in a patch parser or a response decoder, a missing path means this revision
19does not carry that file. Above it, in
20[`ProviderFileRevisions`](../../packages/source-control-core/src/server/PullRequestProvider.ts), a missing path
21means the read could not say. A provider's `getFileRevisions` converts on the way up: a path its
22host looked at and has no version for arrives as the empty string, and absence is kept for what
23the provider never got to look at. Absence below the boundary does not survive it.
24
25What "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
36Those three facts stay with their providers. The contract states only that absence means the read
37could 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
41The empty string is an answer: the host looked, and the head has no version, which is where a
42deleted file sits. It compares equal to a mark stamped with it, so a mark taken against a deletion
43is cleared once and stays cleared. Everything below is the absence of an answer, and the three
44forms are not interchangeable.
45
46A null map, from the service's read of the whole scope, costs those marks their staleness. They
47still 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
49gets no answer does not refuse the reader's tick: it stores the mark with no baseline, which is
50the third form below.
51
52A path absent from an answered map is the per-path case, and it is the one that must not be read
53as a deletion. [`HeldFileRevisions`](../../apps/server/src/pullRequest/pullRequestViewedFiles.ts)
54records what it has asked as well as what it heard, so an absent path keeps the last version given
55for it instead of losing one.
56
57A 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.
60Storing the empty string instead would report the file as changed the moment the host turned out
61to have a version after all. A mark with no baseline reads as cleared until the reader presses it
62again.
63
64Both read-side nulls collapse into that third one on the write path: whether the map was null or
65the path was only absent from it, the press stores a mark with no baseline.