git.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. var fs = require('fs');
  2. var waterfall = require('async/waterfall');
  3. var exec = require('child_process').exec;
  4. var ini = require('ini');
  5. var path = require('path');
  6. var helper = require('../helper.js');
  7. var cliCommand = require('../cliCommand.js');
  8. var jsGitService = require('./js-git-service.js');
  9. var git = {};
  10. var TIMEOUT = 5000;
  11. var MAXBUFFER = 1024 * 64; // 16KB
  12. git.parseGitConfig = function (folder, cb) {
  13. fs.readFile(path.join(folder, '.git/config'), 'utf-8', function (err, data) {
  14. if (err) {
  15. return cb(err);
  16. }
  17. var config = ini.parse(data);
  18. cb(null, config);
  19. });
  20. };
  21. git.getUrl = function (folder, cb) {
  22. git.parseGitConfig(folder, function (err, config) {
  23. if (err) {
  24. return cb(err);
  25. }
  26. var data = {};
  27. data.type = 'git';
  28. data.url = helper.get(config, 'remote "origin".url');
  29. cb(null, data);
  30. });
  31. };
  32. git.getCommitInfo = function (folder, data, cb) {
  33. jsGitService.getHeadCommit(folder, function (err, commit) {
  34. if (err) {
  35. return cb(err);
  36. }
  37. data.revision = helper.get(commit, 'hash');
  38. data.comment = helper.get(commit, 'message');
  39. cb(null, data);
  40. });
  41. };
  42. git.getStaged = function (folder, data, cb) {
  43. exec(cliCommand(folder, 'git status -s'), {timeout: TIMEOUT, maxBuffer: MAXBUFFER},
  44. function (err, stdout, stderr) {
  45. if (err) {
  46. return cb(err);
  47. }
  48. data.unstaged = (stdout === '') ? false : true;
  49. return cb(null, data);
  50. });
  51. };
  52. git.getBranch = function (folder, data, cb) {
  53. fs.readFile(path.join(folder, '.git/HEAD'), 'utf-8', function (err, content) {
  54. if (err) {
  55. return cb(err);
  56. }
  57. var regex = /ref: refs\/heads\/(.*)/;
  58. var match = regex.exec(content);
  59. data.branch = match ? match[1] : 'HEAD';
  60. return cb(null, data);
  61. });
  62. };
  63. git.getRemote = function (folder, data, cb) {
  64. git.parseGitConfig(folder, function (err, config) {
  65. if (err) {
  66. return cb(err);
  67. }
  68. data.remotes = [];
  69. Object.keys(config).map(function (key) {
  70. var regex = /remote "(.*)"/;
  71. var match = regex.exec(key);
  72. if (match) {
  73. data.remotes.push(match[1]);
  74. }
  75. });
  76. data.remote = (data.remotes.indexOf('origin') === -1) ? data.remotes[0] : 'origin';
  77. cb(null, data);
  78. });
  79. };
  80. git.isCurrentBranchOnRemote = function (folder, data, cb) {
  81. jsGitService.getRefHash(folder, data.branch, data.remote, function (err, hash) {
  82. if (err) {
  83. return cb(err);
  84. }
  85. data.branch_exists_on_remote = !!hash;
  86. return cb(null, data);
  87. });
  88. };
  89. git.getPrevNext = function (folder, data, cb) {
  90. var remote = data.branch_exists_on_remote ? data.remote : null;
  91. jsGitService.getCommitHistory(folder, 100, data.branch, remote, function (err, commitHistory) {
  92. if (err) {
  93. return cb(err);
  94. }
  95. var currentCommitIndex = commitHistory.findIndex(({ hash }) => hash === data.revision);
  96. if (currentCommitIndex === -1) {
  97. data.ahead = true;
  98. data.next_rev = null;
  99. data.prev_rev = null;
  100. }
  101. else {
  102. data.ahead = false;
  103. data.next_rev = (currentCommitIndex === 0) ? null : commitHistory[currentCommitIndex - 1].hash;
  104. data.prev_rev = (currentCommitIndex === (commitHistory.length - 1)) ? null : commitHistory[currentCommitIndex + 1].hash;
  105. }
  106. cb(null, data);
  107. });
  108. };
  109. git.getUpdateTime = function (folder, data, cb) {
  110. fs.stat(folder + ".git", function (err, stats) {
  111. if (err) {
  112. return cb(err);
  113. }
  114. data.update_time = helper.trimNewLine(stats.mtime);
  115. return cb(null, data);
  116. });
  117. };
  118. git.getTags = function (folder, data, cb) {
  119. exec(cliCommand(folder, 'git tag'), {timeout: TIMEOUT, maxBuffer: MAXBUFFER},
  120. function (err, stdout, stderr) {
  121. if (err) {
  122. return cb(err);
  123. }
  124. if (stdout.length) {
  125. data.tags = stdout.split('\n');
  126. data.tags.pop();
  127. data.tags = data.tags.slice(0, 10);
  128. }
  129. return cb(null, data);
  130. });
  131. };
  132. git.parse = function (folder, cb) {
  133. waterfall([
  134. git.getUrl.bind(null, folder),
  135. git.getCommitInfo.bind(null, folder),
  136. git.getStaged.bind(null, folder),
  137. git.getBranch.bind(null, folder),
  138. git.getRemote.bind(null, folder),
  139. git.isCurrentBranchOnRemote.bind(null, folder),
  140. git.getPrevNext.bind(null, folder),
  141. git.getUpdateTime.bind(null, folder),
  142. git.getTags.bind(null, folder)],
  143. function (err, data) {
  144. if (err) {
  145. return cb(err);
  146. }
  147. return cb(null, data);
  148. });
  149. };
  150. git.isUpdated = function (folder, cb) {
  151. waterfall([
  152. git.getCommitInfo.bind(null, folder, {}),
  153. git.getBranch.bind(null, folder),
  154. git.getRemote.bind(null, folder),
  155. git.isCurrentBranchOnRemote.bind(null, folder),
  156. ],
  157. function (err, data) {
  158. if (err) {
  159. return cb(err);
  160. }
  161. exec(cliCommand(folder, 'git remote update'), {timeout: 60000, maxBuffer: MAXBUFFER},
  162. function (err, stdout, stderr) {
  163. if (err) {
  164. return cb(err);
  165. }
  166. var remote = data.branch_exists_on_remote ? data.remote : null;
  167. jsGitService.getLastCommit(folder, data.branch, remote, function (err, commit) {
  168. if (err) {
  169. return cb(err);
  170. }
  171. var res = {
  172. new_revision: commit.hash,
  173. current_revision: data.revision,
  174. is_up_to_date: (commit.hash === data.revision)
  175. };
  176. return cb(null, res);
  177. });
  178. });
  179. });
  180. };
  181. git.revert = function (args, cb) {
  182. var ret = {};
  183. var command = cliCommand(args.folder, "git reset --hard " + args.revision);
  184. ret.output = '';
  185. ret.output += command + '\n';
  186. ret.success = true;
  187. exec(command, {timeout: TIMEOUT, maxBuffer: MAXBUFFER},
  188. function (err, stdout, stderr) {
  189. ret.output += stdout;
  190. if (err !== null || stderr.substring(0, 6) === 'fatal:')
  191. ret.success = false;
  192. return cb(null, ret);
  193. });
  194. };
  195. git.update = function (folder, cb) {
  196. git.isUpdated(folder, function (err, data) {
  197. if (err) {
  198. return cb(err);
  199. }
  200. var res = {};
  201. if (data.is_up_to_date === true) {
  202. res.success = false;
  203. res.current_revision = data.new_revision;
  204. return cb(null, res);
  205. }
  206. else {
  207. git.revert({folder: folder, revision: data.new_revision},
  208. function (err, dt) {
  209. if (err) {
  210. return cb(err);
  211. }
  212. res.output = dt.output;
  213. res.success = dt.success;
  214. res.current_revision = (dt.success) ? data.new_revision : data.current_revision;
  215. return cb(null, res);
  216. });
  217. }
  218. });
  219. };
  220. git.prev = function (folder, cb) {
  221. waterfall([
  222. git.getCommitInfo.bind(null, folder, {}),
  223. git.getBranch.bind(null, folder),
  224. git.getRemote.bind(null, folder),
  225. git.isCurrentBranchOnRemote.bind(null, folder),
  226. git.getPrevNext.bind(null, folder),
  227. ], function (err, data) {
  228. if (err) {
  229. return cb(err);
  230. }
  231. var res = {};
  232. if (data.prev_rev !== null) {
  233. git.revert({folder: folder, revision: data.prev_rev}, function (err, meta) {
  234. if (err) {
  235. return cb(err);
  236. }
  237. res.output = meta.output;
  238. res.success = meta.success;
  239. res.current_revision = (res.success) ? data.prev_rev : data.revision;
  240. return cb(null, res);
  241. });
  242. }
  243. else {
  244. res.success = false;
  245. res.current_revision = data.revision;
  246. return cb(null, res);
  247. }
  248. });
  249. };
  250. git.next = function (folder, cb) {
  251. waterfall([
  252. git.getCommitInfo.bind(null, folder, {}),
  253. git.getBranch.bind(null, folder),
  254. git.getRemote.bind(null, folder),
  255. git.isCurrentBranchOnRemote.bind(null, folder),
  256. git.getPrevNext.bind(null, folder),
  257. ], function (err, data) {
  258. if (err) {
  259. return cb(err);
  260. }
  261. var res = {};
  262. if (data.next_rev !== null) {
  263. git.revert({folder: folder, revision: data.next_rev}, function (err, meta) {
  264. if (err) {
  265. return cb(err);
  266. }
  267. res.output = meta.output;
  268. res.success = meta.success;
  269. res.current_revision = (res.success) ? data.next_rev : data.revision;
  270. return cb(null, res);
  271. });
  272. }
  273. else {
  274. res.success = false;
  275. res.current_revision = data.revision;
  276. return cb(null, res);
  277. }
  278. });
  279. };
  280. module.exports = git;