commit
a2b2b10697
@ -0,0 +1,13 @@ |
||||
{ |
||||
"scripts": { |
||||
"start:server": "npx ts-node server.ts" |
||||
}, |
||||
"dependencies": { |
||||
"body-parser": "^1.20.2", |
||||
"child_process": "^1.0.2", |
||||
"express": "^4.19.2", |
||||
"git-http-backend": "^1.1.2", |
||||
"path": "^0.12.7", |
||||
"zlib": "^1.0.5" |
||||
} |
||||
} |
@ -0,0 +1,48 @@ |
||||
import * as http from 'http'; |
||||
import { spawn } from 'child_process'; |
||||
import * as path from 'path'; |
||||
let backend = require('git-http-backend'); |
||||
import * as zlib from 'zlib'; |
||||
|
||||
const directory = process.argv[2]; |
||||
|
||||
if (!directory) { |
||||
console.error('Please provide a directory as a command line argument.'); |
||||
process.exit(1); |
||||
} |
||||
|
||||
const server = http.createServer((req, res) => { |
||||
|
||||
// Set CORS headers
|
||||
res.setHeader('Access-Control-Allow-Origin', '*'); |
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); |
||||
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); |
||||
res.setHeader('Access-Control-Allow-Credentials', 'true'); |
||||
|
||||
if (req.method === 'OPTIONS') { |
||||
// Handle preflight request
|
||||
res.writeHead(204); |
||||
res.end(); |
||||
return; |
||||
} |
||||
|
||||
const repo = req.url?.split('/')[1]; |
||||
const dir = path.join(directory, 'git', repo || ''); |
||||
console.log(dir); |
||||
const reqStream = req.headers['content-encoding'] === 'gzip' ? req.pipe(zlib.createGunzip()) : req; |
||||
|
||||
reqStream.pipe(backend(req.url || '', (err, service) => { |
||||
if (err) return res.end(err + '\n'); |
||||
|
||||
res.setHeader('content-type', service.type); |
||||
console.log(service.action, repo, service.fields); |
||||
|
||||
const ps = spawn(service.cmd, [...service.args, dir]); |
||||
ps.stdout.pipe(service.createStream()).pipe(ps.stdin); |
||||
|
||||
})).pipe(res); |
||||
}); |
||||
|
||||
server.listen(6868, () => { |
||||
console.log('Server is listening on port 6868'); |
||||
}); |
@ -0,0 +1,7 @@ |
||||
|
||||
cd /tmp/ |
||||
rm -rf git/bare.git |
||||
rm -rf git |
||||
mkdir -p git |
||||
cd git |
||||
git clone --bare https://github.com/ethereum/awesome-remix bare.git |
@ -0,0 +1,511 @@ |
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. |
||||
# yarn lockfile v1 |
||||
|
||||
|
||||
accepts@~1.3.8: |
||||
version "1.3.8" |
||||
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" |
||||
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== |
||||
dependencies: |
||||
mime-types "~2.1.34" |
||||
negotiator "0.6.3" |
||||
|
||||
array-flatten@1.1.1: |
||||
version "1.1.1" |
||||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" |
||||
integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== |
||||
|
||||
body-parser@1.20.2, body-parser@^1.20.2: |
||||
version "1.20.2" |
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" |
||||
integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== |
||||
dependencies: |
||||
bytes "3.1.2" |
||||
content-type "~1.0.5" |
||||
debug "2.6.9" |
||||
depd "2.0.0" |
||||
destroy "1.2.0" |
||||
http-errors "2.0.0" |
||||
iconv-lite "0.4.24" |
||||
on-finished "2.4.1" |
||||
qs "6.11.0" |
||||
raw-body "2.5.2" |
||||
type-is "~1.6.18" |
||||
unpipe "1.0.0" |
||||
|
||||
bytes@3.1.2: |
||||
version "3.1.2" |
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" |
||||
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== |
||||
|
||||
call-bind@^1.0.7: |
||||
version "1.0.7" |
||||
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" |
||||
integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== |
||||
dependencies: |
||||
es-define-property "^1.0.0" |
||||
es-errors "^1.3.0" |
||||
function-bind "^1.1.2" |
||||
get-intrinsic "^1.2.4" |
||||
set-function-length "^1.2.1" |
||||
|
||||
child_process@^1.0.2: |
||||
version "1.0.2" |
||||
resolved "https://registry.yarnpkg.com/child_process/-/child_process-1.0.2.tgz#b1f7e7fc73d25e7fd1d455adc94e143830182b5a" |
||||
integrity sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g== |
||||
|
||||
content-disposition@0.5.4: |
||||
version "0.5.4" |
||||
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" |
||||
integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== |
||||
dependencies: |
||||
safe-buffer "5.2.1" |
||||
|
||||
content-type@~1.0.4, content-type@~1.0.5: |
||||
version "1.0.5" |
||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" |
||||
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== |
||||
|
||||
cookie-signature@1.0.6: |
||||
version "1.0.6" |
||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" |
||||
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== |
||||
|
||||
cookie@0.6.0: |
||||
version "0.6.0" |
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" |
||||
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== |
||||
|
||||
debug@2.6.9: |
||||
version "2.6.9" |
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" |
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== |
||||
dependencies: |
||||
ms "2.0.0" |
||||
|
||||
define-data-property@^1.1.4: |
||||
version "1.1.4" |
||||
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" |
||||
integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== |
||||
dependencies: |
||||
es-define-property "^1.0.0" |
||||
es-errors "^1.3.0" |
||||
gopd "^1.0.1" |
||||
|
||||
depd@2.0.0: |
||||
version "2.0.0" |
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" |
||||
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== |
||||
|
||||
destroy@1.2.0: |
||||
version "1.2.0" |
||||
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" |
||||
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== |
||||
|
||||
ee-first@1.1.1: |
||||
version "1.1.1" |
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" |
||||
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== |
||||
|
||||
encodeurl@~1.0.2: |
||||
version "1.0.2" |
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" |
||||
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== |
||||
|
||||
es-define-property@^1.0.0: |
||||
version "1.0.0" |
||||
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" |
||||
integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== |
||||
dependencies: |
||||
get-intrinsic "^1.2.4" |
||||
|
||||
es-errors@^1.3.0: |
||||
version "1.3.0" |
||||
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" |
||||
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== |
||||
|
||||
escape-html@~1.0.3: |
||||
version "1.0.3" |
||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" |
||||
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== |
||||
|
||||
etag@~1.8.1: |
||||
version "1.8.1" |
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" |
||||
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== |
||||
|
||||
express@^4.19.2: |
||||
version "4.19.2" |
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" |
||||
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== |
||||
dependencies: |
||||
accepts "~1.3.8" |
||||
array-flatten "1.1.1" |
||||
body-parser "1.20.2" |
||||
content-disposition "0.5.4" |
||||
content-type "~1.0.4" |
||||
cookie "0.6.0" |
||||
cookie-signature "1.0.6" |
||||
debug "2.6.9" |
||||
depd "2.0.0" |
||||
encodeurl "~1.0.2" |
||||
escape-html "~1.0.3" |
||||
etag "~1.8.1" |
||||
finalhandler "1.2.0" |
||||
fresh "0.5.2" |
||||
http-errors "2.0.0" |
||||
merge-descriptors "1.0.1" |
||||
methods "~1.1.2" |
||||
on-finished "2.4.1" |
||||
parseurl "~1.3.3" |
||||
path-to-regexp "0.1.7" |
||||
proxy-addr "~2.0.7" |
||||
qs "6.11.0" |
||||
range-parser "~1.2.1" |
||||
safe-buffer "5.2.1" |
||||
send "0.18.0" |
||||
serve-static "1.15.0" |
||||
setprototypeof "1.2.0" |
||||
statuses "2.0.1" |
||||
type-is "~1.6.18" |
||||
utils-merge "1.0.1" |
||||
vary "~1.1.2" |
||||
|
||||
finalhandler@1.2.0: |
||||
version "1.2.0" |
||||
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" |
||||
integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== |
||||
dependencies: |
||||
debug "2.6.9" |
||||
encodeurl "~1.0.2" |
||||
escape-html "~1.0.3" |
||||
on-finished "2.4.1" |
||||
parseurl "~1.3.3" |
||||
statuses "2.0.1" |
||||
unpipe "~1.0.0" |
||||
|
||||
forwarded@0.2.0: |
||||
version "0.2.0" |
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" |
||||
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== |
||||
|
||||
fresh@0.5.2: |
||||
version "0.5.2" |
||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" |
||||
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== |
||||
|
||||
function-bind@^1.1.2: |
||||
version "1.1.2" |
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" |
||||
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== |
||||
|
||||
get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: |
||||
version "1.2.4" |
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" |
||||
integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== |
||||
dependencies: |
||||
es-errors "^1.3.0" |
||||
function-bind "^1.1.2" |
||||
has-proto "^1.0.1" |
||||
has-symbols "^1.0.3" |
||||
hasown "^2.0.0" |
||||
|
||||
git-http-backend@^1.1.2: |
||||
version "1.1.2" |
||||
resolved "https://registry.yarnpkg.com/git-http-backend/-/git-http-backend-1.1.2.tgz#fb6c7b261251df8c2bdf9dc30876a96ce323f545" |
||||
integrity sha512-Gx7n/kyCEXGFZlCGmbsEsyeyabLs8XWeb+E/6842up7p3PktQS2/8rlNfB6hCagnW0pJ13Tn8E3yhOkKS6ihdg== |
||||
dependencies: |
||||
git-side-band-message "~0.0.3" |
||||
inherits "~2.0.1" |
||||
|
||||
git-side-band-message@~0.0.3: |
||||
version "0.0.3" |
||||
resolved "https://registry.yarnpkg.com/git-side-band-message/-/git-side-band-message-0.0.3.tgz#b8a5348c2dcbf1949fd295c506014e26c3f26a46" |
||||
integrity sha512-4Rq4xm1+zqCkmuHxRbGdA5ActF7F4UfgK8uI0B7ZfSkByZfikRuF7mqHlvqmycvqos7jpXNkgsZK7DThLLHG3w== |
||||
|
||||
gopd@^1.0.1: |
||||
version "1.0.1" |
||||
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" |
||||
integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== |
||||
dependencies: |
||||
get-intrinsic "^1.1.3" |
||||
|
||||
has-property-descriptors@^1.0.2: |
||||
version "1.0.2" |
||||
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" |
||||
integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== |
||||
dependencies: |
||||
es-define-property "^1.0.0" |
||||
|
||||
has-proto@^1.0.1: |
||||
version "1.0.3" |
||||
resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" |
||||
integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== |
||||
|
||||
has-symbols@^1.0.3: |
||||
version "1.0.3" |
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" |
||||
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== |
||||
|
||||
hasown@^2.0.0: |
||||
version "2.0.2" |
||||
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" |
||||
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== |
||||
dependencies: |
||||
function-bind "^1.1.2" |
||||
|
||||
http-errors@2.0.0: |
||||
version "2.0.0" |
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" |
||||
integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== |
||||
dependencies: |
||||
depd "2.0.0" |
||||
inherits "2.0.4" |
||||
setprototypeof "1.2.0" |
||||
statuses "2.0.1" |
||||
toidentifier "1.0.1" |
||||
|
||||
iconv-lite@0.4.24: |
||||
version "0.4.24" |
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" |
||||
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== |
||||
dependencies: |
||||
safer-buffer ">= 2.1.2 < 3" |
||||
|
||||
inherits@2.0.3: |
||||
version "2.0.3" |
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" |
||||
integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== |
||||
|
||||
inherits@2.0.4, inherits@~2.0.1: |
||||
version "2.0.4" |
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" |
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== |
||||
|
||||
ipaddr.js@1.9.1: |
||||
version "1.9.1" |
||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" |
||||
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== |
||||
|
||||
media-typer@0.3.0: |
||||
version "0.3.0" |
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" |
||||
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== |
||||
|
||||
merge-descriptors@1.0.1: |
||||
version "1.0.1" |
||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" |
||||
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== |
||||
|
||||
methods@~1.1.2: |
||||
version "1.1.2" |
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" |
||||
integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== |
||||
|
||||
mime-db@1.52.0: |
||||
version "1.52.0" |
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" |
||||
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== |
||||
|
||||
mime-types@~2.1.24, mime-types@~2.1.34: |
||||
version "2.1.35" |
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" |
||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== |
||||
dependencies: |
||||
mime-db "1.52.0" |
||||
|
||||
mime@1.6.0: |
||||
version "1.6.0" |
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" |
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== |
||||
|
||||
ms@2.0.0: |
||||
version "2.0.0" |
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" |
||||
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== |
||||
|
||||
ms@2.1.3: |
||||
version "2.1.3" |
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" |
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== |
||||
|
||||
negotiator@0.6.3: |
||||
version "0.6.3" |
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" |
||||
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== |
||||
|
||||
object-inspect@^1.13.1: |
||||
version "1.13.1" |
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" |
||||
integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== |
||||
|
||||
on-finished@2.4.1: |
||||
version "2.4.1" |
||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" |
||||
integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== |
||||
dependencies: |
||||
ee-first "1.1.1" |
||||
|
||||
parseurl@~1.3.3: |
||||
version "1.3.3" |
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" |
||||
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== |
||||
|
||||
path-to-regexp@0.1.7: |
||||
version "0.1.7" |
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" |
||||
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== |
||||
|
||||
path@^0.12.7: |
||||
version "0.12.7" |
||||
resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f" |
||||
integrity sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q== |
||||
dependencies: |
||||
process "^0.11.1" |
||||
util "^0.10.3" |
||||
|
||||
process@^0.11.1: |
||||
version "0.11.10" |
||||
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" |
||||
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== |
||||
|
||||
proxy-addr@~2.0.7: |
||||
version "2.0.7" |
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" |
||||
integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== |
||||
dependencies: |
||||
forwarded "0.2.0" |
||||
ipaddr.js "1.9.1" |
||||
|
||||
qs@6.11.0: |
||||
version "6.11.0" |
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" |
||||
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== |
||||
dependencies: |
||||
side-channel "^1.0.4" |
||||
|
||||
range-parser@~1.2.1: |
||||
version "1.2.1" |
||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" |
||||
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== |
||||
|
||||
raw-body@2.5.2: |
||||
version "2.5.2" |
||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" |
||||
integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== |
||||
dependencies: |
||||
bytes "3.1.2" |
||||
http-errors "2.0.0" |
||||
iconv-lite "0.4.24" |
||||
unpipe "1.0.0" |
||||
|
||||
safe-buffer@5.2.1: |
||||
version "5.2.1" |
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" |
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== |
||||
|
||||
"safer-buffer@>= 2.1.2 < 3": |
||||
version "2.1.2" |
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" |
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== |
||||
|
||||
send@0.18.0: |
||||
version "0.18.0" |
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" |
||||
integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== |
||||
dependencies: |
||||
debug "2.6.9" |
||||
depd "2.0.0" |
||||
destroy "1.2.0" |
||||
encodeurl "~1.0.2" |
||||
escape-html "~1.0.3" |
||||
etag "~1.8.1" |
||||
fresh "0.5.2" |
||||
http-errors "2.0.0" |
||||
mime "1.6.0" |
||||
ms "2.1.3" |
||||
on-finished "2.4.1" |
||||
range-parser "~1.2.1" |
||||
statuses "2.0.1" |
||||
|
||||
serve-static@1.15.0: |
||||
version "1.15.0" |
||||
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" |
||||
integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== |
||||
dependencies: |
||||
encodeurl "~1.0.2" |
||||
escape-html "~1.0.3" |
||||
parseurl "~1.3.3" |
||||
send "0.18.0" |
||||
|
||||
set-function-length@^1.2.1: |
||||
version "1.2.2" |
||||
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" |
||||
integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== |
||||
dependencies: |
||||
define-data-property "^1.1.4" |
||||
es-errors "^1.3.0" |
||||
function-bind "^1.1.2" |
||||
get-intrinsic "^1.2.4" |
||||
gopd "^1.0.1" |
||||
has-property-descriptors "^1.0.2" |
||||
|
||||
setprototypeof@1.2.0: |
||||
version "1.2.0" |
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" |
||||
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== |
||||
|
||||
side-channel@^1.0.4: |
||||
version "1.0.6" |
||||
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" |
||||
integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== |
||||
dependencies: |
||||
call-bind "^1.0.7" |
||||
es-errors "^1.3.0" |
||||
get-intrinsic "^1.2.4" |
||||
object-inspect "^1.13.1" |
||||
|
||||
statuses@2.0.1: |
||||
version "2.0.1" |
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" |
||||
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== |
||||
|
||||
toidentifier@1.0.1: |
||||
version "1.0.1" |
||||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" |
||||
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== |
||||
|
||||
type-is@~1.6.18: |
||||
version "1.6.18" |
||||
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" |
||||
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== |
||||
dependencies: |
||||
media-typer "0.3.0" |
||||
mime-types "~2.1.24" |
||||
|
||||
unpipe@1.0.0, unpipe@~1.0.0: |
||||
version "1.0.0" |
||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" |
||||
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== |
||||
|
||||
util@^0.10.3: |
||||
version "0.10.4" |
||||
resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" |
||||
integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== |
||||
dependencies: |
||||
inherits "2.0.3" |
||||
|
||||
utils-merge@1.0.1: |
||||
version "1.0.1" |
||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" |
||||
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== |
||||
|
||||
vary@~1.1.2: |
||||
version "1.1.2" |
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" |
||||
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== |
||||
|
||||
zlib@^1.0.5: |
||||
version "1.0.5" |
||||
resolved "https://registry.yarnpkg.com/zlib/-/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0" |
||||
integrity sha512-40fpE2II+Cd3k8HWTWONfeKE2jL+P42iWJ1zzps5W51qcTsOUKM5Q5m2PFb0CLxlmFAaUuUdJGc3OfZy947v0w== |
@ -0,0 +1,312 @@ |
||||
import { ChildProcess, spawn } from "child_process" |
||||
import init from "../helpers/init" |
||||
import { Nightwatch, NightwatchBrowser } from "nightwatch" |
||||
|
||||
|
||||
module.exports = { |
||||
'@disabled': true, |
||||
before: function (browser, done) { |
||||
init(browser, done) |
||||
}, |
||||
after: function (browser: NightwatchBrowser) { |
||||
browser.perform((done) => { |
||||
done() |
||||
}) |
||||
}, |
||||
'Update settings for git #group1 #group2': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
clickLaunchIcon('dgit') |
||||
.waitForElementVisible('*[data-id="initgit-btn"]') |
||||
.click('*[data-id="initgit-btn"]') |
||||
.setValue('*[data-id="githubToken"]', process.env.dgit_token) |
||||
.setValue('*[data-id="gitubUsername"]', 'git') |
||||
.setValue('*[data-id="githubEmail"]', 'git@example.com') |
||||
.click('*[data-id="saveGitHubCredentials"]') |
||||
}, |
||||
'check if the settings are loaded #group1 #group2': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
click('*[data-id="github-panel"]') |
||||
.waitForElementVisible('*[data-id="connected-as-bunsenstraat"]') |
||||
.waitForElementVisible('*[data-id="connected-img-bunsenstraat"]') |
||||
.waitForElementVisible('*[data-id="connected-link-bunsenstraat"]') |
||||
}, |
||||
'clone a repository #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.click('*[data-id="clone-panel"]') |
||||
.click({ |
||||
selector: '//*[@data-id="clone-panel-content"]//*[@data-id="fetch-repositories"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="clone-panel-content"]//*[@id="repository-select"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.click({ |
||||
selector: '//*[@data-id="clone-panel-content"]//*[@id="repository-select"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="clone-panel-content"]//*[contains(text(), "awesome-remix")]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.click({ |
||||
selector: '//*[@data-id="clone-panel-content"]//*[contains(text(), "awesome-remix")]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="clone-panel-content"]//*[@id="branch-select"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.click({ |
||||
selector: '//*[@data-id="clone-panel-content"]//*[@id="branch-select"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.click({ |
||||
selector: '//*[@data-id="clone-panel-content"]//*[contains(text(), "master")]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="clone-panel-content"]//*[@data-id="clonebtn-ethereum/awesome-remix-master"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.click({ |
||||
selector: '//*[@data-id="clone-panel-content"]//*[@data-id="clonebtn-ethereum/awesome-remix-master"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
}, |
||||
'check if there is a README.md file #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('filePanel') |
||||
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME.md"]') |
||||
}, |
||||
'check the commands panel #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('dgit') |
||||
.click('*[data-id="commands-panel"]') |
||||
.waitForElementVisible({ |
||||
selector: "//div[@id='commands-remote-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'master')]", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'origin')]", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: "//div[@id='commands-local-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'master')]", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
}, |
||||
'check the remotes #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
|
||||
.click('*[data-id="remotes-panel"]') |
||||
.waitForElementVisible('*[data-id="remotes-panel-content"]') |
||||
.click({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-origin"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-current-branch-master"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.click({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-sync-origin"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
|
||||
}, |
||||
'check the commits of branch links #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.click({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="commit-summary-linking fixed-"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
}, |
||||
'switch to branch links #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-branch-links"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.click({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-toggle-branch-links"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="branches-toggle-current-branch-links"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
}, |
||||
'check the local branches #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.click('*[data-id="branches-panel"]') |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="branches-panel-content"]//*[@data-id="branches-toggle-current-branch-links"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
}, |
||||
'check the local commits #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.click('*[data-id="commits-panel"]') |
||||
.pause(1000) |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="commits-current-branch-links"]//*[@data-id="commit-summary-linking fixed-"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.click({ |
||||
selector: '//*[@data-id="commits-current-branch-links"]//*[@data-id="commit-summary-linking fixed-"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="commits-current-branch-links"]//*[@data-id="commit-change-modified-README.md"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
}, |
||||
'check the commands panel for links #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.click('*[data-id="commands-panel"]') |
||||
.waitForElementVisible({ |
||||
selector: "//div[@id='commands-remote-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'links')]", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'origin')]", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: "//div[@id='commands-local-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'links')]", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
}, |
||||
|
||||
'add a remote #group2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.pause(1000) |
||||
.click('*[data-id="remotes-panel"]') |
||||
.click({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="fetch-repositories"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@id="repository-select"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.click({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@id="repository-select"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[contains(text(), "awesome-remix")]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.click({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[contains(text(), "awesome-remix")]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-panel-remotename"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.setValue({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-panel-remotename"]', |
||||
locateStrategy: 'xpath' |
||||
}, 'newremote') |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-panel-addremote"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.click({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-panel-addremote"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-newremote"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
}, |
||||
'check the commands panel for newremote #group2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.pause(1000) |
||||
.click('*[data-id="commands-panel"]') |
||||
.waitForElementVisible({ |
||||
selector: "//div[@id='commands-remote-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'main')]", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'newremote')]", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: "//div[@id='commands-local-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'main')]", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.getAttribute({ |
||||
selector: '//*[@data-id="sourcecontrol-pull"]', |
||||
locateStrategy: 'xpath' |
||||
}, 'disabled', (result) => { |
||||
if (result.value) { |
||||
browser.assert.fail('Button is disabled') |
||||
} else { |
||||
browser.assert.ok(true) |
||||
} |
||||
}) |
||||
}, |
||||
'remove the remove #group2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.pause(1000) |
||||
.click('*[data-id="remotes-panel"]') |
||||
.waitForElementVisible({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-rm-newremote"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.pause(2000) |
||||
.click({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-rm-newremote"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.pause(1000) |
||||
.waitForElementNotPresent({ |
||||
selector: '//*[@data-id="remotes-panel-content"]//*[@data-id="remote-detail-newremote"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
}, |
||||
'check the commands panel for removed remote #group2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.pause(1000) |
||||
.click('*[data-id="commands-panel"]') |
||||
.waitForElementVisible({ |
||||
selector: "//div[@id='commands-remote-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'main')]", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementNotPresent({ |
||||
selector: "//div[@id='commands-remote-origin-select']//div[contains(@class, 'singleValue') and contains(text(), 'newremote')]", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: "//div[@id='commands-local-branch-select']//div[contains(@class, 'singleValue') and contains(text(), 'main')]", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.getAttribute({ |
||||
selector: '//*[@data-id="sourcecontrol-pull"]', |
||||
locateStrategy: 'xpath' |
||||
}, 'disabled', (result) => { |
||||
if (result.value) { |
||||
browser.assert.ok(true) |
||||
} else { |
||||
browser.assert.fail('Button is not disabled') |
||||
} |
||||
}) |
||||
}, |
||||
} |
@ -0,0 +1,521 @@ |
||||
import { ChildProcess, spawn } from "child_process" |
||||
import kill from 'tree-kill' |
||||
import init from "../helpers/init" |
||||
import { Nightwatch, NightwatchBrowser } from "nightwatch" |
||||
let gitserver: ChildProcess |
||||
|
||||
/* |
||||
/ uses the git-http-backend package to create a git server ( if needed kill the server: kill -9 $(sudo lsof -t -i:6868) ) |
||||
/ GROUP 1: file operations PUSH PULL COMMIT SYNC FETCH CLONE ADD |
||||
/ GROUP 2: branch operations CREATE & PUBLISH |
||||
/ GROUP 3: file operations rename delete |
||||
*/ |
||||
|
||||
module.exports = { |
||||
'@disabled': true, |
||||
before: function (browser, done) { |
||||
init(browser, done) |
||||
}, |
||||
after: function (browser: NightwatchBrowser) { |
||||
browser.perform((done) => { |
||||
console.log('kill server', gitserver.pid) |
||||
kill(gitserver.pid) |
||||
done() |
||||
}) |
||||
}, |
||||
|
||||
'run server #group1 #group2 #group3': function (browser: NightwatchBrowser) { |
||||
browser.perform(async (done) => { |
||||
gitserver = await spawnGitServer('/tmp/') |
||||
console.log('working directory', process.cwd()) |
||||
done() |
||||
}) |
||||
}, |
||||
'Update settings for git #group1 #group2 #group3': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
clickLaunchIcon('dgit') |
||||
.waitForElementVisible('*[data-id="initgit-btn"]') |
||||
.click('*[data-id="initgit-btn"]') |
||||
.setValue('*[data-id="gitubUsername"]', 'git') |
||||
.setValue('*[data-id="githubEmail"]', 'git@example.com') |
||||
.click('*[data-id="saveGitHubCredentials"]') |
||||
.modalFooterOKClick('github-credentials-error') |
||||
.pause(2000) |
||||
}, |
||||
'clone a repo #group1 #group2 #group3': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementVisible('*[data-id="clone-panel"]') |
||||
.click('*[data-id="clone-panel"]') |
||||
.waitForElementVisible('*[data-id="clone-url"]') |
||||
.setValue('*[data-id="clone-url"]', 'http://localhost:6868/bare.git') |
||||
.waitForElementVisible('*[data-id="clone-btn"]') |
||||
.click('*[data-id="clone-btn"]') |
||||
.clickLaunchIcon('filePanel') |
||||
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME.md"]') |
||||
}, |
||||
|
||||
// GROUP 1
|
||||
|
||||
'check file added #group1 #group3': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
addFile('test.txt', { content: 'hello world' }, 'README.md') |
||||
.clickLaunchIcon('dgit') |
||||
.click('*[data-id="sourcecontrol-panel"]') |
||||
.waitForElementVisible({ |
||||
selector: "//*[@data-status='new-untracked' and @data-file='/test.txt']", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible('*[data-id="addToGitChangestest.txt"]') |
||||
.pause(1000) |
||||
.click('*[data-id="addToGitChangestest.txt"]') |
||||
.waitForElementVisible({ |
||||
selector: "//*[@data-status='added-staged' and @data-file='/test.txt']", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.setValue('*[data-id="commitMessage"]', 'testcommit') |
||||
.click('*[data-id="commitButton"]') |
||||
}, |
||||
'look at the commit #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.click('*[data-id="commits-panel"]') |
||||
.waitForElementPresent({ |
||||
selector: '//*[@data-id="commit-summary-testcommit-ahead"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
}, |
||||
'sync the commit #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.pause(1000) |
||||
.waitForElementVisible('*[data-id="sourcecontrol-panel"]') |
||||
.click('*[data-id="sourcecontrol-panel"]') |
||||
.waitForElementVisible('*[data-id="syncButton"]') |
||||
.click('*[data-id="syncButton"]') |
||||
.pause(2000) |
||||
.waitForElementVisible('*[data-id="commitButton"]') |
||||
.click('*[data-id="commits-panel"]') |
||||
.waitForElementPresent({ |
||||
selector: '//*[@data-id="commit-summary-testcommit-"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
}, |
||||
'check the log #group1': async function (browser: NightwatchBrowser) { |
||||
const logs = await getGitLog('/tmp/git/bare.git') |
||||
console.log(logs) |
||||
browser.assert.ok(logs.includes('testcommit')) |
||||
}, |
||||
'change a file #group1': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
openFile('test.txt'). |
||||
pause(1000). |
||||
setEditorValue('changes', null) |
||||
}, |
||||
'stage changed file #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('dgit') |
||||
.click('*[data-id="sourcecontrol-panel"]') |
||||
.waitForElementVisible({ |
||||
selector: "//*[@data-status='modified-unstaged' and @data-file='/test.txt']", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible('*[data-id="addToGitChangestest.txt"]') |
||||
.click('*[data-id="addToGitChangestest.txt"]') |
||||
.waitForElementVisible({ |
||||
selector: "//*[@data-status='modified-staged' and @data-file='/test.txt']", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.setValue('*[data-id="commitMessage"]', 'testcommit2') |
||||
.click('*[data-id="commitButton"]') |
||||
}, |
||||
'push the commit #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.click('*[data-id="commands-panel"]') |
||||
.waitForElementVisible('*[data-id="sourcecontrol-push"]') |
||||
.click('*[data-id="sourcecontrol-push"]') |
||||
.pause(2000) |
||||
.click('*[data-id="commits-panel"]') |
||||
.waitForElementPresent({ |
||||
selector: '//*[@data-id="commit-summary-testcommit2-"]', |
||||
locateStrategy: 'xpath' |
||||
}).pause(2000) |
||||
}, |
||||
'check the log for testcommit2 #group1': async function (browser: NightwatchBrowser) { |
||||
const logs = await getGitLog('/tmp/git/bare.git') |
||||
console.log(logs) |
||||
browser.assert.ok(logs.includes('testcommit2')) |
||||
}, |
||||
'clone locally and add a file and push #group1': async function (browser: NightwatchBrowser) { |
||||
await cloneOnServer('http://localhost:6868/bare.git', '/tmp/') |
||||
await onLocalGitRepoAddFile('/tmp/bare/', 'test2.txt') |
||||
await createCommitOnLocalServer('/tmp/bare/', 'testlocal') |
||||
await onLocalGitRepoPush('/tmp/bare/', 'master') |
||||
}, |
||||
'run a git fetch #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.pause(2000) |
||||
.click('*[data-id="commands-panel"]') |
||||
.waitForElementVisible('*[data-id="sourcecontrol-fetch-branch"]') |
||||
.click('*[data-id="sourcecontrol-fetch-branch"]') |
||||
.pause(2000) |
||||
.click('*[data-id="commits-panel"]') |
||||
.click('*[data-id="commits-panel-behind"]') |
||||
.waitForElementPresent({ |
||||
selector: '//*[@data-id="commit-summary-testlocal-"]', |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
}, |
||||
'run pull from the header #group1': function (browser: NightwatchBrowser) { |
||||
browser. |
||||
click('*[data-id="sourcecontrol-button-pull"]') |
||||
.waitForElementNotPresent('*[data-id="commits-panel-behind"]') |
||||
}, |
||||
'check if the file is added #group1': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('filePanel') |
||||
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest2.txt"]') |
||||
}, |
||||
|
||||
// group 3
|
||||
'rename a file #group3': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('filePanel') |
||||
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.txt"]') |
||||
.click('*[data-id="treeViewLitreeViewItemtest.txt"]') |
||||
.renamePath('test.txt', 'test_rename', 'test_rename.txt') |
||||
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest_rename.txt"]') |
||||
.pause(1000) |
||||
}, |
||||
'stage renamed file #group3': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('dgit') |
||||
.waitForElementVisible({ |
||||
selector: "//*[@data-status='deleted-unstaged' and @data-file='/test.txt']", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible('*[data-id="addToGitChangestest.txt"]') |
||||
.waitForElementVisible({ |
||||
selector: "//*[@data-status='new-untracked' and @data-file='/test_rename.txt']", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.click('*[data-id="sourcecontrol-add-all"]') |
||||
.pause(2000) |
||||
.waitForElementVisible({ |
||||
selector: "//*[@data-status='deleted-staged' and @data-file='/test.txt']", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: "//*[@data-status='added-staged' and @data-file='/test_rename.txt']", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
}, |
||||
'undo the rename #group3': function (browser: NightwatchBrowser) { |
||||
browser |
||||
|
||||
.click('*[data-id="unDoStagedtest.txt"]') |
||||
.pause(1000) |
||||
.waitForElementNotPresent({ |
||||
selector: "//*[@data-file='/test.txt']", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
}, |
||||
'check if file is returned #group3': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('filePanel') |
||||
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest.txt"]') |
||||
}, |
||||
|
||||
|
||||
// GROUP 2
|
||||
'create a branch #group2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('dgit') |
||||
.click('*[data-id="branches-panel"]') |
||||
.waitForElementVisible('*[data-id="newbranchname"]') |
||||
.setValue('*[data-id="newbranchname"]', 'testbranch') |
||||
.click('*[data-id="sourcecontrol-create-branch"]') |
||||
.waitForElementVisible('*[data-id="branches-current-branch-testbranch"]') |
||||
.pause(1000) |
||||
}, |
||||
'check if the branch is in the filePanel #group2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('filePanel') |
||||
.click('[data-id="workspaceGitBranchesDropdown"]') |
||||
.expect.element('[data-id="workspaceGit-testbranch"]').text.to.contain('✓ ') |
||||
}, |
||||
'publish the branch #group2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('dgit') |
||||
.waitForElementVisible('*[data-id="sourcecontrol-panel"]') |
||||
.click('*[data-id="sourcecontrol-panel"]') |
||||
.pause(1000) |
||||
.click('*[data-id="publishBranchButton"]') |
||||
.pause(2000) |
||||
.waitForElementNotVisible('*[data-id="publishBranchButton"]') |
||||
}, |
||||
'check if the branch is published #group2': async function (browser: NightwatchBrowser) { |
||||
const branches = await getBranches('/tmp/git/bare.git') |
||||
browser.assert.ok(branches.includes('testbranch')) |
||||
}, |
||||
'add file to new branch #group2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.pause(1000) |
||||
.addFile('test.txt', { content: 'hello world' }, 'README.md') |
||||
.clickLaunchIcon('dgit') |
||||
.pause(2000) |
||||
.waitForElementVisible({ |
||||
selector: "//*[@data-status='new-untracked' and @data-file='/test.txt']", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.waitForElementVisible('*[data-id="addToGitChangestest.txt"]') |
||||
.pause(1000) |
||||
.click('*[data-id="addToGitChangestest.txt"]') |
||||
.waitForElementVisible({ |
||||
selector: "//*[@data-status='added-staged' and @data-file='/test.txt']", |
||||
locateStrategy: 'xpath' |
||||
}) |
||||
.setValue('*[data-id="commitMessage"]', 'testcommit') |
||||
.click('*[data-id="commitButton"]') |
||||
.pause(1000) |
||||
}, |
||||
'check if the commit is ahead in the branches list #group2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementVisible('*[data-id="branches-panel"]') |
||||
.click('*[data-id="branches-panel"]') |
||||
.waitForElementVisible('*[data-id="branches-current-branch-testbranch"]') |
||||
.click({ |
||||
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branches-current-branch-testbranch']", |
||||
locateStrategy: 'xpath', |
||||
suppressNotFoundErrors: true |
||||
}) |
||||
.click({ |
||||
selector: "//*[@data-id='branches-panel-content']//*[@data-id='commits-panel-ahead']", |
||||
locateStrategy: 'xpath', |
||||
suppressNotFoundErrors: true |
||||
}) |
||||
.click({ |
||||
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branchdifference-commits-testbranch-ahead']//*[@data-id='commit-summary-testcommit-ahead']", |
||||
locateStrategy: 'xpath', |
||||
}) |
||||
.click({ |
||||
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branchdifference-commits-testbranch-ahead']//*[@data-id='commit-change-added-test.txt']", |
||||
locateStrategy: 'xpath', |
||||
}) |
||||
.click({ |
||||
selector: "//*[@data-id='branches-panel-content']//*[@data-id='local-branch-commits-testbranch']//*[@data-id='commit-summary-testcommit-ahead']", |
||||
locateStrategy: 'xpath', |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: "//*[@data-id='branches-panel-content']//*[@data-id='local-branch-commits-testbranch']//*[@data-id='commit-change-added-test.txt']", |
||||
locateStrategy: 'xpath', |
||||
}) |
||||
}, |
||||
'switch back to master #group2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.click({ |
||||
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branches-toggle-branch-master']", |
||||
locateStrategy: 'xpath', |
||||
}) |
||||
.waitForElementVisible({ |
||||
selector: "//*[@data-id='branches-panel-content']//*[@data-id='branches-toggle-current-branch-master']", |
||||
locateStrategy: 'xpath', |
||||
}) |
||||
}, |
||||
'check if test file is gone #group2': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.clickLaunchIcon('filePanel') |
||||
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.txt"]') |
||||
} |
||||
} |
||||
|
||||
async function getBranches(path: string): Promise<string> { |
||||
return new Promise((resolve, reject) => { |
||||
const git = spawn('git', ['branch'], { cwd: path }) |
||||
let branches = '' |
||||
git.stdout.on('data', function (data) { |
||||
console.log('stdout git branches', data.toString()) |
||||
branches += data.toString() |
||||
}) |
||||
git.stderr.on('data', function (data) { |
||||
console.log('stderr git branches', data.toString()) |
||||
reject(data.toString()) |
||||
}) |
||||
git.on('close', function () { |
||||
resolve(branches) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
async function getGitLog(path: string): Promise<string> { |
||||
return new Promise((resolve, reject) => { |
||||
const git = spawn('git', ['log'], { cwd: path }) |
||||
let logs = '' |
||||
git.stdout.on('data', function (data) { |
||||
logs += data.toString() |
||||
}) |
||||
git.stderr.on('err', function (data) { |
||||
reject(data.toString()) |
||||
}) |
||||
git.on('close', function () { |
||||
resolve(logs) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
async function cloneOnServer(repo: string, path: string) { |
||||
console.log('cloning', repo, path) |
||||
return new Promise((resolve, reject) => { |
||||
const git = spawn('rm -rf bare && git', ['clone', repo], { cwd: path, shell: true, detached: true }); |
||||
|
||||
git.stdout.on('data', function (data) { |
||||
console.log('stdout data cloning', data.toString()); |
||||
if (data.toString().includes('done')) { |
||||
resolve(git); |
||||
} |
||||
}); |
||||
|
||||
git.stderr.on('data', function (data) { |
||||
console.log('stderr data cloning', data.toString()); |
||||
if (data.toString().includes('into')) { |
||||
setTimeout(() => { |
||||
resolve(git); |
||||
}, 5000) |
||||
} |
||||
}); |
||||
|
||||
git.on('error', (error) => { |
||||
reject(`Process error: ${error.message}`); |
||||
}); |
||||
|
||||
git.on('exit', (code, signal) => { |
||||
if (code !== 0) { |
||||
reject(`Process exited with code: ${code} and signal: ${signal}`); |
||||
} |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
async function onLocalGitRepoAddFile(path: string, file: string) { |
||||
console.log('adding file', file) |
||||
return new Promise((resolve, reject) => { |
||||
const git = spawn('touch', [file], { cwd: path }); |
||||
|
||||
git.stdout.on('data', function (data) { |
||||
console.log('stdout data adding file', data.toString()); |
||||
if (data.toString().includes('done')) { |
||||
resolve(git); |
||||
} |
||||
}); |
||||
|
||||
git.stderr.on('data', function (data) { |
||||
console.error('stderr adding file', data.toString()); |
||||
reject(data.toString()); |
||||
}); |
||||
|
||||
git.on('error', (error) => { |
||||
reject(`Process error: ${error.message}`); |
||||
}); |
||||
|
||||
git.on('exit', (code, signal) => { |
||||
if (code !== 0) { |
||||
reject(`Process exited with code: ${code} and signal: ${signal}`); |
||||
} else { |
||||
resolve(git); |
||||
} |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
async function onLocalGitRepoPush(path: string, branch: string = 'master') { |
||||
console.log('pushing', path) |
||||
return new Promise((resolve, reject) => { |
||||
const git = spawn('git', ['push', 'origin', branch], { cwd: path, shell: true, detached: true }); |
||||
|
||||
git.stdout.on('data', function (data) { |
||||
console.log('stdout data pushing', data.toString()); |
||||
if (data.toString().includes('done')) { |
||||
resolve(git); |
||||
} |
||||
}); |
||||
|
||||
git.stderr.on('data', function (data) { |
||||
console.error('stderr data pushing', data.toString()); |
||||
if (data.toString().includes(branch)) { |
||||
resolve(git); |
||||
} |
||||
}); |
||||
|
||||
git.on('error', (error) => { |
||||
reject(`Process error: ${error.message}`); |
||||
}); |
||||
|
||||
git.on('exit', (code, signal) => { |
||||
if (code !== 0) { |
||||
reject(`Process exited with code: ${code} and signal: ${signal}`); |
||||
} else { |
||||
resolve(git); |
||||
} |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
|
||||
async function createCommitOnLocalServer(path: string, message: string) { |
||||
console.log('committing', message, path) |
||||
return new Promise((resolve, reject) => { |
||||
const git = spawn('git add . && git', ['commit', '-m', message], { cwd: path, shell: true, detached: true }); |
||||
|
||||
git.stdout.on('data', function (data) { |
||||
console.log('data stdout committing', data.toString()); |
||||
if (data.toString().includes(message)) { |
||||
setTimeout(() => { |
||||
resolve(git); |
||||
}, 1000) |
||||
} |
||||
}); |
||||
|
||||
git.stderr.on('data', function (data) { |
||||
console.error('data commiting', data.toString()); |
||||
reject(data.toString()); |
||||
}); |
||||
|
||||
git.on('error', (error) => { |
||||
console.error('error', error); |
||||
reject(`Process error: ${error.message}`); |
||||
}); |
||||
|
||||
git.on('exit', (code, signal) => { |
||||
if (code !== 0) { |
||||
console.error('exit', code, signal); |
||||
reject(`Process exited with code: ${code} and signal: ${signal}`); |
||||
} else { |
||||
resolve(git); |
||||
} |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
|
||||
async function spawnGitServer(path: string): Promise<ChildProcess> { |
||||
console.log(process.cwd()) |
||||
try { |
||||
const server = spawn('yarn && sh setup.sh && npx ts-node server.ts', [`${path}`], { cwd: process.cwd() + '/apps/remix-ide-e2e/src/githttpbackend/', shell: true, detached: true }) |
||||
console.log('spawned', server.stdout.closed, server.stderr.closed) |
||||
return new Promise((resolve, reject) => { |
||||
server.stdout.on('data', function (data) { |
||||
console.log(data.toString()) |
||||
if ( |
||||
data.toString().includes('is listening') |
||||
|| data.toString().includes('address already in use') |
||||
) { |
||||
console.log('resolving') |
||||
resolve(server) |
||||
} |
||||
}) |
||||
server.stderr.on('err', function (data) { |
||||
console.log(data.toString()) |
||||
reject(data.toString()) |
||||
}) |
||||
}) |
||||
} catch (e) { |
||||
console.log(e) |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,40 @@ |
||||
'use strict' |
||||
import { ViewPlugin } from '@remixproject/engine-web'; |
||||
import React from 'react' // eslint-disable-line
|
||||
import { gitState, GitUI } from '@remix-ui/git'; |
||||
import * as packageJson from '../../../../../package.json' |
||||
|
||||
const profile = { |
||||
name: 'dgit', |
||||
desciption: 'Git plugin for Remix', |
||||
methods: ['pull', 'track', 'diff', 'clone', 'open'], |
||||
events: [''], |
||||
version: packageJson.version, |
||||
maintainedBy: 'Remix', |
||||
permission: true, |
||||
description: 'Use this plugin to interact with your git repositories', |
||||
location: 'sidePanel', |
||||
icon: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIxLjAwNyA4LjIyMTY4QzIxLjAxMDUgNy41Mjc5MiAyMC44MjA3IDYuODQ2ODkgMjAuNDU5MSA2LjI1NDg1QzIwLjA5NzQgNS42NjI4MSAxOS41NzggNS4xODMxNSAxOC45NTkyIDQuODY5NTdDMTguMzQwMyA0LjU1NiAxNy42NDYzIDQuNDIwOTEgMTYuOTU1MSA0LjQ3OTQxQzE2LjI2MzcgNC41Mzc5MyAxNS42MDI1IDQuNzg3NzMgMTUuMDQ1IDUuMjAwODVDMTQuNDg3NyA1LjYxMzk3IDE0LjA1NjMgNi4xNzQwOSAxMy43OTkzIDYuODE4NUMxMy41NDI0IDcuNDYyOSAxMy40Njk3IDguMTY2MTMgMTMuNTg5OCA4Ljg0OTQ0QzEzLjcwOTkgOS41MzI3NCAxNC4wMTc3IDEwLjE2OTIgMTQuNDc4OSAxMC42ODc0QzE0Ljk0MDIgMTEuMjA1NiAxNS41MzY3IDExLjU4NTIgMTYuMjAxNSAxMS43ODM2QzE1Ljk1NiAxMi4yODI0IDE1LjU3NjMgMTIuNzAzIDE1LjEwNDkgMTIuOTk3OUMxNC42MzM2IDEzLjI5MjkgMTQuMDg5NCAxMy40NTA1IDEzLjUzMzQgMTMuNDUzMkgxMC41NDRDOS40MzcyNiAxMy40NTcxIDguMzcxNjMgMTMuODcyNyA3LjU1NDUxIDE0LjYxOTFWNy4zOTgwOUM4LjQ2MTg0IDcuMjEyODggOS4yNjgwOCA2LjY5NzM3IDkuODE2OTIgNS45NTE1MUMxMC4zNjU4IDUuMjA1NjUgMTAuNjE4MSA0LjI4MjU2IDEwLjUyNSAzLjM2MTIxQzEwLjQzMTkgMi40Mzk4NyAxMC4wMDAxIDEuNTg1OSA5LjMxMzE2IDAuOTY0ODczQzguNjI2MjQgMC4zNDM4NDUgNy43MzMxOSAwIDYuODA3MTYgMEM1Ljg4MTEyIDAgNC45ODgwNyAwLjM0Mzg0NSA0LjMwMTE0IDAuOTY0ODczQzMuNjE0MjIgMS41ODU5IDMuMTgyMzYgMi40Mzk4NyAzLjA4OTI4IDMuMzYxMjFDMi45OTYyIDQuMjgyNTYgMy4yNDg1NSA1LjIwNTY1IDMuNzk3MzkgNS45NTE1MUM0LjM0NjIzIDYuNjk3MzcgNS4xNTI0NyA3LjIxMjg4IDYuMDU5OCA3LjM5ODA5VjE2LjUxNTlDNS4xNTQxOCAxNi42ODkxIDQuMzQzMjMgMTcuMTg3NyAzLjc3OTkzIDE3LjkxNzZDMy4yMTY2MyAxOC42NDc2IDIuOTM5OTIgMTkuNTU4NSAzLjAwMTk3IDIwLjQ3ODVDMy4wNjQwMyAyMS4zOTg0IDMuNDYwNTcgMjIuMjYzOSA0LjExNjggMjIuOTExNUM0Ljc3MzAzIDIzLjU1OTIgNS42NDM2IDIzLjk0NDQgNi41NjQyNyAyMy45OTQ0QzcuNDg0OTYgMjQuMDQ0NSA4LjM5MjExIDIzLjc1NTggOS4xMTQ2NCAyMy4xODNDOS44MzcxOCAyMi42MTAyIDEwLjMyNTEgMjEuNzkyOCAxMC40ODY1IDIwLjg4NUMxMC42NDc4IDE5Ljk3NzEgMTAuNDcxNCAxOS4wNDE3IDkuOTkwNDggMTguMjU1QzkuNTA5NTcgMTcuNDY4MyA4Ljc1NzQxIDE2Ljg4NDggNy44NzU4OCAxNi42MTQ1QzguMTIxNzYgMTYuMTE2MiA4LjUwMTY3IDE1LjY5NjMgOC45NzI5NiAxNS40MDE5QzkuNDQ0MjYgMTUuMTA3NCA5Ljk4ODI3IDE0Ljk1MDMgMTAuNTQ0IDE0Ljk0NzlIMTMuNTMzNEMxNC40NjYxIDE0Ljk0MzYgMTUuMzc0MiAxNC42NDg2IDE2LjEzMTMgMTQuMTAzOUMxNi44ODg0IDEzLjU1OTIgMTcuNDU2OCAxMi43OTIgMTcuNzU3NSAxMS45MDkxQzE4LjY1MzQgMTEuNzkxNCAxOS40NzYzIDExLjM1MjggMjAuMDczOCAxMC42NzQ4QzIwLjY3MTMgOS45OTY4IDIxLjAwMjggOS4xMjUzMyAyMS4wMDcgOC4yMjE2OFpNNC41NjUwOCAzLjczNzUyQzQuNTY1MDggMy4yOTQwOCA0LjY5NjU3IDIuODYwNiA0Ljk0MjkzIDIuNDkxOUM1LjE4OTMgMi4xMjMxOSA1LjUzOTQ3IDEuODM1ODEgNS45NDkxNSAxLjY2NjExQzYuMzU4ODQgMS40OTY0MiA2LjgwOTY1IDEuNDUyMDIgNy4yNDQ1NiAxLjUzODU0QzcuNjc5NDggMS42MjUwNCA4LjA3ODk4IDEuODM4NTcgOC4zOTI1NCAyLjE1MjE0QzguNzA2MTEgMi40NjU3IDguOTE5NjQgMi44NjUyIDkuMDA2MTUgMy4zMDAxMkM5LjA5MjY2IDMuNzM1MDQgOS4wNDgyNyA0LjE4NTg1IDguODc4NTcgNC41OTU1M0M4LjcwODg3IDUuMDA1MjEgOC40MjE0OSA1LjM1NTM5IDguMDUyNzggNS42MDE3NUM3LjY4NDA4IDUuODQ4MTEgNy4yNTA2IDUuOTc5NiA2LjgwNzE2IDUuOTc5NkM2LjIxMjUyIDUuOTc5NiA1LjY0MjI0IDUuNzQzMzkgNS4yMjE3NyA1LjMyMjkxQzQuODAxMjkgNC45MDI0NSA0LjU2NTA4IDQuMzMyMTYgNC41NjUwOCAzLjczNzUyWk05LjA0OTIzIDIwLjE3OTRDOS4wNDkyMyAyMC42MjI5IDguOTE3NzQgMjEuMDU2MyA4LjY3MTM4IDIxLjQyNUM4LjQyNTAxIDIxLjc5MzcgOC4wNzQ4NSAyMi4wODExIDcuNjY1MTYgMjIuMjUwOEM3LjI1NTQ3IDIyLjQyMDUgNi44MDQ2NiAyMi40NjQ5IDYuMzY5NzUgMjIuMzc4NEM1LjkzNDgzIDIyLjI5MiA1LjUzNTMzIDIyLjA3ODQgNS4yMjE3NyAyMS43NjQ4QzQuOTA4MjEgMjEuNDUxMiA0LjY5NDY3IDIxLjA1MTcgNC42MDgxNiAyMC42MTY5QzQuNTIxNjUgMjAuMTgxOSA0LjU2NjA1IDE5LjczMTEgNC43MzU3NSAxOS4zMjE0QzQuOTA1NDUgMTguOTExNyA1LjE5MjgyIDE4LjU2MTUgNS41NjE1MyAxOC4zMTUyQzUuOTMwMjMgMTguMDY4OSA2LjM2MzcxIDE3LjkzNzMgNi44MDcxNiAxNy45MzczQzcuNDAxNzkgMTcuOTM3MyA3Ljk3MjA3IDE4LjE3MzYgOC4zOTI1NCAxOC41OTRDOC44MTMwMiAxOS4wMTQ1IDkuMDQ5MjMgMTkuNTg0OCA5LjA0OTIzIDIwLjE3OTRaTTE3LjI3MDIgMTAuNDYzOEMxNi44MjY3IDEwLjQ2MzggMTYuMzkzMyAxMC4zMzIyIDE2LjAyNDYgMTAuMDg1OUMxNS42NTU5IDkuODM5NTQgMTUuMzY4NSA5LjQ4OTM3IDE1LjE5ODggOS4wNzk2OUMxNS4wMjkxIDguNjcgMTQuOTg0NyA4LjIxOTIgMTUuMDcxMiA3Ljc4NDI3QzE1LjE1NzYgNy4zNDkzNSAxNS4zNzEyIDYuOTQ5ODUgMTUuNjg0OCA2LjYzNjI5QzE1Ljk5ODQgNi4zMjI3MyAxNi4zOTc5IDYuMTA5MTkgMTYuODMyNyA2LjAyMjY4QzE3LjI2NzcgNS45MzYxNyAxNy43MTg1IDUuOTgwNTggMTguMTI4MSA2LjE1MDI3QzE4LjUzNzkgNi4zMTk5NyAxOC44ODgxIDYuNjA3MzQgMTkuMTM0NCA2Ljk3NjA1QzE5LjM4MDcgNy4zNDQ3NiAxOS41MTIzIDcuNzc4MjMgMTkuNTEyMyA4LjIyMTY4QzE5LjUxMjMgOC44MTYzMiAxOS4yNzYgOS4zODY2IDE4Ljg1NTYgOS44MDcwNkMxOC40MzUxIDEwLjIyNzUgMTcuODY0OCAxMC40NjM4IDE3LjI3MDIgMTAuNDYzOFoiIGZpbGw9IiM0MjQyNDIiLz4KPC9zdmc+Cg==" |
||||
} |
||||
|
||||
export class GitPlugin extends ViewPlugin { |
||||
|
||||
constructor() { |
||||
|
||||
super(profile) |
||||
} |
||||
|
||||
onDeactivation(): void { |
||||
this.call('fileDecorator', 'clearFileDecorators') |
||||
this.call('manager', 'activatePlugin', 'dgitApi') |
||||
} |
||||
|
||||
async open(panel:string) { |
||||
this.emit('openPanel', panel) |
||||
} |
||||
|
||||
render() { |
||||
return <div id='gitTab'><GitUI plugin={this} /></div> |
||||
} |
||||
|
||||
} |
@ -0,0 +1,20 @@ |
||||
{ |
||||
"git.push": "push", |
||||
"git.pull": "pull", |
||||
"git.commit": "commit", |
||||
"git.sync": "sync", |
||||
"git.syncchanges": "sync changes", |
||||
"git.publish": "publish", |
||||
"git.ignore": "ignore", |
||||
"git.createBranch": "create branch", |
||||
"git.deleteBranch": "delete branch", |
||||
"git.mergeBranch": "merge branch", |
||||
"git.rebaseBranch": "rebase branch", |
||||
"git.checkout": "checkout", |
||||
"git.fetch": "fetch", |
||||
"git.refresh": "refresh", |
||||
"git.unstageall": "unstage all", |
||||
"git.stageall": "stage all", |
||||
"git.noremote": "this repo has no remotes", |
||||
"git.init": "Initialize repository" |
||||
} |
@ -0,0 +1,9 @@ |
||||
export type branch = { |
||||
name: string |
||||
remote: remote |
||||
} |
||||
|
||||
export type remote = { |
||||
name: string |
||||
url: string |
||||
} |
@ -0,0 +1 @@ |
||||
export * from './lib/remix-api' |
@ -0,0 +1,11 @@ |
||||
import { StatusEvents } from "@remixproject/plugin-utils" |
||||
|
||||
export interface IConfigApi { |
||||
events: { |
||||
configChanged: () => void |
||||
} & StatusEvents, |
||||
methods: { |
||||
getAppParameter(key: string): Promise<any>, |
||||
setAppParameter(key: string, value: any): Promise<void> |
||||
} |
||||
} |
@ -0,0 +1,10 @@ |
||||
import { commitChange } from "@remix-ui/git"; |
||||
import { IFileSystem } from "@remixproject/plugin-api" |
||||
|
||||
// Extended interface with 'diff' method
|
||||
export interface IExtendedFileSystem extends IFileSystem { |
||||
methods: IFileSystem['methods'] & { |
||||
/** Compare the differences between two files */ |
||||
diff(change: commitChange): Promise<void> |
||||
}; |
||||
} |
@ -0,0 +1,11 @@ |
||||
import { fileDecoration } from '@remix-ui/file-decorators' |
||||
import { StatusEvents } from '@remixproject/plugin-utils' |
||||
|
||||
export interface IFileDecoratorApi { |
||||
events: { |
||||
} & StatusEvents |
||||
methods: { |
||||
clearFileDecorators(path?: string): void |
||||
setFileDecorators(decorators: fileDecoration[]): void |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
import { ModalTypes } from "@remix-ui/app" |
||||
import { StatusEvents } from "@remixproject/plugin-utils" |
||||
|
||||
export interface INotificationApi { |
||||
events: { |
||||
|
||||
} & StatusEvents, |
||||
methods: { |
||||
toast(key: string): Promise<void>, |
||||
alert({ |
||||
title, |
||||
message, |
||||
id |
||||
}:{ |
||||
title: string, |
||||
message: string, |
||||
id: string |
||||
}): Promise<void>, |
||||
modal({ |
||||
title, |
||||
message, |
||||
okLabel, |
||||
type |
||||
}:{ |
||||
title: string, |
||||
message: string, |
||||
okLabel: string, |
||||
type: ModalTypes |
||||
}): Promise<void>, |
||||
} |
||||
} |
@ -0,0 +1,11 @@ |
||||
import { StatusEvents } from '@remixproject/plugin-utils' |
||||
|
||||
export interface ISettings { |
||||
events: { |
||||
configChanged: () => void, |
||||
} & StatusEvents |
||||
methods: { |
||||
getGithubAccessToken(): string |
||||
get(key: string): Promise<any> |
||||
} |
||||
} |
@ -0,0 +1,19 @@ |
||||
import { IGitApi } from "@remix-ui/git" |
||||
import { IRemixApi } from "@remixproject/plugin-api" |
||||
import { StatusEvents } from "@remixproject/plugin-utils" |
||||
import { IConfigApi } from "./plugins/config-api" |
||||
import { IFileDecoratorApi } from "./plugins/filedecorator-api" |
||||
import { IExtendedFileSystem } from "./plugins/fileSystem-api" |
||||
import { INotificationApi } from "./plugins/notification-api" |
||||
import { ISettings } from "./plugins/settings-api" |
||||
|
||||
export interface ICustomRemixApi extends IRemixApi { |
||||
dgitApi: IGitApi |
||||
config: IConfigApi |
||||
notification: INotificationApi |
||||
settings: ISettings |
||||
fileDecorator: IFileDecoratorApi |
||||
fileManager: IExtendedFileSystem |
||||
} |
||||
|
||||
export declare type CustomRemixApi = Readonly<ICustomRemixApi> |
@ -0,0 +1,70 @@ |
||||
import React, { useEffect, useState } from 'react' |
||||
import { gitActionsContext, pluginActionsContext } from '../state/context' |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import { gitPluginContext } from './gitui' |
||||
export const BranchHeader = () => { |
||||
|
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const pluginActions = React.useContext(pluginActionsContext) |
||||
const [changed, setChanged] = useState(false) |
||||
const [isDetached, setIsDetached] = useState(false) |
||||
const [latestCommit, setLatestCommit] = useState<ReadCommitResult>(null) |
||||
|
||||
useEffect(() => { |
||||
if (context.currentBranch) { |
||||
actions.getBranchDifferences(context.currentBranch, null, context) |
||||
} |
||||
if (!context.currentBranch || (context.currentBranch && context.currentBranch.name === '')) { |
||||
if (context.currentHead === '') { |
||||
setIsDetached(false) |
||||
} else { |
||||
setIsDetached(true) |
||||
} |
||||
} else { |
||||
setIsDetached(false) |
||||
} |
||||
setLatestCommit(null) |
||||
if (context.currentHead !== '') { |
||||
if (context.commits && context.commits.length > 0) { |
||||
const commit = context.commits.find(commit => commit.oid === context.currentHead) |
||||
if (commit) { |
||||
setLatestCommit(commit) |
||||
} |
||||
} |
||||
} |
||||
}, [context.currentBranch, context.commits, context.branches, context.remotes, context.currentHead]) |
||||
|
||||
useEffect(() => { |
||||
if (context.fileStatusResult) { |
||||
const total = context.allchangesnotstaged.length |
||||
const badges = total + context.staged.length |
||||
setChanged((context.deleted.length > 0 || context.staged.length > 0 || context.untracked.length > 0 || context.modified.length > 0)) |
||||
} |
||||
}, [context.fileStatusResult, context.modified, context.allchangesnotstaged, context.untracked, context.deleted]) |
||||
|
||||
const showDetachedWarningText = async () => { |
||||
await pluginActions.showAlert({ |
||||
message: `You are in 'detached HEAD' state. This means you are not on a branch because you checkout a tag or a specific commit. If you want to commit changes, you will need to create a new branch.`, |
||||
title: 'Warning' |
||||
}) |
||||
} |
||||
|
||||
return (<> |
||||
<div className='text-sm w-100'> |
||||
<div className='text-secondary long-and-truncated'> |
||||
<i className="fa fa-code-branch mr-1 pl-2"></i> |
||||
{changed ? '*' : ''}{context.currentBranch && context.currentBranch.name} |
||||
</div> |
||||
{latestCommit ? |
||||
<div className='text-secondary long-and-truncated'> |
||||
{latestCommit.commit && latestCommit.commit.message ? latestCommit.commit.message : ''} |
||||
</div> : null} |
||||
{isDetached ? |
||||
<div className='text-warning long-and-truncated'> |
||||
You are in a detached state<i onClick={showDetachedWarningText} className="btn fa fa-info-circle mr-1 pl-2"></i> |
||||
</div> : null} |
||||
</div> |
||||
<hr></hr> |
||||
</>) |
||||
} |
@ -0,0 +1,147 @@ |
||||
import React, { useEffect } from "react" |
||||
import { useState } from "react" |
||||
import { gitActionsContext } from "../../state/context" |
||||
import { gitPluginContext } from "../gitui" |
||||
import { faArrowDown, faArrowUp, faCheck, faCloudArrowUp, faSync } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { syncStateContext } from "./sourceControlBase"; |
||||
|
||||
enum buttonStateValues { |
||||
Commit, |
||||
Sync = 1, |
||||
PublishBranch = 2 |
||||
} |
||||
|
||||
export const CommitMessage = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const syncState = React.useContext(syncStateContext) |
||||
const [buttonState, setButtonState] = useState<buttonStateValues>(buttonStateValues.Commit) |
||||
|
||||
const [message, setMessage] = useState({ value: '' }) |
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||
setMessage({ value: e.currentTarget.value }) |
||||
} |
||||
|
||||
const commit = async () => { |
||||
if (context.staged.length === 0 && context.allchangesnotstaged.length == 0) return |
||||
if (context.staged.length === 0) |
||||
await actions.addall(context.allchangesnotstaged) |
||||
await actions.commit(message.value) |
||||
setMessage({ value: '' }) |
||||
} |
||||
|
||||
const getRemote = () => { |
||||
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const sync = async () => { |
||||
await actions.pull({ |
||||
remote: getRemote(), |
||||
ref: context.currentBranch |
||||
}) |
||||
await actions.push({ |
||||
remote: getRemote(), |
||||
ref: context.currentBranch |
||||
}) |
||||
await actions.pull({ |
||||
remote: getRemote(), |
||||
ref: context.currentBranch |
||||
}) |
||||
} |
||||
|
||||
const commitNotAllowed = () => { |
||||
return context.canCommit === false || message.value === "" || (context.staged.length === 0 && context.allchangesnotstaged.length == 0) |
||||
} |
||||
|
||||
const commitMessagePlaceholder = () => { |
||||
if (context.currentBranch === undefined || context.currentBranch.name === "") |
||||
return `message` |
||||
return `message ( commit on ${context.currentBranch.name} )` |
||||
} |
||||
|
||||
const syncEnabled = () => { |
||||
return syncState.commitsAhead.length > 0 || syncState.commitsBehind.length > 0 |
||||
} |
||||
|
||||
const upDownArrows = () => { |
||||
return ( |
||||
<> |
||||
{syncState.commitsBehind && syncState.commitsBehind.length ? <>{syncState.commitsBehind.length}<FontAwesomeIcon icon={faArrowDown} className="ml-1" /></> : null} |
||||
{syncState.commitsAhead && syncState.commitsAhead.length ? <>{syncState.commitsAhead.length}<FontAwesomeIcon icon={faArrowUp} className="ml-1" /></> : null} |
||||
</> |
||||
) |
||||
} |
||||
|
||||
const publishEnabled = () => { |
||||
const remoteEquivalentBranch = context.branches.find((b) => b.name === context.currentBranch.name && b.remote) |
||||
return remoteEquivalentBranch === undefined && getRemote() !== null |
||||
} |
||||
|
||||
const publishBranch = async () => { |
||||
if (context.currentBranch === undefined || context.currentBranch.name === "") |
||||
return |
||||
await actions.push({ |
||||
remote: getRemote(), |
||||
ref: context.currentBranch |
||||
}) |
||||
await actions.fetch({ |
||||
remote: getRemote(), |
||||
ref: context.currentBranch, |
||||
singleBranch: false, |
||||
relative: true |
||||
}) |
||||
|
||||
} |
||||
|
||||
const messageEnabled = () => { |
||||
return context.canCommit && (context.allchangesnotstaged.length > 0 || context.staged.length > 0) |
||||
} |
||||
|
||||
const setButtonStateValues = () => { |
||||
|
||||
if (!commitNotAllowed() || context.allchangesnotstaged.length > 0 || context.staged.length > 0) { |
||||
if (context.allchangesnotstaged.length == 0 && context.staged.length == 0 && message.value === "" && publishEnabled()) { |
||||
setButtonState(buttonStateValues.PublishBranch) |
||||
return |
||||
} |
||||
setButtonState(buttonStateValues.Commit) |
||||
return |
||||
} |
||||
if (syncEnabled()) { |
||||
setButtonState(buttonStateValues.Sync) |
||||
return |
||||
} |
||||
if (publishEnabled()) { |
||||
setButtonState(buttonStateValues.PublishBranch) |
||||
return |
||||
} |
||||
setButtonState(buttonStateValues.Commit) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
setButtonStateValues() |
||||
}, [context.canCommit, context.staged, context.allchangesnotstaged, context.currentBranch, syncState.commitsAhead, syncState.commitsBehind, message.value]) |
||||
|
||||
return ( |
||||
<> |
||||
<div className="form-group"> |
||||
<input placeholder={commitMessagePlaceholder()} data-id='commitMessage' disabled={!messageEnabled()} className="form-control" type="text" onChange={handleChange} value={message.value} /> |
||||
</div> |
||||
<button data-id='commitButton' className={`btn btn-primary w-100 ${buttonState === buttonStateValues.Commit ? '' : 'd-none'}`} disabled={commitNotAllowed()} onClick={async () => await commit()} > |
||||
<FontAwesomeIcon icon={faCheck} className="mr-1" /> |
||||
Commit |
||||
</button> |
||||
<button data-id='syncButton' className={`btn btn-primary w-100 ${buttonState === buttonStateValues.Sync ? '' : 'd-none'}`} disabled={!syncEnabled()} onClick={async () => await sync()} > |
||||
<FontAwesomeIcon icon={faSync} className="mr-1" aria-hidden="true" /> |
||||
Sync Changes {upDownArrows()} |
||||
</button> |
||||
<button data-id='publishBranchButton' className={`btn btn-primary w-100 ${buttonState === buttonStateValues.PublishBranch ? '' : 'd-none'}`} onClick={async () => await publishBranch()} > |
||||
<FontAwesomeIcon icon={faCloudArrowUp} className="mr-1" aria-hidden="true" /> |
||||
Publish Branch |
||||
</button> |
||||
<hr></hr> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,24 @@ |
||||
import React, { useContext } from 'react' |
||||
import { gitPluginContext } from '../gitui' |
||||
|
||||
interface ButtonWithContextProps { |
||||
onClick: React.MouseEventHandler<HTMLButtonElement>; |
||||
children: React.ReactNode; |
||||
disabledCondition?: boolean; // Optional additional disabling condition
|
||||
// You can add other props if needed, like 'type', 'className', etc.
|
||||
[key: string]: any; // Allow additional props to be passed
|
||||
} |
||||
|
||||
// This component extends a button, disabling it when loading is true
|
||||
const GitUIButton = ({ children, disabledCondition = false, ...rest }:ButtonWithContextProps) => { |
||||
const { loading } = React.useContext(gitPluginContext) |
||||
|
||||
const isDisabled = loading || disabledCondition |
||||
return ( |
||||
<button disabled={isDisabled} {...rest}> |
||||
{children} |
||||
</button> |
||||
); |
||||
}; |
||||
|
||||
export default GitUIButton; |
@ -0,0 +1,90 @@ |
||||
import { faArrowDown, faArrowUp, faArrowsUpDown, faArrowRotateRight } from "@fortawesome/free-solid-svg-icons" |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" |
||||
import { CustomTooltip } from "@remix-ui/helper" |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import React, { createContext, useEffect, useState } from "react" |
||||
import { FormattedMessage } from "react-intl" |
||||
import { gitActionsContext } from "../../state/context" |
||||
import { branch, remote } from "../../types" |
||||
import { gitPluginContext } from "../gitui" |
||||
import GitUIButton from "./gituibutton" |
||||
|
||||
interface SourceControlButtonsProps { |
||||
remote?: remote, |
||||
branch?: branch, |
||||
children: React.ReactNode |
||||
} |
||||
|
||||
export const syncStateContext = createContext<{ |
||||
commitsAhead: ReadCommitResult[], |
||||
commitsBehind: ReadCommitResult[] |
||||
branch: branch, |
||||
remote: remote |
||||
}> |
||||
({ commitsAhead: [], commitsBehind: [], branch: undefined, remote: undefined }) |
||||
|
||||
export const SourceControlBase = (props: SourceControlButtonsProps) => { |
||||
const [branch, setBranch] = useState(props.branch) |
||||
const [remote, setRemote] = useState(props.remote) |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [commitsAhead, setCommitsAhead] = useState<ReadCommitResult[]>([]) |
||||
const [commitsBehind, setCommitsBehind] = useState<ReadCommitResult[]>([]) |
||||
|
||||
useEffect(() => { |
||||
|
||||
setDefaultRemote() |
||||
if (remote && branch && context.branchDifferences && context.branchDifferences[`${remote.name}/${branch.name}`]) { |
||||
setCommitsAhead(context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueHeadCommits) |
||||
setCommitsBehind(context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueRemoteCommits) |
||||
} else { |
||||
setCommitsAhead([]) |
||||
setCommitsBehind([]) |
||||
} |
||||
}, [context.branchDifferences, context.currentBranch, branch, remote]) |
||||
|
||||
const setDefaultRemote = () => { |
||||
|
||||
if (context.remotes.length > 0) { |
||||
// find remote called origin
|
||||
const origin = context.remotes.find(remote => remote.name === 'origin') |
||||
if (origin) { |
||||
setRemote(origin) |
||||
} else { |
||||
setRemote(context.remotes[0]) |
||||
} |
||||
return origin |
||||
} |
||||
return null |
||||
} |
||||
|
||||
useEffect(() => { |
||||
if (!props.branch) { |
||||
setBranch(context.currentBranch) |
||||
} |
||||
if (!props.remote) { |
||||
setRemote(context.defaultRemote) |
||||
} else { |
||||
setDefaultRemote() |
||||
} |
||||
}, []) |
||||
|
||||
useEffect(() => { |
||||
|
||||
if (!props.branch) { |
||||
setBranch(context.currentBranch) |
||||
} |
||||
if (!props.remote) { |
||||
setRemote(context.defaultRemote) |
||||
} else { |
||||
setDefaultRemote() |
||||
} |
||||
}, [context.defaultRemote, context.currentBranch]) |
||||
|
||||
return (<> |
||||
<syncStateContext.Provider value={{ commitsAhead, commitsBehind, branch, remote }}> |
||||
{props.children} |
||||
</syncStateContext.Provider> |
||||
</>) |
||||
|
||||
} |
@ -0,0 +1,98 @@ |
||||
import { faArrowDown, faArrowUp, faArrowsUpDown, faArrowRotateRight } from "@fortawesome/free-solid-svg-icons" |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" |
||||
import { CustomTooltip } from "@remix-ui/helper" |
||||
import React, { useEffect, useState } from "react" |
||||
import { FormattedMessage } from "react-intl" |
||||
import { gitActionsContext } from "../../state/context" |
||||
import { branch, remote } from "../../types" |
||||
import { gitPluginContext } from "../gitui" |
||||
import GitUIButton from "./gituibutton" |
||||
import { syncStateContext } from "./sourceControlBase" |
||||
|
||||
export const SourceControlButtons = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const syncState = React.useContext(syncStateContext) |
||||
const [branch, setBranch] = useState<branch>(syncState.branch) |
||||
const [remote, setRemote] = useState<remote>(syncState.remote) |
||||
|
||||
const getRemote = () => { |
||||
return remote ? remote : context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const getRemoteName = () => { |
||||
return getRemote() ? getRemote().name : '' |
||||
} |
||||
|
||||
const pull = async () => { |
||||
await actions.pull({ |
||||
remote: getRemote(), |
||||
ref: branch ? branch : context.currentBranch |
||||
}) |
||||
} |
||||
|
||||
const push = async () => { |
||||
await actions.push({ |
||||
remote: getRemote(), |
||||
ref: branch ? branch : context.currentBranch |
||||
}) |
||||
await actions.fetch({ |
||||
remote: getRemote(), |
||||
ref: branch ? branch : context.currentBranch, |
||||
relative: false, |
||||
depth: 1, |
||||
singleBranch: true |
||||
}) |
||||
} |
||||
|
||||
const sync = async () => { |
||||
await pull() |
||||
await push() |
||||
} |
||||
|
||||
const refresh = async() => { |
||||
await actions.getFileStatusMatrix(null) |
||||
await actions.gitlog() |
||||
} |
||||
|
||||
const buttonsDisabled = () => { |
||||
return (!context.upstream) || context.remotes.length === 0 |
||||
} |
||||
|
||||
const getTooltipText = (id: string) => { |
||||
if (buttonsDisabled()) return <FormattedMessage id="git.noremote" /> |
||||
return <><FormattedMessage id={id} /> {getRemoteName()}</> |
||||
} |
||||
|
||||
return ( |
||||
<span className='d-flex justify-content-end align-items-center'> |
||||
<CustomTooltip tooltipText={getTooltipText('git.pull')}> |
||||
<GitUIButton data-id='sourcecontrol-button-pull' disabledCondition={buttonsDisabled()} onClick={pull} className='btn btn-sm pl-0 pr-2'> |
||||
<div className="d-flex align-items-baseline"> |
||||
{syncState.commitsBehind.length ? <div className="badge badge-pill pl-0"> |
||||
{syncState.commitsBehind.length} |
||||
</div> : null} |
||||
<FontAwesomeIcon icon={faArrowDown} className="" /> |
||||
</div> |
||||
</GitUIButton> |
||||
</CustomTooltip> |
||||
<CustomTooltip tooltipText={getTooltipText('git.push')}> |
||||
<GitUIButton data-id='sourcecontrol-button-push' disabledCondition={buttonsDisabled()} onClick={push} className='btn btn-sm pl-0 pr-2'> |
||||
<div className="d-flex align-items-baseline"> |
||||
{syncState.commitsAhead.length ? <div className="badge badge-pill pl-0"> |
||||
{syncState.commitsAhead.length} |
||||
</div> : null} |
||||
<FontAwesomeIcon icon={faArrowUp} className="" /> |
||||
</div> |
||||
</GitUIButton> |
||||
</CustomTooltip> |
||||
<CustomTooltip tooltipText={getTooltipText('git.sync')}> |
||||
<GitUIButton data-id='sourcecontrol-button-sync' disabledCondition={buttonsDisabled()} onClick={sync} className='btn btn-sm pl-0 pr-2'><FontAwesomeIcon icon={faArrowsUpDown} className="" /></GitUIButton> |
||||
</CustomTooltip> |
||||
<CustomTooltip tooltipText={<FormattedMessage id="git.refresh" />}> |
||||
<GitUIButton onClick={refresh} className='btn btn-sm'><FontAwesomeIcon icon={faArrowRotateRight} className="" /></GitUIButton> |
||||
</CustomTooltip> |
||||
</span> |
||||
|
||||
) |
||||
} |
@ -0,0 +1,12 @@ |
||||
import React, { useEffect, useState } from 'react' |
||||
|
||||
export const Disabled = () => { |
||||
|
||||
return ( |
||||
<div data-id='disabled' className='text-sm w-100 alert alert-warning'> |
||||
Git is currently disabled.<br></br> |
||||
If you are using RemixD you can use git on the terminal.<br></br> |
||||
</div> |
||||
) |
||||
|
||||
} |
@ -0,0 +1,51 @@ |
||||
import React, { useState, useCallback, useEffect } from 'react'; |
||||
import Select from 'react-select'; |
||||
import { gitActionsContext } from '../../state/context'; |
||||
import { selectStyles, selectTheme } from '../../types/styles'; |
||||
import { gitPluginContext } from '../gitui'; |
||||
|
||||
interface BranchySelectProps { |
||||
select: (branch: { name: string }) => void; |
||||
} |
||||
|
||||
export const BranchSelect = (props: BranchySelectProps) => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [branchOptions, setBranchOptions] = useState<any>([]); |
||||
|
||||
useEffect(() => { |
||||
if (context.remoteBranches && context.remoteBranches.length > 0) { |
||||
const options = context.remoteBranches |
||||
&& context.remoteBranches.length > 0 |
||||
&& context.remoteBranches.map(branch => { |
||||
return { value: branch.name, label: branch.name } |
||||
}) |
||||
setBranchOptions(options) |
||||
} else { |
||||
setBranchOptions(null) |
||||
} |
||||
}, [context.remoteBranches]) |
||||
|
||||
const selectRemoteBranch = async (e: any) => { |
||||
if (!e || !e.value) { |
||||
props.select(null) |
||||
return |
||||
} |
||||
const value = e && e.value |
||||
props.select({ name: value.toString() }) |
||||
} |
||||
|
||||
return (<>{branchOptions && branchOptions.length ? |
||||
<Select |
||||
options={branchOptions} |
||||
className="mt-1" |
||||
id="branch-select" |
||||
onChange={(e: any) => selectRemoteBranch(e)} |
||||
theme={selectTheme} |
||||
styles={selectStyles} |
||||
isClearable={true} |
||||
placeholder="Type to search for a branch..." |
||||
/> : null} |
||||
</>) |
||||
|
||||
} |
@ -0,0 +1,127 @@ |
||||
import React, { useEffect } from "react"; |
||||
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import axios from "axios"; |
||||
import { CopyToClipboard } from "@remix-ui/clipboard"; |
||||
import { Card } from "react-bootstrap"; |
||||
|
||||
export const GetDeviceCode = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const pluginActions = React.useContext(pluginActionsContext) |
||||
const [gitHubResponse, setGitHubResponse] = React.useState<any>(null) |
||||
const [authorized, setAuthorized] = React.useState<boolean>(false) |
||||
|
||||
const getDeviceCodeFromGitHub = async () => { |
||||
|
||||
setAuthorized(false) |
||||
// Send a POST request
|
||||
const response = await axios({ |
||||
method: 'post', |
||||
url: 'https://github.remixproject.org/login/device/code', |
||||
data: { |
||||
client_id: '2795b4e41e7197d6ea11', |
||||
scope: 'repo gist user:email read:user' |
||||
}, |
||||
headers: { |
||||
'Content-Type': 'application/json', |
||||
'Accept': 'application/json' |
||||
}, |
||||
}); |
||||
|
||||
// convert response to json
|
||||
const githubrespone = await response.data; |
||||
|
||||
setGitHubResponse(githubrespone) |
||||
} |
||||
|
||||
const connectApp = async () => { |
||||
// poll https://github.com/login/oauth/access_token
|
||||
const accestokenresponse = await axios({ |
||||
method: 'post', |
||||
url: 'https://github.remixproject.org/login/oauth/access_token', |
||||
data: { |
||||
client_id: '2795b4e41e7197d6ea11', |
||||
device_code: gitHubResponse.device_code, |
||||
grant_type: 'urn:ietf:params:oauth:grant-type:device_code' |
||||
}, |
||||
headers: { |
||||
'Content-Type': 'application/json', |
||||
'Accept': 'application/json' |
||||
}, |
||||
}); |
||||
|
||||
// convert response to json
|
||||
const response = await accestokenresponse.data; |
||||
|
||||
if (response.access_token) { |
||||
setAuthorized(true) |
||||
await pluginActions.saveToken(response.access_token) |
||||
await actions.loadGitHubUserFromToken() |
||||
} |
||||
|
||||
} |
||||
|
||||
const disconnect = async () => { |
||||
setAuthorized(false) |
||||
setGitHubResponse(null) |
||||
await pluginActions.saveToken(null) |
||||
await actions.loadGitHubUserFromToken() |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
{(context.gitHubUser && context.gitHubUser.login) ? null : |
||||
<button className='btn btn-primary mt-1 w-100' onClick={async () => { |
||||
getDeviceCodeFromGitHub(); |
||||
}}><i className="fab fa-github mr-1"></i>Login in with github</button> |
||||
} |
||||
{gitHubResponse && !authorized && |
||||
<div className="pt-2"> |
||||
|
||||
Step 1: Copy this code: |
||||
<div className="input-group text-secondary mb-0 h6"> |
||||
<input disabled type="text" className="form-control" value={gitHubResponse.user_code} /> |
||||
<div className="input-group-append"> |
||||
<CopyToClipboard content={gitHubResponse.user_code} data-id='copyToClipboardCopyIcon' className='far fa-copy ml-1 p-2 mt-1' direction={"top"} /> |
||||
</div> |
||||
</div> |
||||
<br></br> |
||||
Step 2: Authorize the app here |
||||
<br></br><a target="_blank" href={gitHubResponse.verification_uri}>{gitHubResponse.verification_uri}</a> |
||||
<br /><br></br> |
||||
Step 3: When you are done, click on the button below: |
||||
<button className='btn btn-primary mt-1 w-100' onClick={async () => { |
||||
connectApp() |
||||
}}>Connect</button> |
||||
</div> |
||||
} |
||||
{ |
||||
(context.gitHubUser && context.gitHubUser.login) ? |
||||
<div className="pt-2"> |
||||
<button className='btn btn-primary mt-1 w-100' onClick={async () => { |
||||
disconnect() |
||||
}}>Disconnect</button> |
||||
</div> : null |
||||
} |
||||
{ |
||||
(context.gitHubUser && context.gitHubUser.login) ? |
||||
<div className="pt-2"> |
||||
<Card> |
||||
<Card.Body> |
||||
<Card.Title data-id={`connected-as-${context.gitHubUser.login}`}>Connected as {context.gitHubUser.login}</Card.Title> |
||||
<Card.Text> |
||||
<img data-id={`connected-img-${context.gitHubUser.login}`} src={context.gitHubUser.avatar_url} className="w-100" /> |
||||
<a data-id={`connected-link-${context.gitHubUser.login}`} href={context.gitHubUser.html_url}>{context.gitHubUser.html_url}</a> |
||||
{context.userEmails && context.userEmails.filter((email: any) => email.primary).map((email: any) => { |
||||
return <span key={email.email}><br></br>{email.email}</span> |
||||
})} |
||||
</Card.Text> |
||||
</Card.Body> |
||||
</Card> |
||||
|
||||
</div> : null |
||||
} |
||||
|
||||
</>) |
||||
} |
@ -0,0 +1,87 @@ |
||||
import React, { useState, useEffect } from 'react'; |
||||
import { Button } from 'react-bootstrap'; |
||||
import Select from 'react-select'; |
||||
import { gitActionsContext } from '../../state/context'; |
||||
import { repository } from '../../types'; |
||||
import { selectStyles, selectTheme } from '../../types/styles'; |
||||
import { gitPluginContext } from '../gitui'; |
||||
import { TokenWarning } from '../panels/tokenWarning'; |
||||
|
||||
interface RepositorySelectProps { |
||||
select: (repo: repository) => void; |
||||
} |
||||
|
||||
const RepositorySelect = (props: RepositorySelectProps) => { |
||||
const [repoOtions, setRepoOptions] = useState<any>([]); |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [loading, setLoading] = useState(false) |
||||
const [show, setShow] = useState(false) |
||||
|
||||
useEffect(() => { |
||||
if (context.repositories && context.repositories.length > 0) { |
||||
// map context.repositories to options
|
||||
const options = context.repositories && context.repositories.length > 0 && context.repositories.map(repo => { |
||||
return { value: repo.id, label: repo.full_name } |
||||
}) |
||||
|
||||
setRepoOptions(options) |
||||
setShow(options.length > 0) |
||||
} else { |
||||
setRepoOptions(null) |
||||
setShow(false) |
||||
} |
||||
setLoading(false) |
||||
|
||||
}, [context.repositories]) |
||||
|
||||
const selectRepo = async (e: any) => { |
||||
if (!e || !e.value) { |
||||
props.select(null) |
||||
return |
||||
} |
||||
const value = e && e.value |
||||
|
||||
const repo = context.repositories.find(repo => { |
||||
return repo.id.toString() === value.toString() |
||||
}) |
||||
|
||||
if (repo) { |
||||
props.select(repo) |
||||
await actions.remoteBranches(repo.owner.login, repo.name) |
||||
} |
||||
} |
||||
|
||||
const fetchRepositories = async () => { |
||||
try { |
||||
setShow(true) |
||||
setLoading(true) |
||||
setRepoOptions([]) |
||||
await actions.repositories() |
||||
} catch (e) { |
||||
// do nothing
|
||||
} |
||||
}; |
||||
|
||||
return ( |
||||
<><Button data-id='fetch-repositories' onClick={fetchRepositories} className="w-100 mt-1"> |
||||
<i className="fab fa-github mr-1"></i>Fetch Repositories from GitHub |
||||
</Button> |
||||
{ |
||||
show ? |
||||
<Select |
||||
options={repoOtions} |
||||
className="mt-1" |
||||
id="repository-select" |
||||
onChange={(e: any) => selectRepo(e)} |
||||
theme={selectTheme} |
||||
styles={selectStyles} |
||||
isClearable={true} |
||||
placeholder="Type to search for a repository..." |
||||
isLoading={loading} |
||||
/> : null |
||||
}</> |
||||
); |
||||
}; |
||||
|
||||
export default RepositorySelect; |
@ -0,0 +1,59 @@ |
||||
import React, { useEffect, useState } from "react"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import { repository } from "../../types"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import RepositorySelect from "./repositoryselect"; |
||||
import { BranchSelect } from "./branchselect"; |
||||
import { TokenWarning } from "../panels/tokenWarning"; |
||||
|
||||
interface RepositoriesProps { |
||||
cloneDepth?: number |
||||
cloneAllBranches?: boolean |
||||
} |
||||
|
||||
export const SelectAndCloneRepositories = (props: RepositoriesProps) => { |
||||
const { cloneDepth, cloneAllBranches } = props |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [branch, setBranch] = useState({ name: "" }); |
||||
const [repo, setRepo] = useState<repository>(null); |
||||
|
||||
const selectRemoteBranch = async (branch:{ name: string }) => { |
||||
setBranch(branch) |
||||
} |
||||
|
||||
const selectRepo = async (repo: repository) => { |
||||
setBranch(null) |
||||
setRepo(repo) |
||||
} |
||||
|
||||
const clone = async () => { |
||||
try { |
||||
await actions.clone({ |
||||
url: repo.html_url, |
||||
branch: branch.name, |
||||
depth: cloneDepth, |
||||
singleBranch: !cloneAllBranches |
||||
}) |
||||
//actions.clone(repo.html_url, branch.name, cloneDepth, !cloneAllBranches)
|
||||
} catch (e) { |
||||
// do nothing
|
||||
} |
||||
}; |
||||
|
||||
return ( |
||||
<> |
||||
<TokenWarning /> |
||||
<RepositorySelect select={selectRepo} /> |
||||
|
||||
{repo &&<BranchSelect select={selectRemoteBranch} />} |
||||
|
||||
{repo && branch && branch.name && branch.name !== '0' ? |
||||
<button data-id={`clonebtn-${repo.full_name}-${branch.name}`} className='btn btn-primary mt-1 w-100' onClick={async () => { |
||||
await clone() |
||||
}}>clone {repo.full_name}:{branch.name}</button> : null} |
||||
|
||||
</> |
||||
) |
||||
} |
||||
|
@ -0,0 +1,245 @@ |
||||
import React, { useEffect, useReducer, useState } from 'react' |
||||
import { add, addall, checkout, checkoutfile, clone, commit, createBranch, remoteBranches, repositories, rm, getCommitChanges, diff, resolveRef, getBranchCommits, setUpstreamRemote, loadGitHubUserFromToken, getBranches, getRemotes, remoteCommits, saveGitHubCredentials, getGitHubCredentialsFromLocalStorage, fetch, pull, push, setDefaultRemote, addRemote, removeRemote, sendToGitLog, clearGitLog, getBranchDifferences, getFileStatusMatrix, init, showAlert, gitlog } from '../lib/gitactions' |
||||
import { loadFiles, setCallBacks } from '../lib/listeners' |
||||
import { openDiff, openFile, saveToken, setModifiedDecorator, setPlugin, setUntrackedDecorator, statusChanged } from '../lib/pluginActions' |
||||
import { gitActionsContext, pluginActionsContext } from '../state/context' |
||||
import { gitReducer } from '../state/gitreducer' |
||||
import { defaultGitState, defaultLoaderState, gitState, loaderState } from '../types' |
||||
import { Accordion } from "react-bootstrap"; |
||||
import { CommitMessage } from './buttons/commitmessage' |
||||
import { Commits } from './panels/commits' |
||||
import { Branches } from './panels/branches' |
||||
import { SourceControlNavigation } from './navigation/sourcecontrol' |
||||
import { BranchesNavigation } from './navigation/branches' |
||||
import { CommitsNavigation } from './navigation/commits' |
||||
import '../style/index.css' |
||||
import { CloneNavigation } from './navigation/clone' |
||||
import { Clone } from './panels/clone' |
||||
import { Commands } from './panels/commands' |
||||
import { CommandsNavigation } from './navigation/commands' |
||||
import { RemotesNavigation } from './navigation/remotes' |
||||
import { Remotes } from './panels/remotes' |
||||
import { ViewPlugin } from '@remixproject/engine-web' |
||||
import { GitHubNavigation } from './navigation/github' |
||||
import { loaderReducer } from '../state/loaderReducer' |
||||
import { GetDeviceCode } from './github/devicecode' |
||||
import { LogNavigation } from './navigation/log' |
||||
import LogViewer from './panels/log' |
||||
import { SourceControlBase } from './buttons/sourceControlBase' |
||||
import { BranchHeader } from './branchHeader' |
||||
import { SourceControl } from './panels/sourcontrol' |
||||
import { GitHubCredentials } from './panels/githubcredentials' |
||||
import { Setup } from './panels/setup' |
||||
import { Init } from './panels/init' |
||||
import { CustomRemixApi } from "@remix-api"; |
||||
import { Plugin } from "@remixproject/engine"; |
||||
import { Disabled } from './disabled' |
||||
|
||||
export const gitPluginContext = React.createContext<gitState>(defaultGitState) |
||||
export const loaderContext = React.createContext<loaderState>(defaultLoaderState) |
||||
|
||||
interface IGitUi { |
||||
plugin: Plugin<any, CustomRemixApi> |
||||
} |
||||
|
||||
export const GitUI = (props: IGitUi) => { |
||||
const plugin = props.plugin |
||||
const [gitState, gitDispatch] = useReducer(gitReducer, defaultGitState) |
||||
const [loaderState, loaderDispatch] = useReducer(loaderReducer, defaultLoaderState) |
||||
const [activePanel, setActivePanel] = useState<string>("0") |
||||
const [setup, setSetup] = useState<boolean>(false) |
||||
const [needsInit, setNeedsInit] = useState<boolean>(true) |
||||
const [appLoaded, setAppLoaded] = useState<boolean>(false) |
||||
|
||||
useEffect(() => { |
||||
plugin.emit('statusChanged', { |
||||
key: 'loading', |
||||
type: 'info', |
||||
title: 'Loading Git Plugin' |
||||
}) |
||||
setTimeout(() => { |
||||
setAppLoaded(true) |
||||
}, 2000) |
||||
}, []) |
||||
|
||||
useEffect(() => { |
||||
if (!appLoaded) return |
||||
setCallBacks(plugin, gitDispatch, loaderDispatch, setActivePanel) |
||||
setPlugin(plugin, gitDispatch, loaderDispatch) |
||||
loaderDispatch({ type: 'plugin', payload: true }) |
||||
|
||||
}, [appLoaded]) |
||||
|
||||
useEffect(() => { |
||||
if (!appLoaded) return |
||||
async function checkconfig() { |
||||
|
||||
const username = await plugin.call('settings', 'get', 'settings/github-user-name') |
||||
const email = await plugin.call('settings', 'get', 'settings/github-email') |
||||
const token = await plugin.call('settings', 'get', 'settings/gist-access-token') |
||||
|
||||
setSetup(!(username && email)) |
||||
} |
||||
checkconfig() |
||||
}, [gitState.gitHubAccessToken, gitState.gitHubUser, gitState.userEmails, gitState.commits, gitState.branches]) |
||||
|
||||
useEffect(() => { |
||||
if (!appLoaded) return |
||||
async function setDecorators(gitState: gitState) { |
||||
await plugin.call('fileDecorator', 'clearFileDecorators') |
||||
await setModifiedDecorator(gitState.modified) |
||||
await setUntrackedDecorator(gitState.untracked) |
||||
} |
||||
|
||||
setTimeout(() => { |
||||
setDecorators(gitState) |
||||
}) |
||||
|
||||
}, [gitState.fileStatusResult]) |
||||
|
||||
useEffect(() => { |
||||
if (!appLoaded) return |
||||
async function updatestate() { |
||||
|
||||
if (gitState.currentBranch && gitState.currentBranch.remote && gitState.currentBranch.remote.url) { |
||||
remoteCommits(gitState.currentBranch.remote.url, gitState.currentBranch.name, 1) |
||||
} |
||||
} |
||||
setTimeout(() => { |
||||
updatestate() |
||||
}) |
||||
|
||||
let needsInit = false |
||||
if (!(gitState.currentBranch && gitState.currentBranch.name !== '') && gitState.currentHead === '') { |
||||
needsInit = true |
||||
} |
||||
|
||||
setNeedsInit(needsInit) |
||||
|
||||
}, [gitState.gitHubUser, gitState.currentBranch, gitState.remotes, gitState.gitHubAccessToken, gitState.currentHead]) |
||||
|
||||
const gitActionsProviderValue = { |
||||
commit, |
||||
addall, |
||||
add, |
||||
checkoutfile, |
||||
rm, |
||||
checkout, |
||||
createBranch, |
||||
clone, |
||||
repositories, |
||||
remoteBranches, |
||||
getCommitChanges, |
||||
getBranchCommits, |
||||
getBranchDifferences, |
||||
diff, |
||||
resolveRef, |
||||
setUpstreamRemote, |
||||
loadGitHubUserFromToken, |
||||
getBranches, |
||||
getRemotes, |
||||
fetch, |
||||
pull, |
||||
push, |
||||
setDefaultRemote, |
||||
addRemote, |
||||
removeRemote, |
||||
sendToGitLog, |
||||
clearGitLog, |
||||
getFileStatusMatrix, |
||||
gitlog, |
||||
init |
||||
} |
||||
|
||||
const pluginActionsProviderValue = { |
||||
statusChanged, |
||||
loadFiles, |
||||
openFile, |
||||
openDiff, |
||||
saveToken, |
||||
saveGitHubCredentials, |
||||
getGitHubCredentialsFromLocalStorage, |
||||
showAlert |
||||
} |
||||
|
||||
return ( |
||||
<>{(!gitState.canUseApp) ? <Disabled></Disabled> : |
||||
<div className="m-1"> |
||||
<gitPluginContext.Provider value={gitState}> |
||||
<loaderContext.Provider value={loaderState}> |
||||
<gitActionsContext.Provider value={gitActionsProviderValue}> |
||||
<pluginActionsContext.Provider value={pluginActionsProviderValue}> |
||||
<BranchHeader /> |
||||
|
||||
{setup && !needsInit ? <Setup></Setup> : null} |
||||
{needsInit ? <Init></Init> : null} |
||||
{!setup && !needsInit ? |
||||
<Accordion activeKey={activePanel} defaultActiveKey="0"> |
||||
<SourceControlNavigation eventKey="0" activePanel={activePanel} callback={setActivePanel} /> |
||||
|
||||
<Accordion.Collapse className='bg-light' eventKey="0"> |
||||
<> |
||||
<SourceControlBase><CommitMessage /></SourceControlBase> |
||||
<SourceControl /> |
||||
</> |
||||
</Accordion.Collapse> |
||||
<hr></hr> |
||||
<CommandsNavigation eventKey="1" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className='bg-light' eventKey="1"> |
||||
<> |
||||
<Commands></Commands> |
||||
</> |
||||
</Accordion.Collapse> |
||||
<hr></hr> |
||||
<CommitsNavigation title={`COMMITS`} eventKey="3" activePanel={activePanel} callback={setActivePanel} showButtons={true} /> |
||||
<Accordion.Collapse className='bg-light' eventKey="3"> |
||||
<> |
||||
<Commits /> |
||||
</> |
||||
</Accordion.Collapse> |
||||
<hr></hr> |
||||
<BranchesNavigation eventKey="2" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className='bg-light' eventKey="2"> |
||||
<> |
||||
<Branches /></> |
||||
</Accordion.Collapse> |
||||
<hr></hr> |
||||
<RemotesNavigation eventKey="5" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className='bg-light' eventKey="5"> |
||||
<> |
||||
<Remotes></Remotes> |
||||
</> |
||||
</Accordion.Collapse> |
||||
<hr></hr> |
||||
<CloneNavigation eventKey="4" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className='bg-light' eventKey="4"> |
||||
<> |
||||
<Clone /></> |
||||
</Accordion.Collapse> |
||||
<hr></hr> |
||||
<GitHubNavigation eventKey="7" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className='bg-light' eventKey="7"> |
||||
<> |
||||
<GetDeviceCode></GetDeviceCode> |
||||
<hr></hr> |
||||
<GitHubCredentials></GitHubCredentials> |
||||
</> |
||||
</Accordion.Collapse> |
||||
<hr></hr> |
||||
<LogNavigation eventKey="6" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className='bg-light' eventKey="6"> |
||||
<> |
||||
<LogViewer /> |
||||
</> |
||||
</Accordion.Collapse> |
||||
|
||||
</Accordion> |
||||
: null} |
||||
</pluginActionsContext.Provider> |
||||
</gitActionsContext.Provider> |
||||
</loaderContext.Provider> |
||||
</gitPluginContext.Provider> |
||||
</div>} |
||||
</> |
||||
) |
||||
} |
@ -0,0 +1,97 @@ |
||||
import { faCaretUp, faCaretDown, faCaretRight, faArrowUp, faArrowDown, faArrowRotateRight, faArrowsUpDown, faGlobe, faCheckCircle, faToggleOff, faToggleOn, faSync } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import { branch } from "../../types"; |
||||
import GitUIButton from "../buttons/gituibutton"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
|
||||
interface BrancheDetailsNavigationProps { |
||||
eventKey: string; |
||||
activePanel: string; |
||||
callback: (eventKey: string) => void; |
||||
branch: branch; |
||||
checkout: (branch: branch) => void; |
||||
} |
||||
|
||||
export const BrancheDetailsNavigation = (props: BrancheDetailsNavigationProps) => { |
||||
const { eventKey, activePanel, callback, branch, checkout } = props; |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
|
||||
const getRemote = () => { |
||||
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const openRemote = () => { |
||||
const remote = branch.remote || getRemote() |
||||
window.open(`${remote.url}/tree/${branch.name}`, '_blank'); |
||||
} |
||||
|
||||
const reloadBranch = () => { |
||||
actions.getBranchCommits(branch, 1) |
||||
} |
||||
|
||||
const canFetch = () => { |
||||
if (getRemote()) |
||||
return context.branches.find((b) => b.name === branch.name && b.remote && b.remote.url === getRemote().url) ? true : false |
||||
} |
||||
|
||||
const fetchBranch = async () => { |
||||
await actions.fetch({ |
||||
remote: null, |
||||
ref: branch, |
||||
singleBranch: true, |
||||
relative: true |
||||
}) |
||||
|
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className="d-flex flex-row w-100 mb-2 mt-2"> |
||||
<div data-id={`branches-${context.currentBranch.name === branch.name ? 'current-' : ''}branch-${branch.name}`} onClick={() => handleClick()} role={'button'} className='pointer d-flex flex-row w-100 commit-navigation'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<i className="fa fa-code-branch ml-1"></i> |
||||
<div className={`ml-1 ${context.currentBranch.name === branch.name ? 'text-success' : ''}`}>{branch.name} {branch.remote ? `on ${branch.remote.name}` : ''}</div> |
||||
|
||||
</div> |
||||
{context.currentBranch && context.currentBranch.name === branch.name ? |
||||
<GitUIButton data-id={`branches-toggle-current-branch-${branch.name}`} className="btn btn-sm p-0 mr-1" onClick={() => { }}> |
||||
<FontAwesomeIcon className='pointer text-success' icon={faToggleOff} ></FontAwesomeIcon> |
||||
</GitUIButton> |
||||
: |
||||
<GitUIButton data-id={`branches-toggle-branch-${branch.name}`} className="btn btn-sm p-0 mr-1" onClick={() => checkout(branch)}> |
||||
<FontAwesomeIcon icon={faToggleOn}></FontAwesomeIcon> |
||||
</GitUIButton> |
||||
} |
||||
{!branch.remote && canFetch() && <> |
||||
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => fetchBranch()}><FontAwesomeIcon icon={faSync} ></FontAwesomeIcon></GitUIButton> |
||||
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => openRemote()}><FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon></GitUIButton> |
||||
</>} |
||||
{branch.remote?.url && <> |
||||
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => reloadBranch()}> |
||||
<FontAwesomeIcon icon={faSync} ></FontAwesomeIcon> |
||||
</GitUIButton> |
||||
</>} |
||||
|
||||
{branch.remote?.url && <> |
||||
<GitUIButton className="btn btn-sm p-0 mr-1 text-muted" onClick={() => openRemote()}> |
||||
<FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon> |
||||
</GitUIButton> |
||||
</>} |
||||
|
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,32 @@ |
||||
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { } from "react"; |
||||
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||
import LoaderIndicator from "./loaderindicator"; |
||||
|
||||
export const BranchesNavigation = ({ eventKey, activePanel, callback }) => { |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
const context = React.useContext(gitActionsContext) |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between pt-1 ' + (activePanel === eventKey? 'bg-light': '')}> |
||||
<span data-id='branches-panel' onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="pl-1 nav form-check-label">BRANCHES</label> |
||||
<LoaderIndicator></LoaderIndicator> |
||||
</span> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,29 @@ |
||||
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import LoaderIndicator from "./loaderindicator"; |
||||
|
||||
export const CloneNavigation = ({ eventKey, activePanel, callback }) => { |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between pb-1 pt-1 ' + (activePanel === eventKey? 'bg-light': '')}> |
||||
<span data-id='clone-panel' onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="pl-1 nav form-check-label">CLONE</label> |
||||
<LoaderIndicator></LoaderIndicator> |
||||
</span> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,35 @@ |
||||
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faCircleCheck, faArrowsUpDown, faSpinner } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import { pluginActionsContext } from "../../state/context"; |
||||
import GitUIButton from "../buttons/gituibutton"; |
||||
import { SourceControlButtons } from "../buttons/sourcecontrolbuttons"; |
||||
import LoaderIndicator from "./loaderindicator"; |
||||
|
||||
export const CommandsNavigation = ({ eventKey, activePanel, callback }) => { |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}> |
||||
<span data-id='commands-panel' onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="pl-1 nav form-check-label">COMMANDS</label> |
||||
<LoaderIndicator></LoaderIndicator> |
||||
|
||||
</span> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,37 @@ |
||||
import { faCaretUp, faCaretDown, faCaretRight, faArrowUp, faArrowDown, faArrowRotateRight } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import { CommitSummary } from "../panels/commits/commitsummary"; |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
|
||||
interface CommitDetailsNavigationProps { |
||||
commit: ReadCommitResult, |
||||
checkout: (oid: string) => void |
||||
eventKey: string |
||||
activePanel: string |
||||
callback: (eventKey: string) => void |
||||
isAheadOfRepo: boolean |
||||
} |
||||
|
||||
export const CommitDetailsNavigation = (props: CommitDetailsNavigationProps) => { |
||||
const { commit, checkout, eventKey, activePanel, callback, isAheadOfRepo } = props; |
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
return ( |
||||
<> |
||||
<div onClick={() => handleClick()} role={'button'} className={`pointer mb-2 mt-2 w-100 d-flex flex-row commit-navigation ${isAheadOfRepo ? 'text-success' : ''}`}> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
|
||||
<CommitSummary isAheadOfRepo={isAheadOfRepo} commit={commit} checkout={checkout}></CommitSummary> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,63 @@ |
||||
import { faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown, faCloudArrowUp, faCloudArrowDown } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { CustomTooltip } from "@remix-ui/helper"; |
||||
import React, { useEffect } from "react"; |
||||
import { FormattedMessage } from "react-intl"; |
||||
import { pluginActionsContext } from "../../state/context"; |
||||
import { branch, remote } from "../../types"; |
||||
import { SourceControlBase } from "../buttons/sourceControlBase"; |
||||
import { SourceControlButtons } from "../buttons/sourcecontrolbuttons"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import LoaderIndicator from "./loaderindicator"; |
||||
|
||||
export interface CommitsNavigationProps { |
||||
title: string, |
||||
eventKey: string, |
||||
activePanel: string, |
||||
callback: (eventKey: string) => void |
||||
branch?: branch, |
||||
remote?: remote |
||||
showButtons?: boolean |
||||
ahead?: boolean, |
||||
behind?: boolean, |
||||
} |
||||
|
||||
export const CommitsNavigation = ({ eventKey, activePanel, callback, title, branch, remote, showButtons, ahead, behind }: CommitsNavigationProps) => { |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
const [pullEnabled, setPullEnabled] = React.useState(true) |
||||
const [pushEnabled, setPushEnabled] = React.useState(true) |
||||
const [syncEnabled, setSyncEnabled] = React.useState(false) |
||||
const [fetchEnabled, setFetchEnabled] = React.useState(true) |
||||
const context = React.useContext(gitPluginContext) |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className={`d-flex justify-content-between ${activePanel === eventKey ? 'bg-light' : ''} ${ahead || behind? 'text-success':''}`}> |
||||
<span data-id={`commits-panel${ahead?'-ahead':''}${behind?'-behind':''}`} onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-100'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
{ahead? <FontAwesomeIcon className='ml-1' icon={faCloudArrowUp}></FontAwesomeIcon> : null} |
||||
{behind? <FontAwesomeIcon className='ml-1' icon={faCloudArrowDown}></FontAwesomeIcon> : null} |
||||
<label className={`pl-1 nav form-check-label ${ahead || behind? 'text-success':''}`}>{title}</label> |
||||
<LoaderIndicator></LoaderIndicator> |
||||
|
||||
</span> |
||||
{showButtons ? |
||||
<SourceControlBase branch={branch} remote={remote}> |
||||
<SourceControlButtons /> |
||||
</SourceControlBase> : null} |
||||
|
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,29 @@ |
||||
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { } from "react"; |
||||
import { pluginActionsContext } from "../../state/context"; |
||||
|
||||
export const GitHubNavigation = ({ eventKey, activePanel, callback }) => { |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between pt-1 pb-1 ' + (activePanel === eventKey? 'bg-light': '')}> |
||||
<span data-id='github-panel' onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="pl-1 nav form-check-label">GITHUB SETUP</label> |
||||
</span> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,20 @@ |
||||
import React, { useContext } from 'react' |
||||
import { gitPluginContext } from '../gitui' |
||||
|
||||
interface LoaderIndicatorProps { |
||||
type?: string; |
||||
isLoadingCondition?: boolean; // Optional additional disabling condition
|
||||
} |
||||
|
||||
// This component extends a button, disabling it when loading is true
|
||||
const LoaderIndicator = ({ type, isLoadingCondition }: LoaderIndicatorProps) => { |
||||
const { loading } = React.useContext(gitPluginContext) |
||||
|
||||
const isLoading = loading || isLoadingCondition |
||||
if (!isLoading) return null |
||||
return ( |
||||
<i style={{ fontSize: 'x-small' }} className="ml-1 fas fa-spinner fa-spin fa-4x"></i> |
||||
); |
||||
}; |
||||
|
||||
export default LoaderIndicator; |
@ -0,0 +1,87 @@ |
||||
import { faBan, faCaretDown, faCaretRight, faCircleCheck, faCircleInfo, faInfo, faTrash, faTriangleExclamation, faWarning } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { useContext, useEffect, useState } from "react"; |
||||
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
|
||||
export const LogNavigation = ({ eventKey, activePanel, callback }) => { |
||||
const context = useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
|
||||
const [logState, setLogState] = useState({ |
||||
errorCount: 0, |
||||
warningCount: 0, |
||||
infoCount: 0, |
||||
successCount: 0 |
||||
}); |
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
|
||||
useEffect(() => { |
||||
if (!context.log) return |
||||
// count different types of logs
|
||||
const errorCount = context.log.filter(log => log.type === 'error').length |
||||
const warningCount = context.log.filter(log => log.type === 'warning').length |
||||
const infoCount = context.log.filter(log => log.type === 'info').length |
||||
const successCount = context.log.filter(log => log.type === 'success').length |
||||
// update the state
|
||||
setLogState({ |
||||
errorCount, |
||||
warningCount, |
||||
infoCount, |
||||
successCount |
||||
}) |
||||
}, [context.log]) |
||||
|
||||
const clearLogs = () => { |
||||
actions.clearGitLog() |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between pt-1 pb-1 ' + (activePanel === eventKey ? 'bg-light' : '')}> |
||||
<span onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="pl-1 nav form-check-label mr-2">LOG</label> |
||||
{logState.errorCount > 0 && ( |
||||
<div className="text-danger mr-1"> |
||||
{logState.errorCount} |
||||
<FontAwesomeIcon className="ml-1" icon={faTriangleExclamation} /> |
||||
</div> |
||||
)} |
||||
|
||||
{logState.warningCount > 0 && ( |
||||
<div className="text-warning mr-1"> |
||||
{logState.warningCount} |
||||
<FontAwesomeIcon className="ml-1" icon={faWarning} /> |
||||
</div> |
||||
)} |
||||
|
||||
{logState.infoCount > 0 && ( |
||||
<div className="text-info mr-1"> |
||||
{logState.infoCount} |
||||
<FontAwesomeIcon className="ml-1" icon={faCircleInfo} /> |
||||
</div> |
||||
)} |
||||
|
||||
{logState.successCount > 0 && ( |
||||
<div className="text-success"> |
||||
{logState.successCount} |
||||
<FontAwesomeIcon className="ml-1" icon={faCircleCheck} /> |
||||
</div> |
||||
)} |
||||
</span> |
||||
{context.log && context.log.length > 0 && ( |
||||
<FontAwesomeIcon onClick={clearLogs} className='btn btn-sm' icon={faBan}></FontAwesomeIcon>)} |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,49 @@ |
||||
import { count } from "console" |
||||
import { CustomIconsToggle, CustomMenu, CustomTooltip } from "@remix-ui/helper" |
||||
import React, { useState } from "react" |
||||
import { Dropdown } from "react-bootstrap" |
||||
import { FormattedMessage } from "react-intl" |
||||
|
||||
export const SourceControlMenu = () => { |
||||
const [showIconsMenu, hideIconsMenu] = useState<boolean>(false) |
||||
return ( |
||||
<Dropdown id="workspacesMenuDropdown" data-id="sourceControlMenuDropdown" onToggle={() => hideIconsMenu(!showIconsMenu)} show={showIconsMenu}> |
||||
<Dropdown.Toggle |
||||
onClick={() => { |
||||
hideIconsMenu(!showIconsMenu) |
||||
}} |
||||
as={CustomIconsToggle} |
||||
icon={'fas fa-bars'} |
||||
></Dropdown.Toggle> |
||||
<Dropdown.Menu as={CustomMenu} data-id="wsdropdownMenu" className='custom-dropdown-items remixui_menuwidth' rootCloseEvent="click"> |
||||
<Dropdown.Item key={0}> |
||||
<CustomTooltip |
||||
placement="right-start" |
||||
tooltipId="cloneWorkspaceTooltip" |
||||
tooltipClasses="text-nowrap" |
||||
tooltipText={<FormattedMessage id='filePanel.workspace.clone' defaultMessage='Clone Git Repository' />} |
||||
> |
||||
<div |
||||
data-id='cloneGitRepository' |
||||
onClick={() => { |
||||
hideIconsMenu(!showIconsMenu) |
||||
}} |
||||
key={`cloneGitRepository-fe-ws`} |
||||
> |
||||
<span |
||||
id='cloneGitRepository' |
||||
data-id='cloneGitRepository' |
||||
onClick={() => { |
||||
hideIconsMenu(!showIconsMenu) |
||||
}} |
||||
className='fab fa-github pl-2' |
||||
> |
||||
</span> |
||||
<span className="pl-3"><FormattedMessage id='filePanel.clone' defaultMessage='Clone' /></span> |
||||
</div> |
||||
</CustomTooltip> |
||||
</Dropdown.Item> |
||||
</Dropdown.Menu> |
||||
</Dropdown> |
||||
) |
||||
} |
@ -0,0 +1,32 @@ |
||||
import { faCaretDown, faCaretRight } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import React, { } from "react"; |
||||
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||
import LoaderIndicator from "./loaderindicator"; |
||||
|
||||
export const RemotesNavigation = ({ eventKey, activePanel, callback }) => { |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
const context = React.useContext(gitActionsContext) |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between pt-1 pb-1 ' + (activePanel === eventKey? 'bg-light': '')}> |
||||
<span data-id='remotes-panel' onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="pl-1 nav form-check-label">REMOTES</label> |
||||
<LoaderIndicator></LoaderIndicator> |
||||
</span> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,68 @@ |
||||
import { faCaretDown, faCaretRight, faArrowRightArrowLeft, faGlobe, faToggleOff, faToggleOn, faTrash, faCheck, faSync } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { CustomTooltip } from "@remix-ui/helper"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import { branch, remote } from "../../types"; |
||||
import GitUIButton from "../buttons/gituibutton"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
|
||||
interface RemotesDetailsNavigationProps { |
||||
eventKey: string; |
||||
activePanel: string; |
||||
callback: (eventKey: string) => void; |
||||
remote: remote; |
||||
} |
||||
|
||||
export const RemotesDetailsNavigation = (props: RemotesDetailsNavigationProps) => { |
||||
const { eventKey, activePanel, callback, remote } = props; |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
|
||||
const openRemote = () => { |
||||
window.open(`${remote.url}`, '_blank'); |
||||
} |
||||
|
||||
const setAsDefault = () => { |
||||
actions.setDefaultRemote(remote) |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className="d-flex flex-row w-100 mb-2 mt-2"> |
||||
<div data-id={`remote-detail-${remote.name}${context.defaultRemote && context.defaultRemote?.url === remote.url ? '-default' : ''}`} onClick={() => handleClick()} role={'button'} className='pointer long-and-truncated d-flex flex-row commit-navigation'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<CustomTooltip tooltipText={remote.url} placement="top"> |
||||
<div className={`long-and-truncated ml-1 ${context.defaultRemote && context.defaultRemote?.url === remote.url ? 'text-success' : ''}`}> |
||||
{remote.name} <FontAwesomeIcon className='' icon={faArrowRightArrowLeft}></FontAwesomeIcon> {remote.url} |
||||
</div> |
||||
</CustomTooltip> |
||||
|
||||
</div> |
||||
{context.defaultRemote && context.defaultRemote?.url === remote.url ? |
||||
<GitUIButton className="btn btn-sm" onClick={() => { }} disabledCondition={true}><FontAwesomeIcon className='text-success' icon={faCheck} ></FontAwesomeIcon></GitUIButton> |
||||
: |
||||
<GitUIButton className="btn btn-sm" onClick={setAsDefault}><FontAwesomeIcon icon={faToggleOn}></FontAwesomeIcon></GitUIButton> |
||||
} |
||||
<GitUIButton data-id={`remote-sync-${remote.name}`} className="btn btn-sm" onClick={async () => { |
||||
await actions.fetch({ |
||||
remote |
||||
}) |
||||
}}><FontAwesomeIcon icon={faSync} ></FontAwesomeIcon></GitUIButton> |
||||
<GitUIButton data-id={`remote-rm-${remote.name}`} className="btn btn-sm" onClick={() => actions.removeRemote(remote)}><FontAwesomeIcon className='text-danger' icon={faTrash} ></FontAwesomeIcon></GitUIButton> |
||||
{remote?.url && <GitUIButton className="btn btn-sm pr-0" onClick={() => openRemote()}><FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon></GitUIButton>} |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,41 @@ |
||||
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { CustomTooltip } from "@remix-ui/helper"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import { FormattedMessage } from "react-intl"; |
||||
import { pluginActionsContext } from "../../state/context"; |
||||
|
||||
export const SettingsNavigation = ({ eventKey, activePanel, callback }) => { |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
|
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}> |
||||
<span onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="nav pl-1 form-check-label">SETTINGS</label> |
||||
|
||||
</span> |
||||
|
||||
<span className='d-flex justify-content-end align-items-center w-25'> |
||||
<CustomTooltip tooltipText={<FormattedMessage id="Missing values" />}> |
||||
<button onClick={async () => { await pluginactions.loadFiles() }} className='btn btn-sm text-warning'><FontAwesomeIcon icon={faTriangleExclamation} className="" /></button> |
||||
</CustomTooltip> |
||||
|
||||
</span> |
||||
|
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,42 @@ |
||||
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { CustomTooltip } from "@remix-ui/helper"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import { FormattedMessage } from "react-intl"; |
||||
import { pluginActionsContext } from "../../state/context"; |
||||
import { SourceControlBase } from "../buttons/sourceControlBase"; |
||||
import { SourceControlButtons } from "../buttons/sourcecontrolbuttons"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import LoaderIndicator from "./loaderindicator"; |
||||
import { SourceControlMenu } from "./menu/sourcecontrolmenu"; |
||||
|
||||
export const SourceControlNavigation = ({ eventKey, activePanel, callback }) => { |
||||
const pluginactions = React.useContext(pluginActionsContext) |
||||
const context = React.useContext(gitPluginContext) |
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between ' + (activePanel === eventKey ? 'bg-light' : '')}> |
||||
<span data-id='sourcecontrol-panel' onClick={() => handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="nav pl-1 form-check-label">SOURCE CONTROL</label> |
||||
<LoaderIndicator></LoaderIndicator> |
||||
|
||||
</span> |
||||
|
||||
<SourceControlBase><SourceControlButtons/></SourceControlBase> |
||||
|
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,52 @@ |
||||
import { faCaretUp, faCaretDown, faArrowUp, faArrowDown, faArrowRotateRight, faCaretRight, faArrowsUpDown, faPlus, faMinus } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { CustomTooltip } from "@remix-ui/helper"; |
||||
import React, { useContext, useEffect } from "react"; |
||||
import { FormattedMessage } from "react-intl"; |
||||
import { gitActionsContext, pluginActionsContext } from "../../state/context"; |
||||
import { sourceControlGroup } from "../../types"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
|
||||
interface SourceControlGroupNavigationProps { |
||||
eventKey: string; |
||||
activePanel: string; |
||||
callback: (eventKey: string) => void; |
||||
group: sourceControlGroup |
||||
} |
||||
|
||||
export const SourceControlGroupNavigation = (props: SourceControlGroupNavigationProps) => { |
||||
const { eventKey, activePanel, callback, group } = props; |
||||
const actions = React.useContext(gitActionsContext) |
||||
const pluginActions = React.useContext(pluginActionsContext) |
||||
const context = React.useContext(gitPluginContext) |
||||
const handleClick = () => { |
||||
if (!callback) return |
||||
if (activePanel === eventKey) { |
||||
callback('') |
||||
} else { |
||||
callback(eventKey) |
||||
} |
||||
} |
||||
return ( |
||||
<> |
||||
<div className={'d-flex justify-content-between pt-1 ' + (activePanel === eventKey? 'bg-light': '')}> |
||||
<span onClick={()=>handleClick()} role={'button'} className='nav d-flex justify-content-start align-items-center w-75'> |
||||
{ |
||||
activePanel === eventKey ? <FontAwesomeIcon className='' icon={faCaretDown}></FontAwesomeIcon> : <FontAwesomeIcon className='' icon={faCaretRight}></FontAwesomeIcon> |
||||
} |
||||
<label className="pl-1 nav form-check-label">{group.name}</label> |
||||
</span> |
||||
{ |
||||
activePanel === eventKey ? |
||||
<span className='d-flex justify-content-end align-items-center w-25'> |
||||
{group.name === 'Changes' ? |
||||
<CustomTooltip tooltipText={<FormattedMessage id="git.stageall" />}> |
||||
<button data-id='sourcecontrol-add-all' onClick={async () => { await actions.addall(context.allchangesnotstaged) }} className='btn btn-sm'><FontAwesomeIcon icon={faPlus} className="" /></button> |
||||
</CustomTooltip>: null} |
||||
|
||||
</span> : null |
||||
} |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,60 @@ |
||||
import React, { useEffect, useState } from "react"; |
||||
import { Alert } from "react-bootstrap"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import { remote } from "../../types"; |
||||
import GitUIButton from "../buttons/gituibutton"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import { LocalBranchDetails } from "./branches/localbranchdetails"; |
||||
import { RemoteBranchDetails } from "./branches/remotebranchedetails"; |
||||
|
||||
export const Branches = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [newBranch, setNewBranch] = useState({ value: "" }); |
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||
setNewBranch({ value: e.currentTarget.value }); |
||||
}; |
||||
|
||||
return ( |
||||
<> |
||||
<div data-id='branches-panel-content' className="pt-1"> |
||||
{context.branches && context.branches.length ? |
||||
<div> |
||||
{context.branches && context.branches.filter((branch, index) => !branch.remote).map((branch, index) => { |
||||
return ( |
||||
<LocalBranchDetails key={index} branch={branch}></LocalBranchDetails> |
||||
); |
||||
})} |
||||
<hr /> |
||||
|
||||
</div> : null} |
||||
{context.currentBranch |
||||
&& context.currentBranch.name !== '' |
||||
&& (!context.branches || context.branches.length === 0) ? |
||||
<div className="text-muted">Current branch is `{context.currentBranch.name}` but you have no commits.<hr /></div> |
||||
: null} |
||||
<label>create branch</label> |
||||
<div className="form-group"> |
||||
|
||||
<input |
||||
placeholder="branch name" |
||||
onChange={handleChange} |
||||
className="form-control w-md-25 w-100" |
||||
data-id="newbranchname" |
||||
type="text" |
||||
id="newbranchname" |
||||
/> |
||||
</div> |
||||
<GitUIButton |
||||
data-id="sourcecontrol-create-branch" |
||||
onClick={async () => actions.createBranch(newBranch.value)} |
||||
className="btn w-md-25 w-100 btn-primary" |
||||
id="createbranch-btn" |
||||
> |
||||
create new branch |
||||
</GitUIButton> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,48 @@ |
||||
import { ReadCommitResult } from "isomorphic-git"; |
||||
import { Accordion } from "react-bootstrap"; |
||||
import React, { useEffect, useState } from "react"; |
||||
import { CommitDetails } from "../commits/commitdetails"; |
||||
import { CommitsNavigation } from "../../navigation/commits"; |
||||
import { branch, remote } from "../../../types"; |
||||
import { gitActionsContext } from "../../../state/context"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
|
||||
export interface BrancheDifferenceProps { |
||||
commits: ReadCommitResult[]; |
||||
title: string, |
||||
remote?: remote, |
||||
branch?: branch |
||||
ahead?: boolean, |
||||
behind?: boolean |
||||
} |
||||
|
||||
export const BranchDifferenceDetails = (props: BrancheDifferenceProps) => { |
||||
const { commits, title, branch, remote, ahead, behind } = props; |
||||
const [activePanel, setActivePanel] = useState<string>(""); |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
|
||||
if (commits.length === 0) return null |
||||
|
||||
const getRemote = () => { |
||||
return remote ? remote : context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const getCommitChanges = async (commit: ReadCommitResult) => { |
||||
await actions.getCommitChanges(commit.oid, commit.commit.parent[0], null, getRemote()) |
||||
} |
||||
|
||||
return ( |
||||
<Accordion activeKey={activePanel} defaultActiveKey=""> |
||||
<CommitsNavigation ahead={ahead} behind={behind} branch={branch} remote={remote} title={title} eventKey="0" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0"> |
||||
<div data-id={`branchdifference-commits-${branch.name}${ahead?'-ahead':''}${behind?'-behind':''}`} className="ml-1"> |
||||
{commits && commits.map((commit, index) => { |
||||
return ( |
||||
<CommitDetails branch={branch} getCommitChanges={getCommitChanges} key={index} checkout={()=>{}} commit={commit}></CommitDetails> |
||||
); |
||||
})} |
||||
</div> |
||||
</Accordion.Collapse> |
||||
</Accordion>) |
||||
} |
@ -0,0 +1,39 @@ |
||||
import { branch, remote } from "../../../types"; |
||||
import React, { useEffect, useState } from "react"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
import { CommitDetails } from "../commits/commitdetails"; |
||||
import { BranchDifferenceDetails } from "./branchdifferencedetails"; |
||||
|
||||
export interface BrancheDetailsProps { |
||||
branch: branch; |
||||
showSummary?: boolean; |
||||
} |
||||
|
||||
export const BranchDifferences = (props: BrancheDetailsProps) => { |
||||
const { branch, showSummary } = props; |
||||
const context = React.useContext(gitPluginContext) |
||||
|
||||
const getRemote = (): remote | null => { |
||||
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const commitsAhead = (remote: remote) => { |
||||
if (!remote) return []; |
||||
return context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueHeadCommits || []; |
||||
} |
||||
|
||||
const commitsBehind = (remote: remote) => { |
||||
if (!remote) return []; |
||||
return context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueRemoteCommits || []; |
||||
} |
||||
|
||||
if (!getRemote()) return null; |
||||
|
||||
return ( |
||||
|
||||
<div> |
||||
<BranchDifferenceDetails ahead={true} branch={branch} remote={getRemote()} title={`ahead of ${getRemote().name} by ${commitsAhead(getRemote()).length} commit(s)`} commits={commitsAhead(getRemote())}></BranchDifferenceDetails> |
||||
<BranchDifferenceDetails behind={true} branch={branch} remote={getRemote()} title={`behind ${getRemote().name} by ${commitsBehind(getRemote()).length} commit(s)`} commits={commitsBehind(getRemote())}></BranchDifferenceDetails> |
||||
{commitsAhead(getRemote()).length === 0 && commitsBehind(getRemote()).length === 0 ? null : <hr></hr>} |
||||
</div>) |
||||
} |
@ -0,0 +1,86 @@ |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import React, { useEffect, useState } from "react"; |
||||
import { Accordion } from "react-bootstrap"; |
||||
import { CommitDetailsNavigation } from "../../navigation/commitdetails"; |
||||
import { gitActionsContext } from "../../../state/context"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
import { branch } from "../../../types"; |
||||
import { BrancheDetailsNavigation } from "../../navigation/branchedetails"; |
||||
import { CommitDetailsItems } from "../commits/commitdetailsitem"; |
||||
import { CommitDetails } from "../commits/commitdetails"; |
||||
import { BranchDifferences } from "./branchdifferences"; |
||||
import GitUIButton from "../../buttons/gituibutton"; |
||||
|
||||
export interface BrancheDetailsProps { |
||||
branch: branch; |
||||
} |
||||
|
||||
export const LocalBranchDetails = (props: BrancheDetailsProps) => { |
||||
const { branch } = props; |
||||
const actions = React.useContext(gitActionsContext) |
||||
const context = React.useContext(gitPluginContext) |
||||
const [activePanel, setActivePanel] = useState<string>(""); |
||||
const [hasNextPage, setHasNextPage] = useState<boolean>(false) |
||||
const [lastPageNumber, setLastPageNumber] = useState<number>(0) |
||||
|
||||
useEffect(() => { |
||||
if (activePanel === "0") { |
||||
|
||||
if (lastPageNumber === 0) |
||||
actions.getBranchCommits(branch, 1) |
||||
actions.getBranchDifferences(branch, null, context) |
||||
} |
||||
}, [activePanel]) |
||||
|
||||
const checkout = (branch: branch) => { |
||||
actions.checkout({ |
||||
ref: branch.name, |
||||
remote: branch.remote && branch.remote.name || null, |
||||
refresh: true |
||||
}); |
||||
} |
||||
|
||||
const loadNextPage = () => { |
||||
actions.getBranchCommits(branch, lastPageNumber + 1) |
||||
} |
||||
|
||||
const checkoutCommit = async (oid: string) => { |
||||
try { |
||||
|
||||
actions.checkout({ ref: oid }) |
||||
; |
||||
} catch (e) { |
||||
//
|
||||
} |
||||
}; |
||||
|
||||
const getRemote = () => { |
||||
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const getCommitChanges = async (commit: ReadCommitResult) => { |
||||
await actions.getCommitChanges(commit.oid, commit.commit.parent[0], null, getRemote()) |
||||
} |
||||
|
||||
return (<Accordion activeKey={activePanel} defaultActiveKey=""> |
||||
<BrancheDetailsNavigation checkout={checkout} branch={branch} eventKey="0" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0"> |
||||
<> |
||||
<div className="ml-1"> |
||||
<BranchDifferences branch={branch}></BranchDifferences> |
||||
<div data-id={`local-branch-commits-${branch && branch.name}`}> |
||||
{context.localBranchCommits && Object.entries(context.localBranchCommits).map(([key, value]) => { |
||||
|
||||
if (key == branch.name) { |
||||
return value.map((commit, index) => { |
||||
return (<CommitDetails branch={branch} key={index} getCommitChanges={getCommitChanges} checkout={checkoutCommit} commit={commit}></CommitDetails>) |
||||
}) |
||||
} |
||||
})} |
||||
</div> |
||||
</div> |
||||
{hasNextPage && <GitUIButton className="mb-1 ml-2 btn btn-sm" onClick={loadNextPage}>Load more</GitUIButton>} |
||||
</> |
||||
</Accordion.Collapse> |
||||
</Accordion>) |
||||
} |
@ -0,0 +1,111 @@ |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import React, { useEffect, useState } from "react"; |
||||
import { Accordion } from "react-bootstrap"; |
||||
import { CommitDetailsNavigation } from "../../navigation/commitdetails"; |
||||
import { gitActionsContext } from "../../../state/context"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
import { branch } from "../../../types"; |
||||
import { BrancheDetailsNavigation } from "../../navigation/branchedetails"; |
||||
import { CommitDetailsItems } from "../commits/commitdetailsitem"; |
||||
import { CommitDetails } from "../commits/commitdetails"; |
||||
import GitUIButton from "../../buttons/gituibutton"; |
||||
|
||||
export interface BrancheDetailsProps { |
||||
branch: branch; |
||||
} |
||||
|
||||
export const RemoteBranchDetails = (props: BrancheDetailsProps) => { |
||||
const { branch } = props; |
||||
const actions = React.useContext(gitActionsContext) |
||||
const context = React.useContext(gitPluginContext) |
||||
const [activePanel, setActivePanel] = useState<string>(""); |
||||
const [hasNextPage, setHasNextPage] = useState<boolean>(false) |
||||
const [lastPageNumber, setLastPageNumber] = useState<number>(0) |
||||
|
||||
useEffect(() => { |
||||
if (activePanel === "0") { |
||||
|
||||
if (lastPageNumber === 0) |
||||
actions.getBranchCommits(branch, 1) |
||||
} |
||||
}, [activePanel]) |
||||
|
||||
useEffect(() => { |
||||
let hasNextPage = false |
||||
let lastPageNumber = 0 |
||||
context.remoteBranchCommits && Object.entries(context.remoteBranchCommits).map(([key, value]) => { |
||||
if (key == branch.name) { |
||||
value.map((page, index) => { |
||||
hasNextPage = page.hasNextPage |
||||
lastPageNumber = page.page |
||||
}) |
||||
} |
||||
}) |
||||
setHasNextPage(hasNextPage) |
||||
setLastPageNumber(lastPageNumber) |
||||
}, [context.remoteBranchCommits]) |
||||
|
||||
const checkout = async (branch: branch) => { |
||||
await actions.fetch({ |
||||
remote: branch.remote, |
||||
ref: branch, |
||||
depth: 20, |
||||
singleBranch: true, |
||||
relative: false, |
||||
quiet: true |
||||
}) |
||||
await actions.checkout({ |
||||
ref: branch.name, |
||||
remote: branch.remote && branch.remote.name || null, |
||||
refresh: true |
||||
}); |
||||
await actions.getBranches() |
||||
} |
||||
|
||||
const loadNextPage = () => { |
||||
actions.getBranchCommits(branch, lastPageNumber + 1) |
||||
} |
||||
|
||||
const checkoutCommit = async (oid: string) => { |
||||
try { |
||||
actions.checkout({ ref: oid }) |
||||
} catch (e) { |
||||
//
|
||||
} |
||||
}; |
||||
|
||||
const getCommitChanges = async (commit: ReadCommitResult) => { |
||||
const changes = await actions.getCommitChanges(commit.oid, commit.commit.parent[0], branch, branch.remote) |
||||
if (!changes) { |
||||
await actions.fetch({ |
||||
remote: branch.remote, |
||||
ref: branch, |
||||
depth: 20, |
||||
singleBranch: true, |
||||
relative: false, |
||||
quiet: true |
||||
}) |
||||
} |
||||
} |
||||
|
||||
return (<Accordion activeKey={activePanel} defaultActiveKey=""> |
||||
<BrancheDetailsNavigation checkout={checkout} branch={branch} eventKey="0" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0"> |
||||
<> |
||||
<div data-id={`remote-branch-commits-${branch && branch.name}`} className="ml-1"> |
||||
{context.remoteBranchCommits && Object.entries(context.remoteBranchCommits).map(([key, value]) => { |
||||
if (key == branch.name) { |
||||
return value.map((page, index) => { |
||||
return page.commits.map((commit, index) => { |
||||
return (<CommitDetails branch={branch} getCommitChanges={getCommitChanges} key={index} checkout={checkoutCommit} commit={commit}></CommitDetails>) |
||||
}) |
||||
}) |
||||
} |
||||
})} |
||||
|
||||
</div> |
||||
{hasNextPage && <GitUIButton className="mb-1 ml-2 btn btn-sm" onClick={loadNextPage}>Load more</GitUIButton>} |
||||
</> |
||||
</Accordion.Collapse> |
||||
</Accordion>) |
||||
} |
@ -0,0 +1,101 @@ |
||||
|
||||
import React, { useState } from "react"; |
||||
import { Alert, Form, FormControl, InputGroup } from "react-bootstrap"; |
||||
import { useLocalStorage } from "../../hooks/useLocalStorage"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import { SelectAndCloneRepositories } from "../github/selectandclonerepositories"; |
||||
import { RemixUiCheckbox } from "@remix-ui/checkbox"; |
||||
import GitUIButton from "../buttons/gituibutton"; |
||||
|
||||
export const Clone = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [cloneUrl, setCloneUrl] = useLocalStorage( |
||||
"CLONE_URL", |
||||
'' |
||||
); |
||||
|
||||
const [cloneDepth, setCloneDepth] = useLocalStorage( |
||||
"CLONE_DEPTH", |
||||
1 |
||||
); |
||||
|
||||
const [cloneBranch, setCloneBranch] = useLocalStorage( |
||||
"CLONE_BRANCH", |
||||
'' |
||||
); |
||||
|
||||
const [url, setUrl] = useLocalStorage( |
||||
"GITHUB_URL", |
||||
'' |
||||
); |
||||
|
||||
const clone = async () => { |
||||
await actions.clone({ |
||||
url: cloneUrl, |
||||
branch: cloneBranch, |
||||
depth: cloneDepth, |
||||
singleBranch: !cloneAllBranches |
||||
}) |
||||
} |
||||
|
||||
const onCloneBranchChange = (value: string) => { |
||||
setCloneBranch(value) |
||||
} |
||||
|
||||
const onGitHubCloneUrlChange = (value: string) => { |
||||
setCloneUrl(value) |
||||
} |
||||
|
||||
const onDepthChange = (value: number) => { |
||||
setCloneDepth(value) |
||||
} |
||||
|
||||
const [cloneAllBranches, setcloneAllBranches] = useLocalStorage( |
||||
"GITHUB_CLONE_ALL_BRANCES", |
||||
false |
||||
); |
||||
|
||||
const onAllBranchChange = () => { |
||||
setcloneAllBranches((e: any) => !e) |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div data-id="clone-panel-content"> |
||||
<InputGroup className="mb-1"> |
||||
<FormControl data-id="clone-url" id="cloneulr" placeholder="url" name='cloneurl' value={cloneUrl} onChange={e => onGitHubCloneUrlChange(e.target.value)} aria-describedby="urlprepend" /> |
||||
</InputGroup> |
||||
|
||||
<input name='clonebranch' onChange={e => onCloneBranchChange(e.target.value)} value={cloneBranch} className="form-control mb-1 mt-2" placeholder="branch" type="text" id="clonebranch" /> |
||||
<GitUIButton disabledCondition={!cloneUrl} data-id='clone-btn' className='btn btn-primary mt-1 w-100' onClick={async () => { |
||||
clone() |
||||
}}>clone</GitUIButton> |
||||
<hr /> |
||||
<SelectAndCloneRepositories cloneAllBranches={cloneAllBranches} cloneDepth={cloneDepth} /> |
||||
<hr /> |
||||
<label>options</label> |
||||
<InputGroup className="mt-1 mb-1"> |
||||
<InputGroup.Prepend> |
||||
<InputGroup.Text id="clonedepthprepend"> |
||||
--depth |
||||
</InputGroup.Text> |
||||
</InputGroup.Prepend> |
||||
<FormControl id="clonedepth" type="number" value={cloneDepth} onChange={e => onDepthChange(parseInt(e.target.value))} aria-describedby="clonedepthprepend" /> |
||||
</InputGroup> |
||||
|
||||
<RemixUiCheckbox |
||||
id={`cloneAllBranches`} |
||||
inputType="checkbox" |
||||
name="cloneAllBranches" |
||||
label={`Clone all branches`} |
||||
onClick={() => onAllBranchChange()} |
||||
checked={cloneAllBranches} |
||||
onChange={() => { }} |
||||
/> |
||||
|
||||
<hr></hr> |
||||
</div> |
||||
</>) |
||||
} |
@ -0,0 +1,14 @@ |
||||
import React, { useEffect, useState } from "react"; |
||||
import { PushPull } from "./commands/pushpull"; |
||||
import { Fetch } from "./commands/fetch"; |
||||
import { Merge } from "./commands/merge"; |
||||
|
||||
export const Commands = () => { |
||||
|
||||
return ( |
||||
<> |
||||
<PushPull></PushPull> |
||||
<hr></hr> |
||||
<Fetch></Fetch> |
||||
</>) |
||||
} |
@ -0,0 +1,25 @@ |
||||
import React, { useEffect, useState } from "react"; |
||||
import { gitActionsContext } from "../../../state/context"; |
||||
import GitUIButton from "../../buttons/gituibutton"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
|
||||
export const Fetch = () => { |
||||
const actions = React.useContext(gitActionsContext) |
||||
const context = React.useContext(gitPluginContext) |
||||
|
||||
const fetchIsDisabled = () => { |
||||
return (!context.upstream) || context.remotes.length === 0 |
||||
} |
||||
return ( |
||||
<> |
||||
<div className="btn-group w-100" role="group"> |
||||
<GitUIButton data-id='sourcecontrol-fetch-remote' disabledCondition={fetchIsDisabled()} type="button" onClick={async () => actions.fetch({ |
||||
remote: context.upstream, |
||||
})} className="btn btn-primary mr-1 w-50"><div>Fetch {context.upstream && context.upstream.name}</div></GitUIButton> |
||||
<GitUIButton data-id='sourcecontrol-fetch-branch' disabledCondition={fetchIsDisabled()} type="button" onClick={async () => actions.fetch({ |
||||
remote: context.upstream, |
||||
ref: context.currentBranch |
||||
})} className="btn btn-primary w-50 long-and-truncated">Fetch {context.currentBranch.name}</GitUIButton> |
||||
</div> |
||||
</>) |
||||
} |
@ -0,0 +1,57 @@ |
||||
import React, { useEffect, useState } from "react"; |
||||
import { gitActionsContext } from "../../../state/context"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
import { selectStyles, selectTheme } from "../../../types/styles"; |
||||
import Select from 'react-select' |
||||
import GitUIButton from "../../buttons/gituibutton"; |
||||
|
||||
export const Merge = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [localBranch, setLocalBranch] = useState('') |
||||
const [localBranchOptions, setLocalBranchOptions] = useState<any>([]); |
||||
|
||||
useEffect(() => { |
||||
setLocalBranch(context.currentBranch.name) |
||||
}, [context.currentBranch]) |
||||
|
||||
const onLocalBranchChange = (value: any) => { |
||||
setLocalBranch(value) |
||||
} |
||||
|
||||
const merge = async () => { |
||||
//gitservice.push(currentRemote, branch || '', remoteBranch, force)
|
||||
} |
||||
|
||||
useEffect(() => { |
||||
// map context.repositories to options
|
||||
const localBranches = context.branches && context.branches.length > 0 && context.branches |
||||
.filter(branch => !branch.remote) |
||||
.map(repo => { |
||||
return { value: repo.name, label: repo.name } |
||||
}) |
||||
setLocalBranchOptions(localBranches) |
||||
|
||||
}, [context.branches]) |
||||
|
||||
return ( |
||||
<> |
||||
|
||||
<div className="btn-group w-100" role="group" aria-label="Basic example"> |
||||
<GitUIButton type="button" onClick={async () => merge()} className="btn btn-primary mr-1">Merge</GitUIButton> |
||||
</div> |
||||
|
||||
<label>Merge from Branch</label> |
||||
<Select |
||||
options={localBranchOptions} |
||||
isDisabled={context.branches.length === 0} |
||||
onChange={(e: any) => e && onLocalBranchChange(e.value)} |
||||
theme={selectTheme} |
||||
styles={selectStyles} |
||||
isClearable={true} |
||||
value={{ value: localBranch, label: localBranch }} |
||||
placeholder="Type to search for a branch..." |
||||
/> |
||||
|
||||
</>) |
||||
} |
@ -0,0 +1,196 @@ |
||||
import React, { useEffect, useState } from "react"; |
||||
import { gitActionsContext } from "../../../state/context"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
import { selectStyles, selectTheme } from "../../../types/styles"; |
||||
import Select, { Options, OptionsOrGroups } from 'react-select' |
||||
import GitUIButton from "../../buttons/gituibutton"; |
||||
import { remote } from "../../../types"; |
||||
import { relative } from "path"; |
||||
|
||||
export const PushPull = () => { |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
const [remoteBranch, setRemoteBranch] = useState('') |
||||
const [localBranch, setLocalBranch] = useState('') |
||||
const [localBranchOptions, setLocalBranchOptions] = useState<any>([]); |
||||
const [remoteBranchOptions, setRemoteBranchOptions] = useState<any>([]); |
||||
const [localRemotesOptions, setLocalRemotesOptions] = useState<any>([]); |
||||
const [disabled, setDisabled] = useState(false) |
||||
const [force, setForce] = useState(false) |
||||
|
||||
useEffect(() => { |
||||
setRemoteBranch(context.currentBranch.name) |
||||
setLocalBranch(context.currentBranch.name) |
||||
|
||||
const currentUpstreamIsInRemotes = context.upstream && context.remotes.find(r => r.name === context.upstream.name) |
||||
if (!context.upstream || !currentUpstreamIsInRemotes) { |
||||
if (context.currentBranch && context.currentBranch.remote && context.currentBranch.remote.name) { |
||||
actions.setUpstreamRemote(context.currentBranch.remote) |
||||
setDisabled(false) |
||||
} else { |
||||
if (context.remotes && context.remotes.length > 0) { |
||||
actions.setUpstreamRemote(context.remotes[0]) |
||||
setDisabled(false) |
||||
} else { |
||||
actions.setUpstreamRemote(null) |
||||
setDisabled(true) |
||||
} |
||||
} |
||||
} |
||||
}, [context.currentBranch, context.remotes, context.branches]) |
||||
|
||||
const onRemoteBranchChange = (value: string) => { |
||||
setRemoteBranch(value) |
||||
} |
||||
|
||||
const onLocalBranchChange = (value: any) => { |
||||
setLocalBranch(value) |
||||
} |
||||
|
||||
const onRemoteChange = (value: string) => { |
||||
const remote: remote = context.remotes.find(r => r.name === value) |
||||
if (remote) { |
||||
actions.setUpstreamRemote(remote) |
||||
} |
||||
} |
||||
|
||||
const onForceChange = (event: any) => { |
||||
const target = event.target; |
||||
const value = target.checked; |
||||
setForce(value) |
||||
} |
||||
|
||||
const push = async () => { |
||||
await actions.push({ |
||||
remote: context.upstream, |
||||
ref: { |
||||
name: localBranch, |
||||
remote: null |
||||
}, |
||||
remoteRef: { |
||||
name: remoteBranch, |
||||
remote: null |
||||
}, |
||||
force: force |
||||
}) |
||||
await actions.fetch({ |
||||
remote: context.upstream, |
||||
ref: { |
||||
name: localBranch, |
||||
remote: null |
||||
}, |
||||
remoteRef: { |
||||
name: remoteBranch, |
||||
remote: null |
||||
}, |
||||
depth: 1, |
||||
relative: true, |
||||
singleBranch: true |
||||
}) |
||||
} |
||||
|
||||
const pull = async () => { |
||||
await actions.pull({ |
||||
remote: context.upstream, |
||||
ref: { |
||||
name: localBranch, |
||||
remote: null |
||||
}, |
||||
remoteRef: { |
||||
name: remoteBranch, |
||||
remote: null |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
|
||||
const localBranches = context.branches && context.branches.length > 0 && context.branches |
||||
.filter(branch => !branch.remote) |
||||
.map(repo => { |
||||
return { value: repo.name, label: repo.name } |
||||
}) |
||||
setLocalBranchOptions(localBranches) |
||||
|
||||
const remoteBranches = context.branches && context.branches.length > 0 && context.branches |
||||
.filter(branch => branch.remote) |
||||
.map(repo => { |
||||
return { value: repo.name, label: repo.name } |
||||
} |
||||
) |
||||
setRemoteBranchOptions(remoteBranches) |
||||
|
||||
}, [context.branches]) |
||||
|
||||
useEffect(() => { |
||||
|
||||
// map context.repositories to options
|
||||
const options = context.remotes && context.remotes.length > 0 && context.remotes |
||||
.map(repo => { |
||||
return { value: repo.name, label: repo.name } |
||||
}) |
||||
setLocalRemotesOptions(options) |
||||
|
||||
}, [context.remotes]) |
||||
|
||||
const pushPullIsDisabled = () => { |
||||
return localBranch === '' || remoteBranch === '' || !context.upstream || context.remotes.length === 0 |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
{disabled? <div data-id='disabled' className='text-sm w-100 alert alert-warning mt-1'> |
||||
You cannot push or pull because you haven't connected to or selected a remote. |
||||
</div>: null} |
||||
<div className="btn-group w-100 mt-2" role="group"> |
||||
|
||||
<GitUIButton data-id='sourcecontrol-pull' disabledCondition={pushPullIsDisabled()} type="button" onClick={async () => pull()} className="btn btn-primary mr-1">Pull</GitUIButton> |
||||
<GitUIButton data-id='sourcecontrol-push' disabledCondition={pushPullIsDisabled()} type="button" onClick={async () => push()} className="btn btn-primary">Push</GitUIButton> |
||||
</div> |
||||
|
||||
<label>Local Branch</label> |
||||
<Select |
||||
id='commands-local-branch-select' |
||||
options={localBranchOptions} |
||||
isDisabled={context.branches.length === 0} |
||||
onChange={(e: any) => e && onLocalBranchChange(e.value)} |
||||
theme={selectTheme} |
||||
styles={selectStyles} |
||||
isClearable={true} |
||||
value={{ value: localBranch, label: localBranch }} |
||||
placeholder="Type to search for a branch..." |
||||
/> |
||||
|
||||
<label>Remote Branch</label> |
||||
<Select |
||||
id='commands-remote-branch-select' |
||||
options={remoteBranchOptions} |
||||
isDisabled={context.branches.length === 0} |
||||
onChange={(e: any) => e && onRemoteBranchChange(e.value)} |
||||
theme={selectTheme} |
||||
styles={selectStyles} |
||||
isClearable={true} |
||||
value={{ value: remoteBranch, label: remoteBranch }} |
||||
placeholder="Type to search for a branch..." |
||||
/> |
||||
|
||||
<label>Remote</label> |
||||
<Select |
||||
id='commands-remote-origin-select' |
||||
options={localRemotesOptions} |
||||
isDisabled={context.remotes.length === 0} |
||||
onChange={(e: any) => e && onRemoteChange(e.value)} |
||||
theme={selectTheme} |
||||
styles={selectStyles} |
||||
isClearable={true} |
||||
value={{ value: context.upstream && context.upstream.name, label: context.upstream && context.upstream.name }} |
||||
placeholder="Type to search for a branch..." |
||||
/> |
||||
|
||||
<div className="mt-2 remixui_compilerConfig custom-control custom-checkbox"> |
||||
<input checked={force} onChange={e => onForceChange(e)} className="remixui_autocompile custom-control-input" type="checkbox" data-id="compilerContainerAutoCompile" id="forcepush" title="Force Push" /> |
||||
<label className="form-check-label custom-control-label" htmlFor="forcepush">Force push</label> |
||||
</div> |
||||
|
||||
</>) |
||||
} |
@ -0,0 +1,64 @@ |
||||
import { checkout, ReadCommitResult } from "isomorphic-git"; |
||||
import React from "react"; |
||||
import { gitActionsContext } from "../../state/context"; |
||||
import GitUIButton from "../buttons/gituibutton"; |
||||
import { gitPluginContext } from "../gitui"; |
||||
import LoaderIndicator from "../navigation/loaderindicator"; |
||||
import { BranchDifferences } from "./branches/branchdifferences"; |
||||
import { CommitDetails } from "./commits/commitdetails"; |
||||
import { CommitSummary } from "./commits/commitsummary"; |
||||
|
||||
export const Commits = () => { |
||||
const [hasNextPage, setHasNextPage] = React.useState(true) |
||||
const context = React.useContext(gitPluginContext) |
||||
const actions = React.useContext(gitActionsContext) |
||||
|
||||
const checkout = async (oid: string) => { |
||||
try { |
||||
actions.checkout({ ref: oid }) |
||||
} catch (e) { |
||||
//
|
||||
} |
||||
}; |
||||
|
||||
const loadNextPage = () => { |
||||
actions.fetch({ |
||||
remote: null, |
||||
ref: context.currentBranch, |
||||
relative: true, |
||||
depth: 5, |
||||
singleBranch: true |
||||
}) |
||||
|
||||
} |
||||
|
||||
const getRemote = () => { |
||||
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const getCommitChanges = async (commit: ReadCommitResult) => { |
||||
await actions.getCommitChanges(commit.oid, commit.commit.parent[0],null, getRemote()) |
||||
} |
||||
|
||||
const fetchIsDisabled = () => { |
||||
return (!context.upstream)|| context.remotes.length === 0 |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
{context.commits && context.commits.length ? |
||||
<><BranchDifferences branch={context.currentBranch}></BranchDifferences><div> |
||||
<div data-id={`commits-current-branch-${context.currentBranch && context.currentBranch.name}`} className="pt-1"> |
||||
{context.commits && context.commits.map((commit, index) => { |
||||
return ( |
||||
<CommitDetails branch={context.currentBranch} getCommitChanges={getCommitChanges} key={index} checkout={checkout} commit={commit}></CommitDetails> |
||||
); |
||||
})} |
||||
</div> |
||||
</div> |
||||
{hasNextPage && <GitUIButton disabledCondition={fetchIsDisabled()} className="mb-1 ml-2 btn btn-sm" onClick={loadNextPage}>Load more</GitUIButton>} |
||||
</> |
||||
: <div className="text-muted">No commits</div>} |
||||
</> |
||||
) |
||||
} |
@ -0,0 +1,60 @@ |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import React, { useEffect, useState } from "react"; |
||||
import { Accordion } from "react-bootstrap"; |
||||
import { CommitDetailsNavigation } from "../../navigation/commitdetails"; |
||||
import { gitActionsContext } from "../../../state/context"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
import { CommitDetailsItems } from "./commitdetailsitem"; |
||||
import { branch, remote } from "@remix-ui/git"; |
||||
|
||||
export interface CommitDetailsProps { |
||||
commit: ReadCommitResult; |
||||
checkout: (oid: string) => void; |
||||
getCommitChanges: (commit: ReadCommitResult) => void; |
||||
branch: branch |
||||
} |
||||
|
||||
export const CommitDetails = (props: CommitDetailsProps) => { |
||||
const { commit, checkout, getCommitChanges, branch } = props; |
||||
const actions = React.useContext(gitActionsContext) |
||||
const context = React.useContext(gitPluginContext) |
||||
const [activePanel, setActivePanel] = useState<string>(""); |
||||
|
||||
useEffect(() => { |
||||
if (activePanel === "0") { |
||||
getCommitChanges(commit) |
||||
} |
||||
}, [activePanel]) |
||||
|
||||
const getRemote = (): remote | null => { |
||||
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const commitsAhead = (remote: remote) => { |
||||
if (!remote) return []; |
||||
return context.branchDifferences[`${remote.name}/${branch.name}`]?.uniqueHeadCommits || []; |
||||
} |
||||
|
||||
const isAheadOfRepo = () => { |
||||
return commitsAhead(getRemote()).findIndex((c) => c.oid === commit.oid) > -1 |
||||
} |
||||
|
||||
const openFileOnRemote = (file: string, hash: string) => { |
||||
if (!getRemote()) return |
||||
window.open(`${getRemote() ? `${getRemote().url}/blob/${hash}/${file}` : ""}`, "_blank") |
||||
} |
||||
|
||||
return (<Accordion activeKey={activePanel} defaultActiveKey=""> |
||||
<CommitDetailsNavigation isAheadOfRepo={isAheadOfRepo()} commit={commit} checkout={checkout} eventKey="0" activePanel={activePanel} callback={setActivePanel} /> |
||||
<Accordion.Collapse className="pl-2 border-left ml-1" eventKey="0"> |
||||
<> |
||||
{context.commitChanges && context.commitChanges.filter( |
||||
(change) => change.hashModified === commit.oid && change.hashOriginal === commit.commit.parent[0] |
||||
).map((change, index) => { |
||||
return (<CommitDetailsItems openFileOnRemote={openFileOnRemote} isAheadOfRepo={isAheadOfRepo()} key={index} commitChange={change}></CommitDetailsItems>) |
||||
})} |
||||
|
||||
</> |
||||
</Accordion.Collapse> |
||||
</Accordion>) |
||||
} |
@ -0,0 +1,51 @@ |
||||
import { branch, commitChange } from "../../../types"; |
||||
import React from "react"; |
||||
import path from "path"; |
||||
import { gitActionsContext, pluginActionsContext } from "../../../state/context"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { faGlobe } from "@fortawesome/free-solid-svg-icons"; |
||||
|
||||
export interface CCommitDetailsItemsProps { |
||||
commitChange: commitChange; |
||||
isAheadOfRepo: boolean; |
||||
openFileOnRemote: (file: string, hash: string) => void; |
||||
} |
||||
|
||||
export const CommitDetailsItems = (props: CCommitDetailsItemsProps) => { |
||||
const { commitChange, isAheadOfRepo, openFileOnRemote } = props; |
||||
const actions = React.useContext(gitActionsContext) |
||||
const pluginActions = React.useContext(pluginActionsContext) |
||||
|
||||
const openChanges = async (change: commitChange) => { |
||||
await actions.diff(change) |
||||
await pluginActions.openDiff(change) |
||||
} |
||||
|
||||
const openRemote = () => { |
||||
openFileOnRemote(commitChange.path, commitChange.hashModified) |
||||
} |
||||
|
||||
function FunctionStatusIcons() { |
||||
const status = commitChange.type |
||||
return (<> |
||||
|
||||
{status && status.indexOf("modified") === -1 ? <></> : <span>M</span>} |
||||
{status && status.indexOf("deleted") === -1 ? <></> : <span>D</span>} |
||||
{status && status.indexOf("added") === -1 ? <></> : <span>A</span>} |
||||
|
||||
</>) |
||||
} |
||||
return (<> |
||||
<div data-id={`commit-change-${commitChange.type}-${path.basename(commitChange.path)}`} className={`d-flex w-100 d-flex flex-row commitdetailsitem ${isAheadOfRepo ? 'text-success' : ''}`}> |
||||
<div className='pointer gitfile long-and-truncated' onClick={async () => await openChanges(commitChange)}> |
||||
<span className='font-weight-bold long-and-truncated'>{path.basename(commitChange.path)}</span> |
||||
<div className='text-secondary long-and-truncated'> {commitChange.path}</div> |
||||
</div> |
||||
<div className="d-flex align-items-end"> |
||||
{!isAheadOfRepo ? |
||||
<FontAwesomeIcon role={'button'} icon={faGlobe} onClick={() => openRemote()} className="pointer mr-1 align-self-center" /> : <></>} |
||||
<FunctionStatusIcons></FunctionStatusIcons> |
||||
</div> |
||||
</div> |
||||
</>) |
||||
} |
@ -0,0 +1,70 @@ |
||||
import { ReadCommitResult } from "isomorphic-git" |
||||
import { default as dateFormat } from "dateformat"; |
||||
import React from "react"; |
||||
import { faGlobe } from "@fortawesome/free-solid-svg-icons"; |
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |
||||
import { remote } from "@remix-ui/git"; |
||||
import GitUIButton from "../../buttons/gituibutton"; |
||||
import { gitPluginContext } from "../../gitui"; |
||||
|
||||
export interface CommitSummaryProps { |
||||
commit: ReadCommitResult; |
||||
checkout: (oid: string) => void; |
||||
isAheadOfRepo: boolean |
||||
} |
||||
|
||||
export const CommitSummary = (props: CommitSummaryProps) => { |
||||
const { commit, checkout, isAheadOfRepo } = props; |
||||
const context = React.useContext(gitPluginContext) |
||||
|
||||
const getDate = (commit: ReadCommitResult) => { |
||||
const timestamp = commit.commit.author.timestamp; |
||||
|
||||
if (timestamp) { |
||||
// calculate the difference between the current time and the commit time in days or hours or minutes
|
||||
const diff = Math.floor((Date.now() - timestamp * 1000) / 1000 / 60 / 60 / 24); |
||||
|
||||
if (diff == 0) { |
||||
return "today at " + dateFormat(timestamp * 1000, "HH:MM"); |
||||
} else |
||||
|
||||
if (diff < 1) { |
||||
// return how many hours ago
|
||||
return `${Math.floor(diff * 24)} hour(s) ago`; |
||||
} |
||||
|
||||
if (diff < 7) { |
||||
// return how many days ago
|
||||
return `${diff} day(s) ago`; |
||||
} |
||||
if (diff < 365) { |
||||
return dateFormat(timestamp * 1000, "mmm dd"); |
||||
} |
||||
return dateFormat(timestamp * 1000, "mmm dd yyyy"); |
||||
} |
||||
return ""; |
||||
}; |
||||
|
||||
const getRemote = (): remote | null => { |
||||
return context.upstream ? context.upstream : context.defaultRemote ? context.defaultRemote : null |
||||
} |
||||
|
||||
const openRemote = () => { |
||||
if (getRemote()) |
||||
window.open(`${getRemote().url}/commit/${commit.oid}`, '_blank'); |
||||
} |
||||
function removeLineBreaks(str: string): string { |
||||
return str.replace(/(\r\n|\n|\r)/gm, ''); |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<div data-id={`commit-summary-${removeLineBreaks(commit.commit.message)}-${isAheadOfRepo ? 'ahead' : ''}`} className="long-and-truncated ml-2"> |
||||
{commit.commit.message} |
||||
</div> |
||||
{commit.commit.author.name || ""} |
||||
<span className="ml-1">{getDate(commit)}</span> |
||||
{getRemote() && getRemote()?.url && !isAheadOfRepo && <GitUIButton className="btn btn-sm p-0 text-muted ml-1" onClick={() => openRemote()}><FontAwesomeIcon icon={faGlobe} ></FontAwesomeIcon></GitUIButton>} |
||||
</> |
||||
) |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue