├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── backup.js ├── file.js ├── git.js ├── index.js ├── package.json └── util.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 coney Geng 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. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-backup 2 | 3 | git-backup. 4 | 5 | ## Install 6 | if your hexo version is 2.x.x, you should install as follow: 7 | 8 | ``` bash 9 | $ npm install hexo-git-backup@0.0.91 --save 10 | ``` 11 | 12 | if version is 3.x.x, you should install as follow: 13 | 14 | ``` bash 15 | $ npm install hexo-git-backup --save 16 | ``` 17 | 18 | ## Update 19 | 20 | if you install with --save, you must remove firstly when you update it. 21 | 22 | ``` bash 23 | $ npm remove hexo-git-backup 24 | $ npm install hexo-git-backup --save 25 | ``` 26 | 27 | ## Configure 28 | 29 | You should configure this plugin in `_config.yml`. 30 | 31 | ``` yaml 32 | backup: 33 | type: git 34 | repository: 35 | github: git@github.com:xxx/xxx.git,branchName 36 | gitcafe: git@github.com:xxx/xxx.git,branchName 37 | ``` 38 | 39 | ## Using 40 | ``` 41 | hexo backup 42 | ``` 43 | or 44 | ``` 45 | hexo b 46 | ``` 47 | ## Options 48 | 49 | if you want to back up with your theme,just add `theme: your theme name,your theme name` in `_config.yml`. 50 | 51 | ``` yaml 52 | backup: 53 | type: git 54 | theme: coney,landscape,xxx 55 | repository: 56 | github: git@github.com:xxx/xxx.git,branchName 57 | gitcafe: git@github.com:xxx/xxx.git,branchName 58 | ``` 59 | **Attention: if you do as above, the dir `themes/coney/.git`will be removed** 60 | 61 | if you want DIY commit message, just add 'message: update xxx'. 62 | ``` yaml 63 | backup: 64 | type: git 65 | message: update xxx 66 | repository: 67 | github: git@github.com:xxx/xxx.git,branchName 68 | gitcafe: git@github.com:xxx/xxx.git,branchName 69 | ``` 70 | 71 | 72 | Now you can backup all the blog! 73 | ## Problems 74 | 75 | You may get some troubles by your computer' permission。 76 | 77 | ### Error: EISDIR, open 78 | it is caused by permission. 79 | just do 'sudo hexo b' 80 | ``` 81 | sudo hexo b 82 | ``` 83 | -------------------------------------------------------------------------------- /backup.js: -------------------------------------------------------------------------------- 1 | var async = require('async'), 2 | fs = require('graceful-fs'), 3 | _ = require('lodash'); 4 | git = require('./git'); 5 | 6 | module.exports = function(args, callback){ 7 | var config = this.config.backup, 8 | log = this.log, 9 | backupers = this.extend.deployer.list(), 10 | self = this; 11 | 12 | if (!config){ 13 | var help = ''; 14 | 15 | help += 'You should configure backupment settings in _config.yml first!\n\n'; 16 | help += 'Available Types:\n'; 17 | help += ' ' + Object.keys(backupers).join(', ') + '\n\n'; 18 | help += 'For more help, you can check the online docs: ' + 'http://hexo.io/'.underline; 19 | 20 | console.log(help); 21 | 22 | return callback(); 23 | } 24 | 25 | if (!Array.isArray(config)) config = [config]; 26 | 27 | var onBackupStarted = function() { 28 | /** 29 | * Fired before backupment. 30 | * 31 | * @event backupBefore 32 | * @for Hexo 33 | */ 34 | self.emit('backupBefore'); 35 | }; 36 | 37 | var onBackupFinished = function(err) { 38 | /** 39 | * Fired after backupment. 40 | * 41 | * @event backupAfter 42 | * @param {Error} err 43 | * @for Hexo 44 | */ 45 | self.emit('backupAfter', err); 46 | callback(err); 47 | }; 48 | onBackupStarted(); 49 | // console.log(item); 50 | // console.log(args); 51 | async.eachSeries(config, function(item, next){ 52 | var type = item.type; 53 | 54 | if (type != "git"){ 55 | log.e('backuper not found: ' + type); 56 | // return next(); 57 | } else { 58 | log.i('Start backup: ' + type); 59 | } 60 | 61 | git(_.extend({}, item, args), self,function(err){ 62 | if (err) return next(err); 63 | 64 | log.i('Backup done: ' + type); 65 | next(); 66 | }); 67 | }, onBackupFinished); 68 | }; 69 | -------------------------------------------------------------------------------- /file.js: -------------------------------------------------------------------------------- 1 | /** 2 | * File utilities. 3 | * 4 | * @class file2 5 | * @namespace util 6 | * @module hexo 7 | * @static 8 | */ 9 | 10 | var fs = require('graceful-fs'), 11 | pathFn = require('path'), 12 | async = require('async'), 13 | _ = require('lodash'), 14 | // chokidar = require('chokidar'), 15 | EOL = require('os').EOL, 16 | EOLre = new RegExp(EOL, 'g'); 17 | 18 | if (!fs.exists || !fs.existsSync){ 19 | fs.exists = pathFn.exists; 20 | fs.existsSync = pathFn.existsSync; 21 | } 22 | 23 | var join = function(parent, child){ 24 | return parent ? pathFn.join(parent, child) : child; 25 | }; 26 | 27 | /** 28 | * Creates a directory recursively. 29 | * 30 | * @method mkdirs 31 | * @param {String} path 32 | * @param {Function} callback 33 | * @async 34 | * @static 35 | */ 36 | 37 | var mkdirs = exports.mkdirs = function(path, callback){ 38 | var parent = pathFn.dirname(path); 39 | 40 | fs.exists(parent, function(exist){ 41 | if (exist){ 42 | fs.mkdir(path, callback); 43 | } else { 44 | mkdirs(parent, function(){ 45 | fs.mkdir(path, callback); 46 | }); 47 | } 48 | }); 49 | }; 50 | 51 | var checkParent = function(path, callback){ 52 | var parent = pathFn.dirname(path); 53 | 54 | fs.exists(parent, function(exist){ 55 | if (exist) return callback(); 56 | 57 | mkdirs(parent, function(err){ 58 | if (err && err.code === 'EEXIST') return callback(); 59 | 60 | callback(err); 61 | }); 62 | }); 63 | }; 64 | 65 | /** 66 | * Writes a file. 67 | * 68 | * @method writeFile 69 | * @param {String} path 70 | * @param {String|Buffer} data 71 | * @param {Object} [options] 72 | * @param {Boolean} [options.checkParent=true] Creates the parent directories if not exist. 73 | * @param {Function} callback 74 | * @async 75 | * @static 76 | */ 77 | 78 | var writeFile = exports.writeFile = function(path, data, options, callback){ 79 | if (!callback){ 80 | if (typeof options === 'function'){ 81 | callback = options; 82 | options = {}; 83 | } else { 84 | callback = function(){}; 85 | } 86 | } 87 | 88 | options = _.extend({ 89 | checkParent: true 90 | }, options); 91 | 92 | async.series([ 93 | // Check parent folder existance 94 | function(next){ 95 | if (!options.checkParent) return next(); 96 | 97 | checkParent(path, next); 98 | } 99 | ], function(err){ 100 | if (err) return callback(err); 101 | 102 | fs.writeFile(path, data, options, callback); 103 | }); 104 | }; 105 | 106 | /** 107 | * Appends a file. Creates the file if not yet exists. 108 | * 109 | * @method appendFile 110 | * @param {String} path 111 | * @param {String|Buffer} data 112 | * @param {Object} [options] 113 | * @param {Boolean} [options.checkParent=true] Creates the parent directories if not exist. 114 | * @param {Function} callback 115 | * @async 116 | * @static 117 | */ 118 | 119 | var appendFile = exports.appendFile = function(path, data, options, callback){ 120 | if (!callback){ 121 | if (typeof options === 'function'){ 122 | callback = options; 123 | options = {}; 124 | } else { 125 | callback = function(){}; 126 | } 127 | } 128 | 129 | options = _.extend({ 130 | checkParent: true 131 | }, options); 132 | 133 | async.series([ 134 | // Check parent folder existance 135 | function(next){ 136 | if (!options.checkParent) return next(); 137 | 138 | checkParent(path, next); 139 | } 140 | ], function(err){ 141 | if (err) return callback(err); 142 | 143 | fs.appendFile(path, data, options, callback); 144 | }); 145 | }; 146 | 147 | /** 148 | * Copies a file from `src` to `dest`. 149 | * 150 | * @method copyFile 151 | * @param {String} src 152 | * @param {String} dest 153 | * @param {Object} [options] 154 | * @param {Boolean} [options.checkParent=true] Creates the parent directories if not exist. 155 | * @param {Function} callback 156 | * @async 157 | * @static 158 | */ 159 | 160 | var copyFile = exports.copyFile = function(src, dest, options, callback){ 161 | if (!callback){ 162 | if (typeof options === 'function'){ 163 | callback = options; 164 | options = {}; 165 | } else { 166 | callback = function(){}; 167 | } 168 | } 169 | 170 | options = _.extend({ 171 | checkParent: true 172 | }, options); 173 | 174 | var called = false; 175 | 176 | async.series([ 177 | // Check parent folder existance 178 | function(next){ 179 | if (!options.checkParent) return next(); 180 | 181 | checkParent(dest, next); 182 | } 183 | ], function(err){ 184 | if (err) return callback(err); 185 | 186 | var rs = fs.createReadStream(src), 187 | ws = fs.createWriteStream(dest); 188 | 189 | rs.pipe(ws) 190 | .on('error', function(err){ 191 | if (err && !called){ 192 | called = true; 193 | callback(err); 194 | } 195 | }); 196 | 197 | ws.on('close', callback) 198 | .on('error', function(err){ 199 | if (err && !called){ 200 | called = true; 201 | callback(err); 202 | } 203 | }); 204 | }); 205 | }; 206 | 207 | /** 208 | * Copies a directory from `src` to `dest`. 209 | * 210 | * @method copyDir 211 | * @param {String} src 212 | * @param {String} dest 213 | * @param {Object} [options] 214 | * @param {Boolean} [options.ignoreHidden=true] Ignores hidden files. 215 | * @param {RegExp} [options.ignorePattern] The file name pattern to ignore. 216 | * @param {Function} callback 217 | * @async 218 | * @static 219 | */ 220 | 221 | var copyDir = exports.copyDir = function(src, dest, options, callback){ 222 | if (!callback){ 223 | if (typeof options === 'function'){ 224 | callback = options; 225 | options = {}; 226 | } else { 227 | callback = function(){}; 228 | } 229 | } 230 | 231 | options = _.extend({ 232 | ignoreHidden: true, 233 | ignorePattern: null 234 | }, options); 235 | 236 | async.series([ 237 | // Create parent folder 238 | function(next){ 239 | fs.exists(dest, function(exist){ 240 | if (exist) return next(); 241 | 242 | mkdirs(dest, next); 243 | }); 244 | } 245 | ], function(err){ 246 | if (err) return callback(err); 247 | 248 | fs.readdir(src, function(err, files){ 249 | if (err) return callback(err); 250 | 251 | async.each(files, function(item, next){ 252 | if (options.ignoreHidden && item[0] === '.') return next(); 253 | if (options.ignorePattern && options.ignorePattern.test(item)) return next(); 254 | 255 | var childSrc = join(src, item), 256 | childDest = join(dest, item); 257 | 258 | fs.stat(childSrc, function(err, stats){ 259 | if (err) return callback(err); 260 | 261 | if (stats.isDirectory()){ 262 | copyDir(childSrc, childDest, options, next); 263 | } else { 264 | copyFile(childSrc, childDest, {checkParent: false}, next); 265 | } 266 | }); 267 | }, callback); 268 | }); 269 | }); 270 | }; 271 | 272 | /** 273 | * Returns a list of files in the directory. 274 | * 275 | * @method list 276 | * @param {String} path 277 | * @param {Object} [options] 278 | * @param {Boolean} [options.ignoreHidden=true] Ignores hidden files. 279 | * @param {RegExp} [options.ignorePattern] The file path pattern to ignore. 280 | * @param {Function} callback 281 | * @async 282 | * @static 283 | */ 284 | 285 | var list = exports.list = function(path, options, callback){ 286 | if (!callback){ 287 | if (typeof options === 'function'){ 288 | callback = options; 289 | options = {}; 290 | } else { 291 | callback = function(){}; 292 | } 293 | } 294 | 295 | options = _.extend({ 296 | ignoreHidden: true, 297 | ignorePattern: null 298 | }, options); 299 | 300 | var parent = options._parent; 301 | 302 | fs.readdir(path, function(err, files){ 303 | if (err) return callback(err); 304 | 305 | var arr = []; 306 | 307 | async.each(files, function(item, next){ 308 | if (options.ignoreHidden && item[0] === '.') return next(); 309 | if (options.ignorePattern && options.ignorePattern.test(item)) return next(); 310 | 311 | var childPath = join(path, item); 312 | 313 | fs.stat(childPath, function(err, stats){ 314 | if (err) return callback(err); 315 | 316 | if (stats.isDirectory()){ 317 | var childOptions = _.extend({}, options, { 318 | _parent: parent ? join(parent, item) : item 319 | }); 320 | 321 | list(childPath, childOptions, function(err, files){ 322 | if (err) return callback(err); 323 | 324 | arr = arr.concat(files); 325 | 326 | next(); 327 | }); 328 | } else { 329 | if (parent){ 330 | arr.push(join(parent, item)); 331 | } else { 332 | arr.push(item); 333 | } 334 | 335 | next(); 336 | } 337 | }); 338 | }, function(err){ 339 | callback(err, arr); 340 | }); 341 | }); 342 | }; 343 | 344 | var escape = function(str){ 345 | // Transform EOL 346 | if (EOL !== '\n') str = str.replace(EOLre, '\n'); 347 | 348 | // Remove UTF BOM 349 | str = str.replace(/^\uFEFF/, ''); 350 | 351 | return str; 352 | }; 353 | 354 | /** 355 | * Reads a file. 356 | * 357 | * @method readFile 358 | * @param {String} path 359 | * @param {Object} [options] 360 | * @param {Boolean} [options.escape=true] Transforms EOL & Removes UTF BOM in the file. 361 | * @param {String} [options.encoding=utf8] File encoding. 362 | * @param {Function} callback 363 | * @async 364 | * @static 365 | */ 366 | 367 | var readFile = exports.readFile = function(path, options, callback){ 368 | if (!callback){ 369 | if (typeof options === 'function'){ 370 | callback = options; 371 | options = {}; 372 | } else { 373 | callback = function(){}; 374 | } 375 | } 376 | 377 | options = _.extend({ 378 | escape: true, 379 | encoding: 'utf8' 380 | }, options); 381 | 382 | fs.readFile(path, options.encoding, function(err, content){ 383 | if (err) return callback(err); 384 | 385 | if (options.escape && options.encoding) content = escape(content); 386 | 387 | callback(null, content); 388 | }); 389 | }; 390 | 391 | /** 392 | * Reads a file synchronizedly. 393 | * 394 | * @method readFileSync 395 | * @param {String} path 396 | * @param {Object} [options] 397 | * @param {Boolean} [options.escape=true] Transforms EOL & Removes UTF BOM in the file. 398 | * @param {String} [options.encoding=utf8] File encoding. 399 | * @static 400 | */ 401 | 402 | var readFileSync = exports.readFileSync = function(path, options){ 403 | if (!options) options = {}; 404 | 405 | options = _.extend({ 406 | escape: true, 407 | encoding: 'utf8' 408 | }, options); 409 | 410 | var content = fs.readFileSync(path, options.encoding); 411 | if (options.escape && options.encoding) content = escape(content); 412 | 413 | return content; 414 | }; 415 | 416 | /** 417 | * Cleans a directory. 418 | * 419 | * @method emptyDir 420 | * @param {String} path 421 | * @param {Object} [options] 422 | * @param {Boolean} [options.ignoreHidden=true] Ignores hidden files. 423 | * @param {RegExp} [options.ignorePattern] The file name pattern to ignore. 424 | * @param {Array} [options.exclude] The list of file path you don't want to delete. 425 | * @param {Function} callback 426 | * @async 427 | * @static 428 | */ 429 | 430 | var emptyDir = exports.emptyDir = function(path, options, callback){ 431 | if (!callback){ 432 | if (typeof options === 'function'){ 433 | callback = options; 434 | options = {}; 435 | } else { 436 | callback = function(){}; 437 | } 438 | } 439 | 440 | options = _.extend({ 441 | ignoreHidden: true, 442 | ignorePattern: null, 443 | exclude: [] 444 | }, options); 445 | 446 | var parent = options._parent || '', 447 | arr = []; 448 | 449 | fs.readdir(path, function(err, files){ 450 | if (err) return callback(err); 451 | 452 | async.each(files, function(item, next){ 453 | var itemPath = join(parent, item), 454 | childPath = join(path, item); 455 | 456 | if (options.ignoreHidden && item[0] === '.') return next(); 457 | if (options.ignorePattern && options.ignorePattern.test(itemPath)) return next(); 458 | if (options.exclude && options.exclude.indexOf(itemPath) > -1) return next(); 459 | 460 | fs.stat(childPath, function(err, stats){ 461 | if (err) return callback(err); 462 | 463 | if (stats.isDirectory()){ 464 | emptyDir(childPath, _.extend({}, options, {_parent: itemPath}), function(err, removed){ 465 | if (err) return callback(err); 466 | 467 | arr = arr.concat(removed); 468 | 469 | fs.readdir(childPath, function(err, files){ 470 | if (err) return callback(err); 471 | if (files.length) return next(); 472 | 473 | fs.rmdir(childPath, next); 474 | }); 475 | }); 476 | } else { 477 | arr.push(itemPath); 478 | fs.unlink(childPath, next); 479 | } 480 | }); 481 | }, function(err){ 482 | callback(err, arr); 483 | }); 484 | }); 485 | }; 486 | 487 | /** 488 | * Removes a directory. 489 | * 490 | * @method rmdir 491 | * @param {String} path 492 | * @param {Function} callback 493 | * @async 494 | * @static 495 | */ 496 | 497 | var rmdir = exports.rmdir = function(path, callback){ 498 | if (typeof callback !== 'function') callback = function(){}; 499 | 500 | fs.readdir(path, function(err, files){ 501 | if (err) return callback(err); 502 | 503 | async.each(files, function(item, next){ 504 | var childPath = join(path, item); 505 | 506 | fs.stat(childPath, function(err, stats){ 507 | if (err) return callback(err); 508 | 509 | if (stats.isDirectory()){ 510 | rmdir(childPath, next); 511 | } else { 512 | fs.unlink(childPath, next); 513 | } 514 | }); 515 | }, function(){ 516 | fs.rmdir(path, callback); 517 | }); 518 | }); 519 | }; 520 | 521 | var _parseWatchType = function(type){ 522 | switch (type){ 523 | case 'add': 524 | return 'create'; 525 | 526 | case 'change': 527 | return 'update'; 528 | 529 | case 'unlink': 530 | return 'delete'; 531 | 532 | default: 533 | return type; 534 | } 535 | }; 536 | 537 | /** 538 | * Watch a directory or a file. 539 | * 540 | * @method watch 541 | * @param {String} path 542 | * @param {Object} [options] See [chokidar](https://github.com/paulmillr/chokidar) 543 | * @param {Function} callback 544 | * @static 545 | */ 546 | 547 | // var watch = exports.watch = function(path, options, callback){ 548 | // if (!callback){ 549 | // if (typeof options === 'function'){ 550 | // callback = options; 551 | // options = {}; 552 | // } else { 553 | // callback = function(){}; 554 | // } 555 | // } 556 | 557 | // options = _.extend({ 558 | // persistent: true, 559 | // ignoreInitial: true 560 | // }, options); 561 | 562 | // if (options.ignorePattern) options.ignored = options.ignorePattern; 563 | 564 | // var watcher = chokidar.watch(path, options); 565 | 566 | // watcher.on('all', function(type, src, stats){ 567 | // callback(_parseWatchType(type), src, stats); 568 | // }); 569 | // }; -------------------------------------------------------------------------------- /git.js: -------------------------------------------------------------------------------- 1 | var async = require('async'), 2 | fs = require('fs'), 3 | path = require('path'), 4 | spawn = require('child_process').spawn, 5 | file = require('./file'), 6 | commitMessage = require('./util').commitMessage; 7 | 8 | module.exports = function(args, selfData, callback){ 9 | var self = selfData, 10 | baseDir = self.base_dir, 11 | //deployDir = path.join(baseDir, '.deploy'), 12 | deployDir = baseDir, 13 | gitDir = path.join(baseDir, '.git'), 14 | themesDir = path.join(baseDir, 'themes'), 15 | publicDir = self.public_dir; 16 | if (!args.repo && !args.repository){ 17 | var help = ''; 18 | 19 | help += 'You should configure deployment settings in _config.yml first!\n\n'; 20 | help += 'Example:\n'; 21 | help += ' deploy:\n'; 22 | help += ' type: git\n'; 23 | help += ' message: [message]\n'; 24 | help += ' repo:\n'; 25 | help += ' github: ,\n'; 26 | help += ' gitcafe: ,\n\n'; 27 | help += 'For more help, you can check the docs: ' + 'http://hexo.io/docs/deployment.html'.underline; 28 | 29 | console.log(help); 30 | return callback(); 31 | } 32 | 33 | var repo = args.repo || args.repository; 34 | for (var t in repo){ 35 | var s = repo[t].split(','); 36 | repo[t] = {}; 37 | repo[t].url = s[0]; 38 | repo[t].branch = s.length > 1 ? s[1] : 'master'; 39 | } 40 | if(args.themes){ 41 | var themes = args.themes.split(','); 42 | }else if(self.config.theme){ 43 | var themes = [self.config.theme]; 44 | } 45 | var addThemes = function(commands){ 46 | if(themes){ 47 | for (var t in themes){ 48 | var themeName = themes[t]; 49 | var themeGitDir = path.join(baseDir,'themes/' + themeName+ '/.git'); 50 | var themeDir = path.join(baseDir,'themes/' + themeName); 51 | // fs.existsSync(themeGitDir,function(exist){ 52 | if(fs.existsSync(themeGitDir)){ 53 | var child = spawn('rm', ['-rf', '.git'],{cwd: themeDir}); 54 | if(commands){ 55 | var themeFiles = path.join(themeDir,"*"); 56 | commands.push(['add', themeFiles]); 57 | console.log(commands); 58 | } 59 | self.log.i(themeGitDir); 60 | child.stdout.setEncoding('utf8'); 61 | child.stdout.on('data', function(data) { 62 | self.log.i(data); 63 | }); 64 | child.stderr.on('data', function (data) { 65 | self.log.i('stderr: ' + data); 66 | }); 67 | 68 | child.on('close', function (code) { 69 | self.log.i('child process exited with code ' + code); 70 | }); 71 | } 72 | // }) 73 | } 74 | } 75 | } 76 | var run = function(command, args, callback){ 77 | var cp = spawn(command, args, {cwd: deployDir}); 78 | 79 | cp.stdout.on('data', function(data){ 80 | process.stdout.write(data); 81 | }); 82 | 83 | cp.stderr.on('data', function(data){ 84 | process.stderr.write(data); 85 | }); 86 | 87 | cp.on('close', callback); 88 | }; 89 | 90 | async.series([ 91 | // Set up 92 | function(next){ 93 | fs.exists(gitDir, function(exist){ 94 | if (exist && !args.setup) return next(); 95 | self.log.i('Setting up Git-Backup deployment...'); 96 | var commands = [['init']]; 97 | addThemes(); 98 | if (args.master && repo[args.master]){ 99 | var master = repo[args.master]; 100 | self.log.i('fetch from ['+ args.master.green + ']:', master.url.cyan); 101 | commands.push(['remote', 'add', 'origin', '-t', master.branch, master.url]); 102 | commands.push(['pull']); 103 | } else { 104 | commands.push(['add', '-A', '.']); 105 | commands.push(['commit', '-m', 'First commit']); 106 | self.log.i('First'); 107 | } 108 | 109 | for (var t in repo){ 110 | commands.push(['remote', 'add', t, '-t', repo[t].branch, repo[t].url]); 111 | } 112 | //file.writeFile(deployDir, 'placeHolder', function(err){ 113 | // if (err) callback(err); 114 | async.eachSeries(commands, function(item, next){ 115 | run('git', item, function(code){ 116 | if (code === 0) next(); 117 | }); 118 | }, function(){ 119 | if (!args.setup) next(); 120 | }); 121 | // }); 122 | }); 123 | }, 124 | 125 | function(next){ 126 | //file.emptyDir(deployDir, next); 127 | next(); 128 | }, 129 | function(next){ 130 | var commands = [['add', '-A']]; 131 | addThemes(commands); 132 | commands.push(['commit', '-m', commitMessage(args)]); 133 | for (var t in repo){ 134 | commands.push(['push', '-u', t, 'master:' + repo[t].branch, '--force']); 135 | } 136 | 137 | async.eachSeries(commands, function(item, next){ 138 | run('git', item, function(){ 139 | next(); 140 | }); 141 | }, next); 142 | } 143 | ], callback); 144 | }; 145 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var console = hexo.extend.console; 2 | 3 | var backupOptions = { 4 | alias: 'b', 5 | options: [ 6 | 7 | ] 8 | }; 9 | 10 | console.register('backup', 'backup your website', backupOptions, require('./backup')); 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hexo-git-backup", 3 | "version": "0.1.1", 4 | "description": "hexo backup plugin for Hexo,backup to github", 5 | "main": "index", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/coneycode/hexo-git-backup.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/coneycode/hexo-git-backup/issues" 12 | }, 13 | "keywords": [ 14 | "hexo", 15 | "backup" 16 | ], 17 | "scripts": { 18 | "test": "echo \"Error: no test specified\" && exit 1" 19 | }, 20 | "author": { 21 | "name": "Coney Geng", 22 | "email": "coneylife@gmail.com", 23 | "url": "http://gengbiao.me" 24 | }, 25 | "license": { 26 | "type": "MIT", 27 | "url": "https://github.com/coneycode/hexo-auto-backup/LICENSE" 28 | }, 29 | "dependencies": { 30 | "hexo": "^3.0.0", 31 | "async": "^0.9.0", 32 | "graceful-fs": "^4.0", 33 | "lodash" : "^4.17.5", 34 | "moment": "^2.8.1", 35 | "swig-templates": "^2.0.3" 36 | }, 37 | "readme": "# git-backup\n\ngit-backup.\n\n## Install\n\n``` bash\n$ npm install hexo-git-backup --save\n```\n\n## Update\n\nif you install with --save, you must remove firstly when you update it.\n``` bash\n$ npm remove hexo-git-backup\n$ npm install hexo-git-backup --save\n```\n\n## Configure\n\nYou should configure this plugin in `_config.yml`.\n\n``` yaml\nbackup:\n type: git\n repository:\n github: git@github.com:xxx/xxx.git,branchName\n gitcafe: git@github.com:xxx/xxx.git,branchName\n```\n\n## Using\n```\nhexo backup \n```\nor \n```\nhexo b\n```\n## Options\n\nif you want to back up with your theme,just add `theme: your theme name` in `_config.yml`.\n\n``` yaml\nbackup:\n type: git\n theme: coney\n repository:\n github: git@github.com:xxx/xxx.git,branchName\n gitcafe: git@github.com:xxx/xxx.git,branchName\n```\n**Attention: if you do as above, the dir `themes/coney/.git`will be removed**\n\nNow you can backup all the blog!\n## Problems\n\nYou may get some troubles by your computer' permission。\n\n###Error: EISDIR, open\nit is caused by permission.\njust do 'sudo hexo b' \n```\nsudo hexo b\n```\n", 38 | "readmeFilename": "README.md", 39 | "gitHead": "0ed2d20bd42016af2911fd654d10f4544ed42f4a", 40 | "homepage": "https://github.com/coneycode/hexo-git-backup#readme", 41 | "_id": "hexo-git-backup@0.0.92", 42 | "_shasum": "db2011100f62f2f8920f8996152384da07161fb4", 43 | "_from": "hexo-git-backup@*" 44 | } 45 | -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | var swig = require('swig-templates'), 2 | moment = require('moment'); 3 | 4 | var helpers = { 5 | now: function(format){ 6 | return moment().format(format); 7 | } 8 | }; 9 | 10 | exports.commitMessage = function(args){ 11 | var message = args.m || args.msg || args.message; 12 | 13 | if (!message){ 14 | message = 'Site updated: {{ now(\'YYYY-MM-DD HH:mm:ss\') }}'; 15 | } 16 | 17 | return swig.compile(message)(helpers); 18 | }; 19 | --------------------------------------------------------------------------------