gip-remote 1.2.2 → 1.2.4

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/index.d.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  import { Readable } from 'streamx'
2
2
 
3
3
  interface RemoteOpts {
4
- name?: string
5
- store: any
6
- swarm: any
7
- key?: Buffer
8
4
  timeout?: number
9
- blind?: boolean
5
+ blind?: any
10
6
  }
11
7
 
8
+ type RemoteLink =
9
+ | string
10
+ | { name: string; key?: Buffer }
11
+ | { drive: { key: Buffer }; pathname?: string }
12
+
12
13
  interface GitObject {
13
14
  oid: string
14
15
  type: string
@@ -19,6 +20,7 @@ interface GitObject {
19
20
  interface Ref {
20
21
  ref: string
21
22
  oid: string
23
+ symref?: string
22
24
  }
23
25
 
24
26
  interface RefObject extends GitObject {
@@ -26,13 +28,14 @@ interface RefObject extends GitObject {
26
28
  }
27
29
 
28
30
  declare class Remote {
29
- constructor(opts: RemoteOpts)
31
+ constructor(store: any, link: RemoteLink, opts?: RemoteOpts)
30
32
 
31
33
  readonly name: string
32
34
  readonly core: any
33
35
  readonly key: Buffer
34
36
  readonly discoveryKey: Buffer
35
37
  readonly availablePeers: number
38
+ readonly url: string
36
39
 
37
40
  ready(): Promise<void>
38
41
  close(): Promise<void>
@@ -42,11 +45,20 @@ declare class Remote {
42
45
  commitOid: string,
43
46
  objects: Map<string, { type: string; size: number; data: Buffer }>
44
47
  ): Promise<void>
48
+
49
+ getHead(): Promise<string | null>
50
+ setHead(branch: string): Promise<void>
51
+
45
52
  getAllRefs(): Promise<Ref[]>
46
53
  getBranchRef(branch: string): Promise<Ref | null>
54
+ deleteBranch(branchName: string): Promise<boolean>
55
+
47
56
  getObject(oid: string): Promise<GitObject | null>
48
57
  getRefObjects(commitOid: string, onLoad?: (size: number) => void): Promise<RefObject[]>
58
+
49
59
  toDrive(branch: string): Promise<RemoteDrive | null>
60
+
61
+ update(): Promise<void>
50
62
  waitForPeers(): Promise<void>
51
63
  }
52
64
 
@@ -73,6 +85,22 @@ declare class RemoteDrive {
73
85
  mirror(out: any, opts?: any): any
74
86
  }
75
87
 
88
+ interface ParsedGitPearLink {
89
+ protocol: string
90
+ origin: string
91
+ pathname?: string
92
+ drive: {
93
+ key: Buffer
94
+ length: number
95
+ fork?: number | null
96
+ }
97
+ }
98
+
99
+ declare const GitPearLink: {
100
+ parse(link: string): ParsedGitPearLink
101
+ serialize(o: ParsedGitPearLink): string
102
+ }
103
+
76
104
  interface ToDiskOpts {
77
105
  gitDir: string
78
106
  objects: Array<{ type: string; id: string; size: number; data: Buffer }>
@@ -107,4 +135,16 @@ declare function walkTree(
107
135
  prefix: string
108
136
  ): FileEntry[]
109
137
 
110
- export { Remote, RemoteDrive, toDisk, parseCommit, walkTree }
138
+ export { Remote, RemoteDrive, GitPearLink, toDisk, parseCommit, walkTree }
139
+ export type {
140
+ RemoteOpts,
141
+ RemoteLink,
142
+ GitObject,
143
+ Ref,
144
+ RefObject,
145
+ DriveEntry,
146
+ ParsedGitPearLink,
147
+ ToDiskOpts,
148
+ Commit,
149
+ FileEntry
150
+ }
package/index.js CHANGED
@@ -5,7 +5,7 @@ const z32 = require('z32')
5
5
  const def = require('./schema/hyperdb/index')
6
6
  const RemoteDrive = require('./lib/drive')
7
7
  const GitPearLink = require('./lib/link')
8
- const { parseCommit, walkTree } = require('./lib/git')
8
+ const { parseCommit, parseTag, walkTree } = require('./lib/git')
9
9
 
10
10
  class Remote extends ReadyResource {
11
11
  _swarm = null
@@ -22,7 +22,7 @@ class Remote extends ReadyResource {
22
22
  let config
23
23
  if (typeof this._link === 'string') {
24
24
  this._name = this._link
25
- config = {}
25
+ config = { core: store.get({ name: this._link }) }
26
26
  } else if (this._link.drive) {
27
27
  this._name = this._link.pathname?.split('/').slice(1)[0]
28
28
  config = { key: this._link.drive.key }
@@ -91,13 +91,18 @@ class Remote extends ReadyResource {
91
91
  }
92
92
 
93
93
  async getAllRefs() {
94
- const branches = this._db.find('@gip/branches')
95
94
  const refs = []
96
95
 
96
+ const branches = this._db.find('@gip/branches')
97
97
  for await (const b of branches) {
98
98
  refs.push({ ref: `refs/heads/${b.name}`, oid: b.commitOid })
99
99
  }
100
100
 
101
+ const tags = this._db.find('@gip/tags')
102
+ for await (const t of tags) {
103
+ refs.push({ ref: `refs/tags/${t.name}`, oid: t.oid })
104
+ }
105
+
101
106
  const headBranch = await this.getHead()
102
107
  if (headBranch) {
103
108
  const head = refs.find((r) => r.ref === `refs/heads/${headBranch}`)
@@ -129,28 +134,58 @@ class Remote extends ReadyResource {
129
134
  return true
130
135
  }
131
136
 
132
- // --- Push: store objects + index branch + files ---
137
+ async deleteTag(tagName) {
138
+ const tag = await this._db.get('@gip/tags', { name: tagName })
139
+ if (!tag) return false
140
+
141
+ await this._db.delete('@gip/tags', { name: tagName })
133
142
 
134
- async push(branchName, commitOid, objects) {
143
+ // Remove file records for this tag
144
+ const filesBranch = 'tags/' + tagName
145
+ const files = this._db.find('@gip/files', { branch: filesBranch })
146
+ for await (const file of files) {
147
+ await this._db.delete('@gip/files', { branch: filesBranch, path: file.path })
148
+ }
149
+
150
+ await this._db.flush()
151
+ return true
152
+ }
153
+
154
+ // --- Push: store objects + index branch/tag + files ---
155
+
156
+ async push(refName, oid, objects) {
135
157
  // 1. Store all git objects
136
- for (const [oid, obj] of objects) {
137
- const existing = await this.getObject(oid)
158
+ for (const [objOid, obj] of objects) {
159
+ const existing = await this.getObject(objOid)
138
160
  if (existing) continue
139
161
 
140
162
  await this._db.insert('@gip/objects', {
141
- oid,
163
+ oid: objOid,
142
164
  type: obj.type,
143
165
  size: obj.size,
144
166
  data: obj.data
145
167
  })
146
168
  }
147
169
 
148
- // 2. Parse commit metadata
149
- const commitObj = objects.get(commitOid)
150
- if (!commitObj) throw new Error('Commit object not found: ' + commitOid)
170
+ // 2. Dereference tag objects to find the commit
171
+ let resolvedOid = oid
172
+ let obj = objects.get(resolvedOid)
173
+ if (!obj) throw new Error('Object not found: ' + resolvedOid)
174
+
175
+ let tagMeta = null
176
+ while (obj.type === 'tag') {
177
+ const tag = parseTag(obj.data)
178
+ if (!tagMeta) tagMeta = tag
179
+ if (!tag.object) throw new Error('Tag has no object: ' + resolvedOid)
180
+ resolvedOid = tag.object
181
+ obj = objects.get(resolvedOid)
182
+ if (!obj) throw new Error('Object not found: ' + resolvedOid)
183
+ }
184
+
185
+ if (obj.type !== 'commit') throw new Error('Expected commit, got ' + obj.type + ': ' + resolvedOid)
151
186
 
152
- const commit = parseCommit(commitObj.data)
153
- if (!commit.tree) throw new Error('Commit has no tree: ' + commitOid)
187
+ const commit = parseCommit(obj.data)
188
+ if (!commit.tree) throw new Error('Commit has no tree: ' + resolvedOid)
154
189
 
155
190
  // 3. Walk tree to enumerate files
156
191
  const files = walkTree(objects, commit.tree, '')
@@ -158,7 +193,7 @@ class Remote extends ReadyResource {
158
193
  // 4. Insert file records
159
194
  for (const file of files) {
160
195
  await this._db.insert('@gip/files', {
161
- branch: branchName,
196
+ branch: refName,
162
197
  path: file.path,
163
198
  oid: file.oid,
164
199
  mode: file.mode,
@@ -169,21 +204,38 @@ class Remote extends ReadyResource {
169
204
  })
170
205
  }
171
206
 
172
- // 5. Insert branch record
173
- await this._db.insert('@gip/branches', {
174
- name: branchName,
175
- commitOid,
176
- treeOid: commit.tree,
177
- author: commit.author,
178
- message: commit.message,
179
- timestamp: commit.timestamp,
180
- objects: [...objects.keys()]
181
- })
207
+ const isTag = refName.startsWith('tags/')
182
208
 
183
- // 6. Set HEAD to first branch pushed (like git init)
184
- const currentHead = await this.getHead()
185
- if (!currentHead) {
186
- await this._db.insert('@gip/head', { branch: branchName })
209
+ if (isTag) {
210
+ // 5a. Insert tag record
211
+ const tagName = refName.slice(5) // strip 'tags/'
212
+ await this._db.insert('@gip/tags', {
213
+ name: tagName,
214
+ oid,
215
+ commitOid: resolvedOid,
216
+ treeOid: commit.tree,
217
+ tagger: tagMeta ? tagMeta.tagger : null,
218
+ message: tagMeta ? tagMeta.message : null,
219
+ timestamp: tagMeta ? tagMeta.timestamp : 0,
220
+ objects: [...objects.keys()]
221
+ })
222
+ } else {
223
+ // 5b. Insert branch record
224
+ await this._db.insert('@gip/branches', {
225
+ name: refName,
226
+ commitOid: oid,
227
+ treeOid: commit.tree,
228
+ author: commit.author,
229
+ message: commit.message,
230
+ timestamp: commit.timestamp,
231
+ objects: [...objects.keys()]
232
+ })
233
+
234
+ // 6. Set HEAD to first branch pushed (like git init)
235
+ const currentHead = await this.getHead()
236
+ if (!currentHead) {
237
+ await this._db.insert('@gip/head', { branch: refName })
238
+ }
187
239
  }
188
240
 
189
241
  // 7. Flush
@@ -192,29 +244,41 @@ class Remote extends ReadyResource {
192
244
 
193
245
  // --- Fetch support ---
194
246
 
195
- async getRefObjects(commitOid, onLoad) {
196
- const branches = this._db.find('@gip/branches')
197
- let branch = null
247
+ async getRefObjects(oid, onLoad) {
248
+ let record = null
198
249
 
250
+ // Check branches first
251
+ const branches = this._db.find('@gip/branches')
199
252
  for await (const b of branches) {
200
- if (b.commitOid === commitOid) {
201
- branch = b
253
+ if (b.commitOid === oid) {
254
+ record = b
202
255
  break
203
256
  }
204
257
  }
205
258
 
206
- if (!branch) return []
259
+ // Then check tags
260
+ if (!record) {
261
+ const tags = this._db.find('@gip/tags')
262
+ for await (const t of tags) {
263
+ if (t.oid === oid) {
264
+ record = t
265
+ break
266
+ }
267
+ }
268
+ }
269
+
270
+ if (!record) return []
207
271
 
208
272
  const results = []
209
- for (const oid of branch.objects) {
210
- const obj = await this.getObject(oid)
273
+ for (const objOid of record.objects) {
274
+ const obj = await this.getObject(objOid)
211
275
  if (!obj) continue
212
276
 
213
277
  // Empty blobs have null data after round-tripping through compact-encoding
214
278
  const data = obj.data || Buffer.alloc(0)
215
279
 
216
280
  if (onLoad) onLoad(obj.size)
217
- results.push({ ...obj, data, id: oid })
281
+ results.push({ ...obj, data, id: objOid })
218
282
  }
219
283
 
220
284
  return results
@@ -223,8 +287,16 @@ class Remote extends ReadyResource {
223
287
  // --- Drive ---
224
288
 
225
289
  async toDrive(branch) {
290
+ // Check branches first, then tags
226
291
  const b = await this._db.get('@gip/branches', { name: branch })
227
- if (!b) return null
292
+ if (!b) {
293
+ const t = await this._db.get('@gip/tags', { name: branch })
294
+ if (!t) return null
295
+ // For tags, files are stored under 'tags/<name>'
296
+ const drive = new RemoteDrive(this._db, { branch: 'tags/' + branch })
297
+ await drive.ready()
298
+ return drive
299
+ }
228
300
 
229
301
  const drive = new RemoteDrive(this._db, { branch })
230
302
  await drive.ready()
package/lib/drive.js CHANGED
@@ -26,15 +26,7 @@ class RemoteDrive extends ReadyResource {
26
26
  })
27
27
  if (!record) return null
28
28
 
29
- return {
30
- key,
31
- value: {
32
- executable: record.mode === '100755',
33
- linkname: null,
34
- blob: { byteLength: record.size },
35
- metadata: null
36
- }
37
- }
29
+ return toEntry(key, record)
38
30
  }
39
31
 
40
32
  async get(nameOrEntry) {
@@ -89,7 +81,7 @@ class RemoteDrive extends ReadyResource {
89
81
  try {
90
82
  for await (const record of stream) {
91
83
  if (ignore && ignore(record.path)) continue
92
- this.push(record.path)
84
+ this.push(toEntry(record.path, record))
93
85
  }
94
86
  this.push(null)
95
87
  cb(null)
@@ -130,4 +122,21 @@ class RemoteDrive extends ReadyResource {
130
122
  }
131
123
  }
132
124
 
125
+ function toEntry(key, record) {
126
+ return {
127
+ key,
128
+ value: {
129
+ executable: record.mode === '100755',
130
+ linkname: null,
131
+ blob: {
132
+ blockOffset: 0,
133
+ blockLength: 0,
134
+ byteOffset: 0,
135
+ byteLength: record.size
136
+ },
137
+ metadata: null
138
+ }
139
+ }
140
+ }
141
+
133
142
  module.exports = RemoteDrive
package/lib/git.js CHANGED
@@ -35,6 +35,37 @@ function parseCommit(data) {
35
35
  return result
36
36
  }
37
37
 
38
+ // --- Git tag parser ---
39
+
40
+ function parseTag(data) {
41
+ const text = data.toString('utf8')
42
+ const lines = text.split('\n')
43
+ const result = { object: null, type: null, tag: null, tagger: null, timestamp: 0, message: '' }
44
+
45
+ let i = 0
46
+ for (; i < lines.length; i++) {
47
+ const line = lines[i]
48
+ if (line === '') {
49
+ i++
50
+ break
51
+ }
52
+
53
+ if (line.startsWith('object ')) result.object = line.slice(7)
54
+ else if (line.startsWith('type ')) result.type = line.slice(5)
55
+ else if (line.startsWith('tag ')) result.tag = line.slice(4)
56
+ else if (line.startsWith('tagger ')) {
57
+ const match = line.match(/^tagger (.+) <.+> (\d+) [+-]\d+$/)
58
+ if (match) {
59
+ result.tagger = match[1]
60
+ result.timestamp = parseInt(match[2])
61
+ }
62
+ }
63
+ }
64
+
65
+ result.message = lines.slice(i).join('\n').trim()
66
+ return result
67
+ }
68
+
38
69
  // --- Tree walker: extracts all file paths from git tree objects ---
39
70
 
40
71
  function walkTree(objects, treeOid, prefix) {
@@ -136,4 +167,4 @@ async function toDisk(opts) {
136
167
  }
137
168
  }
138
169
 
139
- module.exports = { parseCommit, walkTree, toDisk }
170
+ module.exports = { parseCommit, parseTag, walkTree, toDisk }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gip-remote",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "Git+Pear remote DB for handling git data",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": 2,
2
+ "version": 3,
3
3
  "offset": 0,
4
4
  "schema": [
5
5
  {
@@ -12,7 +12,9 @@
12
12
  "indexes": [],
13
13
  "schema": "@gip/repos",
14
14
  "derived": false,
15
- "key": ["name"],
15
+ "key": [
16
+ "name"
17
+ ],
16
18
  "trigger": null
17
19
  },
18
20
  {
@@ -25,7 +27,9 @@
25
27
  "indexes": [],
26
28
  "schema": "@gip/branches",
27
29
  "derived": false,
28
- "key": ["name"],
30
+ "key": [
31
+ "name"
32
+ ],
29
33
  "trigger": null
30
34
  },
31
35
  {
@@ -35,10 +39,15 @@
35
39
  "type": 1,
36
40
  "version": 1,
37
41
  "versionField": null,
38
- "indexes": ["@gip/files-by-branch"],
42
+ "indexes": [
43
+ "@gip/files-by-branch"
44
+ ],
39
45
  "schema": "@gip/files",
40
46
  "derived": false,
41
- "key": ["branch", "path"],
47
+ "key": [
48
+ "branch",
49
+ "path"
50
+ ],
42
51
  "trigger": null
43
52
  },
44
53
  {
@@ -51,7 +60,9 @@
51
60
  "indexes": [],
52
61
  "schema": "@gip/objects",
53
62
  "derived": false,
54
- "key": ["oid"],
63
+ "key": [
64
+ "oid"
65
+ ],
55
66
  "trigger": null
56
67
  },
57
68
  {
@@ -63,7 +74,9 @@
63
74
  "collection": "@gip/files",
64
75
  "unique": false,
65
76
  "deprecated": false,
66
- "key": ["branch"]
77
+ "key": [
78
+ "branch"
79
+ ]
67
80
  },
68
81
  {
69
82
  "name": "head",
@@ -77,6 +90,21 @@
77
90
  "derived": false,
78
91
  "key": [],
79
92
  "trigger": null
93
+ },
94
+ {
95
+ "name": "tags",
96
+ "namespace": "gip",
97
+ "id": 6,
98
+ "type": 1,
99
+ "version": 3,
100
+ "versionField": null,
101
+ "indexes": [],
102
+ "schema": "@gip/tags",
103
+ "derived": false,
104
+ "key": [
105
+ "name"
106
+ ],
107
+ "trigger": null
80
108
  }
81
109
  ]
82
- }
110
+ }