index.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. 'use strict'
  2. var url = require('url')
  3. var gitHosts = require('./git-host-info.js')
  4. var GitHost = module.exports = require('./git-host.js')
  5. var protocolToRepresentationMap = {
  6. 'git+ssh': 'sshurl',
  7. 'git+https': 'https',
  8. 'ssh': 'sshurl',
  9. 'git': 'git'
  10. }
  11. function protocolToRepresentation (protocol) {
  12. if (protocol.substr(-1) === ':') protocol = protocol.slice(0, -1)
  13. return protocolToRepresentationMap[protocol] || protocol
  14. }
  15. var authProtocols = {
  16. 'git:': true,
  17. 'https:': true,
  18. 'git+https:': true,
  19. 'http:': true,
  20. 'git+http:': true
  21. }
  22. module.exports.fromUrl = function (giturl) {
  23. if (giturl == null || giturl === '') return
  24. var url = fixupUnqualifiedGist(
  25. isGitHubShorthand(giturl) ? 'github:' + giturl : giturl
  26. )
  27. var parsed = parseGitUrl(url)
  28. var matches = Object.keys(gitHosts).map(function (gitHostName) {
  29. var gitHostInfo = gitHosts[gitHostName]
  30. var auth = null
  31. if (parsed.auth && authProtocols[parsed.protocol]) {
  32. auth = decodeURIComponent(parsed.auth)
  33. }
  34. var committish = parsed.hash ? decodeURIComponent(parsed.hash.substr(1)) : null
  35. var user = null
  36. var project = null
  37. var defaultRepresentation = null
  38. if (parsed.protocol === gitHostName + ':') {
  39. user = decodeURIComponent(parsed.host)
  40. project = parsed.path && decodeURIComponent(parsed.path.replace(/^[/](.*?)(?:[.]git)?$/, '$1'))
  41. defaultRepresentation = 'shortcut'
  42. } else {
  43. if (parsed.host !== gitHostInfo.domain) return
  44. if (!gitHostInfo.protocols_re.test(parsed.protocol)) return
  45. var pathmatch = gitHostInfo.pathmatch
  46. var matched = parsed.path.match(pathmatch)
  47. if (!matched) return
  48. if (matched[1] != null) user = decodeURIComponent(matched[1])
  49. if (matched[2] != null) project = decodeURIComponent(matched[2])
  50. defaultRepresentation = protocolToRepresentation(parsed.protocol)
  51. }
  52. return new GitHost(gitHostName, user, auth, project, committish, defaultRepresentation)
  53. }).filter(function (gitHostInfo) { return gitHostInfo })
  54. if (matches.length !== 1) return
  55. return matches[0]
  56. }
  57. function isGitHubShorthand (arg) {
  58. // Note: This does not fully test the git ref format.
  59. // See https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
  60. //
  61. // The only way to do this properly would be to shell out to
  62. // git-check-ref-format, and as this is a fast sync function,
  63. // we don't want to do that. Just let git fail if it turns
  64. // out that the commit-ish is invalid.
  65. // GH usernames cannot start with . or -
  66. return /^[^:@%/\s.-][^:@%/\s]*[/][^:@\s/%]+(?:#.*)?$/.test(arg)
  67. }
  68. function fixupUnqualifiedGist (giturl) {
  69. // necessary for round-tripping gists
  70. var parsed = url.parse(giturl)
  71. if (parsed.protocol === 'gist:' && parsed.host && !parsed.path) {
  72. return parsed.protocol + '/' + parsed.host
  73. } else {
  74. return giturl
  75. }
  76. }
  77. function parseGitUrl (giturl) {
  78. if (typeof giturl !== 'string') giturl = '' + giturl
  79. var matched = giturl.match(/^([^@]+)@([^:]+):[/]?((?:[^/]+[/])?[^/]+?)(?:[.]git)?(#.*)?$/)
  80. if (!matched) return url.parse(giturl)
  81. return {
  82. protocol: 'git+ssh:',
  83. slashes: true,
  84. auth: matched[1],
  85. host: matched[2],
  86. port: null,
  87. hostname: matched[2],
  88. hash: matched[4],
  89. search: null,
  90. query: null,
  91. pathname: '/' + matched[3],
  92. path: '/' + matched[3],
  93. href: 'git+ssh://' + matched[1] + '@' + matched[2] +
  94. '/' + matched[3] + (matched[4] || '')
  95. }
  96. }