diff --git a/Readme.md b/Readme.md index 9efbebb..b774027 100644 --- a/Readme.md +++ b/Readme.md @@ -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 key` - print out public key. Share it with your peers so that they can do `git pull pear:/` -* `git pear init [-s, --share] ` - 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/). 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 ` later -* `git pear share ` - makes repository sharable +* `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/). 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 (default: "public")]` - share repository, if branch is not specified, default branch will be shared * `git pear unshare ` - stop sharing repository * `git pear list [-s, --shared]` - list all or (only shared) repositories +* `git pear list ` - list repositories of a peer ### ACL (for authenticated access to enable support of PUSH) diff --git a/bin/nix.sh b/bin/nix.sh index b5aabce..b0f1ba9 100755 --- a/bin/nix.sh +++ b/bin/nix.sh @@ -1,4 +1,4 @@ #!/bin/sh rm -rf result -nix build '.#' +nix build '.#' --extra-experimental-features nix-command --extra-experimental-features flakes diff --git a/flake.nix b/flake.nix index 6c8d920..be073ca 100644 --- a/flake.nix +++ b/flake.nix @@ -14,7 +14,7 @@ , ... }: 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 = ./.; source = ./.; projects = ./projects.toml; diff --git a/src/acl.js b/src/acl.js index b22344c..1e8071c 100644 --- a/src/acl.js +++ b/src/acl.js @@ -14,7 +14,7 @@ const ROLES = { } const DEFAULT_ACL = { visibility: 'public', // public|private - protectedBranches: ['master'], + protectedBranches: ['master', 'main'], ACL: {} } diff --git a/src/cli.js b/src/cli.js index 4a5361b..ea963e4 100755 --- a/src/cli.js +++ b/src/cli.js @@ -22,7 +22,7 @@ program .command('init') .description('initialize a gitpear repo') .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) => { const fullPath = path.resolve(p) if (!fs.existsSync(path.join(fullPath, '.git'))) { @@ -51,21 +51,27 @@ program console.log(`Added git remote for "${name}" as "pear"`) } catch (e) { } + let branchToShare = await git.getCurrentBranch() + if (options.share && options.share !== true) { + branchToShare = options.share + } + if (options.share) { try { home.shareAppFolder(name) } catch (e) { } try { acl.setACL(name) } catch (e) { } - try { await git.push() } catch (e) { } - console.log(`Shared "${name}" project`) + try { await git.push(branchToShare) } catch (e) { } + console.log(`Shared "${name}" project, ${branchToShare} branch`) } }) program .command('share') .description('share a gitpear repo') - .addArgument(new commander.Argument('[p]', 'path to the repo').default('.')) - .addArgument(new commander.Argument('[v]', 'visibility of the repo').default('public')) - .action(async (p, v, options) => { - const fullPath = path.resolve(p) + .option('-b, --branch [b]', 'branch to share, default is current branch', '') + .option('-v, --visibility [v]', 'visibility of the repo', 'public') + .option('-p, --path [p]', 'path to the repo', '.') + .action(async (options) => { + const fullPath = path.resolve(options.path) if (!fs.existsSync(path.join(fullPath, '.git'))) { console.error('Not a git repo') process.exit(1) @@ -77,10 +83,12 @@ program process.exit(1) } + const currentBranch = await git.getCurrentBranch() + const branchToShare = options.branch || currentBranch try { home.shareAppFolder(name) } catch (e) { } - try { acl.setACL(name, { visibility: v }) } catch (e) { } - try { await git.push() } catch (e) { } - console.log(`Shared "${name}" project, as ${v} repo`) + 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 }) @@ -224,10 +232,13 @@ program program .command('list') .description('list all gitpear repos') + .addArgument(new commander.Argument('[u]', 'url to remote pear').default('')) .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 s = options.opts().shared + const s = options.shared home.list(s).forEach(n => console.log(n, ...(s ? ['\t', `pear://${k}/${n}`] : []))) }) diff --git a/src/git-remote-pear.js b/src/git-remote-pear.js index 31ac3a7..6aac563 100755 --- a/src/git-remote-pear.js +++ b/src/git-remote-pear.js @@ -28,7 +28,11 @@ const targetKey = matches[1] const repoName = matches[2] 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()) { console.error('Please start git pear daemon') diff --git a/src/git.js b/src/git.js index 41bf38d..789b373 100644 --- a/src/git.js +++ b/src/git.js @@ -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) { const ls = spawn('git', ['ls-remote', url]) 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, +} diff --git a/src/home.js b/src/home.js index 37d6036..c13f035 100644 --- a/src/home.js +++ b/src/home.js @@ -7,7 +7,7 @@ const fs = require('fs') const APP_HOME = process.env.GIT_PEAR || `${homedir}/.gitpear` function createAppFolder (name) { - fs.mkdirSync(`${APP_HOME}/${name}/code`, { recursive: true }) + fs.mkdirSync(`${APP_HOME}/${name}`, { recursive: true }) } function shareAppFolder (name) { @@ -24,7 +24,7 @@ function unshareAppFolder (name) { } function isInitialized (name) { - return fs.existsSync(`${APP_HOME}/${name}/code/HEAD`) + return fs.existsSync(`${APP_HOME}/${name}/HEAD`) } function isShared (name) { @@ -39,7 +39,7 @@ function list (sharedOnly) { } function getCodePath (name) { - return `${APP_HOME}/${name}/code` + return `${APP_HOME}/${name}` } function readPk () { diff --git a/src/list-remote.js b/src/list-remote.js new file mode 100644 index 0000000..576fadb --- /dev/null +++ b/src/list-remote.js @@ -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) + }) +} diff --git a/src/state.js b/src/state.js index fc56bf3..f759f17 100644 --- a/src/state.js +++ b/src/state.js @@ -15,14 +15,15 @@ module.exports = async function setState (store, drives = {}) { 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] = {} for (const ref in ls) { repositories[repo][ref] = ls[ref] 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`) localPackStream.on('ready', () => localPackStream.stdout.pipe(driveStream)) }