├── .editorconfig ├── .eslintrc.json ├── .github ├── FUNDING.yml └── workflows │ ├── codeql-analysis.yml │ ├── greetings.yml │ └── publish.yml ├── .gitignore ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SECURITY.md ├── icon.drawio.png ├── package-lock.json ├── package.json ├── resources └── media │ ├── flow.svg │ ├── mfw.png │ ├── qp.png │ └── ss.png ├── src ├── ViewBranches.ts ├── ViewVersions.ts ├── extension.ts └── lib │ ├── Util.ts │ ├── cm.ts │ ├── decorators.ts │ ├── disposables.ts │ ├── git-base.d.ts │ ├── git.d.ts │ └── logger.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.yml] 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | }, 19 | "ignorePatterns": [ 20 | "out", 21 | "dist", 22 | "**/*.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: serhioromano 2 | custom: https://www.paypal.com/donate/?hosted_button_id=UTU4EMPLLLX54 3 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ main ] 9 | schedule: 10 | - cron: '37 10 * * 4' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | language: [ 'typescript' ] 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v2 29 | 30 | - name: Initialize CodeQL 31 | uses: github/codeql-action/init@v1 32 | with: 33 | languages: ${{ matrix.language }} 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v1 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v1 40 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: 4 | pull_request: 5 | types: [ opened ] 6 | issues: 7 | types: [ opened ] 8 | 9 | jobs: 10 | greeting: 11 | name: Greet new participant 12 | runs-on: ubuntu-latest 13 | permissions: 14 | issues: write 15 | pull-requests: write 16 | steps: 17 | - uses: actions/first-interaction@v1 18 | with: 19 | repo-token: ${{ secrets.GITHUB_TOKEN }} 20 | issue-message: "## Welcome!\n\nThank you for finding time to write an issue and help to make this extension better. I`ll appreciate if you find some time to [rate this extension here](https://marketplace.visualstudio.com/items?itemName=Serhioromano.vscode-gitflow&ssr=false#review-details).\n\nI`ll get to this Issue ASAP." 21 | pr-message: "## Welcome!\n\nThank you very match for your contribution. I very appreciate it. Please find some time to [rate this extension here](https://marketplace.visualstudio.com/items?itemName=Serhioromano.vscode-gitflow&ssr=false#review-details).\n\nI`ll get to this PR ASAP." 22 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Publish 5 | 6 | on: 7 | push: 8 | tags: 9 | - v* 10 | jobs: 11 | publish: 12 | name: Publish VS Code Extension 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout Tag 17 | uses: actions/checkout@v4 18 | with: 19 | ref: ${{ github.event.push.ref }} 20 | - name: Use Node.js 20.* 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: '20' 24 | cache: 'npm' 25 | - name: Install NPM Packages 26 | run: | 27 | npm -v 28 | npm ci 29 | npm install -g vsce ovsx semver 30 | - name: Create Package 31 | run: vsce package 32 | - name: Publish Package 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | run: | 36 | file=$(realpath $(ls -t *.vsix | head -1)) 37 | vsce publish -i $file -p ${{ secrets.VSCE_TOKEN }} 38 | npx ovsx publish $file -p ${{ secrets.OVSX_TOKEN }} 39 | gh release create '${{ github.ref_name }}' -n "See [Changelog](https://github.com/Serhioromano/vscode-gitflow/blob/master/CHANGELOG.md) for details." $file 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out 3 | .vscode-test 4 | *.vsix 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "printWidth": 110, 4 | "tabWidth": 4, 5 | "useTabs": false, 6 | "semi": true, 7 | "bracketSpacing": false, 8 | "parser":"typescript", 9 | "endOfLine": "lf", 10 | "singleQuote": false 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "akamud.vscode-theme-onelight", 5 | "steoates.autoimport", 6 | "alefragnani.bookmarks", 7 | "streetsidesoftware.code-spell-checker", 8 | "editorconfig.editorconfig", 9 | "serhioromano.vscode-gitflow", 10 | "mhutchie.git-graph", 11 | "christian-kohler.npm-intellisense", 12 | "eg2.vscode-npm-script", 13 | "streetsidesoftware.code-spell-checker-russian", 14 | "ms-ceintl.vscode-language-pack-ru", 15 | "ryu1kn.text-marker", 16 | "wayou.vscode-todo-highlight", 17 | "yzhang.markdown-all-in-one", 18 | "jebbs.markdown-extended", 19 | "davidanson.vscode-markdownlint", 20 | "tgreen7.vs-code-node-require", 21 | "github.vscode-github-actions" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Launch Extension", 10 | "args": [ 11 | "--profile=ExtensionDebug", 12 | "--extensionDevelopmentPath=${workspaceFolder}" 13 | ], 14 | "outFiles": [ 15 | "${workspaceFolder}/out/**/*.js" 16 | ], 17 | "preLaunchTask": "npm: compile", 18 | "request": "launch", 19 | "type": "extensionHost" 20 | }, 21 | { 22 | "name": "Run Extension", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "args": [ 26 | "--extensionDevelopmentPath=${workspaceFolder}" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/out/**/*.js" 30 | ], 31 | "preLaunchTask": "watch" 32 | }, 33 | { 34 | "name": "Extension Tests", 35 | "type": "extensionHost", 36 | "request": "launch", 37 | "args": [ 38 | "--extensionDevelopmentPath=${workspaceFolder}", 39 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 40 | ], 41 | "outFiles": [ 42 | "${workspaceFolder}/out/test/**/*.js" 43 | ], 44 | "preLaunchTask": "${defaultBuildTask}" 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | "markdownlint.config": { 10 | "MD046": false 11 | }, 12 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 13 | "typescript.tsc.autoDetect": "off", 14 | "cSpell.words": [ 15 | "endregion", 16 | "esbuild", 17 | "gitflow", 18 | "keeplocal", 19 | "keepremote", 20 | "nodevelopmerge", 21 | "outfile", 22 | "pushdevelop", 23 | "pushproduction", 24 | "pushtag", 25 | "retc", 26 | "showcommands", 27 | "vsix" 28 | ], 29 | "workbench.colorTheme": "Default Dark Modern", 30 | "[typescript]": { 31 | "editor.defaultFormatter": "vscode.typescript-language-features" 32 | }, 33 | "peacock.remoteColor": "#123f78", 34 | "workbench.colorCustomizations": { 35 | "activityBar.activeBackground": "#1956a4", 36 | "activityBar.activeBorder": "#ee90bb", 37 | "activityBar.background": "#1956a4", 38 | "activityBar.foreground": "#e7e7e7", 39 | "activityBar.inactiveForeground": "#e7e7e799", 40 | "activityBarBadge.background": "#e65997", 41 | "activityBarBadge.foreground": "#15202b", 42 | "sash.hoverBorder": "#1956a4", 43 | "statusBar.background": "#123f78", 44 | "statusBar.foreground": "#e7e7e7", 45 | "statusBarItem.hoverBackground": "#1956a4", 46 | "statusBarItem.remoteBackground": "#123f78", 47 | "statusBarItem.remoteForeground": "#e7e7e7", 48 | "titleBar.activeBackground": "#123f78", 49 | "titleBar.activeForeground": "#e7e7e7", 50 | "titleBar.inactiveBackground": "#123f7899", 51 | "titleBar.inactiveForeground": "#e7e7e799", 52 | "commandCenter.border": "#e7e7e799" 53 | }, 54 | "editor.stickyScroll.enabled": true, 55 | "files.autoSave": "afterDelay", 56 | "spellright.language": [ 57 | "en" 58 | ], 59 | "spellright.documentTypes": [ 60 | "markdown", 61 | "latex", 62 | "plaintext" 63 | ], 64 | "gitflow.replaceSymbol": "-" 65 | } 66 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .github/** 3 | .vscode-test/** 4 | src/** 5 | out/test/** 6 | .gitignore 7 | .yarnrc 8 | vsc-extension-quickstart.md 9 | **/tsconfig.json 10 | **/.eslintrc.json 11 | **/.editorconfig 12 | **/.prettier 13 | **/*.map 14 | **/*.ts 15 | node_modules/** 16 | resources/media/icon.psd 17 | resources/media/mfw.png 18 | resources/media/ss.png 19 | resources/media/qp.png 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "gitflow" extension will be documented in this file. 4 | 5 | ## [1.3.28] 03/17/2025 6 | 7 | - add - Setting to disable gitflow check on repository open. It also adds button to disable check on warning. 8 | 9 | ## [1.3.23] 07/20/2023 10 | 11 | - add - Extension parameter to turn notification off 12 | 13 | ## [1.3.18] 06/12/2022 14 | 15 | - add - publish to OVSX. 16 | - enhance - Branch name creation now is checked through `git check-ref-format --branch ***` with allows create any qualified branch name. 17 | 18 | ## [1.3.10] 05/25/2022 19 | 20 | - add - Option to automatically bump version on release or not. 21 | 22 | ## [1.3.9] 05/18/2022 23 | 24 | - add - replace spaces in branch name with `_` 25 | - fix - tmp dir for message files on release and hotfix 26 | - add - parameter `gitflow.path` to manually set gitflow executable. 27 | - fix - Changelog automatic update month is 1 less. 28 | 29 | ## [1.3.4] 05/10/2022 30 | 31 | - fix - Month updated in changelog one month less. 32 | - fix - Some words in command message `-m"Something"` caused command failed. Fixed by using file. 33 | 34 | ## [1.3.0] 03/27/2022 35 | 36 | - add - use `git.path` settings. 37 | - enhance - better find git executable path on windows. 38 | 39 | ## [1.2.9] 03/07/2022 40 | 41 | - security - fix string sanitization 42 | 43 | ## [1.2.8] 03/07/2022 44 | 45 | - fix - stop command on ESC. 46 | - fix - run version bump only when released or hotfix is started 47 | - enhance - `CHANGELOG.md` update conditions 48 | 49 | ## [1.2.7] 03/04/2022 50 | 51 | - fix - changelog update `mm` not to be replaced in a word like co**mm**and. 52 | - fix - release message is undefined 53 | 54 | ## [1.2.0] 03/04/2022 55 | 56 | - change - Now version bump happens on hotfix or release start rather than finish 57 | - add - update changelog automatically 58 | - enhance - Allow change finish message when finish hotfix or release 59 | 60 | ## [1.1.0] 02/21/2022 61 | 62 | - optimize - performance improvement using memoization technique. 63 | 64 | ## [1.0.1] 02/07/2022 65 | 66 | Finlay I have all features I planned in this extension. This is still preview but final extension. 67 | 68 | - add - Parameter to show all internal git commands run in `git flow` in output window 69 | - add - Output logger named Git Flow 70 | - fix - Readme was not visible in marketplace. 71 | - optimize - code refactor. 72 | 73 | ## [0.5.11] 02/05/2022 74 | 75 | - add - Status bar button to call Quick Pick menu 76 | - add - Groups in Quick Pick (require VS Code >=1.64.0) 77 | 78 | ## [0.5.9] 02/04/2022 79 | 80 | - add - Checkout support branch 81 | - add - Publish support branch 82 | - fix - Checkout release branch 83 | - add - Delete tags local and remote 84 | - fix - Release checkout 85 | 86 | ## [0.5.6] 02/03/2022 87 | 88 | - fix - If error tags shows error as tags 89 | - fix - important to update. Fix some release issues. 90 | 91 | ## [0.5.2] 02/03/2022 92 | 93 | - change - use different bundler for extension 94 | - add - Quick Pick popup 95 | - optimize - Code was refactored and optimized. 1800 lines to 580. 96 | 97 | ## [0.4.3] 02/02/2022 98 | 99 | - add - Support for multiple folder workspaces 100 | - fix - Progress notification not resolving 101 | 102 | ## [0.3.0] 02/02/2022 103 | 104 | - add - Support (Start, Checkout, Rebase, Delete) 105 | - add - Release checkout 106 | - add - Hotfix checkout 107 | 108 | ## [0.2.14] 01/31/2022 109 | 110 | - enhance - Order and group context menu elements. 111 | - enhance - use VS Code theme icons instead of SVG. This will apply Product icons theme to this extension too. 112 | - enhance - not related to features of this extension, but CI flow was created for fast delivery of new versions. 113 | - enhance - README overview updated with new features and new picture. 114 | 115 | ## [0.2.11] 01/30/2022 116 | 117 | - fix - take name of flow branches from configuration 118 | - fix - ui buttons 119 | 120 | ## [0.2.9] 01/28/2022 121 | 122 | - add - single command to sync all root branches (develop and master ot main) 123 | - add - command to checkout root branches 124 | - enhance - better icons 125 | 126 | ## [0.2.5] - 01/27/2022 127 | 128 | - fix - Git authenticated commands to remote 129 | - add - icons to menu elements 130 | - add - bugfix support 131 | - delete - configurations 132 | - add - progress notification during process 133 | - Update `README.md` with important instructions 134 | 135 | ## [0.1.5] - 01/26/2022 136 | 137 | - Initial release 138 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Sergey Romanov 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 | # Git FLow Support for VS Code 2 | 3 | > **Attention!!!**. From version 0.5.11 it requires latest VS Code `>=1.64.0`. 4 | 5 | 6 | > Looking for an artist to contribute the extension icon. 7 | 8 | ## Known Issues 9 | 10 | 1. On MAC you might need to configure `gitflow.path` parameter to `/usr/local/bin/git-flow`. 11 | 2. If you name your branch folders with capital letters like `Feature` instead of `feature`, things break due to a bug in the `git-flow` extension of git CLI command. Please make sure you name your branch folders with lower-case names. 12 | 3. When VS Code opens a Windows directory while on a remote WSL, sometimes this might cause problems. But who is gonna do that, right? 13 | 4. When authentication is not configured, commands that make `push` to remote commands may fail. (see: [How to setup](#how-to-setup)) 14 | 15 | ## What is Git Flow 16 | 17 | Git Flow is an abstract idea of a Git workflow. It helps with continuous software development and implementing DevOps practices. The Git Flow Workflow defines a strict branching model designed around the project release. This provides a robust framework for managing larger projects. 18 | 19 | Git Flow is ideally suited for projects that have a scheduled release cycle and for the DevOps best practice of continuous delivery. It assigns very specific roles to different branches and defines how and when they should interact. It uses individual branches for preparing, maintaining, and recording releases. 20 | 21 | [Read more](https://blog.knoldus.com/introduction-to-git-flow/). 22 | 23 | ## How to use 24 | 25 | When installed, you will find 2 new views in SCM side bar, GITFLOW and VERSIONS. Also in status bar you will find **Git Flow** button to launch Quick Pick menu, or you can use `Shift`+`Alt`+`D` short key. 26 | 27 | To see list of all commands use `F1` or `Ctrl`+`Shift`+`P` and type GitFlow. 28 | 29 | ![ext.](https://raw.githubusercontent.com/Serhioromano/vscode-gitflow/main/resources/media/ss.png) 30 | 31 | > We suggest [Git Graph](https://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph) to complement this extension. 32 | 33 | ## Features 34 | 35 | All basic operations you need to do in a single place. 36 | 37 | ### GitFlow 38 | 39 | - Init git flow 40 | - Feature (Start, Finish, Checkout, Delete, Rebase, Publish, Track) 41 | - Bugfix (Start, Finish, Checkout, Delete, Rebase, Publish, Track) 42 | - Release (Start, Finish, Checkout, Delete, Rebase, Publish, Track) 43 | - Hotfix (Start, Finish, Checkout, Delete, Rebase, Publish) 44 | - Support (Start, Checkout, Rebase, Publish, Delete) (See: [How to work with support](#how-to-work-with-support)) 45 | 46 | ### Additional 47 | 48 | - Checkout root branches 49 | - Sync all root branches 50 | - Fetch from origin 51 | - Push all tags 52 | - Push local tag 53 | - Delete tag locally and\or remotely 54 | - Automatic version update in `package.json` 55 | - Automatic update `CHANGELOG.md` 56 | - Multiple folder workspace support 57 | - Quick Pick menu (use `Shift`+`Alt`+`D`) 58 | - All commands results are outputted in output window named `Git Flow`. There is a parameter that also allow to show all internal `git` commands run inside `git flow`. 59 | 60 | ### Options 61 | 62 | - `gitflow.showNotification` - Show notification that current directory is not Git Flow enabled. 63 | - `gitflow.showAllCommands` - This option allows to see in GitFlow output window underground git commands run by git-flow. 64 | - `gitflow.path` - Allow manually set path for Git Flow executable including `flow`. For instance `/usr/bit/git flow`. 65 | - `gitflow.autoBumpVersion` - Either it should automatically bump a version in `package.json` file on `feature` or `hotfix` creation, and commit it to git. 66 | 67 | ## Feature Details 68 | 69 | ### Multiple Folder Workspace 70 | 71 | Multiple folder workspace was long awaited feature of VS Code for many people. It would be a shame not to support it. 72 | 73 | ![GitFlow multiple folder workspace](https://raw.githubusercontent.com/Serhioromano/vscode-gitflow/main/resources/media/mfw.png) 74 | 75 | ### Quick Pick 76 | 77 | Quick Pick is a popup with essential Git Flow commands, like creating a new flow branch or applying actions to the currently selected flow brunch. You can call it with `Shift`+`Alt`+`d` short key. Note this command is available only if extension was initialized successfully. 78 | 79 | ![Git flow quick pick](https://raw.githubusercontent.com/Serhioromano/vscode-gitflow/main/resources/media/qp.png) 80 | 81 | ### Automatic version bump 82 | 83 | This extension can automatically update your `package.json` file on creating a new tag - but only on `release` and `hotfix` branches. When you create one, as a name use version standard. For example create a `1.0.1` release which will result in a `release/1.0.1` branch. The `version` property of `package.json` will be updated to `1.0.1` and automatically committed to git. 84 | 85 | ### Automatic changelog update 86 | 87 | This extension can automatically update your `CHANGELOG.md`. If you have there something like 88 | 89 | ```md 90 | ## [Unreleased] - yyyy-mm-dd 91 | 92 | or 93 | 94 | ### [UNRELEASED] (DD-MM-YYYY) 95 | ``` 96 | 97 | Or any combination of `[Unreleased]`, `[unreleased]`, `[UNRELEASED]`, `yyyy`, `mm` or `dd` and all uppercase variations, these will be replaced with the relevant info. 98 | 99 | ### How to work with Support branch 100 | 101 | #### What is Git Flow Support branch for? 102 | 103 | Support branches are similar to LTS version of Linux distros. 104 | 105 | In the git-flow model, your **latest released** version actually maps to the `master` or `main`, while your "preview release" maps to a git-flow release branch. It is forked from develop and finally merged into `main` when the actual release happens. Then this will become your **latest release** and you will usually fix only bugs for that release, using git-flow hotfix branches. In this way, your `main` always represents the most stable state of your latest released version. 106 | 107 | Say you had a project, and you were happily releasing new versions. Maybe your current production version was 8.x. But you had some Really Important Customers who refused to upgrade to anything after 6.0. Now, if someone found a security flaw in the 6.0 version of your project, it would be a bad idea to hang all those Really Important Customers out to dry. So you release a new hotfix against `support/6.0`, even though all their problems would be solved if they just upgraded to the new 8.x release. 108 | 109 | For this to happen you have to create `support/6.0` at some point of time. Basically you can create support branch on all major version change. 110 | 111 | #### Workflow 112 | 113 | First create your support branch. When you create you can select the tag version to start from. Use the latest version in major set. 114 | 115 | Now if you checkout any support branch, no matter what you start - hotfix, release, bugfix or feature - you will be prompted to confirm to start it based on currently active support branch. And if you started it based on support branch, when you finish your hotfix, release, bugfix or feature, it will be finished against that support branch and not to `main` or `develop` branches. 116 | 117 | Thus your `master` or `main` branch contain most recent version of your product and support branches have major LTS versions. 118 | 119 | ## How to setup 120 | 121 | ### Working locally 122 | 123 | 1. VS Code should be open on a folder not file 124 | 2. Git must be installed. 125 | 3. Root folder must be a git repository. If not run `git init` command in the terminal. 126 | 4. [Git Flow](https://github.com/petervanderdoes/gitflow-avh/wiki/Installation) must be installed. 127 | 5. You have to initialize git flow in the root of your repository with `git flow init` command. 128 | 129 | ### Working remotely 130 | 131 | In order to push branches to or delete branches from a remote repository like GitHub, you must be authenticated. For GitHub there are 2 main ways to work with repositories - over SSH protocol or over HTTPS. Those 2 different protocols usually refer to repository with different URL. Here is example of the SSH and HTTPS URLs for this extension. 132 | 133 | ```text 134 | https://github.com/Serhioromano/vscode-gitflow.git 135 | git@github.com:Serhioromano/vscode-gitflow.git 136 | ``` 137 | 138 | You can clone a repository with either URL. 139 | 140 | #### SSH (recommended) 141 | 142 | First ensure your repository is configured to work over SSH. 143 | 144 | ```bash 145 | git remote remove origin 146 | git remote add origin git@github.com:user/repository.git 147 | ``` 148 | 149 | Or simply edit `your_repository/.git/config` and make sure repository URL there has a SSH link. 150 | 151 | Read [this article](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account) for how to authorize your PC with SSH key. 152 | 153 | Basically what you have to do is to generate key with 154 | 155 | ```bash 156 | ssh-keygen -t rsa -b 4096 -C "your_email@example.com" 157 | ``` 158 | 159 | Then go to `~/.ssh` folder and look for `id_rsa.pub` file and copy it's content. Lastly go to `https://github.com/settings/keys` and add SSH Key there. 160 | 161 | #### HTTPS 162 | 163 | First ensure your repository is configured to work over SSH. 164 | 165 | ```bash 166 | git remote remove origin 167 | git remote add origin https://github.com/user/repository.git 168 | ``` 169 | 170 | Or simple edit `your_repository/.git/config` and make sure the repository URL there has a HTTP link. 171 | 172 | Now you need to [cache your credential](https://docs.github.com/en/get-started/getting-started-with-git/caching-your-github-credentials-in-git). Use the GitHub CLI. 173 | 174 | GitHub CLI will automatically store your Git credentials for you when you choose HTTPS as your preferred protocol for Git operations and answer "yes" to the prompt asking if you would like to authenticate to Git with your GitHub credentials. 175 | 176 | - [Install GitHub CLI](https://cli.github.com/manual/installation) on macOS, Windows, or Linux. 177 | - In the command line, enter `gh auth login`, then follow the prompts. 178 | 179 | ## Changelog 180 | 181 | - 1.3.28 182 | - add - Parameter to disable gitflow check on the repository 183 | - 1.3.23 184 | - add - Extension parameter to turn notification off 185 | - 1.3.18 186 | - add - publish to OVSX. 187 | - enhance - Branch name creation now is checked through `git check-ref-format --branch ***` with allows create any qualified branch name. 188 | - add - Option to automatically bump version on release or not. 189 | - add - replace spaces in branch name with `_` 190 | - fix - `tmp` directory for message files on release and hotfix 191 | - add - parameter `gitflow.path` to manually set GitFlow executable. 192 | - fix - Month updated in changelog one month less. 193 | - fix - Some words in command message `-m"Something"` caused command failed. Fixed by using file. 194 | - add - use `git.path` settings. 195 | - enhance - better find git executable path on windows. 196 | - 1.2.9 197 | - security - fix string sanitization 198 | - fix - stop command on ESC. 199 | - fix - run version bump only when released or hotfix is started 200 | - enhance - `CHANGELOG.md` update conditions 201 | - fix - changelog update `mm` not to be replaced in a word like co**mm**and. 202 | - fix - release message is undefined 203 | - change - Now version bump happens on hotfix or release start rather than finish 204 | - add - update changelog automatically 205 | - enhance - Allow change finish message when finish hotfix or release 206 | - 1.1.0 207 | - optimize - performance improvement using memorization technique. 208 | - 1.0.0 209 | - add - Parameter to show all internal git commands run in `git flow` in output window 210 | - add - Output logger named Git Flow 211 | - fix - Readme was not visible in marketplace. 212 | - optimize - code refactor. 213 | - 0.5.11 214 | - add - Status bar button to call Quick Pick menu 215 | - add - Groups in Quick Pick (require VS Code ^1.64.0) 216 | - 0.5.9 217 | - add - Delete tags local and remote 218 | - fix - Release checkout 219 | - 0.5.2 220 | - add - Quick Pick popup 221 | - optimize - Code was refactored and optimized. 1800 lines to 580. 222 | - 0.4.3 223 | - add - Multiple folder workspaces 224 | - fix - Progress notification not resolving 225 | - 0.3.1 226 | - add - Support (Start, Checkout, Rebase, Delete) 227 | - add - Release checkout 228 | - add - Hotfix checkout 229 | - 0.2.14 230 | - enhance - Order and group context menu elements. 231 | - enhance - use VS Code theme icons instead of SVG. 232 | - enhance - CI flow was created for fast delivery of new versions. 233 | - 0.2.11 234 | - fix - take name of flow branches from configuration 235 | - fix - UI buttons 236 | - 0.2.9 237 | - add - single command to sync all root branches (develop and master or main) 238 | - add - command to checkout root branches 239 | - enhance - better icons 240 | - 0.2.2 241 | - add - progress bas during process 242 | - add - icons to menu elements 243 | - add - bugfix support 244 | - delete - configurations 245 | - fix - git commands with remote 246 | - update - README 247 | - 0.1.0 - Initial release of ... 248 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Versions are currently being supported with security updates. 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 1.2.9 | :white_check_mark: | 10 | 11 | ## Reporting a Vulnerability 12 | 13 | To report a vulnerability, please open an issue. 14 | -------------------------------------------------------------------------------- /icon.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Serhioromano/vscode-gitflow/3fdfcf43e8d8e8ad2a5dd82495286e1458d8e9bc/icon.drawio.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-gitflow", 3 | "displayName": "Git Flow", 4 | "description": "Git-Flow support for VS Code", 5 | "version": "1.3.31", 6 | "engines": { 7 | "vscode": "^1.64.0" 8 | }, 9 | "sponsor": { 10 | "url": "https://www.paypal.com/donate/?hosted_button_id=UTU4EMPLLLX54" 11 | }, 12 | "publisher": "Serhioromano", 13 | "author": "Sergey Romanov", 14 | "preview": true, 15 | "keywords": [ 16 | "git flow", 17 | "gitflow", 18 | "git-flow", 19 | "git", 20 | "flow", 21 | "github", 22 | "feature", 23 | "release", 24 | "bugfix", 25 | "bug fix", 26 | "hotfix", 27 | "hot fix", 28 | "support", 29 | "version", 30 | "version manager", 31 | "versions", 32 | "versioning", 33 | "DevOps", 34 | "CI/CD", 35 | "SCM" 36 | ], 37 | "categories": [ 38 | "Other", 39 | "SCM Providers" 40 | ], 41 | "icon": "icon.drawio.png", 42 | "activationEvents": [ 43 | "workspaceContains:.git" 44 | ], 45 | "extensionDependencies": [ 46 | "vscode.git", 47 | "vscode.git-base" 48 | ], 49 | "repository": { 50 | "type": "git", 51 | "url": "https://github.com/Serhioromano/vscode-gitflow" 52 | }, 53 | "homepage": "https://marketplace.visualstudio.com/items?itemName=serhioromano.vscode-gitflow", 54 | "contributes": { 55 | "configuration": { 56 | "title": "GitFlow", 57 | "properties": { 58 | "gitflow.showNotification": { 59 | "type": "boolean", 60 | "default": true, 61 | "description": "Show notification that current directory is not Git Flow enabled." 62 | }, 63 | "gitflow.showAllCommands": { 64 | "type": "boolean", 65 | "default": false, 66 | "description": "Show all `git` commands that `git flow` runs in output window." 67 | }, 68 | "gitflow.path": { 69 | "type": "string", 70 | "default": "", 71 | "description": "Set you git-flow path manually" 72 | }, 73 | "gitflow.replaceSymbol": { 74 | "type": "string", 75 | "default": "_", 76 | "description": "Replace whitespace in branch names with this symbol" 77 | }, 78 | "gitflow.autoBumpVersion": { 79 | "type": "boolean", 80 | "default": true, 81 | "description": "Bump package version post start release/hotfix." 82 | }, 83 | "gitflow.disableOnRepo": { 84 | "type": "boolean", 85 | "default": false, 86 | "description": "Disable GitFlow check on this repo" 87 | } 88 | } 89 | }, 90 | "keybindings": [ 91 | { 92 | "command": "gitflow.quickPick", 93 | "key": "shift+alt+d", 94 | "when": "gitflow.initialized == true" 95 | } 96 | ], 97 | "commands": [ 98 | { 99 | "command": "gitflow.switchRepo", 100 | "title": "Switch active repository", 101 | "category": "Git-Flow", 102 | "icon": "$(remote)" 103 | }, 104 | { 105 | "command": "gitflow.checkoutBranch", 106 | "title": "Checkout", 107 | "category": "Git-Flow", 108 | "icon": "$(plug)" 109 | }, 110 | { 111 | "command": "gitflow.quickPick", 112 | "title": "Quick Pick", 113 | "category": "Git-Flow", 114 | "icon": "$(extensions-rating)" 115 | }, 116 | { 117 | "command": "gitflow.syncAll", 118 | "title": "Sync all root branches", 119 | "category": "Git-Flow", 120 | "icon": "$(repo-sync)" 121 | }, 122 | { 123 | "command": "gitflow.refreshB", 124 | "title": "Refresh branches view", 125 | "category": "Git-Flow", 126 | "icon": "$(refresh)" 127 | }, 128 | { 129 | "command": "gitflow.refreshT", 130 | "title": "Refresh versions view", 131 | "category": "Git-Flow", 132 | "icon": "$(refresh)" 133 | }, 134 | { 135 | "command": "gitflow.pushTags", 136 | "title": "Push all tags to origin", 137 | "category": "Git-Flow", 138 | "icon": "$(cloud-upload)" 139 | }, 140 | { 141 | "command": "gitflow.pushTag", 142 | "title": "Push tag to origin", 143 | "category": "Git-Flow", 144 | "icon": "$(cloud-upload)" 145 | }, 146 | { 147 | "command": "gitflow.deleteTag", 148 | "title": "Delete tag", 149 | "category": "Git-Flow", 150 | "icon": "$(trash)" 151 | }, 152 | { 153 | "command": "gitflow.fetchAllBranches", 154 | "title": "Fetch all remote branches", 155 | "category": "Git-Flow", 156 | "icon": "$(cloud-upload)" 157 | }, 158 | { 159 | "command": "gitflow.init", 160 | "title": "Init Git Flow", 161 | "category": "Git-Flow" 162 | }, 163 | { 164 | "command": "gitflow.newSupport", 165 | "title": "Support - Start", 166 | "category": "Git-Flow", 167 | "icon": "$(debug-start)" 168 | }, 169 | { 170 | "command": "gitflow.rebaseSupport", 171 | "title": "Support - Rebase", 172 | "category": "Git-Flow", 173 | "icon": "$(git-merge)" 174 | }, 175 | { 176 | "command": "gitflow.publishSupport", 177 | "title": "Support - Publish", 178 | "category": "Git-Flow", 179 | "icon": "$(cloud-upload)" 180 | }, 181 | { 182 | "command": "gitflow.deleteSupport", 183 | "title": "Support - Delete", 184 | "category": "Git-Flow", 185 | "icon": "$(trash)" 186 | }, 187 | { 188 | "command": "gitflow.checkoutSupport", 189 | "title": "Support - Checkout", 190 | "category": "Git-Flow", 191 | "icon": "$(plug)" 192 | }, 193 | { 194 | "command": "gitflow.newFeature", 195 | "title": "Feature - Start", 196 | "category": "Git-Flow", 197 | "icon": "$(debug-start)" 198 | }, 199 | { 200 | "command": "gitflow.trackFeature", 201 | "title": "Feature - Track", 202 | "category": "Git-Flow", 203 | "icon": "$(cloud-upload)" 204 | }, 205 | { 206 | "command": "gitflow.checkoutFeature", 207 | "title": "Feature - Checkout", 208 | "category": "Git-Flow", 209 | "icon": "$(plug)" 210 | }, 211 | { 212 | "command": "gitflow.finishFeature", 213 | "title": "Feature - Finish", 214 | "category": "Git-Flow", 215 | "icon": "$(debug-stop)" 216 | }, 217 | { 218 | "command": "gitflow.publishFeature", 219 | "title": "Feature - Publish", 220 | "category": "Git-Flow", 221 | "icon": "$(cloud-upload)" 222 | }, 223 | { 224 | "command": "gitflow.rebaseFeature", 225 | "title": "Feature - Rebase", 226 | "category": "Git-Flow", 227 | "icon": "$(git-merge)" 228 | }, 229 | { 230 | "command": "gitflow.deleteFeature", 231 | "title": "Feature - Delete", 232 | "category": "Git-Flow", 233 | "icon": "$(trash)" 234 | }, 235 | { 236 | "command": "gitflow.newRelease", 237 | "title": "Release - Start", 238 | "category": "Git-Flow", 239 | "icon": "$(debug-start)" 240 | }, 241 | { 242 | "command": "gitflow.trackRelease", 243 | "title": "Release - Track", 244 | "category": "Git-Flow", 245 | "icon": "$(cloud-upload)" 246 | }, 247 | { 248 | "command": "gitflow.checkoutRelease", 249 | "title": "Release - Checkout", 250 | "category": "Git-Flow", 251 | "icon": "$(plug)" 252 | }, 253 | { 254 | "command": "gitflow.publishRelease", 255 | "title": "Release - Publish", 256 | "category": "Git-Flow", 257 | "icon": "$(cloud-upload)" 258 | }, 259 | { 260 | "command": "gitflow.deleteRelease", 261 | "title": "Release - Delete", 262 | "category": "Git-Flow", 263 | "icon": "$(trash)" 264 | }, 265 | { 266 | "command": "gitflow.finishRelease", 267 | "title": "Release - Finish", 268 | "category": "Git-Flow", 269 | "icon": "$(debug-stop)" 270 | }, 271 | { 272 | "command": "gitflow.rebaseRelease", 273 | "title": "Release - Rebase", 274 | "category": "Git-Flow", 275 | "icon": "$(git-merge)" 276 | }, 277 | { 278 | "command": "gitflow.newHotfix", 279 | "title": "Hotfix - Start", 280 | "category": "Git-Flow", 281 | "icon": "$(debug-start)" 282 | }, 283 | { 284 | "command": "gitflow.finishHotfix", 285 | "title": "Hotfix - Finish", 286 | "category": "Git-Flow", 287 | "icon": "$(debug-stop)" 288 | }, 289 | { 290 | "command": "gitflow.publishHotfix", 291 | "title": "Hotfix - Publish", 292 | "category": "Git-Flow", 293 | "icon": "$(cloud-upload)" 294 | }, 295 | { 296 | "command": "gitflow.deleteHotfix", 297 | "title": "Hotfix - Delete", 298 | "category": "Git-Flow", 299 | "icon": "$(trash)" 300 | }, 301 | { 302 | "command": "gitflow.rebaseHotfix", 303 | "title": "Hotfix - Rebase", 304 | "category": "Git-Flow", 305 | "icon": "$(git-merge)" 306 | }, 307 | { 308 | "command": "gitflow.checkoutHotfix", 309 | "title": "Hotfix - Checkout", 310 | "category": "Git-Flow", 311 | "icon": "$(plug)" 312 | }, 313 | { 314 | "command": "gitflow.newBugfix", 315 | "title": "Bugfix - Start", 316 | "category": "Git-Flow", 317 | "icon": "$(debug-start)" 318 | }, 319 | { 320 | "command": "gitflow.trackBugfix", 321 | "title": "Bugfix - Track", 322 | "category": "Git-Flow", 323 | "icon": "$(cloud-upload)" 324 | }, 325 | { 326 | "command": "gitflow.checkoutBugfix", 327 | "title": "Bugfix - Checkout", 328 | "category": "Git-Flow", 329 | "icon": "$(plug)" 330 | }, 331 | { 332 | "command": "gitflow.finishBugfix", 333 | "title": "Bugfix - Finish", 334 | "category": "Git-Flow", 335 | "icon": "$(debug-stop)" 336 | }, 337 | { 338 | "command": "gitflow.publishBugfix", 339 | "title": "Bugfix - Publish", 340 | "category": "Git-Flow", 341 | "icon": "$(cloud-upload)" 342 | }, 343 | { 344 | "command": "gitflow.deleteBugfix", 345 | "title": "Bugfix - Delete", 346 | "category": "Git-Flow", 347 | "icon": "$(trash)" 348 | }, 349 | { 350 | "command": "gitflow.rebaseBugfix", 351 | "title": "Bugfix - Rebase", 352 | "category": "Git-Flow", 353 | "icon": "$(git-merge)" 354 | } 355 | ], 356 | "views": { 357 | "scm": [ 358 | { 359 | "id": "gitflowExplorer", 360 | "name": "Gitflow" 361 | }, 362 | { 363 | "id": "gitflowTags", 364 | "name": "Versions" 365 | } 366 | ] 367 | }, 368 | "menus": { 369 | "view/title": [ 370 | { 371 | "command": "gitflow.refreshT", 372 | "when": "view == gitflowTags", 373 | "group": "navigation" 374 | }, 375 | { 376 | "command": "gitflow.pushTags", 377 | "when": "view == gitflowTags", 378 | "group": "navigation" 379 | }, 380 | { 381 | "command": "gitflow.refreshB", 382 | "when": "view == gitflowExplorer", 383 | "group": "navigation" 384 | }, 385 | { 386 | "command": "gitflow.fetchAllBranches", 387 | "when": "view == gitflowExplorer", 388 | "group": "navigation" 389 | }, 390 | { 391 | "command": "gitflow.switchRepo", 392 | "when": "view == gitflowExplorer && workspaceFolderCount > 1", 393 | "group": "navigation" 394 | }, 395 | { 396 | "command": "gitflow.syncAll", 397 | "when": "view == gitflowExplorer", 398 | "group": "navigation" 399 | }, 400 | { 401 | "command": "gitflow.switchRepo", 402 | "when": "view == gitflowExplorer && workspaceFolderCount > 1", 403 | "group": "a_group@1" 404 | }, 405 | { 406 | "command": "gitflow.syncAll", 407 | "when": "view == gitflowExplorer", 408 | "group": "b_group@1" 409 | }, 410 | { 411 | "command": "gitflow.refreshB", 412 | "when": "view == gitflowExplorer", 413 | "group": "b_group@3" 414 | }, 415 | { 416 | "command": "gitflow.fetchAllBranches", 417 | "when": "view == gitflowExplorer", 418 | "group": "b_group@4" 419 | }, 420 | { 421 | "command": "gitflow.init", 422 | "when": "view == gitflowExplorer", 423 | "group": "b_group@5" 424 | }, 425 | { 426 | "command": "gitflow.newRelease", 427 | "when": "view == gitflowExplorer", 428 | "group": "c_group@6" 429 | }, 430 | { 431 | "command": "gitflow.newFeature", 432 | "when": "view == gitflowExplorer", 433 | "group": "c_group@7" 434 | }, 435 | { 436 | "command": "gitflow.newBugfix", 437 | "when": "view == gitflowExplorer", 438 | "group": "c_group@8" 439 | }, 440 | { 441 | "command": "gitflow.newHotfix", 442 | "when": "view == gitflowExplorer", 443 | "group": "c_group@9" 444 | } 445 | ], 446 | "view/item/context": [ 447 | { 448 | "command": "gitflow.pushTag", 449 | "when": "view == gitflowTags && viewItem == local", 450 | "group": "inline" 451 | }, 452 | { 453 | "command": "gitflow.deleteTag", 454 | "when": "view == gitflowTags", 455 | "group": "inline" 456 | }, 457 | { 458 | "command": "gitflow.checkoutBranch", 459 | "when": "view == gitflowExplorer && viewItem == branch", 460 | "group": "inline" 461 | }, 462 | { 463 | "command": "gitflow.checkoutBranch", 464 | "when": "view == gitflowExplorer && viewItem == branch" 465 | }, 466 | { 467 | "command": "gitflow.newFeature", 468 | "when": "view == gitflowExplorer && viewItem == f" 469 | }, 470 | { 471 | "command": "gitflow.newRelease", 472 | "when": "view == gitflowExplorer && viewItem == r" 473 | }, 474 | { 475 | "command": "gitflow.newHotfix", 476 | "when": "view == gitflowExplorer && viewItem == h" 477 | }, 478 | { 479 | "command": "gitflow.newBugfix", 480 | "when": "view == gitflowExplorer && viewItem == b" 481 | }, 482 | { 483 | "command": "gitflow.newSupport", 484 | "when": "view == gitflowExplorer && viewItem == s" 485 | }, 486 | { 487 | "command": "gitflow.newFeature", 488 | "when": "view == gitflowExplorer && viewItem == f", 489 | "group": "inline" 490 | }, 491 | { 492 | "command": "gitflow.newRelease", 493 | "when": "view == gitflowExplorer && viewItem == r", 494 | "group": "inline" 495 | }, 496 | { 497 | "command": "gitflow.newHotfix", 498 | "when": "view == gitflowExplorer && viewItem == h", 499 | "group": "inline" 500 | }, 501 | { 502 | "command": "gitflow.newBugfix", 503 | "when": "view == gitflowExplorer && viewItem == b", 504 | "group": "inline" 505 | }, 506 | { 507 | "command": "gitflow.newSupport", 508 | "when": "view == gitflowExplorer && viewItem == s", 509 | "group": "inline" 510 | }, 511 | { 512 | "command": "gitflow.trackFeature", 513 | "when": "view == gitflowExplorer && viewItem == origin_feature" 514 | }, 515 | { 516 | "command": "gitflow.trackFeature", 517 | "when": "view == gitflowExplorer && viewItem == origin_feature", 518 | "group": "inline" 519 | }, 520 | { 521 | "command": "gitflow.trackRelease", 522 | "when": "view == gitflowExplorer && viewItem == origin_release" 523 | }, 524 | { 525 | "command": "gitflow.trackRelease", 526 | "when": "view == gitflowExplorer && viewItem == origin_release", 527 | "group": "inline" 528 | }, 529 | { 530 | "command": "gitflow.trackBugfix", 531 | "when": "view == gitflowExplorer && viewItem == origin_bugfix" 532 | }, 533 | { 534 | "command": "gitflow.trackBugfix", 535 | "when": "view == gitflowExplorer && viewItem == origin_bugfix", 536 | "group": "inline" 537 | }, 538 | { 539 | "command": "gitflow.rebaseSupport", 540 | "when": "view == gitflowExplorer && viewItem == support || viewItem == support_local" 541 | }, 542 | { 543 | "command": "gitflow.rebaseSupport", 544 | "when": "view == gitflowExplorer && viewItem == support || viewItem == support_local", 545 | "group": "inline" 546 | }, 547 | { 548 | "command": "gitflow.deleteSupport", 549 | "when": "view == gitflowExplorer && viewItem == support || viewItem == support_local" 550 | }, 551 | { 552 | "command": "gitflow.deleteSupport", 553 | "when": "view == gitflowExplorer && viewItem == support || viewItem == support_local", 554 | "group": "inline" 555 | }, 556 | { 557 | "command": "gitflow.checkoutSupport", 558 | "when": "view == gitflowExplorer && viewItem == support || viewItem == support_local" 559 | }, 560 | { 561 | "command": "gitflow.checkoutSupport", 562 | "when": "view == gitflowExplorer && viewItem == support || viewItem == support_local", 563 | "group": "inline" 564 | }, 565 | { 566 | "command": "gitflow.publishSupport", 567 | "when": "view == gitflowExplorer && viewItem == support_local" 568 | }, 569 | { 570 | "command": "gitflow.publishSupport", 571 | "when": "view == gitflowExplorer && viewItem == support_local", 572 | "group": "inline" 573 | }, 574 | { 575 | "command": "gitflow.publishHotfix", 576 | "when": "view == gitflowExplorer && viewItem == hotfix_local" 577 | }, 578 | { 579 | "command": "gitflow.rebaseHotfix", 580 | "when": "view == gitflowExplorer && viewItem == hotfix || viewItem == hotfix_local" 581 | }, 582 | { 583 | "command": "gitflow.finishHotfix", 584 | "when": "view == gitflowExplorer && viewItem == hotfix || viewItem == hotfix_local" 585 | }, 586 | { 587 | "command": "gitflow.deleteHotfix", 588 | "when": "view == gitflowExplorer && viewItem == hotfix || viewItem == hotfix_local" 589 | }, 590 | { 591 | "command": "gitflow.checkoutHotfix", 592 | "when": "view == gitflowExplorer && viewItem == hotfix || viewItem == hotfix_local" 593 | }, 594 | { 595 | "command": "gitflow.publishHotfix", 596 | "when": "view == gitflowExplorer && viewItem == hotfix_local", 597 | "group": "inline" 598 | }, 599 | { 600 | "command": "gitflow.rebaseHotfix", 601 | "when": "view == gitflowExplorer && viewItem == hotfix || viewItem == hotfix_local", 602 | "group": "inline" 603 | }, 604 | { 605 | "command": "gitflow.finishHotfix", 606 | "when": "view == gitflowExplorer && viewItem == hotfix || viewItem == hotfix_local", 607 | "group": "inline" 608 | }, 609 | { 610 | "command": "gitflow.deleteHotfix", 611 | "when": "view == gitflowExplorer && viewItem == hotfix || viewItem == hotfix_local", 612 | "group": "inline" 613 | }, 614 | { 615 | "command": "gitflow.checkoutHotfix", 616 | "when": "view == gitflowExplorer && viewItem == hotfix || viewItem == hotfix_local", 617 | "group": "inline" 618 | }, 619 | { 620 | "command": "gitflow.publishFeature", 621 | "when": "view == gitflowExplorer && viewItem == feature_local" 622 | }, 623 | { 624 | "command": "gitflow.checkoutFeature", 625 | "when": "view == gitflowExplorer && viewItem == feature || viewItem == feature_local" 626 | }, 627 | { 628 | "command": "gitflow.rebaseFeature", 629 | "when": "view == gitflowExplorer && viewItem == feature || viewItem == feature_local" 630 | }, 631 | { 632 | "command": "gitflow.finishFeature", 633 | "when": "view == gitflowExplorer && viewItem == feature || viewItem == feature_local" 634 | }, 635 | { 636 | "command": "gitflow.deleteFeature", 637 | "when": "view == gitflowExplorer && viewItem == feature || viewItem == feature_local" 638 | }, 639 | { 640 | "command": "gitflow.publishFeature", 641 | "when": "view == gitflowExplorer && viewItem == feature_local", 642 | "group": "inline" 643 | }, 644 | { 645 | "command": "gitflow.checkoutFeature", 646 | "when": "view == gitflowExplorer && viewItem == feature || viewItem == feature_local", 647 | "group": "inline" 648 | }, 649 | { 650 | "command": "gitflow.rebaseFeature", 651 | "when": "view == gitflowExplorer && viewItem == feature || viewItem == feature_local", 652 | "group": "inline" 653 | }, 654 | { 655 | "command": "gitflow.finishFeature", 656 | "when": "view == gitflowExplorer && viewItem == feature || viewItem == feature_local", 657 | "group": "inline" 658 | }, 659 | { 660 | "command": "gitflow.deleteFeature", 661 | "when": "view == gitflowExplorer && viewItem == feature || viewItem == feature_local", 662 | "group": "inline" 663 | }, 664 | { 665 | "command": "gitflow.publishBugfix", 666 | "when": "view == gitflowExplorer && viewItem == bugfix_local" 667 | }, 668 | { 669 | "command": "gitflow.checkoutBugfix", 670 | "when": "view == gitflowExplorer && viewItem == bugfix || viewItem == bugfix_local" 671 | }, 672 | { 673 | "command": "gitflow.rebaseBugfix", 674 | "when": "view == gitflowExplorer && viewItem == bugfix || viewItem == bugfix_local" 675 | }, 676 | { 677 | "command": "gitflow.finishBugfix", 678 | "when": "view == gitflowExplorer && viewItem == bugfix || viewItem == bugfix_local" 679 | }, 680 | { 681 | "command": "gitflow.deleteBugfix", 682 | "when": "view == gitflowExplorer && viewItem == bugfix || viewItem == bugfix_local" 683 | }, 684 | { 685 | "command": "gitflow.publishBugfix", 686 | "when": "view == gitflowExplorer && viewItem == bugfix_local", 687 | "group": "inline" 688 | }, 689 | { 690 | "command": "gitflow.checkoutBugfix", 691 | "when": "view == gitflowExplorer && viewItem == bugfix || viewItem == bugfix_local", 692 | "group": "inline" 693 | }, 694 | { 695 | "command": "gitflow.rebaseBugfix", 696 | "when": "view == gitflowExplorer && viewItem == bugfix || viewItem == bugfix_local", 697 | "group": "inline" 698 | }, 699 | { 700 | "command": "gitflow.finishBugfix", 701 | "when": "view == gitflowExplorer && viewItem == bugfix || viewItem == bugfix_local", 702 | "group": "inline" 703 | }, 704 | { 705 | "command": "gitflow.deleteBugfix", 706 | "when": "view == gitflowExplorer && viewItem == bugfix || viewItem == bugfix_local", 707 | "group": "inline" 708 | }, 709 | { 710 | "command": "gitflow.publishRelease", 711 | "when": "view == gitflowExplorer && viewItem == release_local" 712 | }, 713 | { 714 | "command": "gitflow.rebaseRelease", 715 | "when": "view == gitflowExplorer && viewItem == release_local" 716 | }, 717 | { 718 | "command": "gitflow.finishRelease", 719 | "when": "view == gitflowExplorer && viewItem == release || viewItem == release_local" 720 | }, 721 | { 722 | "command": "gitflow.deleteRelease", 723 | "when": "view == gitflowExplorer && viewItem == release || viewItem == release_local" 724 | }, 725 | { 726 | "command": "gitflow.checkoutRelease", 727 | "when": "view == gitflowExplorer && viewItem == release || viewItem == release_local" 728 | }, 729 | { 730 | "command": "gitflow.publishRelease", 731 | "when": "view == gitflowExplorer && viewItem == release_local", 732 | "group": "inline" 733 | }, 734 | { 735 | "command": "gitflow.rebaseRelease", 736 | "when": "view == gitflowExplorer && viewItem == release_local", 737 | "group": "inline" 738 | }, 739 | { 740 | "command": "gitflow.finishRelease", 741 | "when": "view == gitflowExplorer && viewItem == release || viewItem == release_local", 742 | "group": "inline" 743 | }, 744 | { 745 | "command": "gitflow.checkoutRelease", 746 | "when": "view == gitflowExplorer && viewItem == release || viewItem == release_local", 747 | "group": "inline" 748 | }, 749 | { 750 | "command": "gitflow.deleteRelease", 751 | "when": "view == gitflowExplorer && viewItem == release || viewItem == release_local", 752 | "group": "inline" 753 | } 754 | ] 755 | } 756 | }, 757 | "main": "./out/extension.js", 758 | "scripts": { 759 | "vscode:prepublish": "npm run esbuild-base -- --minify", 760 | "_vscode:prepublish": "npm run compile", 761 | "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node", 762 | "esbuild": "npm run esbuild-base -- --sourcemap", 763 | "esbuild-watch": "npm run esbuild-base -- --sourcemap --watch", 764 | "watch": "tsc -watch -p ./", 765 | "publish": "vsce package && gh release create $(node -pe \"require('./package.json')['version']\") --generate-notes \"./vscode-gitflow-$(node -pe \"require('./package.json')['version']\").vsix\" && vsce publish", 766 | "compile": "tsc -p ./", 767 | "pretest": "npm run compile && npm run lint", 768 | "lint": "eslint src --ext ts", 769 | "vars": "env | grep npm_package_", 770 | "test": "node ./out/test/runTest.js" 771 | }, 772 | "devDependencies": { 773 | "@types/glob": "^7.2.0", 774 | "@types/mocha": "^9.0.0", 775 | "@types/node": "14.x", 776 | "@types/vscode": "^1.64.0", 777 | "@typescript-eslint/eslint-plugin": "^5.9.1", 778 | "@typescript-eslint/parser": "^5.9.1", 779 | "@vscode/test-electron": "^2.0.3", 780 | "esbuild": "^0.14.10", 781 | "eslint": "^8.6.0", 782 | "glob": "^7.2.0", 783 | "mocha": "^9.2.2", 784 | "ovsx": "^0.5.1", 785 | "typescript": "^4.6.3", 786 | "vsce": "^2.6.4" 787 | }, 788 | "dependencies": { 789 | "npm": "^10.5.2", 790 | "semver": "^7.5.2", 791 | "typescript-memoize": "^1.1.0" 792 | } 793 | } 794 | -------------------------------------------------------------------------------- /resources/media/flow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/media/mfw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Serhioromano/vscode-gitflow/3fdfcf43e8d8e8ad2a5dd82495286e1458d8e9bc/resources/media/mfw.png -------------------------------------------------------------------------------- /resources/media/qp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Serhioromano/vscode-gitflow/3fdfcf43e8d8e8ad2a5dd82495286e1458d8e9bc/resources/media/qp.png -------------------------------------------------------------------------------- /resources/media/ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Serhioromano/vscode-gitflow/3fdfcf43e8d8e8ad2a5dd82495286e1458d8e9bc/resources/media/ss.png -------------------------------------------------------------------------------- /src/ViewBranches.ts: -------------------------------------------------------------------------------- 1 | import { debounce, memoize, throttle } from './lib/decorators'; 2 | import * as vscode from "vscode"; 3 | import { Util } from "./lib/Util"; 4 | import { readFileSync, writeFileSync, existsSync } from "fs"; 5 | import path from "path"; 6 | import os from "os"; 7 | import { Tag } from './ViewVersions'; 8 | 9 | interface BranchList { 10 | develop: string; 11 | master: string; 12 | release: string; 13 | feature: string; 14 | hotfix: string; 15 | bugfix: string; 16 | support: string; 17 | } 18 | type Emitter = Flow | undefined | null | void; 19 | let checked: boolean = false; 20 | 21 | 22 | 23 | export class TreeViewBranches implements vscode.TreeDataProvider { 24 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 25 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; 26 | 27 | public curBranch: string = ""; 28 | public listBranches: string[] = []; 29 | public listRemoteBranches: string[] = []; 30 | public hasOrigin: boolean = false; 31 | 32 | private terminal: vscode.Terminal | null; 33 | private branches: BranchList; 34 | 35 | constructor(private util: Util) { 36 | this.terminal = null; 37 | this.branches = { 38 | develop: "", 39 | master: "", 40 | release: "", 41 | feature: "", 42 | hotfix: "", 43 | bugfix: "", 44 | support: "", 45 | }; 46 | } 47 | 48 | getTreeItem(element: Flow): vscode.TreeItem { 49 | return element; 50 | } 51 | 52 | getChildren(element?: Flow): Thenable { 53 | 54 | if (!checked && !this.util.check()) { 55 | return Promise.resolve([]); 56 | } 57 | checked = true; 58 | 59 | let tree: Flow[] = []; 60 | 61 | if (element === undefined) { 62 | let list = this.util.execSync(`${this.util.flowPath} config list`); 63 | let config = vscode.workspace.getConfiguration("gitflow"); 64 | if (list.toLowerCase().search("not a gitflow-enabled repo yet") > 0 && config.get("showNotification") === true) { 65 | let disabled = config.get("disableOnRepo"); 66 | if (disabled) { 67 | return Promise.resolve([]); 68 | } 69 | 70 | let initLink = "Init"; 71 | let disableCheck = "Disable"; 72 | vscode.window 73 | .showWarningMessage( 74 | "Not a gitflow-enabled repo yet. Please, open a terminal and run `git flow init` command.", 75 | initLink, disableCheck 76 | ) 77 | .then((selection) => { 78 | if (selection === initLink) { 79 | vscode.commands.executeCommand("gitflow.init"); 80 | } 81 | if (selection === disableCheck) { 82 | config.update("disableOnRepo", true, vscode.ConfigurationTarget.Workspace); 83 | } 84 | }); 85 | return Promise.resolve([]); 86 | } 87 | 88 | this.curBranch = this.util.execSync(`"${this.util.path}" rev-parse --abbrev-ref HEAD`).trim(); 89 | 90 | let b = this.util.execSync(`${this.util.flowPath} config list`).replace("\r", "").split("\n"); 91 | this.branches.master = b[0].split(": ")[1].trim(); 92 | this.branches.develop = b[1].split(": ")[1].trim(); 93 | this.branches.feature = b[2].split(": ")[1].trim(); 94 | this.branches.bugfix = b[3].split(": ")[1].trim(); 95 | this.branches.release = b[4].split(": ")[1].trim(); 96 | this.branches.hotfix = b[5].split(": ")[1].trim(); 97 | this.branches.support = b[6].split(": ")[1].trim(); 98 | 99 | // this.listRemotes = [...new Set( 100 | // this.util.execSync(`"${this.util.path}" remote -v') 101 | // .split("\n") 102 | // .map(el => el.split("\t")[0].trim()) 103 | // .filter(el => el !== '') 104 | // )]; 105 | this.listBranches = this.util 106 | .execSync(`"${this.util.path}" branch`) 107 | .split("\n") 108 | .map((el) => el.trim().replace("* ", "")) 109 | .filter((el) => el !== ""); 110 | 111 | this.listRemoteBranches = this.util 112 | .execSync(`"${this.util.path}" branch -r`) 113 | .split("\n") 114 | .map((el) => { 115 | if (el.toLowerCase().search("origin/") !== -1) { 116 | this.hasOrigin = true; 117 | } 118 | let a = el.split("/"); 119 | a.shift(); 120 | return a.join("/"); 121 | }); 122 | 123 | this.listBranches 124 | .filter((el) => el.split("/").length < 2) 125 | .forEach((el) => { 126 | tree.push( 127 | new Flow( 128 | el, 129 | el, 130 | "git-branch", 131 | vscode.TreeItemCollapsibleState.None, 132 | this._isCurrent(el), 133 | "branch" 134 | ) 135 | ); 136 | }); 137 | tree.push( 138 | new Flow( 139 | this.branches.release.replace("/", ""), 140 | "Releases", 141 | "tag", 142 | vscode.TreeItemCollapsibleState.Expanded, 143 | false, 144 | "r" 145 | ) 146 | ); 147 | tree.push( 148 | new Flow( 149 | this.branches.feature.replace("/", ""), 150 | "Features", 151 | "test-view-icon", 152 | vscode.TreeItemCollapsibleState.Expanded, 153 | false, 154 | "f" 155 | ) 156 | ); 157 | tree.push( 158 | new Flow( 159 | this.branches.bugfix.replace("/", ""), 160 | "BugFixes", 161 | "callstack-view-session", 162 | vscode.TreeItemCollapsibleState.Expanded, 163 | false, 164 | "b" 165 | ) 166 | ); 167 | tree.push( 168 | new Flow( 169 | this.branches.hotfix.replace("/", ""), 170 | "HotFixes", 171 | "flame", 172 | vscode.TreeItemCollapsibleState.Expanded, 173 | false, 174 | "h" 175 | ) 176 | ); 177 | tree.push( 178 | new Flow( 179 | this.branches.support.replace("/", ""), 180 | "Support", 181 | "history", 182 | vscode.TreeItemCollapsibleState.Expanded, 183 | false, 184 | "s" 185 | ) 186 | ); 187 | 188 | vscode.commands.executeCommand("setContext", "gitflow.initialized", true); 189 | return Promise.resolve(tree); 190 | } 191 | 192 | this.listBranches 193 | .filter((el) => el.split("/")[0] === element.full) 194 | .forEach((el) => { 195 | tree.push( 196 | new Flow( 197 | el, 198 | el.split("/")[1], 199 | "git-branch", 200 | vscode.TreeItemCollapsibleState.None, 201 | this._isCurrent(el), 202 | element.full + (!this.listRemoteBranches.includes(el) ? "_local" : "") 203 | ) 204 | ); 205 | }); 206 | 207 | this.listRemoteBranches 208 | .filter((el) => { 209 | return ( 210 | el.split("/").length > 1 && 211 | el.search("HEAD") === -1 && 212 | !this.listBranches.includes(el) && 213 | el.split("/")[0] === element.full 214 | ); 215 | }) 216 | .forEach((el) => { 217 | tree.push( 218 | new Flow( 219 | el, 220 | "ORIGIN/" + el.split("/")[1], 221 | "git-branch", 222 | vscode.TreeItemCollapsibleState.None, 223 | false, 224 | "origin_" + element.full 225 | ) 226 | ); 227 | }); 228 | return Promise.resolve(tree); 229 | } 230 | 231 | fetchAllBranches() { 232 | this.util.exec(`"${this.util.path}" fetch --all`, true, (s) => { 233 | this._onDidChangeTreeData.fire(); 234 | }); 235 | } 236 | 237 | syncAll() { 238 | if (!this.hasOrigin) { 239 | vscode.window.showWarningMessage("No ORIGIN remote has been found!"); 240 | return; 241 | } 242 | vscode.window 243 | .withProgress( 244 | { 245 | location: vscode.ProgressLocation.Notification, 246 | title: `Sync all root branches`, 247 | cancellable: false, 248 | }, 249 | (progress, token) => 250 | new Promise((resolve) => { 251 | setTimeout(() => { 252 | this.listBranches 253 | .filter((el) => el.split("/").length < 2) 254 | .forEach((el) => { 255 | if (this.listRemoteBranches.includes(el)) { 256 | this.util.execSync(`"${this.util.path}" pull origin ${el}`); 257 | } 258 | this.util.execSync(`"${this.util.path}" push origin ${el}:${el}`); 259 | }); 260 | resolve(); 261 | }, 100); 262 | }) 263 | ) 264 | .then(() => { 265 | this._onDidChangeTreeData.fire(); 266 | }); 267 | } 268 | 269 | async checkoutBranch(node: Flow | undefined) { 270 | let name = node?.full; 271 | if (name === undefined) { 272 | name = await vscode.window.showQuickPick( 273 | this.listBranches.filter((el) => el.split("/").length < 2), 274 | { title: "Select branch" } 275 | ); 276 | } 277 | 278 | let cmd = `"${this.util.path}" checkout -q ${name}`; 279 | 280 | this.util.exec(cmd, false, (s) => { 281 | this._onDidChangeTreeData.fire(); 282 | }); 283 | } 284 | 285 | async general(what: string, branch: string | undefined, search?: string) { 286 | if (branch === undefined && search !== undefined) { 287 | let branches = this.listBranches.filter((el) => el.search(search) !== -1); 288 | if (branches.length === 0) { 289 | vscode.window.showWarningMessage(`Could not find any ${ucf(search)}`); 290 | return; 291 | } 292 | branch = await vscode.window.showQuickPick(branches, { 293 | title: `Select `, 294 | }); 295 | } 296 | if (branch === undefined) { 297 | return; 298 | } 299 | 300 | let option: string | undefined = ""; 301 | let list: string[]; 302 | let base: string | undefined = ""; 303 | let options: string[] | undefined; 304 | let feature = branch.split("/")[0]; 305 | let name: string | undefined = branch.split("/")[1]; 306 | let progress = false; 307 | let version = ""; 308 | let exist: boolean = existsSync(this.util.workspaceRoot + "/package.json"); 309 | 310 | switch (what) { 311 | case "start": 312 | if (["hotfix", "release"].includes(feature) && exist) { 313 | version = 314 | JSON.parse(readFileSync(this.util.workspaceRoot + "/package.json", "utf8")).version || 315 | ""; 316 | } 317 | 318 | name = await vscode.window.showInputBox({ 319 | title: `Enter a valid ${ucf(feature)} git branch name`, 320 | value: version, 321 | }); 322 | if (name === undefined) { 323 | return; 324 | } 325 | let config = vscode.workspace.getConfiguration("gitflow"); 326 | name = name.replace(/\s/igm, `${config.get("replaceSymbol")}`); 327 | const checked = this.util.execSync(`"${this.util.path}" check-ref-format --branch ${name}`).trim(); 328 | 329 | if (checked !== name) { 330 | vscode.window.showErrorMessage(`Error creating a branch: ${checked}`); 331 | return; 332 | } 333 | 334 | if (this.curBranch.search(this.branches.support) !== -1) { 335 | let b = 336 | (await vscode.window.showQuickPick(["Yes", "No"], { 337 | title: `Start release based on ${this.curBranch}?`, 338 | })) || "No"; 339 | base = b === "Yes" ? this.curBranch : ""; 340 | } 341 | if (feature === "support") { 342 | base = await vscode.window.showQuickPick( 343 | this.util 344 | .execSync(`"${this.util.path}" tag --sort=-v:refname`) 345 | .split("\n") 346 | .map((el) => el.trim().replace("* ", "")) 347 | .filter((el) => el !== ""), 348 | { title: "Start support branch based on a tag" } 349 | ); 350 | if (base === undefined) { 351 | return; 352 | } 353 | } 354 | 355 | break; 356 | case "delete": 357 | list = ["[-f] Force deletion"]; 358 | if (this.listRemoteBranches.includes(branch)) { 359 | list.push("[-r] Delete remote branch"); 360 | } 361 | options = await vscode.window.showQuickPick(list, { 362 | title: "Select options", 363 | canPickMany: true, 364 | }); 365 | if (options === undefined) { 366 | return; 367 | } 368 | if (options.includes("[-r] Delete remote branch")) { 369 | progress = true; 370 | } 371 | option = options 372 | ?.map((el) => { 373 | let m = el.match(/\[([^\]]*)\]/); 374 | return m === null ? "" : m[1]; 375 | }) 376 | .join(" "); 377 | break; 378 | case "rebase": 379 | options = await vscode.window.showQuickPick( 380 | ["[-i] An interactive rebase", "[-p] Preserve merges"], 381 | { 382 | title: "Select options", 383 | canPickMany: true, 384 | } 385 | ); 386 | if (options === undefined) { 387 | return; 388 | } 389 | option = options 390 | .map((el) => { 391 | let m = el.match(/\[([^\]]*)\]/); 392 | return m === null ? "" : m[1]; 393 | }) 394 | .join(" "); 395 | 396 | switch (feature) { 397 | case "support": 398 | let root = this.listBranches.filter((el) => el.split("/").length < 2); 399 | let tags = this.util 400 | .execSync(`"${this.util.path}" tag --sort=-v:refname`) 401 | .split("\n") 402 | .map((el) => el.trim().replace("* ", "")) 403 | .filter((el) => el !== ""); 404 | base = 405 | (await vscode.window.showQuickPick([...root, ...tags], { 406 | title: "Select base branch", 407 | })) || ""; 408 | break; 409 | default: 410 | base = 411 | (await vscode.window.showQuickPick( 412 | this.listBranches.filter((el) => el.split("/").length < 2), 413 | { title: "Select base branch" } 414 | )) || ""; 415 | } 416 | if (base === undefined) { 417 | return; 418 | } 419 | break; 420 | case "finish": 421 | options = await this._getFinishOptions(feature); 422 | if ( 423 | this.listRemoteBranches.includes(branch) && 424 | !options?.includes("[--keepremote] Keep the remote branch") 425 | ) { 426 | progress = true; 427 | } 428 | if (options === undefined) { 429 | return; 430 | } 431 | option = 432 | options.map((el) => { 433 | let m = el.match(/\[([^\]]*)\]/); 434 | return m === null ? "" : m[1]; 435 | }).join(" ") || ""; 436 | 437 | let msg; 438 | 439 | if (["hotfix", "release"].includes(feature)) { 440 | msg = await vscode.window.showInputBox({ 441 | title: 'Message', 442 | value: `Finish ${ucf(feature)}: ${name}`, 443 | }); 444 | if (msg === undefined) { 445 | return; 446 | } 447 | msg = `${msg}`.trim(); 448 | if (msg === "") { 449 | msg = `Finish ${ucf(feature)}: ${name}`; 450 | } 451 | 452 | let tmpMsgFile = path.join(`${os.tmpdir()}`, `vscode-git-flow-${Math.floor(Math.random() * 10000000)}.msg`); 453 | writeFileSync(tmpMsgFile, msg, "utf-8"); 454 | option = `${option} -f ${tmpMsgFile} -T "${name}"`; 455 | 456 | if (existsSync(this.util.workspaceRoot + "/CHANGELOG.md")) { 457 | let updated = false; 458 | let chc = `${readFileSync(this.util.workspaceRoot + "/CHANGELOG.md")}`; 459 | chc = chc.split("\n").map(el => { 460 | if (el.toLowerCase().includes("[unreleased]")) { 461 | let date = new Date(); 462 | updated = true; 463 | el = el 464 | .replace(/\[unreleased\]/i, `[${name}]`) 465 | .replace(/\byyyy\b/i, `${date.getFullYear()}`) 466 | .replace(/\bmm\b/i, `${date.getMonth() < 9 ? '0' : ''}${date.getMonth() + 1}`) 467 | .replace(/\bdd\b/i, `${date.getDate() < 10 ? '0' : ''}${date.getDate()}`); 468 | } 469 | return el; 470 | }).join("\n"); 471 | 472 | if (updated) { 473 | writeFileSync(this.util.workspaceRoot + "/CHANGELOG.md", chc); 474 | this.util.execSync(`"${this.util.path}" add ./CHANGELOG.md`); 475 | this.util.execSync(`"${this.util.path}" commit ./CHANGELOG.md -m"Update Changelog"`); 476 | } 477 | } 478 | } 479 | break; 480 | } 481 | 482 | let config = vscode.workspace.getConfiguration("gitflow"); 483 | let command = config.get("showAllCommands") === true ? " --showcommands " : " "; 484 | let cmd = `${this.util.flowPath} ${feature} ${what}${command}${option} ${name} ${base}`; 485 | // console.log(cmd); 486 | 487 | this.util.exec(cmd, progress, (s) => { 488 | this._onDidChangeTreeData.fire(); 489 | if (["hotfix", "release"].includes(feature)) { 490 | vscode.commands.executeCommand("gitflow.refreshT"); 491 | } 492 | 493 | // Bump version 494 | const shouldBumpVersion = config.get("autoBumpVersion"); 495 | if (shouldBumpVersion && ["hotfix", "release"].includes(feature) && exist && what === 'start') { 496 | version = 497 | JSON.parse(readFileSync(this.util.workspaceRoot + "/package.json", "utf8")).version || 498 | ""; 499 | if (version !== "" && name !== version && `${name}`.match(/^[0-9\.]*$/) !== null) { 500 | writeFileSync( 501 | this.util.workspaceRoot + "/package.json", 502 | readFileSync(this.util.workspaceRoot + "/package.json", "utf8").replace( 503 | version, 504 | `${name}` 505 | ) 506 | ); 507 | this.util.execSync(`"${this.util.path}" add ./package.json`); 508 | this.util.execSync(`"${this.util.path}" commit ./package.json -m "Version bump to ${name}"`); 509 | } 510 | } 511 | }); 512 | 513 | 514 | function ucf(string: string) { 515 | return string.charAt(0).toUpperCase() + string.slice(1); 516 | } 517 | } 518 | 519 | async _getFinishOptions(what: string): Promise { 520 | return new Promise(async (resolve) => { 521 | let list: string[] = []; 522 | switch (what) { 523 | case "bugfix": 524 | list = [ 525 | "[-F] Fetch from origin before performing finish", 526 | "[-r] Rebase before merging", 527 | "[-p] Preserve merges while rebasing", 528 | "[-k] Keep branch after performing finish", 529 | "[--keepremote] Keep the remote branch", 530 | "[--keeplocal] Keep the local branch", 531 | "[-D] Force delete bugfix branch after finish", 532 | "[-S] Squash bugfix during merge", 533 | "[--no-ff] Never fast-forward during the merge", 534 | ]; 535 | break; 536 | case "hotfix": 537 | list = [ 538 | "[-F] Fetch from origin before performing finish", 539 | "[-p] Push to origin after performing finish", 540 | "[-k] Keep branch after performing finish", 541 | "[--keepremote] Keep the remote branch", 542 | "[--keeplocal] Keep the local branch", 543 | "[-D] Force delete hotfix branch after finish", 544 | "[-n] Don't tag this hotfix", 545 | "[-b] Don't back-merge master, or tag if applicable, in develop", 546 | "[-S] Squash hotfix during merge", 547 | ]; 548 | break; 549 | case "feature": 550 | list = [ 551 | "[-F] Fetch from origin before performing finish", 552 | "[-r] Rebase before merging", 553 | "[-p] Preserve merges while rebasing", 554 | "[--push] Push to origin after performing finish", 555 | "[-k] Keep branch after performing finish", 556 | "[--keepremote] Keep the remote branch", 557 | "[--keeplocal] Keep the local branch", 558 | "[-D] Force delete feature branch after finish", 559 | "[-S] Squash feature during merge", 560 | "[--no-ff] Never fast-forward during the merge", 561 | ]; 562 | break; 563 | case "release": 564 | list = [ 565 | "[-F] Fetch from origin before performing finish", 566 | "[-p] Push to origin after performing finish", 567 | "[-D] Force delete release branch after finish", 568 | "[--pushproduction] Push the production branch", 569 | "[--pushdevelop] Push the develop branch", 570 | "[--pushtag] Push the tag", 571 | "[-k] Keep branch after performing finish", 572 | "[--keepremote] Keep the remote branch", 573 | "[--keeplocal] Keep the local branch", 574 | "[-n] Don't tag this release", 575 | "[-b] Don't back-merge master, or tag if applicable, in develop", 576 | "[-S] Squash release during merge", 577 | "[--ff-master] Fast forward master branch if possible", 578 | "[--nodevelopmerge] Don't back-merge develop branch", 579 | ]; 580 | break; 581 | } 582 | resolve( 583 | (await vscode.window.showQuickPick(list, { 584 | title: "Select delete options", 585 | canPickMany: true, 586 | })) || [] 587 | ); 588 | }); 589 | } 590 | 591 | //#region Support 592 | async startSupport() { 593 | this.general("start", "support"); 594 | } 595 | async rebaseSupport(node: Flow | undefined) { 596 | this.general("rebase", node?.full, this.branches.support); 597 | } 598 | async deleteSupport(node: Flow | undefined) { 599 | let name = node?.full; 600 | if (name === undefined) { 601 | name = await vscode.window.showQuickPick( 602 | this.listBranches.filter((el) => el.search(this.branches.support) !== -1), 603 | { title: "Select bugfix" } 604 | ); 605 | } 606 | if (name === undefined) { 607 | return; 608 | } 609 | 610 | let list: string[] = ["[-f] Force deletion"]; 611 | if (this.listRemoteBranches.includes(`${name}`)) { 612 | list.push("[-r] Delete remote branch"); 613 | } 614 | let options = await vscode.window.showQuickPick(list, { 615 | title: "Select options", 616 | canPickMany: true, 617 | }); 618 | let option = options?.includes("[-f] Force deletion") ? "-D" : "-d"; 619 | 620 | this.util.execSync(`"${this.util.path}" checkout -d ${this.branches.develop}`); 621 | this.util.execSync(`"${this.util.path}" branch ${option} ${name}`); 622 | 623 | if (options?.includes("[-r] Delete remote branch")) { 624 | this.util.exec(`"${this.util.path}" push --delete origin ${name}`, true, () => { 625 | this._onDidChangeTreeData.fire(); 626 | }); 627 | return; 628 | } 629 | 630 | this._onDidChangeTreeData.fire(); 631 | } 632 | async publishSupport(node: Flow | undefined) { 633 | if (!this.hasOrigin) { 634 | vscode.window.showWarningMessage("No ORIGIN remote has been found!"); 635 | return; 636 | } 637 | let name = node?.full; 638 | if (name === undefined) { 639 | name = await vscode.window.showQuickPick( 640 | this.listBranches.filter((el) => el.search(this.branches.support) !== -1), 641 | { title: "Select support branch" } 642 | ); 643 | } 644 | if (name === undefined) { 645 | return; 646 | } 647 | 648 | let cmd = `"${this.util.path}" push origin ${name}`; 649 | 650 | this.util.exec(cmd, true, (s) => { 651 | this._onDidChangeTreeData.fire(); 652 | }); 653 | } 654 | async checkoutSupport(node: Flow | undefined) { 655 | let name = node?.full; 656 | if (name === undefined) { 657 | name = await vscode.window.showQuickPick( 658 | this.listBranches.filter((el) => el.search(this.branches.support) !== -1), 659 | { title: "Select support branch" } 660 | ); 661 | } 662 | if (name === undefined) { 663 | return; 664 | } 665 | 666 | let cmd = `"${this.util.path}" checkout -q ${name}`; 667 | 668 | this.util.exec(cmd, false, (s) => { 669 | this._onDidChangeTreeData.fire(); 670 | }); 671 | } 672 | 673 | //#endregion 674 | 675 | //#region Bugfix 676 | async startBugfix() { 677 | this.general("start", "bugfix"); 678 | } 679 | async finishBugfix(node: Flow | undefined) { 680 | this.general("finish", node?.full, this.branches.bugfix); 681 | } 682 | async rebaseBugfix(node: Flow | undefined) { 683 | this.general("rebase", node?.full, this.branches.bugfix); 684 | } 685 | async publishBugfix(node: Flow | undefined) { 686 | this.general("publish", node?.full, this.branches.bugfix); 687 | } 688 | async deleteBugfix(node: Flow | undefined) { 689 | this.general("delete", node?.full, this.branches.bugfix); 690 | } 691 | async checkoutBugfix(node: Flow | undefined) { 692 | this.general("checkout", node?.full, this.branches.bugfix); 693 | } 694 | async trackBugfix(node: Flow | undefined) { 695 | this.general("track", node?.full, this.branches.bugfix); 696 | } 697 | //#endregion 698 | 699 | //#region Features 700 | async startFeature() { 701 | this.general("start", "feature"); 702 | } 703 | async finishFeature(node: Flow | undefined) { 704 | this.general("finish", node?.full, this.branches.feature); 705 | } 706 | async rebaseFeature(node: Flow | undefined) { 707 | this.general("rebase", node?.full, this.branches.feature); 708 | } 709 | async publishFeature(node: Flow | undefined) { 710 | this.general("publish", node?.full, this.branches.feature); 711 | } 712 | async deleteFeature(node: Flow | undefined) { 713 | this.general("delete", node?.full, this.branches.feature); 714 | } 715 | async checkoutFeature(node: Flow | undefined) { 716 | this.general("checkout", node?.full, this.branches.feature); 717 | } 718 | async trackFeature(node: Flow | undefined) { 719 | this.general("track", node?.full, this.branches.feature); 720 | } 721 | //#endregion 722 | 723 | //#region Hotfix 724 | async startHotfix() { 725 | this.general("start", "hotfix"); 726 | } 727 | async checkoutHotfix(node: Flow | undefined) { 728 | this.general("checkout", node?.full, this.branches.hotfix); 729 | } 730 | async publishHotfix(node: Flow | undefined) { 731 | this.general("publish", node?.full, this.branches.hotfix); 732 | } 733 | async deleteHotfix(node: Flow | undefined) { 734 | this.general("delete", node?.full, this.branches.hotfix); 735 | } 736 | async rebaseHotfix(node: Flow | undefined) { 737 | this.general("rebase", node?.full, this.branches.hotfix); 738 | } 739 | async finishHotfix(node: Flow | undefined) { 740 | this.general("finish", node?.full, this.branches.hotfix); 741 | } 742 | //#endregion 743 | 744 | //#region Releases 745 | async startRelease() { 746 | this.general("start", "release"); 747 | } 748 | async checkoutRelease(node: Flow | undefined) { 749 | let name = node?.full; 750 | if (name === undefined) { 751 | name = await vscode.window.showQuickPick( 752 | this.listBranches.filter((el) => el.search(this.branches.release) !== -1), 753 | { title: "Select release branch" } 754 | ); 755 | } 756 | if (name === undefined) { 757 | return; 758 | } 759 | let cmd = `"${this.util.path}" checkout -q ${name}`; 760 | 761 | this.util.exec(cmd, false, (s) => { 762 | this._onDidChangeTreeData.fire(); 763 | }); 764 | } 765 | async publishRelease(node: Flow | undefined) { 766 | this.general("publish", node?.full, this.branches.release); 767 | } 768 | async deleteRelease(node: Flow | undefined) { 769 | this.general("delete", node?.full, this.branches.release); 770 | } 771 | async trackRelease(node: Flow | undefined) { 772 | this.general("track", node?.full, this.branches.release); 773 | } 774 | async finishRelease(node: Flow | undefined) { 775 | this.general("finish", node?.full, this.branches.release); 776 | } 777 | async rebaseRelease(node: Flow | undefined) { 778 | this.general("rebase", node?.full, this.branches.release); 779 | } 780 | //#endregion 781 | 782 | version(): string { 783 | return this.util.execSync(`${this.util.flowPath} version`); 784 | } 785 | 786 | refresh(): void { 787 | this._onDidChangeTreeData.fire(); 788 | } 789 | 790 | init() { 791 | this._runTerminal("git flow init -f"); 792 | } 793 | 794 | _initTerminal() { 795 | let terminal: vscode.Terminal | null = null; 796 | const terminals = (vscode.window).terminals; 797 | terminals.forEach((t) => { 798 | if (t.name === "GitFlow") { 799 | terminal = t; 800 | } 801 | }); 802 | if (terminal === null) { 803 | this.terminal = vscode.window.createTerminal(`GitFlow`); 804 | } else { 805 | this.terminal = terminal; 806 | } 807 | } 808 | _runTerminal(cmd: string): void { 809 | this._initTerminal(); 810 | this.terminal?.show(); 811 | // this.terminal?.sendText(`cd ${this.util.workspaceRoot}`); 812 | const safePath = this.util.workspaceRoot.replace(/([\"\\\s\'\$\`])/g, '\\$1'); 813 | this.terminal?.sendText(`cd "${safePath}"`); 814 | this.terminal?.sendText(cmd); 815 | } 816 | 817 | _isCurrent(name: string | undefined): boolean { 818 | return name === this.curBranch; 819 | } 820 | } 821 | 822 | export class Flow extends vscode.TreeItem { 823 | constructor( 824 | public full: string, 825 | public readonly label: string, 826 | private icon: string, 827 | public readonly collapsibleState: vscode.TreeItemCollapsibleState, 828 | public current?: boolean, 829 | public parent?: string 830 | ) { 831 | super(label, collapsibleState); 832 | if (current) { 833 | this.description = "Current"; 834 | this.iconPath = new vscode.ThemeIcon("home", new vscode.ThemeColor("green")); 835 | } 836 | this.contextValue = parent ? parent : full.replace("* ", "").split("/")[0]; 837 | } 838 | 839 | iconPath = new vscode.ThemeIcon(this.icon); 840 | } 841 | -------------------------------------------------------------------------------- /src/ViewVersions.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { Util } from "./lib/Util"; 3 | 4 | let checked: boolean = false; 5 | 6 | export class TreeViewVersions implements vscode.TreeDataProvider { 7 | private _onDidChangeTreeData: vscode.EventEmitter = 8 | new vscode.EventEmitter(); 9 | readonly onDidChangeTreeData: vscode.Event = 10 | this._onDidChangeTreeData.event; 11 | private terminal: vscode.Terminal | null; 12 | private remotes: string[] = []; 13 | private tags: string[] = []; 14 | private hasOrigin: boolean = false; 15 | 16 | constructor(private util: Util) { 17 | this.terminal = null; 18 | } 19 | 20 | refresh(): void { 21 | this._onDidChangeTreeData.fire(); 22 | } 23 | 24 | getTreeItem(element: Tag): vscode.TreeItem { 25 | return element; 26 | } 27 | 28 | getChildren(element?: Tag): Thenable { 29 | if (!checked && !this.util.check()) { 30 | return Promise.resolve([]); 31 | } 32 | checked = true; 33 | this.util 34 | .execSync(`"${this.util.path}" remote`) 35 | .split("\n") 36 | .map((el) => { 37 | if (el.toLowerCase().trim() === "origin") { 38 | this.hasOrigin = true; 39 | } 40 | }); 41 | 42 | if (this.hasOrigin) { 43 | this.remotes = this.util 44 | .execSync(`"${this.util.path}" ls-remote --tags origin`) 45 | .split("\n") 46 | .filter((el) => el.trim().search("refs/tags/") > 0) 47 | .map((el) => el.split("/")[2].replace("^{}", "")); 48 | } 49 | 50 | this.tags = this.util 51 | .execSync(`"${this.util.path}" tag --sort=-v:refname`) 52 | .split("\n") 53 | .map((el) => el.trim()) 54 | .filter((el) => el !== ""); 55 | let list: Tag[] = []; 56 | this.tags.forEach((el) => { 57 | if (el.search(" ") !== -1) { 58 | return; 59 | } 60 | list.push(new Tag(el, !this.remotes.includes(el))); 61 | }); 62 | 63 | return Promise.resolve(list); 64 | } 65 | 66 | async deleteTag(node: Tag | undefined) { 67 | let name = node?.label; 68 | if (node === undefined) { 69 | let tags = this.util 70 | .execSync(`"${this.util.path}" tag --sort=-v:refname`) 71 | .split("\n") 72 | .map((el) => el.trim()) 73 | .filter((el) => el !== ""); 74 | name = await vscode.window.showQuickPick(tags, {}); 75 | } 76 | if (name === undefined) { 77 | return; 78 | } 79 | 80 | let remotes = ["Delete local"]; 81 | if (this.remotes.includes(name)) { 82 | remotes = 83 | (await vscode.window.showQuickPick(["Delete local", "Delete Remote"], { 84 | title: `Where to delete from?`, 85 | canPickMany: true, 86 | })) || []; 87 | } 88 | if (remotes.includes("Delete Remote")) { 89 | this.util.execSync(`"${this.util.path}" push --delete origin ${name}`); 90 | } 91 | if (remotes.includes("Delete local")) { 92 | this.util.execSync(`"${this.util.path}" tag -d ${name}`); 93 | } 94 | this._onDidChangeTreeData.fire(); 95 | } 96 | 97 | async pushTag(node: Tag | undefined) { 98 | if(!this.hasOrigin) { 99 | vscode.window.showWarningMessage("No ORIGIN remote has been found!"); 100 | return; 101 | } 102 | let name = node?.label; 103 | if (node === undefined) { 104 | let tags = this.util 105 | .execSync(`"${this.util.path}" tag --sort=-v:refname`) 106 | .split("\n") 107 | .map((el) => el.trim()) 108 | .filter((el) => el !== ""); 109 | name = await vscode.window.showQuickPick(tags, {}); 110 | } 111 | if (name === undefined) { 112 | return; 113 | } 114 | 115 | this.util.exec(`"${this.util.path}" push origin ${name}`, true, (s) => { 116 | this._onDidChangeTreeData.fire(); 117 | }); 118 | } 119 | pushTags() { 120 | if(!this.hasOrigin) { 121 | vscode.window.showWarningMessage("No ORIGIN remote has been found!"); 122 | return; 123 | } 124 | this.util.exec(`"${this.util.path}" push origin --tags`, true, (s) => { 125 | this._onDidChangeTreeData.fire(); 126 | }); 127 | } 128 | } 129 | 130 | export class Tag extends vscode.TreeItem { 131 | constructor(public readonly label: string, private descr: boolean = false) { 132 | super(label, vscode.TreeItemCollapsibleState.None); 133 | this.label = label; 134 | this.contextValue = descr ? "local" : ""; 135 | this.description = descr ? "local" : ""; 136 | } 137 | 138 | iconPath = new vscode.ThemeIcon("tag"); 139 | } 140 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { CommandManager } from "./lib/cm"; 3 | import { Logger } from "./lib/logger"; 4 | import { Util } from "./lib/Util"; 5 | import { Flow, TreeViewBranches } from "./ViewBranches"; 6 | import { TreeViewVersions, Tag } from "./ViewVersions"; 7 | 8 | export function activate(context: vscode.ExtensionContext) { 9 | let rootPath: string = 10 | vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0 11 | ? vscode.workspace.workspaceFolders[0].uri.fsPath 12 | : ""; 13 | 14 | const logger = new Logger(); 15 | logger.log("Extension activate", "activate"); 16 | logger.log(rootPath, "Root"); 17 | 18 | 19 | let statBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10); 20 | statBar.command = "gitflow.quickPick"; 21 | statBar.text = "$(list-flat) Git Flow"; 22 | statBar.tooltip = "Show Git Flow Quick Pick menu"; 23 | statBar.show(); 24 | context.subscriptions.push(statBar); 25 | 26 | const util = new Util(rootPath, logger, statBar); 27 | 28 | const viewBranches = new TreeViewBranches(util); 29 | const a = vscode.window.createTreeView("gitflowExplorer", { 30 | treeDataProvider: viewBranches, 31 | showCollapseAll: true, 32 | }); 33 | viewBranches.getChildren(); 34 | const viewVersions = new TreeViewVersions(util); 35 | const b = vscode.window.createTreeView("gitflowTags", { 36 | treeDataProvider: viewVersions, 37 | }); 38 | viewVersions.getChildren(); 39 | 40 | if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1) { 41 | a.message = "Current repo: " + rootPath.split("/").reverse()[0]; 42 | b.message = "Current repo: " + rootPath.split("/").reverse()[0]; 43 | } 44 | 45 | const cm: CommandManager = new CommandManager(context, logger, viewBranches, viewVersions); 46 | 47 | cm.rc("gitflow.switchRepo", async () => { 48 | let list: string[] = vscode.workspace.workspaceFolders?.map((el) => el.uri.fsPath) || []; 49 | if (list.length < 2) { 50 | vscode.window.showInformationMessage("Not a multi folder vscode.workspace"); 51 | return; 52 | } 53 | let repo = await vscode.window.showQuickPick(list, { 54 | title: "Select active repository", 55 | }); 56 | if (repo === undefined) { 57 | return; 58 | } 59 | 60 | util.workspaceRoot = `${repo}`; 61 | a.message = "Current repo: " + `${repo}`.split("/").reverse()[0]; 62 | b.message = "Current repo: " + `${repo}`.split("/").reverse()[0]; 63 | viewBranches.refresh(); 64 | viewVersions.refresh(); 65 | }); 66 | } 67 | 68 | export function deactivate() { } 69 | -------------------------------------------------------------------------------- /src/lib/Util.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { execSync, exec, spawn, SpawnOptions } from "child_process"; 3 | import { Logger, LogLevels } from "./logger"; 4 | import { Memoize, MemoizeExpiring } from "typescript-memoize"; 5 | import { GitExtension, API as GitAPI } from "./git"; 6 | // import { GitBaseExtension, API as GitBaseAPI } from "./lib/git-base"; 7 | 8 | type CmdResult = { 9 | retc: number | null; 10 | stdout: string[]; 11 | stderr: string[]; 12 | }; 13 | 14 | export class Util { 15 | public path: string = ''; 16 | public flowPath: string = ''; 17 | constructor(public workspaceRoot: string, private logger: Logger, public sb: vscode.StatusBarItem) { 18 | this.path = vscode.workspace.getConfiguration('git').get('path') || ""; 19 | if (this.path.trim().length === 0) { 20 | const gitExtension = vscode.extensions.getExtension("vscode.git")!.exports; 21 | const git = gitExtension.getAPI(1); 22 | this.path = git.git.path; 23 | } 24 | if (this.path.trim().length === 0) { 25 | vscode.window.showWarningMessage("Git is not found", this.path); 26 | return; 27 | } 28 | this.flowPath = vscode.workspace.getConfiguration('gitflow').get('path') || `"${this.path}" flow`; 29 | this.logger.log("Git found (path)", this.path); 30 | } 31 | 32 | private progress(cmd: string, cb: (s: string) => void) { 33 | vscode.window.withProgress( 34 | { 35 | location: vscode.ProgressLocation.Notification, 36 | title: `Executing ${cmd}`, 37 | cancellable: false, 38 | }, 39 | (progress, token) => 40 | new Promise((resolve) => { 41 | setTimeout(() => { 42 | this.execCb(cmd, (res) => { 43 | cb(res); 44 | resolve(); 45 | }, resolve); 46 | }, 100); 47 | }) 48 | ); 49 | } 50 | 51 | public exec(cmd: string, progress: boolean, cb: (s: string) => void): void { 52 | if (progress) { 53 | this.progress(cmd, cb); 54 | } else { 55 | this.execCb(cmd, cb); 56 | } 57 | } 58 | 59 | @MemoizeExpiring(1000) 60 | public execSync(cmd: string): string { 61 | if (this.path.trim().length === 0) { 62 | return ""; 63 | } 64 | this.sb.text = "$(sync~spin) Git Flow in progress..."; 65 | try { 66 | let out = execSync(cmd, { cwd: this.workspaceRoot }).toString(); 67 | this.logger.log(out, cmd); 68 | this.sb.text = "$(list-flat) Git Flow"; 69 | return out; 70 | } catch (e) { 71 | this.sb.text = "$(list-flat) Git Flow"; 72 | this.logger.log(`ERROR: ${e}`, cmd, LogLevels.error); 73 | vscode.window.showErrorMessage(`Error executing: ${cmd} : ${e}`); 74 | return "" + e; 75 | } 76 | } 77 | 78 | @MemoizeExpiring(1000) 79 | private execCb(cmd: string, cb: (s: string) => void, resolve?: any): void { 80 | if (this.path.trim().length === 0) { 81 | return; 82 | } 83 | this.sb.text = "$(sync~spin) Git Flow in progress..."; 84 | exec( 85 | cmd, { cwd: this.workspaceRoot, }, 86 | (err, stdout, stderr) => { 87 | this.sb.text = "$(list-flat) Git Flow"; 88 | if (err) { 89 | vscode.window.showErrorMessage(`Error executing: ${cmd} : ${err}`); 90 | this.logger.log(`${err} ${stderr}`, cmd, LogLevels.error); 91 | if (resolve !== undefined) { 92 | resolve(); 93 | } 94 | return; 95 | } 96 | cb(stdout); 97 | this.logger.log(`${stdout}`, cmd); 98 | vscode.window.showInformationMessage(`${stdout}`); 99 | } 100 | ); 101 | } 102 | 103 | public check(): boolean { 104 | if (!this.workspaceRoot) { 105 | vscode.window.showErrorMessage("No folder opened"); 106 | return false; 107 | } 108 | let status = this.execSync(`"${this.path}" version`).toLowerCase(); 109 | if (status.search("git version") === -1) { 110 | vscode.window.showWarningMessage("Looks like git CLI is not installed."); 111 | return false; 112 | } 113 | 114 | status = this.execSync(`"${this.path}" status`).toLowerCase(); 115 | 116 | if (status.search("not a git repository") !== -1) { 117 | vscode.window.showWarningMessage("This project is not a Git repository."); 118 | return false; 119 | } 120 | 121 | if (this.execSync(`${this.flowPath} version`).toLowerCase().search("is not a git command") !== -1) { 122 | let installLink = "Install"; 123 | vscode.window 124 | .showWarningMessage("To use Git Flow extension please install Git flow (AVH).", installLink) 125 | .then((selection) => { 126 | if (selection === installLink) { 127 | vscode.env.openExternal( 128 | vscode.Uri.parse( 129 | "https://github.com/petervanderdoes/gitflow-avh/wiki/Installation" 130 | ) 131 | ); 132 | } 133 | }); 134 | return false; 135 | } 136 | return true; 137 | } 138 | } 139 | // public cmd(cmd: string, args?: string[]): Promise { 140 | // let options: SpawnOptions = {}; 141 | // options.cwd = this.workspaceRoot; 142 | // //options.shell = true; 143 | // //options.stdio = ['inherit', 'inherit', 'inherit']; 144 | // options.detached = true; 145 | 146 | // return new Promise((resolve, reject) => { 147 | // console.log(cmd, args?.join(' ')); 148 | // const child = spawn(cmd, args || [], options); 149 | // child.on('error', err => { reject(err); }); 150 | 151 | // let stdout: string[] = []; 152 | // let stderr: string[] = []; 153 | 154 | // child.stdout?.on('data', (data: Uint8Array) => { 155 | // console.log(`Stdout: ${data}`); 156 | // stdout.push(data.toString()); 157 | // }); 158 | // child.stderr?.on('data', (data: Uint8Array) => { 159 | // console.log(`Stderr: ${data}`); 160 | // stderr.push(data.toString()); 161 | // }); 162 | // child.on('error', err => { 163 | // console.error(`${cmd} ${args?.join(" ")}" returned`, err); 164 | // }); 165 | // child.on('spawm', err => { 166 | // console.log(`Spawn: "${cmd} ${args?.join(" ")}" returned`, err); 167 | // }); 168 | // child.on('exit', code => { 169 | // console.log(`Exit: "${cmd} ${args?.join(" ")} exited`, code); 170 | // }); 171 | // child.on('close', retc => { 172 | // console.log(`Close: "${cmd}" returned code ${retc}:`, stderr, stdout); 173 | // //resolve({ retc: retc, stdout: stdout, stderr: stderr }); 174 | // }); 175 | // }); 176 | // } 177 | 178 | // async cmdCb(cmd: string, args?: string[], cb?: (res: CmdResult) => void) { 179 | // let result = await this.cmd(cmd, args); 180 | // if (result.retc !== 0) { 181 | // vscode.window.showErrorMessage(`Error: ${cmd} ${args?.join(" ")} : ${result.stderr.join("\n")}`); 182 | // return; 183 | // } 184 | // if (typeof cb === 'function' && result.retc === 0) { 185 | // vscode.window.showInformationMessage(`Success:${result.stdout.join("\n")}`); 186 | // cb(result); 187 | // } 188 | // } 189 | -------------------------------------------------------------------------------- /src/lib/cm.ts: -------------------------------------------------------------------------------- 1 | import {commands, ExtensionContext, QuickPickItemKind, window, workspace} from "vscode"; 2 | import {Flow, TreeViewBranches} from "../ViewBranches"; 3 | import {Tag, TreeViewVersions} from "../ViewVersions"; 4 | import {Disposable} from "./disposables"; 5 | import {Logger} from "./logger"; 6 | 7 | export class CommandManager extends Disposable { 8 | constructor( 9 | private context: ExtensionContext, 10 | private logger: Logger, 11 | viewBranches: TreeViewBranches, 12 | viewVersions: TreeViewVersions 13 | ) { 14 | super(); 15 | 16 | this.rc("gitflow.quickPick", async () => { 17 | if (viewBranches.listBranches.length < 2) { 18 | window.showWarningMessage("Looks like view was not yet initialized"); 19 | return; 20 | } 21 | 22 | let list = [ 23 | {label: "Start new branch", id: "", kind: QuickPickItemKind.Separator}, 24 | {label: "$(test-view-icon) Start Feature", id: "newFeature", description: ""}, 25 | {label: "$(callstack-view-session) Start Bugfix", id: "newBugfix", description: ""}, 26 | {label: "$(history) Start Support", id: "newSupport", description: ""}, 27 | ]; 28 | // Only single release might be at a time 29 | if (viewBranches.listBranches.filter((el) => el.search("release/") !== -1).length === 0) { 30 | list.push({label: "$(tag) Start Release", id: "newRelease", description: ""}); 31 | } 32 | // Only single hotfix at a time 33 | if (viewBranches.listBranches.filter((el) => el.search("hotfix/") !== -1).length === 0) { 34 | list.push({label: "$(flame) Start Hotfix", id: "newHotfix", description: ""}); 35 | } 36 | 37 | let cur = viewBranches.curBranch.split("/")[0]; 38 | if (["feature", "release", "hotfix", "bugfix"].includes(cur)) { 39 | list.push({ 40 | label: "Current branch", 41 | id: "", 42 | kind: QuickPickItemKind.Separator, 43 | }); 44 | list.push({ 45 | label: `$(trash) Delete ${ucf(cur)}`, 46 | description: viewBranches.curBranch, 47 | id: "delete", 48 | }); 49 | list.push({ 50 | label: `$(debug-stop) Finalize ${ucf(cur)}`, 51 | description: viewBranches.curBranch, 52 | id: "finish", 53 | }); 54 | list.push({ 55 | label: `$(git-merge) Rebase ${ucf(cur)}`, 56 | description: viewBranches.curBranch, 57 | id: "rebase", 58 | }); 59 | if (!viewBranches.listRemoteBranches.includes(viewBranches.curBranch)) { 60 | list.push({ 61 | label: `$(cloud-upload) Publish ${ucf(cur)}`, 62 | description: viewBranches.curBranch, 63 | id: "publish", 64 | }); 65 | } 66 | } 67 | let action = await window.showQuickPick(list, { 68 | title: "Select an action", 69 | }); 70 | if (action === undefined) { 71 | return; 72 | } 73 | if (action.id.search("new") !== -1) { 74 | await viewBranches.general("start", action.id.replace("new", "").toLowerCase()); 75 | } else { 76 | await viewBranches.general(action.id, action.description); 77 | } 78 | commands.executeCommand("workbench.view.scm"); 79 | 80 | function ucf(string: string) { 81 | return string.charAt(0).toUpperCase() + string.slice(1); 82 | } 83 | }); 84 | this.rc("gitflow.refreshB", () => { 85 | viewBranches.refresh(); 86 | }); 87 | this.rc("gitflow.fetchAllBranches", () => { 88 | viewBranches.fetchAllBranches(); 89 | }); 90 | this.rc("gitflow.init", () => { 91 | viewBranches.init(); 92 | }); 93 | this.rc("gitflow.checkoutBranch", (node?: Flow) => { 94 | viewBranches.checkoutBranch(node); 95 | }); 96 | this.rc("gitflow.syncAll", () => { 97 | viewBranches.syncAll(); 98 | }); 99 | 100 | this.rc("gitflow.newSupport", () => { 101 | viewBranches.startSupport(); 102 | }); 103 | this.rc("gitflow.checkoutSupport", (node?: Flow) => { 104 | viewBranches.checkoutSupport(node); 105 | }); 106 | this.rc("gitflow.deleteSupport", (node?: Flow) => { 107 | viewBranches.deleteSupport(node); 108 | }); 109 | this.rc("gitflow.rebaseSupport", (node?: Flow) => { 110 | viewBranches.rebaseSupport(node); 111 | }); 112 | this.rc("gitflow.publishSupport", (node?: Flow) => { 113 | viewBranches.publishSupport(node); 114 | }); 115 | 116 | this.rc("gitflow.newHotfix", () => { 117 | viewBranches.startHotfix(); 118 | }); 119 | this.rc("gitflow.publishHotfix", (node?: Flow) => { 120 | viewBranches.publishHotfix(node); 121 | }); 122 | this.rc("gitflow.deleteHotfix", (node?: Flow) => { 123 | viewBranches.deleteHotfix(node); 124 | }); 125 | this.rc("gitflow.finishHotfix", (node?: Flow) => { 126 | viewBranches.finishHotfix(node); 127 | }); 128 | this.rc("gitflow.rebaseHotfix", (node?: Flow) => { 129 | viewBranches.rebaseHotfix(node); 130 | }); 131 | this.rc("gitflow.checkoutHotfix", (node?: Flow) => { 132 | viewBranches.checkoutHotfix(node); 133 | }); 134 | 135 | this.rc("gitflow.newRelease", () => { 136 | viewBranches.startRelease(); 137 | }); 138 | this.rc("gitflow.publishRelease", (node?: Flow) => { 139 | viewBranches.publishRelease(node); 140 | }); 141 | this.rc("gitflow.deleteRelease", (node?: Flow) => { 142 | viewBranches.deleteRelease(node); 143 | }); 144 | this.rc("gitflow.finishRelease", (node?: Flow) => { 145 | viewBranches.finishRelease(node); 146 | }); 147 | this.rc("gitflow.trackRelease", (node?: Flow) => { 148 | viewBranches.trackRelease(node); 149 | }); 150 | this.rc("gitflow.rebaseRelease", (node?: Flow) => { 151 | viewBranches.rebaseRelease(node); 152 | }); 153 | this.rc("gitflow.checkoutRelease", (node?: Flow) => { 154 | viewBranches.checkoutRelease(node); 155 | }); 156 | 157 | this.rc("gitflow.newFeature", () => { 158 | viewBranches.startFeature(); 159 | }); 160 | this.rc("gitflow.checkoutFeature", (node?: Flow) => { 161 | viewBranches.checkoutFeature(node); 162 | }); 163 | this.rc("gitflow.publishFeature", (node?: Flow) => { 164 | viewBranches.publishFeature(node); 165 | }); 166 | this.rc("gitflow.deleteFeature", (node?: Flow) => { 167 | viewBranches.deleteFeature(node); 168 | }); 169 | this.rc("gitflow.rebaseFeature", (node?: Flow) => { 170 | viewBranches.rebaseFeature(node); 171 | }); 172 | this.rc("gitflow.trackFeature", (node?: Flow) => { 173 | viewBranches.trackFeature(node); 174 | }); 175 | this.rc("gitflow.finishFeature", (node?: Flow) => { 176 | viewBranches.finishFeature(node); 177 | }); 178 | 179 | this.rc("gitflow.newBugfix", () => { 180 | viewBranches.startBugfix(); 181 | }); 182 | this.rc("gitflow.checkoutBugfix", (node?: Flow) => { 183 | viewBranches.checkoutBugfix(node); 184 | }); 185 | this.rc("gitflow.publishBugfix", (node?: Flow) => { 186 | viewBranches.publishBugfix(node); 187 | }); 188 | this.rc("gitflow.deleteBugfix", (node?: Flow) => { 189 | viewBranches.deleteBugfix(node); 190 | }); 191 | this.rc("gitflow.rebaseBugfix", (node?: Flow) => { 192 | viewBranches.rebaseBugfix(node); 193 | }); 194 | this.rc("gitflow.trackBugfix", (node?: Flow) => { 195 | viewBranches.trackBugfix(node); 196 | }); 197 | this.rc("gitflow.finishBugfix", (node?: Flow) => { 198 | viewBranches.finishBugfix(node); 199 | }); 200 | 201 | this.rc("gitflow.refreshT", () => { 202 | viewVersions.refresh(); 203 | }); 204 | this.rc("gitflow.pushTags", () => { 205 | viewVersions.pushTags(); 206 | }); 207 | this.rc("gitflow.pushTag", (e?: Tag) => { 208 | viewVersions.pushTag(e); 209 | }); 210 | this.rc("gitflow.deleteTag", (e?: Tag) => { 211 | viewVersions.deleteTag(e); 212 | }); 213 | } 214 | 215 | /** 216 | * Register command short way 217 | * @param command 218 | * @param callback 219 | */ 220 | public rc(command: string, callback: (...args: any[]) => any) { 221 | this.context.subscriptions.push( 222 | commands.registerCommand(command, (...args: any[]) => { 223 | this.logger.log(command, "Run command"); 224 | callback(...args); 225 | }) 226 | ); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/lib/decorators.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | function decorate(decorator: (fn: Function, key: string) => Function): Function { 7 | return (_target: any, key: string, descriptor: any) => { 8 | let fnKey: string | null = null; 9 | let fn: Function | null = null; 10 | 11 | if (typeof descriptor.value === 'function') { 12 | fnKey = 'value'; 13 | fn = descriptor.value; 14 | } else if (typeof descriptor.get === 'function') { 15 | fnKey = 'get'; 16 | fn = descriptor.get; 17 | } 18 | 19 | if (!fn || !fnKey) { 20 | throw new Error('not supported'); 21 | } 22 | 23 | descriptor[fnKey] = decorator(fn, key); 24 | }; 25 | } 26 | 27 | function _memoize(fn: Function, key: string): Function { 28 | const memoizeKey = `$memoize$${key}`; 29 | 30 | return function (this: any, ...args: any[]) { 31 | if (!this.hasOwnProperty(memoizeKey)) { 32 | Object.defineProperty(this, memoizeKey, { 33 | configurable: false, 34 | enumerable: false, 35 | writable: false, 36 | value: fn.apply(this, args) 37 | }); 38 | } 39 | 40 | return this[memoizeKey]; 41 | }; 42 | } 43 | 44 | export const memoize = decorate(_memoize); 45 | 46 | function _throttle(fn: Function, key: string): Function { 47 | const currentKey = `$throttle$current$${key}`; 48 | const nextKey = `$throttle$next$${key}`; 49 | 50 | const trigger = function (this: any, ...args: any[]) { 51 | if (this[nextKey]) { 52 | return this[nextKey]; 53 | } 54 | 55 | if (this[currentKey]) { 56 | this[nextKey] = (this[currentKey]).then(() => { 57 | this[nextKey] = undefined; 58 | return trigger.apply(this, args); 59 | }); 60 | 61 | return this[nextKey]; 62 | } 63 | 64 | this[currentKey] = fn.apply(this, args) as Promise; 65 | 66 | const clear = () => this[currentKey] = undefined; 67 | done(this[currentKey]).then(clear, clear); 68 | 69 | return this[currentKey]; 70 | }; 71 | 72 | return trigger; 73 | } 74 | 75 | export const throttle = decorate(_throttle); 76 | 77 | function _sequentialize(fn: Function, key: string): Function { 78 | const currentKey = `__$sequence$${key}`; 79 | 80 | return function (this: any, ...args: any[]) { 81 | const currentPromise = this[currentKey] as Promise || Promise.resolve(null); 82 | const run = async () => await fn.apply(this, args); 83 | this[currentKey] = currentPromise.then(run, run); 84 | return this[currentKey]; 85 | }; 86 | } 87 | 88 | export const sequentialize = decorate(_sequentialize); 89 | 90 | export function debounce(delay: number): Function { 91 | return decorate((fn, key) => { 92 | const timerKey = `$debounce$${key}`; 93 | 94 | return function (this: any, ...args: any[]) { 95 | clearTimeout(this[timerKey]); 96 | this[timerKey] = setTimeout(() => fn.apply(this, args), delay); 97 | }; 98 | }); 99 | } 100 | 101 | function done(promise: Promise): Promise { 102 | return promise.then(() => undefined); 103 | } 104 | -------------------------------------------------------------------------------- /src/lib/disposables.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export class Disposable implements vscode.Disposable { 4 | private disposables: vscode.Disposable[] = []; 5 | 6 | /** 7 | * Disposes the resources used by the subclass. 8 | */ 9 | public dispose() { 10 | this.disposables.forEach((disposable) => disposable.dispose()); 11 | this.disposables = []; 12 | } 13 | 14 | /** 15 | * Register a single disposable. 16 | */ 17 | protected registerDisposable(disposable: vscode.Disposable) { 18 | this.disposables.push(disposable); 19 | } 20 | 21 | /** 22 | * Register multiple disposables. 23 | */ 24 | protected registerDisposables(...disposables: vscode.Disposable[]) { 25 | this.disposables.push(...disposables); 26 | } 27 | } 28 | 29 | export function toDisposable(fn: () => void): vscode.Disposable { 30 | return { 31 | dispose: fn 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/git-base.d.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Disposable, Event, ProviderResult, Uri } from 'vscode'; 7 | export { ProviderResult } from 'vscode'; 8 | 9 | export interface API { 10 | pickRemoteSource(options: PickRemoteSourceOptions): Promise; 11 | registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable; 12 | } 13 | 14 | export interface GitBaseExtension { 15 | 16 | readonly enabled: boolean; 17 | readonly onDidChangeEnablement: Event; 18 | 19 | /** 20 | * Returns a specific API version. 21 | * 22 | * Throws error if git-base extension is disabled. You can listed to the 23 | * [GitBaseExtension.onDidChangeEnablement](#GitBaseExtension.onDidChangeEnablement) 24 | * event to know when the extension becomes enabled/disabled. 25 | * 26 | * @param version Version number. 27 | * @returns API instance 28 | */ 29 | getAPI(version: 1): API; 30 | } 31 | 32 | export interface PickRemoteSourceOptions { 33 | readonly providerLabel?: (provider: RemoteSourceProvider) => string; 34 | readonly urlLabel?: string; 35 | readonly providerName?: string; 36 | readonly branch?: boolean; // then result is PickRemoteSourceResult 37 | } 38 | 39 | export interface PickRemoteSourceResult { 40 | readonly url: string; 41 | readonly branch?: string; 42 | } 43 | 44 | export interface RemoteSource { 45 | readonly name: string; 46 | readonly description?: string; 47 | readonly url: string | string[]; 48 | } 49 | 50 | export interface RemoteSourceProvider { 51 | readonly name: string; 52 | /** 53 | * Codicon name 54 | */ 55 | readonly icon?: string; 56 | readonly supportsQuery?: boolean; 57 | 58 | getBranches?(url: string): ProviderResult; 59 | getRemoteSources(query?: string): ProviderResult; 60 | } 61 | -------------------------------------------------------------------------------- /src/lib/git.d.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Uri, Event, Disposable, ProviderResult } from 'vscode'; 7 | export { ProviderResult } from 'vscode'; 8 | 9 | export interface Git { 10 | readonly path: string; 11 | } 12 | 13 | export interface InputBox { 14 | value: string; 15 | } 16 | 17 | export const enum ForcePushMode { 18 | Force, 19 | ForceWithLease 20 | } 21 | 22 | export const enum RefType { 23 | Head, 24 | RemoteHead, 25 | Tag 26 | } 27 | 28 | export interface Ref { 29 | readonly type: RefType; 30 | readonly name?: string; 31 | readonly commit?: string; 32 | readonly remote?: string; 33 | } 34 | 35 | export interface UpstreamRef { 36 | readonly remote: string; 37 | readonly name: string; 38 | } 39 | 40 | export interface Branch extends Ref { 41 | readonly upstream?: UpstreamRef; 42 | readonly ahead?: number; 43 | readonly behind?: number; 44 | } 45 | 46 | export interface Commit { 47 | readonly hash: string; 48 | readonly message: string; 49 | readonly parents: string[]; 50 | readonly authorDate?: Date; 51 | readonly authorName?: string; 52 | readonly authorEmail?: string; 53 | readonly commitDate?: Date; 54 | } 55 | 56 | export interface Submodule { 57 | readonly name: string; 58 | readonly path: string; 59 | readonly url: string; 60 | } 61 | 62 | export interface Remote { 63 | readonly name: string; 64 | readonly fetchUrl?: string; 65 | readonly pushUrl?: string; 66 | readonly isReadOnly: boolean; 67 | } 68 | 69 | export const enum Status { 70 | INDEX_MODIFIED, 71 | INDEX_ADDED, 72 | INDEX_DELETED, 73 | INDEX_RENAMED, 74 | INDEX_COPIED, 75 | 76 | MODIFIED, 77 | DELETED, 78 | UNTRACKED, 79 | IGNORED, 80 | INTENT_TO_ADD, 81 | 82 | ADDED_BY_US, 83 | ADDED_BY_THEM, 84 | DELETED_BY_US, 85 | DELETED_BY_THEM, 86 | BOTH_ADDED, 87 | BOTH_DELETED, 88 | BOTH_MODIFIED 89 | } 90 | 91 | export interface Change { 92 | 93 | /** 94 | * Returns either `originalUri` or `renameUri`, depending 95 | * on whether this change is a rename change. When 96 | * in doubt always use `uri` over the other two alternatives. 97 | */ 98 | readonly uri: Uri; 99 | readonly originalUri: Uri; 100 | readonly renameUri: Uri | undefined; 101 | readonly status: Status; 102 | } 103 | 104 | export interface RepositoryState { 105 | readonly HEAD: Branch | undefined; 106 | readonly refs: Ref[]; 107 | readonly remotes: Remote[]; 108 | readonly submodules: Submodule[]; 109 | readonly rebaseCommit: Commit | undefined; 110 | 111 | readonly mergeChanges: Change[]; 112 | readonly indexChanges: Change[]; 113 | readonly workingTreeChanges: Change[]; 114 | 115 | readonly onDidChange: Event; 116 | } 117 | 118 | export interface RepositoryUIState { 119 | readonly selected: boolean; 120 | readonly onDidChange: Event; 121 | } 122 | 123 | /** 124 | * Log options. 125 | */ 126 | export interface LogOptions { 127 | /** Max number of log entries to retrieve. If not specified, the default is 32. */ 128 | readonly maxEntries?: number; 129 | readonly path?: string; 130 | } 131 | 132 | export interface CommitOptions { 133 | all?: boolean | 'tracked'; 134 | amend?: boolean; 135 | signoff?: boolean; 136 | signCommit?: boolean; 137 | empty?: boolean; 138 | noVerify?: boolean; 139 | requireUserConfig?: boolean; 140 | } 141 | 142 | export interface FetchOptions { 143 | remote?: string; 144 | ref?: string; 145 | all?: boolean; 146 | prune?: boolean; 147 | depth?: number; 148 | } 149 | 150 | export interface BranchQuery { 151 | readonly remote?: boolean; 152 | readonly pattern?: string; 153 | readonly count?: number; 154 | readonly contains?: string; 155 | } 156 | 157 | export interface Repository { 158 | 159 | readonly rootUri: Uri; 160 | readonly inputBox: InputBox; 161 | readonly state: RepositoryState; 162 | readonly ui: RepositoryUIState; 163 | 164 | getConfigs(): Promise<{ key: string; value: string; }[]>; 165 | getConfig(key: string): Promise; 166 | setConfig(key: string, value: string): Promise; 167 | getGlobalConfig(key: string): Promise; 168 | 169 | getObjectDetails(treeish: string, path: string): Promise<{ mode: string, object: string, size: number }>; 170 | detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }>; 171 | buffer(ref: string, path: string): Promise; 172 | show(ref: string, path: string): Promise; 173 | getCommit(ref: string): Promise; 174 | 175 | add(paths: string[]): Promise; 176 | clean(paths: string[]): Promise; 177 | 178 | apply(patch: string, reverse?: boolean): Promise; 179 | diff(cached?: boolean): Promise; 180 | diffWithHEAD(): Promise; 181 | diffWithHEAD(path: string): Promise; 182 | diffWith(ref: string): Promise; 183 | diffWith(ref: string, path: string): Promise; 184 | diffIndexWithHEAD(): Promise; 185 | diffIndexWithHEAD(path: string): Promise; 186 | diffIndexWith(ref: string): Promise; 187 | diffIndexWith(ref: string, path: string): Promise; 188 | diffBlobs(object1: string, object2: string): Promise; 189 | diffBetween(ref1: string, ref2: string): Promise; 190 | diffBetween(ref1: string, ref2: string, path: string): Promise; 191 | 192 | hashObject(data: string): Promise; 193 | 194 | createBranch(name: string, checkout: boolean, ref?: string): Promise; 195 | deleteBranch(name: string, force?: boolean): Promise; 196 | getBranch(name: string): Promise; 197 | getBranches(query: BranchQuery): Promise; 198 | setBranchUpstream(name: string, upstream: string): Promise; 199 | 200 | getMergeBase(ref1: string, ref2: string): Promise; 201 | 202 | tag(name: string, upstream: string): Promise; 203 | deleteTag(name: string): Promise; 204 | 205 | status(): Promise; 206 | checkout(treeish: string): Promise; 207 | 208 | addRemote(name: string, url: string): Promise; 209 | removeRemote(name: string): Promise; 210 | renameRemote(name: string, newName: string): Promise; 211 | 212 | fetch(options?: FetchOptions): Promise; 213 | fetch(remote?: string, ref?: string, depth?: number): Promise; 214 | pull(unshallow?: boolean): Promise; 215 | push(remoteName?: string, branchName?: string, setUpstream?: boolean, force?: ForcePushMode): Promise; 216 | 217 | blame(path: string): Promise; 218 | log(options?: LogOptions): Promise; 219 | 220 | commit(message: string, opts?: CommitOptions): Promise; 221 | } 222 | 223 | export interface RemoteSource { 224 | readonly name: string; 225 | readonly description?: string; 226 | readonly url: string | string[]; 227 | } 228 | 229 | export interface RemoteSourceProvider { 230 | readonly name: string; 231 | readonly icon?: string; // codicon name 232 | readonly supportsQuery?: boolean; 233 | getRemoteSources(query?: string): ProviderResult; 234 | getBranches?(url: string): ProviderResult; 235 | publishRepository?(repository: Repository): Promise; 236 | } 237 | 238 | export interface RemoteSourcePublisher { 239 | readonly name: string; 240 | readonly icon?: string; // codicon name 241 | publishRepository(repository: Repository): Promise; 242 | } 243 | 244 | export interface Credentials { 245 | readonly username: string; 246 | readonly password: string; 247 | } 248 | 249 | export interface CredentialsProvider { 250 | getCredentials(host: Uri): ProviderResult; 251 | } 252 | 253 | export interface PushErrorHandler { 254 | handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise; 255 | } 256 | 257 | export type APIState = 'uninitialized' | 'initialized'; 258 | 259 | export interface PublishEvent { 260 | repository: Repository; 261 | branch?: string; 262 | } 263 | 264 | export interface API { 265 | readonly state: APIState; 266 | readonly onDidChangeState: Event; 267 | readonly onDidPublish: Event; 268 | readonly git: Git; 269 | readonly repositories: Repository[]; 270 | readonly onDidOpenRepository: Event; 271 | readonly onDidCloseRepository: Event; 272 | 273 | toGitUri(uri: Uri, ref: string): Uri; 274 | getRepository(uri: Uri): Repository | null; 275 | init(root: Uri): Promise; 276 | openRepository(root: Uri): Promise 277 | 278 | registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable; 279 | registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable; 280 | registerCredentialsProvider(provider: CredentialsProvider): Disposable; 281 | registerPushErrorHandler(handler: PushErrorHandler): Disposable; 282 | } 283 | 284 | export interface GitExtension { 285 | 286 | readonly enabled: boolean; 287 | readonly onDidChangeEnablement: Event; 288 | 289 | /** 290 | * Returns a specific API version. 291 | * 292 | * Throws error if git extension is disabled. You can listed to the 293 | * [GitExtension.onDidChangeEnablement](#GitExtension.onDidChangeEnablement) event 294 | * to know when the extension becomes enabled/disabled. 295 | * 296 | * @param version Version number. 297 | * @returns API instance 298 | */ 299 | getAPI(version: 1): API; 300 | } 301 | 302 | export const enum GitErrorCodes { 303 | BadConfigFile = 'BadConfigFile', 304 | AuthenticationFailed = 'AuthenticationFailed', 305 | NoUserNameConfigured = 'NoUserNameConfigured', 306 | NoUserEmailConfigured = 'NoUserEmailConfigured', 307 | NoRemoteRepositorySpecified = 'NoRemoteRepositorySpecified', 308 | NotAGitRepository = 'NotAGitRepository', 309 | NotAtRepositoryRoot = 'NotAtRepositoryRoot', 310 | Conflict = 'Conflict', 311 | StashConflict = 'StashConflict', 312 | UnmergedChanges = 'UnmergedChanges', 313 | PushRejected = 'PushRejected', 314 | RemoteConnectionError = 'RemoteConnectionError', 315 | DirtyWorkTree = 'DirtyWorkTree', 316 | CantOpenResource = 'CantOpenResource', 317 | GitNotFound = 'GitNotFound', 318 | CantCreatePipe = 'CantCreatePipe', 319 | PermissionDenied = 'PermissionDenied', 320 | CantAccessRemote = 'CantAccessRemote', 321 | RepositoryNotFound = 'RepositoryNotFound', 322 | RepositoryIsLocked = 'RepositoryIsLocked', 323 | BranchNotFullyMerged = 'BranchNotFullyMerged', 324 | NoRemoteReference = 'NoRemoteReference', 325 | InvalidBranchName = 'InvalidBranchName', 326 | BranchAlreadyExists = 'BranchAlreadyExists', 327 | NoLocalChanges = 'NoLocalChanges', 328 | NoStashFound = 'NoStashFound', 329 | LocalChangesOverwritten = 'LocalChangesOverwritten', 330 | NoUpstreamBranch = 'NoUpstreamBranch', 331 | IsInSubmodule = 'IsInSubmodule', 332 | WrongCase = 'WrongCase', 333 | CantLockRef = 'CantLockRef', 334 | CantRebaseMultipleBranches = 'CantRebaseMultipleBranches', 335 | PatchDoesNotApply = 'PatchDoesNotApply', 336 | NoPathFound = 'NoPathFound', 337 | UnknownPath = 'UnknownPath', 338 | } 339 | -------------------------------------------------------------------------------- /src/lib/logger.ts: -------------------------------------------------------------------------------- 1 | import { time } from "console"; 2 | import * as vscode from "vscode"; 3 | import { Disposable } from "./disposables"; 4 | 5 | export enum LogLevels { 6 | info = "INFO", 7 | warning = "WARNING", 8 | error = "ERROR", 9 | } 10 | 11 | export class Logger extends Disposable { 12 | private readonly channel: vscode.OutputChannel; 13 | private time = 0; 14 | 15 | constructor() { 16 | super(); 17 | this.channel = vscode.window.createOutputChannel("Git Flow"); 18 | this.registerDisposable(this.channel); 19 | let t = new Date(); 20 | this.time = t.getTime(); 21 | } 22 | 23 | show() { 24 | this.channel.show(); 25 | } 26 | 27 | public log(msg: string, cmd: string, level: LogLevels = LogLevels.info) { 28 | if ( 29 | [ 30 | "git version", 31 | "git status", 32 | "git branch", 33 | "git tag --sort=-v:refname", 34 | "git branch -r", 35 | "git flow config list", 36 | ].includes(cmd) 37 | ) { 38 | //return; 39 | } 40 | let t = new Date(); 41 | this.channel.appendLine(`${level}: (${t.getTime() - this.time}ms) [${cmd}] ${msg}`); 42 | this.time = t.getTime(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "outDir": "out", 6 | "lib": [ 7 | "ES2020" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "experimentalDecorators": true, 12 | "esModuleInterop": true, 13 | "strict": true 14 | }, 15 | "exclude": [ 16 | "node_modules", 17 | ".vscode-test" 18 | ] 19 | } 20 | --------------------------------------------------------------------------------