Skip to content

Commit 05c54f5

Browse files
authored
Local/global config for user name and email (#99)
* set_config * local config * experiimental config fns * rename * undo nonsense * remove commented code Co-authored-by: Misha Kaletsky <[email protected]>
1 parent 6fd8681 commit 05c54f5

File tree

8 files changed

+287
-157
lines changed

8 files changed

+287
-157
lines changed

.eslintrc.js

+8
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,12 @@ module.exports = {
2020
'prettier/prettier': ['warn', require('./.prettierrc')],
2121
'codegen/codegen': 'error',
2222
},
23+
overrides: [
24+
{
25+
files: ['**/*.md'],
26+
rules: {
27+
'prettier/prettier': 'off',
28+
},
29+
},
30+
],
2331
}

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"docker-exec": "docker exec plv8-git_postgres_1",
4040
"eslint": "eslint --max-warnings 0",
4141
"generate": "yarn tsn scripts/generate-queries",
42-
"lint": "yarn eslint . && tsc -p .",
42+
"lint": "tsc -p . && yarn eslint . ",
4343
"prepare": "patch-package",
4444
"psql": "yarn --silent docker-exec psql -h localhost -U postgres postgres",
4545
"test": "jest",
@@ -66,7 +66,7 @@
6666
"path-browserify": "1.0.1",
6767
"prettier": "2.2.1",
6868
"process": "0.11.10",
69-
"slonik": "23.6.1",
69+
"slonik": "23.8.5",
7070
"stream-browserify": "3.0.0",
7171
"ts-jest": "26.5.2",
7272
"ts-loader": "9.1.1",

readme.md

