Merge branch 'dzdidi:master' into master

This commit is contained in:
2024-02-09 14:22:00 +00:00
committed by GitHub
10 changed files with 123 additions and 24 deletions

View File

@@ -38,10 +38,11 @@ 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 [-s, --share] <path>` - 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` or call `gitpear share <path>` later * `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 share <path>` - makes repository sharable * `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 unshare <path>` - stop sharing repository * `git pear unshare <path>` - stop sharing 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
### ACL (for authenticated access to enable support of PUSH) ### ACL (for authenticated access to enable support of PUSH)

View File

@@ -1,4 +1,4 @@
#!/bin/sh #!/bin/sh
rm -rf result rm -rf result
nix build '.#' nix build '.#' --extra-experimental-features nix-command --extra-experimental-features flakes

View File

@@ -14,7 +14,7 @@
, ... , ...
}: }:
dream2nix.lib.makeFlakeOutputs { dream2nix.lib.makeFlakeOutputs {
systems = [ "x86_64-darwin" "x86_64-linux" "aarch64-darwin" ]; systems = [ "x86_64-darwin" "x86_64-linux" "aarch64-darwin" "aarch64-linux" ];
config.projectRoot = ./.; config.projectRoot = ./.;
source = ./.; source = ./.;
projects = ./projects.toml; projects = ./projects.toml;

View File

@@ -14,7 +14,7 @@ const ROLES = {
} }
const DEFAULT_ACL = { const DEFAULT_ACL = {
visibility: 'public', // public|private visibility: 'public', // public|private
protectedBranches: ['master'], protectedBranches: ['master', 'main'],
ACL: {} ACL: {}
} }

View File

@@ -22,7 +22,7 @@ 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('.')) .addArgument(new commander.Argument('[p]', 'path to the repo').default('.'))
.option('-s, --share', 'share the repo, default false') .option('-s, --share [branch]', 'share the repo as public, default false, default branch is current', '')
.action(async (p, options) => { .action(async (p, options) => {
const fullPath = path.resolve(p) const fullPath = path.resolve(p)
if (!fs.existsSync(path.join(fullPath, '.git'))) { if (!fs.existsSync(path.join(fullPath, '.git'))) {
@@ -51,21 +51,27 @@ program
console.log(`Added git remote for "${name}" as "pear"`) console.log(`Added git remote for "${name}" as "pear"`)
} catch (e) { } } catch (e) { }
let branchToShare = await git.getCurrentBranch()
if (options.share && options.share !== true) {
branchToShare = options.share
}
if (options.share) { if (options.share) {
try { home.shareAppFolder(name) } catch (e) { } try { home.shareAppFolder(name) } catch (e) { }
try { acl.setACL(name) } catch (e) { } try { acl.setACL(name) } catch (e) { }
try { await git.push() } catch (e) { } try { await git.push(branchToShare) } catch (e) { }
console.log(`Shared "${name}" project`) console.log(`Shared "${name}" project, ${branchToShare} branch`)
} }
}) })
program program
.command('share') .command('share')
.description('share a gitpear repo') .description('share a gitpear repo')
.addArgument(new commander.Argument('[p]', 'path to the repo').default('.')) .option('-b, --branch [b]', 'branch to share, default is current branch', '')
.addArgument(new commander.Argument('[v]', 'visibility of the repo').default('public')) .option('-v, --visibility [v]', 'visibility of the repo', 'public')
.action(async (p, v, options) => { .option('-p, --path [p]', 'path to the repo', '.')
const fullPath = path.resolve(p) .action(async (options) => {
const fullPath = path.resolve(options.path)
if (!fs.existsSync(path.join(fullPath, '.git'))) { if (!fs.existsSync(path.join(fullPath, '.git'))) {
console.error('Not a git repo') console.error('Not a git repo')
process.exit(1) process.exit(1)
@@ -77,10 +83,12 @@ program
process.exit(1) process.exit(1)
} }
const currentBranch = await git.getCurrentBranch()
const branchToShare = options.branch || currentBranch
try { home.shareAppFolder(name) } catch (e) { } try { home.shareAppFolder(name) } catch (e) { }
try { acl.setACL(name, { visibility: v }) } catch (e) { } try { acl.setACL(name, { visibility: options.visibility }) } catch (e) { }
try { await git.push() } catch (e) { } try { await git.push(branchToShare) } catch (e) { }
console.log(`Shared "${name}" project, as ${v} repo`) console.log(`Shared "${name}" project, ${branchToShare} branch, as ${options.visibility} repo`)
return return
}) })
@@ -224,10 +232,13 @@ program
program program
.command('list') .command('list')
.description('list all gitpear repos') .description('list all gitpear repos')
.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((p, options) => { .action((u, options) => {
if (u) return require('./list-remote')(u)
const k = home.readPk() const k = home.readPk()
const s = options.opts().shared const s = options.shared
home.list(s).forEach(n => console.log(n, ...(s ? ['\t', `pear://${k}/${n}`] : []))) home.list(s).forEach(n => console.log(n, ...(s ? ['\t', `pear://${k}/${n}`] : [])))
}) })

View File

