mirror of
https://github.com/aljazceru/gitpear.git
synced 2025-12-17 06:04:25 +01:00
Merge branch 'dzdidi:master' into master
This commit is contained in:
38
Readme.md
38
Readme.md
@@ -38,13 +38,13 @@ All data will be persisted in application directory (default `~/.gitpear`). To c
|
|||||||
|
|
||||||
* `git pear daemon <-s, --start | -k, --stop>` - start or stop daemon
|
* `git pear daemon <-s, --start | -k, --stop>` - start or stop daemon
|
||||||
* `git pear key` - print out public key. Share it with your peers so that they can do `git pull pear:<public key>/<repo name>`
|
* `git pear key` - print out public key. Share it with your peers so that they can do `git pull pear:<public key>/<repo name>`
|
||||||
* `git pear init <path> [-s, --share [branch]]` - It will create [bare repository](https://git-scm.com/docs/git-init#Documentation/git-init.txt---bare) of the same name in application directory (default ~/.gitpear/<repository name>). It will add [git remote](https://git-scm.com/docs/git-remote) in current repository with name `pear`. So just like in traditional flow doing `git push orign`, here we do `git push pear`. By default repository will not be shared. To enable sharing provide `-s | --share [branch]` (default branch to share is current) or call `gitpear share <path>` later
|
* `git pear init [-s, --share [branch]]` - It will create [bare repository](https://git-scm.com/docs/git-init#Documentation/git-init.txt---bare) of the same name in application directory (default ~/.gitpear/<repository name>). It will add [git remote](https://git-scm.com/docs/git-remote) in current repository with name `pear`. So just like in traditional flow doing `git push orign`, here we do `git push pear`. By default repository will not be shared. To enable sharing provide `-s | --share [branch]` (default branch to share is current) or call `gitpear share` later
|
||||||
* `git pear share [-p, --path [path (default: ".")]> [-b, --branch [branch name (default: "_current_")] [-v, --visibility <private|public> (default: "public")]` - share repository, if branch is not specified, default branch will be shared
|
* `git pear share [-b, --branch [branch name (default: "_current_")] [-v, --visibility <private|public> (default: "public")]` - share current repository, if branch is not specified, default branch will be shared
|
||||||
* `git pear unshare <path>` - stop sharing repository
|
* `git pear unshare` - stop sharing current repository
|
||||||
* `git pear list [-s, --shared]` - list all or (only shared) repositories
|
* `git pear list [-s, --shared]` - list all or (only shared) repositories
|
||||||
* `git pear list <url>` - list repositories of a peer
|
* `git pear list <url>` - list repositories of a peer
|
||||||
|
|
||||||
### ACL (for authenticated access to enable support of PUSH)
|
### User Access and Branch Protection Rules (for authenticated access to enable support of PUSH)
|
||||||
|
|
||||||
Support of `push` capabilities only enabled for authenticated users. Currently supported authentications are based on:
|
Support of `push` capabilities only enabled for authenticated users. Currently supported authentications are based on:
|
||||||
* [noise](https://github.com/libp2p/specs/blob/master/noise/README.md);
|
* [noise](https://github.com/libp2p/specs/blob/master/noise/README.md);
|
||||||
@@ -62,22 +62,26 @@ or
|
|||||||
GIT_PEAR_AUTH=native git pear daemon -s
|
GIT_PEAR_AUTH=native git pear daemon -s
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### User Access Control
|
||||||
|
|
||||||
To manage access to repository use one or combination of the following commands, if `path` is not provide the command will be executed in the current directory. For `userId` use [NIP19 npub](https://github.com/nostr-protocol/nips/blob/master/19.md).
|
To manage access to repository use one or combination of the following commands, if `path` is not provide the command will be executed in the current directory. For `userId` use [NIP19 npub](https://github.com/nostr-protocol/nips/blob/master/19.md).
|
||||||
|
|
||||||
* `git pear acl [command] <path>` - ACL managegement
|
* `git pear acl -u [command] -p <repo path or url (default ".")>` - ACL managegement of for users access in local or remote repo (requires `owner` permission for remote repositories)
|
||||||
* `git pear acl list [userId] <path>` - list repository visitbility and user's role (or roles of all users if userId is not provided)
|
* `git pear acl -u list [userId] -p <repo path or url (default ".")>` - list repository visitbility and user's role (or roles of all users if userId is not provided)
|
||||||
* `git pear acl add <userId:role> <path>` - add user as a "role" to repository, available roles are `viewer`, `contributor`, `admin`. Roles exaplained:
|
* `git pear acl add -u <userId:role> -p <repo path or url (default ".")>` - add user as a "role" to repository available roles are `viewer`, `contributor`, `admin`, `owner`. Roles exaplained:
|
||||||
* `viewer` - can read all branches;
|
* `viewer` - can read all branches;
|
||||||
* `contributor` - can edit all branches except protected (default master)
|
* `contributor` - can edit all branches except protected (default master)
|
||||||
* `admin` - can edit protected branches
|
* `admin` - can edit protected branches
|
||||||
* `git pear acl remove <userId> <path>` - revoke use access to repository
|
* `owner` - can edit repo ack
|
||||||
|
* `git pear acl remove -u| <userId> -p <repo path or url (default ".")>` - revoke use access to repository.
|
||||||
|
|
||||||
|
|
||||||
### Branch protection rules
|
### Branch protection rules
|
||||||
It is possible to setup basic branch protection rules (master is proteted by default).
|
It is possible to setup basic branch protection rules (master is proteted by default).
|
||||||
* `git pear branch`, same as `git pear branch list .` - list protection rules
|
* `git pear acl -b [command] -p <repo path or url (deafult ".")`, same as `git pear branch list` - list protection rules
|
||||||
* `git pear branch add <branch name> <repo path>` - mark branch as protected (defatul repo path is ".")
|
* `git pear acl -b list` - git pear branch list .` - list protection rules;
|
||||||
* `git pear branch remove <branch name> <repo path>` - unmark branch as protected
|
* `git pear acl -b add <branchName> <repo path or url (default ".")>` - mark branch as protected;
|
||||||
|
* `git pear acl -b remove <branchName> <repo path or url (deafult ".")>` - unmark branch as protected;
|
||||||
|
|
||||||
# Examples of usage
|
# Examples of usage
|
||||||
|
|
||||||
@@ -128,7 +132,7 @@ git pull
|
|||||||
|
|
||||||
## Authenticated usage example (push) - at your own risk
|
## Authenticated usage example (push) - at your own risk
|
||||||
|
|
||||||
Collaboration is possible with the following flow between Carol and David in a peer-to-peer manner.
|
Collaboration is possible with the following flow between Carol and Dave in a peer-to-peer manner.
|
||||||
|
|
||||||
Supported authentication methods are `native` and `nip98`. The `nip98` authentication, requires environment variable `GIT_PEAR_AUTH_NSEC` with nsec
|
Supported authentication methods are `native` and `nip98`. The `nip98` authentication, requires environment variable `GIT_PEAR_AUTH_NSEC` with nsec
|
||||||
|
|
||||||
@@ -138,12 +142,12 @@ Supported authentication methods are `native` and `nip98`. The `nip98` authentic
|
|||||||
2. Go to repository
|
2. Go to repository
|
||||||
* `cd repo`
|
* `cd repo`
|
||||||
3. Initialize git pear repository
|
3. Initialize git pear repository
|
||||||
* `git pear init .`
|
* `git pear init`
|
||||||
4. Share repository wit hviben visibility () - (default is `public`)
|
4. Share repository wit hviben visibility () - (default is `public`)
|
||||||
* `git pear share . public`
|
* `git pear share public`
|
||||||
5. Add Daviv as a `contirbutor`.
|
5. Add Dave as a `contirbutor`.
|
||||||
6. List David's npub as a contributor
|
6. List Dave's npub as a contributor
|
||||||
* `git pear acl add <David pub key hex>:contributor`
|
* `git pear acl add <Dave pub key hex>:contributor`
|
||||||
7. Retreive repo url and share it with Dave
|
7. Retreive repo url and share it with Dave
|
||||||
* `git pear list -s`
|
* `git pear list -s`
|
||||||
|
|
||||||
|
|||||||
23
src/acl.js
23
src/acl.js
@@ -2,15 +2,18 @@ const home = require('./home')
|
|||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
|
||||||
const ROLES = {
|
const ROLES = {
|
||||||
|
owner: {
|
||||||
|
description: 'Read and write to all branches, and ACL management'
|
||||||
|
},
|
||||||
admin: {
|
admin: {
|
||||||
description: 'Read and write to all branches',
|
description: 'Read and write to all branches'
|
||||||
},
|
},
|
||||||
contributor: {
|
contributor: {
|
||||||
description: 'Read and write to all branches except protected ones',
|
description: 'Read and write to all branches except protected ones'
|
||||||
},
|
},
|
||||||
viewer: {
|
viewer: {
|
||||||
description: 'Read all branches',
|
description: 'Read all branches'
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
const DEFAULT_ACL = {
|
const DEFAULT_ACL = {
|
||||||
visibility: 'public', // public|private
|
visibility: 'public', // public|private
|
||||||
@@ -23,9 +26,16 @@ function getUserRole (repoName, user) {
|
|||||||
return acl.ACL[user]
|
return acl.ACL[user]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOwners (repoName) {
|
||||||
|
const acl = getACL(repoName)
|
||||||
|
return Object.keys(acl.ACL).filter(user => acl.ACL[user] === 'owner')
|
||||||
|
}
|
||||||
|
|
||||||
function getAdmins (repoName) {
|
function getAdmins (repoName) {
|
||||||
const acl = getACL(repoName)
|
const acl = getACL(repoName)
|
||||||
return Object.keys(acl.ACL).filter(user => acl.ACL[user] === 'admin')
|
const admins = Object.keys(acl.ACL).filter(user => acl.ACL[user] === 'admin')
|
||||||
|
const owners = getOwners(repoName)
|
||||||
|
return [...admins, ...owners].filter((user, i, arr) => arr.indexOf(user) === i)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContributors (repoName) {
|
function getContributors (repoName) {
|
||||||
@@ -113,8 +123,9 @@ module.exports = {
|
|||||||
getACL,
|
getACL,
|
||||||
addProtectedBranch,
|
addProtectedBranch,
|
||||||
removeProtectedBranch,
|
removeProtectedBranch,
|
||||||
|
getOwners,
|
||||||
getAdmins,
|
getAdmins,
|
||||||
getContributors,
|
getContributors,
|
||||||
getViewers,
|
getViewers,
|
||||||
revokeAccessFromUser,
|
revokeAccessFromUser
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
function getId(data) {
|
function getId (data) {
|
||||||
if (!process.env.GIT_PEAR_AUTH) return payload
|
if (!process.env.GIT_PEAR_AUTH) return data
|
||||||
if (process.env.GIT_PEAR_AUTH === 'nip98') {
|
if (process.env.GIT_PEAR_AUTH === 'nip98') {
|
||||||
const nip98 = require('./nip98')
|
const nip98 = require('./nip98')
|
||||||
return nip98.getId(data)
|
return nip98.getId(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getToken(payload) {
|
async function getToken (payload) {
|
||||||
if (!process.env.GIT_PEAR_AUTH) return userId
|
if (!process.env.GIT_PEAR_AUTH) return payload
|
||||||
if (process.env.GIT_PEAR_AUTH === 'nip98') {
|
if (process.env.GIT_PEAR_AUTH === 'nip98') {
|
||||||
const nip98 = require('./nip98')
|
const nip98 = require('./nip98')
|
||||||
return nip98.getToken(payload)
|
return nip98.getToken(payload)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const { nip98, nip19, finalizeEvent } = require('nostr-tools')
|
const { nip98, nip19, finalizeEvent } = require('nostr-tools')
|
||||||
|
|
||||||
async function getToken({ url, method, data }) {
|
async function getToken ({ url, method, data }) {
|
||||||
|
if (!process.env.GIT_PEAR_AUTH_NSEC) throw new Error('Missing NSEC')
|
||||||
const { data: sK } = nip19.decode(process.env.GIT_PEAR_AUTH_NSEC)
|
const { data: sK } = nip19.decode(process.env.GIT_PEAR_AUTH_NSEC)
|
||||||
return nip98.getToken(
|
return nip98.getToken(
|
||||||
url,
|
url,
|
||||||
@@ -12,7 +13,7 @@ async function getToken({ url, method, data }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME
|
// FIXME
|
||||||
async function getId({ payload, url, method, data }) {
|
async function getId ({ payload, url, method, data }) {
|
||||||
const event = JSON.parse(Buffer.from(payload, 'base64').toString())
|
const event = JSON.parse(Buffer.from(payload, 'base64').toString())
|
||||||
const isValid = await nip98.validateEvent(event, url, method, data)
|
const isValid = await nip98.validateEvent(event, url, method, data)
|
||||||
if (!isValid) throw new Error('Invalid event')
|
if (!isValid) throw new Error('Invalid event')
|
||||||
|
|||||||
156
src/cli-helpers.js
Normal file
156
src/cli-helpers.js
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const home = require('./home')
|
||||||
|
const acl = require('./acl')
|
||||||
|
const git = require('./git')
|
||||||
|
|
||||||
|
const { aclRemote } = require('./rpc-requests')
|
||||||
|
const { printACL, printACLForUser, checkIfGitRepo, logBranches } = require('./utils')
|
||||||
|
|
||||||
|
async function remoteACL (a, b, p, options) {
|
||||||
|
if (a === 'list') {
|
||||||
|
await aclRemote.list(p, b)
|
||||||
|
} else if (a === 'add') {
|
||||||
|
if (!b) {
|
||||||
|
console.error('User not provided')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
if (b.split(':').length !== 2) {
|
||||||
|
console.error('Invalid role')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
await aclRemote.add(p, b)
|
||||||
|
} else if (a === 'remove') {
|
||||||
|
if (!b) {
|
||||||
|
console.error('User not provided')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
await aclRemote.remove(p, b)
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid action')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function share (name, branchToShare, options) {
|
||||||
|
let aclOptions
|
||||||
|
let message = `Shared "${name}" project, ${branchToShare} branch`
|
||||||
|
if (options?.visibility) {
|
||||||
|
aclOptions = { visibility: options.visibility }
|
||||||
|
message = `${message}, as ${options.visibility} repo`
|
||||||
|
}
|
||||||
|
|
||||||
|
try { home.shareAppFolder(name) } catch (e) { }
|
||||||
|
try { acl.setACL(name, aclOptions) } catch (e) { }
|
||||||
|
try { await git.push(branchToShare) } catch (e) { }
|
||||||
|
console.log(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function remoteBranchProtectionRules (a, b, p, options) {
|
||||||
|
if (a === 'list') {
|
||||||
|
await aclRemote.list(p, b, { branch: true })
|
||||||
|
} else if (a === 'add') {
|
||||||
|
await aclRemote.add(p, b, { branch: true })
|
||||||
|
if (!b) {
|
||||||
|
console.error('branch is not provided')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
} else if (a === 'remove') {
|
||||||
|
if (!b) {
|
||||||
|
console.error('branch is not provided')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
await aclRemote.remove(p, b, { branch: true })
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid action')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function localACL (a, u, p, options) {
|
||||||
|
const fullPath = path.resolve(p)
|
||||||
|
checkIfGitRepo(fullPath)
|
||||||
|
|
||||||
|
const name = fullPath.split(path.sep).pop()
|
||||||
|
if (!home.isInitialized(name)) {
|
||||||
|
console.error(`${name} is not initialized`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
const repoACL = acl.getACL(name)
|
||||||
|
|
||||||
|
if (a === 'list' && !u) {
|
||||||
|
printACL(repoACL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a === 'list') {
|
||||||
|
printACLForUser(repoACL, u)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a === 'add') {
|
||||||
|
if (!u) {
|
||||||
|
console.error('User not provided')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [userId, role] = u.split(':')
|
||||||
|
if (repoACL.ACL[userId]) {
|
||||||
|
console.error(`${userId} already has access to ${name} as ${repoACL.ACL[userId]}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
acl.grantAccessToUser(name, userId, role)
|
||||||
|
console.log(`Added ${userId} to ${name} as ${role}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a === 'remove') {
|
||||||
|
if (!u) {
|
||||||
|
console.error('User not provided')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!repoACL.ACL[u]) {
|
||||||
|
console.error(`${u} does not have access to ${name}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
acl.revokeAccessFromUser(name, u)
|
||||||
|
console.log(`Removed ${u} from ${name}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function localBranchProtectionRules (a, b, p, options) {
|
||||||
|
const fullPath = path.resolve(p)
|
||||||
|
checkIfGitRepo(fullPath)
|
||||||
|
|
||||||
|
const name = fullPath.split(path.sep).pop()
|
||||||
|
if (!home.isInitialized(name)) {
|
||||||
|
console.error(`${name} is not initialized`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a === 'list' && !b) {
|
||||||
|
const repoACL = acl.getACL(name)
|
||||||
|
logBranches(repoACL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a === 'add') {
|
||||||
|
acl.addProtectedBranch(name, b)
|
||||||
|
const repoACL = acl.getACL(name)
|
||||||
|
logBranches(repoACL)
|
||||||
|
}
|
||||||
|
if (a === 'remove') {
|
||||||
|
acl.removeProtectedBranch(name, b)
|
||||||
|
const repoACL = acl.getACL(name)
|
||||||
|
logBranches(repoACL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
remoteACL,
|
||||||
|
share,
|
||||||
|
remoteBranchProtectionRules,
|
||||||
|
localACL,
|
||||||
|
localBranchProtectionRules
|
||||||
|
|
||||||
|
}
|
||||||
178
src/cli.js
178
src/cli.js
@@ -6,12 +6,15 @@ const commander = require('commander')
|
|||||||
const program = new commander.Command()
|
const program = new commander.Command()
|
||||||
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const fs = require('fs')
|
|
||||||
|
|
||||||
const home = require('./home')
|
const home = require('./home')
|
||||||
const acl = require('./acl')
|
|
||||||
const git = require('./git')
|
const git = require('./git')
|
||||||
|
|
||||||
|
const { listRemote } = require('./rpc-requests')
|
||||||
|
|
||||||
|
const { checkIfGitRepo } = require('./utils')
|
||||||
|
const { remoteACL, share, remoteBranchProtectionRules, localACL, localBranchProtectionRules } = require('./cli-helpers')
|
||||||
|
|
||||||
const pkg = require('../package.json')
|
const pkg = require('../package.json')
|
||||||
program
|
program
|
||||||
.name('gitpear')
|
.name('gitpear')
|
||||||
@@ -21,20 +24,18 @@ program
|
|||||||
program
|
program
|
||||||
.command('init')
|
.command('init')
|
||||||
.description('initialize a gitpear repo')
|
.description('initialize a gitpear repo')
|
||||||
.addArgument(new commander.Argument('[p]', 'path to the repo').default('.'))
|
|
||||||
.option('-s, --share [branch]', 'share the repo as public, default false, default branch is current', '')
|
.option('-s, --share [branch]', 'share the repo as public, default false, default branch is current', '')
|
||||||
.action(async (p, options) => {
|
.action(async (options) => {
|
||||||
const fullPath = path.resolve(p)
|
const fullPath = path.resolve('.')
|
||||||
if (!fs.existsSync(path.join(fullPath, '.git'))) {
|
checkIfGitRepo(fullPath)
|
||||||
console.error('Not a git repo')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = fullPath.split(path.sep).pop()
|
const name = fullPath.split(path.sep).pop()
|
||||||
if ((home.isInitialized(name))) {
|
if ((home.isInitialized(name))) {
|
||||||
console.error(`${name} is already initialized`)
|
console.error(`${name} is already initialized`)
|
||||||
|
try {
|
||||||
await git.addRemote(name)
|
await git.addRemote(name)
|
||||||
console.log(`Added git remote for "${name}" as "pear"`)
|
console.log(`Added git remote for "${name}" as "pear"`)
|
||||||
|
} catch (e) { }
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,12 +57,7 @@ program
|
|||||||
branchToShare = options.share
|
branchToShare = options.share
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.share) {
|
if (options.share) await share(name, branchToShare)
|
||||||
try { home.shareAppFolder(name) } catch (e) { }
|
|
||||||
try { acl.setACL(name) } catch (e) { }
|
|
||||||
try { await git.push(branchToShare) } catch (e) { }
|
|
||||||
console.log(`Shared "${name}" project, ${branchToShare} branch`)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
program
|
program
|
||||||
@@ -69,13 +65,9 @@ program
|
|||||||
.description('share a gitpear repo')
|
.description('share a gitpear repo')
|
||||||
.option('-b, --branch [b]', 'branch to share, default is current branch', '')
|
.option('-b, --branch [b]', 'branch to share, default is current branch', '')
|
||||||
.option('-v, --visibility [v]', 'visibility of the repo', 'public')
|
.option('-v, --visibility [v]', 'visibility of the repo', 'public')
|
||||||
.option('-p, --path [p]', 'path to the repo', '.')
|
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
const fullPath = path.resolve(options.path)
|
const fullPath = path.resolve('.')
|
||||||
if (!fs.existsSync(path.join(fullPath, '.git'))) {
|
checkIfGitRepo(fullPath)
|
||||||
console.error('Not a git repo')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = fullPath.split(path.sep).pop()
|
const name = fullPath.split(path.sep).pop()
|
||||||
if (!home.isInitialized(name)) {
|
if (!home.isInitialized(name)) {
|
||||||
@@ -85,137 +77,49 @@ program
|
|||||||
|
|
||||||
const currentBranch = await git.getCurrentBranch()
|
const currentBranch = await git.getCurrentBranch()
|
||||||
const branchToShare = options.branch || currentBranch
|
const branchToShare = options.branch || currentBranch
|
||||||
try { home.shareAppFolder(name) } catch (e) { }
|
await share(name, branchToShare, options)
|
||||||
try { acl.setACL(name, { visibility: options.visibility }) } catch (e) { }
|
|
||||||
try { await git.push(branchToShare) } catch (e) { }
|
|
||||||
console.log(`Shared "${name}" project, ${branchToShare} branch, as ${options.visibility} repo`)
|
|
||||||
return
|
|
||||||
})
|
})
|
||||||
|
|
||||||
program
|
|
||||||
.command('branch')
|
|
||||||
.description('branch protection rules')
|
|
||||||
.addArgument(new commander.Argument('[a]', 'actiont to perform').choices(['add', 'remove', 'list']).default('list'))
|
|
||||||
.addArgument(new commander.Argument('[b]', 'branch name').default(''))
|
|
||||||
.addArgument(new commander.Argument('[p]', 'path to the repo').default('.'))
|
|
||||||
.action(async (a, b, p, options) => {
|
|
||||||
const fullPath = path.resolve(p)
|
|
||||||
if (!fs.existsSync(path.join(fullPath, '.git'))) {
|
|
||||||
console.error('Not a git repo')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = fullPath.split(path.sep).pop()
|
|
||||||
if (!home.isInitialized(name)) {
|
|
||||||
console.error(`${name} is not initialized`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a === 'list' && !b) { logBranches(name) }
|
|
||||||
|
|
||||||
if (a === 'add') {
|
|
||||||
acl.addProtectedBranch(name, b)
|
|
||||||
logBranches(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a === 'remove') {
|
|
||||||
acl.removeProtectedBranch(name, b)
|
|
||||||
logBranches(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
function logBranches(name) {
|
|
||||||
const repoACL = acl.getACL(name)
|
|
||||||
console.log('Visibility:', '\t', repoACL.visibility)
|
|
||||||
console.log('Branch:')
|
|
||||||
for (const branch of repoACL.protectedBranches) { console.log(branch) }
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('acl')
|
.command('acl')
|
||||||
.description('set acl of a gitpear repo')
|
.description('manage acl of a gitpear repo')
|
||||||
|
.option('-u, --user', 'user to add/remove/list')
|
||||||
|
.option('-b, --branch', 'branch to add/remove/list in protected branches')
|
||||||
|
.option('-p, --path [path]', 'path to the repo', '.')
|
||||||
.addArgument(new commander.Argument('[a]', 'actiont to perform').choices(['add', 'remove', 'list']).default('list'))
|
.addArgument(new commander.Argument('[a]', 'actiont to perform').choices(['add', 'remove', 'list']).default('list'))
|
||||||
.addArgument(new commander.Argument('[u]', 'user to add/remove/list').default(''))
|
.addArgument(new commander.Argument('[n]', 'user or branch to add/remove/list').default(''))
|
||||||
.addArgument(new commander.Argument('[p]', 'path to the repo').default('.'))
|
.action(async (a, n, options) => {
|
||||||
.action(async (a, u, p, options) => {
|
if (options.user && options.branch) {
|
||||||
|
throw new Error('Cannot perform both user and branch action at the same time')
|
||||||
// TODO: add branch protection logic
|
|
||||||
const fullPath = path.resolve(p)
|
|
||||||
if (!fs.existsSync(path.join(fullPath, '.git'))) {
|
|
||||||
console.error('Not a git repo')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = fullPath.split(path.sep).pop()
|
if (!options.user && !options.branch) {
|
||||||
if (!home.isInitialized(name)) {
|
throw new Error('Either user or branch option is required')
|
||||||
console.error(`${name} is not initialized`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
const repoACL = acl.getACL(name)
|
|
||||||
|
|
||||||
if (a === 'list' && !u) {
|
|
||||||
console.log('Repo Visibility:', '\t', repoACL.visibility)
|
|
||||||
console.log('User:', '\t', 'Role:')
|
|
||||||
for (const user in repoACL.ACL) {
|
|
||||||
console.log(user, '\t', repoACL.ACL[user])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a === 'list') {
|
if (options.user) {
|
||||||
console.log('Repo Visibility:', '\t', repoACL.visibility)
|
if (options.path.startsWith('pear://')) {
|
||||||
console.log('User:', u, '\t', repoACL.ACL[u])
|
if (n === '.') n = ''
|
||||||
return
|
await remoteACL(a, n, options.path, options)
|
||||||
|
} else {
|
||||||
|
localACL(a, n, options.path, options)
|
||||||
}
|
}
|
||||||
|
} else if (options.branch) {
|
||||||
if (a === 'add') {
|
if (options.path.startsWith('pear://')) {
|
||||||
if (!u) {
|
await remoteBranchProtectionRules(a, n, options.path, options)
|
||||||
console.error('User not provided')
|
} else {
|
||||||
process.exit(1)
|
localBranchProtectionRules(a, n, options.path, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
const [ userId, role ] = u.split(':')
|
|
||||||
if (repoACL.ACL[userId]) {
|
|
||||||
console.error(`${userId} already has access to ${name} as ${repoACL.ACL[userId]}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
acl.grantAccessToUser(name, userId, role)
|
|
||||||
console.log(`Added ${userId} to ${name} as ${role}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a === 'remove') {
|
|
||||||
if (!u) {
|
|
||||||
console.error('User not provided')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!repoACL.ACL[u]) {
|
|
||||||
console.error(`${u} does not have access to ${name}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
acl.revokeAccessFromUser(name, u)
|
|
||||||
console.log(`Removed ${u} from ${name}`)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('unshare')
|
.command('unshare')
|
||||||
.description('unshare a gitpear repo')
|
.description('unshare a gitpear repo')
|
||||||
.addArgument(new commander.Argument('[p]', 'path to the repo').default('.'))
|
.option('-p, --path [path]', 'path to the repo', '.')
|
||||||
.action((p, options) => {
|
.action((options) => {
|
||||||
const fullPath = path.resolve(p)
|
const fullPath = path.resolve(options.path)
|
||||||
if (!fs.existsSync(path.join(fullPath, '.git'))) {
|
checkIfGitRepo(fullPath)
|
||||||
console.error('Not a git repo')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = fullPath.split(path.sep).pop()
|
const name = fullPath.split(path.sep).pop()
|
||||||
if ((home.isInitialized(name))) {
|
if ((home.isInitialized(name))) {
|
||||||
@@ -235,7 +139,7 @@ program
|
|||||||
.addArgument(new commander.Argument('[u]', 'url to remote pear').default(''))
|
.addArgument(new commander.Argument('[u]', 'url to remote pear').default(''))
|
||||||
.option('-s, --shared', 'list only shared repos')
|
.option('-s, --shared', 'list only shared repos')
|
||||||
.action((u, options) => {
|
.action((u, options) => {
|
||||||
if (u) return require('./list-remote')(u)
|
if (u) return listRemote(u)
|
||||||
|
|
||||||
const k = home.readPk()
|
const k = home.readPk()
|
||||||
const s = options.shared
|
const s = options.shared
|
||||||
@@ -276,7 +180,7 @@ program
|
|||||||
opts.stdio = 'inherit'
|
opts.stdio = 'inherit'
|
||||||
} else {
|
} else {
|
||||||
opts.detached = true
|
opts.detached = true
|
||||||
opts.stdio = [ 'ignore', home.getOutStream(), home.getErrStream() ]
|
opts.stdio = ['ignore', home.getOutStream(), home.getErrStream()]
|
||||||
}
|
}
|
||||||
|
|
||||||
const daemon = spawn('git-peard', opts)
|
const daemon = spawn('git-peard', opts)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const { spawn } = require('child_process')
|
|
||||||
const ProtomuxRPC = require('protomux-rpc')
|
const ProtomuxRPC = require('protomux-rpc')
|
||||||
|
|
||||||
const RAM = require('random-access-memory')
|
const RAM = require('random-access-memory')
|
||||||
@@ -14,8 +13,6 @@ const home = require('./home')
|
|||||||
const auth = require('./auth')
|
const auth = require('./auth')
|
||||||
const acl = require('./acl')
|
const acl = require('./acl')
|
||||||
|
|
||||||
const fs = require('fs')
|
|
||||||
|
|
||||||
const url = process.argv[3]
|
const url = process.argv[3]
|
||||||
const matches = url.match(/pear:\/\/([a-f0-9]{64})\/(.*)/)
|
const matches = url.match(/pear:\/\/([a-f0-9]{64})\/(.*)/)
|
||||||
|
|
||||||
@@ -29,7 +26,7 @@ const repoName = matches[2]
|
|||||||
|
|
||||||
const store = new Corestore(RAM)
|
const store = new Corestore(RAM)
|
||||||
const swarmOpts = {}
|
const swarmOpts = {}
|
||||||
if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') {
|
if (process.env.GIT_PEAR_AUTH === 'native') {
|
||||||
swarmOpts.keyPair = home.getKeyPair()
|
swarmOpts.keyPair = home.getKeyPair()
|
||||||
}
|
}
|
||||||
const swarm = new Hyperswarm(swarmOpts)
|
const swarm = new Hyperswarm(swarmOpts)
|
||||||
@@ -76,7 +73,7 @@ swarm.on('connection', async (socket) => {
|
|||||||
|
|
||||||
await drive.core.update({ wait: true })
|
await drive.core.update({ wait: true })
|
||||||
|
|
||||||
payload = { body: { url, method: 'get-refs', data: repoName }}
|
payload = { body: { url, method: 'get-refs', data: repoName } }
|
||||||
if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') {
|
if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') {
|
||||||
payload.header = await auth.getToken(payload.body)
|
payload.header = await auth.getToken(payload.body)
|
||||||
}
|
}
|
||||||
@@ -99,7 +96,7 @@ async function talkToGit (refs, drive, repoName, rpc, commit) {
|
|||||||
process.stdout.write('push\n')
|
process.stdout.write('push\n')
|
||||||
process.stdout.write('fetch\n\n')
|
process.stdout.write('fetch\n\n')
|
||||||
} else if (chunk && chunk.search(/^push/) !== -1) {
|
} else if (chunk && chunk.search(/^push/) !== -1) {
|
||||||
const [_command, path] = chunk.split(' ')
|
const path = chunk.split(' ')[1]
|
||||||
let [src, dst] = path.split(':')
|
let [src, dst] = path.split(':')
|
||||||
|
|
||||||
const isDelete = !src
|
const isDelete = !src
|
||||||
@@ -107,13 +104,6 @@ async function talkToGit (refs, drive, repoName, rpc, commit) {
|
|||||||
|
|
||||||
dst = dst.replace('refs/heads/', '').replace('\n\n', '')
|
dst = dst.replace('refs/heads/', '').replace('\n\n', '')
|
||||||
|
|
||||||
try { home.createAppFolder(repoName) } catch (e) { }
|
|
||||||
try { await git.createBareRepo(repoName) } catch (e) { }
|
|
||||||
try { await git.addRemote(repoName) } catch (e) { }
|
|
||||||
try { await git.push(dst) } catch (e) { }
|
|
||||||
try { home.shareAppFolder(repoName) } catch (e) { }
|
|
||||||
try { acl.setACL(repoName, acl.getACL(repoName)) } catch (e) { }
|
|
||||||
|
|
||||||
let method
|
let method
|
||||||
if (isDelete) {
|
if (isDelete) {
|
||||||
method = 'd-branch'
|
method = 'd-branch'
|
||||||
@@ -128,16 +118,25 @@ async function talkToGit (refs, drive, repoName, rpc, commit) {
|
|||||||
method = 'push'
|
method = 'push'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try { home.createAppFolder(repoName) } catch (e) { }
|
||||||
|
try { await git.createBareRepo(repoName) } catch (e) { }
|
||||||
|
try { await git.addRemote(repoName, { quite: true }) } catch (e) { }
|
||||||
|
try { await git.push(dst, isForce) } catch (e) { }
|
||||||
|
try { home.shareAppFolder(repoName) } catch (e) { }
|
||||||
|
try { acl.setACL(repoName, acl.getACL(repoName)) } catch (e) { }
|
||||||
|
|
||||||
const publicKey = home.readPk()
|
const publicKey = home.readPk()
|
||||||
let payload = { body: {
|
const payload = {
|
||||||
|
body: {
|
||||||
url: `pear://${publicKey}/${repoName}`,
|
url: `pear://${publicKey}/${repoName}`,
|
||||||
data: `${dst}#${commit}`,
|
data: `${dst}#${commit}`,
|
||||||
method
|
method
|
||||||
} }
|
}
|
||||||
|
}
|
||||||
if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') {
|
if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') {
|
||||||
payload.header = await auth.getToken(payload.body)
|
payload.header = await auth.getToken(payload.body)
|
||||||
}
|
}
|
||||||
const res = await rpc.request(method, Buffer.from(JSON.stringify(payload)))
|
await rpc.request(method, Buffer.from(JSON.stringify(payload)))
|
||||||
|
|
||||||
process.stdout.write('\n\n')
|
process.stdout.write('\n\n')
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
|
|||||||
10
src/git.js
10
src/git.js
@@ -64,9 +64,9 @@ async function createBareRepo (name) {
|
|||||||
return await doGit(init)
|
return await doGit(init)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addRemote (name) {
|
async function addRemote (name, opts = { quiet: false }) {
|
||||||
const init = spawn('git', ['remote', 'add', 'pear', getCodePath(name)])
|
const init = spawn('git', ['remote', 'add', 'pear', getCodePath(name)])
|
||||||
return await doGit(init)
|
return await doGit(init, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function push (branch = 'master', force = false) {
|
async function push (branch = 'master', force = false) {
|
||||||
@@ -76,8 +76,8 @@ async function push (branch = 'master', force = false) {
|
|||||||
return await doGit(push)
|
return await doGit(push)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doGit (child) {
|
async function doGit (child, opts = { quiet: false }) {
|
||||||
child.stderr.pipe(process.stderr)
|
if (!opts.quiet) child.stderr.pipe(process.stderr)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
child.on('close', (code) => {
|
child.on('close', (code) => {
|
||||||
if (code) {
|
if (code) {
|
||||||
@@ -229,5 +229,5 @@ module.exports = {
|
|||||||
addRemote,
|
addRemote,
|
||||||
push,
|
push,
|
||||||
getCommit,
|
getCommit,
|
||||||
getCurrentBranch,
|
getCurrentBranch
|
||||||
}
|
}
|
||||||
|
|||||||
53
src/rpc-handlers/acl.js
Normal file
53
src/rpc-handlers/acl.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
const ACL = require('../acl')
|
||||||
|
const home = require('../home')
|
||||||
|
|
||||||
|
async function getACLHandler (publicKey, req) {
|
||||||
|
const { repoName } = await parseACLRequest.bind(this)(publicKey, req)
|
||||||
|
const repoACL = ACL.getACL(repoName)
|
||||||
|
|
||||||
|
return Buffer.from(JSON.stringify(repoACL))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addACLHandler (publicKey, req) {
|
||||||
|
const { repoName, isBranch, name } = await parseACLRequest.bind(this)(publicKey, req)
|
||||||
|
|
||||||
|
isBranch ? ACL.addProtectedBranch(repoName, name) : ACL.grantAccessToUser(repoName, ...name.split(':'))
|
||||||
|
|
||||||
|
const repoACL = ACL.getACL(repoName)
|
||||||
|
return Buffer.from(JSON.stringify(repoACL))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function delACLHandler (publicKey, req) {
|
||||||
|
const { repoName, isBranch, name } = await parseACLRequest.bind(this)(publicKey, req)
|
||||||
|
|
||||||
|
isBranch ? ACL.removeProtectedBranch(repoName, name) : ACL.revokeAccessFromUser(repoName, name)
|
||||||
|
|
||||||
|
const repoACL = ACL.getACL(repoName)
|
||||||
|
return Buffer.from(JSON.stringify(repoACL))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function parseACLRequest (publicKey, req) {
|
||||||
|
if (!req) throw new Error('Request is empty')
|
||||||
|
const request = JSON.parse(req.toString())
|
||||||
|
const userId = await this.authenticate(publicKey, request)
|
||||||
|
const repoName = request.body.url?.split('/')?.pop()
|
||||||
|
|
||||||
|
if (!home.isInitialized(repoName)) throw new Error('Repo does not exist')
|
||||||
|
|
||||||
|
const isOwner = ACL.getOwners(repoName).includes(userId)
|
||||||
|
if (!isOwner) throw new Error('You are not allowed to access this repo')
|
||||||
|
|
||||||
|
return {
|
||||||
|
repoName,
|
||||||
|
name: request.body.name,
|
||||||
|
userId,
|
||||||
|
acl: request.body.acl,
|
||||||
|
isBranch: !!request.body.branch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getACLHandler,
|
||||||
|
addACLHandler,
|
||||||
|
delACLHandler
|
||||||
|
}
|
||||||
122
src/rpc-handlers/git.js
Normal file
122
src/rpc-handlers/git.js
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
const ACL = require('../acl')
|
||||||
|
const home = require('../home')
|
||||||
|
const { spawn } = require('child_process')
|
||||||
|
|
||||||
|
async function getReposHandler (publicKey, req) {
|
||||||
|
const { userId } = await parseReq.bind(this)(publicKey, req)
|
||||||
|
|
||||||
|
const res = {}
|
||||||
|
for (const repoName in this.repositories) {
|
||||||
|
const isPublic = (ACL.getACL(repoName).visibility === 'public')
|
||||||
|
if (isPublic || ACL.getViewers(repoName).includes(userId)) {
|
||||||
|
res[repoName] = this.drives[repoName].key.toString('hex')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Buffer.from(JSON.stringify(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getRefsHandler (publicKey, req) {
|
||||||
|
const { repoName, userId } = await parseReq.bind(this)(publicKey, req)
|
||||||
|
const res = this.repositories[repoName]
|
||||||
|
|
||||||
|
const isPublic = (ACL.getACL(repoName).visibility === 'public')
|
||||||
|
if (isPublic || ACL.getViewers(repoName).includes(userId)) {
|
||||||
|
return Buffer.from(JSON.stringify(res))
|
||||||
|
} else {
|
||||||
|
throw new Error('You are not allowed to access this repo')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pushHandler (publicKey, req) {
|
||||||
|
const { url, repoName, branch, userId } = await parseReq.bind(this)(publicKey, req)
|
||||||
|
const isContributor = ACL.getContributors(repoName).includes(userId)
|
||||||
|
|
||||||
|
if (!isContributor) throw new Error('You are not allowed to push to this repo')
|
||||||
|
|
||||||
|
const isProtectedBranch = ACL.getACL(repoName).protectedBranches.includes(branch)
|
||||||
|
const isAdmin = ACL.getAdmins(repoName).includes(userId)
|
||||||
|
|
||||||
|
if (isProtectedBranch && !isAdmin) throw new Error('You are not allowed to push to this branch')
|
||||||
|
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
||||||
|
const child = spawn('git', ['fetch', url, `${branch}:${branch}`], { env })
|
||||||
|
return doGit(child, resolve, reject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function forcePushHandler (publicKey, req) {
|
||||||
|
const { url, repoName, branch, userId } = await parseReq.bind(this)(publicKey, req)
|
||||||
|
const isContributor = ACL.getContributors(repoName).includes(userId)
|
||||||
|
|
||||||
|
if (!isContributor) throw new Error('You are not allowed to push to this repo')
|
||||||
|
|
||||||
|
const isProtectedBranch = ACL.getACL(repoName).protectedBranches.includes(branch)
|
||||||
|
const isAdmin = ACL.getAdmins(repoName).includes(userId)
|
||||||
|
|
||||||
|
if (isProtectedBranch && !isAdmin) throw new Error('You are not allowed to push to this branch')
|
||||||
|
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
||||||
|
const child = spawn('git', ['fetch', url, `${branch}:${branch}`, '--force'], { env })
|
||||||
|
return doGit(child, resolve, reject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteBranchHandler (publicKey, req) {
|
||||||
|
const { repoName, branch, userId } = await parseReq.bind(this)(publicKey, req)
|
||||||
|
const isContributor = ACL.getContributors(repoName).includes(userId)
|
||||||
|
|
||||||
|
if (!isContributor) throw new Error('You are not allowed to push to this repo')
|
||||||
|
|
||||||
|
const isProtectedBranch = ACL.getACL(repoName).protectedBranches.includes(branch)
|
||||||
|
const isAdmin = ACL.getAdmins(repoName).includes(userId)
|
||||||
|
|
||||||
|
if (isProtectedBranch && !isAdmin) throw new Error('You are not allowed to push to this branch')
|
||||||
|
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
||||||
|
const child = spawn('git', ['branch', '-D', branch], { env })
|
||||||
|
return doGit(child, resolve, reject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function parseReq (publicKey, req) {
|
||||||
|
if (!req) throw new Error('Request is empty')
|
||||||
|
const request = JSON.parse(req.toString())
|
||||||
|
const parsed = {
|
||||||
|
repoName: request.body.url?.split('/')?.pop(),
|
||||||
|
branch: request.body.data?.split('#')[0],
|
||||||
|
url: request.body.url,
|
||||||
|
userId: await this.authenticate(publicKey, request)
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
function doGit (child, resolve, reject) {
|
||||||
|
let errBuffer = Buffer.from('')
|
||||||
|
let outBuffer = Buffer.from('')
|
||||||
|
|
||||||
|
child.stdout.on('data', data => {
|
||||||
|
outBuffer = Buffer.concat([outBuffer, data])
|
||||||
|
})
|
||||||
|
|
||||||
|
child.stderr.on('data', data => {
|
||||||
|
errBuffer = Buffer.concat([errBuffer, data])
|
||||||
|
})
|
||||||
|
|
||||||
|
child.on('close', code => {
|
||||||
|
console.error('errBuffer', errBuffer.toString())
|
||||||
|
console.log('outBuffer', outBuffer.toString())
|
||||||
|
|
||||||
|
return code === 0 ? resolve(outBuffer) : reject(errBuffer)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getReposHandler,
|
||||||
|
getRefsHandler,
|
||||||
|
pushHandler,
|
||||||
|
forcePushHandler,
|
||||||
|
deleteBranchHandler
|
||||||
|
}
|
||||||
7
src/rpc-handlers/index.js
Normal file
7
src/rpc-handlers/index.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const git = require('./git')
|
||||||
|
const acl = require('./acl')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
git,
|
||||||
|
acl
|
||||||
|
}
|
||||||
116
src/rpc-requests/acl-remote.js
Normal file
116
src/rpc-requests/acl-remote.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
const ProtomuxRPC = require('protomux-rpc')
|
||||||
|
|
||||||
|
const Hyperswarm = require('hyperswarm')
|
||||||
|
const crypto = require('hypercore-crypto')
|
||||||
|
|
||||||
|
const home = require('../home')
|
||||||
|
const auth = require('../auth')
|
||||||
|
|
||||||
|
const { printACL, printACLForUser, logBranches } = require('../utils')
|
||||||
|
|
||||||
|
async function list (url, name, rpc, opts) {
|
||||||
|
const payload = { body: { url, method: 'get-acl' } }
|
||||||
|
if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') {
|
||||||
|
payload.header = await auth.getToken(payload.body)
|
||||||
|
}
|
||||||
|
const repoACLres = await rpc.request('get-acl', Buffer.from(JSON.stringify(payload)))
|
||||||
|
const repoACL = JSON.parse(repoACLres.toString())
|
||||||
|
|
||||||
|
opts.branch ? listACLBranch(repoACL) : listACLUser(repoACL, name)
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function listACLUser (repoACL, u) {
|
||||||
|
u ? printACLForUser(repoACL, u) : printACL(repoACL)
|
||||||
|
}
|
||||||
|
|
||||||
|
function listACLBranch (repoACL) {
|
||||||
|
logBranches(repoACL)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function add (url, name, rpc, opts) {
|
||||||
|
const payload = { body: { url, method: 'add-acl', name } }
|
||||||
|
if (opts.branch) payload.body.branch = true
|
||||||
|
|
||||||
|
if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') {
|
||||||
|
payload.header = await auth.getToken(payload.body)
|
||||||
|
}
|
||||||
|
const repoACLres = await rpc.request('add-acl', Buffer.from(JSON.stringify(payload)))
|
||||||
|
const repoACL = JSON.parse(repoACLres.toString())
|
||||||
|
opts.branch ? listACLBranch(repoACL) : listACLUser(repoACL, name.split(':')[0])
|
||||||
|
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function del (url, name, rpc, opts) {
|
||||||
|
const payload = { body: { url, method: 'del-acl', name } }
|
||||||
|
if (opts.branch) payload.body.branch = true
|
||||||
|
|
||||||
|
if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') {
|
||||||
|
payload.header = await auth.getToken(payload.body)
|
||||||
|
}
|
||||||
|
const repoACLres = await rpc.request('del-acl', Buffer.from(JSON.stringify(payload)))
|
||||||
|
const repoACL = JSON.parse(repoACLres.toString())
|
||||||
|
opts.branch ? listACLBranch(repoACL) : listACLUser(repoACL, name)
|
||||||
|
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wrapper (url, name, opts = {}, cb) {
|
||||||
|
if (typeof opts === 'function') {
|
||||||
|
cb = opts
|
||||||
|
opts = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const matches = url.match(/pear:\/\/([a-f0-9]{64})\/(.*)/)
|
||||||
|
|
||||||
|
if (!matches || matches.length < 3) {
|
||||||
|
console.error('Invalid URL')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetKey = matches[1]
|
||||||
|
const repoName = matches[2]
|
||||||
|
console.log('Connecting to:', targetKey)
|
||||||
|
|
||||||
|
const swarmOpts = {}
|
||||||
|
if (process.env.GIT_PEAR_AUTH === 'native') {
|
||||||
|
swarmOpts.keyPair = home.getKeyPair()
|
||||||
|
}
|
||||||
|
const swarm = new Hyperswarm(swarmOpts)
|
||||||
|
|
||||||
|
swarm.join(crypto.discoveryKey(Buffer.from(targetKey, 'hex')), { server: false })
|
||||||
|
|
||||||
|
swarm.on('connection', async (socket) => {
|
||||||
|
const rpc = new ProtomuxRPC(socket)
|
||||||
|
|
||||||
|
const payload = { body: { url, method: 'get-repos' } }
|
||||||
|
if (!process.env.GIT_PEAR_AUTH) {
|
||||||
|
console.debug('Retreiving data using un-authenticated access')
|
||||||
|
} else {
|
||||||
|
console.debug('Retreiving data using authenticated access')
|
||||||
|
}
|
||||||
|
if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') {
|
||||||
|
payload.header = await auth.getToken(payload.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
const reposRes = await rpc.request('get-repos', Buffer.from(JSON.stringify(payload)))
|
||||||
|
const repositories = JSON.parse(reposRes.toString())
|
||||||
|
if (!repositories) {
|
||||||
|
console.error('Failed to retrieve repositories')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
if (!repositories[repoName]) {
|
||||||
|
console.error('Repository not found')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
await cb(url, name, rpc, opts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
list: (url, name, opts) => wrapper(url, name, opts, list),
|
||||||
|
add: (url, name, opts) => wrapper(url, name, opts, add),
|
||||||
|
remove: (url, name, opts) => wrapper(url, name, opts, del)
|
||||||
|
}
|
||||||
7
src/rpc-requests/index.js
Normal file
7
src/rpc-requests/index.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const listRemote = require('./list-remote')
|
||||||
|
const aclRemote = require('./acl-remote')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
listRemote,
|
||||||
|
aclRemote
|
||||||
|
}
|
||||||
@@ -3,8 +3,8 @@ const ProtomuxRPC = require('protomux-rpc')
|
|||||||
const Hyperswarm = require('hyperswarm')
|
const Hyperswarm = require('hyperswarm')
|
||||||
const crypto = require('hypercore-crypto')
|
const crypto = require('hypercore-crypto')
|
||||||
|
|
||||||
const home = require('./home')
|
const home = require('../home')
|
||||||
const auth = require('./auth')
|
const auth = require('../auth')
|
||||||
|
|
||||||
module.exports = async function listRemote (url) {
|
module.exports = async function listRemote (url) {
|
||||||
const matches = url.match(/pear:\/\/([a-f0-9]{64})/)
|
const matches = url.match(/pear:\/\/([a-f0-9]{64})/)
|
||||||
@@ -18,7 +18,7 @@ module.exports = async function listRemote (url) {
|
|||||||
console.log('Connecting to:', targetKey)
|
console.log('Connecting to:', targetKey)
|
||||||
|
|
||||||
const swarmOpts = {}
|
const swarmOpts = {}
|
||||||
if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') {
|
if (process.env.GIT_PEAR_AUTH === 'native') {
|
||||||
swarmOpts.keyPair = home.getKeyPair()
|
swarmOpts.keyPair = home.getKeyPair()
|
||||||
}
|
}
|
||||||
const swarm = new Hyperswarm(swarmOpts)
|
const swarm = new Hyperswarm(swarmOpts)
|
||||||
@@ -28,14 +28,15 @@ module.exports = async function listRemote (url) {
|
|||||||
swarm.on('connection', async (socket) => {
|
swarm.on('connection', async (socket) => {
|
||||||
const rpc = new ProtomuxRPC(socket)
|
const rpc = new ProtomuxRPC(socket)
|
||||||
|
|
||||||
let payload = { body: { url, method: 'get-repos' } }
|
const payload = { body: { url, method: 'get-repos' } }
|
||||||
|
if (!process.env.GIT_PEAR_AUTH) {
|
||||||
|
console.debug('Retreiving data using un-authenticated access')
|
||||||
|
} else {
|
||||||
|
console.debug('Retreiving data using authenticated access')
|
||||||
|
}
|
||||||
if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') {
|
if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') {
|
||||||
payload.header = await auth.getToken(payload.body)
|
payload.header = await auth.getToken(payload.body)
|
||||||
console.debug('Retreiving data using authenticated access')
|
|
||||||
} else {
|
|
||||||
console.debug('Retreiving data using un-authenticated access')
|
|
||||||
}
|
}
|
||||||
console.log()
|
|
||||||
|
|
||||||
const reposRes = await rpc.request('get-repos', Buffer.from(JSON.stringify(payload)))
|
const reposRes = await rpc.request('get-repos', Buffer.from(JSON.stringify(payload)))
|
||||||
const repositories = JSON.parse(reposRes.toString())
|
const repositories = JSON.parse(reposRes.toString())
|
||||||
141
src/rpc.js
141
src/rpc.js
@@ -1,9 +1,6 @@
|
|||||||
const ProtomuxRPC = require('protomux-rpc')
|
const ProtomuxRPC = require('protomux-rpc')
|
||||||
const SecretStream = require('@hyperswarm/secret-stream')
|
|
||||||
const { spawn } = require('child_process')
|
|
||||||
const home = require('./home')
|
|
||||||
const auth = require('./auth')
|
const auth = require('./auth')
|
||||||
const acl = require('./acl')
|
const { git, acl } = require('./rpc-handlers')
|
||||||
|
|
||||||
module.exports = class RPC {
|
module.exports = class RPC {
|
||||||
constructor (announcedRefs, repositories, drives) {
|
constructor (announcedRefs, repositories, drives) {
|
||||||
@@ -15,8 +12,9 @@ module.exports = class RPC {
|
|||||||
|
|
||||||
async setHandlers (socket, peerInfo) {
|
async setHandlers (socket, peerInfo) {
|
||||||
if (this.connections[peerInfo.publicKey]) return this.connections[peerInfo.publicKey]
|
if (this.connections[peerInfo.publicKey]) return this.connections[peerInfo.publicKey]
|
||||||
|
|
||||||
const rpc = new ProtomuxRPC(socket)
|
const rpc = new ProtomuxRPC(socket)
|
||||||
|
this.connections[peerInfo.publicKey] = rpc
|
||||||
|
|
||||||
rpc.on('error', err => console.error('rpc error', err))
|
rpc.on('error', err => console.error('rpc error', err))
|
||||||
rpc.on('close', () => delete this.connections[peerInfo.publicKey])
|
rpc.on('close', () => delete this.connections[peerInfo.publicKey])
|
||||||
// XXX: handshaking can be used for access and permission management
|
// XXX: handshaking can be used for access and permission management
|
||||||
@@ -24,131 +22,22 @@ module.exports = class RPC {
|
|||||||
// which can in turn be stored in a .git-daemon-export-ok file
|
// which can in turn be stored in a .git-daemon-export-ok file
|
||||||
|
|
||||||
/* -- PULL HANDLERS -- */
|
/* -- PULL HANDLERS -- */
|
||||||
rpc.respond('get-repos', async req => await this.getReposHandler(socket.remotePublicKey, req))
|
rpc.respond('get-repos', async req => await git.getReposHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
rpc.respond('get-refs', async req => await this.getRefsHandler(socket.remotePublicKey, req))
|
rpc.respond('get-refs', async req => await git.getRefsHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
|
|
||||||
|
if (!process.env.GIT_PEAR_AUTH) return
|
||||||
|
|
||||||
if (process.env.GIT_PEAR_AUTH) {
|
|
||||||
/* -- PUSH HANDLERS -- */
|
/* -- PUSH HANDLERS -- */
|
||||||
rpc.respond('push', async req => await this.pushHandler(socket.remotePublicKey, req))
|
rpc.respond('push', async req => await git.pushHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
rpc.respond('f-push', async req => await this.forcePushHandler(socket.remotePublicKey, req))
|
rpc.respond('f-push', async req => await git.forcePushHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
rpc.respond('d-branch', async req => await this.deleteBranchHandler(socket.remotePublicKey, req))
|
rpc.respond('d-branch', async req => await git.deleteBranchHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
}
|
|
||||||
|
|
||||||
this.connections[peerInfo.publicKey] = rpc
|
/* -- REPO ADMINISTRATION HANDLERS -- */
|
||||||
}
|
|
||||||
|
|
||||||
async getReposHandler (publicKey, req) {
|
/* -- ACL HANDLERS -- */
|
||||||
const { branch, url, userId } = await this.parseReq(publicKey, req)
|
rpc.respond('get-acl', async req => await acl.getACLHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
|
rpc.respond('add-acl', async req => await acl.addACLHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
const res = {}
|
rpc.respond('del-acl', async req => await acl.delACLHandler.bind(this)(socket.remotePublicKey, req))
|
||||||
for (const repoName in this.repositories) {
|
|
||||||
// TODO: add only public repos and those which are shared with the peer
|
|
||||||
// Alternatively return only requested repo
|
|
||||||
const isPublic = (acl.getACL(repoName).visibility === 'public')
|
|
||||||
if (isPublic || acl.getViewers(repoName).includes(userId)) {
|
|
||||||
res[repoName] = this.drives[repoName].key.toString('hex')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Buffer.from(JSON.stringify(res))
|
|
||||||
}
|
|
||||||
|
|
||||||
async getRefsHandler (publicKey, req) {
|
|
||||||
const { repoName, branch, url, userId } = await this.parseReq(publicKey, req)
|
|
||||||
const res = this.repositories[repoName]
|
|
||||||
|
|
||||||
const isPublic = (acl.getACL(repoName).visibility === 'public')
|
|
||||||
if (isPublic || acl.getViewers(repoName).includes(userId)) {
|
|
||||||
return Buffer.from(JSON.stringify(res))
|
|
||||||
} else {
|
|
||||||
throw new Error('You are not allowed to access this repo')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async pushHandler (publicKey, req) {
|
|
||||||
const { url, repoName, branch, userId } = await this.parseReq(publicKey, req)
|
|
||||||
const isContributor = acl.getContributors(repoName).includes(userId)
|
|
||||||
|
|
||||||
if (!isContributor) throw new Error('You are not allowed to push to this repo')
|
|
||||||
|
|
||||||
const isProtectedBranch = acl.getACL(repoName).protectedBranches.includes(branch)
|
|
||||||
const isAdmin = acl.getAdmins(repoName).includes(userId)
|
|
||||||
|
|
||||||
if (isProtectedBranch && !isAdmin) throw new Error('You are not allowed to push to this branch')
|
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
|
||||||
const child = spawn('git', ['fetch', url, `${branch}:${branch}`], { env })
|
|
||||||
let errBuffer = Buffer.from('')
|
|
||||||
child.stderr.on('data', data => {
|
|
||||||
errBuffer = Buffer.concat([errBuffer, data])
|
|
||||||
})
|
|
||||||
|
|
||||||
child.on('close', code => {
|
|
||||||
return code === 0 ? resolve(errBuffer) : reject(errBuffer)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async forcePushHandler (publicKey, req) {
|
|
||||||
const { url, repoName, branch, userId } = await this.parseReq(publicKey, req)
|
|
||||||
const isContributor = acl.getContributors(repoName).includes(userId)
|
|
||||||
|
|
||||||
if (!isContributor) throw new Error('You are not allowed to push to this repo')
|
|
||||||
|
|
||||||
const isProtectedBranch = acl.getACL(repoName).protectedBranches.includes(branch)
|
|
||||||
const isAdmin = acl.getAdmins(repoName).includes(userId)
|
|
||||||
|
|
||||||
if (isProtectedBranch && !isAdmin) throw new Error('You are not allowed to push to this branch')
|
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
|
||||||
const child = spawn('git', ['fetch', url, `${branch}:${branch}`, '--force'], { env })
|
|
||||||
let errBuffer = Buffer.from('')
|
|
||||||
child.stderr.on('data', data => {
|
|
||||||
errBuffer = Buffer.concat([errBuffer, data])
|
|
||||||
})
|
|
||||||
|
|
||||||
child.on('close', code => {
|
|
||||||
return code === 0 ? resolve(errBuffer) : reject(errBuffer)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteBranchHandler (publicKey, req) {
|
|
||||||
const { url, repoName, branch, userId } = await this.parseReq(publicKey, req)
|
|
||||||
const isContributor = acl.getContributors(repoName).includes(userId)
|
|
||||||
|
|
||||||
if (!isContributor) throw new Error('You are not allowed to push to this repo')
|
|
||||||
|
|
||||||
const isProtectedBranch = acl.getACL(repoName).protectedBranches.includes(branch)
|
|
||||||
const isAdmin = acl.getAdmins(repoName).includes(userId)
|
|
||||||
|
|
||||||
if (isProtectedBranch && !isAdmin) throw new Error('You are not allowed to push to this branch')
|
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const env = { ...process.env, GIT_DIR: home.getCodePath(repoName) }
|
|
||||||
const child = spawn('git', ['branch', '-D', branch], { env })
|
|
||||||
let errBuffer = Buffer.from('')
|
|
||||||
child.stderr.on('data', data => {
|
|
||||||
errBuffer = Buffer.concat([errBuffer, data])
|
|
||||||
})
|
|
||||||
|
|
||||||
child.on('close', code => {
|
|
||||||
return code === 0 ? resolve(errBuffer) : reject(errBuffer)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async parseReq(publicKey, req) {
|
|
||||||
if (!req) throw new Error('Request is empty')
|
|
||||||
const request = JSON.parse(req.toString())
|
|
||||||
const parsed = {
|
|
||||||
repoName: request.body.url?.split('/')?.pop(),
|
|
||||||
branch: request.body.data?.split('#')[0],
|
|
||||||
url: request.body.url,
|
|
||||||
userId: await this.authenticate(publicKey, request),
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async authenticate (publicKey, request) {
|
async authenticate (publicKey, request) {
|
||||||
|
|||||||
36
src/utils.js
Normal file
36
src/utils.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
function printACL (repoACL) {
|
||||||
|
console.log('Repo Visibility:', '\t', repoACL.visibility)
|
||||||
|
console.log('Protected Branch(s):', '\t', repoACL.protectedBranches.join(', '))
|
||||||
|
console.log('User:', '\t', 'Role:')
|
||||||
|
for (const user in repoACL.ACL) {
|
||||||
|
console.log(user, '\t', repoACL.ACL[user])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function printACLForUser (repoACL, u) {
|
||||||
|
console.log('Repo Visibility:', '\t', repoACL.visibility)
|
||||||
|
console.log('Protected Branch(s):', '\t', repoACL.protectedBranches.join(', '))
|
||||||
|
console.log('User:', u, '\t', repoACL.ACL[u])
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkIfGitRepo (p) {
|
||||||
|
if (!fs.existsSync(path.join(p, '.git'))) {
|
||||||
|
console.error(` ${p} is not a git repo`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function logBranches (repoACL) {
|
||||||
|
console.log('Repo Visibility:', '\t', repoACL.visibility)
|
||||||
|
console.log('Protected Branch(s):', '\t', repoACL.protectedBranches.join(', '))
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
printACL,
|
||||||
|
printACLForUser,
|
||||||
|
checkIfGitRepo,
|
||||||
|
logBranches
|
||||||
|
}
|
||||||
@@ -7,8 +7,8 @@ test('acl', async t => {
|
|||||||
t.test('setACL', async t => {
|
t.test('setACL', async t => {
|
||||||
const aclObj = acl.setACL(repoName)
|
const aclObj = acl.setACL(repoName)
|
||||||
t.is(aclObj.visibility, 'public')
|
t.is(aclObj.visibility, 'public')
|
||||||
t.is(aclObj.protectedBranches.length, 1)
|
t.is(aclObj.protectedBranches.length, 2)
|
||||||
t.is(aclObj.protectedBranches[0], 'master')
|
t.ok(aclObj.protectedBranches.includes('master') && aclObj.protectedBranches.includes('main'))
|
||||||
t.is(Object.keys(aclObj.ACL).length, 0)
|
t.is(Object.keys(aclObj.ACL).length, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -34,12 +34,12 @@ test('acl', async t => {
|
|||||||
|
|
||||||
test('addProtectedBranch', async t => {
|
test('addProtectedBranch', async t => {
|
||||||
acl.addProtectedBranch(repoName, 'branch1')
|
acl.addProtectedBranch(repoName, 'branch1')
|
||||||
t.is(acl.getACL(repoName).protectedBranches.length, 2)
|
t.is(acl.getACL(repoName).protectedBranches.length, 3)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('removeProtectedBranch', async t => {
|
test('removeProtectedBranch', async t => {
|
||||||
acl.removeProtectedBranch(repoName, 'branch1')
|
acl.removeProtectedBranch(repoName, 'branch1')
|
||||||
t.is(acl.getACL(repoName).protectedBranches.length, 1)
|
t.is(acl.getACL(repoName).protectedBranches.length, 2)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('getAdmins', async t => {
|
test('getAdmins', async t => {
|
||||||
|
|||||||
@@ -51,13 +51,13 @@ test('git - uploadPack (w have)', { skip: true }, async t => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('git - createBareRepo', async t => {
|
test('git - createBareRepo', async t => {
|
||||||
t.absent(fs.existsSync(path.join(home.APP_HOME, 'test-git', 'code')))
|
t.absent(fs.existsSync(path.join(home.APP_HOME, 'test-git')))
|
||||||
home.createAppFolder('test-git')
|
home.createAppFolder('test-git')
|
||||||
|
|
||||||
t.absent(fs.existsSync(path.join(home.APP_HOME, 'test-git', 'code', 'HEAD')))
|
t.absent(fs.existsSync(path.join(home.APP_HOME, 'test-git', 'HEAD')))
|
||||||
await git.createBareRepo('test-git')
|
await git.createBareRepo('test-git')
|
||||||
|
|
||||||
t.ok(fs.existsSync(path.join(home.APP_HOME, 'test-git', 'code', 'HEAD')))
|
t.ok(fs.existsSync(path.join(home.APP_HOME, 'test-git', 'HEAD')))
|
||||||
|
|
||||||
t.teardown(() => {
|
t.teardown(() => {
|
||||||
fs.rmSync(path.join(home.APP_HOME, 'test-git'), { recursive: true })
|
fs.rmSync(path.join(home.APP_HOME, 'test-git'), { recursive: true })
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ test('getAppHome', t => {
|
|||||||
test('createAppFolder, share, is shared, unshare, isInitialized, list, getCodePath', t => {
|
test('createAppFolder, share, is shared, unshare, isInitialized, list, getCodePath', t => {
|
||||||
home.createAppFolder('test_code')
|
home.createAppFolder('test_code')
|
||||||
|
|
||||||
t.ok(fs.existsSync(path.join(home.APP_HOME, 'test_code', 'code')))
|
t.ok(fs.existsSync(path.join(home.APP_HOME, 'test_code')))
|
||||||
|
|
||||||
t.absent(home.isShared('test_code'))
|
t.absent(home.isShared('test_code'))
|
||||||
t.absent(fs.existsSync(path.join(home.APP_HOME, 'test_code', '.git-daemon-export-ok')))
|
t.absent(fs.existsSync(path.join(home.APP_HOME, 'test_code', '.git-daemon-export-ok')))
|
||||||
@@ -32,7 +32,7 @@ test('createAppFolder, share, is shared, unshare, isInitialized, list, getCodePa
|
|||||||
t.alike(new Set(home.list()), new Set(['foo', 'bar', 'zar']))
|
t.alike(new Set(home.list()), new Set(['foo', 'bar', 'zar']))
|
||||||
t.alike(new Set(home.list(true)), new Set(['foo', 'bar']))
|
t.alike(new Set(home.list(true)), new Set(['foo', 'bar']))
|
||||||
|
|
||||||
t.alike(path.resolve(home.getCodePath('test_code')), path.resolve(path.join(home.APP_HOME, 'test_code', 'code')))
|
t.alike(path.resolve(home.getCodePath('test_code')), path.resolve(path.join(home.APP_HOME, 'test_code')))
|
||||||
|
|
||||||
t.teardown(() => {
|
t.teardown(() => {
|
||||||
fs.rmSync(path.join(home.APP_HOME, 'test_code'), { recursive: true })
|
fs.rmSync(path.join(home.APP_HOME, 'test_code'), { recursive: true })
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ test('e2e', async t => {
|
|||||||
|
|
||||||
await drive.core.update({ wait: true })
|
await drive.core.update({ wait: true })
|
||||||
|
|
||||||
payload = Buffer.from(JSON.stringify({ body: { url, method: 'get-refs', data: repoName }}))
|
payload = Buffer.from(JSON.stringify({ body: { url, method: 'get-refs', data: repoName } }))
|
||||||
const refsRes = await rpc.request('get-refs', payload)
|
const refsRes = await rpc.request('get-refs', payload)
|
||||||
t.ok(refsRes)
|
t.ok(refsRes)
|
||||||
|
|
||||||
|
|||||||
BIN
test_home.tar.gz
BIN
test_home.tar.gz
Binary file not shown.
Reference in New Issue
Block a user