@pogo-vcs/pogo 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ zlib/libpng license
2
+
3
+ Copyright (c) 2025 Frank Mayer
4
+
5
+ This software is provided 'as-is', without any express or implied warranty.
6
+ In no event will the authors be held liable for any damages arising from the
7
+ use of this software.
8
+
9
+ Permission is granted to anyone to use this software for any purpose,
10
+ including commercial applications, and to alter it and redistribute it
11
+ freely, subject to the following restrictions:
12
+
13
+ 1. The origin of this software must not be misrepresented; you must not
14
+ claim that you wrote the original software. If you use this software in a
15
+ product, an acknowledgment in the product documentation would be
16
+ appreciated but is not required.
17
+
18
+ 2. Altered source versions must be plainly marked as such, and must not
19
+ be misrepresented as being the original software.
20
+
21
+ 3. This notice may not be removed or altered from any source
22
+ distribution.
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ <div align="center">
2
+ <h1 align="center">
3
+ <img src="/brand/logo.svg" width="150" alt="P" />
4
+ <br>
5
+ Pogo
6
+ </h1>
7
+ <p align="center">
8
+ <strong>A centralized version control system that is simple and easy to use.</strong>
9
+ </p>
10
+ <p align="center">
11
+ <a href="https://github.com/pogo-vcs/pogo/releases"><img src="https://img.shields.io/github/v/release/pogo-vcs/pogo" alt="Latest release" /></a>
12
+ <a href="https://github.com/pogo-vcs/pogo/actions/workflows/test.yaml"><img src="https://img.shields.io/github/actions/workflow/status/pogo-vcs/pogo/test.yaml" alt="GitHub Workflow Status" /></a>
13
+ <a href="https://github.com/pogo-vcs/pogo/blob/main/LICENSE"><img src="https://img.shields.io/github/license/pogo-vcs/pogo" alt="License" /></a>
14
+ </p>
15
+ </div>
16
+
17
+ Pogo is a centralized version control system designed to be straightforward and efficient. It features an easy-to-use CLI client, a simple web UI, and robust support for both text and binary files. Pogo treats conflicts as first-class citizens, allowing them to be pushed to the remote to be resolved later.
18
+
19
+ ## ✨ Goals
20
+
21
+ - **🏠 Centralized Server:** A single source of truth for all your data.
22
+ - **đŸ’ģ Easy CLI Client:** No need for a complex GUI.
23
+ - **🌐 Simple Web UI:** For easy viewing of your repositories.
24
+ - **🔄 Cross-Platform Consistency:** Works the same on all major operating systems.
25
+ - **📄 Text & Binary File Support:** Handles all file types with ease.
26
+ - **đŸ’Ĩ First-Class Conflicts:** Push conflicts to the remote and resolve them later.
27
+ - **đŸŒŗ No Named Branches:** Create branches by adding multiple children to a change and merge them by creating a new change with multiple parents. Changes are automaticall named.
28
+ - **🔖 Bookmarks:** Tag versions with bookmarks, like `main` for the current version or `v1.0.0` for a specific version. `main` is treated like a default branch in Git.
29
+ - **đŸ“Ļ Go Module Support:** Import a Pogo repository as a Go module, no additional configuration or software required.
30
+
31
+ ## 🚀 Installation
32
+
33
+ ### NPM
34
+
35
+ ```sh
36
+ npm install -g @pogo-vcs/pogo
37
+ ```
38
+
39
+ ### Homebrew
40
+
41
+ ```sh
42
+ brew install --cask pogo-vcs/tap/pogo
43
+ ```
44
+
45
+ ### Scoop
46
+
47
+ ```sh
48
+ scoop bucket add pogo-vcs https://github.com/pogo-vcs/scoop-bucket.git
49
+ scoop install pogo
50
+ ```
51
+
52
+ ### Docker (server)
53
+
54
+ ```sh
55
+ docker pull ghcr.io/pogo-vcs/pogo:alpine
56
+ ```
57
+
58
+ ### From Source
59
+
60
+ To build Pogo from source, run the following commands:
61
+
62
+ ```sh
63
+ git clone https://github.com/pogo-vcs/pogo.git
64
+ cd pogo
65
+ just build
66
+ ```
67
+
68
+ This will create a `pogo` binary in the current directory. You can move this binary to a directory in your `PATH` to make it accessible from anywhere.
69
+
70
+ Required software for building:
71
+
72
+ - [go](https://go.dev/)
73
+ - [just](https://github.com/casey/just)
74
+ - [protoc](https://protobuf.dev/) (for gRPC)
75
+ - [sqlc](https://sqlc.dev/)
76
+ - [pnpm](https://pnpm.io/) (for Tailwind CSS)
77
+ - [templ](https://templ.guide/)
78
+
79
+ ## đŸ•šī¸ Usage
80
+
81
+ The intended workflow for Pogo is simple and efficient:
82
+
83
+ 1. **describe your changes:** Before you start working, use the `pogo describe` command to write a detailed description of the changes you are about to make and why. This helps you to think about the changes and to communicate them to others.
84
+ 2. **Make your changes:** Make the changes to your files as you normally would.
85
+ 3. **Iterate on the description:** As you work, you can iterate on the description to reflect the changes you are making. Maybe your implementation plan changed and you need your description to reflect that.
86
+ 4. **Push your changes:** Regularly push your changes to the server using the `pogo push` command. A daemon process that pushes automatically will be added later. You constantly overwrite the current change until you are satisfied with it.
87
+ 5. **Create a new change:** When you are done with your changes, create a new one using the `pogo new` command. You can optionally add one or more parent changes to the command. By default, your current change is used as the parent.
88
+ 6. **Maintain a "main" bookmark:** Use bookmarks to tag important changes. You can set a bookmark with `pogo bookmark set main` to set the current change as the main one, or `pogo bookmark set main <change>` to set a specific change as main. "main" ist just a string, you can use any format for version bookmarks you want. But "main" is a special value, treated like a default branch in Git.
89
+
90
+ ## 📋 Commands
91
+
92
+ | Command | Subcommand | Aliases | Description |
93
+ | --------------- | ---------- | ------------------ | ------------------------------------------------------------------------------------------- |
94
+ | `pogo` | | | The root command for the Pogo CLI. |
95
+ | `pogo bookmark` | | `b` | Manage bookmarks. |
96
+ | | `set` | `s` | Set a bookmark to a specific change. If no change is specified, the current change is used. |
97
+ | | `list` | `l` | List all bookmarks. |
98
+ | `pogo commit` | | | Combines `describe`, `push`, and `new` into a single command. |
99
+ | `pogo describe` | | `desc`, `rephrase` | Set the description for the current change. |
100
+ | `pogo edit` | | `checkout` | Sets the specified revision as the working-copy revision. |
101
+ | `pogo gc` | | | Run garbage collection on the server. |
102
+ | `pogo info` | | | Display the current working copy status. |
103
+ | `pogo init` | | | Initialize a new repository. |
104
+ | `pogo log` | | | Show the change history. |
105
+ | `pogo new` | | | Create a new change based on one or more parent changes. |
106
+ | `pogo push` | | | Push a change to the repository. |
107
+ | `pogo rm` | | | Remove a change from the repository. |
108
+ | `pogo serve` | | | Start the Pogo server. |
109
+ | `pogo whoami` | | | Show the personal access token being used for the current repository. |
110
+
111
+ ## đŸ—ī¸ Architecture
112
+
113
+ Pogo uses a PostgreSQL server to store all metadata about repositories, changes, and files. The actual file contents are stored in an object store on the file system. This separation of metadata and content allows for efficient storage and retrieval of data.
114
+
115
+ ## đŸ—‘ī¸ Garbage Collection
116
+
117
+ Pogo includes an automatic garbage collection system that removes unreachable data to prevent unbounded storage growth. The GC system cleans up both database records and filesystem objects that are no longer referenced by any repository.
118
+
119
+ ### How to Use
120
+
121
+ - **Manual GC:** Run `pogo gc` from any repository to trigger garbage collection on the server. This requires authentication.
122
+ - **Automatic GC:** When running `pogo serve`, garbage collection automatically runs daily at 3:00 AM server time.
123
+
124
+ ### Adaptive Implementation
125
+
126
+ The garbage collection system uses an adaptive strategy based on the total number of files in the database:
127
+
128
+ - **Small-scale (< 10 million files):** Uses an in-memory hash map strategy for fast O(1) lookups.
129
+ - **Large-scale (â‰Ĩ 10 million files):** Uses a batch processing strategy that scales to billions of files with constant memory usage.
130
+
131
+ The threshold can be configured via the `GC_MEMORY_THRESHOLD` environment variable.
132
+
133
+ ## 📜 License
134
+
135
+ This project is published under the [Zlib license](LICENSE).
136
+
137
+ In short:
138
+
139
+ - You can use Pogo for any purpose, for free, including commercial use, forever.
140
+ - You can create and distribute modified versions of Pogo, but you must not misrepresent the software's origin, you must clearly mark your changes, and you must retain the original license notice.
141
+ - I don't take any responsibility for any damages or losses that may occur as a result of using Pogo. If you encounter any issues, please report them to me.
142
+
143
+ If you make any modifications to Pogo, I would appreciate it if you shared them with me. I'm always interested in learning from others and improving my own work.
package/install.js ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ // This file was generated by GoReleaser. DO NOT EDIT.
4
+
5
+ const { install } = require("./lib.js");
6
+ install();
package/lib.js ADDED
@@ -0,0 +1,215 @@
1
+ // This file was generated by GoReleaser. DO NOT EDIT.
2
+ const { archives } = require("./package.json");
3
+ const fs = require("fs");
4
+ const crypto = require("crypto");
5
+ const path = require("path");
6
+ const JSZip = require("jszip");
7
+ const tar = require("tar");
8
+ const axios = require("axios");
9
+ const { spawnSync } = require("child_process");
10
+
11
+ const getArchive = () => {
12
+ let target = `${process.platform}-${process.arch}`;
13
+ const archive = archives[target];
14
+ if (!archive) {
15
+ throw new Error(`No archive available for ${target}`);
16
+ }
17
+ return archive;
18
+ };
19
+
20
+ const binDir = path.join(__dirname, "bin");
21
+
22
+ async function extractTar(tarPath, binaries, dir, wrappedIn) {
23
+ try {
24
+ const filesToExtract = wrappedIn
25
+ ? binaries.map((bin) =>
26
+ path.join(wrappedIn, bin).replace(/\\/g, "/"),
27
+ )
28
+ : binaries;
29
+
30
+ await tar.x({
31
+ file: tarPath,
32
+ cwd: dir,
33
+ filter: (path) => filesToExtract.includes(path),
34
+ });
35
+
36
+ // If wrapped, move files from wrapped directory to bin directory
37
+ if (wrappedIn) {
38
+ const wrappedDir = path.join(dir, wrappedIn);
39
+ for (const binary of binaries) {
40
+ const srcPath = path.join(wrappedDir, binary);
41
+ const destPath = path.join(dir, binary);
42
+ if (fs.existsSync(srcPath)) {
43
+ fs.renameSync(srcPath, destPath);
44
+ }
45
+ }
46
+ // Clean up empty wrapped directory
47
+ try {
48
+ fs.rmSync(wrappedDir, { recursive: true, force: true });
49
+ } catch (err) {
50
+ // Ignore cleanup errors
51
+ }
52
+ }
53
+
54
+ console.log(`Successfully extracted ${binaries} to "${dir}"`);
55
+ } catch (err) {
56
+ throw new Error(`Extraction failed: ${err.message}`);
57
+ }
58
+ }
59
+
60
+ async function extractZip(zipPath, binaries, dir, wrappedIn) {
61
+ try {
62
+ const zipData = fs.readFileSync(zipPath);
63
+ const zip = await JSZip.loadAsync(zipData);
64
+
65
+ for (const binary of binaries) {
66
+ const binaryPath = wrappedIn
67
+ ? path.join(wrappedIn, binary).replace(/\\/g, "/")
68
+ : binary;
69
+
70
+ if (!zip.files[binaryPath]) {
71
+ throw new Error(
72
+ `Error: ${binaryPath} does not exist in ${zipPath}`,
73
+ );
74
+ }
75
+
76
+ const content = await zip.files[binaryPath].async("nodebuffer");
77
+ if (!fs.existsSync(dir)) {
78
+ fs.mkdirSync(dir, { recursive: true });
79
+ }
80
+ const file = path.join(dir, binary);
81
+ fs.writeFileSync(file, content);
82
+ fs.chmodSync(file, "755");
83
+ console.log(`Successfully extracted "${binary}" to "${dir}"`);
84
+ }
85
+ } catch (err) {
86
+ throw new Error(`Extraction failed: ${err.message}`);
87
+ }
88
+ }
89
+
90
+ const run = async (bin) => {
91
+ await install();
92
+ if (process.platform === "win32") {
93
+ bin += ".exe";
94
+ }
95
+ const [, , ...args] = process.argv;
96
+ let result = spawnSync(path.join(binDir, bin), args, {
97
+ cwd: process.cwd(),
98
+ stdio: "inherit",
99
+ });
100
+ if (result.error) {
101
+ console.error(result.error);
102
+ }
103
+ return result.status;
104
+ };
105
+
106
+ const install = async () => {
107
+ try {
108
+ let archive = getArchive();
109
+ if (await exists(archive)) {
110
+ return;
111
+ }
112
+ let tmp = fs.mkdtempSync("archive-");
113
+ let archivePath = path.join(tmp, archive.name);
114
+ await download(archive.url, archivePath);
115
+ verify(archivePath, archive.checksum);
116
+
117
+ if (!fs.existsSync(binDir)) {
118
+ fs.mkdirSync(binDir);
119
+ }
120
+ switch (archive.format) {
121
+ case "binary":
122
+ const bin = path.join(binDir, archive.bins[0]);
123
+ fs.copyFileSync(archivePath, bin);
124
+ fs.chmodSync(bin, 0o755);
125
+ return;
126
+ case "zip":
127
+ return extractZip(
128
+ archivePath,
129
+ archive.bins,
130
+ binDir,
131
+ archive.wrappedIn,
132
+ );
133
+ case "tar":
134
+ case "tar.gz":
135
+ case "tgz":
136
+ return extractTar(
137
+ archivePath,
138
+ archive.bins,
139
+ binDir,
140
+ archive.wrappedIn,
141
+ );
142
+ case "tar.zst":
143
+ case "tzst":
144
+ case "tar.xz":
145
+ case "txz":
146
+ default:
147
+ throw new Error(`unsupported format: ${archive.format}`);
148
+ }
149
+ } catch (err) {
150
+ throw new Error(`Installation failed: ${err.message}`);
151
+ }
152
+ };
153
+
154
+ const verify = (filename, checksum) => {
155
+ if (checksum.algorithm == "" || checksum.digest == "") {
156
+ console.warn("Warning: No checksum provided for verification");
157
+ return;
158
+ }
159
+ let digest = crypto
160
+ .createHash(checksum.algorithm)
161
+ .update(fs.readFileSync(filename))
162
+ .digest("hex");
163
+ if (digest != checksum.digest) {
164
+ throw new Error(
165
+ `${filename}: ${checksum.algorithm} does not match, expected ${checksum.digest}, got ${digest}`,
166
+ );
167
+ }
168
+ };
169
+
170
+ const download = async (url, filename) => {
171
+ try {
172
+ console.log(`Downloading ${url} to ${filename}...`);
173
+ const dir = path.dirname(filename);
174
+ if (!fs.existsSync(dir)) {
175
+ fs.mkdirSync(dir, { recursive: true });
176
+ }
177
+
178
+ const response = await axios({
179
+ method: "GET",
180
+ url: url,
181
+ responseType: "stream",
182
+ timeout: 300000, // 5min
183
+ });
184
+
185
+ const writer = fs.createWriteStream(filename);
186
+ response.data.pipe(writer);
187
+
188
+ return new Promise((resolve, reject) => {
189
+ writer.on("finish", () => {
190
+ console.log(`Download complete: ${filename}`);
191
+ resolve(dir);
192
+ });
193
+
194
+ writer.on("error", (err) => {
195
+ console.error(`Error writing file: ${err.message}`);
196
+ reject(err);
197
+ });
198
+ });
199
+ } catch (err) {
200
+ throw new Error(`Download failed: ${err.message}`);
201
+ }
202
+ };
203
+
204
+ function exists(archive) {
205
+ if (!fs.existsSync(binDir)) {
206
+ return false;
207
+ }
208
+ return archive.bins.every((bin) => fs.existsSync(path.join(binDir, bin)));
209
+ }
210
+
211
+ module.exports = {
212
+ install,
213
+ run,
214
+ getArchive,
215
+ };
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@pogo-vcs/pogo",
3
+ "version": "0.0.1",
4
+ "description": "A centralized version control system that is simple and easy to use.",
5
+ "scripts": {
6
+ "postinstall": "node install.js",
7
+ "run": "node run-pogo.js"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/pogo-vcs/pogo"
12
+ },
13
+ "keywords": [
14
+ "cli",
15
+ "golang",
16
+ "version control",
17
+ "vcs",
18
+ "scm",
19
+ "centralized",
20
+ "git"
21
+ ],
22
+ "author": "Frank Mayer",
23
+ "license": "Zlib",
24
+ "bugs": {
25
+ "url": "https://github.com/pogo-vcs/pogo/issues"
26
+ },
27
+ "homepage": "https://github.com/pogo-vcs/pogo",
28
+ "bin": {
29
+ "pogo": "run-pogo.js"
30
+ },
31
+ "dependencies": {
32
+ "axios": "^1.8.2",
33
+ "jszip": "^3.10.1",
34
+ "tar": "^7.4.3"
35
+ },
36
+ "archives": {
37
+ "win32-arm64": {
38
+ "name": "pogo_Windows_arm64.zip",
39
+ "url": "https://github.com/pogo-vcs/pogo/releases/download/v0.0.1/pogo_Windows_arm64.zip",
40
+ "bins": [
41
+ "pogo.exe"
42
+ ],
43
+ "format": "zip",
44
+ "checksum": {
45
+ "algorithm": "sha256",
46
+ "digest": "66e8ba9843321967d5b1bfa0bee94c726edac1694d2eda1316b3d7f4f8dab130"
47
+ }
48
+ },
49
+ "win32-x64": {
50
+ "name": "pogo_Windows_x86_64.zip",
51
+ "url": "https://github.com/pogo-vcs/pogo/releases/download/v0.0.1/pogo_Windows_x86_64.zip",
52
+ "bins": [
53
+ "pogo.exe"
54
+ ],
55
+ "format": "zip",
56
+ "checksum": {
57
+ "algorithm": "sha256",
58
+ "digest": "b018dc7c68c4daf26b3e96aad408d9f5bb59921bc2d0f02f942e1bb0460deaf4"
59
+ }
60
+ }
61
+ }
62
+ }
package/run-pogo.js ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ // This file was generated by GoReleaser. DO NOT EDIT.
4
+
5
+ const { run } = require("./lib.js");
6
+ run("pogo");