Use IntelliJ IDEA Community Edition/Ultimate.
-
(optional) Set up a shortcut for
Pluginssetting, you'll need to access it pretty often. OpenSearch Actionsdialog with Ctrl+Shift+A (⌘⇧A on macOS), typeplugins, press Alt+Enter (⌥↩ on macOS), then press the chosen shortcut (suggested: Ctrl+Alt+Shift+P, or ⌘⌥⇧P on macOS). -
Make sure the following bundled plugins are enabled, in the
Installedtab ofFile > Settings > Plugins(Preferences/Settings > Pluginson macOS):- Git
- Gradle
- IntelliLang (for highlighting of language injections, e.g. shell script within YAML)
- Java Internationalization
- JUnit
- Lombok
- Markdown
- Plugin DevKit
- Properties
- Shell Script (also: agree to enable Shellcheck when asked)
- TOML
- YAML
-
(optional) If working on IntelliJ Ultimate, enable JavaScript and TypeScript plugin (for UI tests).
-
Install the following non-bundled plugins from Marketplace:
- Kotlin plugin will be useful for editing certain parts of UI, esp. dialogs.
-
(optional) Install further non-bundled plugins from Marketplace:
- AWK Support
- Grammar-Kit IntelliJ plugin can be used instead of Gradle plugin
to manually generate grammar and lexer code from
.bnfand.flexfiles. - PsiViewer IntelliJ plugin can be helpful to see parsing result on the
machetefile when running IntelliJ instance with the Git Machete plugin loaded.
-
Enable annotation processing (for Lombok):
File > Settings > Build, Execution, Deployment > Compiler > Annotation Processors > Enable Annotation Processing(Preferences/Settings > Build, Execution, Deployment > Compiler > Annotation Processors > Enable Annotation Processingon macOS). SelectObtain annotation processors from classpathradio box. -
(optional) Increase maximum heap size for the IDE (the default value is 2048 MB) under
Help > Change Memory Settings. -
(optional) Enable internal mode. It can be significantly useful while working with UI components (or tests). To investigate the UI you may want to use
Tools > Internal Actions > UI > UI Inspector. -
(optional) Go to
File > Settings > Editor > Code Style > Java > Imports(Preferences/Settings > Editor > Code Style > Java > Importson macOS). SetClass count before import with '*'andNames count to use static import with '*'to a very high number (e.g. 500) to avoid problems with CheckStyle when editing code, and remove exceptions likejavax.swing.*fromPackages to Use Import with '*'. -
(optional) Go to
File > File Properties > Associate with File Type.... Type*.astubintoFile pattern. SelectOpen matching files in IntelliJ IDEAand thenJavaas file type.
Install shellcheck, e.g. via brew install shellcheck on macOS.
From the main project folder, run the following commands:
git config --local include.path ../.gitconfig
ln -s ../../scripts/git-hooks/machete-status-branch .git/hooks/machete-status-branch
ln -s ../../scripts/git-hooks/post-commit .git/hooks/post-commit
ln -s ../../scripts/run-pre-build-checks .git/hooks/pre-commitThe hooks do not work on Windows (however, their execution seems to be possible theoretically).
Some hooks use grep. The macOS version of grep (FreeBSD) differs from GNU grep.
In order to make grep and eventually the hooks working one must:
- Install
grepviabrew(it will not override system'sgrep— it can be executed asggrep). - Run
brew ls -v grep; among the other a path like should be found/opt/homebrew/Cellar/grep/<grep-version>/libexec/gnubin/grep(or/usr/local/Cellar/grep/...). - Prepend the found path without
/grepsuffix toPATH(/opt/homebrew/Cellar/grep/<grep-version>/libexec/gnubinin that case). You may want to add the followingexport PATH="/opt/homebrew/Cellar/grep/<grep-version>/libexec/gnubin:$PATH"to (.zprofile/.zshrc). - Restart the terminal OR run
sourceagainst the.zprofile/.zshrcfile: for examplesource ~/.zshrc.
It is possible that git pre-commit hook will raise the following error:
fatal: cannot use Perl-compatible regexes when not compiled with USE_LIBPCRE
This can be solved by compiling git with USE_LIBPCRE via brew using commands:
brew install pcre
export USE_LIBPCRE=yes
brew reinstall --build-from-source gitIt might be necessary to additionally restart the terminal after running mentioned commands.
Also, some issues with bash itself have been reported. Make sure that the version you are using is 5.1 or later.
Building this project on Windows has been tested under Git Bash.
Additional setup:
- Open the Registry Editor (
regedit.exe). - Open path by clicking:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem. - Find key named:
LongPathsEnabledand double click. - If the data value is 0, change it to 1.
No JDK has to be installed system-wide before building: the plugin uses Gradle's
Java toolchain feature backed by the
Foojay resolver, so the JDK that actually compiles the plugin
(see jdkVersionForGradleAndGeneratedClassfiles in java-version.properties) is auto-provisioned by Gradle
on first build. Any JDK supported by Gradle as a runtime is therefore fine for invoking ./gradlew
itself - including the one bundled with IntelliJ IDEA, which the IDE uses by default to run Gradle.
To build the project, run ./gradlew build. Please note that for the initial build attempt, you might need to add the --info option, in order to respond to the prompt of accepting Gradle Terms of Service.
Local (non-CI) builds by default skip most of Checker Framework's checkers to speed up Java compilation.
To make local builds more aligned with CI builds (at the expense of ~2x longer compilation from scratch),
set runAllCheckers Gradle project property (e.g. ./gradlew -PrunAllCheckers build).
In case of spurious cache-related issues with the Gradle build, try the following remedies (in the order from the least intrusive to the most):
./gradlew --stopto shut down Gradle daemonpkill -e -9 -f '.*Gradle.*'to kill all Gradle processes./gradlew cleanand re-run the failing./gradlewcommand with--no-build-cache- remove .gradle/ directory in the project directory
- remove ~/.gradle/caches/ (or even the entire ~/.gradle/) directory
- reinstall Gradle AND remove the entire ~/.gradle/ directory
To run an instance of IDE with Git Machete IntelliJ Plugin installed from the current source,
execute :runIde Gradle task (Gradle panel > Tasks > intellij > runIde or ./gradlew runIde).
It's possible to use a different version of IDE than the automatically chosen one (see IntelliJVersions)
for building the plugin and running the IDE:
use project property overrideBuildTarget e.g. ./gradlew runIde -PoverrideBuildTarget=2023.1.6.
To watch the logs of this IntelliJ instance, run tail -f build/idea-sandbox/<VERSION>/system/log/idea.log.
To debug the plugin using IntelliJ go to Run > Edit Configurations... and create a new Run Configuration for Gradle:

Now this new configuration can be chosen in the upper left corner of the IDE, and the debugging can be started with the Debug button or Shift+F9 (^D on macOS).
To force running tests (without using ./gradlew clean and/or ./gradlew --no-build-cache ..., which lead to unnecessary longer build times),
set forceRunTests project property: ./gradlew -PforceRunTests test.
To include test stdout/stderr in the output of tests (without enabling --info log level in Gradle, which leads to a lot of spam),
set printTestOutput project property: ./gradlew -PprintTestOutput test.
See backend/impl/src/test/resources and
StatusAndDiscoverIntegrationTestSuite
for context.
Regeneration is needed when:
- A change is done to test repository setup scripts in testCommon/src/testFixtures/resources
- A change is done to backend logic in this plugin, or to logic/rendering of
git machete statusorgit machete discover.
To regenerate the CLI outputs:
- Make sure you've got the latest git-machete CLI installed.
- Run
./gradlew backend:impl:regenerateCliOutputs. - Commit the changes to
backend/impl/src/test/resources.
On macOS, make sure that the terminal (e.g. iTerm) where you run the tests has the permissions to move the cursor: System Settings > Privacy & Security > Accessibility > (+) > iTerm
./gradlew [-Pagainst=<e.g. 2021.2>] [-Ptests=<e.g. Squash>] uiTestSee Robot plugin and docs on UI testing for more details.
To generate a plugin archive (zip), run :buildPlugin Gradle task (Gradle panel > Tasks > intellij > buildPlugin or ./gradlew buildPlugin).
The resulting file will be available under build/distributions/.
Alternatively, download the plugin zip from the artifacts of the given build
in CircleCI.
In either case (locally-built or CI-built), the zip can be installed via File > Settings > Plugins > (gear icon) > Install Plugin from Disk...
(Preferences/Settings > Plugins > (gear icon) > Install Plugin from Disk... on macOS).
Select the zip and restart the IDE.
By default, IntelliJ logs everything with level INFO and above into idea.log file.
The exact location depends on a specific IntelliJ installation; check Help > Show Log in Files (Help > Show Log in Finder on macOS) to find out.
Tip: use tail -f to watch the log file as it grows.
To enable logging of this plugin in DEBUG level, add com.virtuslab category to list in Help > Diagnostic Tools > Debug Log Settings.
A relatively small amount of TRACE-level logs is generated as well (com.virtuslab:trace to enable).
Most non-standard/project-specific conventions are enforced by:
- pre-commit hook
- Spotless for Java code formatting (see Eclipse-compatible config)
- Checkstyle for code style/detecting basic smells (see the config)
- ArchUnit for forbidden method calls/class naming patterns etc. (see tests in top-level project)
- Checker Framework for formal correctness, esp. wrt. null safety and UI thread handling (most config in build.gradle.kts, stubs in config/checker/)
Other coding conventions include:
- Don't write nullary lambdas in
receiver::methodnotation, use explicit() -> receiver.method()notation instead.
::notation is confusing when applied to parameterless lambdas, as it suggests a unary lambda. - Use
get...method names for pure methods that only return the value without doing any heavy workload like accessing git repository.
Usederive...method names for methods that actually compute their result and/or can return a different value every time when accessed. - Non-obvious method params that have values like
false,true,0,1,null,""should be preceded with a/* comment */containing the name of the param. - Avoid running code outside IDE-managed threads.
Use either UI thread (for lightweight operations) or
Task.Backgroundable(for heavyweight operations). - Properties in
GitMacheteBundle.propertiesthat use HTML should be wrapped in tags<html>...</html>. Additionally, their keys should have a.HTMLsuffix. @Taintedand@Untaintedannotations are used in the context of method parameters that may or may not use HTML. Those annotated with@Untaintedshould not contain HTML tags, whereas values annotated with@Taintedcan contain HTML (but they don't have to). Please note that you need to useorg.checkerframework.checker.tainting.qual.Taintedororg.checkerframework.checker.tainting.qual.Untaintedannotations, rather than thejavax.annotationones.- Avoid
Branchword in action class names and action ids to keep them shorter. Some exceptions are allowed (e.g. the backgroundable task classes).
So far created UI conventions:
- Add
…(ellipsis,\u2026) at the end of an action name if it is not executed immediately after clicking e.g.Sync to Parent by Rebase…(after this operation the interactive rebase window opens) - Toolbar name texts of a toolbar actions that refer to a branch should indicate the branch under action with the word
Current. On the other hand, context-menu actions text names should be kept short (noThis/Selected).
We follow Semantic versioning for the plugin releases:
- MAJOR version must be bumped for each plugin release that stops supporting any IDEA build.
This does not apply to 0->1 major version transition, which is going to happen when the plugin's compatibility range is considered stable. - MINOR version must be bumped for each plugin release that either adds a new user-facing feature
or starts supporting a new quarterly (
year.number) IDEA build. - PATCH version must be bumped for each plugin release that adds no new user-facing features and doesn't change the range of supported IDEA builds.
After a release e.g. 1.0.3, subsequent PRs merged to develop might change prospectiveReleaseVersion
in version.gradle.kts in the following way:
1.0.4(bugfix PR) — the first PR merged to develop after the release must bumpprospectiveReleaseVersionsince of course the prospective release won't be1.0.3anymore1.0.4(bugfix PR) — even if a new set of patch-level changes has been added on the PR, the released version is still going to be1.0.4(not1.0.5)1.1.0(feature PR) — since we've just added a new feature, the new release won't be a PATCH-level anymore, but MINOR-level one1.1.0(bugfix PR) — even if a new feature has been added on the PR, the released version is still going to be1.1.0(not1.2.0)2.0.0(breaking change PR)2.0.0(feature PR) — again, still2.0.0and not e.g.2.1.02.0.0(bugfix PR)2.0.0(release PR) — finally releasing as a major release; as a consequence,1.0.4and1.1.0never actually gets released
Change notes are stored in CHANGE-NOTES.md file.
The file is incremental, the entries for all previous versions are stored there and never deleted.
Change notes should only be added as new bullet points listed under the topmost section of the file.
Change notes should contain only the user-facing plugin alterations like new features, public-requested bug fixes, etc.
In spite of the above, change notes can sometimes contain a general description of the work done in the new version
if the alternative is leaving them empty, which should be avoided.
The change notes should be in past tense (e.g. use Added... instead of Add...).
They should start with words like Added, Changed, Deprecated, Removed, Fixed,
and be grouped by these first words.
They should be described in full sentences and end with a period.
Special phrases like action name references should be highlighted with <i>...</i>.
Since we cannot skip untilBuild field in a plugin build configuration
(see related issue
and YouTrack ticket),
the most reasonable approach is to bump untilBuild to X.* when the new X EAP or RC version is released.
Once stable (non-EAP/RC) X is released, we should verify ASAP that our plugin is compatible with X.
There is a rather little risk that the plugin which is compatible with X - 1
and does not use any X EAP/RC-specific API
turns out to be not compatible with stable X release of IDE.
Version updates are performed automatically by a cronned CI job using ./gradlew updateIntellijVersions, see .circleci/config.yml.
The whole logic of the process can be illustrated with an example:
- our plugin in version
0.7.0is compatible with IntelliJ2020.2 - then IntelliJ
2020.3-EAPis released (see snapshot repository -> Ctrl+F.idea), and is detected with./gradlew updateIntellijVersions,
newupcomingMajorEapis set in intellij-versions.properties - we check if
0.7.0is compatible with IntelliJ2020.3-EAP— see if the CI pipeline passes (this will both check binary compatibility and run UI tests against the given EAP) - we release the plugin as
0.8.0(untilBuildwill extend automatically to2020.3.*vialatestSupportedMajorin intellij-versions.properties) - new stable version
2020.3is released (see release repository -> Ctrl+F.idea) and is detected using./gradlew updateIntellijVersions,
latestStableand additionallylatestMinorsOfOldSupportedMajorsare changed in intellij-versions.properties - we verify ASAP that
0.8.0is binary compatible with2020.3as well - since
latestStableis used as the version to build against, a few source incompatibilities might appear oncelatestStableis updated, even when the plugin was binary compatible with the new IDE version.
The default branch of the repository is master, but each regular (non-hotfix, non-release, non-backport) PR must be merged to develop.
Because of that all regular PR branches should start from develop and not master.
Due to the fact that the default branch is not develop, merging of PRs does not close linked issues (you have to close the issues manually).
Stacked PRs (Y -> X -> develop) must never be merged until their base is finally changed to develop.
After merging the parent PR, child's base changes automatically (see GitHub blogpost)
To create a release:
- make sure the prospective version's section in CHANGE-NOTES.md exists and is updated, especially that it is not empty
- merge patched CHANGE-NOTES.md into
develop - open PR from
developtomaster
Once the release PR is fast-forward merged (do not use GitHub Merge Button), master is built.
After manual approval, the master build:
- pushes a tag (
v<version>) back to the repository - creates a GitHub release
- publishes the plugin to JetBrains marketplace
Backport PRs are recognized by the backport/* branch name.
They must have develop as its base.
Hotfix PRs (hotfix/* branch name) are PRs to master but NOT from develop commit.
They always introduce a non-linear history on develop since after a hotfix PR is merged,
a backport PR from hotfixed master to develop is opened, and it cannot be fast-forward merged.
The alternative that would preserve linear history is to rebase the develop history
since the latest release over the hotfixed master.
This would mean, however, that the commits referenced from PRs previously merged to develop will no longer be part of develop's history,
which is rather unacceptable.
The valid non-expired key pair together with the password required for the plugin signing should be present in the CI environment. If they are absent, please take a look at the plugin signing in IntelliJ for updated instructions on how to do it. Currently, you would need to first generate the private key with:
export PLUGIN_SIGN_PRIVATE_KEY_PASS="Change2UrStr0ngP4ssword4PrivateKi"openssl genpkey\
-aes-256-cbc\
-algorithm RSA\
-out private.pem\
-pkeyopt rsa_keygen_bits:4096\
-pass env:PLUGIN_SIGN_PRIVATE_KEY_PASSThen, use that for generating the certificate chain by running:
openssl req\
-key private.pem\
-new\
-x509\
-days 365\
-out chain.crt\
-passin env:PLUGIN_SIGN_PRIVATE_KEY_PASS\
-subj "/C=PL/ST=Krakow/L=Krakow/O=VirtusLab/OU=Git Machete team/CN=www.virtuslab.com/emailAddress=gitmachete@virtuslab.com"Please note that you would have to copy the contents of private.pem, chain.crt, and PLUGIN_SIGN_PRIVATE_KEY_PASS,
in the corresponding environment variables,
in order for the Gradle IntelliJ Plugin to pick them up and use them for the plugin signing task, before publishing to the Marketplace.
For doing so, on a macOS system you can follow the below instructions (on Linux, use xclip -selection clipboard instead of pbcopy):
- Type the following command for copying the contents of the private key to the clipboard
cat private.pem | base64 -w 0 | pbcopy
- Create an environment variable named
PLUGIN_SIGN_PRIVATE_KEY_BASE64on the CI, and paste the content from the clipboard as its value. - for copying the contents of the certificate to clipboard:
cat chain.crt | base64 -w 0 | pbcopy
- Create an environment variable named
PLUGIN_SIGN_CERT_CHAIN_BASE64on the CI, and paste the content from the clipboard as its value. - Type the following command for copying the value of the private key password to the clipboard
echo "$PLUGIN_SIGN_PRIVATE_KEY_PASS" | pbcopy
- Create an environment variable named
PLUGIN_SIGN_PRIVATE_KEY_PASSon the CI and paste the contents of the clipboard as its value.
export PLUGIN_SIGN_PRIVATE_KEY_BASE64=$(base64 -w 0 < private.pem)
export PLUGIN_SIGN_CERT_CHAIN_BASE64=$(base64 -w 0 < chain.crt)Then run ./gradlew publishPlugin to produce the unsigned and signed .zip files in the build/distributions/ directory.
If an unsigned zip is already present there, then you can run ./gradlew signPlugin to produce the signed zip file.
You need to download the IntelliJ signer CLI. Then, following the instructions you should run:
java -jar marketplace-zip-signer-cli.jar sign\
-in "$(./gradlew -q printPluginZipPath)"\
-out "build/distributions/signed-machete-plugin.zip"\
-cert-file "/path/to/chain.crt"\
-key-file "/path/to/private.pem"\
-key-pass "$PLUGIN_SIGN_PRIVATE_KEY_PASS"There is a test suite com.virtuslab.gitmachete.uitest.UIScenarioSuite that helps to re-record the scenario recordings.
The most effective way to record all scenarios (probably) is to record all scenarios and trim the video.
To record the screen you can use Quick Time Player (on macOS).
The scenario suite is not fully automatic.
The following steps shall be performed manually at the beginning:
- resize the tool window to ~4/5 of IDE window height (to show all toolbar actions and avoid the context-menu exceeding the window area)
- increase the font size to 16
- resize the rebase dialog (to fit inside the window area)
- sometimes an IDE internal error occurs — clear it
The suite covers only scenarios — recordings for features must be updated manually. Gifs can optimized with https://www.xconvert.com/ — over 50% size reduction.