diff options
Diffstat (limited to 'org.eclipse.jgit.pgm')
21 files changed, 778 insertions, 114 deletions
diff --git a/org.eclipse.jgit.pgm/BUCK b/org.eclipse.jgit.pgm/BUCK new file mode 100644 index 0000000000..edcf2fc28f --- /dev/null +++ b/org.eclipse.jgit.pgm/BUCK @@ -0,0 +1,70 @@ +include_defs('//tools/git.defs') + +java_library( + name = 'pgm', + srcs = glob(['src/**']), + resources = glob(['resources/**']), + deps = [ + ':services', + '//org.eclipse.jgit:jgit', + '//org.eclipse.jgit.archive:jgit-archive', + '//org.eclipse.jgit.http.apache:http-apache', + '//org.eclipse.jgit.ui:ui', + '//lib:args4j', + ], + visibility = ['PUBLIC'], +) + +prebuilt_jar( + name = 'services', + binary_jar = ':services__jar', +) + +genrule( + name = 'services__jar', + cmd = 'cd $SRCDIR ; zip -qr $OUT .', + srcs = glob(['META-INF/services/*']), + out = 'services.jar', +) + +genrule( + name = 'jgit', + cmd = ''.join([ + 'mkdir $TMP/META-INF &&', + 'cp $(location :binary_manifest) $TMP/META-INF/MANIFEST.MF &&', + 'cp $(location :jgit_jar) $TMP/jgit.jar &&', + 'cd $TMP && zip $TMP/jgit.jar META-INF/MANIFEST.MF &&', + 'cat $SRCDIR/jgit.sh $TMP/jgit.jar >$OUT &&', + 'chmod a+x $OUT', + ]), + srcs = ['jgit.sh'], + out = 'jgit', + visibility = ['PUBLIC'], +) + +java_binary( + name = 'jgit_jar', + deps = [ + ':pgm', + '//lib:slf4j-simple', + '//lib:tukaani-xz', + ], + blacklist = [ + 'META-INF/DEPENDENCIES', + 'META-INF/maven/.*', + ], +) + +genrule( + name = 'binary_manifest', + cmd = ';'.join(['echo "%s: %s" >>$OUT' % e for e in [ + ('Manifest-Version', '1.0'), + ('Main-Class', 'org.eclipse.jgit.pgm.Main'), + ('Bundle-Version', git_version()), + ('Implementation-Title', 'JGit Command Line Interface'), + ('Implementation-Vendor', 'Eclipse.org - JGit'), + ('Implementation-Vendor-URL', 'http://www.eclipse.org/jgit/'), + ('Implementation-Vendor-Id', 'org.eclipse.jgit'), + ]] + ['echo >>$OUT']), + out = 'MANIFEST.MF', +) diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF index 567fd05750..9dc6aea16f 100644 --- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF @@ -21,6 +21,7 @@ Import-Package: org.apache.commons.compress.archivers;version="[1.3,2.0)", org.eclipse.jgit.gitrepo;version="[4.2.0,4.3.0)", org.eclipse.jgit.internal.storage.file;version="[4.2.0,4.3.0)", org.eclipse.jgit.internal.storage.pack;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.reftree;version="[4.2.0,4.3.0)", org.eclipse.jgit.lib;version="[4.2.0,4.3.0)", org.eclipse.jgit.merge;version="4.2.0", org.eclipse.jgit.nls;version="[4.2.0,4.3.0)", @@ -31,6 +32,7 @@ Import-Package: org.apache.commons.compress.archivers;version="[1.3,2.0)", org.eclipse.jgit.storage.file;version="[4.2.0,4.3.0)", org.eclipse.jgit.storage.pack;version="[4.2.0,4.3.0)", org.eclipse.jgit.transport;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport.http.apache;version="[4.2.0,4.3.0)", org.eclipse.jgit.transport.resolver;version="[4.2.0,4.3.0)", org.eclipse.jgit.treewalk;version="[4.2.0,4.3.0)", org.eclipse.jgit.treewalk.filter;version="[4.2.0,4.3.0)", diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin index c13f63e80f..6aa20041b5 100644 --- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin +++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin @@ -41,6 +41,7 @@ org.eclipse.jgit.pgm.debug.DiffAlgorithms org.eclipse.jgit.pgm.debug.MakeCacheTree org.eclipse.jgit.pgm.debug.ReadDirCache org.eclipse.jgit.pgm.debug.RebuildCommitGraph +org.eclipse.jgit.pgm.debug.RebuildRefTree org.eclipse.jgit.pgm.debug.ShowCacheTree org.eclipse.jgit.pgm.debug.ShowCommands org.eclipse.jgit.pgm.debug.ShowDirCache diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml index ca2ead2925..2642491321 100644 --- a/org.eclipse.jgit.pgm/pom.xml +++ b/org.eclipse.jgit.pgm/pom.xml @@ -95,6 +95,17 @@ </dependency> <dependency> + <groupId>org.eclipse.jgit</groupId> + <artifactId>org.eclipse.jgit.http.apache</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + </dependency> + + <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j-version}</version> diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties index 335336da28..b4b1261b37 100644 --- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties +++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties @@ -20,6 +20,7 @@ branchAlreadyExists=A branch named ''{0}'' already exists. branchCreatedFrom=branch: Created from {0} branchDetachedHEAD=detached HEAD branchIsNotAnAncestorOfYourCurrentHEAD=The branch ''{0}'' is not an ancestor of your current HEAD.\nIf you are sure you want to delete it, run ''jgit branch -D {0}''. +branchNameRequired=branch name required branchNotFound=branch ''{0}'' not found. cacheTreePathInfo="{0}": {1} entries, {2} children cannotBeRenamed={0} cannot be renamed @@ -89,7 +90,9 @@ metaVar_author=AUTHOR metaVar_base=base metaVar_blameL=START,END metaVar_blameReverse=START..END +metaVar_branchAndStartPoint=branch [start-name] metaVar_branchName=branch +metaVar_branchNames=branch ... metaVar_bucket=BUCKET metaVar_command=command metaVar_commandDetail=DETAIL @@ -109,6 +112,7 @@ metaVar_message=message metaVar_n=n metaVar_name=name metaVar_object=object +metaVar_oldNewBranchNames=[oldbranch] newbranch metaVar_op=OP metaVar_pass=PASS metaVar_path=path @@ -125,6 +129,7 @@ metaVar_treeish=tree-ish metaVar_uriish=uri-ish metaVar_url=URL metaVar_user=USER +metaVar_values=value ... metaVar_version=VERSION mostCommonlyUsedCommandsAre=The most commonly used commands are: needApprovalToDestroyCurrentRepository=Need approval to destroy current repository @@ -223,6 +228,7 @@ usage_MergeBase=Find as good common ancestors as possible for a merge usage_MergesTwoDevelopmentHistories=Merges two development histories usage_ReadDirCache= Read the DirCache 100 times usage_RebuildCommitGraph=Recreate a repository from another one's commit graph +usage_RebuildRefTree=Copy references into a RefTree usage_Remote=Manage set of tracked repositories usage_RepositoryToReadFrom=Repository to read from usage_RepositoryToReceiveInto=Repository to receive into @@ -337,6 +343,7 @@ usage_recordChangesToRepository=Record changes to the repository usage_recurseIntoSubtrees=recurse into subtrees usage_renameLimit=limit size of rename matrix usage_reset=Reset current HEAD to the specified state +usage_resetReference=Reset to given reference name usage_resetHard=Resets the index and working tree usage_resetSoft=Resets without touching the index file nor the working tree usage_resetMixed=Resets the index but not the working tree @@ -353,6 +360,7 @@ usage_tags=fetch all tags usage_notags=do not fetch tags usage_tagMessage=tag message usage_untrackedFilesMode=show untracked files +usage_updateRef=reference to update usage_updateRemoteRefsFromAnotherRepository=Update remote refs from another repository usage_useNameInsteadOfOriginToTrackUpstream=use <name> instead of 'origin' to track upstream usage_checkoutBranchAfterClone=checkout named branch instead of remotes's HEAD diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java index 65aa24f356..045f3571e5 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java @@ -45,7 +45,6 @@ package org.eclipse.jgit.pgm; import java.io.IOException; import java.text.MessageFormat; -import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; @@ -65,15 +64,18 @@ import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.pgm.internal.CLIText; -import org.eclipse.jgit.pgm.opt.CmdLineParser; +import org.eclipse.jgit.pgm.opt.OptionWithValuesListHandler; import org.eclipse.jgit.revwalk.RevWalk; import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.ExampleMode; import org.kohsuke.args4j.Option; @Command(common = true, usage = "usage_listCreateOrDeleteBranches") class Branch extends TextBuiltin { + private String otherBranch; + private boolean createForce; + private boolean rename; + @Option(name = "--remote", aliases = { "-r" }, usage = "usage_actOnRemoteTrackingBranches") private boolean remote = false; @@ -83,23 +85,69 @@ class Branch extends TextBuiltin { @Option(name = "--contains", metaVar = "metaVar_commitish", usage = "usage_printOnlyBranchesThatContainTheCommit") private String containsCommitish; - @Option(name = "--delete", aliases = { "-d" }, usage = "usage_deleteFullyMergedBranch") - private boolean delete = false; + private List<String> delete; - @Option(name = "--delete-force", aliases = { "-D" }, usage = "usage_deleteBranchEvenIfNotMerged") - private boolean deleteForce = false; + @Option(name = "--delete", aliases = { + "-d" }, metaVar = "metaVar_branchNames", usage = "usage_deleteFullyMergedBranch", handler = OptionWithValuesListHandler.class) + public void delete(List<String> names) { + if (names.isEmpty()) { + throw die(CLIText.get().branchNameRequired); + } + delete = names; + } - @Option(name = "--create-force", aliases = { "-f" }, usage = "usage_forceCreateBranchEvenExists") - private boolean createForce = false; + private List<String> deleteForce; - @Option(name = "-m", usage = "usage_moveRenameABranch") - private boolean rename = false; + @Option(name = "--delete-force", aliases = { + "-D" }, metaVar = "metaVar_branchNames", usage = "usage_deleteBranchEvenIfNotMerged", handler = OptionWithValuesListHandler.class) + public void deleteForce(List<String> names) { + if (names.isEmpty()) { + throw die(CLIText.get().branchNameRequired); + } + deleteForce = names; + } + + @Option(name = "--create-force", aliases = { + "-f" }, metaVar = "metaVar_branchAndStartPoint", usage = "usage_forceCreateBranchEvenExists", handler = OptionWithValuesListHandler.class) + public void createForce(List<String> branchAndStartPoint) { + createForce = true; + if (branchAndStartPoint.isEmpty()) { + throw die(CLIText.get().branchNameRequired); + } + if (branchAndStartPoint.size() > 2) { + throw die(CLIText.get().tooManyRefsGiven); + } + if (branchAndStartPoint.size() == 1) { + branch = branchAndStartPoint.get(0); + } else { + branch = branchAndStartPoint.get(0); + otherBranch = branchAndStartPoint.get(1); + } + } + + @Option(name = "--move", aliases = { + "-m" }, metaVar = "metaVar_oldNewBranchNames", usage = "usage_moveRenameABranch", handler = OptionWithValuesListHandler.class) + public void moveRename(List<String> currentAndNew) { + rename = true; + if (currentAndNew.isEmpty()) { + throw die(CLIText.get().branchNameRequired); + } + if (currentAndNew.size() > 2) { + throw die(CLIText.get().tooManyRefsGiven); + } + if (currentAndNew.size() == 1) { + branch = currentAndNew.get(0); + } else { + branch = currentAndNew.get(0); + otherBranch = currentAndNew.get(1); + } + } @Option(name = "--verbose", aliases = { "-v" }, usage = "usage_beVerbose") private boolean verbose = false; - @Argument - private List<String> branches = new ArrayList<String>(); + @Argument(metaVar = "metaVar_name") + private String branch; private final Map<String, Ref> printRefs = new LinkedHashMap<String, Ref>(); @@ -110,30 +158,33 @@ class Branch extends TextBuiltin { @Override protected void run() throws Exception { - if (delete || deleteForce) - delete(deleteForce); - else { - if (branches.size() > 2) - throw die(CLIText.get().tooManyRefsGiven + new CmdLineParser(this).printExample(ExampleMode.ALL)); - + if (delete != null || deleteForce != null) { + if (delete != null) { + delete(delete, false); + } + if (deleteForce != null) { + delete(deleteForce, true); + } + } else { if (rename) { String src, dst; - if (branches.size() == 1) { + if (otherBranch == null) { final Ref head = db.getRef(Constants.HEAD); - if (head != null && head.isSymbolic()) + if (head != null && head.isSymbolic()) { src = head.getLeaf().getName(); - else + } else { throw die(CLIText.get().cannotRenameDetachedHEAD); - dst = branches.get(0); + } + dst = branch; } else { - src = branches.get(0); + src = branch; final Ref old = db.getRef(src); if (old == null) throw die(MessageFormat.format(CLIText.get().doesNotExist, src)); if (!old.getName().startsWith(Constants.R_HEADS)) throw die(MessageFormat.format(CLIText.get().notABranch, src)); src = old.getName(); - dst = branches.get(1); + dst = otherBranch; } if (!dst.startsWith(Constants.R_HEADS)) @@ -145,13 +196,14 @@ class Branch extends TextBuiltin { if (r.rename() != Result.RENAMED) throw die(MessageFormat.format(CLIText.get().cannotBeRenamed, src)); - } else if (branches.size() > 0) { - String newHead = branches.get(0); + } else if (createForce || branch != null) { + String newHead = branch; String startBranch; - if (branches.size() == 2) - startBranch = branches.get(1); - else + if (createForce) { + startBranch = otherBranch; + } else { startBranch = Constants.HEAD; + } Ref startRef = db.getRef(startBranch); ObjectId startAt = db.resolve(startBranch + "^0"); //$NON-NLS-1$ if (startRef != null) { @@ -164,22 +216,27 @@ class Branch extends TextBuiltin { } startBranch = Repository.shortenRefName(startBranch); String newRefName = newHead; - if (!newRefName.startsWith(Constants.R_HEADS)) + if (!newRefName.startsWith(Constants.R_HEADS)) { newRefName = Constants.R_HEADS + newRefName; - if (!Repository.isValidRefName(newRefName)) + } + if (!Repository.isValidRefName(newRefName)) { throw die(MessageFormat.format(CLIText.get().notAValidRefName, newRefName)); - if (!createForce && db.resolve(newRefName) != null) + } + if (!createForce && db.resolve(newRefName) != null) { throw die(MessageFormat.format(CLIText.get().branchAlreadyExists, newHead)); + } RefUpdate updateRef = db.updateRef(newRefName); updateRef.setNewObjectId(startAt); updateRef.setForceUpdate(createForce); updateRef.setRefLogMessage(MessageFormat.format(CLIText.get().branchCreatedFrom, startBranch), false); Result update = updateRef.update(); - if (update == Result.REJECTED) + if (update == Result.REJECTED) { throw die(MessageFormat.format(CLIText.get().couldNotCreateBranch, newHead, update.toString())); + } } else { - if (verbose) + if (verbose) { rw = new RevWalk(db); + } list(); } } @@ -249,7 +306,8 @@ class Branch extends TextBuiltin { outw.println(); } - private void delete(boolean force) throws IOException { + private void delete(List<String> branches, boolean force) + throws IOException { String current = db.getBranch(); ObjectId head = db.resolve(Constants.HEAD); for (String branch : branches) { diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java index 45794629ec..94517dbf2f 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java @@ -60,7 +60,7 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.pgm.internal.CLIText; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; -import org.kohsuke.args4j.spi.StopOptionHandler; +import org.kohsuke.args4j.spi.RestOfArgumentsHandler; @Command(common = true, usage = "usage_checkout") class Checkout extends TextBuiltin { @@ -74,11 +74,10 @@ class Checkout extends TextBuiltin { @Option(name = "--orphan", usage = "usage_orphan") private boolean orphan = false; - @Argument(required = true, index = 0, metaVar = "metaVar_name", usage = "usage_checkout") + @Argument(required = false, index = 0, metaVar = "metaVar_name", usage = "usage_checkout") private String name; - @Argument(index = 1) - @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = StopOptionHandler.class) + @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = RestOfArgumentsHandler.class) private List<String> paths = new ArrayList<String>(); @Override diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java index cd6953cb05..04078287fb 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java @@ -50,6 +50,7 @@ import org.eclipse.jgit.api.CloneCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.TextProgressMonitor; import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.util.SystemReader; @@ -70,6 +71,9 @@ class Clone extends AbstractFetchCommand { @Option(name = "--bare", usage = "usage_bareClone") private boolean isBare; + @Option(name = "--quiet", usage = "usage_quiet") + private Boolean quiet; + @Argument(index = 0, required = true, metaVar = "metaVar_uriish") private String sourceUri; @@ -109,10 +113,16 @@ class Clone extends AbstractFetchCommand { command.setGitDir(gitdir == null ? null : new File(gitdir)); command.setDirectory(localNameF); - outw.println(MessageFormat.format(CLIText.get().cloningInto, localName)); + boolean msgs = quiet == null || !quiet.booleanValue(); + if (msgs) { + command.setProgressMonitor(new TextProgressMonitor(errw)); + outw.println(MessageFormat.format( + CLIText.get().cloningInto, localName)); + outw.flush(); + } try { db = command.call().getRepository(); - if (db.resolve(Constants.HEAD) == null) + if (msgs && db.resolve(Constants.HEAD) == null) outw.println(CLIText.get().clonedEmptyRepository); } catch (InvalidRemoteException e) { throw die(MessageFormat.format(CLIText.get().doesNotExist, @@ -121,8 +131,9 @@ class Clone extends AbstractFetchCommand { if (db != null) db.close(); } - - outw.println(); - outw.flush(); + if (msgs) { + outw.println(); + outw.flush(); + } } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java index f07df1a4b5..a25f1e9305 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java @@ -86,6 +86,21 @@ public class Die extends RuntimeException { * @since 3.4 */ public Die(boolean aborted) { + this(aborted, null); + } + + /** + * Construct a new exception reflecting the fact that the command execution + * has been aborted before running. + * + * @param aborted + * boolean indicating the fact the execution has been aborted + * @param cause + * can be null + * @since 4.2 + */ + public Die(boolean aborted, final Throwable cause) { + super(cause != null ? cause.getMessage() : null, cause); this.aborted = aborted; } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java index ceb0d6b2fe..d701f22c38 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java @@ -62,6 +62,8 @@ import org.eclipse.jgit.lib.RepositoryBuilder; import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.pgm.opt.CmdLineParser; import org.eclipse.jgit.pgm.opt.SubcommandHandler; +import org.eclipse.jgit.transport.HttpTransport; +import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory; import org.eclipse.jgit.util.CachedAuthenticator; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; @@ -88,13 +90,23 @@ public class Main { @Argument(index = 1, metaVar = "metaVar_arg") private List<String> arguments = new ArrayList<String>(); + PrintWriter writer; + + /** + * + */ + public Main() { + HttpTransport.setConnectionFactory(new HttpClientConnectionFactory()); + } + /** * Execute the command line. * * @param argv * arguments. + * @throws Exception */ - public static void main(final String[] argv) { + public static void main(final String[] argv) throws Exception { new Main().run(argv); } @@ -113,8 +125,10 @@ public class Main { * * @param argv * arguments. + * @throws Exception */ - protected void run(final String[] argv) { + protected void run(final String[] argv) throws Exception { + writer = createErrorWriter(); try { if (!installConsole()) { AwtAuthenticator.install(); @@ -123,12 +137,14 @@ public class Main { configureHttpProxy(); execute(argv); } catch (Die err) { - if (err.isAborted()) - System.exit(1); - System.err.println(MessageFormat.format(CLIText.get().fatalError, err.getMessage())); - if (showStackTrace) - err.printStackTrace(); - System.exit(128); + if (err.isAborted()) { + exit(1, err); + } + writer.println(CLIText.fatalError(err.getMessage())); + if (showStackTrace) { + err.printStackTrace(writer); + } + exit(128, err); } catch (Exception err) { // Try to detect errno == EPIPE and exit normally if that happens // There may be issues with operating system versions and locale, @@ -136,46 +152,54 @@ public class Main { // under other circumstances. if (err.getClass() == IOException.class) { // Linux, OS X - if (err.getMessage().equals("Broken pipe")) //$NON-NLS-1$ - System.exit(0); + if (err.getMessage().equals("Broken pipe")) { //$NON-NLS-1$ + exit(0, err); + } // Windows - if (err.getMessage().equals("The pipe is being closed")) //$NON-NLS-1$ - System.exit(0); + if (err.getMessage().equals("The pipe is being closed")) { //$NON-NLS-1$ + exit(0, err); + } } if (!showStackTrace && err.getCause() != null - && err instanceof TransportException) - System.err.println(MessageFormat.format(CLIText.get().fatalError, err.getCause().getMessage())); + && err instanceof TransportException) { + writer.println(CLIText.fatalError(err.getCause().getMessage())); + } if (err.getClass().getName().startsWith("org.eclipse.jgit.errors.")) { //$NON-NLS-1$ - System.err.println(MessageFormat.format(CLIText.get().fatalError, err.getMessage())); - if (showStackTrace) + writer.println(CLIText.fatalError(err.getMessage())); + if (showStackTrace) { err.printStackTrace(); - System.exit(128); + } + exit(128, err); } err.printStackTrace(); - System.exit(1); + exit(1, err); } if (System.out.checkError()) { - System.err.println(CLIText.get().unknownIoErrorStdout); - System.exit(1); + writer.println(CLIText.get().unknownIoErrorStdout); + exit(1, null); } - if (System.err.checkError()) { + if (writer.checkError()) { // No idea how to present an error here, most likely disk full or // broken pipe - System.exit(1); + exit(1, null); } } + PrintWriter createErrorWriter() { + return new PrintWriter(System.err); + } + private void execute(final String[] argv) throws Exception { - final CmdLineParser clp = new CmdLineParser(this); - PrintWriter writer = new PrintWriter(System.err); + final CmdLineParser clp = new SubcommandLineParser(this); + try { clp.parseArgument(argv); } catch (CmdLineException err) { if (argv.length > 0 && !help && !version) { - writer.println(MessageFormat.format(CLIText.get().fatalError, err.getMessage())); + writer.println(CLIText.fatalError(err.getMessage())); writer.flush(); - System.exit(1); + exit(1, err); } } @@ -191,22 +215,24 @@ public class Main { writer.println(CLIText.get().mostCommonlyUsedCommandsAre); final CommandRef[] common = CommandCatalog.common(); int width = 0; - for (final CommandRef c : common) + for (final CommandRef c : common) { width = Math.max(width, c.getName().length()); + } width += 2; for (final CommandRef c : common) { writer.print(' '); writer.print(c.getName()); - for (int i = c.getName().length(); i < width; i++) + for (int i = c.getName().length(); i < width; i++) { writer.print(' '); + } writer.print(CLIText.get().resourceBundle().getString(c.getUsage())); writer.println(); } writer.println(); } writer.flush(); - System.exit(1); + exit(1, null); } if (version) { @@ -215,21 +241,39 @@ public class Main { } final TextBuiltin cmd = subcommand; - if (cmd.requiresRepository()) - cmd.init(openGitDir(gitdir), null); - else - cmd.init(null, gitdir); + init(cmd); try { cmd.execute(arguments.toArray(new String[arguments.size()])); } finally { - if (cmd.outw != null) + if (cmd.outw != null) { cmd.outw.flush(); - if (cmd.errw != null) + } + if (cmd.errw != null) { cmd.errw.flush(); + } + } + } + + void init(final TextBuiltin cmd) throws IOException { + if (cmd.requiresRepository()) { + cmd.init(openGitDir(gitdir), null); + } else { + cmd.init(null, gitdir); } } /** + * @param status + * @param t + * can be {@code null} + * @throws Exception + */ + void exit(int status, Exception t) throws Exception { + writer.flush(); + System.exit(status); + } + + /** * Evaluate the {@code --git-dir} option and open the repository. * * @param aGitdir @@ -278,7 +322,7 @@ public class Main { throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { try { - Class.forName(name).getMethod("install").invoke(null); //$NON-NLS-1$ + Class.forName(name).getMethod("install").invoke(null); //$NON-NLS-1$ } catch (InvocationTargetException e) { if (e.getCause() instanceof RuntimeException) throw (RuntimeException) e.getCause(); @@ -332,4 +376,19 @@ public class Main { } } } + + /** + * Parser for subcommands which doesn't stop parsing on help options and so + * proceeds all specified options + */ + static class SubcommandLineParser extends CmdLineParser { + public SubcommandLineParser(Object bean) { + super(bean); + } + + @Override + protected boolean containsHelp(String... args) { + return false; + } + } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java index cd65af9549..e739b58ae7 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java @@ -148,9 +148,12 @@ class Merge extends TextBuiltin { break; case FAST_FORWARD: ObjectId oldHeadId = oldHead.getObjectId(); - outw.println(MessageFormat.format(CLIText.get().updating, oldHeadId - .abbreviate(7).name(), result.getNewHead().abbreviate(7) - .name())); + if (oldHeadId != null) { + String oldId = oldHeadId.abbreviate(7).name(); + String newId = result.getNewHead().abbreviate(7).name(); + outw.println(MessageFormat.format(CLIText.get().updating, oldId, + newId)); + } outw.println(result.getMergeStatus().toString()); break; case CHECKOUT_CONFLICT: diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java index 70868e920e..24916bd1c2 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java @@ -144,7 +144,7 @@ class Remote extends TextBuiltin { } @Override - public void printUsageAndExit(final String message, final CmdLineParser clp) + public void printUsage(final String message, final CmdLineParser clp) throws IOException { errw.println(message); errw.println("jgit remote [--verbose (-v)] [--help (-h)]"); //$NON-NLS-1$ @@ -160,7 +160,6 @@ class Remote extends TextBuiltin { errw.println(); errw.flush(); - throw die(true); } private void print(List<RemoteConfig> remotes) throws IOException { diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java index db88008e10..ea59527fed 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java @@ -55,7 +55,7 @@ class Repo extends TextBuiltin { @Option(name = "--groups", aliases = { "-g" }, usage = "usage_groups") private String groups = "default"; //$NON-NLS-1$ - @Argument(required = true, usage = "usage_pathToXml") + @Argument(required = true, metaVar = "metaVar_path", usage = "usage_pathToXml") private String path; @Option(name = "--record-remote-branch", usage = "usage_branches") diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java index 4d3af4b560..9cee37b791 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java @@ -51,7 +51,7 @@ import org.eclipse.jgit.api.ResetCommand; import org.eclipse.jgit.api.ResetCommand.ResetType; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; -import org.kohsuke.args4j.spi.StopOptionHandler; +import org.kohsuke.args4j.spi.RestOfArgumentsHandler; @Command(common = true, usage = "usage_reset") class Reset extends TextBuiltin { @@ -65,12 +65,12 @@ class Reset extends TextBuiltin { @Option(name = "--hard", usage = "usage_resetHard") private boolean hard = false; - @Argument(required = true, index = 0, metaVar = "metaVar_name", usage = "usage_reset") + @Argument(required = false, index = 0, metaVar = "metaVar_commitish", usage = "usage_resetReference") private String commit; - @Argument(index = 1) - @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = StopOptionHandler.class) - private List<String> paths = new ArrayList<String>(); + @Argument(required = false, index = 1, metaVar = "metaVar_paths") + @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = RestOfArgumentsHandler.class) + private List<String> paths = new ArrayList<>(); @Override protected void run() throws Exception { diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java index e32fc9cab4..c5ecb8496e 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java @@ -75,7 +75,13 @@ class RevParse extends TextBuiltin { if (all) { Map<String, Ref> allRefs = db.getRefDatabase().getRefs(ALL); for (final Ref r : allRefs.values()) { - outw.println(r.getObjectId().name()); + ObjectId objectId = r.getObjectId(); + // getRefs skips dangling symrefs, so objectId should never be + // null. + if (objectId == null) { + throw new NullPointerException(); + } + outw.println(objectId.name()); } } else { if (verify && commits.size() > 1) { diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java index be82d070f7..6a6322131a 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java @@ -59,8 +59,9 @@ import org.eclipse.jgit.lib.IndexDiff.StageState; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.pgm.internal.CLIText; +import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; - +import org.kohsuke.args4j.spi.RestOfArgumentsHandler; import org.eclipse.jgit.pgm.opt.UntrackedFilesHandler; /** @@ -83,7 +84,8 @@ class Status extends TextBuiltin { @Option(name = "--untracked-files", aliases = { "-u", "-uno", "-uall" }, usage = "usage_untrackedFilesMode", handler = UntrackedFilesHandler.class) protected String untrackedFilesMode = "all"; // default value //$NON-NLS-1$ - @Option(name = "--", metaVar = "metaVar_path", multiValued = true) + @Argument(required = false, index = 0, metaVar = "metaVar_paths") + @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = RestOfArgumentsHandler.class) protected List<String> filterPaths; @Override diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java index 56cfc7e8ef..0dc549c7d7 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java @@ -212,17 +212,20 @@ public abstract class TextBuiltin { */ protected void parseArguments(final String[] args) throws IOException { final CmdLineParser clp = new CmdLineParser(this); + help = containsHelp(args); try { clp.parseArgument(args); } catch (CmdLineException err) { - if (!help) { - this.errw.println(MessageFormat.format(CLIText.get().fatalError, err.getMessage())); - throw die(true); + this.errw.println(CLIText.fatalError(err.getMessage())); + if (help) { + printUsage("", clp); //$NON-NLS-1$ } + throw die(true, err); } if (help) { - printUsageAndExit(clp); + printUsage("", clp); //$NON-NLS-1$ + throw new TerminatedByHelpException(); } argWalk = clp.getRevWalkGently(); @@ -246,6 +249,20 @@ public abstract class TextBuiltin { * @throws IOException */ public void printUsageAndExit(final String message, final CmdLineParser clp) throws IOException { + printUsage(message, clp); + throw die(true); + } + + /** + * @param message + * non null + * @param clp + * parser used to print options + * @throws IOException + * @since 4.2 + */ + protected void printUsage(final String message, final CmdLineParser clp) + throws IOException { errw.println(message); errw.print("jgit "); //$NON-NLS-1$ errw.print(commandName); @@ -257,12 +274,19 @@ public abstract class TextBuiltin { errw.println(); errw.flush(); - throw die(true); } /** - * @return the resource bundle that will be passed to args4j for purpose - * of string localization + * @return error writer, typically this is standard error. + * @since 4.2 + */ + public ThrowingPrintWriter getErrorWriter() { + return errw; + } + + /** + * @return the resource bundle that will be passed to args4j for purpose of + * string localization */ protected ResourceBundle getResourceBundle() { return CLIText.get().resourceBundle(); @@ -324,6 +348,19 @@ public abstract class TextBuiltin { return new Die(aborted); } + /** + * @param aborted + * boolean indicating that the execution has been aborted before + * running + * @param cause + * why the command has failed. + * @return a runtime exception the caller is expected to throw + * @since 4.2 + */ + protected static Die die(boolean aborted, final Throwable cause) { + return new Die(aborted, cause); + } + String abbreviateRef(String dst, boolean abbreviateRemote) { if (dst.startsWith(R_HEADS)) dst = dst.substring(R_HEADS.length()); @@ -333,4 +370,36 @@ public abstract class TextBuiltin { dst = dst.substring(R_REMOTES.length()); return dst; } + + /** + * @param args + * non null + * @return true if the given array contains help option + * @since 4.2 + */ + public static boolean containsHelp(String[] args) { + for (String str : args) { + if (str.equals("-h") || str.equals("--help")) { //$NON-NLS-1$ //$NON-NLS-2$ + return true; + } + } + return false; + } + + /** + * Exception thrown by {@link TextBuiltin} if it proceeds 'help' option + * + * @since 4.2 + */ + public static class TerminatedByHelpException extends Die { + private static final long serialVersionUID = 1L; + + /** + * Default constructor + */ + public TerminatedByHelpException() { + super(true); + } + + } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java new file mode 100644 index 0000000000..78ca1a7128 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.debug; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.eclipse.jgit.internal.storage.reftree.RefTree; +import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.pgm.Command; +import org.eclipse.jgit.pgm.TextBuiltin; +import org.eclipse.jgit.revwalk.RevWalk; + +@Command(usage = "usage_RebuildRefTree") +class RebuildRefTree extends TextBuiltin { + private String txnNamespace; + private String txnCommitted; + + @Override + protected void run() throws Exception { + try (ObjectReader reader = db.newObjectReader(); + RevWalk rw = new RevWalk(reader); + ObjectInserter inserter = db.newObjectInserter()) { + RefDatabase refDb = db.getRefDatabase(); + if (refDb instanceof RefTreeDatabase) { + RefTreeDatabase d = (RefTreeDatabase) refDb; + refDb = d.getBootstrap(); + txnNamespace = d.getTxnNamespace(); + txnCommitted = d.getTxnCommitted(); + } else { + RefTreeDatabase d = new RefTreeDatabase(db, refDb); + txnNamespace = d.getTxnNamespace(); + txnCommitted = d.getTxnCommitted(); + } + + errw.format("Rebuilding %s from %s", //$NON-NLS-1$ + txnCommitted, refDb.getClass().getSimpleName()); + errw.println(); + errw.flush(); + + CommitBuilder b = new CommitBuilder(); + Ref ref = refDb.exactRef(txnCommitted); + RefUpdate update = refDb.newUpdate(txnCommitted, true); + ObjectId oldTreeId; + + if (ref != null && ref.getObjectId() != null) { + ObjectId oldId = ref.getObjectId(); + update.setExpectedOldObjectId(oldId); + b.setParentId(oldId); + oldTreeId = rw.parseCommit(oldId).getTree(); + } else { + update.setExpectedOldObjectId(ObjectId.zeroId()); + oldTreeId = ObjectId.zeroId(); + } + + RefTree tree = rebuild(refDb.getRefs(RefDatabase.ALL)); + b.setTreeId(tree.writeTree(inserter)); + b.setAuthor(new PersonIdent(db)); + b.setCommitter(b.getAuthor()); + if (b.getTreeId().equals(oldTreeId)) { + return; + } + + update.setNewObjectId(inserter.insert(b)); + inserter.flush(); + + RefUpdate.Result result = update.update(rw); + switch (result) { + case NEW: + case FAST_FORWARD: + break; + default: + throw die(String.format("%s: %s", update.getName(), result)); //$NON-NLS-1$ + } + } + } + + private RefTree rebuild(Map<String, Ref> refMap) { + RefTree tree = RefTree.newEmptyTree(); + List<org.eclipse.jgit.internal.storage.reftree.Command> cmds + = new ArrayList<>(); + + for (Ref r : refMap.values()) { + if (r.getName().equals(txnCommitted) + || r.getName().startsWith(txnNamespace)) { + continue; + } + cmds.add(new org.eclipse.jgit.internal.storage.reftree.Command( + null, + db.peel(r))); + } + tree.apply(cmds); + return tree; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java index f5d581ad01..2812137266 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java @@ -74,6 +74,19 @@ public class CLIText extends TranslationBundle { return MessageFormat.format(get().lineFormat, line); } + /** + * Format the given argument as fatal error using the format defined by + * {@link #fatalError} ("fatal: " by default). + * + * @param message + * the message to format + * @return the formatted line + * @since 4.2 + */ + public static String fatalError(String message) { + return MessageFormat.format(get().fatalError, message); + } + // @formatter:off /***/ public String alreadyOnBranch; /***/ public String alreadyUpToDate; @@ -85,6 +98,7 @@ public class CLIText extends TranslationBundle { /***/ public String branchCreatedFrom; /***/ public String branchDetachedHEAD; /***/ public String branchIsNotAnAncestorOfYourCurrentHEAD; + /***/ public String branchNameRequired; /***/ public String branchNotFound; /***/ public String cacheTreePathInfo; /***/ public String configFileNotFound; @@ -184,6 +198,7 @@ public class CLIText extends TranslationBundle { /***/ public String metaVar_uriish; /***/ public String metaVar_url; /***/ public String metaVar_user; + /***/ public String metaVar_values; /***/ public String metaVar_version; /***/ public String mostCommonlyUsedCommandsAre; /***/ public String needApprovalToDestroyCurrentRepository; diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java index 3f77aa6687..b531ba65a4 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java @@ -43,19 +43,18 @@ package org.eclipse.jgit.pgm.opt; +import java.io.IOException; +import java.io.Writer; import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ResourceBundle; -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.CmdLineException; -import org.kohsuke.args4j.IllegalAnnotationError; -import org.kohsuke.args4j.NamedOptionDef; -import org.kohsuke.args4j.Option; -import org.kohsuke.args4j.OptionDef; -import org.kohsuke.args4j.spi.OptionHandler; -import org.kohsuke.args4j.spi.Setter; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.pgm.Die; import org.eclipse.jgit.pgm.TextBuiltin; import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.revwalk.RevCommit; @@ -63,6 +62,15 @@ import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.treewalk.AbstractTreeIterator; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.IllegalAnnotationError; +import org.kohsuke.args4j.NamedOptionDef; +import org.kohsuke.args4j.Option; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.RestOfArgumentsHandler; +import org.kohsuke.args4j.spi.Setter; /** * Extended command line parser which handles --foo=value arguments. @@ -80,12 +88,17 @@ public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser { registerHandler(RefSpec.class, RefSpecHandler.class); registerHandler(RevCommit.class, RevCommitHandler.class); registerHandler(RevTree.class, RevTreeHandler.class); + registerHandler(List.class, OptionWithValuesListHandler.class); } private final Repository db; private RevWalk walk; + private boolean seenHelp; + + private TextBuiltin cmd; + /** * Creates a new command line owner that parses arguments/options and set * them into the given object. @@ -117,8 +130,12 @@ public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser { */ public CmdLineParser(final Object bean, Repository repo) { super(bean); - if (repo == null && bean instanceof TextBuiltin) - repo = ((TextBuiltin) bean).getRepository(); + if (bean instanceof TextBuiltin) { + cmd = (TextBuiltin) bean; + } + if (repo == null && cmd != null) { + repo = cmd.getRepository(); + } this.db = repo; } @@ -143,9 +160,75 @@ public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser { } tmp.add(str); + + if (containsHelp(args)) { + // suppress exceptions on required parameters if help is present + seenHelp = true; + // stop argument parsing here + break; + } + } + List<OptionHandler> backup = null; + if (seenHelp) { + backup = unsetRequiredOptions(); } - super.parseArgument(tmp.toArray(new String[tmp.size()])); + try { + super.parseArgument(tmp.toArray(new String[tmp.size()])); + } catch (Die e) { + if (!seenHelp) { + throw e; + } + printToErrorWriter(CLIText.fatalError(e.getMessage())); + } finally { + // reset "required" options to defaults for correct command printout + if (backup != null && !backup.isEmpty()) { + restoreRequiredOptions(backup); + } + seenHelp = false; + } + } + + private void printToErrorWriter(String error) { + if (cmd == null) { + System.err.println(error); + } else { + try { + cmd.getErrorWriter().println(error); + } catch (IOException e1) { + System.err.println(error); + } + } + } + + private List<OptionHandler> unsetRequiredOptions() { + List<OptionHandler> options = getOptions(); + List<OptionHandler> backup = new ArrayList<>(options); + for (Iterator<OptionHandler> iterator = options.iterator(); iterator + .hasNext();) { + OptionHandler handler = iterator.next(); + if (handler.option instanceof NamedOptionDef + && handler.option.required()) { + iterator.remove(); + } + } + return backup; + } + + private void restoreRequiredOptions(List<OptionHandler> backup) { + List<OptionHandler> options = getOptions(); + options.clear(); + options.addAll(backup); + } + + /** + * @param args + * non null + * @return true if the given array contains help option + * @since 4.2 + */ + protected boolean containsHelp(final String... args) { + return TextBuiltin.containsHelp(args); } /** @@ -181,7 +264,7 @@ public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser { return walk; } - static class MyOptionDef extends OptionDef { + class MyOptionDef extends OptionDef { public MyOptionDef(OptionDef o) { super(o.usage(), o.metaVar(), o.required(), o.handler(), o @@ -201,6 +284,11 @@ public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser { return metaVar(); } } + + @Override + public boolean required() { + return seenHelp ? false : super.required(); + } } @Override @@ -211,4 +299,55 @@ public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser { return super.createOptionHandler(new MyOptionDef(o), setter); } + + @SuppressWarnings("unchecked") + private List<OptionHandler> getOptions() { + List<OptionHandler> options = null; + try { + Field field = org.kohsuke.args4j.CmdLineParser.class + .getDeclaredField("options"); //$NON-NLS-1$ + field.setAccessible(true); + options = (List<OptionHandler>) field.get(this); + } catch (NoSuchFieldException | SecurityException + | IllegalArgumentException | IllegalAccessException e) { + // ignore + } + if (options == null) { + return Collections.emptyList(); + } + return options; + } + + @Override + public void printSingleLineUsage(Writer w, ResourceBundle rb) { + List<OptionHandler> options = getOptions(); + if (options.isEmpty()) { + super.printSingleLineUsage(w, rb); + return; + } + List<OptionHandler> backup = new ArrayList<>(options); + boolean changed = sortRestOfArgumentsHandlerToTheEnd(options); + try { + super.printSingleLineUsage(w, rb); + } finally { + if (changed) { + options.clear(); + options.addAll(backup); + } + } + } + + private boolean sortRestOfArgumentsHandlerToTheEnd( + List<OptionHandler> options) { + for (int i = 0; i < options.size(); i++) { + OptionHandler handler = options.get(i); + if (handler instanceof RestOfArgumentsHandler + || handler instanceof PathTreeFilterHandler) { + options.remove(i); + options.add(handler); + return true; + } + } + return false; + } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/OptionWithValuesListHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/OptionWithValuesListHandler.java new file mode 100644 index 0000000000..3de7a81091 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/OptionWithValuesListHandler.java @@ -0,0 +1,52 @@ +package org.eclipse.jgit.pgm.opt; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.pgm.internal.CLIText; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.Parameters; +import org.kohsuke.args4j.spi.Setter; + +/** + * Handler which allows to parse option with few values + * + * @since 4.2 + */ +public class OptionWithValuesListHandler extends OptionHandler<List<?>> { + + /** + * @param parser + * @param option + * @param setter + */ + public OptionWithValuesListHandler(CmdLineParser parser, + OptionDef option, Setter<List<?>> setter) { + super(parser, option, setter); + } + + @Override + public int parseArguments(Parameters params) throws CmdLineException { + final List<String> list = new ArrayList<>(); + for (int idx = 0; idx < params.size(); idx++) { + final String p; + try { + p = params.getParameter(idx); + } catch (CmdLineException cle) { + break; + } + list.add(p); + } + setter.addValue(list); + return list.size(); + } + + @Override + public String getDefaultMetaVariable() { + return CLIText.get().metaVar_values; + } + +} |