@@ -28,7 +28,11 @@ const targetKey = matches[1]
const repoName = matches[2] const repoName = matches[2]
const store = new Corestore(RAM) const store = new Corestore(RAM)
const swarm = new Hyperswarm({ keyPair: home.getKeyPair() }) const swarmOpts = {}
if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') {
swarmOpts.keyPair = home.getKeyPair()
}
const swarm = new Hyperswarm(swarmOpts)
if (!home.isDaemonRunning()) { if (!home.isDaemonRunning()) {
console.error('Please start git pear daemon') console.error('Please start git pear daemon')

View File

@@ -20,6 +20,25 @@ async function getCommit () {
}) })
} }
async function getCurrentBranch () {
return await new Promise((resolve, reject) => {
const process = spawn('git', ['rev-parse', '--abbrev-ref', 'HEAD'])
let outBuffer = Buffer.from('')
process.stdout.on('data', data => {
outBuffer = Buffer.concat([outBuffer, data])
})
let errBuffer = Buffer.from('')
process.stderr.on('err', data => {
errBuffer = Buffer.concat([errBuffer, data])
})
process.on('close', code => {
return code === 0 ? resolve(outBuffer.toString().replace('\n', '')) : reject(errBuffer)
})
})
}
async function lsPromise (url) { async function lsPromise (url) {
const ls = spawn('git', ['ls-remote', url]) const ls = spawn('git', ['ls-remote', url])
const res = {} const res = {}
@@ -201,4 +220,14 @@ async function unpackStream (packStream) {
}) })
} }
module.exports = { lsPromise, uploadPack, unpackFile, unpackStream, createBareRepo, addRemote, push, getCommit } module.exports = {
lsPromise,
uploadPack,
unpackFile,
unpackStream,
createBareRepo,
addRemote,
push,
getCommit,
getCurrentBranch,
}

View File

@@ -7,7 +7,7 @@ const fs = require('fs')
const APP_HOME = process.env.GIT_PEAR || `${homedir}/.gitpear` const APP_HOME = process.env.GIT_PEAR || `${homedir}/.gitpear`
function createAppFolder (name) { function createAppFolder (name) {
fs.mkdirSync(`${APP_HOME}/${name}/code`, { recursive: true }) fs.mkdirSync(`${APP_HOME}/${name}`, { recursive: true })
} }
function shareAppFolder (name) { function shareAppFolder (name) {
@@ -24,7 +24,7 @@ function unshareAppFolder (name) {
} }
function isInitialized (name) { function isInitialized (name) {
return fs.existsSync(`${APP_HOME}/${name}/code/HEAD`) return fs.existsSync(`${APP_HOME}/${name}/HEAD`)
} }
function isShared (name) { function isShared (name) {
@@ -39,7 +39,7 @@ function list (sharedOnly) {
} }
function getCodePath (name) { function getCodePath (name) {
return `${APP_HOME}/${name}/code` return `${APP_HOME}/${name}`
} }
function readPk () { function readPk () {

53
src/list-remote.js Normal file
View File

@@ -0,0 +1,53 @@
const ProtomuxRPC = require('protomux-rpc')
const Hyperswarm = require('hyperswarm')
const crypto = require('hypercore-crypto')
const home = require('./home')
const auth = require('./auth')
module.exports = async function listRemote (url) {
const matches = url.match(/pear:\/\/([a-f0-9]{64})/)
if (!matches || matches.length < 2) {
console.error('Invalid URL')
process.exit(1)
}
const targetKey = matches[1]
console.log('Connecting to:', targetKey)
const swarmOpts = {}
if (process.env.GIT_PEAR_AUTH && 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)
let payload = { body: { url, method: 'get-repos' } }
if (process.env.GIT_PEAR_AUTH && process.env.GIT_PEAR_AUTH !== 'native') {
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 repositories = JSON.parse(reposRes.toString())
if (!repositories) {
console.error('Failed to retrieve repositories')
process.exit(1)
}
console.log('Repositories:', '\t', 'HEAD')
for (const repo in repositories) {
console.log(repo, '\t', repositories[repo])
}
process.exit(0)
})
}

View File

@@ -15,14 +15,15 @@ module.exports = async function setState (store, drives = {}) {
await drives[repo].ready() await drives[repo].ready()
} }
const ls = await git.lsPromise(home.getCodePath(repo)) const homePath = home.getCodePath(repo)
const ls = await git.lsPromise(homePath)
repositories[repo] = {} repositories[repo] = {}
for (const ref in ls) { for (const ref in ls) {
repositories[repo][ref] = ls[ref] repositories[repo][ref] = ls[ref]
announcedRefs[ls[ref]] = repo announcedRefs[ls[ref]] = repo
const localPackStream = git.uploadPack(home.getCodePath(repo), ls[ref]) const localPackStream = git.uploadPack(homePath, ls[ref])
const driveStream = drives[repo].createWriteStream(`/packs/${ls[ref]}.pack`) const driveStream = drives[repo].createWriteStream(`/packs/${ls[ref]}.pack`)
localPackStream.on('ready', () => localPackStream.stdout.pipe(driveStream)) localPackStream.on('ready', () => localPackStream.stdout.pipe(driveStream))
} }