object-codec.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. "use strict";
  2. var bodec = require('bodec');
  3. var modes = require('./modes');
  4. // (body) -> raw-buffer
  5. var encoders = exports.encoders = {
  6. blob: encodeBlob,
  7. tree: encodeTree,
  8. commit: encodeCommit,
  9. tag: encodeTag
  10. };
  11. // ({type:type, body:raw-buffer}) -> buffer
  12. exports.frame = frame;
  13. // (raw-buffer) -> body
  14. var decoders = exports.decoders ={
  15. blob: decodeBlob,
  16. tree: decodeTree,
  17. commit: decodeCommit,
  18. tag: decodeTag
  19. };
  20. // (buffer) -> {type:type, body:raw-buffer}
  21. exports.deframe = deframe;
  22. // Export git style path sort in case it's wanted.
  23. exports.treeMap = treeMap;
  24. exports.treeSort = treeSort;
  25. function encodeBlob(body) {
  26. if (!bodec.isBinary(body)) throw new TypeError("Blobs must be binary values");
  27. return body;
  28. }
  29. function treeMap(key) {
  30. /*jshint validthis:true*/
  31. var entry = this[key];
  32. return {
  33. name: key,
  34. mode: entry.mode,
  35. hash: entry.hash
  36. };
  37. }
  38. function treeSort(a, b) {
  39. var aa = (a.mode === modes.tree) ? a.name + "/" : a.name;
  40. var bb = (b.mode === modes.tree) ? b.name + "/" : b.name;
  41. return aa > bb ? 1 : aa < bb ? -1 : 0;
  42. }
  43. function encodeTree(body) {
  44. var tree = "";
  45. if (Array.isArray(body)) throw new TypeError("Tree must be in object form");
  46. var list = Object.keys(body).map(treeMap, body).sort(treeSort);
  47. for (var i = 0, l = list.length; i < l; i++) {
  48. var entry = list[i];
  49. tree += entry.mode.toString(8) + " " + bodec.encodeUtf8(entry.name) +
  50. "\0" + bodec.decodeHex(entry.hash);
  51. }
  52. return bodec.fromRaw(tree);
  53. }
  54. function encodeTag(body) {
  55. var str = "object " + body.object +
  56. "\ntype " + body.type +
  57. "\ntag " + body.tag +
  58. "\ntagger " + formatPerson(body.tagger) +
  59. "\n\n" + body.message;
  60. return bodec.fromUnicode(str);
  61. }
  62. function encodeCommit(body) {
  63. var str = "tree " + body.tree;
  64. for (var i = 0, l = body.parents.length; i < l; ++i) {
  65. str += "\nparent " + body.parents[i];
  66. }
  67. str += "\nauthor " + formatPerson(body.author) +
  68. "\ncommitter " + formatPerson(body.committer) +
  69. "\n\n" + body.message;
  70. return bodec.fromUnicode(str);
  71. }
  72. function formatPerson(person) {
  73. return safe(person.name) +
  74. " <" + safe(person.email) + "> " +
  75. formatDate(person.date);
  76. }
  77. function safe(string) {
  78. return string.replace(/(?:^[\.,:;<>"']+|[\0\n<>]+|[\.,:;<>"']+$)/gm, "");
  79. }
  80. function two(num) {
  81. return (num < 10 ? "0" : "") + num;
  82. }
  83. function formatDate(date) {
  84. var seconds, offset;
  85. if (date.seconds) {
  86. seconds = date.seconds;
  87. offset = date.offset;
  88. }
  89. // Also accept Date instances
  90. else {
  91. seconds = Math.floor(date.getTime() / 1000);
  92. offset = date.getTimezoneOffset();
  93. }
  94. var neg = "+";
  95. if (offset <= 0) offset = -offset;
  96. else neg = "-";
  97. offset = neg + two(Math.floor(offset / 60)) + two(offset % 60);
  98. return seconds + " " + offset;
  99. }
  100. function frame(obj) {
  101. var type = obj.type;
  102. var body = obj.body;
  103. if (!bodec.isBinary(body)) body = encoders[type](body);
  104. return bodec.join([
  105. bodec.fromRaw(type + " " + body.length + "\0"),
  106. body
  107. ]);
  108. }
  109. function decodeBlob(body) {
  110. return body;
  111. }
  112. function decodeTree(body) {
  113. var i = 0;
  114. var length = body.length;
  115. var start;
  116. var mode;
  117. var name;
  118. var hash;
  119. var tree = {};
  120. while (i < length) {
  121. start = i;
  122. i = indexOf(body, 0x20, start);
  123. if (i < 0) throw new SyntaxError("Missing space");
  124. mode = parseOct(body, start, i++);
  125. start = i;
  126. i = indexOf(body, 0x00, start);
  127. name = bodec.toUnicode(body, start, i++);
  128. hash = bodec.toHex(body, i, i += 20);
  129. tree[name] = {
  130. mode: mode,
  131. hash: hash
  132. };
  133. }
  134. return tree;
  135. }
  136. function decodeCommit(body) {
  137. var i = 0;
  138. var start;
  139. var key;
  140. var parents = [];
  141. var commit = {
  142. tree: "",
  143. parents: parents,
  144. author: "",
  145. committer: "",
  146. message: ""
  147. };
  148. while (body[i] !== 0x0a) {
  149. start = i;
  150. i = indexOf(body, 0x20, start);
  151. if (i < 0) throw new SyntaxError("Missing space");
  152. key = bodec.toRaw(body, start, i++);
  153. start = i;
  154. i = indexOf(body, 0x0a, start);
  155. if (i < 0) throw new SyntaxError("Missing linefeed");
  156. var value = bodec.toUnicode(body, start, i++);
  157. if (key === "parent") {
  158. parents.push(value);
  159. }
  160. else {
  161. if (key === "author" || key === "committer") {
  162. value = decodePerson(value);
  163. }
  164. commit[key] = value;
  165. }
  166. }
  167. i++;
  168. commit.message = bodec.toUnicode(body, i, body.length);
  169. return commit;
  170. }
  171. function decodeTag(body) {
  172. var i = 0;
  173. var start;
  174. var key;
  175. var tag = {};
  176. while (body[i] !== 0x0a) {
  177. start = i;
  178. i = indexOf(body, 0x20, start);
  179. if (i < 0) throw new SyntaxError("Missing space");
  180. key = bodec.toRaw(body, start, i++);
  181. start = i;
  182. i = indexOf(body, 0x0a, start);
  183. if (i < 0) throw new SyntaxError("Missing linefeed");
  184. var value = bodec.toUnicode(body, start, i++);
  185. if (key === "tagger") value = decodePerson(value);
  186. tag[key] = value;
  187. }
  188. i++;
  189. tag.message = bodec.toUnicode(body, i, body.length);
  190. return tag;
  191. }
  192. function decodePerson(string) {
  193. var match = string.match(/^([^<]*) <([^>]*)> ([^ ]*) (.*)$/);
  194. if (!match) throw new Error("Improperly formatted person string");
  195. return {
  196. name: match[1],
  197. email: match[2],
  198. date: {
  199. seconds: parseInt(match[3], 10),
  200. offset: parseInt(match[4], 10) / 100 * -60
  201. }
  202. };
  203. }
  204. function deframe(buffer, decode) {
  205. var space = indexOf(buffer, 0x20);
  206. if (space < 0) throw new Error("Invalid git object buffer");
  207. var nil = indexOf(buffer, 0x00, space);
  208. if (nil < 0) throw new Error("Invalid git object buffer");
  209. var body = bodec.slice(buffer, nil + 1);
  210. var size = parseDec(buffer, space + 1, nil);
  211. if (size !== body.length) throw new Error("Invalid body length.");
  212. var type = bodec.toRaw(buffer, 0, space);
  213. return {
  214. type: type,
  215. body: decode ? decoders[type](body) : body
  216. };
  217. }
  218. function indexOf(buffer, byte, i) {
  219. i |= 0;
  220. var length = buffer.length;
  221. for (;;i++) {
  222. if (i >= length) return -1;
  223. if (buffer[i] === byte) return i;
  224. }
  225. }
  226. function parseOct(buffer, start, end) {
  227. var val = 0;
  228. while (start < end) {
  229. val = (val << 3) + buffer[start++] - 0x30;
  230. }
  231. return val;
  232. }
  233. function parseDec(buffer, start, end) {
  234. var val = 0;
  235. while (start < end) {
  236. val = val * 10 + buffer[start++] - 0x30;
  237. }
  238. return val;
  239. }