├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── DOCUMENTATION.md ├── LICENSE ├── README.md ├── example └── index.js ├── lib ├── index.d.ts └── index.js ├── package.json └── test └── index.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ionicabizau 2 | patreon: ionicabizau 3 | open_collective: ionicabizau 4 | custom: https://www.buymeacoffee.com/h96wwchmy -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | *.log 5 | node_modules 6 | *.env 7 | .DS_Store 8 | package-lock.json 9 | .bloggify/* 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 🌟 Contributing 2 | 3 | Want to contribute to this project? Great! Please read these quick steps to streamline the process and avoid unnecessary tasks. ✨ 4 | 5 | ## 💬 Discuss Changes 6 | Start by opening an issue in the repository using the [bug tracker][1]. Describe your proposed contribution or the bug you've found. If relevant, include platform info and screenshots. 🖼️ 7 | 8 | Wait for feedback before proceeding unless the fix is straightforward, like a typo. 📝 9 | 10 | ## 🔧 Fixing Issues 11 | 12 | Fork the project and create a branch for your fix, naming it `some-great-feature` or `some-issue-fix`. Commit changes while following the [code style][2]. If the project has tests, add one. ✅ 13 | 14 | If a `package.json` or `bower.json` exists, add yourself to the `contributors` array; create it if it doesn't. 🙌 15 | 16 | ```json 17 | { 18 | "contributors": [ 19 | "Your Name (http://your.website)" 20 | ] 21 | } 22 | ``` 23 | 24 | ## 📬 Creating a Pull Request 25 | Open a pull request and reference the initial issue (e.g., *fixes #*). Provide a clear title and consider adding visual aids for clarity. 📊 26 | 27 | ## ⏳ Wait for Feedback 28 | Your contributions will be reviewed. If feedback is given, update your branch as needed, and the pull request will auto-update. 🔄 29 | 30 | ## 🎉 Everyone Is Happy! 31 | Your contributions will be merged, and everyone will appreciate your effort! 😄❤️ 32 | 33 | Thanks! 🤩 34 | 35 | [1]: /issues 36 | [2]: https://github.com/IonicaBizau/code-style -------------------------------------------------------------------------------- /DOCUMENTATION.md: -------------------------------------------------------------------------------- 1 | ## Documentation 2 | 3 | You can see below the API reference of this module. 4 | 5 | ### `gitUrlParse(url, refs)` 6 | Parses a Git url. 7 | 8 | #### Params 9 | 10 | - **String** `url`: The Git url to parse. 11 | - **Array** `refs`: An array of strings representing the refs. This is helpful in the context of the URLs that contain branches with slashes. 12 | If user wants to identify the branch, he should pass all branch names 13 | of the project as part of refs parameter 14 | 15 | #### Return 16 | - **GitUrl** The `GitUrl` object containing: 17 | - `protocols` (Array): An array with the url protocols (usually it has one element). 18 | - `port` (String): The domain port. 19 | - `resource` (String): The url domain (including subdomains). 20 | - `user` (String): The authentication user (usually for ssh urls). 21 | - `pathname` (String): The url pathname. 22 | - `hash` (String): The url hash. 23 | - `search` (String): The url querystring value. 24 | - `href` (String): The input url. 25 | - `protocol` (String): The git url protocol. 26 | - `token` (String): The oauth token (could appear in the https urls). 27 | - `source` (String): The Git provider (e.g. `"github.com"`). 28 | - `owner` (String): The repository owner. 29 | - `name` (String): The repository name. 30 | - `ref` (String): The repository ref (e.g., "master" or "dev"). 31 | - `filepath` (String): A filepath relative to the repository root. 32 | - `filepathtype` (String): The type of filepath in the url ("blob" or "tree"). 33 | - `full_name` (String): The owner and name values in the `owner/name` format. 34 | - `toString` (Function): A function to stringify the parsed url into another url type. 35 | - `organization` (String): The organization the owner belongs to. This is CloudForge specific. 36 | - `git_suffix` (Boolean): Whether to add the `.git` suffix or not. 37 | 38 | ### `stringify(obj, type)` 39 | Stringifies a `GitUrl` object. 40 | 41 | #### Params 42 | 43 | - **GitUrl** `obj`: The parsed Git url object. 44 | - **String** `type`: The type of the stringified url (default `obj.protocol`). 45 | 46 | #### Return 47 | - **String** The stringified url. 48 | 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-25 Ionică Bizău (https://ionicabizau.net) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | [![git-url-parse](http://i.imgur.com/HlfMsVf.png)](#) 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | # git-url-parse 23 | 24 | [![Support me on Patreon][badge_patreon]][patreon] [![Buy me a book][badge_amazon]][amazon] [![PayPal][badge_paypal_donate]][paypal-donations] [![Ask me anything](https://img.shields.io/badge/ask%20me-anything-1abc9c.svg)](https://github.com/IonicaBizau/ama) [![Travis](https://img.shields.io/travis/IonicaBizau/git-url-parse.svg)](https://travis-ci.org/IonicaBizau/git-url-parse/) [![Version](https://img.shields.io/npm/v/git-url-parse.svg)](https://www.npmjs.com/package/git-url-parse) [![Downloads](https://img.shields.io/npm/dt/git-url-parse.svg)](https://www.npmjs.com/package/git-url-parse) [![Get help on Codementor](https://cdn.codementor.io/badges/get_help_github.svg)](https://www.codementor.io/@johnnyb?utm_source=github&utm_medium=button&utm_term=johnnyb&utm_campaign=github) 25 | 26 | Buy Me A Coffee 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | > A high level git url parser for common git providers. 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | ## :cloud: Installation 53 | 54 | ```sh 55 | # Using npm 56 | npm install --save git-url-parse 57 | 58 | # Using yarn 59 | yarn add git-url-parse 60 | ``` 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | ## :clipboard: Example 75 | 76 | 77 | 78 | ```js 79 | // Dependencies 80 | const GitUrlParse = require("git-url-parse"); 81 | 82 | console.log(GitUrlParse("git@github.com:IonicaBizau/node-git-url-parse.git")); 83 | // => { 84 | // protocols: [] 85 | // , port: null 86 | // , resource: "github.com" 87 | // , user: "git" 88 | // , pathname: "/IonicaBizau/node-git-url-parse.git" 89 | // , hash: "" 90 | // , search: "" 91 | // , href: "git@github.com:IonicaBizau/node-git-url-parse.git" 92 | // , token: "" 93 | // , protocol: "ssh" 94 | // , toString: [Function] 95 | // , source: "github.com" 96 | // , name: "node-git-url-parse" 97 | // , owner: "IonicaBizau" 98 | // } 99 | 100 | console.log(GitUrlParse("https://github.com/IonicaBizau/node-git-url-parse.git")); 101 | // => { 102 | // protocols: ["https"] 103 | // , port: null 104 | // , resource: "github.com" 105 | // , user: "" 106 | // , pathname: "/IonicaBizau/node-git-url-parse.git" 107 | // , hash: "" 108 | // , search: "" 109 | // , href: "https://github.com/IonicaBizau/node-git-url-parse.git" 110 | // , token: "" 111 | // , protocol: "https" 112 | // , toString: [Function] 113 | // , source: "github.com" 114 | // , name: "node-git-url-parse" 115 | // , owner: "IonicaBizau" 116 | // } 117 | 118 | console.log(GitUrlParse("https://github.com/IonicaBizau/git-url-parse/blob/master/test/index.js")); 119 | // { protocols: [ 'https' ], 120 | // protocol: 'https', 121 | // port: null, 122 | // resource: 'github.com', 123 | // user: '', 124 | // pathname: '/IonicaBizau/git-url-parse/blob/master/test/index.js', 125 | // hash: '', 126 | // search: '', 127 | // href: 'https://github.com/IonicaBizau/git-url-parse/blob/master/test/index.js', 128 | // token: '', 129 | // toString: [Function], 130 | // source: 'github.com', 131 | // name: 'git-url-parse', 132 | // owner: 'IonicaBizau', 133 | // organization: '', 134 | // ref: 'master', 135 | // filepathtype: 'blob', 136 | // filepath: 'test/index.js', 137 | // full_name: 'IonicaBizau/git-url-parse' } 138 | 139 | console.log(GitUrlParse("https://github.com/IonicaBizau/node-git-url-parse.git").toString("ssh")); 140 | // => "git@github.com:IonicaBizau/node-git-url-parse.git" 141 | 142 | console.log(GitUrlParse("git@github.com:IonicaBizau/node-git-url-parse.git").toString("https")); 143 | // => "https://github.com/IonicaBizau/node-git-url-parse.git" 144 | ``` 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | ## :memo: Documentation 157 | 158 | 159 | ### `gitUrlParse(url, refs)` 160 | Parses a Git url. 161 | 162 | #### Params 163 | 164 | - **String** `url`: The Git url to parse. 165 | - **Array** `refs`: An array of strings representing the refs. This is helpful in the context of the URLs that contain branches with slashes. 166 | If user wants to identify the branch, he should pass all branch names 167 | of the project as part of refs parameter 168 | 169 | #### Return 170 | - **GitUrl** The `GitUrl` object containing: 171 | - `protocols` (Array): An array with the url protocols (usually it has one element). 172 | - `port` (String): The domain port. 173 | - `resource` (String): The url domain (including subdomains). 174 | - `user` (String): The authentication user (usually for ssh urls). 175 | - `pathname` (String): The url pathname. 176 | - `hash` (String): The url hash. 177 | - `search` (String): The url querystring value. 178 | - `href` (String): The input url. 179 | - `protocol` (String): The git url protocol. 180 | - `token` (String): The oauth token (could appear in the https urls). 181 | - `source` (String): The Git provider (e.g. `"github.com"`). 182 | - `owner` (String): The repository owner. 183 | - `name` (String): The repository name. 184 | - `ref` (String): The repository ref (e.g., "master" or "dev"). 185 | - `filepath` (String): A filepath relative to the repository root. 186 | - `filepathtype` (String): The type of filepath in the url ("blob" or "tree"). 187 | - `full_name` (String): The owner and name values in the `owner/name` format. 188 | - `toString` (Function): A function to stringify the parsed url into another url type. 189 | - `organization` (String): The organization the owner belongs to. This is CloudForge specific. 190 | - `git_suffix` (Boolean): Whether to add the `.git` suffix or not. 191 | 192 | ### `stringify(obj, type)` 193 | Stringifies a `GitUrl` object. 194 | 195 | #### Params 196 | 197 | - **GitUrl** `obj`: The parsed Git url object. 198 | - **String** `type`: The type of the stringified url (default `obj.protocol`). 199 | 200 | #### Return 201 | - **String** The stringified url. 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | ## :question: Get Help 212 | 213 | There are few ways to get help: 214 | 215 | 216 | 217 | 1. Please [post questions on Stack Overflow](https://stackoverflow.com/questions/ask). You can open issues with questions, as long you add a link to your Stack Overflow question. 218 | 2. For bug reports and feature requests, open issues. :bug: 219 | 3. For direct and quick help, you can [use Codementor](https://www.codementor.io/johnnyb). :rocket: 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | ## :yum: How to contribute 235 | Have an idea? Found a bug? See [how to contribute][contributing]. 236 | 237 | 238 | ## :sparkling_heart: Support my projects 239 | I open-source almost everything I can, and I try to reply to everyone needing help using these projects. Obviously, 240 | this takes time. You can integrate and use these projects in your applications *for free*! You can even change the source code and redistribute (even resell it). 241 | 242 | However, if you get some profit from this or just want to encourage me to continue creating stuff, there are few ways you can do it: 243 | 244 | 245 | - Starring and sharing the projects you like :rocket: 246 | - [![Buy me a book][badge_amazon]][amazon]—I love books! I will remember you after years if you buy me one. :grin: :book: 247 | - [![PayPal][badge_paypal]][paypal-donations]—You can make one-time donations via PayPal. I'll probably buy a ~~coffee~~ tea. :tea: 248 | - [![Support me on Patreon][badge_patreon]][patreon]—Set up a recurring monthly donation and you will get interesting news about what I'm doing (things that I don't share with everyone). 249 | - **Bitcoin**—You can send me bitcoins at this address (or scanning the code below): `1P9BRsmazNQcuyTxEqveUsnf5CERdq35V6` 250 | 251 | ![](https://i.imgur.com/z6OQI95.png) 252 | 253 | 254 | Thanks! :heart: 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | ## :dizzy: Where is this library used? 272 | If you are using this library in one of your projects, add it in this list. :sparkles: 273 | 274 | - `@0x-lerna-fork/github-client` 275 | - `@1coin178/github-compare` 276 | - `@_nomtek/react-native-shimmer-animation` 277 | - `@adityasinghal26/backstage-plugin-daytona` 278 | - `@ahmed_shaban123/react-native-currencyinput` 279 | - `@akemona-org/strapi-generate-new` 280 | - `@amiruldev/wajs` 281 | - `@anakz/backstage-plugin-library-check-backend` 282 | - `@antv/gatsby-theme-antv` 283 | - `@appirio/appirio` 284 | - `@appworks/project-utils` 285 | - `@arcanis/sherlock` 286 | - `@ashcorrguardian/cdk` 287 | - `@atomist/automation-client` 288 | - `@atomist/automation-client-ext-raven` 289 | - `@atomist/cli` 290 | - `@atomist/sdm-pack-analysis` 291 | - `@atomist/skill` 292 | - `@atomist/uhura` 293 | - `@axetroy/gpm` 294 | - `@backstage-community/plugin-github-actions` 295 | - `@backstage/backend-common` 296 | - `@backstage/backend-defaults` 297 | - `@backstage/cli` 298 | - `@backstage/integration` 299 | - `@backstage/plugin-catalog-backend` 300 | - `@backstage/plugin-catalog-backend-module-github` 301 | - `@backstage/plugin-catalog-import` 302 | - `@backstage/plugin-github-actions` 303 | - `@backstage/plugin-scaffolder` 304 | - `@backstage/plugin-scaffolder-backend-module-confluence-to-markdown` 305 | - `@backstage/plugin-techdocs` 306 | - `@backstage/plugin-techdocs-module-addons-contrib` 307 | - `@backstage/plugin-techdocs-node` 308 | - `@belt/repo` 309 | - `@blackglory/git-list` 310 | - `@brisk-docs/gatsby-generator` 311 | - `@brisk-docs/website` 312 | - `@buschtoens/documentation` 313 | - `@cilyn/bitbucket` 314 | - `@ckatzorke/renovate` 315 | - `@cliz/gpm` 316 | - `@codemod.com/workflow` 317 | - `@corelmax/react-native-my2c2p-sdk` 318 | - `@cs6/react-native-test-native-view-library` 319 | - `@csmith/release-it` 320 | - `@ctdesarrollo/pack-up` 321 | - `@dandean/storybook-deployer` 322 | - `@daytona-io/backstage-plugin-daytona` 323 | - `@deskbtm/workspace-tools` 324 | - `@dfatwork-pkgs/backstage-cli` 325 | - `@dougkulak/semantic-release-gh-pages-plugin` 326 | - `@dtwo/telemetry` 327 | - `@eat-fish/changelog` 328 | - `@emedvedev/renovate` 329 | - `@enkeledi/react-native-week-month-date-picker` 330 | - `@env0/backstage-plugin-env0` 331 | - `@era-ci/utils` 332 | - `@erquhart/lerna-github-client` 333 | - `@esops/publish-github-pages` 334 | - `@eteg/nextra-theme-docs` 335 | - `@facadecompany/ignition-ui` 336 | - `@feizheng/next-git-url` 337 | - `@felipesimmi/react-native-datalogic-module` 338 | - `@flxbl-io/sfp` 339 | - `@focusworkstemp/project-utils` 340 | - `@form8ion/lift` 341 | - `@gambitnash/cli-plugin-scaffold` 342 | - `@gasket/plugin-metrics` 343 | - `@geut/chan-parser` 344 | - `@geut/git-compare-template` 345 | - `@geut/git-url-parse` 346 | - `@git-stack/hemera-plugin` 347 | - `@git-stack/server-core` 348 | - `@guardian/cdk` 349 | - `@hawkingnetwork/react-native-tab-view` 350 | - `@hbglobal/react-native-actions-shortcuts` 351 | - `@hemith/react-native-tnk` 352 | - `@holipoly/cli` 353 | - `@hugomrdias/documentation` 354 | - `@hygiene/plugin-github-url` 355 | - `@iceworks/project-utils` 356 | - `@infinitecsolutions/storybook-deployer` 357 | - `@janus-idp/backstage-plugin-topology` 358 | - `@jaredpalmer/workspace-tools` 359 | - `@jswork/next-git-url` 360 | - `@jswork/topics2keywords` 361 | - `@kadira/storybook-deployer` 362 | - `@kevinbds/techdocs-common` 363 | - `@kgit/readbility` 364 | - `@koumoul/gh-pages-multi` 365 | - `@labiebhn_/react-native-multiplier` 366 | - `@lehuyaa/my-assets` 367 | - `@lerna-lite/version` 368 | - `@lerna-test-v1/markdown` 369 | - `@lerna/create` 370 | - `@lerna/github-client` 371 | - `@lerna/legacy-package-management` 372 | - `@log4brains/core` 373 | - `@madm4ttus3r/l4bcore` 374 | - `@merna/github-client` 375 | - `@micro-app/shared-utils` 376 | - `@microservices/cli` 377 | - `@mmomtchev/documentation` 378 | - `@mongchhi/plugin-blocks` 379 | - `@monokle/synchronizer` 380 | - `@narfeta/catalog-backend` 381 | - `@navabi/react-native-ssl-pinning` 382 | - `@nuxt/content` 383 | - `@nuxt/telemetry` 384 | - `@nuxt/ui-pro` 385 | - `@nuxthq/studio` 386 | - `@oumi/block-sdk` 387 | - `@oumi/cli-ui` 388 | - `@pageshare/cli` 389 | - `@parallelnft/web3modal` 390 | - `@pipelinedoc/cli` 391 | - `@plone/scripts` 392 | - `@pmworks/project-utils` 393 | - `@polearn/core` 394 | - `@positionex/position-sdk` 395 | - `@praella/localisationist` 396 | - `@pubbo/github-client` 397 | - `@pubgcorp/semantic-release-gitlabmonorepo` 398 | - `@pvdlg/semantic-release` 399 | - `@pvm/core` 400 | - `@qiwi/semantic-release-gh-pages-plugin` 401 | - `@qoopido/lerna.version` 402 | - `@radjs/block-sdk` 403 | - `@rdfrontier/plugin-mobile` 404 | - `@react-18-pdf/root` 405 | - `@rianfowler/backstage-backend-common` 406 | - `@rnx-kit/build` 407 | - `@roadiehq/backstage-plugin-github-insights` 408 | - `@roadiehq/backstage-plugin-github-pull-requests` 409 | - `@roadiehq/backstage-plugin-security-insights` 410 | - `@rocali/apollo` 411 | - `@s-ui/mono` 412 | - `@s-ui/ssr` 413 | - `@s-ui/studio` 414 | - `@safaricom/strapi` 415 | - `@safely-project/safely-ts` 416 | - `@salla.sa/cli` 417 | - `@sanity/pkg-utils` 418 | - `@sanv/apify-shared` 419 | - `@scafflater/scafflater` 420 | - `@secustor/backstage-plugin-renovate-common` 421 | - `@servable/manifest` 422 | - `@servable/tools` 423 | - `@shopgate/pwa-releaser` 424 | - `@sobird/actions` 425 | - `@speakeasy-sdks/nextra-theme-docs` 426 | - `@spotify/backstage-plugin-soundcheck-backend-module-gitlab` 427 | - `@spryker-lerna/github-client` 428 | - `@ssaitho/nextra-theme-docs` 429 | - `@stackbit/cms-git` 430 | - `@stackbit/dev-common` 431 | - `@status-im/react-native-transparent-video` 432 | - `@stepsec/release` 433 | - `@storybook/storybook-deployer` 434 | - `@strapi/pack-up` 435 | - `@strapi/sdk-plugin` 436 | - `@strapi/strapi` 437 | - `@syedt/hellosdk` 438 | - `@tahul/ui-fix` 439 | - `@taingo97/react-native-sunmi-printer` 440 | - `@taingo97/react-native-telpo-printer` 441 | - `@tasenor/common-node` 442 | - `@terasky/backstage-plugin-crossplane-claim-updater` 443 | - `@theowenyoung/gatsby-source-git` 444 | - `@tmplr/node` 445 | - `@toanz/strapi-generate-new` 446 | - `@umijs/block-sdk` 447 | - `@unibtc/release-it` 448 | - `@useoptic/optic` 449 | - `@vamsikc/plugin-catalog` 450 | - `@veecode-platform/plugin-github-actions` 451 | - `@visulima/nextra-theme-docs` 452 | - `@voodeng/uppacks` 453 | - `@vrabbi/plugin-scaffolder` 454 | - `@vrabbi/plugin-scaffolder-backend` 455 | - `@wetrial/block-sdk` 456 | - `@whhjdi/nextra-theme-docs` 457 | - `@wolfy1339/gatsby-source-git` 458 | - `@x-team/strapi-generate-new` 459 | - `@xdn/cli` 460 | - `@xyz/create-package` 461 | - `@yarnpkg/plugin-git` 462 | - `@zeplin/cli` 463 | - `anakketiga` 464 | - `anaklanangtea` 465 | - `anakwadontea` 466 | - `apify-shared` 467 | - `apollo` 468 | - `auto-changelog-vsts` 469 | - `autorelease-setup` 470 | - `belt-repo` 471 | - `biometric-st` 472 | - `bitbucket-pullr` 473 | - `changelog.md` 474 | - `clipped` 475 | - `codesandbox-theme-docs` 476 | - `common-boilerplate` 477 | - `copy-github-directory` 478 | - `cover-builder` 479 | - `create-apex-js-app` 480 | - `create-release-it` 481 | - `cz-conventional-changelog-befe` 482 | - `datoit-generate-new` 483 | - `debone` 484 | - `development-efficiency` 485 | - `docula-ui` 486 | - `docula-ui-express` 487 | - `documentation` 488 | - `documentation-custom-markdown` 489 | - `domaincomponent` 490 | - `dx-scanner` 491 | - `ewizard-cli` 492 | - `fluent.adflow.reactnativesdk-alpha` 493 | - `flutter-boot` 494 | - `fmsl` 495 | - `fotingo` 496 | - `framework_test_library_sixdee` 497 | - `fster` 498 | - `ftl-renovate` 499 | - `gatsby-source-git` 500 | - `gatsby-source-git-remotes` 501 | - `gatsby-source-npmjs` 502 | - `gatsby-theme-zh` 503 | - `gaurav-react-native-loop` 504 | - `gcpayz-block-sdk` 505 | - `generate-github-directory` 506 | - `generator-openapi-repo` 507 | - `gerimismalamsenin` 508 | - `ghseek` 509 | - `git-issues` 510 | - `git-origin-check` 511 | - `git-service-node` 512 | - `git-source` 513 | - `git-url-promise` 514 | - `git-yoink` 515 | - `gitbook-start-iaas-bbdd-alex-moi` 516 | - `gitbook-start-iaas-ull-es-merquililycony` 517 | - `gitbook-start-plugin-iaas-ull-es-noejaco2017` 518 | - `gitc` 519 | - `github-action-readme` 520 | - `github-publish-npm` 521 | - `gitlab-ci-variables-cli` 522 | - `gtni` 523 | - `harry-reporter` 524 | - `hzero-block-sdk` 525 | - `jrennsoh88-react-native-scroll-indicator` 526 | - `jsnix` 527 | - `kef-core` 528 | - `khaled-salem-custom-components` 529 | - `konfig-release-it` 530 | - `konitor` 531 | - `kuqoi-git` 532 | - `l-other-data` 533 | - `laborious` 534 | - `lambda-service` 535 | - `lcov-server` 536 | - `lerna` 537 | - `library-nuxt-ui` 538 | - `license-analysis` 539 | - `lime-cli` 540 | - `manage-dependency` 541 | - `mangudinlagirajin` 542 | - `mdnext-loader` 543 | - `mf-cta-testing` 544 | - `miguelcostero-ng2-toasty` 545 | - `native-apple-login` 546 | - `native-kakao-login` 547 | - `nextra-theme-docs-mdxcomponents` 548 | - `ng-apollo` 549 | - `npm_one_1_2_3` 550 | - `npm_qwerty` 551 | - `octokit-downloader` 552 | - `octopulse` 553 | - `omg` 554 | - `open-pull-request` 555 | - `package-health-checker` 556 | - `package-json-from-git` 557 | - `patchanon-cli` 558 | - `patepangdeui` 559 | - `pbc` 560 | - `pileuleuyantea` 561 | - `pr-changelog-gen` 562 | - `prep-barv11` 563 | - `project-wajs-dv` 564 | - `publish-version` 565 | - `pupils-generate-new` 566 | - `pyreswap-sdk` 567 | - `qts-fission` 568 | - `quick-release` 569 | - `quinntainer` 570 | - `rdmobile` 571 | - `reablocks-docs-theme` 572 | - `react-native-android-native-view` 573 | - `react-native-android-video-player-view` 574 | - `react-native-animate-text` 575 | - `react-native-arunjeyam1987` 576 | - `react-native-arunmeena1987` 577 | - `react-native-awesome-module-two` 578 | - `react-native-azure-communication-services` 579 | - `react-native-basic-app` 580 | - `react-native-basic-screen` 581 | - `react-native-biometric-authenticate` 582 | - `react-native-bleccs-components` 583 | - `react-native-bubble-chart` 584 | - `react-native-build-vesion-getter` 585 | - `react-native-check-component` 586 | - `react-native-components-design` 587 | - `react-native-contact-list` 588 | - `react-native-cplus` 589 | - `react-native-create-video-thumbnail` 590 | - `react-native-custom-poccomponent` 591 | - `react-native-datacapture-core` 592 | - `react-native-dhp-printer` 593 | - `react-native-fedlight-dsm` 594 | - `react-native-ghn-ekyc` 595 | - `react-native-ideo-rn-notifications` 596 | - `react-native-innity-remaster` 597 | - `react-native-input-library` 598 | - `react-native-is7` 599 | - `react-native-kakao-maps` 600 | - `react-native-klarify-ios` 601 | - `react-native-klc` 602 | - `react-native-library-testing-422522` 603 | - `react-native-login-demo-test` 604 | - `react-native-manh-test` 605 | - `react-native-module-for-testing` 606 | - `react-native-multiplier-component` 607 | - `react-native-multiplier-demo` 608 | - `react-native-multiplier2` 609 | - `react-native-multiselector` 610 | - `react-native-my-first-try-arun-ramya` 611 | - `react-native-nice-learning` 612 | - `react-native-paynow-generator` 613 | - `react-native-payu-payment-testing` 614 | - `react-native-progress-arrow` 615 | - `react-native-rabbitmq-all` 616 | - `react-native-reanimated-sortable-list` 617 | - `react-native-rom-components` 618 | - `react-native-s-airlines` 619 | - `react-native-sandycomponent` 620 | - `react-native-savczuk-feature-library` 621 | - `react-native-shared-gesture` 622 | - `react-native-test-comlibrary` 623 | - `react-native-ticker-tape` 624 | - `react-native-uvc-camera-android` 625 | - `react-native-withframework-check` 626 | - `react-native-ytximkit` 627 | - `redhatinsights-plugin-scaffolder-backend` 628 | - `refinejs-repo` 629 | - `release-it` 630 | - `release-it-http` 631 | - `remax-stats` 632 | - `renovate` 633 | - `renovate-csm` 634 | - `repom` 635 | - `repomix` 636 | - `rn-adyen-dropin` 637 | - `rn-check-btn` 638 | - `rn_unique_device_id` 639 | - `scaffolder-core` 640 | - `sedanbosok` 641 | - `semantic-release-github-milestones` 642 | - `semantic-release-gitmoji` 643 | - `semantic-release-squash3` 644 | - `sherry-utils` 645 | - `simple-github-release` 646 | - `snipx` 647 | - `spk` 648 | - `strapi-generate-new` 649 | - `stylelint-formatter-utils` 650 | - `sync-repos` 651 | - `tanyao` 652 | - `tegit` 653 | - `tehmusimhujan` 654 | - `template-typescript-node-package` 655 | - `test-library-123` 656 | - `testarmada-midway` 657 | - `tldw` 658 | - `todo2issue` 659 | - `tooling-personal` 660 | - `toxics-rpc` 661 | - `umi-build-dev` 662 | - `vision-camera-plugin-face-detector` 663 | - `vuepress-plugin-remote-url` 664 | - `wiby` 665 | - `winx-form-winx` 666 | - `workspace-tools` 667 | - `wsj.gatsby-source-git` 668 | - `zephyr-agent` 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | ## :scroll: License 681 | 682 | [MIT][license] © [Ionică Bizău][website] 683 | 684 | 685 | 686 | 687 | 688 | 689 | [license]: /LICENSE 690 | [website]: https://ionicabizau.net 691 | [contributing]: /CONTRIBUTING.md 692 | [docs]: /DOCUMENTATION.md 693 | [badge_patreon]: https://ionicabizau.github.io/badges/patreon.svg 694 | [badge_amazon]: https://ionicabizau.github.io/badges/amazon.svg 695 | [badge_paypal]: https://ionicabizau.github.io/badges/paypal.svg 696 | [badge_paypal_donate]: https://ionicabizau.github.io/badges/paypal_donate.svg 697 | [patreon]: https://www.patreon.com/ionicabizau 698 | [amazon]: http://amzn.eu/hRo9sIZ 699 | [paypal-donations]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=RVXDDLKKLQRJW 700 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | const GitUrlParse = require("../lib"); 3 | 4 | console.log(GitUrlParse("git@github.com:IonicaBizau/node-git-url-parse.git")); 5 | // => { 6 | // protocols: [] 7 | // , port: null 8 | // , resource: "github.com" 9 | // , user: "git" 10 | // , pathname: "/IonicaBizau/node-git-url-parse.git" 11 | // , hash: "" 12 | // , search: "" 13 | // , href: "git@github.com:IonicaBizau/node-git-url-parse.git" 14 | // , token: "" 15 | // , protocol: "ssh" 16 | // , toString: [Function] 17 | // , source: "github.com" 18 | // , name: "node-git-url-parse" 19 | // , owner: "IonicaBizau" 20 | // } 21 | 22 | console.log(GitUrlParse("https://github.com/IonicaBizau/node-git-url-parse.git")); 23 | // => { 24 | // protocols: ["https"] 25 | // , port: null 26 | // , resource: "github.com" 27 | // , user: "" 28 | // , pathname: "/IonicaBizau/node-git-url-parse.git" 29 | // , hash: "" 30 | // , search: "" 31 | // , href: "https://github.com/IonicaBizau/node-git-url-parse.git" 32 | // , token: "" 33 | // , protocol: "https" 34 | // , toString: [Function] 35 | // , source: "github.com" 36 | // , name: "node-git-url-parse" 37 | // , owner: "IonicaBizau" 38 | // } 39 | 40 | console.log(GitUrlParse("https://github.com/IonicaBizau/git-url-parse/blob/master/test/index.js")); 41 | // { protocols: [ 'https' ], 42 | // protocol: 'https', 43 | // port: null, 44 | // resource: 'github.com', 45 | // user: '', 46 | // pathname: '/IonicaBizau/git-url-parse/blob/master/test/index.js', 47 | // hash: '', 48 | // search: '', 49 | // href: 'https://github.com/IonicaBizau/git-url-parse/blob/master/test/index.js', 50 | // token: '', 51 | // toString: [Function], 52 | // source: 'github.com', 53 | // name: 'git-url-parse', 54 | // owner: 'IonicaBizau', 55 | // organization: '', 56 | // ref: 'master', 57 | // filepathtype: 'blob', 58 | // filepath: 'test/index.js', 59 | // full_name: 'IonicaBizau/git-url-parse' } 60 | 61 | console.log(GitUrlParse("https://github.com/IonicaBizau/node-git-url-parse.git").toString("ssh")); 62 | // => "git@github.com:IonicaBizau/node-git-url-parse.git" 63 | 64 | console.log(GitUrlParse("git@github.com:IonicaBizau/node-git-url-parse.git").toString("https")); 65 | // => "https://github.com/IonicaBizau/node-git-url-parse.git" 66 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import gitUp = require("git-up"); 2 | 3 | declare namespace gitUrlParse { 4 | interface GitUrl extends gitUp.ParsedUrl { 5 | /** The Git provider (e.g. `"github.com"`). */ 6 | source: string; 7 | /** The repository owner. */ 8 | owner: string; 9 | /** The repository name. */ 10 | name: string; 11 | /** The repository ref (e.g., "master" or "dev"). */ 12 | ref: string; 13 | /** A filepath relative to the repository root. */ 14 | filepath: string; 15 | /** The type of filepath in the url ("blob" or "tree"). */ 16 | filepathtype: string; 17 | /** The owner and name values in the `owner/name` format. */ 18 | full_name: string; 19 | /** The organization the owner belongs to. This is CloudForge specific. */ 20 | organization: string; 21 | /** Whether to add the `.git` suffix or not. */ 22 | git_suffix?: boolean | undefined; 23 | toString(type?: string): string; 24 | } 25 | 26 | function stringify(url: GitUrl, type?: string): string; 27 | } 28 | 29 | declare function gitUrlParse(url: string): gitUrlParse.GitUrl; 30 | 31 | export = gitUrlParse; 32 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const gitUp = require("git-up"); 4 | 5 | /** 6 | * gitUrlParse 7 | * Parses a Git url. 8 | * 9 | * @name gitUrlParse 10 | * @function 11 | * @param {String} url The Git url to parse. 12 | * @param {Array} refs An array of strings representing the refs. This is 13 | * helpful in the context of the URLs that contain branches with slashes. 14 | * If user wants to identify the branch, he should pass all branch names 15 | * of the project as part of refs parameter 16 | * @return {GitUrl} The `GitUrl` object containing: 17 | * 18 | * - `protocols` (Array): An array with the url protocols (usually it has one element). 19 | * - `port` (String): The domain port. 20 | * - `resource` (String): The url domain (including subdomains). 21 | * - `user` (String): The authentication user (usually for ssh urls). 22 | * - `pathname` (String): The url pathname. 23 | * - `hash` (String): The url hash. 24 | * - `search` (String): The url querystring value. 25 | * - `href` (String): The input url. 26 | * - `protocol` (String): The git url protocol. 27 | * - `token` (String): The oauth token (could appear in the https urls). 28 | * - `source` (String): The Git provider (e.g. `"github.com"`). 29 | * - `owner` (String): The repository owner. 30 | * - `name` (String): The repository name. 31 | * - `ref` (String): The repository ref (e.g., "master" or "dev"). 32 | * - `filepath` (String): A filepath relative to the repository root. 33 | * - `filepathtype` (String): The type of filepath in the url ("blob" or "tree"). 34 | * - `full_name` (String): The owner and name values in the `owner/name` format. 35 | * - `toString` (Function): A function to stringify the parsed url into another url type. 36 | * - `organization` (String): The organization the owner belongs to. This is CloudForge specific. 37 | * - `git_suffix` (Boolean): Whether to add the `.git` suffix or not. 38 | * 39 | */ 40 | function gitUrlParse(url, refs) { 41 | refs = refs || [] 42 | 43 | if (typeof url !== "string") { 44 | throw new Error("The url must be a string."); 45 | } 46 | 47 | if (!refs.every(item => typeof item === "string")) { 48 | throw new Error("The refs should contain only strings") 49 | } 50 | 51 | const shorthandRe = /^([a-z\d-]{1,39})\/([-\.\w]{1,100})$/i 52 | 53 | if (shorthandRe.test(url)) { 54 | url = `https://github.com/${url}` 55 | } 56 | 57 | let urlInfo = gitUp(url) 58 | , sourceParts = urlInfo.resource.split(".") 59 | , splits = null 60 | ; 61 | 62 | urlInfo.toString = function (type) { 63 | return gitUrlParse.stringify(this, type); 64 | }; 65 | 66 | urlInfo.source = sourceParts.length > 2 67 | ? sourceParts.slice(1 - sourceParts.length).join(".") 68 | : urlInfo.source = urlInfo.resource 69 | ; 70 | 71 | // Note: Some hosting services (e.g. Visual Studio Team Services) allow whitespace characters 72 | // in the repository and owner names so we decode the URL pieces to get the correct result 73 | urlInfo.git_suffix = /\.git$/.test(urlInfo.pathname); 74 | urlInfo.name = decodeURIComponent((urlInfo.pathname || urlInfo.href).replace(/(^\/)|(\/$)/g, "").replace(/\.git$/, "")); 75 | urlInfo.owner = decodeURIComponent(urlInfo.user); 76 | 77 | switch (urlInfo.source) { 78 | case "git.cloudforge.com": 79 | urlInfo.owner = urlInfo.user; 80 | urlInfo.organization = sourceParts[0]; 81 | urlInfo.source = "cloudforge.com"; 82 | break; 83 | case "visualstudio.com": 84 | // Handle VSTS SSH URLs 85 | if (urlInfo.resource === "vs-ssh.visualstudio.com") { 86 | splits = urlInfo.name.split("/"); 87 | if (splits.length === 4) { 88 | urlInfo.organization = splits[1]; 89 | urlInfo.owner = splits[2]; 90 | urlInfo.name = splits[3]; 91 | urlInfo.full_name = splits[2] + "/" + splits[3]; 92 | } 93 | break; 94 | } else { 95 | splits = urlInfo.name.split("/"); 96 | if (splits.length === 2) { 97 | urlInfo.owner = splits[1]; 98 | urlInfo.name = splits[1]; 99 | urlInfo.full_name = "_git/" + urlInfo.name; 100 | } else if (splits.length === 3) { 101 | urlInfo.name = splits[2]; 102 | if (splits[0] === "DefaultCollection") { 103 | urlInfo.owner = splits[2]; 104 | urlInfo.organization = splits[0]; 105 | urlInfo.full_name = urlInfo.organization + "/_git/" + urlInfo.name; 106 | } else { 107 | urlInfo.owner = splits[0]; 108 | urlInfo.full_name = urlInfo.owner + "/_git/" + urlInfo.name; 109 | } 110 | } else if (splits.length === 4) { 111 | urlInfo.organization = splits[0]; 112 | urlInfo.owner = splits[1]; 113 | urlInfo.name = splits[3]; 114 | urlInfo.full_name = urlInfo.organization + "/" + urlInfo.owner + "/_git/" + urlInfo.name; 115 | } 116 | break; 117 | } 118 | 119 | // Azure DevOps (formerly Visual Studio Team Services) 120 | case "dev.azure.com": 121 | case "azure.com": 122 | if (urlInfo.resource === "ssh.dev.azure.com") { 123 | splits = urlInfo.name.split("/"); 124 | if (splits.length === 4) { 125 | urlInfo.organization = splits[1]; 126 | urlInfo.owner = splits[2]; 127 | urlInfo.name = splits[3]; 128 | } 129 | break; 130 | } else { 131 | splits = urlInfo.name.split("/"); 132 | if (splits.length === 5) { 133 | urlInfo.organization = splits[0]; 134 | urlInfo.owner = splits[1]; 135 | urlInfo.name = splits[4]; 136 | urlInfo.full_name = "_git/" + urlInfo.name; 137 | } else if (splits.length === 3) { 138 | urlInfo.name = splits[2]; 139 | if (splits[0] === "DefaultCollection") { 140 | urlInfo.owner = splits[2]; 141 | urlInfo.organization = splits[0]; 142 | urlInfo.full_name = urlInfo.organization + "/_git/" + urlInfo.name; 143 | } else { 144 | urlInfo.owner = splits[0]; 145 | urlInfo.full_name = urlInfo.owner + "/_git/" + urlInfo.name; 146 | } 147 | } else if (splits.length === 4) { 148 | urlInfo.organization = splits[0]; 149 | urlInfo.owner = splits[1]; 150 | urlInfo.name = splits[3]; 151 | urlInfo.full_name = urlInfo.organization + "/" + urlInfo.owner + "/_git/" + urlInfo.name; 152 | } 153 | if(urlInfo.query && urlInfo.query["path"]) { 154 | urlInfo.filepath = urlInfo.query["path"].replace(/^\/+/g, ""); // Strip leading slash (/) 155 | } 156 | if(urlInfo.query && urlInfo.query["version"]) { // version=GB 157 | urlInfo.ref = urlInfo.query["version"].replace(/^GB/, ""); // remove GB 158 | } 159 | break; 160 | } 161 | default: 162 | splits = urlInfo.name.split("/"); 163 | let nameIndex = splits.length - 1; 164 | if (splits.length >= 2) { 165 | const dashIndex = splits.indexOf("-", 2) 166 | const blobIndex = splits.indexOf("blob", 2); 167 | const treeIndex = splits.indexOf("tree", 2); 168 | const commitIndex = splits.indexOf("commit", 2); 169 | const issuesIndex = splits.indexOf("issues", 2); 170 | const srcIndex = splits.indexOf("src", 2); 171 | const rawIndex = splits.indexOf("raw", 2); 172 | const editIndex = splits.indexOf("edit", 2); 173 | nameIndex = dashIndex > 0 ? dashIndex - 1 174 | : blobIndex > 0 && treeIndex > 0 ? Math.min(blobIndex - 1, treeIndex - 1) 175 | : blobIndex > 0 ? blobIndex - 1 176 | : issuesIndex > 0 ? issuesIndex - 1 177 | : treeIndex > 0 ? treeIndex - 1 178 | : commitIndex > 0 ? commitIndex - 1 179 | : srcIndex > 0 ? srcIndex - 1 180 | : rawIndex > 0 ? rawIndex - 1 181 | : editIndex > 0 ? editIndex - 1 182 | : nameIndex; 183 | 184 | urlInfo.owner = splits.slice(0, nameIndex).join("/"); 185 | urlInfo.name = splits[nameIndex]; 186 | if (commitIndex && issuesIndex < 0) { 187 | urlInfo.commit = splits[nameIndex + 2] 188 | } 189 | } 190 | 191 | urlInfo.ref = ""; 192 | urlInfo.filepathtype = ""; 193 | urlInfo.filepath = ""; 194 | const offsetNameIndex = splits.length > nameIndex && splits[nameIndex+1] === "-" ? nameIndex + 1 : nameIndex; 195 | 196 | if ((splits.length > offsetNameIndex + 2) && (["raw", "src", "blob", "tree", "edit"].indexOf(splits[offsetNameIndex + 1]) >= 0)) { 197 | urlInfo.filepathtype = splits[offsetNameIndex + 1]; 198 | urlInfo.ref = splits[offsetNameIndex + 2]; 199 | if (splits.length > offsetNameIndex + 3) { 200 | urlInfo.filepath = splits.slice(offsetNameIndex + 3).join("/"); 201 | } 202 | } 203 | urlInfo.organization = urlInfo.owner; 204 | break; 205 | } 206 | 207 | if (!urlInfo.full_name) { 208 | urlInfo.full_name = urlInfo.owner; 209 | if (urlInfo.name) { 210 | urlInfo.full_name && (urlInfo.full_name += "/"); 211 | urlInfo.full_name += urlInfo.name; 212 | } 213 | } 214 | // Bitbucket Server 215 | if(urlInfo.owner.startsWith("scm/")) { 216 | urlInfo.source = "bitbucket-server"; 217 | urlInfo.owner = urlInfo.owner.replace("scm/",""); 218 | urlInfo.organization = urlInfo.owner; 219 | urlInfo.full_name = `${urlInfo.owner}/${urlInfo.name}` 220 | } 221 | 222 | const bitbucket = /(projects|users)\/(.*?)\/repos\/(.*?)((\/.*$)|$)/ 223 | const matches = bitbucket.exec(urlInfo.pathname) 224 | if(matches != null) { 225 | urlInfo.source = "bitbucket-server"; 226 | if (matches[1] === "users") { 227 | urlInfo.owner = "~" + matches[2]; 228 | } else { 229 | urlInfo.owner = matches[2]; 230 | } 231 | 232 | urlInfo.organization = urlInfo.owner; 233 | urlInfo.name = matches[3]; 234 | 235 | splits = matches[4].split("/"); 236 | if(splits.length > 1) { 237 | if(["raw","browse"].indexOf(splits[1]) >= 0) { 238 | urlInfo.filepathtype = splits[1]; 239 | if (splits.length > 2) { 240 | urlInfo.filepath = splits.slice(2).join("/"); 241 | } 242 | } else if(splits[1] === "commits" && splits.length > 2) { 243 | urlInfo.commit = splits[2]; 244 | } 245 | } 246 | urlInfo.full_name = `${urlInfo.owner}/${urlInfo.name}` 247 | 248 | if(urlInfo.query.at) { 249 | urlInfo.ref = urlInfo.query.at; 250 | } else { 251 | urlInfo.ref = ""; 252 | } 253 | } 254 | 255 | if (refs.length !== 0 && urlInfo.ref) { 256 | urlInfo.ref = findLongestMatchingSubstring(urlInfo.href, refs) || urlInfo.ref 257 | urlInfo.filepath = urlInfo.href.split(urlInfo.ref + "/")[1] 258 | } 259 | 260 | return urlInfo; 261 | } 262 | 263 | /** 264 | * stringify 265 | * Stringifies a `GitUrl` object. 266 | * 267 | * @name stringify 268 | * @function 269 | * @param {GitUrl} obj The parsed Git url object. 270 | * @param {String} type The type of the stringified url (default `obj.protocol`). 271 | * @return {String} The stringified url. 272 | */ 273 | gitUrlParse.stringify = function (obj, type) { 274 | type = type || ((obj.protocols && obj.protocols.length) ? obj.protocols.join("+") : obj.protocol); 275 | const port = obj.port ? `:${obj.port}` : ""; 276 | const user = obj.user || "git"; 277 | const maybeGitSuffix = obj.git_suffix ? ".git" : "" 278 | switch (type) { 279 | case "ssh": 280 | if (port) 281 | return `ssh://${user}@${obj.resource}${port}/${obj.full_name}${maybeGitSuffix}`; 282 | else 283 | return `${user}@${obj.resource}:${obj.full_name}${maybeGitSuffix}`; 284 | case "git+ssh": 285 | case "ssh+git": 286 | case "ftp": 287 | case "ftps": 288 | return `${type}://${user}@${obj.resource}${port}/${obj.full_name}${maybeGitSuffix}`; 289 | case "http": 290 | case "https": 291 | const auth = obj.token 292 | ? buildToken(obj) : obj.user && (obj.protocols.includes("http") || obj.protocols.includes("https")) 293 | ? `${obj.user}@` : ""; 294 | return `${type}://${auth}${obj.resource}${port}/${buildPath(obj)}${maybeGitSuffix}`; 295 | default: 296 | return obj.href; 297 | } 298 | }; 299 | 300 | /*! 301 | * buildToken 302 | * Builds OAuth token prefix (helper function) 303 | * 304 | * @name buildToken 305 | * @function 306 | * @param {GitUrl} obj The parsed Git url object. 307 | * @return {String} token prefix 308 | */ 309 | function buildToken(obj) { 310 | switch (obj.source) { 311 | case "bitbucket.org": 312 | return `x-token-auth:${obj.token}@`; 313 | default: 314 | return `${obj.token}@` 315 | } 316 | } 317 | 318 | function buildPath(obj) { 319 | switch(obj.source) { 320 | case "bitbucket-server": 321 | return `scm/${obj.full_name}`; 322 | default: 323 | // Note: Re-encode the repository and owner names for hosting services that allow whitespace characters 324 | const encoded_full_name = obj.full_name 325 | .split("/") 326 | .map(x => encodeURIComponent(x)) 327 | .join("/"); 328 | 329 | return encoded_full_name; 330 | } 331 | } 332 | 333 | function findLongestMatchingSubstring(string, array) { 334 | let longestMatch = ""; 335 | 336 | array.forEach(item => { 337 | if (string.includes(item) && item.length > longestMatch.length) { 338 | longestMatch = item; 339 | } 340 | }); 341 | 342 | return longestMatch; 343 | } 344 | 345 | module.exports = gitUrlParse; 346 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "git-url-parse", 3 | "version": "16.1.0", 4 | "description": "A high level git url parser for common git providers.", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "node test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/IonicaBizau/git-url-parse.git" 12 | }, 13 | "keywords": [ 14 | "parse", 15 | "git", 16 | "url" 17 | ], 18 | "author": "Ionică Bizău (https://ionicabizau.net)", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/IonicaBizau/git-url-parse/issues" 22 | }, 23 | "homepage": "https://github.com/IonicaBizau/git-url-parse", 24 | "blah": { 25 | "h_img": "http://i.imgur.com/HlfMsVf.png" 26 | }, 27 | "directories": { 28 | "example": "example", 29 | "test": "test" 30 | }, 31 | "dependencies": { 32 | "git-up": "^8.1.0" 33 | }, 34 | "devDependencies": { 35 | "tester": "^1.3.1" 36 | }, 37 | "files": [ 38 | "bin/", 39 | "app/", 40 | "lib/", 41 | "dist/", 42 | "src/", 43 | "scripts/", 44 | "resources/", 45 | "menu/", 46 | "cli.js", 47 | "index.js", 48 | "index.d.ts", 49 | "package-lock.json", 50 | "bloggify.js", 51 | "bloggify.json", 52 | "bloggify/" 53 | ] 54 | } -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | const gitUrlParse = require("..") 3 | , tester = require("tester") 4 | ; 5 | 6 | // Constants 7 | const URLS = { 8 | ssh: "custom-user@github.com:42IonicaBizau/git-url-parse" 9 | , https: "https://github.com/42IonicaBizau/git-url-parse" 10 | , ftp: "ftp://github.com/42IonicaBizau/git-url-parse" 11 | , ftps: "ftps://github.com/42IonicaBizau/git-url-parse" 12 | , gitSsh: "git+ssh://custom-user@github.com/42IonicaBizau/git-url-parse" 13 | , ref: "https://github.com/42IonicaBizau/git-url-parse/blob/master/test/index.js" 14 | , shorthand: "42IonicaBizau/git-url-parse" 15 | , commit: "https://github.com/42IonicaBizau/git-url-parse/commit/9c6443245ace92d237b7b274d4606a616e071c4e" 16 | , issue: "https://github.com/IonicaBizau/git-url-parse/issues/133" 17 | }; 18 | 19 | const gitUser = (url) => url.replace('custom-user@', 'git@'); 20 | 21 | tester.describe("parse urls", test => { 22 | 23 | // SSH urls 24 | test.should("parse ssh urls", () => { 25 | var res = gitUrlParse(URLS.ssh); 26 | test.expect(res.protocol).toBe("ssh"); 27 | test.expect(res.source).toBe("github.com"); 28 | test.expect(res.owner).toBe("42IonicaBizau"); 29 | test.expect(res.name).toBe("git-url-parse"); 30 | test.expect(res.full_name).toBe("42IonicaBizau/git-url-parse"); 31 | test.expect(res.href).toBe(URLS.ssh); 32 | test.expect(res.toString("https")).toBe(URLS.https); 33 | test.expect(res.toString("git+ssh")).toBe(URLS.gitSsh); 34 | test.expect(res.toString("ssh")).toBe(URLS.ssh); 35 | 36 | res.git_suffix = true 37 | test.expect(res.toString("https")).toBe(URLS.https + ".git"); 38 | test.expect(res.toString("git+ssh")).toBe(URLS.gitSsh + ".git"); 39 | test.expect(res.toString("ssh")).toBe(URLS.ssh + ".git"); 40 | }); 41 | 42 | // FTP urls 43 | test.should("parse ftp urls", () => { 44 | var res = gitUrlParse(URLS.ftp); 45 | test.expect(res.protocol).toBe("ftp"); 46 | test.expect(res.source).toBe("github.com"); 47 | test.expect(res.owner).toBe("42IonicaBizau"); 48 | test.expect(res.name).toBe("git-url-parse"); 49 | test.expect(res.href).toBe(URLS.ftp); 50 | test.expect(res.toString("https")).toBe(URLS.https); 51 | test.expect(res.toString("git+ssh")).toBe(gitUser(URLS.gitSsh)); 52 | test.expect(res.toString("ssh")).toBe(gitUser(URLS.ssh)); 53 | }); 54 | 55 | // FTPS urls 56 | test.should("parse ftps urls", () => { 57 | var res = gitUrlParse(URLS.ftps); 58 | test.expect(res.protocol).toBe("ftps"); 59 | test.expect(res.source).toBe("github.com"); 60 | test.expect(res.owner).toBe("42IonicaBizau"); 61 | test.expect(res.name).toBe("git-url-parse"); 62 | test.expect(res.href).toBe(URLS.ftps); 63 | test.expect(res.toString("https")).toBe(URLS.https); 64 | test.expect(res.toString("git+ssh")).toBe(gitUser(URLS.gitSsh)); 65 | test.expect(res.toString("ssh")).toBe(gitUser(URLS.ssh)); 66 | }); 67 | 68 | // HTTPS urls 69 | test.should("parse https urls", () => { 70 | var res = gitUrlParse(URLS.https); 71 | test.expect(res.protocol).toBe("https"); 72 | test.expect(res.source).toBe("github.com"); 73 | test.expect(res.owner).toBe("42IonicaBizau"); 74 | test.expect(res.name).toBe("git-url-parse"); 75 | test.expect(res.href).toBe(URLS.https); 76 | test.expect(res.toString("https")).toBe(URLS.https); 77 | test.expect(res.toString("git+ssh")).toBe(gitUser(URLS.gitSsh)); 78 | test.expect(res.toString("ssh")).toBe(gitUser(URLS.ssh)); 79 | }); 80 | 81 | // HTTPS with ending slash 82 | test.should("parse https urls with ending slash", () => { 83 | var res = gitUrlParse("https://github.com/42IonicaBizau/git-url-parse/"); 84 | test.expect(res.protocol).toBe("https"); 85 | test.expect(res.source).toBe("github.com"); 86 | test.expect(res.owner).toBe("42IonicaBizau"); 87 | test.expect(res.name).toBe("git-url-parse"); 88 | test.expect(res.toString("https")).toBe(URLS.https); 89 | test.expect(res.toString("git+ssh")).toBe(gitUser(URLS.gitSsh)); 90 | test.expect(res.toString("ssh")).toBe(gitUser(URLS.ssh)); 91 | }); 92 | 93 | // git+ssh protocol 94 | test.should("parse git+ssh urls", () => { 95 | var res = gitUrlParse(URLS.gitSsh); 96 | test.expect(res.protocol).toBe("ssh"); 97 | test.expect(res.source).toBe("github.com"); 98 | test.expect(res.owner).toBe("42IonicaBizau"); 99 | test.expect(res.name).toBe("git-url-parse"); 100 | test.expect(res.toString("https")).toBe(URLS.https); 101 | test.expect(res.toString("git+ssh")).toBe(URLS.gitSsh); 102 | test.expect(res.toString("ssh")).toBe(URLS.ssh); 103 | }); 104 | 105 | // HTTPS with basic auth 106 | test.should("parse https urls with basic auth", () => { 107 | var res = gitUrlParse("https://user:password@github.com/42IonicaBizau/git-url-parse"); 108 | test.expect(res.protocol).toBe("https"); 109 | test.expect(res.source).toBe("github.com"); 110 | test.expect(res.owner).toBe("42IonicaBizau"); 111 | test.expect(res.name).toBe("git-url-parse"); 112 | test.expect(res.user).toBe("user"); 113 | test.expect(res.password).toBe("password"); 114 | }); 115 | 116 | // oauth 117 | test.should("parse oauth urls", () => { 118 | var res = gitUrlParse("https://token:x-oauth-basic@github.com/owner/name.git"); 119 | test.expect(res.source).toBe("github.com"); 120 | test.expect(res.owner).toBe("owner"); 121 | test.expect(res.name).toBe("name"); 122 | }); 123 | 124 | // oauth bitbucket 125 | test.should("parse Bitbucket oauth urls", () => { 126 | var res = gitUrlParse("https://x-token-auth:token@bitbucket.org/owner/name.git"); 127 | test.expect(res.source).toBe("bitbucket.org"); 128 | test.expect(res.owner).toBe("owner"); 129 | test.expect(res.name).toBe("name"); 130 | }); 131 | 132 | // https bitbucket 133 | test.should("parse Bitbucket https urls", () => { 134 | var res = gitUrlParse("https://owner@bitbucket.org/owner/name"); 135 | test.expect(res.source).toBe("bitbucket.org"); 136 | test.expect(res.owner).toBe("owner"); 137 | test.expect(res.name).toBe("name"); 138 | }); 139 | 140 | // bitbucket cloud src file 141 | test.should("parse Bitbucket Cloud src file", () => { 142 | var res = gitUrlParse("https://bitbucket.org/owner/name/src/master/README.md"); 143 | test.expect(res.owner).toBe("owner"); 144 | test.expect(res.name).toBe("name"); 145 | test.expect(res.filepath).toBe("README.md"); 146 | test.expect(res.ref).toBe("master"); 147 | test.expect(res.filepathtype).toBe("src"); 148 | }); 149 | 150 | // bitbucket cloud raw file 151 | test.should("parse Bitbucket Cloud raw file", () => { 152 | var res = gitUrlParse("https://bitbucket.org/owner/name/raw/master/README.md"); 153 | test.expect(res.owner).toBe("owner"); 154 | test.expect(res.name).toBe("name"); 155 | test.expect(res.filepath).toBe("README.md"); 156 | test.expect(res.ref).toBe("master"); 157 | test.expect(res.filepathtype).toBe("raw"); 158 | }); 159 | 160 | // https bitbucket server 161 | test.should("parse Bitbucket Server clone over http", () => { 162 | var res = gitUrlParse("https://user@bitbucket.companyname.com/scm/owner/name.git"); 163 | test.expect(res.owner).toBe("owner"); 164 | test.expect(res.name).toBe("name"); 165 | }); 166 | 167 | // ssh bitbucket server 168 | test.should("parse Bitbucket Server clone over ssh", () => { 169 | var res = gitUrlParse("ssh://git@bitbucket.companyname.com/owner/name.git"); 170 | test.expect(res.owner).toBe("owner"); 171 | test.expect(res.name).toBe("name"); 172 | }); 173 | 174 | // bitbucket server raw file 175 | test.should("parse Bitbucket Server raw file", () => { 176 | var res = gitUrlParse("https://bitbucket.mycompany.com/projects/owner/repos/name/raw/README.md?at=master"); 177 | test.expect(res.owner).toBe("owner"); 178 | test.expect(res.name).toBe("name"); 179 | test.expect(res.filepath).toBe("README.md") 180 | test.expect(res.ref).toBe("master"); 181 | test.expect(res.filepathtype).toBe("raw"); 182 | }); 183 | 184 | // bitbucket server raw file 185 | test.should("parse Bitbucket Server raw file without ref", () => { 186 | var res = gitUrlParse("https://bitbucket.mycompany.com/projects/owner/repos/name/raw/README.md"); 187 | test.expect(res.owner).toBe("owner"); 188 | test.expect(res.name).toBe("name"); 189 | test.expect(res.filepath).toBe("README.md") 190 | test.expect(res.ref).toBe(""); 191 | test.expect(res.filepathtype).toBe("raw"); 192 | }); 193 | 194 | test.should("parse Bitbucket server browse repository", () => { 195 | var res = gitUrlParse("https://bitbucket.mycompany.com/projects/owner/repos/name/browse"); 196 | test.expect(res.owner).toBe("owner"); 197 | test.expect(res.name).toBe("name"); 198 | test.expect(res.filepathtype).toBe("browse"); 199 | }); 200 | 201 | test.should("parse Bitbucket server browse repository with a trailing slash", () => { 202 | var res = gitUrlParse("https://bitbucket.mycompany.com/projects/owner/repos/name/browse/"); 203 | test.expect(res.owner).toBe("owner"); 204 | test.expect(res.name).toBe("name"); 205 | test.expect(res.filepathtype).toBe("browse"); 206 | }); 207 | 208 | test.should("parse Bitbucket server browse file", () => { 209 | var res = gitUrlParse("https://bitbucket.mycompany.com/projects/owner/repos/name/browse/README.md?at=master"); 210 | test.expect(res.owner).toBe("owner"); 211 | test.expect(res.name).toBe("name"); 212 | test.expect(res.filepath).toBe("README.md"); 213 | test.expect(res.ref).toBe("master"); 214 | test.expect(res.filepathtype).toBe("browse"); 215 | test.expect(res.toString()).toBe("https://bitbucket.mycompany.com/scm/owner/name") 216 | }); 217 | 218 | test.should("parse Bitbucket Server personal repository browse url", () => { 219 | var res = gitUrlParse("https://bitbucket.mycompany.com/users/owner/repos/name/browse/README.md?at=master"); 220 | test.expect(res.owner).toBe("~owner"); 221 | test.expect(res.name).toBe("name"); 222 | test.expect(res.filepath).toBe("README.md"); 223 | test.expect(res.ref).toBe("master"); 224 | test.expect(res.filepathtype).toBe("browse"); 225 | }); 226 | 227 | test.should("parse Bitbucket Server personal repository raw url", () => { 228 | var res = gitUrlParse("https://bitbucket.mycompany.com/users/owner/repos/name/raw/README.md?at=master"); 229 | test.expect(res.owner).toBe("~owner"); 230 | test.expect(res.name).toBe("name"); 231 | test.expect(res.filepath).toBe("README.md"); 232 | test.expect(res.ref).toBe("master"); 233 | test.expect(res.filepathtype).toBe("raw"); 234 | test.expect(res.toString()).toBe("https://bitbucket.mycompany.com/scm/~owner/name") 235 | }); 236 | 237 | test.should("parse Bitbucket Server personal repository clone over ssh", () => { 238 | var res = gitUrlParse("ssh://git@bitbucket.mycompany.com/~owner/name.git"); 239 | test.expect(res.owner).toBe("~owner"); 240 | test.expect(res.name).toBe("name"); 241 | }); 242 | 243 | test.should("parse Bitbucket Server personal repository clone over http", () => { 244 | var res = gitUrlParse("https://bitbucket.mycompany.com/scm/~owner/name.git"); 245 | test.expect(res.owner).toBe("~owner"); 246 | test.expect(res.organization).toBe("~owner") 247 | test.expect(res.name).toBe("name"); 248 | test.expect(res.full_name).toBe("~owner/name") 249 | }); 250 | 251 | test.should("parse Bitbucket Server root endpoint", () => { 252 | var res = gitUrlParse("https://bitbucket.mycompany.com/projects/owner/repos/name") 253 | test.expect(res.owner).toBe("owner"); 254 | test.expect(res.organization).toBe("owner"); 255 | test.expect(res.name).toBe("name"); 256 | test.expect(res.full_name).toBe("owner/name"); 257 | }); 258 | 259 | test.should("parse Bitbucket Server root endpoint with trailing slash", () => { 260 | var res = gitUrlParse("https://bitbucket.mycompany.com/projects/owner/repos/name/") 261 | test.expect(res.owner).toBe("owner"); 262 | test.expect(res.organization).toBe("owner"); 263 | test.expect(res.name).toBe("name"); 264 | test.expect(res.full_name).toBe("owner/name"); 265 | }); 266 | 267 | test.should("parse Bitbucket Server commit endpoint", () => { 268 | var res = gitUrlParse("https://bitbucket.mycompany.com/projects/owner/repos/name/commits/9c6443245ace92d237b7b274d4606a616e071c4e") 269 | test.expect(res.owner).toBe("owner"); 270 | test.expect(res.organization).toBe("owner"); 271 | test.expect(res.name).toBe("name"); 272 | test.expect(res.full_name).toBe("owner/name"); 273 | test.expect(res.commit).toBe("9c6443245ace92d237b7b274d4606a616e071c4e") 274 | }); 275 | 276 | test.should("parse Bitbucket Server with subfolder", () => { 277 | var res = gitUrlParse("https://bitbucket.mycompany.com/projects/owner/repos/name/browse/entities/filename.yaml") 278 | test.expect(res.filepath).toBe("entities/filename.yaml"); 279 | }); 280 | 281 | // https cloudforge 282 | test.should("parse CloudForge urls", () => { 283 | var res = gitUrlParse("https://owner@organization.git.cloudforge.com/name.git"); 284 | test.expect(res.source).toBe("cloudforge.com"); 285 | test.expect(res.owner).toBe("owner"); 286 | test.expect(res.organization).toBe("organization"); 287 | test.expect(res.name).toBe("name"); 288 | }); 289 | 290 | // https Azure DevOps (formerly Visual Studio Team Services) 291 | test.should("parse Azure DevOps HTTPS urls", () => { 292 | // Parse URL for matching project and repo names 293 | var res = gitUrlParse("https://dev.azure.com/MyOrganization/MatchedName/MyTeam/_git/MatchedName?path=%2Ftest%2Findex.js"); 294 | test.expect(res.source).toBe("azure.com"); 295 | test.expect(res.owner).toBe("MatchedName"); 296 | test.expect(res.name).toBe("MatchedName"); 297 | test.expect(res.filepath).toBe("test/index.js"); 298 | 299 | // Parse URL for non-matching project and repo names 300 | res = gitUrlParse("https://dev.azure.com/MyOrganization/MyProject/_git/MyRepo?path=%2Ftest%2Findex.js"); 301 | test.expect(res.source).toBe("azure.com"); 302 | test.expect(res.owner).toBe("MyProject"); 303 | test.expect(res.name).toBe("MyRepo"); 304 | test.expect(res.filepath).toBe("test/index.js"); 305 | }); 306 | 307 | // https Azure DevOps (formerly Visual Studio Team Services) 308 | test.should("parse Azure DevOps HTTPS urls with branch", () => { 309 | // Parse URL for matching project and repo names 310 | var res = gitUrlParse("https://dev.azure.com/MyOrganization/MatchedName/MyTeam/_git/MatchedName?path=%2Ftest%2Findex.js&version=GBother"); 311 | test.expect(res.source).toBe("azure.com"); 312 | test.expect(res.owner).toBe("MatchedName"); 313 | test.expect(res.name).toBe("MatchedName"); 314 | test.expect(res.filepath).toBe("test/index.js"); 315 | test.expect(res.ref).toBe("other"); 316 | 317 | // Parse URL for non-matching project and repo names with branch 318 | res = gitUrlParse("https://dev.azure.com/MyOrganization/MyProject/_git/MyRepo?path=%2Ftest%2Findex.js&version=GBother"); 319 | test.expect(res.source).toBe("azure.com"); 320 | test.expect(res.owner).toBe("MyProject"); 321 | test.expect(res.name).toBe("MyRepo"); 322 | test.expect(res.filepath).toBe("test/index.js"); 323 | test.expect(res.ref).toBe("other"); 324 | }); 325 | 326 | // ssh Azure DevOps (formerly Visual Studio Team Services) 327 | test.should("parse Azure DevOps SSH urls", () => { 328 | // Parse URL for matching project and repo names 329 | var res = gitUrlParse("git@ssh.dev.azure.com:v3/CompanyName/ProjectName/RepoName"); 330 | test.expect(res.source).toBe("dev.azure.com"); 331 | test.expect(res.owner).toBe("ProjectName"); 332 | test.expect(res.name).toBe("RepoName"); 333 | 334 | // Parse URL for URL-encoded spaces 335 | var res = gitUrlParse("git@ssh.dev.azure.com:v3/CompanyName/Project%20Name/Repo%20Name"); 336 | test.expect(res.source).toBe("dev.azure.com"); 337 | test.expect(res.owner).toBe("Project Name"); 338 | test.expect(res.name).toBe("Repo Name"); 339 | }); 340 | 341 | // https Visual Studio Team Services (VSTS) 342 | test.should("parse Visual Studio Team Services (VSTS) HTTPS urls", () => { 343 | var res = gitUrlParse("https://companyname.visualstudio.com/_git/MyProject"); 344 | test.expect(res.source).toBe("visualstudio.com"); 345 | test.expect(res.owner).toBe("MyProject"); 346 | test.expect(res.name).toBe("MyProject"); 347 | test.expect(res.toString()).toBe("https://companyname.visualstudio.com/_git/MyProject"); 348 | 349 | res = gitUrlParse("https://companyname.visualstudio.com/MyProject/_git/MyRepo"); 350 | test.expect(res.source).toBe("visualstudio.com"); 351 | test.expect(res.owner).toBe("MyProject"); 352 | test.expect(res.name).toBe("MyRepo"); 353 | test.expect(res.toString()).toBe("https://companyname.visualstudio.com/MyProject/_git/MyRepo"); 354 | 355 | // Legacy URLs 356 | res = gitUrlParse("https://companyname.visualstudio.com/DefaultCollection/_git/MyRepo"); 357 | test.expect(res.source).toBe("visualstudio.com"); 358 | test.expect(res.owner).toBe("MyRepo"); 359 | test.expect(res.name).toBe("MyRepo"); 360 | test.expect(res.organization).toBe("DefaultCollection"); 361 | test.expect(res.toString()).toBe("https://companyname.visualstudio.com/DefaultCollection/_git/MyRepo"); 362 | 363 | res = gitUrlParse("https://companyname.visualstudio.com/DefaultCollection/MyProject/_git/MyRepo"); 364 | test.expect(res.source).toBe("visualstudio.com"); 365 | test.expect(res.owner).toBe("MyProject"); 366 | test.expect(res.name).toBe("MyRepo"); 367 | test.expect(res.organization).toBe("DefaultCollection"); 368 | test.expect(res.toString()).toBe("https://companyname.visualstudio.com/DefaultCollection/MyProject/_git/MyRepo"); 369 | }); 370 | 371 | // ssh Visual Studio Team Services (VSTS) 372 | test.should("parse Visual Studio Team Services (VSTS) SSH urls", () => { 373 | var res = gitUrlParse("CompanyName@vs-ssh.visualstudio.com:v3/CompanyName/ProjectName/RepoName"); 374 | test.expect(res.source).toBe("visualstudio.com"); 375 | test.expect(res.owner).toBe("ProjectName"); 376 | test.expect(res.name).toBe("RepoName"); 377 | }); 378 | 379 | // custom git hosted URL with 2 parts SLD 380 | test.should("parse Gih hosted urls with two parts SLD", () => { 381 | var res = gitUrlParse("https://domain.git.com.cn/owner/name.git"); 382 | test.expect(res.source).toBe("git.com.cn"); 383 | test.expect(res.owner).toBe("owner"); 384 | test.expect(res.name).toBe("name"); 385 | }); 386 | 387 | // Handle URL encoded names of owners and repositories 388 | test.should("https URLs with URL encoded characters", () => { 389 | var res = gitUrlParse("https://companyname.visualstudio.com/My%20Project/_git/My%20Repo"); 390 | test.expect(res.source).toBe("visualstudio.com"); 391 | test.expect(res.owner).toBe("My Project"); 392 | test.expect(res.name).toBe("My Repo"); 393 | }); 394 | 395 | // ref and filepath urls 396 | test.should("parse ref/filepath urls", () => { 397 | var res = gitUrlParse(URLS.ref); 398 | test.expect(res.protocol).toBe("https"); 399 | test.expect(res.source).toBe("github.com"); 400 | test.expect(res.owner).toBe("42IonicaBizau"); 401 | test.expect(res.name).toBe("git-url-parse"); 402 | test.expect(res.href).toBe(URLS.ref); 403 | test.expect(res.ref).toBe("master"); 404 | test.expect(res.filepathtype).toBe("blob"); 405 | test.expect(res.filepath).toBe("test/index.js"); 406 | 407 | function testForFilepathtypeURL(type) { 408 | res = gitUrlParse(`https://gitlab.com/a/b/c/d/${type}/master/test/index.js`); 409 | 410 | test.expect(res.protocol).toBe("https"); 411 | test.expect(res.source).toBe("gitlab.com"); 412 | test.expect(res.owner).toBe("a/b/c"); 413 | test.expect(res.name).toBe("d"); 414 | test.expect(res.href).toBe(`https://gitlab.com/a/b/c/d/${type}/master/test/index.js`); 415 | test.expect(res.ref).toBe("master"); 416 | test.expect(res.filepathtype).toBe(type); 417 | test.expect(res.filepath).toBe("test/index.js"); 418 | } 419 | 420 | // execute for raw, src, blob, and tree 421 | ['raw', 'blob', 'tree', 'edit'].forEach(testForFilepathtypeURL); 422 | }); 423 | 424 | // shorthand urls 425 | test.should("parse shorthand urls", () => { 426 | var res = gitUrlParse(URLS.shorthand); 427 | test.expect(res.owner).toBe("42IonicaBizau"); 428 | test.expect(res.name).toBe("git-url-parse"); 429 | test.expect(res.href).toBe(res.toString()); 430 | test.expect(res.full_name).toBe("42IonicaBizau/git-url-parse"); 431 | }); 432 | 433 | test.should("parse subdomains", () => { 434 | var res = gitUrlParse("https://gist.github.com/owner/id"); 435 | test.expect(res.source).toBe("github.com"); 436 | test.expect(res.resource).toBe("gist.github.com"); 437 | test.expect(res.owner).toBe("owner"); 438 | test.expect(res.name).toBe("id"); 439 | test.expect(res.toString()).toBe("https://gist.github.com/owner/id"); 440 | 441 | res = gitUrlParse("https://gist.github.com/id"); 442 | test.expect(res.source).toBe("github.com"); 443 | test.expect(res.resource).toBe("gist.github.com"); 444 | test.expect(res.owner).toBe(""); 445 | test.expect(res.name).toBe("id"); 446 | test.expect(res.toString()).toBe("https://gist.github.com/id"); 447 | }); 448 | 449 | // subgroups 450 | test.should("parse gitlab subgroups", () => { 451 | var res = gitUrlParse("https://gitlab.com/group/subgroup/id"); 452 | test.expect(res.protocol).toBe("https"); 453 | test.expect(res.source).toBe("gitlab.com"); 454 | test.expect(res.owner).toBe("group/subgroup"); 455 | test.expect(res.name).toBe("id"); 456 | test.expect(res.href).toBe("https://gitlab.com/group/subgroup/id"); 457 | test.expect(res.toString("https")).toBe("https://gitlab.com/group/subgroup/id"); 458 | test.expect(res.toString("git+ssh")).toBe("git+ssh://git@gitlab.com/group/subgroup/id"); 459 | test.expect(res.toString("ssh")).toBe("git@gitlab.com:group/subgroup/id"); 460 | test.expect(res.toString()).toBe("https://gitlab.com/group/subgroup/id"); 461 | 462 | res = gitUrlParse("git@gitlab.com:a/b/c/d.git"); 463 | test.expect(res.protocol).toBe("ssh"); 464 | test.expect(res.source).toBe("gitlab.com"); 465 | test.expect(res.owner).toBe("a/b/c"); 466 | test.expect(res.name).toBe("d"); 467 | test.expect(res.href).toBe("git@gitlab.com:a/b/c/d.git"); 468 | test.expect(res.toString("https")).toBe("https://gitlab.com/a/b/c/d.git"); 469 | test.expect(res.toString("git+ssh")).toBe("git+ssh://git@gitlab.com/a/b/c/d.git"); 470 | test.expect(res.toString("ssh")).toBe("git@gitlab.com:a/b/c/d.git"); 471 | test.expect(res.toString()).toBe("git@gitlab.com:a/b/c/d.git"); 472 | }); 473 | 474 | test.should("stringify token", () => { 475 | var res = gitUrlParse("https://github.com/owner/name.git"); 476 | res.token = "token"; 477 | test.expect(res.toString()).toBe("https://token@github.com/owner/name.git"); 478 | 479 | var res = gitUrlParse("https://gitlab.com/group/subgroup/name.git"); 480 | res.token = "token"; 481 | test.expect(res.toString()).toBe("https://token@gitlab.com/group/subgroup/name.git"); 482 | 483 | var res = gitUrlParse("https://owner@bitbucket.org/owner/name"); 484 | res.token = "token"; 485 | test.expect(res.toString()).toBe("https://x-token-auth:token@bitbucket.org/owner/name"); 486 | 487 | var res = gitUrlParse("git@github.com:owner/name"); 488 | res.port = 22; 489 | test.expect(res.toString()).toBe("ssh://git@github.com:22/owner/name"); 490 | 491 | var res = gitUrlParse("user@github.com:owner/name.git"); 492 | test.expect(res.toString()).toBe("user@github.com:owner/name.git"); 493 | 494 | var res = gitUrlParse("git@github.com:owner/name.git"); 495 | res.port = 22; 496 | res.user = "user"; 497 | test.expect(res.toString()).toBe("ssh://user@github.com:22/owner/name.git"); 498 | 499 | var res = gitUrlParse("git+ssh://git@github.com/owner/name.git"); 500 | res.port = 22; 501 | res.user = "user"; 502 | test.expect(res.toString()).toBe("git+ssh://user@github.com:22/owner/name.git"); 503 | 504 | var res = gitUrlParse("https://github.com/owner/name.git"); 505 | res.user = "user"; 506 | test.expect(res.toString()).toBe("https://user@github.com/owner/name.git"); 507 | 508 | var res = gitUrlParse("http://github.com/owner/name.git"); 509 | res.user = "user"; 510 | test.expect(res.toString()).toBe("http://user@github.com/owner/name.git"); 511 | 512 | var res = gitUrlParse("https://dev.azure.com/organization/owner%20with%20space/_git/repo%20with%20space") 513 | res.user = "pat" 514 | test.expect(res.toString()).toBe("https://pat@dev.azure.com/organization/owner%20with%20space/_git/repo%20with%20space") 515 | }); 516 | 517 | // commits 518 | test.should("parse commit urls", () => { 519 | var res = gitUrlParse(URLS.commit); 520 | test.expect(res.name).toBe("git-url-parse"); 521 | test.expect(res.commit).toBe("9c6443245ace92d237b7b274d4606a616e071c4e"); 522 | }); 523 | 524 | test.should("parse issues urls", () => { 525 | var res = gitUrlParse(URLS.issue); 526 | test.expect(res.name).toBe("git-url-parse"); 527 | test.expect(res.organization).toBe("IonicaBizau"); 528 | }); 529 | 530 | // blob in repo path: https://github.com/IonicaBizau/git-url-parse/issues/167 531 | test.should("handle 'blob' in repo path", () => { 532 | var res = gitUrlParse("https://github.com/owner/id/tree/main/pkg/blob/data.yaml"); 533 | test.expect(res.source).toBe("github.com"); 534 | test.expect(res.owner).toBe("owner"); 535 | test.expect(res.name).toBe("id"); 536 | test.expect(res.pathname).toBe("/owner/id/tree/main/pkg/blob/data.yaml"); 537 | test.expect(res.filepath).toBe("pkg/blob/data.yaml"); 538 | test.expect(res.toString()).toBe("https://github.com/owner/id"); 539 | }); 540 | 541 | test.should("parse branch names with slashes, when refs are provided", () => { 542 | var res = gitUrlParse("https://github.com/owner/id/blob/branch1/branch2/data.yaml", ["branch1/branch2", "branch1"]); 543 | test.expect(res.pathname).toBe("/owner/id/blob/branch1/branch2/data.yaml"); 544 | test.expect(res.ref).toBe("branch1/branch2"); 545 | test.expect(res.filepath).toBe("data.yaml"); 546 | var res = gitUrlParse("https://github.com/owner/id/blob/branch1/branch2/branch3/folder/data.yaml", ["branch1/branch2/branch3", "branch1"]); 547 | test.expect(res.ref).toBe("branch1/branch2/branch3"); 548 | test.expect(res.filepath).toBe("folder/data.yaml"); 549 | var res = gitUrlParse("https://github.com/owner/id/blob/main/folder/data.yaml", ["branch1/branch2/branch3", "main"]); 550 | test.expect(res.ref).toBe("main"); 551 | test.expect(res.filepath).toBe("folder/data.yaml"); 552 | }); 553 | }); 554 | --------------------------------------------------------------------------------