index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. "use strict";
  2. var gitUp = require("git-up");
  3. /**
  4. * gitUrlParse
  5. * Parses a Git url.
  6. *
  7. * @name gitUrlParse
  8. * @function
  9. * @param {String} url The Git url to parse.
  10. * @return {GitUrl} The `GitUrl` object containing:
  11. *
  12. * - `protocols` (Array): An array with the url protocols (usually it has one element).
  13. * - `port` (null|Number): The domain port.
  14. * - `resource` (String): The url domain (including subdomains).
  15. * - `user` (String): The authentication user (usually for ssh urls).
  16. * - `pathname` (String): The url pathname.
  17. * - `hash` (String): The url hash.
  18. * - `search` (String): The url querystring value.
  19. * - `href` (String): The input url.
  20. * - `protocol` (String): The git url protocol.
  21. * - `token` (String): The oauth token (could appear in the https urls).
  22. * - `source` (String): The Git provider (e.g. `"github.com"`).
  23. * - `owner` (String): The repository owner.
  24. * - `name` (String): The repository name.
  25. * - `ref` (String): The repository ref (e.g., "master" or "dev").
  26. * - `filepath` (String): A filepath relative to the repository root.
  27. * - `filepathtype` (String): The type of filepath in the url ("blob" or "tree").
  28. * - `full_name` (String): The owner and name values in the `owner/name` format.
  29. * - `toString` (Function): A function to stringify the parsed url into another url type.
  30. * - `organization` (String): The organization the owner belongs to. This is CloudForge specific.
  31. * - `git_suffix` (Boolean): Whether to add the `.git` suffix or not.
  32. *
  33. */
  34. function gitUrlParse(url) {
  35. if (typeof url !== "string") {
  36. throw new Error("The url must be a string.");
  37. }
  38. var shorthandRe = /^([a-z\d-]{1,39})\/([-\.\w]{1,100})$/i;
  39. if (shorthandRe.test(url)) {
  40. url = "https://github.com/" + url;
  41. }
  42. var urlInfo = gitUp(url),
  43. sourceParts = urlInfo.resource.split("."),
  44. splits = null;
  45. urlInfo.toString = function (type) {
  46. return gitUrlParse.stringify(this, type);
  47. };
  48. urlInfo.source = sourceParts.length > 2 ? sourceParts.slice(1 - sourceParts.length).join(".") : urlInfo.source = urlInfo.resource;
  49. // Note: Some hosting services (e.g. Visual Studio Team Services) allow whitespace characters
  50. // in the repository and owner names so we decode the URL pieces to get the correct result
  51. urlInfo.git_suffix = /\.git$/.test(urlInfo.pathname);
  52. urlInfo.name = decodeURIComponent((urlInfo.pathname || urlInfo.href).replace(/(^\/)|(\/$)/g, '').replace(/\.git$/, ""));
  53. urlInfo.owner = decodeURIComponent(urlInfo.user);
  54. switch (urlInfo.source) {
  55. case "git.cloudforge.com":
  56. urlInfo.owner = urlInfo.user;
  57. urlInfo.organization = sourceParts[0];
  58. urlInfo.source = "cloudforge.com";
  59. break;
  60. case "visualstudio.com":
  61. // Handle VSTS SSH URLs
  62. if (urlInfo.resource === 'vs-ssh.visualstudio.com') {
  63. splits = urlInfo.name.split("/");
  64. if (splits.length === 4) {
  65. urlInfo.organization = splits[1];
  66. urlInfo.owner = splits[2];
  67. urlInfo.name = splits[3];
  68. urlInfo.full_name = splits[2] + '/' + splits[3];
  69. }
  70. break;
  71. } else {
  72. splits = urlInfo.name.split("/");
  73. if (splits.length === 2) {
  74. urlInfo.owner = splits[1];
  75. urlInfo.name = splits[1];
  76. urlInfo.full_name = '_git/' + urlInfo.name;
  77. } else if (splits.length === 3) {
  78. urlInfo.name = splits[2];
  79. if (splits[0] === 'DefaultCollection') {
  80. urlInfo.owner = splits[2];
  81. urlInfo.organization = splits[0];
  82. urlInfo.full_name = urlInfo.organization + '/_git/' + urlInfo.name;
  83. } else {
  84. urlInfo.owner = splits[0];
  85. urlInfo.full_name = urlInfo.owner + '/_git/' + urlInfo.name;
  86. }
  87. } else if (splits.length === 4) {
  88. urlInfo.organization = splits[0];
  89. urlInfo.owner = splits[1];
  90. urlInfo.name = splits[3];
  91. urlInfo.full_name = urlInfo.organization + '/' + urlInfo.owner + '/_git/' + urlInfo.name;
  92. }
  93. break;
  94. }
  95. // Azure DevOps (formerly Visual Studio Team Services)
  96. case "dev.azure.com":
  97. case "azure.com":
  98. if (urlInfo.resource === 'ssh.dev.azure.com') {
  99. splits = urlInfo.name.split("/");
  100. if (splits.length === 4) {
  101. urlInfo.organization = splits[1];
  102. urlInfo.owner = splits[2];
  103. urlInfo.name = splits[3];
  104. }
  105. break;
  106. } else {
  107. splits = urlInfo.name.split("/");
  108. if (splits.length === 5) {
  109. urlInfo.organization = splits[0];
  110. urlInfo.owner = splits[1];
  111. urlInfo.name = splits[4];
  112. urlInfo.full_name = '_git/' + urlInfo.name;
  113. } else if (splits.length === 3) {
  114. urlInfo.name = splits[2];
  115. if (splits[0] === 'DefaultCollection') {
  116. urlInfo.owner = splits[2];
  117. urlInfo.organization = splits[0];
  118. urlInfo.full_name = urlInfo.organization + '/_git/' + urlInfo.name;
  119. } else {
  120. urlInfo.owner = splits[0];
  121. urlInfo.full_name = urlInfo.owner + '/_git/' + urlInfo.name;
  122. }
  123. } else if (splits.length === 4) {
  124. urlInfo.organization = splits[0];
  125. urlInfo.owner = splits[1];
  126. urlInfo.name = splits[3];
  127. urlInfo.full_name = urlInfo.organization + '/' + urlInfo.owner + '/_git/' + urlInfo.name;
  128. }
  129. if (urlInfo.query && urlInfo.query['path']) {
  130. urlInfo.filepath = urlInfo.query['path'].replace(/^\/+/g, ''); // Strip leading slash (/)
  131. }
  132. if (urlInfo.query && urlInfo.query['version']) {
  133. // version=GB<branch>
  134. urlInfo.ref = urlInfo.query['version'].replace(/^GB/, ''); // remove GB
  135. }
  136. break;
  137. }
  138. default:
  139. splits = urlInfo.name.split("/");
  140. var nameIndex = splits.length - 1;
  141. if (splits.length >= 2) {
  142. var dashIndex = splits.indexOf("-", 2);
  143. var blobIndex = splits.indexOf("blob", 2);
  144. var treeIndex = splits.indexOf("tree", 2);
  145. var commitIndex = splits.indexOf("commit", 2);
  146. var srcIndex = splits.indexOf("src", 2);
  147. var rawIndex = splits.indexOf("raw", 2);
  148. var editIndex = splits.indexOf("edit", 2);
  149. nameIndex = dashIndex > 0 ? dashIndex - 1 : blobIndex > 0 ? blobIndex - 1 : treeIndex > 0 ? treeIndex - 1 : commitIndex > 0 ? commitIndex - 1 : srcIndex > 0 ? srcIndex - 1 : rawIndex > 0 ? rawIndex - 1 : editIndex > 0 ? editIndex - 1 : nameIndex;
  150. urlInfo.owner = splits.slice(0, nameIndex).join('/');
  151. urlInfo.name = splits[nameIndex];
  152. if (commitIndex) {
  153. urlInfo.commit = splits[nameIndex + 2];
  154. }
  155. }
  156. urlInfo.ref = "";
  157. urlInfo.filepathtype = "";
  158. urlInfo.filepath = "";
  159. var offsetNameIndex = splits.length > nameIndex && splits[nameIndex + 1] === "-" ? nameIndex + 1 : nameIndex;
  160. if (splits.length > offsetNameIndex + 2 && ["raw", "src", "blob", "tree", "edit"].indexOf(splits[offsetNameIndex + 1]) >= 0) {
  161. urlInfo.filepathtype = splits[offsetNameIndex + 1];
  162. urlInfo.ref = splits[offsetNameIndex + 2];
  163. if (splits.length > offsetNameIndex + 3) {
  164. urlInfo.filepath = splits.slice(offsetNameIndex + 3).join('/');
  165. }
  166. }
  167. urlInfo.organization = urlInfo.owner;
  168. break;
  169. }
  170. if (!urlInfo.full_name) {
  171. urlInfo.full_name = urlInfo.owner;
  172. if (urlInfo.name) {
  173. urlInfo.full_name && (urlInfo.full_name += "/");
  174. urlInfo.full_name += urlInfo.name;
  175. }
  176. }
  177. // Bitbucket Server
  178. if (urlInfo.owner.startsWith("scm/")) {
  179. urlInfo.source = "bitbucket-server";
  180. urlInfo.owner = urlInfo.owner.replace("scm/", "");
  181. urlInfo.organization = urlInfo.owner;
  182. urlInfo.full_name = urlInfo.owner + "/" + urlInfo.name;
  183. }
  184. var bitbucket = /(projects|users)\/(.*?)\/repos\/(.*?)((\/.*$)|$)/;
  185. var matches = bitbucket.exec(urlInfo.pathname);
  186. if (matches != null) {
  187. urlInfo.source = "bitbucket-server";
  188. if (matches[1] === "users") {
  189. urlInfo.owner = "~" + matches[2];
  190. } else {
  191. urlInfo.owner = matches[2];
  192. }
  193. urlInfo.organization = urlInfo.owner;
  194. urlInfo.name = matches[3];
  195. splits = matches[4].split("/");
  196. if (splits.length > 1) {
  197. if (["raw", "browse"].indexOf(splits[1]) >= 0) {
  198. urlInfo.filepathtype = splits[1];
  199. if (splits.length > 2) {
  200. urlInfo.filepath = splits.slice(2).join('/');
  201. }
  202. } else if (splits[1] === "commits" && splits.length > 2) {
  203. urlInfo.commit = splits[2];
  204. }
  205. }
  206. urlInfo.full_name = urlInfo.owner + "/" + urlInfo.name;
  207. if (urlInfo.query.at) {
  208. urlInfo.ref = urlInfo.query.at;
  209. } else {
  210. urlInfo.ref = "";
  211. }
  212. }
  213. return urlInfo;
  214. }
  215. /**
  216. * stringify
  217. * Stringifies a `GitUrl` object.
  218. *
  219. * @name stringify
  220. * @function
  221. * @param {GitUrl} obj The parsed Git url object.
  222. * @param {String} type The type of the stringified url (default `obj.protocol`).
  223. * @return {String} The stringified url.
  224. */
  225. gitUrlParse.stringify = function (obj, type) {
  226. type = type || (obj.protocols && obj.protocols.length ? obj.protocols.join('+') : obj.protocol);
  227. var port = obj.port ? ":" + obj.port : '';
  228. var user = obj.user || 'git';
  229. var maybeGitSuffix = obj.git_suffix ? ".git" : "";
  230. switch (type) {
  231. case "ssh":
  232. if (port) return "ssh://" + user + "@" + obj.resource + port + "/" + obj.full_name + maybeGitSuffix;else return user + "@" + obj.resource + ":" + obj.full_name + maybeGitSuffix;
  233. case "git+ssh":
  234. case "ssh+git":
  235. case "ftp":
  236. case "ftps":
  237. return type + "://" + user + "@" + obj.resource + port + "/" + obj.full_name + maybeGitSuffix;
  238. case "http":
  239. case "https":
  240. var auth = obj.token ? buildToken(obj) : obj.user && (obj.protocols.includes('http') || obj.protocols.includes('https')) ? obj.user + "@" : "";
  241. return type + "://" + auth + obj.resource + port + "/" + buildPath(obj) + maybeGitSuffix;
  242. default:
  243. return obj.href;
  244. }
  245. };
  246. /*!
  247. * buildToken
  248. * Builds OAuth token prefix (helper function)
  249. *
  250. * @name buildToken
  251. * @function
  252. * @param {GitUrl} obj The parsed Git url object.
  253. * @return {String} token prefix
  254. */
  255. function buildToken(obj) {
  256. switch (obj.source) {
  257. case "bitbucket.org":
  258. return "x-token-auth:" + obj.token + "@";
  259. default:
  260. return obj.token + "@";
  261. }
  262. }
  263. function buildPath(obj) {
  264. switch (obj.source) {
  265. case "bitbucket-server":
  266. return "scm/" + obj.full_name;
  267. default:
  268. return "" + obj.full_name;
  269. }
  270. }
  271. module.exports = gitUrlParse;