+45-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ The implementation uses [plv8](https://github.com/plv8/plv8) to run JavaScript i
1111
- [Deletions](#deletions)
1212
- [Options](#options)
1313
- [Commit messages](#commit-messages)
14+
- [Git config](#git-config)
1415
- [Log depth](#log-depth)
1516
- [Tags](#tags)
1617
- [Restoring previous versions](#restoring-previous-versions)
@@ -33,9 +34,7 @@ To paraphrase [@mayfer's twitter thread](https://twitter.com/mayfer/status/13086
3334
- with just 1 extra column, you
3435
can add multiuser versioning to *any* indexed column!
3536

36-
- how cool this will be for large JSON or other text blob columns that get overwritten a lot during the app's lifetime
37-
38-
- since all commits are controlled by the main app, it's trivial to integrate commit authors directly into any regular application's user auth system
37+
- how cool this will be for large JSON or other text blob c get overwritten a lot duringall commits are controlled by the main app, it's trivial to integrate commit authors directly into any regular application's user auth system
3938

4039
- due to the git standard, this repo then can easily be fed into any generic git UI for all sorts of diffing, logging & visualizing
4140

@@ -50,7 +49,6 @@ psql -c "
5049
create extension if not exists plv8;
5150
select plv8_version();
5251
"
53-
5452
psql -f node_modules/plv8-git/queries/create-git-functions.sql
5553
```
5654

@@ -342,6 +340,49 @@ where id = 2
342340
}
343341
```
344342

343+
#### Git config
344+
345+
You can configure git using `git_set_local_config` or `git_set_global_config`:
346+
347+
```sql
348+
select git_set_local_config('user.name', 'Bob');
349+
select git_set_local_config('user.email', '[email protected]');
350+
351+
insert into test_table(id, text)
352+
values(201, 'value set by bob')
353+
```
354+
355+
```sql
356+
select git_log(git)
357+
from test_table
358+
where id = 201
359+
```
360+
361+
```json
362+
{
363+
"git_log": [
364+
{
365+
"message": "test_table_git_track_trigger: BEFORE INSERT ROW on public.test_table",
366+
"author": "Bob ([email protected])",
367+
"timestamp": "2000-12-25T12:00:00.000Z",
368+
"oid": "[oid]",
369+
"changes": [
370+
{
371+
"field": "id",
372+
"new": 201
373+
},
374+
{
375+
"field": "text",
376+
"new": "value set by bob"
377+
}
378+
]
379+
}
380+
]
381+
}
382+
```
383+
384+
Under the hood these use `set_config` with the `is_local` parameter respectively true/false for the local/global variants.
385+
345386
#### Log depth
346387

347388
`git_log` also accepts a `depth` parameter to limit the amount of history that is fetched:

scripts/docs.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ module.exports = params => {
1313
}
1414
const end = lines.findIndex((line, i) => i > start && line.startsWith('})'))
1515

16-
const codeBlockLinePrefix = 'CODE_BLOCK_LINE:'
16+
let codeBlockLinePrefix = 'CODE_BLOCK_LINE:'
1717

1818
return lines
1919
.slice(start, end)
@@ -32,6 +32,14 @@ module.exports = params => {
3232
.split('\n')
3333
.filter(line => !line.startsWith('// todo') && !line.startsWith('// TODO'))
3434
.map((line, i) => {
35+
if (line.endsWith('=> {')) {
36+
codeBlockLinePrefix += ' '
37+
return null
38+
}
39+
if (line.endsWith('})')) {
40+
codeBlockLinePrefix = codeBlockLinePrefix.replace(/ $/, '')
41+
return null
42+
}
3543
if (line.includes('sql`')) return '```sql'
3644
if (line.includes('.toMatchInlineSnapshot(`')) return '```json'
3745
if (line.trim().endsWith('`)')) return '```'
@@ -51,5 +59,6 @@ module.exports = params => {
5159
}
5260
return line
5361
})
62+
.filter(line => line !== null)
5463
.join('\n')
5564
}

scripts/generate-queries.ts

+18
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,24 @@ export const getQuery = (js: string) => {
7878
7979
$$
8080
language plpgsql;
81+
82+
create or replace function git_set_local_config(name text, value text) returns text as
83+
$$
84+
select set_config('git.' || name, value, /* is_local */ true);
85+
$$
86+
language sql;
87+
88+
create or replace function git_set_global_config(name text, value text) returns text as
89+
$$
90+
select set_config('git.' || name, value, /* is_local */ false);
91+
$$
92+
language sql;
93+
94+
create or replace function git_get_config(name text) returns text as
95+
$$
96+
select current_setting('git.' || name, /* missing_ok */ true);
97+
$$
98+
language sql;
8199
`
82100
}
83101

src/git.ts

+31-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import {PG_Vars} from './pg-types'
66
import {setupMemfs} from './fs'
77

88
function writeGitFiles(gitFiles: any, fs: memfs.IFs) {
9+
if (!gitFiles) {
10+
throw new Error(`Expected gitFiles as object, got ${gitFiles}`)
11+
}
912
Object.keys(gitFiles).map(filepath => {
1013
fs.mkdirSync(path.dirname(filepath), {recursive: true})
1114
fs.writeFileSync(filepath, Buffer.from(gitFiles[filepath]))
@@ -25,8 +28,19 @@ export const rowToRepo = ({OLD, NEW, ...pg}: PG_Vars) => {
2528
throw new Error(`Invalid column name ${repoColumn}`)
2629
}
2730

28-
const setupGitFolder =
29-
pg.TG_OP === 'INSERT' ? () => git.init({...repo, defaultBranch: 'main'}) : () => writeGitFiles(OLD![repoColumn], fs)
31+
const setupGitFolder = () => {
32+
if (pg.TG_OP === 'INSERT') {
33+
return git.init({...repo, defaultBranch: 'main'})
34+
}
35+
36+
if (!OLD![repoColumn]) {
37+
throw new Error(`expected ${repoColumn} column on ${pg.TG_OP} old value: ${JSON.stringify(OLD, null, 2)}.`)
38+
}
39+
40+
return writeGitFiles(OLD![repoColumn], fs)
41+
}
42+
43+
const gitParams = NEW?.[repoColumn] || {}
3044

3145
const commitMessage = `${pg.TG_NAME}: ${pg.TG_WHEN} ${pg.TG_OP} ${pg.TG_LEVEL} on ${pg.TG_TABLE_SCHEMA}.${pg.TG_TABLE_NAME}`.trim()
3246

@@ -46,13 +60,16 @@ export const rowToRepo = ({OLD, NEW, ...pg}: PG_Vars) => {
4660
.then(() =>
4761
git.commit({
4862
...repo,
49-
message: [NEW?.[repoColumn]?.commit?.message, commitMessage].filter(Boolean).join('\n\n'),
50-
author: {name: 'pguser', email: '[email protected]', ...NEW?.[repoColumn]?.commit?.author},
63+
message: [gitParams.commit?.message, commitMessage].filter(Boolean).join('\n\n'),
64+
author: {
65+
name: gitParams.commit?.author?.name || getSetting('user.name') || 'pguser',
66+
email: gitParams.commit?.author?.email || getSetting('user.email') || '[email protected]',
67+
},
5168
}),
5269
)
5370
.then(commit =>
5471
Promise.all(
55-
(NEW?.git?.tags || []).map((tag: string) => {
72+
(gitParams.tags || []).map((tag: string) => {
5673
return git.tag({...repo, ref: tag, object: commit})
5774
}),
5875
),
@@ -75,6 +92,15 @@ export const rowToRepo = ({OLD, NEW, ...pg}: PG_Vars) => {
7592
}))
7693
}
7794

95+
declare const plv8: {
96+
execute(sql: string, args?: unknown[]): Record<string, unknown>[]
97+
}
98+
const getSetting = (name: string) => {
99+
// https://www.postgresql.org/docs/9.4/functions-admin.html
100+
const [{git_get_config}] = plv8.execute('select git_get_config($1)', [name])
101+
return git_get_config
102+
}
103+
78104
type TreeInfo = {type: string; content: string; oid: string}
79105
type WalkResult = {filepath: string; ChildInfo: TreeInfo; ParentInfo?: TreeInfo}
80106

test/walkthrough.test.ts

+53-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {createPool, sql} from 'slonik'
22
import {readFileSync} from 'fs'
33
import * as path from 'path'
4-
import {createHash} from 'crypto'
54
import {fuzzifyDate, readableJson} from './result-printer'
65

76
// NOTE! This file is used to auto-generate the readme.
@@ -34,6 +33,14 @@ beforeAll(async () => {
3433
drop function if exists git_log cascade;
3534
drop function if exists git_resolve cascade;
3635
drop function if exists git_call_sync cascade;
36+
drop function if exists git_set_config cascade;
37+
drop function if exists git_set_local_config cascade;
38+
drop function if exists git_set_global_config cascade;
39+
drop function if exists git_get_config cascade;
40+
drop function if exists set_local_git_config;
41+
drop function if exists set_global_git_config;
42+
drop function if exists get_git_config;
43+
drop function if exists git_get_config;
3744
`)
3845

3946
await client.query({
@@ -329,6 +336,51 @@ test('walkthrough', async () => {
329336
}
330337
`)
331338

339+
// #### Git config
340+
341+
// You can configure git using `git_set_local_config` or `git_set_global_config`:
342+
343+
result = await client.transaction(async transaction => {
344+
await transaction.query(sql`
345+
select git_set_local_config('user.name', 'Bob');
346+
select git_set_local_config('user.email', '[email protected]');
347+
348+
insert into test_table(id, text)
349+
values(201, 'value set by bob')
350+
`)
351+
352+
return transaction.one(sql`
353+
select git_log(git)
354+
from test_table
355+
where id = 201
356+
`)
357+
})
358+
359+
expect(result).toMatchInlineSnapshot(`
360+
{
361+
"git_log": [
362+
{
363+
"message": "test_table_git_track_trigger: BEFORE INSERT ROW on public.test_table",
364+
"author": "Bob ([email protected])",
365+
"timestamp": "2000-12-25T12:00:00.000Z",
366+
"oid": "[oid]",
367+
"changes": [
368+
{
369+
"field": "id",
370+
"new": 201
371+
},
372+
{
373+
"field": "text",
374+
"new": "value set by bob"
375+
}
376+
]
377+
}
378+
]
379+
}
380+
`)
381+
382+
// Under the hood these use `set_config` with the `is_local` parameter respectively true/false for the local/global variants.
383+
332384
// #### Log depth
333385

334386
// `git_log` also accepts a `depth` parameter to limit the amount of history that is fetched:

0 commit comments

Comments
 (0)