diff --git a/package-lock.json b/package-lock.json index f7728eb..7e415ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,9 @@ } }, "@nodelib/fs.stat": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.1.tgz", - "integrity": "sha512-KU/VDjC5RwtDUZiz3d+DHXJF2lp5hB9dn552TXIyptj8SH1vXmR40mG0JgGq03IlYsOgGfcv8xrLpSQ0YUMQdA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.2.tgz", + "integrity": "sha512-yprFYuno9FtNsSHVlSWd+nRlmGoAbqbeCwOryP6sC/zoCjhpArcRMYp19EvpSUSizJAlsXEwJv+wcWS9XaXdMw==", "dev": true }, "@types/clipboardy": { @@ -32,12 +32,6 @@ "integrity": "sha512-zbteaWZ2mdduacm0byELwtRyhYE40aK+pAanQk415gr1eRuu67x7QGOLmn8jz5zI8LDK7d0WI/oT6r5Trz4rzQ==", "dev": true }, - "@types/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0=", - "dev": true - }, "@webassemblyjs/ast": { "version": "1.5.13", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.5.13.tgz", @@ -1062,7 +1056,7 @@ }, "buffer": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-3.6.0.tgz", + "resolved": "http://registry.npmjs.org/buffer/-/buffer-3.6.0.tgz", "integrity": "sha1-pyyTb3e5a/UvX357RnGAYoVR3vs=", "dev": true, "requires": { @@ -1266,9 +1260,9 @@ } }, "capture-stack-trace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", - "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", "dev": true }, "caseless": { @@ -1299,7 +1293,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -1311,9 +1305,9 @@ } }, "chardet": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.5.0.tgz", - "integrity": "sha512-9ZTaoBaePSCFvNlNGrsyI8ZVACP2svUtq0DkM7t4K2ClAa96sqOIRjAzDTc8zXzFt1cZR46rRzLTiHFSJ+Qw0g==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, "chokidar": { @@ -1364,9 +1358,9 @@ } }, "ci-info": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.4.0.tgz", - "integrity": "sha512-Oqmw2pVfCl8sCL+1QgMywPfdxPJPkC51y4usw0iiE2S9qnEOAqXy8bwl1CpMpnoU39g4iKJTz6QZj+28FvOnjQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.5.0.tgz", + "integrity": "sha512-Bx/xWOzip4whERIvC97aIHjWCa8FxEn0ezng0oVn4kma6p+90Fbs3bTcJw6ZL0da2EPHydxsXJPZxNUv5oWb1Q==", "dev": true }, "cipher-base": { @@ -1571,18 +1565,18 @@ } }, "color-convert": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", - "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { - "color-name": "1.1.1" + "color-name": "1.1.3" } }, "color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "color-support": { @@ -1718,10 +1712,13 @@ "dev": true }, "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } }, "copy-concurrently": { "version": "1.0.5", @@ -1934,6 +1931,12 @@ "source-map": "^0.5.3" } }, + "mdn-data": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz", + "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==", + "dev": true + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -2688,18 +2691,18 @@ "dev": true }, "event-stream": { - "version": "3.3.4", - "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.5.tgz", + "integrity": "sha512-vyibDcu5JL20Me1fP734QBH/kenBGLZap2n0+XXM7mvuUPzJ20Ydqj1aKcIeMdri1p+PU+4yAKugjN8KCVst+g==", "dev": true, "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" + "duplexer": "^0.1.1", + "from": "^0.1.7", + "map-stream": "0.0.7", + "pause-stream": "^0.0.11", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through": "^2.3.8" } }, "events": { @@ -2920,13 +2923,13 @@ } }, "external-editor": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.1.tgz", - "integrity": "sha512-e1neqvSt5pSwQcFnYc6yfGuJD2Q4336cdbHs5VeUO0zTkqPbrHMyw2q1r47fpfLWbvIG8H8A6YO3sck7upTV6Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "dev": true, "requires": { - "chardet": "^0.5.0", - "iconv-lite": "^0.4.22", + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, @@ -3934,9 +3937,9 @@ } }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -4182,7 +4185,7 @@ }, "got": { "version": "5.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz", + "resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz", "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=", "dev": true, "requires": { @@ -5011,7 +5014,7 @@ }, "html-webpack-plugin": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", "dev": true, "requires": { @@ -5833,9 +5836,9 @@ } }, "js-base64": { - "version": "2.4.8", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.8.tgz", - "integrity": "sha512-hm2nYpDrwoO/OzBhdcqs/XGT6XjSuSSCVEpia+Kl2J6x4CYt5hISlVL/AYU1khoDXv0AQVgxtdJySb9gjAn56Q==", + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.9.tgz", + "integrity": "sha512-xcinL3AuDJk7VSzsHgb9DvvIXayBbadtMZ4HFPx8rUszbW1MuNMlwYVC4zzCZ6e1sqZpnNS5ZFYOhXqA39T7LQ==", "dev": true }, "js-tokens": { @@ -6299,6 +6302,15 @@ "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", "dev": true }, + "map-age-cleaner": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz", + "integrity": "sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -6312,9 +6324,9 @@ "dev": true }, "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", "dev": true }, "map-visit": { @@ -6343,9 +6355,9 @@ } }, "mdn-data": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz", - "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.2.0.tgz", + "integrity": "sha512-esDqNvsJB2q5V28+u7NdtdMg6Rmg4khQmAVSjUiX7BY/7haIv0K2yWM43hYp0or+3nvG7+UaTF1JHz31hgU1TA==", "dev": true }, "mem": { @@ -6556,7 +6568,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -6661,7 +6673,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -6670,7 +6682,7 @@ "dependencies": { "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true } @@ -6715,6 +6727,20 @@ "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", "dev": true }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "has-flag": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", @@ -6915,7 +6941,7 @@ }, "buffer": { "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -7275,7 +7301,7 @@ }, "os-locale": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { @@ -7285,7 +7311,8 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true }, "osenv": { "version": "0.1.5", @@ -7303,6 +7330,12 @@ "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", "dev": true }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, "p-event": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-event/-/p-event-1.3.0.tgz", @@ -7317,6 +7350,12 @@ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, + "p-is-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", + "dev": true + }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -8217,7 +8256,7 @@ }, "yargs": { "version": "11.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", "dev": true, "requires": { @@ -8860,9 +8899,9 @@ } }, "rxjs": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz", - "integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==", + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.2.tgz", + "integrity": "sha512-hV7criqbR0pe7EeL3O66UYVg92IR0XsA97+9y+BWTePK9SKmEI5Qd3Zj6uPnGkNzXsBywBQWTvujPl+1Kn9Zjw==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -8963,7 +9002,7 @@ "dependencies": { "commander": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz", "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", "dev": true, "requires": { @@ -9344,15 +9383,15 @@ } }, "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz", + "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==", "dev": true }, "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "dev": true, "requires": { "through": "2" @@ -9527,12 +9566,13 @@ } }, "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", "dev": true, "requires": { - "duplexer": "~0.1.1" + "duplexer": "~0.1.1", + "through": "~2.3.4" } }, "stream-combiner2": { @@ -10016,6 +10056,7 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, "requires": { "os-tmpdir": "~1.0.2" } @@ -10301,9 +10342,9 @@ "dev": true }, "uglify-js": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.8.tgz", - "integrity": "sha512-WatYTD84gP/867bELqI2F/2xC9PQBETn/L+7RGq9MQOA/7yFBNvY1UwXqvtILeE6n0ITwBXxp34M0/o70dzj6A==", + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", "dev": true, "requires": { "commander": "~2.17.1", @@ -10929,16 +10970,6 @@ "ajv": "^6.1.0", "ajv-keywords": "^3.1.0" } - }, - "webpack-sources": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.2.0.tgz", - "integrity": "sha512-9BZwxR85dNsjWz3blyxdOhTgtnQvv3OEs5xofI0wPYTwu5kaWxS08UuD1oI7WLBLpRO+ylf0ofnXLXWmGb2WMw==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } } } }, @@ -11027,31 +11058,18 @@ } }, "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", "dev": true, "requires": { - "cross-spawn": "^5.0.1", + "cross-spawn": "^6.0.0", "get-stream": "^3.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", "signal-exit": "^3.0.0", "strip-eof": "^1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - } } }, "find-up": { @@ -11063,12 +11081,27 @@ "locate-path": "^3.0.0" } }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -11079,15 +11112,26 @@ "path-exists": "^3.0.0" } }, + "mem": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", + "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^1.1.0" + } + }, "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", + "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", "dev": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "execa": "^0.10.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" } }, "p-limit": { @@ -11155,16 +11199,16 @@ "dev": true }, "yargs": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.1.tgz", - "integrity": "sha512-B0vRAp1hRX4jgIOWFtjfNjd9OA9RWYZ6tqGA9/I/IrTMsxmKvtWy+ersM+jzpQqbC3YfLzeABPdeTgcJ9eu1qQ==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", + "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", "dev": true, "requires": { "cliui": "^4.0.0", "decamelize": "^2.0.0", "find-up": "^3.0.0", "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", + "os-locale": "^3.0.0", "require-directory": "^2.1.1", "require-main-filename": "^1.0.1", "set-blocking": "^2.0.0", @@ -11186,9 +11230,9 @@ } }, "webpack-deep-scope-analysis": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/webpack-deep-scope-analysis/-/webpack-deep-scope-analysis-1.5.2.tgz", - "integrity": "sha512-10Gna02usg2HSfx14MsTj+OojaenSewMSVjdTNFUndEE7yCKjEiIAH1N9V+bV1oypIvhTqmfKQToR2y6ppOclA==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/webpack-deep-scope-analysis/-/webpack-deep-scope-analysis-1.5.4.tgz", + "integrity": "sha512-jnj1XNSO3eNBVZFhyHLFTD4sAxAtWMum0bdLF3UkMWSgVD2AhZMoXzfP2u4QSt2obaFDmTBMar5bIBnDpk+ZzA==", "dev": true, "requires": { "esrecurse": "^4.2.1", @@ -11211,9 +11255,9 @@ "dev": true }, "webpack-sources": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", - "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.2.0.tgz", + "integrity": "sha512-9BZwxR85dNsjWz3blyxdOhTgtnQvv3OEs5xofI0wPYTwu5kaWxS08UuD1oI7WLBLpRO+ylf0ofnXLXWmGb2WMw==", "dev": true, "requires": { "source-list-map": "^2.0.0", @@ -11254,7 +11298,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { diff --git a/package.json b/package.json index 58ec54c..bfcb57a 100644 --- a/package.json +++ b/package.json @@ -1801,6 +1801,11 @@ "category": "GitLens" }, { + "command": "gitlens.explorers.exploreRepoRevision", + "title": "Explore the Repository from Here", + "category": "GitLens" + }, + { "command": "gitlens.explorers.openDirectoryDiff", "title": "Open Directory Compare", "category": "GitLens" @@ -2383,6 +2388,10 @@ "when": "gitlens:enabled" }, { + "command": "gitlens.explorers.exploreRepoRevision", + "when": "false" + }, + { "command": "gitlens.explorers.openChanges", "when": "false" }, @@ -3031,7 +3040,7 @@ { "command": "gitlens.explorers.openDirectoryDiffWithWorking", "when": "viewItem =~ /gitlens:(branch|tag)\\b/", - "group": "7_gitlens_diff@1" + "group": "7_gitlens_more@2" }, { "command": "gitlens.explorers.terminalCheckoutBranch", @@ -3239,6 +3248,11 @@ "group": "8_gitlens@1" }, { + "command": "gitlens.explorers.exploreRepoRevision", + "when": "viewItem =~ /gitlens:(branch|commit|file:(commit|status)|stash|tag)\\b/", + "group": "7_gitlens_more@1" + }, + { "command": "gitlens.openRepoInRemote", "when": "viewItem == gitlens:repository && gitlens:hasRemotes", "group": "1_gitlens@1" @@ -3582,13 +3596,11 @@ "iconv-lite": "0.4.24", "lodash.debounce": "4.0.8", "lodash.once": "4.1.1", - "tmp": "0.0.33", "tslib": "1.9.3" }, "devDependencies": { "@types/clipboardy": "1.1.0", "@types/node": "8.10.29", - "@types/tmp": "0.0.33", "clean-webpack-plugin": "0.1.19", "css-loader": "1.0.0", "html-webpack-inline-source-plugin": "0.0.10", diff --git a/src/codelens/gitCodeLensProvider.ts b/src/codelens/gitCodeLensProvider.ts index e37f76c..d4b45b1 100644 --- a/src/codelens/gitCodeLensProvider.ts +++ b/src/codelens/gitCodeLensProvider.ts @@ -86,7 +86,7 @@ export class GitCodeLensProvider implements CodeLensProvider { static selector: DocumentSelector = [ { scheme: DocumentSchemes.File }, { scheme: DocumentSchemes.Git }, - { scheme: DocumentSchemes.GitLensGit } + { scheme: DocumentSchemes.GitLens } ]; constructor( diff --git a/src/commands/common.ts b/src/commands/common.ts index 983408d..5e2826c 100644 --- a/src/commands/common.ts +++ b/src/commands/common.ts @@ -397,13 +397,8 @@ export async function openEditor( uri = uri.documentUri({ noSha: true }); } - // This is a bit of an ugly hack, but I added it because there a bunch of call sites and toRevisionUri can't be easily made async because of its use in ctors - if (uri.scheme === DocumentSchemes.GitLensGit && ImageMimetypes[path.extname(uri.fsPath)]) { - const gitUri = GitUri.fromRevisionUri(uri); - const imageUri = await Container.git.getVersionedFile(gitUri.repoPath, gitUri.fsPath, gitUri.sha); - if (imageUri !== undefined) { - await commands.executeCommand(BuiltInCommands.Open, imageUri); - } + if (uri.scheme === DocumentSchemes.GitLens && ImageMimetypes[path.extname(uri.fsPath)]) { + await commands.executeCommand(BuiltInCommands.Open, uri); return undefined; } @@ -430,3 +425,14 @@ export async function openEditor( return undefined; } } + +export function openWorkspace(uri: Uri, name: string, options: { openInNewWindow?: boolean } = {}) { + if (options.openInNewWindow) { + commands.executeCommand(BuiltInCommands.OpenFolder, uri, true); + + return true; + } + + const count = (workspace.workspaceFolders && workspace.workspaceFolders.length) || 0; + return workspace.updateWorkspaceFolders(count, 0, { uri, name }); +} diff --git a/src/constants.ts b/src/constants.ts index ad267e6..df65e5f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -16,6 +16,7 @@ export enum BuiltInCommands { ExecuteDocumentSymbolProvider = 'vscode.executeDocumentSymbolProvider', ExecuteCodeLensProvider = 'vscode.executeCodeLensProvider', Open = 'vscode.open', + OpenFolder = 'vscode.openFolder', NextEditor = 'workbench.action.nextEditor', PreviewHtml = 'vscode.previewHtml', RevealLine = 'revealLine', @@ -48,7 +49,7 @@ export enum DocumentSchemes { DebugConsole = 'debug', File = 'file', Git = 'git', - GitLensGit = 'gitlens-git', + GitLens = 'gitlens', Output = 'output' } diff --git a/src/container.ts b/src/container.ts index 1d550d9..b34c877 100644 --- a/src/container.ts +++ b/src/container.ts @@ -1,10 +1,10 @@ 'use strict'; -import { Disposable, ExtensionContext, languages, workspace } from 'vscode'; +import { Disposable, ExtensionContext } from 'vscode'; import { FileAnnotationController } from './annotations/fileAnnotationController'; import { LineAnnotationController } from './annotations/lineAnnotationController'; import { CodeLensController } from './codelens/codeLensController'; import { configuration, IConfig } from './configuration'; -import { GitContentProvider } from './gitContentProvider'; +import { GitFileSystemProvider } from './git/fsProvider'; import { GitService } from './gitService'; import { LineHoverController } from './hovers/lineHoverController'; import { Keyboard } from './keyboard'; @@ -65,9 +65,7 @@ export class Container { }); } - context.subscriptions.push( - workspace.registerTextDocumentContentProvider(GitContentProvider.scheme, new GitContentProvider()) - ); + context.subscriptions.push(new GitFileSystemProvider()); } private static _codeLensController: CodeLensController; diff --git a/src/git/fsProvider.ts b/src/git/fsProvider.ts new file mode 100644 index 0000000..200c44e --- /dev/null +++ b/src/git/fsProvider.ts @@ -0,0 +1,172 @@ +'use strict'; +import { + Disposable, + Event, + EventEmitter, + FileChangeEvent, + FileStat, + FileSystemError, + FileSystemProvider, + FileType, + Uri, + workspace +} from 'vscode'; +import { DocumentSchemes } from '../constants'; +import { Container } from '../container'; +import { GitService, GitTree, GitUri } from '../gitService'; +import { Iterables, TernarySearchTree } from '../system'; + +export function fromGitLensFSUri(uri: Uri): { path: string; ref: string; repoPath: string } { + const gitUri = uri instanceof GitUri ? uri : GitUri.fromRevisionUri(uri); + return { path: gitUri.getRelativePath(), ref: gitUri.sha!, repoPath: gitUri.repoPath! }; +} + +export function toGitLensFSUri(ref: string, repoPath: string): Uri { + return GitUri.toRevisionUri(ref, repoPath, repoPath); +} + +const emptyArray = new Uint8Array(0); + +export class GitFileSystemProvider implements FileSystemProvider, Disposable { + private readonly _disposable: Disposable; + private readonly _searchTreeMap = new Map>>(); + + constructor() { + this._disposable = Disposable.from( + workspace.registerFileSystemProvider(DocumentSchemes.GitLens, this, { + isCaseSensitive: true, + isReadonly: true + }) + ); + } + + dispose() { + this._disposable && this._disposable.dispose(); + } + + private _onDidChangeFile = new EventEmitter(); + get onDidChangeFile(): Event { + return this._onDidChangeFile.event; + } + + copy?(): void | Thenable { + throw FileSystemError.NoPermissions; + } + createDirectory(): void | Thenable { + throw FileSystemError.NoPermissions; + } + delete(): void | Thenable { + throw FileSystemError.NoPermissions; + } + + async readDirectory(uri: Uri): Promise<[string, FileType][]> { + const tree = await this.getTree(uri); + if (tree === undefined) { + debugger; + throw FileSystemError.FileNotFound(uri); + } + + const items = [...Iterables.map(tree, t => [t.path, typeToFileType(t.type)])]; + return items; + } + + async readFile(uri: Uri): Promise { + const { path, ref, repoPath } = fromGitLensFSUri(uri); + + if (ref === GitService.deletedSha) return emptyArray; + + const buffer = await Container.git.getVersionedFileBuffer(repoPath, path, ref); + if (buffer === undefined) return emptyArray; + + return buffer; + } + + rename(): void | Thenable { + throw FileSystemError.NoPermissions; + } + + async stat(uri: Uri): Promise { + const { path, ref, repoPath } = fromGitLensFSUri(uri); + + if (ref === GitService.deletedSha) { + return { + type: FileType.File, + size: 0, + ctime: 0, + mtime: 0 + }; + } + + let treeItem; + + const searchTree = this._searchTreeMap.get(ref); + if (searchTree !== undefined) { + // Add the fake root folder to the path + treeItem = (await searchTree).get(`/~/${path}`); + } + else { + treeItem = await Container.git.getTreeFileForRevision(repoPath, path, ref); + } + + if (treeItem === undefined) { + throw FileSystemError.FileNotFound(uri); + } + + return { + type: typeToFileType(treeItem.type), + size: treeItem.size, + ctime: 0, + mtime: 0 + }; + } + + watch(): Disposable { + return { dispose: () => {} }; + } + + writeFile(): void | Thenable { + throw FileSystemError.NoPermissions; + } + + private async createSearchTree(ref: string, repoPath: string) { + const searchTree = TernarySearchTree.forPaths() as TernarySearchTree; + const trees = await Container.git.getTreeForRevision(repoPath, ref); + + // Add a fake root folder so that searches will work + searchTree.set(`~`, { commitSha: '', path: '~', size: 0, type: 'tree' }); + for (const item of trees) { + searchTree.set(`~/${item.path}`, item); + } + + return searchTree; + } + + private async getOrCreateSearchTree(ref: string, repoPath: string) { + let searchTree = this._searchTreeMap.get(ref); + if (searchTree === undefined) { + searchTree = this.createSearchTree(ref, repoPath); + this._searchTreeMap.set(ref, searchTree); + } + + return searchTree; + } + + private async getTree(uri: Uri) { + const { path, ref, repoPath } = fromGitLensFSUri(uri); + + const searchTree = await this.getOrCreateSearchTree(ref, repoPath); + // Add the fake root folder to the path + return searchTree!.findSuperstr(`/~/${path}`, true); + } +} + +function typeToFileType(type: 'blob' | 'tree' | undefined | null) { + switch (type) { + case 'blob': + return FileType.File; + case 'tree': + return FileType.Directory; + default: + return FileType.Unknown; + } +} diff --git a/src/git/git.ts b/src/git/git.ts index a5d1f7a..fb03479 100644 --- a/src/git/git.ts +++ b/src/git/git.ts @@ -1,5 +1,4 @@ 'use strict'; -import * as fs from 'fs'; import * as iconv from 'iconv-lite'; import * as path from 'path'; import { GlyphChars } from '../constants'; @@ -71,9 +70,9 @@ interface GitCommandOptions extends RunOptions { } // A map of running git commands -- avoids running duplicate overlaping commands -const pendingCommands: Map> = new Map(); +const pendingCommands: Map> = new Map(); -async function git(options: GitCommandOptions, ...args: any[]): Promise { +async function git(options: GitCommandOptions, ...args: any[]): Promise { const start = process.hrtime(); const { correlationKey, exceptionHandler, ...opts } = options; @@ -81,7 +80,7 @@ async function git(options: GitCommandOptions, ...args: any[]): Promise const encoding = options.encoding || 'utf8'; const runOpts = { ...opts, - encoding: encoding === 'utf8' ? 'utf8' : 'binary', + encoding: encoding === 'utf8' ? 'utf8' : encoding === 'buffer' ? 'buffer' : 'binary', // Adds GCM environment variables to avoid any possible credential issues -- from https://github.com/Microsoft/vscode/issues/26573#issuecomment-338686581 // Shouldn't *really* be needed but better safe than sorry env: { ...(options.env || process.env), GCM_INTERACTIVE: 'NEVER', GCM_PRESERVE_CREDS: 'TRUE', LC_ALL: 'C' } @@ -99,7 +98,7 @@ async function git(options: GitCommandOptions, ...args: any[]): Promise // See https://stackoverflow.com/questions/4144417/how-to-handle-asian-characters-in-file-names-in-git-on-os-x args.splice(0, 0, '-c', 'core.quotepath=false', '-c', 'color.ui=false'); - promise = run(gitInfo.path, args, encoding, runOpts); + promise = run(gitInfo.path, args, encoding, runOpts); pendingCommands.set(command, promise); } @@ -109,19 +108,19 @@ async function git(options: GitCommandOptions, ...args: any[]): Promise let exception: Error | undefined; try { - return await promise; + return (await promise) as TOut; } catch (ex) { exception = ex; if (exceptionHandler !== undefined) { const result = exceptionHandler(ex); exception = undefined; - return result; + return result as TOut; } const result = defaultExceptionHandler(ex, options, ...args); exception = undefined; - return result; + return result as TOut; } finally { pendingCommands.delete(command); @@ -196,47 +195,47 @@ export class Git { ); } - static async getVersionedFile(repoPath: string | undefined, fileName: string, ref: string) { - const data = await Git.show(repoPath, fileName, ref, { encoding: 'binary' }); - if (data === undefined) return undefined; - - if (Git.isStagedUncommitted(ref)) { - ref = ''; - } - - const suffix = Strings.truncate( - Strings.sanitizeForFileSystem(Git.isSha(ref) ? Git.shortenSha(ref)! : ref), - 50, - '' - ); - const ext = path.extname(fileName); - - const tmp = await import('tmp'); - return new Promise((resolve, reject) => { - tmp.file( - { prefix: `${path.basename(fileName, ext)}-${suffix}__`, postfix: ext }, - (err, destination, fd, cleanupCallback) => { - if (err) { - reject(err); - return; - } - - Logger.log(`getVersionedFile[${destination}]('${repoPath}', '${fileName}', ${ref})`); - fs.appendFile(destination, data, { encoding: 'binary' }, err => { - if (err) { - reject(err); - return; - } - - const ReadOnly = 0o100444; // 33060 0b1000000100100100 - fs.chmod(destination, ReadOnly, err => { - resolve(destination); - }); - }); - } - ); - }); - } + // static async getVersionedFile(repoPath: string | undefined, fileName: string, ref: string) { + // const buffer = await Git.show(repoPath, fileName, ref, { encoding: 'buffer' }); + // if (buffer === undefined) return undefined; + + // if (Git.isStagedUncommitted(ref)) { + // ref = ''; + // } + + // const suffix = Strings.truncate( + // Strings.sanitizeForFileSystem(Git.isSha(ref) ? Git.shortenSha(ref)! : ref), + // 50, + // '' + // ); + // const ext = path.extname(fileName); + + // const tmp = await import('tmp'); + // return new Promise((resolve, reject) => { + // tmp.file( + // { prefix: `${path.basename(fileName, ext)}-${suffix}__`, postfix: ext }, + // (err, destination, fd, cleanupCallback) => { + // if (err) { + // reject(err); + // return; + // } + + // Logger.log(`getVersionedFile[${destination}]('${repoPath}', '${fileName}', ${ref})`); + // fs.appendFile(destination, buffer, { encoding: 'binary' }, err => { + // if (err) { + // reject(err); + // return; + // } + + // const ReadOnly = 0o100444; // 33060 0b1000000100100100 + // fs.chmod(destination, ReadOnly, err => { + // resolve(destination); + // }); + // }); + // } + // ); + // }); + // } static isResolveRequired(sha: string) { return Git.isSha(sha) && !Git.shaStrictRegex.test(sha); @@ -325,14 +324,14 @@ export class Git { params.push('--contents', '-'); // Get the file contents for the staged version using `:` - stdin = await Git.show(repoPath, fileName, ':'); + stdin = await Git.show(repoPath, fileName, ':'); } else { params.push(sha); } } - return git({ cwd: root, stdin: stdin }, ...params, '--', file); + return git({ cwd: root, stdin: stdin }, ...params, '--', file); } static async blame_contents( @@ -364,7 +363,12 @@ export class Git { // Pipe the blame contents to stdin params.push('--contents', '-'); - return git({ cwd: root, stdin: contents, correlationKey: options.correlationKey }, ...params, '--', file); + return git( + { cwd: root, stdin: contents, correlationKey: options.correlationKey }, + ...params, + '--', + file + ); } static branch(repoPath: string, options: { all: boolean } = { all: false }) { @@ -373,7 +377,7 @@ export class Git { params.push('-a'); } - return git({ cwd: repoPath }, ...params); + return git({ cwd: repoPath }, ...params); } static branch_contains(repoPath: string, ref: string, options: { remote: boolean } = { remote: false }) { @@ -382,21 +386,21 @@ export class Git { params.push('-r'); } - return git({ cwd: repoPath }, ...params, ref); + return git({ cwd: repoPath }, ...params, ref); } static check_mailmap(repoPath: string, author: string) { - return git({ cwd: repoPath }, 'check-mailmap', author); + return git({ cwd: repoPath }, 'check-mailmap', author); } static checkout(repoPath: string, fileName: string, sha: string) { const [file, root] = Git.splitPath(fileName, repoPath); - return git({ cwd: root }, 'checkout', sha, '--', file); + return git({ cwd: root }, 'checkout', sha, '--', file); } static async config_get(key: string, repoPath?: string) { - const data = await git( + const data = await git( { cwd: repoPath || '', exceptionHandler: ignoreExceptionsHandler }, 'config', '--get', @@ -406,7 +410,7 @@ export class Git { } static async config_getRegex(pattern: string, repoPath?: string) { - const data = await git( + const data = await git( { cwd: repoPath || '', exceptionHandler: ignoreExceptionsHandler }, 'config', '--get-regex', @@ -425,7 +429,7 @@ export class Git { } const encoding: BufferEncoding = options.encoding === 'utf8' ? 'utf8' : 'binary'; - return git({ cwd: repoPath, encoding: encoding }, ...params, '--', fileName); + return git({ cwd: repoPath, encoding: encoding }, ...params, '--', fileName); } static diff_nameStatus(repoPath: string, sha1?: string, sha2?: string, options: { filter?: string } = {}) { @@ -440,7 +444,7 @@ export class Git { params.push(sha2); } - return git({ cwd: repoPath }, ...params); + return git({ cwd: repoPath }, ...params); } static diff_shortstat(repoPath: string, sha?: string) { @@ -448,7 +452,7 @@ export class Git { if (sha) { params.push(sha); } - return git({ cwd: repoPath }, ...params); + return git({ cwd: repoPath }, ...params); } static difftool_dirDiff(repoPath: string, tool: string, ref1: string, ref2?: string) { @@ -457,7 +461,7 @@ export class Git { params.push(ref2); } - return git({ cwd: repoPath }, ...params); + return git({ cwd: repoPath }, ...params); } static difftool_fileDiff(repoPath: string, fileName: string, tool: string, staged: boolean) { @@ -467,7 +471,7 @@ export class Git { } params.push('--', fileName); - return git({ cwd: repoPath }, ...params); + return git({ cwd: repoPath }, ...params); } static log(repoPath: string, options: { author?: string; maxCount?: number; ref?: string; reverse?: boolean }) { @@ -486,7 +490,7 @@ export class Git { params.push(options.ref); } } - return git({ cwd: repoPath }, ...params, '--'); + return git({ cwd: repoPath }, ...params, '--'); } static log_file( @@ -525,11 +529,11 @@ export class Git { params.push(`-L ${options.startLine},${options.endLine}:${file}`); } - return git({ cwd: root }, ...params, '--', file); + return git({ cwd: root }, ...params, '--', file); } static async log_recent(repoPath: string, fileName: string) { - const data = await git( + const data = await git( { cwd: repoPath, exceptionHandler: ignoreExceptionsHandler }, 'log', '-M', @@ -542,7 +546,7 @@ export class Git { } static async log_resolve(repoPath: string, fileName: string, ref: string) { - const data = await git( + const data = await git( { cwd: repoPath, exceptionHandler: ignoreExceptionsHandler }, 'log', '-M', @@ -561,7 +565,7 @@ export class Git { params.push(`-n${options.maxCount}`); } - return git({ cwd: repoPath }, ...params, ...search); + return git({ cwd: repoPath }, ...params, ...search); } static log_shortstat(repoPath: string, options: { ref?: string }) { @@ -569,7 +573,7 @@ export class Git { if (options.ref && !Git.isStagedUncommitted(options.ref)) { params.push(options.ref); } - return git({ cwd: repoPath }, ...params, '--'); + return git({ cwd: repoPath }, ...params, '--'); } static async ls_files( @@ -582,7 +586,23 @@ export class Git { params.push(`--with-tree=${options.ref}`); } - const data = await git({ cwd: repoPath, exceptionHandler: ignoreExceptionsHandler }, ...params, fileName); + const data = await git( + { cwd: repoPath, exceptionHandler: ignoreExceptionsHandler }, + ...params, + fileName + ); + return data === '' ? undefined : data.trim(); + } + + static async ls_tree(repoPath: string, ref: string, options: { fileName?: string } = {}) { + const params = ['ls-tree']; + if (options.fileName) { + params.push('-l', ref, '--', options.fileName); + } + else { + params.push('-lrt', ref, '--'); + } + const data = await git({ cwd: repoPath, exceptionHandler: ignoreExceptionsHandler }, ...params); return data === '' ? undefined : data.trim(); } @@ -592,19 +612,19 @@ export class Git { params.push('--fork-point'); } - return git({ cwd: repoPath }, ...params, ref1, ref2); + return git({ cwd: repoPath }, ...params, ref1, ref2); } static remote(repoPath: string): Promise { - return git({ cwd: repoPath }, 'remote', '-v'); + return git({ cwd: repoPath }, 'remote', '-v'); } static remote_url(repoPath: string, remote: string): Promise { - return git({ cwd: repoPath }, 'remote', 'get-url', remote); + return git({ cwd: repoPath }, 'remote', 'get-url', remote); } static async revparse(repoPath: string, ref: string): Promise { - const data = await git({ cwd: repoPath, exceptionHandler: ignoreExceptionsHandler }, 'rev-parse', ref); + const data = await git({ cwd: repoPath, exceptionHandler: ignoreExceptionsHandler }, 'rev-parse', ref); return data === '' ? undefined : data.trim(); } @@ -617,13 +637,13 @@ export class Git { } as GitCommandOptions; try { - const data = await git(opts, ...params); + const data = await git(opts, ...params); return [data, undefined]; } catch (ex) { const msg = ex && ex.toString(); if (GitWarnings.headNotABranch.test(msg)) { - const data = await git( + const data = await git( { ...opts, exceptionHandler: ignoreExceptionsHandler }, 'log', '-n1', @@ -641,7 +661,7 @@ export class Git { if (result !== null) return [result[1], undefined]; if (GitWarnings.unknownRevision.test(msg)) { - const data = await git( + const data = await git( { ...opts, exceptionHandler: ignoreExceptionsHandler }, 'symbolic-ref', '-q', @@ -657,16 +677,20 @@ export class Git { } static async revparse_toplevel(cwd: string): Promise { - const data = await git({ cwd: cwd, exceptionHandler: ignoreExceptionsHandler }, 'rev-parse', '--show-toplevel'); + const data = await git( + { cwd: cwd, exceptionHandler: ignoreExceptionsHandler }, + 'rev-parse', + '--show-toplevel' + ); return data === '' ? undefined : data.trim(); } - static async show( + static async show( repoPath: string | undefined, fileName: string, ref: string, options: { encoding?: string } = {} - ): Promise { + ): Promise { const [file, root] = Git.splitPath(fileName, repoPath); if (Git.isStagedUncommitted(ref)) { @@ -682,13 +706,13 @@ export class Git { const args = ref.endsWith(':') ? `${ref}./${file}` : `${ref}:./${file}`; try { - const data = await git(opts, 'show', args, '--'); + const data = await git(opts, 'show', args, '--'); // , '--binary', '--no-textconv' return data; } catch (ex) { const msg = ex && ex.toString(); if (ref === ':' && GitErrors.badRevision.test(msg)) { - return Git.show(repoPath, fileName, 'HEAD:', options); + return Git.show(repoPath, fileName, 'HEAD:', options); } if ( @@ -699,22 +723,22 @@ export class Git { return undefined; } - return defaultExceptionHandler(ex, opts, args); + return defaultExceptionHandler(ex, opts, args) as TOut; } } static stash_apply(repoPath: string, stashName: string, deleteAfter: boolean) { if (!stashName) return undefined; - return git({ cwd: repoPath }, 'stash', deleteAfter ? 'pop' : 'apply', stashName); + return git({ cwd: repoPath }, 'stash', deleteAfter ? 'pop' : 'apply', stashName); } static stash_delete(repoPath: string, stashName: string) { if (!stashName) return undefined; - return git({ cwd: repoPath }, 'stash', 'drop', stashName); + return git({ cwd: repoPath }, 'stash', 'drop', stashName); } static stash_list(repoPath: string) { - return git({ cwd: repoPath }, ...defaultStashParams); + return git({ cwd: repoPath }, ...defaultStashParams); } static stash_push(repoPath: string, pathspecs: string[], message?: string) { @@ -723,7 +747,7 @@ export class Git { params.push('-m', message); } params.splice(params.length, 0, '--', ...pathspecs); - return git({ cwd: repoPath }, ...params); + return git({ cwd: repoPath }, ...params); } static stash_save(repoPath: string, message?: string) { @@ -731,12 +755,12 @@ export class Git { if (message) { params.push(message); } - return git({ cwd: repoPath }, ...params); + return git({ cwd: repoPath }, ...params); } static status(repoPath: string, porcelainVersion: number = 1): Promise { const porcelain = porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain'; - return git( + return git( { cwd: repoPath, env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' } }, '-c', 'color.status=false', @@ -751,7 +775,7 @@ export class Git { const [file, root] = Git.splitPath(fileName, repoPath); const porcelain = porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain'; - return git( + return git( { cwd: root, env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' } }, '-c', 'color.status=false', @@ -763,6 +787,6 @@ export class Git { } static tag(repoPath: string) { - return git({ cwd: repoPath }, 'tag', '-l', '-n1'); + return git({ cwd: repoPath }, 'tag', '-l', '-n1'); } } diff --git a/src/git/gitLocator.ts b/src/git/gitLocator.ts index 2bcf8c6..a2d3c98 100644 --- a/src/git/gitLocator.ts +++ b/src/git/gitLocator.ts @@ -13,7 +13,7 @@ function parseVersion(raw: string): string { } async function findSpecificGit(path: string): Promise { - const version = await run(path, ['--version'], 'utf8'); + const version = await run(path, ['--version'], 'utf8'); // If needed, let's update our path to avoid the search on every command if (!path || path === 'git') { path = findExecutable(path, ['--version']).cmd; @@ -27,7 +27,7 @@ async function findSpecificGit(path: string): Promise { async function findGitDarwin(): Promise { try { - let path = await run('which', ['git'], 'utf8'); + let path = await run('which', ['git'], 'utf8'); path = path.replace(/^\s+|\s+$/g, ''); if (path !== '/usr/bin/git') { @@ -35,7 +35,7 @@ async function findGitDarwin(): Promise { } try { - await run('xcode-select', ['-p'], 'utf8'); + await run('xcode-select', ['-p'], 'utf8'); return findSpecificGit(path); } catch (ex) { diff --git a/src/git/gitUri.ts b/src/git/gitUri.ts index 14740ac..5fcb105 100644 --- a/src/git/gitUri.ts +++ b/src/git/gitUri.ts @@ -30,6 +30,8 @@ interface UriEx { new (components: UriComponents): Uri; } +const stripRepoRevisionFromPathRegex = /\/[^\/]+\/?(.*)/; + export class GitUri extends ((Uri as any) as UriEx) { repoPath?: string; sha?: string; @@ -45,18 +47,25 @@ export class GitUri extends ((Uri as any) as UriEx) { return; } - if (uri.scheme === DocumentSchemes.GitLensGit) { - const data: IUriRevisionData = JSON.parse(uri.query); + if (uri.scheme === DocumentSchemes.GitLens) { + const data = JSON.parse(uri.query) as IUriRevisionData; - const [authority, fsPath] = GitUri.ensureValidUNCPath( - uri.authority, - path.resolve(data.repoPath, data.fileName) + data.repoPath = Strings.normalizePath(data.repoPath); + data.path = Strings.normalizePath( + `/${data.repoPath}/${uri.path.replace(stripRepoRevisionFromPathRegex, '$1')}` ); - super({ scheme: uri.scheme, authority: authority, path: fsPath, query: uri.query, fragment: uri.fragment }); + + super({ + scheme: uri.scheme, + authority: uri.authority, + path: data.path, + query: JSON.stringify(data), + fragment: uri.fragment + }); this.repoPath = data.repoPath; - if (GitService.isStagedUncommitted(data.sha) || !GitService.isUncommitted(data.sha)) { - this.sha = data.sha; + if (GitService.isStagedUncommitted(data.ref) || !GitService.isUncommitted(data.ref)) { + this.sha = data.ref; } return; @@ -131,7 +140,10 @@ export class GitUri extends ((Uri as any) as UriEx) { private static ensureValidUNCPath(authority: string, fsPath: string): [string, string] { // Taken from https://github.com/Microsoft/vscode/blob/master/src/vs/base/common/uri.ts#L239-L251 // check for authority as used in UNC shares or use the path as given - if (fsPath[0] === '\\' && fsPath[1] === '\\') { + if ( + fsPath.charCodeAt(0) === Strings.CharCode.Backslash && + fsPath.charCodeAt(1) === Strings.CharCode.Backslash + ) { const index = fsPath.indexOf('\\', 2); if (index === -1) { authority = fsPath.substring(2); @@ -175,7 +187,7 @@ export class GitUri extends ((Uri as any) as UriEx) { if (!Container.git.isTrackable(uri)) return new GitUri(uri); - if (uri.scheme === DocumentSchemes.GitLensGit) return new GitUri(uri); + if (uri.scheme === DocumentSchemes.GitLens) return new GitUri(uri); // If this is a git uri, find its repoPath if (uri.scheme === DocumentSchemes.Git) { @@ -300,23 +312,28 @@ export class GitUri extends ((Uri as any) as UriEx) { shortSha = uriOrSha.shortSha; } + repoPath = Strings.normalizePath(repoPath!); + const repoName = path.basename(repoPath); const data: IUriRevisionData = { - fileName: Strings.normalizePath(path.relative(repoPath!, fileName)), - repoPath: repoPath!, - sha: sha + path: Strings.normalizePath(fileName, { addLeadingSlash: true }), + ref: sha, + repoPath: repoPath }; - const parsed = path.parse(fileName); - return Uri.parse( - `${DocumentSchemes.GitLensGit}:${path.join(parsed.dir, parsed.name)} (${shortSha})${ - parsed.ext - }?${JSON.stringify(data)}` + let filePath = Strings.normalizePath(path.relative(repoPath, fileName), { addLeadingSlash: true }); + if (filePath === '/') { + filePath = ''; + } + + const uri = Uri.parse( + `${DocumentSchemes.GitLens}://git/${repoName}@${shortSha}${filePath}?${JSON.stringify(data)}` ); + return uri; } } interface IUriRevisionData { - sha?: string; - fileName: string; + path: string; + ref?: string; repoPath: string; } diff --git a/src/git/models/models.ts b/src/git/models/models.ts index 2c03bc9..638e1ac 100644 --- a/src/git/models/models.ts +++ b/src/git/models/models.ts @@ -13,3 +13,4 @@ export * from './stash'; export * from './stashCommit'; export * from './status'; export * from './tag'; +export * from './tree'; diff --git a/src/git/models/repository.ts b/src/git/models/repository.ts index 5478659..31d2148 100644 --- a/src/git/models/repository.ts +++ b/src/git/models/repository.ts @@ -12,9 +12,9 @@ import { } from 'vscode'; import { configuration, IRemotesConfig } from '../../configuration'; import { Container } from '../../container'; -import { GitUri } from '../../gitService'; import { Functions } from '../../system'; import { GitBranch, GitDiffShortStat, GitRemote, GitStash, GitStatus, GitTag } from '../git'; +import { GitUri } from '../gitUri'; import { RemoteProviderFactory, RemoteProviderMap } from '../remotes/factory'; export enum RepositoryChange { diff --git a/src/git/models/tree.ts b/src/git/models/tree.ts new file mode 100644 index 0000000..18e631f --- /dev/null +++ b/src/git/models/tree.ts @@ -0,0 +1,8 @@ +'use strict'; + +export interface GitTree { + commitSha: string; + path: string; + size: number; + type: 'blob' | 'tree'; +} diff --git a/src/git/parsers/parsers.ts b/src/git/parsers/parsers.ts index 1366911..bac644d 100644 --- a/src/git/parsers/parsers.ts +++ b/src/git/parsers/parsers.ts @@ -8,3 +8,4 @@ export * from './remoteParser'; export * from './stashParser'; export * from './statusParser'; export * from './tagParser'; +export * from './treeParser'; diff --git a/src/git/parsers/treeParser.ts b/src/git/parsers/treeParser.ts new file mode 100644 index 0000000..7e185bc --- /dev/null +++ b/src/git/parsers/treeParser.ts @@ -0,0 +1,32 @@ +'use strict'; +import { GitTree } from './../git'; + +const treeRegex = /(?:.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+)/gm; + +export class GitTreeParser { + static parse(data: string | undefined): GitTree[] | undefined { + if (!data) return undefined; + + const trees: GitTree[] = []; + + let match: RegExpExecArray | null = null; + do { + match = treeRegex.exec(data); + if (match == null) break; + + const [, type, commitSha, size, filePath] = match; + trees.push({ + // Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869 + commitSha: commitSha === undefined ? '' : (' ' + commitSha).substr(1), + path: filePath === undefined ? '' : filePath, + size: size === '-' ? 0 : Number(size || 0), + // Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869 + type: (type === undefined ? '' : (' ' + type).substr(1)) as 'blob' | 'tree' + }); + } while (match != null); + + if (!trees.length) return undefined; + + return trees; + } +} diff --git a/src/git/remotes/provider.ts b/src/git/remotes/provider.ts index ae2b8a4..5d63d61 100644 --- a/src/git/remotes/provider.ts +++ b/src/git/remotes/provider.ts @@ -1,8 +1,8 @@ 'use strict'; import { commands, Range, Uri, window } from 'vscode'; import { BuiltInCommands } from '../../constants'; -import { GitLogCommit } from '../../gitService'; import { Logger } from '../../logger'; +import { GitLogCommit } from '../models/logCommit'; export enum RemoteResourceType { Branch = 'branch', diff --git a/src/git/shell.ts b/src/git/shell.ts index 69756b7..34951a5 100644 --- a/src/git/shell.ts +++ b/src/git/shell.ts @@ -104,7 +104,7 @@ export class RunError extends Error { export interface RunOptions { readonly cwd?: string; readonly env?: Object; - readonly encoding?: BufferEncoding; + readonly encoding?: BufferEncoding | 'buffer'; /** * The size the output buffer to allocate to the spawned process. Set this * if you are anticipating a large amount of output. @@ -126,10 +126,15 @@ export interface RunOptions { readonly stdinEncoding?: string; } -export function run(command: string, args: any[], encoding: BufferEncoding, options: RunOptions = {}): Promise { +export function run( + command: string, + args: any[], + encoding: BufferEncoding | 'buffer', + options: RunOptions = {} +): Promise { const { stdin, stdinEncoding, ...opts } = { maxBuffer: 100 * 1024 * 1024, ...options } as RunOptions; - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const proc = execFile( command, args, @@ -155,9 +160,9 @@ export function run(command: string, args: any[], encoding: BufferEncoding, opti } resolve( - encoding === 'utf8' || encoding === 'binary' - ? stdout - : iconv.decode(Buffer.from(stdout, 'binary'), encoding) + encoding === 'utf8' || encoding === 'binary' || encoding === 'buffer' + ? (stdout as TOut) + : (iconv.decode(Buffer.from(stdout, 'binary'), encoding) as TOut) ); } ); diff --git a/src/gitContentProvider.ts b/src/gitContentProvider.ts deleted file mode 100644 index 966f075..0000000 --- a/src/gitContentProvider.ts +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; -import * as path from 'path'; -import { CancellationToken, TextDocumentContentProvider, Uri, window, workspace } from 'vscode'; -import { DocumentSchemes } from './constants'; -import { GitService, GitUri } from './gitService'; -import { Logger } from './logger'; - -export class GitContentProvider implements TextDocumentContentProvider { - static scheme = DocumentSchemes.GitLensGit; - - async provideTextDocumentContent(uri: Uri, token: CancellationToken): Promise { - const gitUri = GitUri.fromRevisionUri(uri); - if (!gitUri.repoPath || gitUri.sha === GitService.deletedSha) return ''; - - try { - const document = await workspace.openTextDocument( - Uri.parse(`git:/${gitUri.fsPath}?${JSON.stringify({ path: gitUri.fsPath, ref: gitUri.sha || 'HEAD' })}`) - ); - return document.getText(); - } - catch (ex) { - Logger.error(ex, 'GitContentProvider', 'getVersionedFileText'); - window.showErrorMessage( - `Unable to show Git revision ${GitService.shortenSha(gitUri.sha)} of '${path.relative( - gitUri.repoPath, - gitUri.fsPath - )}'` - ); - return undefined; - } - } -} diff --git a/src/gitService.ts b/src/gitService.ts index 4623718..4d51d53 100644 --- a/src/gitService.ts +++ b/src/gitService.ts @@ -18,7 +18,7 @@ import { } from 'vscode'; import { GitExtension } from './@types/git'; import { configuration, IRemotesConfig } from './configuration'; -import { CommandContext, DocumentSchemes, GlyphChars, ImageMimetypes, setCommandContext } from './constants'; +import { CommandContext, DocumentSchemes, GlyphChars, setCommandContext } from './constants'; import { Container } from './container'; import { CommitFormatting, @@ -49,6 +49,8 @@ import { GitStatusParser, GitTag, GitTagParser, + GitTree, + GitTreeParser, Repository, RepositoryChange } from './git/git'; @@ -191,18 +193,25 @@ export class GitService implements Disposable { if (f.uri.scheme !== DocumentSchemes.File) continue; const fsPath = f.uri.fsPath; - const filteredTree = this._repositoryTree.findSuperstr(fsPath); + const repos = this._repositoryTree.findSuperstr(fsPath); const reposToDelete = - filteredTree !== undefined + repos !== undefined ? // Since the filtered tree will have keys that are relative to the fsPath, normalize to the full path - [ - ...Iterables.map<[Repository, string], [Repository, string]>( - filteredTree.entries(), - ([r, k]) => [r, path.join(fsPath, k)] - ) - ] + [...Iterables.map(repos, r => [r, r.path])] : []; + // const filteredTree = this._repositoryTree.findSuperstr(fsPath); + // const reposToDelete = + // filteredTree !== undefined + // ? // Since the filtered tree will have keys that are relative to the fsPath, normalize to the full path + // [ + // ...Iterables.map<[Repository, string], [Repository, string]>( + // filteredTree.entries(), + // ([r, k]) => [r, path.join(fsPath, k)] + // ) + // ] + // : []; + const repo = this._repositoryTree.get(fsPath); if (repo !== undefined) { reposToDelete.push([repo, fsPath]); @@ -1648,6 +1657,25 @@ export class GitService implements Disposable { return GitTagParser.parse(data, repoPath) || []; } + async getTreeFileForRevision(repoPath: string, fileName: string, ref: string): Promise { + if (repoPath === undefined) return undefined; + + Logger.log(`getTreeFileForRevision('${repoPath}', '${fileName}', '${ref}')`); + + const data = await Git.ls_tree(repoPath, ref, { fileName: fileName }); + const trees = GitTreeParser.parse(data); + return trees === undefined || trees.length === 0 ? undefined : trees[0]; + } + + async getTreeForRevision(repoPath: string, ref: string): Promise { + if (repoPath === undefined) return []; + + Logger.log(`getTreeForRevision('${repoPath}', '${ref}')`); + + const data = await Git.ls_tree(repoPath, ref); + return GitTreeParser.parse(data) || []; + } + async getVersionedFile( repoPath: string | undefined, fileName: string, @@ -1663,27 +1691,21 @@ export class GitService implements Disposable { return undefined; } - if (ImageMimetypes[path.extname(fileName)]) { - const file = await Git.getVersionedFile(repoPath, fileName, sha); - if (file === undefined) return undefined; - - this._versionedUriCache.set( - GitUri.toKey(file), - new GitUri(Uri.file(fileName), { sha: sha, repoPath: repoPath!, versionedPath: file }) - ); - - return Uri.file(file); - } - return GitUri.toRevisionUri(sha, fileName, repoPath!); } - getVersionedFileText(repoPath: string, fileName: string, sha: string) { - Logger.log(`getVersionedFileText('${repoPath}', '${fileName}', ${sha})`); + getVersionedFileBuffer(repoPath: string, fileName: string, sha: string) { + Logger.log(`getVersionedFileBuffer('${repoPath}', '${fileName}', ${sha})`); - return Git.show(repoPath, fileName, sha, { encoding: GitService.getEncoding(repoPath, fileName) }); + return Git.show(repoPath, fileName, sha, { encoding: 'buffer' }); } + // getVersionedFileText(repoPath: string, fileName: string, sha: string) { + // Logger.log(`getVersionedFileText('${repoPath}', '${fileName}', ${sha})`); + + // return Git.show(repoPath, fileName, sha, { encoding: GitService.getEncoding(repoPath, fileName) }); + // } + getVersionedUri(uri: Uri) { return this._versionedUriCache.get(GitUri.toKey(uri)); } @@ -1699,9 +1721,7 @@ export class GitService implements Disposable { scheme = schemeOruri.scheme; } - return ( - scheme === DocumentSchemes.File || scheme === DocumentSchemes.Git || scheme === DocumentSchemes.GitLensGit - ); + return scheme === DocumentSchemes.File || scheme === DocumentSchemes.Git || scheme === DocumentSchemes.GitLens; } async isTracked( diff --git a/src/system/searchTree.ts b/src/system/searchTree.ts index c911435..63430dd 100644 --- a/src/system/searchTree.ts +++ b/src/system/searchTree.ts @@ -1,5 +1,6 @@ 'use strict'; -import { Iterables } from '../system/iterable'; +import { Iterables } from './iterable'; +import { Strings } from './string'; // Code stolen from https://github.com/Microsoft/vscode/blob/b3e6d5bb039a4a9362b52a2c8726267ca68cf64e/src/vs/base/common/map.ts#L352 @@ -42,17 +43,6 @@ export class StringIterator implements IKeyIterator { } } -const enum CharCode { - /** - * The `/` character. - */ - Slash = 47, - /** - * The `\` character. - */ - Backslash = 92 -} - export class PathIterator implements IKeyIterator { private _value!: string; private _from!: number; @@ -75,7 +65,7 @@ export class PathIterator implements IKeyIterator { let justSeps = true; for (; this._to < this._value.length; this._to++) { const ch = this._value.charCodeAt(this._to); - if (ch === CharCode.Slash || ch === CharCode.Backslash) { + if (ch === Strings.CharCode.Slash || ch === Strings.CharCode.Backslash) { if (justSeps) { this._from++; } @@ -301,7 +291,7 @@ export class TernarySearchTree { return (node && node.value) || candidate; } - findSuperstr(key: string): TernarySearchTree | undefined { + findSuperstr(key: string, limit: boolean = false): Iterable | undefined { const iter = this._iter.reset(key); let node = this._root; while (node) { @@ -324,34 +314,70 @@ export class TernarySearchTree { if (!node.mid) { return undefined; } - const ret = new TernarySearchTree(this._iter); - ret._root = node.mid; - return ret; + else { + node = node.mid; + return { + [Symbol.iterator]: () => this._nodeIterator(node!, limit) + }; + } } } return undefined; } + private _nodeIterator(node: TernarySearchTreeNode, limit: boolean = false): Iterator { + const res = { + done: false, + value: undefined! as E + }; + let idx: number; + let data: E[]; + const next = () => { + if (!data) { + // lazy till first invocation + data = []; + idx = 0; + this._forEach(node, value => data.push(value), limit); + } + if (idx >= data.length) { + res.done = true; + res.value = undefined!; + } + else { + res.done = false; + res.value = data[idx++]; + } + return res; + }; + return { next }; + } + forEach(callback: (value: E, index: string) => any) { this._forEach(this._root!, callback); } - private _forEach(node: TernarySearchTreeNode, callback: (value: E, index: string) => any) { + private _forEach( + node: TernarySearchTreeNode, + callback: (value: E, index: string) => any, + limit: boolean = false + ) { if (node === undefined) return; // left - this._forEach(node.left!, callback); + this._forEach(node.left!, callback, limit); // node if (node.value) { callback(node.value, node.key); } - // mid - this._forEach(node.mid!, callback); + if (!limit) { + // mid + this._forEach(node.mid!, callback, limit); + } // right - this._forEach(node.right!, callback); + this._forEach(node.right!, callback, limit); } any(): boolean { diff --git a/src/system/string.ts b/src/system/string.ts index 9e14e57..fc6dcba 100644 --- a/src/system/string.ts +++ b/src/system/string.ts @@ -2,14 +2,26 @@ import { createHash, HexBase64Latin1Encoding } from 'crypto'; export namespace Strings { + export const enum CharCode { + /** + * The `/` character. + */ + Slash = 47, + /** + * The `\` character. + */ + Backslash = 92 + } + export function getDurationMilliseconds(start: [number, number]) { const [secs, nanosecs] = process.hrtime(start); return secs * 1000 + Math.floor(nanosecs / 1000000); } - const pathNormalizer = /\\/g; - const TokenRegex = /\$\{(\W*)?([^|]*?)(?:\|(\d+)(\-|\?)?)?(\W*)?\}/g; - const TokenSanitizeRegex = /\$\{(?:\W*)?(\w*?)(?:[\W\d]*)\}/g; + const pathNormalizeRegex = /\\/g; + const pathStripTrailingSlashRegex = /\/$/g; + const tokenRegex = /\$\{(\W*)?([^|]*?)(?:\|(\d+)(\-|\?)?)?(\W*)?\}/g; + const tokenSanitizeRegex = /\$\{(?:\W*)?(\w*?)(?:[\W\d]*)\}/g; export interface ITokenOptions { collapseWhitespace: boolean; @@ -22,7 +34,7 @@ export namespace Strings { export function getTokensFromTemplate(template: string) { const tokens: { key: string; options: ITokenOptions }[] = []; - let match = TokenRegex.exec(template); + let match = tokenRegex.exec(template); while (match != null) { const [, prefix, key, truncateTo, option, suffix] = match; tokens.push({ @@ -35,7 +47,7 @@ export namespace Strings { truncateTo: truncateTo == null ? undefined : parseInt(truncateTo, 10) } }); - match = TokenRegex.exec(template); + match = tokenRegex.exec(template); } return tokens; @@ -43,9 +55,9 @@ export namespace Strings { export function interpolate(template: string, context: object | undefined): string { if (!template) return template; - if (context === undefined) return template.replace(TokenSanitizeRegex, ''); + if (context === undefined) return template.replace(tokenSanitizeRegex, ''); - template = template.replace(TokenSanitizeRegex, '$${this.$1}'); + template = template.replace(tokenSanitizeRegex, '$${this.$1}'); return new Function(`return \`${template}\`;`).call(context); } @@ -68,11 +80,24 @@ export namespace Strings { .digest(encoding); } - export function normalizePath(fileName: string) { - const normalized = fileName && fileName.replace(pathNormalizer, '/'); - // if (normalized && normalized.includes('..')) { - // debugger; - // } + export function normalizePath( + fileName: string, + options: { addLeadingSlash?: boolean; stripTrailingSlash?: boolean } = { stripTrailingSlash: true } + ) { + if (fileName == null || fileName === '') return fileName; + + let normalized = fileName.replace(pathNormalizeRegex, '/'); + + const { addLeadingSlash, stripTrailingSlash } = { stripTrailingSlash: true, ...options }; + + if (stripTrailingSlash) { + normalized = normalized.replace(pathStripTrailingSlashRegex, ''); + } + + if (addLeadingSlash && normalized.charCodeAt(0) !== CharCode.Slash) { + normalized = `/${normalized}`; + } + return normalized; } diff --git a/src/views/explorerCommands.ts b/src/views/explorerCommands.ts index f097f34..f60de28 100644 --- a/src/views/explorerCommands.ts +++ b/src/views/explorerCommands.ts @@ -1,4 +1,5 @@ 'use strict'; +import * as path from 'path'; import { commands, Disposable, InputBoxOptions, Terminal, TextDocumentShowOptions, Uri, window } from 'vscode'; import { Commands, @@ -8,10 +9,12 @@ import { DiffWithWorkingCommandArgs, openEditor, OpenFileInRemoteCommandArgs, - OpenFileRevisionCommandArgs + OpenFileRevisionCommandArgs, + openWorkspace } from '../commands'; import { CommandContext, extensionTerminalName, setCommandContext } from '../constants'; import { Container } from '../container'; +import { toGitLensFSUri } from '../git/fsProvider'; import { GitService, GitUri } from '../gitService'; import { Arrays } from '../system'; import { @@ -47,6 +50,8 @@ export class ExplorerCommands implements Disposable { private _terminalCwd: string | undefined; constructor() { + commands.registerCommand('gitlens.explorers.exploreRepoRevision', this.exploreRepoRevision, this); + commands.registerCommand('gitlens.explorers.openChanges', this.openChanges, this); commands.registerCommand('gitlens.explorers.openChangesWithWorking', this.openChangesWithWorking, this); commands.registerCommand('gitlens.explorers.openFile', this.openFile, this); @@ -184,6 +189,15 @@ export class ExplorerCommands implements Disposable { setCommandContext(CommandContext.ExplorersCanCompare, true); } + private exploreRepoRevision(node: ExplorerRefNode, options: { openInNewWindow?: boolean } = {}) { + if (!(node instanceof ExplorerRefNode)) return; + + const uri = toGitLensFSUri(node.ref, node.repoPath); + const gitUri = GitUri.fromRevisionUri(uri); + + openWorkspace(uri, `${path.basename(gitUri.repoPath!)} @ ${gitUri.shortSha}`, options); + } + private openChanges(node: CommitNode | StashNode) { const command = node.getCommand(); if (command === undefined || command.arguments === undefined) return;