Use IntelliJ IDEA Community Edition/Ultimate.
-
(optional) Set up a shortcut for
Plugins
setting, you'll need to access it pretty often. OpenSearch Actions
dialog 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
Installed
tab ofFile > Settings > Plugins
(Preferences/Settings > Plugins
on macOS):- Git
- Gradle
- IntelliLang (for highlighting of language injections, e.g. JavaScript within Scala, or 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.
- Scala plugin might be useful for editing UI tests.
-
(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
.bnf
and.flex
files. - HOCON plugin for
.conf
file support in UI tests - PsiViewer IntelliJ plugin can be helpful to see parsing result on the
machete
file 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 Processing
on macOS). SelectObtain annotation processors from classpath
radio 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 > Imports
on 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*.astub
intoFile pattern
. SelectOpen matching files in IntelliJ IDEA
and thenJava
as 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-commit
The hooks do not work on Windows (however, their execution seems to be possible theoretically). This is because one may not be emulating bash environment in any way or doing it in some specific way.
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
grep
viabrew
(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
/grep
suffix toPATH
(/opt/homebrew/Cellar/grep/<grep-version>/libexec/gnubin
in 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
source
against the.zprofile
/.zshrc
file: 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 git
It 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:
LongPathsEnabled
and double click. - If the data value is 0, change it to 1.
Set up Java 17, preferably using sdkman,
with sdkman_auto_env=true
and .sdkmanrc
.
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.
Currently, quite generous maximum heap size options are applied for Gradle's Java compilation tasks (search for -Xmx
in build.gradle.kts).
To overwrite them, use compileJavaJvmArgs
Gradle project property
(e.g. ./gradlew -PcompileJavaJvmArgs='-Xmx2g -XX:+HeapDumpOnOutOfMemoryError' build
,
or equivalently with an env var: ORG_GRADLE_PROJECT_compileJavaJvmArgs='-Xmx2g -XX:+HeapDumpOnOutOfMemoryError' ./gradlew build
).
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 --stop
to shut down Gradle daemonpkill -e -9 -f '.*Gradle.*'
to kill all Gradle processes./gradlew clean
and re-run the failing./gradlew
command 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 a project property overrideBuildTarget
e.g. ./gradlew runIde -PoverrideBuildTarget=2022.1.4
.
To watch the logs of this IntelliJ instance, run tail -f build/idea-sandbox/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 status
orgit 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
.
./gradlew [-Pagainst=<e.g. 2021.2>] [-PvirtualDisplay] [-Ptests=<e.g. toggle>] uiTest
See Robot plugin and a preso on testing UI of IntelliJ Plugins for more details.
It is not possible to run uiTest
Gradle task in display mode Xvfb
on macOS systems directly via ./gradlew
.
You can run UI tests inside Docker container (based on gitmachete/intellij-plugin-ci
image) or using a Linux virtual machine.
Sample configuration for launching docker container is shown below. Environment variables UID
and GID
placed into the container will be the user and group identifiers
of the files created inside it. In example below, current working directory navigates to top-level directory of the project. When starting the container we should care about setting
environment variable IDEPROBE_PATHS_BASE
to the directory to which the user whose UID
and GID
was passed to the container has access to (e.g. user's home directory).
./gradlew --stop # to avoid conflicts on Gradle's internal lock files
docker run --rm -e UID=$(id -u) -e GID=$(id -g) \
-v "$PWD":/home/docker/git-machete-intellij-plugin \
-v ~/.gradle:/home/docker/.gradle \
-v ~/.pluginVerifier:/home/docker/.pluginVerifier \
-v /tmp/ide-probe/cache:/tmp/ide-probe/cache \
-w /home/docker/git-machete-intellij-plugin \
gitmachete/intellij-plugin-ci \
./gradlew -PvirtualDisplay uiTest
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::method
notation, 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.properties
that use HTML should be wrapped in tags<html>
...</html>
. Additionally, their keys should have a.HTML
suffix. @Tainted
and@Untainted
annotations are used in the context of method parameters that may or may not use HTML. Those annotated with@Untainted
should not contain HTML tags, whereas values annotated with@Tainted
can contain HTML (but they don't have to). Please note that you need to useorg.checkerframework.checker.tainting.qual.Tainted
ororg.checkerframework.checker.tainting.qual.Untainted
annotations, rather than thejavax.annotation
ones.- Avoid
Branch
word 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
).
To push the rebuilt image, you need write access to gitmachete
organization on Docker Hub.
Dockerfile
together with the necessary entrypoint.sh
exists under docker
folder in project root directory.
cd docker/
version=... # determine the image version using Semantic Versioning rules
docker build -t gitmachete/intellij-plugin-ci:$version -t gitmachete/intellij-plugin-ci:latest .
docker push gitmachete/intellij-plugin-ci:$version
docker push gitmachete/intellij-plugin-ci:latest
Then, update executors.docker_executor.docker[0].image
in .circleci/config.yml.
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 bumpprospectiveReleaseVersion
since of course the prospective release won't be1.0.3
anymore1.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.0
and not e.g.2.1.0
2.0.0
(bugfix PR)2.0.0
(release PR) — finally releasing as a major release; as a consequence,1.0.4
and1.1.0
never 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.0
is compatible with IntelliJ2020.2
- then IntelliJ
2020.3-EAP
is released (see snapshot repository -> Ctrl+F.idea
), and is detected with./gradlew updateIntellijVersions
,
neweapOfLatestSupportedMajor
is set in intellij-versions.properties - we check if
0.7.0
is 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
(untilBuild
will extend automatically to2020.3.*
vialatestSupportedMajor
in intellij-versions.properties) - new stable version
2020.3
is released (see release repository -> Ctrl+F.idea
) and is detected using./gradlew updateIntellijVersions
,
latestStable
and additionallylatestMinorsOfOldSupportedMajors
are changed in intellij-versions.properties - we verify ASAP that
0.8.0
is binary compatible with2020.3
as well - since
latestStable
is used as the version to build against, a few source incompatibilities might appear oncelatestStable
is 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
develop
tomaster
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_PASS
Then, 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/[email protected]"
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_BASE64
on 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_BASE64
on 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_PASS
on 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.