GitLab CI中如何生成仅含当前MR变更的changes.diff文件?
Problem Context
在GitLab CI的合并请求(MR)流程中,我需要生成changes.diff文件来汇总当前MR引入的变更,用于通过clang-format-diff验证代码格式是否符合.clang-format规范,同时检查是否新增#if 0代码块。目前项目暂无法整体格式化清理,因此需避免新增不符合规范的内容。现有脚本如下:
oldrev=$(git rev-list HEAD --grep="Merge branch '.*' into 'master'" |head -n1) newrev=${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA:-$(git rev-parse HEAD)} # 1 查找变更文件,仅考虑新增、复制、修改(ACM),排除删除、移动 # 2 忽略非代码文件 files=$(git diff-tree --no-commit-id --name-only --diff-filter=ACM -r $oldrev $newrev | egrep "\.(c|cpp|cxx|cc|h|hpp|ipp|tpp)$") if [[ -z "${files}" ]]; then echo "nothing interesting changed" exit 0 fi echo "Changed files:" echo $files echo $files | xargs git diff -U0 --no-color $oldrev $newrev -- > changes.diff该脚本仅在分支直接从master分叉且未合并过master的场景下正常工作,若分支存在从master合并的操作,changes.diff会包含其他MR的变更,请问如何优化该脚本?
Solution
Great question! The core issue here is that your current way of finding oldrev hunts for merge commits into master, which breaks as soon as you merge master into your branch to keep it up-to-date—this pulls in all the changes from those upstream merges, which aren't part of your MR.
Instead, we should use Git's merge-base command, which reliably finds the latest common ancestor between your MR's source branch and the target branch. This ensures we only diff the changes that are unique to your MR, regardless of how many times you've merged upstream.
Here's the optimized script:
# Use GitLab CI's target branch variable, default to master for local testing TARGET_BRANCH=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-master} # Fetch the latest target branch (critical for shallow clones in CI runners) git fetch origin $TARGET_BRANCH --depth 1 # Find the common ancestor between our branch and the target branch oldrev=$(git merge-base origin/$TARGET_BRANCH ${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA:-$(git rev-parse HEAD)}) newrev=${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA:-$(git rev-parse HEAD)} # 1 查找变更文件,仅考虑新增、复制、修改(ACM),排除删除、移动 # 2 忽略非代码文件 files=$(git diff-tree --no-commit-id --name-only --diff-filter=ACM -r $oldrev $newrev | egrep "\.(c|cpp|cxx|cc|h|hpp|ipp|tpp)$") if [[ -z "${files}" ]]; then echo "nothing interesting changed" exit 0 fi echo "Changed files:" echo $files echo $files | xargs git diff -U0 --no-color $oldrev $newrev -- > changes.diff
What Changed & Why:
git merge-baseinstead of merge commit hunting: This command always finds the last commit that both your branch and the target branch share. Even if you've merged master into your branch multiple times, this will correctly isolate the changes your MR introduces.- Target branch flexibility: We use
CI_MERGE_REQUEST_TARGET_BRANCH_NAMEto get the actual target branch of the MR (no more hardcoding master), which works if your project usesmainor another default branch. - Fetch the target branch: CI runners often use shallow clones to save time, so adding
git fetch origin $TARGET_BRANCHensures we have the latest version of the target branch to compute the correct ancestor. - Local testing support: The script still works if you run it on your local machine, falling back to
masteras the target branch.
Quick Notes:
- If your CI runner uses a very shallow clone, the
--depth 1flag ongit fetchis enough to get the latest target branch commit needed formerge-base. - This approach completely excludes changes from upstream merges—only your MR's unique changes will be in
changes.diff, which is exactly what you need for clang-format-diff validation.
内容的提问来源于stack exchange,提问作者MateuszL

