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 dir = new ArrayList<>(); /** Directories under which to NOT search for clones. May include leading "~/". */ @Option("Directory under which to NOT search for clones") - public List ignore_dir = new ArrayList<>(); + public List ignoreDir = new ArrayList<>(); - /** Files, each a directory, corresponding to strings in {@link ignore_dir}. */ + /** Files, each a directory, corresponding to strings in {@link ignoreDir}. */ private List ignoreDirs = new ArrayList<>(); // These *-executable command-line options are handy: @@ -351,19 +363,19 @@ public class MultiVersionControl { /** Path to the cvs program. */ @OptionGroup("Paths to programs") @Option("Path to the cvs program") - public String cvs_executable = "cvs"; + public String cvsExecutable = "cvs"; /** Path to the git program. */ @Option("Path to the git program") - public String git_executable = "git"; + public String gitExecutable = "git"; /** Path to the hg program. */ @Option("Path to the hg program") - public String hg_executable = "hg"; + public String hgExecutable = "hg"; /** Path to the svn program. */ @Option("Path to the svn program") - public String svn_executable = "svn"; + public String svnExecutable = "svn"; /** If true, use --insecure when invoking programs. */ @Option("Pass --insecure argument to hg") @@ -375,19 +387,19 @@ public class MultiVersionControl { /** Extra argument to pass to the cvs program. */ @Option("Extra argument to pass to the cvs program") - public List cvs_arg = new ArrayList<>(); + public List cvsArg = new ArrayList<>(); /** Extra argument to pass to the git program. */ @Option("Extra argument to pass to the git program") - public List git_arg = new ArrayList<>(); + public List gitArg = new ArrayList<>(); /** Extra argument to pass to the hg program. */ @Option("Extra argument to pass to the hg program") - public List hg_arg = new ArrayList<>(); + public List hgArg = new ArrayList<>(); /** Extra argument to pass to the svn program. */ @Option("Extra argument to pass to the svn program") - public List svn_arg = new ArrayList<>(); + public List svnArg = new ArrayList<>(); // TODO: use consistent names: both "show" or both "print" @@ -398,11 +410,11 @@ public class MultiVersionControl { /** If true, print the directory before executing commands in it. */ @Option("Print the directory before executing commands") - public boolean print_directory = false; + public boolean printDirectory = false; /** Perform a "dry run": print commands but do not execute them. */ @Option("Do not execute commands; just print them. Implies --show --redo-existing") - public boolean dry_run = false; + public boolean dryRun = false; /** If true, run quietly (e.g., no output about missing directories). */ @Option("-q Run quietly (e.g., no output about missing directories)") @@ -416,11 +428,11 @@ public class MultiVersionControl { /** Debug 'replacers' that filter command output. */ @Option("Debug 'replacers' that filter command output") - public boolean debug_replacers = false; + public boolean debugReplacers = false; /** Lightweight debugging of 'replacers' that filter command output. */ @Option("Lightweight debugging of 'replacers' that filter command output") - public boolean debug_process_output = false; + public boolean debugProcessOutput = false; /** Actions that MultiVersionControl can perform. */ static enum Action { @@ -432,14 +444,18 @@ static enum Action { PULL, /** List the known repositories. */ LIST - }; + } + // Shorter variants /** Clone a repository. */ private static Action CLONE = Action.CLONE; + /** Show the working tree status. */ private static Action STATUS = Action.STATUS; + /** Pull changes from upstream. */ private static Action PULL = Action.PULL; + /** List the known repositories. */ private static Action LIST = Action.LIST; @@ -470,14 +486,14 @@ public static void main(String[] args) { Set checkouts = new LinkedHashSet<>(); try { - readCheckouts(new File(mvc.checkouts), checkouts, mvc.search_prefix); + readCheckouts(new File(mvc.checkouts), checkouts, mvc.searchPrefix); } catch (IOException e) { System.err.println("Problem reading file " + mvc.checkouts + ": " + e.getMessage()); } if (mvc.search) { // Postprocess command-line arguments - for (String adir : mvc.ignore_dir) { + for (String adir : mvc.ignoreDir) { File afile = new File(expandTilde(adir)); if (!afile.exists()) { System.err.printf( @@ -598,10 +614,10 @@ public void parseArgs(@UnknownInitialization MultiVersionControl this, String[] // Checkouts can be much slower than other operations. timeout = timeout * 10; - // Set dry_run to true unless it was explicitly specified + // Set dryRun to true unless it was explicitly specified boolean explicitDryRun = false; for (String arg : args) { - if (arg.startsWith("--dry-run") || arg.startsWith("--dry_run")) { + if (arg.startsWith("--dry-run") || arg.startsWith("--dryRun")) { explicitDryRun = true; } } @@ -610,13 +626,13 @@ public void parseArgs(@UnknownInitialization MultiVersionControl this, String[] System.out.println( "No --dry-run argument, so using --dry-run=true; override with --dry-run=false"); } - dry_run = true; + dryRun = true; } } - if (dry_run) { + if (dryRun) { show = true; - redo_existing = true; + redoExisting = true; } if (debug) { @@ -636,16 +652,19 @@ static enum RepoType { HG, /** Subversion. */ SVN - }; + } /** Class that represents a clone on the local file system. */ static class Checkout { /** The type of repository to clone. */ RepoType repoType; + /** Local directory. */ File directory; + /** Local directory (canonical version). */ String canonicalDirectory; + /** * Non-null for CVS and SVN. May be null for distributed version control systems (Bzr, Git, Hg). * For distributed systems, refers to the parent repository from which this was cloned, not the @@ -654,6 +673,7 @@ static class Checkout { *

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 checkouts, boolean search_prefix) + static void readCheckouts(File file, Set checkouts, boolean searchPrefix) throws IOException { RepoType currentType = RepoType.BZR; // arbitrary choice, to avoid uninitialized variable String currentRoot = null; @@ -897,7 +917,7 @@ static void readCheckouts(File file, Set checkouts, boolean search_pre // /a/b/c-fork-d-branch-e // Then the latter is included twice, once each with the repository of `c` and of // `c-fork-d`. - if (search_prefix) { + if (searchPrefix) { String dirName = dir.getName(); FileFilter namePrefixFilter = new FileFilter() { @@ -1087,7 +1107,6 @@ static void addCheckoutCvs(File cvsDir, File parentDir, Set checkouts) return; } String pathInRepo = FilesPlume.readFile(repositoryFile).trim(); - String repoRoot = FilesPlume.readFile(rootFile).trim(); @NonNull File repoFileRoot = new File(pathInRepo); while (repoFileRoot.getParentFile() != null) { repoFileRoot = repoFileRoot.getParentFile(); @@ -1108,6 +1127,7 @@ static void addCheckoutCvs(File cvsDir, File parentDir, Set checkouts) pathInRepoAtCheckout = dirRelative.getName(); } + String repoRoot = FilesPlume.readFile(rootFile).trim(); checkouts.add(new Checkout(RepoType.CVS, dirRelative, repoRoot, pathInRepoAtCheckout)); } @@ -1256,6 +1276,7 @@ static Checkout dirToCheckoutGit(File gitDir, File parentDir) { static class FilePair { /** The first file. */ final @Nullable File file1; + /** The second file. */ final @Nullable File file2; @@ -1342,6 +1363,7 @@ private void addArgs(ProcessBuilder pb, List args) { private static class Replacer { /** The regular expression matching text that should be replaced. */ Pattern regexp; + /** The replacement text. */ String replacement; @@ -1377,14 +1399,15 @@ public String replaceAll(String s) { public void process(Set checkouts) { // Always run at least one command, but sometimes up to three. ProcessBuilder pb = new ProcessBuilder(""); + pb.redirectErrorStream(true); ProcessBuilder pb2 = new ProcessBuilder(new ArrayList()); + pb2.redirectErrorStream(true); ProcessBuilder pb3 = new ProcessBuilder(new ArrayList()); + pb3.redirectErrorStream(true); // pb4 is only for checking whether there are no commits in this branch. ProcessBuilder pb4 = new ProcessBuilder(new ArrayList()); - pb.redirectErrorStream(true); - pb2.redirectErrorStream(true); - pb3.redirectErrorStream(true); pb4.redirectErrorStream(true); + // I really want to be able to redirect output to a Reader, but that // isn't possible. I have to send it to a file. // I can't just use the InputStream directly, because if the process is @@ -1495,35 +1518,35 @@ public void process(Set checkouts) { case CVS: assert c.module != null : "@AssumeAssertion(nullness): dependent type CVS"; pb.command( - cvs_executable, + cvsExecutable, "-d", c.repository, "checkout", "-P", // prune empty directories "-ko", // no keyword substitution c.module); - addArgs(pb, cvs_arg); + addArgs(pb, cvsArg); break; case GIT: // "--" is to prevent the directory name from being interpreted as a command-line // option, if it starts with a hyphen. - pb.command(git_executable, "clone", "--", c.repository, dirbase); - addArgs(pb, git_arg); + pb.command(gitExecutable, "clone", "--", c.repository, dirbase); + addArgs(pb, gitArg); break; case HG: - pb.command(hg_executable, "clone", c.repository, dirbase); - addArgs(pb, hg_arg); + pb.command(hgExecutable, "clone", c.repository, dirbase); + addArgs(pb, hgArg); if (insecure) { addArg(pb, "--insecure"); } break; case SVN: if (c.module != null) { - pb.command(svn_executable, "checkout", c.repository, c.module); + pb.command(svnExecutable, "checkout", c.repository, c.module); } else { - pb.command(svn_executable, "checkout", c.repository); + pb.command(svnExecutable, "checkout", c.repository); } - addArgs(pb, svn_arg); + addArgs(pb, svnArg); break; default: assert false; @@ -1540,7 +1563,7 @@ public void process(Set checkouts) { case CVS: assert c.repository != null; pb.command( - cvs_executable, + cvsExecutable, "-q", // Including "-d REPOS" seems to give errors when a // subdirectory is in a different CVS repository. @@ -1549,7 +1572,7 @@ public void process(Set checkouts) { "-b", // compress whitespace "--brief", // report only whether files differ, not details "-N"); // report new files - addArgs(pb, cvs_arg); + addArgs(pb, cvsArg); // # For the last perl command, this also works: // # perl -p -e 'chomp(\$cwd = `pwd`); s/^Index: /\$cwd\\//'"; // # but the one we use is briefer and uses the abbreviated directory name. @@ -1576,8 +1599,8 @@ public void process(Set checkouts) { replacers.add(new Replacer("(^|\\n)(cvs diff: ignoring )", "$1$2" + dir + "/")); break; case GIT: - pb.command(git_executable, "status"); - addArgs(pb, git_arg); + pb.command(gitExecutable, "status"); + addArgs(pb, gitArg); // Why was I using this option?? // addArg(pb, "--untracked-files=no"); addArg(pb, "--porcelain"); // experimenting with porcelain output @@ -1650,8 +1673,8 @@ public void process(Set checkouts) { // Necessary because "git status --porcelain" does not report: // # Your branch is ahead of 'origin/master' by 1 commit. // If you have pushed but not pulled, then this will report - pb2.command(git_executable, "log", "--branches", "--not", "--remotes"); - addArgs(pb2, git_arg); + pb2.command(gitExecutable, "log", "--branches", "--not", "--remotes"); + addArgs(pb2, gitArg); replacers.add( new Replacer( "^commit .*(.*\\n)+", "unpushed commits: " + pb2.directory() + "\n")); @@ -1660,22 +1683,22 @@ public void process(Set checkouts) { // TODO: use `if git merge-base --is-ancestor origin/master HEAD ; then ...` to // determine whether this branch has no changes and thus can be deleted. - pb4.command(git_executable, "merge-base", "--is-ancestor", "origin/master", "HEAD"); + pb4.command(gitExecutable, "merge-base", "--is-ancestor", "origin/master", "HEAD"); break; case HG: - pb.command(hg_executable, "status"); - addArgs(pb, hg_arg); + pb.command(hgExecutable, "status"); + addArgs(pb, hgArg); if (debug) { System.out.printf( "invalidCertificate(%s) => %s%n", c.directory, invalidCertificate(c.directory)); } if (invalidCertificate(c.directory)) { - pb2.command(hg_executable, "outgoing", "-l", "1", "--config", "web.cacerts="); + pb2.command(hgExecutable, "outgoing", "-l", "1", "--config", "web.cacerts="); } else { - pb2.command(hg_executable, "outgoing", "-l", "1"); + pb2.command(hgExecutable, "outgoing", "-l", "1"); } - addArgs(pb2, hg_arg); + addArgs(pb2, hgArg); if (insecure) { addArg(pb2, "--insecure"); } @@ -1687,8 +1710,8 @@ public void process(Set checkouts) { replacers.add( new Replacer( "^\\n?comparing with .*\\nsearching for changes\\nno changes found\n", "")); - pb3.command(hg_executable, "shelve", "-l"); - addArgs(pb3, hg_arg); + pb3.command(hgExecutable, "shelve", "-l"); + addArgs(pb3, hgArg); // Shelve is an optional extension, so don't print anything if not installed. replacers3.add(new Replacer("^hg: unknown command 'shelve'\\n(.*\\n)+", "")); replacers3.add( @@ -1699,8 +1722,8 @@ public void process(Set checkouts) { // "svn status" outputs an eighth column, if you pass the --show-updates switch: [* ] replacers.add( new Replacer("(^|\\n)([ACDIMRX?!~ ][CM ][L ][+ ][$ ]) *", "$1$2 " + dir + "/")); - pb.command(svn_executable, "status"); - addArgs(pb, svn_arg); + pb.command(svnExecutable, "status"); + addArgs(pb, svnArg); break; default: assert false; @@ -1722,14 +1745,14 @@ public void process(Set checkouts) { "$1$2 " + dir + "/")); assert c.repository != null; pb.command( - cvs_executable, + cvsExecutable, // Including -d causes problems with CVS repositories // that are embedded inside other repositories. // "-d", c.repository, "-Q", "update", "-d"); - addArgs(pb, cvs_arg); + addArgs(pb, cvsArg); // $filter = "grep -v \"config: unrecognized keyword // 'UseNewInfoFmtStrings'\""; replacers.add(new Replacer("(cvs update: move away )", "$1" + dir + "/")); @@ -1747,22 +1770,22 @@ public void process(Set checkouts) { new Replacer( "((^|\\n)CONFLICT \\(content\\): Merge conflict in )", "$1" + dir + "/")); replacers.add(new Replacer("(^|\\n)([ACDMRU]\t)", "$1$2" + dir + "/")); - pb.command(git_executable, "pull", "-q"); - addArgs(pb, git_arg); + pb.command(gitExecutable, "pull", "-q"); + addArgs(pb, gitArg); // prune branches; alternately do "git remote prune origin"; "git gc" doesn't do this. - pb2.command(git_executable, "fetch", "-p"); + pb2.command(gitExecutable, "fetch", "-p"); break; case HG: replacers.add(new Replacer("(^|\\n)([?!AMR] ) +", "$1$2 " + dir + "/")); replacers.add(new Replacer("(^|\\n)abort: ", "$1")); - pb.command(hg_executable, "-q", "update"); - addArgs(pb, hg_arg); + pb.command(hgExecutable, "-q", "update"); + addArgs(pb, hgArg); if (invalidCertificate(c.directory)) { - pb2.command(hg_executable, "-q", "fetch", "--config", "web.cacerts="); + pb2.command(hgExecutable, "-q", "fetch", "--config", "web.cacerts="); } else { - pb2.command(hg_executable, "-q", "fetch"); + pb2.command(hgExecutable, "-q", "fetch"); } - addArgs(pb2, hg_arg); + addArgs(pb2, hgArg); if (insecure) { addArg(pb2, "--insecure"); } @@ -1771,8 +1794,8 @@ public void process(Set checkouts) { replacers.add(new Replacer("(^|\\n)([?!AMR] ) +", "$1$2 " + dir + "/")); replacers.add(new Replacer("(svn: Failed to add file ')(.*')", "$1" + dir + "/$2")); assert c.repository != null; - pb.command(svn_executable, "-q", "update"); - addArgs(pb, svn_arg); + pb.command(svnExecutable, "-q", "update"); + addArgs(pb, svnArg); // $filter = "grep -v \"Killed by signal 15.\""; break; default: @@ -1788,7 +1811,7 @@ public void process(Set checkouts) { System.out.println(dir + ":"); } if (dir.exists()) { - if (action == CLONE && !redo_existing && !quiet) { + if (action == CLONE && !redoExisting && !quiet) { System.out.println("Skipping checkout (dir already exists): " + dir); continue; } @@ -1805,15 +1828,15 @@ public void process(Set checkouts) { case CLONE: if (!parent.exists()) { if (show) { - if (!dry_run) { + if (!dryRun) { System.out.printf( "Parent directory %s does not exist%s%n", - parent, (dry_run ? "" : " (creating)")); + parent, (dryRun ? "" : " (creating)")); } else { System.out.printf(" mkdir -p %s%n", parent); } } - if (!dry_run) { + if (!dryRun) { if (!parent.mkdirs()) { System.err.println("Could not create directory: " + parent); System.exit(1); @@ -1833,7 +1856,7 @@ public void process(Set checkouts) { } } - if (print_directory) { + if (printDirectory) { System.out.println(dir + " :"); } perform_command(pb, replacers, showNormalOutput); @@ -1918,7 +1941,7 @@ int perform_command(ProcessBuilder pb, List replacers, boolean showNor if (show) { System.out.println(command(pb)); } - if (dry_run) { + if (dryRun) { return 0; } // Perform the command @@ -1987,7 +2010,7 @@ int perform_command(ProcessBuilder pb, List replacers, boolean showNor // * when debugging // * other circumstances? // I could try printing always, to better understand this question. - if (showNormalOutput || exitValue != 0 || debug_replacers || debug_process_output) { + if (showNormalOutput || exitValue != 0 || debugReplacers || debugProcessOutput) { // Filter then print the output. String output; try { @@ -1998,17 +2021,17 @@ int perform_command(ProcessBuilder pb, List replacers, boolean showNor throw new Error("Exception getting process standard output"); } - if (debug_replacers || debug_process_output) { + if (debugReplacers || debugProcessOutput) { System.out.println("preoutput=<<<" + output + ">>>"); } if (!output.equals("")) { boolean noReplacement = false; for (Replacer r : replacers) { String printableRegexp = r.regexp.toString().replace("\r", "\\r").replace("\n", "\\n"); - if (debug_replacers) { + if (debugReplacers) { System.out.println("midoutput_pre[" + printableRegexp + "]=<<<" + output + ">>>"); } - String orig_output = output; + String origOutput = output; // Don't loop, because some regexps will continue to match repeatedly try { output = r.replaceAll(output); @@ -2019,19 +2042,18 @@ int perform_command(ProcessBuilder pb, List replacers, boolean showNor System.out.println(" defaultDirectory = " + defaultDirectory); System.out.println(" cmdLine = " + cmdLine); System.out.println(" regexp = " + printableRegexp); - System.out.println( - " orig output (size " + orig_output.length() + ") = " + orig_output); + System.out.println(" orig output (size " + origOutput.length() + ") = " + origOutput); System.out.println(" output (size " + output.length() + ") = " + output); throw e; } - if (debug_replacers) { + if (debugReplacers) { System.out.println("midoutput_post[" + printableRegexp + "]=<<<" + output + ">>>"); } } - if (debug_replacers || debug_process_output) { + if (debugReplacers || debugProcessOutput) { System.out.println("postoutput=<<<" + output + ">>>"); } - if (debug_replacers) { + if (debugReplacers) { for (int i = 0; i < Math.min(100, output.length()); i++) { System.out.println( i + ": " + (int) output.charAt(i) + "\n \"" + output.charAt(i) + "\"");