diff --git a/lib/workers/repository/updates/generate.spec.ts b/lib/workers/repository/updates/generate.spec.ts
index ab9ec59aea2f8e7d6aa582ada0b825aaa6563db0..557ae43779ceda41079f23ea32fc738e8f0c0dca 100644
--- a/lib/workers/repository/updates/generate.spec.ts
+++ b/lib/workers/repository/updates/generate.spec.ts
@@ -1,3 +1,4 @@
+import { codeBlock } from 'common-tags';
 import { getConfig } from '../../../../test/util';
 import type { RenovateConfig, UpdateType } from '../../../config/types';
 import { NpmDatasource } from '../../../modules/datasource/npm';
@@ -1164,6 +1165,69 @@ describe('workers/repository/updates/generate', () => {
       );
     });
 
+    it('dedupes duplicate table rows', () => {
+      const branch: BranchUpgradeConfig[] = [
+        {
+          commitBodyTable: true,
+          manager: 'some-manager',
+          datasource: NpmDatasource.id,
+          depName: 'some-dep',
+          groupName: 'some-group',
+          branchName: 'some-branch',
+          prTitle: 'some-title',
+          currentVersion: '5.1.0',
+          newVersion: '5.1.2',
+        },
+        {
+          commitBodyTable: true,
+          manager: 'some-manager',
+          datasource: 'docker',
+          depName: 'some-dep',
+          groupName: 'some-group',
+          branchName: 'some-branch',
+          prTitle: 'some-title',
+          currentVersion: '5.1.0',
+          newVersion: '5.1.2',
+        },
+        {
+          commitBodyTable: true,
+          manager: 'some-manager',
+          datasource: NpmDatasource.id,
+          depName: 'another-dep',
+          groupName: 'some-group',
+          branchName: 'some-branch',
+          prTitle: 'some-title',
+          currentVersion: '5.1.1',
+          newVersion: '5.1.2',
+        },
+        {
+          commitBodyTable: true,
+          manager: 'some-manager',
+          datasource: NpmDatasource.id,
+          depName: 'another-dep',
+          groupName: 'some-group',
+          branchName: 'some-branch',
+          prTitle: 'some-title',
+          currentVersion: '5.1.1',
+          newVersion: '5.1.2',
+        },
+      ];
+      const res = generateBranchConfig(branch);
+      expect(res.commitMessage?.trim()).toBe(codeBlock`
+        | datasource | package     | from  | to    |
+        | ---------- | ----------- | ----- | ----- |
+        | npm        | another-dep | 5.1.1 | 5.1.2 |
+        | npm        | some-dep    | 5.1.0 | 5.1.2 |
+        | docker     | some-dep    | 5.1.0 | 5.1.2 |
+      `);
+      expect([
+        ...(res.commitMessage?.matchAll(/another-dep/g) ?? []),
+      ]).toBeArrayOfSize(1);
+      expect([
+        ...(res.commitMessage?.matchAll(/some-dep/g) ?? []),
+      ]).toBeArrayOfSize(2);
+    });
+
     it('using commitMessagePrefix without separator', () => {
       const branch: BranchUpgradeConfig[] = [
         {
diff --git a/lib/workers/repository/updates/generate.ts b/lib/workers/repository/updates/generate.ts
index b569f3f507a597bb7a3b5997ab521321609873cd..93d2e12209b186b2d166dc9cfbbbf30f6ca23f8a 100644
--- a/lib/workers/repository/updates/generate.ts
+++ b/lib/workers/repository/updates/generate.ts
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
 import is from '@sindresorhus/is';
 import { DateTime } from 'luxon';
 import mdTable from 'markdown-table';
@@ -8,6 +7,7 @@ import { CONFIG_SECRETS_EXPOSED } from '../../../constants/error-messages';
 import { logger } from '../../../logger';
 import { newlineRegex, regEx } from '../../../util/regex';
 import { sanitize } from '../../../util/sanitize';
+import { safeStringify } from '../../../util/stringify';
 import * as template from '../../../util/template';
 import type { BranchConfig, BranchUpgradeConfig } from '../../types';
 import { CommitMessage } from '../model/commit-message';
@@ -387,10 +387,21 @@ export function generateBranchConfig(
   const tableRows = config.upgrades
     .map(getTableValues)
     .filter((x): x is string[] => is.array(x, is.string));
+
   if (tableRows.length) {
-    let table: string[][] = [];
+    const table: string[][] = [];
     table.push(['datasource', 'package', 'from', 'to']);
-    table = table.concat(tableRows!);
+
+    const seenRows = new Set<string>();
+
+    for (const row of tableRows) {
+      const key = safeStringify(row);
+      if (seenRows.has(key)) {
+        continue;
+      }
+      seenRows.add(key);
+      table.push(row);
+    }
     config.commitMessage += '\n\n' + mdTable(table) + '\n';
   }
   return config;