diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 887a272..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: 2 -updates: -- package-ecosystem: gradle - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 - ignore: - - dependency-name: com.google.errorprone:javac - versions: - - 1.8.0-u20 - - dependency-name: org.plumelib:plume-util - versions: - - 1.5.1 diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..090f465 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base", + ":automergeAll", + ":automergeRequireAllStatusChecks", + "schedule:nonOfficeHours", + ":disableDependencyDashboard" + ], + "timezone": "America/Los_Angeles" +} diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index a165171..3f09b49 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -9,15 +9,18 @@ jobs: strategy: matrix: - java: [ '8', '11', '17', '19' ] + java: [ '11', '17', '19' ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up JDK - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: 'temurin' java-version: ${{ matrix.java }} - name: ./gradlew build javadoc run: ./gradlew build - name: ./gradlew requireJavadoc run: ./gradlew requireJavadoc + - name: ./gradlew spotlessCheck + run: ./gradlew spotlessCheck diff --git a/build.gradle b/build.gradle index 30f1065..b6c0662 100644 --- a/build.gradle +++ b/build.gradle @@ -2,51 +2,50 @@ plugins { id 'java' id 'application' - // To create a fat jar build/libs/multi-version-control-all.jar, run: ./gradlew shadowJar - id 'com.github.johnrengelman.shadow' version '7.1.2' + // To create a fat jar build/libs/...-all.jar, run: ./gradlew shadowJar + id 'com.github.johnrengelman.shadow' version '8.1.1' // Code formatting; defines targets "spotlessApply" and "spotlessCheck" - id "com.diffplug.spotless" version "6.12.0" + // Requires JDK 11 or higher; the plugin crashes under JDK 8. + id 'com.diffplug.spotless' version '6.19.0' // Error Prone linter - id("net.ltgt.errorprone") version "3.0.1" + id('net.ltgt.errorprone') version '3.1.0' // Checker Framework pluggable type-checking - id 'org.checkerframework' version '0.6.20' + id 'org.checkerframework' version '0.6.27' } repositories { mavenCentral() + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } } dependencies { - // https://mvnrepository.com/artifact/org.tmatesoft.svnkit/svnkit - implementation group: 'org.tmatesoft.svnkit', name: 'svnkit', version: '1.10.9' - - // https://mvnrepository.com/artifact/org.ini4j/ini4j - implementation group: 'org.ini4j', name: 'ini4j', version: '0.5.4' - implementation 'org.apache.commons:commons-exec:1.3' - if (JavaVersion.current() == JavaVersion.VERSION_1_8) { - implementation 'org.plumelib:options:1.0.6' - } else { - implementation 'org.plumelib:options:2.0.3' - } - implementation 'org.plumelib:plume-util:1.6.0' + implementation 'org.ini4j:ini4j:0.5.4' + implementation 'org.plumelib:options:2.0.3' + implementation 'org.plumelib:plume-util:1.7.0' + implementation 'org.tmatesoft.svnkit:svnkit:1.10.11' } application { mainClass = 'org.plumelib.multiversioncontrol.MultiVersionControl' } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +if (JavaVersion.current().compareTo(org.gradle.api.JavaVersion.VERSION_20) < 0) { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 +} else { + sourceCompatibility = 11 + targetCompatibility = 11 +} spotless { format 'misc', { // define the files to apply `misc` to - target '*.gradle', '*.md', '.gitignore' + target '*.md', '.gitignore' // define the steps to apply to those files trimTrailingWhitespace() @@ -54,23 +53,34 @@ spotless { endWithNewline() } java { - targetExclude("**/WeakIdentityHashMap.java") + targetExclude('**/WeakIdentityHashMap.java') googleJavaFormat() formatAnnotations() } + groovyGradle { + target '**/*.gradle' + greclipse() // which formatter Spotless should use to format .gradle files. + indentWithSpaces(2) + trimTrailingWhitespace() + // endWithNewline() // Don't want to end empty files with a newline + } } /// Error Prone linter dependencies { - errorprone("com.google.errorprone:error_prone_core:2.16") + errorprone('com.google.errorprone:error_prone_core:2.19.1') } tasks.withType(JavaCompile).configureEach { - options.compilerArgs << "-Xlint:all,-processing" << "-Werror" + // "-processing" avoids javac warning "No processor claimed any of these annotations". + // "-options" is because starting in JDK 20, javac warns about using -source 8. + options.compilerArgs << '-Xlint:all,-processing,-options' << '-Werror' options.errorprone { enabled = JavaVersion.current() != JavaVersion.VERSION_1_8 - disable("ReferenceEquality") // Use Interning Checker instead. + disable('ReferenceEquality') // Use Interning Checker instead. } + options.forkOptions.jvmArgs += '-Xmx2g' + options.release = 8 } /// Checker Framework pluggable type-checking @@ -87,30 +97,48 @@ checkerFramework { checkers = [ // No need to run CalledMethodsChecker, because ResourceLeakChecker does so. // 'org.checkerframework.checker.calledmethods.CalledMethodsChecker', + 'org.checkerframework.checker.formatter.FormatterChecker', 'org.checkerframework.checker.index.IndexChecker', + 'org.checkerframework.checker.interning.InterningChecker', 'org.checkerframework.checker.lock.LockChecker', 'org.checkerframework.checker.nullness.NullnessChecker', 'org.checkerframework.checker.regex.RegexChecker', 'org.checkerframework.checker.resourceleak.ResourceLeakChecker', + 'org.checkerframework.checker.signature.SignatureChecker', 'org.checkerframework.checker.signedness.SignednessChecker', 'org.checkerframework.common.initializedfields.InitializedFieldsChecker', ] extraJavacArgs = [ '-Werror', + // '-Aversion', + // '-verbose', '-AcheckPurityAnnotations', '-ArequirePrefixInWarningSuppressions', + '-AwarnRedundantAnnotations', '-AwarnUnneededSuppressions', '-AnoJreVersionCheck', ] } - +// To use a snapshot version of the Checker Framework. +if (true) { + // TODO: change the above test to false when CF is released + ext.checkerFrameworkVersion = '3.34.1-SNAPSHOT' + dependencies { + compileOnly "org.checkerframework:checker-qual:${checkerFrameworkVersion}" + testCompileOnly "org.checkerframework:checker-qual:${checkerFrameworkVersion}" + checkerFramework "org.checkerframework:checker:${checkerFrameworkVersion}" + } + configurations.all { + resolutionStrategy.cacheChangingModulesFor 0, 'minutes' + } +} // To use a locally-built Checker Framework, run gradle with "-PcfLocal". -if (project.hasProperty("cfLocal")) { - def cfHome = String.valueOf(System.getenv("CHECKERFRAMEWORK")) +if (project.hasProperty('cfLocal')) { + def cfHome = String.valueOf(System.getenv('CHECKERFRAMEWORK')) dependencies { - compileOnly files(cfHome + "/checker/dist/checker-qual.jar") - testCompileOnly files(cfHome + "/checker/dist/checker-qual.jar") - checkerFramework files(cfHome + "/checker/dist/checker.jar") + compileOnly files(cfHome + '/checker/dist/checker-qual.jar') + testCompileOnly files(cfHome + '/checker/dist/checker-qual.jar') + checkerFramework files(cfHome + '/checker/dist/checker.jar') } } @@ -124,18 +152,16 @@ javadoc { options.addStringOption('source', '8') } doLast { - if (JavaVersion.current() == JavaVersion.VERSION_1_8) { - ant.replaceregexp(match:"@import url\\('resources/fonts/dejavu.css'\\);\\s*", replace:'', - flags:'g', byline:true) { - fileset(dir: destinationDir) - } + ant.replaceregexp(match:"@import url\\('resources/fonts/dejavu.css'\\);\\s*", replace:'', + flags:'g', byline:true) { + fileset(dir: destinationDir) } } } check.dependsOn javadoc task javadocWeb(type: Javadoc) { - description "Upload API documentation to website." + description 'Upload API documentation to website.' source = sourceSets.main.allJava destinationDir = file("/cse/web/research/plumelib/${project.name}/api") classpath = project.sourceSets.main.compileClasspath @@ -143,11 +169,9 @@ task javadocWeb(type: Javadoc) { options.addStringOption('source', '8') } doLast { - if (JavaVersion.current() == JavaVersion.VERSION_1_8) { - ant.replaceregexp(match:"@import url\\('resources/fonts/dejavu.css'\\);\\s*", replace:'', - flags:'g', byline:true) { - fileset(dir: destinationDir) - } + ant.replaceregexp(match:"@import url\\('resources/fonts/dejavu.css'\\);\\s*", replace:'', + flags:'g', byline:true) { + fileset(dir: destinationDir) } } } @@ -156,35 +180,35 @@ configurations { requireJavadoc } dependencies { - requireJavadoc "org.plumelib:require-javadoc:1.0.6" + requireJavadoc 'org.plumelib:require-javadoc:1.0.6' } task requireJavadoc(type: JavaExec) { description = 'Ensures that Javadoc documentation exists.' - mainClass = "org.plumelib.javadoc.RequireJavadoc" + mainClass = 'org.plumelib.javadoc.RequireJavadoc' classpath = configurations.requireJavadoc - args "src/main/java" + args 'src/main/java' } check.dependsOn requireJavadoc javadocWeb.dependsOn requireJavadoc task updateUserOptions(type: Javadoc, dependsOn: 'assemble') { - description "Updates printed documentation of command-line arguments." + description 'Updates printed documentation of command-line arguments.' source = sourceSets.main.allJava.files.sort() classpath = project.sourceSets.main.compileClasspath options.memberLevel = JavadocMemberLevel.PRIVATE options.docletpath = project.sourceSets.main.runtimeClasspath as List - options.doclet = "org.plumelib.options.OptionsDoclet" - options.addStringOption("docfile", "${projectDir}/src/main/java/org/plumelib/multiversioncontrol/MultiVersionControl.java") - options.addStringOption("format", "javadoc") - options.addStringOption("i", "-quiet") + options.doclet = 'org.plumelib.options.OptionsDoclet' + options.addStringOption('docfile', "${projectDir}/src/main/java/org/plumelib/multiversioncontrol/MultiVersionControl.java") + options.addStringOption('format', 'javadoc') + options.addStringOption('i', '-quiet') options.noTimestamp(false) - title = "" + title = '' } /// Emacs support /* Make Emacs TAGS table */ task tags(type: Exec) { - description "Run etags to create an Emacs TAGS table" - commandLine "bash", "-c", "find src/ -name '*.java' | sort | xargs etags" + description 'Run etags to create an Emacs TAGS table' + commandLine 'bash', '-c', "find src/ -name '*.java' | sort | xargs etags" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e583..c1962a7 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 070cb70..37aef8d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index a69d9cb..aeb74cb 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -143,12 +140,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,6 +194,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/gradlew.bat b/gradlew.bat index 53a6b23..6689b85 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% diff --git a/settings.gradle b/settings.gradle index 579ee24..ac3c0c8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,8 @@ +buildscript { + if (JavaVersion.current() == JavaVersion.VERSION_1_8) { + throw new Error("Use Java 11 or later.") + } +} + // Project name is read-only in build scripts, and defaults to directory name. rootProject.name = 'multi-version-control' diff --git a/src/main/java/org/plumelib/multiversioncontrol/MultiVersionControl.java b/src/main/java/org/plumelib/multiversioncontrol/MultiVersionControl.java index 92ea662..c736945 100644 --- a/src/main/java/org/plumelib/multiversioncontrol/MultiVersionControl.java +++ b/src/main/java/org/plumelib/multiversioncontrol/MultiVersionControl.java @@ -33,6 +33,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.checker.regex.qual.Regex; +import org.checkerframework.checker.signedness.qual.UnknownSignedness; import org.checkerframework.common.initializedfields.qual.EnsuresInitializedFields; import org.checkerframework.common.value.qual.MinLen; import org.checkerframework.dataflow.qual.Pure; @@ -79,11 +80,13 @@ * *
You can specify the set of clones for the program to manage in a file {@code .mvc-checkouts}, * or you can pass {@code --search} to make the program search your directory structure to find all - * of your clones. For example, to list all un-committed changed files under your home directory: + * of your clones. For example (assuming you have a {@code mvc} alias), + * to list all un-committed changed files under your home directory: * - *
java org.plumelib.multiversioncontrol.MultiVersionControl status --search=true+ *
+ * mvc status --search=true* - * This program accepts these arguments: + *
This program accepts these arguments: * *
* clone -- Clone (check out) all repositories. @@ -95,8 +98,8 @@ * list -- List the clones/checkouts that this program is aware of. ** - * (The {@code commit} action is not supported, because that is not something that should be done in - * an automated way — it needs a user-written commit message.) + *
(The {@code commit} action is not supported, because that is not something that should be done + * in an automated way — it needs a user-written commit message.) * *
Command-line arguments * @@ -192,11 +195,11 @@ *
* CVSROOT: :ext:login.csail.mit.edu:/afs/csail.mit.edu/u/m/mernst/.CVS/.CVS-mernst * SVNROOT: svn+ssh://tricycle.cs.washington.edu/cse/courses/cse403/09sp - * SVNREPOS: svn+ssh://login.csail.mit.edu/afs/csail/u/a/akiezun/.SVN/papers/parameterization-paper/trunk + * SVNREPOS: svn+ssh://login.csail.mit.edu/afs/csail/u/a/user/.SVN/papers/parameterize-paper/trunk * HGREPOS: https://jsr308-langtools.googlecode.com/hg* - * Within each section is a list of directories that contain a checkout from that repository. If the - * section names a root, then a module or subdirectory is needed. By default, the directory's + *
Within each section is a list of directories that contain a checkout from that repository. If + * the section names a root, then a module or subdirectory is needed. By default, the directory's * basename is used. This can be overridden by specifying the module/subdirectory on the same line, * after a space. If the section names a repository, then no module information is needed or used. * @@ -224,10 +227,9 @@ * * SVNROOT: svn+ssh://login.csail.mit.edu/afs/csail/u/d/dannydig/REPOS/ * ~/research/concurrency/concurrentPaper - * ~/research/concurrency/mit.edu.concurrencyRefactorings concurrencyRefactorings/project/mit.edu.concurrencyRefactorings - * + * ~/research/concurrency/mit.edu.refactorings concRefactor/project/mit.edu.refactorings * - * Furthermore, these 2 sections have identical effects: + *
Furthermore, these 2 sections have identical effects: * *
* SVNROOT: https://crashma.googlecode.com/svn/ @@ -236,7 +238,7 @@ * SVNREPOS: https://crashma.googlecode.com/svn/trunk * ~/research/crashma* - * and, all 3 of these sections have identical effects: + *
and, all 3 of these sections have identical effects: * *
* SVNROOT: svn+ssh://login.csail.mit.edu/afs/csail/group/pag/projects/ @@ -247,6 +249,16 @@ * * SVNREPOS: svn+ssh://login.csail.mit.edu/afs/csail/group/pag/projects/annotations * ~/research/typequals/annotations+ * + *
Installation + * + *
+ * git clone https://github.com/plume-lib/multi-version-control + * cd multi-version-control + * ./gradlew shadowJar + * + * alias mvc='java -ea -cp CURRENT_DIR/build/libs/multi-version-control-all.jar org.plumelib.multiversioncontrol.MultiVersionControl' + **/ // TODO: @@ -308,7 +320,7 @@ public class MultiVersionControl { /** If false, clone command skips existing directories. */ @OptionGroup("Miscellaneous options") @Option("Redo existing clones; relevant only to clone command") - public boolean redo_existing = false; + public boolean redoExisting = false; /** * Terminating the process can leave the repository in a bad state, so set this rather high for @@ -326,20 +338,20 @@ public class MultiVersionControl { /** If true, search for all clones whose directory is a prefix of one in the cofiguration file. */ @Option("Search for all clones whose directory is a prefix of one listed in a file") - public boolean search_prefix = false; + public boolean searchPrefix = false; /** - * Directory under which to search for clones, when using {@code --search} [default home - * directory] + * Directory under which to search for clones, when using {@code --search} [default = home + * directory]. */ @Option("Directory under which to search for clones; default=home dir") public List
Most operations don't need this. It is needed for checkout, though.
*/
@Nullable String repository;
+
/**
* Null if no module, just whole thing. Non-null for CVS and, optionally, for SVN. Null for
* distributed version control systems (Bzr, Git, Hg).
@@ -764,7 +784,7 @@ public boolean equals(@GuardSatisfied Checkout this, @GuardSatisfied @Nullable O
@Override
@Pure
- public int hashCode(@GuardSatisfied Checkout this) {
+ public int hashCode(@GuardSatisfied @UnknownSignedness Checkout this) {
return Objects.hash(repoType, canonicalDirectory, module);
}
@@ -784,14 +804,14 @@ public String toString(@GuardSatisfied Checkout this) {
*
* @param file the .mvc-checkouts file
* @param checkouts the set to populate; is side-effected by this method
- * @param search_prefix if true, search for all clones whose directory is a prefix of one in the
+ * @param searchPrefix if true, search for all clones whose directory is a prefix of one in the
* cofiguration file
* @throws IOException if there is trouble reading the file (or file sysetm?)
*/
@SuppressWarnings({
"StringSplitter" // don't add dependence on Guava
})
- static void readCheckouts(File file, Set