Skip to content

Commit 450b646

Browse files
committed
Merge remote-tracking branch 'upstream/main' into revert-dir-parsePush
2 parents dbce7af + 464929e commit 450b646

23 files changed

+2183
-518
lines changed

index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/usr/bin/env tsx
22
/* eslint-disable max-len */
3+
import path from 'path';
34
import yargs from 'yargs';
45
import { hideBin } from 'yargs/helpers';
56
import * as fs from 'fs';
@@ -19,7 +20,7 @@ const argv = yargs(hideBin(process.argv))
1920
},
2021
config: {
2122
description: 'Path to custom git-proxy configuration file.',
22-
default: 'proxy.config.json',
23+
default: path.join(__dirname, 'proxy.config.json'),
2324
required: false,
2425
alias: 'c',
2526
type: 'string',

package-lock.json

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

package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"cors": "^2.8.5",
5353
"diff2html": "^3.4.33",
5454
"env-paths": "^2.2.1",
55-
"express": "^4.18.2",
55+
"express": "^4.21.2",
5656
"express-http-proxy": "^2.0.0",
5757
"express-rate-limit": "^7.1.5",
5858
"express-session": "^1.17.1",
@@ -83,9 +83,9 @@
8383
"yargs": "^17.7.2"
8484
},
8585
"devDependencies": {
86-
"@babel/core": "^7.23.2",
87-
"@babel/eslint-parser": "^7.22.9",
88-
"@babel/preset-react": "^7.22.5",
86+
"@babel/core": "^7.27.1",
87+
"@babel/eslint-parser": "^7.27.1",
88+
"@babel/preset-react": "^7.27.1",
8989
"@commitlint/cli": "^19.0.0",
9090
"@commitlint/config-conventional": "^19.0.0",
9191
"@types/domutils": "^1.7.8",
@@ -102,7 +102,7 @@
102102
"@vitejs/plugin-react": "^4.0.2",
103103
"chai": "^4.2.0",
104104
"chai-http": "^4.3.0",
105-
"cypress": "^14.0.0",
105+
"cypress": "^14.5.1",
106106
"eslint": "^8.57.0",
107107
"eslint-config-google": "^0.14.0",
108108
"eslint-config-prettier": "^10.0.1",
@@ -113,22 +113,22 @@
113113
"eslint-plugin-typescript": "^0.14.0",
114114
"husky": "^9.0.0",
115115
"mocha": "^10.8.2",
116-
"nyc": "^17.0.0",
116+
"nyc": "^17.1.0",
117117
"prettier": "^3.0.0",
118118
"proxyquire": "^2.1.3",
119119
"sinon": "^21.0.0",
120120
"ts-mocha": "^11.1.0",
121121
"ts-node": "^10.9.2",
122122
"tsx": "^4.19.3",
123123
"typescript": "^5.7.3",
124-
"vite": "^4.5.13",
124+
"vite": "^4.5.14",
125125
"vite-tsconfig-paths": "^5.1.4"
126126
},
127127
"optionalDependencies": {
128-
"@esbuild/darwin-arm64": "^0.18.20",
129-
"@esbuild/darwin-x64": "^0.23.0",
130-
"@esbuild/linux-x64": "0.23.0",
131-
"@esbuild/win32-x64": "0.23.0"
128+
"@esbuild/darwin-arm64": "^0.25.6",
129+
"@esbuild/darwin-x64": "^0.25.6",
130+
"@esbuild/linux-x64": "0.25.6",
131+
"@esbuild/win32-x64": "0.25.6"
132132
},
133133
"browserslist": {
134134
"production": [

src/config/file.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { readFileSync } from 'fs';
22
import { join } from 'path';
33
import { validate as jsonSchemaValidate } from 'jsonschema';
44

5-
export let configFile: string = join(process.cwd(), 'proxy.config.json');
5+
export let configFile: string = join(__dirname, '../../proxy.config.json');
66

77
/**
88
* Set the config file path.
@@ -20,7 +20,7 @@ export function setConfigFile(file: string) {
2020
*/
2121
export function validate(configFilePath: string = configFile!): boolean {
2222
const config = JSON.parse(readFileSync(configFilePath, 'utf-8'));
23-
const schemaPath = join(process.cwd(), 'config.schema.json');
23+
const schemaPath = join(__dirname, '../../config.schema.json');
2424
const schema = JSON.parse(readFileSync(schemaPath, 'utf-8'));
2525
jsonSchemaValidate(config, schema, { required: true, throwError: true });
2626
return true;

src/config/index.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,7 @@ export const getAPIAuthMethods = (): Authentication[] => {
126126
_apiAuthentication = _userSettings.apiAuthentication;
127127
}
128128

129-
const enabledAuthMethods = _apiAuthentication.filter((auth) => auth.enabled);
130-
131-
if (enabledAuthMethods.length === 0) {
132-
console.log('Warning: No authentication method enabled for API endpoints.');
133-
}
134-
135-
return enabledAuthMethods;
129+
return _apiAuthentication.filter((auth) => auth.enabled);
136130
};
137131

138132
// Log configuration to console

src/proxy/actions/Action.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class Action {
4848
attestation?: string;
4949
lastStep?: Step;
5050
proxyGitPath?: string;
51+
newIdxFiles?: string[];
5152

5253
/**
5354
* Create an action.

src/proxy/chain.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ const pushActionChain: ((req: any, action: Action) => Promise<Action>)[] = [
99
proc.push.checkCommitMessages,
1010
proc.push.checkAuthorEmails,
1111
proc.push.checkUserPushPermission,
12-
proc.push.checkIfWaitingAuth,
1312
proc.push.pullRemote,
1413
proc.push.writePack,
14+
proc.push.checkHiddenCommits,
15+
proc.push.checkIfWaitingAuth,
16+
proc.push.getMissingData,
1517
proc.push.preReceive,
1618
proc.push.getDiff,
1719
// run before clear remote

src/proxy/processors/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const BRANCH_PREFIX = 'refs/heads/';
2+
export const EMPTY_COMMIT_HASH = '0000000000000000000000000000000000000000';
3+
export const FLUSH_PACKET = '0000';
4+
export const PACK_SIGNATURE = 'PACK';
5+
export const PACKET_SIZE = 4;
6+
export const GIT_OBJECT_TYPE_COMMIT = 1;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import path from 'path';
2+
import { Action, Step } from '../../actions';
3+
import { spawnSync } from 'child_process';
4+
5+
const exec = async (req: any, action: Action): Promise<Action> => {
6+
const step = new Step('checkHiddenCommits');
7+
8+
try {
9+
const repoPath = `${action.proxyGitPath}/${action.repoName}`;
10+
11+
const oldOid = action.commitFrom;
12+
const newOid = action.commitTo;
13+
if (!oldOid || !newOid) {
14+
throw new Error('Both action.commitFrom and action.commitTo must be defined');
15+
}
16+
17+
// build introducedCommits set
18+
const introducedCommits = new Set<string>();
19+
const revRange =
20+
oldOid === '0000000000000000000000000000000000000000' ? newOid : `${oldOid}..${newOid}`;
21+
const revList = spawnSync('git', ['rev-list', revRange], { cwd: repoPath, encoding: 'utf-8' })
22+
.stdout.trim()
23+
.split('\n')
24+
.filter(Boolean);
25+
revList.forEach((sha) => introducedCommits.add(sha));
26+
step.log(`Total introduced commits: ${introducedCommits.size}`);
27+
28+
// build packCommits set
29+
const packPath = path.join('.git', 'objects', 'pack');
30+
const packCommits = new Set<string>();
31+
(action.newIdxFiles || []).forEach((idxFile) => {
32+
const idxPath = path.join(packPath, idxFile);
33+
const out = spawnSync('git', ['verify-pack', '-v', idxPath], {
34+
cwd: repoPath,
35+
encoding: 'utf-8',
36+
})
37+
.stdout.trim()
38+
.split('\n');
39+
out.forEach((line) => {
40+
const [sha, type] = line.split(/\s+/);
41+
if (type === 'commit') packCommits.add(sha);
42+
});
43+
});
44+
step.log(`Total commits in the pack: ${packCommits.size}`);
45+
46+
// subset check
47+
const isSubset = [...packCommits].every((sha) => introducedCommits.has(sha));
48+
if (!isSubset) {
49+
// build detailed lists
50+
const [referenced, unreferenced] = [...packCommits].reduce<[string[], string[]]>(
51+
([ref, unref], sha) =>
52+
introducedCommits.has(sha) ? [[...ref, sha], unref] : [ref, [...unref, sha]],
53+
[[], []],
54+
);
55+
56+
step.log(`Referenced commits: ${referenced.length}`);
57+
step.log(`Unreferenced commits: ${unreferenced.length}`);
58+
59+
step.setError(
60+
`Unreferenced commits in pack (${unreferenced.length}): ${unreferenced.join(', ')}.\n` +
61+
`This usually happens when a branch was made from a commit that hasn't been approved and pushed to the remote.\n` +
62+
`Please get approval on the commits, push them and try again.`,
63+
);
64+
action.error = true;
65+
step.setContent(`Referenced: ${referenced.length}, Unreferenced: ${unreferenced.length}`);
66+
} else {
67+
// all good, no logging of individual SHAs needed
68+
step.log('All pack commits are referenced in the introduced range.');
69+
step.setContent(`All ${packCommits.size} pack commits are within introduced commits.`);
70+
}
71+
} catch (e: any) {
72+
step.setError(e.message);
73+
throw e;
74+
} finally {
75+
action.addStep(step);
76+
}
77+
78+
return action;
79+
};
80+
81+
exec.displayName = 'checkHiddenCommits.exec';
82+
export { exec };

src/proxy/processors/push-action/checkUserPushPermission.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,26 @@ import { trimTrailingDotGit } from '../../../db/helper';
55
// Execute if the repo is approved
66
const exec = async (req: any, action: Action): Promise<Action> => {
77
const step = new Step('checkUserPushPermission');
8+
const user = action.user;
89

10+
if (!user) {
11+
console.log('Action has no user set. This may be due to a fast-forward ref update. Deferring to getMissingData action.');
12+
return action;
13+
}
14+
15+
return await validateUser(user, action, step);
16+
};
17+
18+
/**
19+
* Helper that validates the user's push permission.
20+
* This can be used by other actions that need it. For example, when the user is missing from the commit data,
21+
* validation is deferred to getMissingData, but the logic is the same.
22+
* @param {string} user The user to validate
23+
* @param {Action} action The action object
24+
* @param {Step} step The step object
25+
* @return {Promise<Action>} The action object
26+
*/
27+
const validateUser = async (user: string, action: Action, step: Step): Promise<Action> => {
928
const repoSplit = trimTrailingDotGit(action.repo.toLowerCase()).split('/');
1029
// we expect there to be exactly one / separating org/repoName
1130
if (repoSplit.length != 2) {
@@ -16,7 +35,6 @@ const exec = async (req: any, action: Action): Promise<Action> => {
1635
// pull the 2nd value of the split for repoName
1736
const repoName = repoSplit[1];
1837
let isUserAllowed = false;
19-
let user = action.user;
2038

2139
// Find the user associated with this Git Account
2240
const list = await getUsers({ gitAccount: action.user });
@@ -53,4 +71,4 @@ const exec = async (req: any, action: Action): Promise<Action> => {
5371

5472
exec.displayName = 'checkUserPushPermission.exec';
5573

56-
export { exec };
74+
export { exec, validateUser };

0 commit comments

Comments
 (0)