create-tree.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. "use strict";
  2. var modes = require('../lib/modes.js');
  3. module.exports = function (repo) {
  4. repo.createTree = createTree;
  5. function createTree(entries, callback) {
  6. if (!callback) return createTree.bind(null, entries);
  7. callback = singleCall(callback);
  8. if (!Array.isArray(entries)) {
  9. entries = Object.keys(entries).map(function (path) {
  10. var entry = entries[path];
  11. entry.path = path;
  12. return entry;
  13. });
  14. }
  15. // Tree paths that we need loaded
  16. var toLoad = {};
  17. function markTree(path) {
  18. while(true) {
  19. if (toLoad[path]) return;
  20. toLoad[path] = true;
  21. trees[path] = {
  22. add: [],
  23. del: [],
  24. tree: {}
  25. };
  26. if (!path) break;
  27. path = path.substring(0, path.lastIndexOf("/"));
  28. }
  29. }
  30. // Commands to run organized by tree path
  31. var trees = {};
  32. // Counter for parallel I/O operations
  33. var left = 1; // One extra counter to protect again zalgo cache callbacks.
  34. // First pass, stubs out the trees structure, sorts adds from deletes,
  35. // and saves any inline content blobs.
  36. entries.forEach(function (entry) {
  37. var index = entry.path.lastIndexOf("/");
  38. var parentPath = entry.path.substr(0, index);
  39. var name = entry.path.substr(index + 1);
  40. markTree(parentPath);
  41. var tree = trees[parentPath];
  42. var adds = tree.add;
  43. var dels = tree.del;
  44. if (!entry.mode) {
  45. dels.push(name);
  46. return;
  47. }
  48. var add = {
  49. name: name,
  50. mode: entry.mode,
  51. hash: entry.hash
  52. };
  53. adds.push(add);
  54. if (entry.hash) return;
  55. left++;
  56. repo.saveAs("blob", entry.content, function (err, hash) {
  57. if (err) return callback(err);
  58. add.hash = hash;
  59. check();
  60. });
  61. });
  62. // Preload the base trees
  63. if (entries.base) loadTree("", entries.base);
  64. // Check just in case there was no IO to perform
  65. check();
  66. function loadTree(path, hash) {
  67. left++;
  68. delete toLoad[path];
  69. repo.loadAs("tree", hash, function (err, tree) {
  70. if (err) return callback(err);
  71. trees[path].tree = tree;
  72. Object.keys(tree).forEach(function (name) {
  73. var childPath = path ? path + "/" + name : name;
  74. if (toLoad[childPath]) loadTree(childPath, tree[name].hash);
  75. });
  76. check();
  77. });
  78. }
  79. function check() {
  80. if (--left) return;
  81. findLeaves().forEach(processLeaf);
  82. }
  83. function processLeaf(path) {
  84. var entry = trees[path];
  85. delete trees[path];
  86. var tree = entry.tree;
  87. entry.del.forEach(function (name) {
  88. delete tree[name];
  89. });
  90. entry.add.forEach(function (item) {
  91. tree[item.name] = {
  92. mode: item.mode,
  93. hash: item.hash
  94. };
  95. });
  96. left++;
  97. repo.saveAs("tree", tree, function (err, hash, tree) {
  98. if (err) return callback(err);
  99. if (!path) return callback(null, hash, tree);
  100. var index = path.lastIndexOf("/");
  101. var parentPath = path.substring(0, index);
  102. var name = path.substring(index + 1);
  103. trees[parentPath].add.push({
  104. name: name,
  105. mode: modes.tree,
  106. hash: hash
  107. });
  108. if (--left) return;
  109. findLeaves().forEach(processLeaf);
  110. });
  111. }
  112. function findLeaves() {
  113. var paths = Object.keys(trees);
  114. var parents = {};
  115. paths.forEach(function (path) {
  116. if (!path) return;
  117. var parent = path.substring(0, path.lastIndexOf("/"));
  118. parents[parent] = true;
  119. });
  120. return paths.filter(function (path) {
  121. return !parents[path];
  122. });
  123. }
  124. }
  125. };
  126. function singleCall(callback) {
  127. var done = false;
  128. return function () {
  129. if (done) return console.warn("Discarding extra callback");
  130. done = true;
  131. return callback.apply(this, arguments);
  132. };
  133. }