index.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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 urlInfo = gitUp(url),
  39. sourceParts = urlInfo.resource.split("."),
  40. splits = null;
  41. urlInfo.toString = function (type) {
  42. return gitUrlParse.stringify(this, type);
  43. };
  44. urlInfo.source = sourceParts.length > 2 ? sourceParts.slice(1 - sourceParts.length).join(".") : urlInfo.source = urlInfo.resource;
  45. // Note: Some hosting services (e.g. Visual Studio Team Services) allow whitespace characters
  46. // in the repository and owner names so we decode the URL pieces to get the correct result
  47. urlInfo.git_suffix = /\.git$/.test(urlInfo.pathname);
  48. urlInfo.name = decodeURIComponent(urlInfo.pathname.replace(/^\//, '').replace(/\.git$/, ""));
  49. urlInfo.owner = decodeURIComponent(urlInfo.user);
  50. switch (urlInfo.source) {
  51. case "git.cloudforge.com":
  52. urlInfo.owner = urlInfo.user;
  53. urlInfo.organization = sourceParts[0];
  54. urlInfo.source = "cloudforge.com";
  55. break;
  56. case "visualstudio.com":
  57. // Handle VSTS SSH URLs
  58. if (urlInfo.resource === 'vs-ssh.visualstudio.com') {
  59. splits = urlInfo.name.split("/");
  60. if (splits.length === 4) {
  61. urlInfo.organization = splits[1];
  62. urlInfo.owner = splits[2];
  63. urlInfo.name = splits[3];
  64. urlInfo.full_name = splits[2] + '/' + splits[3];
  65. }
  66. break;
  67. } else {
  68. splits = urlInfo.name.split("/");
  69. if (splits.length === 2) {
  70. urlInfo.owner = splits[1];
  71. urlInfo.name = splits[1];
  72. urlInfo.full_name = '_git/' + urlInfo.name;
  73. } else if (splits.length === 3) {
  74. urlInfo.name = splits[2];
  75. if (splits[0] === 'DefaultCollection') {
  76. urlInfo.owner = splits[2];
  77. urlInfo.organization = splits[0];
  78. urlInfo.full_name = urlInfo.organization + '/_git/' + urlInfo.name;
  79. } else {
  80. urlInfo.owner = splits[0];
  81. urlInfo.full_name = urlInfo.owner + '/_git/' + urlInfo.name;
  82. }
  83. } else if (splits.length === 4) {
  84. urlInfo.organization = splits[0];
  85. urlInfo.owner = splits[1];
  86. urlInfo.name = splits[3];
  87. urlInfo.full_name = urlInfo.organization + '/' + urlInfo.owner + '/_git/' + urlInfo.name;
  88. }
  89. break;
  90. }
  91. // Azure DevOps (formerly Visual Studio Team Services)
  92. case "dev.azure.com":
  93. case "azure.com":
  94. if (urlInfo.resource === 'ssh.dev.azure.com') {
  95. splits = urlInfo.name.split("/");
  96. if (splits.length === 4) {
  97. urlInfo.organization = splits[1];
  98. urlInfo.owner = splits[2];
  99. urlInfo.name = splits[3];
  100. }
  101. break;
  102. } else {
  103. splits = urlInfo.name.split("/");
  104. if (splits.length === 5) {
  105. urlInfo.organization = splits[0];
  106. urlInfo.owner = splits[1];
  107. urlInfo.name = splits[4];
  108. urlInfo.full_name = '_git/' + urlInfo.name;
  109. } else if (splits.length === 3) {
  110. urlInfo.name = splits[2];
  111. if (splits[0] === 'DefaultCollection') {
  112. urlInfo.owner = splits[2];
  113. urlInfo.organization = splits[0];
  114. urlInfo.full_name = urlInfo.organization + '/_git/' + urlInfo.name;
  115. } else {
  116. urlInfo.owner = splits[0];
  117. urlInfo.full_name = urlInfo.owner + '/_git/' + urlInfo.name;
  118. }
  119. } else if (splits.length === 4) {
  120. urlInfo.organization = splits[0];
  121. urlInfo.owner = splits[1];
  122. urlInfo.name = splits[3];
  123. urlInfo.full_name = urlInfo.organization + '/' + urlInfo.owner + '/_git/' + urlInfo.name;
  124. }
  125. if (urlInfo.query && urlInfo.query['path']) {
  126. urlInfo.filepath = urlInfo.query['path'].replace(/^\/+/g, ''); // Strip leading slash (/)
  127. }
  128. if (urlInfo.query && urlInfo.query['version']) {
  129. // version=GB<branch>
  130. urlInfo.ref = urlInfo.query['version'].replace(/^GB/, ''); // remove GB
  131. }
  132. break;
  133. }
  134. default:
  135. splits = urlInfo.name.split("/");
  136. var nameIndex = splits.length - 1;
  137. if (splits.length >= 2) {
  138. var dashIndex = splits.indexOf("-", 2);
  139. var blobIndex = splits.indexOf("blob", 2);
  140. var treeIndex = splits.indexOf("tree", 2);
  141. var commitIndex = splits.indexOf("commit", 2);
  142. var srcIndex = splits.indexOf("src", 2);
  143. var rawIndex = splits.indexOf("raw", 2);
  144. 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 : nameIndex;
  145. urlInfo.owner = splits.slice(0, nameIndex).join('/');
  146. urlInfo.name = splits[nameIndex];
  147. if (commitIndex) {
  148. urlInfo.commit = splits[nameIndex + 2];
  149. }
  150. }
  151. urlInfo.ref = "";
  152. urlInfo.filepathtype = "";
  153. urlInfo.filepath = "";
  154. var offsetNameIndex = splits.length > nameIndex && splits[nameIndex + 1] === "-" ? nameIndex + 1 : nameIndex;
  155. if (splits.length > offsetNameIndex + 2 && ["raw", "src", "blob", "tree"].indexOf(splits[offsetNameIndex + 1]) >= 0) {
  156. urlInfo.filepathtype = splits[offsetNameIndex + 1];
  157. urlInfo.ref = splits[offsetNameIndex + 2];
  158. if (splits.length > offsetNameIndex + 3) {
  159. urlInfo.filepath = splits.slice(offsetNameIndex + 3).join('/');
  160. }
  161. }
  162. urlInfo.organization = urlInfo.owner;
  163. break;
  164. }
  165. if (!urlInfo.full_name) {
  166. urlInfo.full_name = urlInfo.owner;
  167. if (urlInfo.name) {
  168. urlInfo.full_name && (urlInfo.full_name += "/");
  169. urlInfo.full_name += urlInfo.name;
  170. }
  171. }
  172. // Bitbucket Server
  173. if (urlInfo.owner.startsWith("scm/")) {
  174. urlInfo.source = "bitbucket-server";
  175. urlInfo.owner = urlInfo.owner.replace("scm/", "");
  176. urlInfo.organization = urlInfo.owner;
  177. urlInfo.full_name = urlInfo.owner + "/" + urlInfo.name;
  178. }
  179. var bitbucket = /(projects|users)\/(.*?)\/repos\/(.*?)((\/.*$)|$)/;
  180. var matches = bitbucket.exec(urlInfo.pathname);
  181. if (matches != null) {
  182. urlInfo.source = "bitbucket-server";
  183. if (matches[1] === "users") {
  184. urlInfo.owner = "~" + matches[2];
  185. } else {
  186. urlInfo.owner = matches[2];
  187. }
  188. urlInfo.organization = urlInfo.owner;
  189. urlInfo.name = matches[3];
  190. splits = matches[4].split("/");
  191. if (splits.length > 1) {
  192. if (["raw", "browse"].indexOf(splits[1]) >= 0) {
  193. urlInfo.filepathtype = splits[1];
  194. if (splits.length > 2) {
  195. urlInfo.filepath = splits.slice(2).join('/');
  196. }
  197. } else if (splits[1] === "commits" && splits.length > 2) {
  198. urlInfo.commit = splits[2];
  199. }
  200. }
  201. urlInfo.full_name = urlInfo.owner + "/" + urlInfo.name;
  202. if (urlInfo.query.at) {
  203. urlInfo.ref = urlInfo.query.at;
  204. } else {
  205. urlInfo.ref = "";
  206. }
  207. }
  208. return urlInfo;
  209. }
  210. /**
  211. * stringify
  212. * Stringifies a `GitUrl` object.
  213. *
  214. * @name stringify
  215. * @function
  216. * @param {GitUrl} obj The parsed Git url object.
  217. * @param {String} type The type of the stringified url (default `obj.protocol`).
  218. * @return {String} The stringified url.
  219. */
  220. gitUrlParse.stringify = function (obj, type) {
  221. type = type || (obj.protocols && obj.protocols.length ? obj.protocols.join('+') : obj.protocol);
  222. var port = obj.port ? ":" + obj.port : '';
  223. var user = obj.user || 'git';
  224. var maybeGitSuffix = obj.git_suffix ? ".git" : "";
  225. switch (type) {
  226. case "ssh":
  227. if (port) return "ssh://" + user + "@" + obj.resource + port + "/" + obj.full_name + maybeGitSuffix;else return user + "@" + obj.resource + ":" + obj.full_name + maybeGitSuffix;
  228. case "git+ssh":
  229. case "ssh+git":
  230. case "ftp":
  231. case "ftps":
  232. return type + "://" + user + "@" + obj.resource + port + "/" + obj.full_name + maybeGitSuffix;
  233. case "http":
  234. case "https":
  235. var auth = obj.token ? buildToken(obj) : obj.user && (obj.protocols.includes('http') || obj.protocols.includes('https')) ? obj.user + "@" : "";
  236. return type + "://" + auth + obj.resource + port + "/" + buildPath(obj) + maybeGitSuffix;
  237. default:
  238. return obj.href;
  239. }
  240. };
  241. /*!
  242. * buildToken
  243. * Builds OAuth token prefix (helper function)
  244. *
  245. * @name buildToken
  246. * @function
  247. * @param {GitUrl} obj The parsed Git url object.
  248. * @return {String} token prefix
  249. */
  250. function buildToken(obj) {
  251. switch (obj.source) {
  252. case "bitbucket.org":
  253. return "x-token-auth:" + obj.token + "@";
  254. default:
  255. return obj.token + "@";
  256. }
  257. }
  258. function buildPath(obj) {
  259. switch (obj.source) {
  260. case "bitbucket-server":
  261. return "scm/" + obj.full_name;
  262. default:
  263. return "" + obj.full_name;
  264. }
  265. }
  266. module.exports = gitUrlParse;