"use strict"; var gitUp = require("git-up"); /** * gitUrlParse * Parses a Git url. * * @name gitUrlParse * @function * @param {String} url The Git url to parse. * @return {GitUrl} The `GitUrl` object containing: * * - `protocols` (Array): An array with the url protocols (usually it has one element). * - `port` (null|Number): The domain port. * - `resource` (String): The url domain (including subdomains). * - `user` (String): The authentication user (usually for ssh urls). * - `pathname` (String): The url pathname. * - `hash` (String): The url hash. * - `search` (String): The url querystring value. * - `href` (String): The input url. * - `protocol` (String): The git url protocol. * - `token` (String): The oauth token (could appear in the https urls). * - `source` (String): The Git provider (e.g. `"github.com"`). * - `owner` (String): The repository owner. * - `name` (String): The repository name. * - `ref` (String): The repository ref (e.g., "master" or "dev"). * - `filepath` (String): A filepath relative to the repository root. * - `filepathtype` (String): The type of filepath in the url ("blob" or "tree"). * - `full_name` (String): The owner and name values in the `owner/name` format. * - `toString` (Function): A function to stringify the parsed url into another url type. * - `organization` (String): The organization the owner belongs to. This is CloudForge specific. * - `git_suffix` (Boolean): Whether to add the `.git` suffix or not. * */ function gitUrlParse(url) { if (typeof url !== "string") { throw new Error("The url must be a string."); } var urlInfo = gitUp(url), sourceParts = urlInfo.resource.split("."), splits = null; urlInfo.toString = function (type) { return gitUrlParse.stringify(this, type); }; urlInfo.source = sourceParts.length > 2 ? sourceParts.slice(1 - sourceParts.length).join(".") : urlInfo.source = urlInfo.resource; // Note: Some hosting services (e.g. Visual Studio Team Services) allow whitespace characters // in the repository and owner names so we decode the URL pieces to get the correct result urlInfo.git_suffix = /\.git$/.test(urlInfo.pathname); urlInfo.name = decodeURIComponent(urlInfo.pathname.replace(/^\//, '').replace(/\.git$/, "")); urlInfo.owner = decodeURIComponent(urlInfo.user); switch (urlInfo.source) { case "git.cloudforge.com": urlInfo.owner = urlInfo.user; urlInfo.organization = sourceParts[0]; urlInfo.source = "cloudforge.com"; break; case "visualstudio.com": // Handle VSTS SSH URLs if (urlInfo.resource === 'vs-ssh.visualstudio.com') { splits = urlInfo.name.split("/"); if (splits.length === 4) { urlInfo.organization = splits[1]; urlInfo.owner = splits[2]; urlInfo.name = splits[3]; urlInfo.full_name = splits[2] + '/' + splits[3]; } break; } else { splits = urlInfo.name.split("/"); if (splits.length === 2) { urlInfo.owner = splits[1]; urlInfo.name = splits[1]; urlInfo.full_name = '_git/' + urlInfo.name; } else if (splits.length === 3) { urlInfo.name = splits[2]; if (splits[0] === 'DefaultCollection') { urlInfo.owner = splits[2]; urlInfo.organization = splits[0]; urlInfo.full_name = urlInfo.organization + '/_git/' + urlInfo.name; } else { urlInfo.owner = splits[0]; urlInfo.full_name = urlInfo.owner + '/_git/' + urlInfo.name; } } else if (splits.length === 4) { urlInfo.organization = splits[0]; urlInfo.owner = splits[1]; urlInfo.name = splits[3]; urlInfo.full_name = urlInfo.organization + '/' + urlInfo.owner + '/_git/' + urlInfo.name; } break; } // Azure DevOps (formerly Visual Studio Team Services) case "dev.azure.com": case "azure.com": if (urlInfo.resource === 'ssh.dev.azure.com') { splits = urlInfo.name.split("/"); if (splits.length === 4) { urlInfo.organization = splits[1]; urlInfo.owner = splits[2]; urlInfo.name = splits[3]; } break; } else { splits = urlInfo.name.split("/"); if (splits.length === 5) { urlInfo.organization = splits[0]; urlInfo.owner = splits[1]; urlInfo.name = splits[4]; urlInfo.full_name = '_git/' + urlInfo.name; } else if (splits.length === 3) { urlInfo.name = splits[2]; if (splits[0] === 'DefaultCollection') { urlInfo.owner = splits[2]; urlInfo.organization = splits[0]; urlInfo.full_name = urlInfo.organization + '/_git/' + urlInfo.name; } else { urlInfo.owner = splits[0]; urlInfo.full_name = urlInfo.owner + '/_git/' + urlInfo.name; } } else if (splits.length === 4) { urlInfo.organization = splits[0]; urlInfo.owner = splits[1]; urlInfo.name = splits[3]; urlInfo.full_name = urlInfo.organization + '/' + urlInfo.owner + '/_git/' + urlInfo.name; } if (urlInfo.query && urlInfo.query['path']) { urlInfo.filepath = urlInfo.query['path'].replace(/^\/+/g, ''); // Strip leading slash (/) } if (urlInfo.query && urlInfo.query['version']) { // version=GB urlInfo.ref = urlInfo.query['version'].replace(/^GB/, ''); // remove GB } break; } default: splits = urlInfo.name.split("/"); var nameIndex = splits.length - 1; if (splits.length >= 2) { var dashIndex = splits.indexOf("-", 2); var blobIndex = splits.indexOf("blob", 2); var treeIndex = splits.indexOf("tree", 2); var commitIndex = splits.indexOf("commit", 2); var srcIndex = splits.indexOf("src", 2); var rawIndex = splits.indexOf("raw", 2); 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; urlInfo.owner = splits.slice(0, nameIndex).join('/'); urlInfo.name = splits[nameIndex]; if (commitIndex) { urlInfo.commit = splits[nameIndex + 2]; } } urlInfo.ref = ""; urlInfo.filepathtype = ""; urlInfo.filepath = ""; var offsetNameIndex = splits.length > nameIndex && splits[nameIndex + 1] === "-" ? nameIndex + 1 : nameIndex; if (splits.length > offsetNameIndex + 2 && ["raw", "src", "blob", "tree"].indexOf(splits[offsetNameIndex + 1]) >= 0) { urlInfo.filepathtype = splits[offsetNameIndex + 1]; urlInfo.ref = splits[offsetNameIndex + 2]; if (splits.length > offsetNameIndex + 3) { urlInfo.filepath = splits.slice(offsetNameIndex + 3).join('/'); } } urlInfo.organization = urlInfo.owner; break; } if (!urlInfo.full_name) { urlInfo.full_name = urlInfo.owner; if (urlInfo.name) { urlInfo.full_name && (urlInfo.full_name += "/"); urlInfo.full_name += urlInfo.name; } } // Bitbucket Server if (urlInfo.owner.startsWith("scm/")) { urlInfo.source = "bitbucket-server"; urlInfo.owner = urlInfo.owner.replace("scm/", ""); urlInfo.organization = urlInfo.owner; urlInfo.full_name = urlInfo.owner + "/" + urlInfo.name; } var bitbucket = /(projects|users)\/(.*?)\/repos\/(.*?)((\/.*$)|$)/; var matches = bitbucket.exec(urlInfo.pathname); if (matches != null) { urlInfo.source = "bitbucket-server"; if (matches[1] === "users") { urlInfo.owner = "~" + matches[2]; } else { urlInfo.owner = matches[2]; } urlInfo.organization = urlInfo.owner; urlInfo.name = matches[3]; splits = matches[4].split("/"); if (splits.length > 1) { if (["raw", "browse"].indexOf(splits[1]) >= 0) { urlInfo.filepathtype = splits[1]; if (splits.length > 2) { urlInfo.filepath = splits.slice(2).join('/'); } } else if (splits[1] === "commits" && splits.length > 2) { urlInfo.commit = splits[2]; } } urlInfo.full_name = urlInfo.owner + "/" + urlInfo.name; if (urlInfo.query.at) { urlInfo.ref = urlInfo.query.at; } else { urlInfo.ref = ""; } } return urlInfo; } /** * stringify * Stringifies a `GitUrl` object. * * @name stringify * @function * @param {GitUrl} obj The parsed Git url object. * @param {String} type The type of the stringified url (default `obj.protocol`). * @return {String} The stringified url. */ gitUrlParse.stringify = function (obj, type) { type = type || (obj.protocols && obj.protocols.length ? obj.protocols.join('+') : obj.protocol); var port = obj.port ? ":" + obj.port : ''; var user = obj.user || 'git'; var maybeGitSuffix = obj.git_suffix ? ".git" : ""; switch (type) { case "ssh": if (port) return "ssh://" + user + "@" + obj.resource + port + "/" + obj.full_name + maybeGitSuffix;else return user + "@" + obj.resource + ":" + obj.full_name + maybeGitSuffix; case "git+ssh": case "ssh+git": case "ftp": case "ftps": return type + "://" + user + "@" + obj.resource + port + "/" + obj.full_name + maybeGitSuffix; case "http": case "https": var auth = obj.token ? buildToken(obj) : obj.user && (obj.protocols.includes('http') || obj.protocols.includes('https')) ? obj.user + "@" : ""; return type + "://" + auth + obj.resource + port + "/" + buildPath(obj) + maybeGitSuffix; default: return obj.href; } }; /*! * buildToken * Builds OAuth token prefix (helper function) * * @name buildToken * @function * @param {GitUrl} obj The parsed Git url object. * @return {String} token prefix */ function buildToken(obj) { switch (obj.source) { case "bitbucket.org": return "x-token-auth:" + obj.token + "@"; default: return obj.token + "@"; } } function buildPath(obj) { switch (obj.source) { case "bitbucket-server": return "scm/" + obj.full_name; default: return "" + obj.full_name; } } module.exports = gitUrlParse;