diff --git a/.github/workflows/content-aware-hash.yml b/.github/workflows/content-aware-hash.yml index 84d27b4ccc6..931470b4bba 100644 --- a/.github/workflows/content-aware-hash.yml +++ b/.github/workflows/content-aware-hash.yml @@ -13,14 +13,12 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Fetch base commit and origin/master - run: | - git fetch --no-tags --prune --depth=1 origin ${{ github.event.pull_request.base.sha }} - - name: Generate Hash run: | - engine_content_hash=$(bin/internal/content_aware_hash.sh) + # IMPORTANT: Keep the list of files in sync with bin/internal/content_aware_hash.sh + # We call this directly here as we're expected to be in the merge queue (not master) + engine_content_hash=$(git ls-tree --format "%(objectname) %(path)" HEAD -- DEPS engine bin/internal/release-candidate-branch.version | git hash-object --stdin) # test notice annotation for retrival from api echo "::notice ::{\"engine_content_hash\": \"${engine_content_hash}\"}" # test summary writing - echo "{\"engine_content_hash\": \"${engine_content_hash}\"" >> $GITHUB_STEP_SUMMARY + echo "{\"engine_content_hash\": \"${engine_content_hash}\"" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/bin/internal/content_aware_hash.ps1 b/bin/internal/content_aware_hash.ps1 index 126de3de314..1b1221a52b6 100644 --- a/bin/internal/content_aware_hash.ps1 +++ b/bin/internal/content_aware_hash.ps1 @@ -36,6 +36,28 @@ $flutterRoot = (Get-Item $progName).parent.parent.FullName # 3. Out-File -NoNewline -Encoding ascii outputs 8bit ascii # 4. git hash-object with stdin from a pipeline consumes UTF-16, so consume #. the contents of hash.txt -(git -C "$flutterRoot" ls-tree --format "%(objectname) %(path)" HEAD DEPS engine bin/internal/release-candidate-branch.version | Out-String) -replace "`r`n", "`n" | Out-File -NoNewline -Encoding ascii hash.txt +$trackedFiles = "DEPS", "engine", "bin/internal/release-candidate-branch.version" +$baseRef = "HEAD" + +$ErrorActionPreference = "Continue" +# We will fallback to origin/master if upstream is not detected. +git -C "$flutterRoot" remote get-url upstream *> $null +$exitCode = $? +$ErrorActionPreference = "Stop" +if ($exitCode) { + $mergeBase = (git -C "$flutterRoot" merge-base HEAD upstream/master) +} else { + $mergeBase = (git -C "$flutterRoot" merge-base HEAD origin/master) +} + +# Check to see if we're in a local development branch and the branch has any +# changes to engine code - including non-committed changes. +if ((git -C "$flutterRoot" rev-parse --abbrev-ref HEAD) -ne "master") { + git -C "$flutterRoot" diff --quiet "$(git -C "$flutterRoot" merge-base $mergeBase HEAD)" -- $trackedFiles | Out-Null + if ($LASTEXITCODE -ne 0) { + $baseRef = "$mergeBase" + } +} +(git -C "$flutterRoot" ls-tree --format "%(objectname) %(path)" $baseRef -- $trackedFiles | Out-String) -replace "`r`n", "`n" | Out-File -NoNewline -Encoding ascii hash.txt git hash-object hash.txt Remove-Item hash.txt diff --git a/bin/internal/content_aware_hash.sh b/bin/internal/content_aware_hash.sh index 7b3863d7ad3..efde9581be5 100755 --- a/bin/internal/content_aware_hash.sh +++ b/bin/internal/content_aware_hash.sh @@ -25,4 +25,24 @@ unset GIT_WORK_TREE # bin/internal/content_aware_hash.ps1: script for calculating the hash on windows # bin/internal/content_aware_hash.sh: script for calculating the hash on mac/linux # .github/workflows/content-aware-hash.yml: github action for CI/CD hashing -git -C "$FLUTTER_ROOT" ls-tree --format "%(objectname) %(path)" HEAD DEPS engine bin/internal/release-candidate-branch.version | git hash-object --stdin +TRACKEDFILES="DEPS engine bin/internal/release-candidate-branch.version" +BASEREF="HEAD" + +set +e +# We will fallback to origin/master if upstream is not detected. +git -C "$FLUTTER_ROOT" remote get-url upstream >/dev/null 2>&1 +exit_code=$? +set -e +if [[ $exit_code -eq 0 ]]; then + MERGEBASE=$(git -C "$FLUTTER_ROOT" merge-base HEAD upstream/master) +else + MERGEBASE=$(git -C "$FLUTTER_ROOT" merge-base HEAD origin/master) +fi + +# Check to see if we're in a local development branch and the branch has any +# changes to engine code - including non-committed changes. +if [ "$(git -C "$FLUTTER_ROOT" rev-parse --abbrev-ref HEAD)" != "master" ] && \ + ! git -C "$FLUTTER_ROOT" diff --quiet "$MERGEBASE" -- $TRACKEDFILES; then + BASEREF="$MERGEBASE" +fi +git -C "$FLUTTER_ROOT" ls-tree --format "%(objectname) %(path)" $BASEREF -- $TRACKEDFILES | git hash-object --stdin diff --git a/dev/tools/test/content_aware_hash_test.dart b/dev/tools/test/content_aware_hash_test.dart index 88b166abf2d..a708111c75a 100644 --- a/dev/tools/test/content_aware_hash_test.dart +++ b/dev/tools/test/content_aware_hash_test.dart @@ -114,8 +114,21 @@ void main() { return run(executable, args); } + /// Sets up and fetches a [remote] (such as `upstream` or `origin`) for [testRoot.root]. + /// + /// The remote points at itself (`testRoot.root.path`) for ease of testing. + void setupRemote({required String remote, String? rootPath}) { + run('git', [ + 'remote', + 'add', + remote, + rootPath ?? testRoot.root.path, + ], workingPath: rootPath); + run('git', ['fetch', remote], workingPath: rootPath); + } + /// Initializes a blank git repo in [testRoot.root]. - void initGitRepoWithBlankInitialCommit({String? workingPath}) { + void initGitRepoWithBlankInitialCommit({String? workingPath, String remote = 'upstream'}) { run('git', ['init', '--initial-branch', 'master'], workingPath: workingPath); // autocrlf is very important for tests to work on windows. run('git', 'config --local core.autocrlf true'.split(' '), workingPath: workingPath); @@ -133,6 +146,8 @@ void main() { '-m', 'Initial commit', ], workingPath: workingPath); + + setupRemote(remote: remote, rootPath: workingPath); } void writeFileAndCommit(File file, String contents) { @@ -141,11 +156,58 @@ void main() { run('git', ['commit', '--all', '-m', 'changed ${file.basename} to $contents']); } + void gitSwitchBranch(String branch, {bool create = true}) { + run('git', ['switch', if (create) '-c', branch]); + } + test('generates a hash', () async { initGitRepoWithBlankInitialCommit(); expect(runContentAwareHash(), processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814')); }); + test('generates a hash for origin', () { + initGitRepoWithBlankInitialCommit(remote: 'origin'); + expect(runContentAwareHash(), processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814')); + }); + + group('ignores local engine for', () { + test('upstream', () { + initGitRepoWithBlankInitialCommit(); + gitSwitchBranch('engineTest'); + testRoot.deps.writeAsStringSync('deps changed'); + expect( + runContentAwareHash(), + processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814'), + reason: 'content hash from master for non-committed file', + ); + + writeFileAndCommit(testRoot.deps, 'deps changed'); + expect( + runContentAwareHash(), + processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814'), + reason: 'content hash from master for committed file', + ); + }); + + test('origin', () { + initGitRepoWithBlankInitialCommit(remote: 'origin'); + gitSwitchBranch('engineTest'); + testRoot.deps.writeAsStringSync('deps changed'); + expect( + runContentAwareHash(), + processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814'), + reason: 'content hash from master for non-committed file', + ); + + writeFileAndCommit(testRoot.deps, 'deps changed'); + expect( + runContentAwareHash(), + processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814'), + reason: 'content hash from master for committed file', + ); + }); + }); + group('generates a different hash when', () { setUp(() { initGitRepoWithBlankInitialCommit();