Commit
·
45a32e2
1
Parent(s):
5319ac2
initial commit
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- CNAME +1 -0
- LICENSE.md +21 -0
- README.md +1 -0
- assets/favicon.ico +0 -0
- assets/font/fontawesome-webfont.eot +0 -0
- assets/font/fontawesome-webfont.svg +0 -0
- assets/font/fontawesome-webfont.ttf +0 -0
- assets/font/fontawesome-webfont.woff +0 -0
- assets/learnGitBranching.png +0 -0
- checkgit.sh +6 -0
- gulpfile.js +242 -0
- package.json +51 -0
- src/js/actions/CommandLineActions.js +19 -0
- src/js/actions/GlobalStateActions.js +38 -0
- src/js/actions/LevelActions.js +25 -0
- src/js/actions/LocaleActions.js +32 -0
- src/js/app/index.js +363 -0
- src/js/commands/index.js +208 -0
- src/js/constants/AppConstants.js +42 -0
- src/js/dialogs/confirmShowSolution.js +192 -0
- src/js/dialogs/levelBuilder.js +368 -0
- src/js/dialogs/nextLevel.js +216 -0
- src/js/dialogs/sandbox.js +812 -0
- src/js/dispatcher/AppDispatcher.js +24 -0
- src/js/git/commands.js +955 -0
- src/js/git/gitShim.js +83 -0
- src/js/git/headless.js +145 -0
- src/js/git/index.js +3202 -0
- src/js/graph/index.js +167 -0
- src/js/graph/treeCompare.js +465 -0
- src/js/intl/checkStrings.js +33 -0
- src/js/intl/index.js +118 -0
- src/js/intl/strings.js +0 -0
- src/js/level/builder.js +408 -0
- src/js/level/disabledMap.js +42 -0
- src/js/level/index.js +672 -0
- src/js/level/parseWaterfall.js +122 -0
- src/js/log/index.js +31 -0
- src/js/mercurial/commands.js +261 -0
- src/js/models/collections.js +128 -0
- src/js/models/commandModel.js +309 -0
- src/js/react_views/CommandHistoryView.jsx +107 -0
- src/js/react_views/CommandView.jsx +134 -0
- src/js/react_views/CommandsHelperBarView.jsx +71 -0
- src/js/react_views/HelperBarView.jsx +70 -0
- src/js/react_views/IntlHelperBarView.jsx +157 -0
- src/js/react_views/LevelToolbarView.jsx +89 -0
- src/js/react_views/MainHelperBarView.jsx +76 -0
- src/js/sandbox/commands.js +219 -0
- src/js/sandbox/index.js +487 -0
CNAME
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
learnGitBranching.js.org
|
LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2012 Peter Cottle
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
CHANGED
|
@@ -6,6 +6,7 @@ colorTo: green
|
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
license: mit
|
|
|
|
| 9 |
---
|
| 10 |
|
| 11 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
license: mit
|
| 9 |
+
app_file: /src/template.index.html
|
| 10 |
---
|
| 11 |
|
| 12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
assets/favicon.ico
ADDED
|
|
assets/font/fontawesome-webfont.eot
ADDED
|
Binary file (38.7 kB). View file
|
|
|
assets/font/fontawesome-webfont.svg
ADDED
|
|
assets/font/fontawesome-webfont.ttf
ADDED
|
Binary file (68.5 kB). View file
|
|
|
assets/font/fontawesome-webfont.woff
ADDED
|
Binary file (41.8 kB). View file
|
|
|
assets/learnGitBranching.png
ADDED
|
checkgit.sh
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
GIT_STATUS=$(git status --porcelain | wc -l )
|
| 2 |
+
if [[ GIT_STATUS -ne 0 ]]; then
|
| 3 |
+
echo "${1:-Source files were modified}"
|
| 4 |
+
git status
|
| 5 |
+
exit $GIT_STATUS
|
| 6 |
+
fi;
|
gulpfile.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var { execSync } = require('child_process');
|
| 2 |
+
var {
|
| 3 |
+
writeFileSync, readdirSync, readFileSync,
|
| 4 |
+
existsSync, statSync, mkdirSync, copyFileSync,
|
| 5 |
+
} = require('fs');
|
| 6 |
+
var path = require('path');
|
| 7 |
+
|
| 8 |
+
var glob = require('glob');
|
| 9 |
+
var _ = require('underscore');
|
| 10 |
+
|
| 11 |
+
var { src, dest, series, watch } = require('gulp');
|
| 12 |
+
var log = require('fancy-log');
|
| 13 |
+
var gHash = require('gulp-hash');
|
| 14 |
+
var gClean = require('gulp-clean');
|
| 15 |
+
var concat = require('gulp-concat');
|
| 16 |
+
var cleanCSS = require('gulp-clean-css');
|
| 17 |
+
var gTerser = require('gulp-terser');
|
| 18 |
+
var gJasmine = require('gulp-jasmine');
|
| 19 |
+
var { minify } = require('html-minifier');
|
| 20 |
+
var { SpecReporter } = require('jasmine-spec-reporter');
|
| 21 |
+
var gJshint = require('gulp-jshint');
|
| 22 |
+
|
| 23 |
+
var source = require('vinyl-source-stream');
|
| 24 |
+
var buffer = require('vinyl-buffer');
|
| 25 |
+
var browserify = require('browserify');
|
| 26 |
+
var babelify = require('babelify');
|
| 27 |
+
|
| 28 |
+
_.templateSettings.interpolate = /\{\{(.+?)\}\}/g;
|
| 29 |
+
_.templateSettings.escape = /\{\{\{(.*?)\}\}\}/g;
|
| 30 |
+
_.templateSettings.evaluate = /\{\{-(.*?)\}\}/g;
|
| 31 |
+
|
| 32 |
+
// precompile for speed
|
| 33 |
+
var indexFile = readFileSync('src/template.index.html').toString();
|
| 34 |
+
var indexTemplate = _.template(indexFile);
|
| 35 |
+
|
| 36 |
+
var compliments = [
|
| 37 |
+
'Thanks to Hong4rc for the modern and amazing gulp workflow!',
|
| 38 |
+
'I hope you all have a great day :)'
|
| 39 |
+
];
|
| 40 |
+
var compliment = (done) => {
|
| 41 |
+
var index = Math.floor(Math.random() * compliments.length);
|
| 42 |
+
|
| 43 |
+
log(compliments[index]);
|
| 44 |
+
done();
|
| 45 |
+
};
|
| 46 |
+
|
| 47 |
+
const lintStrings = (done) => {
|
| 48 |
+
execSync('node src/js/intl/checkStrings');
|
| 49 |
+
done();
|
| 50 |
+
};
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
var destDir = './build/';
|
| 54 |
+
|
| 55 |
+
var copyRecursiveSync = (src, dest) => {
|
| 56 |
+
var exists = existsSync(src);
|
| 57 |
+
var stats = exists && statSync(src);
|
| 58 |
+
var isDirectory = exists && stats.isDirectory();
|
| 59 |
+
if (isDirectory) {
|
| 60 |
+
mkdirSync(dest);
|
| 61 |
+
readdirSync(src).forEach((childItemName) => {
|
| 62 |
+
copyRecursiveSync(
|
| 63 |
+
path.join(src, childItemName),
|
| 64 |
+
path.join(dest, childItemName));
|
| 65 |
+
});
|
| 66 |
+
} else {
|
| 67 |
+
copyFileSync(src, dest);
|
| 68 |
+
}
|
| 69 |
+
};
|
| 70 |
+
|
| 71 |
+
var buildIndex = function(done) {
|
| 72 |
+
log('Building index...');
|
| 73 |
+
|
| 74 |
+
// first find the one in here that we want
|
| 75 |
+
var buildFiles = readdirSync(destDir);
|
| 76 |
+
|
| 77 |
+
var jsRegex = /bundle-[\.\w]+\.js/;
|
| 78 |
+
var jsFile = buildFiles.find(function(name) {
|
| 79 |
+
return jsRegex.exec(name);
|
| 80 |
+
});
|
| 81 |
+
if (!jsFile) {
|
| 82 |
+
throw new Error('no hashed min file found!');
|
| 83 |
+
}
|
| 84 |
+
log('Found hashed js file: ' + jsFile);
|
| 85 |
+
|
| 86 |
+
var styleRegex = /main-[\.\w]+\.css/;
|
| 87 |
+
var styleFile = buildFiles.find(function(name) {
|
| 88 |
+
return styleRegex.exec(name);
|
| 89 |
+
});
|
| 90 |
+
if (!styleFile) {
|
| 91 |
+
throw new Error('no hashed css file found!');
|
| 92 |
+
}
|
| 93 |
+
log('Found hashed style file: ' + styleFile);
|
| 94 |
+
|
| 95 |
+
var buildDir = process.env.CI ? '.' : 'build';
|
| 96 |
+
|
| 97 |
+
// output these filenames to our index template
|
| 98 |
+
var outputIndex = indexTemplate({
|
| 99 |
+
buildDir,
|
| 100 |
+
jsFile,
|
| 101 |
+
styleFile,
|
| 102 |
+
});
|
| 103 |
+
|
| 104 |
+
if (process.env.NODE_ENV === 'production') {
|
| 105 |
+
outputIndex = minify(outputIndex, {
|
| 106 |
+
minifyJS: true,
|
| 107 |
+
collapseWhitespace: true,
|
| 108 |
+
processScripts: ['text/html'],
|
| 109 |
+
removeComments: true,
|
| 110 |
+
});
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
if (process.env.CI) {
|
| 114 |
+
writeFileSync('build/index.html', outputIndex);
|
| 115 |
+
copyRecursiveSync('assets', 'build/assets');
|
| 116 |
+
} else {
|
| 117 |
+
writeFileSync('index.html', outputIndex);
|
| 118 |
+
}
|
| 119 |
+
done();
|
| 120 |
+
};
|
| 121 |
+
|
| 122 |
+
var getBundle = function() {
|
| 123 |
+
return browserify({
|
| 124 |
+
entries: [...glob.sync('src/**/*.js'), ...glob.sync('src/**/*.jsx')],
|
| 125 |
+
debug: true,
|
| 126 |
+
})
|
| 127 |
+
.transform(babelify, { presets: ['@babel/preset-react'] })
|
| 128 |
+
.bundle()
|
| 129 |
+
.pipe(source('bundle.js'))
|
| 130 |
+
.pipe(buffer())
|
| 131 |
+
.pipe(gHash());
|
| 132 |
+
};
|
| 133 |
+
|
| 134 |
+
var clean = function () {
|
| 135 |
+
return src(destDir, { read: false, allowEmpty: true })
|
| 136 |
+
.pipe(gClean());
|
| 137 |
+
};
|
| 138 |
+
|
| 139 |
+
var jshint = function() {
|
| 140 |
+
return src([
|
| 141 |
+
'gulpfile.js',
|
| 142 |
+
'__tests__/',
|
| 143 |
+
'src/'
|
| 144 |
+
])
|
| 145 |
+
.pipe(gJshint())
|
| 146 |
+
.pipe(gJshint.reporter('default'));
|
| 147 |
+
};
|
| 148 |
+
|
| 149 |
+
var ifyBuild = function() {
|
| 150 |
+
return getBundle()
|
| 151 |
+
.pipe(dest(destDir));
|
| 152 |
+
};
|
| 153 |
+
|
| 154 |
+
var miniBuild = function() {
|
| 155 |
+
process.env.NODE_ENV = 'production';
|
| 156 |
+
return getBundle()
|
| 157 |
+
.pipe(gTerser())
|
| 158 |
+
.pipe(dest(destDir));
|
| 159 |
+
};
|
| 160 |
+
|
| 161 |
+
var style = function() {
|
| 162 |
+
var chain = src('src/style/*.css')
|
| 163 |
+
.pipe(concat('main.css'));
|
| 164 |
+
|
| 165 |
+
if (process.env.NODE_ENV === 'production') {
|
| 166 |
+
chain = chain.pipe(cleanCSS());
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
return chain.pipe(gHash())
|
| 170 |
+
.pipe(dest(destDir));
|
| 171 |
+
};
|
| 172 |
+
|
| 173 |
+
var jasmine = function() {
|
| 174 |
+
return src('__tests__/*.spec.js')
|
| 175 |
+
.pipe(gJasmine({
|
| 176 |
+
config: {
|
| 177 |
+
verbose: true,
|
| 178 |
+
random: false,
|
| 179 |
+
},
|
| 180 |
+
reporter: new SpecReporter(),
|
| 181 |
+
}));
|
| 182 |
+
};
|
| 183 |
+
|
| 184 |
+
var gitAdd = function(done) {
|
| 185 |
+
execSync('git add build/');
|
| 186 |
+
done();
|
| 187 |
+
};
|
| 188 |
+
|
| 189 |
+
var gitDeployMergeMain = function(done) {
|
| 190 |
+
execSync('git checkout gh-pages && git merge main -m "merge main"');
|
| 191 |
+
done();
|
| 192 |
+
};
|
| 193 |
+
|
| 194 |
+
var gitDeployPushOrigin = function(done) {
|
| 195 |
+
execSync('git commit -am "rebuild for prod"; ' +
|
| 196 |
+
'git push origin gh-pages --force && ' +
|
| 197 |
+
'git branch -f trunk gh-pages && ' +
|
| 198 |
+
'git checkout main'
|
| 199 |
+
);
|
| 200 |
+
done();
|
| 201 |
+
};
|
| 202 |
+
|
| 203 |
+
var fastBuild = series(clean, ifyBuild, style, buildIndex, jshint);
|
| 204 |
+
|
| 205 |
+
var build = series(
|
| 206 |
+
clean,
|
| 207 |
+
miniBuild, style, buildIndex,
|
| 208 |
+
gitAdd, jasmine, jshint,
|
| 209 |
+
lintStrings, compliment
|
| 210 |
+
);
|
| 211 |
+
|
| 212 |
+
var deploy = series(
|
| 213 |
+
clean,
|
| 214 |
+
jasmine,
|
| 215 |
+
jshint,
|
| 216 |
+
gitDeployMergeMain,
|
| 217 |
+
build,
|
| 218 |
+
gitDeployPushOrigin,
|
| 219 |
+
compliment
|
| 220 |
+
);
|
| 221 |
+
|
| 222 |
+
var lint = series(jshint, compliment);
|
| 223 |
+
|
| 224 |
+
var watching = function() {
|
| 225 |
+
return watch([
|
| 226 |
+
'gulpfile.js',
|
| 227 |
+
'__tests__/git.spec.js',
|
| 228 |
+
'src/js/**/*.js',
|
| 229 |
+
'src/js/**/**/*.js',
|
| 230 |
+
'src/levels/**/*.js'
|
| 231 |
+
], series([fastBuild , jasmine, jshint, lintStrings]));
|
| 232 |
+
};
|
| 233 |
+
|
| 234 |
+
module.exports = {
|
| 235 |
+
default: build,
|
| 236 |
+
lint,
|
| 237 |
+
fastBuild,
|
| 238 |
+
watching,
|
| 239 |
+
build,
|
| 240 |
+
test: jasmine,
|
| 241 |
+
deploy,
|
| 242 |
+
};
|
package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "LearnGitBranching",
|
| 3 |
+
"version": "0.8.0",
|
| 4 |
+
"description": "An interactive git visualization to challenge and educate!",
|
| 5 |
+
"homepage": "https://learngitbranching.js.org",
|
| 6 |
+
"author": "Peter Cottle <petermcottle@gmail.com>",
|
| 7 |
+
"license": "MIT",
|
| 8 |
+
"scripts": {
|
| 9 |
+
"test": "gulp test"
|
| 10 |
+
},
|
| 11 |
+
"repository": {
|
| 12 |
+
"type": "git",
|
| 13 |
+
"url": "https://github.com/pcottle/learnGitBranching"
|
| 14 |
+
},
|
| 15 |
+
"devDependencies": {
|
| 16 |
+
"@babel/core": "^7.17.5",
|
| 17 |
+
"@babel/preset-react": "^7.16.7",
|
| 18 |
+
"babelify": "^10.0.0",
|
| 19 |
+
"browserify": "^17.0.0",
|
| 20 |
+
"fancy-log": "^1.3.3",
|
| 21 |
+
"glob": "^7.2.0",
|
| 22 |
+
"gulp": "^4.0.2",
|
| 23 |
+
"gulp-clean": "^0.4.0",
|
| 24 |
+
"gulp-clean-css": "^4.3.0",
|
| 25 |
+
"gulp-concat": "^2.6.1",
|
| 26 |
+
"gulp-hash": "^4.2.2",
|
| 27 |
+
"gulp-jasmine": "^4.0.0",
|
| 28 |
+
"gulp-jshint": "^2.1.0",
|
| 29 |
+
"gulp-terser": "^2.1.0",
|
| 30 |
+
"gulp-uglify": "^3.0.2",
|
| 31 |
+
"html-minifier": "^4.0.0",
|
| 32 |
+
"jasmine-spec-reporter": "^7.0.0",
|
| 33 |
+
"jshint": "^2.13.4",
|
| 34 |
+
"prompt": "^1.2.2",
|
| 35 |
+
"vinyl-buffer": "^1.0.1",
|
| 36 |
+
"vinyl-source-stream": "^2.0.0"
|
| 37 |
+
},
|
| 38 |
+
"dependencies": {
|
| 39 |
+
"backbone": "^1.4.0",
|
| 40 |
+
"flux": "^4.0.3",
|
| 41 |
+
"jquery": "^3.4.0",
|
| 42 |
+
"jquery-ui": "^1.13.1",
|
| 43 |
+
"marked": "^4.0.12",
|
| 44 |
+
"prop-types": "^15.8.1",
|
| 45 |
+
"q": "^1.5.1",
|
| 46 |
+
"raphael": "^2.1.0",
|
| 47 |
+
"react": "^17.0.2",
|
| 48 |
+
"react-dom": "^17.0.2",
|
| 49 |
+
"underscore": "^1.13.2"
|
| 50 |
+
}
|
| 51 |
+
}
|
src/js/actions/CommandLineActions.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use strict";
|
| 2 |
+
|
| 3 |
+
var AppConstants = require('../constants/AppConstants');
|
| 4 |
+
var AppDispatcher = require('../dispatcher/AppDispatcher');
|
| 5 |
+
|
| 6 |
+
var ActionTypes = AppConstants.ActionTypes;
|
| 7 |
+
|
| 8 |
+
var CommandLineActions = {
|
| 9 |
+
|
| 10 |
+
submitCommand: function(text) {
|
| 11 |
+
AppDispatcher.handleViewAction({
|
| 12 |
+
type: ActionTypes.SUBMIT_COMMAND,
|
| 13 |
+
text: text
|
| 14 |
+
});
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
module.exports = CommandLineActions;
|
src/js/actions/GlobalStateActions.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use strict";
|
| 2 |
+
|
| 3 |
+
var AppConstants = require('../constants/AppConstants');
|
| 4 |
+
var AppDispatcher = require('../dispatcher/AppDispatcher');
|
| 5 |
+
|
| 6 |
+
var ActionTypes = AppConstants.ActionTypes;
|
| 7 |
+
|
| 8 |
+
var GlobalStateActions = {
|
| 9 |
+
|
| 10 |
+
changeIsAnimating: function(isAnimating) {
|
| 11 |
+
AppDispatcher.handleViewAction({
|
| 12 |
+
type: ActionTypes.CHANGE_IS_ANIMATING,
|
| 13 |
+
isAnimating: isAnimating
|
| 14 |
+
});
|
| 15 |
+
},
|
| 16 |
+
|
| 17 |
+
levelSolved: function() {
|
| 18 |
+
AppDispatcher.handleViewAction({
|
| 19 |
+
type: ActionTypes.LEVEL_SOLVED,
|
| 20 |
+
});
|
| 21 |
+
},
|
| 22 |
+
|
| 23 |
+
disableLevelInstructions: function() {
|
| 24 |
+
AppDispatcher.handleViewAction({
|
| 25 |
+
type: ActionTypes.DISABLE_LEVEL_INSTRUCTIONS,
|
| 26 |
+
});
|
| 27 |
+
},
|
| 28 |
+
|
| 29 |
+
changeFlipTreeY: function(flipTreeY) {
|
| 30 |
+
AppDispatcher.handleViewAction({
|
| 31 |
+
type: ActionTypes.CHANGE_FLIP_TREE_Y,
|
| 32 |
+
flipTreeY: flipTreeY
|
| 33 |
+
});
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
};
|
| 37 |
+
|
| 38 |
+
module.exports = GlobalStateActions;
|
src/js/actions/LevelActions.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use strict";
|
| 2 |
+
|
| 3 |
+
var AppConstants = require('../constants/AppConstants');
|
| 4 |
+
var AppDispatcher = require('../dispatcher/AppDispatcher');
|
| 5 |
+
|
| 6 |
+
var ActionTypes = AppConstants.ActionTypes;
|
| 7 |
+
|
| 8 |
+
var LevelActions = {
|
| 9 |
+
|
| 10 |
+
setLevelSolved: function(levelID) {
|
| 11 |
+
AppDispatcher.handleViewAction({
|
| 12 |
+
type: ActionTypes.SET_LEVEL_SOLVED,
|
| 13 |
+
levelID: levelID
|
| 14 |
+
});
|
| 15 |
+
},
|
| 16 |
+
|
| 17 |
+
resetLevelsSolved: function() {
|
| 18 |
+
AppDispatcher.handleViewAction({
|
| 19 |
+
type: ActionTypes.RESET_LEVELS_SOLVED
|
| 20 |
+
});
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
+
module.exports = LevelActions;
|
src/js/actions/LocaleActions.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use strict";
|
| 2 |
+
|
| 3 |
+
var AppConstants = require('../constants/AppConstants');
|
| 4 |
+
var AppDispatcher = require('../dispatcher/AppDispatcher');
|
| 5 |
+
|
| 6 |
+
var ActionTypes = AppConstants.ActionTypes;
|
| 7 |
+
|
| 8 |
+
var LocaleActions = {
|
| 9 |
+
|
| 10 |
+
changeLocale: function(newLocale) {
|
| 11 |
+
AppDispatcher.handleViewAction({
|
| 12 |
+
type: ActionTypes.CHANGE_LOCALE,
|
| 13 |
+
locale: newLocale
|
| 14 |
+
});
|
| 15 |
+
},
|
| 16 |
+
|
| 17 |
+
changeLocaleFromURI: function(newLocale) {
|
| 18 |
+
AppDispatcher.handleURIAction({
|
| 19 |
+
type: ActionTypes.CHANGE_LOCALE,
|
| 20 |
+
locale: newLocale
|
| 21 |
+
});
|
| 22 |
+
},
|
| 23 |
+
|
| 24 |
+
changeLocaleFromHeader: function(header) {
|
| 25 |
+
AppDispatcher.handleViewAction({
|
| 26 |
+
type: ActionTypes.CHANGE_LOCALE_FROM_HEADER,
|
| 27 |
+
header: header
|
| 28 |
+
});
|
| 29 |
+
}
|
| 30 |
+
};
|
| 31 |
+
|
| 32 |
+
module.exports = LocaleActions;
|
src/js/app/index.js
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var Backbone = require('backbone');
|
| 2 |
+
var jQuery = require('jquery');
|
| 3 |
+
var EventEmitter = require('events').EventEmitter;
|
| 4 |
+
var React = require('react');
|
| 5 |
+
var ReactDOM = require('react-dom');
|
| 6 |
+
|
| 7 |
+
var util = require('../util');
|
| 8 |
+
var intl = require('../intl');
|
| 9 |
+
var LocaleStore = require('../stores/LocaleStore');
|
| 10 |
+
var LocaleActions = require('../actions/LocaleActions');
|
| 11 |
+
|
| 12 |
+
/**
|
| 13 |
+
* Globals
|
| 14 |
+
*/
|
| 15 |
+
|
| 16 |
+
Backbone.$ = jQuery;
|
| 17 |
+
|
| 18 |
+
// Bypass jasmine
|
| 19 |
+
if (util.isBrowser()) {
|
| 20 |
+
window.jQuery = jQuery;
|
| 21 |
+
window.$ = jQuery;
|
| 22 |
+
window.Raphael = require('raphael');
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
var events = Object.assign(
|
| 26 |
+
{},
|
| 27 |
+
EventEmitter.prototype,
|
| 28 |
+
{
|
| 29 |
+
trigger: function() {
|
| 30 |
+
// alias this for backwards compatibility
|
| 31 |
+
this.emit.apply(this, arguments);
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
);
|
| 35 |
+
// Allow unlimited listeners, so FF doesn't break
|
| 36 |
+
events.setMaxListeners(0);
|
| 37 |
+
var commandUI;
|
| 38 |
+
var sandbox;
|
| 39 |
+
var eventBaton;
|
| 40 |
+
var levelDropdown;
|
| 41 |
+
|
| 42 |
+
///////////////////////////////////////////////////////////////////////
|
| 43 |
+
|
| 44 |
+
var init = function() {
|
| 45 |
+
/**
|
| 46 |
+
* There is a decent amount of bootstrapping we need just to hook
|
| 47 |
+
* everything up. The init() method takes on these responsibilities,
|
| 48 |
+
* including but not limited to:
|
| 49 |
+
* - setting up Events and EventBaton
|
| 50 |
+
* - calling the constructor for the main visualization
|
| 51 |
+
* - initializing the command input bar
|
| 52 |
+
* - handling window.focus and zoom events
|
| 53 |
+
**/
|
| 54 |
+
var Sandbox = require('../sandbox/').Sandbox;
|
| 55 |
+
var EventBaton = require('../util/eventBaton').EventBaton;
|
| 56 |
+
var LevelDropdownView = require('../views/levelDropdownView').LevelDropdownView;
|
| 57 |
+
|
| 58 |
+
eventBaton = new EventBaton();
|
| 59 |
+
commandUI = new CommandUI();
|
| 60 |
+
sandbox = new Sandbox();
|
| 61 |
+
levelDropdown = new LevelDropdownView({
|
| 62 |
+
wait: true
|
| 63 |
+
});
|
| 64 |
+
|
| 65 |
+
LocaleStore.subscribe(function() {
|
| 66 |
+
if (LocaleStore.getLocale() !== LocaleStore.getDefaultLocale()) {
|
| 67 |
+
intlRefresh();
|
| 68 |
+
}
|
| 69 |
+
});
|
| 70 |
+
events.on('vcsModeChange', vcsModeRefresh);
|
| 71 |
+
|
| 72 |
+
initRootEvents(eventBaton);
|
| 73 |
+
initDemo(sandbox);
|
| 74 |
+
// unfortunate global export for casper tests
|
| 75 |
+
window.LocaleStore = LocaleStore;
|
| 76 |
+
window.LocaleActions = LocaleActions;
|
| 77 |
+
window.intl = intl;
|
| 78 |
+
};
|
| 79 |
+
|
| 80 |
+
var vcsModeRefresh = function(eventData) {
|
| 81 |
+
if (!window.$) { return; }
|
| 82 |
+
|
| 83 |
+
var mode = eventData.mode;
|
| 84 |
+
var isGit = eventData.mode === 'git';
|
| 85 |
+
|
| 86 |
+
var displayMode = mode.slice(0, 1).toUpperCase() + mode.slice(1);
|
| 87 |
+
var otherMode = (displayMode === 'Git') ? 'Hg' : 'Git';
|
| 88 |
+
var regex = new RegExp(otherMode, 'g');
|
| 89 |
+
|
| 90 |
+
document.title = intl.str('learn-git-branching').replace(regex, displayMode);
|
| 91 |
+
$('span.vcs-mode-aware').each(function(i, el) {
|
| 92 |
+
var text = $(el).text().replace(regex, displayMode);
|
| 93 |
+
$(el).text(text);
|
| 94 |
+
});
|
| 95 |
+
|
| 96 |
+
$('body').toggleClass('gitMode', isGit);
|
| 97 |
+
$('body').toggleClass('hgMode', !isGit);
|
| 98 |
+
};
|
| 99 |
+
|
| 100 |
+
var insertAlternateLinks = function(pageId) {
|
| 101 |
+
// For now pageId is null, which would link to the main page.
|
| 102 |
+
// In future if pageId is provided this method should link to a specific page
|
| 103 |
+
|
| 104 |
+
// The value of the hreflang attribute identifies the language (in ISO 639-1 format)
|
| 105 |
+
// and optionally a region (in ISO 3166-1 Alpha 2 format) of an alternate URL
|
| 106 |
+
|
| 107 |
+
var altLinks = LocaleStore.getSupportedLocales().map(function(langCode) {
|
| 108 |
+
var url = "https://learngitbranching.js.org/?locale=" + langCode;
|
| 109 |
+
return '<link rel="alternate" hreflang="'+langCode+'" href="' + url +'" />';
|
| 110 |
+
});
|
| 111 |
+
var defaultUrl = "https://learngitbranching.js.org/?locale=" + LocaleStore.getDefaultLocale();
|
| 112 |
+
altLinks.push('<link rel="alternate" hreflang="x-default" href="' + defaultUrl +'" />');
|
| 113 |
+
$('head').prepend(altLinks);
|
| 114 |
+
|
| 115 |
+
};
|
| 116 |
+
|
| 117 |
+
var intlRefresh = function() {
|
| 118 |
+
if (!window.$) { return; }
|
| 119 |
+
var countryCode = LocaleStore.getLocale().split("_")[0];
|
| 120 |
+
$("html").attr('lang', countryCode);
|
| 121 |
+
$("meta[http-equiv='content-language']").attr("content", countryCode);
|
| 122 |
+
$('span.intl-aware').each(function(i, el) {
|
| 123 |
+
var intl = require('../intl');
|
| 124 |
+
var key = $(el).attr('data-intl');
|
| 125 |
+
$(el).text(intl.str(key));
|
| 126 |
+
});
|
| 127 |
+
};
|
| 128 |
+
|
| 129 |
+
var initRootEvents = function(eventBaton) {
|
| 130 |
+
// we always want to focus the text area to collect input
|
| 131 |
+
var focusTextArea = function() {
|
| 132 |
+
$('#commandTextField').focus();
|
| 133 |
+
};
|
| 134 |
+
focusTextArea();
|
| 135 |
+
|
| 136 |
+
$(window).focus(function(e) {
|
| 137 |
+
eventBaton.trigger('windowFocus', e);
|
| 138 |
+
});
|
| 139 |
+
$(document).click(function(e) {
|
| 140 |
+
eventBaton.trigger('documentClick', e);
|
| 141 |
+
});
|
| 142 |
+
$(document).bind('keydown', function(e) {
|
| 143 |
+
eventBaton.trigger('docKeydown', e);
|
| 144 |
+
});
|
| 145 |
+
$(document).bind('keyup', function(e) {
|
| 146 |
+
eventBaton.trigger('docKeyup', e);
|
| 147 |
+
});
|
| 148 |
+
$(window).on('resize', function(e) {
|
| 149 |
+
events.trigger('resize', e);
|
| 150 |
+
});
|
| 151 |
+
|
| 152 |
+
eventBaton.stealBaton('docKeydown', function() { });
|
| 153 |
+
eventBaton.stealBaton('docKeyup', function() { });
|
| 154 |
+
|
| 155 |
+
// the default action on window focus and document click is to just focus the text area
|
| 156 |
+
eventBaton.stealBaton('windowFocus', focusTextArea);
|
| 157 |
+
eventBaton.stealBaton('documentClick', focusTextArea);
|
| 158 |
+
|
| 159 |
+
// but when the input is fired in the text area, we pipe that to whoever is
|
| 160 |
+
// listenining
|
| 161 |
+
var makeKeyListener = function(name) {
|
| 162 |
+
return function() {
|
| 163 |
+
var args = [name];
|
| 164 |
+
Array.prototype.slice.apply(arguments).forEach(function(arg) {
|
| 165 |
+
args.push(arg);
|
| 166 |
+
});
|
| 167 |
+
eventBaton.trigger.apply(eventBaton, args);
|
| 168 |
+
};
|
| 169 |
+
};
|
| 170 |
+
|
| 171 |
+
$('#commandTextField').on('keydown', makeKeyListener('keydown'));
|
| 172 |
+
$('#commandTextField').on('keyup', makeKeyListener('keyup'));
|
| 173 |
+
$(window).trigger('resize');
|
| 174 |
+
};
|
| 175 |
+
|
| 176 |
+
var initDemo = function(sandbox) {
|
| 177 |
+
var params = util.parseQueryString(window.location.href);
|
| 178 |
+
|
| 179 |
+
// being the smart programmer I am (not), I don't include a true value on demo, so
|
| 180 |
+
// I have to check if the key exists here
|
| 181 |
+
var commands;
|
| 182 |
+
if (/(iPhone|iPod|iPad).*AppleWebKit/i.test(navigator.userAgent) || /android/i.test(navigator.userAgent)) {
|
| 183 |
+
sandbox.mainVis.customEvents.on('gitEngineReady', function() {
|
| 184 |
+
eventBaton.trigger('commandSubmitted', 'mobile alert');
|
| 185 |
+
});
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
if (params.hasOwnProperty('demo')) {
|
| 189 |
+
commands = [
|
| 190 |
+
"git commit; git checkout -b bugFix C1; git commit; git merge main; git checkout main; git commit; git rebase bugFix;",
|
| 191 |
+
"delay 1000; reset;",
|
| 192 |
+
"level advanced1 --noFinishDialog --noStartCommand --noIntroDialog;",
|
| 193 |
+
"delay 2000; show goal; delay 1000; hide goal;",
|
| 194 |
+
"git checkout bugFix; git rebase main; git checkout side; git rebase bugFix;",
|
| 195 |
+
"git checkout another; git rebase side; git rebase another main;",
|
| 196 |
+
"help; levels"
|
| 197 |
+
];
|
| 198 |
+
} else if (params.hasOwnProperty('hgdemo')) {
|
| 199 |
+
commands = [
|
| 200 |
+
'importTreeNow {"branches":{"main":{"target":"C3","id":"main"},"feature":{"target":"C2","id":"feature"},"debug":{"target":"C4","id":"debug"}},"commits":{"C0":{"parents":[],"id":"C0","rootCommit":true},"C1":{"parents":["C0"],"id":"C1"},"C2":{"parents":["C1"],"id":"C2"},"C3":{"parents":["C1"],"id":"C3"},"C4":{"parents":["C2"],"id":"C4"}},"HEAD":{"target":"feature","id":"HEAD"}}',
|
| 201 |
+
'delay 1000',
|
| 202 |
+
'git rebase main',
|
| 203 |
+
'delay 1000',
|
| 204 |
+
'undo',
|
| 205 |
+
'hg book',
|
| 206 |
+
'delay 1000',
|
| 207 |
+
'hg rebase -d main'
|
| 208 |
+
];
|
| 209 |
+
commands = commands.join(';#').split('#'); // hax
|
| 210 |
+
} else if (params.hasOwnProperty('hgdemo2')) {
|
| 211 |
+
commands = [
|
| 212 |
+
'importTreeNow {"branches":{"main":{"target":"C3","id":"main"},"feature":{"target":"C2","id":"feature"},"debug":{"target":"C4","id":"debug"}},"commits":{"C0":{"parents":[],"id":"C0","rootCommit":true},"C1":{"parents":["C0"],"id":"C1"},"C2":{"parents":["C1"],"id":"C2"},"C3":{"parents":["C1"],"id":"C3"},"C4":{"parents":["C2"],"id":"C4"}},"HEAD":{"target":"debug","id":"HEAD"}}',
|
| 213 |
+
'delay 1000',
|
| 214 |
+
'git rebase main',
|
| 215 |
+
'delay 1000',
|
| 216 |
+
'undo',
|
| 217 |
+
'hg sum',
|
| 218 |
+
'delay 1000',
|
| 219 |
+
'hg rebase -d main'
|
| 220 |
+
];
|
| 221 |
+
commands = commands.join(';#').split('#'); // hax
|
| 222 |
+
} else if (params.hasOwnProperty('remoteDemo')) {
|
| 223 |
+
commands = [
|
| 224 |
+
'git clone',
|
| 225 |
+
'git commit',
|
| 226 |
+
'git fakeTeamwork',
|
| 227 |
+
'git pull',
|
| 228 |
+
'git push',
|
| 229 |
+
'git commit',
|
| 230 |
+
'git fakeTeamwork',
|
| 231 |
+
'git pull --rebase',
|
| 232 |
+
'git push',
|
| 233 |
+
'levels'
|
| 234 |
+
];
|
| 235 |
+
commands = commands.join(';#').split('#'); // hax
|
| 236 |
+
} else if (params.gist_level_id) {
|
| 237 |
+
$.ajax({
|
| 238 |
+
url: 'https://api.github.com/gists/' + params.gist_level_id,
|
| 239 |
+
type: 'GET',
|
| 240 |
+
dataType: 'jsonp',
|
| 241 |
+
success: function(response) {
|
| 242 |
+
var data = response.data || {};
|
| 243 |
+
var files = data.files || {};
|
| 244 |
+
if (!Object.keys(files).length) {
|
| 245 |
+
console.warn('no files found');
|
| 246 |
+
return;
|
| 247 |
+
}
|
| 248 |
+
var file = files[Object.keys(files)[0]];
|
| 249 |
+
if (!file.content) {
|
| 250 |
+
console.warn('file empty');
|
| 251 |
+
}
|
| 252 |
+
eventBaton.trigger(
|
| 253 |
+
'commandSubmitted',
|
| 254 |
+
'importLevelNow ' + escape(file.content) + '; clear; show goal;'
|
| 255 |
+
);
|
| 256 |
+
}
|
| 257 |
+
});
|
| 258 |
+
} else if (!params.hasOwnProperty('NODEMO')) {
|
| 259 |
+
commands = [
|
| 260 |
+
"help;",
|
| 261 |
+
"levels"
|
| 262 |
+
];
|
| 263 |
+
}
|
| 264 |
+
if (params.hasOwnProperty('STARTREACT')) {
|
| 265 |
+
/*
|
| 266 |
+
ReactDOM.render(
|
| 267 |
+
React.createElement(CommandView, {}),
|
| 268 |
+
document.getElementById(params['STARTREACT'])
|
| 269 |
+
);*/
|
| 270 |
+
}
|
| 271 |
+
if (commands) {
|
| 272 |
+
sandbox.mainVis.customEvents.on('gitEngineReady', function() {
|
| 273 |
+
eventBaton.trigger('commandSubmitted', commands.join(''));
|
| 274 |
+
});
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
if (params.locale !== undefined && params.locale.length) {
|
| 278 |
+
LocaleActions.changeLocaleFromURI(params.locale);
|
| 279 |
+
} else {
|
| 280 |
+
tryLocaleDetect();
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
insertAlternateLinks();
|
| 284 |
+
|
| 285 |
+
if (params.command) {
|
| 286 |
+
var command = unescape(params.command);
|
| 287 |
+
sandbox.mainVis.customEvents.on('gitEngineReady', function() {
|
| 288 |
+
eventBaton.trigger('commandSubmitted', command);
|
| 289 |
+
});
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
};
|
| 293 |
+
|
| 294 |
+
function tryLocaleDetect() {
|
| 295 |
+
// use navigator to get the locale setting
|
| 296 |
+
changeLocaleFromHeaders(navigator.language || navigator.browserLanguage);
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
function changeLocaleFromHeaders(langString) {
|
| 300 |
+
LocaleActions.changeLocaleFromHeader(langString);
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
if (require('../util').isBrowser()) {
|
| 304 |
+
// this file gets included via node sometimes as well
|
| 305 |
+
$(document).ready(init);
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
/**
|
| 309 |
+
* the UI method simply bootstraps the command buffer and
|
| 310 |
+
* command prompt views. It only interacts with user input
|
| 311 |
+
* and simply pipes commands to the main events system
|
| 312 |
+
**/
|
| 313 |
+
function CommandUI() {
|
| 314 |
+
Backbone.$ = $; // lol WTF BACKBONE MANAGE YOUR DEPENDENCIES
|
| 315 |
+
var Views = require('../views');
|
| 316 |
+
var Collections = require('../models/collections');
|
| 317 |
+
var CommandViews = require('../views/commandViews');
|
| 318 |
+
var CommandHistoryView = require('../react_views/CommandHistoryView.jsx');
|
| 319 |
+
var MainHelperBarView = require('../react_views/MainHelperBarView.jsx');
|
| 320 |
+
|
| 321 |
+
this.commandCollection = new Collections.CommandCollection();
|
| 322 |
+
this.commandBuffer = new Collections.CommandBuffer({
|
| 323 |
+
collection: this.commandCollection
|
| 324 |
+
});
|
| 325 |
+
|
| 326 |
+
this.commandPromptView = new CommandViews.CommandPromptView({
|
| 327 |
+
el: $('#commandLineBar')
|
| 328 |
+
});
|
| 329 |
+
|
| 330 |
+
ReactDOM.render(
|
| 331 |
+
React.createElement(MainHelperBarView),
|
| 332 |
+
document.getElementById('helperBarMount')
|
| 333 |
+
);
|
| 334 |
+
ReactDOM.render(
|
| 335 |
+
React.createElement(
|
| 336 |
+
CommandHistoryView,
|
| 337 |
+
{ commandCollection: this.commandCollection }
|
| 338 |
+
),
|
| 339 |
+
document.getElementById('commandDisplay')
|
| 340 |
+
);
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
exports.getEvents = function() {
|
| 344 |
+
return events;
|
| 345 |
+
};
|
| 346 |
+
|
| 347 |
+
exports.getSandbox = function() {
|
| 348 |
+
return sandbox;
|
| 349 |
+
};
|
| 350 |
+
|
| 351 |
+
exports.getEventBaton = function() {
|
| 352 |
+
return eventBaton;
|
| 353 |
+
};
|
| 354 |
+
|
| 355 |
+
exports.getCommandUI = function() {
|
| 356 |
+
return commandUI;
|
| 357 |
+
};
|
| 358 |
+
|
| 359 |
+
exports.getLevelDropdown = function() {
|
| 360 |
+
return levelDropdown;
|
| 361 |
+
};
|
| 362 |
+
|
| 363 |
+
exports.init = init;
|
src/js/commands/index.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var intl = require('../intl');
|
| 2 |
+
|
| 3 |
+
var Errors = require('../util/errors');
|
| 4 |
+
var GitCommands = require('../git/commands');
|
| 5 |
+
var MercurialCommands = require('../mercurial/commands');
|
| 6 |
+
|
| 7 |
+
var CommandProcessError = Errors.CommandProcessError;
|
| 8 |
+
var CommandResult = Errors.CommandResult;
|
| 9 |
+
|
| 10 |
+
var commandConfigs = {
|
| 11 |
+
'git': GitCommands.commandConfig,
|
| 12 |
+
'hg': MercurialCommands.commandConfig
|
| 13 |
+
};
|
| 14 |
+
|
| 15 |
+
var commands = {
|
| 16 |
+
execute: function(vcs, name, engine, commandObj) {
|
| 17 |
+
if (!commandConfigs[vcs][name]) {
|
| 18 |
+
throw new Error('i don\'t have a command for ' + name);
|
| 19 |
+
}
|
| 20 |
+
var config = commandConfigs[vcs][name];
|
| 21 |
+
if (config.delegate) {
|
| 22 |
+
return this.delegateExecute(config, engine, commandObj);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
config.execute.call(this, engine, commandObj);
|
| 26 |
+
},
|
| 27 |
+
|
| 28 |
+
delegateExecute: function(config, engine, commandObj) {
|
| 29 |
+
// we have delegated to another vcs command, so lets
|
| 30 |
+
// execute that and get the result
|
| 31 |
+
var result = config.delegate.call(this, engine, commandObj);
|
| 32 |
+
|
| 33 |
+
if (result.multiDelegate) {
|
| 34 |
+
// we need to do multiple delegations with
|
| 35 |
+
// a different command at each step
|
| 36 |
+
result.multiDelegate.forEach(function(delConfig) {
|
| 37 |
+
// copy command, and then set opts
|
| 38 |
+
commandObj.setOptionsMap(delConfig.options || {});
|
| 39 |
+
commandObj.setGeneralArgs(delConfig.args || []);
|
| 40 |
+
|
| 41 |
+
commandConfigs[delConfig.vcs][delConfig.name].execute.call(this, engine, commandObj);
|
| 42 |
+
}, this);
|
| 43 |
+
} else {
|
| 44 |
+
config = commandConfigs[result.vcs][result.name];
|
| 45 |
+
// commandObj is PASSED BY REFERENCE
|
| 46 |
+
// and modified in the function
|
| 47 |
+
commandConfigs[result.vcs][result.name].execute.call(this, engine, commandObj);
|
| 48 |
+
}
|
| 49 |
+
},
|
| 50 |
+
|
| 51 |
+
blankMap: function() {
|
| 52 |
+
return {git: {}, hg: {}};
|
| 53 |
+
},
|
| 54 |
+
|
| 55 |
+
getShortcutMap: function() {
|
| 56 |
+
var map = this.blankMap();
|
| 57 |
+
this.loop(function(config, name, vcs) {
|
| 58 |
+
if (!config.sc) {
|
| 59 |
+
return;
|
| 60 |
+
}
|
| 61 |
+
map[vcs][name] = config.sc;
|
| 62 |
+
}, this);
|
| 63 |
+
return map;
|
| 64 |
+
},
|
| 65 |
+
|
| 66 |
+
getOptionMap: function() {
|
| 67 |
+
var optionMap = this.blankMap();
|
| 68 |
+
this.loop(function(config, name, vcs) {
|
| 69 |
+
var displayName = config.displayName || name;
|
| 70 |
+
var thisMap = {};
|
| 71 |
+
// start all options off as disabled
|
| 72 |
+
(config.options || []).forEach(function(option) {
|
| 73 |
+
thisMap[option] = false;
|
| 74 |
+
});
|
| 75 |
+
optionMap[vcs][displayName] = thisMap;
|
| 76 |
+
});
|
| 77 |
+
return optionMap;
|
| 78 |
+
},
|
| 79 |
+
|
| 80 |
+
getRegexMap: function() {
|
| 81 |
+
var map = this.blankMap();
|
| 82 |
+
this.loop(function(config, name, vcs) {
|
| 83 |
+
var displayName = config.displayName || name;
|
| 84 |
+
map[vcs][displayName] = config.regex;
|
| 85 |
+
});
|
| 86 |
+
return map;
|
| 87 |
+
},
|
| 88 |
+
|
| 89 |
+
/**
|
| 90 |
+
* which commands count for the git golf game
|
| 91 |
+
*/
|
| 92 |
+
getCommandsThatCount: function() {
|
| 93 |
+
var counted = this.blankMap();
|
| 94 |
+
this.loop(function(config, name, vcs) {
|
| 95 |
+
if (config.dontCountForGolf) {
|
| 96 |
+
return;
|
| 97 |
+
}
|
| 98 |
+
counted[vcs][name] = config.regex;
|
| 99 |
+
});
|
| 100 |
+
return counted;
|
| 101 |
+
},
|
| 102 |
+
|
| 103 |
+
loop: function(callback, context) {
|
| 104 |
+
Object.keys(commandConfigs).forEach(function(vcs) {
|
| 105 |
+
var commandConfig = commandConfigs[vcs];
|
| 106 |
+
Object.keys(commandConfig).forEach(function(name) {
|
| 107 |
+
var config = commandConfig[name];
|
| 108 |
+
callback(config, name, vcs);
|
| 109 |
+
});
|
| 110 |
+
});
|
| 111 |
+
}
|
| 112 |
+
};
|
| 113 |
+
|
| 114 |
+
var parse = function(str) {
|
| 115 |
+
var vcs;
|
| 116 |
+
var method;
|
| 117 |
+
var options;
|
| 118 |
+
|
| 119 |
+
// see if we support this particular command
|
| 120 |
+
var regexMap = commands.getRegexMap();
|
| 121 |
+
Object.keys(regexMap).forEach(function (thisVCS) {
|
| 122 |
+
var map = regexMap[thisVCS];
|
| 123 |
+
Object.keys(map).forEach(function(thisMethod) {
|
| 124 |
+
var regex = map[thisMethod];
|
| 125 |
+
if (regex.exec(str)) {
|
| 126 |
+
vcs = thisVCS;
|
| 127 |
+
method = thisMethod;
|
| 128 |
+
// every valid regex has to have the parts of
|
| 129 |
+
// <vcs> <command> <stuff>
|
| 130 |
+
// because there are always two space-groups
|
| 131 |
+
// before our "stuff" we can simply
|
| 132 |
+
// split on space-groups and grab everything after
|
| 133 |
+
// the second:
|
| 134 |
+
options = str.match(/('.*?'|".*?"|\S+)/g).slice(2);
|
| 135 |
+
}
|
| 136 |
+
});
|
| 137 |
+
});
|
| 138 |
+
|
| 139 |
+
if (!method) {
|
| 140 |
+
return false;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
// we support this command!
|
| 144 |
+
// parse off the options and assemble the map / general args
|
| 145 |
+
var parsedOptions = new CommandOptionParser(vcs, method, options);
|
| 146 |
+
var error = parsedOptions.explodeAndSet();
|
| 147 |
+
return {
|
| 148 |
+
toSet: {
|
| 149 |
+
generalArgs: parsedOptions.generalArgs,
|
| 150 |
+
supportedMap: parsedOptions.supportedMap,
|
| 151 |
+
error: error,
|
| 152 |
+
vcs: vcs,
|
| 153 |
+
method: method,
|
| 154 |
+
options: options,
|
| 155 |
+
eventName: 'processGitCommand'
|
| 156 |
+
}
|
| 157 |
+
};
|
| 158 |
+
};
|
| 159 |
+
|
| 160 |
+
/**
|
| 161 |
+
* CommandOptionParser
|
| 162 |
+
*/
|
| 163 |
+
function CommandOptionParser(vcs, method, options) {
|
| 164 |
+
this.vcs = vcs;
|
| 165 |
+
this.method = method;
|
| 166 |
+
this.rawOptions = options;
|
| 167 |
+
|
| 168 |
+
this.supportedMap = commands.getOptionMap()[vcs][method];
|
| 169 |
+
if (this.supportedMap === undefined) {
|
| 170 |
+
throw new Error('No option map for ' + method);
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
this.generalArgs = [];
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
CommandOptionParser.prototype.explodeAndSet = function() {
|
| 177 |
+
for (var i = 0; i < this.rawOptions.length; i++) {
|
| 178 |
+
var part = this.rawOptions[i];
|
| 179 |
+
|
| 180 |
+
if (part.slice(0,1) == '-') {
|
| 181 |
+
// it's an option, check supportedMap
|
| 182 |
+
if (this.supportedMap[part] === undefined) {
|
| 183 |
+
return new CommandProcessError({
|
| 184 |
+
msg: intl.str(
|
| 185 |
+
'option-not-supported',
|
| 186 |
+
{ option: part }
|
| 187 |
+
)
|
| 188 |
+
});
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
var next = this.rawOptions[i + 1];
|
| 192 |
+
var optionArgs = [];
|
| 193 |
+
if (next && next.slice(0,1) !== '-') {
|
| 194 |
+
// only store the next argument as this
|
| 195 |
+
// option value if its not another option
|
| 196 |
+
i++;
|
| 197 |
+
optionArgs = [next];
|
| 198 |
+
}
|
| 199 |
+
this.supportedMap[part] = optionArgs;
|
| 200 |
+
} else {
|
| 201 |
+
// must be a general arg
|
| 202 |
+
this.generalArgs.push(part);
|
| 203 |
+
}
|
| 204 |
+
}
|
| 205 |
+
};
|
| 206 |
+
|
| 207 |
+
exports.commands = commands;
|
| 208 |
+
exports.parse = parse;
|
src/js/constants/AppConstants.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use strict";
|
| 2 |
+
|
| 3 |
+
var keyMirror = require('../util/keyMirror');
|
| 4 |
+
|
| 5 |
+
var CHANGE_EVENT = 'change';
|
| 6 |
+
|
| 7 |
+
module.exports = {
|
| 8 |
+
|
| 9 |
+
CHANGE_EVENT: CHANGE_EVENT,
|
| 10 |
+
|
| 11 |
+
StoreSubscribePrototype: {
|
| 12 |
+
subscribe: function(cb) {
|
| 13 |
+
this.on(CHANGE_EVENT, cb);
|
| 14 |
+
},
|
| 15 |
+
|
| 16 |
+
unsubscribe: function(cb) {
|
| 17 |
+
this.removeListener(CHANGE_EVENT, cb);
|
| 18 |
+
}
|
| 19 |
+
},
|
| 20 |
+
|
| 21 |
+
ActionTypes: keyMirror({
|
| 22 |
+
SET_LEVEL_SOLVED: null,
|
| 23 |
+
RESET_LEVELS_SOLVED: null,
|
| 24 |
+
CHANGE_IS_ANIMATING: null,
|
| 25 |
+
CHANGE_FLIP_TREE_Y: null,
|
| 26 |
+
SUBMIT_COMMAND: null,
|
| 27 |
+
CHANGE_LOCALE: null,
|
| 28 |
+
CHANGE_LOCALE_FROM_HEADER: null,
|
| 29 |
+
DISABLE_LEVEL_INSTRUCTIONS: null,
|
| 30 |
+
/**
|
| 31 |
+
* only dispatched when you actually
|
| 32 |
+
* solve the level, not ask for solution
|
| 33 |
+
* or solve it again.
|
| 34 |
+
*/
|
| 35 |
+
SOLVE_LEVEL: null
|
| 36 |
+
}),
|
| 37 |
+
|
| 38 |
+
PayloadSources: keyMirror({
|
| 39 |
+
VIEW_ACTION: null,
|
| 40 |
+
URI_ACTION: null
|
| 41 |
+
})
|
| 42 |
+
};
|
src/js/dialogs/confirmShowSolution.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
exports.dialog = {
|
| 2 |
+
'en_US': [{
|
| 3 |
+
type: 'ModalAlert',
|
| 4 |
+
options: {
|
| 5 |
+
markdowns: [
|
| 6 |
+
'## Are you sure you want to see the solution?',
|
| 7 |
+
'',
|
| 8 |
+
'I believe in you! You can do it'
|
| 9 |
+
]
|
| 10 |
+
}
|
| 11 |
+
}],
|
| 12 |
+
'de_DE': [{
|
| 13 |
+
type: 'ModalAlert',
|
| 14 |
+
options: {
|
| 15 |
+
markdowns: [
|
| 16 |
+
'## Bist du sicher, dass du die Auflösung sehen willst?',
|
| 17 |
+
'',
|
| 18 |
+
'Ich glaube an dich! Du schaffst das!'
|
| 19 |
+
]
|
| 20 |
+
}
|
| 21 |
+
}],
|
| 22 |
+
'zh_CN': [{
|
| 23 |
+
type: 'ModalAlert',
|
| 24 |
+
options: {
|
| 25 |
+
markdowns: [
|
| 26 |
+
'## 确定要看答案吗?',
|
| 27 |
+
'',
|
| 28 |
+
'相信自己,你可以的!'
|
| 29 |
+
]
|
| 30 |
+
}
|
| 31 |
+
}],
|
| 32 |
+
'zh_TW': [{
|
| 33 |
+
type: 'ModalAlert',
|
| 34 |
+
options: {
|
| 35 |
+
markdowns: [
|
| 36 |
+
'## 確定偷看解答嗎?',
|
| 37 |
+
'',
|
| 38 |
+
'我相信你!你可以的'
|
| 39 |
+
]
|
| 40 |
+
}
|
| 41 |
+
}],
|
| 42 |
+
'es_AR': [{
|
| 43 |
+
type: 'ModalAlert',
|
| 44 |
+
options: {
|
| 45 |
+
markdowns: [
|
| 46 |
+
'## ¿Realmente querés ver la solución?',
|
| 47 |
+
'',
|
| 48 |
+
'¡Creo en vos! ¡Dale que podés!'
|
| 49 |
+
]
|
| 50 |
+
}
|
| 51 |
+
}],
|
| 52 |
+
'es_MX': [{
|
| 53 |
+
type: 'ModalAlert',
|
| 54 |
+
options: {
|
| 55 |
+
markdowns: [
|
| 56 |
+
'## ¿Estás seguro de que quieres ver la solución?',
|
| 57 |
+
'',
|
| 58 |
+
'¡Creo en ti! ¡Yo sé que puedes!'
|
| 59 |
+
]
|
| 60 |
+
}
|
| 61 |
+
}],
|
| 62 |
+
'es_ES': [{
|
| 63 |
+
type: 'ModalAlert',
|
| 64 |
+
options: {
|
| 65 |
+
markdowns: [
|
| 66 |
+
'## ¿Estás seguro de que quieres ver la solución?',
|
| 67 |
+
'',
|
| 68 |
+
'¡Creo en ti! ¡Ánimo!'
|
| 69 |
+
]
|
| 70 |
+
}
|
| 71 |
+
}],
|
| 72 |
+
'pt_BR': [{
|
| 73 |
+
type: 'ModalAlert',
|
| 74 |
+
options: {
|
| 75 |
+
markdowns: [
|
| 76 |
+
'## Tem certeza que quer ver a solução?',
|
| 77 |
+
'',
|
| 78 |
+
'Vamos lá, acredito que você consegue!'
|
| 79 |
+
]
|
| 80 |
+
}
|
| 81 |
+
}],
|
| 82 |
+
'gl': [{
|
| 83 |
+
type: 'ModalAlert',
|
| 84 |
+
options: {
|
| 85 |
+
markdowns: [
|
| 86 |
+
'## ¿Queres ver a solución?',
|
| 87 |
+
'',
|
| 88 |
+
'Seguro que podes, ¡inténtao unha vez máis!'
|
| 89 |
+
]
|
| 90 |
+
}
|
| 91 |
+
}],
|
| 92 |
+
'fr_FR': [{
|
| 93 |
+
type: 'ModalAlert',
|
| 94 |
+
options: {
|
| 95 |
+
markdowns: [
|
| 96 |
+
'## Êtes-vous sûr de vouloir voir la solution ?',
|
| 97 |
+
'',
|
| 98 |
+
'Je crois en vous ! Vous pouvez le faire !'
|
| 99 |
+
]
|
| 100 |
+
}
|
| 101 |
+
}],
|
| 102 |
+
'ja': [{
|
| 103 |
+
type: 'ModalAlert',
|
| 104 |
+
options: {
|
| 105 |
+
markdowns: [
|
| 106 |
+
'## どうしても正解がみたいですか?',
|
| 107 |
+
'',
|
| 108 |
+
'頑張れ頑張れできるできる絶対できる頑張れもっとやれるって',
|
| 109 |
+
'',
|
| 110 |
+
'やれる気持ちの問題だ頑張れ頑張れそこだ!',
|
| 111 |
+
'',
|
| 112 |
+
'そこで諦めるな絶対に頑張れ積極的にポジティブに頑張る頑張る',
|
| 113 |
+
'',
|
| 114 |
+
'北京だって頑張ってるんだから!'
|
| 115 |
+
]
|
| 116 |
+
}
|
| 117 |
+
}],
|
| 118 |
+
'ru_RU': [{
|
| 119 |
+
type: 'ModalAlert',
|
| 120 |
+
options: {
|
| 121 |
+
markdowns: [
|
| 122 |
+
'## Уверен, что хочешь посмотреть решение?',
|
| 123 |
+
'',
|
| 124 |
+
'Мы верим в тебя! Не прыгай! Ты сможешь!'
|
| 125 |
+
]
|
| 126 |
+
}
|
| 127 |
+
}],
|
| 128 |
+
'uk': [{
|
| 129 |
+
type: 'ModalAlert',
|
| 130 |
+
options: {
|
| 131 |
+
markdowns: [
|
| 132 |
+
'## Впевнений, що хочеш побачити розв’язок?',
|
| 133 |
+
'',
|
| 134 |
+
'Я вірю в тебе! Ти впораєшся!'
|
| 135 |
+
]
|
| 136 |
+
}
|
| 137 |
+
}],
|
| 138 |
+
'vi': [{
|
| 139 |
+
type: 'ModalAlert',
|
| 140 |
+
options: {
|
| 141 |
+
markdowns: [
|
| 142 |
+
'## Bạn chắc là muốn xem đáp án chứ?',
|
| 143 |
+
'',
|
| 144 |
+
'Tôi tin ở bạn! Bạn có thể làm được!'
|
| 145 |
+
]
|
| 146 |
+
}
|
| 147 |
+
}],
|
| 148 |
+
'sl_SI': [{
|
| 149 |
+
type: 'ModalAlert',
|
| 150 |
+
options: {
|
| 151 |
+
markdowns: [
|
| 152 |
+
'## Si prepričan, da hočeš videti rešitev?',
|
| 153 |
+
'',
|
| 154 |
+
'Verjamem vate! Maš ti to! Ali pač ne?'
|
| 155 |
+
]
|
| 156 |
+
}
|
| 157 |
+
}],
|
| 158 |
+
'pl': [{
|
| 159 |
+
type: 'ModalAlert',
|
| 160 |
+
options: {
|
| 161 |
+
markdowns: [
|
| 162 |
+
'## Czy na pewno chcesz zobaczyć rozwiązanie?',
|
| 163 |
+
'',
|
| 164 |
+
'Wierzę w Ciebie! Możesz to zrobić'
|
| 165 |
+
]
|
| 166 |
+
}
|
| 167 |
+
}],
|
| 168 |
+
'ta_IN': [{
|
| 169 |
+
type: 'ModalAlert',
|
| 170 |
+
options: {
|
| 171 |
+
markdowns: [
|
| 172 |
+
'## நீங்கள் நிச்சயமாக தீர்வை காண விரும்புகிறீர்களா?',
|
| 173 |
+
'',
|
| 174 |
+
'நான் உங்களால் அதை செய்ய முடியும் என நினைக்கிறேன்!'
|
| 175 |
+
]
|
| 176 |
+
}
|
| 177 |
+
}],
|
| 178 |
+
"it_IT": [
|
| 179 |
+
{
|
| 180 |
+
type: "ModalAlert",
|
| 181 |
+
options: {
|
| 182 |
+
markdowns: [
|
| 183 |
+
"## Sicuro di voler sbirciare la soluzione?",
|
| 184 |
+
"",
|
| 185 |
+
"Io credo in te, dai che ce la fai!",
|
| 186 |
+
],
|
| 187 |
+
},
|
| 188 |
+
},
|
| 189 |
+
],
|
| 190 |
+
};
|
| 191 |
+
|
| 192 |
+
|
src/js/dialogs/levelBuilder.js
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
exports.dialog = {
|
| 2 |
+
'en_US': [{
|
| 3 |
+
type: 'ModalAlert',
|
| 4 |
+
options: {
|
| 5 |
+
markdowns: [
|
| 6 |
+
'## Welcome to the level builder!',
|
| 7 |
+
'',
|
| 8 |
+
'Here are the main steps:',
|
| 9 |
+
'',
|
| 10 |
+
' * Set up the initial environment with git commands',
|
| 11 |
+
' * Define the starting tree with ```define start```',
|
| 12 |
+
' * Enter the series of git commands that compose the (optimal) solution',
|
| 13 |
+
' * Define the goal tree with ```define goal```. Defining the goal also defines the solution',
|
| 14 |
+
' * Optionally define a hint with ```define hint```',
|
| 15 |
+
' * Edit the name with ```define name```',
|
| 16 |
+
' * Optionally define a nice start dialog with ```edit dialog```',
|
| 17 |
+
' * Enter the command ```finish``` to output your level JSON!'
|
| 18 |
+
]
|
| 19 |
+
}
|
| 20 |
+
}],
|
| 21 |
+
'de_DE': [{
|
| 22 |
+
type: 'ModalAlert',
|
| 23 |
+
options: {
|
| 24 |
+
markdowns: [
|
| 25 |
+
'## Willkommen zum Level-Editor!',
|
| 26 |
+
'',
|
| 27 |
+
'So funktioniert\'s:',
|
| 28 |
+
'',
|
| 29 |
+
' * Stelle mit Git-Befehlen die Ausgangssituation her',
|
| 30 |
+
' * Leg den Startpunkt mit ```define start``` fest',
|
| 31 |
+
' * Gib eine Abfolge von Git-Befehlen ein, welche die (optimale) Lösung darstellen',
|
| 32 |
+
' * Leg den Ziel-Baum mit ```define goal``` fest. Damit markierst du den Endpunkt der Lösung',
|
| 33 |
+
' * Gib einen Hinweis mittels ```define hint``` an, wenn du willst',
|
| 34 |
+
' * Änder den Namen mittels ```define name```',
|
| 35 |
+
' * Wenn du magst, erstelle einen schönen Einführungsdialog mit ```edit dialog```',
|
| 36 |
+
' * Gib das Kommando ```finish``` ein um deinen Level als JSON auszugeben'
|
| 37 |
+
]
|
| 38 |
+
}
|
| 39 |
+
}],
|
| 40 |
+
'zh_CN': [{
|
| 41 |
+
type: 'ModalAlert',
|
| 42 |
+
options: {
|
| 43 |
+
markdowns: [
|
| 44 |
+
'## 欢迎使用关卡生成器!',
|
| 45 |
+
'',
|
| 46 |
+
'关键步骤如下:',
|
| 47 |
+
'',
|
| 48 |
+
' * 用 Git 命令建立初始环境',
|
| 49 |
+
' * 用 ```define start``` 命令定义初始提交树',
|
| 50 |
+
' * 输入一系列 Git 命令作为(最佳)答案',
|
| 51 |
+
' * 用 ```define goal``` 命令定义目标提交树。定义目标的同时也定义了答案',
|
| 52 |
+
' * (选做)还可以用 ```define hint``` 命令定义提示',
|
| 53 |
+
' * 用 ```define name``` 命令设置关卡名称',
|
| 54 |
+
' * (选做)还可以用 ```edit dialog``` 定义一个漂亮的开始对话框',
|
| 55 |
+
' * 用 ```finish``` 命令就可以输出你的关卡的JSON数据了!'
|
| 56 |
+
]
|
| 57 |
+
}
|
| 58 |
+
}],
|
| 59 |
+
'zh_TW': [{
|
| 60 |
+
type: 'ModalAlert',
|
| 61 |
+
options: {
|
| 62 |
+
markdowns: [
|
| 63 |
+
'## 歡迎使用關卡編輯器!',
|
| 64 |
+
'',
|
| 65 |
+
'主要步驟如下:',
|
| 66 |
+
'',
|
| 67 |
+
' * 使用 git 指令建立初始環境',
|
| 68 |
+
' * 使用 ```define start``` 指令定義起始樹',
|
| 69 |
+
' * 輸入一系列 git 命令,編好答案',
|
| 70 |
+
' * 使用 ```define goal``` 指令定義目標樹。定義目標的同時定義答案',
|
| 71 |
+
' * 還可以用 ```define hint``` 指令定義一個提示',
|
| 72 |
+
' * 用 ```define name``` 修改名稱',
|
| 73 |
+
' * 還可以用 ```edit dialog``` 定義一個漂亮的開始對話視窗',
|
| 74 |
+
' * 輸入 ```finish``` 即可將您的關卡輸出為 JSON!'
|
| 75 |
+
]
|
| 76 |
+
}
|
| 77 |
+
}],
|
| 78 |
+
'es_AR': [{
|
| 79 |
+
type: 'ModalAlert',
|
| 80 |
+
options: {
|
| 81 |
+
markdowns: [
|
| 82 |
+
'## ¡Te damos la bienvenida al constructor de niveles!',
|
| 83 |
+
'',
|
| 84 |
+
'Estos son los pasos principales:',
|
| 85 |
+
'',
|
| 86 |
+
' * Prepará el entorno inicial usando comandos de Git',
|
| 87 |
+
' * Definí el árbol inicial con ```define start```',
|
| 88 |
+
' * Ingresá la serie de comandos de git que representan la solución óptima',
|
| 89 |
+
' * Definí el árbol objetivo con ```define goal```. El objetivo también determina la solución',
|
| 90 |
+
' * Opcionalmente, definí pistas con ```define hint```',
|
| 91 |
+
' * Dale un nombre con ```define name```',
|
| 92 |
+
' * Opcionalmente, definí un mensaje inicial con ```edit dialog```',
|
| 93 |
+
' * ¡Ingresá el comando ```finish``` para obtener tu nivel en formato JSON!'
|
| 94 |
+
]
|
| 95 |
+
}
|
| 96 |
+
}],
|
| 97 |
+
'es_MX': [{
|
| 98 |
+
type: 'ModalAlert',
|
| 99 |
+
options: {
|
| 100 |
+
markdowns: [
|
| 101 |
+
'## ¡Bienvenido al constructor de niveles!',
|
| 102 |
+
'',
|
| 103 |
+
'Estos son los pasos principales:',
|
| 104 |
+
'',
|
| 105 |
+
' * Preparar el entorno inicial usando comandos de Git',
|
| 106 |
+
' * Definir el árbol inicial con ```define start```',
|
| 107 |
+
' * Introducir la serie de comandos de git que representan la solución óptima',
|
| 108 |
+
' * Crear el árbol objetivo con ```define goal```. El objetivo también determina la solución',
|
| 109 |
+
' * Opcionalmente, crea pistas con ```define hint```',
|
| 110 |
+
' * Dale un nombre con ```define name```',
|
| 111 |
+
' * Opcionalmente, crea un mensaje inicial con ```edit dialog```',
|
| 112 |
+
' * ¡Introduce el comando ```finish``` para obtener tu nivel en formato JSON!'
|
| 113 |
+
]
|
| 114 |
+
}
|
| 115 |
+
}],
|
| 116 |
+
'es_ES': [{
|
| 117 |
+
type: 'ModalAlert',
|
| 118 |
+
options: {
|
| 119 |
+
markdowns: [
|
| 120 |
+
'## ¡Bienvenido al constructor de niveles!',
|
| 121 |
+
'',
|
| 122 |
+
'Estos son los pasos principales:',
|
| 123 |
+
'',
|
| 124 |
+
' * Prepara el entorno inicial usando comandos de Git',
|
| 125 |
+
' * Define el árbol inicial con ```define start```',
|
| 126 |
+
' * Introduce la serie de comandos de git que representan la solución óptima',
|
| 127 |
+
' * Crea el árbol objetivo con ```define goal```. El objetivo también determina la solución',
|
| 128 |
+
' * Opcionalmente, crea pistas con ```define hint```',
|
| 129 |
+
' * Dale un nombre con ```define name```',
|
| 130 |
+
' * Opcionalmente, crea un mensaje inicial con ```edit dialog```',
|
| 131 |
+
' * ¡Introduce el comando ```finish``` para obtener tu nivel en formato JSON!'
|
| 132 |
+
]
|
| 133 |
+
}
|
| 134 |
+
}],
|
| 135 |
+
'pt_BR': [{
|
| 136 |
+
type: 'ModalAlert',
|
| 137 |
+
options: {
|
| 138 |
+
markdowns: [
|
| 139 |
+
'## Bem-vindo ao construtor de níveis!',
|
| 140 |
+
'',
|
| 141 |
+
'Estes são os passos principais:',
|
| 142 |
+
'',
|
| 143 |
+
' * Prepare o ambiente inicial usando comandos do Git',
|
| 144 |
+
' * Define a árvore inicial com ```define start```',
|
| 145 |
+
' * Insira a série de comandos do git que representam a solução ótima',
|
| 146 |
+
' * Defina a árvore objetivo com ```define goal```. O objetivo também determina a solução',
|
| 147 |
+
' * Opcionalmente, defina dicas com ```define hint```',
|
| 148 |
+
' * Dê um nome com ```define name```',
|
| 149 |
+
' * Opcionalmente, defina uma mensagem inicial com ```edit dialog```',
|
| 150 |
+
' * Digite o comando ```finish``` para obter seu nível em formato JSON!'
|
| 151 |
+
]
|
| 152 |
+
}
|
| 153 |
+
}],
|
| 154 |
+
'gl': [{
|
| 155 |
+
type: 'ModalAlert',
|
| 156 |
+
options: {
|
| 157 |
+
markdowns: [
|
| 158 |
+
'## Benvido ó constructor de niveis!',
|
| 159 |
+
'',
|
| 160 |
+
'Estes son os pasos principais:',
|
| 161 |
+
'',
|
| 162 |
+
' * Prepara o eido inicial usando comandos de Git',
|
| 163 |
+
' * Define a árbore inicial con ```define start```',
|
| 164 |
+
' * Inserta a secuencia de comandos de git que representan a mellor solución',
|
| 165 |
+
' * Define a árbore obxectivo con ```define goal```. O obxectivo tamén determina a solución',
|
| 166 |
+
' * Opcionalmente, define axudas con ```define hint```',
|
| 167 |
+
' * Dalle un nome con ```define name```',
|
| 168 |
+
' * Opcionalmente, define unha mensaxe inicial con ```edit dialog```',
|
| 169 |
+
' * Escribe o comando ```finish``` para obter seu nivel en formato JSON!'
|
| 170 |
+
]
|
| 171 |
+
}
|
| 172 |
+
}],
|
| 173 |
+
'fr_FR': [{
|
| 174 |
+
type: 'ModalAlert',
|
| 175 |
+
options: {
|
| 176 |
+
markdowns: [
|
| 177 |
+
'## Bienvenue dans l\'éditeur niveaux !',
|
| 178 |
+
'',
|
| 179 |
+
'Voici les étapes principales :',
|
| 180 |
+
'',
|
| 181 |
+
' * Mettez en place l\'environnement initial avec des commandes git',
|
| 182 |
+
' * Définissez l\'arbre de départ avec ```define start```',
|
| 183 |
+
' * Saisissez la série de commandes git qui composent la solution (optimale)',
|
| 184 |
+
' * Définissez l\'arbre cible avec ```define goal```. Cela définit aussi la solution',
|
| 185 |
+
' * Optionnellement, définissez un indice avec ```define hint```',
|
| 186 |
+
' * Changez le nom avec ```define name```',
|
| 187 |
+
' * Optionellement, definissez un joli dialogue de départ avec ```edit dialog```',
|
| 188 |
+
' * Entrez la commande ```finish``` pour délivrer votre niveau JSON!'
|
| 189 |
+
]
|
| 190 |
+
}
|
| 191 |
+
}],
|
| 192 |
+
'ja': [{
|
| 193 |
+
type: 'ModalAlert',
|
| 194 |
+
options: {
|
| 195 |
+
markdowns: [
|
| 196 |
+
'## Levelエディタへようこそ!',
|
| 197 |
+
'',
|
| 198 |
+
'ここでは、以下の主にステップを踏みます:',
|
| 199 |
+
'',
|
| 200 |
+
' * Gitコマンドで初期設定をします',
|
| 201 |
+
' * ```define start```で開始時のコミットツリーを定義します',
|
| 202 |
+
' * 一連のGitコマンドの(最適な)解答を入力します',
|
| 203 |
+
' * ```define goal```でゴールのコミットツリーを定義します(ゴールを定義するということは、解答を定義するということでもあります)',
|
| 204 |
+
' * オプションで```define hint```でヒントを定義します',
|
| 205 |
+
' * ```define name```で名前を編集します',
|
| 206 |
+
' * オプションで```edit dialog```で良い感じに開始時のダイアログを定義します',
|
| 207 |
+
' * ```finish```コマンドを打つことであなたのlevelがJSONで出力されます',
|
| 208 |
+
'',
|
| 209 |
+
'*Note: このダイアログは`help builder`で何回でも表示できます!活用してください!*'
|
| 210 |
+
]
|
| 211 |
+
}
|
| 212 |
+
}],
|
| 213 |
+
'ru_RU': [{
|
| 214 |
+
type: 'ModalAlert',
|
| 215 |
+
options: {
|
| 216 |
+
markdowns: [
|
| 217 |
+
'## Добро пожаловать в конструктор уровней!',
|
| 218 |
+
'',
|
| 219 |
+
'Вот основные шаги:',
|
| 220 |
+
'',
|
| 221 |
+
' * Настроить стартовое дерево при помощи команд git',
|
| 222 |
+
' * Обозначить старовое дерево при помощи ```define start```',
|
| 223 |
+
' * Ввести команды "оптимального" решения уровня',
|
| 224 |
+
' * Обозначить цель уровня при помощи ```define goal```. Одновременно обозначится решение.',
|
| 225 |
+
' * По желанию, можно указать подсказку при помощи ```define hint```',
|
| 226 |
+
' * Указать название уровня при помощи ```define name```',
|
| 227 |
+
' * По желанию, указать стартовое сообщение при помощи ```edit dialog```',
|
| 228 |
+
' * Ввести ```finish``` и получить JSON с описанием уровня!'
|
| 229 |
+
]
|
| 230 |
+
}
|
| 231 |
+
}],
|
| 232 |
+
'uk': [{
|
| 233 |
+
type: 'ModalAlert',
|
| 234 |
+
options: {
|
| 235 |
+
markdowns: [
|
| 236 |
+
'## Ласкаво просимо до конструктора рівнів!',
|
| 237 |
+
'',
|
| 238 |
+
'Ось основні кроки:',
|
| 239 |
+
'',
|
| 240 |
+
' * Налаштувати початкове середовище за допомогою команд git',
|
| 241 |
+
' * Визначити стартове дерево за допомогою ```define start```',
|
| 242 |
+
' * Ввести набір команд, що описують (оптимальний) розв’язок',
|
| 243 |
+
' * Визначити кінцеве дерево за допомогою ```define goal```. Одночасно це визначить розв’язок',
|
| 244 |
+
' * Додатково можна задати підказку за допомогою ```define hint```',
|
| 245 |
+
' * Редагувати назву рівня за допомогою ```define name```',
|
| 246 |
+
' * Додатково можна вказати файний початковий діалог за допомогою ```edit dialog```',
|
| 247 |
+
' * Ввести команду ```finish``` й отримати JSON з описом рівня!'
|
| 248 |
+
]
|
| 249 |
+
}
|
| 250 |
+
}],
|
| 251 |
+
'ko': [{
|
| 252 |
+
type: 'ModalAlert',
|
| 253 |
+
options: {
|
| 254 |
+
markdowns: [
|
| 255 |
+
'## 레벨 생성기 입니다. 환영합니다!',
|
| 256 |
+
'',
|
| 257 |
+
'Here are the main steps:',
|
| 258 |
+
'',
|
| 259 |
+
' * git 명령어로 초기 환경을 만들어주세요',
|
| 260 |
+
' * 시작 트리를 ```define start```로 정의하세요',
|
| 261 |
+
' * (최적화된)정답을 만드는 git 명령어들을 입력하세요',
|
| 262 |
+
' * 골 트리를 ```define goal```로 정의해주세요. 골을 정의하면 정답도 같이 정의됩니다',
|
| 263 |
+
' * ```define hint```로 원하면 힌트도 정의해줄수 있습니다',
|
| 264 |
+
' * 문제의 이름을 ```define name```로 수정하세요',
|
| 265 |
+
' * 시작 글이 필요하다면 ```edit dialog```로 쓸 수 있습니다',
|
| 266 |
+
' * ```finish```로 여러분의 레벨을 JSON결과로 받을 수 있습니다!'
|
| 267 |
+
]
|
| 268 |
+
}
|
| 269 |
+
}],
|
| 270 |
+
'vi': [{
|
| 271 |
+
type: 'ModalAlert',
|
| 272 |
+
options: {
|
| 273 |
+
markdowns: [
|
| 274 |
+
'## Chào mừng đến trình tạo cấp độ!',
|
| 275 |
+
'',
|
| 276 |
+
'Có những bước chính sau:',
|
| 277 |
+
'',
|
| 278 |
+
' * Khởi tạo môi trường với các lệnh git',
|
| 279 |
+
' * Định nghĩa cây để bắt đầu với ```define start```',
|
| 280 |
+
' * Nhập chuỗi lệnh git để tạo đáp án (tốt nhất) của bạn',
|
| 281 |
+
' * Định nghĩa cây mục tiêu với ```define goal```. Định nghĩa mục tiêu đồng thời cũng xác định đáp án',
|
| 282 |
+
' * Có thể định nghĩa gợi ý với ```define hint```',
|
| 283 |
+
' * Chỉnh sửa tên với ```define name```',
|
| 284 |
+
' * Có thể định nghĩa hội thoại bắt đầu với ```edit dialog```',
|
| 285 |
+
' * Nhập lệnh ```finish``` xuất cấp độ của bạn dưới dạng JSON!'
|
| 286 |
+
]
|
| 287 |
+
}
|
| 288 |
+
}],
|
| 289 |
+
'sl_SI': [{
|
| 290 |
+
type: 'ModalAlert',
|
| 291 |
+
options: {
|
| 292 |
+
markdowns: [
|
| 293 |
+
'## Dobrodošel v graditelju stopenj!',
|
| 294 |
+
'',
|
| 295 |
+
'Tu so glavni koraki:',
|
| 296 |
+
'',
|
| 297 |
+
' * Postavi začetno stanje z git ukazi',
|
| 298 |
+
' * Določi začetno drevo z ```define start```',
|
| 299 |
+
' * Vnesi zaporedje ukazov, ki predstavljajo (najboljšo) rešitev',
|
| 300 |
+
' * Določi ciljno drevo z ```define goal```. Določanje cilja določi tudi rešitev',
|
| 301 |
+
' * Opcijsko določi namig z ```define hint```',
|
| 302 |
+
' * Uredi ime z ```define name```',
|
| 303 |
+
' * Opcijsko določi ličen začetni dialog z ```edit dialog```',
|
| 304 |
+
' * Vnesi ukaz ```finish``` za ustvarjanje JSON različice tvoje stopnje!'
|
| 305 |
+
]
|
| 306 |
+
}
|
| 307 |
+
}],
|
| 308 |
+
'pl': [{
|
| 309 |
+
type: 'ModalAlert',
|
| 310 |
+
options: {
|
| 311 |
+
markdowns: [
|
| 312 |
+
'## Witamy w kreatorze poziomów!',
|
| 313 |
+
'',
|
| 314 |
+
'Oto główne kroki:',
|
| 315 |
+
'',
|
| 316 |
+
' * Przygotuj środowisko początkowe za pomocą poleceń GIT-a',
|
| 317 |
+
' * Zdefiniuj drzewo początkowe za pomocą ```define start```',
|
| 318 |
+
' * Wprowadź serię poleceń GIT-a, które tworzą (optymalne) rozwiązanie',
|
| 319 |
+
' * Utwórz drzewo celów za pomocą ```define goal```. Określenie celu określa również rozwiązanie',
|
| 320 |
+
' * Opcjonalnie utwórz podpowiedzi (wskazówkę) za pomocą ```define hint```',
|
| 321 |
+
' * Nadaj nazwę za pomocą ```define name```',
|
| 322 |
+
' * Opcjonalnie, utwórz wiadomość początkową za pomocą ```edit dialog```',
|
| 323 |
+
' * Wpisz polecenie ```finish```, aby wyświetlić swój poziom w JSON!'
|
| 324 |
+
]
|
| 325 |
+
}
|
| 326 |
+
}],
|
| 327 |
+
'ta_IN': [{
|
| 328 |
+
type: 'ModalAlert',
|
| 329 |
+
options: {
|
| 330 |
+
markdowns: [
|
| 331 |
+
'## நிலைகளை நிருவகிக்கும் கட்டமைப்பிற்க்கு வருக!',
|
| 332 |
+
'',
|
| 333 |
+
'அடிப்படை நடைமுறைகள்:',
|
| 334 |
+
'',
|
| 335 |
+
' * முதலாவதாக ஆரம்ப சூழலை git கட்டளைகள் கொன்டுகொன்டு அமைக்கவும்.',
|
| 336 |
+
' * ```define start``` தொடக்க செயல் முறையை வரையறுக்கவும்.',
|
| 337 |
+
' * உகந்த தீர்வினை அடையும் git கட்டளைகளின் தொடரை உள்ளிடவும்.',
|
| 338 |
+
' * ```define goal``` கொண்டு இலக்கினை அடையும் கிளை வரைமுரைகளை தீர்வுடன் அமைக்கவும்.',
|
| 339 |
+
' * தேவை எனில் ```define hint``` கொண்டு உதவி குறிப்பை வரையறுக்கவும்.',
|
| 340 |
+
' * ```define name``` கொண்டு பெயரைத் திருத்தவும்.',
|
| 341 |
+
' * தேவை எனில் ```edit dialog``` கொண்டு ஒரு நல்ல முன்னுறையை வரையறுக்கவும்.',
|
| 342 |
+
' * ```finish``` கொண்டு இந்த நிலையின் JSON!-ஐ அச்சிடுக.'
|
| 343 |
+
]
|
| 344 |
+
}
|
| 345 |
+
}],
|
| 346 |
+
"it_IT": [
|
| 347 |
+
{
|
| 348 |
+
type: "ModalAlert",
|
| 349 |
+
options: {
|
| 350 |
+
markdowns: [
|
| 351 |
+
"## Benvenuto al generatore di livelli !",
|
| 352 |
+
"",
|
| 353 |
+
"Ecco i passaggi principali:",
|
| 354 |
+
"",
|
| 355 |
+
" * Inizializza l'ambiente con i comandi git",
|
| 356 |
+
" * Definisci l'albero di partenza con ```define start```",
|
| 357 |
+
" * Inserisci la serie di comandi git che compongono la soluzione (ottimale)",
|
| 358 |
+
" * Definisci l'albero finale da ottenere con ```define goal```. L'albero finale costituisce la soluzione",
|
| 359 |
+
" * E' possibile inserire un suggerimento con ```define hint```",
|
| 360 |
+
" * Modifica il nome con ```define name```",
|
| 361 |
+
" * E' possibile personalizzare la finestra iniziale con ```edit dialog```",
|
| 362 |
+
" * Inserire il comando ```finish``` per generare il livello JSON!",
|
| 363 |
+
],
|
| 364 |
+
},
|
| 365 |
+
},
|
| 366 |
+
],
|
| 367 |
+
};
|
| 368 |
+
|
src/js/dialogs/nextLevel.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
exports.dialog = {
|
| 2 |
+
'en_US': [{
|
| 3 |
+
type: 'ModalAlert',
|
| 4 |
+
options: {
|
| 5 |
+
markdowns: [
|
| 6 |
+
'## Great Job!!',
|
| 7 |
+
'',
|
| 8 |
+
'You solved the level in *{numCommands}* command(s); ',
|
| 9 |
+
'our solution uses {best}.'
|
| 10 |
+
]
|
| 11 |
+
}
|
| 12 |
+
}],
|
| 13 |
+
'de_DE': [{
|
| 14 |
+
type: 'ModalAlert',
|
| 15 |
+
options: {
|
| 16 |
+
markdowns: [
|
| 17 |
+
'## Super gemacht',
|
| 18 |
+
'',
|
| 19 |
+
'Du hast den Level in *{numCommands}* Befehl(en) gelöst;',
|
| 20 |
+
'meine Lösung besteht aus {best}.'
|
| 21 |
+
]
|
| 22 |
+
}
|
| 23 |
+
}],
|
| 24 |
+
'ja': [{
|
| 25 |
+
type: 'ModalAlert',
|
| 26 |
+
options: {
|
| 27 |
+
markdowns: [
|
| 28 |
+
'## 完成!',
|
| 29 |
+
'',
|
| 30 |
+
'あなたは*{numCommands}*回のコマンドでこの課題をクリアしました; ',
|
| 31 |
+
'模範解答では{best}回です。',
|
| 32 |
+
'',
|
| 33 |
+
'模範解答は、右下の`?`メニューの`Solution`から見ることができます。'
|
| 34 |
+
]
|
| 35 |
+
}
|
| 36 |
+
}],
|
| 37 |
+
'zh_CN': [{
|
| 38 |
+
type: 'ModalAlert',
|
| 39 |
+
options: {
|
| 40 |
+
markdowns: [
|
| 41 |
+
'## 好样的!',
|
| 42 |
+
'',
|
| 43 |
+
'你用 *{numCommands}* 条命令通过了这一关;',
|
| 44 |
+
'我们的答案要用 {best} 条命令。'
|
| 45 |
+
]
|
| 46 |
+
}
|
| 47 |
+
}],
|
| 48 |
+
'zh_TW': [{
|
| 49 |
+
type: 'ModalAlert',
|
| 50 |
+
options: {
|
| 51 |
+
markdowns: [
|
| 52 |
+
'## 太棒了!',
|
| 53 |
+
'',
|
| 54 |
+
'您用了 *{numCommands}* 個指令完成這一關,',
|
| 55 |
+
'我們的解答用了 {best} 個。'
|
| 56 |
+
]
|
| 57 |
+
}
|
| 58 |
+
}],
|
| 59 |
+
'es_AR': [{
|
| 60 |
+
type: 'ModalAlert',
|
| 61 |
+
options: {
|
| 62 |
+
markdowns: [
|
| 63 |
+
'## ¡Buen trabajo!',
|
| 64 |
+
'',
|
| 65 |
+
'Resolviste el nivel en *{numCommands}* comandos; ',
|
| 66 |
+
'nuestra mejor solución usa {best}.'
|
| 67 |
+
]
|
| 68 |
+
}
|
| 69 |
+
}],
|
| 70 |
+
'es_MX': [{
|
| 71 |
+
type: 'ModalAlert',
|
| 72 |
+
options: {
|
| 73 |
+
markdowns: [
|
| 74 |
+
'## ¡Buen trabajo!',
|
| 75 |
+
'',
|
| 76 |
+
'Resolviste el nivel en *{numCommands}* comandos; ',
|
| 77 |
+
'nuestra mejor solución usa: {best}.'
|
| 78 |
+
]
|
| 79 |
+
}
|
| 80 |
+
}],
|
| 81 |
+
'es_ES': [{
|
| 82 |
+
type: 'ModalAlert',
|
| 83 |
+
options: {
|
| 84 |
+
markdowns: [
|
| 85 |
+
'## ¡Buen trabajo!',
|
| 86 |
+
'',
|
| 87 |
+
'Resolviste el nivel en *{numCommands}* comandos; ',
|
| 88 |
+
'nuestra mejor solución usa {best}.'
|
| 89 |
+
]
|
| 90 |
+
}
|
| 91 |
+
}],
|
| 92 |
+
'pt_BR': [{
|
| 93 |
+
type: 'ModalAlert',
|
| 94 |
+
options: {
|
| 95 |
+
markdowns: [
|
| 96 |
+
'## Bom trabalho!!',
|
| 97 |
+
'',
|
| 98 |
+
'Você resolveu o nível usando *{numCommands}* comandos; ',
|
| 99 |
+
'nossa melhor solução usa {best}.'
|
| 100 |
+
]
|
| 101 |
+
}
|
| 102 |
+
}],
|
| 103 |
+
'gl': [{
|
| 104 |
+
type: 'ModalAlert',
|
| 105 |
+
options: {
|
| 106 |
+
markdowns: [
|
| 107 |
+
'## Bo traballo!!',
|
| 108 |
+
'',
|
| 109 |
+
'Resolviches o nivel empregando *{numCommands}* comandos; ',
|
| 110 |
+
'a nosa mellor solución é en {best}.'
|
| 111 |
+
]
|
| 112 |
+
}
|
| 113 |
+
}],
|
| 114 |
+
'fr_FR': [{
|
| 115 |
+
type: 'ModalAlert',
|
| 116 |
+
options: {
|
| 117 |
+
markdowns: [
|
| 118 |
+
'## Beau Travail!!',
|
| 119 |
+
'',
|
| 120 |
+
'Vous avez résolu le niveau en *{numCommands}* commande(s); ',
|
| 121 |
+
'notre solution le fait en {best}.'
|
| 122 |
+
]
|
| 123 |
+
}
|
| 124 |
+
}],
|
| 125 |
+
'ru_RU': [{
|
| 126 |
+
type: 'ModalAlert',
|
| 127 |
+
options: {
|
| 128 |
+
markdowns: [
|
| 129 |
+
'## Супер!',
|
| 130 |
+
'',
|
| 131 |
+
'Ты прошёл уровень. Количество использованных команд - *{numCommands}* ; ',
|
| 132 |
+
'а наше решение состоит из {best}.'
|
| 133 |
+
]
|
| 134 |
+
}
|
| 135 |
+
}],
|
| 136 |
+
'uk': [{
|
| 137 |
+
type: 'ModalAlert',
|
| 138 |
+
options: {
|
| 139 |
+
markdowns: [
|
| 140 |
+
'## Молодець!',
|
| 141 |
+
'',
|
| 142 |
+
'Ти пройшов рівень. Кількість використаних команд \u2014 *{numCommands}*; ',
|
| 143 |
+
'наш розв’язок складається з {best}.'
|
| 144 |
+
]
|
| 145 |
+
}
|
| 146 |
+
}],
|
| 147 |
+
'ko': [{
|
| 148 |
+
type: 'ModalAlert',
|
| 149 |
+
options: {
|
| 150 |
+
markdowns: [
|
| 151 |
+
'## 훌륭합니다!!',
|
| 152 |
+
'',
|
| 153 |
+
'*{numCommands}*개의 명령으로 레벨을 통과했습니다; ',
|
| 154 |
+
'모범 답안은 {best}개를 사용합니다.'
|
| 155 |
+
]
|
| 156 |
+
}
|
| 157 |
+
}],
|
| 158 |
+
'vi': [{
|
| 159 |
+
type: 'ModalAlert',
|
| 160 |
+
options: {
|
| 161 |
+
markdowns: [
|
| 162 |
+
'## Làm tốt lắm!!',
|
| 163 |
+
'',
|
| 164 |
+
'Bạn hoàn thành cấp độ này với *{numCommands}* câu lệnh; ',
|
| 165 |
+
'Đáp án của chúng tôi sử dụng {best}.'
|
| 166 |
+
]
|
| 167 |
+
}
|
| 168 |
+
}],
|
| 169 |
+
'sl_SI': [{
|
| 170 |
+
type: 'ModalAlert',
|
| 171 |
+
options: {
|
| 172 |
+
markdowns: [
|
| 173 |
+
'## Dobro opravljeno!!',
|
| 174 |
+
'',
|
| 175 |
+
'Rešil si stopnjo z *{numCommands}* ukazi; ',
|
| 176 |
+
'naša rešitev uporabi {best}.'
|
| 177 |
+
]
|
| 178 |
+
}
|
| 179 |
+
}],
|
| 180 |
+
'pl': [{
|
| 181 |
+
type: 'ModalAlert',
|
| 182 |
+
options: {
|
| 183 |
+
markdowns: [
|
| 184 |
+
'## Dobra robota!!',
|
| 185 |
+
'',
|
| 186 |
+
'Rozwiązałeś poziom używając *{numCommands}* poleceń/ia; ',
|
| 187 |
+
'nasze rozwiązanie składa się z {best}.'
|
| 188 |
+
]
|
| 189 |
+
}
|
| 190 |
+
}],
|
| 191 |
+
'ta_IN': [{
|
| 192 |
+
type: 'ModalAlert',
|
| 193 |
+
options: {
|
| 194 |
+
markdowns: [
|
| 195 |
+
'## ஆக சிரந்த செயல்!!',
|
| 196 |
+
'',
|
| 197 |
+
'நீங்கள் *{numCommands}* நிலைக்கான கட்டளை(கள்) கொண்டு தீர்வை அடிந்து விட்டீர்கள்; ',
|
| 198 |
+
'நமது தீர்வு {best}-ஐ பயன்படுத்து கின்றது.'
|
| 199 |
+
]
|
| 200 |
+
}
|
| 201 |
+
}],
|
| 202 |
+
"it_IT": [
|
| 203 |
+
{
|
| 204 |
+
type: "ModalAlert",
|
| 205 |
+
options: {
|
| 206 |
+
markdowns: [
|
| 207 |
+
"## Ben fatto!!",
|
| 208 |
+
"",
|
| 209 |
+
"Hai risolto il livello con *{numCommands}* comando(i); ",
|
| 210 |
+
"noi l'abbiamo risolto con {best}.",
|
| 211 |
+
],
|
| 212 |
+
},
|
| 213 |
+
},
|
| 214 |
+
],
|
| 215 |
+
};
|
| 216 |
+
|
src/js/dialogs/sandbox.js
ADDED
|
@@ -0,0 +1,812 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
exports.dialog = {
|
| 2 |
+
'en_US': [{
|
| 3 |
+
type: 'ModalAlert',
|
| 4 |
+
options: {
|
| 5 |
+
markdowns: [
|
| 6 |
+
'## Welcome to Learn Git Branching',
|
| 7 |
+
'',
|
| 8 |
+
'Interested in learning Git? Well you\'ve come to the right place! ',
|
| 9 |
+
'"Learn Git Branching" is the most visual and interactive way to learn Git ',
|
| 10 |
+
'on the web; you\'ll be challenged with exciting levels, given step-by-step ',
|
| 11 |
+
'demonstrations of powerful features, and maybe even have a bit of fun along the way.',
|
| 12 |
+
'',
|
| 13 |
+
'After this dialog you\'ll see the variety of levels we have to offer. If you\'re a ',
|
| 14 |
+
'beginner, just go ahead and start with the first. If you already know some Git basics, ',
|
| 15 |
+
'try some of our later more challenging levels.',
|
| 16 |
+
'',
|
| 17 |
+
'You can see all the commands available with `show commands` at the terminal.',
|
| 18 |
+
'',
|
| 19 |
+
'PS: Want to go straight to a sandbox next time?',
|
| 20 |
+
'Try out ',
|
| 21 |
+
'[this special link](https://pcottle.github.io/learnGitBranching/?NODEMO)',
|
| 22 |
+
'',
|
| 23 |
+
'PPS: GitHub has started naming the default branch `main` instead of `master` ',
|
| 24 |
+
'to migrate away from biased terminology [(more details available here)](https://github.com/github/renaming). ',
|
| 25 |
+
'In accordance with this industry-wide movement, we have also updated "Learn Git Branching" to ',
|
| 26 |
+
'use `main` instead of `master` in our lessons. This rename should be fairly consistent by ',
|
| 27 |
+
'now but if you notice any errors, feel free to submit a PR (or open an issue).'
|
| 28 |
+
]
|
| 29 |
+
}
|
| 30 |
+
}],
|
| 31 |
+
'es_AR': [{
|
| 32 |
+
type: 'ModalAlert',
|
| 33 |
+
options: {
|
| 34 |
+
markdowns: [
|
| 35 |
+
'## ¡Te damos la bienvenida a Learn Git Branching!',
|
| 36 |
+
'',
|
| 37 |
+
'Esta aplicación está diseñada para ayudar a los principiantes ',
|
| 38 |
+
'a manejar los poderosos conceptos que hay detrás del trabajo ',
|
| 39 |
+
'con ramas (branches) en Git. Esperamos que disfrutes la aplicación ',
|
| 40 |
+
'y tal vez incluso ¡que aprendas algo! ',
|
| 41 |
+
'',
|
| 42 |
+
'# ¡Demo!',
|
| 43 |
+
'',
|
| 44 |
+
'Si no viste la demo, mirala en esta dirección:',
|
| 45 |
+
'',
|
| 46 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=es_AR&demo](https://pcottle.github.io/learnGitBranching/?locale=es_AR&demo)',
|
| 47 |
+
'',
|
| 48 |
+
'¿Querés dejar de ver este mensaje? Agregale `NODEMO` a la URL para dejar de verlo, como en este link:',
|
| 49 |
+
'',
|
| 50 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=es_AR&NODEMO](https://pcottle.github.io/learnGitBranching/?locale=es_AR&NODEMO)'
|
| 51 |
+
]
|
| 52 |
+
}
|
| 53 |
+
}, {
|
| 54 |
+
type: 'ModalAlert',
|
| 55 |
+
options: {
|
| 56 |
+
markdowns: [
|
| 57 |
+
'## Comandos de Git',
|
| 58 |
+
'',
|
| 59 |
+
'Tenés una gran variedad de comandos de git en este sandbox. Estos incluyen: ',
|
| 60 |
+
'',
|
| 61 |
+
' * commit',
|
| 62 |
+
' * branch',
|
| 63 |
+
' * checkout',
|
| 64 |
+
' * cherry-pick',
|
| 65 |
+
' * reset',
|
| 66 |
+
' * revert',
|
| 67 |
+
' * rebase',
|
| 68 |
+
' * merge'
|
| 69 |
+
]
|
| 70 |
+
}
|
| 71 |
+
}, {
|
| 72 |
+
type: 'ModalAlert',
|
| 73 |
+
options: {
|
| 74 |
+
markdowns: [
|
| 75 |
+
'## ¡Compartí!',
|
| 76 |
+
'',
|
| 77 |
+
'Compartí tus árboles con tus amigos usando `export tree` e `import tree`',
|
| 78 |
+
'',
|
| 79 |
+
'¿Tenés una buena lección que compartir? Probá construyendo un nivel con `build level` o probá el nivel de un amigo con `import level`',
|
| 80 |
+
'',
|
| 81 |
+
'Para ver todos los comandos disponibles, probá `show commands`. Hay algunas joyitas como `undo` y `reset`',
|
| 82 |
+
'',
|
| 83 |
+
'Por ahora, arranquemos con los `levels`...'
|
| 84 |
+
]
|
| 85 |
+
}
|
| 86 |
+
}],
|
| 87 |
+
'es_MX': [{
|
| 88 |
+
type: 'ModalAlert',
|
| 89 |
+
options: {
|
| 90 |
+
markdowns: [
|
| 91 |
+
'## ¡Bienvenid@ a Learn Git Branching!',
|
| 92 |
+
'',
|
| 93 |
+
'Esta aplicación está diseñada para ayudar a los principiantes',
|
| 94 |
+
'a manejar los poderosos conceptos que hay detrás del trabajo',
|
| 95 |
+
'con ramas (branches) en Git. Esperamos que disfrutes la aplicación',
|
| 96 |
+
'y tal vez incluso ¡que aprendas algo!',
|
| 97 |
+
'',
|
| 98 |
+
'# ¡Demo!',
|
| 99 |
+
'',
|
| 100 |
+
'Si no viste la demo, mirala en ésta dirección:',
|
| 101 |
+
'',
|
| 102 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=es_MX&demo](https://pcottle.github.io/learnGitBranching/?locale=es_MX&demo)',
|
| 103 |
+
'',
|
| 104 |
+
'¿Harto de este mensaje? Agregale `NODEMO` a la URL para dejar de verlo, como en éste link:',
|
| 105 |
+
'',
|
| 106 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=es_MX&NODEMO](https://pcottle.github.io/learnGitBranching/?locale=es_MX&NODEMO)'
|
| 107 |
+
]
|
| 108 |
+
}
|
| 109 |
+
}, {
|
| 110 |
+
type: 'ModalAlert',
|
| 111 |
+
options: {
|
| 112 |
+
markdowns: [
|
| 113 |
+
'## Comandos de git',
|
| 114 |
+
'',
|
| 115 |
+
'Tienes una gran variedad de comandos de git en este sandbox. He aquí una lista de los incluidos:',
|
| 116 |
+
'',
|
| 117 |
+
' * commit',
|
| 118 |
+
' * branch',
|
| 119 |
+
' * checkout',
|
| 120 |
+
' * cherry-pick',
|
| 121 |
+
' * reset',
|
| 122 |
+
' * revert',
|
| 123 |
+
' * rebase',
|
| 124 |
+
' * merge'
|
| 125 |
+
]
|
| 126 |
+
}
|
| 127 |
+
}, {
|
| 128 |
+
type: 'ModalAlert',
|
| 129 |
+
options: {
|
| 130 |
+
markdowns: [
|
| 131 |
+
'## ¡Comparte!',
|
| 132 |
+
'',
|
| 133 |
+
'Comparte tus árboles con tus amigos usando `export tree` e `import tree`',
|
| 134 |
+
'',
|
| 135 |
+
'¿Tienes una buena lección que compartir? Prueba construyendo un nivel con `build level` o prueba el nivel de un amigo con `import level`',
|
| 136 |
+
'',
|
| 137 |
+
'Para ver todos los comandos disponibles, prueba `show commands`. Hay algunos muy prácticos como `undo` y `reset`',
|
| 138 |
+
'',
|
| 139 |
+
'Por ahora, arranquemos con los `levels`...'
|
| 140 |
+
]
|
| 141 |
+
}
|
| 142 |
+
}],
|
| 143 |
+
'es_ES': [{
|
| 144 |
+
type: 'ModalAlert',
|
| 145 |
+
options: {
|
| 146 |
+
markdowns: [
|
| 147 |
+
'## ¡Bienvenid@ a Learn Git Branching!',
|
| 148 |
+
'',
|
| 149 |
+
'Esta aplicación está diseñada para ayudar a los principiantes ',
|
| 150 |
+
'a manejar los poderosos conceptos que hay detrás del trabajo ',
|
| 151 |
+
'con ramas (branches) en Git. Esperamos que disfrutes la aplicación ',
|
| 152 |
+
'y tal vez incluso ¡que aprendas algo! ',
|
| 153 |
+
'',
|
| 154 |
+
'# ¡Demo!',
|
| 155 |
+
'',
|
| 156 |
+
'Si no viste la demo, mírala en esta dirección:',
|
| 157 |
+
'',
|
| 158 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=es_ES&demo](https://pcottle.github.io/learnGitBranching/?locale=es_ES&demo)',
|
| 159 |
+
'',
|
| 160 |
+
'¿Harto de este mensaje? Agrégale `NODEMO` a la URL para dejar de verlo, como en este link:',
|
| 161 |
+
'',
|
| 162 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=es_ES&NODEMO](https://pcottle.github.io/learnGitBranching/?locale=es_ES&NODEMO)'
|
| 163 |
+
]
|
| 164 |
+
}
|
| 165 |
+
}, {
|
| 166 |
+
type: 'ModalAlert',
|
| 167 |
+
options: {
|
| 168 |
+
markdowns: [
|
| 169 |
+
'## Comandos de Git',
|
| 170 |
+
'',
|
| 171 |
+
'Tienes una gran variedad de comandos de git en este sandbox. Estos incluyen: ',
|
| 172 |
+
'',
|
| 173 |
+
' * commit',
|
| 174 |
+
' * branch',
|
| 175 |
+
' * checkout',
|
| 176 |
+
' * cherry-pick',
|
| 177 |
+
' * reset',
|
| 178 |
+
' * revert',
|
| 179 |
+
' * rebase',
|
| 180 |
+
' * merge'
|
| 181 |
+
]
|
| 182 |
+
}
|
| 183 |
+
}, {
|
| 184 |
+
type: 'ModalAlert',
|
| 185 |
+
options: {
|
| 186 |
+
markdowns: [
|
| 187 |
+
'## ¡Comparte!',
|
| 188 |
+
'',
|
| 189 |
+
'Comparte tus árboles con tus amigos usando `export tree` e `import tree`',
|
| 190 |
+
'',
|
| 191 |
+
'¿Tienes una buena lección que compartir? Prueba construyendo un nivel con `build level` o prueba el nivel de un amigo con `import level`',
|
| 192 |
+
'',
|
| 193 |
+
'Para ver todos los comandos disponibles, escribe `show commands`. Hay algunas joyitas como `undo` y `reset`',
|
| 194 |
+
'',
|
| 195 |
+
'Por ahora, empecemos con los `levels`...'
|
| 196 |
+
]
|
| 197 |
+
}
|
| 198 |
+
}],
|
| 199 |
+
'pt_BR': [{
|
| 200 |
+
type: 'ModalAlert',
|
| 201 |
+
options: {
|
| 202 |
+
markdowns: [
|
| 203 |
+
'## Bem-vindo ao Learn Git Branching!',
|
| 204 |
+
'',
|
| 205 |
+
'Este aplicativo foi desenvolvido para ajudar os iniciantes a ',
|
| 206 |
+
'aprender os poderosos conceitos por trás do branching com ',
|
| 207 |
+
'o git. Esperamos que você goste deste aplicativo e talvez ',
|
| 208 |
+
'até aprenda alguma coisa!',
|
| 209 |
+
'',
|
| 210 |
+
'# Demo!',
|
| 211 |
+
'',
|
| 212 |
+
'Se você ainda não viu o demo, veja aqui:',
|
| 213 |
+
'',
|
| 214 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=pt_BR&demo](https://pcottle.github.io/learnGitBranching/?locale=pt_BR&demo)',
|
| 215 |
+
'',
|
| 216 |
+
'Farto desta mensagem? Acrescente `NODEMO` ao endereço para se livrar dela, como no link abaixo:',
|
| 217 |
+
'',
|
| 218 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=pt_BR&NODEMO](https://pcottle.github.io/learnGitBranching/?locale=pt_BR&NODEMO)'
|
| 219 |
+
]
|
| 220 |
+
}
|
| 221 |
+
}, {
|
| 222 |
+
type: 'ModalAlert',
|
| 223 |
+
options: {
|
| 224 |
+
markdowns: [
|
| 225 |
+
'## Comandos do git',
|
| 226 |
+
'',
|
| 227 |
+
'Você tem à sua disposição no sandbox uma variedade de comandos do git:',
|
| 228 |
+
'',
|
| 229 |
+
' * commit',
|
| 230 |
+
' * branch',
|
| 231 |
+
' * checkout',
|
| 232 |
+
' * cherry-pick',
|
| 233 |
+
' * reset',
|
| 234 |
+
' * revert',
|
| 235 |
+
' * rebase',
|
| 236 |
+
' * merge'
|
| 237 |
+
]
|
| 238 |
+
}
|
| 239 |
+
}, {
|
| 240 |
+
type: 'ModalAlert',
|
| 241 |
+
options: {
|
| 242 |
+
markdowns: [
|
| 243 |
+
'## Compartilhar é se importar!',
|
| 244 |
+
'',
|
| 245 |
+
'Compartilhe árvores com seus amigos usando `export tree` e `import tree`',
|
| 246 |
+
'',
|
| 247 |
+
'Tem uma grande lição para compartilhar? Tente construir um nível com `build level` ou experimente o nível de um amigo com `import level`',
|
| 248 |
+
'',
|
| 249 |
+
'Para ver todos os comandos, use `show commands`. Há algumas jóias como `undo` e `reset`',
|
| 250 |
+
'',
|
| 251 |
+
'Por hora, vamos começar com o `levels`...'
|
| 252 |
+
]
|
| 253 |
+
}
|
| 254 |
+
}],
|
| 255 |
+
'gl': [{
|
| 256 |
+
type: 'ModalAlert',
|
| 257 |
+
options: {
|
| 258 |
+
markdowns: [
|
| 259 |
+
'## Benvido a Learn Git Branching!',
|
| 260 |
+
'',
|
| 261 |
+
'Esta aplicación foi desenvolvida para axudar os iniciados en git a ',
|
| 262 |
+
'aprender os poderosos conceptos que hai por detrás do branching con ',
|
| 263 |
+
' git. Agardamos que disfrutes desta aplicación, e tal vez, ',
|
| 264 |
+
'ata aprendas algunha cousa!',
|
| 265 |
+
'',
|
| 266 |
+
'# Demostracións!',
|
| 267 |
+
'',
|
| 268 |
+
'Se aínda non viches a demo, olla aquí:',
|
| 269 |
+
'',
|
| 270 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=gl&demo](https://pcottle.github.io/learnGitBranching/?locale=gl&demo)',
|
| 271 |
+
'',
|
| 272 |
+
'¿Farto destas mensaxes? Engade `NODEMO` á dirección para librarte dela, como no link de abaixo:',
|
| 273 |
+
'',
|
| 274 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=gl&NODEMO](https://pcottle.github.io/learnGitBranching/?locale=gl&NODEMO)'
|
| 275 |
+
]
|
| 276 |
+
}
|
| 277 |
+
}, {
|
| 278 |
+
type: 'ModalAlert',
|
| 279 |
+
options: {
|
| 280 |
+
markdowns: [
|
| 281 |
+
'## Comandos de git',
|
| 282 |
+
'',
|
| 283 |
+
'Tes a túa disposición unha caixa de área con unha variedade de comandos de git:',
|
| 284 |
+
'',
|
| 285 |
+
' * commit',
|
| 286 |
+
' * branch',
|
| 287 |
+
' * checkout',
|
| 288 |
+
' * cherry-pick',
|
| 289 |
+
' * reset',
|
| 290 |
+
' * revert',
|
| 291 |
+
' * rebase',
|
| 292 |
+
' * merge'
|
| 293 |
+
]
|
| 294 |
+
}
|
| 295 |
+
}, {
|
| 296 |
+
type: 'ModalAlert',
|
| 297 |
+
options: {
|
| 298 |
+
markdowns: [
|
| 299 |
+
'## Compartir e importar!',
|
| 300 |
+
'',
|
| 301 |
+
'Comparte árbores cos seus amigas con `export tree` e `import tree`',
|
| 302 |
+
'',
|
| 303 |
+
'¿Tes un enlace moi grande para compartir? Intenta construír un nivel con `build level` ou importe o nivel dun amigo con `import level`',
|
| 304 |
+
'',
|
| 305 |
+
'Para ver tódolos comandos, usa `show commands`. Hai algunha xoia como `undo` e `reset`',
|
| 306 |
+
'',
|
| 307 |
+
'Por agora, imos comezar cos `levels`...'
|
| 308 |
+
]
|
| 309 |
+
}
|
| 310 |
+
}],
|
| 311 |
+
'de_DE': [{
|
| 312 |
+
type: 'ModalAlert',
|
| 313 |
+
options: {
|
| 314 |
+
markdowns: [
|
| 315 |
+
'## Willkommen bei Learn Git Branching!',
|
| 316 |
+
'',
|
| 317 |
+
'Der Sinn dieser Anwendung ist, die umfangreichen und komplexen Zusammenhänge der Prozesse, die bei der Arbeit mit Git ablaufen, zu verdeutlichen. Ich hoffe du hast Spaß dabei und lernst vielleicht sogar etwas!',
|
| 318 |
+
'',
|
| 319 |
+
'# Demo!',
|
| 320 |
+
'',
|
| 321 |
+
'Falls du die Demonstration noch nicht gesehen hast, schau sie dir hier an:',
|
| 322 |
+
'',
|
| 323 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=de_DE&demo](https://pcottle.github.io/learnGitBranching/?locale=de_DE&demo)',
|
| 324 |
+
'',
|
| 325 |
+
'Genervt von diesem Fenster? Häng `NODEMO` an die URL um es los zu werden, so wie hier:',
|
| 326 |
+
'',
|
| 327 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=de_DE&NODEMO](https://pcottle.github.io/learnGitBranching/?locale=de_DE&NODEMO)'
|
| 328 |
+
]
|
| 329 |
+
}
|
| 330 |
+
}, {
|
| 331 |
+
type: 'ModalAlert',
|
| 332 |
+
options: {
|
| 333 |
+
markdowns: [
|
| 334 |
+
'## Git-Kommandos',
|
| 335 |
+
'',
|
| 336 |
+
'Dir steht eine große Zahl von Git-Befehlen im Sandkasten-Modus zur Verfügung. Unter anderem',
|
| 337 |
+
'',
|
| 338 |
+
' * commit',
|
| 339 |
+
' * branch',
|
| 340 |
+
' * checkout',
|
| 341 |
+
' * cherry-pick',
|
| 342 |
+
' * reset',
|
| 343 |
+
' * revert',
|
| 344 |
+
' * rebase',
|
| 345 |
+
' * merge'
|
| 346 |
+
]
|
| 347 |
+
}
|
| 348 |
+
}, {
|
| 349 |
+
type: 'ModalAlert',
|
| 350 |
+
options: {
|
| 351 |
+
markdowns: [
|
| 352 |
+
'## Teilen macht Spaß!',
|
| 353 |
+
'',
|
| 354 |
+
'Teile diese Git-Bäume mit deinen Freunden mittels `export tree` und `import tree`.',
|
| 355 |
+
'',
|
| 356 |
+
'Hast du Wissenswertes zu Git zu vermitteln? Versuch einen Level mit `build level` zu bauen oder probier den Level eines Freundes mit `import level` aus.',
|
| 357 |
+
'',
|
| 358 |
+
'Um alle Kommandos zu sehen, gib `show commands` ein. Darunter gibt\'s kleine Schätze wie `undo` und `reset`.',
|
| 359 |
+
'',
|
| 360 |
+
'Für\'s Erste lass uns mit `levels` anfangen ...'
|
| 361 |
+
]
|
| 362 |
+
}
|
| 363 |
+
}],
|
| 364 |
+
'ja': [{
|
| 365 |
+
type: 'ModalAlert',
|
| 366 |
+
options: {
|
| 367 |
+
markdowns: [
|
| 368 |
+
'## Learn Git Branchingへようこそ',
|
| 369 |
+
'',
|
| 370 |
+
'gitのパワフルなブランチ機能のコンセプトが ',
|
| 371 |
+
'学びやすくなるようにこのアプリケーションを作りました。 ',
|
| 372 |
+
'このアプリケーションを楽しんで使って頂いて、 ',
|
| 373 |
+
'何かを学習して頂けたなら嬉しいです。',
|
| 374 |
+
'',
|
| 375 |
+
'# とりあえず触ってみたい方へ:',
|
| 376 |
+
'',
|
| 377 |
+
'簡単なデモを用意してあるので、もしよければこちらもご覧ください:',
|
| 378 |
+
'',
|
| 379 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=ja&demo](https://pcottle.github.io/learnGitBranching/?demo&locale=ja)',
|
| 380 |
+
'',
|
| 381 |
+
'このダイアログ自体を省略するには、以下のようにURLの末尾にクエリストリング`NODEMO`を付加してアクセスしてください。',
|
| 382 |
+
'',
|
| 383 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=ja&NODEMO](https://pcottle.github.io/learnGitBranching/?NODEMO&locale=ja)'
|
| 384 |
+
]
|
| 385 |
+
}
|
| 386 |
+
}, {
|
| 387 |
+
type: 'ModalAlert',
|
| 388 |
+
options: {
|
| 389 |
+
markdowns: [
|
| 390 |
+
'## ここで学べるGitのオペレーション',
|
| 391 |
+
'',
|
| 392 |
+
'ここでは、下記の種類のgitコマンドを学ぶことができます。',
|
| 393 |
+
'',
|
| 394 |
+
' * commit',
|
| 395 |
+
' * branch',
|
| 396 |
+
' * checkout',
|
| 397 |
+
' * cherry-pick',
|
| 398 |
+
' * reset',
|
| 399 |
+
' * revert',
|
| 400 |
+
' * rebase',
|
| 401 |
+
' * merge'
|
| 402 |
+
]
|
| 403 |
+
}
|
| 404 |
+
}, {
|
| 405 |
+
type: 'ModalAlert',
|
| 406 |
+
options: {
|
| 407 |
+
markdowns: [
|
| 408 |
+
'## 学習した内容を共有できます',
|
| 409 |
+
'',
|
| 410 |
+
'画面左のコマンドプロンプトから`export tree`や`import tree`とタイプすることで、gitのツリー構造を友達に送ることができます',
|
| 411 |
+
'',
|
| 412 |
+
'何か教材になるようなケースはご存知ないでしょうか。`build level`で課題を作成したり、`import level`で他の人の課題に挑戦してみてください。',
|
| 413 |
+
'',
|
| 414 |
+
'何か詰まったことがあったら、右下メニューの?ボタンを押してみてください',
|
| 415 |
+
'',
|
| 416 |
+
'それから、不自然な記号が出てきたときは顔を左方向に傾けてみるといいかもしれません :P(ペロッ)',
|
| 417 |
+
'',
|
| 418 |
+
'それでは教材の選択画面に進んでみることにします。',
|
| 419 |
+
'',
|
| 420 |
+
'(なお、日本語版製作者のフォークサイトは[こちら](https://remore.github.io/learnGitBranching-ja/)になります。)'
|
| 421 |
+
]
|
| 422 |
+
}
|
| 423 |
+
}],
|
| 424 |
+
'zh_CN': [{
|
| 425 |
+
type: 'ModalAlert',
|
| 426 |
+
options: {
|
| 427 |
+
markdowns: [
|
| 428 |
+
'## 欢迎光临 Learn Git Branching',
|
| 429 |
+
'',
|
| 430 |
+
'你对 Git 感兴趣吗?那么算是来对地方了!',
|
| 431 |
+
'“Learning Git Branching” 可以说是目前为止最好的教程了,在沙盒里你能执行相应的命令,还能看到每个命令的执行情况;',
|
| 432 |
+
'通过一系列刺激的关卡挑战,逐步深入的学习 Git 的强大功能,在这个过程中你可能还会发现一些有意思的事情。',
|
| 433 |
+
'',
|
| 434 |
+
'关闭这个对话框以后,你会看到我们提供的许多关卡。如果你是初学者,从第一关开始逐个向后挑战就是了。',
|
| 435 |
+
'而如果你已经入门了,可以略过前面,直接挑战后面更有难度的关卡。',
|
| 436 |
+
'',
|
| 437 |
+
'### 演示',
|
| 438 |
+
'',
|
| 439 |
+
'如果你还没看过演示,请[到此](?demo)查看。',
|
| 440 |
+
'',
|
| 441 |
+
'PS:想直接进入沙盒? 在 URL 后头加上 `NODEMO` 就可以了,试一下[这个链接](https://pcottle.github.io/learnGitBranching/?locale=zh_CN&NODEMO)'
|
| 442 |
+
]
|
| 443 |
+
}
|
| 444 |
+
}],
|
| 445 |
+
'zh_TW': [{
|
| 446 |
+
type: 'ModalAlert',
|
| 447 |
+
options: {
|
| 448 |
+
markdowns: [
|
| 449 |
+
'## 歡迎光臨 Learn Git Branching!',
|
| 450 |
+
'',
|
| 451 |
+
'本應用旨在幫助初學者領會 git 分支背後的強大概念。',
|
| 452 |
+
'希望你能喜歡這個應用,並學到知識!',
|
| 453 |
+
'',
|
| 454 |
+
'# 演示!',
|
| 455 |
+
'',
|
| 456 |
+
'如果你還沒看過演示,請到此查看:',
|
| 457 |
+
'',
|
| 458 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=zh_TW&demo](https://pcottle.github.io/learnGitBranching/?locale=zh_TW&demo)',
|
| 459 |
+
'',
|
| 460 |
+
'厭煩這個對話視窗嗎?在 URL 後頭加上 `NODEMO` 就看不到它了,也可以直接點下邊這個連結:',
|
| 461 |
+
'',
|
| 462 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=zh_TW&NODEMO](https://pcottle.github.io/learnGitBranching/?locale=zh_TW&NODEMO)'
|
| 463 |
+
]
|
| 464 |
+
}
|
| 465 |
+
}, {
|
| 466 |
+
type: 'ModalAlert',
|
| 467 |
+
options: {
|
| 468 |
+
markdowns: [
|
| 469 |
+
'## Git 命令',
|
| 470 |
+
'',
|
| 471 |
+
'在沙盒模式中,你有很多指令可用。包括:',
|
| 472 |
+
'',
|
| 473 |
+
' * commit',
|
| 474 |
+
' * branch',
|
| 475 |
+
' * checkout',
|
| 476 |
+
' * cherry-pick',
|
| 477 |
+
' * reset',
|
| 478 |
+
' * revert',
|
| 479 |
+
' * rebase',
|
| 480 |
+
' * merge'
|
| 481 |
+
]
|
| 482 |
+
}
|
| 483 |
+
}, {
|
| 484 |
+
type: 'ModalAlert',
|
| 485 |
+
options: {
|
| 486 |
+
markdowns: [
|
| 487 |
+
'## 分享即關懷',
|
| 488 |
+
'',
|
| 489 |
+
'使用 `export tree` 和 `import tree` 與朋友分享 Git 樹',
|
| 490 |
+
'',
|
| 491 |
+
'有個好課程可以分享?試試用 `build level` 創建一個關卡,或者 `import level` 試試朋友的。',
|
| 492 |
+
'',
|
| 493 |
+
'言歸正傳,讓我們先從 `levels` 開始……'
|
| 494 |
+
]
|
| 495 |
+
}
|
| 496 |
+
}],
|
| 497 |
+
'ko': [{
|
| 498 |
+
type: 'ModalAlert',
|
| 499 |
+
options: {
|
| 500 |
+
markdowns: [
|
| 501 |
+
'## Git 브랜치 배우기를 시작합니다!',
|
| 502 |
+
'',
|
| 503 |
+
'이 애플리케이션은 git을 쓸 때 필요한 브랜치에 대한 개념을',
|
| 504 |
+
'탄탄히 잡게끔 도와드리기 위해 만들었습니다. 재밌게 사용해주시기를',
|
| 505 |
+
'바라며, 무언가를 배워가신다면 더 기쁘겠습니다!',
|
| 506 |
+
'',
|
| 507 |
+
'이 애플리케이션은 [Peter Cottle](https://github.io/pcottle)님의 [LearnGitBranching](https://pcottle.github.io/learnGitBranching/)를 번역한 것입니다.',
|
| 508 |
+
'아래 데모를 먼저 보셔도 좋습니다.',
|
| 509 |
+
'',
|
| 510 |
+
'<https://pcottle.github.io/learnGitBranching/?demo&locale=ko>'
|
| 511 |
+
]
|
| 512 |
+
}
|
| 513 |
+
}, {
|
| 514 |
+
type: 'ModalAlert',
|
| 515 |
+
options: {
|
| 516 |
+
markdowns: [
|
| 517 |
+
'## Git 명령어',
|
| 518 |
+
'',
|
| 519 |
+
'연습 모드에서 쓸 수 있는 다양한 git명령어는 다음과 같습니다',
|
| 520 |
+
'',
|
| 521 |
+
' * commit',
|
| 522 |
+
' * branch',
|
| 523 |
+
' * checkout',
|
| 524 |
+
' * cherry-pick',
|
| 525 |
+
' * reset',
|
| 526 |
+
' * revert',
|
| 527 |
+
' * rebase',
|
| 528 |
+
' * merge'
|
| 529 |
+
]
|
| 530 |
+
}
|
| 531 |
+
}, {
|
| 532 |
+
type: 'ModalAlert',
|
| 533 |
+
options: {
|
| 534 |
+
markdowns: [
|
| 535 |
+
'## 공유해주세요!',
|
| 536 |
+
'',
|
| 537 |
+
'`export tree` 와 `import tree`로 여러분의 친구들에게 트리를 공유해주세요',
|
| 538 |
+
'',
|
| 539 |
+
'훌륭한 학습 자료가 있으신가요? `build level`로 레벨을 만들어 보시거나, 친구의 레벨을 `import level`로 가져와서 실험해보세요',
|
| 540 |
+
'',
|
| 541 |
+
'이제 레슨을 시작해봅시다...'
|
| 542 |
+
]
|
| 543 |
+
}
|
| 544 |
+
}],
|
| 545 |
+
'fr_FR': [{
|
| 546 |
+
type: 'ModalAlert',
|
| 547 |
+
options: {
|
| 548 |
+
markdowns: [
|
| 549 |
+
'## Bienvenue sur Learn Git Branching !',
|
| 550 |
+
'',
|
| 551 |
+
'Cette application a été conçue pour aider les débutants à saisir ',
|
| 552 |
+
'les puissants concepts derrière les branches en travaillant ',
|
| 553 |
+
'avec git. Nous espérons que vous apprécierez cette application et ',
|
| 554 |
+
'que vous apprendrez peut-être quelque chose d\'intéressant !',
|
| 555 |
+
'',
|
| 556 |
+
'# Démo !',
|
| 557 |
+
'',
|
| 558 |
+
'Si vous n\'avez pas vu la démo, vous pouvez le faire là :',
|
| 559 |
+
'',
|
| 560 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=fr_FR&demo](https://pcottle.github.io/learnGitBranching/?locale=fr_FR&demo)',
|
| 561 |
+
'',
|
| 562 |
+
'Agacé par ce dialogue ? Ajoutez `NODEMO` à l\'URL pour le supprimer, en lien ci-dessous pour votre commodité :',
|
| 563 |
+
'',
|
| 564 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=fr_FR&NODEMO](https://pcottle.github.io/learnGitBranching/?locale=fr_FR&NODEMO)'
|
| 565 |
+
]
|
| 566 |
+
}
|
| 567 |
+
}, {
|
| 568 |
+
type: 'ModalAlert',
|
| 569 |
+
options: {
|
| 570 |
+
markdowns: [
|
| 571 |
+
'## Commandes Git',
|
| 572 |
+
'',
|
| 573 |
+
'Il existe une large variété de commandes git disponibles dans le mode bac à sable. Sont incluses :',
|
| 574 |
+
'',
|
| 575 |
+
' * commit',
|
| 576 |
+
' * branch',
|
| 577 |
+
' * checkout',
|
| 578 |
+
' * cherry-pick',
|
| 579 |
+
' * reset',
|
| 580 |
+
' * revert',
|
| 581 |
+
' * rebase',
|
| 582 |
+
' * merge'
|
| 583 |
+
]
|
| 584 |
+
}
|
| 585 |
+
}, {
|
| 586 |
+
type: 'ModalAlert',
|
| 587 |
+
options: {
|
| 588 |
+
markdowns: [
|
| 589 |
+
'## Partager, c\'est se soucier !',
|
| 590 |
+
'',
|
| 591 |
+
'Partagez des arbres avec vos amis via `export tree` et `import tree`',
|
| 592 |
+
'',
|
| 593 |
+
'Vous avez une grande leçon à partager ? Essayez de construire un niveau avec `build level` ou essayez le niveau d\'un ami avec `import level`',
|
| 594 |
+
'',
|
| 595 |
+
'Pour voir la gamme complète des commandes, tapez `show commands`. Il y a quelques perles telles que `undo` et `reset`',
|
| 596 |
+
'',
|
| 597 |
+
'Mais tout de suite commencez sur les `levels`…'
|
| 598 |
+
]
|
| 599 |
+
}
|
| 600 |
+
}],
|
| 601 |
+
'ru_RU': [{
|
| 602 |
+
type: 'ModalAlert',
|
| 603 |
+
options: {
|
| 604 |
+
markdowns: [
|
| 605 |
+
'## Добро пожаловать в LearnGitBranching!',
|
| 606 |
+
'',
|
| 607 |
+
'Это приложение создано, чтобы помочь новичкам постичь ',
|
| 608 |
+
'мощные возможности ветвления и работы ',
|
| 609 |
+
'с git. Мы надеемся, что вам понравится эта игра ',
|
| 610 |
+
'и может вы что-то усвоите!',
|
| 611 |
+
'',
|
| 612 |
+
'# Демо!',
|
| 613 |
+
'',
|
| 614 |
+
'Если ты не видел демонстрацию – посмотри её тут:',
|
| 615 |
+
'',
|
| 616 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=ru_RU&demo](https://pcottle.github.io/learnGitBranching/?locale=ru_RU&demo)',
|
| 617 |
+
'',
|
| 618 |
+
'Достало это сообщение? Добавь `NODEMO` к адресу и навсегда забудь о нём, ниже ссылка для удобства:',
|
| 619 |
+
'',
|
| 620 |
+
'[https://pcottle.github.io/learnGitBranching/?locale=ru_RU&NODEMO](?locale=ru_RU&NODEMO)'
|
| 621 |
+
]
|
| 622 |
+
}
|
| 623 |
+
}, {
|
| 624 |
+
type: 'ModalAlert',
|
| 625 |
+
options: {
|
| 626 |
+
markdowns: [
|
| 627 |
+
'## Команды Git',
|
| 628 |
+
'',
|
| 629 |
+
'В нашей песочнице можно использовать множество команд:',
|
| 630 |
+
'',
|
| 631 |
+
' * commit',
|
| 632 |
+
' * branch',
|
| 633 |
+
' * checkout',
|
| 634 |
+
' * cherry-pick',
|
| 635 |
+
' * reset',
|
| 636 |
+
' * revert',
|
| 637 |
+
' * rebase',
|
| 638 |
+
' * merge'
|
| 639 |
+
]
|
| 640 |
+
}
|
| 641 |
+
}, {
|
| 642 |
+
type: 'ModalAlert',
|
| 643 |
+
options: {
|
| 644 |
+
markdowns: [
|
| 645 |
+
'## Бог велел – делись!',
|
| 646 |
+
'',
|
| 647 |
+
'Ты можешь делиться результатами с друзьями при помощи `export tree` и `import tree`',
|
| 648 |
+
'',
|
| 649 |
+
'Хочешь создать классный уровень? Сделай это при помощи `build level` или добавь уровень друга при помощи `import level`',
|
| 650 |
+
'',
|
| 651 |
+
'Команда `show commands` покажет все доступные инструкции. Там есть очень полезные, например `undo` и `reset`',
|
| 652 |
+
'',
|
| 653 |
+
'А пока просто начни игру при помощи `levels`...'
|
| 654 |
+
]
|
| 655 |
+
}
|
| 656 |
+
}],
|
| 657 |
+
'uk': [{
|
| 658 |
+
type: 'ModalAlert',
|
| 659 |
+
options: {
|
| 660 |
+
markdowns: [
|
| 661 |
+
'## Ласкаво просимо до Learn Git Branching',
|
| 662 |
+
'',
|
| 663 |
+
'Хочеш вивчити Git? Тоді ти знайшов те, що шукав!',
|
| 664 |
+
'"Learn Git Branching" \u2014 це найбільш візуальний та інтерактивний спосіб вивчення Git в Інтернеті. ',
|
| 665 |
+
'Ти з��ожеш проходити захопливі рівні, дивитися ',
|
| 666 |
+
'покрокові інструкції з використання потужних функцій Git, навіть трохи ',
|
| 667 |
+
'розважитись в процесі навчання.',
|
| 668 |
+
'',
|
| 669 |
+
'Після цього діалогу побачиш список доступних рівнів. Якщо ти новачок, ',
|
| 670 |
+
'просто почни з першого рівня. Якщо вже знаєш основи Git, ',
|
| 671 |
+
'спробуй більш складні рівні в кінці.',
|
| 672 |
+
'',
|
| 673 |
+
'P.S. Хочеш перейти одразу до пісочниці наступного разу?',
|
| 674 |
+
'Спробуй ',
|
| 675 |
+
'[це спеціальне посилання.](https://pcottle.github.io/learnGitBranching/?locale=uk&NODEMO)'
|
| 676 |
+
]
|
| 677 |
+
}
|
| 678 |
+
}],
|
| 679 |
+
'vi': [{
|
| 680 |
+
type: 'ModalAlert',
|
| 681 |
+
options: {
|
| 682 |
+
markdowns: [
|
| 683 |
+
'## Chào mừng đến với Học Nhánh Git',
|
| 684 |
+
'',
|
| 685 |
+
'Bạn có hứng thú học Git? Bạn đến đúng nơi rồi đấy! ',
|
| 686 |
+
'"Học Nhánh Git" là cách trực quan và hiệu quả nhất để học Git trên web; ',
|
| 687 |
+
'thông qua một loạt các thử thách cấp độ thú vị, bạn sẽ từng bước tìm hiểu sức mạnh của git',
|
| 688 |
+
'',
|
| 689 |
+
'Sau khi hội thoại này đóng lại, bạn sẽ thấy nhiều cấp độ mà chúng tôi cung cấp. ',
|
| 690 |
+
'Nếu bạn là người mới thì hãy bắt đầu từ bài đầu tiên. Nếu bạn đã có hiểu biết cơ bản về git, ',
|
| 691 |
+
'hãy thử những bài mang tính thách thức hơn phía sau.',
|
| 692 |
+
'',
|
| 693 |
+
'Bạn có thể dùng lệnh `show commands` để xem tất cả các lệnh hiện hữu.',
|
| 694 |
+
'',
|
| 695 |
+
'Ghi chú: Nếu muốn trực tiếp vào hộp cát ở lần sau?',
|
| 696 |
+
'Hãy dùng',
|
| 697 |
+
'[đường link đặc biệt này của chúng tôi](https://pcottle.github.io/learnGitBranching/?locale=vi&NODEMO)'
|
| 698 |
+
]
|
| 699 |
+
}
|
| 700 |
+
}],
|
| 701 |
+
'sl_SI': [{
|
| 702 |
+
type: 'ModalAlert',
|
| 703 |
+
options: {
|
| 704 |
+
markdowns: [
|
| 705 |
+
'## Dobrodošel na učenju Git Branchanja',
|
| 706 |
+
'',
|
| 707 |
+
'Bi se rad naučil Git? No, prišel si na pravo mesto! ',
|
| 708 |
+
'"Learn Git Branching" je najbolj vizualen in interaktiven način učenja Git-a ',
|
| 709 |
+
'na spletu; zagrizel boš v zanimive stopnje, po korakih boš spoznaval osupljive ',
|
| 710 |
+
'funkcije in kaj pa veš, morda ti bo celo zabavno. ;)',
|
| 711 |
+
'',
|
| 712 |
+
'Za tem oknom boš videl kopico stopenj, ki so na razpolago. Če si ',
|
| 713 |
+
'začetnik, kar pogumno, začni s prvo. Če pa že poznaš Git osnove, ',
|
| 714 |
+
'se preizkusi v zahtevnejših stopnjah.',
|
| 715 |
+
'',
|
| 716 |
+
'Vidiš lahko vse ukaze, ki so na voljo, z ukazom `show commands` v terminalu.',
|
| 717 |
+
'',
|
| 718 |
+
'PS: Bi šel rad naslednjič naravnost v peskovnik?',
|
| 719 |
+
'Poizkusi s',
|
| 720 |
+
'[to posebno povezavo](https://pcottle.github.io/learnGitBranching/?locale=sl_SI&NODEMO)'
|
| 721 |
+
]
|
| 722 |
+
}
|
| 723 |
+
}],
|
| 724 |
+
'pl': [{
|
| 725 |
+
type: 'ModalAlert',
|
| 726 |
+
options: {
|
| 727 |
+
markdowns: [
|
| 728 |
+
'## Witaj w Learn Git Branching!',
|
| 729 |
+
'',
|
| 730 |
+
'Jesteś zainteresowany nauką Gita? Cóż, trafiłeś we właściwe miejsce!',
|
| 731 |
+
'"Learn Git Branching" jest najbardziej wizualnym i interaktywnym sposobem na naukę gita w sieci.',
|
| 732 |
+
'Czekają na Ciebie ekscytujące poziomy, demonstracje zaawansowanych funkcji krok po kroku. Może nawet będziesz się dobrze bawić.',
|
| 733 |
+
'',
|
| 734 |
+
'Po tym oknie dialogowym zobaczysz różnorodność poziomów, które mamy do zaoferowania.',
|
| 735 |
+
'Jeśli jesteś początkujący, po prostu zacznij od pierwszego poziomu.',
|
| 736 |
+
'Jeśli znasz już podstawy gita, wypróbuj niektóre z naszych późniejszych, bardziej wymagających poziomów.',
|
| 737 |
+
'',
|
| 738 |
+
'Możesz zobaczyć wszystkie komendy wpisując `show commands` w terminalu.',
|
| 739 |
+
'',
|
| 740 |
+
'Chcesz następnym razem przejść prosto do trybu piaskownicy? Kilknij [tutaj](https://pcottle.github.io/learnGitBranching/?locale=pl&NODEMO)',
|
| 741 |
+
'',
|
| 742 |
+
'PS. GitHub zaczął nazywać domyślną gałąź `main` zamiast `master`, aby odejść od tendencyjnej terminologii. [(więcej informacji tutaj)](https://github.com/github/renaming)',,
|
| 743 |
+
'Zgodnie z tym ogólnobranżowym ruchem, zaktualizowaliśmy również "Learn Git Branching", by używać `main` zamiast `master` w naszych zadaniach.',
|
| 744 |
+
'Ta zmiana nazwy powinna być już w miarę spójna, ale jeśli zauważysz jakieś błędy, nie krępuj się dodać pull request (lub zgłosić issue na githubie - prawy górny róg).'
|
| 745 |
+
]
|
| 746 |
+
}
|
| 747 |
+
}],
|
| 748 |
+
'ta_IN': [{
|
| 749 |
+
type: 'ModalAlert',
|
| 750 |
+
options: {
|
| 751 |
+
markdowns: [
|
| 752 |
+
'## Git Branching கற்க வரவேற்கிறோம்',
|
| 753 |
+
'',
|
| 754 |
+
'கிட் கற்க ஆர்வமா? அப்படியானால் நீங்கள் சரியான இடத்திற்கு வந்துவிட்டீர்கள்! ',
|
| 755 |
+
'"Learn Git Branching" Git ஐக் கற்றுக்கொள்வதற்கான வரைபடம் மற்றும் செயல்முறை ',
|
| 756 |
+
'பயிற்சியுடன் கூடிய சிரந்த கருவி; ',
|
| 757 |
+
'உங்களை சோதிக்கும் வகையிலான நிலைகளுடன் மிகுந்த சக்திவாய்ந்த அம்சங்களை ',
|
| 758 |
+
'படிப்படியாகவும், சில சமையம் விளையாட்டாகவும் கற்றுத்தர கூடியது.',
|
| 759 |
+
'',
|
| 760 |
+
'இந்த அறிவிற்ப்புக்கு பிறகு, நாங்கள் வழங்க உள்ள பல்வேறு நிலைகளை நீங்கள் காண்பீர்கள். ',
|
| 761 |
+
'நீங்கள் ஆரம்ம நிலையில் இருந்தால், முதல் கட்டத்தில் இருந்து தொடங்கவும். ',
|
| 762 |
+
'கிட்டின் சில அடிப்படைகளை நீங்கள் ஏற்கனவே அறிந்திருந்தால், மெலும் உள்ள கடினமான கட்டங்களை முயற்ச்சி செய்யுங்கள்.',
|
| 763 |
+
'',
|
| 764 |
+
'தேவையானால் `show commands` ஐ பயன்படுத்தி அனைத்து கட்டளைகளையும் முனையத்தில் பார்க்கலாம்.',
|
| 765 |
+
'',
|
| 766 |
+
'பின்குறிப்பு: அடுத்தமுறை நேராக sandbox செல்ல வேண்டுமா?',
|
| 767 |
+
'அப்படியானால் பின் வரும் இணைப்பை பயன்பாடித்துக ',
|
| 768 |
+
'[this special link](https://pcottle.github.io/learnGitBranching/?locale=ta_IN&NODEMO)',
|
| 769 |
+
'',
|
| 770 |
+
'பின்குறிப்பு: GitHub (பெரிய அளவில் பயன்பாட்டில் உள்ள இணையதலம்) `main` என்ற கிளையை `master`-க்கு பதில் ',
|
| 771 |
+
'முன்னிருப்பு கிளையாக பயன் படுத்த உள்ளது [more details available here](https://github.com/github/renaming). ',
|
| 772 |
+
'இந்த மாற்றத்தை பின்னோக்கி இணக்கமான வழியில் பொருத்துவதற்காக, `main`-ஐ முதன்மையாக கருதி ',
|
| 773 |
+
'இந்த இரண்டு பெயர்களும் ஒன்றுக்கொன்று மாற்றுப்பெயர்களாகக் கருதப்படும். ',
|
| 774 |
+
'இந்த மாற்றத்தை அனைத்து நிலை உள்ளடக்கங்களிலும் புதுப்பிக்க நாங்கள் சிறந்த முயற்சியை ',
|
| 775 |
+
'மேற்கொண்டோம், ஆயினும் ஏதேனும் விடுபட்டி இருந்தால் PR உருவாக்கி உதவுங்கள்.',
|
| 776 |
+
'ஒருபக்கச்சார்பான சொற்களிலிருந்து விலகிச் செல்ல உதவியதற்கு நன்றி.'
|
| 777 |
+
]
|
| 778 |
+
}
|
| 779 |
+
}],
|
| 780 |
+
"it_IT": [
|
| 781 |
+
{
|
| 782 |
+
type: "ModalAlert",
|
| 783 |
+
options: {
|
| 784 |
+
markdowns: [
|
| 785 |
+
"## Benvenuto su Learn Git Branching",
|
| 786 |
+
"",
|
| 787 |
+
"Vorresti imparare Git? Bene, sei nel posto giusto! ",
|
| 788 |
+
'"Learn Git Branching" è il modo più chiaro e interattivo per imparare Git ',
|
| 789 |
+
"su internet; sarai messo alla prova tramite livelli stimolanti, con dimostrazioni ",
|
| 790 |
+
"passo a passo sulle sue potenzialità e, perché no, magari ti divertirai lungo questo percorso.",
|
| 791 |
+
"",
|
| 792 |
+
"Dopo questa finestra vedrai una varietà di livelli che abbiamo da offrire. Se sei alle",
|
| 793 |
+
"prime armi procedi e parti dall'inizio. Se hai delle conoscenze base di Git ",
|
| 794 |
+
"prova con gli ultimi livelli più impegnativi.",
|
| 795 |
+
"",
|
| 796 |
+
"Puoi vedere tutti i comandi disponibili digitando `show commands` sul terminale.",
|
| 797 |
+
"",
|
| 798 |
+
"PS: Preferisci andare direttamente al sandbox?",
|
| 799 |
+
"Prova ",
|
| 800 |
+
"[questo link](https://pcottle.github.io/learnGitBranching/?NODEMO?locale=it_IT).",
|
| 801 |
+
"",
|
| 802 |
+
"PPS: GitHub (e il settore in generale) sta modificando il nome del ramo di default in `main` invece che `master` ",
|
| 803 |
+
"(leggi [qui per ulteriori dettagli](https://github.com/github/renaming)). Per adattarci a questo cambiamento ",
|
| 804 |
+
"mantenendo la retrocompatibilità, questi nomi saranno considerati equivalenti. `main` sarà comunque ",
|
| 805 |
+
"il nome predefinito. Ci siamo impegnati per aggiornare tutti i livelli ma ci sarà ",
|
| 806 |
+
"sicuramente qualcosa che potremmo aver dimenticato. Esegui una PR (o segnala un problema) se ne trovi. ",
|
| 807 |
+
"Grazie per l'aiuto!",
|
| 808 |
+
],
|
| 809 |
+
},
|
| 810 |
+
},
|
| 811 |
+
],
|
| 812 |
+
};
|
src/js/dispatcher/AppDispatcher.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use strict';
|
| 2 |
+
|
| 3 |
+
var AppConstants = require('../constants/AppConstants');
|
| 4 |
+
var Dispatcher = require('flux').Dispatcher;
|
| 5 |
+
|
| 6 |
+
var PayloadSources = AppConstants.PayloadSources;
|
| 7 |
+
|
| 8 |
+
var AppDispatcher = new Dispatcher();
|
| 9 |
+
|
| 10 |
+
AppDispatcher.handleViewAction = function(action) {
|
| 11 |
+
this.dispatch({
|
| 12 |
+
source: PayloadSources.VIEW_ACTION,
|
| 13 |
+
action: action
|
| 14 |
+
});
|
| 15 |
+
};
|
| 16 |
+
|
| 17 |
+
AppDispatcher.handleURIAction = function(action) {
|
| 18 |
+
this.dispatch({
|
| 19 |
+
source: PayloadSources.URI_ACTION,
|
| 20 |
+
action: action
|
| 21 |
+
});
|
| 22 |
+
};
|
| 23 |
+
|
| 24 |
+
module.exports = AppDispatcher;
|
src/js/git/commands.js
ADDED
|
@@ -0,0 +1,955 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var escapeString = require('../util/escapeString');
|
| 2 |
+
var intl = require('../intl');
|
| 3 |
+
|
| 4 |
+
var Graph = require('../graph');
|
| 5 |
+
var Errors = require('../util/errors');
|
| 6 |
+
var CommandProcessError = Errors.CommandProcessError;
|
| 7 |
+
var GitError = Errors.GitError;
|
| 8 |
+
var Warning = Errors.Warning;
|
| 9 |
+
var CommandResult = Errors.CommandResult;
|
| 10 |
+
|
| 11 |
+
var ORIGIN_PREFIX = 'o/';
|
| 12 |
+
|
| 13 |
+
var crappyUnescape = function(str) {
|
| 14 |
+
return str.replace(/'/g, "'").replace(///g, "/");
|
| 15 |
+
};
|
| 16 |
+
|
| 17 |
+
function isColonRefspec(str) {
|
| 18 |
+
return str.indexOf(':') !== -1 && str.split(':').length === 2;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
var assertIsRef = function(engine, ref) {
|
| 22 |
+
engine.resolveID(ref); // will throw git error if can't resolve
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
+
var validateBranchName = function(engine, name) {
|
| 26 |
+
return engine.validateBranchName(name);
|
| 27 |
+
};
|
| 28 |
+
|
| 29 |
+
var validateOriginBranchName = function(engine, name) {
|
| 30 |
+
return engine.origin.validateBranchName(name);
|
| 31 |
+
};
|
| 32 |
+
|
| 33 |
+
var validateBranchNameIfNeeded = function(engine, name) {
|
| 34 |
+
if (engine.refs[name]) {
|
| 35 |
+
return name;
|
| 36 |
+
}
|
| 37 |
+
return validateBranchName(engine, name);
|
| 38 |
+
};
|
| 39 |
+
|
| 40 |
+
var assertNotCheckedOut = function(engine, ref) {
|
| 41 |
+
if (!engine.refs[ref]) {
|
| 42 |
+
return;
|
| 43 |
+
}
|
| 44 |
+
if (engine.HEAD.get('target') === engine.refs[ref]) {
|
| 45 |
+
throw new GitError({
|
| 46 |
+
msg: intl.todo(
|
| 47 |
+
'cannot fetch to ' + ref + ' when checked out on ' + ref
|
| 48 |
+
)
|
| 49 |
+
});
|
| 50 |
+
}
|
| 51 |
+
};
|
| 52 |
+
|
| 53 |
+
var assertIsBranch = function(engine, ref) {
|
| 54 |
+
assertIsRef(engine, ref);
|
| 55 |
+
var obj = engine.resolveID(ref);
|
| 56 |
+
if (!obj || obj.get('type') !== 'branch') {
|
| 57 |
+
throw new GitError({
|
| 58 |
+
msg: intl.todo(
|
| 59 |
+
ref + ' is not a branch'
|
| 60 |
+
)
|
| 61 |
+
});
|
| 62 |
+
}
|
| 63 |
+
};
|
| 64 |
+
|
| 65 |
+
var assertIsRemoteBranch = function(engine, ref) {
|
| 66 |
+
assertIsRef(engine, ref);
|
| 67 |
+
var obj = engine.resolveID(ref);
|
| 68 |
+
|
| 69 |
+
if (obj.get('type') !== 'branch' ||
|
| 70 |
+
!obj.getIsRemote()) {
|
| 71 |
+
throw new GitError({
|
| 72 |
+
msg: intl.todo(
|
| 73 |
+
ref + ' is not a remote branch'
|
| 74 |
+
)
|
| 75 |
+
});
|
| 76 |
+
}
|
| 77 |
+
};
|
| 78 |
+
|
| 79 |
+
var assertOriginSpecified = function(generalArgs) {
|
| 80 |
+
if (!generalArgs.length) {
|
| 81 |
+
return;
|
| 82 |
+
}
|
| 83 |
+
if (generalArgs[0] !== 'origin') {
|
| 84 |
+
throw new GitError({
|
| 85 |
+
msg: intl.todo(
|
| 86 |
+
generalArgs[0] + ' is not a remote in your repository! try adding origin to that argument'
|
| 87 |
+
)
|
| 88 |
+
});
|
| 89 |
+
}
|
| 90 |
+
};
|
| 91 |
+
|
| 92 |
+
var assertBranchIsRemoteTracking = function(engine, branchName) {
|
| 93 |
+
branchName = crappyUnescape(branchName);
|
| 94 |
+
if (!engine.resolveID(branchName)) {
|
| 95 |
+
throw new GitError({
|
| 96 |
+
msg: intl.todo(branchName + ' is not a branch!')
|
| 97 |
+
});
|
| 98 |
+
}
|
| 99 |
+
var branch = engine.resolveID(branchName);
|
| 100 |
+
if (branch.get('type') !== 'branch') {
|
| 101 |
+
throw new GitError({
|
| 102 |
+
msg: intl.todo(branchName + ' is not a branch!')
|
| 103 |
+
});
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
var tracking = branch.getRemoteTrackingBranchID();
|
| 107 |
+
if (!tracking) {
|
| 108 |
+
throw new GitError({
|
| 109 |
+
msg: intl.todo(
|
| 110 |
+
branchName + ' is not a remote tracking branch! I don\'t know where to push'
|
| 111 |
+
)
|
| 112 |
+
});
|
| 113 |
+
}
|
| 114 |
+
return tracking;
|
| 115 |
+
};
|
| 116 |
+
|
| 117 |
+
var commandConfig = {
|
| 118 |
+
commit: {
|
| 119 |
+
sc: /^(gc|git ci)($|\s)/,
|
| 120 |
+
regex: /^git +commit($|\s)/,
|
| 121 |
+
options: [
|
| 122 |
+
'--amend',
|
| 123 |
+
'-a',
|
| 124 |
+
'--all',
|
| 125 |
+
'-am',
|
| 126 |
+
'-m'
|
| 127 |
+
],
|
| 128 |
+
execute: function(engine, command) {
|
| 129 |
+
var commandOptions = command.getOptionsMap();
|
| 130 |
+
command.acceptNoGeneralArgs();
|
| 131 |
+
|
| 132 |
+
if (commandOptions['-am'] && (
|
| 133 |
+
commandOptions['-a'] || commandOptions['--all'] || commandOptions['-m'])) {
|
| 134 |
+
throw new GitError({
|
| 135 |
+
msg: intl.str('git-error-options')
|
| 136 |
+
});
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
var msg = null;
|
| 140 |
+
var args = null;
|
| 141 |
+
if (commandOptions['-a'] || commandOptions['--all']) {
|
| 142 |
+
command.addWarning(intl.str('git-warning-add'));
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
if (commandOptions['-am']) {
|
| 146 |
+
args = commandOptions['-am'];
|
| 147 |
+
command.validateArgBounds(args, 1, 1, '-am');
|
| 148 |
+
msg = args[0];
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
if (commandOptions['-m']) {
|
| 152 |
+
args = commandOptions['-m'];
|
| 153 |
+
command.validateArgBounds(args, 1, 1, '-m');
|
| 154 |
+
msg = args[0];
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
if (commandOptions['--amend']) {
|
| 158 |
+
args = commandOptions['--amend'];
|
| 159 |
+
command.validateArgBounds(args, 0, 0, '--amend');
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
var newCommit = engine.commit({
|
| 163 |
+
isAmend: !!commandOptions['--amend']
|
| 164 |
+
});
|
| 165 |
+
if (msg) {
|
| 166 |
+
msg = msg
|
| 167 |
+
.replace(/"/g, '"')
|
| 168 |
+
.replace(/^"/g, '')
|
| 169 |
+
.replace(/"$/g, '');
|
| 170 |
+
|
| 171 |
+
newCommit.set('commitMessage', msg);
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
var promise = engine.animationFactory.playCommitBirthPromiseAnimation(
|
| 175 |
+
newCommit,
|
| 176 |
+
engine.gitVisuals
|
| 177 |
+
);
|
| 178 |
+
engine.animationQueue.thenFinish(promise);
|
| 179 |
+
}
|
| 180 |
+
},
|
| 181 |
+
|
| 182 |
+
cherrypick: {
|
| 183 |
+
displayName: 'cherry-pick',
|
| 184 |
+
regex: /^git +cherry-pick($|\s)/,
|
| 185 |
+
execute: function(engine, command) {
|
| 186 |
+
var commandOptions = command.getOptionsMap();
|
| 187 |
+
var generalArgs = command.getGeneralArgs();
|
| 188 |
+
|
| 189 |
+
command.validateArgBounds(generalArgs, 1, Number.MAX_VALUE);
|
| 190 |
+
|
| 191 |
+
var set = Graph.getUpstreamSet(engine, 'HEAD');
|
| 192 |
+
// first resolve all the refs (as an error check)
|
| 193 |
+
var toCherrypick = generalArgs.map(function (arg) {
|
| 194 |
+
var commit = engine.getCommitFromRef(arg);
|
| 195 |
+
// and check that its not upstream
|
| 196 |
+
if (set[commit.get('id')]) {
|
| 197 |
+
throw new GitError({
|
| 198 |
+
msg: intl.str(
|
| 199 |
+
'git-error-already-exists',
|
| 200 |
+
{ commit: commit.get('id') }
|
| 201 |
+
)
|
| 202 |
+
});
|
| 203 |
+
}
|
| 204 |
+
return commit;
|
| 205 |
+
}, this);
|
| 206 |
+
|
| 207 |
+
engine.setupCherrypickChain(toCherrypick);
|
| 208 |
+
}
|
| 209 |
+
},
|
| 210 |
+
|
| 211 |
+
gc: {
|
| 212 |
+
displayName: 'gc',
|
| 213 |
+
regex: /^git +gc($|\s)/,
|
| 214 |
+
execute: function(engine, command) {
|
| 215 |
+
engine.pruneTree(false);
|
| 216 |
+
}
|
| 217 |
+
},
|
| 218 |
+
|
| 219 |
+
pull: {
|
| 220 |
+
regex: /^git +pull($|\s)/,
|
| 221 |
+
options: [
|
| 222 |
+
'--rebase'
|
| 223 |
+
],
|
| 224 |
+
execute: function(engine, command) {
|
| 225 |
+
if (!engine.hasOrigin()) {
|
| 226 |
+
throw new GitError({
|
| 227 |
+
msg: intl.str('git-error-origin-required')
|
| 228 |
+
});
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
var commandOptions = command.getOptionsMap();
|
| 232 |
+
var generalArgs = command.getGeneralArgs();
|
| 233 |
+
command.twoArgsForOrigin(generalArgs);
|
| 234 |
+
assertOriginSpecified(generalArgs);
|
| 235 |
+
// here is the deal -- git pull is pretty complex with
|
| 236 |
+
// the arguments it wants. You can
|
| 237 |
+
// A) specify the remote branch you want to
|
| 238 |
+
// merge & fetch, in which case it completely
|
| 239 |
+
// ignores the properties of branch you are on, or
|
| 240 |
+
//
|
| 241 |
+
// B) specify no args, in which case it figures out
|
| 242 |
+
// the branch to fetch from the remote tracking
|
| 243 |
+
// and merges those in, or
|
| 244 |
+
//
|
| 245 |
+
// C) specify the colon refspec like fetch, where it does
|
| 246 |
+
// the fetch and then just merges the dest
|
| 247 |
+
|
| 248 |
+
var source;
|
| 249 |
+
var destination;
|
| 250 |
+
var firstArg = generalArgs[1];
|
| 251 |
+
// COPY PASTA validation code from fetch. maybe fix this?
|
| 252 |
+
if (firstArg && isColonRefspec(firstArg)) {
|
| 253 |
+
var refspecParts = firstArg.split(':');
|
| 254 |
+
source = refspecParts[0];
|
| 255 |
+
destination = validateBranchNameIfNeeded(
|
| 256 |
+
engine,
|
| 257 |
+
crappyUnescape(refspecParts[1])
|
| 258 |
+
);
|
| 259 |
+
assertNotCheckedOut(engine, destination);
|
| 260 |
+
} else if (firstArg) {
|
| 261 |
+
source = firstArg;
|
| 262 |
+
assertIsBranch(engine.origin, source);
|
| 263 |
+
// get o/main locally if main is specified
|
| 264 |
+
destination = engine.origin.resolveID(source).getPrefixedID();
|
| 265 |
+
} else {
|
| 266 |
+
// can't be detached
|
| 267 |
+
if (engine.getDetachedHead()) {
|
| 268 |
+
throw new GitError({
|
| 269 |
+
msg: intl.todo('Git pull can not be executed in detached HEAD mode if no remote branch specified!')
|
| 270 |
+
});
|
| 271 |
+
}
|
| 272 |
+
// ok we need to get our currently checked out branch
|
| 273 |
+
// and then specify source and dest
|
| 274 |
+
var branch = engine.getOneBeforeCommit('HEAD');
|
| 275 |
+
var branchName = branch.get('id');
|
| 276 |
+
assertBranchIsRemoteTracking(engine, branchName);
|
| 277 |
+
destination = branch.getRemoteTrackingBranchID();
|
| 278 |
+
source = destination.replace(ORIGIN_PREFIX, '');
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
engine.pull({
|
| 282 |
+
source: source,
|
| 283 |
+
destination: destination,
|
| 284 |
+
isRebase: !!commandOptions['--rebase']
|
| 285 |
+
});
|
| 286 |
+
}
|
| 287 |
+
},
|
| 288 |
+
|
| 289 |
+
fakeTeamwork: {
|
| 290 |
+
regex: /^git +fakeTeamwork($|\s)/,
|
| 291 |
+
execute: function(engine, command) {
|
| 292 |
+
var generalArgs = command.getGeneralArgs();
|
| 293 |
+
if (!engine.hasOrigin()) {
|
| 294 |
+
throw new GitError({
|
| 295 |
+
msg: intl.str('git-error-origin-required')
|
| 296 |
+
});
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
command.validateArgBounds(generalArgs, 0, 2);
|
| 300 |
+
var branch;
|
| 301 |
+
var numToMake;
|
| 302 |
+
|
| 303 |
+
// allow formats of: git fakeTeamwork 2 or git fakeTeamwork side 3
|
| 304 |
+
switch (generalArgs.length) {
|
| 305 |
+
// git fakeTeamwork
|
| 306 |
+
case 0:
|
| 307 |
+
branch = 'main';
|
| 308 |
+
numToMake = 1;
|
| 309 |
+
break;
|
| 310 |
+
|
| 311 |
+
// git fakeTeamwork 10 or git fakeTeamwork foo
|
| 312 |
+
case 1:
|
| 313 |
+
if (isNaN(parseInt(generalArgs[0], 10))) {
|
| 314 |
+
branch = validateOriginBranchName(engine, generalArgs[0]);
|
| 315 |
+
numToMake = 1;
|
| 316 |
+
} else {
|
| 317 |
+
numToMake = parseInt(generalArgs[0], 10);
|
| 318 |
+
branch = 'main';
|
| 319 |
+
}
|
| 320 |
+
break;
|
| 321 |
+
|
| 322 |
+
case 2:
|
| 323 |
+
branch = validateOriginBranchName(engine, generalArgs[0]);
|
| 324 |
+
if (isNaN(parseInt(generalArgs[1], 10))) {
|
| 325 |
+
throw new GitError({
|
| 326 |
+
msg: 'Bad numeric argument: ' + generalArgs[1]
|
| 327 |
+
});
|
| 328 |
+
}
|
| 329 |
+
numToMake = parseInt(generalArgs[1], 10);
|
| 330 |
+
break;
|
| 331 |
+
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
// make sure its a branch and exists
|
| 335 |
+
var destBranch = engine.origin.resolveID(branch);
|
| 336 |
+
if (destBranch.get('type') !== 'branch') {
|
| 337 |
+
throw new GitError({
|
| 338 |
+
msg: intl.str('git-error-options')
|
| 339 |
+
});
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
engine.fakeTeamwork(numToMake, branch);
|
| 343 |
+
}
|
| 344 |
+
},
|
| 345 |
+
|
| 346 |
+
clone: {
|
| 347 |
+
regex: /^git +clone *?$/,
|
| 348 |
+
execute: function(engine, command) {
|
| 349 |
+
command.acceptNoGeneralArgs();
|
| 350 |
+
engine.makeOrigin(engine.printTree());
|
| 351 |
+
}
|
| 352 |
+
},
|
| 353 |
+
|
| 354 |
+
remote: {
|
| 355 |
+
regex: /^git +remote($|\s)/,
|
| 356 |
+
options: [
|
| 357 |
+
'-v'
|
| 358 |
+
],
|
| 359 |
+
execute: function(engine, command) {
|
| 360 |
+
command.acceptNoGeneralArgs();
|
| 361 |
+
if (!engine.hasOrigin()) {
|
| 362 |
+
throw new CommandResult({
|
| 363 |
+
msg: ''
|
| 364 |
+
});
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
engine.printRemotes({
|
| 368 |
+
verbose: !!command.getOptionsMap()['-v']
|
| 369 |
+
});
|
| 370 |
+
}
|
| 371 |
+
},
|
| 372 |
+
|
| 373 |
+
fetch: {
|
| 374 |
+
regex: /^git +fetch($|\s)/,
|
| 375 |
+
execute: function(engine, command) {
|
| 376 |
+
if (!engine.hasOrigin()) {
|
| 377 |
+
throw new GitError({
|
| 378 |
+
msg: intl.str('git-error-origin-required')
|
| 379 |
+
});
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
var source;
|
| 383 |
+
var destination;
|
| 384 |
+
var generalArgs = command.getGeneralArgs();
|
| 385 |
+
command.twoArgsForOrigin(generalArgs);
|
| 386 |
+
assertOriginSpecified(generalArgs);
|
| 387 |
+
|
| 388 |
+
var firstArg = generalArgs[1];
|
| 389 |
+
if (firstArg && isColonRefspec(firstArg)) {
|
| 390 |
+
var refspecParts = firstArg.split(':');
|
| 391 |
+
source = refspecParts[0];
|
| 392 |
+
destination = validateBranchNameIfNeeded(
|
| 393 |
+
engine,
|
| 394 |
+
crappyUnescape(refspecParts[1])
|
| 395 |
+
);
|
| 396 |
+
assertNotCheckedOut(engine, destination);
|
| 397 |
+
} else if (firstArg) {
|
| 398 |
+
// here is the deal -- its JUST like git push. the first arg
|
| 399 |
+
// is used as both the destination and the source, so we need
|
| 400 |
+
// to make sure it exists as the source on REMOTE. however
|
| 401 |
+
// technically we have a destination here as the remote branch
|
| 402 |
+
source = firstArg;
|
| 403 |
+
assertIsBranch(engine.origin, source);
|
| 404 |
+
// get o/main locally if main is specified
|
| 405 |
+
destination = engine.origin.resolveID(source).getPrefixedID();
|
| 406 |
+
}
|
| 407 |
+
if (source) { // empty string fails this check
|
| 408 |
+
assertIsRef(engine.origin, source);
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
engine.fetch({
|
| 412 |
+
source: source,
|
| 413 |
+
destination: destination
|
| 414 |
+
});
|
| 415 |
+
}
|
| 416 |
+
},
|
| 417 |
+
|
| 418 |
+
branch: {
|
| 419 |
+
sc: /^(gb|git br)($|\s)/,
|
| 420 |
+
regex: /^git +branch($|\s)/,
|
| 421 |
+
options: [
|
| 422 |
+
'-d',
|
| 423 |
+
'-D',
|
| 424 |
+
'-f',
|
| 425 |
+
'--force',
|
| 426 |
+
'-a',
|
| 427 |
+
'-r',
|
| 428 |
+
'-u',
|
| 429 |
+
'--contains'
|
| 430 |
+
],
|
| 431 |
+
execute: function(engine, command) {
|
| 432 |
+
var commandOptions = command.getOptionsMap();
|
| 433 |
+
var generalArgs = command.getGeneralArgs();
|
| 434 |
+
|
| 435 |
+
var args = null;
|
| 436 |
+
// handle deletion first
|
| 437 |
+
if (commandOptions['-d'] || commandOptions['-D']) {
|
| 438 |
+
var names = commandOptions['-d'] || commandOptions['-D'];
|
| 439 |
+
names = names.concat(generalArgs);
|
| 440 |
+
command.validateArgBounds(names, 1, Number.MAX_VALUE, '-d');
|
| 441 |
+
|
| 442 |
+
names.forEach(function(name) {
|
| 443 |
+
engine.validateAndDeleteBranch(name);
|
| 444 |
+
});
|
| 445 |
+
return;
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
if (commandOptions['-u']) {
|
| 449 |
+
args = commandOptions['-u'].concat(generalArgs);
|
| 450 |
+
command.validateArgBounds(args, 1, 2, '-u');
|
| 451 |
+
var remoteBranch = crappyUnescape(args[0]);
|
| 452 |
+
var branch = args[1] || engine.getOneBeforeCommit('HEAD').get('id');
|
| 453 |
+
|
| 454 |
+
// some assertions, both of these have to exist first
|
| 455 |
+
assertIsRemoteBranch(engine, remoteBranch);
|
| 456 |
+
assertIsBranch(engine, branch);
|
| 457 |
+
engine.setLocalToTrackRemote(
|
| 458 |
+
engine.resolveID(branch),
|
| 459 |
+
engine.resolveID(remoteBranch)
|
| 460 |
+
);
|
| 461 |
+
return;
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
if (commandOptions['--contains']) {
|
| 465 |
+
args = commandOptions['--contains'];
|
| 466 |
+
command.validateArgBounds(args, 1, 1, '--contains');
|
| 467 |
+
engine.printBranchesWithout(args[0]);
|
| 468 |
+
return;
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
if (commandOptions['-f'] || commandOptions['--force']) {
|
| 472 |
+
args = commandOptions['-f'] || commandOptions['--force'];
|
| 473 |
+
args = args.concat(generalArgs);
|
| 474 |
+
command.twoArgsImpliedHead(args, '-f');
|
| 475 |
+
|
| 476 |
+
// we want to force a branch somewhere
|
| 477 |
+
engine.forceBranch(args[0], args[1]);
|
| 478 |
+
return;
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
|
| 482 |
+
if (generalArgs.length === 0) {
|
| 483 |
+
var branches;
|
| 484 |
+
if (commandOptions['-a']) {
|
| 485 |
+
branches = engine.getBranches();
|
| 486 |
+
} else if (commandOptions['-r']) {
|
| 487 |
+
branches = engine.getRemoteBranches();
|
| 488 |
+
} else {
|
| 489 |
+
branches = engine.getLocalBranches();
|
| 490 |
+
}
|
| 491 |
+
engine.printBranches(branches);
|
| 492 |
+
return;
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
command.twoArgsImpliedHead(generalArgs);
|
| 496 |
+
engine.branch(generalArgs[0], generalArgs[1]);
|
| 497 |
+
}
|
| 498 |
+
},
|
| 499 |
+
|
| 500 |
+
add: {
|
| 501 |
+
dontCountForGolf: true,
|
| 502 |
+
sc: /^ga($|\s)/,
|
| 503 |
+
regex: /^git +add($|\s)/,
|
| 504 |
+
execute: function() {
|
| 505 |
+
throw new CommandResult({
|
| 506 |
+
msg: intl.str('git-error-staging')
|
| 507 |
+
});
|
| 508 |
+
}
|
| 509 |
+
},
|
| 510 |
+
|
| 511 |
+
reset: {
|
| 512 |
+
regex: /^git +reset($|\s)/,
|
| 513 |
+
options: [
|
| 514 |
+
'--hard',
|
| 515 |
+
'--soft'
|
| 516 |
+
],
|
| 517 |
+
execute: function(engine, command) {
|
| 518 |
+
var commandOptions = command.getOptionsMap();
|
| 519 |
+
var generalArgs = command.getGeneralArgs();
|
| 520 |
+
|
| 521 |
+
if (commandOptions['--soft']) {
|
| 522 |
+
throw new GitError({
|
| 523 |
+
msg: intl.str('git-error-staging')
|
| 524 |
+
});
|
| 525 |
+
}
|
| 526 |
+
if (commandOptions['--hard']) {
|
| 527 |
+
command.addWarning(
|
| 528 |
+
intl.str('git-warning-hard')
|
| 529 |
+
);
|
| 530 |
+
// don't absorb the arg off of --hard
|
| 531 |
+
generalArgs = generalArgs.concat(commandOptions['--hard']);
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
command.validateArgBounds(generalArgs, 1, 1);
|
| 535 |
+
|
| 536 |
+
if (engine.getDetachedHead()) {
|
| 537 |
+
throw new GitError({
|
| 538 |
+
msg: intl.str('git-error-reset-detached')
|
| 539 |
+
});
|
| 540 |
+
}
|
| 541 |
+
|
| 542 |
+
engine.reset(generalArgs[0]);
|
| 543 |
+
}
|
| 544 |
+
},
|
| 545 |
+
|
| 546 |
+
revert: {
|
| 547 |
+
regex: /^git +revert($|\s)/,
|
| 548 |
+
execute: function(engine, command) {
|
| 549 |
+
var generalArgs = command.getGeneralArgs();
|
| 550 |
+
|
| 551 |
+
command.validateArgBounds(generalArgs, 1, Number.MAX_VALUE);
|
| 552 |
+
engine.revert(generalArgs);
|
| 553 |
+
}
|
| 554 |
+
},
|
| 555 |
+
|
| 556 |
+
merge: {
|
| 557 |
+
regex: /^git +merge($|\s)/,
|
| 558 |
+
options: [
|
| 559 |
+
'--no-ff'
|
| 560 |
+
],
|
| 561 |
+
execute: function(engine, command) {
|
| 562 |
+
var commandOptions = command.getOptionsMap();
|
| 563 |
+
var generalArgs = command.getGeneralArgs().concat(commandOptions['--no-ff'] || []);
|
| 564 |
+
command.validateArgBounds(generalArgs, 1, 1);
|
| 565 |
+
|
| 566 |
+
var newCommit = engine.merge(
|
| 567 |
+
generalArgs[0],
|
| 568 |
+
{ noFF: !!commandOptions['--no-ff'] }
|
| 569 |
+
);
|
| 570 |
+
|
| 571 |
+
if (newCommit === undefined) {
|
| 572 |
+
// its just a fast forward
|
| 573 |
+
engine.animationFactory.refreshTree(
|
| 574 |
+
engine.animationQueue, engine.gitVisuals
|
| 575 |
+
);
|
| 576 |
+
return;
|
| 577 |
+
}
|
| 578 |
+
|
| 579 |
+
engine.animationFactory.genCommitBirthAnimation(
|
| 580 |
+
engine.animationQueue, newCommit, engine.gitVisuals
|
| 581 |
+
);
|
| 582 |
+
}
|
| 583 |
+
},
|
| 584 |
+
|
| 585 |
+
revlist: {
|
| 586 |
+
dontCountForGolf: true,
|
| 587 |
+
displayName: 'rev-list',
|
| 588 |
+
regex: /^git +rev-list($|\s)/,
|
| 589 |
+
execute: function(engine, command) {
|
| 590 |
+
var generalArgs = command.getGeneralArgs();
|
| 591 |
+
command.validateArgBounds(generalArgs, 1);
|
| 592 |
+
|
| 593 |
+
engine.revlist(generalArgs);
|
| 594 |
+
}
|
| 595 |
+
},
|
| 596 |
+
|
| 597 |
+
log: {
|
| 598 |
+
dontCountForGolf: true,
|
| 599 |
+
regex: /^git +log($|\s)/,
|
| 600 |
+
execute: function(engine, command) {
|
| 601 |
+
var generalArgs = command.getGeneralArgs();
|
| 602 |
+
|
| 603 |
+
command.impliedHead(generalArgs, 0);
|
| 604 |
+
engine.log(generalArgs);
|
| 605 |
+
}
|
| 606 |
+
},
|
| 607 |
+
|
| 608 |
+
show: {
|
| 609 |
+
dontCountForGolf: true,
|
| 610 |
+
regex: /^git +show($|\s)/,
|
| 611 |
+
execute: function(engine, command) {
|
| 612 |
+
var generalArgs = command.getGeneralArgs();
|
| 613 |
+
command.oneArgImpliedHead(generalArgs);
|
| 614 |
+
engine.show(generalArgs[0]);
|
| 615 |
+
}
|
| 616 |
+
},
|
| 617 |
+
|
| 618 |
+
rebase: {
|
| 619 |
+
sc: /^gr($|\s)/,
|
| 620 |
+
options: [
|
| 621 |
+
'-i',
|
| 622 |
+
'--solution-ordering',
|
| 623 |
+
'--interactive-test',
|
| 624 |
+
'--aboveAll',
|
| 625 |
+
'-p',
|
| 626 |
+
'--preserve-merges',
|
| 627 |
+
'--onto'
|
| 628 |
+
],
|
| 629 |
+
regex: /^git +rebase($|\s)/,
|
| 630 |
+
execute: function(engine, command) {
|
| 631 |
+
var commandOptions = command.getOptionsMap();
|
| 632 |
+
var generalArgs = command.getGeneralArgs();
|
| 633 |
+
|
| 634 |
+
if (commandOptions['-i']) {
|
| 635 |
+
var args = commandOptions['-i'].concat(generalArgs);
|
| 636 |
+
command.twoArgsImpliedHead(args, ' -i');
|
| 637 |
+
|
| 638 |
+
if (commandOptions['--interactive-test']) {
|
| 639 |
+
engine.rebaseInteractiveTest(
|
| 640 |
+
args[0],
|
| 641 |
+
args[1], {
|
| 642 |
+
interactiveTest: commandOptions['--interactive-test']
|
| 643 |
+
}
|
| 644 |
+
);
|
| 645 |
+
} else {
|
| 646 |
+
engine.rebaseInteractive(
|
| 647 |
+
args[0],
|
| 648 |
+
args[1], {
|
| 649 |
+
aboveAll: !!commandOptions['--aboveAll'],
|
| 650 |
+
initialCommitOrdering: commandOptions['--solution-ordering']
|
| 651 |
+
}
|
| 652 |
+
);
|
| 653 |
+
}
|
| 654 |
+
return;
|
| 655 |
+
}
|
| 656 |
+
|
| 657 |
+
if (commandOptions['--onto']) {
|
| 658 |
+
var args = commandOptions['--onto'].concat(generalArgs);
|
| 659 |
+
command.threeArgsImpliedHead(args, ' --onto');
|
| 660 |
+
|
| 661 |
+
engine.rebaseOnto(args[0], args[1], args[2], {
|
| 662 |
+
preserveMerges: commandOptions['-p'] || commandOptions['--preserve-merges']
|
| 663 |
+
});
|
| 664 |
+
|
| 665 |
+
return;
|
| 666 |
+
}
|
| 667 |
+
|
| 668 |
+
command.twoArgsImpliedHead(generalArgs);
|
| 669 |
+
engine.rebase(generalArgs[0], generalArgs[1], {
|
| 670 |
+
preserveMerges: commandOptions['-p'] || commandOptions['--preserve-merges']
|
| 671 |
+
});
|
| 672 |
+
}
|
| 673 |
+
},
|
| 674 |
+
|
| 675 |
+
status: {
|
| 676 |
+
dontCountForGolf: true,
|
| 677 |
+
sc: /^(gst|gs|git st)($|\s)/,
|
| 678 |
+
regex: /^git +status($|\s)/,
|
| 679 |
+
execute: function(engine) {
|
| 680 |
+
// no parsing at all
|
| 681 |
+
engine.status();
|
| 682 |
+
}
|
| 683 |
+
},
|
| 684 |
+
|
| 685 |
+
checkout: {
|
| 686 |
+
sc: /^(go|git co)($|\s)/,
|
| 687 |
+
regex: /^git +checkout($|\s)/,
|
| 688 |
+
options: [
|
| 689 |
+
'-b',
|
| 690 |
+
'-B',
|
| 691 |
+
'-'
|
| 692 |
+
],
|
| 693 |
+
execute: function(engine, command) {
|
| 694 |
+
var commandOptions = command.getOptionsMap();
|
| 695 |
+
var generalArgs = command.getGeneralArgs();
|
| 696 |
+
|
| 697 |
+
var args = null;
|
| 698 |
+
if (commandOptions['-b']) {
|
| 699 |
+
// the user is really trying to just make a
|
| 700 |
+
// branch and then switch to it. so first:
|
| 701 |
+
args = commandOptions['-b'].concat(generalArgs);
|
| 702 |
+
command.twoArgsImpliedHead(args, '-b');
|
| 703 |
+
|
| 704 |
+
var validId = engine.validateBranchName(args[0]);
|
| 705 |
+
engine.branch(validId, args[1]);
|
| 706 |
+
engine.checkout(validId);
|
| 707 |
+
return;
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
if (commandOptions['-']) {
|
| 711 |
+
// get the heads last location
|
| 712 |
+
var lastPlace = engine.HEAD.get('lastLastTarget');
|
| 713 |
+
if (!lastPlace) {
|
| 714 |
+
throw new GitError({
|
| 715 |
+
msg: intl.str('git-result-nothing')
|
| 716 |
+
});
|
| 717 |
+
}
|
| 718 |
+
engine.HEAD.set('target', lastPlace);
|
| 719 |
+
return;
|
| 720 |
+
}
|
| 721 |
+
|
| 722 |
+
if (commandOptions['-B']) {
|
| 723 |
+
args = commandOptions['-B'].concat(generalArgs);
|
| 724 |
+
command.twoArgsImpliedHead(args, '-B');
|
| 725 |
+
|
| 726 |
+
engine.forceBranch(args[0], args[1]);
|
| 727 |
+
engine.checkout(args[0]);
|
| 728 |
+
return;
|
| 729 |
+
}
|
| 730 |
+
|
| 731 |
+
command.validateArgBounds(generalArgs, 1, 1);
|
| 732 |
+
|
| 733 |
+
engine.checkout(engine.crappyUnescape(generalArgs[0]));
|
| 734 |
+
}
|
| 735 |
+
},
|
| 736 |
+
|
| 737 |
+
push: {
|
| 738 |
+
regex: /^git +push($|\s)/,
|
| 739 |
+
options: [
|
| 740 |
+
'--force'
|
| 741 |
+
],
|
| 742 |
+
execute: function(engine, command) {
|
| 743 |
+
if (!engine.hasOrigin()) {
|
| 744 |
+
throw new GitError({
|
| 745 |
+
msg: intl.str('git-error-origin-required')
|
| 746 |
+
});
|
| 747 |
+
}
|
| 748 |
+
|
| 749 |
+
var options = {};
|
| 750 |
+
var destination;
|
| 751 |
+
var source;
|
| 752 |
+
var sourceObj;
|
| 753 |
+
var commandOptions = command.getOptionsMap();
|
| 754 |
+
|
| 755 |
+
// git push is pretty complex in terms of
|
| 756 |
+
// the arguments it wants as well... get ready!
|
| 757 |
+
var generalArgs = command.getGeneralArgs();
|
| 758 |
+
command.twoArgsForOrigin(generalArgs);
|
| 759 |
+
assertOriginSpecified(generalArgs);
|
| 760 |
+
|
| 761 |
+
var firstArg = generalArgs[1];
|
| 762 |
+
if (firstArg && isColonRefspec(firstArg)) {
|
| 763 |
+
var refspecParts = firstArg.split(':');
|
| 764 |
+
source = refspecParts[0];
|
| 765 |
+
destination = validateBranchName(engine, refspecParts[1]);
|
| 766 |
+
if (source === "" && !engine.origin.resolveID(destination)) {
|
| 767 |
+
throw new GitError({
|
| 768 |
+
msg: intl.todo(
|
| 769 |
+
'cannot delete branch ' + options.destination + ' which doesnt exist'
|
| 770 |
+
)
|
| 771 |
+
});
|
| 772 |
+
}
|
| 773 |
+
} else {
|
| 774 |
+
if (firstArg) {
|
| 775 |
+
// we are using this arg as destination AND source. the dest branch
|
| 776 |
+
// can be created on demand but we at least need this to be a source
|
| 777 |
+
// locally otherwise we will fail
|
| 778 |
+
assertIsRef(engine, firstArg);
|
| 779 |
+
sourceObj = engine.resolveID(firstArg);
|
| 780 |
+
} else {
|
| 781 |
+
// since they have not specified a source or destination, then
|
| 782 |
+
// we source from the branch we are on (or HEAD)
|
| 783 |
+
sourceObj = engine.getOneBeforeCommit('HEAD');
|
| 784 |
+
}
|
| 785 |
+
source = sourceObj.get('id');
|
| 786 |
+
|
| 787 |
+
// HOWEVER we push to either the remote tracking branch we have
|
| 788 |
+
// OR a new named branch if we aren't tracking anything
|
| 789 |
+
if (sourceObj.getRemoteTrackingBranchID &&
|
| 790 |
+
sourceObj.getRemoteTrackingBranchID()) {
|
| 791 |
+
assertBranchIsRemoteTracking(engine, source);
|
| 792 |
+
var remoteBranch = sourceObj.getRemoteTrackingBranchID();
|
| 793 |
+
destination = engine.resolveID(remoteBranch).getBaseID();
|
| 794 |
+
} else {
|
| 795 |
+
destination = validateBranchName(engine, source);
|
| 796 |
+
}
|
| 797 |
+
}
|
| 798 |
+
if (source) {
|
| 799 |
+
assertIsRef(engine, source);
|
| 800 |
+
}
|
| 801 |
+
|
| 802 |
+
engine.push({
|
| 803 |
+
// NOTE -- very important! destination and source here
|
| 804 |
+
// are always, always strings. very important :D
|
| 805 |
+
destination: destination,
|
| 806 |
+
source: source,
|
| 807 |
+
force: !!commandOptions['--force']
|
| 808 |
+
});
|
| 809 |
+
}
|
| 810 |
+
},
|
| 811 |
+
|
| 812 |
+
describe: {
|
| 813 |
+
regex: /^git +describe($|\s)/,
|
| 814 |
+
execute: function(engine, command) {
|
| 815 |
+
// first if there are no tags, we cant do anything so just throw
|
| 816 |
+
if (engine.tagCollection.toArray().length === 0) {
|
| 817 |
+
throw new GitError({
|
| 818 |
+
msg: intl.todo(
|
| 819 |
+
'fatal: No tags found, cannot describe anything.'
|
| 820 |
+
)
|
| 821 |
+
});
|
| 822 |
+
}
|
| 823 |
+
|
| 824 |
+
var generalArgs = command.getGeneralArgs();
|
| 825 |
+
command.oneArgImpliedHead(generalArgs);
|
| 826 |
+
assertIsRef(engine, generalArgs[0]);
|
| 827 |
+
|
| 828 |
+
engine.describe(generalArgs[0]);
|
| 829 |
+
}
|
| 830 |
+
},
|
| 831 |
+
|
| 832 |
+
tag: {
|
| 833 |
+
regex: /^git +tag($|\s)/,
|
| 834 |
+
options: [
|
| 835 |
+
'-d'
|
| 836 |
+
],
|
| 837 |
+
execute: function(engine, command) {
|
| 838 |
+
var generalArgs = command.getGeneralArgs();
|
| 839 |
+
var commandOptions = command.getOptionsMap();
|
| 840 |
+
|
| 841 |
+
if (commandOptions['-d']) {
|
| 842 |
+
var tagID = commandOptions['-d'];
|
| 843 |
+
var tagToRemove;
|
| 844 |
+
|
| 845 |
+
assertIsRef(engine, tagID);
|
| 846 |
+
|
| 847 |
+
command.oneArgImpliedHead(tagID);
|
| 848 |
+
engine.tagCollection.each(function(tag) {
|
| 849 |
+
if(tag.get('id') == tagID){
|
| 850 |
+
tagToRemove = tag;
|
| 851 |
+
}
|
| 852 |
+
}, true);
|
| 853 |
+
|
| 854 |
+
if(tagToRemove == undefined){
|
| 855 |
+
throw new GitError({
|
| 856 |
+
msg: intl.todo(
|
| 857 |
+
'No tag found, nothing to remove'
|
| 858 |
+
)
|
| 859 |
+
});
|
| 860 |
+
}
|
| 861 |
+
|
| 862 |
+
engine.tagCollection.remove(tagToRemove);
|
| 863 |
+
delete engine.refs[tagID];
|
| 864 |
+
|
| 865 |
+
engine.gitVisuals.refreshTree();
|
| 866 |
+
return;
|
| 867 |
+
}
|
| 868 |
+
|
| 869 |
+
if (generalArgs.length === 0) {
|
| 870 |
+
var tags = engine.getTags();
|
| 871 |
+
engine.printTags(tags);
|
| 872 |
+
return;
|
| 873 |
+
}
|
| 874 |
+
|
| 875 |
+
command.twoArgsImpliedHead(generalArgs);
|
| 876 |
+
engine.tag(generalArgs[0], generalArgs[1]);
|
| 877 |
+
}
|
| 878 |
+
},
|
| 879 |
+
|
| 880 |
+
switch: {
|
| 881 |
+
sc: /^(gsw|git sw)($|\s)/,
|
| 882 |
+
regex: /^git +switch($|\s)/,
|
| 883 |
+
options: [
|
| 884 |
+
'-c',
|
| 885 |
+
'-'
|
| 886 |
+
],
|
| 887 |
+
execute: function(engine, command) {
|
| 888 |
+
var generalArgs = command.getGeneralArgs();
|
| 889 |
+
var commandOptions = command.getOptionsMap();
|
| 890 |
+
|
| 891 |
+
var args = null;
|
| 892 |
+
if (commandOptions['-c']) {
|
| 893 |
+
// the user is really trying to just make a
|
| 894 |
+
// branch and then switch to it. so first:
|
| 895 |
+
args = commandOptions['-c'].concat(generalArgs);
|
| 896 |
+
command.twoArgsImpliedHead(args, '-c');
|
| 897 |
+
|
| 898 |
+
var validId = engine.validateBranchName(args[0]);
|
| 899 |
+
engine.branch(validId, args[1]);
|
| 900 |
+
engine.checkout(validId);
|
| 901 |
+
return;
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
if (commandOptions['-']) {
|
| 905 |
+
// get the heads last location
|
| 906 |
+
var lastPlace = engine.HEAD.get('lastLastTarget');
|
| 907 |
+
if (!lastPlace) {
|
| 908 |
+
throw new GitError({
|
| 909 |
+
msg: intl.str('git-result-nothing')
|
| 910 |
+
});
|
| 911 |
+
}
|
| 912 |
+
engine.HEAD.set('target', lastPlace);
|
| 913 |
+
return;
|
| 914 |
+
}
|
| 915 |
+
|
| 916 |
+
command.validateArgBounds(generalArgs, 1, 1);
|
| 917 |
+
|
| 918 |
+
engine.checkout(engine.crappyUnescape(generalArgs[0]));
|
| 919 |
+
}
|
| 920 |
+
}
|
| 921 |
+
};
|
| 922 |
+
|
| 923 |
+
var instantCommands = [
|
| 924 |
+
[/^(git help($|\s)|git$)/, function() {
|
| 925 |
+
var lines = [
|
| 926 |
+
intl.str('git-version'),
|
| 927 |
+
'<br/>',
|
| 928 |
+
intl.str('git-usage'),
|
| 929 |
+
escapeString(intl.str('git-usage-command')),
|
| 930 |
+
'<br/>',
|
| 931 |
+
intl.str('git-supported-commands'),
|
| 932 |
+
'<br/>'
|
| 933 |
+
];
|
| 934 |
+
|
| 935 |
+
var commands = require('../commands').commands.getOptionMap()['git'];
|
| 936 |
+
// build up a nice display of what we support
|
| 937 |
+
Object.keys(commands).forEach(function(command) {
|
| 938 |
+
var commandOptions = commands[command];
|
| 939 |
+
lines.push('git ' + command);
|
| 940 |
+
Object.keys(commandOptions).forEach(function(optionName) {
|
| 941 |
+
lines.push('\t ' + optionName);
|
| 942 |
+
}, this);
|
| 943 |
+
}, this);
|
| 944 |
+
|
| 945 |
+
// format and throw
|
| 946 |
+
var msg = lines.join('\n');
|
| 947 |
+
msg = msg.replace(/\t/g, ' ');
|
| 948 |
+
throw new CommandResult({
|
| 949 |
+
msg: msg
|
| 950 |
+
});
|
| 951 |
+
}]
|
| 952 |
+
];
|
| 953 |
+
|
| 954 |
+
exports.commandConfig = commandConfig;
|
| 955 |
+
exports.instantCommands = instantCommands;
|
src/js/git/gitShim.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var Q = require('q');
|
| 2 |
+
|
| 3 |
+
var Main = require('../app');
|
| 4 |
+
var MultiView = require('../views/multiView').MultiView;
|
| 5 |
+
|
| 6 |
+
function GitShim(options) {
|
| 7 |
+
options = options || {};
|
| 8 |
+
|
| 9 |
+
// these variables are just functions called before / after for
|
| 10 |
+
// simple things (like incrementing a counter)
|
| 11 |
+
this.beforeCB = options.beforeCB || function() {};
|
| 12 |
+
this.afterCB = options.afterCB || function() {};
|
| 13 |
+
|
| 14 |
+
// these guys handle an optional async process before the git
|
| 15 |
+
// command executes or afterwards. If there is none,
|
| 16 |
+
// it just resolves the deferred immediately
|
| 17 |
+
var resolveImmediately = function(deferred) {
|
| 18 |
+
deferred.resolve();
|
| 19 |
+
};
|
| 20 |
+
this.beforeDeferHandler = options.beforeDeferHandler || resolveImmediately;
|
| 21 |
+
this.afterDeferHandler = options.afterDeferHandler || resolveImmediately;
|
| 22 |
+
this.eventBaton = options.eventBaton || Main.getEventBaton();
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
GitShim.prototype.insertShim = function() {
|
| 26 |
+
this.eventBaton.stealBaton('processGitCommand', this.processGitCommand, this);
|
| 27 |
+
};
|
| 28 |
+
|
| 29 |
+
GitShim.prototype.removeShim = function() {
|
| 30 |
+
this.eventBaton.releaseBaton('processGitCommand', this.processGitCommand, this);
|
| 31 |
+
};
|
| 32 |
+
|
| 33 |
+
GitShim.prototype.processGitCommand = function(command, deferred) {
|
| 34 |
+
this.beforeCB(command);
|
| 35 |
+
|
| 36 |
+
// ok we make a NEW deferred that will, upon resolution,
|
| 37 |
+
// call our afterGitCommandProcessed. This inserts the 'after' shim
|
| 38 |
+
// functionality. we give this new deferred to the eventBaton handler
|
| 39 |
+
var newDeferred = Q.defer();
|
| 40 |
+
newDeferred.promise
|
| 41 |
+
.then(function() {
|
| 42 |
+
// give this method the original defer so it can resolve it
|
| 43 |
+
this.afterGitCommandProcessed(command, deferred);
|
| 44 |
+
}.bind(this))
|
| 45 |
+
.done();
|
| 46 |
+
|
| 47 |
+
// now our shim owner might want to launch some kind of deferred beforehand, like
|
| 48 |
+
// a modal or something. in order to do this, we need to defer the passing
|
| 49 |
+
// of the event baton backwards, and either resolve that promise immediately or
|
| 50 |
+
// give it to our shim owner.
|
| 51 |
+
var passBaton = function() {
|
| 52 |
+
// punt to the previous listener
|
| 53 |
+
this.eventBaton.passBatonBack('processGitCommand', this.processGitCommand, this, [command, newDeferred]);
|
| 54 |
+
}.bind(this);
|
| 55 |
+
|
| 56 |
+
var beforeDefer = Q.defer();
|
| 57 |
+
beforeDefer.promise
|
| 58 |
+
.then(passBaton)
|
| 59 |
+
.done();
|
| 60 |
+
|
| 61 |
+
// if we didnt receive a defer handler in the options, this just
|
| 62 |
+
// resolves immediately
|
| 63 |
+
this.beforeDeferHandler(beforeDefer, command);
|
| 64 |
+
};
|
| 65 |
+
|
| 66 |
+
GitShim.prototype.afterGitCommandProcessed = function(command, deferred) {
|
| 67 |
+
this.afterCB(command);
|
| 68 |
+
|
| 69 |
+
// again we can't just resolve this deferred right away... our shim owner might
|
| 70 |
+
// want to insert some promise functionality before that happens. so again
|
| 71 |
+
// we make a defer
|
| 72 |
+
var afterDefer = Q.defer();
|
| 73 |
+
afterDefer.promise
|
| 74 |
+
.then(function() {
|
| 75 |
+
deferred.resolve();
|
| 76 |
+
})
|
| 77 |
+
.done();
|
| 78 |
+
|
| 79 |
+
this.afterDeferHandler(afterDefer, command);
|
| 80 |
+
};
|
| 81 |
+
|
| 82 |
+
exports.GitShim = GitShim;
|
| 83 |
+
|
src/js/git/headless.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var Backbone = require('backbone');
|
| 2 |
+
var Q = require('q');
|
| 3 |
+
|
| 4 |
+
var GitEngine = require('../git').GitEngine;
|
| 5 |
+
var AnimationFactory = require('../visuals/animation/animationFactory').AnimationFactory;
|
| 6 |
+
var GitVisuals = require('../visuals').GitVisuals;
|
| 7 |
+
var TreeCompare = require('../graph/treeCompare');
|
| 8 |
+
var EventBaton = require('../util/eventBaton').EventBaton;
|
| 9 |
+
|
| 10 |
+
var Collections = require('../models/collections');
|
| 11 |
+
var CommitCollection = Collections.CommitCollection;
|
| 12 |
+
var BranchCollection = Collections.BranchCollection;
|
| 13 |
+
var TagCollection = Collections.TagCollection;
|
| 14 |
+
var Command = require('../models/commandModel').Command;
|
| 15 |
+
|
| 16 |
+
var mock = require('../util/mock').mock;
|
| 17 |
+
var util = require('../util');
|
| 18 |
+
|
| 19 |
+
function getMockFactory() {
|
| 20 |
+
var mockFactory = {};
|
| 21 |
+
var mockReturn = function() {
|
| 22 |
+
var d = Q.defer();
|
| 23 |
+
// fall through!
|
| 24 |
+
d.resolve();
|
| 25 |
+
return d.promise;
|
| 26 |
+
};
|
| 27 |
+
for (var key in AnimationFactory) {
|
| 28 |
+
mockFactory[key] = mockReturn;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
mockFactory.playRefreshAnimationAndFinish = function(gitVisuals, aQueue) {
|
| 32 |
+
aQueue.finish();
|
| 33 |
+
};
|
| 34 |
+
mockFactory.refreshTree = function(aQueue, gitVisuals) {
|
| 35 |
+
aQueue.finish();
|
| 36 |
+
};
|
| 37 |
+
|
| 38 |
+
mockFactory.highlightEachWithPromise = function(chain, toRebase, destBranch) {
|
| 39 |
+
// don't add any steps
|
| 40 |
+
return chain;
|
| 41 |
+
};
|
| 42 |
+
|
| 43 |
+
return mockFactory;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
function getMockVisualization() {
|
| 47 |
+
return {
|
| 48 |
+
makeOrigin: function(options) {
|
| 49 |
+
var localRepo = options.localRepo;
|
| 50 |
+
var treeString = options.treeString;
|
| 51 |
+
|
| 52 |
+
var headless = new HeadlessGit();
|
| 53 |
+
headless.gitEngine.loadTreeFromString(treeString);
|
| 54 |
+
return {
|
| 55 |
+
customEvents: {
|
| 56 |
+
on: function(key, cb, context) {
|
| 57 |
+
cb.apply(context, []);
|
| 58 |
+
}
|
| 59 |
+
},
|
| 60 |
+
gitEngine: headless.gitEngine
|
| 61 |
+
};
|
| 62 |
+
}
|
| 63 |
+
};
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
var HeadlessGit = function() {
|
| 67 |
+
this.init();
|
| 68 |
+
};
|
| 69 |
+
|
| 70 |
+
HeadlessGit.prototype.init = function() {
|
| 71 |
+
this.commitCollection = new CommitCollection();
|
| 72 |
+
this.branchCollection = new BranchCollection();
|
| 73 |
+
this.tagCollection = new TagCollection();
|
| 74 |
+
|
| 75 |
+
// here we mock visuals and animation factory so the git engine
|
| 76 |
+
// is headless
|
| 77 |
+
var animationFactory = getMockFactory();
|
| 78 |
+
var gitVisuals = mock(GitVisuals);
|
| 79 |
+
// add some stuff for origin making
|
| 80 |
+
var mockVis = getMockVisualization();
|
| 81 |
+
gitVisuals.getVisualization = function() {
|
| 82 |
+
return mockVis;
|
| 83 |
+
};
|
| 84 |
+
|
| 85 |
+
this.gitEngine = new GitEngine({
|
| 86 |
+
collection: this.commitCollection,
|
| 87 |
+
branches: this.branchCollection,
|
| 88 |
+
tags: this.tagCollection,
|
| 89 |
+
gitVisuals: gitVisuals,
|
| 90 |
+
animationFactory: animationFactory,
|
| 91 |
+
eventBaton: new EventBaton()
|
| 92 |
+
});
|
| 93 |
+
this.gitEngine.init();
|
| 94 |
+
};
|
| 95 |
+
|
| 96 |
+
// horrible hack so we can just quickly get a tree string for async git
|
| 97 |
+
// operations, aka for git demonstration views
|
| 98 |
+
var getTreeQuick = function(commandStr, getTreePromise) {
|
| 99 |
+
var deferred = Q.defer();
|
| 100 |
+
var headless = new HeadlessGit();
|
| 101 |
+
headless.sendCommand(commandStr, deferred);
|
| 102 |
+
deferred.promise.then(function() {
|
| 103 |
+
getTreePromise.resolve(headless.gitEngine.exportTree());
|
| 104 |
+
});
|
| 105 |
+
};
|
| 106 |
+
|
| 107 |
+
HeadlessGit.prototype.sendCommand = function(value, entireCommandPromise) {
|
| 108 |
+
var deferred = Q.defer();
|
| 109 |
+
var chain = deferred.promise;
|
| 110 |
+
var startTime = new Date().getTime();
|
| 111 |
+
|
| 112 |
+
var commands = [];
|
| 113 |
+
|
| 114 |
+
util.splitTextCommand(value, function(commandStr) {
|
| 115 |
+
chain = chain.then(function() {
|
| 116 |
+
var commandObj = new Command({
|
| 117 |
+
rawStr: commandStr
|
| 118 |
+
});
|
| 119 |
+
|
| 120 |
+
var thisDeferred = Q.defer();
|
| 121 |
+
this.gitEngine.dispatch(commandObj, thisDeferred);
|
| 122 |
+
commands.push(commandObj);
|
| 123 |
+
return thisDeferred.promise;
|
| 124 |
+
}.bind(this));
|
| 125 |
+
}, this);
|
| 126 |
+
|
| 127 |
+
chain.then(function() {
|
| 128 |
+
var nowTime = new Date().getTime();
|
| 129 |
+
if (entireCommandPromise) {
|
| 130 |
+
entireCommandPromise.resolve(commands);
|
| 131 |
+
}
|
| 132 |
+
});
|
| 133 |
+
|
| 134 |
+
chain.fail(function(err) {
|
| 135 |
+
console.log('!!!!!!!! error !!!!!!!');
|
| 136 |
+
console.log(err);
|
| 137 |
+
console.log(err.stack);
|
| 138 |
+
console.log('!!!!!!!!!!!!!!!!!!!!!!');
|
| 139 |
+
});
|
| 140 |
+
deferred.resolve();
|
| 141 |
+
return chain;
|
| 142 |
+
};
|
| 143 |
+
|
| 144 |
+
exports.HeadlessGit = HeadlessGit;
|
| 145 |
+
exports.getTreeQuick = getTreeQuick;
|
src/js/git/index.js
ADDED
|
@@ -0,0 +1,3202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var Backbone = require('backbone');
|
| 2 |
+
var Q = require('q');
|
| 3 |
+
|
| 4 |
+
var intl = require('../intl');
|
| 5 |
+
|
| 6 |
+
var AnimationFactory = require('../visuals/animation/animationFactory').AnimationFactory;
|
| 7 |
+
var AnimationQueue = require('../visuals/animation').AnimationQueue;
|
| 8 |
+
var TreeCompare = require('../graph/treeCompare');
|
| 9 |
+
|
| 10 |
+
var Graph = require('../graph');
|
| 11 |
+
var Errors = require('../util/errors');
|
| 12 |
+
var Main = require('../app');
|
| 13 |
+
var Commands = require('../commands');
|
| 14 |
+
var GitError = Errors.GitError;
|
| 15 |
+
var CommandResult = Errors.CommandResult;
|
| 16 |
+
|
| 17 |
+
var ORIGIN_PREFIX = 'o/';
|
| 18 |
+
var TAB = ' ';
|
| 19 |
+
var SHORT_CIRCUIT_CHAIN = 'STAPH';
|
| 20 |
+
|
| 21 |
+
function catchShortCircuit(err) {
|
| 22 |
+
if (err !== SHORT_CIRCUIT_CHAIN) {
|
| 23 |
+
throw err;
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
function GitEngine(options) {
|
| 28 |
+
this.rootCommit = null;
|
| 29 |
+
this.refs = {};
|
| 30 |
+
this.HEAD = null;
|
| 31 |
+
this.origin = null;
|
| 32 |
+
this.mode = 'git';
|
| 33 |
+
this.localRepo = null;
|
| 34 |
+
|
| 35 |
+
this.branchCollection = options.branches;
|
| 36 |
+
this.tagCollection = options.tags;
|
| 37 |
+
this.commitCollection = options.collection;
|
| 38 |
+
this.gitVisuals = options.gitVisuals;
|
| 39 |
+
|
| 40 |
+
this.eventBaton = options.eventBaton;
|
| 41 |
+
this.eventBaton.stealBaton('processGitCommand', this.dispatch, this);
|
| 42 |
+
|
| 43 |
+
// poor man's dependency injection. we can't reassign
|
| 44 |
+
// the module variable because its get clobbered :P
|
| 45 |
+
this.animationFactory = (options.animationFactory) ?
|
| 46 |
+
options.animationFactory : AnimationFactory;
|
| 47 |
+
|
| 48 |
+
this.initUniqueID();
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
GitEngine.prototype.initUniqueID = function() {
|
| 52 |
+
// backbone or something uses _ .uniqueId, so we make our own here
|
| 53 |
+
this.uniqueId = (function() {
|
| 54 |
+
var n = 0;
|
| 55 |
+
return function(prepend) {
|
| 56 |
+
return prepend ? prepend + n++ : n++;
|
| 57 |
+
};
|
| 58 |
+
})();
|
| 59 |
+
};
|
| 60 |
+
|
| 61 |
+
GitEngine.prototype.handleModeChange = function(vcs, callback) {
|
| 62 |
+
if (this.mode === vcs) {
|
| 63 |
+
// don't fire event aggressively
|
| 64 |
+
callback();
|
| 65 |
+
return;
|
| 66 |
+
}
|
| 67 |
+
Main.getEvents().trigger('vcsModeChange', {mode: vcs});
|
| 68 |
+
var chain = this.setMode(vcs);
|
| 69 |
+
if (this.origin) {
|
| 70 |
+
this.origin.setMode(vcs, function() {});
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
if (!chain) {
|
| 74 |
+
callback();
|
| 75 |
+
return;
|
| 76 |
+
}
|
| 77 |
+
// we have to do it async
|
| 78 |
+
chain.then(callback);
|
| 79 |
+
};
|
| 80 |
+
|
| 81 |
+
GitEngine.prototype.getIsHg = function() {
|
| 82 |
+
return this.mode === 'hg';
|
| 83 |
+
};
|
| 84 |
+
|
| 85 |
+
GitEngine.prototype.setMode = function(vcs) {
|
| 86 |
+
var switchedToHg = (this.mode === 'git' && vcs === 'hg');
|
| 87 |
+
this.mode = vcs;
|
| 88 |
+
if (!switchedToHg) {
|
| 89 |
+
return;
|
| 90 |
+
}
|
| 91 |
+
// if we are switching to mercurial then we have some
|
| 92 |
+
// garbage collection and other tidying up to do. this
|
| 93 |
+
// may or may not require a refresh so lets check.
|
| 94 |
+
var deferred = Q.defer();
|
| 95 |
+
deferred.resolve();
|
| 96 |
+
var chain = deferred.promise;
|
| 97 |
+
|
| 98 |
+
// this stuff is tricky because we don't animate when
|
| 99 |
+
// we didn't do anything, but we DO animate when
|
| 100 |
+
// either of the operations happen. so a lot of
|
| 101 |
+
// branching ahead...
|
| 102 |
+
var neededUpdate = this.updateAllBranchesForHg();
|
| 103 |
+
if (neededUpdate) {
|
| 104 |
+
chain = chain.then(function() {
|
| 105 |
+
return this.animationFactory.playRefreshAnimationSlow(this.gitVisuals);
|
| 106 |
+
}.bind(this));
|
| 107 |
+
|
| 108 |
+
// ok we need to refresh anyways, so do the prune after
|
| 109 |
+
chain = chain.then(function() {
|
| 110 |
+
var neededPrune = this.pruneTree();
|
| 111 |
+
if (!neededPrune) {
|
| 112 |
+
return;
|
| 113 |
+
}
|
| 114 |
+
return this.animationFactory.playRefreshAnimation(this.gitVisuals);
|
| 115 |
+
}.bind(this));
|
| 116 |
+
|
| 117 |
+
return chain;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
// ok might need prune though
|
| 121 |
+
var pruned = this.pruneTree();
|
| 122 |
+
if (!pruned) {
|
| 123 |
+
// do sync
|
| 124 |
+
return;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
return this.animationFactory.playRefreshAnimation(this.gitVisuals);
|
| 128 |
+
};
|
| 129 |
+
|
| 130 |
+
GitEngine.prototype.assignLocalRepo = function(repo) {
|
| 131 |
+
this.localRepo = repo;
|
| 132 |
+
};
|
| 133 |
+
|
| 134 |
+
GitEngine.prototype.defaultInit = function() {
|
| 135 |
+
var defaultTree = Graph.getDefaultTree();
|
| 136 |
+
this.loadTree(defaultTree);
|
| 137 |
+
};
|
| 138 |
+
|
| 139 |
+
GitEngine.prototype.init = function() {
|
| 140 |
+
// make an initial commit and a main branch
|
| 141 |
+
this.rootCommit = this.makeCommit(null, null, {rootCommit: true});
|
| 142 |
+
this.commitCollection.add(this.rootCommit);
|
| 143 |
+
|
| 144 |
+
var main = this.makeBranch('main', this.rootCommit);
|
| 145 |
+
this.HEAD = new Ref({
|
| 146 |
+
id: 'HEAD',
|
| 147 |
+
target: main
|
| 148 |
+
});
|
| 149 |
+
this.refs[this.HEAD.get('id')] = this.HEAD;
|
| 150 |
+
|
| 151 |
+
// commit once to get things going
|
| 152 |
+
this.commit();
|
| 153 |
+
};
|
| 154 |
+
|
| 155 |
+
GitEngine.prototype.hasOrigin = function() {
|
| 156 |
+
return !!this.origin;
|
| 157 |
+
};
|
| 158 |
+
|
| 159 |
+
GitEngine.prototype.isOrigin = function() {
|
| 160 |
+
return !!this.localRepo;
|
| 161 |
+
};
|
| 162 |
+
|
| 163 |
+
GitEngine.prototype.exportTreeForBranch = function(branchName) {
|
| 164 |
+
// this method exports the tree and then prunes everything that
|
| 165 |
+
// is not connected to branchname
|
| 166 |
+
var tree = this.exportTree();
|
| 167 |
+
// get the upstream set
|
| 168 |
+
var set = Graph.getUpstreamSet(this, branchName);
|
| 169 |
+
// now loop through and delete commits
|
| 170 |
+
var commitsToLoop = tree.commits;
|
| 171 |
+
tree.commits = {};
|
| 172 |
+
commitsToLoop.forEach(function(commit, id) {
|
| 173 |
+
if (set[id]) {
|
| 174 |
+
// if included in target branch
|
| 175 |
+
tree.commits[id] = commit;
|
| 176 |
+
}
|
| 177 |
+
});
|
| 178 |
+
|
| 179 |
+
var branchesToLoop = tree.branches;
|
| 180 |
+
tree.branches = {};
|
| 181 |
+
branchesToLoop.forEach(function(branch, id) {
|
| 182 |
+
if (id === branchName) {
|
| 183 |
+
tree.branches[id] = branch;
|
| 184 |
+
}
|
| 185 |
+
});
|
| 186 |
+
|
| 187 |
+
tree.HEAD.target = branchName;
|
| 188 |
+
return tree;
|
| 189 |
+
};
|
| 190 |
+
|
| 191 |
+
GitEngine.prototype.exportTree = function() {
|
| 192 |
+
// need to export all commits, their connectivity / messages, branches, and state of head.
|
| 193 |
+
// this would be simple if didn't have circular structures.... :P
|
| 194 |
+
// thus, we need to loop through and "flatten" our graph of objects referencing one another
|
| 195 |
+
var totalExport = {
|
| 196 |
+
branches: {},
|
| 197 |
+
commits: {},
|
| 198 |
+
tags: {},
|
| 199 |
+
HEAD: null
|
| 200 |
+
};
|
| 201 |
+
|
| 202 |
+
this.branchCollection.toJSON().forEach(function(branch) {
|
| 203 |
+
branch.target = branch.target.get('id');
|
| 204 |
+
delete branch.visBranch;
|
| 205 |
+
|
| 206 |
+
totalExport.branches[branch.id] = branch;
|
| 207 |
+
});
|
| 208 |
+
|
| 209 |
+
this.commitCollection.toJSON().forEach(function(commit) {
|
| 210 |
+
// clear out the fields that reference objects and create circular structure
|
| 211 |
+
Commit.prototype.constants.circularFields.forEach(function(field) {
|
| 212 |
+
delete commit[field];
|
| 213 |
+
});
|
| 214 |
+
|
| 215 |
+
// convert parents
|
| 216 |
+
commit.parents = (commit.parents || []).map(function(par) {
|
| 217 |
+
return par.get('id');
|
| 218 |
+
});
|
| 219 |
+
|
| 220 |
+
totalExport.commits[commit.id] = commit;
|
| 221 |
+
}, this);
|
| 222 |
+
|
| 223 |
+
this.tagCollection.toJSON().forEach(function(tag) {
|
| 224 |
+
delete tag.visTag;
|
| 225 |
+
tag.target = tag.target.get('id');
|
| 226 |
+
|
| 227 |
+
totalExport.tags[tag.id] = tag;
|
| 228 |
+
}, this);
|
| 229 |
+
|
| 230 |
+
var HEAD = this.HEAD.toJSON();
|
| 231 |
+
HEAD.lastTarget = HEAD.lastLastTarget = HEAD.visBranch = HEAD.visTag = undefined;
|
| 232 |
+
HEAD.target = HEAD.target.get('id');
|
| 233 |
+
totalExport.HEAD = HEAD;
|
| 234 |
+
|
| 235 |
+
if (this.hasOrigin()) {
|
| 236 |
+
totalExport.originTree = this.origin.exportTree();
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
return totalExport;
|
| 240 |
+
};
|
| 241 |
+
|
| 242 |
+
GitEngine.prototype.printTree = function(tree) {
|
| 243 |
+
tree = tree || this.exportTree();
|
| 244 |
+
TreeCompare.reduceTreeFields([tree]);
|
| 245 |
+
|
| 246 |
+
var str = JSON.stringify(tree);
|
| 247 |
+
if (/'/.test(str)) {
|
| 248 |
+
// escape it to make it more copy paste friendly
|
| 249 |
+
str = escape(str);
|
| 250 |
+
}
|
| 251 |
+
return str;
|
| 252 |
+
};
|
| 253 |
+
|
| 254 |
+
GitEngine.prototype.printAndCopyTree = function() {
|
| 255 |
+
window.prompt(
|
| 256 |
+
intl.str('Copy the tree string below'),
|
| 257 |
+
this.printTree()
|
| 258 |
+
);
|
| 259 |
+
};
|
| 260 |
+
|
| 261 |
+
GitEngine.prototype.loadTree = function(tree) {
|
| 262 |
+
// deep copy in case we use it a bunch. lol awesome copy method
|
| 263 |
+
tree = JSON.parse(JSON.stringify(tree));
|
| 264 |
+
|
| 265 |
+
// first clear everything
|
| 266 |
+
this.removeAll();
|
| 267 |
+
|
| 268 |
+
this.instantiateFromTree(tree);
|
| 269 |
+
|
| 270 |
+
this.reloadGraphics();
|
| 271 |
+
this.initUniqueID();
|
| 272 |
+
};
|
| 273 |
+
|
| 274 |
+
GitEngine.prototype.loadTreeFromString = function(treeString) {
|
| 275 |
+
this.loadTree(JSON.parse(unescape(this.crappyUnescape(treeString))));
|
| 276 |
+
};
|
| 277 |
+
|
| 278 |
+
GitEngine.prototype.instantiateFromTree = function(tree) {
|
| 279 |
+
// now we do the loading part
|
| 280 |
+
var createdSoFar = {};
|
| 281 |
+
|
| 282 |
+
Object.values(tree.commits).forEach(function(commitJSON) {
|
| 283 |
+
var commit = this.getOrMakeRecursive(tree, createdSoFar, commitJSON.id, this.gitVisuals);
|
| 284 |
+
this.commitCollection.add(commit);
|
| 285 |
+
}, this);
|
| 286 |
+
|
| 287 |
+
Object.values(tree.branches).forEach(function(branchJSON) {
|
| 288 |
+
var branch = this.getOrMakeRecursive(tree, createdSoFar, branchJSON.id, this.gitVisuals);
|
| 289 |
+
|
| 290 |
+
this.branchCollection.add(branch, {silent: true});
|
| 291 |
+
}, this);
|
| 292 |
+
|
| 293 |
+
Object.values(tree.tags || {}).forEach(function(tagJSON) {
|
| 294 |
+
var tag = this.getOrMakeRecursive(tree, createdSoFar, tagJSON.id, this.gitVisuals);
|
| 295 |
+
|
| 296 |
+
this.tagCollection.add(tag, {silent: true});
|
| 297 |
+
}, this);
|
| 298 |
+
|
| 299 |
+
var HEAD = this.getOrMakeRecursive(tree, createdSoFar, tree.HEAD.id, this.gitVisuals);
|
| 300 |
+
this.HEAD = HEAD;
|
| 301 |
+
|
| 302 |
+
this.rootCommit = createdSoFar['C0'];
|
| 303 |
+
if (!this.rootCommit) {
|
| 304 |
+
throw new Error('Need root commit of C0 for calculations');
|
| 305 |
+
}
|
| 306 |
+
this.refs = createdSoFar;
|
| 307 |
+
|
| 308 |
+
this.gitVisuals.gitReady = false;
|
| 309 |
+
this.branchCollection.each(function(branch) {
|
| 310 |
+
this.gitVisuals.addBranch(branch);
|
| 311 |
+
}, this);
|
| 312 |
+
this.tagCollection.each(function(tag) {
|
| 313 |
+
this.gitVisuals.addTag(tag);
|
| 314 |
+
}, this);
|
| 315 |
+
|
| 316 |
+
if (tree.originTree) {
|
| 317 |
+
var treeString = JSON.stringify(tree.originTree);
|
| 318 |
+
// if we don't have an animation queue (like when loading
|
| 319 |
+
// right away), just go ahead and make an empty one
|
| 320 |
+
this.animationQueue = this.animationQueue || new AnimationQueue({
|
| 321 |
+
callback: function() {}
|
| 322 |
+
});
|
| 323 |
+
this.makeOrigin(treeString);
|
| 324 |
+
}
|
| 325 |
+
};
|
| 326 |
+
|
| 327 |
+
GitEngine.prototype.makeOrigin = function(treeString) {
|
| 328 |
+
if (this.hasOrigin()) {
|
| 329 |
+
throw new GitError({
|
| 330 |
+
msg: intl.str('git-error-origin-exists')
|
| 331 |
+
});
|
| 332 |
+
}
|
| 333 |
+
treeString = treeString || this.printTree(this.exportTreeForBranch('main'));
|
| 334 |
+
|
| 335 |
+
// this is super super ugly but a necessary hack because of the way LGB was
|
| 336 |
+
// originally designed. We need to get to the top level visualization from
|
| 337 |
+
// the git engine -- aka we need to access our own visuals, then the
|
| 338 |
+
// visualization and ask the main vis to create a new vis/git pair. Then
|
| 339 |
+
// we grab the gitengine out of that and assign that as our origin repo
|
| 340 |
+
// which connects the two. epic
|
| 341 |
+
var mainVis = this.gitVisuals.getVisualization();
|
| 342 |
+
var originVis = mainVis.makeOrigin({
|
| 343 |
+
localRepo: this,
|
| 344 |
+
treeString: treeString
|
| 345 |
+
});
|
| 346 |
+
|
| 347 |
+
// defer the starting of our animation until origin has been created
|
| 348 |
+
this.animationQueue.set('promiseBased', true);
|
| 349 |
+
originVis.customEvents.on('gitEngineReady', function() {
|
| 350 |
+
this.origin = originVis.gitEngine;
|
| 351 |
+
originVis.gitEngine.assignLocalRepo(this);
|
| 352 |
+
this.syncRemoteBranchFills();
|
| 353 |
+
// and then here is the crazy part -- we need the ORIGIN to refresh
|
| 354 |
+
// itself in a separate animation. @_____@
|
| 355 |
+
this.origin.externalRefresh();
|
| 356 |
+
this.animationFactory.playRefreshAnimationAndFinish(this.gitVisuals, this.animationQueue);
|
| 357 |
+
}, this);
|
| 358 |
+
|
| 359 |
+
var originTree = JSON.parse(unescape(treeString));
|
| 360 |
+
// make an origin branch for each branch mentioned in the tree if its
|
| 361 |
+
// not made already...
|
| 362 |
+
Object.keys(originTree.branches).forEach(function(branchName) {
|
| 363 |
+
var branchJSON = originTree.branches[branchName];
|
| 364 |
+
if (this.refs[ORIGIN_PREFIX + branchName]) {
|
| 365 |
+
// we already have this branch
|
| 366 |
+
return;
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
var originTarget = this.findCommonAncestorWithRemote(
|
| 370 |
+
branchJSON.target
|
| 371 |
+
);
|
| 372 |
+
|
| 373 |
+
// now we have something in common, lets make the tracking branch
|
| 374 |
+
var remoteBranch = this.makeBranch(
|
| 375 |
+
ORIGIN_PREFIX + branchName,
|
| 376 |
+
this.getCommitFromRef(originTarget)
|
| 377 |
+
);
|
| 378 |
+
|
| 379 |
+
this.setLocalToTrackRemote(this.refs[branchJSON.id], remoteBranch);
|
| 380 |
+
}, this);
|
| 381 |
+
};
|
| 382 |
+
|
| 383 |
+
GitEngine.prototype.makeRemoteBranchIfNeeded = function(branchName) {
|
| 384 |
+
if (this.doesRefExist(ORIGIN_PREFIX + branchName)) {
|
| 385 |
+
return;
|
| 386 |
+
}
|
| 387 |
+
// if its not a branch on origin then bounce
|
| 388 |
+
var source = this.origin.resolveID(branchName);
|
| 389 |
+
if (source.get('type') !== 'branch') {
|
| 390 |
+
return;
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
return this.makeRemoteBranchForRemote(branchName);
|
| 394 |
+
};
|
| 395 |
+
|
| 396 |
+
GitEngine.prototype.makeBranchIfNeeded = function(branchName) {
|
| 397 |
+
if (this.doesRefExist(branchName)) {
|
| 398 |
+
return;
|
| 399 |
+
}
|
| 400 |
+
var where = this.findCommonAncestorForRemote(
|
| 401 |
+
this.getCommitFromRef('HEAD').get('id')
|
| 402 |
+
);
|
| 403 |
+
|
| 404 |
+
return this.validateAndMakeBranch(branchName, this.getCommitFromRef(where));
|
| 405 |
+
};
|
| 406 |
+
|
| 407 |
+
GitEngine.prototype.makeRemoteBranchForRemote = function(branchName) {
|
| 408 |
+
var target = this.origin.resolveID(branchName).get('target');
|
| 409 |
+
var originTarget = this.findCommonAncestorWithRemote(
|
| 410 |
+
target.get('id')
|
| 411 |
+
);
|
| 412 |
+
return this.makeBranch(
|
| 413 |
+
ORIGIN_PREFIX + branchName,
|
| 414 |
+
this.getCommitFromRef(originTarget)
|
| 415 |
+
);
|
| 416 |
+
};
|
| 417 |
+
|
| 418 |
+
GitEngine.prototype.findCommonAncestorForRemote = function(myTarget) {
|
| 419 |
+
if (this.origin.refs[myTarget]) {
|
| 420 |
+
return myTarget;
|
| 421 |
+
}
|
| 422 |
+
var parents = this.refs[myTarget].get('parents');
|
| 423 |
+
if (parents.length === 1) {
|
| 424 |
+
// Easy, we only have one parent. lets just go upwards
|
| 425 |
+
myTarget = parents[0].get('id');
|
| 426 |
+
// Recurse upwards to find where our remote has a commit.
|
| 427 |
+
return this.findCommonAncestorForRemote(myTarget);
|
| 428 |
+
}
|
| 429 |
+
// We have multiple parents so find out where these two meet.
|
| 430 |
+
var leftTarget = this.findCommonAncestorForRemote(parents[0].get('id'));
|
| 431 |
+
var rightTarget = this.findCommonAncestorForRemote(parents[1].get('id'));
|
| 432 |
+
return this.getCommonAncestor(
|
| 433 |
+
leftTarget,
|
| 434 |
+
rightTarget,
|
| 435 |
+
true // don't throw since we don't know the order here.
|
| 436 |
+
).get('id');
|
| 437 |
+
};
|
| 438 |
+
|
| 439 |
+
GitEngine.prototype.findCommonAncestorWithRemote = function(originTarget) {
|
| 440 |
+
if (this.refs[originTarget]) {
|
| 441 |
+
return originTarget;
|
| 442 |
+
}
|
| 443 |
+
// now this is tricky -- our remote could have commits that we do
|
| 444 |
+
// not have. so lets go upwards until we find one that we have
|
| 445 |
+
var parents = this.origin.refs[originTarget].get('parents');
|
| 446 |
+
if (parents.length === 1) {
|
| 447 |
+
return this.findCommonAncestorWithRemote(parents[0].get('id'));
|
| 448 |
+
}
|
| 449 |
+
// Like above, could have two parents
|
| 450 |
+
var leftTarget = this.findCommonAncestorWithRemote(parents[0].get('id'));
|
| 451 |
+
var rightTarget = this.findCommonAncestorWithRemote(parents[1].get('id'));
|
| 452 |
+
return this.getCommonAncestor(leftTarget, rightTarget, true /* don't throw */).get('id');
|
| 453 |
+
};
|
| 454 |
+
|
| 455 |
+
GitEngine.prototype.makeBranchOnOriginAndTrack = function(branchName, target) {
|
| 456 |
+
var remoteBranch = this.makeBranch(
|
| 457 |
+
ORIGIN_PREFIX + branchName,
|
| 458 |
+
this.getCommitFromRef(target)
|
| 459 |
+
);
|
| 460 |
+
|
| 461 |
+
if (this.refs[branchName]) { // not all remote branches have tracking ones
|
| 462 |
+
this.setLocalToTrackRemote(this.refs[branchName], remoteBranch);
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
var originTarget = this.findCommonAncestorForRemote(
|
| 466 |
+
this.getCommitFromRef(target).get('id')
|
| 467 |
+
);
|
| 468 |
+
this.origin.makeBranch(
|
| 469 |
+
branchName,
|
| 470 |
+
this.origin.getCommitFromRef(originTarget)
|
| 471 |
+
);
|
| 472 |
+
};
|
| 473 |
+
|
| 474 |
+
GitEngine.prototype.setLocalToTrackRemote = function(localBranch, remoteBranch) {
|
| 475 |
+
localBranch.setRemoteTrackingBranchID(remoteBranch.get('id'));
|
| 476 |
+
|
| 477 |
+
if (!this.command) {
|
| 478 |
+
// during init we have no command
|
| 479 |
+
return;
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
var msg = 'local branch "' +
|
| 483 |
+
localBranch.get('id') +
|
| 484 |
+
'" set to track remote branch "' +
|
| 485 |
+
remoteBranch.get('id') +
|
| 486 |
+
'"';
|
| 487 |
+
this.command.addWarning(intl.todo(msg));
|
| 488 |
+
};
|
| 489 |
+
|
| 490 |
+
GitEngine.prototype.getOrMakeRecursive = function(
|
| 491 |
+
tree,
|
| 492 |
+
createdSoFar,
|
| 493 |
+
objID,
|
| 494 |
+
gitVisuals
|
| 495 |
+
) {
|
| 496 |
+
if (createdSoFar[objID]) {
|
| 497 |
+
// base case
|
| 498 |
+
return createdSoFar[objID];
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
var getType = function(tree, id) {
|
| 502 |
+
if (tree.commits[id]) {
|
| 503 |
+
return 'commit';
|
| 504 |
+
} else if (tree.branches[id]) {
|
| 505 |
+
return 'branch';
|
| 506 |
+
} else if (id == 'HEAD') {
|
| 507 |
+
return 'HEAD';
|
| 508 |
+
} else if (tree.tags[id]) {
|
| 509 |
+
return 'tag';
|
| 510 |
+
}
|
| 511 |
+
throw new Error("bad type for " + id);
|
| 512 |
+
};
|
| 513 |
+
|
| 514 |
+
// figure out what type
|
| 515 |
+
var type = getType(tree, objID);
|
| 516 |
+
|
| 517 |
+
if (type == 'HEAD') {
|
| 518 |
+
var headJSON = tree.HEAD;
|
| 519 |
+
var HEAD = new Ref(Object.assign(
|
| 520 |
+
tree.HEAD,
|
| 521 |
+
{
|
| 522 |
+
target: this.getOrMakeRecursive(tree, createdSoFar, headJSON.target)
|
| 523 |
+
}
|
| 524 |
+
));
|
| 525 |
+
createdSoFar[objID] = HEAD;
|
| 526 |
+
return HEAD;
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
if (type == 'branch') {
|
| 530 |
+
var branchJSON = tree.branches[objID];
|
| 531 |
+
|
| 532 |
+
var branch = new Branch(Object.assign(
|
| 533 |
+
tree.branches[objID],
|
| 534 |
+
{
|
| 535 |
+
target: this.getOrMakeRecursive(tree, createdSoFar, branchJSON.target)
|
| 536 |
+
}
|
| 537 |
+
));
|
| 538 |
+
createdSoFar[objID] = branch;
|
| 539 |
+
return branch;
|
| 540 |
+
}
|
| 541 |
+
|
| 542 |
+
if (type == 'tag') {
|
| 543 |
+
var tagJSON = tree.tags[objID];
|
| 544 |
+
|
| 545 |
+
var tag = new Tag(Object.assign(
|
| 546 |
+
tree.tags[objID],
|
| 547 |
+
{
|
| 548 |
+
target: this.getOrMakeRecursive(tree, createdSoFar, tagJSON.target)
|
| 549 |
+
}
|
| 550 |
+
));
|
| 551 |
+
createdSoFar[objID] = tag;
|
| 552 |
+
return tag;
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
if (type == 'commit') {
|
| 556 |
+
// for commits, we need to grab all the parents
|
| 557 |
+
var commitJSON = tree.commits[objID];
|
| 558 |
+
|
| 559 |
+
var parentObjs = commitJSON.parents.map(function(parentID) {
|
| 560 |
+
return this.getOrMakeRecursive(tree, createdSoFar, parentID);
|
| 561 |
+
}, this);
|
| 562 |
+
|
| 563 |
+
var commit = new Commit(Object.assign(
|
| 564 |
+
commitJSON,
|
| 565 |
+
{
|
| 566 |
+
parents: parentObjs,
|
| 567 |
+
gitVisuals: this.gitVisuals
|
| 568 |
+
}
|
| 569 |
+
));
|
| 570 |
+
createdSoFar[objID] = commit;
|
| 571 |
+
return commit;
|
| 572 |
+
}
|
| 573 |
+
|
| 574 |
+
throw new Error('ruh rho!! unsupported type for ' + objID);
|
| 575 |
+
};
|
| 576 |
+
|
| 577 |
+
GitEngine.prototype.tearDown = function() {
|
| 578 |
+
if (this.tornDown) {
|
| 579 |
+
return;
|
| 580 |
+
}
|
| 581 |
+
this.eventBaton.releaseBaton('processGitCommand', this.dispatch, this);
|
| 582 |
+
this.removeAll();
|
| 583 |
+
this.tornDown = true;
|
| 584 |
+
};
|
| 585 |
+
|
| 586 |
+
GitEngine.prototype.reloadGraphics = function() {
|
| 587 |
+
// get the root commit
|
| 588 |
+
this.gitVisuals.rootCommit = this.refs['C0'];
|
| 589 |
+
// this just basically makes the HEAD branch. the head branch really should have been
|
| 590 |
+
// a member of a collection and not this annoying edge case stuff... one day
|
| 591 |
+
this.gitVisuals.initHeadBranch();
|
| 592 |
+
|
| 593 |
+
// when the paper is ready
|
| 594 |
+
this.gitVisuals.drawTreeFromReload();
|
| 595 |
+
|
| 596 |
+
this.gitVisuals.refreshTreeHarsh();
|
| 597 |
+
};
|
| 598 |
+
|
| 599 |
+
GitEngine.prototype.removeAll = function() {
|
| 600 |
+
this.branchCollection.reset();
|
| 601 |
+
this.tagCollection.reset();
|
| 602 |
+
this.commitCollection.reset();
|
| 603 |
+
this.refs = {};
|
| 604 |
+
this.HEAD = null;
|
| 605 |
+
this.rootCommit = null;
|
| 606 |
+
|
| 607 |
+
if (this.origin) {
|
| 608 |
+
// we will restart all this jazz during init from tree
|
| 609 |
+
this.origin.gitVisuals.getVisualization().tearDown();
|
| 610 |
+
delete this.origin;
|
| 611 |
+
this.gitVisuals.getVisualization().clearOrigin();
|
| 612 |
+
}
|
| 613 |
+
|
| 614 |
+
this.gitVisuals.resetAll();
|
| 615 |
+
};
|
| 616 |
+
|
| 617 |
+
GitEngine.prototype.getDetachedHead = function() {
|
| 618 |
+
// detached head is if HEAD points to a commit instead of a branch...
|
| 619 |
+
var target = this.HEAD.get('target');
|
| 620 |
+
var targetType = target.get('type');
|
| 621 |
+
return targetType !== 'branch';
|
| 622 |
+
};
|
| 623 |
+
|
| 624 |
+
GitEngine.prototype.validateBranchName = function(name) {
|
| 625 |
+
// Lets escape some of the nasty characters
|
| 626 |
+
name = name.replace(///g,"\/");
|
| 627 |
+
name = name.replace(/\s/g, '');
|
| 628 |
+
// And then just make sure it starts with alpha-numeric,
|
| 629 |
+
// can contain a slash or dash, and then ends with alpha
|
| 630 |
+
if (
|
| 631 |
+
!/^(\w+[.\/\-]?)+\w+$/.test(name) ||
|
| 632 |
+
name.search('o/') === 0
|
| 633 |
+
) {
|
| 634 |
+
throw new GitError({
|
| 635 |
+
msg: intl.str(
|
| 636 |
+
'bad-branch-name',
|
| 637 |
+
{ branch: name }
|
| 638 |
+
)
|
| 639 |
+
});
|
| 640 |
+
}
|
| 641 |
+
if (/^[cC]\d+$/.test(name)) {
|
| 642 |
+
throw new GitError({
|
| 643 |
+
msg: intl.str(
|
| 644 |
+
'bad-branch-name',
|
| 645 |
+
{ branch: name }
|
| 646 |
+
)
|
| 647 |
+
});
|
| 648 |
+
}
|
| 649 |
+
if (/[hH][eE][aA][dD]/.test(name)) {
|
| 650 |
+
throw new GitError({
|
| 651 |
+
msg: intl.str(
|
| 652 |
+
'bad-branch-name',
|
| 653 |
+
{ branch: name }
|
| 654 |
+
)
|
| 655 |
+
});
|
| 656 |
+
}
|
| 657 |
+
if (name.length > 9) {
|
| 658 |
+
name = name.slice(0, 9);
|
| 659 |
+
this.command.addWarning(
|
| 660 |
+
intl.str(
|
| 661 |
+
'branch-name-short',
|
| 662 |
+
{ branch: name }
|
| 663 |
+
)
|
| 664 |
+
);
|
| 665 |
+
}
|
| 666 |
+
return name;
|
| 667 |
+
};
|
| 668 |
+
|
| 669 |
+
GitEngine.prototype.validateAndMakeBranch = function(id, target) {
|
| 670 |
+
id = this.validateBranchName(id);
|
| 671 |
+
if (this.doesRefExist(id)) {
|
| 672 |
+
throw new GitError({
|
| 673 |
+
msg: intl.str(
|
| 674 |
+
'bad-branch-name',
|
| 675 |
+
{ branch: id }
|
| 676 |
+
)
|
| 677 |
+
});
|
| 678 |
+
}
|
| 679 |
+
|
| 680 |
+
return this.makeBranch(id, target);
|
| 681 |
+
};
|
| 682 |
+
|
| 683 |
+
GitEngine.prototype.validateAndMakeTag = function(id, target) {
|
| 684 |
+
id = this.validateBranchName(id);
|
| 685 |
+
if (this.refs[id]) {
|
| 686 |
+
throw new GitError({
|
| 687 |
+
msg: intl.str(
|
| 688 |
+
'bad-tag-name',
|
| 689 |
+
{ tag: id }
|
| 690 |
+
)
|
| 691 |
+
});
|
| 692 |
+
}
|
| 693 |
+
|
| 694 |
+
this.makeTag(id, target);
|
| 695 |
+
};
|
| 696 |
+
|
| 697 |
+
GitEngine.prototype.makeBranch = function(id, target) {
|
| 698 |
+
if (this.refs[id]) {
|
| 699 |
+
var err = new Error();
|
| 700 |
+
throw new Error('woah already have that ref ' + id + ' ' + err.stack);
|
| 701 |
+
}
|
| 702 |
+
|
| 703 |
+
var branch = new Branch({
|
| 704 |
+
target: target,
|
| 705 |
+
id: id
|
| 706 |
+
});
|
| 707 |
+
this.branchCollection.add(branch);
|
| 708 |
+
this.refs[branch.get('id')] = branch;
|
| 709 |
+
return branch;
|
| 710 |
+
};
|
| 711 |
+
|
| 712 |
+
GitEngine.prototype.makeTag = function(id, target) {
|
| 713 |
+
if (this.refs[id]) {
|
| 714 |
+
throw new Error('woah already have that');
|
| 715 |
+
}
|
| 716 |
+
|
| 717 |
+
var tag = new Tag({
|
| 718 |
+
target: target,
|
| 719 |
+
id: id
|
| 720 |
+
});
|
| 721 |
+
this.tagCollection.add(tag);
|
| 722 |
+
this.refs[tag.get('id')] = tag;
|
| 723 |
+
return tag;
|
| 724 |
+
};
|
| 725 |
+
|
| 726 |
+
GitEngine.prototype.getHead = function() {
|
| 727 |
+
return Object.assign({}, this.HEAD);
|
| 728 |
+
};
|
| 729 |
+
|
| 730 |
+
GitEngine.prototype.getTags = function() {
|
| 731 |
+
var toReturn = [];
|
| 732 |
+
this.tagCollection.each(function(tag) {
|
| 733 |
+
toReturn.push({
|
| 734 |
+
id: tag.get('id'),
|
| 735 |
+
target: tag.get('target'),
|
| 736 |
+
remote: tag.getIsRemote(),
|
| 737 |
+
obj: tag
|
| 738 |
+
});
|
| 739 |
+
}, this);
|
| 740 |
+
return toReturn;
|
| 741 |
+
};
|
| 742 |
+
|
| 743 |
+
GitEngine.prototype.getBranches = function() {
|
| 744 |
+
var toReturn = [];
|
| 745 |
+
this.branchCollection.each(function(branch) {
|
| 746 |
+
toReturn.push({
|
| 747 |
+
id: branch.get('id'),
|
| 748 |
+
selected: this.HEAD.get('target') === branch,
|
| 749 |
+
target: branch.get('target'),
|
| 750 |
+
remote: branch.getIsRemote(),
|
| 751 |
+
obj: branch
|
| 752 |
+
});
|
| 753 |
+
}, this);
|
| 754 |
+
return toReturn;
|
| 755 |
+
};
|
| 756 |
+
|
| 757 |
+
GitEngine.prototype.getRemoteBranches = function() {
|
| 758 |
+
var all = this.getBranches();
|
| 759 |
+
return all.filter(function(branchJSON) {
|
| 760 |
+
return branchJSON.remote === true;
|
| 761 |
+
});
|
| 762 |
+
};
|
| 763 |
+
|
| 764 |
+
GitEngine.prototype.getLocalBranches = function() {
|
| 765 |
+
var all = this.getBranches();
|
| 766 |
+
return all.filter(function(branchJSON) {
|
| 767 |
+
return branchJSON.remote === false;
|
| 768 |
+
});
|
| 769 |
+
};
|
| 770 |
+
|
| 771 |
+
GitEngine.prototype.printBranchesWithout = function(without) {
|
| 772 |
+
var commitToBranches = this.getUpstreamBranchSet();
|
| 773 |
+
var commitID = this.getCommitFromRef(without).get('id');
|
| 774 |
+
|
| 775 |
+
var toPrint = commitToBranches[commitID].map(function (branchJSON) {
|
| 776 |
+
branchJSON.selected = this.HEAD.get('target').get('id') == branchJSON.id;
|
| 777 |
+
return branchJSON;
|
| 778 |
+
}, this);
|
| 779 |
+
this.printBranches(toPrint);
|
| 780 |
+
};
|
| 781 |
+
|
| 782 |
+
GitEngine.prototype.printBranches = function(branches) {
|
| 783 |
+
var result = '';
|
| 784 |
+
branches.forEach(branch => {
|
| 785 |
+
result += (branch.selected ? '* ' : '') + this.resolveName(branch.id).split('"')[1] + '\n';
|
| 786 |
+
});
|
| 787 |
+
throw new CommandResult({
|
| 788 |
+
msg: result
|
| 789 |
+
});
|
| 790 |
+
};
|
| 791 |
+
|
| 792 |
+
GitEngine.prototype.printTags = function(tags) {
|
| 793 |
+
var result = '';
|
| 794 |
+
tags.forEach(function (tag) {
|
| 795 |
+
result += tag.id + '\n';
|
| 796 |
+
});
|
| 797 |
+
throw new CommandResult({
|
| 798 |
+
msg: result
|
| 799 |
+
});
|
| 800 |
+
};
|
| 801 |
+
|
| 802 |
+
GitEngine.prototype.printRemotes = function(options) {
|
| 803 |
+
var result = '';
|
| 804 |
+
if (options.verbose) {
|
| 805 |
+
result += 'origin (fetch)\n';
|
| 806 |
+
result += TAB + 'git@github.com:pcottle/foo.git' + '\n\n';
|
| 807 |
+
result += 'origin (push)\n';
|
| 808 |
+
result += TAB + 'git@github.com:pcottle/foo.git';
|
| 809 |
+
} else {
|
| 810 |
+
result += 'origin';
|
| 811 |
+
}
|
| 812 |
+
throw new CommandResult({
|
| 813 |
+
msg: result
|
| 814 |
+
});
|
| 815 |
+
};
|
| 816 |
+
|
| 817 |
+
GitEngine.prototype.getUniqueID = function() {
|
| 818 |
+
var id = this.uniqueId('C');
|
| 819 |
+
|
| 820 |
+
var hasID = function(idToCheck) {
|
| 821 |
+
// loop through and see if we have it locally or
|
| 822 |
+
// remotely
|
| 823 |
+
if (this.refs[idToCheck]) {
|
| 824 |
+
return true;
|
| 825 |
+
}
|
| 826 |
+
if (this.origin && this.origin.refs[idToCheck]) {
|
| 827 |
+
return true;
|
| 828 |
+
}
|
| 829 |
+
return false;
|
| 830 |
+
}.bind(this);
|
| 831 |
+
|
| 832 |
+
while (hasID(id)) {
|
| 833 |
+
id = this.uniqueId('C');
|
| 834 |
+
}
|
| 835 |
+
return id;
|
| 836 |
+
};
|
| 837 |
+
|
| 838 |
+
GitEngine.prototype.makeCommit = function(parents, id, options) {
|
| 839 |
+
// ok we need to actually manually create commit IDs now because
|
| 840 |
+
// people like nikita (thanks for finding this!) could
|
| 841 |
+
// make branches named C2 before creating the commit C2
|
| 842 |
+
if (!id) {
|
| 843 |
+
id = this.getUniqueID();
|
| 844 |
+
}
|
| 845 |
+
|
| 846 |
+
var commit = new Commit(Object.assign({
|
| 847 |
+
parents: parents,
|
| 848 |
+
id: id,
|
| 849 |
+
gitVisuals: this.gitVisuals
|
| 850 |
+
},
|
| 851 |
+
options || {}
|
| 852 |
+
));
|
| 853 |
+
|
| 854 |
+
this.refs[commit.get('id')] = commit;
|
| 855 |
+
this.commitCollection.add(commit);
|
| 856 |
+
return commit;
|
| 857 |
+
};
|
| 858 |
+
|
| 859 |
+
GitEngine.prototype.revert = function(whichCommits) {
|
| 860 |
+
// resolve the commits we will rebase
|
| 861 |
+
var toRevert = whichCommits.map(function(stringRef) {
|
| 862 |
+
return this.getCommitFromRef(stringRef);
|
| 863 |
+
}, this);
|
| 864 |
+
|
| 865 |
+
var deferred = Q.defer();
|
| 866 |
+
var chain = deferred.promise;
|
| 867 |
+
var destBranch = this.resolveID('HEAD');
|
| 868 |
+
|
| 869 |
+
chain = this.animationFactory.highlightEachWithPromise(
|
| 870 |
+
chain,
|
| 871 |
+
toRevert,
|
| 872 |
+
destBranch
|
| 873 |
+
);
|
| 874 |
+
|
| 875 |
+
var base = this.getCommitFromRef('HEAD');
|
| 876 |
+
// each step makes a new commit
|
| 877 |
+
var chainStep = function(oldCommit) {
|
| 878 |
+
var newId = this.rebaseAltID(oldCommit.get('id'));
|
| 879 |
+
var commitMessage = intl.str('git-revert-msg', {
|
| 880 |
+
oldCommit: this.resolveName(oldCommit),
|
| 881 |
+
oldMsg: oldCommit.get('commitMessage')
|
| 882 |
+
});
|
| 883 |
+
var newCommit = this.makeCommit([base], newId, {
|
| 884 |
+
commitMessage: commitMessage
|
| 885 |
+
});
|
| 886 |
+
base = newCommit;
|
| 887 |
+
|
| 888 |
+
return this.animationFactory.playCommitBirthPromiseAnimation(
|
| 889 |
+
newCommit,
|
| 890 |
+
this.gitVisuals
|
| 891 |
+
);
|
| 892 |
+
}.bind(this);
|
| 893 |
+
|
| 894 |
+
// set up the promise chain
|
| 895 |
+
toRevert.forEach(function (commit) {
|
| 896 |
+
chain = chain.then(function() {
|
| 897 |
+
return chainStep(commit);
|
| 898 |
+
});
|
| 899 |
+
});
|
| 900 |
+
|
| 901 |
+
// done! update our location
|
| 902 |
+
chain = chain.then(function() {
|
| 903 |
+
this.setTargetLocation('HEAD', base);
|
| 904 |
+
return this.animationFactory.playRefreshAnimation(this.gitVisuals);
|
| 905 |
+
}.bind(this));
|
| 906 |
+
|
| 907 |
+
this.animationQueue.thenFinish(chain, deferred);
|
| 908 |
+
};
|
| 909 |
+
|
| 910 |
+
GitEngine.prototype.reset = function(target) {
|
| 911 |
+
this.setTargetLocation('HEAD', this.getCommitFromRef(target));
|
| 912 |
+
};
|
| 913 |
+
|
| 914 |
+
GitEngine.prototype.setupCherrypickChain = function(toCherrypick) {
|
| 915 |
+
// error checks are all good, lets go!
|
| 916 |
+
var deferred = Q.defer();
|
| 917 |
+
var chain = deferred.promise;
|
| 918 |
+
var destinationBranch = this.resolveID('HEAD');
|
| 919 |
+
|
| 920 |
+
chain = this.animationFactory.highlightEachWithPromise(
|
| 921 |
+
chain,
|
| 922 |
+
toCherrypick,
|
| 923 |
+
destinationBranch
|
| 924 |
+
);
|
| 925 |
+
|
| 926 |
+
var chainStep = function(commit) {
|
| 927 |
+
var newCommit = this.cherrypick(commit);
|
| 928 |
+
return this.animationFactory.playCommitBirthPromiseAnimation(
|
| 929 |
+
newCommit,
|
| 930 |
+
this.gitVisuals
|
| 931 |
+
);
|
| 932 |
+
}.bind(this);
|
| 933 |
+
|
| 934 |
+
toCherrypick.forEach(function (arg) {
|
| 935 |
+
chain = chain.then(function() {
|
| 936 |
+
return chainStep(arg);
|
| 937 |
+
});
|
| 938 |
+
}, this);
|
| 939 |
+
|
| 940 |
+
this.animationQueue.thenFinish(chain, deferred);
|
| 941 |
+
};
|
| 942 |
+
|
| 943 |
+
/*************************************
|
| 944 |
+
* Origin stuff!
|
| 945 |
+
************************************/
|
| 946 |
+
|
| 947 |
+
GitEngine.prototype.checkUpstreamOfSource = function(
|
| 948 |
+
target,
|
| 949 |
+
source,
|
| 950 |
+
targetBranch,
|
| 951 |
+
sourceBranch,
|
| 952 |
+
errorMsg
|
| 953 |
+
) {
|
| 954 |
+
// here we are downloading some X number of commits from source onto
|
| 955 |
+
// target. Hence target should be strictly upstream of source
|
| 956 |
+
|
| 957 |
+
// lets first get the upstream set from source's dest branch
|
| 958 |
+
var upstream = Graph.getUpstreamSet(source, sourceBranch);
|
| 959 |
+
|
| 960 |
+
var targetLocationID = target.getCommitFromRef(targetBranch).get('id');
|
| 961 |
+
if (!upstream[targetLocationID]) {
|
| 962 |
+
throw new GitError({
|
| 963 |
+
msg: errorMsg || intl.str('git-error-origin-fetch-no-ff')
|
| 964 |
+
});
|
| 965 |
+
}
|
| 966 |
+
};
|
| 967 |
+
|
| 968 |
+
GitEngine.prototype.getTargetGraphDifference = function(
|
| 969 |
+
target,
|
| 970 |
+
source,
|
| 971 |
+
targetBranch,
|
| 972 |
+
sourceBranch,
|
| 973 |
+
options
|
| 974 |
+
) {
|
| 975 |
+
options = options || {};
|
| 976 |
+
sourceBranch = source.resolveID(sourceBranch);
|
| 977 |
+
|
| 978 |
+
var targetSet = Graph.getUpstreamSet(target, targetBranch);
|
| 979 |
+
var sourceStartCommit = source.getCommitFromRef(sourceBranch);
|
| 980 |
+
|
| 981 |
+
var sourceTree = source.exportTree();
|
| 982 |
+
var sourceStartCommitJSON = sourceTree.commits[sourceStartCommit.get('id')];
|
| 983 |
+
|
| 984 |
+
if (targetSet[sourceStartCommitJSON.id]) {
|
| 985 |
+
// either we throw since theres no work to be done, or we return an empty array
|
| 986 |
+
if (options.dontThrowOnNoFetch) {
|
| 987 |
+
return [];
|
| 988 |
+
} else {
|
| 989 |
+
throw new GitError({
|
| 990 |
+
msg: intl.str('git-error-origin-fetch-uptodate')
|
| 991 |
+
});
|
| 992 |
+
}
|
| 993 |
+
}
|
| 994 |
+
|
| 995 |
+
// ok great, we have our starting point and our stopping set. lets go ahead
|
| 996 |
+
// and traverse upwards and keep track of depth manually
|
| 997 |
+
sourceStartCommitJSON.depth = 0;
|
| 998 |
+
var difference = [];
|
| 999 |
+
var toExplore = [sourceStartCommitJSON];
|
| 1000 |
+
|
| 1001 |
+
var pushParent = function(parentID) {
|
| 1002 |
+
if (targetSet[parentID]) {
|
| 1003 |
+
// we already have that commit, lets bounce
|
| 1004 |
+
return;
|
| 1005 |
+
}
|
| 1006 |
+
|
| 1007 |
+
var parentJSON = sourceTree.commits[parentID];
|
| 1008 |
+
parentJSON.depth = here.depth + 1;
|
| 1009 |
+
toExplore.push(parentJSON);
|
| 1010 |
+
};
|
| 1011 |
+
|
| 1012 |
+
while (toExplore.length) {
|
| 1013 |
+
var here = toExplore.pop();
|
| 1014 |
+
difference.push(here);
|
| 1015 |
+
here.parents.forEach(pushParent);
|
| 1016 |
+
}
|
| 1017 |
+
|
| 1018 |
+
// filter because we weren't doing graph search
|
| 1019 |
+
var differenceUnique = Graph.getUniqueObjects(difference);
|
| 1020 |
+
/**
|
| 1021 |
+
* Ok now we have to determine the order in which to make these commits.
|
| 1022 |
+
* We used to just sort by depth because we were lazy but that is incorrect
|
| 1023 |
+
* since it doesn't represent the actual dependency tree of the commits.
|
| 1024 |
+
*
|
| 1025 |
+
* So here is what we are going to do -- loop through the differenceUnique
|
| 1026 |
+
* set and find a commit that has _all_ its parents in the targetSet. Then
|
| 1027 |
+
* decide to make that commit first, expand targetSet, and then rinse & repeat
|
| 1028 |
+
*/
|
| 1029 |
+
var inOrder = [];
|
| 1030 |
+
var allParentsMade = function(node) {
|
| 1031 |
+
var allParents = true;
|
| 1032 |
+
node.parents.forEach(function(parent) {
|
| 1033 |
+
allParents = allParents && targetSet[parent];
|
| 1034 |
+
});
|
| 1035 |
+
return allParents;
|
| 1036 |
+
};
|
| 1037 |
+
|
| 1038 |
+
while (differenceUnique.length) {
|
| 1039 |
+
for (var i = 0; i < differenceUnique.length; i++) {
|
| 1040 |
+
if (!allParentsMade(differenceUnique[i])) {
|
| 1041 |
+
// This commit cannot be made since not all of its dependencies are
|
| 1042 |
+
// satisfied.
|
| 1043 |
+
continue;
|
| 1044 |
+
}
|
| 1045 |
+
|
| 1046 |
+
var makeThis = differenceUnique[i];
|
| 1047 |
+
inOrder.push(makeThis);
|
| 1048 |
+
// remove the commit
|
| 1049 |
+
differenceUnique.splice(i, 1);
|
| 1050 |
+
// expand target set
|
| 1051 |
+
targetSet[makeThis.id] = true;
|
| 1052 |
+
}
|
| 1053 |
+
}
|
| 1054 |
+
return inOrder;
|
| 1055 |
+
};
|
| 1056 |
+
|
| 1057 |
+
GitEngine.prototype.push = function(options) {
|
| 1058 |
+
options = options || {};
|
| 1059 |
+
|
| 1060 |
+
if (options.source === "") {
|
| 1061 |
+
// delete case
|
| 1062 |
+
this.pushDeleteRemoteBranch(
|
| 1063 |
+
this.refs[ORIGIN_PREFIX + options.destination],
|
| 1064 |
+
this.origin.refs[options.destination]
|
| 1065 |
+
);
|
| 1066 |
+
return;
|
| 1067 |
+
}
|
| 1068 |
+
|
| 1069 |
+
var sourceBranch = this.resolveID(options.source);
|
| 1070 |
+
if (sourceBranch && sourceBranch.attributes.type === 'tag') {
|
| 1071 |
+
throw new GitError({
|
| 1072 |
+
msg: intl.todo('Tags are not allowed as sources for pushing'),
|
| 1073 |
+
});
|
| 1074 |
+
}
|
| 1075 |
+
|
| 1076 |
+
if (!this.origin.doesRefExist(options.destination)) {
|
| 1077 |
+
console.warn('ref', options.destination);
|
| 1078 |
+
this.makeBranchOnOriginAndTrack(
|
| 1079 |
+
options.destination,
|
| 1080 |
+
this.getCommitFromRef(sourceBranch)
|
| 1081 |
+
);
|
| 1082 |
+
// play an animation now since we might not have to fast forward
|
| 1083 |
+
// anything... this is weird because we are punting an animation
|
| 1084 |
+
// and not resolving the promise but whatever
|
| 1085 |
+
this.animationFactory.playRefreshAnimation(this.origin.gitVisuals);
|
| 1086 |
+
this.animationFactory.playRefreshAnimation(this.gitVisuals);
|
| 1087 |
+
}
|
| 1088 |
+
var branchOnRemote = this.origin.resolveID(options.destination);
|
| 1089 |
+
var sourceLocation = this.resolveID(options.source || 'HEAD');
|
| 1090 |
+
|
| 1091 |
+
// first check if this is even allowed by checking the sync between
|
| 1092 |
+
if (!options.force) {
|
| 1093 |
+
this.checkUpstreamOfSource(
|
| 1094 |
+
this,
|
| 1095 |
+
this.origin,
|
| 1096 |
+
branchOnRemote,
|
| 1097 |
+
sourceLocation,
|
| 1098 |
+
intl.str('git-error-origin-push-no-ff')
|
| 1099 |
+
);
|
| 1100 |
+
}
|
| 1101 |
+
|
| 1102 |
+
var commitsToMake = this.getTargetGraphDifference(
|
| 1103 |
+
this.origin,
|
| 1104 |
+
this,
|
| 1105 |
+
branchOnRemote,
|
| 1106 |
+
sourceLocation,
|
| 1107 |
+
/* options */ {
|
| 1108 |
+
dontThrowOnNoFetch: true,
|
| 1109 |
+
}
|
| 1110 |
+
);
|
| 1111 |
+
if (!commitsToMake.length) {
|
| 1112 |
+
if (!options.force) {
|
| 1113 |
+
// We are already up to date, and we can't be deleting
|
| 1114 |
+
// either since we don't have --force
|
| 1115 |
+
throw new GitError({
|
| 1116 |
+
msg: intl.str('git-error-origin-fetch-uptodate')
|
| 1117 |
+
});
|
| 1118 |
+
} else {
|
| 1119 |
+
var sourceCommit = this.getCommitFromRef(sourceBranch);
|
| 1120 |
+
var originCommit = this.getCommitFromRef(branchOnRemote);
|
| 1121 |
+
if (sourceCommit.id === originCommit.id) {
|
| 1122 |
+
// This is essentially also being up to date
|
| 1123 |
+
throw new GitError({
|
| 1124 |
+
msg: intl.str('git-error-origin-fetch-uptodate')
|
| 1125 |
+
});
|
| 1126 |
+
}
|
| 1127 |
+
// Otherwise fall through! We will update origin
|
| 1128 |
+
// and essentially delete the commit
|
| 1129 |
+
}
|
| 1130 |
+
}
|
| 1131 |
+
|
| 1132 |
+
// now here is the tricky part -- the difference between local main
|
| 1133 |
+
// and remote main might be commits C2, C3, and C4, but the remote
|
| 1134 |
+
// might already have those commits. In this case, we don't need to
|
| 1135 |
+
// make them, so filter these out
|
| 1136 |
+
commitsToMake = commitsToMake.filter(function(commitJSON) {
|
| 1137 |
+
return !this.origin.refs[commitJSON.id];
|
| 1138 |
+
}, this);
|
| 1139 |
+
|
| 1140 |
+
var makeCommit = function(id, parentIDs) {
|
| 1141 |
+
// need to get the parents first. since we order by depth, we know
|
| 1142 |
+
// the dependencies are there already
|
| 1143 |
+
var parents = parentIDs.map(function(parentID) {
|
| 1144 |
+
return this.origin.refs[parentID];
|
| 1145 |
+
}, this);
|
| 1146 |
+
return this.origin.makeCommit(parents, id);
|
| 1147 |
+
}.bind(this);
|
| 1148 |
+
|
| 1149 |
+
// now make the promise chain to make each commit
|
| 1150 |
+
var chainStep = function(id, parents) {
|
| 1151 |
+
var newCommit = makeCommit(id, parents);
|
| 1152 |
+
return this.animationFactory.playCommitBirthPromiseAnimation(
|
| 1153 |
+
newCommit,
|
| 1154 |
+
this.origin.gitVisuals
|
| 1155 |
+
);
|
| 1156 |
+
}.bind(this);
|
| 1157 |
+
|
| 1158 |
+
var deferred = Q.defer();
|
| 1159 |
+
var chain = deferred.promise;
|
| 1160 |
+
|
| 1161 |
+
commitsToMake.forEach(function(commitJSON) {
|
| 1162 |
+
chain = chain.then(function() {
|
| 1163 |
+
return this.animationFactory.playHighlightPromiseAnimation(
|
| 1164 |
+
this.refs[commitJSON.id],
|
| 1165 |
+
branchOnRemote
|
| 1166 |
+
);
|
| 1167 |
+
}.bind(this));
|
| 1168 |
+
|
| 1169 |
+
chain = chain.then(function() {
|
| 1170 |
+
return chainStep(
|
| 1171 |
+
commitJSON.id,
|
| 1172 |
+
commitJSON.parents
|
| 1173 |
+
);
|
| 1174 |
+
});
|
| 1175 |
+
}, this);
|
| 1176 |
+
|
| 1177 |
+
chain = chain.then(function() {
|
| 1178 |
+
var localLocationID = this.getCommitFromRef(sourceLocation).get('id');
|
| 1179 |
+
var remoteCommit = this.origin.refs[localLocationID];
|
| 1180 |
+
this.origin.setTargetLocation(branchOnRemote, remoteCommit);
|
| 1181 |
+
// unhighlight local
|
| 1182 |
+
this.animationFactory.playRefreshAnimation(this.gitVisuals);
|
| 1183 |
+
return this.animationFactory.playRefreshAnimation(this.origin.gitVisuals);
|
| 1184 |
+
}.bind(this));
|
| 1185 |
+
|
| 1186 |
+
// HAX HAX update main and remote tracking for main
|
| 1187 |
+
chain = chain.then(function() {
|
| 1188 |
+
var localCommit = this.getCommitFromRef(sourceLocation);
|
| 1189 |
+
this.setTargetLocation(this.resolveID(ORIGIN_PREFIX + options.destination), localCommit);
|
| 1190 |
+
return this.animationFactory.playRefreshAnimation(this.gitVisuals);
|
| 1191 |
+
}.bind(this));
|
| 1192 |
+
|
| 1193 |
+
if (!options.dontResolvePromise) {
|
| 1194 |
+
this.animationQueue.thenFinish(chain, deferred);
|
| 1195 |
+
}
|
| 1196 |
+
};
|
| 1197 |
+
|
| 1198 |
+
GitEngine.prototype.pushDeleteRemoteBranch = function(
|
| 1199 |
+
remoteBranch,
|
| 1200 |
+
branchOnRemote
|
| 1201 |
+
) {
|
| 1202 |
+
if (branchOnRemote.get('id') === 'main') {
|
| 1203 |
+
throw new GitError({
|
| 1204 |
+
msg: intl.todo('You cannot delete main branch on remote!')
|
| 1205 |
+
});
|
| 1206 |
+
}
|
| 1207 |
+
// ok so this isn't too bad -- we basically just:
|
| 1208 |
+
// 1) instruct the remote to delete the branch
|
| 1209 |
+
// 2) kill off the remote branch locally
|
| 1210 |
+
// 3) find any branches tracking this remote branch and set them to not track
|
| 1211 |
+
var id = remoteBranch.get('id');
|
| 1212 |
+
this.origin.deleteBranch(branchOnRemote);
|
| 1213 |
+
this.deleteBranch(remoteBranch);
|
| 1214 |
+
this.branchCollection.each(function(branch) {
|
| 1215 |
+
if (branch.getRemoteTrackingBranchID() === id) {
|
| 1216 |
+
branch.setRemoteTrackingBranchID(null);
|
| 1217 |
+
}
|
| 1218 |
+
}, this);
|
| 1219 |
+
|
| 1220 |
+
// animation needs to be triggered on origin directly
|
| 1221 |
+
this.origin.pruneTree();
|
| 1222 |
+
this.origin.externalRefresh();
|
| 1223 |
+
};
|
| 1224 |
+
|
| 1225 |
+
GitEngine.prototype.fetch = function(options) {
|
| 1226 |
+
options = options || {};
|
| 1227 |
+
var didMakeBranch;
|
| 1228 |
+
|
| 1229 |
+
// first check for super stupid case where we are just making
|
| 1230 |
+
// a branch with fetch...
|
| 1231 |
+
if (options.destination && options.source === '') {
|
| 1232 |
+
this.validateAndMakeBranch(
|
| 1233 |
+
options.destination,
|
| 1234 |
+
this.getCommitFromRef('HEAD')
|
| 1235 |
+
);
|
| 1236 |
+
return;
|
| 1237 |
+
} else if (options.destination && options.source) {
|
| 1238 |
+
didMakeBranch = didMakeBranch || this.makeRemoteBranchIfNeeded(options.source);
|
| 1239 |
+
didMakeBranch = didMakeBranch || this.makeBranchIfNeeded(options.destination);
|
| 1240 |
+
options.didMakeBranch = didMakeBranch;
|
| 1241 |
+
|
| 1242 |
+
return this.fetchCore([{
|
| 1243 |
+
destination: options.destination,
|
| 1244 |
+
source: options.source
|
| 1245 |
+
}],
|
| 1246 |
+
options
|
| 1247 |
+
);
|
| 1248 |
+
}
|
| 1249 |
+
// get all remote branches and specify the dest / source pairs
|
| 1250 |
+
var allBranchesOnRemote = this.origin.branchCollection.toArray();
|
| 1251 |
+
var sourceDestPairs = allBranchesOnRemote.map(function(branch) {
|
| 1252 |
+
var branchName = branch.get('id');
|
| 1253 |
+
didMakeBranch = didMakeBranch || this.makeRemoteBranchIfNeeded(branchName);
|
| 1254 |
+
|
| 1255 |
+
return {
|
| 1256 |
+
destination: branch.getPrefixedID(),
|
| 1257 |
+
source: branchName
|
| 1258 |
+
};
|
| 1259 |
+
}, this);
|
| 1260 |
+
options.didMakeBranch = didMakeBranch;
|
| 1261 |
+
return this.fetchCore(sourceDestPairs, options);
|
| 1262 |
+
};
|
| 1263 |
+
|
| 1264 |
+
GitEngine.prototype.fetchCore = function(sourceDestPairs, options) {
|
| 1265 |
+
// first check if our local remote branch is upstream of the origin branch set.
|
| 1266 |
+
// this check essentially pretends the local remote branch is in origin and
|
| 1267 |
+
// could be fast forwarded (basic sanity check)
|
| 1268 |
+
sourceDestPairs.forEach(function (pair) {
|
| 1269 |
+
this.checkUpstreamOfSource(
|
| 1270 |
+
this,
|
| 1271 |
+
this.origin,
|
| 1272 |
+
pair.destination,
|
| 1273 |
+
pair.source
|
| 1274 |
+
);
|
| 1275 |
+
}, this);
|
| 1276 |
+
|
| 1277 |
+
// then we get the difference in commits between these two graphs
|
| 1278 |
+
var commitsToMake = [];
|
| 1279 |
+
sourceDestPairs.forEach(function (pair) {
|
| 1280 |
+
commitsToMake = commitsToMake.concat(this.getTargetGraphDifference(
|
| 1281 |
+
this,
|
| 1282 |
+
this.origin,
|
| 1283 |
+
pair.destination,
|
| 1284 |
+
pair.source,
|
| 1285 |
+
Object.assign(
|
| 1286 |
+
{},
|
| 1287 |
+
options,
|
| 1288 |
+
{dontThrowOnNoFetch: true}
|
| 1289 |
+
)
|
| 1290 |
+
));
|
| 1291 |
+
}, this);
|
| 1292 |
+
|
| 1293 |
+
if (!commitsToMake.length && !options.dontThrowOnNoFetch) {
|
| 1294 |
+
throw new GitError({
|
| 1295 |
+
msg: intl.str('git-error-origin-fetch-uptodate')
|
| 1296 |
+
});
|
| 1297 |
+
}
|
| 1298 |
+
|
| 1299 |
+
// we did this for each remote branch, but we still need to reduce to unique
|
| 1300 |
+
// and sort. in this particular app we can never have unfected remote
|
| 1301 |
+
// commits that are upstream of multiple branches (since the fakeTeamwork
|
| 1302 |
+
// command simply commits), but we are doing it anyways for correctness
|
| 1303 |
+
commitsToMake = Graph.getUniqueObjects(commitsToMake);
|
| 1304 |
+
commitsToMake = Graph.descendSortDepth(commitsToMake);
|
| 1305 |
+
|
| 1306 |
+
// now here is the tricky part -- the difference between local main
|
| 1307 |
+
// and remote main might be commits C2, C3, and C4, but we
|
| 1308 |
+
// might already have those commits. In this case, we don't need to
|
| 1309 |
+
// make them, so filter these out
|
| 1310 |
+
commitsToMake = commitsToMake.filter(function(commitJSON) {
|
| 1311 |
+
return !this.refs[commitJSON.id];
|
| 1312 |
+
}, this);
|
| 1313 |
+
|
| 1314 |
+
var makeCommit = function(id, parentIDs) {
|
| 1315 |
+
// need to get the parents first. since we order by depth, we know
|
| 1316 |
+
// the dependencies are there already
|
| 1317 |
+
var parents = parentIDs.map(function(parentID) {
|
| 1318 |
+
return this.resolveID(parentID);
|
| 1319 |
+
}, this);
|
| 1320 |
+
return this.makeCommit(parents, id);
|
| 1321 |
+
}.bind(this);
|
| 1322 |
+
|
| 1323 |
+
// now make the promise chain to make each commit
|
| 1324 |
+
var chainStep = function(id, parents) {
|
| 1325 |
+
var newCommit = makeCommit(id, parents);
|
| 1326 |
+
return this.animationFactory.playCommitBirthPromiseAnimation(
|
| 1327 |
+
newCommit,
|
| 1328 |
+
this.gitVisuals
|
| 1329 |
+
);
|
| 1330 |
+
}.bind(this);
|
| 1331 |
+
|
| 1332 |
+
var deferred = Q.defer();
|
| 1333 |
+
var chain = deferred.promise;
|
| 1334 |
+
if (options.didMakeBranch) {
|
| 1335 |
+
chain = chain.then(function() {
|
| 1336 |
+
this.animationFactory.playRefreshAnimation(this.origin.gitVisuals);
|
| 1337 |
+
return this.animationFactory.playRefreshAnimation(this.gitVisuals);
|
| 1338 |
+
}.bind(this));
|
| 1339 |
+
}
|
| 1340 |
+
|
| 1341 |
+
var originBranchSet = this.origin.getUpstreamBranchSet();
|
| 1342 |
+
commitsToMake.forEach(function (commitJSON) {
|
| 1343 |
+
// technically we could grab the wrong one here
|
| 1344 |
+
// but this works for now
|
| 1345 |
+
var originBranch = originBranchSet[commitJSON.id][0].obj;
|
| 1346 |
+
var localBranch = this.refs[originBranch.getPrefixedID()];
|
| 1347 |
+
|
| 1348 |
+
chain = chain.then(function() {
|
| 1349 |
+
return this.animationFactory.playHighlightPromiseAnimation(
|
| 1350 |
+
this.origin.resolveID(commitJSON.id),
|
| 1351 |
+
localBranch
|
| 1352 |
+
);
|
| 1353 |
+
}.bind(this));
|
| 1354 |
+
|
| 1355 |
+
chain = chain.then(function() {
|
| 1356 |
+
return chainStep(
|
| 1357 |
+
commitJSON.id,
|
| 1358 |
+
commitJSON.parents
|
| 1359 |
+
);
|
| 1360 |
+
});
|
| 1361 |
+
}, this);
|
| 1362 |
+
|
| 1363 |
+
chain = chain.then(function() {
|
| 1364 |
+
// update all the destinations
|
| 1365 |
+
sourceDestPairs.forEach(function (pair) {
|
| 1366 |
+
var ours = this.resolveID(pair.destination);
|
| 1367 |
+
var theirCommitID = this.origin.getCommitFromRef(pair.source).get('id');
|
| 1368 |
+
// by definition we just made the commit with this id,
|
| 1369 |
+
// so we can grab it now
|
| 1370 |
+
var localCommit = this.refs[theirCommitID];
|
| 1371 |
+
this.setTargetLocation(ours, localCommit);
|
| 1372 |
+
}, this);
|
| 1373 |
+
|
| 1374 |
+
// unhighlight origin by refreshing
|
| 1375 |
+
this.animationFactory.playRefreshAnimation(this.origin.gitVisuals);
|
| 1376 |
+
return this.animationFactory.playRefreshAnimation(this.gitVisuals);
|
| 1377 |
+
}.bind(this));
|
| 1378 |
+
|
| 1379 |
+
if (!options.dontResolvePromise) {
|
| 1380 |
+
this.animationQueue.thenFinish(chain, deferred);
|
| 1381 |
+
}
|
| 1382 |
+
return {
|
| 1383 |
+
chain: chain,
|
| 1384 |
+
deferred: deferred
|
| 1385 |
+
};
|
| 1386 |
+
};
|
| 1387 |
+
|
| 1388 |
+
GitEngine.prototype.pull = function(options) {
|
| 1389 |
+
options = options || {};
|
| 1390 |
+
var localBranch = this.getOneBeforeCommit('HEAD');
|
| 1391 |
+
|
| 1392 |
+
// no matter what fetch
|
| 1393 |
+
var pendingFetch = this.fetch({
|
| 1394 |
+
dontResolvePromise: true,
|
| 1395 |
+
dontThrowOnNoFetch: true,
|
| 1396 |
+
source: options.source,
|
| 1397 |
+
destination: options.destination
|
| 1398 |
+
});
|
| 1399 |
+
|
| 1400 |
+
if (!pendingFetch) {
|
| 1401 |
+
// short circuited for some reason
|
| 1402 |
+
return;
|
| 1403 |
+
}
|
| 1404 |
+
|
| 1405 |
+
var destBranch = this.resolveID(options.destination);
|
| 1406 |
+
// then either rebase or merge
|
| 1407 |
+
if (options.isRebase) {
|
| 1408 |
+
this.pullFinishWithRebase(pendingFetch, localBranch, destBranch);
|
| 1409 |
+
} else {
|
| 1410 |
+
this.pullFinishWithMerge(pendingFetch, localBranch, destBranch);
|
| 1411 |
+
}
|
| 1412 |
+
};
|
| 1413 |
+
|
| 1414 |
+
GitEngine.prototype.pullFinishWithRebase = function(
|
| 1415 |
+
pendingFetch,
|
| 1416 |
+
localBranch,
|
| 1417 |
+
remoteBranch
|
| 1418 |
+
) {
|
| 1419 |
+
var chain = pendingFetch.chain;
|
| 1420 |
+
var deferred = pendingFetch.deferred;
|
| 1421 |
+
chain = chain.then(function() {
|
| 1422 |
+
if (this.isUpstreamOf(remoteBranch, localBranch)) {
|
| 1423 |
+
this.command.set('error', new CommandResult({
|
| 1424 |
+
msg: intl.str('git-result-uptodate')
|
| 1425 |
+
}));
|
| 1426 |
+
throw SHORT_CIRCUIT_CHAIN;
|
| 1427 |
+
}
|
| 1428 |
+
}.bind(this));
|
| 1429 |
+
|
| 1430 |
+
// delay a bit after the intense refresh animation from
|
| 1431 |
+
// fetch
|
| 1432 |
+
chain = chain.then(function() {
|
| 1433 |
+
return this.animationFactory.getDelayedPromise(300);
|
| 1434 |
+
}.bind(this));
|
| 1435 |
+
|
| 1436 |
+
chain = chain.then(function() {
|
| 1437 |
+
// highlight last commit on o/main to color of
|
| 1438 |
+
// local branch
|
| 1439 |
+
return this.animationFactory.playHighlightPromiseAnimation(
|
| 1440 |
+
this.getCommitFromRef(remoteBranch),
|
| 1441 |
+
localBranch
|
| 1442 |
+
);
|
| 1443 |
+
}.bind(this));
|
| 1444 |
+
|
| 1445 |
+
chain = chain.then(function() {
|
| 1446 |
+
pendingFetch.dontResolvePromise = true;
|
| 1447 |
+
|
| 1448 |
+
// Lets move the git pull --rebase check up here.
|
| 1449 |
+
if (this.isUpstreamOf(localBranch, remoteBranch)) {
|
| 1450 |
+
this.setTargetLocation(
|
| 1451 |
+
localBranch,
|
| 1452 |
+
this.getCommitFromRef(remoteBranch)
|
| 1453 |
+
);
|
| 1454 |
+
this.checkout(localBranch);
|
| 1455 |
+
return this.animationFactory.playRefreshAnimation(this.gitVisuals);
|
| 1456 |
+
}
|
| 1457 |
+
|
| 1458 |
+
try {
|
| 1459 |
+
return this.rebase(remoteBranch, localBranch, pendingFetch);
|
| 1460 |
+
} catch (err) {
|
| 1461 |
+
this.filterError(err);
|
| 1462 |
+
if (err.getMsg() !== intl.str('git-error-rebase-none')) {
|
| 1463 |
+
throw err;
|
| 1464 |
+
}
|
| 1465 |
+
this.setTargetLocation(
|
| 1466 |
+
localBranch,
|
| 1467 |
+
this.getCommitFromRef(remoteBranch)
|
| 1468 |
+
);
|
| 1469 |
+
this.checkout(localBranch);
|
| 1470 |
+
return this.animationFactory.playRefreshAnimation(this.gitVisuals);
|
| 1471 |
+
}
|
| 1472 |
+
}.bind(this));
|
| 1473 |
+
chain = chain.fail(catchShortCircuit);
|
| 1474 |
+
|
| 1475 |
+
this.animationQueue.thenFinish(chain, deferred);
|
| 1476 |
+
};
|
| 1477 |
+
|
| 1478 |
+
GitEngine.prototype.pullFinishWithMerge = function(
|
| 1479 |
+
pendingFetch,
|
| 1480 |
+
localBranch,
|
| 1481 |
+
remoteBranch
|
| 1482 |
+
) {
|
| 1483 |
+
var chain = pendingFetch.chain;
|
| 1484 |
+
var deferred = pendingFetch.deferred;
|
| 1485 |
+
|
| 1486 |
+
chain = chain.then(function() {
|
| 1487 |
+
if (this.mergeCheck(remoteBranch, localBranch)) {
|
| 1488 |
+
this.command.set('error', new CommandResult({
|
| 1489 |
+
msg: intl.str('git-result-uptodate')
|
| 1490 |
+
}));
|
| 1491 |
+
throw SHORT_CIRCUIT_CHAIN;
|
| 1492 |
+
}
|
| 1493 |
+
}.bind(this));
|
| 1494 |
+
|
| 1495 |
+
// delay a bit after the intense refresh animation from
|
| 1496 |
+
// fetch
|
| 1497 |
+
chain = chain.then(function() {
|
| 1498 |
+
return this.animationFactory.getDelayedPromise(300);
|
| 1499 |
+
}.bind(this));
|
| 1500 |
+
|
| 1501 |
+
chain = chain.then(function() {
|
| 1502 |
+
// highlight last commit on o/main to color of
|
| 1503 |
+
// local branch
|
| 1504 |
+
return this.animationFactory.playHighlightPromiseAnimation(
|
| 1505 |
+
this.getCommitFromRef(remoteBranch),
|
| 1506 |
+
localBranch
|
| 1507 |
+
);
|
| 1508 |
+
}.bind(this));
|
| 1509 |
+
|
| 1510 |
+
chain = chain.then(function() {
|
| 1511 |
+
// highlight commit on main to color of remote
|
| 1512 |
+
return this.animationFactory.playHighlightPromiseAnimation(
|
| 1513 |
+
this.getCommitFromRef(localBranch),
|
| 1514 |
+
remoteBranch
|
| 1515 |
+
);
|
| 1516 |
+
}.bind(this));
|
| 1517 |
+
|
| 1518 |
+
// delay and merge
|
| 1519 |
+
chain = chain.then(function() {
|
| 1520 |
+
return this.animationFactory.getDelayedPromise(700);
|
| 1521 |
+
}.bind(this));
|
| 1522 |
+
chain = chain.then(function() {
|
| 1523 |
+
var newCommit = this.merge(remoteBranch);
|
| 1524 |
+
if (!newCommit) {
|
| 1525 |
+
// it is a fast forward
|
| 1526 |
+
return this.animationFactory.playRefreshAnimation(this.gitVisuals);
|
| 1527 |
+
}
|
| 1528 |
+
|
| 1529 |
+
return this.animationFactory.playCommitBirthPromiseAnimation(
|
| 1530 |
+
newCommit,
|
| 1531 |
+
this.gitVisuals
|
| 1532 |
+
);
|
| 1533 |
+
}.bind(this));
|
| 1534 |
+
chain = chain.fail(catchShortCircuit);
|
| 1535 |
+
|
| 1536 |
+
this.animationQueue.thenFinish(chain, deferred);
|
| 1537 |
+
};
|
| 1538 |
+
|
| 1539 |
+
GitEngine.prototype.fakeTeamwork = function(numToMake, branch) {
|
| 1540 |
+
var makeOriginCommit = function() {
|
| 1541 |
+
var id = this.getUniqueID();
|
| 1542 |
+
return this.origin.receiveTeamwork(id, branch, this.animationQueue);
|
| 1543 |
+
}.bind(this);
|
| 1544 |
+
|
| 1545 |
+
var chainStep = function() {
|
| 1546 |
+
var newCommit = makeOriginCommit();
|
| 1547 |
+
return this.animationFactory.playCommitBirthPromiseAnimation(
|
| 1548 |
+
newCommit,
|
| 1549 |
+
this.origin.gitVisuals
|
| 1550 |
+
);
|
| 1551 |
+
}.bind(this);
|
| 1552 |
+
|
| 1553 |
+
var deferred = Q.defer();
|
| 1554 |
+
var chain = deferred.promise;
|
| 1555 |
+
|
| 1556 |
+
for(var i = 0; i < numToMake; i++) {
|
| 1557 |
+
chain = chain.then(chainStep);
|
| 1558 |
+
}
|
| 1559 |
+
this.animationQueue.thenFinish(chain, deferred);
|
| 1560 |
+
};
|
| 1561 |
+
|
| 1562 |
+
GitEngine.prototype.receiveTeamwork = function(id, branch, animationQueue) {
|
| 1563 |
+
this.checkout(this.resolveID(branch));
|
| 1564 |
+
var newCommit = this.makeCommit([this.getCommitFromRef('HEAD')], id);
|
| 1565 |
+
this.setTargetLocation(this.HEAD, newCommit);
|
| 1566 |
+
|
| 1567 |
+
return newCommit;
|
| 1568 |
+
};
|
| 1569 |
+
|
| 1570 |
+
GitEngine.prototype.cherrypick = function(commit) {
|
| 1571 |
+
// alter the ID slightly
|
| 1572 |
+
var id = this.rebaseAltID(commit.get('id'));
|
| 1573 |
+
|
| 1574 |
+
// now commit with that id onto HEAD
|
| 1575 |
+
var newCommit = this.makeCommit([this.getCommitFromRef('HEAD')], id);
|
| 1576 |
+
this.setTargetLocation(this.HEAD, newCommit);
|
| 1577 |
+
|
| 1578 |
+
return newCommit;
|
| 1579 |
+
};
|
| 1580 |
+
|
| 1581 |
+
GitEngine.prototype.commit = function(options) {
|
| 1582 |
+
options = options || {};
|
| 1583 |
+
var targetCommit = this.getCommitFromRef(this.HEAD);
|
| 1584 |
+
var id = null;
|
| 1585 |
+
|
| 1586 |
+
// if we want to amend, go one above
|
| 1587 |
+
if (options.isAmend) {
|
| 1588 |
+
targetCommit = this.resolveID('HEAD~1');
|
| 1589 |
+
id = this.rebaseAltID(this.getCommitFromRef('HEAD').get('id'));
|
| 1590 |
+
}
|
| 1591 |
+
|
| 1592 |
+
var newCommit = this.makeCommit([targetCommit], id);
|
| 1593 |
+
if (this.getDetachedHead() && this.mode === 'git') {
|
| 1594 |
+
this.command.addWarning(intl.str('git-warning-detached'));
|
| 1595 |
+
}
|
| 1596 |
+
|
| 1597 |
+
this.setTargetLocation(this.HEAD, newCommit);
|
| 1598 |
+
return newCommit;
|
| 1599 |
+
};
|
| 1600 |
+
|
| 1601 |
+
GitEngine.prototype.resolveNameNoPrefix = function(someRef) {
|
| 1602 |
+
// first get the obj
|
| 1603 |
+
var obj = this.resolveID(someRef);
|
| 1604 |
+
if (obj.get('type') == 'commit') {
|
| 1605 |
+
return obj.get('id');
|
| 1606 |
+
}
|
| 1607 |
+
if (obj.get('type') == 'branch') {
|
| 1608 |
+
return obj.get('id');
|
| 1609 |
+
}
|
| 1610 |
+
// we are dealing with HEAD
|
| 1611 |
+
return this.resolveNameNoPrefix(obj.get('target'));
|
| 1612 |
+
};
|
| 1613 |
+
|
| 1614 |
+
GitEngine.prototype.resolveName = function(someRef) {
|
| 1615 |
+
// first get the obj
|
| 1616 |
+
var obj = this.resolveID(someRef);
|
| 1617 |
+
if (obj.get('type') == 'commit') {
|
| 1618 |
+
return 'commit ' + obj.get('id');
|
| 1619 |
+
}
|
| 1620 |
+
if (obj.get('type') == 'branch') {
|
| 1621 |
+
return 'branch "' + obj.get('id') + '"';
|
| 1622 |
+
}
|
| 1623 |
+
// we are dealing with HEAD
|
| 1624 |
+
return this.resolveName(obj.get('target'));
|
| 1625 |
+
};
|
| 1626 |
+
|
| 1627 |
+
GitEngine.prototype.resolveID = function(idOrTarget) {
|
| 1628 |
+
if (idOrTarget === null || idOrTarget === undefined) {
|
| 1629 |
+
var err = new Error();
|
| 1630 |
+
throw new Error('Don\'t call this with null / undefined: ' + err.stack);
|
| 1631 |
+
}
|
| 1632 |
+
|
| 1633 |
+
if (typeof idOrTarget !== 'string') {
|
| 1634 |
+
return idOrTarget;
|
| 1635 |
+
}
|
| 1636 |
+
return this.resolveStringRef(idOrTarget);
|
| 1637 |
+
};
|
| 1638 |
+
|
| 1639 |
+
GitEngine.prototype.resolveRelativeRef = function(commit, relative) {
|
| 1640 |
+
var regex = /([~\^])(\d*)/g;
|
| 1641 |
+
var matches;
|
| 1642 |
+
|
| 1643 |
+
while (matches = regex.exec(relative)) {
|
| 1644 |
+
var next = commit;
|
| 1645 |
+
var num = matches[2] ? parseInt(matches[2], 10) : 1;
|
| 1646 |
+
|
| 1647 |
+
if (matches[1] == '^') {
|
| 1648 |
+
next = commit.getParent(num-1);
|
| 1649 |
+
} else {
|
| 1650 |
+
while (next && num--) {
|
| 1651 |
+
next = next.getParent(0);
|
| 1652 |
+
}
|
| 1653 |
+
}
|
| 1654 |
+
|
| 1655 |
+
if (!next) {
|
| 1656 |
+
var msg = intl.str('git-error-relative-ref', {
|
| 1657 |
+
commit: commit.id,
|
| 1658 |
+
match: matches[0]
|
| 1659 |
+
});
|
| 1660 |
+
throw new GitError({
|
| 1661 |
+
msg: msg
|
| 1662 |
+
});
|
| 1663 |
+
}
|
| 1664 |
+
|
| 1665 |
+
commit = next;
|
| 1666 |
+
}
|
| 1667 |
+
|
| 1668 |
+
return commit;
|
| 1669 |
+
};
|
| 1670 |
+
|
| 1671 |
+
GitEngine.prototype.doesRefExist = function(ref) {
|
| 1672 |
+
return !!this.refs[ref]
|
| 1673 |
+
};
|
| 1674 |
+
|
| 1675 |
+
GitEngine.prototype.resolveStringRef = function(ref) {
|
| 1676 |
+
ref = this.crappyUnescape(ref);
|
| 1677 |
+
|
| 1678 |
+
if (this.refs[ref]) {
|
| 1679 |
+
return this.refs[ref];
|
| 1680 |
+
}
|
| 1681 |
+
// Commit hashes like C4 are case insensitive
|
| 1682 |
+
if (ref.match(/^c\d+'*/) && this.refs[ref.toUpperCase()]) {
|
| 1683 |
+
return this.refs[ref.toUpperCase()];
|
| 1684 |
+
}
|
| 1685 |
+
|
| 1686 |
+
// Attempt to split ref string into a reference and a string of ~ and ^ modifiers.
|
| 1687 |
+
var startRef = null;
|
| 1688 |
+
var relative = null;
|
| 1689 |
+
var regex = /^([a-zA-Z0-9]+)(([~\^]\d*)*)$/;
|
| 1690 |
+
var matches = regex.exec(ref);
|
| 1691 |
+
if (matches) {
|
| 1692 |
+
startRef = matches[1];
|
| 1693 |
+
relative = matches[2];
|
| 1694 |
+
} else {
|
| 1695 |
+
throw new GitError({
|
| 1696 |
+
msg: intl.str('git-error-exist', {ref: ref})
|
| 1697 |
+
});
|
| 1698 |
+
}
|
| 1699 |
+
|
| 1700 |
+
if (!this.refs[startRef]) {
|
| 1701 |
+
throw new GitError({
|
| 1702 |
+
msg: intl.str('git-error-exist', {ref: ref})
|
| 1703 |
+
});
|
| 1704 |
+
}
|
| 1705 |
+
var commit = this.getCommitFromRef(startRef);
|
| 1706 |
+
|
| 1707 |
+
if (relative) {
|
| 1708 |
+
commit = this.resolveRelativeRef( commit, relative );
|
| 1709 |
+
}
|
| 1710 |
+
|
| 1711 |
+
return commit;
|
| 1712 |
+
};
|
| 1713 |
+
|
| 1714 |
+
GitEngine.prototype.getCommitFromRef = function(ref) {
|
| 1715 |
+
var start = this.resolveID(ref);
|
| 1716 |
+
|
| 1717 |
+
// works for both HEAD and just a single layer. aka branch
|
| 1718 |
+
while (start.get('type') !== 'commit') {
|
| 1719 |
+
start = start.get('target');
|
| 1720 |
+
}
|
| 1721 |
+
return start;
|
| 1722 |
+
};
|
| 1723 |
+
|
| 1724 |
+
GitEngine.prototype.getType = function(ref) {
|
| 1725 |
+
return this.resolveID(ref).get('type');
|
| 1726 |
+
};
|
| 1727 |
+
|
| 1728 |
+
GitEngine.prototype.setTargetLocation = function(ref, target) {
|
| 1729 |
+
if (this.getType(ref) == 'commit') {
|
| 1730 |
+
// nothing to do
|
| 1731 |
+
return;
|
| 1732 |
+
}
|
| 1733 |
+
|
| 1734 |
+
// sets whatever ref is (branch, HEAD, etc) to a target. so if
|
| 1735 |
+
// you pass in HEAD, and HEAD is pointing to a branch, it will update
|
| 1736 |
+
// the branch to that commit, not the HEAD
|
| 1737 |
+
ref = this.getOneBeforeCommit(ref);
|
| 1738 |
+
ref.set('target', target);
|
| 1739 |
+
};
|
| 1740 |
+
|
| 1741 |
+
GitEngine.prototype.updateBranchesFromSet = function(commitSet) {
|
| 1742 |
+
if (!commitSet) {
|
| 1743 |
+
throw new Error('need commit set here');
|
| 1744 |
+
}
|
| 1745 |
+
// commitSet is the set of commits that are stale or moved or whatever.
|
| 1746 |
+
// any branches POINTING to these commits need to be moved!
|
| 1747 |
+
|
| 1748 |
+
// first get a list of what branches influence what commits
|
| 1749 |
+
var upstreamSet = this.getUpstreamBranchSet();
|
| 1750 |
+
|
| 1751 |
+
var branchesToUpdate = {};
|
| 1752 |
+
// now loop over the set we got passed in and find which branches
|
| 1753 |
+
// that means (aka intersection)
|
| 1754 |
+
commitSet.forEach(function (val, id) {
|
| 1755 |
+
upstreamSet[id].forEach(function (branchJSON) {
|
| 1756 |
+
branchesToUpdate[branchJSON.id] = true;
|
| 1757 |
+
});
|
| 1758 |
+
}, this);
|
| 1759 |
+
|
| 1760 |
+
var branchList = branchesToUpdate.map(function(val, id) {
|
| 1761 |
+
return id;
|
| 1762 |
+
});
|
| 1763 |
+
return this.updateBranchesForHg(branchList);
|
| 1764 |
+
};
|
| 1765 |
+
|
| 1766 |
+
GitEngine.prototype.updateAllBranchesForHgAndPlay = function(branchList) {
|
| 1767 |
+
return this.updateBranchesForHg(branchList) &&
|
| 1768 |
+
this.animationFactory.playRefreshAnimationSlow(this.gitVisuals);
|
| 1769 |
+
};
|
| 1770 |
+
|
| 1771 |
+
GitEngine.prototype.updateAllBranchesForHg = function() {
|
| 1772 |
+
var branchList = this.branchCollection.map(function(branch) {
|
| 1773 |
+
return branch.get('id');
|
| 1774 |
+
});
|
| 1775 |
+
return this.updateBranchesForHg(branchList);
|
| 1776 |
+
};
|
| 1777 |
+
|
| 1778 |
+
GitEngine.prototype.syncRemoteBranchFills = function() {
|
| 1779 |
+
this.branchCollection.each(function(branch) {
|
| 1780 |
+
if (!branch.getIsRemote()) {
|
| 1781 |
+
return;
|
| 1782 |
+
}
|
| 1783 |
+
var originBranch = this.origin.refs[branch.getBaseID()];
|
| 1784 |
+
if (!originBranch.get('visBranch')) {
|
| 1785 |
+
// testing mode doesn't get this
|
| 1786 |
+
return;
|
| 1787 |
+
}
|
| 1788 |
+
var originFill = originBranch.get('visBranch').get('fill');
|
| 1789 |
+
branch.get('visBranch').set('fill', originFill);
|
| 1790 |
+
}, this);
|
| 1791 |
+
};
|
| 1792 |
+
|
| 1793 |
+
GitEngine.prototype.updateBranchesForHg = function(branchList) {
|
| 1794 |
+
var hasUpdated = false;
|
| 1795 |
+
branchList.forEach(function (branchID) {
|
| 1796 |
+
// ok now just check if this branch has a more recent commit available.
|
| 1797 |
+
// that mapping is easy because we always do rebase alt id --
|
| 1798 |
+
// theres no way to have C3' and C3''' but no C3''. so just
|
| 1799 |
+
// bump the ID once -- if thats not filled in we are updated,
|
| 1800 |
+
// otherwise loop until you find undefined
|
| 1801 |
+
var commitID = this.getCommitFromRef(branchID).get('id');
|
| 1802 |
+
var altID = this.getBumpedID(commitID);
|
| 1803 |
+
if (!this.refs[altID]) {
|
| 1804 |
+
return;
|
| 1805 |
+
}
|
| 1806 |
+
hasUpdated = true;
|
| 1807 |
+
|
| 1808 |
+
var lastID;
|
| 1809 |
+
while (this.refs[altID]) {
|
| 1810 |
+
lastID = altID;
|
| 1811 |
+
altID = this.rebaseAltID(altID);
|
| 1812 |
+
}
|
| 1813 |
+
|
| 1814 |
+
// last ID is the one we want to update to
|
| 1815 |
+
this.setTargetLocation(this.refs[branchID], this.refs[lastID]);
|
| 1816 |
+
}, this);
|
| 1817 |
+
|
| 1818 |
+
if (!hasUpdated) {
|
| 1819 |
+
return false;
|
| 1820 |
+
}
|
| 1821 |
+
return true;
|
| 1822 |
+
};
|
| 1823 |
+
|
| 1824 |
+
GitEngine.prototype.updateCommitParentsForHgRebase = function(commitSet) {
|
| 1825 |
+
var anyChange = false;
|
| 1826 |
+
Object.keys(commitSet).forEach(function(commitID) {
|
| 1827 |
+
var commit = this.refs[commitID];
|
| 1828 |
+
var thisUpdated = commit.checkForUpdatedParent(this);
|
| 1829 |
+
anyChange = anyChange || thisUpdated;
|
| 1830 |
+
}, this);
|
| 1831 |
+
return anyChange;
|
| 1832 |
+
};
|
| 1833 |
+
|
| 1834 |
+
GitEngine.prototype.pruneTreeAndPlay = function() {
|
| 1835 |
+
return this.pruneTree() &&
|
| 1836 |
+
this.animationFactory.playRefreshAnimationSlow(this.gitVisuals);
|
| 1837 |
+
};
|
| 1838 |
+
|
| 1839 |
+
GitEngine.prototype.pruneTree = function(doPrintWarning = true) {
|
| 1840 |
+
var set = this.getUpstreamBranchSet();
|
| 1841 |
+
// don't prune commits that HEAD depends on
|
| 1842 |
+
var headSet = Graph.getUpstreamSet(this, 'HEAD');
|
| 1843 |
+
Object.keys(headSet).forEach(function(commitID) {
|
| 1844 |
+
set[commitID] = true;
|
| 1845 |
+
});
|
| 1846 |
+
Object.keys(this.getUpstreamTagSet()).forEach(commitID => set[commitID] = true);
|
| 1847 |
+
|
| 1848 |
+
var toDelete = [];
|
| 1849 |
+
this.commitCollection.each(function(commit) {
|
| 1850 |
+
// nothing cares about this commit :(
|
| 1851 |
+
if (!set[commit.get('id')]) {
|
| 1852 |
+
toDelete.push(commit);
|
| 1853 |
+
}
|
| 1854 |
+
}, this);
|
| 1855 |
+
|
| 1856 |
+
if (!toDelete.length) {
|
| 1857 |
+
// returning nothing will perform
|
| 1858 |
+
// the switch sync
|
| 1859 |
+
return;
|
| 1860 |
+
}
|
| 1861 |
+
if (this.command && doPrintWarning) {
|
| 1862 |
+
this.command.addWarning(intl.str('hg-prune-tree'));
|
| 1863 |
+
}
|
| 1864 |
+
|
| 1865 |
+
toDelete.forEach(function (commit) {
|
| 1866 |
+
commit.removeFromParents();
|
| 1867 |
+
this.commitCollection.remove(commit);
|
| 1868 |
+
|
| 1869 |
+
var ID = commit.get('id');
|
| 1870 |
+
this.refs[ID] = undefined;
|
| 1871 |
+
delete this.refs[ID];
|
| 1872 |
+
|
| 1873 |
+
var visNode = commit.get('visNode');
|
| 1874 |
+
if (visNode) {
|
| 1875 |
+
visNode.removeAll();
|
| 1876 |
+
}
|
| 1877 |
+
}, this);
|
| 1878 |
+
|
| 1879 |
+
return true;
|
| 1880 |
+
};
|
| 1881 |
+
GitEngine.prototype.getUpstreamBranchSet = function() {
|
| 1882 |
+
return this.getUpstreamCollectionSet(this.branchCollection);
|
| 1883 |
+
};
|
| 1884 |
+
|
| 1885 |
+
GitEngine.prototype.getUpstreamTagSet = function() {
|
| 1886 |
+
return this.getUpstreamCollectionSet(this.tagCollection);
|
| 1887 |
+
};
|
| 1888 |
+
|
| 1889 |
+
GitEngine.prototype.getUpstreamCollectionSet = function(collection) {
|
| 1890 |
+
// this is expensive!! so only call once in a while
|
| 1891 |
+
var commitToSet = {};
|
| 1892 |
+
|
| 1893 |
+
var inArray = function(arr, id) {
|
| 1894 |
+
var found = false;
|
| 1895 |
+
arr.forEach(function (wrapper) {
|
| 1896 |
+
if (wrapper.id == id) {
|
| 1897 |
+
found = true;
|
| 1898 |
+
}
|
| 1899 |
+
});
|
| 1900 |
+
|
| 1901 |
+
return found;
|
| 1902 |
+
};
|
| 1903 |
+
|
| 1904 |
+
var bfsSearch = function(commit) {
|
| 1905 |
+
var set = [];
|
| 1906 |
+
var pQueue = [commit];
|
| 1907 |
+
while (pQueue.length) {
|
| 1908 |
+
var popped = pQueue.pop();
|
| 1909 |
+
set.push(popped.get('id'));
|
| 1910 |
+
|
| 1911 |
+
if (popped.get('parents') && popped.get('parents').length) {
|
| 1912 |
+
pQueue = pQueue.concat(popped.get('parents'));
|
| 1913 |
+
}
|
| 1914 |
+
}
|
| 1915 |
+
return set;
|
| 1916 |
+
};
|
| 1917 |
+
|
| 1918 |
+
collection.each(function(ref) {
|
| 1919 |
+
var set = bfsSearch(ref.get('target'));
|
| 1920 |
+
set.forEach(function (id) {
|
| 1921 |
+
commitToSet[id] = commitToSet[id] || [];
|
| 1922 |
+
|
| 1923 |
+
// only add it if it's not there, so hue blending is ok
|
| 1924 |
+
if (!inArray(commitToSet[id], ref.get('id'))) {
|
| 1925 |
+
commitToSet[id].push({
|
| 1926 |
+
obj: ref,
|
| 1927 |
+
id: ref.get('id')
|
| 1928 |
+
});
|
| 1929 |
+
}
|
| 1930 |
+
});
|
| 1931 |
+
});
|
| 1932 |
+
|
| 1933 |
+
return commitToSet;
|
| 1934 |
+
};
|
| 1935 |
+
|
| 1936 |
+
GitEngine.prototype.getUpstreamHeadSet = function() {
|
| 1937 |
+
var set = Graph.getUpstreamSet(this, 'HEAD');
|
| 1938 |
+
var including = this.getCommitFromRef('HEAD').get('id');
|
| 1939 |
+
|
| 1940 |
+
set[including] = true;
|
| 1941 |
+
return set;
|
| 1942 |
+
};
|
| 1943 |
+
|
| 1944 |
+
GitEngine.prototype.getOneBeforeCommit = function(ref) {
|
| 1945 |
+
// you can call this command on HEAD in detached, HEAD, or on a branch
|
| 1946 |
+
// and it will return the ref that is one above a commit. aka
|
| 1947 |
+
// it resolves HEAD to something that we can move the ref with
|
| 1948 |
+
var start = this.resolveID(ref);
|
| 1949 |
+
if (start === this.HEAD && !this.getDetachedHead()) {
|
| 1950 |
+
start = start.get('target');
|
| 1951 |
+
}
|
| 1952 |
+
return start;
|
| 1953 |
+
};
|
| 1954 |
+
|
| 1955 |
+
GitEngine.prototype.scrapeBaseID = function(id) {
|
| 1956 |
+
var results = /^C(\d+)/.exec(id);
|
| 1957 |
+
|
| 1958 |
+
if (!results) {
|
| 1959 |
+
throw new Error('regex failed on ' + id);
|
| 1960 |
+
}
|
| 1961 |
+
|
| 1962 |
+
return 'C' + results[1];
|
| 1963 |
+
};
|
| 1964 |
+
|
| 1965 |
+
/*
|
| 1966 |
+
* grabs a bumped ID that is NOT currently reserved
|
| 1967 |
+
*/
|
| 1968 |
+
GitEngine.prototype.rebaseAltID = function(id) {
|
| 1969 |
+
var newID = this.getBumpedID(id);
|
| 1970 |
+
while (this.refs[newID]) {
|
| 1971 |
+
newID = this.getBumpedID(newID);
|
| 1972 |
+
}
|
| 1973 |
+
return newID;
|
| 1974 |
+
};
|
| 1975 |
+
|
| 1976 |
+
GitEngine.prototype.getMostRecentBumpedID = function(id) {
|
| 1977 |
+
var newID = id;
|
| 1978 |
+
var lastID;
|
| 1979 |
+
while (this.refs[newID]) {
|
| 1980 |
+
lastID = newID;
|
| 1981 |
+
newID = this.getBumpedID(newID);
|
| 1982 |
+
}
|
| 1983 |
+
return lastID;
|
| 1984 |
+
};
|
| 1985 |
+
|
| 1986 |
+
GitEngine.prototype.getBumpedID = function(id) {
|
| 1987 |
+
// this function alters an ID to add a quote to the end,
|
| 1988 |
+
// indicating that it was rebased.
|
| 1989 |
+
var regexMap = [
|
| 1990 |
+
[/^C(\d+)[']{0,2}$/, function(bits) {
|
| 1991 |
+
// this id can use another quote, so just add it
|
| 1992 |
+
return bits[0] + "'";
|
| 1993 |
+
}],
|
| 1994 |
+
[/^C(\d+)[']{3}$/, function(bits) {
|
| 1995 |
+
// here we switch from C''' to C'^4
|
| 1996 |
+
return bits[0].slice(0, -3) + "'^4";
|
| 1997 |
+
}],
|
| 1998 |
+
[/^C(\d+)['][\^](\d+)$/, function(bits) {
|
| 1999 |
+
return 'C' + String(bits[1]) + "'^" + String(Number(bits[2]) + 1);
|
| 2000 |
+
}]
|
| 2001 |
+
];
|
| 2002 |
+
|
| 2003 |
+
// for loop for early return (instead of _.each)
|
| 2004 |
+
for (var i = 0; i < regexMap.length; i++) {
|
| 2005 |
+
var regex = regexMap[i][0];
|
| 2006 |
+
var func = regexMap[i][1];
|
| 2007 |
+
var results = regex.exec(id);
|
| 2008 |
+
if (results) {
|
| 2009 |
+
return func(results);
|
| 2010 |
+
}
|
| 2011 |
+
}
|
| 2012 |
+
throw new Error('could not modify the id ' + id);
|
| 2013 |
+
};
|
| 2014 |
+
|
| 2015 |
+
GitEngine.prototype.idSortFunc = function(cA, cB) {
|
| 2016 |
+
// commit IDs can come in many forms:
|
| 2017 |
+
// C4
|
| 2018 |
+
// C4' (from a rebase)
|
| 2019 |
+
// C4'' (from multiple rebases)
|
| 2020 |
+
// C4'^3 (from a BUNCH of rebases)
|
| 2021 |
+
|
| 2022 |
+
var scale = 1000;
|
| 2023 |
+
|
| 2024 |
+
var regexMap = [
|
| 2025 |
+
[/^C(\d+)$/, function(bits) {
|
| 2026 |
+
// return the 4 from C4
|
| 2027 |
+
return scale * bits[1];
|
| 2028 |
+
}],
|
| 2029 |
+
[/^C(\d+)([']+)$/, function(bits) {
|
| 2030 |
+
// return the 4 from C4, plus the length of the quotes
|
| 2031 |
+
return scale * bits[1] + bits[2].length;
|
| 2032 |
+
}],
|
| 2033 |
+
[/^C(\d+)['][\^](\d+)$/, function(bits) {
|
| 2034 |
+
return scale * bits[1] + Number(bits[2]);
|
| 2035 |
+
}]
|
| 2036 |
+
];
|
| 2037 |
+
|
| 2038 |
+
var getNumToSort = function(id) {
|
| 2039 |
+
for (var i = 0; i < regexMap.length; i++) {
|
| 2040 |
+
var regex = regexMap[i][0];
|
| 2041 |
+
var func = regexMap[i][1];
|
| 2042 |
+
var results = regex.exec(id);
|
| 2043 |
+
if (results) {
|
| 2044 |
+
return func(results);
|
| 2045 |
+
}
|
| 2046 |
+
}
|
| 2047 |
+
throw new Error('Could not parse commit ID ' + id);
|
| 2048 |
+
};
|
| 2049 |
+
|
| 2050 |
+
// We usually want to sort by reverse chronological order, aka the
|
| 2051 |
+
// "latest" commits have the highest values. When we did this
|
| 2052 |
+
// with date sorting, that means the commit C1 at t=0 should have
|
| 2053 |
+
// a lower value than the commit C2 at t=1. We do this by doing
|
| 2054 |
+
// t0 - t1 and get a negative number. Same goes for ID sorting,
|
| 2055 |
+
// which means C1 - C2 = -1
|
| 2056 |
+
return getNumToSort(cA.get('id')) - getNumToSort(cB.get('id'));
|
| 2057 |
+
};
|
| 2058 |
+
|
| 2059 |
+
GitEngine.prototype.dateSortFunc = function(cA, cB) {
|
| 2060 |
+
// We used to use date sorting, but its hacky so lets switch to ID sorting
|
| 2061 |
+
// to eliminate non-determinism
|
| 2062 |
+
return GitEngine.prototype.idSortFunc(cA, cB);
|
| 2063 |
+
};
|
| 2064 |
+
|
| 2065 |
+
GitEngine.prototype.hgRebase = function(destination, base) {
|
| 2066 |
+
var deferred = Q.defer();
|
| 2067 |
+
var chain = this.rebase(destination, base, {
|
| 2068 |
+
dontResolvePromise: true,
|
| 2069 |
+
deferred: deferred
|
| 2070 |
+
});
|
| 2071 |
+
|
| 2072 |
+
// was upstream or something
|
| 2073 |
+
if (!chain) {
|
| 2074 |
+
return;
|
| 2075 |
+
}
|
| 2076 |
+
|
| 2077 |
+
// ok lets grab the merge base first
|
| 2078 |
+
var commonAncestor = this.getCommonAncestor(destination, base);
|
| 2079 |
+
var baseCommit = this.getCommitFromRef(base);
|
| 2080 |
+
// we need everything BELOW ourselves...
|
| 2081 |
+
var downstream = this.getDownstreamSet(base);
|
| 2082 |
+
// and we need to go upwards to the stop set
|
| 2083 |
+
var stopSet = Graph.getUpstreamSet(this, destination);
|
| 2084 |
+
var upstream = this.getUpstreamDiffSetFromSet(stopSet, base);
|
| 2085 |
+
|
| 2086 |
+
// and NOWWWwwww get all the descendants of this set
|
| 2087 |
+
var moreSets = [];
|
| 2088 |
+
Object.keys(upstream).forEach(function(id) {
|
| 2089 |
+
moreSets.push(this.getDownstreamSet(id));
|
| 2090 |
+
}, this);
|
| 2091 |
+
|
| 2092 |
+
var mainSet = {};
|
| 2093 |
+
mainSet[baseCommit.get('id')] = true;
|
| 2094 |
+
[upstream, downstream].concat(moreSets).forEach(function(set) {
|
| 2095 |
+
Object.keys(set).forEach(function(id) {
|
| 2096 |
+
mainSet[id] = true;
|
| 2097 |
+
});
|
| 2098 |
+
});
|
| 2099 |
+
|
| 2100 |
+
// we also need the branches POINTING to main set
|
| 2101 |
+
var branchMap = {};
|
| 2102 |
+
var upstreamSet = this.getUpstreamBranchSet();
|
| 2103 |
+
Object.keys(mainSet).forEach(function(commitID) {
|
| 2104 |
+
// now loop over that commits branches
|
| 2105 |
+
upstreamSet[commitID].forEach(function(branchJSON) {
|
| 2106 |
+
branchMap[branchJSON.id] = true;
|
| 2107 |
+
});
|
| 2108 |
+
});
|
| 2109 |
+
|
| 2110 |
+
var branchList = Object.keys(branchMap);
|
| 2111 |
+
|
| 2112 |
+
chain = chain.then(function() {
|
| 2113 |
+
// now we just moved a bunch of commits, but we haven't updated the
|
| 2114 |
+
// dangling guys. lets do that and then prune
|
| 2115 |
+
var anyChange = this.updateCommitParentsForHgRebase(mainSet);
|
| 2116 |
+
if (!anyChange) {
|
| 2117 |
+
return;
|
| 2118 |
+
}
|
| 2119 |
+
return this.animationFactory.playRefreshAnimationSlow(this.gitVisuals);
|
| 2120 |
+
}.bind(this));
|
| 2121 |
+
|
| 2122 |
+
chain = chain.then(function() {
|
| 2123 |
+
return this.updateAllBranchesForHgAndPlay(branchList);
|
| 2124 |
+
}.bind(this));
|
| 2125 |
+
|
| 2126 |
+
chain = chain.then(function() {
|
| 2127 |
+
// now that we have moved branches, lets prune
|
| 2128 |
+
return this.pruneTreeAndPlay();
|
| 2129 |
+
}.bind(this));
|
| 2130 |
+
|
| 2131 |
+
this.animationQueue.thenFinish(chain, deferred);
|
| 2132 |
+
};
|
| 2133 |
+
|
| 2134 |
+
GitEngine.prototype.rebase = function(targetSource, currentLocation, options) {
|
| 2135 |
+
// first some conditions
|
| 2136 |
+
if (this.isUpstreamOf(targetSource, currentLocation)) {
|
| 2137 |
+
this.command.setResult(intl.str('git-result-uptodate'));
|
| 2138 |
+
|
| 2139 |
+
// git for some reason always checks out the branch you are rebasing,
|
| 2140 |
+
// no matter the result of the rebase
|
| 2141 |
+
this.checkout(currentLocation);
|
| 2142 |
+
|
| 2143 |
+
// returning instead of throwing makes a tree refresh
|
| 2144 |
+
return;
|
| 2145 |
+
}
|
| 2146 |
+
|
| 2147 |
+
if (this.isUpstreamOf(currentLocation, targetSource)) {
|
| 2148 |
+
// just set the target of this current location to the source
|
| 2149 |
+
this.setTargetLocation(currentLocation, this.getCommitFromRef(targetSource));
|
| 2150 |
+
// we need the refresh tree animation to happen, so set the result directly
|
| 2151 |
+
// instead of throwing
|
| 2152 |
+
this.command.setResult(intl.str('git-result-fastforward'));
|
| 2153 |
+
|
| 2154 |
+
this.checkout(currentLocation);
|
| 2155 |
+
return;
|
| 2156 |
+
}
|
| 2157 |
+
|
| 2158 |
+
// now the part of actually rebasing.
|
| 2159 |
+
// We need to get the downstream set of targetSource first.
|
| 2160 |
+
// then we BFS from currentLocation, using the downstream set as our stopping point.
|
| 2161 |
+
// we need to BFS because we need to include all commits below
|
| 2162 |
+
// pop these commits on top of targetSource and modify their ids with quotes
|
| 2163 |
+
var stopSet = Graph.getUpstreamSet(this, targetSource);
|
| 2164 |
+
var toRebaseRough = this.getUpstreamDiffFromSet(stopSet, currentLocation);
|
| 2165 |
+
return this.rebaseFinish(toRebaseRough, stopSet, targetSource, currentLocation, options);
|
| 2166 |
+
};
|
| 2167 |
+
|
| 2168 |
+
GitEngine.prototype.rebaseOnto = function(targetSource, oldSource, unit, options) {
|
| 2169 |
+
if (this.isUpstreamOf(unit, targetSource)) {
|
| 2170 |
+
this.setTargetLocation(unit, this.getCommitFromRef(targetSource));
|
| 2171 |
+
this.command.setResult(intl.str('git-result-fastforward'));
|
| 2172 |
+
|
| 2173 |
+
this.checkout(unit);
|
| 2174 |
+
return;
|
| 2175 |
+
}
|
| 2176 |
+
|
| 2177 |
+
var stopSet = Graph.getUpstreamSet(this, targetSource);
|
| 2178 |
+
var oldBranchSet = Graph.getUpstreamSet(this, oldSource);
|
| 2179 |
+
var toRebaseRough = this.getUpstreamDiffFromSet(oldBranchSet, unit);
|
| 2180 |
+
return this.rebaseFinish(toRebaseRough, stopSet, targetSource, unit, options);
|
| 2181 |
+
};
|
| 2182 |
+
|
| 2183 |
+
GitEngine.prototype.getUpstreamDiffSetFromSet = function(stopSet, location) {
|
| 2184 |
+
var set = {};
|
| 2185 |
+
this.getUpstreamDiffFromSet(stopSet, location).forEach(function (commit) {
|
| 2186 |
+
set[commit.get('id')] = true;
|
| 2187 |
+
});
|
| 2188 |
+
return set;
|
| 2189 |
+
};
|
| 2190 |
+
|
| 2191 |
+
GitEngine.prototype.getUpstreamDiffFromSet = function(stopSet, location) {
|
| 2192 |
+
var result = Graph.bfsFromLocationWithSet(this, location, stopSet);
|
| 2193 |
+
result.sort(this.dateSortFunc);
|
| 2194 |
+
return result;
|
| 2195 |
+
};
|
| 2196 |
+
|
| 2197 |
+
GitEngine.prototype.getInteractiveRebaseCommits = function(targetSource, currentLocation) {
|
| 2198 |
+
var stopSet = Graph.getUpstreamSet(this, targetSource);
|
| 2199 |
+
var toRebaseRough = [];
|
| 2200 |
+
|
| 2201 |
+
// standard BFS
|
| 2202 |
+
var pQueue = [this.getCommitFromRef(currentLocation)];
|
| 2203 |
+
|
| 2204 |
+
while (pQueue.length) {
|
| 2205 |
+
var popped = pQueue.pop();
|
| 2206 |
+
|
| 2207 |
+
if (stopSet[popped.get('id')]) {
|
| 2208 |
+
continue;
|
| 2209 |
+
}
|
| 2210 |
+
|
| 2211 |
+
toRebaseRough.push(popped);
|
| 2212 |
+
pQueue = pQueue.concat(popped.get('parents'));
|
| 2213 |
+
pQueue.sort(this.dateSortFunc);
|
| 2214 |
+
}
|
| 2215 |
+
|
| 2216 |
+
// throw out merge's real fast and see if we have anything to do
|
| 2217 |
+
var toRebase = [];
|
| 2218 |
+
toRebaseRough.forEach(function (commit) {
|
| 2219 |
+
if (commit.get('parents').length == 1) {
|
| 2220 |
+
toRebase.push(commit);
|
| 2221 |
+
}
|
| 2222 |
+
});
|
| 2223 |
+
|
| 2224 |
+
if (!toRebase.length) {
|
| 2225 |
+
throw new GitError({
|
| 2226 |
+
msg: intl.str('git-error-rebase-none')
|
| 2227 |
+
});
|
| 2228 |
+
}
|
| 2229 |
+
|
| 2230 |
+
return toRebase;
|
| 2231 |
+
};
|
| 2232 |
+
|
| 2233 |
+
GitEngine.prototype.rebaseInteractiveTest = function(targetSource, currentLocation, options) {
|
| 2234 |
+
options = options || {};
|
| 2235 |
+
|
| 2236 |
+
// Get the list of commits that would be displayed to the user
|
| 2237 |
+
var toRebase = this.getInteractiveRebaseCommits(targetSource, currentLocation);
|
| 2238 |
+
|
| 2239 |
+
var rebaseMap = {};
|
| 2240 |
+
toRebase.forEach(function (commit) {
|
| 2241 |
+
var id = commit.get('id');
|
| 2242 |
+
rebaseMap[id] = commit;
|
| 2243 |
+
});
|
| 2244 |
+
|
| 2245 |
+
var rebaseOrder;
|
| 2246 |
+
if (options['interactiveTest'].length === 0) {
|
| 2247 |
+
// If no commits were explicitly specified for the rebase, act like the user didn't change anything
|
| 2248 |
+
// in the rebase dialog and hit confirm
|
| 2249 |
+
rebaseOrder = toRebase;
|
| 2250 |
+
} else {
|
| 2251 |
+
// Get the list and order of commits specified
|
| 2252 |
+
var idsToRebase = options['interactiveTest'][0].split(',');
|
| 2253 |
+
|
| 2254 |
+
// Verify each chosen commit exists in the list of commits given to the user
|
| 2255 |
+
var extraCommits = [];
|
| 2256 |
+
rebaseOrder = [];
|
| 2257 |
+
idsToRebase.forEach(function (id) {
|
| 2258 |
+
if (id in rebaseMap) {
|
| 2259 |
+
rebaseOrder.push(rebaseMap[id]);
|
| 2260 |
+
} else {
|
| 2261 |
+
extraCommits.push(id);
|
| 2262 |
+
}
|
| 2263 |
+
});
|
| 2264 |
+
|
| 2265 |
+
if (extraCommits.length > 0) {
|
| 2266 |
+
throw new GitError({
|
| 2267 |
+
msg: intl.todo('Hey those commits don\'t exist in the set!')
|
| 2268 |
+
});
|
| 2269 |
+
}
|
| 2270 |
+
}
|
| 2271 |
+
|
| 2272 |
+
this.rebaseFinish(rebaseOrder, {}, targetSource, currentLocation);
|
| 2273 |
+
};
|
| 2274 |
+
|
| 2275 |
+
GitEngine.prototype.rebaseInteractive = function(targetSource, currentLocation, options) {
|
| 2276 |
+
options = options || {};
|
| 2277 |
+
|
| 2278 |
+
// there are a reduced set of checks now, so we can't exactly use parts of the rebase function
|
| 2279 |
+
// but it will look similar.
|
| 2280 |
+
var toRebase = this.getInteractiveRebaseCommits(targetSource, currentLocation);
|
| 2281 |
+
|
| 2282 |
+
// now do stuff :D since all our validation checks have passed, we are going to defer animation
|
| 2283 |
+
// and actually launch the dialog
|
| 2284 |
+
this.animationQueue.set('defer', true);
|
| 2285 |
+
|
| 2286 |
+
var deferred = Q.defer();
|
| 2287 |
+
deferred.promise
|
| 2288 |
+
.then(function(userSpecifiedRebase) {
|
| 2289 |
+
// first, they might have dropped everything (annoying)
|
| 2290 |
+
if (!userSpecifiedRebase.length) {
|
| 2291 |
+
throw new CommandResult({
|
| 2292 |
+
msg: intl.str('git-result-nothing')
|
| 2293 |
+
});
|
| 2294 |
+
}
|
| 2295 |
+
|
| 2296 |
+
// finish the rebase crap and animate!
|
| 2297 |
+
this.rebaseFinish(userSpecifiedRebase, {}, targetSource, currentLocation);
|
| 2298 |
+
}.bind(this))
|
| 2299 |
+
.fail(function(err) {
|
| 2300 |
+
this.filterError(err);
|
| 2301 |
+
this.command.set('error', err);
|
| 2302 |
+
this.animationQueue.start();
|
| 2303 |
+
}.bind(this))
|
| 2304 |
+
.done();
|
| 2305 |
+
|
| 2306 |
+
// If we have a solution provided, set up the GUI to display it by default
|
| 2307 |
+
var initialCommitOrdering;
|
| 2308 |
+
if (options.initialCommitOrdering && options.initialCommitOrdering.length > 0) {
|
| 2309 |
+
var rebaseMap = {};
|
| 2310 |
+
toRebase.forEach(function (commit) {
|
| 2311 |
+
rebaseMap[commit.get('id')] = true;
|
| 2312 |
+
});
|
| 2313 |
+
|
| 2314 |
+
// Verify each chosen commit exists in the list of commits given to the user
|
| 2315 |
+
initialCommitOrdering = [];
|
| 2316 |
+
options.initialCommitOrdering[0].split(',').forEach(function (id) {
|
| 2317 |
+
if (!rebaseMap[id]) {
|
| 2318 |
+
throw new GitError({
|
| 2319 |
+
msg: intl.todo('Hey those commits don\'t exist in the set!')
|
| 2320 |
+
});
|
| 2321 |
+
}
|
| 2322 |
+
initialCommitOrdering.push(id);
|
| 2323 |
+
});
|
| 2324 |
+
}
|
| 2325 |
+
|
| 2326 |
+
var InteractiveRebaseView = require('../views/rebaseView').InteractiveRebaseView;
|
| 2327 |
+
// interactive rebase view will reject or resolve our promise
|
| 2328 |
+
new InteractiveRebaseView({
|
| 2329 |
+
deferred: deferred,
|
| 2330 |
+
toRebase: toRebase,
|
| 2331 |
+
initialCommitOrdering: initialCommitOrdering,
|
| 2332 |
+
aboveAll: options.aboveAll
|
| 2333 |
+
});
|
| 2334 |
+
};
|
| 2335 |
+
|
| 2336 |
+
GitEngine.prototype.filterRebaseCommits = function(
|
| 2337 |
+
toRebaseRough,
|
| 2338 |
+
stopSet,
|
| 2339 |
+
options
|
| 2340 |
+
) {
|
| 2341 |
+
var changesAlreadyMade = {};
|
| 2342 |
+
Object.keys(stopSet).forEach(function(key) {
|
| 2343 |
+
changesAlreadyMade[this.scrapeBaseID(key)] = true;
|
| 2344 |
+
}, this);
|
| 2345 |
+
var uniqueIDs = {};
|
| 2346 |
+
|
| 2347 |
+
// resolve the commits we will rebase
|
| 2348 |
+
return toRebaseRough.filter(function(commit) {
|
| 2349 |
+
// no merge commits, unless we preserve
|
| 2350 |
+
if (commit.get('parents').length !== 1 && !options.preserveMerges) {
|
| 2351 |
+
return false;
|
| 2352 |
+
}
|
| 2353 |
+
|
| 2354 |
+
// we ALSO need to throw out commits that will do the same changes. like
|
| 2355 |
+
// if the upstream set has a commit C4 and we have C4', we don't rebase the C4' again.
|
| 2356 |
+
var baseID = this.scrapeBaseID(commit.get('id'));
|
| 2357 |
+
if (changesAlreadyMade[baseID]) {
|
| 2358 |
+
return false;
|
| 2359 |
+
}
|
| 2360 |
+
|
| 2361 |
+
// make unique
|
| 2362 |
+
if (uniqueIDs[commit.get('id')]) {
|
| 2363 |
+
return false;
|
| 2364 |
+
}
|
| 2365 |
+
|
| 2366 |
+
uniqueIDs[commit.get('id')] = true;
|
| 2367 |
+
return true;
|
| 2368 |
+
}, this);
|
| 2369 |
+
};
|
| 2370 |
+
|
| 2371 |
+
GitEngine.prototype.getRebasePreserveMergesParents = function(oldCommit) {
|
| 2372 |
+
var oldParents = oldCommit.get('parents');
|
| 2373 |
+
return oldParents.map(function(parent) {
|
| 2374 |
+
var oldID = parent.get('id');
|
| 2375 |
+
var newID = this.getMostRecentBumpedID(oldID);
|
| 2376 |
+
return this.refs[newID];
|
| 2377 |
+
}, this);
|
| 2378 |
+
};
|
| 2379 |
+
|
| 2380 |
+
GitEngine.prototype.rebaseFinish = function(
|
| 2381 |
+
toRebaseRough,
|
| 2382 |
+
stopSet,
|
| 2383 |
+
targetSource,
|
| 2384 |
+
currentLocation,
|
| 2385 |
+
options
|
| 2386 |
+
) {
|
| 2387 |
+
options = options || {};
|
| 2388 |
+
// now we have the all the commits between currentLocation and the set of target to rebase.
|
| 2389 |
+
var destinationBranch = this.resolveID(targetSource);
|
| 2390 |
+
var deferred = options.deferred || Q.defer();
|
| 2391 |
+
var chain = options.chain || deferred.promise;
|
| 2392 |
+
|
| 2393 |
+
var toRebase = this.filterRebaseCommits(toRebaseRough, stopSet, options);
|
| 2394 |
+
if (!toRebase.length) {
|
| 2395 |
+
throw new GitError({
|
| 2396 |
+
msg: intl.str('git-error-rebase-none')
|
| 2397 |
+
});
|
| 2398 |
+
}
|
| 2399 |
+
|
| 2400 |
+
chain = this.animationFactory.highlightEachWithPromise(
|
| 2401 |
+
chain,
|
| 2402 |
+
toRebase,
|
| 2403 |
+
destinationBranch
|
| 2404 |
+
);
|
| 2405 |
+
|
| 2406 |
+
// now pop all of these commits onto targetLocation
|
| 2407 |
+
var base = this.getCommitFromRef(targetSource);
|
| 2408 |
+
var hasStartedChain = false;
|
| 2409 |
+
// each step makes a new commit
|
| 2410 |
+
var chainStep = function(oldCommit) {
|
| 2411 |
+
var newId = this.rebaseAltID(oldCommit.get('id'));
|
| 2412 |
+
var parents;
|
| 2413 |
+
if (!options.preserveMerges || !hasStartedChain) {
|
| 2414 |
+
// easy logic since we just have a straight line
|
| 2415 |
+
parents = [base];
|
| 2416 |
+
} else { // preserving merges
|
| 2417 |
+
// we always define the parent for the first commit to plop,
|
| 2418 |
+
// otherwise search for most recent parents
|
| 2419 |
+
parents = (hasStartedChain) ?
|
| 2420 |
+
this.getRebasePreserveMergesParents(oldCommit) :
|
| 2421 |
+
[base];
|
| 2422 |
+
}
|
| 2423 |
+
|
| 2424 |
+
var newCommit = this.makeCommit(parents, newId);
|
| 2425 |
+
base = newCommit;
|
| 2426 |
+
hasStartedChain = true;
|
| 2427 |
+
|
| 2428 |
+
return this.animationFactory.playCommitBirthPromiseAnimation(
|
| 2429 |
+
newCommit,
|
| 2430 |
+
this.gitVisuals
|
| 2431 |
+
);
|
| 2432 |
+
}.bind(this);
|
| 2433 |
+
|
| 2434 |
+
// set up the promise chain
|
| 2435 |
+
toRebase.forEach(function (commit) {
|
| 2436 |
+
chain = chain.then(function() {
|
| 2437 |
+
return chainStep(commit);
|
| 2438 |
+
});
|
| 2439 |
+
}, this);
|
| 2440 |
+
|
| 2441 |
+
chain = chain.then(function() {
|
| 2442 |
+
if (this.resolveID(currentLocation).get('type') == 'commit') {
|
| 2443 |
+
// we referenced a commit like git rebase C2 C1, so we have
|
| 2444 |
+
// to manually check out C1'
|
| 2445 |
+
this.checkout(base);
|
| 2446 |
+
} else {
|
| 2447 |
+
// now we just need to update the rebased branch is
|
| 2448 |
+
this.setTargetLocation(currentLocation, base);
|
| 2449 |
+
this.checkout(currentLocation);
|
| 2450 |
+
}
|
| 2451 |
+
return this.animationFactory.playRefreshAnimation(this.gitVisuals);
|
| 2452 |
+
}.bind(this));
|
| 2453 |
+
|
| 2454 |
+
if (!options.dontResolvePromise) {
|
| 2455 |
+
this.animationQueue.thenFinish(chain, deferred);
|
| 2456 |
+
}
|
| 2457 |
+
return chain;
|
| 2458 |
+
};
|
| 2459 |
+
|
| 2460 |
+
GitEngine.prototype.mergeCheck = function(targetSource, currentLocation) {
|
| 2461 |
+
var sameCommit = this.getCommitFromRef(targetSource) ===
|
| 2462 |
+
this.getCommitFromRef(currentLocation);
|
| 2463 |
+
return this.isUpstreamOf(targetSource, currentLocation) || sameCommit;
|
| 2464 |
+
};
|
| 2465 |
+
|
| 2466 |
+
GitEngine.prototype.merge = function(targetSource, options) {
|
| 2467 |
+
options = options || {};
|
| 2468 |
+
var currentLocation = 'HEAD';
|
| 2469 |
+
|
| 2470 |
+
// first some conditions
|
| 2471 |
+
if (this.mergeCheck(targetSource, currentLocation)) {
|
| 2472 |
+
throw new CommandResult({
|
| 2473 |
+
msg: intl.str('git-result-uptodate')
|
| 2474 |
+
});
|
| 2475 |
+
}
|
| 2476 |
+
|
| 2477 |
+
if (this.isUpstreamOf(currentLocation, targetSource) && !options.noFF) {
|
| 2478 |
+
// just set the target of this current location to the source
|
| 2479 |
+
this.setTargetLocation(currentLocation, this.getCommitFromRef(targetSource));
|
| 2480 |
+
// get fresh animation to happen
|
| 2481 |
+
this.command.setResult(intl.str('git-result-fastforward'));
|
| 2482 |
+
return;
|
| 2483 |
+
}
|
| 2484 |
+
|
| 2485 |
+
// now the part of making a merge commit
|
| 2486 |
+
var parent1 = this.getCommitFromRef(currentLocation);
|
| 2487 |
+
var parent2 = this.getCommitFromRef(targetSource);
|
| 2488 |
+
|
| 2489 |
+
// we need a fancy commit message
|
| 2490 |
+
var msg = intl.str(
|
| 2491 |
+
'git-merge-msg',
|
| 2492 |
+
{
|
| 2493 |
+
target: this.resolveName(targetSource),
|
| 2494 |
+
current: this.resolveName(currentLocation)
|
| 2495 |
+
}
|
| 2496 |
+
);
|
| 2497 |
+
// since we specify parent 1 as the first parent, it is the "main" parent
|
| 2498 |
+
// and the node will be displayed below that branch / commit / whatever
|
| 2499 |
+
var mergeCommit = this.makeCommit(
|
| 2500 |
+
[parent1, parent2],
|
| 2501 |
+
null,
|
| 2502 |
+
{
|
| 2503 |
+
commitMessage: msg
|
| 2504 |
+
}
|
| 2505 |
+
);
|
| 2506 |
+
|
| 2507 |
+
this.setTargetLocation(currentLocation, mergeCommit);
|
| 2508 |
+
return mergeCommit;
|
| 2509 |
+
};
|
| 2510 |
+
|
| 2511 |
+
GitEngine.prototype.checkout = function(idOrTarget) {
|
| 2512 |
+
var target = this.resolveID(idOrTarget);
|
| 2513 |
+
if (target.get('id') === 'HEAD') {
|
| 2514 |
+
// git checkout HEAD is a
|
| 2515 |
+
// meaningless command but i used to do this back in the day
|
| 2516 |
+
return;
|
| 2517 |
+
}
|
| 2518 |
+
|
| 2519 |
+
var type = target.get('type');
|
| 2520 |
+
// check if this is an origin branch, and if so go to the commit referenced
|
| 2521 |
+
if (type === 'branch' && target.getIsRemote()) {
|
| 2522 |
+
target = this.getCommitFromRef(target.get('id'));
|
| 2523 |
+
}
|
| 2524 |
+
|
| 2525 |
+
if (type !== 'branch' && type !== 'tag' && type !== 'commit') {
|
| 2526 |
+
throw new GitError({
|
| 2527 |
+
msg: intl.str('git-error-options')
|
| 2528 |
+
});
|
| 2529 |
+
}
|
| 2530 |
+
if (type === 'tag') {
|
| 2531 |
+
target = target.get('target');
|
| 2532 |
+
}
|
| 2533 |
+
|
| 2534 |
+
this.HEAD.set('target', target);
|
| 2535 |
+
};
|
| 2536 |
+
|
| 2537 |
+
GitEngine.prototype.forceBranch = function(branchName, where) {
|
| 2538 |
+
branchName = this.crappyUnescape(branchName);
|
| 2539 |
+
// if branchname doesn't exist...
|
| 2540 |
+
if (!this.doesRefExist(branchName)) {
|
| 2541 |
+
this.branch(branchName, where);
|
| 2542 |
+
}
|
| 2543 |
+
|
| 2544 |
+
var branch = this.resolveID(branchName);
|
| 2545 |
+
|
| 2546 |
+
if (branch.get('type') !== 'branch') {
|
| 2547 |
+
throw new GitError({
|
| 2548 |
+
msg: intl.str('git-error-options')
|
| 2549 |
+
});
|
| 2550 |
+
}
|
| 2551 |
+
if (branch.getIsRemote()) {
|
| 2552 |
+
throw new GitError({
|
| 2553 |
+
msg: intl.str('git-error-remote-branch')
|
| 2554 |
+
});
|
| 2555 |
+
}
|
| 2556 |
+
|
| 2557 |
+
var whereCommit = this.getCommitFromRef(where);
|
| 2558 |
+
|
| 2559 |
+
this.setTargetLocation(branch, whereCommit);
|
| 2560 |
+
};
|
| 2561 |
+
|
| 2562 |
+
GitEngine.prototype.branch = function(name, ref) {
|
| 2563 |
+
var target = this.getCommitFromRef(ref);
|
| 2564 |
+
var newBranch = this.validateAndMakeBranch(name, target);
|
| 2565 |
+
|
| 2566 |
+
ref = this.resolveID(ref);
|
| 2567 |
+
if (this.isRemoteBranchRef(ref)) {
|
| 2568 |
+
this.setLocalToTrackRemote(newBranch, ref);
|
| 2569 |
+
}
|
| 2570 |
+
};
|
| 2571 |
+
|
| 2572 |
+
GitEngine.prototype.isRemoteBranchRef = function(ref) {
|
| 2573 |
+
var resolved = this.resolveID(ref);
|
| 2574 |
+
if (resolved.get('type') !== 'branch') {
|
| 2575 |
+
return false;
|
| 2576 |
+
}
|
| 2577 |
+
return resolved.getIsRemote();
|
| 2578 |
+
};
|
| 2579 |
+
|
| 2580 |
+
GitEngine.prototype.tag = function(name, ref) {
|
| 2581 |
+
var target = this.getCommitFromRef(ref);
|
| 2582 |
+
this.validateAndMakeTag(name, target);
|
| 2583 |
+
};
|
| 2584 |
+
|
| 2585 |
+
GitEngine.prototype.describe = function(ref) {
|
| 2586 |
+
var startCommit = this.getCommitFromRef(ref);
|
| 2587 |
+
// ok we need to BFS from start upwards until we hit a tag. but
|
| 2588 |
+
// first we need to get a reverse mapping from tag to commit
|
| 2589 |
+
var tagMap = {};
|
| 2590 |
+
this.tagCollection.toJSON().forEach(function (tag) {
|
| 2591 |
+
tagMap[tag.target.get('id')] = tag.id;
|
| 2592 |
+
});
|
| 2593 |
+
|
| 2594 |
+
var pQueue = [startCommit];
|
| 2595 |
+
var foundTag;
|
| 2596 |
+
var numAway = [];
|
| 2597 |
+
while (pQueue.length) {
|
| 2598 |
+
var popped = pQueue.pop();
|
| 2599 |
+
var thisID = popped.get('id');
|
| 2600 |
+
if (tagMap[thisID]) {
|
| 2601 |
+
foundTag = tagMap[thisID];
|
| 2602 |
+
break;
|
| 2603 |
+
}
|
| 2604 |
+
// ok keep going
|
| 2605 |
+
numAway.push(popped.get('id'));
|
| 2606 |
+
|
| 2607 |
+
var parents = popped.get('parents');
|
| 2608 |
+
if (parents && parents.length) {
|
| 2609 |
+
pQueue = pQueue.concat(parents);
|
| 2610 |
+
pQueue.sort(this.dateSortFunc);
|
| 2611 |
+
}
|
| 2612 |
+
}
|
| 2613 |
+
|
| 2614 |
+
if (!foundTag) {
|
| 2615 |
+
throw new GitError({
|
| 2616 |
+
msg: intl.todo('Fatal: no tags found upstream')
|
| 2617 |
+
});
|
| 2618 |
+
}
|
| 2619 |
+
|
| 2620 |
+
if (numAway.length === 0) {
|
| 2621 |
+
throw new CommandResult({
|
| 2622 |
+
msg: foundTag
|
| 2623 |
+
});
|
| 2624 |
+
}
|
| 2625 |
+
|
| 2626 |
+
// then join
|
| 2627 |
+
throw new CommandResult({
|
| 2628 |
+
msg: foundTag + '_' + numAway.length + '_g' + startCommit.get('id')
|
| 2629 |
+
});
|
| 2630 |
+
};
|
| 2631 |
+
|
| 2632 |
+
GitEngine.prototype.validateAndDeleteBranch = function(name) {
|
| 2633 |
+
// trying to delete, lets check our refs
|
| 2634 |
+
var target = this.resolveID(name);
|
| 2635 |
+
|
| 2636 |
+
if (target.get('type') !== 'branch' ||
|
| 2637 |
+
target.get('id') == 'main' ||
|
| 2638 |
+
this.HEAD.get('target') === target) {
|
| 2639 |
+
throw new GitError({
|
| 2640 |
+
msg: intl.str('git-error-branch')
|
| 2641 |
+
});
|
| 2642 |
+
}
|
| 2643 |
+
|
| 2644 |
+
// now we know it's a branch
|
| 2645 |
+
var branch = target;
|
| 2646 |
+
// if its remote
|
| 2647 |
+
if (target.getIsRemote()) {
|
| 2648 |
+
throw new GitError({
|
| 2649 |
+
msg: intl.str('git-error-remote-branch')
|
| 2650 |
+
});
|
| 2651 |
+
}
|
| 2652 |
+
this.deleteBranch(branch);
|
| 2653 |
+
};
|
| 2654 |
+
|
| 2655 |
+
GitEngine.prototype.deleteBranch = function(branch) {
|
| 2656 |
+
this.branchCollection.remove(branch);
|
| 2657 |
+
this.refs[branch.get('id')] = undefined;
|
| 2658 |
+
delete this.refs[branch.get('id')];
|
| 2659 |
+
// also in some cases external engines call our delete, so
|
| 2660 |
+
// verify integrity of HEAD here
|
| 2661 |
+
if (this.HEAD.get('target') === branch) {
|
| 2662 |
+
this.HEAD.set('target', this.refs['main']);
|
| 2663 |
+
}
|
| 2664 |
+
|
| 2665 |
+
if (branch.get('visBranch')) {
|
| 2666 |
+
branch.get('visBranch').remove();
|
| 2667 |
+
}
|
| 2668 |
+
};
|
| 2669 |
+
|
| 2670 |
+
GitEngine.prototype.crappyUnescape = function(str) {
|
| 2671 |
+
return str.replace(/'/g, "'").replace(///g, "/");
|
| 2672 |
+
};
|
| 2673 |
+
|
| 2674 |
+
GitEngine.prototype.filterError = function(err) {
|
| 2675 |
+
if (!(err instanceof GitError ||
|
| 2676 |
+
err instanceof CommandResult)) {
|
| 2677 |
+
throw err;
|
| 2678 |
+
}
|
| 2679 |
+
};
|
| 2680 |
+
|
| 2681 |
+
// called on a origin repo from a local -- simply refresh immediately with
|
| 2682 |
+
// an animation
|
| 2683 |
+
GitEngine.prototype.externalRefresh = function() {
|
| 2684 |
+
this.animationQueue = new AnimationQueue({
|
| 2685 |
+
callback: function() {}
|
| 2686 |
+
});
|
| 2687 |
+
this.animationFactory.refreshTree(this.animationQueue, this.gitVisuals);
|
| 2688 |
+
this.animationQueue.start();
|
| 2689 |
+
};
|
| 2690 |
+
|
| 2691 |
+
GitEngine.prototype.dispatch = function(command, deferred) {
|
| 2692 |
+
this.command = command;
|
| 2693 |
+
var vcs = command.get('vcs');
|
| 2694 |
+
var executeCommand = function() {
|
| 2695 |
+
this.dispatchProcess(command, deferred);
|
| 2696 |
+
}.bind(this);
|
| 2697 |
+
// handle mode change will either execute sync or
|
| 2698 |
+
// animate during tree pruning / etc
|
| 2699 |
+
this.handleModeChange(vcs, executeCommand);
|
| 2700 |
+
};
|
| 2701 |
+
|
| 2702 |
+
GitEngine.prototype.dispatchProcess = function(command, deferred) {
|
| 2703 |
+
// set up the animation queue
|
| 2704 |
+
var whenDone = function() {
|
| 2705 |
+
command.finishWith(deferred);
|
| 2706 |
+
}.bind(this);
|
| 2707 |
+
this.animationQueue = new AnimationQueue({
|
| 2708 |
+
callback: whenDone
|
| 2709 |
+
});
|
| 2710 |
+
|
| 2711 |
+
var vcs = command.get('vcs');
|
| 2712 |
+
var methodName = command.get('method').replace(/-/g, '');
|
| 2713 |
+
|
| 2714 |
+
try {
|
| 2715 |
+
Commands.commands.execute(vcs, methodName, this, this.command);
|
| 2716 |
+
} catch (err) {
|
| 2717 |
+
this.filterError(err);
|
| 2718 |
+
// short circuit animation by just setting error and returning
|
| 2719 |
+
command.set('error', err);
|
| 2720 |
+
deferred.resolve();
|
| 2721 |
+
return;
|
| 2722 |
+
}
|
| 2723 |
+
|
| 2724 |
+
var willStartAuto = this.animationQueue.get('defer') ||
|
| 2725 |
+
this.animationQueue.get('promiseBased');
|
| 2726 |
+
|
| 2727 |
+
// only add the refresh if we didn't do manual animations
|
| 2728 |
+
if (!this.animationQueue.get('animations').length && !willStartAuto) {
|
| 2729 |
+
this.animationFactory.refreshTree(this.animationQueue, this.gitVisuals);
|
| 2730 |
+
}
|
| 2731 |
+
|
| 2732 |
+
// animation queue will call the callback when its done
|
| 2733 |
+
if (!willStartAuto) {
|
| 2734 |
+
this.animationQueue.start();
|
| 2735 |
+
}
|
| 2736 |
+
};
|
| 2737 |
+
|
| 2738 |
+
GitEngine.prototype.show = function(ref) {
|
| 2739 |
+
var commit = this.getCommitFromRef(ref);
|
| 2740 |
+
|
| 2741 |
+
throw new CommandResult({
|
| 2742 |
+
msg: commit.getShowEntry()
|
| 2743 |
+
});
|
| 2744 |
+
};
|
| 2745 |
+
|
| 2746 |
+
GitEngine.prototype.status = function() {
|
| 2747 |
+
// UGLY todo
|
| 2748 |
+
var lines = [];
|
| 2749 |
+
if (this.getDetachedHead()) {
|
| 2750 |
+
lines.push(intl.str('git-status-detached'));
|
| 2751 |
+
} else {
|
| 2752 |
+
var branchName = this.resolveNameNoPrefix('HEAD');
|
| 2753 |
+
lines.push(intl.str('git-status-onbranch', {branch: branchName}));
|
| 2754 |
+
}
|
| 2755 |
+
lines.push('Changes to be committed:');
|
| 2756 |
+
lines.push('');
|
| 2757 |
+
lines.push(TAB + 'modified: cal/OskiCostume.stl');
|
| 2758 |
+
lines.push('');
|
| 2759 |
+
lines.push(intl.str('git-status-readytocommit'));
|
| 2760 |
+
|
| 2761 |
+
var msg = '';
|
| 2762 |
+
lines.forEach(function (line) {
|
| 2763 |
+
msg += '# ' + line + '\n';
|
| 2764 |
+
});
|
| 2765 |
+
|
| 2766 |
+
throw new CommandResult({
|
| 2767 |
+
msg: msg
|
| 2768 |
+
});
|
| 2769 |
+
};
|
| 2770 |
+
|
| 2771 |
+
GitEngine.prototype.logWithout = function(ref, omitBranch) {
|
| 2772 |
+
// slice off the ^branch
|
| 2773 |
+
omitBranch = omitBranch.slice(1);
|
| 2774 |
+
this.log(ref, Graph.getUpstreamSet(this, omitBranch));
|
| 2775 |
+
};
|
| 2776 |
+
|
| 2777 |
+
GitEngine.prototype.revlist = function(refs) {
|
| 2778 |
+
var range = new RevisionRange(this, refs);
|
| 2779 |
+
|
| 2780 |
+
// now go through and collect ids
|
| 2781 |
+
var bigLogStr = range.formatRevisions(function(c) {
|
| 2782 |
+
return c.id + '\n';
|
| 2783 |
+
});
|
| 2784 |
+
|
| 2785 |
+
throw new CommandResult({
|
| 2786 |
+
msg: bigLogStr
|
| 2787 |
+
});
|
| 2788 |
+
};
|
| 2789 |
+
|
| 2790 |
+
GitEngine.prototype.log = function(refs) {
|
| 2791 |
+
var range = new RevisionRange(this, refs);
|
| 2792 |
+
|
| 2793 |
+
// now go through and collect logs
|
| 2794 |
+
var bigLogStr = range.formatRevisions(function(c) {
|
| 2795 |
+
return c.getLogEntry();
|
| 2796 |
+
});
|
| 2797 |
+
|
| 2798 |
+
throw new CommandResult({
|
| 2799 |
+
msg: bigLogStr
|
| 2800 |
+
});
|
| 2801 |
+
};
|
| 2802 |
+
|
| 2803 |
+
GitEngine.prototype.getCommonAncestor = function(ancestor, cousin, dontThrow) {
|
| 2804 |
+
if (this.isUpstreamOf(cousin, ancestor) && !dontThrow) {
|
| 2805 |
+
throw new Error('Don\'t use common ancestor if we are upstream!');
|
| 2806 |
+
}
|
| 2807 |
+
|
| 2808 |
+
var upstreamSet = Graph.getUpstreamSet(this, ancestor);
|
| 2809 |
+
// now BFS off of cousin until you find something
|
| 2810 |
+
|
| 2811 |
+
var queue = [this.getCommitFromRef(cousin)];
|
| 2812 |
+
while (queue.length) {
|
| 2813 |
+
var here = queue.pop();
|
| 2814 |
+
if (upstreamSet[here.get('id')]) {
|
| 2815 |
+
return here;
|
| 2816 |
+
}
|
| 2817 |
+
queue = queue.concat(here.get('parents'));
|
| 2818 |
+
}
|
| 2819 |
+
throw new Error('something has gone very wrong... two nodes aren\'t connected!');
|
| 2820 |
+
};
|
| 2821 |
+
|
| 2822 |
+
GitEngine.prototype.isUpstreamOf = function(child, ancestor) {
|
| 2823 |
+
child = this.getCommitFromRef(child);
|
| 2824 |
+
|
| 2825 |
+
// basically just do a completely BFS search on ancestor to the root, then
|
| 2826 |
+
// check for membership of child in that set of explored nodes
|
| 2827 |
+
var upstream = Graph.getUpstreamSet(this, ancestor);
|
| 2828 |
+
return upstream[child.get('id')] !== undefined;
|
| 2829 |
+
};
|
| 2830 |
+
|
| 2831 |
+
GitEngine.prototype.getDownstreamSet = function(ancestor) {
|
| 2832 |
+
var commit = this.getCommitFromRef(ancestor);
|
| 2833 |
+
|
| 2834 |
+
var ancestorID = commit.get('id');
|
| 2835 |
+
var queue = [commit];
|
| 2836 |
+
|
| 2837 |
+
var exploredSet = {};
|
| 2838 |
+
exploredSet[ancestorID] = true;
|
| 2839 |
+
|
| 2840 |
+
var addToExplored = function(child) {
|
| 2841 |
+
exploredSet[child.get('id')] = true;
|
| 2842 |
+
queue.push(child);
|
| 2843 |
+
};
|
| 2844 |
+
|
| 2845 |
+
while (queue.length) {
|
| 2846 |
+
var here = queue.pop();
|
| 2847 |
+
var children = here.get('children');
|
| 2848 |
+
|
| 2849 |
+
children.forEach(addToExplored);
|
| 2850 |
+
}
|
| 2851 |
+
return exploredSet;
|
| 2852 |
+
};
|
| 2853 |
+
|
| 2854 |
+
var Ref = Backbone.Model.extend({
|
| 2855 |
+
initialize: function() {
|
| 2856 |
+
if (!this.get('target')) {
|
| 2857 |
+
throw new Error('must be initialized with target');
|
| 2858 |
+
}
|
| 2859 |
+
if (!this.get('id')) {
|
| 2860 |
+
throw new Error('must be given an id');
|
| 2861 |
+
}
|
| 2862 |
+
this.set('type', 'general ref');
|
| 2863 |
+
|
| 2864 |
+
if (this.get('id') == 'HEAD') {
|
| 2865 |
+
this.set('lastLastTarget', null);
|
| 2866 |
+
this.set('lastTarget', this.get('target'));
|
| 2867 |
+
// have HEAD remember where it is for checkout -
|
| 2868 |
+
this.on('change:target', this.targetChanged, this);
|
| 2869 |
+
}
|
| 2870 |
+
},
|
| 2871 |
+
|
| 2872 |
+
getIsRemote: function() {
|
| 2873 |
+
return false;
|
| 2874 |
+
},
|
| 2875 |
+
|
| 2876 |
+
getName: function() {
|
| 2877 |
+
return this.get('id');
|
| 2878 |
+
},
|
| 2879 |
+
|
| 2880 |
+
targetChanged: function(model, targetValue, ev) {
|
| 2881 |
+
// push our little 3 stack back. we need to do this because
|
| 2882 |
+
// backbone doesn't give you what the value WAS, only what it was changed
|
| 2883 |
+
// TO
|
| 2884 |
+
this.set('lastLastTarget', this.get('lastTarget'));
|
| 2885 |
+
this.set('lastTarget', targetValue);
|
| 2886 |
+
},
|
| 2887 |
+
|
| 2888 |
+
toString: function() {
|
| 2889 |
+
return 'a ' + this.get('type') + 'pointing to ' + String(this.get('target'));
|
| 2890 |
+
}
|
| 2891 |
+
});
|
| 2892 |
+
|
| 2893 |
+
var Branch = Ref.extend({
|
| 2894 |
+
defaults: {
|
| 2895 |
+
visBranch: null,
|
| 2896 |
+
remoteTrackingBranchID: null,
|
| 2897 |
+
remote: false
|
| 2898 |
+
},
|
| 2899 |
+
|
| 2900 |
+
initialize: function() {
|
| 2901 |
+
Ref.prototype.initialize.call(this);
|
| 2902 |
+
this.set('type', 'branch');
|
| 2903 |
+
},
|
| 2904 |
+
|
| 2905 |
+
/**
|
| 2906 |
+
* Here is the deal -- there are essentially three types of branches
|
| 2907 |
+
* we deal with:
|
| 2908 |
+
* 1) Normal local branches (that may track a remote branch)
|
| 2909 |
+
* 2) Local remote branches (o/main) that track an origin branch
|
| 2910 |
+
* 3) Origin branches (main) that exist in origin
|
| 2911 |
+
*
|
| 2912 |
+
* With that in mind, we change our branch model to support the following
|
| 2913 |
+
*/
|
| 2914 |
+
setRemoteTrackingBranchID: function(id) {
|
| 2915 |
+
this.set('remoteTrackingBranchID', id);
|
| 2916 |
+
},
|
| 2917 |
+
|
| 2918 |
+
getRemoteTrackingBranchID: function() {
|
| 2919 |
+
return this.get('remoteTrackingBranchID');
|
| 2920 |
+
},
|
| 2921 |
+
|
| 2922 |
+
getPrefixedID: function() {
|
| 2923 |
+
if (this.getIsRemote()) {
|
| 2924 |
+
throw new Error('im already remote');
|
| 2925 |
+
}
|
| 2926 |
+
return ORIGIN_PREFIX + this.get('id');
|
| 2927 |
+
},
|
| 2928 |
+
|
| 2929 |
+
getBaseID: function() {
|
| 2930 |
+
if (!this.getIsRemote()) {
|
| 2931 |
+
throw new Error('im not remote so can\'t get base');
|
| 2932 |
+
}
|
| 2933 |
+
return this.get('id').replace(ORIGIN_PREFIX, '');
|
| 2934 |
+
},
|
| 2935 |
+
|
| 2936 |
+
getIsRemote: function() {
|
| 2937 |
+
if (typeof this.get('id') !== 'string') {
|
| 2938 |
+
debugger;
|
| 2939 |
+
}
|
| 2940 |
+
return this.get('id').slice(0, 2) === ORIGIN_PREFIX;
|
| 2941 |
+
}
|
| 2942 |
+
});
|
| 2943 |
+
|
| 2944 |
+
var Commit = Backbone.Model.extend({
|
| 2945 |
+
defaults: {
|
| 2946 |
+
type: 'commit',
|
| 2947 |
+
children: null,
|
| 2948 |
+
parents: null,
|
| 2949 |
+
author: 'Peter Cottle',
|
| 2950 |
+
createTime: null,
|
| 2951 |
+
commitMessage: null,
|
| 2952 |
+
visNode: null,
|
| 2953 |
+
gitVisuals: null
|
| 2954 |
+
},
|
| 2955 |
+
|
| 2956 |
+
constants: {
|
| 2957 |
+
circularFields: ['gitVisuals', 'visNode', 'children']
|
| 2958 |
+
},
|
| 2959 |
+
|
| 2960 |
+
getLogEntry: function() {
|
| 2961 |
+
return [
|
| 2962 |
+
'Author: ' + this.get('author'),
|
| 2963 |
+
'Date: ' + this.get('createTime'),
|
| 2964 |
+
'',
|
| 2965 |
+
this.get('commitMessage'),
|
| 2966 |
+
'',
|
| 2967 |
+
'Commit: ' + this.get('id')
|
| 2968 |
+
].join('<br/>') + '\n';
|
| 2969 |
+
},
|
| 2970 |
+
|
| 2971 |
+
getShowEntry: function() {
|
| 2972 |
+
// same deal as above, show log entry and some fake changes
|
| 2973 |
+
return [
|
| 2974 |
+
this.getLogEntry().replace('\n', ''),
|
| 2975 |
+
'diff --git a/bigGameResults.html b/bigGameResults.html',
|
| 2976 |
+
'--- bigGameResults.html',
|
| 2977 |
+
'+++ bigGameResults.html',
|
| 2978 |
+
'@@ 13,27 @@ Winner, Score',
|
| 2979 |
+
'- Stanfurd, 14-7',
|
| 2980 |
+
'+ Cal, 21-14'
|
| 2981 |
+
].join('<br/>') + '\n';
|
| 2982 |
+
},
|
| 2983 |
+
|
| 2984 |
+
validateAtInit: function() {
|
| 2985 |
+
if (!this.get('id')) {
|
| 2986 |
+
throw new Error('Need ID!!');
|
| 2987 |
+
}
|
| 2988 |
+
|
| 2989 |
+
if (!this.get('createTime')) {
|
| 2990 |
+
this.set('createTime', new Date().toString());
|
| 2991 |
+
}
|
| 2992 |
+
if (!this.get('commitMessage')) {
|
| 2993 |
+
this.set('commitMessage', intl.str('git-dummy-msg'));
|
| 2994 |
+
}
|
| 2995 |
+
|
| 2996 |
+
this.set('children', []);
|
| 2997 |
+
|
| 2998 |
+
// root commits have no parents
|
| 2999 |
+
if (!this.get('rootCommit')) {
|
| 3000 |
+
if (!this.get('parents') || !this.get('parents').length) {
|
| 3001 |
+
throw new Error('needs parents');
|
| 3002 |
+
}
|
| 3003 |
+
}
|
| 3004 |
+
},
|
| 3005 |
+
|
| 3006 |
+
addNodeToVisuals: function() {
|
| 3007 |
+
var visNode = this.get('gitVisuals').addNode(this.get('id'), this);
|
| 3008 |
+
this.set('visNode', visNode);
|
| 3009 |
+
},
|
| 3010 |
+
|
| 3011 |
+
addEdgeToVisuals: function(parent) {
|
| 3012 |
+
this.get('gitVisuals').addEdge(this.get('id'), parent.get('id'));
|
| 3013 |
+
},
|
| 3014 |
+
|
| 3015 |
+
getParent: function(parentNum) {
|
| 3016 |
+
if (this && this.attributes && this.attributes.parents) {
|
| 3017 |
+
return this.attributes.parents[parentNum];
|
| 3018 |
+
} else {
|
| 3019 |
+
return null;
|
| 3020 |
+
}
|
| 3021 |
+
},
|
| 3022 |
+
|
| 3023 |
+
removeFromParents: function() {
|
| 3024 |
+
this.get('parents').forEach(function (parent) {
|
| 3025 |
+
parent.removeChild(this);
|
| 3026 |
+
}, this);
|
| 3027 |
+
},
|
| 3028 |
+
|
| 3029 |
+
checkForUpdatedParent: function(engine) {
|
| 3030 |
+
var parents = this.get('parents');
|
| 3031 |
+
if (parents.length > 1) {
|
| 3032 |
+
return;
|
| 3033 |
+
}
|
| 3034 |
+
var parent = parents[0];
|
| 3035 |
+
var parentID = parent.get('id');
|
| 3036 |
+
var newestID = engine.getMostRecentBumpedID(parentID);
|
| 3037 |
+
|
| 3038 |
+
if (parentID === newestID) {
|
| 3039 |
+
// BOOM done, its already updated
|
| 3040 |
+
return;
|
| 3041 |
+
}
|
| 3042 |
+
|
| 3043 |
+
// crap we have to switch
|
| 3044 |
+
var newParent = engine.refs[newestID];
|
| 3045 |
+
|
| 3046 |
+
this.removeFromParents();
|
| 3047 |
+
this.set('parents', [newParent]);
|
| 3048 |
+
newParent.get('children').push(this);
|
| 3049 |
+
|
| 3050 |
+
// when we run in test mode, our visnode and
|
| 3051 |
+
// visuals will be undefined so we need to check for their existence
|
| 3052 |
+
var visNode = this.get('visNode');
|
| 3053 |
+
if (visNode) {
|
| 3054 |
+
visNode.removeAllEdges();
|
| 3055 |
+
}
|
| 3056 |
+
|
| 3057 |
+
var gitVisuals = this.get('gitVisuals');
|
| 3058 |
+
if (gitVisuals) {
|
| 3059 |
+
gitVisuals.addEdge(this.get('id'), newestID);
|
| 3060 |
+
}
|
| 3061 |
+
|
| 3062 |
+
return true;
|
| 3063 |
+
},
|
| 3064 |
+
|
| 3065 |
+
removeChild: function(childToRemove) {
|
| 3066 |
+
var newChildren = [];
|
| 3067 |
+
this.get('children').forEach(function (child) {
|
| 3068 |
+
if (child !== childToRemove) {
|
| 3069 |
+
newChildren.push(child);
|
| 3070 |
+
}
|
| 3071 |
+
});
|
| 3072 |
+
this.set('children', newChildren);
|
| 3073 |
+
},
|
| 3074 |
+
|
| 3075 |
+
isMainParent: function(parent) {
|
| 3076 |
+
var index = this.get('parents').indexOf(parent);
|
| 3077 |
+
return index === 0;
|
| 3078 |
+
},
|
| 3079 |
+
|
| 3080 |
+
initialize: function(options) {
|
| 3081 |
+
this.validateAtInit();
|
| 3082 |
+
this.addNodeToVisuals();
|
| 3083 |
+
|
| 3084 |
+
(this.get('parents') || []).forEach(function (parent) {
|
| 3085 |
+
parent.get('children').push(this);
|
| 3086 |
+
this.addEdgeToVisuals(parent);
|
| 3087 |
+
}, this);
|
| 3088 |
+
}
|
| 3089 |
+
});
|
| 3090 |
+
|
| 3091 |
+
var Tag = Ref.extend({
|
| 3092 |
+
defaults: {
|
| 3093 |
+
visTag: null
|
| 3094 |
+
},
|
| 3095 |
+
|
| 3096 |
+
initialize: function() {
|
| 3097 |
+
Ref.prototype.initialize.call(this);
|
| 3098 |
+
this.set('type', 'tag');
|
| 3099 |
+
}
|
| 3100 |
+
});
|
| 3101 |
+
|
| 3102 |
+
function RevisionRange(engine, specifiers) {
|
| 3103 |
+
this.engine = engine;
|
| 3104 |
+
this.tipsToInclude = [];
|
| 3105 |
+
this.tipsToExclude = [];
|
| 3106 |
+
this.includedRefs = {};
|
| 3107 |
+
this.excludedRefs = {};
|
| 3108 |
+
this.revisions = [];
|
| 3109 |
+
|
| 3110 |
+
this.processSpecifiers(specifiers);
|
| 3111 |
+
}
|
| 3112 |
+
|
| 3113 |
+
var rangeRegex = /^(.*)\.\.(.*)$/;
|
| 3114 |
+
|
| 3115 |
+
RevisionRange.prototype.processAsRange = function(specifier) {
|
| 3116 |
+
var match = specifier.match(rangeRegex);
|
| 3117 |
+
if(!match) {
|
| 3118 |
+
return false;
|
| 3119 |
+
}
|
| 3120 |
+
this.tipsToExclude.push(match[1]);
|
| 3121 |
+
this.tipsToInclude.push(match[2]);
|
| 3122 |
+
return true;
|
| 3123 |
+
};
|
| 3124 |
+
|
| 3125 |
+
RevisionRange.prototype.processAsExclusion = function(specifier) {
|
| 3126 |
+
if(!specifier.startsWith('^')) {
|
| 3127 |
+
return false;
|
| 3128 |
+
}
|
| 3129 |
+
this.tipsToExclude.push(specifier.slice(1));
|
| 3130 |
+
return true;
|
| 3131 |
+
};
|
| 3132 |
+
|
| 3133 |
+
RevisionRange.prototype.processAsInclusion = function(specifier) {
|
| 3134 |
+
this.tipsToInclude.push(specifier);
|
| 3135 |
+
return true;
|
| 3136 |
+
};
|
| 3137 |
+
|
| 3138 |
+
RevisionRange.prototype.processSpecifiers = function(specifiers) {
|
| 3139 |
+
var self = this;
|
| 3140 |
+
var processors = [
|
| 3141 |
+
this.processAsRange,
|
| 3142 |
+
this.processAsExclusion
|
| 3143 |
+
];
|
| 3144 |
+
|
| 3145 |
+
specifiers.forEach(function(specifier) {
|
| 3146 |
+
if(!processors.some(function(processor) { return processor.bind(self)(specifier); })) {
|
| 3147 |
+
self.processAsInclusion(specifier);
|
| 3148 |
+
}
|
| 3149 |
+
});
|
| 3150 |
+
|
| 3151 |
+
this.tipsToExclude.forEach(function(exclusion) {
|
| 3152 |
+
self.addExcluded(Graph.getUpstreamSet(self.engine, exclusion));
|
| 3153 |
+
});
|
| 3154 |
+
|
| 3155 |
+
this.tipsToInclude.forEach(function(inclusion) {
|
| 3156 |
+
self.addIncluded(Graph.getUpstreamSet(self.engine, inclusion));
|
| 3157 |
+
});
|
| 3158 |
+
|
| 3159 |
+
var includedKeys = Array.from(Object.keys(self.includedRefs));
|
| 3160 |
+
|
| 3161 |
+
self.revisions = includedKeys.map(function(revision) {
|
| 3162 |
+
return self.engine.resolveStringRef(revision);
|
| 3163 |
+
});
|
| 3164 |
+
self.revisions.sort(self.engine.dateSortFunc);
|
| 3165 |
+
self.revisions.reverse();
|
| 3166 |
+
};
|
| 3167 |
+
|
| 3168 |
+
RevisionRange.prototype.isExcluded = function(revision) {
|
| 3169 |
+
return this.excludedRefs.hasOwnProperty(revision);
|
| 3170 |
+
};
|
| 3171 |
+
|
| 3172 |
+
RevisionRange.prototype.addExcluded = function(setToExclude) {
|
| 3173 |
+
var self = this;
|
| 3174 |
+
Object.keys(setToExclude).forEach(function(toExclude) {
|
| 3175 |
+
if(!self.isExcluded(toExclude)) {
|
| 3176 |
+
self.excludedRefs[toExclude] = true;
|
| 3177 |
+
}
|
| 3178 |
+
});
|
| 3179 |
+
};
|
| 3180 |
+
|
| 3181 |
+
RevisionRange.prototype.addIncluded = function(setToInclude) {
|
| 3182 |
+
var self = this;
|
| 3183 |
+
Object.keys(setToInclude).forEach(function(toInclude) {
|
| 3184 |
+
if(!self.isExcluded(toInclude)) {
|
| 3185 |
+
self.includedRefs[toInclude] = true;
|
| 3186 |
+
}
|
| 3187 |
+
});
|
| 3188 |
+
};
|
| 3189 |
+
|
| 3190 |
+
RevisionRange.prototype.formatRevisions = function(revisionFormatter) {
|
| 3191 |
+
var output = "";
|
| 3192 |
+
this.revisions.forEach(function(c) {
|
| 3193 |
+
output += revisionFormatter(c);
|
| 3194 |
+
});
|
| 3195 |
+
return output;
|
| 3196 |
+
};
|
| 3197 |
+
|
| 3198 |
+
exports.GitEngine = GitEngine;
|
| 3199 |
+
exports.Commit = Commit;
|
| 3200 |
+
exports.Branch = Branch;
|
| 3201 |
+
exports.Tag = Tag;
|
| 3202 |
+
exports.Ref = Ref;
|
src/js/graph/index.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function invariant(truthy, reason) {
|
| 2 |
+
if (!truthy) {
|
| 3 |
+
throw new Error(reason);
|
| 4 |
+
}
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
var Graph = {
|
| 8 |
+
|
| 9 |
+
getOrMakeRecursive: function(
|
| 10 |
+
tree,
|
| 11 |
+
createdSoFar,
|
| 12 |
+
objID,
|
| 13 |
+
gitVisuals
|
| 14 |
+
) {
|
| 15 |
+
// circular dependency, should move these base models OUT of
|
| 16 |
+
// the git class to resolve this
|
| 17 |
+
var Git = require('../git');
|
| 18 |
+
var Commit = Git.Commit;
|
| 19 |
+
var Ref = Git.Ref;
|
| 20 |
+
var Branch = Git.Branch;
|
| 21 |
+
var Tag = Git.Tag;
|
| 22 |
+
if (createdSoFar[objID]) {
|
| 23 |
+
// base case
|
| 24 |
+
return createdSoFar[objID];
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
var getType = function(tree, id) {
|
| 28 |
+
if (tree.commits[id]) {
|
| 29 |
+
return 'commit';
|
| 30 |
+
} else if (tree.branches[id]) {
|
| 31 |
+
return 'branch';
|
| 32 |
+
} else if (id == 'HEAD') {
|
| 33 |
+
return 'HEAD';
|
| 34 |
+
} else if (tree.tags[id]) {
|
| 35 |
+
return 'tag';
|
| 36 |
+
}
|
| 37 |
+
throw new Error("bad type for " + id);
|
| 38 |
+
};
|
| 39 |
+
|
| 40 |
+
// figure out what type
|
| 41 |
+
var type = getType(tree, objID);
|
| 42 |
+
|
| 43 |
+
if (type == 'HEAD') {
|
| 44 |
+
var headJSON = tree.HEAD;
|
| 45 |
+
var HEAD = new Ref(Object.assign(
|
| 46 |
+
tree.HEAD,
|
| 47 |
+
{
|
| 48 |
+
target: this.getOrMakeRecursive(tree, createdSoFar, headJSON.target)
|
| 49 |
+
}
|
| 50 |
+
));
|
| 51 |
+
createdSoFar[objID] = HEAD;
|
| 52 |
+
return HEAD;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
if (type == 'branch') {
|
| 56 |
+
var branchJSON = tree.branches[objID];
|
| 57 |
+
|
| 58 |
+
var branch = new Branch(Object.assign(
|
| 59 |
+
tree.branches[objID],
|
| 60 |
+
{
|
| 61 |
+
target: this.getOrMakeRecursive(tree, createdSoFar, branchJSON.target)
|
| 62 |
+
}
|
| 63 |
+
));
|
| 64 |
+
createdSoFar[objID] = branch;
|
| 65 |
+
return branch;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
if (type == 'tag') {
|
| 69 |
+
var tagJSON = tree.tags[objID];
|
| 70 |
+
|
| 71 |
+
var tag = new Tag(Object.assign(
|
| 72 |
+
tree.tags[objID],
|
| 73 |
+
{
|
| 74 |
+
target: this.getOrMakeRecursive(tree, createdSoFar, tagJSON.target)
|
| 75 |
+
}
|
| 76 |
+
));
|
| 77 |
+
createdSoFar[objID] = tag;
|
| 78 |
+
return tag;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
if (type == 'commit') {
|
| 82 |
+
// for commits, we need to grab all the parents
|
| 83 |
+
var commitJSON = tree.commits[objID];
|
| 84 |
+
|
| 85 |
+
var parentObjs = [];
|
| 86 |
+
commitJSON.parents.forEach(function(parentID) {
|
| 87 |
+
parentObjs.push(this.getOrMakeRecursive(tree, createdSoFar, parentID));
|
| 88 |
+
}, this);
|
| 89 |
+
|
| 90 |
+
var commit = new Commit(Object.assign(
|
| 91 |
+
commitJSON,
|
| 92 |
+
{
|
| 93 |
+
parents: parentObjs,
|
| 94 |
+
gitVisuals: this.gitVisuals
|
| 95 |
+
}
|
| 96 |
+
));
|
| 97 |
+
createdSoFar[objID] = commit;
|
| 98 |
+
return commit;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
throw new Error('ruh rho!! unsupported type for ' + objID);
|
| 102 |
+
},
|
| 103 |
+
|
| 104 |
+
descendSortDepth: function(objects) {
|
| 105 |
+
return objects.sort(function(oA, oB) {
|
| 106 |
+
return oB.depth - oA.depth;
|
| 107 |
+
});
|
| 108 |
+
},
|
| 109 |
+
|
| 110 |
+
bfsFromLocationWithSet: function(engine, location, set) {
|
| 111 |
+
var result = [];
|
| 112 |
+
var pQueue = [engine.getCommitFromRef(location)];
|
| 113 |
+
|
| 114 |
+
while (pQueue.length) {
|
| 115 |
+
var popped = pQueue.pop();
|
| 116 |
+
if (set[popped.get('id')]) {
|
| 117 |
+
continue;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
result.push(popped);
|
| 121 |
+
// keep searching
|
| 122 |
+
pQueue = pQueue.concat(popped.get('parents'));
|
| 123 |
+
}
|
| 124 |
+
return result;
|
| 125 |
+
},
|
| 126 |
+
|
| 127 |
+
getUpstreamSet: function(engine, ancestor) {
|
| 128 |
+
var commit = engine.getCommitFromRef(ancestor);
|
| 129 |
+
var ancestorID = commit.get('id');
|
| 130 |
+
var queue = [commit];
|
| 131 |
+
|
| 132 |
+
var exploredSet = {};
|
| 133 |
+
exploredSet[ancestorID] = true;
|
| 134 |
+
|
| 135 |
+
var addToExplored = function(rent) {
|
| 136 |
+
exploredSet[rent.get('id')] = true;
|
| 137 |
+
queue.push(rent);
|
| 138 |
+
};
|
| 139 |
+
|
| 140 |
+
while (queue.length) {
|
| 141 |
+
var here = queue.pop();
|
| 142 |
+
var rents = here.get('parents');
|
| 143 |
+
|
| 144 |
+
(rents || []).forEach(addToExplored);
|
| 145 |
+
}
|
| 146 |
+
return exploredSet;
|
| 147 |
+
},
|
| 148 |
+
|
| 149 |
+
getUniqueObjects: function(objects) {
|
| 150 |
+
var unique = {};
|
| 151 |
+
var result = [];
|
| 152 |
+
objects.forEach(function(object) {
|
| 153 |
+
if (unique[object.id]) {
|
| 154 |
+
return;
|
| 155 |
+
}
|
| 156 |
+
unique[object.id] = true;
|
| 157 |
+
result.push(object);
|
| 158 |
+
});
|
| 159 |
+
return result;
|
| 160 |
+
},
|
| 161 |
+
|
| 162 |
+
getDefaultTree: function() {
|
| 163 |
+
return JSON.parse(unescape("%7B%22branches%22%3A%7B%22main%22%3A%7B%22target%22%3A%22C1%22%2C%22id%22%3A%22main%22%2C%22type%22%3A%22branch%22%7D%7D%2C%22commits%22%3A%7B%22C0%22%3A%7B%22type%22%3A%22commit%22%2C%22parents%22%3A%5B%5D%2C%22author%22%3A%22Peter%20Cottle%22%2C%22createTime%22%3A%22Mon%20Nov%2005%202012%2000%3A56%3A47%20GMT-0800%20%28PST%29%22%2C%22commitMessage%22%3A%22Quick%20Commit.%20Go%20Bears%21%22%2C%22id%22%3A%22C0%22%2C%22rootCommit%22%3Atrue%7D%2C%22C1%22%3A%7B%22type%22%3A%22commit%22%2C%22parents%22%3A%5B%22C0%22%5D%2C%22author%22%3A%22Peter%20Cottle%22%2C%22createTime%22%3A%22Mon%20Nov%2005%202012%2000%3A56%3A47%20GMT-0800%20%28PST%29%22%2C%22commitMessage%22%3A%22Quick%20Commit.%20Go%20Bears%21%22%2C%22id%22%3A%22C1%22%7D%7D%2C%22HEAD%22%3A%7B%22id%22%3A%22HEAD%22%2C%22target%22%3A%22main%22%2C%22type%22%3A%22general%20ref%22%7D%7D"));
|
| 164 |
+
}
|
| 165 |
+
};
|
| 166 |
+
|
| 167 |
+
module.exports = Graph;
|
src/js/graph/treeCompare.js
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var _ = require('underscore');
|
| 2 |
+
|
| 3 |
+
// static class...
|
| 4 |
+
var TreeCompare = {};
|
| 5 |
+
|
| 6 |
+
TreeCompare.dispatchFromLevel = function(levelBlob, treeToCompare) {
|
| 7 |
+
var goalTreeString = levelBlob.goalTreeString;
|
| 8 |
+
if (typeof treeToCompare !== 'string') {
|
| 9 |
+
console.warn('NEED to pass in string!! gah');
|
| 10 |
+
}
|
| 11 |
+
return TreeCompare.dispatch(levelBlob, goalTreeString, treeToCompare);
|
| 12 |
+
};
|
| 13 |
+
|
| 14 |
+
TreeCompare.onlyMainCompared = function(levelBlob) {
|
| 15 |
+
var getAroundLintTrue = true;
|
| 16 |
+
switch (getAroundLintTrue) {
|
| 17 |
+
case !!levelBlob.compareOnlyMain:
|
| 18 |
+
case !!levelBlob.compareOnlyMainHashAgnostic:
|
| 19 |
+
case !!levelBlob.compareOnlyMainHashAgnosticWithAsserts:
|
| 20 |
+
return true;
|
| 21 |
+
default:
|
| 22 |
+
return false;
|
| 23 |
+
}
|
| 24 |
+
};
|
| 25 |
+
|
| 26 |
+
TreeCompare.dispatch = function(levelBlob, goalTreeString, treeToCompare) {
|
| 27 |
+
var goalTree = this.convertTreeSafe(goalTreeString);
|
| 28 |
+
treeToCompare = this.convertTreeSafe(treeToCompare);
|
| 29 |
+
if (typeof goalTree.originTree !== typeof treeToCompare.originTree) {
|
| 30 |
+
// origin status does not match
|
| 31 |
+
return false;
|
| 32 |
+
}
|
| 33 |
+
var shallowResult = this.dispatchShallow(
|
| 34 |
+
levelBlob, goalTree, treeToCompare
|
| 35 |
+
);
|
| 36 |
+
if (!shallowResult || !goalTree.originTree) {
|
| 37 |
+
// we only have one level (or failed on shallow), punt
|
| 38 |
+
return shallowResult;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
var originBlob = (levelBlob.originCompare) ?
|
| 42 |
+
levelBlob.originCompare : levelBlob;
|
| 43 |
+
// compare origin trees
|
| 44 |
+
return shallowResult && this.dispatchShallow(
|
| 45 |
+
originBlob, goalTree.originTree, treeToCompare.originTree
|
| 46 |
+
);
|
| 47 |
+
};
|
| 48 |
+
|
| 49 |
+
TreeCompare.dispatchShallow = function(levelBlob, goalTreeString, treeToCompare) {
|
| 50 |
+
var getAroundLintTrue = true;
|
| 51 |
+
// i actually prefer this to else if
|
| 52 |
+
switch (getAroundLintTrue) {
|
| 53 |
+
case !!levelBlob.compareOnlyMain:
|
| 54 |
+
return TreeCompare.compareBranchWithinTrees(
|
| 55 |
+
treeToCompare, goalTreeString, 'main'
|
| 56 |
+
);
|
| 57 |
+
case !!levelBlob.compareAllBranchesAndEnforceBranchCleanup:
|
| 58 |
+
return TreeCompare.compareAllBranchesAndEnforceBranchCleanup(
|
| 59 |
+
treeToCompare, goalTreeString
|
| 60 |
+
);
|
| 61 |
+
case !!levelBlob.compareOnlyBranches:
|
| 62 |
+
return TreeCompare.compareAllBranchesWithinTrees(
|
| 63 |
+
treeToCompare, goalTreeString
|
| 64 |
+
);
|
| 65 |
+
case !!levelBlob.compareAllBranchesHashAgnostic:
|
| 66 |
+
return TreeCompare.compareAllBranchesWithinTreesHashAgnostic(
|
| 67 |
+
treeToCompare, goalTreeString
|
| 68 |
+
);
|
| 69 |
+
case !!levelBlob.compareOnlyMainHashAgnostic:
|
| 70 |
+
return TreeCompare.compareBranchesWithinTreesHashAgnostic(
|
| 71 |
+
treeToCompare, goalTreeString, ['main']
|
| 72 |
+
);
|
| 73 |
+
case !!levelBlob.compareOnlyMainHashAgnosticWithAsserts:
|
| 74 |
+
return TreeCompare.compareBranchesWithinTreesHashAgnostic(
|
| 75 |
+
treeToCompare, goalTreeString, ['main']
|
| 76 |
+
) && TreeCompare.evalAsserts(treeToCompare, levelBlob.goalAsserts);
|
| 77 |
+
default:
|
| 78 |
+
return TreeCompare.compareAllBranchesWithinTreesAndHEAD(
|
| 79 |
+
treeToCompare, goalTreeString
|
| 80 |
+
);
|
| 81 |
+
}
|
| 82 |
+
};
|
| 83 |
+
|
| 84 |
+
// would love to have copy properties here.. :(
|
| 85 |
+
TreeCompare.compareAllBranchesWithinTreesAndHEAD = function(treeToCompare, goalTree) {
|
| 86 |
+
treeToCompare = this.convertTreeSafe(treeToCompare);
|
| 87 |
+
goalTree = this.convertTreeSafe(goalTree);
|
| 88 |
+
|
| 89 |
+
// also compare tags!! for just one level
|
| 90 |
+
return treeToCompare.HEAD.target === goalTree.HEAD.target &&
|
| 91 |
+
this.compareAllBranchesWithinTrees(treeToCompare, goalTree) &&
|
| 92 |
+
this.compareAllTagsWithinTrees(treeToCompare, goalTree);
|
| 93 |
+
};
|
| 94 |
+
|
| 95 |
+
TreeCompare.compareAllBranchesAndEnforceBranchCleanup = function(treeToCompare, goalTree) {
|
| 96 |
+
treeToCompare = this.convertTreeSafe(treeToCompare);
|
| 97 |
+
goalTree = this.convertTreeSafe(goalTree);
|
| 98 |
+
|
| 99 |
+
// Unlike compareAllBranchesWithinTrees, here we consider both the branches
|
| 100 |
+
// in the goalTree and the branches in the treeToCompare. This means that
|
| 101 |
+
// we enforce that you clean up any branches that you have locally that
|
| 102 |
+
// the goal does not have. this is helpful when we want to verify that you
|
| 103 |
+
// have deleted branch, for instance.
|
| 104 |
+
var allBranches = Object.assign(
|
| 105 |
+
{},
|
| 106 |
+
treeToCompare.branches,
|
| 107 |
+
goalTree.branches
|
| 108 |
+
);
|
| 109 |
+
return Object.keys(allBranches).every(function(branch) {
|
| 110 |
+
return this.compareBranchWithinTrees(treeToCompare, goalTree, branch);
|
| 111 |
+
}.bind(this));
|
| 112 |
+
};
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
TreeCompare.compareAllBranchesWithinTrees = function(treeToCompare, goalTree) {
|
| 116 |
+
treeToCompare = this.convertTreeSafe(treeToCompare);
|
| 117 |
+
goalTree = this.convertTreeSafe(goalTree);
|
| 118 |
+
|
| 119 |
+
/**
|
| 120 |
+
* Disclaimer / reminder!! We only care about branches in the goal tree;
|
| 121 |
+
* if you have extra branches in your source tree thats ok. but that means
|
| 122 |
+
* the arguments here are important -- always call this function with
|
| 123 |
+
* goalTree being the latter argument, since we will discard extra branches
|
| 124 |
+
* from treeToCompare (the first argument).
|
| 125 |
+
*/
|
| 126 |
+
return Object.keys(goalTree.branches).every(function(branch) {
|
| 127 |
+
return this.compareBranchWithinTrees(treeToCompare, goalTree, branch);
|
| 128 |
+
}.bind(this));
|
| 129 |
+
};
|
| 130 |
+
|
| 131 |
+
TreeCompare.compareAllTagsWithinTrees = function(treeToCompare, goalTree) {
|
| 132 |
+
treeToCompare = this.convertTreeSafe(treeToCompare);
|
| 133 |
+
goalTree = this.convertTreeSafe(goalTree);
|
| 134 |
+
this.reduceTreeFields([treeToCompare, goalTree]);
|
| 135 |
+
|
| 136 |
+
return _.isEqual(treeToCompare.tags, goalTree.tags);
|
| 137 |
+
};
|
| 138 |
+
|
| 139 |
+
TreeCompare.compareBranchesWithinTrees = function(treeToCompare, goalTree, branches) {
|
| 140 |
+
var result = true;
|
| 141 |
+
branches.forEach(function(branchName) {
|
| 142 |
+
result = result && this.compareBranchWithinTrees(treeToCompare, goalTree, branchName);
|
| 143 |
+
}, this);
|
| 144 |
+
|
| 145 |
+
return result;
|
| 146 |
+
};
|
| 147 |
+
|
| 148 |
+
TreeCompare.compareBranchWithinTrees = function(treeToCompare, goalTree, branchName) {
|
| 149 |
+
treeToCompare = this.convertTreeSafe(treeToCompare);
|
| 150 |
+
goalTree = this.convertTreeSafe(goalTree);
|
| 151 |
+
this.reduceTreeFields([treeToCompare, goalTree]);
|
| 152 |
+
|
| 153 |
+
var recurseCompare = this.getRecurseCompare(treeToCompare, goalTree);
|
| 154 |
+
var branchA = treeToCompare.branches[branchName];
|
| 155 |
+
var branchB = goalTree.branches[branchName];
|
| 156 |
+
|
| 157 |
+
return _.isEqual(branchA, branchB) &&
|
| 158 |
+
recurseCompare(treeToCompare.commits[branchA.target], goalTree.commits[branchB.target]);
|
| 159 |
+
};
|
| 160 |
+
|
| 161 |
+
TreeCompare.compareAllBranchesWithinTreesHashAgnostic = function(treeToCompare, goalTree) {
|
| 162 |
+
treeToCompare = this.convertTreeSafe(treeToCompare);
|
| 163 |
+
goalTree = this.convertTreeSafe(goalTree);
|
| 164 |
+
this.reduceTreeFields([treeToCompare, goalTree]);
|
| 165 |
+
|
| 166 |
+
var allBranches = Object.assign(
|
| 167 |
+
{},
|
| 168 |
+
treeToCompare.branches,
|
| 169 |
+
goalTree.branches
|
| 170 |
+
);
|
| 171 |
+
var branchNames = Object.keys(allBranches || {});
|
| 172 |
+
|
| 173 |
+
return this.compareBranchesWithinTreesHashAgnostic(treeToCompare, goalTree, branchNames);
|
| 174 |
+
};
|
| 175 |
+
|
| 176 |
+
TreeCompare.compareBranchesWithinTreesHashAgnostic = function(treeToCompare, goalTree, branches) {
|
| 177 |
+
// we can't DRY unfortunately here because we need a special _.isEqual function
|
| 178 |
+
// for both the recursive compare and the branch compare
|
| 179 |
+
treeToCompare = this.convertTreeSafe(treeToCompare);
|
| 180 |
+
goalTree = this.convertTreeSafe(goalTree);
|
| 181 |
+
this.reduceTreeFields([treeToCompare, goalTree]);
|
| 182 |
+
|
| 183 |
+
// get a function to compare branch objects without hashes
|
| 184 |
+
var compareBranchObjs = function(branchA, branchB) {
|
| 185 |
+
if (!branchA || !branchB) {
|
| 186 |
+
return false;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
// don't mess up the rest of comparison
|
| 190 |
+
branchA = Object.assign({}, branchA);
|
| 191 |
+
branchB = Object.assign({}, branchB);
|
| 192 |
+
branchA.target = this.getBaseRef(branchA.target);
|
| 193 |
+
branchB.target = this.getBaseRef(branchB.target);
|
| 194 |
+
|
| 195 |
+
return _.isEqual(branchA, branchB);
|
| 196 |
+
}.bind(this);
|
| 197 |
+
// and a function to compare recursively without worrying about hashes
|
| 198 |
+
var recurseCompare = this.getRecurseCompareHashAgnostic(treeToCompare, goalTree);
|
| 199 |
+
|
| 200 |
+
var result = true;
|
| 201 |
+
branches.forEach(function(branchName) {
|
| 202 |
+
var branchA = treeToCompare.branches[branchName];
|
| 203 |
+
var branchB = goalTree.branches[branchName];
|
| 204 |
+
|
| 205 |
+
result = result && compareBranchObjs(branchA, branchB) &&
|
| 206 |
+
recurseCompare(treeToCompare.commits[branchA.target], goalTree.commits[branchB.target]);
|
| 207 |
+
}, this);
|
| 208 |
+
return result;
|
| 209 |
+
};
|
| 210 |
+
|
| 211 |
+
TreeCompare.evalAsserts = function(tree, assertsPerBranch) {
|
| 212 |
+
var result = true;
|
| 213 |
+
Object.keys(assertsPerBranch).forEach(function(branchName) {
|
| 214 |
+
var asserts = assertsPerBranch[branchName];
|
| 215 |
+
result = result && this.evalAssertsOnBranch(tree, branchName, asserts);
|
| 216 |
+
}, this);
|
| 217 |
+
return result;
|
| 218 |
+
};
|
| 219 |
+
|
| 220 |
+
TreeCompare.evalAssertsOnBranch = function(tree, branchName, asserts) {
|
| 221 |
+
tree = this.convertTreeSafe(tree);
|
| 222 |
+
|
| 223 |
+
// here is the outline:
|
| 224 |
+
// * make a data object
|
| 225 |
+
// * go to the branch given by the key
|
| 226 |
+
// * traverse upwards, storing the amount of hashes on each in the data object
|
| 227 |
+
// * then come back and perform functions on data
|
| 228 |
+
|
| 229 |
+
if (!tree.branches[branchName]) {
|
| 230 |
+
return false;
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
var branch = tree.branches[branchName];
|
| 234 |
+
var queue = [branch.target];
|
| 235 |
+
var data = {};
|
| 236 |
+
while (queue.length) {
|
| 237 |
+
var commitRef = queue.pop();
|
| 238 |
+
data[this.getBaseRef(commitRef)] = this.getNumHashes(commitRef);
|
| 239 |
+
queue = queue.concat(tree.commits[commitRef].parents);
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
var result = true;
|
| 243 |
+
asserts.forEach(function(assert) {
|
| 244 |
+
try {
|
| 245 |
+
result = result && assert(data);
|
| 246 |
+
} catch (err) {
|
| 247 |
+
console.warn('error during assert', err);
|
| 248 |
+
console.log(err);
|
| 249 |
+
result = false;
|
| 250 |
+
}
|
| 251 |
+
});
|
| 252 |
+
|
| 253 |
+
return result;
|
| 254 |
+
};
|
| 255 |
+
|
| 256 |
+
TreeCompare.getNumHashes = function(ref) {
|
| 257 |
+
var regexMap = [
|
| 258 |
+
[/^C(\d+)([']{0,3})$/, function(bits) {
|
| 259 |
+
if (!bits[2]) {
|
| 260 |
+
return 0;
|
| 261 |
+
}
|
| 262 |
+
return bits[2].length;
|
| 263 |
+
}],
|
| 264 |
+
[/^C(\d+)['][\^](\d+)$/, function(bits) {
|
| 265 |
+
return Number(bits[2]);
|
| 266 |
+
}]
|
| 267 |
+
];
|
| 268 |
+
|
| 269 |
+
for (var i = 0; i < regexMap.length; i++) {
|
| 270 |
+
var regex = regexMap[i][0];
|
| 271 |
+
var func = regexMap[i][1];
|
| 272 |
+
var results = regex.exec(ref);
|
| 273 |
+
if (results) {
|
| 274 |
+
return func(results);
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
throw new Error('couldn\'t parse ref ' + ref);
|
| 278 |
+
};
|
| 279 |
+
|
| 280 |
+
TreeCompare.getBaseRef = function(ref) {
|
| 281 |
+
var idRegex = /^C(\d+)/;
|
| 282 |
+
var bits = idRegex.exec(ref);
|
| 283 |
+
if (!bits) { throw new Error('no regex matchy for ' + ref); }
|
| 284 |
+
// no matter what hash this is (aka C1', C1'', C1'^3, etc) we
|
| 285 |
+
// return C1
|
| 286 |
+
return 'C' + bits[1];
|
| 287 |
+
};
|
| 288 |
+
|
| 289 |
+
TreeCompare.getRecurseCompareHashAgnostic = function(treeToCompare, goalTree) {
|
| 290 |
+
// here we pass in a special comparison function to pass into the base
|
| 291 |
+
// recursive compare.
|
| 292 |
+
|
| 293 |
+
// some buildup functions
|
| 294 |
+
var getStrippedCommitCopy = function(commit) {
|
| 295 |
+
if (!commit) { return {}; }
|
| 296 |
+
return Object.assign(
|
| 297 |
+
{},
|
| 298 |
+
commit,
|
| 299 |
+
{
|
| 300 |
+
id: this.getBaseRef(commit.id),
|
| 301 |
+
parents: null
|
| 302 |
+
}
|
| 303 |
+
);
|
| 304 |
+
}.bind(this);
|
| 305 |
+
|
| 306 |
+
var isEqual = function(commitA, commitB) {
|
| 307 |
+
return _.isEqual(
|
| 308 |
+
getStrippedCommitCopy(commitA),
|
| 309 |
+
getStrippedCommitCopy(commitB)
|
| 310 |
+
);
|
| 311 |
+
};
|
| 312 |
+
return this.getRecurseCompare(treeToCompare, goalTree, {isEqual: isEqual});
|
| 313 |
+
};
|
| 314 |
+
|
| 315 |
+
TreeCompare.getRecurseCompare = function(treeToCompare, goalTree, options) {
|
| 316 |
+
options = options || {};
|
| 317 |
+
|
| 318 |
+
// we need a recursive comparison function to bubble up the branch
|
| 319 |
+
var recurseCompare = function(commitA, commitB) {
|
| 320 |
+
// this is the short-circuit base case
|
| 321 |
+
var result = options.isEqual ?
|
| 322 |
+
options.isEqual(commitA, commitB) : _.isEqual(commitA, commitB);
|
| 323 |
+
if (!result) {
|
| 324 |
+
return false;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
// we loop through each parent ID. we sort the parent ID's beforehand
|
| 328 |
+
// so the index lookup is valid. for merge commits this will duplicate some of the
|
| 329 |
+
// checking (because we aren't doing graph search) but it's not a huge deal
|
| 330 |
+
var maxNumParents = Math.max(commitA.parents.length, commitB.parents.length);
|
| 331 |
+
for (var index = 0; index < maxNumParents; index++) {
|
| 332 |
+
var pAid = commitA.parents[index];
|
| 333 |
+
var pBid = commitB.parents[index];
|
| 334 |
+
|
| 335 |
+
// if treeToCompare or goalTree doesn't have this parent,
|
| 336 |
+
// then we get an undefined child which is fine when we pass into _.isEqual
|
| 337 |
+
var childA = treeToCompare.commits[pAid];
|
| 338 |
+
var childB = goalTree.commits[pBid];
|
| 339 |
+
|
| 340 |
+
result = result && recurseCompare(childA, childB);
|
| 341 |
+
}
|
| 342 |
+
// if each of our children recursively are equal, we are good
|
| 343 |
+
return result;
|
| 344 |
+
};
|
| 345 |
+
return recurseCompare;
|
| 346 |
+
};
|
| 347 |
+
|
| 348 |
+
TreeCompare.lowercaseTree = function(tree) {
|
| 349 |
+
if (tree.HEAD) {
|
| 350 |
+
tree.HEAD.target = tree.HEAD.target.toLocaleLowerCase();
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
var branches = tree.branches || {};
|
| 354 |
+
tree.branches = {};
|
| 355 |
+
Object.keys(branches).forEach(function(name) {
|
| 356 |
+
var obj = branches[name];
|
| 357 |
+
obj.id = obj.id.toLocaleLowerCase();
|
| 358 |
+
tree.branches[name.toLocaleLowerCase()] = obj;
|
| 359 |
+
});
|
| 360 |
+
return tree;
|
| 361 |
+
};
|
| 362 |
+
|
| 363 |
+
TreeCompare.convertTreeSafe = function(tree) {
|
| 364 |
+
if (typeof tree !== 'string') {
|
| 365 |
+
return tree;
|
| 366 |
+
}
|
| 367 |
+
tree = JSON.parse(unescape(tree));
|
| 368 |
+
// ok we are almost done -- but we need to case insensitive
|
| 369 |
+
// certain fields. so go ahead and do that.
|
| 370 |
+
// handle HEAD target first
|
| 371 |
+
this.lowercaseTree(tree);
|
| 372 |
+
if (tree.originTree) {
|
| 373 |
+
tree.originTree = this.lowercaseTree(tree.originTree);
|
| 374 |
+
}
|
| 375 |
+
return tree;
|
| 376 |
+
};
|
| 377 |
+
|
| 378 |
+
TreeCompare.reduceTreeFields = function(trees) {
|
| 379 |
+
var commitSaveFields = [
|
| 380 |
+
'parents',
|
| 381 |
+
'id',
|
| 382 |
+
'rootCommit'
|
| 383 |
+
];
|
| 384 |
+
var branchSaveFields = [
|
| 385 |
+
'target',
|
| 386 |
+
'id',
|
| 387 |
+
'remoteTrackingBranchID'
|
| 388 |
+
];
|
| 389 |
+
var tagSaveFields = [
|
| 390 |
+
'target',
|
| 391 |
+
'id'
|
| 392 |
+
];
|
| 393 |
+
|
| 394 |
+
var commitSortFields = ['children', 'parents'];
|
| 395 |
+
// for backwards compatibility, fill in some fields if missing
|
| 396 |
+
var defaults = {
|
| 397 |
+
remoteTrackingBranchID: null
|
| 398 |
+
};
|
| 399 |
+
// also fill tree-level defaults
|
| 400 |
+
var treeDefaults = {
|
| 401 |
+
tags: {}
|
| 402 |
+
};
|
| 403 |
+
|
| 404 |
+
trees.forEach(function(tree) {
|
| 405 |
+
Object.keys(treeDefaults).forEach(function(key) {
|
| 406 |
+
var val = treeDefaults[key];
|
| 407 |
+
if (tree[key] === undefined) {
|
| 408 |
+
tree[key] = val;
|
| 409 |
+
}
|
| 410 |
+
});
|
| 411 |
+
});
|
| 412 |
+
|
| 413 |
+
// this function saves only the specified fields of a tree
|
| 414 |
+
var saveOnly = function(tree, treeKey, saveFields, sortFields) {
|
| 415 |
+
var objects = tree[treeKey];
|
| 416 |
+
Object.keys(objects).forEach(function(objKey) {
|
| 417 |
+
var obj = objects[objKey];
|
| 418 |
+
// our blank slate to copy over
|
| 419 |
+
var blank = {};
|
| 420 |
+
saveFields.forEach(function(field) {
|
| 421 |
+
if (obj[field] !== undefined) {
|
| 422 |
+
blank[field] = obj[field];
|
| 423 |
+
} else if (defaults[field] !== undefined) {
|
| 424 |
+
blank[field] = defaults[field];
|
| 425 |
+
}
|
| 426 |
+
});
|
| 427 |
+
|
| 428 |
+
Object.values(sortFields || {}).forEach(function(field) {
|
| 429 |
+
// also sort some fields
|
| 430 |
+
if (obj[field]) {
|
| 431 |
+
obj[field].sort();
|
| 432 |
+
blank[field] = obj[field];
|
| 433 |
+
}
|
| 434 |
+
});
|
| 435 |
+
tree[treeKey][objKey] = blank;
|
| 436 |
+
});
|
| 437 |
+
};
|
| 438 |
+
|
| 439 |
+
trees.forEach(function(tree) {
|
| 440 |
+
saveOnly(tree, 'commits', commitSaveFields, commitSortFields);
|
| 441 |
+
saveOnly(tree, 'branches', branchSaveFields);
|
| 442 |
+
saveOnly(tree, 'tags', tagSaveFields);
|
| 443 |
+
|
| 444 |
+
tree.HEAD = {
|
| 445 |
+
target: tree.HEAD.target,
|
| 446 |
+
id: tree.HEAD.id
|
| 447 |
+
};
|
| 448 |
+
if (tree.originTree) {
|
| 449 |
+
this.reduceTreeFields([tree.originTree]);
|
| 450 |
+
}
|
| 451 |
+
}, this);
|
| 452 |
+
};
|
| 453 |
+
|
| 454 |
+
TreeCompare.compareTrees = function(treeToCompare, goalTree) {
|
| 455 |
+
treeToCompare = this.convertTreeSafe(treeToCompare);
|
| 456 |
+
goalTree = this.convertTreeSafe(goalTree);
|
| 457 |
+
|
| 458 |
+
// now we need to strip out the fields we don't care about, aka things
|
| 459 |
+
// like createTime, message, author
|
| 460 |
+
this.reduceTreeFields([treeToCompare, goalTree]);
|
| 461 |
+
|
| 462 |
+
return _.isEqual(treeToCompare, goalTree);
|
| 463 |
+
};
|
| 464 |
+
|
| 465 |
+
module.exports = TreeCompare;
|
src/js/intl/checkStrings.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var { join } = require('path');
|
| 2 |
+
var { readFileSync } = require('fs');
|
| 3 |
+
|
| 4 |
+
var util = require('../util');
|
| 5 |
+
var { strings } = require('../intl/strings');
|
| 6 |
+
|
| 7 |
+
var easyRegex = /intl\.str\(\s*'([a-zA-Z\-]+)'/g;
|
| 8 |
+
|
| 9 |
+
var allKetSet = new Set(Object.keys(strings));
|
| 10 |
+
allKetSet.delete('error-untranslated'); // used in ./index.js
|
| 11 |
+
|
| 12 |
+
var goodKeySet = new Set();
|
| 13 |
+
var validateKey = function(key) {
|
| 14 |
+
if (!strings[key]) {
|
| 15 |
+
console.log('NO KEY for: "', key, '"');
|
| 16 |
+
} else {
|
| 17 |
+
goodKeySet.add(key);
|
| 18 |
+
allKetSet.delete(key);
|
| 19 |
+
}
|
| 20 |
+
};
|
| 21 |
+
|
| 22 |
+
if (!util.isBrowser()) {
|
| 23 |
+
util.readDirDeep(join(__dirname, '../../')).forEach(function(path) {
|
| 24 |
+
var content = readFileSync(path);
|
| 25 |
+
var match;
|
| 26 |
+
while (match = easyRegex.exec(content)) {
|
| 27 |
+
validateKey(match[1]);
|
| 28 |
+
}
|
| 29 |
+
});
|
| 30 |
+
console.log(goodKeySet.size, ' good keys found!');
|
| 31 |
+
console.log(allKetSet.size, ' keys did not use!');
|
| 32 |
+
console.log(allKetSet);
|
| 33 |
+
}
|
src/js/intl/index.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var LocaleStore = require('../stores/LocaleStore');
|
| 2 |
+
|
| 3 |
+
var _ = require('underscore');
|
| 4 |
+
var strings = require('../intl/strings').strings;
|
| 5 |
+
|
| 6 |
+
var getDefaultLocale = LocaleStore.getDefaultLocale;
|
| 7 |
+
|
| 8 |
+
var fallbackMap = {
|
| 9 |
+
'zh_TW': 'zh_CN',
|
| 10 |
+
'es_AR': 'es_ES',
|
| 11 |
+
'es_MX': 'es_ES'
|
| 12 |
+
};
|
| 13 |
+
|
| 14 |
+
// lets change underscores template settings so it interpolates
|
| 15 |
+
// things like "{branchName} does not exist".
|
| 16 |
+
var templateSettings = Object.assign({}, _.templateSettings);
|
| 17 |
+
templateSettings.interpolate = /\{(.+?)\}/g;
|
| 18 |
+
var template = exports.template = function(str, params) {
|
| 19 |
+
return _.template(str, templateSettings)(params);
|
| 20 |
+
};
|
| 21 |
+
|
| 22 |
+
var str = exports.str = function(key, params) {
|
| 23 |
+
params = params || {};
|
| 24 |
+
// this function takes a key like "error-branch-delete"
|
| 25 |
+
// and parameters like {branchName: 'bugFix', num: 3}.
|
| 26 |
+
//
|
| 27 |
+
// it sticks those into a translation string like:
|
| 28 |
+
// 'en': 'You can not delete the branch {branchName} because' +
|
| 29 |
+
// 'you are currently on that branch! This is error number + {num}'
|
| 30 |
+
//
|
| 31 |
+
// to produce:
|
| 32 |
+
//
|
| 33 |
+
// 'You can not delete the branch bugFix because you are currently on that branch!
|
| 34 |
+
// This is error number 3'
|
| 35 |
+
|
| 36 |
+
var locale = LocaleStore.getLocale();
|
| 37 |
+
if (!strings[key]) {
|
| 38 |
+
console.warn('NO INTL support for key ' + key);
|
| 39 |
+
return 'NO INTL support for key ' + key + '. this is probably a dev error';
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
if (!strings[key][locale]) {
|
| 43 |
+
// try falling back to another locale if in the map
|
| 44 |
+
locale = fallbackMap[locale] || getDefaultLocale();
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
if (!strings[key][locale]) {
|
| 48 |
+
if (key !== 'error-untranslated') {
|
| 49 |
+
return str('error-untranslated');
|
| 50 |
+
}
|
| 51 |
+
return 'No translation for the key "' + key + '"';
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
return template(
|
| 55 |
+
strings[key][locale],
|
| 56 |
+
params
|
| 57 |
+
);
|
| 58 |
+
};
|
| 59 |
+
|
| 60 |
+
var getIntlKey = exports.getIntlKey = function(obj, key, overrideLocale) {
|
| 61 |
+
if (!obj || !obj[key]) {
|
| 62 |
+
throw new Error('that key ' + key + ' doesn\'t exist in this blob ' + obj);
|
| 63 |
+
}
|
| 64 |
+
if (!obj[key][getDefaultLocale()]) {
|
| 65 |
+
console.warn(
|
| 66 |
+
'WARNING!! This blob does not have intl support:',
|
| 67 |
+
obj,
|
| 68 |
+
'for this key',
|
| 69 |
+
key
|
| 70 |
+
);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
var locale = overrideLocale || LocaleStore.getLocale();
|
| 74 |
+
return obj[key][locale];
|
| 75 |
+
};
|
| 76 |
+
|
| 77 |
+
exports.todo = function(str) {
|
| 78 |
+
return str;
|
| 79 |
+
};
|
| 80 |
+
|
| 81 |
+
exports.getDialog = function(obj) {
|
| 82 |
+
return getIntlKey(obj, 'dialog') || obj.dialog[getDefaultLocale()];
|
| 83 |
+
};
|
| 84 |
+
|
| 85 |
+
exports.getHint = function(level) {
|
| 86 |
+
if (!getIntlKey(level, 'hint')) {
|
| 87 |
+
return getIntlKey(level, 'hint', getDefaultLocale()) + ' -- ' + str('error-untranslated');
|
| 88 |
+
}
|
| 89 |
+
return getIntlKey(level, 'hint');
|
| 90 |
+
};
|
| 91 |
+
|
| 92 |
+
exports.getName = function(level) {
|
| 93 |
+
if (!getIntlKey(level, 'name')) {
|
| 94 |
+
return getIntlKey(level, 'name', getDefaultLocale()) + ' -- ' + str('error-untranslated');
|
| 95 |
+
}
|
| 96 |
+
return getIntlKey(level, 'name');
|
| 97 |
+
};
|
| 98 |
+
|
| 99 |
+
exports.getStartDialog = function(level) {
|
| 100 |
+
var startDialog = getIntlKey(level, 'startDialog');
|
| 101 |
+
if (startDialog) { return startDialog; }
|
| 102 |
+
|
| 103 |
+
// this level translation isn't supported yet, so lets add
|
| 104 |
+
// an alert to the front and give the english version.
|
| 105 |
+
var errorAlert = {
|
| 106 |
+
type: 'ModalAlert',
|
| 107 |
+
options: {
|
| 108 |
+
markdown: str('error-untranslated')
|
| 109 |
+
}
|
| 110 |
+
};
|
| 111 |
+
var startCopy = Object.assign(
|
| 112 |
+
{},
|
| 113 |
+
level.startDialog[getDefaultLocale()] || level.startDialog
|
| 114 |
+
);
|
| 115 |
+
startCopy.childViews.unshift(errorAlert);
|
| 116 |
+
|
| 117 |
+
return startCopy;
|
| 118 |
+
};
|
src/js/intl/strings.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/js/level/builder.js
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var Backbone = require('backbone');
|
| 2 |
+
var Q = require('q');
|
| 3 |
+
|
| 4 |
+
var util = require('../util');
|
| 5 |
+
var Main = require('../app');
|
| 6 |
+
var intl = require('../intl');
|
| 7 |
+
var Errors = require('../util/errors');
|
| 8 |
+
|
| 9 |
+
var Visualization = require('../visuals/visualization').Visualization;
|
| 10 |
+
var ParseWaterfall = require('../level/parseWaterfall').ParseWaterfall;
|
| 11 |
+
var Level = require('../level').Level;
|
| 12 |
+
var LocaleStore = require('../stores/LocaleStore');
|
| 13 |
+
var LevelStore = require('../stores/LevelStore');
|
| 14 |
+
|
| 15 |
+
var Command = require('../models/commandModel').Command;
|
| 16 |
+
var GitShim = require('../git/gitShim').GitShim;
|
| 17 |
+
|
| 18 |
+
var MultiView = require('../views/multiView').MultiView;
|
| 19 |
+
|
| 20 |
+
var CanvasTerminalHolder = require('../views').CanvasTerminalHolder;
|
| 21 |
+
var ConfirmCancelTerminal = require('../views').ConfirmCancelTerminal;
|
| 22 |
+
var NextLevelConfirm = require('../views').NextLevelConfirm;
|
| 23 |
+
|
| 24 |
+
var MarkdownPresenter = require('../views/builderViews').MarkdownPresenter;
|
| 25 |
+
var MultiViewBuilder = require('../views/builderViews').MultiViewBuilder;
|
| 26 |
+
var MarkdownGrabber = require('../views/builderViews').MarkdownGrabber;
|
| 27 |
+
|
| 28 |
+
var regexMap = {
|
| 29 |
+
'define goal': /^define goal$/,
|
| 30 |
+
'define name': /^define name$/,
|
| 31 |
+
'help builder': /^help builder$/,
|
| 32 |
+
'define start': /^define start$/,
|
| 33 |
+
'edit dialog': /^edit dialog$/,
|
| 34 |
+
'show start': /^show start$/,
|
| 35 |
+
'hide start': /^hide start$/,
|
| 36 |
+
'define hint': /^define hint$/,
|
| 37 |
+
'finish': /^finish$/
|
| 38 |
+
};
|
| 39 |
+
|
| 40 |
+
var parse = util.genParseCommand(regexMap, 'processLevelBuilderCommand');
|
| 41 |
+
|
| 42 |
+
var LevelBuilder = Level.extend({
|
| 43 |
+
initialize: function(options) {
|
| 44 |
+
options = options || {};
|
| 45 |
+
options.level = {};
|
| 46 |
+
this.options = options;
|
| 47 |
+
|
| 48 |
+
var locale = LocaleStore.getLocale();
|
| 49 |
+
options.level.startDialog = {};
|
| 50 |
+
options.level.startDialog[locale] = {
|
| 51 |
+
childViews: intl.getDialog(require('../dialogs/levelBuilder'))
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
+
// if we are editing a level our behavior is a bit different
|
| 55 |
+
var editLevelJSON;
|
| 56 |
+
if (options.editLevel) {
|
| 57 |
+
editLevelJSON = LevelStore.getLevel(options.editLevel);
|
| 58 |
+
options.level = editLevelJSON;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
LevelBuilder.__super__.initialize.apply(this, [options]);
|
| 62 |
+
if (!options.editLevel) {
|
| 63 |
+
this.startDialogObj = undefined;
|
| 64 |
+
this.definedGoal = false;
|
| 65 |
+
} else {
|
| 66 |
+
this.startDialogObj = editLevelJSON.startDialog[locale];
|
| 67 |
+
this.definedGoal = true;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
// we won't be using this stuff, and it is deleted to ensure we overwrite all functions that
|
| 71 |
+
// include that functionality
|
| 72 |
+
delete this.treeCompare;
|
| 73 |
+
delete this.solved;
|
| 74 |
+
},
|
| 75 |
+
|
| 76 |
+
initName: function() {
|
| 77 |
+
},
|
| 78 |
+
|
| 79 |
+
initGoalData: function() {
|
| 80 |
+
// add some default behavior in the beginning if we are not editing
|
| 81 |
+
if (!this.options.editLevel) {
|
| 82 |
+
this.level.goalTreeString = '{"branches":{"main":{"target":"C1","id":"main"},"makeLevel":{"target":"C2","id":"makeLevel"}},"commits":{"C0":{"parents":[],"id":"C0","rootCommit":true},"C1":{"parents":["C0"],"id":"C1"},"C2":{"parents":["C1"],"id":"C2"}},"HEAD":{"target":"makeLevel","id":"HEAD"}}';
|
| 83 |
+
this.level.solutionCommand = 'git checkout -b makeLevel; git commit';
|
| 84 |
+
}
|
| 85 |
+
LevelBuilder.__super__.initGoalData.apply(this, arguments);
|
| 86 |
+
},
|
| 87 |
+
|
| 88 |
+
/**
|
| 89 |
+
* need custom handlers since we have two visualizations >___<
|
| 90 |
+
*/
|
| 91 |
+
minimizeGoal: function (position, size) {
|
| 92 |
+
this.doBothVis('hide');
|
| 93 |
+
this.goalWindowPos = position;
|
| 94 |
+
this.goalWindowSize = size;
|
| 95 |
+
if ($('#goalPlaceholder').is(':visible')) {
|
| 96 |
+
$('#goalPlaceholder').hide();
|
| 97 |
+
this.mainVis.myResize();
|
| 98 |
+
}
|
| 99 |
+
},
|
| 100 |
+
|
| 101 |
+
doBothVis: function(method) {
|
| 102 |
+
if (this.startVis) {
|
| 103 |
+
this.startVis[method].call(this.startVis);
|
| 104 |
+
}
|
| 105 |
+
if (this.goalVis) {
|
| 106 |
+
this.goalVis[method].call(this.goalVis);
|
| 107 |
+
}
|
| 108 |
+
},
|
| 109 |
+
|
| 110 |
+
resizeGoal: function () {
|
| 111 |
+
this.doBothVis('myResize');
|
| 112 |
+
},
|
| 113 |
+
|
| 114 |
+
initStartVisualization: function() {
|
| 115 |
+
this.startCanvasHolder = new CanvasTerminalHolder({
|
| 116 |
+
parent: this,
|
| 117 |
+
additionalClass: 'startTree',
|
| 118 |
+
text: intl.str('hide-start')
|
| 119 |
+
});
|
| 120 |
+
|
| 121 |
+
this.startVis = new Visualization({
|
| 122 |
+
el: this.startCanvasHolder.getCanvasLocation(),
|
| 123 |
+
containerElement: this.startCanvasHolder.getCanvasLocation(),
|
| 124 |
+
treeString: this.level.startTree,
|
| 125 |
+
noKeyboardInput: true,
|
| 126 |
+
smallCanvas: true,
|
| 127 |
+
noClick: true
|
| 128 |
+
});
|
| 129 |
+
return this.startCanvasHolder;
|
| 130 |
+
},
|
| 131 |
+
|
| 132 |
+
startOffCommand: function() {
|
| 133 |
+
Main.getEventBaton().trigger(
|
| 134 |
+
'commandSubmitted',
|
| 135 |
+
'echo :D'
|
| 136 |
+
);
|
| 137 |
+
},
|
| 138 |
+
|
| 139 |
+
objectiveDialog: function(command, deferred) {
|
| 140 |
+
var args = [
|
| 141 |
+
command,
|
| 142 |
+
deferred,
|
| 143 |
+
(this.startDialogObj === undefined) ?
|
| 144 |
+
null :
|
| 145 |
+
{
|
| 146 |
+
startDialog: {
|
| 147 |
+
'en_US': this.startDialogObj
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
];
|
| 151 |
+
LevelBuilder.__super__.objectiveDialog.apply(this, args);
|
| 152 |
+
},
|
| 153 |
+
|
| 154 |
+
initParseWaterfall: function(options) {
|
| 155 |
+
LevelBuilder.__super__.initParseWaterfall.apply(this, [options]);
|
| 156 |
+
|
| 157 |
+
this.parseWaterfall.addFirst(
|
| 158 |
+
'parseWaterfall',
|
| 159 |
+
parse
|
| 160 |
+
);
|
| 161 |
+
this.parseWaterfall.addFirst(
|
| 162 |
+
'instantWaterfall',
|
| 163 |
+
this.getInstantCommands()
|
| 164 |
+
);
|
| 165 |
+
},
|
| 166 |
+
|
| 167 |
+
buildLevel: function(command, deferred) {
|
| 168 |
+
this.exitLevel();
|
| 169 |
+
|
| 170 |
+
setTimeout(function() {
|
| 171 |
+
Main.getSandbox().buildLevel(command, deferred);
|
| 172 |
+
}, this.getAnimationTime() * 1.5);
|
| 173 |
+
},
|
| 174 |
+
|
| 175 |
+
getInstantCommands: function() {
|
| 176 |
+
return [
|
| 177 |
+
[/^help$|^\?$/, function() {
|
| 178 |
+
throw new Errors.CommandResult({
|
| 179 |
+
msg: intl.str('help-vague-builder')
|
| 180 |
+
});
|
| 181 |
+
}]
|
| 182 |
+
];
|
| 183 |
+
},
|
| 184 |
+
|
| 185 |
+
takeControl: function() {
|
| 186 |
+
Main.getEventBaton().stealBaton('processLevelBuilderCommand', this.processLevelBuilderCommand, this);
|
| 187 |
+
|
| 188 |
+
LevelBuilder.__super__.takeControl.apply(this);
|
| 189 |
+
},
|
| 190 |
+
|
| 191 |
+
releaseControl: function() {
|
| 192 |
+
Main.getEventBaton().releaseBaton('processLevelBuilderCommand', this.processLevelBuilderCommand, this);
|
| 193 |
+
|
| 194 |
+
LevelBuilder.__super__.releaseControl.apply(this);
|
| 195 |
+
},
|
| 196 |
+
|
| 197 |
+
showGoal: function() {
|
| 198 |
+
this.hideStart();
|
| 199 |
+
LevelBuilder.__super__.showGoal.apply(this, arguments);
|
| 200 |
+
},
|
| 201 |
+
|
| 202 |
+
showStart: function(command, deferred) {
|
| 203 |
+
this.hideGoal();
|
| 204 |
+
this.showSideVis(command, deferred, this.startCanvasHolder, this.initStartVisualization);
|
| 205 |
+
},
|
| 206 |
+
|
| 207 |
+
resetSolution: function() {
|
| 208 |
+
this.gitCommandsIssued = [];
|
| 209 |
+
this.level.solutionCommand = undefined;
|
| 210 |
+
},
|
| 211 |
+
|
| 212 |
+
hideStart: function(command, deferred) {
|
| 213 |
+
this.hideSideVis(command, deferred, this.startCanvasHolder);
|
| 214 |
+
},
|
| 215 |
+
|
| 216 |
+
defineStart: function(command, deferred) {
|
| 217 |
+
this.hideStart();
|
| 218 |
+
|
| 219 |
+
command.addWarning(intl.str('define-start-warning'));
|
| 220 |
+
this.resetSolution();
|
| 221 |
+
|
| 222 |
+
this.level.startTree = this.mainVis.gitEngine.printTree();
|
| 223 |
+
this.mainVis.resetFromThisTreeNow(this.level.startTree);
|
| 224 |
+
|
| 225 |
+
this.showStart(command, deferred);
|
| 226 |
+
},
|
| 227 |
+
|
| 228 |
+
defineGoal: function(command, deferred) {
|
| 229 |
+
this.hideGoal();
|
| 230 |
+
|
| 231 |
+
if (!this.gitCommandsIssued.length) {
|
| 232 |
+
command.set('error', new Errors.GitError({
|
| 233 |
+
msg: intl.str('solution-empty')
|
| 234 |
+
}));
|
| 235 |
+
deferred.resolve();
|
| 236 |
+
return;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
this.definedGoal = true;
|
| 240 |
+
this.level.solutionCommand = this.gitCommandsIssued.join(';');
|
| 241 |
+
this.level.goalTreeString = this.mainVis.gitEngine.printTree();
|
| 242 |
+
this.initGoalVisualization();
|
| 243 |
+
|
| 244 |
+
this.showGoal(command, deferred);
|
| 245 |
+
},
|
| 246 |
+
|
| 247 |
+
defineName: function(command, deferred) {
|
| 248 |
+
this.level.name = {
|
| 249 |
+
'en_US': prompt(intl.str('prompt-name'))
|
| 250 |
+
};
|
| 251 |
+
|
| 252 |
+
if (command) { command.finishWith(deferred); }
|
| 253 |
+
},
|
| 254 |
+
|
| 255 |
+
defineHint: function(command, deferred) {
|
| 256 |
+
this.level.hint = {
|
| 257 |
+
'en_US': prompt(intl.str('prompt-hint'))
|
| 258 |
+
};
|
| 259 |
+
if (command) { command.finishWith(deferred); }
|
| 260 |
+
},
|
| 261 |
+
|
| 262 |
+
editDialog: function(command, deferred) {
|
| 263 |
+
var whenDoneEditing = Q.defer();
|
| 264 |
+
this.currentBuilder = new MultiViewBuilder({
|
| 265 |
+
multiViewJSON: this.startDialogObj,
|
| 266 |
+
deferred: whenDoneEditing
|
| 267 |
+
});
|
| 268 |
+
whenDoneEditing.promise
|
| 269 |
+
.then(function(levelObj) {
|
| 270 |
+
this.startDialogObj = levelObj;
|
| 271 |
+
}.bind(this))
|
| 272 |
+
.fail(function() {
|
| 273 |
+
// nothing to do, they don't want to edit it apparently
|
| 274 |
+
})
|
| 275 |
+
.done(function() {
|
| 276 |
+
if (command) {
|
| 277 |
+
command.finishWith(deferred);
|
| 278 |
+
} else {
|
| 279 |
+
deferred.resolve();
|
| 280 |
+
}
|
| 281 |
+
});
|
| 282 |
+
},
|
| 283 |
+
|
| 284 |
+
finish: function(command, deferred) {
|
| 285 |
+
if (!this.options.editLevel && (!this.gitCommandsIssued.length || !this.definedGoal)) {
|
| 286 |
+
command.set('error', new Errors.GitError({
|
| 287 |
+
msg: intl.str('solution-empty')
|
| 288 |
+
}));
|
| 289 |
+
deferred.resolve();
|
| 290 |
+
return;
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
while (!this.level.name) {
|
| 294 |
+
this.defineName();
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
var mainDeferred = Q.defer();
|
| 298 |
+
var chain = mainDeferred.promise;
|
| 299 |
+
|
| 300 |
+
if (this.level.hint === undefined) {
|
| 301 |
+
var askForHintDeferred = Q.defer();
|
| 302 |
+
chain = chain.then(function() {
|
| 303 |
+
return askForHintDeferred.promise;
|
| 304 |
+
});
|
| 305 |
+
|
| 306 |
+
// ask for a hint if there is none
|
| 307 |
+
var askForHintView = new ConfirmCancelTerminal({
|
| 308 |
+
markdowns: [
|
| 309 |
+
intl.str('want-hint')
|
| 310 |
+
]
|
| 311 |
+
});
|
| 312 |
+
askForHintView.getPromise()
|
| 313 |
+
.then(this.defineHint.bind(this))
|
| 314 |
+
.fail(function() {
|
| 315 |
+
this.level.hint = {'en_US': ''};
|
| 316 |
+
}.bind(this))
|
| 317 |
+
.done(function() {
|
| 318 |
+
askForHintDeferred.resolve();
|
| 319 |
+
});
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
if (this.startDialogObj === undefined) {
|
| 323 |
+
var askForStartDeferred = Q.defer();
|
| 324 |
+
chain = chain.then(function() {
|
| 325 |
+
return askForStartDeferred.promise;
|
| 326 |
+
});
|
| 327 |
+
|
| 328 |
+
var askForStartView = new ConfirmCancelTerminal({
|
| 329 |
+
markdowns: [
|
| 330 |
+
intl.str('want-start-dialog')
|
| 331 |
+
]
|
| 332 |
+
});
|
| 333 |
+
askForStartView.getPromise()
|
| 334 |
+
.then(function() {
|
| 335 |
+
// oh boy this is complex
|
| 336 |
+
var whenEditedDialog = Q.defer();
|
| 337 |
+
// the undefined here is the command that doesn't need resolving just yet...
|
| 338 |
+
this.editDialog(undefined, whenEditedDialog);
|
| 339 |
+
return whenEditedDialog.promise;
|
| 340 |
+
}.bind(this))
|
| 341 |
+
.fail(function() {
|
| 342 |
+
// if they don't want to edit the start dialog, do nothing
|
| 343 |
+
})
|
| 344 |
+
.done(function() {
|
| 345 |
+
askForStartDeferred.resolve();
|
| 346 |
+
});
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
chain = chain.done(function() {
|
| 350 |
+
// ok great! lets just give them the goods
|
| 351 |
+
new MarkdownPresenter({
|
| 352 |
+
fillerText: JSON.stringify(this.getExportObj(), null, 2),
|
| 353 |
+
previewText: intl.str('share-json')
|
| 354 |
+
});
|
| 355 |
+
command.finishWith(deferred);
|
| 356 |
+
}.bind(this));
|
| 357 |
+
|
| 358 |
+
mainDeferred.resolve();
|
| 359 |
+
},
|
| 360 |
+
|
| 361 |
+
getExportObj: function() {
|
| 362 |
+
var compiledLevel = Object.assign(
|
| 363 |
+
{},
|
| 364 |
+
this.level
|
| 365 |
+
);
|
| 366 |
+
// the start dialog now is just our help intro thing
|
| 367 |
+
delete compiledLevel.startDialog;
|
| 368 |
+
if (this.startDialogObj) {
|
| 369 |
+
compiledLevel.startDialog = {'en_US': this.startDialogObj};
|
| 370 |
+
}
|
| 371 |
+
return compiledLevel;
|
| 372 |
+
},
|
| 373 |
+
|
| 374 |
+
processLevelBuilderCommand: function(command, deferred) {
|
| 375 |
+
var methodMap = {
|
| 376 |
+
'define goal': this.defineGoal,
|
| 377 |
+
'define start': this.defineStart,
|
| 378 |
+
'show start': this.showStart,
|
| 379 |
+
'hide start': this.hideStart,
|
| 380 |
+
'finish': this.finish,
|
| 381 |
+
'define hint': this.defineHint,
|
| 382 |
+
'define name': this.defineName,
|
| 383 |
+
'edit dialog': this.editDialog,
|
| 384 |
+
'help builder': LevelBuilder.__super__.startDialog
|
| 385 |
+
};
|
| 386 |
+
if (!methodMap[command.get('method')]) {
|
| 387 |
+
throw new Error('woah we don\'t support that method yet');
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
methodMap[command.get('method')].apply(this, arguments);
|
| 391 |
+
},
|
| 392 |
+
|
| 393 |
+
afterCommandDefer: function(defer, command) {
|
| 394 |
+
// we don't need to compare against the goal anymore
|
| 395 |
+
defer.resolve();
|
| 396 |
+
},
|
| 397 |
+
|
| 398 |
+
die: function() {
|
| 399 |
+
this.hideStart();
|
| 400 |
+
LevelBuilder.__super__.die.apply(this, arguments);
|
| 401 |
+
|
| 402 |
+
delete this.startVis;
|
| 403 |
+
delete this.startCanvasHolder;
|
| 404 |
+
}
|
| 405 |
+
});
|
| 406 |
+
|
| 407 |
+
exports.LevelBuilder = LevelBuilder;
|
| 408 |
+
exports.regexMap = regexMap;
|
src/js/level/disabledMap.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var intl = require('../intl');
|
| 2 |
+
|
| 3 |
+
var Commands = require('../commands');
|
| 4 |
+
|
| 5 |
+
var Errors = require('../util/errors');
|
| 6 |
+
var GitError = Errors.GitError;
|
| 7 |
+
|
| 8 |
+
function DisabledMap(options) {
|
| 9 |
+
options = options || {};
|
| 10 |
+
this.disabledMap = options.disabledMap || {
|
| 11 |
+
'git cherry-pick': true,
|
| 12 |
+
'git rebase': true
|
| 13 |
+
};
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
DisabledMap.prototype.getInstantCommands = function() {
|
| 17 |
+
// this produces an array of regex / function pairs that can be
|
| 18 |
+
// piped into a parse waterfall to disable certain git commands
|
| 19 |
+
// :D
|
| 20 |
+
var instants = [];
|
| 21 |
+
var onMatch = function() {
|
| 22 |
+
throw new GitError({
|
| 23 |
+
msg: intl.str('command-disabled')
|
| 24 |
+
});
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
+
Object.keys(this.disabledMap).forEach(function(disabledCommand) {
|
| 28 |
+
// XXX get hold of vcs from disabledMap
|
| 29 |
+
var vcs = 'git';
|
| 30 |
+
disabledCommand = disabledCommand.slice(vcs.length + 1);
|
| 31 |
+
var gitRegex = Commands.commands.getRegexMap()[vcs][disabledCommand];
|
| 32 |
+
if (!gitRegex) {
|
| 33 |
+
throw new Error('wuttttt this disbaled command' + disabledCommand +
|
| 34 |
+
' has no regex matching');
|
| 35 |
+
}
|
| 36 |
+
instants.push([gitRegex, onMatch]);
|
| 37 |
+
}.bind(this));
|
| 38 |
+
return instants;
|
| 39 |
+
};
|
| 40 |
+
|
| 41 |
+
exports.DisabledMap = DisabledMap;
|
| 42 |
+
|
src/js/level/index.js
ADDED
|
@@ -0,0 +1,672 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var Q = require('q');
|
| 2 |
+
var React = require('react');
|
| 3 |
+
var ReactDOM = require('react-dom');
|
| 4 |
+
|
| 5 |
+
var util = require('../util');
|
| 6 |
+
var Main = require('../app');
|
| 7 |
+
var intl = require('../intl');
|
| 8 |
+
var log = require('../log');
|
| 9 |
+
|
| 10 |
+
var Errors = require('../util/errors');
|
| 11 |
+
var Sandbox = require('../sandbox/').Sandbox;
|
| 12 |
+
var GlobalStateActions = require('../actions/GlobalStateActions');
|
| 13 |
+
var GlobalStateStore = require('../stores/GlobalStateStore');
|
| 14 |
+
var LevelActions = require('../actions/LevelActions');
|
| 15 |
+
var LevelStore = require('../stores/LevelStore');
|
| 16 |
+
var Visualization = require('../visuals/visualization').Visualization;
|
| 17 |
+
var DisabledMap = require('../level/disabledMap').DisabledMap;
|
| 18 |
+
var GitShim = require('../git/gitShim').GitShim;
|
| 19 |
+
var Commands = require('../commands');
|
| 20 |
+
|
| 21 |
+
var MultiView = require('../views/multiView').MultiView;
|
| 22 |
+
var CanvasTerminalHolder = require('../views').CanvasTerminalHolder;
|
| 23 |
+
var ConfirmCancelTerminal = require('../views').ConfirmCancelTerminal;
|
| 24 |
+
var NextLevelConfirm = require('../views').NextLevelConfirm;
|
| 25 |
+
var LevelToolbarView = require('../react_views/LevelToolbarView.jsx');
|
| 26 |
+
|
| 27 |
+
var TreeCompare = require('../graph/treeCompare');
|
| 28 |
+
|
| 29 |
+
var regexMap = {
|
| 30 |
+
'help level': /^help level$/,
|
| 31 |
+
'start dialog': /^start dialog$/,
|
| 32 |
+
'show goal': /^(show goal|goal|help goal)$/,
|
| 33 |
+
'hide goal': /^hide goal$/,
|
| 34 |
+
'show solution': /^show solution($|\s)/,
|
| 35 |
+
'objective': /^(objective|assignment)$/
|
| 36 |
+
};
|
| 37 |
+
|
| 38 |
+
var parse = util.genParseCommand(regexMap, 'processLevelCommand');
|
| 39 |
+
|
| 40 |
+
var Level = Sandbox.extend({
|
| 41 |
+
initialize: function(options) {
|
| 42 |
+
options = options || {};
|
| 43 |
+
options.level = options.level || {};
|
| 44 |
+
|
| 45 |
+
this.level = options.level;
|
| 46 |
+
|
| 47 |
+
this.gitCommandsIssued = [];
|
| 48 |
+
this.solved = false;
|
| 49 |
+
this.wasResetAfterSolved = false;
|
| 50 |
+
|
| 51 |
+
this.initGoalData(options);
|
| 52 |
+
this.initName(options);
|
| 53 |
+
this.on('minimizeCanvas', this.minimizeGoal);
|
| 54 |
+
this.on('resizeCanvas', this.resizeGoal);
|
| 55 |
+
this.isGoalExpanded = false;
|
| 56 |
+
|
| 57 |
+
Level.__super__.initialize.apply(this, [options]);
|
| 58 |
+
this.startOffCommand();
|
| 59 |
+
|
| 60 |
+
this.handleOpen(options.deferred);
|
| 61 |
+
},
|
| 62 |
+
|
| 63 |
+
getIsGoalExpanded: function() {
|
| 64 |
+
return this.isGoalExpanded;
|
| 65 |
+
},
|
| 66 |
+
|
| 67 |
+
handleOpen: function(deferred) {
|
| 68 |
+
deferred = deferred || Q.defer();
|
| 69 |
+
|
| 70 |
+
// if there is a multiview in the beginning, open that
|
| 71 |
+
// and let it resolve our deferred
|
| 72 |
+
if (GlobalStateStore.getShouldDisableLevelInstructions()) {
|
| 73 |
+
setTimeout(function() {
|
| 74 |
+
deferred.resolve();
|
| 75 |
+
}, 100);
|
| 76 |
+
return;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
if (this.level.startDialog && !this.testOption('noIntroDialog')) {
|
| 80 |
+
new MultiView(Object.assign(
|
| 81 |
+
{},
|
| 82 |
+
intl.getStartDialog(this.level),
|
| 83 |
+
{ deferred: deferred }
|
| 84 |
+
));
|
| 85 |
+
return;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
// otherwise, resolve after a 700 second delay to allow
|
| 89 |
+
// for us to animate easily
|
| 90 |
+
setTimeout(function() {
|
| 91 |
+
deferred.resolve();
|
| 92 |
+
}, this.getAnimationTime() * 1.2);
|
| 93 |
+
},
|
| 94 |
+
|
| 95 |
+
objectiveDialog: function(command, deferred, levelObj) {
|
| 96 |
+
levelObj = (levelObj === undefined) ? this.level : levelObj;
|
| 97 |
+
|
| 98 |
+
if (!levelObj || !levelObj.startDialog) {
|
| 99 |
+
command.set('error', new Errors.GitError({
|
| 100 |
+
msg: intl.str('no-start-dialog')
|
| 101 |
+
}));
|
| 102 |
+
deferred.resolve();
|
| 103 |
+
return;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
var dialog = $.extend({}, intl.getStartDialog(levelObj));
|
| 107 |
+
// grab the last slide only
|
| 108 |
+
dialog.childViews = dialog.childViews.slice(-1);
|
| 109 |
+
new MultiView(Object.assign(
|
| 110 |
+
dialog,
|
| 111 |
+
{ deferred: deferred }
|
| 112 |
+
));
|
| 113 |
+
|
| 114 |
+
// when its closed we are done
|
| 115 |
+
deferred.promise.then(function() {
|
| 116 |
+
command.set('status', 'finished');
|
| 117 |
+
});
|
| 118 |
+
},
|
| 119 |
+
|
| 120 |
+
startDialog: function(command, deferred) {
|
| 121 |
+
if (!this.level.startDialog) {
|
| 122 |
+
command.set('error', new Errors.GitError({
|
| 123 |
+
msg: intl.str('no-start-dialog')
|
| 124 |
+
}));
|
| 125 |
+
deferred.resolve();
|
| 126 |
+
return;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
this.handleOpen(deferred);
|
| 130 |
+
deferred.promise.then(function() {
|
| 131 |
+
command.set('status', 'finished');
|
| 132 |
+
});
|
| 133 |
+
},
|
| 134 |
+
|
| 135 |
+
getEnglishName: function() {
|
| 136 |
+
return this.level.name.en_US;
|
| 137 |
+
},
|
| 138 |
+
|
| 139 |
+
initName: function() {
|
| 140 |
+
var name = intl.getName(this.level);
|
| 141 |
+
this.levelToolbar = React.createElement(
|
| 142 |
+
LevelToolbarView,
|
| 143 |
+
{
|
| 144 |
+
name: name,
|
| 145 |
+
onGoalClick: this.toggleGoal.bind(this),
|
| 146 |
+
onObjectiveClick: this.toggleObjective.bind(this),
|
| 147 |
+
parent: this
|
| 148 |
+
}
|
| 149 |
+
);
|
| 150 |
+
ReactDOM.render(
|
| 151 |
+
this.levelToolbar,
|
| 152 |
+
document.getElementById('levelToolbarMount')
|
| 153 |
+
);
|
| 154 |
+
},
|
| 155 |
+
|
| 156 |
+
initGoalData: function(options) {
|
| 157 |
+
if (!this.level.goalTreeString || !this.level.solutionCommand) {
|
| 158 |
+
throw new Error('need goal tree and solution');
|
| 159 |
+
}
|
| 160 |
+
},
|
| 161 |
+
|
| 162 |
+
takeControl: function() {
|
| 163 |
+
Main.getEventBaton().stealBaton('processLevelCommand', this.processLevelCommand, this);
|
| 164 |
+
|
| 165 |
+
Level.__super__.takeControl.apply(this);
|
| 166 |
+
},
|
| 167 |
+
|
| 168 |
+
releaseControl: function() {
|
| 169 |
+
Main.getEventBaton().releaseBaton('processLevelCommand', this.processLevelCommand, this);
|
| 170 |
+
|
| 171 |
+
Level.__super__.releaseControl.apply(this);
|
| 172 |
+
},
|
| 173 |
+
|
| 174 |
+
startOffCommand: function() {
|
| 175 |
+
var method = this.options.command.get('method');
|
| 176 |
+
if (GlobalStateStore.getShouldDisableLevelInstructions()) {
|
| 177 |
+
Main.getEventBaton().trigger(
|
| 178 |
+
'commandSubmitted',
|
| 179 |
+
'hint; show goal'
|
| 180 |
+
);
|
| 181 |
+
return;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
if (!this.testOption('noStartCommand') && method !== 'importLevelNow') {
|
| 185 |
+
Main.getEventBaton().trigger(
|
| 186 |
+
'commandSubmitted',
|
| 187 |
+
'hint; delay 2000; show goal'
|
| 188 |
+
);
|
| 189 |
+
}
|
| 190 |
+
},
|
| 191 |
+
|
| 192 |
+
initVisualization: function(options) {
|
| 193 |
+
this.mainVis = new Visualization({
|
| 194 |
+
el: options.el || this.getDefaultVisEl(),
|
| 195 |
+
treeString: options.level.startTree
|
| 196 |
+
});
|
| 197 |
+
},
|
| 198 |
+
|
| 199 |
+
initGoalVisualization: function() {
|
| 200 |
+
var onlyMain = TreeCompare.onlyMainCompared(this.level);
|
| 201 |
+
// first we make the goal visualization holder
|
| 202 |
+
this.goalCanvasHolder = new CanvasTerminalHolder({
|
| 203 |
+
text: (onlyMain) ? intl.str('goal-only-main') : undefined,
|
| 204 |
+
parent: this
|
| 205 |
+
});
|
| 206 |
+
|
| 207 |
+
// then we make a visualization. the "el" here is the element to
|
| 208 |
+
// track for size information. the container is where the canvas will be placed
|
| 209 |
+
this.goalVis = new Visualization({
|
| 210 |
+
el: this.goalCanvasHolder.getCanvasLocation(),
|
| 211 |
+
containerElement: this.goalCanvasHolder.getCanvasLocation(),
|
| 212 |
+
treeString: this.level.goalTreeString,
|
| 213 |
+
noKeyboardInput: true,
|
| 214 |
+
smallCanvas: true,
|
| 215 |
+
isGoalVis: true,
|
| 216 |
+
levelBlob: this.level,
|
| 217 |
+
noClick: true
|
| 218 |
+
});
|
| 219 |
+
|
| 220 |
+
// If the goal visualization gets dragged to the right side of the screen, then squeeze the main
|
| 221 |
+
// repo visualization a bit to make room. This way, you could have the goal window hang out on
|
| 222 |
+
// the right side of the screen and still see the repo visualization.
|
| 223 |
+
this.goalVis.customEvents.on('drag', function(event, ui) {
|
| 224 |
+
if (ui.position.left > 0.5 * $(window).width()) {
|
| 225 |
+
if (!$('#goalPlaceholder').is(':visible')) {
|
| 226 |
+
$('#goalPlaceholder').show();
|
| 227 |
+
this.mainVis.myResize();
|
| 228 |
+
}
|
| 229 |
+
} else {
|
| 230 |
+
if ($('#goalPlaceholder').is(':visible')) {
|
| 231 |
+
$('#goalPlaceholder').hide();
|
| 232 |
+
this.mainVis.myResize();
|
| 233 |
+
}
|
| 234 |
+
}
|
| 235 |
+
}.bind(this));
|
| 236 |
+
|
| 237 |
+
return this.goalCanvasHolder;
|
| 238 |
+
},
|
| 239 |
+
|
| 240 |
+
minimizeGoal: function (position, size) {
|
| 241 |
+
this.isGoalExpanded = false;
|
| 242 |
+
this.trigger('goalToggled');
|
| 243 |
+
this.goalVis.hide();
|
| 244 |
+
this.goalWindowPos = position;
|
| 245 |
+
this.goalWindowSize = size;
|
| 246 |
+
if ($('#goalPlaceholder').is(':visible')) {
|
| 247 |
+
$('#goalPlaceholder').hide();
|
| 248 |
+
this.mainVis.myResize();
|
| 249 |
+
}
|
| 250 |
+
},
|
| 251 |
+
|
| 252 |
+
resizeGoal: function () {
|
| 253 |
+
if (!this.goalVis) {
|
| 254 |
+
return;
|
| 255 |
+
}
|
| 256 |
+
this.goalVis.myResize();
|
| 257 |
+
},
|
| 258 |
+
|
| 259 |
+
showSolution: function(command, deferred) {
|
| 260 |
+
var toIssue = this.level.solutionCommand;
|
| 261 |
+
var issueFunc = function() {
|
| 262 |
+
this.isShowingSolution = true;
|
| 263 |
+
Main.getEventBaton().trigger(
|
| 264 |
+
'commandSubmitted',
|
| 265 |
+
toIssue
|
| 266 |
+
);
|
| 267 |
+
log.showLevelSolution(this.getEnglishName());
|
| 268 |
+
}.bind(this);
|
| 269 |
+
|
| 270 |
+
var commandStr = command.get('rawStr');
|
| 271 |
+
if (!this.testOptionOnString(commandStr, 'noReset')) {
|
| 272 |
+
toIssue = 'reset --forSolution; ' + toIssue;
|
| 273 |
+
}
|
| 274 |
+
if (this.testOptionOnString(commandStr, 'force')) {
|
| 275 |
+
issueFunc();
|
| 276 |
+
command.finishWith(deferred);
|
| 277 |
+
return;
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
// allow them for force the solution
|
| 281 |
+
var confirmDefer = Q.defer();
|
| 282 |
+
var dialog = intl.getDialog(require('../dialogs/confirmShowSolution'))[0];
|
| 283 |
+
var confirmView = new ConfirmCancelTerminal({
|
| 284 |
+
markdowns: dialog.options.markdowns,
|
| 285 |
+
deferred: confirmDefer
|
| 286 |
+
});
|
| 287 |
+
|
| 288 |
+
confirmDefer.promise
|
| 289 |
+
.then(issueFunc)
|
| 290 |
+
.fail(function() {
|
| 291 |
+
command.setResult("");
|
| 292 |
+
})
|
| 293 |
+
.done(function() {
|
| 294 |
+
// either way we animate, so both options can share this logic
|
| 295 |
+
setTimeout(function() {
|
| 296 |
+
command.finishWith(deferred);
|
| 297 |
+
}, confirmView.getAnimationTime());
|
| 298 |
+
});
|
| 299 |
+
},
|
| 300 |
+
|
| 301 |
+
toggleObjective: function() {
|
| 302 |
+
Main.getEventBaton().trigger(
|
| 303 |
+
'commandSubmitted',
|
| 304 |
+
'objective'
|
| 305 |
+
);
|
| 306 |
+
},
|
| 307 |
+
|
| 308 |
+
toggleGoal: function () {
|
| 309 |
+
if (this.goalCanvasHolder && this.goalCanvasHolder.inDom) {
|
| 310 |
+
this.hideGoal();
|
| 311 |
+
} else {
|
| 312 |
+
this.showGoal();
|
| 313 |
+
}
|
| 314 |
+
},
|
| 315 |
+
|
| 316 |
+
showGoal: function(command, defer) {
|
| 317 |
+
this.isGoalExpanded = true;
|
| 318 |
+
this.trigger('goalToggled');
|
| 319 |
+
this.showSideVis(command, defer, this.goalCanvasHolder, this.initGoalVisualization);
|
| 320 |
+
// show the squeezer again we are to the side
|
| 321 |
+
if ($(this.goalVis.el).offset().left > 0.5 * $(window).width()) {
|
| 322 |
+
$('#goalPlaceholder').show();
|
| 323 |
+
this.mainVis.myResize();
|
| 324 |
+
}
|
| 325 |
+
},
|
| 326 |
+
|
| 327 |
+
showSideVis: function(command, defer, canvasHolder, initMethod) {
|
| 328 |
+
var safeFinish = function() {
|
| 329 |
+
if (command) { command.finishWith(defer); }
|
| 330 |
+
};
|
| 331 |
+
if (!canvasHolder || !canvasHolder.inDom) {
|
| 332 |
+
canvasHolder = initMethod.apply(this);
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
canvasHolder.restore(this.goalWindowPos, this.goalWindowSize);
|
| 336 |
+
setTimeout(safeFinish, canvasHolder.getAnimationTime());
|
| 337 |
+
},
|
| 338 |
+
|
| 339 |
+
hideGoal: function(command, defer) {
|
| 340 |
+
this.isGoalExpanded = false;
|
| 341 |
+
this.trigger('goalToggled');
|
| 342 |
+
this.hideSideVis(command, defer, this.goalCanvasHolder);
|
| 343 |
+
},
|
| 344 |
+
|
| 345 |
+
hideSideVis: function(command, defer, canvasHolder, vis) {
|
| 346 |
+
var safeFinish = function() {
|
| 347 |
+
if (command) { command.finishWith(defer); }
|
| 348 |
+
};
|
| 349 |
+
|
| 350 |
+
if (canvasHolder && canvasHolder.inDom) {
|
| 351 |
+
canvasHolder.die();
|
| 352 |
+
setTimeout(safeFinish, canvasHolder.getAnimationTime());
|
| 353 |
+
} else {
|
| 354 |
+
safeFinish();
|
| 355 |
+
}
|
| 356 |
+
},
|
| 357 |
+
|
| 358 |
+
initParseWaterfall: function(options) {
|
| 359 |
+
Level.__super__.initParseWaterfall.apply(this, [options]);
|
| 360 |
+
|
| 361 |
+
// add our specific functionality
|
| 362 |
+
this.parseWaterfall.addFirst(
|
| 363 |
+
'parseWaterfall',
|
| 364 |
+
parse
|
| 365 |
+
);
|
| 366 |
+
|
| 367 |
+
this.parseWaterfall.addFirst(
|
| 368 |
+
'instantWaterfall',
|
| 369 |
+
this.getInstantCommands()
|
| 370 |
+
);
|
| 371 |
+
|
| 372 |
+
// if we want to disable certain commands...
|
| 373 |
+
if (options.level.disabledMap) {
|
| 374 |
+
// disable these other commands
|
| 375 |
+
this.parseWaterfall.addFirst(
|
| 376 |
+
'instantWaterfall',
|
| 377 |
+
new DisabledMap({
|
| 378 |
+
disabledMap: options.level.disabledMap
|
| 379 |
+
}).getInstantCommands()
|
| 380 |
+
);
|
| 381 |
+
}
|
| 382 |
+
},
|
| 383 |
+
|
| 384 |
+
initGitShim: function(options) {
|
| 385 |
+
// ok we definitely want a shim here
|
| 386 |
+
this.gitShim = new GitShim({
|
| 387 |
+
beforeCB: this.beforeCommandCB.bind(this),
|
| 388 |
+
afterCB: this.afterCommandCB.bind(this),
|
| 389 |
+
afterDeferHandler: this.afterCommandDefer.bind(this)
|
| 390 |
+
});
|
| 391 |
+
},
|
| 392 |
+
|
| 393 |
+
undo: function() {
|
| 394 |
+
this.gitCommandsIssued.pop();
|
| 395 |
+
Level.__super__.undo.apply(this, arguments);
|
| 396 |
+
},
|
| 397 |
+
|
| 398 |
+
beforeCommandCB: function(command) {
|
| 399 |
+
// Alright we actually no-op this in the level subclass
|
| 400 |
+
// so we can tell if the command counted or not... kinda :P
|
| 401 |
+
// We have to save the state in this method since the git
|
| 402 |
+
// engine will change by the time afterCommandCB runs
|
| 403 |
+
this._treeBeforeCommand = this.mainVis.gitEngine.printTree();
|
| 404 |
+
},
|
| 405 |
+
|
| 406 |
+
afterCommandCB: function(command) {
|
| 407 |
+
if (this.doesCommandCountTowardsTotal(command)) {
|
| 408 |
+
// Count it as a command AND...
|
| 409 |
+
this.gitCommandsIssued.push(command.get('rawStr'));
|
| 410 |
+
// add our state for undo since our undo pops a command.
|
| 411 |
+
//
|
| 412 |
+
// Ugly inheritance overriding on private implementations ahead!
|
| 413 |
+
this.undoStack.push(this._treeBeforeCommand);
|
| 414 |
+
}
|
| 415 |
+
},
|
| 416 |
+
|
| 417 |
+
doesCommandCountTowardsTotal: function(command) {
|
| 418 |
+
if (command.get('error')) {
|
| 419 |
+
// don't count errors towards our count
|
| 420 |
+
return false;
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
var matched = false;
|
| 424 |
+
var commandsThatCount = Commands.commands.getCommandsThatCount();
|
| 425 |
+
Object.values(commandsThatCount).forEach(function(map) {
|
| 426 |
+
Object.values(map).forEach(function(regex) {
|
| 427 |
+
matched = matched || regex.test(command.get('rawStr'));
|
| 428 |
+
});
|
| 429 |
+
});
|
| 430 |
+
return matched;
|
| 431 |
+
},
|
| 432 |
+
|
| 433 |
+
afterCommandDefer: function(defer, command) {
|
| 434 |
+
if (this.solved) {
|
| 435 |
+
command.addWarning(intl.str('already-solved'));
|
| 436 |
+
defer.resolve();
|
| 437 |
+
return;
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
var current = this.mainVis.gitEngine.printTree();
|
| 441 |
+
var solved = TreeCompare.dispatchFromLevel(this.level, current);
|
| 442 |
+
|
| 443 |
+
if (!solved) {
|
| 444 |
+
defer.resolve();
|
| 445 |
+
return;
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
// woohoo!!! they solved the level, lets animate and such
|
| 449 |
+
this.levelSolved(defer);
|
| 450 |
+
},
|
| 451 |
+
|
| 452 |
+
getNumSolutionCommands: function() {
|
| 453 |
+
// strip semicolons in bad places
|
| 454 |
+
var toAnalyze = this.level.solutionCommand.replace(/^;|;$/g, '');
|
| 455 |
+
return toAnalyze.split(';').length;
|
| 456 |
+
},
|
| 457 |
+
|
| 458 |
+
testOption: function(option) {
|
| 459 |
+
return this.options.command && new RegExp('--' + option).test(this.options.command.get('rawStr'));
|
| 460 |
+
},
|
| 461 |
+
|
| 462 |
+
testOptionOnString: function(str, option) {
|
| 463 |
+
return str && new RegExp('--' + option).test(str);
|
| 464 |
+
},
|
| 465 |
+
|
| 466 |
+
levelSolved: function(defer) {
|
| 467 |
+
this.solved = true;
|
| 468 |
+
if (!this.isShowingSolution) {
|
| 469 |
+
LevelActions.setLevelSolved(this.level.id);
|
| 470 |
+
log.levelSolved(this.getEnglishName());
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
this.hideGoal();
|
| 474 |
+
|
| 475 |
+
var nextLevel = LevelStore.getNextLevel(this.level.id);
|
| 476 |
+
var numCommands = this.gitCommandsIssued.length;
|
| 477 |
+
var best = this.getNumSolutionCommands();
|
| 478 |
+
|
| 479 |
+
var skipFinishDialog = this.testOption('noFinishDialog') ||
|
| 480 |
+
this.wasResetAfterSolved;
|
| 481 |
+
var skipFinishAnimation = this.wasResetAfterSolved;
|
| 482 |
+
|
| 483 |
+
if (!skipFinishAnimation) {
|
| 484 |
+
GlobalStateActions.levelSolved();
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
/**
|
| 488 |
+
* Speed up the animation each time we see it.
|
| 489 |
+
*/
|
| 490 |
+
var speed = 1.0;
|
| 491 |
+
switch (GlobalStateStore.getNumLevelsSolved()) {
|
| 492 |
+
case 2:
|
| 493 |
+
speed = 1.5;
|
| 494 |
+
break;
|
| 495 |
+
case 3:
|
| 496 |
+
speed = 1.8;
|
| 497 |
+
break;
|
| 498 |
+
case 4:
|
| 499 |
+
speed = 2.1;
|
| 500 |
+
break;
|
| 501 |
+
case 5:
|
| 502 |
+
speed = 2.4;
|
| 503 |
+
break;
|
| 504 |
+
}
|
| 505 |
+
if (GlobalStateStore.getNumLevelsSolved() > 5) {
|
| 506 |
+
speed = 2.5;
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
var finishAnimationChain = null;
|
| 510 |
+
if (skipFinishAnimation) {
|
| 511 |
+
var deferred = Q.defer();
|
| 512 |
+
deferred.resolve();
|
| 513 |
+
finishAnimationChain = deferred.promise;
|
| 514 |
+
Main.getEventBaton().trigger(
|
| 515 |
+
'commandSubmitted',
|
| 516 |
+
'echo "level solved! type in \'levels\' to access the next level"'
|
| 517 |
+
);
|
| 518 |
+
} else {
|
| 519 |
+
GlobalStateActions.changeIsAnimating(true);
|
| 520 |
+
finishAnimationChain = this.mainVis.gitVisuals.finishAnimation(speed);
|
| 521 |
+
if (this.mainVis.originVis) {
|
| 522 |
+
finishAnimationChain = finishAnimationChain.then(
|
| 523 |
+
this.mainVis.originVis.gitVisuals.finishAnimation(speed)
|
| 524 |
+
);
|
| 525 |
+
}
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
if (!skipFinishDialog) {
|
| 529 |
+
finishAnimationChain = finishAnimationChain.then(function() {
|
| 530 |
+
// we want to ask if they will move onto the next level
|
| 531 |
+
// while giving them their results...
|
| 532 |
+
var nextDialog = new NextLevelConfirm({
|
| 533 |
+
nextLevel: nextLevel,
|
| 534 |
+
numCommands: numCommands,
|
| 535 |
+
best: best
|
| 536 |
+
});
|
| 537 |
+
|
| 538 |
+
return nextDialog.getPromise();
|
| 539 |
+
});
|
| 540 |
+
}
|
| 541 |
+
|
| 542 |
+
finishAnimationChain
|
| 543 |
+
.then(function() {
|
| 544 |
+
if (!skipFinishDialog && nextLevel) {
|
| 545 |
+
log.choseNextLevel(nextLevel.id);
|
| 546 |
+
Main.getEventBaton().trigger(
|
| 547 |
+
'commandSubmitted',
|
| 548 |
+
'level ' + nextLevel.id
|
| 549 |
+
);
|
| 550 |
+
}
|
| 551 |
+
})
|
| 552 |
+
.fail(function() {
|
| 553 |
+
// nothing to do, we will just close
|
| 554 |
+
})
|
| 555 |
+
.done(function() {
|
| 556 |
+
GlobalStateActions.changeIsAnimating(false);
|
| 557 |
+
defer.resolve();
|
| 558 |
+
});
|
| 559 |
+
},
|
| 560 |
+
|
| 561 |
+
die: function() {
|
| 562 |
+
ReactDOM.unmountComponentAtNode(
|
| 563 |
+
document.getElementById('levelToolbarMount')
|
| 564 |
+
);
|
| 565 |
+
|
| 566 |
+
this.hideGoal();
|
| 567 |
+
this.mainVis.die();
|
| 568 |
+
this.releaseControl();
|
| 569 |
+
|
| 570 |
+
this.clear();
|
| 571 |
+
|
| 572 |
+
delete this.commandCollection;
|
| 573 |
+
delete this.mainVis;
|
| 574 |
+
delete this.goalVis;
|
| 575 |
+
delete this.goalCanvasHolder;
|
| 576 |
+
},
|
| 577 |
+
|
| 578 |
+
getInstantCommands: function() {
|
| 579 |
+
var getHint = function() {
|
| 580 |
+
var hint = intl.getHint(this.level);
|
| 581 |
+
if (!hint || !hint.length) {
|
| 582 |
+
return intl.str('no-hint');
|
| 583 |
+
}
|
| 584 |
+
return hint;
|
| 585 |
+
}.bind(this);
|
| 586 |
+
|
| 587 |
+
return [
|
| 588 |
+
[/^help$|^\?$/, function() {
|
| 589 |
+
throw new Errors.CommandResult({
|
| 590 |
+
msg: intl.str('help-vague-level')
|
| 591 |
+
});
|
| 592 |
+
}],
|
| 593 |
+
[/^hint$/, function() {
|
| 594 |
+
throw new Errors.CommandResult({
|
| 595 |
+
msg: getHint()
|
| 596 |
+
});
|
| 597 |
+
}]
|
| 598 |
+
];
|
| 599 |
+
},
|
| 600 |
+
|
| 601 |
+
reset: function(command, deferred) {
|
| 602 |
+
this.gitCommandsIssued = [];
|
| 603 |
+
|
| 604 |
+
var commandStr = (command) ? command.get('rawStr') : '';
|
| 605 |
+
if (!this.testOptionOnString(commandStr, 'forSolution')) {
|
| 606 |
+
this.isShowingSolution = false;
|
| 607 |
+
}
|
| 608 |
+
if (this.solved) {
|
| 609 |
+
this.wasResetAfterSolved = true;
|
| 610 |
+
}
|
| 611 |
+
this.solved = false;
|
| 612 |
+
Level.__super__.reset.apply(this, arguments);
|
| 613 |
+
},
|
| 614 |
+
|
| 615 |
+
buildLevel: function(command, deferred) {
|
| 616 |
+
this.exitLevel();
|
| 617 |
+
setTimeout(function() {
|
| 618 |
+
Main.getSandbox().buildLevel(command, deferred);
|
| 619 |
+
}, this.getAnimationTime() * 1.5);
|
| 620 |
+
},
|
| 621 |
+
|
| 622 |
+
importLevel: function(command, deferred) {
|
| 623 |
+
this.exitLevel();
|
| 624 |
+
setTimeout(function() {
|
| 625 |
+
Main.getSandbox().importLevel(command, deferred);
|
| 626 |
+
}, this.getAnimationTime() * 1.5);
|
| 627 |
+
},
|
| 628 |
+
|
| 629 |
+
startLevel: function(command, deferred) {
|
| 630 |
+
this.exitLevel();
|
| 631 |
+
|
| 632 |
+
setTimeout(function() {
|
| 633 |
+
Main.getSandbox().startLevel(command, deferred);
|
| 634 |
+
}, this.getAnimationTime() * 1.5);
|
| 635 |
+
// wow! that was simple :D
|
| 636 |
+
},
|
| 637 |
+
|
| 638 |
+
exitLevel: function(command, deferred) {
|
| 639 |
+
this.die();
|
| 640 |
+
|
| 641 |
+
if (!command || !deferred) {
|
| 642 |
+
return;
|
| 643 |
+
}
|
| 644 |
+
|
| 645 |
+
setTimeout(function() {
|
| 646 |
+
command.finishWith(deferred);
|
| 647 |
+
}, this.getAnimationTime());
|
| 648 |
+
|
| 649 |
+
// we need to fade in the sandbox
|
| 650 |
+
Main.getEventBaton().trigger('levelExited');
|
| 651 |
+
},
|
| 652 |
+
|
| 653 |
+
processLevelCommand: function(command, defer) {
|
| 654 |
+
var methodMap = {
|
| 655 |
+
'show goal': this.showGoal,
|
| 656 |
+
'hide goal': this.hideGoal,
|
| 657 |
+
'show solution': this.showSolution,
|
| 658 |
+
'start dialog': this.startDialog,
|
| 659 |
+
'help level': this.startDialog,
|
| 660 |
+
'objective': this.objectiveDialog
|
| 661 |
+
};
|
| 662 |
+
var method = methodMap[command.get('method')];
|
| 663 |
+
if (!method) {
|
| 664 |
+
throw new Error('woah we don\'t support that method yet', method);
|
| 665 |
+
}
|
| 666 |
+
|
| 667 |
+
method.apply(this, [command, defer]);
|
| 668 |
+
}
|
| 669 |
+
});
|
| 670 |
+
|
| 671 |
+
exports.Level = Level;
|
| 672 |
+
exports.regexMap = regexMap;
|
src/js/level/parseWaterfall.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var GitCommands = require('../git/commands');
|
| 2 |
+
var Commands = require('../commands');
|
| 3 |
+
var SandboxCommands = require('../sandbox/commands');
|
| 4 |
+
|
| 5 |
+
// more or less a static class
|
| 6 |
+
var ParseWaterfall = function(options) {
|
| 7 |
+
options = options || {};
|
| 8 |
+
this.options = options;
|
| 9 |
+
this.shortcutWaterfall = options.shortcutWaterfall || [
|
| 10 |
+
Commands.commands.getShortcutMap()
|
| 11 |
+
];
|
| 12 |
+
|
| 13 |
+
this.instantWaterfall = options.instantWaterfall || [
|
| 14 |
+
GitCommands.instantCommands,
|
| 15 |
+
SandboxCommands.instantCommands
|
| 16 |
+
];
|
| 17 |
+
|
| 18 |
+
// defer the parse waterfall until later...
|
| 19 |
+
};
|
| 20 |
+
|
| 21 |
+
ParseWaterfall.prototype.initParseWaterfall = function() {
|
| 22 |
+
// check for node when testing
|
| 23 |
+
if (!require('../util').isBrowser()) {
|
| 24 |
+
this.parseWaterfall = [Commands.parse];
|
| 25 |
+
return;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
// by deferring the initialization here, we don't require()
|
| 29 |
+
// level too early (which barfs our init)
|
| 30 |
+
this.parseWaterfall = this.options.parseWaterfall || [
|
| 31 |
+
Commands.parse,
|
| 32 |
+
SandboxCommands.parse,
|
| 33 |
+
SandboxCommands.getOptimisticLevelParse(),
|
| 34 |
+
SandboxCommands.getOptimisticLevelBuilderParse()
|
| 35 |
+
];
|
| 36 |
+
};
|
| 37 |
+
|
| 38 |
+
ParseWaterfall.prototype.clone = function() {
|
| 39 |
+
return new ParseWaterfall({
|
| 40 |
+
shortcutWaterfall: this.shortcutWaterfall.slice(),
|
| 41 |
+
instantWaterfall: this.instantWaterfall.slice(),
|
| 42 |
+
parseWaterfall: this.parseWaterfall.slice()
|
| 43 |
+
});
|
| 44 |
+
};
|
| 45 |
+
|
| 46 |
+
ParseWaterfall.prototype.getWaterfallMap = function() {
|
| 47 |
+
if (!this.parseWaterfall) {
|
| 48 |
+
this.initParseWaterfall();
|
| 49 |
+
}
|
| 50 |
+
return {
|
| 51 |
+
shortcutWaterfall: this.shortcutWaterfall,
|
| 52 |
+
instantWaterfall: this.instantWaterfall,
|
| 53 |
+
parseWaterfall: this.parseWaterfall
|
| 54 |
+
};
|
| 55 |
+
};
|
| 56 |
+
|
| 57 |
+
ParseWaterfall.prototype.addFirst = function(which, value) {
|
| 58 |
+
if (!which || !value) {
|
| 59 |
+
throw new Error('need to know which!!!');
|
| 60 |
+
}
|
| 61 |
+
this.getWaterfallMap()[which].unshift(value);
|
| 62 |
+
};
|
| 63 |
+
|
| 64 |
+
ParseWaterfall.prototype.addLast = function(which, value) {
|
| 65 |
+
this.getWaterfallMap()[which].push(value);
|
| 66 |
+
};
|
| 67 |
+
|
| 68 |
+
ParseWaterfall.prototype.expandAllShortcuts = function(commandStr) {
|
| 69 |
+
this.shortcutWaterfall.forEach(function(shortcutMap) {
|
| 70 |
+
commandStr = this.expandShortcut(commandStr, shortcutMap);
|
| 71 |
+
}, this);
|
| 72 |
+
return commandStr;
|
| 73 |
+
};
|
| 74 |
+
|
| 75 |
+
ParseWaterfall.prototype.expandShortcut = function(commandStr, shortcutMap) {
|
| 76 |
+
Object.keys(shortcutMap).forEach(function(vcs) {
|
| 77 |
+
var map = shortcutMap[vcs];
|
| 78 |
+
Object.keys(map).forEach(function(method) {
|
| 79 |
+
var regex = map[method];
|
| 80 |
+
var results = regex.exec(commandStr);
|
| 81 |
+
if (results) {
|
| 82 |
+
commandStr = vcs + ' ' + method + ' ' + commandStr.slice(results[0].length);
|
| 83 |
+
}
|
| 84 |
+
});
|
| 85 |
+
});
|
| 86 |
+
return commandStr;
|
| 87 |
+
};
|
| 88 |
+
|
| 89 |
+
ParseWaterfall.prototype.processAllInstants = function(commandStr) {
|
| 90 |
+
this.instantWaterfall.forEach(function(instantCommands) {
|
| 91 |
+
this.processInstant(commandStr, instantCommands);
|
| 92 |
+
}, this);
|
| 93 |
+
};
|
| 94 |
+
|
| 95 |
+
ParseWaterfall.prototype.processInstant = function(commandStr, instantCommands) {
|
| 96 |
+
instantCommands.forEach(function(tuple) {
|
| 97 |
+
var regex = tuple[0];
|
| 98 |
+
var results = regex.exec(commandStr);
|
| 99 |
+
if (results) {
|
| 100 |
+
// this will throw a result because it's an instant
|
| 101 |
+
tuple[1](results);
|
| 102 |
+
}
|
| 103 |
+
});
|
| 104 |
+
};
|
| 105 |
+
|
| 106 |
+
ParseWaterfall.prototype.parseAll = function(commandStr) {
|
| 107 |
+
if (!this.parseWaterfall) {
|
| 108 |
+
this.initParseWaterfall();
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
var toReturn = false;
|
| 112 |
+
this.parseWaterfall.forEach(function(parseFunc) {
|
| 113 |
+
var results = parseFunc(commandStr);
|
| 114 |
+
if (results) {
|
| 115 |
+
toReturn = results;
|
| 116 |
+
}
|
| 117 |
+
}, this);
|
| 118 |
+
|
| 119 |
+
return toReturn;
|
| 120 |
+
};
|
| 121 |
+
|
| 122 |
+
exports.ParseWaterfall = ParseWaterfall;
|
src/js/log/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
var log = function(category, action, label) {
|
| 3 |
+
window._gaq = window._gaq || [];
|
| 4 |
+
window._gaq.push(['_trackEvent', category, action, label]);
|
| 5 |
+
//console.log('just logged ', [category, action, label].join('|'));
|
| 6 |
+
};
|
| 7 |
+
|
| 8 |
+
exports.viewInteracted = function(viewName) {
|
| 9 |
+
log('views', 'interacted', viewName);
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
exports.showLevelSolution = function(levelName) {
|
| 13 |
+
log('levels', 'showedLevelSolution', levelName);
|
| 14 |
+
};
|
| 15 |
+
|
| 16 |
+
exports.choseNextLevel = function(levelID) {
|
| 17 |
+
log('levels', 'nextLevelChosen', levelID);
|
| 18 |
+
};
|
| 19 |
+
|
| 20 |
+
exports.levelSelected = function(levelName) {
|
| 21 |
+
log('levels', 'levelSelected', levelName);
|
| 22 |
+
};
|
| 23 |
+
|
| 24 |
+
exports.levelSolved = function(levelName) {
|
| 25 |
+
log('levels', 'levelSolved', levelName);
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
exports.commandEntered = function(value) {
|
| 29 |
+
log('commands', 'commandEntered', value);
|
| 30 |
+
};
|
| 31 |
+
|
src/js/mercurial/commands.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var intl = require('../intl');
|
| 2 |
+
|
| 3 |
+
var GitCommands = require('../git/commands');
|
| 4 |
+
var Errors = require('../util/errors');
|
| 5 |
+
|
| 6 |
+
var CommandProcessError = Errors.CommandProcessError;
|
| 7 |
+
var GitError = Errors.GitError;
|
| 8 |
+
var Warning = Errors.Warning;
|
| 9 |
+
var CommandResult = Errors.CommandResult;
|
| 10 |
+
|
| 11 |
+
var commandConfig = {
|
| 12 |
+
commit: {
|
| 13 |
+
regex: /^hg +(commit|ci)($|\s)/,
|
| 14 |
+
options: [
|
| 15 |
+
'--amend',
|
| 16 |
+
'-A',
|
| 17 |
+
'-m'
|
| 18 |
+
],
|
| 19 |
+
delegate: function(engine, command) {
|
| 20 |
+
var options = command.getOptionsMap();
|
| 21 |
+
if (options['-A']) {
|
| 22 |
+
command.addWarning(intl.str('hg-a-option'));
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
return {
|
| 26 |
+
vcs: 'git',
|
| 27 |
+
name: 'commit'
|
| 28 |
+
};
|
| 29 |
+
}
|
| 30 |
+
},
|
| 31 |
+
|
| 32 |
+
status: {
|
| 33 |
+
regex: /^hg +(status|st) *$/,
|
| 34 |
+
dontCountForGolf: true,
|
| 35 |
+
execute: function(engine, command) {
|
| 36 |
+
throw new GitError({
|
| 37 |
+
msg: intl.str('hg-error-no-status')
|
| 38 |
+
});
|
| 39 |
+
}
|
| 40 |
+
},
|
| 41 |
+
|
| 42 |
+
'export': {
|
| 43 |
+
regex: /^hg +export($|\s)/,
|
| 44 |
+
dontCountForGolf: true,
|
| 45 |
+
delegate: function(engine, command) {
|
| 46 |
+
command.mapDotToHead();
|
| 47 |
+
return {
|
| 48 |
+
vcs: 'git',
|
| 49 |
+
name: 'show'
|
| 50 |
+
};
|
| 51 |
+
}
|
| 52 |
+
},
|
| 53 |
+
|
| 54 |
+
graft: {
|
| 55 |
+
regex: /^hg +graft($|\s)/,
|
| 56 |
+
options: [
|
| 57 |
+
'-r'
|
| 58 |
+
],
|
| 59 |
+
delegate: function(engine, command) {
|
| 60 |
+
command.acceptNoGeneralArgs();
|
| 61 |
+
command.prependOptionR();
|
| 62 |
+
return {
|
| 63 |
+
vcs: 'git',
|
| 64 |
+
name: 'cherrypick'
|
| 65 |
+
};
|
| 66 |
+
}
|
| 67 |
+
},
|
| 68 |
+
|
| 69 |
+
log: {
|
| 70 |
+
regex: /^hg +log($|\s)/,
|
| 71 |
+
options: [
|
| 72 |
+
'-f'
|
| 73 |
+
],
|
| 74 |
+
dontCountForGolf: true,
|
| 75 |
+
delegate: function(engine, command) {
|
| 76 |
+
var options = command.getOptionsMap();
|
| 77 |
+
command.acceptNoGeneralArgs();
|
| 78 |
+
|
| 79 |
+
if (!options['-f']) {
|
| 80 |
+
throw new GitError({
|
| 81 |
+
msg: intl.str('hg-error-log-no-follow')
|
| 82 |
+
});
|
| 83 |
+
}
|
| 84 |
+
command.mapDotToHead();
|
| 85 |
+
return {
|
| 86 |
+
vcs: 'git',
|
| 87 |
+
name: 'log'
|
| 88 |
+
};
|
| 89 |
+
}
|
| 90 |
+
},
|
| 91 |
+
|
| 92 |
+
bookmark: {
|
| 93 |
+
regex: /^hg (bookmarks|bookmark|book)($|\s)/,
|
| 94 |
+
options: [
|
| 95 |
+
'-r',
|
| 96 |
+
'-f',
|
| 97 |
+
'-d'
|
| 98 |
+
],
|
| 99 |
+
delegate: function(engine, command) {
|
| 100 |
+
var options = command.getOptionsMap();
|
| 101 |
+
var generalArgs = command.getGeneralArgs();
|
| 102 |
+
var branchName;
|
| 103 |
+
var rev;
|
| 104 |
+
|
| 105 |
+
var delegate = {vcs: 'git'};
|
| 106 |
+
|
| 107 |
+
if (options['-m'] && options['-d']) {
|
| 108 |
+
throw new GitError({
|
| 109 |
+
msg: intl.todo('-m and -d are incompatible')
|
| 110 |
+
});
|
| 111 |
+
}
|
| 112 |
+
if (options['-d'] && options['-r']) {
|
| 113 |
+
throw new GitError({
|
| 114 |
+
msg: intl.todo('-r is incompatible with -d')
|
| 115 |
+
});
|
| 116 |
+
}
|
| 117 |
+
if (options['-m'] && options['-r']) {
|
| 118 |
+
throw new GitError({
|
| 119 |
+
msg: intl.todo('-r is incompatible with -m')
|
| 120 |
+
});
|
| 121 |
+
}
|
| 122 |
+
if (generalArgs.length + (options['-r'] ? options['-r'].length : 0) +
|
| 123 |
+
(options['-d'] ? options['-d'].length : 0) === 0) {
|
| 124 |
+
delegate.name = 'branch';
|
| 125 |
+
return delegate;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
if (options['-d']) {
|
| 129 |
+
options['-D'] = options['-d'];
|
| 130 |
+
delete options['-d'];
|
| 131 |
+
delegate.name = 'branch';
|
| 132 |
+
} else {
|
| 133 |
+
if (options['-r']) {
|
| 134 |
+
// we specified a revision with -r but
|
| 135 |
+
// need to flip the order
|
| 136 |
+
generalArgs = command.getGeneralArgs();
|
| 137 |
+
branchName = generalArgs[0];
|
| 138 |
+
rev = options['-r'][0];
|
| 139 |
+
delegate.name = 'branch';
|
| 140 |
+
|
| 141 |
+
// transform to what git wants
|
| 142 |
+
command.setGeneralArgs([branchName, rev]);
|
| 143 |
+
} else if (generalArgs.length > 0) {
|
| 144 |
+
command.setOptionsMap({'-b': [generalArgs[0]]});
|
| 145 |
+
delegate.name = 'checkout';
|
| 146 |
+
command.setGeneralArgs([]);
|
| 147 |
+
} else {
|
| 148 |
+
delegate.name = 'branch';
|
| 149 |
+
}
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
return delegate;
|
| 153 |
+
}
|
| 154 |
+
},
|
| 155 |
+
|
| 156 |
+
rebase: {
|
| 157 |
+
regex: /^hg +rebase($|\s+)/,
|
| 158 |
+
options: [
|
| 159 |
+
'-d',
|
| 160 |
+
'-s',
|
| 161 |
+
'-b'
|
| 162 |
+
],
|
| 163 |
+
execute: function(engine, command) {
|
| 164 |
+
var throwE = function() {
|
| 165 |
+
throw new GitError({
|
| 166 |
+
msg: intl.str('git-error-options')
|
| 167 |
+
});
|
| 168 |
+
};
|
| 169 |
+
|
| 170 |
+
var options = command.getOptionsMap();
|
| 171 |
+
// if we have both OR if we have neither
|
| 172 |
+
if ((options['-d'] && options['-s']) ||
|
| 173 |
+
(!options['-d'] && !options['-s'])) {
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
if (!options['-b']) {
|
| 177 |
+
options['-b'] = ['.'];
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
command.setOptionsMap(options);
|
| 181 |
+
command.mapDotToHead();
|
| 182 |
+
options = command.getOptionsMap();
|
| 183 |
+
|
| 184 |
+
if (options['-d']) {
|
| 185 |
+
var dest = options['-d'][0] || throwE();
|
| 186 |
+
var base = options['-b'][0];
|
| 187 |
+
|
| 188 |
+
engine.hgRebase(dest, base);
|
| 189 |
+
} else {
|
| 190 |
+
// TODO!!!
|
| 191 |
+
throwE();
|
| 192 |
+
}
|
| 193 |
+
}
|
| 194 |
+
},
|
| 195 |
+
|
| 196 |
+
update: {
|
| 197 |
+
regex: /^hg +(update|up)($|\s+)/,
|
| 198 |
+
options: [
|
| 199 |
+
'-r'
|
| 200 |
+
],
|
| 201 |
+
delegate: function(engine, command) {
|
| 202 |
+
command.appendOptionR();
|
| 203 |
+
return {
|
| 204 |
+
vcs: 'git',
|
| 205 |
+
name: 'checkout'
|
| 206 |
+
};
|
| 207 |
+
}
|
| 208 |
+
},
|
| 209 |
+
|
| 210 |
+
backout: {
|
| 211 |
+
regex: /^hg +backout($|\s+)/,
|
| 212 |
+
options: [
|
| 213 |
+
'-r'
|
| 214 |
+
],
|
| 215 |
+
delegate: function(engine, command) {
|
| 216 |
+
command.prependOptionR();
|
| 217 |
+
return {
|
| 218 |
+
vcs: 'git',
|
| 219 |
+
name: 'revert'
|
| 220 |
+
};
|
| 221 |
+
}
|
| 222 |
+
},
|
| 223 |
+
|
| 224 |
+
histedit: {
|
| 225 |
+
regex: /^hg +histedit($|\s+)/,
|
| 226 |
+
delegate: function(engine, command) {
|
| 227 |
+
var args = command.getGeneralArgs();
|
| 228 |
+
command.validateArgBounds(args, 1, 1);
|
| 229 |
+
command.setOptionsMap({
|
| 230 |
+
'-i': args
|
| 231 |
+
});
|
| 232 |
+
command.setGeneralArgs([]);
|
| 233 |
+
return {
|
| 234 |
+
vcs: 'git',
|
| 235 |
+
name: 'rebase'
|
| 236 |
+
};
|
| 237 |
+
}
|
| 238 |
+
},
|
| 239 |
+
|
| 240 |
+
pull: {
|
| 241 |
+
regex: /^hg +pull($|\s+)/,
|
| 242 |
+
delegate: function(engine, command) {
|
| 243 |
+
return {
|
| 244 |
+
vcs: 'git',
|
| 245 |
+
name: 'pull'
|
| 246 |
+
};
|
| 247 |
+
}
|
| 248 |
+
},
|
| 249 |
+
|
| 250 |
+
summary: {
|
| 251 |
+
regex: /^hg +(summary|sum) *$/,
|
| 252 |
+
delegate: function(engine, command) {
|
| 253 |
+
return {
|
| 254 |
+
vcs: 'git',
|
| 255 |
+
name: 'branch'
|
| 256 |
+
};
|
| 257 |
+
}
|
| 258 |
+
}
|
| 259 |
+
};
|
| 260 |
+
|
| 261 |
+
exports.commandConfig = commandConfig;
|
src/js/models/collections.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var Q = require('q');
|
| 2 |
+
var Backbone = require('backbone');
|
| 3 |
+
|
| 4 |
+
var Commit = require('../git').Commit;
|
| 5 |
+
var Branch = require('../git').Branch;
|
| 6 |
+
var Tag = require('../git').Tag;
|
| 7 |
+
|
| 8 |
+
var Command = require('../models/commandModel').Command;
|
| 9 |
+
var TIME = require('../util/constants').TIME;
|
| 10 |
+
|
| 11 |
+
var intl = require('../intl');
|
| 12 |
+
|
| 13 |
+
var CommitCollection = Backbone.Collection.extend({
|
| 14 |
+
model: Commit
|
| 15 |
+
});
|
| 16 |
+
|
| 17 |
+
var CommandCollection = Backbone.Collection.extend({
|
| 18 |
+
model: Command
|
| 19 |
+
});
|
| 20 |
+
|
| 21 |
+
var BranchCollection = Backbone.Collection.extend({
|
| 22 |
+
model: Branch
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
var TagCollection = Backbone.Collection.extend({
|
| 26 |
+
model: Tag
|
| 27 |
+
});
|
| 28 |
+
|
| 29 |
+
var CommandBuffer = Backbone.Model.extend({
|
| 30 |
+
defaults: {
|
| 31 |
+
collection: null
|
| 32 |
+
},
|
| 33 |
+
|
| 34 |
+
initialize: function(options) {
|
| 35 |
+
options.collection.bind('add', this.addCommand, this);
|
| 36 |
+
|
| 37 |
+
this.buffer = [];
|
| 38 |
+
this.timeout = null;
|
| 39 |
+
},
|
| 40 |
+
|
| 41 |
+
addCommand: function(command) {
|
| 42 |
+
this.buffer.push(command);
|
| 43 |
+
this.touchBuffer();
|
| 44 |
+
},
|
| 45 |
+
|
| 46 |
+
touchBuffer: function() {
|
| 47 |
+
// touch buffer just essentially means we just check if our buffer is being
|
| 48 |
+
// processed. if it's not, we immediately process the first item
|
| 49 |
+
// and then set the timeout.
|
| 50 |
+
if (this.timeout) {
|
| 51 |
+
// timeout existence implies its being processed
|
| 52 |
+
return;
|
| 53 |
+
}
|
| 54 |
+
this.setTimeout();
|
| 55 |
+
},
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
setTimeout: function() {
|
| 59 |
+
this.timeout = setTimeout(function() {
|
| 60 |
+
this.sipFromBuffer();
|
| 61 |
+
}.bind(this), TIME.betweenCommandsDelay);
|
| 62 |
+
},
|
| 63 |
+
|
| 64 |
+
popAndProcess: function() {
|
| 65 |
+
var popped = this.buffer.shift(0);
|
| 66 |
+
|
| 67 |
+
// find a command with no error (aka unprocessed)
|
| 68 |
+
while (popped.get('error') && this.buffer.length) {
|
| 69 |
+
popped = this.buffer.shift(0);
|
| 70 |
+
}
|
| 71 |
+
if (!popped.get('error')) {
|
| 72 |
+
this.processCommand(popped);
|
| 73 |
+
} else {
|
| 74 |
+
// no more commands to process
|
| 75 |
+
this.clear();
|
| 76 |
+
}
|
| 77 |
+
},
|
| 78 |
+
|
| 79 |
+
processCommand: function(command) {
|
| 80 |
+
command.set('status', 'processing');
|
| 81 |
+
|
| 82 |
+
var deferred = Q.defer();
|
| 83 |
+
deferred.promise.then(function() {
|
| 84 |
+
this.setTimeout();
|
| 85 |
+
}.bind(this));
|
| 86 |
+
|
| 87 |
+
var eventName = command.get('eventName');
|
| 88 |
+
if (!eventName) {
|
| 89 |
+
throw new Error('I need an event to trigger when this guy is parsed and ready');
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
var Main = require('../app');
|
| 93 |
+
var eventBaton = Main.getEventBaton();
|
| 94 |
+
|
| 95 |
+
var numListeners = eventBaton.getNumListeners(eventName);
|
| 96 |
+
if (!numListeners) {
|
| 97 |
+
var Errors = require('../util/errors');
|
| 98 |
+
command.set('error', new Errors.GitError({
|
| 99 |
+
msg: intl.str('error-command-currently-not-supported')
|
| 100 |
+
}));
|
| 101 |
+
deferred.resolve();
|
| 102 |
+
return;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
Main.getEventBaton().trigger(eventName, command, deferred);
|
| 106 |
+
},
|
| 107 |
+
|
| 108 |
+
clear: function() {
|
| 109 |
+
clearTimeout(this.timeout);
|
| 110 |
+
this.timeout = null;
|
| 111 |
+
},
|
| 112 |
+
|
| 113 |
+
sipFromBuffer: function() {
|
| 114 |
+
if (!this.buffer.length) {
|
| 115 |
+
this.clear();
|
| 116 |
+
return;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
this.popAndProcess();
|
| 120 |
+
}
|
| 121 |
+
});
|
| 122 |
+
|
| 123 |
+
exports.CommitCollection = CommitCollection;
|
| 124 |
+
exports.CommandCollection = CommandCollection;
|
| 125 |
+
exports.BranchCollection = BranchCollection;
|
| 126 |
+
exports.TagCollection = TagCollection;
|
| 127 |
+
exports.CommandBuffer = CommandBuffer;
|
| 128 |
+
|
src/js/models/commandModel.js
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var Backbone = require('backbone');
|
| 2 |
+
|
| 3 |
+
var Errors = require('../util/errors');
|
| 4 |
+
|
| 5 |
+
var ParseWaterfall = require('../level/parseWaterfall').ParseWaterfall;
|
| 6 |
+
var LevelStore = require('../stores/LevelStore');
|
| 7 |
+
var intl = require('../intl');
|
| 8 |
+
|
| 9 |
+
var CommandProcessError = Errors.CommandProcessError;
|
| 10 |
+
var GitError = Errors.GitError;
|
| 11 |
+
var Warning = Errors.Warning;
|
| 12 |
+
var CommandResult = Errors.CommandResult;
|
| 13 |
+
|
| 14 |
+
var Command = Backbone.Model.extend({
|
| 15 |
+
defaults: {
|
| 16 |
+
status: 'inqueue',
|
| 17 |
+
rawStr: null,
|
| 18 |
+
result: '',
|
| 19 |
+
createTime: null,
|
| 20 |
+
|
| 21 |
+
error: null,
|
| 22 |
+
warnings: null,
|
| 23 |
+
parseWaterfall: new ParseWaterfall(),
|
| 24 |
+
|
| 25 |
+
generalArgs: null,
|
| 26 |
+
supportedMap: null,
|
| 27 |
+
options: null,
|
| 28 |
+
method: null
|
| 29 |
+
|
| 30 |
+
},
|
| 31 |
+
|
| 32 |
+
initialize: function() {
|
| 33 |
+
this.initDefaults();
|
| 34 |
+
this.validateAtInit();
|
| 35 |
+
|
| 36 |
+
this.on('change:error', this.errorChanged, this);
|
| 37 |
+
// catch errors on init
|
| 38 |
+
if (this.get('error')) {
|
| 39 |
+
this.errorChanged();
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
this.parseOrCatch();
|
| 43 |
+
},
|
| 44 |
+
|
| 45 |
+
initDefaults: function() {
|
| 46 |
+
// weird things happen with defaults if you don't
|
| 47 |
+
// make new objects
|
| 48 |
+
this.set('generalArgs', []);
|
| 49 |
+
this.set('supportedMap', {});
|
| 50 |
+
this.set('warnings', []);
|
| 51 |
+
},
|
| 52 |
+
|
| 53 |
+
replaceDotWithHead: function(string) {
|
| 54 |
+
return string.replace(/\./g, 'HEAD');
|
| 55 |
+
},
|
| 56 |
+
|
| 57 |
+
/**
|
| 58 |
+
* Since mercurial always wants revisions with
|
| 59 |
+
* -r, we want to just make these general
|
| 60 |
+
* args for git
|
| 61 |
+
*/
|
| 62 |
+
appendOptionR: function() {
|
| 63 |
+
var rOptions = this.getOptionsMap()['-r'] || [];
|
| 64 |
+
this.setGeneralArgs(
|
| 65 |
+
this.getGeneralArgs().concat(rOptions)
|
| 66 |
+
);
|
| 67 |
+
},
|
| 68 |
+
|
| 69 |
+
// if order is important
|
| 70 |
+
prependOptionR: function() {
|
| 71 |
+
var rOptions = this.getOptionsMap()['-r'] || [];
|
| 72 |
+
this.setGeneralArgs(
|
| 73 |
+
rOptions.concat(this.getGeneralArgs())
|
| 74 |
+
);
|
| 75 |
+
},
|
| 76 |
+
|
| 77 |
+
mapDotToHead: function() {
|
| 78 |
+
var generalArgs = this.getGeneralArgs();
|
| 79 |
+
var options = this.getOptionsMap();
|
| 80 |
+
|
| 81 |
+
generalArgs = generalArgs.map(function(arg) {
|
| 82 |
+
return this.replaceDotWithHead(arg);
|
| 83 |
+
}, this);
|
| 84 |
+
var newMap = {};
|
| 85 |
+
Object.keys(options).forEach(function(key) {
|
| 86 |
+
var args = options[key];
|
| 87 |
+
newMap[key] = Object.values(args).map(function (arg) {
|
| 88 |
+
return this.replaceDotWithHead(arg);
|
| 89 |
+
}, this);
|
| 90 |
+
}, this);
|
| 91 |
+
this.setGeneralArgs(generalArgs);
|
| 92 |
+
this.setOptionsMap(newMap);
|
| 93 |
+
},
|
| 94 |
+
|
| 95 |
+
deleteOptions: function(options) {
|
| 96 |
+
var map = this.getOptionsMap();
|
| 97 |
+
options.forEach(function(option) {
|
| 98 |
+
delete map[option];
|
| 99 |
+
}, this);
|
| 100 |
+
this.setOptionsMap(map);
|
| 101 |
+
},
|
| 102 |
+
|
| 103 |
+
getGeneralArgs: function() {
|
| 104 |
+
return this.get('generalArgs');
|
| 105 |
+
},
|
| 106 |
+
|
| 107 |
+
setGeneralArgs: function(args) {
|
| 108 |
+
this.set('generalArgs', args);
|
| 109 |
+
},
|
| 110 |
+
|
| 111 |
+
setOptionsMap: function(map) {
|
| 112 |
+
this.set('supportedMap', map);
|
| 113 |
+
},
|
| 114 |
+
|
| 115 |
+
getOptionsMap: function() {
|
| 116 |
+
return this.get('supportedMap');
|
| 117 |
+
},
|
| 118 |
+
|
| 119 |
+
acceptNoGeneralArgs: function() {
|
| 120 |
+
if (this.getGeneralArgs().length) {
|
| 121 |
+
throw new GitError({
|
| 122 |
+
msg: intl.str('git-error-no-general-args')
|
| 123 |
+
});
|
| 124 |
+
}
|
| 125 |
+
},
|
| 126 |
+
|
| 127 |
+
argImpliedHead: function (args, lower, upper, option) {
|
| 128 |
+
// our args we expect to be between {lower} and {upper}
|
| 129 |
+
this.validateArgBounds(args, lower, upper, option);
|
| 130 |
+
// and if it's one, add a HEAD to the back
|
| 131 |
+
this.impliedHead(args, lower);
|
| 132 |
+
},
|
| 133 |
+
|
| 134 |
+
oneArgImpliedHead: function(args, option) {
|
| 135 |
+
this.argImpliedHead(args, 0, 1, option);
|
| 136 |
+
},
|
| 137 |
+
|
| 138 |
+
twoArgsImpliedHead: function(args, option) {
|
| 139 |
+
this.argImpliedHead(args, 1, 2, option);
|
| 140 |
+
},
|
| 141 |
+
|
| 142 |
+
threeArgsImpliedHead: function(args, option) {
|
| 143 |
+
this.argImpliedHead(args, 2, 3, option);
|
| 144 |
+
},
|
| 145 |
+
|
| 146 |
+
oneArgImpliedOrigin: function(args) {
|
| 147 |
+
this.validateArgBounds(args, 0, 1);
|
| 148 |
+
if (!args.length) {
|
| 149 |
+
args.unshift('origin');
|
| 150 |
+
}
|
| 151 |
+
},
|
| 152 |
+
|
| 153 |
+
twoArgsForOrigin: function(args) {
|
| 154 |
+
this.validateArgBounds(args, 0, 2);
|
| 155 |
+
},
|
| 156 |
+
|
| 157 |
+
impliedHead: function(args, min) {
|
| 158 |
+
if(args.length == min) {
|
| 159 |
+
args.push('HEAD');
|
| 160 |
+
}
|
| 161 |
+
},
|
| 162 |
+
|
| 163 |
+
// this is a little utility class to help arg validation that happens over and over again
|
| 164 |
+
validateArgBounds: function(args, lower, upper, option) {
|
| 165 |
+
var what = (option === undefined) ?
|
| 166 |
+
'git ' + this.get('method') :
|
| 167 |
+
this.get('method') + ' ' + option + ' ';
|
| 168 |
+
what = 'with ' + what;
|
| 169 |
+
|
| 170 |
+
if (args.length < lower) {
|
| 171 |
+
throw new GitError({
|
| 172 |
+
msg: intl.str(
|
| 173 |
+
'git-error-args-few',
|
| 174 |
+
{
|
| 175 |
+
lower: String(lower),
|
| 176 |
+
what: what
|
| 177 |
+
}
|
| 178 |
+
)
|
| 179 |
+
});
|
| 180 |
+
}
|
| 181 |
+
if (args.length > upper) {
|
| 182 |
+
throw new GitError({
|
| 183 |
+
msg: intl.str(
|
| 184 |
+
'git-error-args-many',
|
| 185 |
+
{
|
| 186 |
+
upper: String(upper),
|
| 187 |
+
what: what
|
| 188 |
+
}
|
| 189 |
+
)
|
| 190 |
+
});
|
| 191 |
+
}
|
| 192 |
+
},
|
| 193 |
+
|
| 194 |
+
validateAtInit: function() {
|
| 195 |
+
if (this.get('rawStr') === null) {
|
| 196 |
+
throw new Error('Give me a string!');
|
| 197 |
+
}
|
| 198 |
+
if (!this.get('createTime')) {
|
| 199 |
+
this.set('createTime', new Date().toString());
|
| 200 |
+
}
|
| 201 |
+
},
|
| 202 |
+
|
| 203 |
+
setResult: function(msg) {
|
| 204 |
+
this.set('result', msg);
|
| 205 |
+
},
|
| 206 |
+
|
| 207 |
+
finishWith: function(deferred) {
|
| 208 |
+
this.set('status', 'finished');
|
| 209 |
+
deferred.resolve();
|
| 210 |
+
},
|
| 211 |
+
|
| 212 |
+
addWarning: function(msg) {
|
| 213 |
+
this.get('warnings').push(msg);
|
| 214 |
+
// change numWarnings so the change event fires. This is bizarre -- Backbone can't
|
| 215 |
+
// detect if an array changes, so adding an element does nothing
|
| 216 |
+
this.set('numWarnings', this.get('numWarnings') ? this.get('numWarnings') + 1 : 1);
|
| 217 |
+
},
|
| 218 |
+
|
| 219 |
+
parseOrCatch: function() {
|
| 220 |
+
this.expandShortcuts(this.get('rawStr'));
|
| 221 |
+
try {
|
| 222 |
+
this.processInstants();
|
| 223 |
+
} catch (err) {
|
| 224 |
+
Errors.filterError(err);
|
| 225 |
+
// errorChanged() will handle status and all of that
|
| 226 |
+
this.set('error', err);
|
| 227 |
+
return;
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
if (this.parseAll()) {
|
| 231 |
+
// something in our parse waterfall succeeded
|
| 232 |
+
return;
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
// if we reach here, this command is not supported :-/
|
| 236 |
+
this.set('error', new CommandProcessError({
|
| 237 |
+
msg: intl.str(
|
| 238 |
+
'git-error-command-not-supported',
|
| 239 |
+
{
|
| 240 |
+
command: this.get('rawStr')
|
| 241 |
+
})
|
| 242 |
+
})
|
| 243 |
+
);
|
| 244 |
+
},
|
| 245 |
+
|
| 246 |
+
errorChanged: function() {
|
| 247 |
+
var err = this.get('error');
|
| 248 |
+
if (!err) { return; }
|
| 249 |
+
if (err instanceof CommandProcessError ||
|
| 250 |
+
err instanceof GitError) {
|
| 251 |
+
this.set('status', 'error');
|
| 252 |
+
} else if (err instanceof CommandResult) {
|
| 253 |
+
this.set('status', 'finished');
|
| 254 |
+
} else if (err instanceof Warning) {
|
| 255 |
+
this.set('status', 'warning');
|
| 256 |
+
}
|
| 257 |
+
this.formatError();
|
| 258 |
+
},
|
| 259 |
+
|
| 260 |
+
formatError: function() {
|
| 261 |
+
this.set('result', this.get('error').getMsg());
|
| 262 |
+
},
|
| 263 |
+
|
| 264 |
+
expandShortcuts: function(str) {
|
| 265 |
+
str = this.get('parseWaterfall').expandAllShortcuts(str);
|
| 266 |
+
this.set('rawStr', str);
|
| 267 |
+
},
|
| 268 |
+
|
| 269 |
+
processInstants: function() {
|
| 270 |
+
var str = this.get('rawStr');
|
| 271 |
+
// first if the string is empty, they just want a blank line
|
| 272 |
+
if (!str.length) {
|
| 273 |
+
throw new CommandResult({msg: ""});
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
// then instant commands that will throw
|
| 277 |
+
this.get('parseWaterfall').processAllInstants(str);
|
| 278 |
+
},
|
| 279 |
+
|
| 280 |
+
parseAll: function() {
|
| 281 |
+
var rawInput = this.get('rawStr');
|
| 282 |
+
const aliasMap = LevelStore.getAliasMap();
|
| 283 |
+
for (var i = 0; i<Object.keys(aliasMap).length; i++) {
|
| 284 |
+
var alias = Object.keys(aliasMap)[i];
|
| 285 |
+
var searcher = new RegExp(alias + "(\\s|$)", "g");
|
| 286 |
+
if (searcher.test(rawInput)) {
|
| 287 |
+
rawInput = rawInput.replace(searcher, aliasMap[alias] + ' ');
|
| 288 |
+
break;
|
| 289 |
+
}
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
var results = this.get('parseWaterfall').parseAll(rawInput);
|
| 293 |
+
|
| 294 |
+
if (!results) {
|
| 295 |
+
// nothing parsed successfully
|
| 296 |
+
return false;
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
Object.keys(results.toSet).forEach(function(key) {
|
| 300 |
+
var obj = results.toSet[key];
|
| 301 |
+
// data comes back from the parsing functions like
|
| 302 |
+
// options (etc) that need to be set
|
| 303 |
+
this.set(key, obj);
|
| 304 |
+
}, this);
|
| 305 |
+
return true;
|
| 306 |
+
}
|
| 307 |
+
});
|
| 308 |
+
|
| 309 |
+
exports.Command = Command;
|
src/js/react_views/CommandHistoryView.jsx
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var React = require('react');
|
| 2 |
+
var PropTypes = require('prop-types');
|
| 3 |
+
|
| 4 |
+
var CommandView = require('../react_views/CommandView.jsx');
|
| 5 |
+
var Main = require('../app');
|
| 6 |
+
|
| 7 |
+
var _subscribeEvents = [
|
| 8 |
+
'add',
|
| 9 |
+
'reset',
|
| 10 |
+
'change',
|
| 11 |
+
'all'
|
| 12 |
+
];
|
| 13 |
+
|
| 14 |
+
class CommandHistoryView extends React.Component {
|
| 15 |
+
|
| 16 |
+
componentDidMount() {
|
| 17 |
+
for (var i = 0; i < _subscribeEvents.length; i++) {
|
| 18 |
+
this.props.commandCollection.on(
|
| 19 |
+
_subscribeEvents[i],
|
| 20 |
+
this.updateFromCollection,
|
| 21 |
+
this
|
| 22 |
+
);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
this.props.commandCollection.on('change', this.scrollDown, this);
|
| 26 |
+
Main.getEvents().on('commandScrollDown', this.scrollDown, this);
|
| 27 |
+
Main.getEvents().on('clearOldCommands', () => this.clearOldCommands(), this);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
componentWillUnmount() {
|
| 31 |
+
for (var i = 0; i < _subscribeEvents.length; i++) {
|
| 32 |
+
this.props.commandCollection.off(
|
| 33 |
+
_subscribeEvents[i],
|
| 34 |
+
this.updateFromCollection,
|
| 35 |
+
this
|
| 36 |
+
);
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
updateFromCollection() {
|
| 41 |
+
this.forceUpdate();
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
render() {
|
| 45 |
+
var allCommands = [];
|
| 46 |
+
this.props.commandCollection.each(function(command, index) {
|
| 47 |
+
allCommands.push(
|
| 48 |
+
<CommandView
|
| 49 |
+
id={'command_' + index}
|
| 50 |
+
command={command}
|
| 51 |
+
key={command.cid}
|
| 52 |
+
/>
|
| 53 |
+
);
|
| 54 |
+
}, this);
|
| 55 |
+
return (
|
| 56 |
+
<div>
|
| 57 |
+
{allCommands}
|
| 58 |
+
</div>
|
| 59 |
+
);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
scrollDown() {
|
| 63 |
+
var cD = document.getElementById('commandDisplay');
|
| 64 |
+
var t = document.getElementById('terminal');
|
| 65 |
+
|
| 66 |
+
// firefox hack
|
| 67 |
+
var shouldScroll = (cD.clientHeight > t.clientHeight) ||
|
| 68 |
+
(window.innerHeight < cD.clientHeight);
|
| 69 |
+
|
| 70 |
+
// ugh sometimes i wish i had toggle class
|
| 71 |
+
var hasScroll = t.className.match(/scrolling/g);
|
| 72 |
+
if (shouldScroll && !hasScroll) {
|
| 73 |
+
t.className += ' scrolling';
|
| 74 |
+
} else if (!shouldScroll && hasScroll) {
|
| 75 |
+
t.className = t.className.replace(/shouldScroll/g, '');
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
if (shouldScroll) {
|
| 79 |
+
t.scrollTop = t.scrollHeight;
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
clearOldCommands() {
|
| 84 |
+
// go through and get rid of every command that is "processed" or done
|
| 85 |
+
var toDestroy = [];
|
| 86 |
+
|
| 87 |
+
this.props.commandCollection.each(function(command) {
|
| 88 |
+
if (command.get('status') !== 'inqueue' &&
|
| 89 |
+
command.get('status') !== 'processing') {
|
| 90 |
+
toDestroy.push(command);
|
| 91 |
+
}
|
| 92 |
+
}, this);
|
| 93 |
+
for (var i = 0; i < toDestroy.length; i++) {
|
| 94 |
+
toDestroy[i].destroy();
|
| 95 |
+
}
|
| 96 |
+
this.updateFromCollection();
|
| 97 |
+
this.scrollDown();
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
CommandHistoryView.propTypes = {
|
| 103 |
+
// the backbone command model collection
|
| 104 |
+
commandCollection: PropTypes.object.isRequired
|
| 105 |
+
};
|
| 106 |
+
|
| 107 |
+
module.exports = CommandHistoryView;
|
src/js/react_views/CommandView.jsx
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var React = require('react');
|
| 2 |
+
var ReactDOM = require('react-dom');
|
| 3 |
+
var PropTypes = require('prop-types');
|
| 4 |
+
|
| 5 |
+
var reactUtil = require('../util/reactUtil');
|
| 6 |
+
var keyMirror = require('../util/keyMirror');
|
| 7 |
+
|
| 8 |
+
var STATUSES = keyMirror({
|
| 9 |
+
inqueue: null,
|
| 10 |
+
processing: null,
|
| 11 |
+
finished: null
|
| 12 |
+
});
|
| 13 |
+
|
| 14 |
+
class CommandView extends React.Component{
|
| 15 |
+
|
| 16 |
+
componentDidMount() {
|
| 17 |
+
this.props.command.on('change', this.updateStateFromModel, this);
|
| 18 |
+
this.updateStateFromModel();
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
componentWillUnmount() {
|
| 22 |
+
this.props.command.off('change', this.updateStateFromModel, this);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
updateStateFromModel() {
|
| 26 |
+
var commandJSON = this.props.command.toJSON();
|
| 27 |
+
this.setState({
|
| 28 |
+
status: commandJSON.status,
|
| 29 |
+
rawStr: commandJSON.rawStr,
|
| 30 |
+
warnings: commandJSON.warnings,
|
| 31 |
+
result: commandJSON.result
|
| 32 |
+
});
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
constructor(props, context) {
|
| 36 |
+
super(props, context);
|
| 37 |
+
this.state = {
|
| 38 |
+
status: STATUSES.inqueue,
|
| 39 |
+
rawStr: 'git commit',
|
| 40 |
+
warnings: [],
|
| 41 |
+
result: ''
|
| 42 |
+
};
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
render() {
|
| 46 |
+
var commandClass = reactUtil.joinClasses([
|
| 47 |
+
this.state.status,
|
| 48 |
+
'commandLine',
|
| 49 |
+
'transitionBackground'
|
| 50 |
+
]);
|
| 51 |
+
|
| 52 |
+
return (
|
| 53 |
+
<div id={this.props.id} className="reactCommandView">
|
| 54 |
+
<p className={commandClass}>
|
| 55 |
+
<span className="prompt">{'$'}</span>
|
| 56 |
+
{' '}
|
| 57 |
+
<span dangerouslySetInnerHTML={{
|
| 58 |
+
__html: this.state.rawStr
|
| 59 |
+
}}
|
| 60 |
+
/>
|
| 61 |
+
<span className="icons transitionAllSlow">
|
| 62 |
+
<i className="icon-exclamation-sign"></i>
|
| 63 |
+
<i className="icon-check-empty"></i>
|
| 64 |
+
<i className="icon-retweet"></i>
|
| 65 |
+
<i className="icon-check"></i>
|
| 66 |
+
</span>
|
| 67 |
+
</p>
|
| 68 |
+
{this.renderResult()}
|
| 69 |
+
<div className="commandLineWarnings">
|
| 70 |
+
{this.renderFormattedWarnings()}
|
| 71 |
+
</div>
|
| 72 |
+
</div>
|
| 73 |
+
);
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
renderResult() {
|
| 77 |
+
if (!this.state.result) {
|
| 78 |
+
return null;
|
| 79 |
+
}
|
| 80 |
+
// We are going to get a ton of raw markup here
|
| 81 |
+
// so lets split into paragraphs ourselves
|
| 82 |
+
var paragraphs = this.state.result.split("\n");
|
| 83 |
+
var result = [];
|
| 84 |
+
for (var i = 0; i < paragraphs.length; i++) {
|
| 85 |
+
if (paragraphs[i].startsWith('https://')) {
|
| 86 |
+
result.push(
|
| 87 |
+
<a
|
| 88 |
+
href={paragraphs[i]}
|
| 89 |
+
key={'paragraph_' + i}
|
| 90 |
+
dangerouslySetInnerHTML={{
|
| 91 |
+
__html: paragraphs[i]
|
| 92 |
+
}}
|
| 93 |
+
/>
|
| 94 |
+
);
|
| 95 |
+
} else {
|
| 96 |
+
result.push(
|
| 97 |
+
<p
|
| 98 |
+
key={'paragraph_' + i}
|
| 99 |
+
dangerouslySetInnerHTML={{
|
| 100 |
+
__html: paragraphs[i]
|
| 101 |
+
}}
|
| 102 |
+
/>
|
| 103 |
+
);
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
return (
|
| 107 |
+
<div className={'commandLineResult'}>
|
| 108 |
+
{result}
|
| 109 |
+
</div>
|
| 110 |
+
);
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
renderFormattedWarnings() {
|
| 114 |
+
var warnings = this.state.warnings;
|
| 115 |
+
var result = [];
|
| 116 |
+
for (var i = 0; i < warnings.length; i++) {
|
| 117 |
+
result.push(
|
| 118 |
+
<p key={'warning_' + i}>
|
| 119 |
+
<i className="icon-exclamation-sign"></i>
|
| 120 |
+
{warnings[i]}
|
| 121 |
+
</p>
|
| 122 |
+
);
|
| 123 |
+
}
|
| 124 |
+
return result;
|
| 125 |
+
}
|
| 126 |
+
};
|
| 127 |
+
|
| 128 |
+
CommandView.propTypes = {
|
| 129 |
+
// the backbone command model
|
| 130 |
+
command: PropTypes.object.isRequired,
|
| 131 |
+
id: PropTypes.string,
|
| 132 |
+
};
|
| 133 |
+
|
| 134 |
+
module.exports = CommandView;
|
src/js/react_views/CommandsHelperBarView.jsx
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var React = require('react');
|
| 2 |
+
var PropTypes = require('prop-types');
|
| 3 |
+
var HelperBarView = require('../react_views/HelperBarView.jsx');
|
| 4 |
+
var Main = require('../app');
|
| 5 |
+
|
| 6 |
+
var log = require('../log');
|
| 7 |
+
var intl = require('../intl');
|
| 8 |
+
|
| 9 |
+
class CommandsHelperBarView extends React.Component {
|
| 10 |
+
|
| 11 |
+
render() {
|
| 12 |
+
return (
|
| 13 |
+
<HelperBarView
|
| 14 |
+
items={this.getItems()}
|
| 15 |
+
shown={this.props.shown}
|
| 16 |
+
/>
|
| 17 |
+
);
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
fireCommand(command) {
|
| 21 |
+
log.viewInteracted('commandHelperBar');
|
| 22 |
+
Main.getEventBaton().trigger('commandSubmitted', command);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
getItems() {
|
| 26 |
+
return [{
|
| 27 |
+
text: intl.str('command-helper-bar-levels'),
|
| 28 |
+
onClick: function() {
|
| 29 |
+
this.fireCommand('levels');
|
| 30 |
+
}.bind(this),
|
| 31 |
+
}, {
|
| 32 |
+
text: intl.str('command-helper-bar-solution'),
|
| 33 |
+
onClick: function() {
|
| 34 |
+
this.fireCommand('show solution');
|
| 35 |
+
}.bind(this),
|
| 36 |
+
}, {
|
| 37 |
+
text: intl.str('command-helper-bar-reset'),
|
| 38 |
+
onClick: function() {
|
| 39 |
+
this.fireCommand('reset');
|
| 40 |
+
}.bind(this),
|
| 41 |
+
}, {
|
| 42 |
+
text: intl.str('command-helper-bar-undo'),
|
| 43 |
+
onClick: function() {
|
| 44 |
+
this.fireCommand('undo');
|
| 45 |
+
}.bind(this),
|
| 46 |
+
}, {
|
| 47 |
+
text: intl.str('command-helper-bar-objective'),
|
| 48 |
+
onClick: function() {
|
| 49 |
+
this.fireCommand('objective');
|
| 50 |
+
}.bind(this),
|
| 51 |
+
}, {
|
| 52 |
+
text: intl.str('command-helper-bar-help'),
|
| 53 |
+
onClick: function() {
|
| 54 |
+
this.fireCommand('help general; git help');
|
| 55 |
+
}.bind(this)
|
| 56 |
+
}, {
|
| 57 |
+
icon: 'signout',
|
| 58 |
+
onClick: function() {
|
| 59 |
+
this.props.onExit();
|
| 60 |
+
}.bind(this)
|
| 61 |
+
}];
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
};
|
| 65 |
+
|
| 66 |
+
CommandsHelperBarView.propTypes = {
|
| 67 |
+
shown: PropTypes.bool.isRequired,
|
| 68 |
+
onExit: PropTypes.func.isRequired
|
| 69 |
+
};
|
| 70 |
+
|
| 71 |
+
module.exports = CommandsHelperBarView;
|
src/js/react_views/HelperBarView.jsx
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var React = require('react');
|
| 2 |
+
var PropTypes = require('prop-types');
|
| 3 |
+
|
| 4 |
+
var reactUtil = require('../util/reactUtil');
|
| 5 |
+
|
| 6 |
+
class HelperBarView extends React.Component {
|
| 7 |
+
|
| 8 |
+
render() {
|
| 9 |
+
var topClassName = reactUtil.joinClasses([
|
| 10 |
+
'helperBar',
|
| 11 |
+
'transitionAll',
|
| 12 |
+
this.props.shown ? 'show' : '',
|
| 13 |
+
this.props.className ? this.props.className : ''
|
| 14 |
+
]);
|
| 15 |
+
|
| 16 |
+
return (
|
| 17 |
+
<div className={topClassName}>
|
| 18 |
+
{this.props.items.map(function(item, index) {
|
| 19 |
+
return [
|
| 20 |
+
this.renderItem(item, index),
|
| 21 |
+
// ugh -- we need this spacer at the end only
|
| 22 |
+
// if we are not the last element
|
| 23 |
+
index === this.props.items.length - 1 ?
|
| 24 |
+
null :
|
| 25 |
+
<span key={'helper_bar_span_' + index}>{' '}</span>
|
| 26 |
+
];
|
| 27 |
+
}.bind(this))}
|
| 28 |
+
</div>
|
| 29 |
+
);
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
renderItem(item, index) {
|
| 33 |
+
var testID = item.icon || item.testID ||
|
| 34 |
+
item.text.toLowerCase();
|
| 35 |
+
if (item.newPageLink) {
|
| 36 |
+
return (
|
| 37 |
+
<a
|
| 38 |
+
data-testid={testID}
|
| 39 |
+
key={'helper_bar_' + index}
|
| 40 |
+
onClick={item.onClick}
|
| 41 |
+
target="_blank"
|
| 42 |
+
href={item.href}>
|
| 43 |
+
<i className={'icon-' + item.icon} />
|
| 44 |
+
{' '}
|
| 45 |
+
</a>
|
| 46 |
+
);
|
| 47 |
+
}
|
| 48 |
+
return (
|
| 49 |
+
<a
|
| 50 |
+
data-testid={testID}
|
| 51 |
+
key={'helper_bar_' + index}
|
| 52 |
+
onClick={item.onClick}>
|
| 53 |
+
{item.text ? item.text :
|
| 54 |
+
<i className={'icon-' + item.icon} />
|
| 55 |
+
}
|
| 56 |
+
{' '}
|
| 57 |
+
</a>
|
| 58 |
+
);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
};
|
| 62 |
+
|
| 63 |
+
HelperBarView.propTypes = {
|
| 64 |
+
className: PropTypes.string,
|
| 65 |
+
shown: PropTypes.bool.isRequired,
|
| 66 |
+
items: PropTypes.array.isRequired
|
| 67 |
+
};
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
module.exports = HelperBarView;
|
src/js/react_views/IntlHelperBarView.jsx
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var PropTypes = require('prop-types');
|
| 2 |
+
|
| 3 |
+
var HelperBarView = require('../react_views/HelperBarView.jsx');
|
| 4 |
+
var Main = require('../app');
|
| 5 |
+
var React = require('react');
|
| 6 |
+
|
| 7 |
+
var log = require('../log');
|
| 8 |
+
|
| 9 |
+
class IntlHelperBarView extends React.Component{
|
| 10 |
+
|
| 11 |
+
render() {
|
| 12 |
+
return (
|
| 13 |
+
<HelperBarView
|
| 14 |
+
items={this.getItems()}
|
| 15 |
+
shown={this.props.shown}
|
| 16 |
+
/>
|
| 17 |
+
);
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
fireCommand(command) {
|
| 21 |
+
log.viewInteracted('intlSelect');
|
| 22 |
+
Main.getEventBaton().trigger('commandSubmitted', command);
|
| 23 |
+
this.props.onExit();
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
getItems() {
|
| 27 |
+
return [{
|
| 28 |
+
text: 'Git Branching',
|
| 29 |
+
testID: 'english',
|
| 30 |
+
onClick: function() {
|
| 31 |
+
this.fireCommand('locale en_US; levels');
|
| 32 |
+
}.bind(this)
|
| 33 |
+
}, {
|
| 34 |
+
text: '日本語版リポジトリ',
|
| 35 |
+
testID: 'japanese',
|
| 36 |
+
onClick: function() {
|
| 37 |
+
this.fireCommand('locale ja; levels');
|
| 38 |
+
}.bind(this)
|
| 39 |
+
}, {
|
| 40 |
+
text: 'Git 브랜치 배우기',
|
| 41 |
+
testID: 'korean',
|
| 42 |
+
onClick: function() {
|
| 43 |
+
this.fireCommand('locale ko; levels');
|
| 44 |
+
}.bind(this)
|
| 45 |
+
}, {
|
| 46 |
+
text: '学习 Git 分支',
|
| 47 |
+
testID: 'simplifiedChinese',
|
| 48 |
+
onClick: function() {
|
| 49 |
+
this.fireCommand('locale zh_CN; levels');
|
| 50 |
+
}.bind(this)
|
| 51 |
+
}, {
|
| 52 |
+
text: '學習 Git 分支',
|
| 53 |
+
testID: 'traditionalChinese',
|
| 54 |
+
onClick: function() {
|
| 55 |
+
this.fireCommand('locale zh_TW; levels');
|
| 56 |
+
}.bind(this)
|
| 57 |
+
}, {
|
| 58 |
+
text: 'español',
|
| 59 |
+
testID: 'spanish',
|
| 60 |
+
onClick: function() {
|
| 61 |
+
this.fireCommand('locale es_ES; levels');
|
| 62 |
+
}.bind(this)
|
| 63 |
+
}, {
|
| 64 |
+
text: 'argentino',
|
| 65 |
+
testID: 'argentinian',
|
| 66 |
+
onClick: function() {
|
| 67 |
+
this.fireCommand('locale es_AR; levels');
|
| 68 |
+
}.bind(this)
|
| 69 |
+
}, {
|
| 70 |
+
text: 'mexicano',
|
| 71 |
+
testID: 'mexican',
|
| 72 |
+
onClick: function() {
|
| 73 |
+
this.fireCommand('locale es_MX; levels');
|
| 74 |
+
}.bind(this)
|
| 75 |
+
}, {
|
| 76 |
+
text: 'português',
|
| 77 |
+
testID: 'portuguese',
|
| 78 |
+
onClick: function() {
|
| 79 |
+
this.fireCommand('locale pt_BR; levels');
|
| 80 |
+
}.bind(this)
|
| 81 |
+
}, {
|
| 82 |
+
text: 'français',
|
| 83 |
+
testID: 'french',
|
| 84 |
+
onClick: function() {
|
| 85 |
+
this.fireCommand('locale fr_FR; levels');
|
| 86 |
+
}.bind(this)
|
| 87 |
+
}, {
|
| 88 |
+
text: 'Deutsch',
|
| 89 |
+
testID: 'german',
|
| 90 |
+
onClick: function() {
|
| 91 |
+
this.fireCommand('locale de_DE; levels');
|
| 92 |
+
}.bind(this)
|
| 93 |
+
}, {
|
| 94 |
+
text: 'Русский',
|
| 95 |
+
testID: 'russian',
|
| 96 |
+
onClick: function() {
|
| 97 |
+
this.fireCommand('locale ru_RU; levels');
|
| 98 |
+
}.bind(this)
|
| 99 |
+
}, {
|
| 100 |
+
text: 'Українська',
|
| 101 |
+
testID: 'ukrainian',
|
| 102 |
+
onClick: function() {
|
| 103 |
+
this.fireCommand('locale uk; levels');
|
| 104 |
+
}.bind(this)
|
| 105 |
+
}, {
|
| 106 |
+
text: 'Tiếng Việt',
|
| 107 |
+
testID: 'vietnamese',
|
| 108 |
+
onClick: function() {
|
| 109 |
+
this.fireCommand('locale vi; levels');
|
| 110 |
+
}.bind(this)
|
| 111 |
+
}, {
|
| 112 |
+
text: 'Galego',
|
| 113 |
+
testID: 'galician',
|
| 114 |
+
onClick: function() {
|
| 115 |
+
this.fireCommand('locale gl; levels');
|
| 116 |
+
}.bind(this)
|
| 117 |
+
}, {
|
| 118 |
+
text: 'Slovensko',
|
| 119 |
+
testID: 'slovenian',
|
| 120 |
+
onClick: function() {
|
| 121 |
+
this.fireCommand('locale sl_SI; levels');
|
| 122 |
+
}.bind(this)
|
| 123 |
+
}, {
|
| 124 |
+
text: 'Polski',
|
| 125 |
+
testID: 'polish',
|
| 126 |
+
onClick: function() {
|
| 127 |
+
this.fireCommand('locale pl; levels');
|
| 128 |
+
}.bind(this)
|
| 129 |
+
}, {
|
| 130 |
+
text: 'தமிழ்',
|
| 131 |
+
testID: 'tamil',
|
| 132 |
+
onClick: function() {
|
| 133 |
+
this.fireCommand('locale ta_IN; levels');
|
| 134 |
+
}.bind(this)
|
| 135 |
+
}, {
|
| 136 |
+
text: "italiano",
|
| 137 |
+
testID: "italian",
|
| 138 |
+
onClick: function () {
|
| 139 |
+
this.fireCommand("locale it_IT; levels");
|
| 140 |
+
}.bind(this),
|
| 141 |
+
},{
|
| 142 |
+
icon: 'signout',
|
| 143 |
+
onClick: function() {
|
| 144 |
+
this.props.onExit();
|
| 145 |
+
}.bind(this)
|
| 146 |
+
}
|
| 147 |
+
];
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
};
|
| 151 |
+
|
| 152 |
+
IntlHelperBarView.propTypes = {
|
| 153 |
+
shown: PropTypes.bool.isRequired,
|
| 154 |
+
onExit: PropTypes.func.isRequired
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
module.exports = IntlHelperBarView;
|
src/js/react_views/LevelToolbarView.jsx
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var React = require('react');
|
| 2 |
+
var PropTypes = require('prop-types');
|
| 3 |
+
|
| 4 |
+
var intl = require('../intl');
|
| 5 |
+
var reactUtil = require('../util/reactUtil');
|
| 6 |
+
|
| 7 |
+
class LevelToolbarView extends React.Component {
|
| 8 |
+
|
| 9 |
+
constructor(props, context) {
|
| 10 |
+
super(props, context);
|
| 11 |
+
this.state = {
|
| 12 |
+
isHidden: true,
|
| 13 |
+
isGoalExpanded: this.props.parent.getIsGoalExpanded()
|
| 14 |
+
};
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
componentWillUnmount() {
|
| 18 |
+
this._isMounted = false;
|
| 19 |
+
}
|
| 20 |
+
componentDidMount() {
|
| 21 |
+
this._isMounted = true;
|
| 22 |
+
this.setState({
|
| 23 |
+
isHidden: this.props.parent.getIsGoalExpanded(),
|
| 24 |
+
isGoalExpanded: this.props.parent.getIsGoalExpanded()
|
| 25 |
+
});
|
| 26 |
+
this.props.parent.on('goalToggled', function() {
|
| 27 |
+
if (!this._isMounted) {
|
| 28 |
+
return;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
this.setState({
|
| 32 |
+
isGoalExpanded: this.props.parent.getIsGoalExpanded()
|
| 33 |
+
});
|
| 34 |
+
}.bind(this));
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
render() {
|
| 38 |
+
return (
|
| 39 |
+
<div className={reactUtil.joinClasses([
|
| 40 |
+
'toolbar',
|
| 41 |
+
'level-toolbar',
|
| 42 |
+
'box',
|
| 43 |
+
'vertical',
|
| 44 |
+
'center',
|
| 45 |
+
'transitionAll',
|
| 46 |
+
this.state.isHidden ? 'hidden' : ''
|
| 47 |
+
])}>
|
| 48 |
+
<div className="clearfix">
|
| 49 |
+
<div className="levelNameWrapper">
|
| 50 |
+
<i className="icon-bolt"></i>
|
| 51 |
+
{ intl.str('level-label') }
|
| 52 |
+
<span className="levelToolbarSpan">
|
| 53 |
+
{this.props.name}
|
| 54 |
+
</span>
|
| 55 |
+
</div>
|
| 56 |
+
</div>
|
| 57 |
+
<div className="buttonsWrapper">
|
| 58 |
+
<div className="showGoalWrapper">
|
| 59 |
+
<button
|
| 60 |
+
onClick={this.props.onGoalClick}
|
| 61 |
+
type="button">
|
| 62 |
+
{this.state.isGoalExpanded ?
|
| 63 |
+
intl.str('hide-goal-button') :
|
| 64 |
+
intl.str('show-goal-button')
|
| 65 |
+
}
|
| 66 |
+
</button>
|
| 67 |
+
</div>
|
| 68 |
+
<div className="showObjectiveWrapper">
|
| 69 |
+
<button
|
| 70 |
+
onClick={this.props.onObjectiveClick}
|
| 71 |
+
type="button">
|
| 72 |
+
{intl.str('objective-button')}
|
| 73 |
+
</button>
|
| 74 |
+
</div>
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
+
);
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
};
|
| 81 |
+
|
| 82 |
+
LevelToolbarView.propTypes = {
|
| 83 |
+
name: PropTypes.string.isRequired,
|
| 84 |
+
onGoalClick: PropTypes.func.isRequired,
|
| 85 |
+
onObjectiveClick: PropTypes.func.isRequired,
|
| 86 |
+
parent: PropTypes.object.isRequired
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
module.exports = LevelToolbarView;
|
src/js/react_views/MainHelperBarView.jsx
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var HelperBarView = require('../react_views/HelperBarView.jsx');
|
| 2 |
+
var IntlHelperBarView =
|
| 3 |
+
require('../react_views/IntlHelperBarView.jsx');
|
| 4 |
+
var CommandsHelperBarView =
|
| 5 |
+
require('../react_views/CommandsHelperBarView.jsx');
|
| 6 |
+
var React = require('react');
|
| 7 |
+
|
| 8 |
+
var keyMirror = require('../util/keyMirror');
|
| 9 |
+
var log = require('../log');
|
| 10 |
+
|
| 11 |
+
var BARS = keyMirror({
|
| 12 |
+
SELF: null,
|
| 13 |
+
INTL: null,
|
| 14 |
+
COMMANDS: null
|
| 15 |
+
});
|
| 16 |
+
|
| 17 |
+
class MainHelperBarView extends React.Component {
|
| 18 |
+
|
| 19 |
+
constructor(props, context) {
|
| 20 |
+
super(props, context);
|
| 21 |
+
this.state = {
|
| 22 |
+
shownBar: BARS.SELF
|
| 23 |
+
};
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
render() {
|
| 27 |
+
return (
|
| 28 |
+
<div>
|
| 29 |
+
<HelperBarView
|
| 30 |
+
className="BaseHelperBar"
|
| 31 |
+
items={this.getItems()}
|
| 32 |
+
shown={this.state.shownBar === BARS.SELF}
|
| 33 |
+
/>
|
| 34 |
+
<CommandsHelperBarView
|
| 35 |
+
shown={this.state.shownBar === BARS.COMMANDS}
|
| 36 |
+
onExit={this.showSelf.bind(this)}
|
| 37 |
+
/>
|
| 38 |
+
<IntlHelperBarView
|
| 39 |
+
shown={this.state.shownBar === BARS.INTL}
|
| 40 |
+
onExit={this.showSelf.bind(this)}
|
| 41 |
+
/>
|
| 42 |
+
</div>
|
| 43 |
+
);
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
showSelf() {
|
| 47 |
+
this.setState({
|
| 48 |
+
shownBar: BARS.SELF
|
| 49 |
+
});
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
getItems() {
|
| 53 |
+
return [{
|
| 54 |
+
icon: 'question-sign',
|
| 55 |
+
onClick: function() {
|
| 56 |
+
this.setState({
|
| 57 |
+
shownBar: BARS.COMMANDS
|
| 58 |
+
});
|
| 59 |
+
}.bind(this)
|
| 60 |
+
}, {
|
| 61 |
+
icon: 'globe',
|
| 62 |
+
onClick: function() {
|
| 63 |
+
this.setState({
|
| 64 |
+
shownBar: BARS.INTL
|
| 65 |
+
});
|
| 66 |
+
}.bind(this)
|
| 67 |
+
}, {
|
| 68 |
+
newPageLink: true,
|
| 69 |
+
icon: 'twitter',
|
| 70 |
+
href: 'https://twitter.com/petermcottle'
|
| 71 |
+
}];
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
};
|
| 75 |
+
|
| 76 |
+
module.exports = MainHelperBarView;
|
src/js/sandbox/commands.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var util = require('../util');
|
| 2 |
+
|
| 3 |
+
var constants = require('../util/constants');
|
| 4 |
+
var intl = require('../intl');
|
| 5 |
+
|
| 6 |
+
var Commands = require('../commands');
|
| 7 |
+
var Errors = require('../util/errors');
|
| 8 |
+
var CommandProcessError = Errors.CommandProcessError;
|
| 9 |
+
var LocaleStore = require('../stores/LocaleStore');
|
| 10 |
+
var LocaleActions = require('../actions/LocaleActions');
|
| 11 |
+
var LevelStore = require('../stores/LevelStore');
|
| 12 |
+
var GlobalStateStore = require('../stores/GlobalStateStore');
|
| 13 |
+
var GlobalStateActions = require('../actions/GlobalStateActions');
|
| 14 |
+
var GitError = Errors.GitError;
|
| 15 |
+
var Warning = Errors.Warning;
|
| 16 |
+
var CommandResult = Errors.CommandResult;
|
| 17 |
+
|
| 18 |
+
var instantCommands = [
|
| 19 |
+
[/^ls( |$)/, function() {
|
| 20 |
+
throw new CommandResult({
|
| 21 |
+
msg: intl.str('ls-command')
|
| 22 |
+
});
|
| 23 |
+
}],
|
| 24 |
+
[/^cd( |$)/, function() {
|
| 25 |
+
throw new CommandResult({
|
| 26 |
+
msg: intl.str('cd-command')
|
| 27 |
+
});
|
| 28 |
+
}],
|
| 29 |
+
[/^(locale|locale reset)$/, function(bits) {
|
| 30 |
+
LocaleActions.changeLocale(
|
| 31 |
+
LocaleStore.getDefaultLocale()
|
| 32 |
+
);
|
| 33 |
+
|
| 34 |
+
throw new CommandResult({
|
| 35 |
+
msg: intl.str(
|
| 36 |
+
'locale-reset-command',
|
| 37 |
+
{ locale: LocaleStore.getDefaultLocale() }
|
| 38 |
+
)
|
| 39 |
+
});
|
| 40 |
+
}],
|
| 41 |
+
[/^show$/, function(bits) {
|
| 42 |
+
var lines = [
|
| 43 |
+
intl.str('show-command'),
|
| 44 |
+
'<br/>',
|
| 45 |
+
'show commands',
|
| 46 |
+
'show solution',
|
| 47 |
+
'show goal'
|
| 48 |
+
];
|
| 49 |
+
|
| 50 |
+
throw new CommandResult({
|
| 51 |
+
msg: lines.join('\n')
|
| 52 |
+
});
|
| 53 |
+
}],
|
| 54 |
+
[/^alias (\w+)="(.+)"$/, function(bits) {
|
| 55 |
+
const alias = bits[1];
|
| 56 |
+
const expansion = bits[2];
|
| 57 |
+
LevelStore.addToAliasMap(alias, expansion);
|
| 58 |
+
throw new CommandResult({
|
| 59 |
+
msg: 'Set alias "'+alias+'" to "'+expansion+'"',
|
| 60 |
+
});
|
| 61 |
+
}],
|
| 62 |
+
[/^unalias (\w+)$/, function(bits) {
|
| 63 |
+
const alias = bits[1];
|
| 64 |
+
LevelStore.removeFromAliasMap(alias);
|
| 65 |
+
throw new CommandResult({
|
| 66 |
+
msg: 'Removed alias "'+alias+'"',
|
| 67 |
+
});
|
| 68 |
+
}],
|
| 69 |
+
[/^locale (\w+)$/, function(bits) {
|
| 70 |
+
LocaleActions.changeLocale(bits[1]);
|
| 71 |
+
throw new CommandResult({
|
| 72 |
+
msg: intl.str(
|
| 73 |
+
'locale-command',
|
| 74 |
+
{ locale: bits[1] }
|
| 75 |
+
)
|
| 76 |
+
});
|
| 77 |
+
}],
|
| 78 |
+
[/^flip$/, function() {
|
| 79 |
+
GlobalStateActions.changeFlipTreeY(
|
| 80 |
+
!GlobalStateStore.getFlipTreeY()
|
| 81 |
+
);
|
| 82 |
+
require('../app').getEvents().trigger('refreshTree');
|
| 83 |
+
throw new CommandResult({
|
| 84 |
+
msg: intl.str('flip-tree-command')
|
| 85 |
+
});
|
| 86 |
+
}],
|
| 87 |
+
[/^disableLevelInstructions$/, function() {
|
| 88 |
+
GlobalStateActions.disableLevelInstructions();
|
| 89 |
+
throw new CommandResult({
|
| 90 |
+
msg: intl.todo('Level instructions disabled'),
|
| 91 |
+
});
|
| 92 |
+
}],
|
| 93 |
+
[/^refresh$/, function() {
|
| 94 |
+
var events = require('../app').getEvents();
|
| 95 |
+
|
| 96 |
+
events.trigger('refreshTree');
|
| 97 |
+
throw new CommandResult({
|
| 98 |
+
msg: intl.str('refresh-tree-command')
|
| 99 |
+
});
|
| 100 |
+
}],
|
| 101 |
+
[/^rollup (\d+)$/, function(bits) {
|
| 102 |
+
var events = require('../app').getEvents();
|
| 103 |
+
|
| 104 |
+
// go roll up these commands by joining them with semicolons
|
| 105 |
+
events.trigger('rollupCommands', bits[1]);
|
| 106 |
+
throw new CommandResult({
|
| 107 |
+
msg: 'Commands combined!'
|
| 108 |
+
});
|
| 109 |
+
}],
|
| 110 |
+
[/^echo "(.*?)"$|^echo (.*?)$/, function(bits) {
|
| 111 |
+
var msg = bits[1] || bits[2];
|
| 112 |
+
throw new CommandResult({
|
| 113 |
+
msg: msg
|
| 114 |
+
});
|
| 115 |
+
}],
|
| 116 |
+
[/^show +commands$/, function(bits) {
|
| 117 |
+
var allCommands = getAllCommands();
|
| 118 |
+
var allOptions = Commands.commands.getOptionMap();
|
| 119 |
+
var commandToOptions = {};
|
| 120 |
+
|
| 121 |
+
Object.keys(allOptions).forEach(function(vcs) {
|
| 122 |
+
var vcsMap = allOptions[vcs];
|
| 123 |
+
Object.keys(vcsMap).forEach(function(method) {
|
| 124 |
+
var options = vcsMap[method];
|
| 125 |
+
if (options) {
|
| 126 |
+
commandToOptions[vcs + ' ' + method] = Object.keys(options).filter(option => option.length > 1);
|
| 127 |
+
}
|
| 128 |
+
});
|
| 129 |
+
});
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
var lines = [
|
| 133 |
+
intl.str('show-all-commands'),
|
| 134 |
+
'<br/>'
|
| 135 |
+
];
|
| 136 |
+
Object.keys(allCommands)
|
| 137 |
+
.forEach(function(command) {
|
| 138 |
+
lines.push(command);
|
| 139 |
+
if (commandToOptions[command]) {
|
| 140 |
+
commandToOptions[command].forEach(option => lines.push(' ' + option));
|
| 141 |
+
}
|
| 142 |
+
});
|
| 143 |
+
|
| 144 |
+
throw new CommandResult({
|
| 145 |
+
msg: lines.join('\n')
|
| 146 |
+
});
|
| 147 |
+
}]
|
| 148 |
+
];
|
| 149 |
+
|
| 150 |
+
var regexMap = {
|
| 151 |
+
'reset solved': /^reset solved($|\s)/,
|
| 152 |
+
'help': /^help( +general)?$|^\?$/,
|
| 153 |
+
'reset': /^reset( +--forSolution)?$/,
|
| 154 |
+
'delay': /^delay (\d+)$/,
|
| 155 |
+
'clear': /^clear($|\s)/,
|
| 156 |
+
'exit level': /^exit level($|\s)/,
|
| 157 |
+
'sandbox': /^sandbox($|\s)/,
|
| 158 |
+
'level': /^level\s?([a-zA-Z0-9]*)/,
|
| 159 |
+
'levels': /^levels($|\s)/,
|
| 160 |
+
'mobileAlert': /^mobile alert($|\s)/,
|
| 161 |
+
'build level': /^build +level\s?([a-zA-Z0-9]*)$/,
|
| 162 |
+
'export tree': /^export +tree$/,
|
| 163 |
+
'importTreeNow': /^importTreeNow($|\s)/,
|
| 164 |
+
'importLevelNow': /^importLevelNow($|\s)/,
|
| 165 |
+
'import tree': /^import +tree$/,
|
| 166 |
+
'import level': /^import +level$/,
|
| 167 |
+
'undo': /^undo($|\s)/,
|
| 168 |
+
'share permalink': /^share( +permalink)?$/
|
| 169 |
+
};
|
| 170 |
+
|
| 171 |
+
var getAllCommands = function() {
|
| 172 |
+
var toDelete = [
|
| 173 |
+
'mobileAlert'
|
| 174 |
+
];
|
| 175 |
+
|
| 176 |
+
var allCommands = Object.assign(
|
| 177 |
+
{},
|
| 178 |
+
require('../level').regexMap,
|
| 179 |
+
regexMap
|
| 180 |
+
);
|
| 181 |
+
var mRegexMap = Commands.commands.getRegexMap();
|
| 182 |
+
Object.keys(mRegexMap).forEach(function(vcs) {
|
| 183 |
+
var map = mRegexMap[vcs];
|
| 184 |
+
Object.keys(map).forEach(function(method) {
|
| 185 |
+
var regex = map[method];
|
| 186 |
+
allCommands[vcs + ' ' + method] = regex;
|
| 187 |
+
});
|
| 188 |
+
});
|
| 189 |
+
toDelete.forEach(function(key) {
|
| 190 |
+
delete allCommands[key];
|
| 191 |
+
});
|
| 192 |
+
|
| 193 |
+
return allCommands;
|
| 194 |
+
};
|
| 195 |
+
|
| 196 |
+
exports.getAllCommands = getAllCommands;
|
| 197 |
+
exports.instantCommands = instantCommands;
|
| 198 |
+
exports.parse = util.genParseCommand(regexMap, 'processSandboxCommand');
|
| 199 |
+
|
| 200 |
+
// optimistically parse some level and level builder commands; we do this
|
| 201 |
+
// so you can enter things like "level intro1; show goal" and not
|
| 202 |
+
// have it barf. when the
|
| 203 |
+
// command fires the event, it will check if there is a listener and if not throw
|
| 204 |
+
// an error
|
| 205 |
+
|
| 206 |
+
// note: these are getters / setters because the require kills us
|
| 207 |
+
exports.getOptimisticLevelParse = function() {
|
| 208 |
+
return util.genParseCommand(
|
| 209 |
+
require('../level').regexMap,
|
| 210 |
+
'processLevelCommand'
|
| 211 |
+
);
|
| 212 |
+
};
|
| 213 |
+
|
| 214 |
+
exports.getOptimisticLevelBuilderParse = function() {
|
| 215 |
+
return util.genParseCommand(
|
| 216 |
+
require('../level/builder').regexMap,
|
| 217 |
+
'processLevelBuilderCommand'
|
| 218 |
+
);
|
| 219 |
+
};
|
src/js/sandbox/index.js
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var Q = require('q');
|
| 2 |
+
var Backbone = require('backbone');
|
| 3 |
+
|
| 4 |
+
var util = require('../util');
|
| 5 |
+
var intl = require('../intl');
|
| 6 |
+
var Main = require('../app');
|
| 7 |
+
var Errors = require('../util/errors');
|
| 8 |
+
|
| 9 |
+
var Visualization = require('../visuals/visualization').Visualization;
|
| 10 |
+
var ParseWaterfall = require('../level/parseWaterfall').ParseWaterfall;
|
| 11 |
+
var DisabledMap = require('../level/disabledMap').DisabledMap;
|
| 12 |
+
var Command = require('../models/commandModel').Command;
|
| 13 |
+
var GitShim = require('../git/gitShim').GitShim;
|
| 14 |
+
var LevelActions = require('../actions/LevelActions');
|
| 15 |
+
var LevelStore = require('../stores/LevelStore');
|
| 16 |
+
|
| 17 |
+
var Views = require('../views');
|
| 18 |
+
var ModalTerminal = Views.ModalTerminal;
|
| 19 |
+
var ModalAlert = Views.ModalAlert;
|
| 20 |
+
var BuilderViews = require('../views/builderViews');
|
| 21 |
+
var MultiView = require('../views/multiView').MultiView;
|
| 22 |
+
|
| 23 |
+
var Sandbox = Backbone.View.extend({
|
| 24 |
+
// tag name here is purely vestigial. I made this a view
|
| 25 |
+
// simply to use inheritance and have a nice event system in place
|
| 26 |
+
tagName: 'div',
|
| 27 |
+
initialize: function(options) {
|
| 28 |
+
options = options || {};
|
| 29 |
+
this.options = options;
|
| 30 |
+
|
| 31 |
+
this.initVisualization(options);
|
| 32 |
+
this.initCommandCollection(options);
|
| 33 |
+
this.initParseWaterfall(options);
|
| 34 |
+
this.initGitShim(options);
|
| 35 |
+
this.initUndoStack(options);
|
| 36 |
+
|
| 37 |
+
if (!options.wait) {
|
| 38 |
+
this.takeControl();
|
| 39 |
+
}
|
| 40 |
+
},
|
| 41 |
+
|
| 42 |
+
getDefaultVisEl: function() {
|
| 43 |
+
return $('#mainVisSpace')[0];
|
| 44 |
+
},
|
| 45 |
+
|
| 46 |
+
getAnimationTime: function() { return 700 * 1.5; },
|
| 47 |
+
|
| 48 |
+
initVisualization: function(options) {
|
| 49 |
+
this.mainVis = new Visualization({
|
| 50 |
+
el: options.el || this.getDefaultVisEl()
|
| 51 |
+
});
|
| 52 |
+
},
|
| 53 |
+
|
| 54 |
+
initUndoStack: function(options) {
|
| 55 |
+
this.undoStack = [];
|
| 56 |
+
},
|
| 57 |
+
|
| 58 |
+
initCommandCollection: function(options) {
|
| 59 |
+
// don't add it to just any collection -- adding to the
|
| 60 |
+
// CommandUI collection will put in history
|
| 61 |
+
this.commandCollection = Main.getCommandUI().commandCollection;
|
| 62 |
+
},
|
| 63 |
+
|
| 64 |
+
initParseWaterfall: function(options) {
|
| 65 |
+
this.parseWaterfall = new ParseWaterfall();
|
| 66 |
+
},
|
| 67 |
+
|
| 68 |
+
initGitShim: function(options) {
|
| 69 |
+
this.gitShim = new GitShim({
|
| 70 |
+
beforeCB: this.beforeCommandCB.bind(this)
|
| 71 |
+
});
|
| 72 |
+
},
|
| 73 |
+
|
| 74 |
+
takeControl: function() {
|
| 75 |
+
// we will be handling commands that are submitted, mainly to add the sandbox
|
| 76 |
+
// functionality (which is included by default in ParseWaterfall())
|
| 77 |
+
Main.getEventBaton().stealBaton('commandSubmitted', this.commandSubmitted, this);
|
| 78 |
+
// we obviously take care of sandbox commands
|
| 79 |
+
Main.getEventBaton().stealBaton('processSandboxCommand', this.processSandboxCommand, this);
|
| 80 |
+
|
| 81 |
+
// a few things to help transition between levels and sandbox
|
| 82 |
+
Main.getEventBaton().stealBaton('levelExited', this.levelExited, this);
|
| 83 |
+
|
| 84 |
+
this.insertGitShim();
|
| 85 |
+
},
|
| 86 |
+
|
| 87 |
+
releaseControl: function() {
|
| 88 |
+
// we will be handling commands that are submitted, mainly to add the sandbox
|
| 89 |
+
// functionality (which is included by default in ParseWaterfall())
|
| 90 |
+
Main.getEventBaton().releaseBaton('commandSubmitted', this.commandSubmitted, this);
|
| 91 |
+
// we obviously take care of sandbox commands
|
| 92 |
+
Main.getEventBaton().releaseBaton('processSandboxCommand', this.processSandboxCommand, this);
|
| 93 |
+
// a few things to help transition between levels and sandbox
|
| 94 |
+
Main.getEventBaton().releaseBaton('levelExited', this.levelExited, this);
|
| 95 |
+
|
| 96 |
+
this.releaseGitShim();
|
| 97 |
+
},
|
| 98 |
+
|
| 99 |
+
releaseGitShim: function() {
|
| 100 |
+
if (this.gitShim) {
|
| 101 |
+
this.gitShim.removeShim();
|
| 102 |
+
}
|
| 103 |
+
},
|
| 104 |
+
|
| 105 |
+
insertGitShim: function() {
|
| 106 |
+
// and our git shim goes in after the git engine is ready so it doesn't steal the baton
|
| 107 |
+
// too early
|
| 108 |
+
if (this.gitShim) {
|
| 109 |
+
this.mainVis.customEvents.on('gitEngineReady', function() {
|
| 110 |
+
this.gitShim.insertShim();
|
| 111 |
+
},this);
|
| 112 |
+
}
|
| 113 |
+
},
|
| 114 |
+
|
| 115 |
+
beforeCommandCB: function(command) {
|
| 116 |
+
this.pushUndo();
|
| 117 |
+
},
|
| 118 |
+
|
| 119 |
+
pushUndo: function() {
|
| 120 |
+
// go ahead and push the three onto the stack
|
| 121 |
+
this.undoStack.push(this.mainVis.gitEngine.printTree());
|
| 122 |
+
},
|
| 123 |
+
|
| 124 |
+
undo: function(command, deferred) {
|
| 125 |
+
var toRestore = this.undoStack.pop();
|
| 126 |
+
if (!toRestore) {
|
| 127 |
+
command.set('error', new Errors.GitError({
|
| 128 |
+
msg: intl.str('undo-stack-empty')
|
| 129 |
+
}));
|
| 130 |
+
deferred.resolve();
|
| 131 |
+
return;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
this.mainVis.reset(toRestore);
|
| 135 |
+
setTimeout(function() {
|
| 136 |
+
command.finishWith(deferred);
|
| 137 |
+
}, this.mainVis.getAnimationTime());
|
| 138 |
+
},
|
| 139 |
+
|
| 140 |
+
commandSubmitted: function(value) {
|
| 141 |
+
// allow other things to see this command (aka command history on terminal)
|
| 142 |
+
Main.getEvents().trigger('commandSubmittedPassive', value);
|
| 143 |
+
|
| 144 |
+
util.splitTextCommand(value, function(command) {
|
| 145 |
+
this.commandCollection.add(new Command({
|
| 146 |
+
rawStr: command,
|
| 147 |
+
parseWaterfall: this.parseWaterfall
|
| 148 |
+
}));
|
| 149 |
+
}, this);
|
| 150 |
+
},
|
| 151 |
+
|
| 152 |
+
startLevel: function(command, deferred) {
|
| 153 |
+
var regexResults = command.get('regexResults') || [];
|
| 154 |
+
var desiredID = regexResults[1] || '';
|
| 155 |
+
var levelJSON = LevelStore.getLevel(desiredID);
|
| 156 |
+
|
| 157 |
+
// handle the case where that level is not found...
|
| 158 |
+
if (!levelJSON) {
|
| 159 |
+
command.addWarning(
|
| 160 |
+
intl.str(
|
| 161 |
+
'level-no-id',
|
| 162 |
+
{ id: desiredID }
|
| 163 |
+
)
|
| 164 |
+
);
|
| 165 |
+
Main.getEventBaton().trigger('commandSubmitted', 'levels');
|
| 166 |
+
|
| 167 |
+
command.set('status', 'error');
|
| 168 |
+
deferred.resolve();
|
| 169 |
+
return;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
// we are good to go!! lets prep a bit visually
|
| 173 |
+
this.hide();
|
| 174 |
+
this.clear();
|
| 175 |
+
|
| 176 |
+
// we don't even need a reference to this,
|
| 177 |
+
// everything will be handled via event baton :DDDDDDDDD
|
| 178 |
+
var whenLevelOpen = Q.defer();
|
| 179 |
+
var Level = require('../level').Level;
|
| 180 |
+
|
| 181 |
+
this.currentLevel = new Level({
|
| 182 |
+
level: levelJSON,
|
| 183 |
+
deferred: whenLevelOpen,
|
| 184 |
+
command: command
|
| 185 |
+
});
|
| 186 |
+
|
| 187 |
+
whenLevelOpen.promise.then(function() {
|
| 188 |
+
command.finishWith(deferred);
|
| 189 |
+
});
|
| 190 |
+
},
|
| 191 |
+
|
| 192 |
+
buildLevel: function(command, deferred) {
|
| 193 |
+
this.hide();
|
| 194 |
+
this.clear();
|
| 195 |
+
|
| 196 |
+
var whenBuilderOpen = Q.defer();
|
| 197 |
+
var LevelBuilder = require('../level/builder').LevelBuilder;
|
| 198 |
+
|
| 199 |
+
var regexResults = command.get('regexResults') || [];
|
| 200 |
+
var toEdit = regexResults[1] || false;
|
| 201 |
+
this.levelBuilder = new LevelBuilder({
|
| 202 |
+
deferred: whenBuilderOpen,
|
| 203 |
+
editLevel: toEdit
|
| 204 |
+
});
|
| 205 |
+
whenBuilderOpen.promise.then(function() {
|
| 206 |
+
command.finishWith(deferred);
|
| 207 |
+
});
|
| 208 |
+
},
|
| 209 |
+
|
| 210 |
+
exitLevel: function(command, deferred) {
|
| 211 |
+
command.addWarning(
|
| 212 |
+
intl.str('level-cant-exit')
|
| 213 |
+
);
|
| 214 |
+
command.set('status', 'error');
|
| 215 |
+
deferred.resolve();
|
| 216 |
+
},
|
| 217 |
+
|
| 218 |
+
showLevels: function(command, deferred) {
|
| 219 |
+
var whenClosed = Q.defer();
|
| 220 |
+
Main.getLevelDropdown().show(whenClosed, command);
|
| 221 |
+
whenClosed.promise.done(function() {
|
| 222 |
+
command.finishWith(deferred);
|
| 223 |
+
});
|
| 224 |
+
},
|
| 225 |
+
|
| 226 |
+
sharePermalink: function(command, deferred) {
|
| 227 |
+
var treeJSON = JSON.stringify(this.mainVis.gitEngine.exportTree());
|
| 228 |
+
var url =
|
| 229 |
+
'https://learngitbranching.js.org/?NODEMO&command=importTreeNow%20' + escape(treeJSON);
|
| 230 |
+
command.setResult(
|
| 231 |
+
intl.todo('Here is a link to the current state of the tree: ') + '\n' + url
|
| 232 |
+
);
|
| 233 |
+
command.finishWith(deferred);
|
| 234 |
+
},
|
| 235 |
+
|
| 236 |
+
resetSolved: function(command, deferred) {
|
| 237 |
+
if (command.get('regexResults').input !== 'reset solved --confirm') {
|
| 238 |
+
command.set('error', new Errors.GitError({
|
| 239 |
+
msg: 'Reset solved will mark each level as not yet solved; because ' +
|
| 240 |
+
'this is a destructive command, please pass in --confirm to execute',
|
| 241 |
+
}));
|
| 242 |
+
command.finishWith(deferred);
|
| 243 |
+
return;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
LevelActions.resetLevelsSolved();
|
| 247 |
+
command.addWarning(
|
| 248 |
+
intl.str('solved-map-reset')
|
| 249 |
+
);
|
| 250 |
+
command.finishWith(deferred);
|
| 251 |
+
},
|
| 252 |
+
|
| 253 |
+
processSandboxCommand: function(command, deferred) {
|
| 254 |
+
// I'm tempted to do cancel case conversion, but there are
|
| 255 |
+
// some exceptions to the rule
|
| 256 |
+
var commandMap = {
|
| 257 |
+
'reset solved': this.resetSolved,
|
| 258 |
+
'undo': this.undo,
|
| 259 |
+
'help general': this.helpDialog,
|
| 260 |
+
'help': this.helpDialog,
|
| 261 |
+
'reset': this.reset,
|
| 262 |
+
'delay': this.delay,
|
| 263 |
+
'clear': this.clear,
|
| 264 |
+
'exit level': this.exitLevel,
|
| 265 |
+
'level': this.startLevel,
|
| 266 |
+
'sandbox': this.exitLevel,
|
| 267 |
+
'levels': this.showLevels,
|
| 268 |
+
'mobileAlert': this.mobileAlert,
|
| 269 |
+
'build level': this.buildLevel,
|
| 270 |
+
'export tree': this.exportTree,
|
| 271 |
+
'import tree': this.importTree,
|
| 272 |
+
'importTreeNow': this.importTreeNow,
|
| 273 |
+
'import level': this.importLevel,
|
| 274 |
+
'importLevelNow': this.importLevelNow,
|
| 275 |
+
'share permalink': this.sharePermalink,
|
| 276 |
+
};
|
| 277 |
+
|
| 278 |
+
var method = commandMap[command.get('method')];
|
| 279 |
+
if (!method) { throw new Error('no method for that wut'); }
|
| 280 |
+
|
| 281 |
+
method.apply(this, [command, deferred]);
|
| 282 |
+
},
|
| 283 |
+
|
| 284 |
+
hide: function() {
|
| 285 |
+
this.mainVis.hide();
|
| 286 |
+
},
|
| 287 |
+
|
| 288 |
+
levelExited: function() {
|
| 289 |
+
this.show();
|
| 290 |
+
},
|
| 291 |
+
|
| 292 |
+
show: function() {
|
| 293 |
+
this.mainVis.show();
|
| 294 |
+
},
|
| 295 |
+
|
| 296 |
+
importLevelNow: function(command, deferred) {
|
| 297 |
+
var options = command.get('regexResults') || [];
|
| 298 |
+
if (options.length < 2) {
|
| 299 |
+
command.set('error', new Errors.GitError({
|
| 300 |
+
msg: intl.str('git-error-options')
|
| 301 |
+
}));
|
| 302 |
+
command.finishWith(deferred);
|
| 303 |
+
return;
|
| 304 |
+
}
|
| 305 |
+
var string = options.input.replace(/importLevelNow\s+/g, '');
|
| 306 |
+
var Level = require('../level').Level;
|
| 307 |
+
try {
|
| 308 |
+
var levelJSON = JSON.parse(unescape(string));
|
| 309 |
+
var whenLevelOpen = Q.defer();
|
| 310 |
+
this.currentLevel = new Level({
|
| 311 |
+
level: levelJSON,
|
| 312 |
+
deferred: whenLevelOpen,
|
| 313 |
+
command: command
|
| 314 |
+
});
|
| 315 |
+
this.hide();
|
| 316 |
+
|
| 317 |
+
whenLevelOpen.promise.then(function() {
|
| 318 |
+
command.finishWith(deferred);
|
| 319 |
+
});
|
| 320 |
+
} catch(e) {
|
| 321 |
+
command.set('error', new Errors.GitError({
|
| 322 |
+
msg: 'Something went wrong ' + String(e)
|
| 323 |
+
}));
|
| 324 |
+
throw e;
|
| 325 |
+
}
|
| 326 |
+
command.finishWith(deferred);
|
| 327 |
+
},
|
| 328 |
+
|
| 329 |
+
importTreeNow: function(command, deferred) {
|
| 330 |
+
var options = command.get('regexResults') || [];
|
| 331 |
+
if (options.length < 2) {
|
| 332 |
+
command.set('error', new Errors.GitError({
|
| 333 |
+
msg: intl.str('git-error-options')
|
| 334 |
+
}));
|
| 335 |
+
command.finishWith(deferred);
|
| 336 |
+
}
|
| 337 |
+
var string = options.input.replace(/importTreeNow\s+/g, '');
|
| 338 |
+
try {
|
| 339 |
+
this.mainVis.gitEngine.loadTreeFromString(string);
|
| 340 |
+
} catch (e) {
|
| 341 |
+
command.set('error', new Errors.GitError({
|
| 342 |
+
msg: String(e)
|
| 343 |
+
}));
|
| 344 |
+
}
|
| 345 |
+
command.finishWith(deferred);
|
| 346 |
+
},
|
| 347 |
+
|
| 348 |
+
importTree: function(command, deferred) {
|
| 349 |
+
var jsonGrabber = new BuilderViews.MarkdownPresenter({
|
| 350 |
+
previewText: intl.str('paste-json'),
|
| 351 |
+
fillerText: ' '
|
| 352 |
+
});
|
| 353 |
+
jsonGrabber.deferred.promise
|
| 354 |
+
.then(function(treeJSON) {
|
| 355 |
+
try {
|
| 356 |
+
this.mainVis.gitEngine.loadTree(JSON.parse(treeJSON));
|
| 357 |
+
} catch(e) {
|
| 358 |
+
this.mainVis.reset();
|
| 359 |
+
new MultiView({
|
| 360 |
+
childViews: [{
|
| 361 |
+
type: 'ModalAlert',
|
| 362 |
+
options: {
|
| 363 |
+
markdowns: [
|
| 364 |
+
'## Error!',
|
| 365 |
+
'',
|
| 366 |
+
'Something is wrong with that JSON! Here is the error:',
|
| 367 |
+
'',
|
| 368 |
+
String(e)
|
| 369 |
+
]
|
| 370 |
+
}
|
| 371 |
+
}]
|
| 372 |
+
});
|
| 373 |
+
}
|
| 374 |
+
}.bind(this))
|
| 375 |
+
.fail(function() { })
|
| 376 |
+
.done(function() {
|
| 377 |
+
command.finishWith(deferred);
|
| 378 |
+
});
|
| 379 |
+
},
|
| 380 |
+
|
| 381 |
+
importLevel: function(command, deferred) {
|
| 382 |
+
var jsonGrabber = new BuilderViews.MarkdownPresenter({
|
| 383 |
+
previewText: intl.str('paste-json'),
|
| 384 |
+
fillerText: ' '
|
| 385 |
+
});
|
| 386 |
+
|
| 387 |
+
jsonGrabber.deferred.promise
|
| 388 |
+
.then(function(inputText) {
|
| 389 |
+
var Level = require('../level').Level;
|
| 390 |
+
try {
|
| 391 |
+
var levelJSON = JSON.parse(inputText);
|
| 392 |
+
var whenLevelOpen = Q.defer();
|
| 393 |
+
this.currentLevel = new Level({
|
| 394 |
+
level: levelJSON,
|
| 395 |
+
deferred: whenLevelOpen,
|
| 396 |
+
command: command
|
| 397 |
+
});
|
| 398 |
+
this.hide();
|
| 399 |
+
|
| 400 |
+
whenLevelOpen.promise.then(function() {
|
| 401 |
+
command.finishWith(deferred);
|
| 402 |
+
});
|
| 403 |
+
} catch(e) {
|
| 404 |
+
new MultiView({
|
| 405 |
+
childViews: [{
|
| 406 |
+
type: 'ModalAlert',
|
| 407 |
+
options: {
|
| 408 |
+
markdowns: [
|
| 409 |
+
'## Error!',
|
| 410 |
+
'',
|
| 411 |
+
'Something is wrong with that level JSON, this happened:',
|
| 412 |
+
'',
|
| 413 |
+
String(e)
|
| 414 |
+
]
|
| 415 |
+
}
|
| 416 |
+
}]
|
| 417 |
+
});
|
| 418 |
+
command.finishWith(deferred);
|
| 419 |
+
}
|
| 420 |
+
}.bind(this))
|
| 421 |
+
.fail(function() {
|
| 422 |
+
command.finishWith(deferred);
|
| 423 |
+
})
|
| 424 |
+
.done();
|
| 425 |
+
},
|
| 426 |
+
|
| 427 |
+
exportTree: function(command, deferred) {
|
| 428 |
+
var treeJSON = JSON.stringify(this.mainVis.gitEngine.exportTree(), null, 2);
|
| 429 |
+
|
| 430 |
+
var showJSON = new MultiView({
|
| 431 |
+
childViews: [{
|
| 432 |
+
type: 'MarkdownPresenter',
|
| 433 |
+
options: {
|
| 434 |
+
previewText: intl.str('share-tree'),
|
| 435 |
+
fillerText: treeJSON,
|
| 436 |
+
noConfirmCancel: true
|
| 437 |
+
}
|
| 438 |
+
}]
|
| 439 |
+
});
|
| 440 |
+
showJSON.getPromise()
|
| 441 |
+
.then(function() {
|
| 442 |
+
command.finishWith(deferred);
|
| 443 |
+
})
|
| 444 |
+
.done();
|
| 445 |
+
},
|
| 446 |
+
|
| 447 |
+
clear: function(command, deferred) {
|
| 448 |
+
Main.getEvents().trigger('clearOldCommands');
|
| 449 |
+
if (command && deferred) {
|
| 450 |
+
command.finishWith(deferred);
|
| 451 |
+
}
|
| 452 |
+
},
|
| 453 |
+
|
| 454 |
+
mobileAlert: function(command, deferred) {
|
| 455 |
+
alert(intl.str('mobile-alert'));
|
| 456 |
+
command.finishWith(deferred);
|
| 457 |
+
},
|
| 458 |
+
|
| 459 |
+
delay: function(command, deferred) {
|
| 460 |
+
var amount = parseInt(command.get('regexResults')[1], 10);
|
| 461 |
+
setTimeout(function() {
|
| 462 |
+
command.finishWith(deferred);
|
| 463 |
+
}, amount);
|
| 464 |
+
},
|
| 465 |
+
|
| 466 |
+
reset: function(command, deferred) {
|
| 467 |
+
this.mainVis.reset();
|
| 468 |
+
this.initUndoStack();
|
| 469 |
+
|
| 470 |
+
setTimeout(function() {
|
| 471 |
+
command.finishWith(deferred);
|
| 472 |
+
}, this.mainVis.getAnimationTime());
|
| 473 |
+
},
|
| 474 |
+
|
| 475 |
+
helpDialog: function(command, deferred) {
|
| 476 |
+
var helpDialog = new MultiView({
|
| 477 |
+
childViews: intl.getDialog(require('../dialogs/sandbox'))
|
| 478 |
+
});
|
| 479 |
+
helpDialog.getPromise().then(function() {
|
| 480 |
+
// the view has been closed, lets go ahead and resolve our command
|
| 481 |
+
command.finishWith(deferred);
|
| 482 |
+
}.bind(this))
|
| 483 |
+
.done();
|
| 484 |
+
}
|
| 485 |
+
});
|
| 486 |
+
|
| 487 |
+
exports.Sandbox = Sandbox;
|