Skip to content

Commit e6cbaf6

Browse files
Add JIP-0002: Git Support for Containers
Enable containers to function as Git repositories using git http-backend for clone/push/pull operations. Based on nosdav/server implementation. References: - Issue: JavaScriptSolidServer/JavaScriptSolidServer#5 - nosdav: https://github.com/nosdav/server
1 parent 09a99b2 commit e6cbaf6

File tree

2 files changed

+249
-0
lines changed

2 files changed

+249
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Inspired by [BIPs](https://github.com/bitcoin/bips) (Bitcoin) and [NIPs](https:/
99
| JIP | Title | Status |
1010
|-----|-------|--------|
1111
| [JIP-0001](jip-0001.md) | did:nostr Authentication & Schnorr Signatures | Draft |
12+
| [JIP-0002](jip-0002.md) | Git Support for Containers | Draft |
1213

1314
## Process
1415

jip-0002.md

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
# JIP-0002: Git Support for Containers
2+
3+
## Status
4+
5+
Draft
6+
7+
## Abstract
8+
9+
Enable Solid containers to function as Git repositories, allowing standard `git clone`, `git push`, and `git pull` operations for versioned, offline-capable data management.
10+
11+
## Motivation
12+
13+
1. **Familiar workflow** - Developers already know Git
14+
2. **Offline editing** - Clone pod locally, work offline, push when ready
15+
3. **Collaboration** - Branch, merge, and collaborate on pod content
16+
4. **Backup** - Clone provides automatic incremental backup
17+
5. **Version history** - Built-in audit trail for all resource changes
18+
6. **Tooling** - Leverage existing Git ecosystem (editors, diff tools, CI/CD)
19+
20+
## Specification
21+
22+
### 1. Git HTTP Smart Protocol
23+
24+
JSS SHALL implement the [Git Smart HTTP Protocol](https://git-scm.com/docs/http-protocol) by delegating to `git http-backend`.
25+
26+
### 2. Request Detection
27+
28+
Git requests are identified by URL patterns:
29+
30+
| Pattern | Operation |
31+
|---------|-----------|
32+
| `*/info/refs?service=git-upload-pack` | Clone/fetch negotiation |
33+
| `*/info/refs?service=git-receive-pack` | Push negotiation |
34+
| `*/git-upload-pack` | Clone/fetch data transfer |
35+
| `*/git-receive-pack` | Push data transfer |
36+
37+
The server MAY also recognize `.git` suffix on container URLs.
38+
39+
### 3. Repository Structure
40+
41+
Each container MAY have an associated Git repository:
42+
43+
```
44+
/alice/
45+
├── .git/ # Git metadata (bare or regular)
46+
├── inbox/
47+
├── public/
48+
│ └── notes.ttl
49+
└── settings/
50+
```
51+
52+
#### 3.1 Bare vs Regular Repositories
53+
54+
The server SHOULD use bare repositories for efficiency:
55+
56+
```
57+
/alice/.git/ # Bare repository
58+
├── HEAD
59+
├── config
60+
├── objects/
61+
└── refs/
62+
```
63+
64+
The actual container contents serve as the working tree.
65+
66+
### 4. Environment Configuration
67+
68+
The handler SHALL set CGI environment variables for `git http-backend`:
69+
70+
```javascript
71+
{
72+
GIT_PROJECT_ROOT: '<data-root>',
73+
GIT_HTTP_EXPORT_ALL: '1',
74+
GIT_HTTP_RECEIVE_PACK: 'true',
75+
REQUEST_METHOD: request.method,
76+
PATH_INFO: '/<pod>/.git' + gitPath,
77+
QUERY_STRING: request.queryString,
78+
CONTENT_TYPE: request.headers['content-type'],
79+
CONTENT_LENGTH: request.headers['content-length'],
80+
HTTP_CONTENT_ENCODING: request.headers['content-encoding'],
81+
GIT_CONFIG_PARAMETERS: "'uploadpack.allowTipSHA1InWant=true'"
82+
}
83+
```
84+
85+
### 5. Authorization
86+
87+
Git operations SHALL use existing WAC authorization:
88+
89+
| Git Operation | Required Mode |
90+
|---------------|---------------|
91+
| `clone` / `fetch` | `acl:Read` on container |
92+
| `push` | `acl:Write` on container |
93+
94+
The server MUST check authorization before invoking `git http-backend`.
95+
96+
### 6. Repository Initialization
97+
98+
#### 6.1 Explicit Initialization
99+
100+
POST to container with `Link: <http://git-scm.com/>; rel="type"`:
101+
102+
```http
103+
POST /alice/projects/ HTTP/1.1
104+
Link: <http://git-scm.com/>; rel="type"
105+
```
106+
107+
Response:
108+
```http
109+
HTTP/1.1 201 Created
110+
Location: /alice/projects/.git/
111+
```
112+
113+
#### 6.2 Auto-Initialization (Optional)
114+
115+
The server MAY auto-initialize a Git repository on first clone attempt if the container exists but has no `.git`.
116+
117+
### 7. Clone URL Format
118+
119+
Containers are cloneable at their LDP URL:
120+
121+
```bash
122+
git clone https://example.com/alice/projects/
123+
```
124+
125+
Or with explicit `.git` suffix:
126+
127+
```bash
128+
git clone https://example.com/alice/projects/.git
129+
```
130+
131+
### 8. CORS Headers
132+
133+
CORS headers MUST be applied before Git handling to support browser-based Git clients:
134+
135+
```http
136+
Access-Control-Allow-Origin: *
137+
Access-Control-Allow-Methods: GET, POST, OPTIONS
138+
Access-Control-Allow-Headers: Content-Type, Authorization
139+
```
140+
141+
### 9. Content Negotiation
142+
143+
When a Git client requests `/info/refs`, the server detects this via:
144+
- Query parameter: `?service=git-upload-pack` or `?service=git-receive-pack`
145+
- User-Agent containing "git"
146+
147+
Non-Git requests to the same URL return normal LDP container listing.
148+
149+
## Implementation
150+
151+
### Reference Implementation
152+
153+
Based on [nosdav/server](https://github.com/nosdav/server) git support:
154+
155+
```javascript
156+
import { spawn } from 'child_process';
157+
158+
const GIT_PATHS = ['/info/refs', '/git-upload-pack', '/git-receive-pack'];
159+
160+
function isGitRequest(url) {
161+
return GIT_PATHS.some(p => url.includes(p)) || url.endsWith('.git');
162+
}
163+
164+
async function handleGit(request, reply) {
165+
const repoPath = extractRepoPath(request.url);
166+
167+
// Check authorization
168+
const canRead = await checkAccess(repoPath, request.webId, 'read');
169+
const canWrite = await checkAccess(repoPath, request.webId, 'write');
170+
171+
if (request.url.includes('receive-pack') && !canWrite) {
172+
return reply.code(403).send({ error: 'Write access denied' });
173+
}
174+
if (!canRead) {
175+
return reply.code(403).send({ error: 'Read access denied' });
176+
}
177+
178+
// Spawn git http-backend
179+
const git = spawn('git', ['http-backend'], {
180+
env: {
181+
...process.env,
182+
GIT_PROJECT_ROOT: DATA_ROOT,
183+
GIT_HTTP_EXPORT_ALL: '1',
184+
GIT_HTTP_RECEIVE_PACK: canWrite ? 'true' : 'false',
185+
REQUEST_METHOD: request.method,
186+
PATH_INFO: repoPath,
187+
QUERY_STRING: request.url.split('?')[1] || '',
188+
CONTENT_TYPE: request.headers['content-type'] || '',
189+
GIT_CONFIG_PARAMETERS: "'uploadpack.allowTipSHA1InWant=true'"
190+
}
191+
});
192+
193+
// Pipe request body to git stdin
194+
request.raw.pipe(git.stdin);
195+
196+
// Parse CGI response headers
197+
let headersParsed = false;
198+
git.stdout.on('data', (chunk) => {
199+
if (!headersParsed) {
200+
const headerEnd = chunk.indexOf('\r\n\r\n');
201+
if (headerEnd !== -1) {
202+
const headers = chunk.slice(0, headerEnd).toString();
203+
// Parse and set response headers
204+
headersParsed = true;
205+
}
206+
}
207+
});
208+
209+
// Pipe git stdout to response
210+
git.stdout.pipe(reply.raw);
211+
}
212+
```
213+
214+
Tracking issue: [#5](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/5)
215+
216+
## Security Considerations
217+
218+
1. **Path Traversal**: Validate repository paths to prevent access outside data root
219+
2. **Resource Exhaustion**: Large pushes could exhaust disk; consider quotas
220+
3. **Executable Content**: Git hooks are disabled by default on server
221+
4. **Authentication**: Reuse existing Bearer/DPoP/Nostr auth before git operations
222+
223+
## Compatibility
224+
225+
| Spec | Compatibility |
226+
|------|---------------|
227+
| [Solid Protocol](https://solidproject.org/TR/protocol) | Compatible - containers remain LDP-accessible |
228+
| [LDP](https://www.w3.org/TR/ldp/) | Compatible - Git is additive |
229+
| [WAC](https://solidproject.org/TR/wac) | Uses for authorization |
230+
| [Git HTTP Protocol](https://git-scm.com/docs/http-protocol) | Implements |
231+
232+
## Prior Art
233+
234+
- **[nosdav/server](https://github.com/nosdav/server)** - Git support via `git http-backend`
235+
- **[QuitStore](https://github.com/AKSW/QuitStore)** - "Quads in Git" - Git + RDF/SPARQL versioning
236+
- **[express-git](https://www.npmjs.com/package/express-git)** - Express middleware for Git HTTP
237+
- **[git-http-backend npm](https://www.npmjs.com/package/git-http-backend)** - Node.js wrapper
238+
239+
## References
240+
241+
- [Git Smart HTTP](https://git-scm.com/book/en/v2/Git-on-the-Server-Smart-HTTP)
242+
- [git-http-backend Documentation](https://git-scm.com/docs/git-http-backend)
243+
- [Git HTTP Protocol](https://git-scm.com/docs/http-protocol)
244+
- [nosdav git commits](https://github.com/nosdav/server/commits/gh-pages/)
245+
246+
## Changelog
247+
248+
- 2024-12-27: Initial draft

0 commit comments

Comments
 (0)