Add alternative linkrev lookup logic

Summary:
The old linkrev lookup logic depended on the repo containing the latest commit
to have contained that particular version of the file. If the latest version had
been stripped however (like what happens in rebase --abort currently), the
linkrev function would attempt to scan history from the current rev,
trying to find the linkrev node.

If the filectx was not provided with a 'current node', the linkrev function
would return None. This caused certain places to break, like the Mercurial
merge conflict resolution logic (which constructs a filectx using only a
fileid, and no changeid, for the merge ancestor).

The fix is to allow scanning all the latest commits in the repo, looking for the
appropriate linkrev. This is pretty slow (1 second for every 14,000 commits
inspected), but is better than just returning None and crashing.

Test Plan:
Manually repro'd the issue by making a commit, amending it, stripping the
amended version and going back to the original, making two sibling commits on
top of the original, then rebasing sibling 1 onto sibling 2 (so that the
original commit that had the bad linknode data was the ancestor during the
merge). Previously this failed, now it passes. I'd write a test, but it's 11pm
and I'm tired and I need this in by early tomorrow morning to make the cut.

Reviewers: #sourcecontrol, ttung, rmcelroy

Reviewed By: rmcelroy

Subscribers: trunkagent, rmcelroy

Differential Revision: https://phabricator.fb.com/D2826850

Signature: t1:2826850:1452680293:cb8c1f8c20ce13ad632925137dbdce6e994ab360
This commit is contained in:
Durham Goode 2016-01-13 11:25:26 -08:00
parent 707f243248
commit 13c2a7823f

View File

@ -56,7 +56,26 @@ class remotefilectx(context.filectx):
if rev is not None:
return rev
# TODO: look up alternate histories in historical blob files
# Search all commits for the appropriate linkrev (slow, but uncommon)
path = self._path
fileid = self._fileid
cl = self._repo.unfiltered().changelog
ma = self._repo.manifest
# If we don't find it within the top 100,000 commits, it probably
# doesn't exist, so give up early instead of wasting the user's time.
searchlimit = max(0, len(cl) - 100000)
for rev in range(len(cl) - 1, searchlimit, -1):
node = cl.node(rev)
data = cl.read(node) # get changeset data (we avoid object creation)
if path in data[3]: # checking the 'files' field.
# The file has been touched, check if the hash is what we're
# looking for.
if fileid == ma.readfast(data[0]).get(path):
return rev
# Couldn't find the linkrev. This should generally not happen, and will
# likely cause a crash.
return None
def introrev(self):