git-off

git off handles large files in git repos
git clone https://noulin.net/git/git-off.git
Log | Files | Refs | README

git-off (50220B)


      1 #! /usr/bin/env node
      2 /*
      3  * CODE
      4  *
      5  * modules
      6  * defaults
      7  * helpers
      8  * core
      9  * main
     10  *
     11  *
     12  * modules
     13  *   all dependencies
     14  *
     15  * defaults
     16  *   objInfo fields:  for indexing cat-diff response in push command
     17  *   externalHelpers: shell commands to be used with the exec function
     18  *   runtimeConfig:   config built by offHelpers
     19  *   offDEFAULTS:     default configuration for first time install
     20  *
     21  * helpers
     22  *   expandHome:      expands ~/
     23  *   gitConfig:       handles global git config
     24  *   offLog:          appends log to git config off.log
     25  *   offLogRepo:      log for commands run in git repo
     26  *   exec:            runs an externalHelpers with parameters
     27  *   mkdirParents:    recursive mkdir
     28  *   rmAll:           delete recursively files and directories
     29  *   copy:            copies files
     30  *   offHelpers:      git-off helpers
     31  *
     32  * core
     33  *   transport:       transport functions for git off store
     34  *   offCommands:     command line functions
     35  *
     36  * main
     37  *   parse CLI arguments
     38  */
     39 
     40 /*
     41  * modules
     42  */
     43 var COMMAND_MAP, StringDecoder, copy, exec, expandHome, externalHelpers, fs, fssync, gitConfig, mkdirParents, mkdirp, offCommands, offDEFAULTS, offHelpers, offLog, offLogRepo, oiNAME, oiOID, oiPERMISSIONS, oiPREVIOUSOID, oiPREVIOUSPERMISSIONS, path, readline, rimraf, rmAll, runtimeConfig, showCommandHelp, syncexec, thisrepo, transport, walkSync;
     44 
     45 require('colors');
     46 
     47 fs = require('fs');
     48 
     49 path = require('path');
     50 
     51 fssync = require('fs-sync');
     52 
     53 syncexec = require('sync-exec');
     54 
     55 mkdirp = require('mkdirp');
     56 
     57 rimraf = require('rimraf');
     58 
     59 readline = require('readline');
     60 
     61 StringDecoder = require('string_decoder').StringDecoder;
     62 
     63 
     64 /*
     65  * defaults
     66  */
     67 
     68 oiPREVIOUSPERMISSIONS = 0;
     69 
     70 oiPERMISSIONS = 1;
     71 
     72 oiPREVIOUSOID = 2;
     73 
     74 oiOID = 3;
     75 
     76 oiNAME = 4;
     77 
     78 externalHelpers = {
     79   'gitConfigGlobal': 'git config --global',
     80   'gitConfig': 'git config',
     81   'gitRepoRoot': 'git rev-parse --show-toplevel',
     82   'gitList': 'git rev-list',
     83   'gitListEmptyRemote': 'git rev-list --max-parents=0',
     84   'gitDiff': 'git show --raw --format="" --no-abbrev',
     85   'gitCat': 'git cat-file -p',
     86   'sha': 'git hash-object --no-filters',
     87   'listAttr': 'git check-attr -a',
     88   'ssh': 'ssh',
     89   'scp': 'scp',
     90   'curl': 'curl',
     91   'mv': 'mv',
     92   'rsync': 'rsync'
     93 };
     94 
     95 runtimeConfig = {
     96   'currentRepoRoot': '',
     97   'objectPath': '',
     98   'offStore': '',
     99   'offHttp': '',
    100   'offMode': '',
    101   'offIntegrity': '',
    102   'offScp': '',
    103   'offScpUser': '',
    104   'offPem': '',
    105   'offSshOptions': '',
    106   'offScpOptions': '',
    107   'offRsyncOptions': '',
    108   'offCurlOptions': '',
    109   'log': '',
    110   'offConfigAlways': '',
    111   's3Region': '',
    112   's3Bucket': '',
    113   'transform': '',
    114   'transformTo': '',
    115   'transformFrom': ''
    116 };
    117 
    118 offDEFAULTS = {
    119   'objectPath': '/.git/off/objects',
    120   'mode': 'copy',
    121   'integrity': 'disable',
    122   'pem': 'offNoValue',
    123   'scpHost': 'offNoValue',
    124   'scpUser': '',
    125   'sshOptions': '-C -o StrictHostKeyChecking=no -o ConnectTimeout=3',
    126   'scpOptions': '-C -o StrictHostKeyChecking=no -o ConnectTimeout=3 -p',
    127   'rsyncOptions': '-az -e \'ssh -i _i\'',
    128   'store': '~/.git-off/offStore',
    129   'http': 'offNoValue',
    130   'curlOptions': '-o',
    131   'log': '~/.git-off/log',
    132   'configAlways': 'offNoValue',
    133   's3Region': 'offNoValue',
    134   's3Bucket': 'offNoValue',
    135   'transform': 'disable',
    136   'transformTo': 'pbzip2 -9 -c _1 > _2',
    137   'transformFrom': 'pbzip2 -d -c _1 > _2',
    138   'prePush': '#!/bin/sh\ncommand -v git-off >/dev/null 2>&1 || { echo >&2 "\\nThis repository is configured for Git off but \'git-off\' was not found on your path. If you no longer wish to use git off, remove this hook by deleting .git/hooks/pre-push.\\n"; exit 2; }\ngit off pre-push "$@"',
    139   'offSignature': '### git-off v1 sha:',
    140   'shaLength': 40
    141 };
    142 
    143 
    144 /*
    145  * helpers
    146  */
    147 
    148 expandHome = function(p) {
    149   if (p.slice(0, 2) === '~/') {
    150     p = process.env.HOME + '/' + p.slice(2);
    151   }
    152   return p;
    153 };
    154 
    155 gitConfig = {
    156   'set': function(key, value) {
    157     exec('gitConfig', ['--global', key, '"' + value + '"']);
    158   },
    159   'setLocal': function(key, value) {
    160     exec('gitConfig', [key, '"' + value + '"']);
    161   },
    162   'setThisRepo': function(key, value) {
    163     exec('gitConfig', ['--file ' + offHelpers.gitRepoRoot() + '/.git-off', key, '"' + value + '"']);
    164   },
    165   'get': function(key) {
    166     var r;
    167     if (offHelpers.offConfigAlways() === 'GIT_OFF_CONFIG') {
    168       r = '';
    169       if (process.env.GIT_OFF_CONFIG !== void 0 && fs.existsSync(process.env.GIT_OFF_CONFIG) === true) {
    170         r = exec('gitConfig', ['--file ' + process.env.GIT_OFF_CONFIG, key]).stdout.trim();
    171       }
    172       return r;
    173     }
    174     if (offHelpers.offConfigAlways() === 'repo') {
    175       r = '';
    176       if (fs.existsSync(offHelpers.gitRepoRoot(true) + '/.git-off') === true) {
    177         r = exec('gitConfig', ['--file ' + offHelpers.gitRepoRoot() + '/.git-off', key]).stdout.trim();
    178       }
    179       return r;
    180     }
    181     if (offHelpers.offConfigAlways() === 'global') {
    182       return exec('gitConfig', [key]).stdout.trim();
    183     }
    184     if (process.env.GIT_OFF_CONFIG !== void 0 && fs.existsSync(process.env.GIT_OFF_CONFIG) === true) {
    185       r = exec('gitConfig', ['--file ' + process.env.GIT_OFF_CONFIG, key]).stdout.trim();
    186       if (r !== '') {
    187         return r;
    188       }
    189     }
    190     if (fs.existsSync(offHelpers.gitRepoRoot(true) + '/.git-off') === true) {
    191       r = exec('gitConfig', ['--file ' + offHelpers.gitRepoRoot() + '/.git-off', key]).stdout.trim();
    192       if (r !== '') {
    193         return r;
    194       }
    195     }
    196     return exec('gitConfig', [key]).stdout.trim();
    197   },
    198   'getSyncexec': function(key) {
    199     return syncexec(externalHelpers.gitConfig + ' ' + key);
    200   }
    201 };
    202 
    203 mkdirParents = function(p) {
    204   p = expandHome(p);
    205   mkdirp.sync(p, function(err) {
    206     console.error(err);
    207   });
    208 };
    209 
    210 rmAll = function(p) {
    211   p = expandHome(p);
    212   rimraf.sync(p);
    213 };
    214 
    215 offLog = function(s) {
    216   var r;
    217   if (runtimeConfig.log === '') {
    218     r = gitConfig.getSyncexec('off.log');
    219     runtimeConfig.log = expandHome(r.stdout.trim());
    220     if (runtimeConfig.log === '') {
    221       console.error('Missing off.log config. Run "git config --global off.log ~/.git-off/log".'.red.bold);
    222       process.exit(1);
    223     }
    224   }
    225   if (fs.existsSync(path.dirname(runtimeConfig.log)) === false) {
    226     mkdirParents(path.dirname(runtimeConfig.log));
    227   }
    228   fs.appendFileSync(runtimeConfig.log, s + '\n');
    229 };
    230 
    231 offLogRepo = function(s) {
    232   offLog('REPO path: ' + offHelpers.gitRepoRoot() + ' ' + s);
    233 };
    234 
    235 exec = function(cmdName, paramsArray, ignoreStderr) {
    236   var r;
    237   if (paramsArray == null) {
    238     paramsArray = [];
    239   }
    240   if (ignoreStderr == null) {
    241     ignoreStderr = false;
    242   }
    243   r = syncexec(externalHelpers[cmdName] + ' ' + paramsArray.join(' '));
    244   if (r.stderr !== '' && ignoreStderr === false) {
    245     console.error(r.stderr.red.bold);
    246     offLog(r.stderr.red.bold);
    247     process.exit(1);
    248   }
    249   return r;
    250 };
    251 
    252 copy = function(src, dst) {
    253   fssync.copy(src, dst);
    254 };
    255 
    256 walkSync = function(dir, filelist) {
    257   var files;
    258   if (filelist == null) {
    259     filelist = [];
    260   }
    261   files = fs.readdirSync(dir);
    262   files.forEach(function(file) {
    263     if (fs.statSync(dir + '/' + file).isDirectory()) {
    264       filelist = walkSync(dir + '/' + file, filelist);
    265     } else {
    266       filelist.push(dir + '/' + file);
    267     }
    268   });
    269   return filelist;
    270 };
    271 
    272 offHelpers = {
    273   'gitRepoRoot': function(ignoreStderr) {
    274     var r;
    275     if (ignoreStderr == null) {
    276       ignoreStderr = false;
    277     }
    278     if (runtimeConfig.currentRepoRoot === '') {
    279       r = exec('gitRepoRoot', [], ignoreStderr);
    280       runtimeConfig.currentRepoRoot = r.stdout.trim();
    281     }
    282     return runtimeConfig.currentRepoRoot;
    283   },
    284   'objectPath': function() {
    285     if (runtimeConfig.objectPath === '') {
    286       runtimeConfig.objectPath = this.gitRepoRoot() + offDEFAULTS.objectPath;
    287     }
    288     return runtimeConfig.objectPath;
    289   },
    290   'offStore': function() {
    291     var r;
    292     if (runtimeConfig.offStore === '') {
    293       r = gitConfig.get('off.store');
    294       runtimeConfig.offStore = expandHome(r);
    295     }
    296     return runtimeConfig.offStore;
    297   },
    298   'offHttp': function() {
    299     if (runtimeConfig.offHttp === '') {
    300       runtimeConfig.offHttp = gitConfig.get('off.http');
    301     }
    302     return runtimeConfig.offHttp;
    303   },
    304   'offCurlOptions': function() {
    305     if (runtimeConfig.offCurlOptions === '') {
    306       runtimeConfig.offCurlOptions = gitConfig.get('off.curloptions');
    307     }
    308     return runtimeConfig.offCurlOptions;
    309   },
    310   'offMode': function() {
    311     if (runtimeConfig.offMode === '') {
    312       runtimeConfig.offMode = gitConfig.get('off.mode');
    313     }
    314     return runtimeConfig.offMode;
    315   },
    316   'offIntegrity': function() {
    317     if (runtimeConfig.offIntegrity === '') {
    318       runtimeConfig.offIntegrity = gitConfig.get('off.integrity');
    319     }
    320     return runtimeConfig.offIntegrity;
    321   },
    322   'offPem': function() {
    323     var r;
    324     if (runtimeConfig.offPem === '') {
    325       r = gitConfig.get('off.pem');
    326       runtimeConfig.offPem = expandHome(r);
    327     }
    328     return runtimeConfig.offPem;
    329   },
    330   'offSshOptions': function() {
    331     if (runtimeConfig.offSshOptions === '') {
    332       runtimeConfig.offSshOptions = gitConfig.get('off.sshoptions');
    333     }
    334     return runtimeConfig.offSshOptions;
    335   },
    336   'offScpOptions': function() {
    337     if (runtimeConfig.offScpOptions === '') {
    338       runtimeConfig.offScpOptions = gitConfig.get('off.scpoptions');
    339     }
    340     return runtimeConfig.offScpOptions;
    341   },
    342   'offRsyncOptions': function() {
    343     if (runtimeConfig.offRsyncOptions === '') {
    344       runtimeConfig.offRsyncOptions = gitConfig.get('off.rsyncoptions');
    345     }
    346     return runtimeConfig.offRsyncOptions;
    347   },
    348   'offScp': function() {
    349     if (runtimeConfig.offScp === '') {
    350       runtimeConfig.offScp = gitConfig.get('off.scphost');
    351     }
    352     return runtimeConfig.offScp;
    353   },
    354   'offScpUser': function() {
    355     if (runtimeConfig.offScpUser === '') {
    356       runtimeConfig.offScpUser = gitConfig.get('off.scpuser');
    357     }
    358     return runtimeConfig.offScpUser;
    359   },
    360   'log': function() {
    361     var r;
    362     if (runtimeConfig.log === '') {
    363       r = gitConfig.getSyncexec('off.log');
    364       runtimeConfig.log = expandHome(r.stdout.trim());
    365     }
    366     return runtimeConfig.log;
    367   },
    368   'getLog': function() {
    369     offHelpers.log();
    370     if (runtimeConfig.log === '') {
    371       console.error('Missing off.log config. Run "git config --global off.log ~/.git-off/log".'.red.bold);
    372       process.exit(1);
    373     }
    374     return runtimeConfig.log;
    375   },
    376   'offConfigAlways': function() {
    377     var r;
    378     if (runtimeConfig.offConfigAlways === '') {
    379       r = gitConfig.getSyncexec('off.configAlways');
    380       runtimeConfig.offConfigAlways = r.stdout.trim();
    381     }
    382     return runtimeConfig.offConfigAlways;
    383   },
    384   's3Region': function() {
    385     if (runtimeConfig.s3Region === '') {
    386       runtimeConfig.s3Region = gitConfig.get('off.s3region');
    387     }
    388     return runtimeConfig.s3Region;
    389   },
    390   's3Bucket': function() {
    391     if (runtimeConfig.s3Bucket === '') {
    392       runtimeConfig.s3Bucket = gitConfig.get('off.s3bucket');
    393     }
    394     return runtimeConfig.s3Bucket;
    395   },
    396   'transform': function() {
    397     if (runtimeConfig.transform === '') {
    398       runtimeConfig.transform = gitConfig.get('off.transform');
    399     }
    400     return runtimeConfig.transform;
    401   },
    402   'transformTo': function() {
    403     if (runtimeConfig.transformTo === '') {
    404       runtimeConfig.transformTo = gitConfig.get('off.transformTo');
    405     }
    406     return runtimeConfig.transformTo;
    407   },
    408   'transformFrom': function() {
    409     if (runtimeConfig.transformFrom === '') {
    410       runtimeConfig.transformFrom = gitConfig.get('off.transformFrom');
    411     }
    412     return runtimeConfig.transformFrom;
    413   },
    414   'userAt': function() {
    415     var user;
    416     if (offHelpers.offScpUser() !== '') {
    417       user = offHelpers.offScpUser() + '@';
    418     } else {
    419       user = '';
    420     }
    421     return user;
    422   },
    423   'getSSHConfig': function() {
    424     var h_l, host, port, portAndPath_l, storePath, user;
    425     user = offHelpers.userAt();
    426     h_l = offHelpers.offScp().split(':');
    427     if (h_l[1] === void 0) {
    428       port = NaN;
    429     } else {
    430       portAndPath_l = h_l[1].split('/');
    431       port = parseInt(portAndPath_l[0]);
    432     }
    433     if (isNaN(port)) {
    434       host = h_l[0];
    435       storePath = h_l[1];
    436     } else {
    437       host = h_l[0];
    438       storePath = '/' + portAndPath_l.slice(1).join('/');
    439     }
    440     return [user + host, storePath, port];
    441   },
    442   'mkdirStore': function(p) {
    443     var h_l, pem, sshCmd;
    444     h_l = offHelpers.getSSHConfig();
    445     sshCmd = '"mkdir -p ' + h_l[1] + '/' + p;
    446     sshCmd += '"';
    447     if (offHelpers.offPem() === '' || offHelpers.offPem() === 'offNoValue') {
    448       pem = '';
    449     } else {
    450       pem = '-i ' + offHelpers.offPem();
    451     }
    452     if (isNaN(h_l[2])) {
    453       exec('ssh', [offHelpers.offSshOptions(), pem, h_l[0], sshCmd], true);
    454     } else {
    455       exec('ssh', [offHelpers.offSshOptions(), pem, '-p ' + h_l[2], h_l[0], sshCmd], true);
    456     }
    457   },
    458   'rmAllStore': function(p) {
    459     var h_l, pem, sshCmd;
    460     if (offHelpers.offPem() === '' || offHelpers.offPem() === 'offNoValue') {
    461       pem = '';
    462     } else {
    463       pem = '-i ' + offHelpers.offPem();
    464     }
    465     h_l = offHelpers.getSSHConfig();
    466     sshCmd = '"rm -rf ' + h_l[1] + '/' + p;
    467     sshCmd += '"';
    468     if (isNaN(h_l[2])) {
    469       exec('ssh', [offHelpers.offSshOptions(), pem, h_l[0], sshCmd], true);
    470     } else {
    471       exec('ssh', [offHelpers.offSshOptions(), pem, '-p ' + h_l[2], h_l[0], sshCmd], true);
    472     }
    473   },
    474   'copyTo': function() {
    475     var f, files, i, j, len1, len2, tfiles;
    476     tfiles = walkSync(offHelpers.objectPath());
    477     files = [];
    478     for (i = 0, len1 = tfiles.length; i < len1; i++) {
    479       f = tfiles[i];
    480       files.push(f.replace(offHelpers.objectPath() + '/', ''));
    481     }
    482     for (j = 0, len2 = files.length; j < len2; j++) {
    483       f = files[j];
    484       transport.send(f);
    485     }
    486   },
    487   'checkIntegrity': function(p) {
    488     var r, receivedSha, result;
    489     result = true;
    490     if (offHelpers.offIntegrity() === 'disable') {
    491       return true;
    492     }
    493     r = exec('sha', [p]);
    494     receivedSha = r.stdout.split(' ')[0].trim();
    495     if (path.basename(p) !== receivedSha) {
    496       console.log('git-off: The file ' + p + ' differs from the one that was pushed.');
    497       result = false;
    498     }
    499     return result;
    500   },
    501   'setTransport': function(mode) {
    502     if (mode == null) {
    503       mode = 'config';
    504     }
    505     if (mode === 'config') {
    506       mode = offHelpers.offMode();
    507     }
    508     if (mode === 'copy') {
    509       transport['send'] = function(file) {
    510         var f_l;
    511         f_l = file.split('/');
    512         if (fs.existsSync(offHelpers.offStore() + '/' + f_l[0] + '/' + f_l[1]) === false) {
    513           mkdirParents(offHelpers.offStore() + '/' + f_l[0] + '/' + f_l[1]);
    514         }
    515         copy(offHelpers.objectPath() + '/' + file, offHelpers.offStore() + '/' + file);
    516         fs.chmodSync(offHelpers.offStore() + '/' + file, '444');
    517       };
    518       transport['receive'] = function(file) {
    519         var f_l, readStream;
    520         if (offHelpers.transform() === 'enable' && offHelpers.transformFrom() !== '') {
    521           f_l = file.split('/');
    522           if (fs.existsSync(offHelpers.objectPath() + '/' + f_l[0] + '/' + f_l[1]) === false) {
    523             mkdirParents(offHelpers.objectPath() + '/' + f_l[0] + '/' + f_l[1]);
    524           }
    525           copy(offHelpers.offStore() + '/' + file, offHelpers.objectPath() + '/' + file);
    526           transport['transformFrom'](file);
    527           if (offHelpers.checkIntegrity(offHelpers.objectPath() + '/../tmp/' + file)) {
    528             readStream = fs.createReadStream(offHelpers.objectPath() + '/../tmp/' + file);
    529             readStream.pipe(process.stdout);
    530           }
    531         } else {
    532           if (offHelpers.checkIntegrity(offHelpers.offStore() + '/' + file)) {
    533             readStream = fs.createReadStream(offHelpers.offStore() + '/' + file);
    534             readStream.pipe(process.stdout);
    535           }
    536         }
    537       };
    538     } else if (mode === 'rsync') {
    539       transport['send'] = function(file) {
    540         var f_l, options;
    541         f_l = file.split('/');
    542         offHelpers.mkdirStore(f_l[0] + '/' + f_l[1]);
    543         options = offHelpers.offRsyncOptions();
    544         if (options.indexOf('_i') !== -1) {
    545           if (offHelpers.offPem() !== '' && offHelpers.offPem() !== 'offNoValue') {
    546             options = options.replace('_i', offHelpers.offPem());
    547           }
    548         }
    549         exec('rsync', [options, offHelpers.objectPath() + '/' + file, offHelpers.offScp() + '/' + file]);
    550       };
    551       transport['receive'] = function(file) {
    552         var f_l, options, readStream;
    553         f_l = file.split('/');
    554         if (fs.existsSync(offHelpers.objectPath() + '/' + f_l[0] + '/' + f_l[1]) === false) {
    555           mkdirParents(offHelpers.objectPath() + '/' + f_l[0] + '/' + f_l[1]);
    556         }
    557         options = offHelpers.offRsyncOptions();
    558         if (options.indexOf('_i') !== -1) {
    559           if (offHelpers.offPem() !== '' && offHelpers.offPem() !== 'offNoValue') {
    560             options = options.replace('_i', offHelpers.offPem());
    561           }
    562         }
    563         exec('rsync', [options, offHelpers.offScp() + '/' + file, offHelpers.objectPath() + '/' + file]);
    564         if (offHelpers.transform() === 'enable' && offHelpers.transformFrom() !== '') {
    565           transport['transformFrom'](file);
    566           if (offHelpers.checkIntegrity(offHelpers.objectPath() + '/../tmp/' + file)) {
    567             readStream = fs.createReadStream(offHelpers.objectPath() + '/../tmp/' + file);
    568             readStream.pipe(process.stdout);
    569           }
    570         } else {
    571           if (offHelpers.checkIntegrity(offHelpers.objectPath() + '/' + file)) {
    572             readStream = fs.createReadStream(offHelpers.objectPath() + '/' + file);
    573             readStream.pipe(process.stdout);
    574           }
    575         }
    576       };
    577     } else if (mode === 'scp') {
    578       transport['send'] = function(file) {
    579         var f_l, h_l, pem;
    580         f_l = file.split('/');
    581         offHelpers.mkdirStore(f_l[0] + '/' + f_l[1]);
    582         if (offHelpers.offPem() === '' || offHelpers.offPem() === 'offNoValue') {
    583           pem = '';
    584         } else {
    585           pem = '-i ' + offHelpers.offPem();
    586         }
    587         h_l = offHelpers.getSSHConfig();
    588         if (isNaN(h_l[2])) {
    589           exec('scp', [offHelpers.offScpOptions(), pem, offHelpers.objectPath() + '/' + file, h_l[0] + ':' + h_l[1] + '/' + file]);
    590         } else {
    591           exec('scp', [offHelpers.offScpOptions(), pem, '-P ' + h_l[2], offHelpers.objectPath() + '/' + file, h_l[0] + ':' + h_l[1] + '/' + file]);
    592         }
    593       };
    594       transport['receive'] = function(file) {
    595         var f_l, h_l, pem, readStream;
    596         f_l = file.split('/');
    597         if (fs.existsSync(offHelpers.objectPath() + '/' + f_l[0] + '/' + f_l[1]) === false) {
    598           mkdirParents(offHelpers.objectPath() + '/' + f_l[0] + '/' + f_l[1]);
    599         }
    600         if (offHelpers.offPem() === '' || offHelpers.offPem() === 'offNoValue') {
    601           pem = '';
    602         } else {
    603           pem = '-i ' + offHelpers.offPem();
    604         }
    605         h_l = offHelpers.getSSHConfig();
    606         if (isNaN(h_l[2])) {
    607           exec('scp', [offHelpers.offScpOptions(), pem, h_l[0] + ':' + h_l[1] + '/' + file, offHelpers.objectPath() + '/' + file]);
    608         } else {
    609           exec('scp', [offHelpers.offScpOptions(), pem, '-P ' + h_l[2], h_l[0] + ':' + h_l[1] + '/' + file, offHelpers.objectPath() + '/' + file]);
    610         }
    611         if (offHelpers.transform() === 'enable' && offHelpers.transformFrom() !== '') {
    612           transport['transformFrom'](file);
    613           if (offHelpers.checkIntegrity(offHelpers.objectPath() + '/../tmp/' + file)) {
    614             readStream = fs.createReadStream(offHelpers.objectPath() + '/../tmp/' + file);
    615             readStream.pipe(process.stdout);
    616           }
    617         } else {
    618           if (offHelpers.checkIntegrity(offHelpers.objectPath() + '/' + file)) {
    619             readStream = fs.createReadStream(offHelpers.objectPath() + '/' + file);
    620             readStream.pipe(process.stdout);
    621           }
    622         }
    623       };
    624     } else if (mode === 'http') {
    625       transport['send'] = function(file) {
    626         offLogRepo('Http mode is read-only: ' + file + ' is stored in cache only');
    627       };
    628       transport['receive'] = function(file) {
    629         var f_l, readStream;
    630         f_l = file.split('/');
    631         if (fs.existsSync(offHelpers.objectPath() + '/' + f_l[0] + '/' + f_l[1]) === false) {
    632           mkdirParents(offHelpers.objectPath() + '/' + f_l[0] + '/' + f_l[1]);
    633         }
    634         exec('curl', [offHelpers.offCurlOptions(), offHelpers.objectPath() + '/' + file, offHelpers.offHttp() + '/' + file]);
    635         if (offHelpers.transform() === 'enable' && offHelpers.transformFrom() !== '') {
    636           transport['transformFrom'](file);
    637           if (offHelpers.checkIntegrity(offHelpers.objectPath() + '/../tmp/' + file)) {
    638             readStream = fs.createReadStream(offHelpers.objectPath() + '/../tmp/' + file);
    639             readStream.pipe(process.stdout);
    640           }
    641         } else {
    642           if (offHelpers.checkIntegrity(offHelpers.objectPath() + '/' + file)) {
    643             readStream = fs.createReadStream(offHelpers.objectPath() + '/' + file);
    644             readStream.pipe(process.stdout);
    645           }
    646         }
    647       };
    648     } else if (mode === 's3') {
    649       transport['send'] = function(file) {
    650         var AWS, fileStream, s3, upParams;
    651         AWS = require('aws-sdk');
    652         s3 = new AWS.S3({
    653           region: offHelpers.s3Region(),
    654           signatureVersion: 'v4'
    655         });
    656         upParams = {
    657           Bucket: offHelpers.s3Bucket(),
    658           Key: file,
    659           Body: ''
    660         };
    661         fileStream = fs.createReadStream(offHelpers.objectPath() + '/' + file);
    662         upParams.Body = fileStream;
    663         s3.upload(upParams, function(err, data) {});
    664       };
    665       transport['receive'] = function(file) {
    666         var AWS, dlParams, f_l, fileStream, s3;
    667         AWS = require('aws-sdk');
    668         s3 = new AWS.S3({
    669           region: offHelpers.s3Region(),
    670           signatureVersion: 'v4'
    671         });
    672         f_l = file.split('/');
    673         if (fs.existsSync(offHelpers.objectPath() + '/' + f_l[0] + '/' + f_l[1]) === false) {
    674           mkdirParents(offHelpers.objectPath() + '/' + f_l[0] + '/' + f_l[1]);
    675         }
    676         dlParams = {
    677           Bucket: offHelpers.s3Bucket(),
    678           Key: file
    679         };
    680         fileStream = fs.createWriteStream(offHelpers.objectPath() + '/' + file);
    681         fileStream.on('finish', function() {
    682           var readStream;
    683           if (offHelpers.transform() === 'enable' && offHelpers.transformFrom() !== '') {
    684             transport['transformFrom'](file);
    685             if (offHelpers.checkIntegrity(offHelpers.objectPath() + '/../tmp/' + file)) {
    686               readStream = fs.createReadStream(offHelpers.objectPath() + '/../tmp/' + file);
    687               readStream.pipe(process.stdout);
    688             }
    689           } else {
    690             if (offHelpers.checkIntegrity(offHelpers.objectPath() + '/' + file)) {
    691               readStream = fs.createReadStream(offHelpers.objectPath() + '/' + file);
    692               readStream.pipe(process.stdout);
    693             }
    694           }
    695         });
    696         s3.getObject(dlParams, function(err, data) {}).on('httpData', function(chunk) {
    697           fileStream.write(chunk);
    698         }).on('httpDone', function() {
    699           fileStream.end();
    700         });
    701       };
    702     }
    703   },
    704   'getOffFilePath': function(offFile) {
    705     return [offFile.slice(0, 2) + '/' + offFile.slice(2, 4) + '/' + offFile, offFile.slice(0, 2) + '/' + offFile.slice(2, 4)];
    706   }
    707 };
    708 
    709 
    710 /*
    711  * core
    712  */
    713 
    714 transport = {
    715   'send': function(src) {},
    716   'receive': function(src) {},
    717   'transformFrom': function(file) {
    718     var cmd, offFile, offFilePath, r;
    719     offFile = path.basename(file);
    720     offFilePath = offHelpers.getOffFilePath(offFile);
    721     if (fs.existsSync(offHelpers.objectPath() + '/../tmp/' + offFilePath[1]) === false) {
    722       mkdirParents(offHelpers.objectPath() + '/../tmp/' + offFilePath[1]);
    723     }
    724     cmd = offHelpers.transformFrom();
    725     cmd = cmd.replace('_1', offHelpers.objectPath() + '/' + file).replace('_2', offHelpers.objectPath() + '/../tmp/' + file);
    726     r = syncexec(cmd);
    727   }
    728 };
    729 
    730 offCommands = {
    731   'localSetup': function() {
    732     var hook;
    733     mkdirParents(offHelpers.objectPath());
    734     hook = offHelpers.gitRepoRoot() + '/.git/hooks/pre-push';
    735     if (fs.existsSync(hook) === false) {
    736       fs.writeFileSync(hook, offDEFAULTS.prePush);
    737       fs.chmodSync(hook, '755');
    738     }
    739   },
    740   'install': function(setCfg) {
    741     if (setCfg == null) {
    742       setCfg = gitConfig.set;
    743     }
    744     offCommands.localSetup();
    745     if (gitConfig.get('filter.off.clean') === '' || setCfg !== gitConfig.set) {
    746       if (setCfg === gitConfig.setThisRepo) {
    747         gitConfig.setLocal('filter.off.clean', 'git off clean %f');
    748       } else {
    749         setCfg('filter.off.clean', 'git off clean %f');
    750       }
    751     }
    752     if (gitConfig.get('filter.off.smudge') === '' || setCfg !== gitConfig.set) {
    753       if (setCfg === gitConfig.setThisRepo) {
    754         gitConfig.setLocal('filter.off.smudge', 'git off smudge %f');
    755       } else {
    756         setCfg('filter.off.smudge', 'git off smudge %f');
    757       }
    758     }
    759     if (offHelpers.log() === '' || setCfg !== gitConfig.set) {
    760       gitConfig.set('off.log', offDEFAULTS.log);
    761     }
    762     if (offHelpers.offMode() === '' || setCfg !== gitConfig.set) {
    763       setCfg('off.mode', offDEFAULTS.mode);
    764     }
    765     if (offHelpers.offIntegrity() === '' || setCfg !== gitConfig.set) {
    766       setCfg('off.integrity', offDEFAULTS.integrity);
    767     }
    768     if (offHelpers.offPem() === '' || setCfg !== gitConfig.set) {
    769       setCfg('off.pem', offDEFAULTS.pem);
    770     }
    771     if (offHelpers.offScp() === '' || setCfg !== gitConfig.set) {
    772       setCfg('off.scphost', offDEFAULTS.scpHost);
    773     }
    774     if (offHelpers.offScpUser() === '' || setCfg !== gitConfig.set) {
    775       setCfg('off.scpuser', offDEFAULTS.scpUser);
    776     }
    777     if (offHelpers.offSshOptions() === '' || setCfg !== gitConfig.set) {
    778       setCfg('off.sshoptions', offDEFAULTS.sshOptions);
    779     }
    780     if (offHelpers.offScpOptions() === '' || setCfg !== gitConfig.set) {
    781       setCfg('off.scpoptions', offDEFAULTS.scpOptions);
    782     }
    783     if (offHelpers.offRsyncOptions() === '' || setCfg !== gitConfig.set) {
    784       setCfg('off.rsyncoptions', offDEFAULTS.rsyncOptions);
    785     }
    786     if (offHelpers.offStore() === '' || setCfg !== gitConfig.set) {
    787       setCfg('off.store', offDEFAULTS.store);
    788     }
    789     if (offHelpers.offHttp() === '' || setCfg !== gitConfig.set) {
    790       setCfg('off.http', offDEFAULTS.http);
    791     }
    792     if (offHelpers.offCurlOptions() === '' || setCfg !== gitConfig.set) {
    793       setCfg('off.curloptions', offDEFAULTS.curlOptions);
    794     }
    795     if (offHelpers.offConfigAlways() === '' || setCfg !== gitConfig.set) {
    796       setCfg('off.configAlways', offDEFAULTS.configAlways);
    797     }
    798     if (offHelpers.s3Region() === '' || setCfg !== gitConfig.set) {
    799       setCfg('off.s3Region', offDEFAULTS.s3Region);
    800     }
    801     if (offHelpers.s3Bucket() === '' || setCfg !== gitConfig.set) {
    802       setCfg('off.s3Bucket', offDEFAULTS.s3Bucket);
    803     }
    804     if (offHelpers.transform() === '' || setCfg !== gitConfig.set) {
    805       setCfg('off.transform', offDEFAULTS.transform);
    806     }
    807     if (offHelpers.transformTo() === '' || setCfg !== gitConfig.set) {
    808       setCfg('off.transformTo', offDEFAULTS.transformTo);
    809     }
    810     if (offHelpers.transformFrom() === '' || setCfg !== gitConfig.set) {
    811       setCfg('off.transformFrom', offDEFAULTS.transformFrom);
    812     }
    813     if (runtimeConfig.offMode === 'copy') {
    814       mkdirParents(runtimeConfig.offStore);
    815     }
    816     if (runtimeConfig.offMode === 'scp' || runtimeConfig.offMode === 'rsync') {
    817       offHelpers.mkdirStore('');
    818     }
    819   },
    820   'mode': function(setCfg) {
    821     var len, mode;
    822     len = process.argv.length;
    823     mode = process.argv[len - 1];
    824     if (mode !== 'mode' && mode !== 'thisrepo') {
    825       setCfg('off.mode', mode);
    826     } else {
    827       console.log('off.mode '.blue.bold + offHelpers.offMode());
    828     }
    829   },
    830   'store': function(setCfg) {
    831     var len, store;
    832     len = process.argv.length;
    833     store = process.argv[len - 1];
    834     if (store !== 'store' && store !== 'thisrepo') {
    835       setCfg('off.store', store);
    836     } else {
    837       console.log('off.store '.blue.bold + offHelpers.offStore());
    838     }
    839   },
    840   'scp': function(setCfg) {
    841     var len, scpHost;
    842     len = process.argv.length;
    843     scpHost = process.argv[len - 1];
    844     if (scpHost !== 'scp' && scpHost !== 'thisrepo') {
    845       setCfg('off.scphost', scpHost);
    846     } else {
    847       console.log('off.scp '.blue.bold + offHelpers.offScp());
    848     }
    849   },
    850   'http': function(setCfg) {
    851     var http, len;
    852     len = process.argv.length;
    853     http = process.argv[len - 1];
    854     if (http !== 'http' && http !== 'thisrepo') {
    855       setCfg('off.http', http);
    856     } else {
    857       console.log('off.http '.blue.bold + offHelpers.offHttp());
    858     }
    859   },
    860   'curl': function(setCfg) {
    861     var curl, len;
    862     len = process.argv.length;
    863     curl = process.argv[len - 1];
    864     if (curl !== 'curl' && curl !== 'thisrepo') {
    865       setCfg('off.curloptions', curl);
    866     } else {
    867       console.log('off.curloptions '.blue.bold + offHelpers.offCurlOptions());
    868     }
    869   },
    870   'integrity': function(setCfg) {
    871     var integrity, len;
    872     len = process.argv.length;
    873     integrity = process.argv[len - 1];
    874     if (integrity !== 'integrity' && integrity !== 'thisrepo') {
    875       setCfg('off.integrity', integrity);
    876     } else {
    877       console.log('off.integrity '.blue.bold + offHelpers.offIntegrity());
    878     }
    879   },
    880   'pem': function(setCfg) {
    881     var len, pem;
    882     len = process.argv.length;
    883     pem = process.argv[len - 1];
    884     if (pem !== 'pem' && pem !== 'thisrepo') {
    885       setCfg('off.pem', pem);
    886     } else {
    887       console.log('off.pem '.blue.bold + offHelpers.offPem());
    888     }
    889   },
    890   'sshoptions': function(setCfg) {
    891     var len, sshoptions;
    892     len = process.argv.length;
    893     sshoptions = process.argv[len - 1];
    894     if (sshoptions !== 'sshoptions' && sshoptions !== 'thisrepo') {
    895       setCfg('off.sshoptions', sshoptions);
    896     } else {
    897       console.log('off.sshoptions '.blue.bold + offHelpers.offSshOptions());
    898     }
    899   },
    900   'scpoptions': function(setCfg) {
    901     var len, scpoptions;
    902     len = process.argv.length;
    903     scpoptions = process.argv[len - 1];
    904     if (scpoptions !== 'scpoptions' && scpoptions !== 'thisrepo') {
    905       setCfg('off.scpoptions', scpoptions);
    906     } else {
    907       console.log('off.scpoptions '.blue.bold + offHelpers.offScpOptions());
    908     }
    909   },
    910   'rsyncoptions': function(setCfg) {
    911     var len, rsyncoptions;
    912     len = process.argv.length;
    913     rsyncoptions = process.argv[len - 1];
    914     if (rsyncoptions !== 'rsyncoptions' && rsyncoptions !== 'thisrepo') {
    915       setCfg('off.rsyncoptions', rsyncoptions);
    916     } else {
    917       console.log('off.rsyncoptions '.blue.bold + offHelpers.offRsyncOptions());
    918     }
    919   },
    920   'scpUser': function(setCfg) {
    921     var len, scpUser;
    922     len = process.argv.length;
    923     scpUser = process.argv[len - 1];
    924     if (scpUser !== 'scpuser' && scpUser !== 'thisrepo') {
    925       setCfg('off.scpuser', scpUser);
    926     } else {
    927       console.log('off.scpuser '.blue.bold + offHelpers.offScpUser());
    928     }
    929   },
    930   'track': function() {
    931     var i, l, len1, r, ref;
    932     if (process.argv[3] !== void 0) {
    933       offCommands.install();
    934       fs.appendFileSync(offHelpers.gitRepoRoot() + '/.gitattributes', process.argv[3] + ' filter=off -text\n');
    935     } else {
    936       r = exec('listAttr', ['`cd ' + offHelpers.gitRepoRoot() + '; git ls-files`']);
    937       ref = r.stdout.split('\n');
    938       for (i = 0, len1 = ref.length; i < len1; i++) {
    939         l = ref[i];
    940         if (l.indexOf(': filter: off') !== -1) {
    941           console.log(l);
    942         }
    943       }
    944     }
    945   },
    946   'configAlways': function() {
    947     var configAlways, len;
    948     len = process.argv.length;
    949     configAlways = process.argv[len - 1];
    950     if (configAlways !== 'configAlways') {
    951       gitConfig.set('off.configAlways', configAlways);
    952     } else {
    953       console.log('off.configAlways '.blue.bold + offHelpers.offConfigAlways());
    954     }
    955   },
    956   's3region': function(setCfg) {
    957     var len, region;
    958     len = process.argv.length;
    959     region = process.argv[len - 1];
    960     if (region !== 's3region' && region !== 'thisrepo') {
    961       setCfg('off.s3region', region);
    962     } else {
    963       console.log('off.s3region '.blue.bold + offHelpers.s3Region());
    964     }
    965   },
    966   's3bucket': function(setCfg) {
    967     var bucket, len;
    968     len = process.argv.length;
    969     bucket = process.argv[len - 1];
    970     if (bucket !== 's3bucket' && bucket !== 'thisrepo') {
    971       setCfg('off.s3bucket', bucket);
    972     } else {
    973       console.log('off.s3bucket '.blue.bold + offHelpers.s3Bucket());
    974     }
    975   },
    976   'transform': function(setCfg) {
    977     var len, setting;
    978     len = process.argv.length;
    979     setting = process.argv[len - 1];
    980     if (setting !== 'transform' && setting !== 'thisrepo') {
    981       setCfg('off.transform', setting);
    982     } else {
    983       console.log('off.transform '.blue.bold + offHelpers.transform());
    984     }
    985   },
    986   'transformTo': function(setCfg) {
    987     var len, setting;
    988     len = process.argv.length;
    989     setting = process.argv[len - 1];
    990     if (setting !== 'transformTo' && setting !== 'thisrepo') {
    991       setCfg('off.transformTo', setting);
    992     } else {
    993       console.log('off.transformTo '.blue.bold + offHelpers.transformTo());
    994     }
    995   },
    996   'transformFrom': function(setCfg) {
    997     var len, setting;
    998     len = process.argv.length;
    999     setting = process.argv[len - 1];
   1000     if (setting !== 'transformFrom' && setting !== 'thisrepo') {
   1001       setCfg('off.transformFrom', setting);
   1002     } else {
   1003       console.log('off.transformFrom '.blue.bold + offHelpers.transformFrom());
   1004     }
   1005   },
   1006   'clean': function() {
   1007     var file, offFile, offFilePath, pipe, quotedFile, r, size, writeStream;
   1008     offCommands.localSetup();
   1009     file = process.argv[3];
   1010     size = fs.statSync(file).size;
   1011     quotedFile = '"' + file + '"';
   1012     r = exec('sha', [quotedFile]);
   1013     offFile = r.stdout.split(' ')[0].trim();
   1014     offFilePath = offHelpers.getOffFilePath(offFile);
   1015     if (fs.existsSync(offHelpers.objectPath() + '/' + offFilePath[1]) === false) {
   1016       mkdirParents(offHelpers.objectPath() + '/' + offFilePath[1]);
   1017     }
   1018     offFilePath = offFilePath[0];
   1019     if (fs.existsSync(offHelpers.objectPath() + '/' + offFilePath) === false) {
   1020       writeStream = fs.createWriteStream(offHelpers.objectPath() + '/' + offFilePath);
   1021       pipe = process.stdin.pipe(writeStream);
   1022       pipe.on('finish', function() {
   1023         var cmd;
   1024         fs.chmodSync(offHelpers.objectPath() + '/' + offFilePath, '444');
   1025         if (offHelpers.transform() === 'enable' && offHelpers.transformTo() !== '') {
   1026           if (fs.existsSync(offHelpers.objectPath() + '/../tmp' === false)) {
   1027             mkdirParents(offHelpers.objectPath() + '/../tmp');
   1028           }
   1029           offFilePath = offHelpers.getOffFilePath(offFile);
   1030           if (fs.existsSync(offHelpers.objectPath() + '/../tmp/' + offFilePath[1]) === false) {
   1031             mkdirParents(offHelpers.objectPath() + '/../tmp/' + offFilePath[1]);
   1032           }
   1033           offFilePath = offFilePath[0];
   1034           exec('mv', [offHelpers.objectPath() + '/' + offFilePath, offHelpers.objectPath() + '/../tmp/' + offFilePath]);
   1035           cmd = offHelpers.transformTo();
   1036           cmd = cmd.replace('_1', offHelpers.objectPath() + '/../tmp/' + offFilePath).replace('_2', offHelpers.objectPath() + '/' + offFilePath);
   1037           r = syncexec(cmd);
   1038         }
   1039       });
   1040     } else {
   1041       writeStream = fs.createWriteStream('/dev/null');
   1042       process.stdin.pipe(writeStream);
   1043     }
   1044     console.log(offDEFAULTS.offSignature + offFile + ' size:' + size);
   1045   },
   1046   'prepush': function() {
   1047     var rl;
   1048     offHelpers.setTransport();
   1049     rl = readline.createInterface({
   1050       input: process.stdin,
   1051       output: process.stdout,
   1052       terminal: false
   1053     });
   1054     rl.on('line', function(line) {
   1055       offCommands.push(line);
   1056     });
   1057   },
   1058   'push': function(line) {
   1059     var commitListToPush, i, j, len, len1, len2, line_l, localRef, localSha, objInfo, objInfoList, offFile, offFilePath, offRef, oi, r, remoteName, remoteRef, remoteSha, sha, url;
   1060     len = process.argv.length;
   1061     remoteName = process.argv[len - 2];
   1062     url = process.argv[len - 1];
   1063     line_l = line.split(' ');
   1064     localRef = line_l[0];
   1065     localSha = line_l[1];
   1066     remoteRef = line_l[2];
   1067     remoteSha = line_l[3];
   1068     r = 0;
   1069     if (remoteSha === '0000000000000000000000000000000000000000') {
   1070       r = exec('gitListEmptyRemote', [localSha]);
   1071     } else {
   1072       r = exec('gitList', [localSha, '^' + remoteSha]);
   1073     }
   1074     commitListToPush = r.stdout.split('\n');
   1075     commitListToPush = commitListToPush.slice(0, commitListToPush.length - 1);
   1076     for (i = 0, len1 = commitListToPush.length; i < len1; i++) {
   1077       sha = commitListToPush[i];
   1078       r = exec('gitDiff', [sha]);
   1079       objInfoList = r.stdout.split('\n');
   1080       objInfoList = objInfoList.slice(1, objInfoList.length - 1);
   1081       for (j = 0, len2 = objInfoList.length; j < len2; j++) {
   1082         oi = objInfoList[j];
   1083         objInfo = oi.split(' ');
   1084         if (objInfo[oiNAME].slice(0, 1) !== 'D') {
   1085           r = exec('gitCat', [objInfo[oiOID]]);
   1086           offRef = r.stdout.split('\n')[0];
   1087           if (offRef.indexOf(offDEFAULTS.offSignature) !== -1) {
   1088             offFile = offRef.split(':')[1].split(' ')[0];
   1089             offFilePath = offHelpers.getOffFilePath(offFile)[0];
   1090             transport.send(offFilePath);
   1091           }
   1092         }
   1093       }
   1094     }
   1095   },
   1096   'smudge': function() {
   1097     var status;
   1098     offHelpers.setTransport();
   1099     offCommands.localSetup();
   1100     status = 'header';
   1101     process.stdin.on('readable', function() {
   1102       var cmd, data, decoder, header, offFile, offFilePath, r, readStream;
   1103       if (status === 'header') {
   1104         status = 'stream';
   1105         data = process.stdin.read(offDEFAULTS.offSignature.length + offDEFAULTS.shaLength);
   1106         decoder = new StringDecoder('utf8');
   1107         if (data === null) {
   1108           offLogRepo('smudge error: cant get data from stdin.');
   1109           process.exit(1);
   1110         }
   1111         header = decoder.write(data);
   1112         if (header.slice(0, offDEFAULTS.offSignature.length) === offDEFAULTS.offSignature) {
   1113           offFile = header.split(':')[1];
   1114           offFilePath = offHelpers.getOffFilePath(offFile)[0];
   1115           if (fs.existsSync(offHelpers.objectPath() + '/' + offFilePath) === true) {
   1116             if (offHelpers.transform() === 'enable' && offHelpers.transformFrom() !== '') {
   1117               if (fs.existsSync(offHelpers.objectPath() + '/../tmp' === false)) {
   1118                 mkdirParents(offHelpers.objectPath() + '/../tmp');
   1119               }
   1120               offFilePath = offHelpers.getOffFilePath(offFile);
   1121               if (fs.existsSync(offHelpers.objectPath() + '/../tmp/' + offFilePath[1]) === false) {
   1122                 mkdirParents(offHelpers.objectPath() + '/../tmp/' + offFilePath[1]);
   1123               }
   1124               offFilePath = offFilePath[0];
   1125               cmd = offHelpers.transformFrom();
   1126               cmd = cmd.replace('_1', offHelpers.objectPath() + '/' + offFilePath).replace('_2', offHelpers.objectPath() + '/../tmp/' + offFilePath);
   1127               r = syncexec(cmd);
   1128               readStream = fs.createReadStream(offHelpers.objectPath() + '/../tmp/' + offFilePath);
   1129             } else {
   1130               readStream = fs.createReadStream(offHelpers.objectPath() + '/' + offFilePath);
   1131             }
   1132             readStream.pipe(process.stdout);
   1133           } else {
   1134             transport.receive(offFilePath);
   1135           }
   1136         } else {
   1137           process.stdout.write(data);
   1138           process.stdin.pipe(process.stdout);
   1139         }
   1140       }
   1141     });
   1142   },
   1143   'copyTo': function() {
   1144     if (process.argv[3] === void 0) {
   1145       console.log('Choose a mode where to copy the cache'.red.bold);
   1146       return;
   1147     }
   1148     offHelpers.setTransport(process.argv[3]);
   1149     offHelpers.copyTo();
   1150   },
   1151   'pushTo': function() {
   1152     if (offHelpers.offMode() !== void 0) {
   1153       offHelpers.setTransport(offHelpers.offMode());
   1154       offHelpers.copyTo();
   1155     }
   1156   },
   1157   'clearAll': function() {
   1158     offCommands.clearStore();
   1159     offCommands.clearCache();
   1160     rmAll(offHelpers.getLog());
   1161   },
   1162   'clearCache': function() {
   1163     rmAll(offHelpers.gitRepoRoot() + '/.git/off');
   1164   },
   1165   'clearStore': function() {
   1166     if (offHelpers.offMode() === 'copy') {
   1167       rmAll(offHelpers.offStore());
   1168     }
   1169     if (offHelpers.offMode() === 'scp' || offHelpers.offMode() === 'rsync') {
   1170       offHelpers.rmAllStore('');
   1171     }
   1172   },
   1173   'clearTmp': function() {
   1174     rmAll(offHelpers.gitRepoRoot() + '/.git/off/tmp');
   1175   },
   1176   'defaults': function() {
   1177     var i, k, len1, ref;
   1178     ref = Object.keys(offDEFAULTS);
   1179     for (i = 0, len1 = ref.length; i < len1; i++) {
   1180       k = ref[i];
   1181       console.log(k.blue.bold + ' ' + offDEFAULTS[k]);
   1182     }
   1183   },
   1184   'env': function() {
   1185     console.log('off.mode '.blue.bold + offHelpers.offMode());
   1186     console.log('off.integrity '.blue.bold + offHelpers.offIntegrity());
   1187     console.log('off.pem '.blue.bold + offHelpers.offPem());
   1188     console.log('off.sshoptions '.blue.bold + offHelpers.offSshOptions());
   1189     console.log('off.scpoptions '.blue.bold + offHelpers.offScpOptions());
   1190     console.log('off.rsyncoptions '.blue.bold + offHelpers.offRsyncOptions());
   1191     console.log('off.store '.blue.bold + offHelpers.offStore());
   1192     console.log('off.http '.blue.bold + offHelpers.offHttp());
   1193     console.log('off.curloptions '.blue.bold + offHelpers.offCurlOptions());
   1194     console.log('off.scphost '.blue.bold + offHelpers.offScp());
   1195     console.log('off.scpuser '.blue.bold + offHelpers.offScpUser());
   1196     console.log('off.log '.blue.bold + offHelpers.getLog());
   1197     console.log('off.configAlways '.blue.bold + offHelpers.offConfigAlways());
   1198     console.log('off.s3region '.blue.bold + offHelpers.s3Region());
   1199     console.log('off.s3bucket '.blue.bold + offHelpers.s3Bucket());
   1200     console.log('off.transform '.blue.bold + offHelpers.transform());
   1201     console.log('off.transformTo '.blue.bold + offHelpers.transformTo());
   1202     console.log('off.transformFrom '.blue.bold + offHelpers.transformFrom());
   1203   },
   1204   'help': function() {
   1205     var c, i, len1, ref;
   1206     console.log('git-off help\n'.green.bold);
   1207     if (process.argv[3] === void 0) {
   1208       console.log('# Quick Start'.green.bold);
   1209       console.log('Setup:');
   1210       console.log("git off track '*.bin'");
   1211       console.log('git add .');
   1212       console.log('git commit');
   1213       console.log('git push');
   1214       console.log('git checkout master');
   1215       console.log('\n# Other'.green.bold);
   1216       console.log('git off install');
   1217       console.log('git off mode scp');
   1218       console.log('git off scp localhost:/tmp/offStore');
   1219       console.log('git off scpuser username');
   1220       console.log('git off cc');
   1221       console.log('git off ca');
   1222       console.log('git off env');
   1223       console.log('git off defaults\n');
   1224       ref = Object.keys(COMMAND_MAP);
   1225       for (i = 0, len1 = ref.length; i < len1; i++) {
   1226         c = ref[i];
   1227         showCommandHelp(c);
   1228       }
   1229       console.log('\nGo to https://noulin.net/git-off/file/README.md.html for more information'.green.bold);
   1230     } else {
   1231       if (COMMAND_MAP[process.argv[3]] === void 0) {
   1232         console.log(COMMAND_MAP.help.h.green);
   1233       } else {
   1234         showCommandHelp(process.argv[3]);
   1235       }
   1236     }
   1237   }
   1238 };
   1239 
   1240 
   1241 /*
   1242  * main
   1243  */
   1244 
   1245 thisrepo = function(cmd) {
   1246   if (process.argv[3] !== 'thisrepo') {
   1247     cmd(gitConfig['set']);
   1248   } else {
   1249     cmd(gitConfig['setThisRepo']);
   1250   }
   1251 };
   1252 
   1253 showCommandHelp = function(cmd) {
   1254   var l_l;
   1255   l_l = COMMAND_MAP[cmd].h.split('\n');
   1256   console.log(l_l[0]);
   1257   l_l = l_l.slice(1);
   1258   console.log(l_l.join('\n').green);
   1259 };
   1260 
   1261 COMMAND_MAP = {
   1262   'install': {
   1263     f: function() {
   1264       thisrepo(offCommands['install']);
   1265     },
   1266     h: 'git off install [thisrepo]\n  setup git config (default global)\n  thisrepo sets up config in current repo'
   1267   },
   1268   'mode': {
   1269     f: function() {
   1270       thisrepo(offCommands['mode']);
   1271     },
   1272     h: 'git off mode [thisrepo] [copy|rsync|scp|http|s3]\n  set/show git off mode'
   1273   },
   1274   'store': {
   1275     f: function() {
   1276       thisrepo(offCommands['store']);
   1277     },
   1278     h: 'git off store [thisrepo] [path]\n  set/show git off store path for copy mode'
   1279   },
   1280   'scp': {
   1281     f: function() {
   1282       thisrepo(offCommands['scp']);
   1283     },
   1284     h: 'git off scp [thisrepo] [host]\n  setup scp config\n  host has format host:path, user@host:path, user@host:port/path\n  Example: localhost:/tmp/offStore'
   1285   },
   1286   'http': {
   1287     f: function() {
   1288       thisrepo(offCommands['http']);
   1289     },
   1290     h: 'git off http [thisrepo] [host]\n  setup http config\n  host has format http://host/path\n  Example: http://localhost/offStore'
   1291   },
   1292   'curl': {
   1293     f: function() {
   1294       thisrepo(offCommands['curl']);
   1295     },
   1296     h: 'git off curl [thisrepo] [options]\n  setup curl config'
   1297   },
   1298   'integrity': {
   1299     f: function() {
   1300       thisrepo(offCommands['integrity']);
   1301     },
   1302     h: 'git off integrity [thisrepo] [enable|disable]\n  set/show git off integrity.\n  when enabled, the SHA of the file received from the store is\n  checked again the SHA of the original file'
   1303   },
   1304   'pem': {
   1305     f: function() {
   1306       thisrepo(offCommands['pem']);
   1307     },
   1308     h: "git off pem [thisrepo] [pathToPrivateKey]\n  set/show git off pem.\n  off.pem is the private key for ssh, rsync and scp\n  set 'offNoValue' to set an empty value (useful when there are multiple configs)"
   1309   },
   1310   'sshoptions': {
   1311     f: function() {
   1312       thisrepo(offCommands['sshoptions']);
   1313     },
   1314     h: 'git off sshoptions [thisrepo] [options]\n  set/show git off sshoptions'
   1315   },
   1316   'scpoptions': {
   1317     f: function() {
   1318       thisrepo(offCommands['scpoptions']);
   1319     },
   1320     h: 'git off scpoptions [thisrepo] [options]\n  set/show git off scpoptions'
   1321   },
   1322   'rsyncoptions': {
   1323     f: function() {
   1324       thisrepo(offCommands['rsyncoptions']);
   1325     },
   1326     h: 'git off rsyncoptions [thisrepo] [options]\n  set/show git off rsyncoptions'
   1327   },
   1328   'scpuser': {
   1329     f: function() {
   1330       thisrepo(offCommands['scpUser']);
   1331     },
   1332     h: 'git off scpuser [thisrepo] [username]\n  setup scp username config'
   1333   },
   1334   'track': {
   1335     f: function() {
   1336       offCommands.track();
   1337     },
   1338     h: "git off track\n  setup gitattribute filters\n  example: git off track '*.bin'\n  without parameter, list git off attributes\n  calls git off install"
   1339   },
   1340   'configAlways': {
   1341     f: function() {
   1342       offCommands.configAlways();
   1343     },
   1344     h: "git off configAlways [''|GIT_OFF_CONFIG|repo|global]\n  '' disable configAlways\n  GIT_OFF_CONFIG load all configurations from $GIT_OFF_CONFIG\n  repo load all configurations from current git repo\n  global load all configurations from global git config\n  set 'offNoValue' to set an empty value"
   1345   },
   1346   's3region': {
   1347     f: function() {
   1348       thisrepo(offCommands['s3region']);
   1349     },
   1350     h: 'git off s3region [thisrepo] [region]\n  setup amazon s3 region for the bucket'
   1351   },
   1352   's3bucket': {
   1353     f: function() {
   1354       thisrepo(offCommands['s3bucket']);
   1355     },
   1356     h: 'git off s3bucket [thisrepo] [bucket]\n  setup amazon s3 bucket'
   1357   },
   1358   'transform': {
   1359     f: function() {
   1360       thisrepo(offCommands['transform']);
   1361     },
   1362     h: 'git off transform [thisrepo] [enable|disable]\n  enable transform in clean and smudge filters'
   1363   },
   1364   'transformTo': {
   1365     f: function() {
   1366       thisrepo(offCommands['transformTo']);
   1367     },
   1368     h: "git off transformTo [thisrepo] ['cmd _1 _2']\n  setup transform command for clear filter\n  When the command is empty the regular transport is performed"
   1369   },
   1370   'transformFrom': {
   1371     f: function() {
   1372       thisrepo(offCommands['transformFrom']);
   1373     },
   1374     h: "git off transformFrom [thisrepo] ['cmd _1 _2']\n  setup transform command for smudge filter\n  When the command is empty the regular transport is performed"
   1375   },
   1376   'clean': {
   1377     f: function() {
   1378       offCommands.clean();
   1379     },
   1380     h: 'git off clean\n  internal filter\n  dont use directly'
   1381   },
   1382   'pre-push': {
   1383     f: function() {
   1384       offCommands.prepush();
   1385     },
   1386     h: 'git off pre-push\n  internal filter\n  dont use directly'
   1387   },
   1388   'smudge': {
   1389     f: function() {
   1390       offCommands.smudge();
   1391     },
   1392     h: 'git off smudge\n  internal filter\n  dont use directly'
   1393   },
   1394   'copyTo': {
   1395     f: function() {
   1396       offCommands.copyTo();
   1397     },
   1398     h: 'git off copyTo [copy|rsync|scp|s3]\n  copy cache to store for specified mode'
   1399   },
   1400   'push': {
   1401     f: function() {
   1402       offCommands.pushTo();
   1403     },
   1404     h: 'git off push\n  copy cache to store for selected mode'
   1405   },
   1406   'clearAll': {
   1407     f: function() {
   1408       offCommands.clearAll();
   1409     },
   1410     h: 'git off clearAll\n  delete store, cache and log'
   1411   },
   1412   'ca': {
   1413     f: function() {
   1414       offCommands.clearAll();
   1415     },
   1416     h: 'git off ca\n  delete store, cache and log'
   1417   },
   1418   'clearCache': {
   1419     f: function() {
   1420       offCommands.clearCache();
   1421     },
   1422     h: 'git off clearCache\n  delete cache in current git'
   1423   },
   1424   'cc': {
   1425     f: function() {
   1426       offCommands.clearCache();
   1427     },
   1428     h: 'git off cc\n  delete cache in current git'
   1429   },
   1430   'clearStore': {
   1431     f: function() {
   1432       offCommands.clearStore();
   1433     },
   1434     h: 'git off clearStore\n  delete store'
   1435   },
   1436   'cs': {
   1437     f: function() {
   1438       offCommands.clearStore();
   1439     },
   1440     h: 'git off ct\n  delete store'
   1441   },
   1442   'clearTmp': {
   1443     f: function() {
   1444       offCommands.clearTmp();
   1445     },
   1446     h: 'git off clearTmp\n  delete tmp in git off cache\n  Useful when transform is enabled'
   1447   },
   1448   'ct': {
   1449     f: function() {
   1450       offCommands.clearTmp();
   1451     },
   1452     h: 'git off cs\n  delete tmp in git off cache\n  Useful when transform is enabled'
   1453   },
   1454   'defaults': {
   1455     f: function() {
   1456       offCommands.defaults();
   1457     },
   1458     h: 'git off defaults\n  shows first time config'
   1459   },
   1460   'env': {
   1461     f: function() {
   1462       offCommands.env();
   1463     },
   1464     h: 'git off env\n  shows config'
   1465   },
   1466   'help': {
   1467     f: function() {
   1468       offCommands.help();
   1469     },
   1470     h: 'git off help [cmd]\n  git off help. Run git off help command to get help for a specific command.'
   1471   }
   1472 };
   1473 
   1474 if (process.argv[2] === void 0) {
   1475   COMMAND_MAP.help.f();
   1476 } else {
   1477   if (COMMAND_MAP[process.argv[2]] === void 0) {
   1478     COMMAND_MAP['help'].f();
   1479   } else {
   1480     COMMAND_MAP[process.argv[2]].f();
   1481   }
   1482 }
   1483 
   1484 
   1485 /*
   1486  * exports for unit tests
   1487  * export everything
   1488  */
   1489 
   1490 module.exports = {
   1491   runtimeConfig: runtimeConfig,
   1492   offDEFAULTS: offDEFAULTS,
   1493   expandHome: expandHome,
   1494   gitConfig: gitConfig,
   1495   offLog: offLog,
   1496   offLogRepo: offLogRepo,
   1497   exec: exec,
   1498   mkdirParents: mkdirParents,
   1499   rmAll: rmAll,
   1500   copy: copy,
   1501   walkSync: walkSync,
   1502   offHelpers: offHelpers,
   1503   transport: transport,
   1504   offCommands: offCommands,
   1505   thisrepo: thisrepo,
   1506   showCommandHelp: showCommandHelp,
   1507   COMMAND_MAP: COMMAND_MAP
   1508 };