mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-11 22:46:27 +00:00
feat: compare all branch authors when deciding if a branch is modified (#21558)
Co-authored-by: Rhys Arkins <rhys@arkins.net>
This commit is contained in:
parent
e13f5a09f5
commit
c2cb93ce44
9 changed files with 106 additions and 41 deletions
|
@ -45,7 +45,7 @@ describe('modules/platform/default-scm', () => {
|
|||
|
||||
it('delegate isBranchModified to util/git', async () => {
|
||||
git.isBranchModified.mockResolvedValueOnce(true);
|
||||
await defaultGitScm.isBranchModified('branchName');
|
||||
await defaultGitScm.isBranchModified('branchName', 'main');
|
||||
expect(git.isBranchModified).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
|
|
@ -27,8 +27,8 @@ export class DefaultGitScm implements PlatformScm {
|
|||
return git.isBranchConflicted(baseBranch, branch);
|
||||
}
|
||||
|
||||
isBranchModified(branchName: string): Promise<boolean> {
|
||||
return git.isBranchModified(branchName);
|
||||
isBranchModified(branchName: string, baseBranch?: string): Promise<boolean> {
|
||||
return git.isBranchModified(branchName, baseBranch);
|
||||
}
|
||||
|
||||
getFileList(): Promise<string[]> {
|
||||
|
|
|
@ -233,7 +233,7 @@ export interface Platform {
|
|||
|
||||
export interface PlatformScm {
|
||||
isBranchBehindBase(branchName: string, baseBranch: string): Promise<boolean>;
|
||||
isBranchModified(branchName: string): Promise<boolean>;
|
||||
isBranchModified(branchName: string, baseBranch?: string): Promise<boolean>;
|
||||
isBranchConflicted(baseBranch: string, branch: string): Promise<boolean>;
|
||||
branchExists(branchName: string): Promise<boolean>;
|
||||
getBranchCommit(branchName: string): Promise<CommitSha | null>;
|
||||
|
|
|
@ -77,6 +77,20 @@ describe('util/git/index', () => {
|
|||
await repo.addConfig('user.email', 'custom@example.com');
|
||||
await repo.commit('custom message');
|
||||
|
||||
await repo.checkoutBranch('renovate/multiple_commits', defaultBranch);
|
||||
await fs.writeFile(base.path + '/commit1', 'commit1');
|
||||
await repo.add(['commit1']);
|
||||
await repo.addConfig('user.email', 'author1@example.com');
|
||||
await repo.commit('commit1 message');
|
||||
await fs.writeFile(base.path + '/commit2', 'commit2');
|
||||
await repo.add(['commit2']);
|
||||
await repo.addConfig('user.email', 'author2@example.com');
|
||||
await repo.commit('commit2 message');
|
||||
await fs.writeFile(base.path + '/commit3', 'commit3');
|
||||
await repo.add(['commit3']);
|
||||
await repo.addConfig('user.email', 'author1@example.com');
|
||||
await repo.commit('commit3 message');
|
||||
|
||||
await repo.checkoutBranch('renovate/nested_files', defaultBranch);
|
||||
await fs.mkdirp(base.path + '/bin/');
|
||||
await fs.writeFile(base.path + '/bin/nested', 'nested');
|
||||
|
@ -275,11 +289,16 @@ describe('util/git/index', () => {
|
|||
|
||||
it('should return false when branch is not found', async () => {
|
||||
expect(await git.isBranchModified('renovate/not_found')).toBeFalse();
|
||||
expect(
|
||||
await git.isBranchModified('renovate/not_found', defaultBranch)
|
||||
).toBeFalse();
|
||||
});
|
||||
|
||||
it('should return false when author matches', async () => {
|
||||
expect(await git.isBranchModified('renovate/future_branch')).toBeFalse();
|
||||
expect(await git.isBranchModified('renovate/future_branch')).toBeFalse();
|
||||
expect(
|
||||
await git.isBranchModified('renovate/future_branch', defaultBranch)
|
||||
).toBeFalse();
|
||||
});
|
||||
|
||||
it('should return false when author is ignored', async () => {
|
||||
|
@ -287,15 +306,33 @@ describe('util/git/index', () => {
|
|||
gitIgnoredAuthors: ['custom@example.com'],
|
||||
});
|
||||
expect(await git.isBranchModified('renovate/custom_author')).toBeFalse();
|
||||
expect(
|
||||
await git.isBranchModified('renovate/custom_author', defaultBranch)
|
||||
).toBeFalse();
|
||||
});
|
||||
|
||||
it('should return true when custom author is unknown', async () => {
|
||||
expect(await git.isBranchModified('renovate/custom_author')).toBeTrue();
|
||||
expect(
|
||||
await git.isBranchModified('renovate/custom_author', defaultBranch)
|
||||
).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return true if any commit is modified', async () => {
|
||||
git.setUserRepoConfig({
|
||||
gitIgnoredAuthors: ['author1@example.com'],
|
||||
});
|
||||
expect(
|
||||
await git.isBranchModified('renovate/multiple_commits', defaultBranch)
|
||||
).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return value stored in modifiedCacheResult', async () => {
|
||||
modifiedCache.getCachedModifiedResult.mockReturnValue(true);
|
||||
expect(await git.isBranchModified('renovate/future_branch')).toBeTrue();
|
||||
expect(
|
||||
await git.isBranchModified('renovate/future_branch', defaultBranch)
|
||||
).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -616,7 +616,10 @@ export async function isBranchBehindBase(
|
|||
}
|
||||
}
|
||||
|
||||
export async function isBranchModified(branchName: string): Promise<boolean> {
|
||||
export async function isBranchModified(
|
||||
branchName: string,
|
||||
baseBranch?: string
|
||||
): Promise<boolean> {
|
||||
if (!branchExists(branchName)) {
|
||||
logger.debug('branch.isModified(): no cache');
|
||||
return false;
|
||||
|
@ -636,21 +639,49 @@ export async function isBranchModified(branchName: string): Promise<boolean> {
|
|||
return isModified;
|
||||
}
|
||||
|
||||
logger.debug('branch.isModified(): using git to calculate');
|
||||
|
||||
await syncGit();
|
||||
// Retrieve the author of the most recent commit
|
||||
let lastAuthor: string | undefined;
|
||||
// Retrieve the commit authors
|
||||
let branchAuthors: string[] = [];
|
||||
try {
|
||||
lastAuthor = (
|
||||
await git.raw([
|
||||
'log',
|
||||
'-1',
|
||||
'--pretty=format:%ae',
|
||||
`origin/${branchName}`,
|
||||
'--',
|
||||
])
|
||||
).trim();
|
||||
if (baseBranch) {
|
||||
logger.debug(
|
||||
`branch.isModified(): using git to calculate authors between ${branchName} and ${baseBranch}`
|
||||
);
|
||||
branchAuthors = [
|
||||
...new Set(
|
||||
(
|
||||
await git.raw([
|
||||
'log',
|
||||
'--pretty=format:%ae',
|
||||
`origin/${branchName}..origin/${baseBranch}`,
|
||||
'--',
|
||||
])
|
||||
)
|
||||
.trim()
|
||||
.split(newlineRegex)
|
||||
),
|
||||
];
|
||||
logger.debug(`Git authors in branch: ${branchAuthors.join(', ')}`);
|
||||
} else {
|
||||
logger.debug(
|
||||
`branch.isModified(): checking last author of ${branchName}`
|
||||
);
|
||||
branchAuthors = [
|
||||
...new Set(
|
||||
(
|
||||
await git.raw([
|
||||
'log',
|
||||
'-1',
|
||||
'--pretty=format:%ae',
|
||||
`origin/${branchName}`,
|
||||
'--',
|
||||
])
|
||||
)
|
||||
.trim()
|
||||
.split(newlineRegex)
|
||||
),
|
||||
];
|
||||
}
|
||||
} catch (err) /* istanbul ignore next */ {
|
||||
if (err.message?.includes('fatal: bad revision')) {
|
||||
logger.debug(
|
||||
|
@ -659,26 +690,20 @@ export async function isBranchModified(branchName: string): Promise<boolean> {
|
|||
);
|
||||
throw new Error(REPOSITORY_CHANGED);
|
||||
}
|
||||
logger.warn({ err }, 'Error checking last author for isBranchModified');
|
||||
logger.warn({ err }, 'Error retrieving git authors for isBranchModified');
|
||||
}
|
||||
const { gitAuthorEmail } = config;
|
||||
if (
|
||||
lastAuthor === gitAuthorEmail ||
|
||||
config.ignoredAuthors.some((ignoredAuthor) => lastAuthor === ignoredAuthor)
|
||||
) {
|
||||
// author matches - branch has not been modified
|
||||
logger.debug('branch.isModified() = false');
|
||||
config.branchIsModified[branchName] = false;
|
||||
setCachedModifiedResult(branchName, false);
|
||||
return false;
|
||||
let branchIsModified = false;
|
||||
for (const author of branchAuthors) {
|
||||
if (author !== gitAuthorEmail && !config.ignoredAuthors.includes(author)) {
|
||||
logger.debug(`branch.isModified=true due to this git author: ${author}`);
|
||||
branchIsModified = true;
|
||||
}
|
||||
}
|
||||
logger.debug(
|
||||
{ branchName, lastAuthor, gitAuthorEmail },
|
||||
'branch.isModified() = true'
|
||||
);
|
||||
config.branchIsModified[branchName] = true;
|
||||
setCachedModifiedResult(branchName, true);
|
||||
return true;
|
||||
logger.debug(`branch.isModified() = ${branchIsModified}`);
|
||||
config.branchIsModified[branchName] = branchIsModified;
|
||||
setCachedModifiedResult(branchName, branchIsModified);
|
||||
return branchIsModified;
|
||||
}
|
||||
|
||||
export async function isBranchConflicted(
|
||||
|
|
|
@ -16,7 +16,7 @@ export async function rebaseMigrationBranch(
|
|||
): Promise<string | null> {
|
||||
logger.debug('Checking if migration branch needs rebasing');
|
||||
const branchName = getMigrationBranchName(config);
|
||||
if (await scm.isBranchModified(branchName)) {
|
||||
if (await scm.isBranchModified(branchName, config.baseBranch)) {
|
||||
logger.debug('Migration branch has been edited and cannot be rebased');
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -242,7 +242,10 @@ export async function processBranch(
|
|||
}
|
||||
|
||||
logger.debug('Checking if PR has been edited');
|
||||
const branchIsModified = await scm.isBranchModified(config.branchName);
|
||||
const branchIsModified = await scm.isBranchModified(
|
||||
config.branchName,
|
||||
config.baseBranch
|
||||
);
|
||||
if (branchPr) {
|
||||
logger.debug('Found existing branch PR');
|
||||
if (branchPr.state !== 'open') {
|
||||
|
|
|
@ -29,7 +29,7 @@ export async function shouldReuseExistingBranch(
|
|||
if (await scm.isBranchBehindBase(branchName, baseBranch)) {
|
||||
logger.debug(`Branch is behind base branch and needs rebasing`);
|
||||
// We can rebase the branch only if no PR or PR can be rebased
|
||||
if (await scm.isBranchModified(branchName)) {
|
||||
if (await scm.isBranchModified(branchName, baseBranch)) {
|
||||
logger.debug('Cannot rebase branch as it has been modified');
|
||||
result.reuseExistingBranch = true;
|
||||
result.isModified = true;
|
||||
|
@ -50,7 +50,7 @@ export async function shouldReuseExistingBranch(
|
|||
if (result.isConflicted) {
|
||||
logger.debug('Branch is conflicted');
|
||||
|
||||
if ((await scm.isBranchModified(branchName)) === false) {
|
||||
if ((await scm.isBranchModified(branchName, baseBranch)) === false) {
|
||||
logger.debug(`Branch is not mergeable and needs rebasing`);
|
||||
if (config.rebaseWhen === 'never') {
|
||||
logger.debug('Rebasing disabled by config');
|
||||
|
|
|
@ -82,7 +82,7 @@ export async function checkAutoMerge(
|
|||
};
|
||||
}
|
||||
// Check if it's been touched
|
||||
if (await scm.isBranchModified(branchName)) {
|
||||
if (await scm.isBranchModified(branchName, config.baseBranch)) {
|
||||
logger.debug('PR is ready for automerge but has been modified');
|
||||
return {
|
||||
automerged: false,
|
||||
|
|
Loading…
Reference in a new issue