walkers.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. var modes = require('../lib/modes.js');
  2. module.exports = function (repo) {
  3. repo.logWalk = logWalk; // (ref) => stream<commit>
  4. repo.treeWalk = treeWalk; // (treeHash) => stream<object>
  5. };
  6. module.exports.walk = walk;
  7. function logWalk(ref, callback) {
  8. if (!callback) return logWalk.bind(this, ref);
  9. var last, seen = {};
  10. var repo = this;
  11. if (!repo.readRef) return onShallow();
  12. return repo.readRef("shallow", onShallow);
  13. function onShallow(err, shallow) {
  14. last = shallow;
  15. resolveRef(repo, ref, onHash);
  16. }
  17. function onHash(err, hash) {
  18. if (err) return callback(err);
  19. return repo.loadAs("commit", hash, function (err, commit) {
  20. if (commit === undefined) return callback(err);
  21. commit.hash = hash;
  22. seen[hash] = true;
  23. return callback(null, walk(commit, scan, loadKey, compare));
  24. });
  25. }
  26. function scan(commit) {
  27. if (last === commit) return [];
  28. return commit.parents.filter(function (hash) {
  29. return !seen[hash];
  30. });
  31. }
  32. function loadKey(hash, callback) {
  33. return repo.loadAs("commit", hash, function (err, commit) {
  34. if (!commit) return callback(err || new Error("Missing commit " + hash));
  35. commit.hash = hash;
  36. if (hash === last) commit.last = true;
  37. return callback(null, commit);
  38. });
  39. }
  40. }
  41. function compare(commit, other) {
  42. return commit.author.date < other.author.date;
  43. }
  44. function treeWalk(hash, callback) {
  45. if (!callback) return treeWalk.bind(this, hash);
  46. var repo = this;
  47. return repo.loadAs("tree", hash, onTree);
  48. function onTree(err, body) {
  49. if (!body) return callback(err || new Error("Missing tree " + hash));
  50. var tree = {
  51. mode: modes.tree,
  52. hash: hash,
  53. body: body,
  54. path: "/"
  55. };
  56. return callback(null, walk(tree, treeScan, treeLoadKey, treeCompare));
  57. }
  58. function treeLoadKey(entry, callback) {
  59. if (entry.mode !== modes.tree) return callback(null, entry);
  60. var type = modes.toType(entry.mode);
  61. return repo.loadAs(type, entry.hash, function (err, body) {
  62. if (err) return callback(err);
  63. entry.body = body;
  64. return callback(null, entry);
  65. });
  66. }
  67. }
  68. function treeScan(object) {
  69. if (object.mode !== modes.tree) return [];
  70. var tree = object.body;
  71. return Object.keys(tree).map(function (name) {
  72. var entry = tree[name];
  73. var path = object.path + name;
  74. if (entry.mode === modes.tree) path += "/";
  75. return {
  76. mode: entry.mode,
  77. hash: entry.hash,
  78. path: path
  79. };
  80. });
  81. }
  82. function treeCompare(first, second) {
  83. return first.path < second.path;
  84. }
  85. function resolveRef(repo, hashish, callback) {
  86. if (/^[0-9a-f]{40}$/.test(hashish)) {
  87. return callback(null, hashish);
  88. }
  89. repo.readRef(hashish, function (err, hash) {
  90. if (!hash) return callback(err || new Error("Bad ref " + hashish));
  91. callback(null, hash);
  92. });
  93. }
  94. function walk(seed, scan, loadKey, compare) {
  95. var queue = [seed];
  96. var working = 0, error, cb;
  97. return {read: read, abort: abort};
  98. function read(callback) {
  99. if (!callback) return read;
  100. if (cb) return callback(new Error("Only one read at a time"));
  101. if (working) { cb = callback; return; }
  102. var item = queue.shift();
  103. if (!item) return callback();
  104. try { scan(item).forEach(onKey); }
  105. catch (err) { return callback(err); }
  106. return callback(null, item);
  107. }
  108. function abort(callback) { return callback(); }
  109. function onError(err) {
  110. if (cb) {
  111. var callback = cb; cb = null;
  112. return callback(err);
  113. }
  114. error = err;
  115. }
  116. function onKey(key) {
  117. working++;
  118. loadKey(key, onItem);
  119. }
  120. function onItem(err, item) {
  121. working--;
  122. if (err) return onError(err);
  123. var index = queue.length;
  124. while (index && compare(item, queue[index - 1])) index--;
  125. queue.splice(index, 0, item);
  126. if (!working && cb) {
  127. var callback = cb; cb = null;
  128. return read(callback);
  129. }
  130. }
  131. }