Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions src/taskgraph/util/vcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ def base_rev(self):
def branch(self):
return self.run("branch", "--show-current").strip() or None

@property
def is_shallow(self):
return self.run("rev-parse", "--is-shallow-repository").strip() == "true"

@property
def all_remote_names(self):
remotes = self.run("remote").splitlines()
Expand Down Expand Up @@ -546,10 +550,36 @@ def update(self, ref):
self.run("checkout", ref)

def find_latest_common_revision(self, base_ref_or_rev, head_rev):
try:
return self.run("merge-base", base_ref_or_rev, head_rev).strip()
except subprocess.CalledProcessError:
return self.NULL_REVISION
def run_merge_base():
try:
return self.run("merge-base", base_ref_or_rev, head_rev).strip()
except subprocess.CalledProcessError:
return None

if not self.is_shallow:
return run_merge_base() or self.NULL_REVISION

rev = run_merge_base()
deepen = 10
while not rev:
self.run("fetch", "--deepen", str(deepen), self.remote_name)
rev = run_merge_base()
deepen = deepen * 10

try:
self.run("rev-list", "--max-parents=0", head_rev)
# We've reached a root commit, stop trying to deepen.
break
except subprocess.CalledProcessError:
pass

if not rev:
# If we still haven't found a merge base, unshallow the repo and
# try one last time.
self.run("fetch", "--unshallow", self.remote_name)
rev = run_merge_base()

return rev or self.NULL_REVISION

def does_revision_exist_locally(self, revision):
try:
Expand Down
53 changes: 53 additions & 0 deletions test/test_util_vcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import os
import shutil
import subprocess
from pathlib import Path
from textwrap import dedent
Expand Down Expand Up @@ -495,6 +496,58 @@ def test_find_latest_common_revision(repo_with_remote):
)


def test_find_latest_common_revision_shallow_clone(
tmpdir, git_repo, default_git_branch
):
"""Test finding common revision in a shallow clone that requires deepening."""
remote_path = str(tmpdir / "remote_repo")
shutil.copytree(git_repo, remote_path)

# Add several commits to the remote repository to create depth
remote_repo = get_repository(remote_path)

# Create multiple commits to establish depth
for i in range(5):
test_file = os.path.join(remote_path, f"file_{i}.txt")
with open(test_file, "w") as f:
f.write(f"content {i}")
remote_repo.run("add", test_file)
remote_repo.run("commit", "-m", f"Commit {i}")

# Store the head revision of remote for comparison
remote_head = remote_repo.head_rev

# Create a shallow clone with depth 1
# Need to use file:// protocol for --depth to work with local repos
shallow_clone_path = str(tmpdir / "shallow_clone")
subprocess.check_call(
["git", "clone", "--depth", "1", f"file://{remote_path}", shallow_clone_path]
)

shallow_repo = get_repository(shallow_clone_path)
assert shallow_repo.is_shallow

remote_name = "origin"

# Create a new commit in the shallow clone to diverge from remote
new_file = os.path.join(shallow_clone_path, "local_file.txt")
with open(new_file, "w") as f:
f.write("local content")
shallow_repo.run("add", new_file)
shallow_repo.run("commit", "-m", "Local commit")

# Now try to find the common revision - this should trigger deepening
# because the shallow clone doesn't have enough history
base_ref = f"{remote_name}/{default_git_branch}"
result = shallow_repo.find_latest_common_revision(base_ref, shallow_repo.head_rev)

# The result should be the remote's head (the common ancestor)
assert result == remote_head

# Verify the repository has been deepened
assert shallow_repo.does_revision_exist_locally(result)


def test_does_revision_exist_locally(repo):
first_revision = repo.head_rev

Expand Down
Loading