Reland "[gclient] Read submodule status information

This reverts commit 58ff1badf9.

This change also restricts diff only to changes that are submodule related by
utilizing patch diff search.

Original change's description:
> [gclient] Read submodule status information
>
> This allow us to skip sync if we know the state is correct.
>
> R=gavinmak@google.com
>
> Bug: 40283612, 40942309
> Change-Id: I30fd5bfb9ca8ab0f7dcce567e2a5cb4aebdc7b2f
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5480172
> Commit-Queue: Josip Sokcevic <sokcevic@chromium.org>
> Reviewed-by: Gavin Mak <gavinmak@google.com>

Bug: 40283612, 40942309
Change-Id: Iac7ed8c927de1a03a3d60dd50108ddb0b6d96c7e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5513190
Reviewed-by: Gavin Mak <gavinmak@google.com>
Commit-Queue: Josip Sokcevic <sokcevic@chromium.org>
This commit is contained in:
Josip Sokcevic
2024-05-07 00:06:44 +00:00
committed by LUCI CQ
parent 5e65fc3f7a
commit 4c9d29b23b
3 changed files with 214 additions and 29 deletions

View File

@@ -229,6 +229,7 @@ class GitWrapper(SCMWrapper):
filter_kwargs['predicate'] = self.out_cb
self.filter = gclient_utils.GitFilter(**filter_kwargs)
self._running_under_rosetta = None
self.current_revision = None
def GetCheckoutRoot(self):
return scm.GIT.GetCheckoutRoot(self.checkout_path)
@@ -250,6 +251,75 @@ class GitWrapper(SCMWrapper):
['-c', 'core.quotePath=false', 'diff', '--name-only', base])
)).split()
def GetSubmoduleStateFromIndex(self):
"""Returns a map where keys are submodule names and values are commit
hashes. It reads data from the Git index, so only committed values are
present."""
out = self._Capture(['ls-files', '-s'])
result = {}
for l in out.split('\n'):
if not l.startswith('160000'):
# Not a submodule
continue
(_, commit, _, filepath) = l.split(maxsplit=3)
result[filepath] = commit
return result
def GetSubmoduleDiff(self):
"""Returns a map where keys are submodule names and values are tuples of
(old_commit_hash, new_commit_hash). old_commit_hash matches the Git
index, whereas new_commit_hash matches currently checked out commit
hash."""
out = self._Capture([
'diff',
'--no-prefix',
'--no-ext-diff',
'--no-color',
'--ignore-submodules=dirty',
'--submodule=short',
'-G',
'Subproject commit',
])
NO_COMMIT = 40 * '0'
committed_submodule = None
checked_submodule = None
filepath = None
state = 0
diff = {}
# Parsing git diff uses simple state machine. States:
# 0 - start state
# 1 - diff file/line detected, ready to process content
# 2 - gitlink detected, ready to process gitlink past and current
# content.
# 3 - past gitlink content detected. It contains a commit hash that's in
# git index.
# 4 - new gitlink content detected. It contains currently checked
# commit. At this point, we have all information needed, and we can
# reset state to 0.
for l in out.split('\n'):
if l.startswith('diff --git'):
# New file detected, reset state.
state = 1
elif state == 1 and l.startswith('index') and l.endswith('160000'):
# We detected gitlink
state = 2
elif state == 2 and l.startswith('+++ '):
# This line contains filename
filepath = l[4:]
state = 3
elif state == 3 and l.startswith('-Subproject commit '):
# This line contains what commit hash Git index expects
# (ls-files).
committed_submodule = l.split(' ')[-1]
state = 4
elif state == 4 and l.startswith('+Subproject commit '):
# This line contains currently checked out commit for this submodule.
checked_submodule = l.split(' ')[-1]
if NO_COMMIT not in (committed_submodule, checked_submodule):
diff[filepath] = (committed_submodule, checked_submodule)
state = 0
return diff
def diff(self, options, _args, _file_list):
_, revision = gclient_utils.SplitUrlRevision(self.url)
if not revision:
@@ -638,7 +708,6 @@ class GitWrapper(SCMWrapper):
raise gclient_utils.Error("Unsupported argument(s): %s" %
",".join(args))
current_revision = None
url, deps_revision = gclient_utils.SplitUrlRevision(self.url)
revision = deps_revision
managed = True
@@ -714,11 +783,11 @@ class GitWrapper(SCMWrapper):
self._UpdateMirrorIfNotContains(mirror, options, rev_type,
revision)
try:
current_revision = self._Clone(revision, url, options)
self.current_revision = self._Clone(revision, url, options)
except subprocess2.CalledProcessError as e:
logging.warning('Clone failed due to: %s', e)
self._DeleteOrMove(options.force)
current_revision = self._Clone(revision, url, options)
self.current_revision = self._Clone(revision, url, options)
if file_list is not None:
files = self._Capture(
['-c', 'core.quotePath=false', 'ls-files']).splitlines()
@@ -742,6 +811,17 @@ class GitWrapper(SCMWrapper):
self.relpath)
return self._Capture(['rev-parse', '--verify', 'HEAD'])
# Special case for rev_type = hash. If we use submodules, we can check
# information already.
if rev_type == 'hash':
if self.current_revision == revision:
if verbose:
self.Print('Using submodule information to skip check')
if options.reset or options.force:
self._Scrub('HEAD', options)
return revision
self._maybe_break_locks(options)
if mirror: