Skip to content

Commit

Permalink
feat: compare all branch authors when deciding if a branch is modified (
Browse files Browse the repository at this point in the history
  • Loading branch information
Keysox authored Apr 13, 2023
1 parent a12ac33 commit 8b0acd4
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 43 deletions.
2 changes: 1 addition & 1 deletion lib/modules/platform/default-scm.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
4 changes: 2 additions & 2 deletions lib/modules/platform/default-scm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ 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);
}
}
2 changes: 1 addition & 1 deletion lib/modules/platform/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,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>;
Expand Down
48 changes: 47 additions & 1 deletion lib/util/git/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@ describe('util/git/index', () => {
await repo.addConfig('user.email', '[email protected]');
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', '[email protected]');
await repo.commit('commit1 message');
await fs.writeFile(base.path + '/commit2', 'commit2');
await repo.add(['commit2']);
await repo.addConfig('user.email', '[email protected]');
await repo.commit('commit2 message');
await fs.writeFile(base.path + '/commit3', 'commit3');
await repo.add(['commit3']);
await repo.addConfig('user.email', '[email protected]');
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');
Expand Down Expand Up @@ -275,27 +289,59 @@ 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 () => {
git.setUserRepoConfig({
gitIgnoredAuthors: ['[email protected]'],
});
expect(await git.isBranchModified('renovate/custom_author')).toBeFalse();
expect(
await git.isBranchModified('renovate/custom_author', defaultBranch)
).toBeFalse();
});

it('should return false if every author is ignored with multiple commits', async () => {
git.setUserRepoConfig({
gitIgnoredAuthors: ['[email protected]', '[email protected]'],
});
expect(
await git.isBranchModified('renovate/multiple_commits', 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: ['[email protected]'],
});
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();
});
});

Expand Down
85 changes: 54 additions & 31 deletions lib/util/git/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,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;
Expand All @@ -623,21 +626,48 @@ 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('\n')
),
];
} 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('\n')
),
];
}
} catch (err) /* istanbul ignore next */ {
if (err.message?.includes('fatal: bad revision')) {
logger.debug(
Expand All @@ -646,26 +676,19 @@ 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)) {
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(
Expand Down
2 changes: 1 addition & 1 deletion lib/workers/repository/config-migration/branch/rebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 3 additions & 1 deletion lib/workers/repository/onboarding/branch/rebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export async function rebaseOnboardingBranch(
}

// TODO #7154
if (await scm.isBranchModified(config.onboardingBranch!)) {
if (
await scm.isBranchModified(config.onboardingBranch!, config.defaultBranch)
) {
logger.debug('Onboarding branch has been edited and cannot be rebased');
return null;
}
Expand Down
4 changes: 3 additions & 1 deletion lib/workers/repository/onboarding/pr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ If you need any further assistance then you can also [request help here](${
if (GlobalConfig.get('dryRun')) {
// TODO: types (#7154)
logger.info(`DRY-RUN: Would check branch ${config.onboardingBranch!}`);
} else if (await scm.isBranchModified(config.onboardingBranch!)) {
} else if (
await scm.isBranchModified(config.onboardingBranch!, config.defaultBranch)
) {
configDesc = emojify(
`### Configuration\n\n:abcd: Renovate has detected a custom config for this PR. Feel free to ask for [help](${
config.productLinks!.help
Expand Down
5 changes: 4 additions & 1 deletion lib/workers/repository/update/branch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,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') {
Expand Down
4 changes: 2 additions & 2 deletions lib/workers/repository/update/branch/reuse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,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;
Expand All @@ -80,7 +80,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');
Expand Down
2 changes: 1 addition & 1 deletion lib/workers/repository/update/pr/automerge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 8b0acd4

Please sign in to comment.