Skip to content

Commit 3a6520e

Browse files
Better handling of squash merging dependabot/renovate commits
1 parent c68269d commit 3a6520e

7 files changed

Lines changed: 90 additions & 39 deletions

File tree

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ The app can be started using the following `compose.yml`:
3939
services:
4040
containers-up:
4141
# https://github.com/DigitallyRefined/containers-up/releases
42-
image: ghcr.io/digitallyrefined/containers-up:1.4.1
42+
image: ghcr.io/digitallyrefined/containers-up:1.4.2
4343
restart: unless-stopped
4444
ports:
4545
- 3000:3000
@@ -63,7 +63,7 @@ Optional system wide configuration can be changed by copying `.env.default` to `
6363
services:
6464
containers-up:
6565
# https://github.com/DigitallyRefined/containers-up/releases
66-
image: ghcr.io/digitallyrefined/containers-up:1.4.1
66+
image: ghcr.io/digitallyrefined/containers-up:1.4.2
6767
restart: unless-stopped
6868
volumes:
6969
- ./containers-up/storage:/storage
@@ -92,13 +92,14 @@ services:
9292

9393
pocket-id:
9494
# https://github.com/pocket-id/pocket-id/releases
95-
image: ghcr.io/pocket-id/pocket-id:v1.13.1
95+
image: ghcr.io/pocket-id/pocket-id:v2.2.0
9696
restart: unless-stopped
9797
volumes:
9898
- './pocket-id/data:/app/data'
9999
environment:
100100
- APP_URL=https://id.example.com # < Update this
101101
- TRUST_PROXY=true
102+
- ENCRYPTION_KEY="run `openssl rand -base64 32`" # < Run to generate a unique key
102103
networks:
103104
- 'traefik'
104105
labels:
@@ -111,7 +112,7 @@ services:
111112
traefik:
112113
# Check migration guide first: https://doc.traefik.io/traefik/master/migration/v3/
113114
# https://github.com/traefik/traefik/releases
114-
image: docker.io/traefik:3.5.0
115+
image: traefik:v3.6.7
115116
container_name: 'traefik'
116117
restart: unless-stopped
117118
ports:
@@ -226,7 +227,7 @@ jobs:
226227
renovate:
227228
runs-on: docker
228229
container:
229-
image: renovate/renovate:42.90.2
230+
image: renovate/renovate:43.0.6
230231
231232
steps:
232233
- name: Restore Renovate Cache

bun.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
services:
22
containers-up:
33
# https://github.com/DigitallyRefined/containers-up/releases
4-
image: ghcr.io/digitallyrefined/containers-up:1.4.1
4+
image: ghcr.io/digitallyrefined/containers-up:1.4.2
55
container_name: containers-up
66
restart: unless-stopped
77
volumes:

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "containers-up",
3-
"version": "1.4.1",
3+
"version": "1.4.2",
44
"description": "Containers Up! a web based platform designed to manage and update containers",
55
"author": "DigitallyRefined",
66
"license": "ISC",
@@ -31,7 +31,7 @@
3131
"@radix-ui/react-toast": "^1.2.15",
3232
"@radix-ui/react-tooltip": "^1.2.8",
3333
"@tanstack/react-query": "^5.90.20",
34-
"@tanstack/react-query-devtools": "^5.91.2",
34+
"@tanstack/react-query-devtools": "^5.91.3",
3535
"bun-plugin-tailwind": "^0.1.2",
3636
"class-variance-authority": "^0.7.1",
3737
"clsx": "^2.1.1",
@@ -51,7 +51,7 @@
5151
"devDependencies": {
5252
"@biomejs/biome": "2.3.13",
5353
"@tailwindcss/cli": "^4.1.18",
54-
"@types/bun": "^1.3.7",
54+
"@types/bun": "^1.3.8",
5555
"@types/pino": "^7.0.5",
5656
"@types/pino-pretty": "^5.0.0",
5757
"@types/react": "^19.2.10",

src/backend/db/job.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ export const job = {
6161

6262
return addLogsToJobs(jobs);
6363
},
64+
getIncompleteJobsWithPr: async (hostId: number) => {
65+
const db = await getDb();
66+
return db
67+
.query(
68+
`SELECT * FROM job WHERE hostId = $hostId AND status != ${JobStatus.completed} AND status != ${JobStatus.closed} AND repoPr IS NOT NULL ORDER BY status, updated DESC LIMIT 50`
69+
)
70+
.as(JobWithLogs)
71+
.all({ hostId });
72+
},
6473
getRunningJobs: async (hostId: number) => {
6574
const db = await getDb();
6675
return db

src/backend/endpoints/webhook/pull-restart.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import path from 'node:path';
22
import type { Logger } from 'pino';
3-
3+
import { job as jobDb } from '@/backend/db/job';
44
import type { Host } from '@/backend/db/schema/host';
55
import { isComposeFilename } from '@/backend/utils';
66
import { createDockerExec } from '@/backend/utils/docker';
@@ -18,7 +18,7 @@ export const pullRestartUpdatedContainers = async (
1818
repoConfig: Host,
1919
logger: Logger
2020
) => {
21-
const { workingFolder, excludeFolders, name, sshHost: host } = repoConfig;
21+
const { id, workingFolder, excludeFolders, name, sshHost: host } = repoConfig;
2222

2323
const exec = createExec(logger);
2424
const dockerExec = createDockerExec(logger);
@@ -31,7 +31,7 @@ export const pullRestartUpdatedContainers = async (
3131
};
3232

3333
const checkAndSquashUpdates = async () => {
34-
if (repoConfig.squashUpdates) {
34+
if (repoConfig.squashUpdates && (await jobDb.getIncompleteJobsWithPr(id)).length <= 1) {
3535
try {
3636
await squashUpdates(sshRun, logger);
3737
} catch (err) {

src/backend/utils/git.ts

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,27 @@ const hasUncommittedChanges = async (
2929
return !!status.stdout.trim();
3030
};
3131

32+
const getCommitMetadata = async (
33+
sshRun: (cmd: string) => Promise<{ stdout: string }>,
34+
skipCount = 0
35+
) => {
36+
const skip = skipCount > 0 ? `--skip=${skipCount} ` : '';
37+
const subject = (await sshRun(`git log -1 ${skip}--format=%s`)).stdout.trim();
38+
const body = (await sshRun(`git log -1 ${skip}--format=%b`)).stdout.trim();
39+
const authorDate = (await sshRun(`git log -1 ${skip}--format=%aI`)).stdout.trim();
40+
const authorName = (await sshRun(`git log -1 ${skip}--format=%an`)).stdout.trim();
41+
const timestamp =
42+
Number.parseInt((await sshRun(`git log -1 ${skip}--format=%at`)).stdout.trim()) * 1000;
43+
44+
return {
45+
subject,
46+
body,
47+
authorDate,
48+
authorName,
49+
timestamp,
50+
};
51+
};
52+
3253
const updateCurrentCommit = async (
3354
sshRun: (cmd: string) => Promise<{ stdout: string }>,
3455
subject: string,
@@ -132,33 +153,53 @@ export const squashUpdates = async (
132153
return;
133154
}
134155

135-
// Get current commit info
136-
const lastCommitSubject = (await sshRun('git log -1 --format=%s')).stdout.trim();
137-
const lastCommitBody = (await sshRun('git log -1 --format=%b')).stdout.trim();
156+
// Keep squashing while the previous commit is from an automated author
157+
let continueSquashing = true;
158+
let loopCount = 0;
159+
const MAX_SQUASH_LOOPS = 50; // To prevent infinite loops
160+
161+
while (continueSquashing && loopCount < MAX_SQUASH_LOOPS) {
162+
loopCount++;
163+
logger.info(`Squash loop iteration ${loopCount}`);
164+
165+
// Get current commit info (needs to be inside loop as HEAD changes after each squash)
166+
const lastCommit = await getCommitMetadata(sshRun, 0);
167+
168+
// Get previous commit info
169+
const prevCommit = await getCommitMetadata(sshRun, 1);
170+
171+
const isAutomatedAuthor = /dependabot|renovate/i.test(prevCommit.authorName);
172+
173+
// If previous commit is not from automated author, stop squashing
174+
if (!isAutomatedAuthor) {
175+
continueSquashing = false;
176+
} else {
177+
// Previous commit is from automated author, squash them together
178+
logger.info(`Squashing last 2 commits together (previous author: ${prevCommit.authorName})`);
179+
await squashLastTwoCommits(
180+
sshRun,
181+
lastCommit.subject,
182+
lastCommit.body,
183+
prevCommit.body,
184+
prevCommit.authorDate
185+
);
186+
}
187+
}
188+
189+
if (loopCount >= MAX_SQUASH_LOOPS) {
190+
logger.warn(`Reached maximum squash loop limit of ${MAX_SQUASH_LOOPS}`);
191+
}
138192

139-
// Get previous commit info
140-
const prevCommitSubject = (await sshRun('git log -1 --skip=1 --format=%s')).stdout.trim();
141-
const prevCommitBody = (await sshRun('git log -1 --skip=1 --format=%b')).stdout.trim();
142-
const prevCommitAuthorDate = (await sshRun('git log -1 --skip=1 --format=%aI')).stdout.trim();
143-
const prevCommitTimestamp =
144-
Number.parseInt((await sshRun('git log -1 --skip=1 --format=%at')).stdout.trim()) * 1000;
193+
// Get current commit info after squashing
194+
const lastCommit = await getCommitMetadata(sshRun, 0);
195+
const prevCommit = await getCommitMetadata(sshRun, 1);
145196

146197
const DAYS_AGO = Date.now() - SQUASH__DEPS_DAYS_AGO * 24 * 60 * 60 * 1000;
147198

148199
// If previous commit is old or doesn't start with "Update dependencies", just update current commit
149-
if (prevCommitTimestamp < DAYS_AGO || !prevCommitSubject.includes(SQUASH_UPDATE_MESSAGE)) {
200+
if (prevCommit.timestamp < DAYS_AGO || !prevCommit.subject.includes(SQUASH_UPDATE_MESSAGE)) {
150201
logger.info('Updating current commit message only (previous is old or not an update commit)');
151-
await updateCurrentCommit(sshRun, lastCommitSubject, lastCommitBody);
152-
} else {
153-
// Previous commit already has "Update dependencies", squash them together
154-
logger.info('Squashing last 2 commits together');
155-
await squashLastTwoCommits(
156-
sshRun,
157-
lastCommitSubject,
158-
lastCommitBody,
159-
prevCommitBody,
160-
prevCommitAuthorDate
161-
);
202+
await updateCurrentCommit(sshRun, lastCommit.subject, lastCommit.body);
162203
}
163204

164205
// After squashing, check if we've exceeded the max number of update commits

0 commit comments

Comments
 (0)