diff options
Diffstat (limited to 'org.eclipse.jgit')
58 files changed, 1799 insertions, 434 deletions
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index 40255670ad..7c175e3905 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -3,8 +3,8 @@ <resource path="META-INF/MANIFEST.MF"> <filter id="924844039"> <message_arguments> - <message_argument value="4.7.3"/> - <message_argument value="4.7.0"/> + <message_argument value="4.8.1"/> + <message_argument value="4.8.0"/> </message_arguments> </filter> </resource> @@ -12,26 +12,23 @@ <filter id="336658481"> <message_arguments> <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/> - <message_argument value="CONFIG_KEY_AUTODETACH"/> - </message_arguments> - </filter> - <filter id="336658481"> - <message_arguments> - <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/> - <message_argument value="CONFIG_KEY_LOGEXPIRY"/> + <message_argument value="CONFIG_KEY_SUPPORTSATOMICFILECREATION"/> </message_arguments> </filter> - <filter id="336658481"> + <filter id="1141899266"> <message_arguments> - <message_argument value="org.eclipse.jgit.lib.ConfigConstants"/> + <message_argument value="4.5"/> + <message_argument value="4.8"/> <message_argument value="CONFIG_KEY_SUPPORTSATOMICFILECREATION"/> </message_arguments> </filter> - <filter id="1141899266"> + </resource> + <resource path="src/org/eclipse/jgit/lib/Constants.java" type="org.eclipse.jgit.lib.Constants"> + <filter comment="LOCK_SUFFIX was backported to 4.7.3" id="1141899266"> <message_arguments> - <message_argument value="4.5"/> <message_argument value="4.7"/> - <message_argument value="CONFIG_KEY_SUPPORTSATOMICFILECREATION"/> + <message_argument value="4.8"/> + <message_argument value="LOCK_SUFFIX"/> </message_arguments> </filter> </resource> @@ -39,16 +36,32 @@ <filter id="1141899266"> <message_arguments> <message_argument value="4.5"/> - <message_argument value="4.7"/> + <message_argument value="4.8"/> <message_argument value="createNewFile(File)"/> </message_arguments> </filter> <filter id="1141899266"> <message_arguments> <message_argument value="4.5"/> - <message_argument value="4.7"/> + <message_argument value="4.8"/> <message_argument value="supportsAtomicCreateNewFile()"/> </message_arguments> </filter> + <filter id="1141899266"> + <message_arguments> + <message_argument value="4.7"/> + <message_argument value="4.8"/> + <message_argument value="createNewFileAtomic(File)"/> + </message_arguments> + </filter> + </resource> + <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$LockToken"> + <filter id="1141899266"> + <message_arguments> + <message_argument value="4.7"/> + <message_argument value="4.8"/> + <message_argument value="LockToken"/> + </message_arguments> + </filter> </resource> </component> diff --git a/org.eclipse.jgit/BUILD b/org.eclipse.jgit/BUILD index 75f4fe69c1..a8a53f2742 100644 --- a/org.eclipse.jgit/BUILD +++ b/org.eclipse.jgit/BUILD @@ -5,7 +5,7 @@ INSECURE_CIPHER_FACTORY = [ ] SRCS = glob( - ["src/**"], + ["src/**/*.java"], exclude = INSECURE_CIPHER_FACTORY, ) diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index 4b3732395c..9b6a9f182a 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -2,12 +2,12 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit -Bundle-Version: 4.7.4.qualifier +Bundle-Version: 4.8.1.qualifier Bundle-Localization: plugin Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy -Export-Package: org.eclipse.jgit.annotations;version="4.7.4", - org.eclipse.jgit.api;version="4.7.4"; +Export-Package: org.eclipse.jgit.annotations;version="4.8.1", + org.eclipse.jgit.api;version="4.8.1"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.diff, @@ -21,51 +21,51 @@ Export-Package: org.eclipse.jgit.annotations;version="4.7.4", org.eclipse.jgit.submodule, org.eclipse.jgit.transport, org.eclipse.jgit.merge", - org.eclipse.jgit.api.errors;version="4.7.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors", - org.eclipse.jgit.attributes;version="4.7.4", - org.eclipse.jgit.blame;version="4.7.4"; + org.eclipse.jgit.api.errors;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors", + org.eclipse.jgit.attributes;version="4.8.1", + org.eclipse.jgit.blame;version="4.8.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.diff", - org.eclipse.jgit.diff;version="4.7.4"; + org.eclipse.jgit.diff;version="4.8.1"; uses:="org.eclipse.jgit.patch, org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.util", - org.eclipse.jgit.dircache;version="4.7.4"; + org.eclipse.jgit.dircache;version="4.8.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.util, org.eclipse.jgit.events, org.eclipse.jgit.attributes", - org.eclipse.jgit.errors;version="4.7.4"; + org.eclipse.jgit.errors;version="4.8.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.internal.storage.pack, org.eclipse.jgit.transport, org.eclipse.jgit.dircache", - org.eclipse.jgit.events;version="4.7.4";uses:="org.eclipse.jgit.lib", - org.eclipse.jgit.fnmatch;version="4.7.4", - org.eclipse.jgit.gitrepo;version="4.7.4"; + org.eclipse.jgit.events;version="4.8.1";uses:="org.eclipse.jgit.lib", + org.eclipse.jgit.fnmatch;version="4.8.1", + org.eclipse.jgit.gitrepo;version="4.8.1"; uses:="org.eclipse.jgit.api, org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, org.xml.sax.helpers, org.xml.sax", - org.eclipse.jgit.gitrepo.internal;version="4.7.4";x-internal:=true, - org.eclipse.jgit.hooks;version="4.7.4";uses:="org.eclipse.jgit.lib", - org.eclipse.jgit.ignore;version="4.7.4", - org.eclipse.jgit.ignore.internal;version="4.7.4";x-friends:="org.eclipse.jgit.test", - org.eclipse.jgit.internal;version="4.7.4";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test", - org.eclipse.jgit.internal.ketch;version="4.7.4";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", - org.eclipse.jgit.internal.storage.dfs;version="4.7.4"; + org.eclipse.jgit.gitrepo.internal;version="4.8.1";x-internal:=true, + org.eclipse.jgit.hooks;version="4.8.1";uses:="org.eclipse.jgit.lib", + org.eclipse.jgit.ignore;version="4.8.1", + org.eclipse.jgit.ignore.internal;version="4.8.1";x-friends:="org.eclipse.jgit.test", + org.eclipse.jgit.internal;version="4.8.1";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test", + org.eclipse.jgit.internal.ketch;version="4.8.1";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", + org.eclipse.jgit.internal.storage.dfs;version="4.8.1"; x-friends:="org.eclipse.jgit.test, org.eclipse.jgit.http.server, org.eclipse.jgit.http.test, org.eclipse.jgit.lfs.test", - org.eclipse.jgit.internal.storage.file;version="4.7.4"; + org.eclipse.jgit.internal.storage.file;version="4.8.1"; x-friends:="org.eclipse.jgit.test, org.eclipse.jgit.junit, org.eclipse.jgit.junit.http, @@ -73,9 +73,9 @@ Export-Package: org.eclipse.jgit.annotations;version="4.7.4", org.eclipse.jgit.lfs, org.eclipse.jgit.pgm, org.eclipse.jgit.pgm.test", - org.eclipse.jgit.internal.storage.pack;version="4.7.4";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", - org.eclipse.jgit.internal.storage.reftree;version="4.7.4";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", - org.eclipse.jgit.lib;version="4.7.4"; + org.eclipse.jgit.internal.storage.pack;version="4.8.1";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", + org.eclipse.jgit.internal.storage.reftree;version="4.8.1";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", + org.eclipse.jgit.lib;version="4.8.1"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.util, @@ -85,32 +85,33 @@ Export-Package: org.eclipse.jgit.annotations;version="4.7.4", org.eclipse.jgit.treewalk, org.eclipse.jgit.transport, org.eclipse.jgit.submodule", - org.eclipse.jgit.merge;version="4.7.4"; + org.eclipse.jgit.lib.internal;version="4.8.1";x-internal:=true, + org.eclipse.jgit.merge;version="4.8.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.revwalk, org.eclipse.jgit.diff, org.eclipse.jgit.dircache, org.eclipse.jgit.api", - org.eclipse.jgit.nls;version="4.7.4", - org.eclipse.jgit.notes;version="4.7.4"; + org.eclipse.jgit.nls;version="4.8.1", + org.eclipse.jgit.notes;version="4.8.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.revwalk, org.eclipse.jgit.merge", - org.eclipse.jgit.patch;version="4.7.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff", - org.eclipse.jgit.revplot;version="4.7.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk", - org.eclipse.jgit.revwalk;version="4.7.4"; + org.eclipse.jgit.patch;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff", + org.eclipse.jgit.revplot;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk", + org.eclipse.jgit.revwalk;version="4.8.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.diff, org.eclipse.jgit.revwalk.filter", - org.eclipse.jgit.revwalk.filter;version="4.7.4";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util", - org.eclipse.jgit.storage.file;version="4.7.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util", - org.eclipse.jgit.storage.pack;version="4.7.4";uses:="org.eclipse.jgit.lib", - org.eclipse.jgit.submodule;version="4.7.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk", - org.eclipse.jgit.transport;version="4.7.4"; + org.eclipse.jgit.revwalk.filter;version="4.8.1";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util", + org.eclipse.jgit.storage.file;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util", + org.eclipse.jgit.storage.pack;version="4.8.1";uses:="org.eclipse.jgit.lib", + org.eclipse.jgit.submodule;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk", + org.eclipse.jgit.transport;version="4.8.1"; uses:="org.eclipse.jgit.transport.resolver, org.eclipse.jgit.revwalk, org.eclipse.jgit.internal.storage.pack, @@ -122,24 +123,24 @@ Export-Package: org.eclipse.jgit.annotations;version="4.7.4", org.eclipse.jgit.transport.http, org.eclipse.jgit.errors, org.eclipse.jgit.storage.pack", - org.eclipse.jgit.transport.http;version="4.7.4";uses:="javax.net.ssl", - org.eclipse.jgit.transport.resolver;version="4.7.4";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport", - org.eclipse.jgit.treewalk;version="4.7.4"; + org.eclipse.jgit.transport.http;version="4.8.1";uses:="javax.net.ssl", + org.eclipse.jgit.transport.resolver;version="4.8.1";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport", + org.eclipse.jgit.treewalk;version="4.8.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, org.eclipse.jgit.attributes, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.util, org.eclipse.jgit.dircache", - org.eclipse.jgit.treewalk.filter;version="4.7.4";uses:="org.eclipse.jgit.treewalk", - org.eclipse.jgit.util;version="4.7.4"; + org.eclipse.jgit.treewalk.filter;version="4.8.1";uses:="org.eclipse.jgit.treewalk", + org.eclipse.jgit.util;version="4.8.1"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.transport.http, org.eclipse.jgit.storage.file, org.ietf.jgss", - org.eclipse.jgit.util.io;version="4.7.4", - org.eclipse.jgit.util.sha1;version="4.7.4", - org.eclipse.jgit.util.time;version="4.7.4" + org.eclipse.jgit.util.io;version="4.8.1", + org.eclipse.jgit.util.sha1;version="4.8.1", + org.eclipse.jgit.util.time;version="4.8.1" Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", com.jcraft.jsch;version="[0.1.37,0.2.0)", diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF index c95c3458c5..63e6bbccbe 100644 --- a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF @@ -3,5 +3,5 @@ Bundle-ManifestVersion: 2 Bundle-Name: org.eclipse.jgit - Sources Bundle-SymbolicName: org.eclipse.jgit.source Bundle-Vendor: Eclipse.org - JGit -Bundle-Version: 4.7.4.qualifier -Eclipse-SourceBundle: org.eclipse.jgit;version="4.7.4.qualifier";roots="." +Bundle-Version: 4.8.1.qualifier +Eclipse-SourceBundle: org.eclipse.jgit;version="4.8.1.qualifier";roots="." diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml index c42e38d3d7..673f3ffc0d 100644 --- a/org.eclipse.jgit/pom.xml +++ b/org.eclipse.jgit/pom.xml @@ -53,7 +53,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.7.4-SNAPSHOT</version> + <version>4.8.1-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit</artifactId> diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 34457c9367..c9aaa39945 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -437,6 +437,7 @@ noHEADExistsAndNoExplicitStartingRevisionWasSpecified=No HEAD exists and no expl noHMACsupport=No {0} support: {1} noMergeBase=No merge base could be determined. Reason={0}. {1} noMergeHeadSpecified=No merge head specified +nonBareLinkFilesNotSupported=Link files are not supported with nonbare repos noSuchRef=no such ref notABoolean=Not a boolean: {0} notABundle=not a bundle @@ -544,6 +545,7 @@ renamesFindingExact=Finding exact renames renamesRejoiningModifies=Rejoining modified file pairs repositoryAlreadyExists=Repository already exists: {0} repositoryConfigFileInvalid=Repository config file {0} invalid {1} +repositoryIsRequired=repository is required repositoryNotFound=repository not found: {0} repositoryState_applyMailbox=Apply mailbox repositoryState_bare=Bare diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java index 4b815b439d..d450c64679 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011, 2013 Chris Aniszczyk <caniszczyk@gmail.com> + * Copyright (C) 2011, 2017 Chris Aniszczyk <caniszczyk@gmail.com> * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -58,6 +58,7 @@ import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; @@ -75,6 +76,8 @@ import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.TagOpt; import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FileUtils; /** * Clone a repository into a new working directory @@ -106,6 +109,46 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { private Collection<String> branchesToClone; + private Callback callback; + + private boolean directoryExistsInitially; + + private boolean gitDirExistsInitially; + + /** + * Callback for status of clone operation. + * + * @since 4.8 + */ + public interface Callback { + /** + * Notify initialized submodules. + * + * @param submodules + * the submodules + * + */ + void initializedSubmodules(Collection<String> submodules); + + /** + * Notify starting to clone a submodule. + * + * @param path + * the submodule path + */ + void cloningSubmodule(String path); + + /** + * Notify checkout of commit + * + * @param commit + * the id of the commit being checked out + * @param path + * the submodule path + */ + void checkingOut(AnyObjectId commit, String path); + } + /** * Create clone command with no repository set */ @@ -130,26 +173,55 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { @Override public Git call() throws GitAPIException, InvalidRemoteException, org.eclipse.jgit.api.errors.TransportException { + URIish u = null; + try { + u = new URIish(uri); + verifyDirectories(u); + } catch (URISyntaxException e) { + throw new InvalidRemoteException( + MessageFormat.format(JGitText.get().invalidURL, uri)); + } Repository repository = null; + FetchResult fetchResult = null; + Thread cleanupHook = new Thread(() -> cleanup()); + Runtime.getRuntime().addShutdownHook(cleanupHook); try { - URIish u = new URIish(uri); - repository = init(u); - FetchResult result = fetch(repository, u); - if (!noCheckout) - checkout(repository, result); - return new Git(repository, true); + repository = init(); + fetchResult = fetch(repository, u); } catch (IOException ioe) { if (repository != null) { repository.close(); } + cleanup(); throw new JGitInternalException(ioe.getMessage(), ioe); } catch (URISyntaxException e) { if (repository != null) { repository.close(); } + cleanup(); throw new InvalidRemoteException(MessageFormat.format( JGitText.get().invalidRemote, remote)); + } catch (GitAPIException | RuntimeException e) { + if (repository != null) { + repository.close(); + } + cleanup(); + throw e; + } finally { + Runtime.getRuntime().removeShutdownHook(cleanupHook); + } + if (!noCheckout) { + try { + checkout(repository, fetchResult); + } catch (IOException ioe) { + repository.close(); + throw new JGitInternalException(ioe.getMessage(), ioe); + } catch (GitAPIException | RuntimeException e) { + repository.close(); + throw e; + } } + return new Git(repository, true); } private static boolean isNonEmptyDirectory(File dir) { @@ -160,12 +232,12 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { return false; } - private Repository init(URIish u) throws GitAPIException { - InitCommand command = Git.init(); - command.setBare(bare); + private void verifyDirectories(URIish u) { if (directory == null && gitDir == null) { directory = new File(u.getHumanishName(), Constants.DOT_GIT); } + directoryExistsInitially = directory != null && directory.exists(); + gitDirExistsInitially = gitDir != null && gitDir.exists(); validateDirs(directory, gitDir, bare); if (isNonEmptyDirectory(directory)) { throw new JGitInternalException(MessageFormat.format( @@ -175,6 +247,11 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { throw new JGitInternalException(MessageFormat.format( JGitText.get().cloneNonEmptyDirectory, gitDir.getName())); } + } + + private Repository init() throws GitAPIException { + InitCommand command = Git.init(); + command.setBare(bare); if (directory != null) { command.setDirectory(directory); } @@ -280,12 +357,18 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { private void cloneSubmodules(Repository clonedRepo) throws IOException, GitAPIException { SubmoduleInitCommand init = new SubmoduleInitCommand(clonedRepo); - if (init.call().isEmpty()) + Collection<String> submodules = init.call(); + if (submodules.isEmpty()) { return; + } + if (callback != null) { + callback.initializedSubmodules(submodules); + } SubmoduleUpdateCommand update = new SubmoduleUpdateCommand(clonedRepo); configure(update); update.setProgressMonitor(monitor); + update.setCallback(callback); if (!update.call().isEmpty()) { SubmoduleWalk walk = SubmoduleWalk.forIndex(clonedRepo); while (walk.next()) { @@ -523,6 +606,19 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { return this; } + /** + * Register a progress callback. + * + * @param callback + * the callback + * @return {@code this} + * @since 4.8 + */ + public CloneCommand setCallback(Callback callback) { + this.callback = callback; + return this; + } + private static void validateDirs(File directory, File gitDir, boolean bare) throws IllegalStateException { if (directory != null) { @@ -548,4 +644,38 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { } } } + + private void cleanup() { + try { + if (directory != null) { + if (!directoryExistsInitially) { + FileUtils.delete(directory, FileUtils.RECURSIVE + | FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS); + } else { + deleteChildren(directory); + } + } + if (gitDir != null) { + if (!gitDirExistsInitially) { + FileUtils.delete(gitDir, FileUtils.RECURSIVE + | FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS); + } else { + deleteChildren(directory); + } + } + } catch (IOException e) { + // Ignore; this is a best-effort cleanup in error cases, and + // IOException should not be raised anyway + } + } + + private void deleteChildren(File file) throws IOException { + if (!FS.DETECTED.isDirectory(file)) { + return; + } + for (File child : file.listFiles()) { + FileUtils.delete(child, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING + | FileUtils.IGNORE_ERRORS); + } + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java index cc3302b486..785c20c8af 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java @@ -99,6 +99,24 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> { private FetchRecurseSubmodulesMode submoduleRecurseMode = null; + private Callback callback; + + /** + * Callback for status of fetch operation. + * + * @since 4.8 + * + */ + public interface Callback { + /** + * Notify fetching a submodule. + * + * @param name + * the submodule name. + */ + void fetchingSubmodule(String name); + } + /** * @param repo */ @@ -173,6 +191,9 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> { .setThin(thin).setRefSpecs(refSpecs) .setDryRun(dryRun) .setRecurseSubmodules(recurseMode); + if (callback != null) { + callback.fetchingSubmodule(walk.getPath()); + } results.addSubmodule(walk.getPath(), f.call()); } } @@ -434,4 +455,17 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> { this.tagOption = tagOpt; return this; } + + /** + * Register a progress callback. + * + * @param callback + * the callback + * @return {@code this} + * @since 4.8 + */ + public FetchCommand setCallback(Callback callback) { + this.callback = callback; + return this; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java index ae822da568..9c5ae432e2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java @@ -203,62 +203,63 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> { @Override public PullResult call() throws GitAPIException, WrongRepositoryStateException, InvalidConfigurationException, - DetachedHeadException, InvalidRemoteException, CanceledException, + InvalidRemoteException, CanceledException, RefNotFoundException, RefNotAdvertisedException, NoHeadException, org.eclipse.jgit.api.errors.TransportException { checkCallable(); monitor.beginTask(JGitText.get().pullTaskName, 2); + Config repoConfig = repo.getConfig(); - String branchName; + String branchName = null; try { String fullBranch = repo.getFullBranch(); - if (fullBranch == null) - throw new NoHeadException( - JGitText.get().pullOnRepoWithoutHEADCurrentlyNotSupported); - if (!fullBranch.startsWith(Constants.R_HEADS)) { - // we can not pull if HEAD is detached and branch is not - // specified explicitly - throw new DetachedHeadException(); + if (fullBranch != null + && fullBranch.startsWith(Constants.R_HEADS)) { + branchName = fullBranch.substring(Constants.R_HEADS.length()); } - branchName = fullBranch.substring(Constants.R_HEADS.length()); } catch (IOException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfPullCommand, e); } + if (remoteBranchName == null && branchName != null) { + // get the name of the branch in the remote repository + // stored in configuration key branch.<branch name>.merge + remoteBranchName = repoConfig.getString( + ConfigConstants.CONFIG_BRANCH_SECTION, branchName, + ConfigConstants.CONFIG_KEY_MERGE); + } + if (remoteBranchName == null) { + remoteBranchName = branchName; + } + if (remoteBranchName == null) { + throw new NoHeadException( + JGitText.get().cannotCheckoutFromUnbornBranch); + } if (!repo.getRepositoryState().equals(RepositoryState.SAFE)) throw new WrongRepositoryStateException(MessageFormat.format( JGitText.get().cannotPullOnARepoWithState, repo .getRepositoryState().name())); - Config repoConfig = repo.getConfig(); - if (remote == null) { + if (remote == null && branchName != null) { // get the configured remote for the currently checked out branch // stored in configuration key branch.<branch name>.remote remote = repoConfig.getString( ConfigConstants.CONFIG_BRANCH_SECTION, branchName, ConfigConstants.CONFIG_KEY_REMOTE); } - if (remote == null) + if (remote == null) { // fall back to default remote remote = Constants.DEFAULT_REMOTE_NAME; - - if (remoteBranchName == null) - // get the name of the branch in the remote repository - // stored in configuration key branch.<branch name>.merge - remoteBranchName = repoConfig.getString( - ConfigConstants.CONFIG_BRANCH_SECTION, branchName, - ConfigConstants.CONFIG_KEY_MERGE); + } // determines whether rebase should be used after fetching - if (pullRebaseMode == null) { + if (pullRebaseMode == null && branchName != null) { pullRebaseMode = getRebaseMode(branchName, repoConfig); } - if (remoteBranchName == null) - remoteBranchName = branchName; final boolean isRemote = !remote.equals("."); //$NON-NLS-1$ String remoteUri; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java index 29d5d49a45..4d3dff02cd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java @@ -89,6 +89,8 @@ public class SubmoduleUpdateCommand extends private MergeStrategy strategy = MergeStrategy.RECURSIVE; + private CloneCommand.Callback callback; + /** * @param repo */ @@ -161,6 +163,9 @@ public class SubmoduleUpdateCommand extends Repository submoduleRepo = generator.getRepository(); // Clone repository is not present if (submoduleRepo == null) { + if (callback != null) { + callback.cloningSubmodule(generator.getPath()); + } CloneCommand clone = Git.cloneRepository(); configure(clone); clone.setURI(url); @@ -201,6 +206,10 @@ public class SubmoduleUpdateCommand extends Constants.HEAD, true); refUpdate.setNewObjectId(commit); refUpdate.forceUpdate(); + if (callback != null) { + callback.checkingOut(commit, + generator.getPath()); + } } } finally { submoduleRepo.close(); @@ -225,4 +234,17 @@ public class SubmoduleUpdateCommand extends this.strategy = strategy; return this; } + + /** + * Set status callback for submodule clone operation. + * + * @param callback + * the callback + * @return {@code this} + * @since 4.8 + */ + public SubmoduleUpdateCommand setCallback(CloneCommand.Callback callback) { + this.callback = callback; + return this; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargeObjectInPackException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargeObjectInPackException.java index 08c25c28ed..b841f57f1e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargeObjectInPackException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/TooLargeObjectInPackException.java @@ -38,7 +38,8 @@ package org.eclipse.jgit.api.errors; /** - * Exception thrown when the server rejected a too large pack + * Exception thrown when PackParser finds an object larger than a predefined + * limit * * @since 4.4 */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java index bd6e5c859a..5f01366c47 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java @@ -121,23 +121,7 @@ public abstract class DiffAlgorithm { Subsequence<S> as = Subsequence.a(a, region); Subsequence<S> bs = Subsequence.b(b, region); EditList e = Subsequence.toBase(diffNonCommon(cs, as, bs), as, bs); - - // The last insertion may need to be shifted later if it - // inserts elements that were previously reduced out as - // common at the end. - // - Edit last = e.get(e.size() - 1); - if (last.getType() == Edit.Type.INSERT) { - while (last.endB < b.size() - && cmp.equals(b, last.beginB, b, last.endB)) { - last.beginA++; - last.endA++; - last.beginB++; - last.endB++; - } - } - - return e; + return normalize(cmp, e, a, b); } case EMPTY: @@ -153,6 +137,107 @@ public abstract class DiffAlgorithm { } /** + * Reorganize an {@link EditList} for better diff consistency. + * <p> + * {@code DiffAlgorithms} may return {@link Edit.Type#INSERT} or + * {@link Edit.Type#DELETE} edits that can be "shifted". For + * example, the deleted section + * <pre> + * -a + * -b + * -c + * a + * b + * c + * </pre> + * can be shifted down by 1, 2 or 3 locations. + * <p> + * To avoid later merge issues, we shift such edits to a + * consistent location. {@code normalize} uses a simple strategy of + * shifting such edits to their latest possible location. + * <p> + * This strategy may not always produce an aesthetically pleasing + * diff. For instance, it works well with + * <pre> + * function1 { + * ... + * } + * + * +function2 { + * + ... + * +} + * + + * function3 { + * ... + * } + * </pre> + * but less so for + * <pre> + * # + * # comment1 + * # + * function1() { + * } + * + * # + * +# comment3 + * +# + * +function3() { + * +} + * + + * +# + * # comment2 + * # + * function2() { + * } + * </pre> + * <a href="https://github.com/mhagger/diff-slider-tools">More + * sophisticated strategies</a> are possible, say by calculating a + * suitable "aesthetic cost" for each possible position and using + * the lowest cost, but {@code normalize} just shifts edits + * to the end as much as possible. + * + * @param <S> + * type of sequence being compared. + * @param cmp + * the comparator supplying the element equivalence function. + * @param e + * a modifiable edit list comparing the provided sequences. + * @param a + * the first (also known as old or pre-image) sequence. + * @param b + * the second (also known as new or post-image) sequence. + * @return a modifiable edit list with edit regions shifted to their + * latest possible location. The result list is never null. + * @since 4.7 + */ + private static <S extends Sequence> EditList normalize( + SequenceComparator<? super S> cmp, EditList e, S a, S b) { + Edit prev = null; + for (int i = e.size() - 1; i >= 0; i--) { + Edit cur = e.get(i); + Edit.Type curType = cur.getType(); + + int maxA = (prev == null) ? a.size() : prev.beginA; + int maxB = (prev == null) ? b.size() : prev.beginB; + + if (curType == Edit.Type.INSERT) { + while (cur.endA < maxA && cur.endB < maxB + && cmp.equals(b, cur.beginB, b, cur.endB)) { + cur.shift(1); + } + } else if (curType == Edit.Type.DELETE) { + while (cur.endA < maxA && cur.endB < maxB + && cmp.equals(a, cur.beginA, a, cur.endA)) { + cur.shift(1); + } + } + prev = cur; + } + return e; + } + + /** * Compare two sequences and identify a list of edits between them. * * This method should be invoked only after the two sequences have been diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java index 7eecd13513..a2e167fd20 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java @@ -170,6 +170,21 @@ public class Edit { } /** + * Move the edit region by the specified amount. + * + * @param amount + * the region is shifted by this amount, and can be positive or + * negative. + * @since 4.8 + */ + public final void shift(int amount) { + beginA += amount; + endA += amount; + beginB += amount; + endB += amount; + } + + /** * Construct a new edit representing the region before cut. * * @param cut diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java index bc52473d93..d899429c3b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java @@ -220,7 +220,9 @@ public class RenameDetector { * must be allocated, and 1,000,000 file compares may need to be performed. * * @param limit - * new file limit. + * new file limit. 0 means no limit; a negative number means no + * inexact rename detection will be performed, only exact rename + * detection. */ public void setRenameLimit(int limit) { renameLimit = limit; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java index 84f0da9e88..aed76ac66b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java @@ -1299,8 +1299,13 @@ public class DirCacheCheckout { return; } + String name = f.getName(); + if (name.length() > 200) { + name = name.substring(0, 200); + } File tmpFile = File.createTempFile( - "._" + f.getName(), null, parentDir); //$NON-NLS-1$ + "._" + name, null, parentDir); //$NON-NLS-1$ + EolStreamType nonNullEolStreamType; if (checkoutMetadata.eolStreamType != null) { nonNullEolStreamType = checkoutMetadata.eolStreamType; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java index 94c8e437c3..ddc6addbc3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java @@ -60,6 +60,8 @@ import java.util.Set; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.gitrepo.RepoProject.CopyFile; +import org.eclipse.jgit.gitrepo.RepoProject.LinkFile; +import org.eclipse.jgit.gitrepo.RepoProject.ReferenceFile; import org.eclipse.jgit.gitrepo.internal.RepoText; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; @@ -209,6 +211,15 @@ public class ManifestParser extends DefaultHandler { currentProject.getPath(), attributes.getValue("src"), //$NON-NLS-1$ attributes.getValue("dest"))); //$NON-NLS-1$ + } else if ("linkfile".equals(qName)) { //$NON-NLS-1$ + if (currentProject == null) { + throw new SAXException(RepoText.get().invalidManifest); + } + currentProject.addLinkFile(new LinkFile( + rootRepo, + currentProject.getPath(), + attributes.getValue("src"), //$NON-NLS-1$ + attributes.getValue("dest"))); //$NON-NLS-1$ } else if ("include".equals(qName)) { //$NON-NLS-1$ String name = attributes.getValue("name"); //$NON-NLS-1$ if (includedReader != null) { @@ -359,19 +370,25 @@ public class ManifestParser extends DefaultHandler { else last = p; } - removeNestedCopyfiles(); + removeNestedCopyAndLinkfiles(); } - /** Remove copyfiles that sit in a subdirectory of any other project. */ - void removeNestedCopyfiles() { + private void removeNestedCopyAndLinkfiles() { for (RepoProject proj : filteredProjects) { List<CopyFile> copyfiles = new ArrayList<>(proj.getCopyFiles()); proj.clearCopyFiles(); for (CopyFile copyfile : copyfiles) { - if (!isNestedCopyfile(copyfile)) { + if (!isNestedReferencefile(copyfile)) { proj.addCopyFile(copyfile); } } + List<LinkFile> linkfiles = new ArrayList<>(proj.getLinkFiles()); + proj.clearLinkFiles(); + for (LinkFile linkfile : linkfiles) { + if (!isNestedReferencefile(linkfile)) { + proj.addLinkFile(linkfile); + } + } } } @@ -393,18 +410,18 @@ public class ManifestParser extends DefaultHandler { return false; } - private boolean isNestedCopyfile(CopyFile copyfile) { - if (copyfile.dest.indexOf('/') == -1) { - // If the copyfile is at root level then it won't be nested. + private boolean isNestedReferencefile(ReferenceFile referencefile) { + if (referencefile.dest.indexOf('/') == -1) { + // If the referencefile is at root level then it won't be nested. return false; } for (RepoProject proj : filteredProjects) { - if (proj.getPath().compareTo(copyfile.dest) > 0) { + if (proj.getPath().compareTo(referencefile.dest) > 0) { // Early return as remaining projects can't be ancestor of this - // copyfile config (filteredProjects is sorted). + // referencefile config (filteredProjects is sorted). return false; } - if (proj.isAncestorOf(copyfile.dest)) { + if (proj.isAncestorOf(referencefile.dest)) { return true; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java index e105dc063b..1de8a0be2e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java @@ -49,11 +49,14 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.UnsupportedOperationException; +import java.net.URI; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.StringJoiner; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.Git; @@ -67,6 +70,7 @@ import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.gitrepo.ManifestParser.IncludedFileReader; import org.eclipse.jgit.gitrepo.RepoProject.CopyFile; +import org.eclipse.jgit.gitrepo.RepoProject.LinkFile; import org.eclipse.jgit.gitrepo.internal.RepoText; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; @@ -106,7 +110,8 @@ import org.eclipse.jgit.util.FileUtils; */ public class RepoCommand extends GitCommand<RevCommit> { private String manifestPath; - private String uri; + private String baseUri; + private URI targetUri; private String groupsParam; private String branch; private String targetBranch = Constants.HEAD; @@ -274,7 +279,25 @@ public class RepoCommand extends GitCommand<RevCommit> { * @return this command */ public RepoCommand setURI(String uri) { - this.uri = uri; + this.baseUri = uri; + return this; + } + + /** + * Set the URI of the superproject (this repository), so the .gitmodules + * file can specify the submodule URLs relative to the superproject. + * + * @param uri + * the URI of the repository holding the superproject. + * @return this command + * @since 4.8 + */ + public RepoCommand setTargetURI(String uri) { + // The repo name is interpreted as a directory, for example + // Gerrit (http://gerrit.googlesource.com/gerrit) has a + // .gitmodules referencing ../plugins/hooks, which is + // on http://gerrit.googlesource.com/plugins/hooks, + this.targetUri = URI.create(uri + "/"); //$NON-NLS-1$ return this; } @@ -452,9 +475,8 @@ public class RepoCommand extends GitCommand<RevCommit> { public RevCommit call() throws GitAPIException { try { checkCallable(); - if (uri == null || uri.length() == 0) { - throw new IllegalArgumentException( - JGitText.get().uriNotConfigured); + if (baseUri == null) { + baseUri = ""; //$NON-NLS-1$ } if (inputStream == null) { if (manifestPath == null || manifestPath.length() == 0) @@ -478,7 +500,7 @@ public class RepoCommand extends GitCommand<RevCommit> { git = new Git(repo); ManifestParser parser = new ManifestParser( - includedReader, manifestPath, branch, uri, groupsParam, repo); + includedReader, manifestPath, branch, baseUri, groupsParam, repo); try { parser.read(inputStream); for (RepoProject proj : parser.getFilteredProjects()) { @@ -486,6 +508,7 @@ public class RepoCommand extends GitCommand<RevCommit> { proj.getPath(), proj.getRevision(), proj.getCopyFiles(), + proj.getLinkFiles(), proj.getGroups(), proj.getRecommendShallow()); } @@ -550,8 +573,13 @@ public class RepoCommand extends GitCommand<RevCommit> { rec.append("\n"); //$NON-NLS-1$ attributes.append(rec.toString()); } + + URI submodUrl = URI.create(nameUri); + if (targetUri != null) { + submodUrl = relativize(targetUri, submodUrl); + } cfg.setString("submodule", path, "path", path); //$NON-NLS-1$ //$NON-NLS-2$ - cfg.setString("submodule", path, "url", nameUri); //$NON-NLS-1$ //$NON-NLS-2$ + cfg.setString("submodule", path, "url", submodUrl.toString()); //$NON-NLS-1$ //$NON-NLS-2$ // create gitlink DirCacheEntry dcEntry = new DirCacheEntry(path); @@ -568,6 +596,25 @@ public class RepoCommand extends GitCommand<RevCommit> { dcEntry.setFileMode(FileMode.REGULAR_FILE); builder.add(dcEntry); } + for (LinkFile linkfile : proj.getLinkFiles()) { + String link; + if (linkfile.dest.contains("/")) { //$NON-NLS-1$ + link = FileUtils.relativizeGitPath( + linkfile.dest.substring(0, + linkfile.dest.lastIndexOf('/')), + proj.getPath() + "/" + linkfile.src); //$NON-NLS-1$ + } else { + link = proj.getPath() + "/" + linkfile.src; //$NON-NLS-1$ + } + + objectId = inserter.insert(Constants.OBJ_BLOB, + link.getBytes( + Constants.CHARACTER_ENCODING)); + dcEntry = new DirCacheEntry(linkfile.dest); + dcEntry.setObjectId(objectId); + dcEntry.setFileMode(FileMode.SYMLINK); + builder.add(dcEntry); + } } String content = cfg.toText(); @@ -642,13 +689,20 @@ public class RepoCommand extends GitCommand<RevCommit> { } private void addSubmodule(String url, String path, String revision, - List<CopyFile> copyfiles, Set<String> groups, String recommendShallow) + List<CopyFile> copyfiles, List<LinkFile> linkfiles, + Set<String> groups, String recommendShallow) throws GitAPIException, IOException { if (repo.isBare()) { RepoProject proj = new RepoProject(url, path, revision, null, groups, recommendShallow); proj.addCopyFiles(copyfiles); + proj.addLinkFiles(linkfiles); bareProjects.add(proj); } else { + if (!linkfiles.isEmpty()) { + throw new UnsupportedOperationException( + JGitText.get().nonBareLinkFilesNotSupported); + } + SubmoduleAddCommand add = git .submoduleAdd() .setPath(path) @@ -672,6 +726,77 @@ public class RepoCommand extends GitCommand<RevCommit> { } } + /* + * Assume we are document "a/b/index.html", what should we put in a href to get to "a/" ? + * Returns the child if either base or child is not a bare path. This provides a missing feature in + * java.net.URI (see http://bugs.java.com/view_bug.do?bug_id=6226081). + */ + private static final String SLASH = "/"; //$NON-NLS-1$ + static URI relativize(URI current, URI target) { + + // We only handle bare paths for now. + if (!target.toString().equals(target.getPath())) { + return target; + } + if (!current.toString().equals(current.getPath())) { + return target; + } + + String cur = current.normalize().getPath(); + String dest = target.normalize().getPath(); + + // TODO(hanwen): maybe (absolute, relative) should throw an exception. + if (cur.startsWith(SLASH) != dest.startsWith(SLASH)) { + return target; + } + + while (cur.startsWith(SLASH)) { + cur = cur.substring(1); + } + while (dest.startsWith(SLASH)) { + dest = dest.substring(1); + } + + if (cur.indexOf('/') == -1 || dest.indexOf('/') == -1) { + // Avoid having to special-casing in the next two ifs. + String prefix = "prefix/"; //$NON-NLS-1$ + cur = prefix + cur; + dest = prefix + dest; + } + + if (!cur.endsWith(SLASH)) { + // The current file doesn't matter. + int lastSlash = cur.lastIndexOf('/'); + cur = cur.substring(0, lastSlash); + } + String destFile = ""; //$NON-NLS-1$ + if (!dest.endsWith(SLASH)) { + // We always have to provide the destination file. + int lastSlash = dest.lastIndexOf('/'); + destFile = dest.substring(lastSlash + 1, dest.length()); + dest = dest.substring(0, dest.lastIndexOf('/')); + } + + String[] cs = cur.split(SLASH); + String[] ds = dest.split(SLASH); + + int common = 0; + while (common < cs.length && common < ds.length && cs[common].equals(ds[common])) { + common++; + } + + StringJoiner j = new StringJoiner(SLASH); + for (int i = common; i < cs.length; i++) { + j.add(".."); //$NON-NLS-1$ + } + for (int i = common; i < ds.length; i++) { + j.add(ds[i]); + } + + j.add(destFile); + return URI.create(j.toString()); + } + private static String findRef(String ref, Repository repo) throws IOException { if (!ObjectId.isId(ref)) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java index 700cf11070..00cd38d69e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java @@ -70,14 +70,17 @@ public class RepoProject implements Comparable<RepoProject> { private final String remote; private final Set<String> groups; private final List<CopyFile> copyfiles; + private final List<LinkFile> linkfiles; private String recommendShallow; private String url; private String defaultRevision; /** - * The representation of a copy file configuration. + * The representation of a reference file configuration. + * + * @since 4.8 */ - public static class CopyFile { + public static class ReferenceFile { final Repository repo; final String path; final String src; @@ -93,12 +96,31 @@ public class RepoProject implements Comparable<RepoProject> { * @param dest * the destination path relative to the super project. */ - public CopyFile(Repository repo, String path, String src, String dest) { + public ReferenceFile(Repository repo, String path, String src, String dest) { this.repo = repo; this.path = path; this.src = src; this.dest = dest; } + } + + /** + * The representation of a copy file configuration. + */ + public static class CopyFile extends ReferenceFile { + /** + * @param repo + * the super project. + * @param path + * the path of the project containing this copyfile config. + * @param src + * the source path relative to the sub repo. + * @param dest + * the destination path relative to the super project. + */ + public CopyFile(Repository repo, String path, String src, String dest) { + super(repo, path, src, dest); + } /** * Do the copy file action. @@ -126,6 +148,27 @@ public class RepoProject implements Comparable<RepoProject> { } /** + * The representation of a link file configuration. + * + * @since 4.8 + */ + public static class LinkFile extends ReferenceFile { + /** + * @param repo + * the super project. + * @param path + * the path of the project containing this linkfile config. + * @param src + * the source path relative to the sub repo. + * @param dest + * the destination path relative to the super project. + */ + public LinkFile(Repository repo, String path, String src, String dest) { + super(repo, path, src, dest); + } + } + + /** * @param name * the relative path to the {@code remote} * @param path @@ -156,6 +199,7 @@ public class RepoProject implements Comparable<RepoProject> { this.groups = groups; this.recommendShallow = recommendShallow; copyfiles = new ArrayList<>(); + linkfiles = new ArrayList<>(); } /** @@ -250,6 +294,16 @@ public class RepoProject implements Comparable<RepoProject> { } /** + * Getter for the linkfile configurations. + * + * @return Immutable copy of {@code linkfiles} + * @since 4.8 + */ + public List<LinkFile> getLinkFiles() { + return Collections.unmodifiableList(linkfiles); + } + + /** * Get the url of the sub repo. * * @return {@code url} @@ -335,6 +389,35 @@ public class RepoProject implements Comparable<RepoProject> { this.copyfiles.clear(); } + /** + * Add a link file configuration. + * + * @param linkfile + * @since 4.8 + */ + public void addLinkFile(LinkFile linkfile) { + linkfiles.add(linkfile); + } + + /** + * Add a bunch of linkfile configurations. + * + * @param linkFiles + * @since 4.8 + */ + public void addLinkFiles(Collection<LinkFile> linkFiles) { + this.linkfiles.addAll(linkFiles); + } + + /** + * Clear all the linkfiles. + * + * @since 4.8 + */ + public void clearLinkFiles() { + this.linkfiles.clear(); + } + private String getPathWithSlash() { if (path.endsWith("/")) //$NON-NLS-1$ return path; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java index c1aca6a136..62a674924a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java @@ -42,9 +42,12 @@ */ package org.eclipse.jgit.hooks; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; +import java.io.UnsupportedEncodingException; import java.util.concurrent.Callable; import org.eclipse.jgit.api.errors.AbortedByHookException; @@ -147,12 +150,19 @@ abstract class GitHook<T> implements Callable<T> { */ protected void doRun() throws AbortedByHookException { final ByteArrayOutputStream errorByteArray = new ByteArrayOutputStream(); - final PrintStream hookErrRedirect = new PrintStream(errorByteArray); + PrintStream hookErrRedirect = null; + try { + hookErrRedirect = new PrintStream(errorByteArray, false, + UTF_8.name()); + } catch (UnsupportedEncodingException e) { + // UTF-8 is guaranteed to be available + } ProcessResult result = FS.DETECTED.runHookIfPresent(getRepository(), getHookName(), getParameters(), getOutputStream(), hookErrRedirect, getStdinArgs()); if (result.isExecutedWithError()) { - throw new AbortedByHookException(errorByteArray.toString(), + throw new AbortedByHookException( + new String(errorByteArray.toByteArray(), UTF_8), getHookName(), result.getExitCode()); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 41f3c2a488..a8dfc2d2a2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -496,6 +496,7 @@ public class JGitText extends TranslationBundle { /***/ public String noHMACsupport; /***/ public String noMergeBase; /***/ public String noMergeHeadSpecified; + /***/ public String nonBareLinkFilesNotSupported; /***/ public String noSuchRef; /***/ public String notABoolean; /***/ public String notABundle; @@ -603,6 +604,7 @@ public class JGitText extends TranslationBundle { /***/ public String renamesRejoiningModifies; /***/ public String repositoryAlreadyExists; /***/ public String repositoryConfigFileInvalid; + /***/ public String repositoryIsRequired; /***/ public String repositoryNotFound; /***/ public String repositoryState_applyMailbox; /***/ public String repositoryState_bare; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java index ef0b80c11f..6fff656e7b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java @@ -322,6 +322,7 @@ public final class DfsBlockCache { HashEntry e1 = table.get(slot); DfsBlock v = scan(e1, key, position); if (v != null) { + ctx.stats.blockCacheHit++; statHit.incrementAndGet(); return v; } @@ -334,6 +335,7 @@ public final class DfsBlockCache { if (e2 != e1) { v = scan(e2, key, position); if (v != null) { + ctx.stats.blockCacheHit++; statHit.incrementAndGet(); creditSpace(blockSize); return v; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java index d3803024a5..55f9cc2127 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java @@ -53,12 +53,12 @@ import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UN import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX; import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX; import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK; +import static org.eclipse.jgit.internal.storage.pack.PackWriter.NONE; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; -import java.util.Collections; import java.util.EnumSet; import java.util.GregorianCalendar; import java.util.HashSet; @@ -112,7 +112,8 @@ public class DfsGarbageCollector { private List<DfsPackFile> packsBefore; private List<DfsPackFile> expiredGarbagePacks; - private Set<ObjectId> allHeads; + private Set<ObjectId> allHeadsAndTags; + private Set<ObjectId> allTags; private Set<ObjectId> nonHeads; private Set<ObjectId> txnHeads; private Set<ObjectId> tagTargets; @@ -234,7 +235,7 @@ public class DfsGarbageCollector { JGitText.get().supportOnlyPackIndexVersion2); startTimeMillis = SystemReader.getInstance().getCurrentTime(); - ctx = (DfsReader) objdb.newReader(); + ctx = objdb.newReader(); try { refdb.refresh(); objdb.clearCache(); @@ -242,30 +243,36 @@ public class DfsGarbageCollector { Collection<Ref> refsBefore = getAllRefs(); readPacksBefore(); - if (packsBefore.isEmpty()) { - if (!expiredGarbagePacks.isEmpty()) { - objdb.commitPack(noPacks(), toPrune()); - } - return true; - } - - allHeads = new HashSet<>(); + Set<ObjectId> allHeads = new HashSet<>(); + allHeadsAndTags = new HashSet<>(); + allTags = new HashSet<>(); nonHeads = new HashSet<>(); txnHeads = new HashSet<>(); tagTargets = new HashSet<>(); for (Ref ref : refsBefore) { - if (ref.isSymbolic() || ref.getObjectId() == null) + if (ref.isSymbolic() || ref.getObjectId() == null) { continue; - if (isHead(ref) || isTag(ref)) + } + if (isHead(ref)) { allHeads.add(ref.getObjectId()); - else if (RefTreeNames.isRefTree(refdb, ref.getName())) + } else if (isTag(ref)) { + allTags.add(ref.getObjectId()); + } else if (RefTreeNames.isRefTree(refdb, ref.getName())) { txnHeads.add(ref.getObjectId()); - else + } else { nonHeads.add(ref.getObjectId()); - if (ref.getPeeledObjectId() != null) + } + if (ref.getPeeledObjectId() != null) { tagTargets.add(ref.getPeeledObjectId()); + } } - tagTargets.addAll(allHeads); + // Don't exclude tags that are also branch tips. + allTags.removeAll(allHeads); + allHeadsAndTags.addAll(allHeads); + allHeadsAndTags.addAll(allTags); + + // Hoist all branch tips and tags earlier in the pack file + tagTargets.addAll(allHeadsAndTags); boolean rollback = true; try { @@ -307,13 +314,12 @@ public class DfsGarbageCollector { packsBefore = new ArrayList<>(packs.length); expiredGarbagePacks = new ArrayList<>(packs.length); - long mostRecentGC = mostRecentGC(packs); long now = SystemReader.getInstance().getCurrentTime(); for (DfsPackFile p : packs) { DfsPackDescription d = p.getPackDescription(); if (d.getPackSource() != UNREACHABLE_GARBAGE) { packsBefore.add(p); - } else if (packIsExpiredGarbage(d, mostRecentGC, now)) { + } else if (packIsExpiredGarbage(d, now)) { expiredGarbagePacks.add(p); } else if (packIsCoalesceableGarbage(d, now)) { packsBefore.add(p); @@ -321,39 +327,13 @@ public class DfsGarbageCollector { } } - private static long mostRecentGC(DfsPackFile[] packs) { - long r = 0; - for (DfsPackFile p : packs) { - DfsPackDescription d = p.getPackDescription(); - if (d.getPackSource() == GC || d.getPackSource() == GC_REST) { - r = Math.max(r, d.getLastModified()); - } - } - return r; - } - - private boolean packIsExpiredGarbage(DfsPackDescription d, - long mostRecentGC, long now) { - // It should be safe to remove an UNREACHABLE_GARBAGE pack if it: - // - // (a) Predates the most recent prior run of this class. This check - // ensures the graph traversal algorithm had a chance to consider - // all objects in this pack and copied them into a GC or GC_REST - // pack if the graph contained live edges to the objects. - // - // This check is safe because of the ordering of packing; the GC - // packs are written first and then the UNREACHABLE_GARBAGE is - // constructed. Any UNREACHABLE_GARBAGE dated earlier than the GC - // was input to the prior GC's graph traversal. - // - // (b) Is older than garbagePackTtl. This check gives concurrent - // inserter threads sufficient time to identify an object is not - // in the graph and should have a new copy written, rather than - // relying on something from an UNREACHABLE_GARBAGE pack. - // - // Both (a) and (b) must be met to safely remove UNREACHABLE_GARBAGE. + private boolean packIsExpiredGarbage(DfsPackDescription d, long now) { + // Consider the garbage pack as expired when it's older than + // garbagePackTtl. This check gives concurrent inserter threads + // sufficient time to identify an object is not in the graph and should + // have a new copy written, rather than relying on something from an + // UNREACHABLE_GARBAGE pack. return d.getPackSource() == UNREACHABLE_GARBAGE - && d.getLastModified() < mostRecentGC && garbageTtlMillis > 0 && now - d.getLastModified() >= garbageTtlMillis; } @@ -448,12 +428,12 @@ public class DfsGarbageCollector { } private void packHeads(ProgressMonitor pm) throws IOException { - if (allHeads.isEmpty()) + if (allHeadsAndTags.isEmpty()) return; try (PackWriter pw = newPackWriter()) { pw.setTagTargets(tagTargets); - pw.preparePack(pm, allHeads, PackWriter.NONE); + pw.preparePack(pm, allHeadsAndTags, NONE, NONE, allTags); if (0 < pw.getObjectCount()) writePack(GC, pw, pm, estimateGcPackSize(INSERT, RECEIVE, COMPACT, GC)); @@ -467,7 +447,7 @@ public class DfsGarbageCollector { try (PackWriter pw = newPackWriter()) { for (ObjectIdSet packedObjs : newPackObj) pw.excludeObjects(packedObjs); - pw.preparePack(pm, nonHeads, allHeads); + pw.preparePack(pm, nonHeads, allHeadsAndTags); if (0 < pw.getObjectCount()) writePack(GC_REST, pw, pm, estimateGcPackSize(INSERT, RECEIVE, COMPACT, GC_REST)); @@ -481,7 +461,7 @@ public class DfsGarbageCollector { try (PackWriter pw = newPackWriter()) { for (ObjectIdSet packedObjs : newPackObj) pw.excludeObjects(packedObjs); - pw.preparePack(pm, txnHeads, PackWriter.NONE); + pw.preparePack(pm, txnHeads, NONE); if (0 < pw.getObjectCount()) writePack(GC_TXN, pw, pm, 0 /* unknown pack size */); } @@ -605,8 +585,4 @@ public class DfsGarbageCollector { DfsBlockCache.getInstance().getOrCreate(pack, null); return pack; } - - private static List<DfsPackDescription> noPacks() { - return Collections.emptyList(); - } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java index fd72756e39..e65c9fda7a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java @@ -530,7 +530,7 @@ public class DfsInserter extends ObjectInserter { } private class Reader extends ObjectReader { - private final DfsReader ctx = new DfsReader(db); + private final DfsReader ctx = db.newReader(); @Override public ObjectReader newReader() { @@ -647,7 +647,7 @@ public class DfsInserter extends ObjectInserter { @Override public ObjectStream openStream() throws IOException { - final DfsReader ctx = new DfsReader(db); + final DfsReader ctx = db.newReader(); if (srcPack != packKey) { try { // Post DfsInserter.flush() use the normal code path. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java index b1cb72dec9..32ee6c288e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java @@ -170,7 +170,7 @@ public abstract class DfsObjDatabase extends ObjectDatabase { } @Override - public ObjectReader newReader() { + public DfsReader newReader() { return new DfsReader(this); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java index 0a29ac515f..f7c87a4e79 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java @@ -201,7 +201,7 @@ public class DfsPackCompactor { pm = NullProgressMonitor.INSTANCE; DfsObjDatabase objdb = repo.getObjectDatabase(); - try (DfsReader ctx = (DfsReader) objdb.newReader()) { + try (DfsReader ctx = objdb.newReader()) { PackConfig pc = new PackConfig(repo); pc.setIndexVersion(2); pc.setDeltaCompress(false); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java index f15d427f8d..ae2e7e4127 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java @@ -251,6 +251,8 @@ public final class DfsPackFile { PackIndex idx; try { + ctx.stats.readIdx++; + long start = System.nanoTime(); ReadableChannel rc = ctx.db.openFile(packDesc, INDEX); try { InputStream in = Channels.newInputStream(rc); @@ -260,10 +262,11 @@ public final class DfsPackFile { bs = (wantSize / bs) * bs; else if (bs <= 0) bs = wantSize; - in = new BufferedInputStream(in, bs); - idx = PackIndex.read(in); + idx = PackIndex.read(new BufferedInputStream(in, bs)); + ctx.stats.readIdxBytes += rc.position(); } finally { rc.close(); + ctx.stats.readIdxMicros += elapsedMicros(start); } } catch (EOFException e) { invalid = true; @@ -286,6 +289,10 @@ public final class DfsPackFile { } } + private static long elapsedMicros(long start) { + return (System.nanoTime() - start) / 1000L; + } + final boolean isGarbage() { return packDesc.getPackSource() == UNREACHABLE_GARBAGE; } @@ -314,6 +321,8 @@ public final class DfsPackFile { long size; PackBitmapIndex idx; try { + ctx.stats.readBitmap++; + long start = System.nanoTime(); ReadableChannel rc = ctx.db.openFile(packDesc, BITMAP_INDEX); try { InputStream in = Channels.newInputStream(rc); @@ -329,6 +338,8 @@ public final class DfsPackFile { } finally { size = rc.position(); rc.close(); + ctx.stats.readIdxBytes += size; + ctx.stats.readIdxMicros += elapsedMicros(start); } } catch (EOFException e) { IOException e2 = new IOException(MessageFormat.format( @@ -777,6 +788,8 @@ public final class DfsPackFile { if (invalid) throw new PackInvalidException(getPackName()); + ctx.stats.readBlock++; + long start = System.nanoTime(); ReadableChannel rc = ctx.db.openFile(packDesc, PACK); try { int size = blockSize(rc); @@ -803,6 +816,7 @@ public final class DfsPackFile { byte[] buf = new byte[size]; rc.position(pos); int cnt = read(rc, ByteBuffer.wrap(buf, 0, size)); + ctx.stats.readBlockBytes += cnt; if (cnt != size) { if (0 <= len) { throw new EOFException(MessageFormat.format( @@ -824,10 +838,10 @@ public final class DfsPackFile { length = len = rc.size(); } - DfsBlock v = new DfsBlock(key, pos, buf); - return v; + return new DfsBlock(key, pos, buf); } finally { rc.close(); + ctx.stats.readBlockMicros += elapsedMicros(start); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java index 755b163127..d611469afc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java @@ -95,7 +95,7 @@ import org.eclipse.jgit.util.BlockList; * See the base {@link ObjectReader} documentation for details. Notably, a * reader is not thread safe. */ -public final class DfsReader extends ObjectReader implements ObjectReuseAsIs { +public class DfsReader extends ObjectReader implements ObjectReuseAsIs { private static final int MAX_RESOLVE_MATCHES = 256; /** Temporary buffer large enough for at least one raw object id. */ @@ -104,17 +104,21 @@ public final class DfsReader extends ObjectReader implements ObjectReuseAsIs { /** Database this reader loads objects from. */ final DfsObjDatabase db; - private Inflater inf; + final DfsReaderIoStats.Accumulator stats = new DfsReaderIoStats.Accumulator(); + private Inflater inf; private DfsBlock block; - private DeltaBaseCache baseCache; - private DfsPackFile last; - private boolean avoidUnreachable; - DfsReader(DfsObjDatabase db) { + /** + * Initialize a new DfsReader + * + * @param db + * parent DfsObjDatabase. + */ + protected DfsReader(DfsObjDatabase db) { this.db = db; this.streamFileThreshold = db.getReaderOptions().getStreamFileThreshold(); } @@ -131,7 +135,7 @@ public final class DfsReader extends ObjectReader implements ObjectReuseAsIs { @Override public ObjectReader newReader() { - return new DfsReader(db); + return db.newReader(); } @Override @@ -170,6 +174,7 @@ public final class DfsReader extends ObjectReader implements ObjectReuseAsIs { PackList packList = db.getPackList(); resolveImpl(packList, id, matches); if (matches.size() < MAX_RESOLVE_MATCHES && packList.dirty()) { + stats.scanPacks++; resolveImpl(db.scanPacks(packList), id, matches); } return matches; @@ -198,6 +203,7 @@ public final class DfsReader extends ObjectReader implements ObjectReuseAsIs { if (hasImpl(packList, objectId)) { return true; } else if (packList.dirty()) { + stats.scanPacks++; return hasImpl(db.scanPacks(packList), objectId); } return false; @@ -234,6 +240,7 @@ public final class DfsReader extends ObjectReader implements ObjectReuseAsIs { return checkType(ldr, objectId, typeHint); } if (packList.dirty()) { + stats.scanPacks++; ldr = openImpl(db.scanPacks(packList), objectId); if (ldr != null) { return checkType(ldr, objectId, typeHint); @@ -316,6 +323,7 @@ public final class DfsReader extends ObjectReader implements ObjectReuseAsIs { List<FoundObject<T>> r = new ArrayList<>(); findAllImpl(packList, pending, r); if (!pending.isEmpty() && packList.dirty()) { + stats.scanPacks++; findAllImpl(db.scanPacks(packList), pending, r); } for (T t : pending) { @@ -452,7 +460,6 @@ public final class DfsReader extends ObjectReader implements ObjectReuseAsIs { final IOException findAllError = error; return new AsyncObjectSizeQueue<T>() { private FoundObject<T> cur; - private long sz; @Override @@ -718,9 +725,10 @@ public final class DfsReader extends ObjectReader implements ObjectReuseAsIs { for (int dstoff = 0;;) { int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff); dstoff += n; - if (inf.finished() || (headerOnly && dstoff == dstbuf.length)) + if (inf.finished() || (headerOnly && dstoff == dstbuf.length)) { + stats.inflatedBytes += dstoff; return dstoff; - if (inf.needsInput()) { + } else if (inf.needsInput()) { pin(pack, position); position += block.setInput(position, inf); } else if (n == 0) @@ -764,6 +772,11 @@ public final class DfsReader extends ObjectReader implements ObjectReuseAsIs { block = null; } + /** @return IO statistics accumulated by this reader. */ + public DfsReaderIoStats getIoStats() { + return new DfsReaderIoStats(stats); + } + /** Release the current window cursor. */ @Override public void close() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java new file mode 100644 index 0000000000..9a174c81d0 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2017, 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.internal.storage.dfs; + +/** IO statistics for a {@link DfsReader}. */ +public class DfsReaderIoStats { + /** POJO to accumulate IO statistics. */ + public static class Accumulator { + /** Number of times the reader explicitly called scanPacks. */ + long scanPacks; + + /** Total number of complete pack indexes read into memory. */ + long readIdx; + + /** Total number of complete bitmap indexes read into memory. */ + long readBitmap; + + /** Total number of bytes read from indexes. */ + long readIdxBytes; + + /** Total microseconds spent reading pack or bitmap indexes. */ + long readIdxMicros; + + /** Total number of block cache hits. */ + long blockCacheHit; + + /** Total number of discrete blocks read from pack file(s). */ + long readBlock; + + /** Total number of compressed bytes read as block sized units. */ + long readBlockBytes; + + /** Total microseconds spent reading {@link #readBlock} blocks. */ + long readBlockMicros; + + /** Total number of bytes decompressed. */ + long inflatedBytes; + + Accumulator() { + } + } + + private final Accumulator stats; + + DfsReaderIoStats(Accumulator stats) { + this.stats = stats; + } + + /** @return number of times the reader explicitly called scanPacks. */ + public long getScanPacks() { + return stats.scanPacks; + } + + /** @return total number of complete pack indexes read into memory. */ + public long getReadPackIndexCount() { + return stats.readIdx; + } + + /** @return total number of complete bitmap indexes read into memory. */ + public long getReadBitmapIndexCount() { + return stats.readBitmap; + } + + /** @return total number of bytes read from indexes. */ + public long getReadIndexBytes() { + return stats.readIdxBytes; + } + + /** @return total microseconds spent reading pack or bitmap indexes. */ + public long getReadIndexMicros() { + return stats.readIdxMicros; + } + + /** @return total number of block cache hits. */ + public long getBlockCacheHits() { + return stats.blockCacheHit; + } + + /** @return total number of discrete blocks read from pack file(s). */ + public long getReadBlocksCount() { + return stats.readBlock; + } + + /** @return total number of compressed bytes read as block sized units. */ + public long getReadBlocksBytes() { + return stats.readBlockBytes; + } + + /** @return total microseconds spent reading blocks. */ + public long getReadBlocksMicros() { + return stats.readBlockMicros; + } + + /** @return total number of bytes decompressed. */ + public long getInflatedBytes() { + return stats.inflatedBytes; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java index 6d40a7505e..73a93e6575 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java @@ -99,7 +99,7 @@ final class LargePackedWholeObject extends ObjectLoader { @Override public ObjectStream openStream() throws MissingObjectException, IOException { - DfsReader ctx = new DfsReader(db); + DfsReader ctx = db.newReader(); InputStream in; try { in = new PackInputStream(pack, objectOffset + headerLength, ctx); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java index d47b304688..ae8260a997 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java @@ -47,8 +47,10 @@ package org.eclipse.jgit.internal.storage.file; import java.io.File; import java.io.IOException; import java.util.Collection; +import java.util.HashSet; import java.util.Set; +import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle; import org.eclipse.jgit.internal.storage.pack.ObjectToPack; import org.eclipse.jgit.internal.storage.pack.PackWriter; import org.eclipse.jgit.lib.AbbreviatedObjectId; @@ -160,43 +162,70 @@ class CachedObjectDirectory extends FileObjectDatabase { return alts; } + private Set<AlternateHandle.Id> skipMe(Set<AlternateHandle.Id> skips) { + Set<AlternateHandle.Id> withMe = new HashSet<>(); + if (skips != null) { + withMe.addAll(skips); + } + withMe.add(getAlternateId()); + return withMe; + } + @Override void resolve(Set<ObjectId> matches, AbbreviatedObjectId id) throws IOException { - // In theory we could accelerate the loose object scan using our - // unpackedObjects map, but its not worth the huge code complexity. - // Scanning a single loose directory is fast enough, and this is - // unlikely to be called anyway. - // wrapped.resolve(matches, id); } @Override public boolean has(final AnyObjectId objectId) throws IOException { - if (unpackedObjects.contains(objectId)) + return has(objectId, null); + } + + private boolean has(final AnyObjectId objectId, Set<AlternateHandle.Id> skips) + throws IOException { + if (unpackedObjects.contains(objectId)) { return true; - if (wrapped.hasPackedObject(objectId)) + } + if (wrapped.hasPackedObject(objectId)) { return true; + } + skips = skipMe(skips); for (CachedObjectDirectory alt : myAlternates()) { - if (alt.has(objectId)) - return true; + if (!skips.contains(alt.getAlternateId())) { + if (alt.has(objectId, skips)) { + return true; + } + } } return false; } @Override - ObjectLoader openObject(final WindowCursor curs, - final AnyObjectId objectId) throws IOException { + ObjectLoader openObject(final WindowCursor curs, final AnyObjectId objectId) + throws IOException { + return openObject(curs, objectId, null); + } + + private ObjectLoader openObject(final WindowCursor curs, + final AnyObjectId objectId, Set<AlternateHandle.Id> skips) + throws IOException { ObjectLoader ldr = openLooseObject(curs, objectId); - if (ldr != null) + if (ldr != null) { return ldr; + } ldr = wrapped.openPackedObject(curs, objectId); - if (ldr != null) + if (ldr != null) { return ldr; + } + skips = skipMe(skips); for (CachedObjectDirectory alt : myAlternates()) { - ldr = alt.openObject(curs, objectId); - if (ldr != null) - return ldr; + if (!skips.contains(alt.getAlternateId())) { + ldr = alt.openObject(curs, objectId, skips); + if (ldr != null) { + return ldr; + } + } } return null; } @@ -260,4 +289,8 @@ class CachedObjectDirectory extends FileObjectDatabase { super(id); } } + + private AlternateHandle.Id getAlternateId() { + return wrapped.getAlternateId(); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java index ef9aa37d06..6a674aa658 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java @@ -490,10 +490,28 @@ public class FileRepository extends Repository { */ @Override public Set<ObjectId> getAdditionalHaves() { + return getAdditionalHaves(null); + } + + /** + * Objects known to exist but not expressed by {@link #getAllRefs()}. + * <p> + * When a repository borrows objects from another repository, it can + * advertise that it safely has that other repository's references, without + * exposing any other details about the other repository. This may help + * a client trying to push changes avoid pushing more than it needs to. + * + * @param skips + * Set of AlternateHandle Ids already seen + * + * @return unmodifiable collection of other known objects. + */ + private Set<ObjectId> getAdditionalHaves(Set<AlternateHandle.Id> skips) { HashSet<ObjectId> r = new HashSet<>(); + skips = objectDatabase.addMe(skips); for (AlternateHandle d : objectDatabase.myAlternates()) { - if (d instanceof AlternateRepository) { - Repository repo; + if (d instanceof AlternateRepository && !skips.contains(d.getId())) { + FileRepository repo; repo = ((AlternateRepository) d).repository; for (Ref ref : repo.getAllRefs().values()) { @@ -502,7 +520,7 @@ public class FileRepository extends Repository { if (ref.getPeeledObjectId() != null) r.add(ref.getPeeledObjectId()); } - r.addAll(repo.getAdditionalHaves()); + r.addAll(repo.getAdditionalHaves(skips)); } } return r; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java index a4a2baa55b..7ff209fb81 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java @@ -80,7 +80,6 @@ import java.util.Set; import java.util.TreeMap; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -107,6 +106,7 @@ import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; +import org.eclipse.jgit.lib.internal.WorkQueue; import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.ReflogEntry; import org.eclipse.jgit.lib.ReflogReader; @@ -151,7 +151,19 @@ public class GC { private static final int DEFAULT_AUTOLIMIT = 6700; - private static ExecutorService executor = Executors.newFixedThreadPool(1); + private static volatile ExecutorService executor; + + /** + * Set the executor for running auto-gc in the background. If no executor is + * set JGit's own WorkQueue will be used. + * + * @param e + * the executor to be used for running auto-gc + * @since 4.8 + */ + public static void setExecutor(ExecutorService e) { + executor = e; + } private final FileRepository repo; @@ -265,10 +277,14 @@ public class GC { return Collections.emptyList(); }; // TODO(ms): in 5.0 change signature and return the Future - executor.submit(gcTask); + executor().submit(gcTask); return Collections.emptyList(); } + private ExecutorService executor() { + return (executor != null) ? executor : WorkQueue.getExecutor(); + } + private Collection<PackFile> doGc() throws IOException, ParseException { if (automatic && !needGc()) { return Collections.emptyList(); @@ -790,7 +806,9 @@ public class GC { long time = System.currentTimeMillis(); Collection<Ref> refsBefore = getAllRefs(); + Set<ObjectId> allHeadsAndTags = new HashSet<>(); Set<ObjectId> allHeads = new HashSet<>(); + Set<ObjectId> allTags = new HashSet<>(); Set<ObjectId> nonHeads = new HashSet<>(); Set<ObjectId> txnHeads = new HashSet<>(); Set<ObjectId> tagTargets = new HashSet<>(); @@ -800,16 +818,21 @@ public class GC { for (Ref ref : refsBefore) { checkCancelled(); nonHeads.addAll(listRefLogObjects(ref, 0)); - if (ref.isSymbolic() || ref.getObjectId() == null) + if (ref.isSymbolic() || ref.getObjectId() == null) { continue; - if (isHead(ref) || isTag(ref)) + } + if (isHead(ref)) { allHeads.add(ref.getObjectId()); - else if (RefTreeNames.isRefTree(refdb, ref.getName())) + } else if (isTag(ref)) { + allTags.add(ref.getObjectId()); + } else if (RefTreeNames.isRefTree(refdb, ref.getName())) { txnHeads.add(ref.getObjectId()); - else + } else { nonHeads.add(ref.getObjectId()); - if (ref.getPeeledObjectId() != null) + } + if (ref.getPeeledObjectId() != null) { tagTargets.add(ref.getPeeledObjectId()); + } } List<ObjectIdSet> excluded = new LinkedList<>(); @@ -819,13 +842,19 @@ public class GC { excluded.add(f.getIndex()); } - tagTargets.addAll(allHeads); + // Don't exclude tags that are also branch tips + allTags.removeAll(allHeads); + allHeadsAndTags.addAll(allHeads); + allHeadsAndTags.addAll(allTags); + + // Hoist all branch tips and tags earlier in the pack file + tagTargets.addAll(allHeadsAndTags); nonHeads.addAll(indexObjects); List<PackFile> ret = new ArrayList<>(2); PackFile heads = null; - if (!allHeads.isEmpty()) { - heads = writePack(allHeads, Collections.<ObjectId> emptySet(), + if (!allHeadsAndTags.isEmpty()) { + heads = writePack(allHeadsAndTags, PackWriter.NONE, allTags, tagTargets, excluded); if (heads != null) { ret.add(heads); @@ -833,12 +862,14 @@ public class GC { } } if (!nonHeads.isEmpty()) { - PackFile rest = writePack(nonHeads, allHeads, tagTargets, excluded); + PackFile rest = writePack(nonHeads, allHeadsAndTags, PackWriter.NONE, + tagTargets, excluded); if (rest != null) ret.add(rest); } if (!txnHeads.isEmpty()) { - PackFile txn = writePack(txnHeads, PackWriter.NONE, null, excluded); + PackFile txn = writePack(txnHeads, PackWriter.NONE, PackWriter.NONE, + null, excluded); if (txn != null) ret.add(txn); } @@ -1074,8 +1105,9 @@ public class GC { } private PackFile writePack(@NonNull Set<? extends ObjectId> want, - @NonNull Set<? extends ObjectId> have, Set<ObjectId> tagTargets, - List<ObjectIdSet> excludeObjects) throws IOException { + @NonNull Set<? extends ObjectId> have, @NonNull Set<ObjectId> tags, + Set<ObjectId> tagTargets, List<ObjectIdSet> excludeObjects) + throws IOException { checkCancelled(); File tmpPack = null; Map<PackExt, File> tmpExts = new TreeMap<>( @@ -1101,12 +1133,13 @@ public class GC { // prepare the PackWriter pw.setDeltaBaseAsOffset(true); pw.setReuseDeltaCommits(false); - if (tagTargets != null) + if (tagTargets != null) { pw.setTagTargets(tagTargets); + } if (excludeObjects != null) for (ObjectIdSet idx : excludeObjects) pw.excludeObjects(idx); - pw.preparePack(pm, want, have); + pw.preparePack(pm, want, have, PackWriter.NONE, tags); if (pw.getObjectCount() == 0) return null; checkCancelled(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java index 00e39533b1..d953b87c4b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java @@ -64,6 +64,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; @@ -117,6 +118,8 @@ public class ObjectDirectory extends FileObjectDatabase { /** Maximum number of candidates offered as resolutions of abbreviation. */ private static final int RESOLVE_ABBREV_LIMIT = 256; + private final AlternateHandle handle = new AlternateHandle(this); + private final Config config; private final File objects; @@ -294,26 +297,38 @@ public class ObjectDirectory extends FileObjectDatabase { @Override public boolean has(AnyObjectId objectId) { return unpackedObjectCache.isUnpacked(objectId) - || hasPackedInSelfOrAlternate(objectId) - || hasLooseInSelfOrAlternate(objectId); + || hasPackedInSelfOrAlternate(objectId, null) + || hasLooseInSelfOrAlternate(objectId, null); } - private boolean hasPackedInSelfOrAlternate(AnyObjectId objectId) { - if (hasPackedObject(objectId)) + private boolean hasPackedInSelfOrAlternate(AnyObjectId objectId, + Set<AlternateHandle.Id> skips) { + if (hasPackedObject(objectId)) { return true; + } + skips = addMe(skips); for (AlternateHandle alt : myAlternates()) { - if (alt.db.hasPackedInSelfOrAlternate(objectId)) - return true; + if (!skips.contains(alt.getId())) { + if (alt.db.hasPackedInSelfOrAlternate(objectId, skips)) { + return true; + } + } } return false; } - private boolean hasLooseInSelfOrAlternate(AnyObjectId objectId) { - if (fileFor(objectId).exists()) + private boolean hasLooseInSelfOrAlternate(AnyObjectId objectId, + Set<AlternateHandle.Id> skips) { + if (fileFor(objectId).exists()) { return true; + } + skips = addMe(skips); for (AlternateHandle alt : myAlternates()) { - if (alt.db.hasLooseInSelfOrAlternate(objectId)) - return true; + if (!skips.contains(alt.getId())) { + if (alt.db.hasLooseInSelfOrAlternate(objectId, skips)) { + return true; + } + } } return false; } @@ -340,6 +355,12 @@ public class ObjectDirectory extends FileObjectDatabase { @Override void resolve(Set<ObjectId> matches, AbbreviatedObjectId id) throws IOException { + resolve(matches, id, null); + } + + private void resolve(Set<ObjectId> matches, AbbreviatedObjectId id, + Set<AlternateHandle.Id> skips) + throws IOException { // Go through the packs once. If we didn't find any resolutions // scan for new packs and check once more. int oldSize = matches.size(); @@ -376,10 +397,14 @@ public class ObjectDirectory extends FileObjectDatabase { } } + skips = addMe(skips); for (AlternateHandle alt : myAlternates()) { - alt.db.resolve(matches, id); - if (matches.size() > RESOLVE_ABBREV_LIMIT) - return; + if (!skips.contains(alt.getId())) { + alt.db.resolve(matches, id, skips); + if (matches.size() > RESOLVE_ABBREV_LIMIT) { + return; + } + } } } @@ -388,37 +413,50 @@ public class ObjectDirectory extends FileObjectDatabase { throws IOException { if (unpackedObjectCache.isUnpacked(objectId)) { ObjectLoader ldr = openLooseObject(curs, objectId); - if (ldr != null) + if (ldr != null) { return ldr; + } } - ObjectLoader ldr = openPackedFromSelfOrAlternate(curs, objectId); - if (ldr != null) + ObjectLoader ldr = openPackedFromSelfOrAlternate(curs, objectId, null); + if (ldr != null) { return ldr; - return openLooseFromSelfOrAlternate(curs, objectId); + } + return openLooseFromSelfOrAlternate(curs, objectId, null); } private ObjectLoader openPackedFromSelfOrAlternate(WindowCursor curs, - AnyObjectId objectId) { + AnyObjectId objectId, Set<AlternateHandle.Id> skips) { ObjectLoader ldr = openPackedObject(curs, objectId); - if (ldr != null) + if (ldr != null) { return ldr; + } + skips = addMe(skips); for (AlternateHandle alt : myAlternates()) { - ldr = alt.db.openPackedFromSelfOrAlternate(curs, objectId); - if (ldr != null) - return ldr; + if (!skips.contains(alt.getId())) { + ldr = alt.db.openPackedFromSelfOrAlternate(curs, objectId, skips); + if (ldr != null) { + return ldr; + } + } } return null; } private ObjectLoader openLooseFromSelfOrAlternate(WindowCursor curs, - AnyObjectId objectId) throws IOException { + AnyObjectId objectId, Set<AlternateHandle.Id> skips) + throws IOException { ObjectLoader ldr = openLooseObject(curs, objectId); - if (ldr != null) + if (ldr != null) { return ldr; + } + skips = addMe(skips); for (AlternateHandle alt : myAlternates()) { - ldr = alt.db.openLooseFromSelfOrAlternate(curs, objectId); - if (ldr != null) - return ldr; + if (!skips.contains(alt.getId())) { + ldr = alt.db.openLooseFromSelfOrAlternate(curs, objectId, skips); + if (ldr != null) { + return ldr; + } + } } return null; } @@ -469,37 +507,49 @@ public class ObjectDirectory extends FileObjectDatabase { throws IOException { if (unpackedObjectCache.isUnpacked(id)) { long len = getLooseObjectSize(curs, id); - if (0 <= len) + if (0 <= len) { return len; + } } - long len = getPackedSizeFromSelfOrAlternate(curs, id); - if (0 <= len) + long len = getPackedSizeFromSelfOrAlternate(curs, id, null); + if (0 <= len) { return len; - return getLooseSizeFromSelfOrAlternate(curs, id); + } + return getLooseSizeFromSelfOrAlternate(curs, id, null); } private long getPackedSizeFromSelfOrAlternate(WindowCursor curs, - AnyObjectId id) { + AnyObjectId id, Set<AlternateHandle.Id> skips) { long len = getPackedObjectSize(curs, id); - if (0 <= len) + if (0 <= len) { return len; + } + skips = addMe(skips); for (AlternateHandle alt : myAlternates()) { - len = alt.db.getPackedSizeFromSelfOrAlternate(curs, id); - if (0 <= len) - return len; + if (!skips.contains(alt.getId())) { + len = alt.db.getPackedSizeFromSelfOrAlternate(curs, id, skips); + if (0 <= len) { + return len; + } + } } return -1; } private long getLooseSizeFromSelfOrAlternate(WindowCursor curs, - AnyObjectId id) throws IOException { + AnyObjectId id, Set<AlternateHandle.Id> skips) throws IOException { long len = getLooseObjectSize(curs, id); - if (0 <= len) + if (0 <= len) { return len; + } + skips = addMe(skips); for (AlternateHandle alt : myAlternates()) { - len = alt.db.getLooseSizeFromSelfOrAlternate(curs, id); - if (0 <= len) - return len; + if (!skips.contains(alt.getId())) { + len = alt.db.getLooseSizeFromSelfOrAlternate(curs, id, skips); + if (0 <= len) { + return len; + } + } } return -1; } @@ -546,7 +596,12 @@ public class ObjectDirectory extends FileObjectDatabase { @Override void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, - WindowCursor curs) throws IOException { + WindowCursor curs) throws IOException { + selectObjectRepresentation(packer, otp, curs, null); + } + + private void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, + WindowCursor curs, Set<AlternateHandle.Id> skips) throws IOException { PackList pList = packList.get(); SEARCH: for (;;) { for (final PackFile p : pList.packs) { @@ -567,8 +622,12 @@ public class ObjectDirectory extends FileObjectDatabase { break SEARCH; } - for (AlternateHandle h : myAlternates()) - h.db.selectObjectRepresentation(packer, otp, curs); + skips = addMe(skips); + for (AlternateHandle h : myAlternates()) { + if (!skips.contains(h.getId())) { + h.db.selectObjectRepresentation(packer, otp, curs, skips); + } + } } private void handlePackError(IOException e, PackFile p) { @@ -930,6 +989,14 @@ public class ObjectDirectory extends FileObjectDatabase { return alt; } + Set<AlternateHandle.Id> addMe(Set<AlternateHandle.Id> skips) { + if (skips == null) { + skips = new HashSet<>(); + } + skips.add(handle.getId()); + return skips; + } + private AlternateHandle[] loadAlternates() throws IOException { final List<AlternateHandle> l = new ArrayList<>(4); final BufferedReader br = open(alternatesFile); @@ -996,6 +1063,38 @@ public class ObjectDirectory extends FileObjectDatabase { } static class AlternateHandle { + static class Id { + String alternateId; + + public Id(File object) { + try { + this.alternateId = object.getCanonicalPath(); + } catch (Exception e) { + alternateId = null; + } + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o == null || !(o instanceof Id)) { + return false; + } + Id aId = (Id) o; + return Objects.equals(alternateId, aId.alternateId); + } + + @Override + public int hashCode() { + if (alternateId == null) { + return 1; + } + return alternateId.hashCode(); + } + } + final ObjectDirectory db; AlternateHandle(ObjectDirectory db) { @@ -1005,6 +1104,10 @@ public class ObjectDirectory extends FileObjectDatabase { void close() { db.close(); } + + public Id getId(){ + return db.getAlternateId(); + } } static class AlternateRepository extends AlternateHandle { @@ -1029,4 +1132,8 @@ public class ObjectDirectory extends FileObjectDatabase { CachedObjectDirectory newCachedFileObjectDatabase() { return new CachedObjectDirectory(this); } + + AlternateHandle.Id getAlternateId() { + return new AlternateHandle.Id(objects); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java index 93dbee3e79..7271560e3c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java @@ -233,7 +233,9 @@ public class PackWriter implements AutoCloseable { private List<CachedPack> cachedPacks = new ArrayList<>(2); - private Set<ObjectId> tagTargets = Collections.emptySet(); + private Set<ObjectId> tagTargets = NONE; + + private Set<? extends ObjectId> excludeFromBitmapSelection = NONE; private ObjectIdSet[] excludeInPacks; @@ -712,8 +714,7 @@ public class PackWriter implements AutoCloseable { public void preparePack(ProgressMonitor countingMonitor, @NonNull Set<? extends ObjectId> want, @NonNull Set<? extends ObjectId> have) throws IOException { - preparePack(countingMonitor, - want, have, Collections.<ObjectId> emptySet()); + preparePack(countingMonitor, want, have, NONE, NONE); } /** @@ -721,9 +722,9 @@ public class PackWriter implements AutoCloseable { * <p> * Like {@link #preparePack(ProgressMonitor, Set, Set)} but also allows * specifying commits that should not be walked past ("shallow" commits). - * The caller is responsible for filtering out commits that should not - * be shallow any more ("unshallow" commits as in {@link #setShallowPack}) - * from the shallow set. + * The caller is responsible for filtering out commits that should not be + * shallow any more ("unshallow" commits as in {@link #setShallowPack}) from + * the shallow set. * * @param countingMonitor * progress during object enumeration. @@ -731,27 +732,67 @@ public class PackWriter implements AutoCloseable { * objects of interest, ancestors of which will be included in * the pack. Must not be {@code null}. * @param have - * objects whose ancestors (up to and including - * {@code shallow} commits) do not need to be included in the - * pack because they are already available from elsewhere. - * Must not be {@code null}. + * objects whose ancestors (up to and including {@code shallow} + * commits) do not need to be included in the pack because they + * are already available from elsewhere. Must not be + * {@code null}. * @param shallow * commits indicating the boundary of the history marked with - * {@code have}. Shallow commits have parents but those - * parents are considered not to be already available. - * Parents of {@code shallow} commits and earlier generations - * will be included in the pack if requested by {@code want}. - * Must not be {@code null}. + * {@code have}. Shallow commits have parents but those parents + * are considered not to be already available. Parents of + * {@code shallow} commits and earlier generations will be + * included in the pack if requested by {@code want}. Must not be + * {@code null}. * @throws IOException - * an I/O problem occured while reading objects. + * an I/O problem occurred while reading objects. */ public void preparePack(ProgressMonitor countingMonitor, @NonNull Set<? extends ObjectId> want, @NonNull Set<? extends ObjectId> have, @NonNull Set<? extends ObjectId> shallow) throws IOException { + preparePack(countingMonitor, want, have, shallow, NONE); + } + + /** + * Prepare the list of objects to be written to the pack stream. + * <p> + * Like {@link #preparePack(ProgressMonitor, Set, Set)} but also allows + * specifying commits that should not be walked past ("shallow" commits). + * The caller is responsible for filtering out commits that should not be + * shallow any more ("unshallow" commits as in {@link #setShallowPack}) from + * the shallow set. + * + * @param countingMonitor + * progress during object enumeration. + * @param want + * objects of interest, ancestors of which will be included in + * the pack. Must not be {@code null}. + * @param have + * objects whose ancestors (up to and including {@code shallow} + * commits) do not need to be included in the pack because they + * are already available from elsewhere. Must not be + * {@code null}. + * @param shallow + * commits indicating the boundary of the history marked with + * {@code have}. Shallow commits have parents but those parents + * are considered not to be already available. Parents of + * {@code shallow} commits and earlier generations will be + * included in the pack if requested by {@code want}. Must not be + * {@code null}. + * @param noBitmaps + * collection of objects to be excluded from bitmap commit + * selection. + * @throws IOException + * an I/O problem occurred while reading objects. + */ + public void preparePack(ProgressMonitor countingMonitor, + @NonNull Set<? extends ObjectId> want, + @NonNull Set<? extends ObjectId> have, + @NonNull Set<? extends ObjectId> shallow, + @NonNull Set<? extends ObjectId> noBitmaps) throws IOException { try (ObjectWalk ow = getObjectWalk()) { ow.assumeShallow(shallow); - preparePack(countingMonitor, ow, want, have); + preparePack(countingMonitor, ow, want, have, noBitmaps); } } @@ -784,13 +825,17 @@ public class PackWriter implements AutoCloseable { * points of graph traversal). Pass {@link #NONE} if all objects * reachable from {@code want} are desired, such as when serving * a clone. + * @param noBitmaps + * collection of objects to be excluded from bitmap commit + * selection. * @throws IOException * when some I/O problem occur during reading objects. */ public void preparePack(ProgressMonitor countingMonitor, @NonNull ObjectWalk walk, @NonNull Set<? extends ObjectId> interestingObjects, - @NonNull Set<? extends ObjectId> uninterestingObjects) + @NonNull Set<? extends ObjectId> uninterestingObjects, + @NonNull Set<? extends ObjectId> noBitmaps) throws IOException { if (countingMonitor == null) countingMonitor = NullProgressMonitor.INSTANCE; @@ -798,7 +843,7 @@ public class PackWriter implements AutoCloseable { throw new IllegalArgumentException( JGitText.get().shallowPacksRequireDepthWalk); findObjectsToPack(countingMonitor, walk, interestingObjects, - uninterestingObjects); + uninterestingObjects, noBitmaps); } /** @@ -965,8 +1010,9 @@ public class PackWriter implements AutoCloseable { /** * Write the prepared pack to the supplied stream. * <p> - * Called after {@link #preparePack(ProgressMonitor, ObjectWalk, Set, Set)} - * or {@link #preparePack(ProgressMonitor, Set, Set)}. + * Called after + * {@link #preparePack(ProgressMonitor, ObjectWalk, Set, Set, Set)} or + * {@link #preparePack(ProgressMonitor, Set, Set)}. * <p> * Performs delta search if enabled and writes the pack stream. * <p> @@ -1652,12 +1698,14 @@ public class PackWriter implements AutoCloseable { private void findObjectsToPack(@NonNull ProgressMonitor countingMonitor, @NonNull ObjectWalk walker, @NonNull Set<? extends ObjectId> want, - @NonNull Set<? extends ObjectId> have) throws IOException { + @NonNull Set<? extends ObjectId> have, + @NonNull Set<? extends ObjectId> noBitmaps) throws IOException { final long countingStart = System.currentTimeMillis(); beginPhase(PackingPhase.COUNTING, countingMonitor, ProgressMonitor.UNKNOWN); stats.interestingObjects = Collections.unmodifiableSet(new HashSet<ObjectId>(want)); stats.uninterestingObjects = Collections.unmodifiableSet(new HashSet<ObjectId>(have)); + excludeFromBitmapSelection = noBitmaps; canBuildBitmaps = config.isBuildBitmaps() && !shallowPack @@ -1874,7 +1922,6 @@ public class PackWriter implements AutoCloseable { throws MissingObjectException, IncorrectObjectTypeException, IOException { BitmapBuilder haveBitmap = bitmapWalker.findObjects(have, null, true); - bitmapWalker.reset(); BitmapBuilder wantBitmap = bitmapWalker.findObjects(want, haveBitmap, false); BitmapBuilder needBitmap = wantBitmap.andNot(haveBitmap); @@ -2071,19 +2118,17 @@ public class PackWriter implements AutoCloseable { PackWriterBitmapPreparer bitmapPreparer = new PackWriterBitmapPreparer( reader, writeBitmaps, pm, stats.interestingObjects, config); - Collection<PackWriterBitmapPreparer.BitmapCommit> selectedCommits = - bitmapPreparer.selectCommits(numCommits); + Collection<PackWriterBitmapPreparer.BitmapCommit> selectedCommits = bitmapPreparer + .selectCommits(numCommits, excludeFromBitmapSelection); beginPhase(PackingPhase.BUILDING_BITMAPS, pm, selectedCommits.size()); PackWriterBitmapWalker walker = bitmapPreparer.newBitmapWalker(); AnyObjectId last = null; for (PackWriterBitmapPreparer.BitmapCommit cmit : selectedCommits) { - if (cmit.isReuseWalker()) - walker.reset(); - else + if (!cmit.isReuseWalker()) { walker = bitmapPreparer.newBitmapWalker(); - + } BitmapBuilder bitmap = walker.findObjects( Collections.singleton(cmit), null, false); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java index 07a03b4040..8bedddb935 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java @@ -141,6 +141,8 @@ class PackWriterBitmapPreparer { * * @param expectedCommitCount * count of commits in the pack + * @param excludeFromBitmapSelection + * commits that should be excluded from bitmap selection * @return commit objects for which bitmap indices should be built * @throws IncorrectObjectTypeException * if any of the processed objects is not a commit @@ -149,7 +151,8 @@ class PackWriterBitmapPreparer { * @throws MissingObjectException * if an expected object is missing */ - Collection<BitmapCommit> selectCommits(int expectedCommitCount) + Collection<BitmapCommit> selectCommits(int expectedCommitCount, + Set<? extends ObjectId> excludeFromBitmapSelection) throws IncorrectObjectTypeException, IOException, MissingObjectException { /* @@ -164,7 +167,7 @@ class PackWriterBitmapPreparer { RevWalk rw = new RevWalk(reader); rw.setRetainBody(false); CommitSelectionHelper selectionHelper = setupTipCommitBitmaps(rw, - expectedCommitCount); + expectedCommitCount, excludeFromBitmapSelection); pm.endTask(); int totCommits = selectionHelper.getCommitCount(); @@ -363,6 +366,8 @@ class PackWriterBitmapPreparer { * @param expectedCommitCount * expected count of commits. The actual count may be less due to * unreachable garbage. + * @param excludeFromBitmapSelection + * commits that should be excluded from bitmap selection * @return a {@link CommitSelectionHelper} containing bitmaps for the tip * commits * @throws IncorrectObjectTypeException @@ -373,8 +378,10 @@ class PackWriterBitmapPreparer { * if an expected object is missing */ private CommitSelectionHelper setupTipCommitBitmaps(RevWalk rw, - int expectedCommitCount) throws IncorrectObjectTypeException, - IOException, MissingObjectException { + int expectedCommitCount, + Set<? extends ObjectId> excludeFromBitmapSelection) + throws IncorrectObjectTypeException, IOException, + MissingObjectException { BitmapBuilder reuse = commitBitmapIndex.newBitmapBuilder(); List<BitmapCommit> reuseCommits = new ArrayList<>(); for (PackBitmapIndexRemapper.Entry entry : bitmapRemapper) { @@ -403,7 +410,8 @@ class PackWriterBitmapPreparer { Set<RevCommit> peeledWant = new HashSet<>(want.size()); for (AnyObjectId objectId : want) { RevObject ro = rw.peel(rw.parseAny(objectId)); - if (!(ro instanceof RevCommit) || reuse.contains(ro)) { + if (!(ro instanceof RevCommit) || reuse.contains(ro) + || excludeFromBitmapSelection.contains(ro)) { continue; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java index 2ec4d568c7..a5c3b71eb2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java @@ -44,7 +44,7 @@ package org.eclipse.jgit.internal.storage.pack; import java.io.IOException; -import java.util.Set; +import java.util.Arrays; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; @@ -84,9 +84,60 @@ final class PackWriterBitmapWalker { return countOfBitmapIndexMisses; } - BitmapBuilder findObjects(Set<? extends ObjectId> start, BitmapBuilder seen, boolean ignoreMissingStart) + BitmapBuilder findObjects(Iterable<? extends ObjectId> start, BitmapBuilder seen, + boolean ignoreMissing) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + if (!ignoreMissing) { + return findObjectsWalk(start, seen, false); + } + + try { + return findObjectsWalk(start, seen, true); + } catch (MissingObjectException ignore) { + // An object reachable from one of the "start"s is missing. + // Walk from the "start"s one at a time so it can be excluded. + } + + final BitmapBuilder result = bitmapIndex.newBitmapBuilder(); + for (ObjectId obj : start) { + Bitmap bitmap = bitmapIndex.getBitmap(obj); + if (bitmap != null) { + result.or(bitmap); + } + } + + for (ObjectId obj : start) { + if (result.contains(obj)) { + continue; + } + try { + result.or(findObjectsWalk(Arrays.asList(obj), result, false)); + } catch (MissingObjectException ignore) { + // An object reachable from this "start" is missing. + // + // This can happen when the client specified a "have" line + // pointing to an object that is present but unreachable: + // "git prune" and "git fsck" only guarantee that the object + // database will continue to contain all objects reachable + // from a ref and does not guarantee connectivity for other + // objects in the object database. + // + // In this situation, skip the relevant "start" and move on + // to the next one. + // + // TODO(czhen): Make findObjectsWalk resume the walk instead + // once RevWalk and ObjectWalk support that. + } + } + return result; + } + + private BitmapBuilder findObjectsWalk(Iterable<? extends ObjectId> start, BitmapBuilder seen, + boolean ignoreMissingStart) throws MissingObjectException, IncorrectObjectTypeException, IOException { + walker.reset(); final BitmapBuilder bitmapResult = bitmapIndex.newBitmapBuilder(); for (ObjectId obj : start) { @@ -141,10 +192,6 @@ final class PackWriterBitmapWalker { return bitmapResult; } - void reset() { - walker.reset(); - } - /** * A RevFilter that adds the visited commits to {@code bitmap} as a side * effect. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java index 54c80522b8..a75293d6cb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java @@ -46,6 +46,8 @@ package org.eclipse.jgit.lib; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import org.eclipse.jgit.lib.internal.WorkQueue; + /** ProgressMonitor that batches update events. */ public abstract class BatchingProgressMonitor implements ProgressMonitor { private long delayStartTime; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java index 1047a6df99..c4923a359e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java @@ -43,6 +43,8 @@ package org.eclipse.jgit.lib; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -177,8 +179,8 @@ public class RebaseTodoFile { while (tokenCount < 3 && nextSpace < lineEnd) { switch (tokenCount) { case 0: - String actionToken = new String(buf, tokenBegin, nextSpace - - tokenBegin - 1); + String actionToken = new String(buf, tokenBegin, + nextSpace - tokenBegin - 1, UTF_8); tokenBegin = nextSpace; action = RebaseTodoLine.Action.parse(actionToken); if (action == null) @@ -186,14 +188,14 @@ public class RebaseTodoFile { break; case 1: nextSpace = RawParseUtils.next(buf, tokenBegin, ' '); - String commitToken = new String(buf, tokenBegin, nextSpace - - tokenBegin - 1); + String commitToken = new String(buf, tokenBegin, + nextSpace - tokenBegin - 1, UTF_8); tokenBegin = nextSpace; commit = AbbreviatedObjectId.fromString(commitToken); break; case 2: - return new RebaseTodoLine(action, commit, RawParseUtils.decode( - buf, tokenBegin, 1 + lineEnd)); + return new RebaseTodoLine(action, commit, + RawParseUtils.decode(buf, tokenBegin, 1 + lineEnd)); } tokenCount++; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index aa70f4209b..bd23ab988d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -1151,6 +1151,33 @@ public abstract class Repository implements AutoCloseable { } /** + * Locate a reference to a commit and immediately parse its content. + * <p> + * This method only returns successfully if the commit object exists, + * is verified to be a commit, and was parsed without error. + * + * @param id + * name of the commit object. + * @return reference to the commit object. Never null. + * @throws MissingObjectException + * the supplied commit does not exist. + * @throws IncorrectObjectTypeException + * the supplied id is not a commit or an annotated tag. + * @throws IOException + * a pack file or loose object could not be read. + * @since 4.8 + */ + public RevCommit parseCommit(AnyObjectId id) throws IncorrectObjectTypeException, + IOException, MissingObjectException { + if (id instanceof RevCommit && ((RevCommit) id).getRawBuffer() != null) { + return (RevCommit) id; + } + try (RevWalk walk = new RevWalk(this)) { + return walk.parseCommit(id); + } + } + + /** * Create a new in-core index representation and read an index from disk. * <p> * The new index will be read before it is returned to the caller. Read diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java index baa5286862..53e9fe3c53 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java @@ -55,6 +55,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.internal.storage.file.FileRepository; +import org.eclipse.jgit.lib.internal.WorkQueue; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java index 3126160c33..12f7b82343 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SubmoduleConfig.java @@ -43,6 +43,10 @@ package org.eclipse.jgit.lib; +import java.util.Locale; + +import org.eclipse.jgit.util.StringUtils; + /** * Submodule section of a Git configuration file. * @@ -75,12 +79,17 @@ public class SubmoduleConfig { @Override public String toConfigValue() { - return configValue; + return name().toLowerCase(Locale.ROOT).replace('_', '-'); } @Override public boolean matchConfigValue(String s) { - return configValue.equals(s); + if (StringUtils.isEmptyOrNull(s)) { + return false; + } + s = s.replace('-', '_'); + return name().equalsIgnoreCase(s) + || configValue.equalsIgnoreCase(s); } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java index 7675fccb69..c31c3c6939 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java @@ -44,7 +44,10 @@ package org.eclipse.jgit.lib; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.IOException; +import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Writer; @@ -56,7 +59,7 @@ public class TextProgressMonitor extends BatchingProgressMonitor { /** Initialize a new progress monitor. */ public TextProgressMonitor() { - this(new PrintWriter(System.err)); + this(new PrintWriter(new OutputStreamWriter(System.err, UTF_8))); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/WorkQueue.java index 07b87f58d2..3303f47722 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkQueue.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/WorkQueue.java @@ -41,7 +41,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.lib; +package org.eclipse.jgit.lib.internal; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -50,7 +50,7 @@ import java.util.concurrent.ThreadFactory; /** * Simple work queue to run tasks in the background */ -class WorkQueue { +public class WorkQueue { private static final ScheduledThreadPoolExecutor executor; static final Object executorKiller; @@ -94,7 +94,10 @@ class WorkQueue { }; } - static ScheduledThreadPoolExecutor getExecutor() { + /** + * @return the WorkQueue's executor + */ + public static ScheduledThreadPoolExecutor getExecutor() { return executor; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java index 656480e468..af3d5ca078 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java @@ -49,6 +49,8 @@ import java.text.MessageFormat; import java.util.HashMap; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; /** @@ -170,4 +172,20 @@ public abstract class MergeStrategy { * @return the new merge instance which implements this strategy. */ public abstract Merger newMerger(Repository db, boolean inCore); + + /** + * Create a new merge instance. + * <p> + * The merge will happen in memory, working folder will not be modified, in + * case of a non-trivial merge that requires manual resolution, the merger + * will fail. + * + * @param inserter + * inserter to write results back to. + * @param config + * repo config for reading diff algorithm settings. + * @return the new merge instance which implements this strategy. + * @since 4.8 + */ + public abstract Merger newMerger(ObjectInserter inserter, Config config); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java index bee2d03523..0c4488c984 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java @@ -47,6 +47,7 @@ package org.eclipse.jgit.merge; import java.io.IOException; import java.text.MessageFormat; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.NoMergeBaseException; import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason; @@ -70,7 +71,15 @@ import org.eclipse.jgit.treewalk.CanonicalTreeParser; * Instance of a specific {@link MergeStrategy} for a single {@link Repository}. */ public abstract class Merger { - /** The repository this merger operates on. */ + /** + * The repository this merger operates on. + * <p> + * Null if and only if the merger was constructed with {@link + * #Merger(ObjectInserter)}. Callers that want to assume the repo is not null + * (e.g. because of a previous check that the merger is not in-core) may use + * {@link #nonNullRepo()}. + */ + @Nullable protected final Repository db; /** Reader to support {@link #walk} and other object loading. */ @@ -104,20 +113,55 @@ public abstract class Merger { * the repository this merger will read and write data on. */ protected Merger(final Repository local) { + if (local == null) { + throw new NullPointerException(JGitText.get().repositoryIsRequired); + } db = local; - inserter = db.newObjectInserter(); + inserter = local.newObjectInserter(); reader = inserter.newReader(); walk = new RevWalk(reader); } /** + * Create a new in-core merge instance from an inserter. + * + * @param oi + * the inserter to write objects to. Will be closed at the + * conclusion of {@code merge}, unless {@code flush} is false. + * @since 4.8 + */ + protected Merger(ObjectInserter oi) { + db = null; + inserter = oi; + reader = oi.newReader(); + walk = new RevWalk(reader); + } + + /** * @return the repository this merger operates on. */ + @Nullable public Repository getRepository() { return db; } - /** @return an object writer to create objects in {@link #getRepository()}. */ + /** + * @return non-null repository instance + * @throws NullPointerException + * if the merger was constructed without a repository. + * @since 4.8 + */ + protected Repository nonNullRepo() { + if (db == null) { + throw new NullPointerException(JGitText.get().repositoryIsRequired); + } + return db; + } + + /** + * @return an object writer to create objects, writing objects to {@link + * #getRepository()} (if a repository was provided). + */ public ObjectInserter getObjectInserter() { return inserter; } @@ -131,7 +175,9 @@ public abstract class Merger { * * @param oi * the inserter instance to use. Must be associated with the - * repository instance returned by {@link #getRepository()}. + * repository instance returned by {@link #getRepository()} (if a + * repository was provided). Will be closed at the conclusion of + * {@code merge}, unless {@code flush} is false. */ public void setObjectInserter(ObjectInserter oi) { walk.close(); @@ -173,9 +219,9 @@ public abstract class Merger { * * @since 3.5 * @param flush - * whether to flush the underlying object inserter when finished to - * store any content-merged blobs and virtual merged bases; if - * false, callers are responsible for flushing. + * whether to flush and close the underlying object inserter when + * finished to store any content-merged blobs and virtual merged + * bases; if false, callers are responsible for flushing. * @param tips * source trees to be combined together. The merge base is not * included in this set. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java index f8e1998ed7..1375cd3ea2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java @@ -61,7 +61,9 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.NoMergeBaseException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; @@ -111,6 +113,17 @@ public class RecursiveMerger extends ResolveMerger { } /** + * Normal recursive merge, implies inCore. + * + * @param inserter + * @param config + * @since 4.8 + */ + protected RecursiveMerger(ObjectInserter inserter, Config config) { + super(inserter, config); + } + + /** * Get a single base commit for two given commits. If the two source commits * have more than one base commit recursively merge the base commits * together until you end up with a single base commit. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java index 90107be2ed..86003e9243 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java @@ -44,6 +44,9 @@ */ package org.eclipse.jgit.merge; +import static org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm.HISTOGRAM; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_ALGORITHM; import static org.eclipse.jgit.lib.Constants.CHARACTER_ENCODING; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; @@ -79,9 +82,10 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.IndexWriteException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.NoWorkTreeException; -import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevTree; @@ -266,18 +270,25 @@ public class ResolveMerger extends ThreeWayMerger { */ protected MergeAlgorithm mergeAlgorithm; + private static MergeAlgorithm getMergeAlgorithm(Config config) { + SupportedAlgorithm diffAlg = config.getEnum( + CONFIG_DIFF_SECTION, null, CONFIG_KEY_ALGORITHM, + HISTOGRAM); + return new MergeAlgorithm(DiffAlgorithm.getAlgorithm(diffAlg)); + } + + private static String[] defaultCommitNames() { + return new String[] { "BASE", "OURS", "THEIRS" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + /** * @param local * @param inCore */ protected ResolveMerger(Repository local, boolean inCore) { super(local); - SupportedAlgorithm diffAlg = local.getConfig().getEnum( - ConfigConstants.CONFIG_DIFF_SECTION, null, - ConfigConstants.CONFIG_KEY_ALGORITHM, - SupportedAlgorithm.HISTOGRAM); - mergeAlgorithm = new MergeAlgorithm(DiffAlgorithm.getAlgorithm(diffAlg)); - commitNames = new String[] { "BASE", "OURS", "THEIRS" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + mergeAlgorithm = getMergeAlgorithm(local.getConfig()); + commitNames = defaultCommitNames(); this.inCore = inCore; if (inCore) { @@ -295,10 +306,24 @@ public class ResolveMerger extends ThreeWayMerger { this(local, false); } + /** + * @param inserter + * @param config + * @since 4.8 + */ + protected ResolveMerger(ObjectInserter inserter, Config config) { + super(inserter); + mergeAlgorithm = getMergeAlgorithm(config); + commitNames = defaultCommitNames(); + inCore = true; + implicitDirCache = false; + dircache = DirCache.newInCore(); + } + @Override protected boolean mergeImpl() throws IOException { if (implicitDirCache) - dircache = getRepository().lockDirCache(); + dircache = nonNullRepo().lockDirCache(); try { return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1], @@ -315,7 +340,7 @@ public class ResolveMerger extends ThreeWayMerger { // of a non-empty directory, for which delete() would fail. for (int i = toBeDeleted.size() - 1; i >= 0; i--) { String fileName = toBeDeleted.get(i); - File f = new File(db.getWorkTree(), fileName); + File f = new File(nonNullRepo().getWorkTree(), fileName); if (!f.delete()) if (!f.isDirectory()) failingPaths.put(fileName, @@ -348,7 +373,7 @@ public class ResolveMerger extends ThreeWayMerger { return; } - DirCache dc = db.readDirCache(); + DirCache dc = nonNullRepo().readDirCache(); Iterator<String> mpathsIt=modifiedFiles.iterator(); while(mpathsIt.hasNext()) { String mpath=mpathsIt.next(); @@ -785,8 +810,8 @@ public class ResolveMerger extends ThreeWayMerger { */ private File writeMergedFile(MergeResult<RawText> result) throws FileNotFoundException, IOException { - File workTree = db.getWorkTree(); - FS fs = db.getFS(); + File workTree = nonNullRepo().getWorkTree(); + FS fs = nonNullRepo().getFS(); File of = new File(workTree, tw.getPathString()); File parentFolder = of.getParentFile(); if (!fs.exists(parentFolder)) @@ -802,7 +827,7 @@ public class ResolveMerger extends ThreeWayMerger { private ObjectId insertMergeResult(MergeResult<RawText> result) throws IOException { TemporaryBuffer.LocalFile buf = new TemporaryBuffer.LocalFile( - db.getDirectory(), 10 << 20); + db != null ? nonNullRepo().getDirectory() : null, 10 << 20); try { new MergeFormatter().formatMerge(buf, result, Arrays.asList(commitNames), CHARACTER_ENCODING); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java index 12d6c6b413..2224dbc448 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java @@ -46,7 +46,9 @@ package org.eclipse.jgit.merge; import java.io.IOException; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; /** @@ -89,6 +91,11 @@ public class StrategyOneSided extends MergeStrategy { return new OneSide(db, treeIndex); } + @Override + public Merger newMerger(final ObjectInserter inserter, final Config config) { + return new OneSide(inserter, treeIndex); + } + static class OneSide extends Merger { private final int treeIndex; @@ -97,6 +104,11 @@ public class StrategyOneSided extends MergeStrategy { treeIndex = index; } + protected OneSide(final ObjectInserter inserter, final int index) { + super(inserter); + treeIndex = index; + } + @Override protected boolean mergeImpl() throws IOException { return treeIndex < sourceTrees.length; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java index 22e608ec9d..56128dd93e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java @@ -43,6 +43,8 @@ package org.eclipse.jgit.merge; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; /** @@ -63,6 +65,11 @@ public class StrategyRecursive extends StrategyResolve { } @Override + public ThreeWayMerger newMerger(ObjectInserter inserter, Config config) { + return new RecursiveMerger(inserter, config); + } + + @Override public String getName() { return "recursive"; //$NON-NLS-1$ } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java index 07368e5746..17044b53ae 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java @@ -43,6 +43,8 @@ */ package org.eclipse.jgit.merge; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; /** @@ -60,8 +62,16 @@ public class StrategyResolve extends ThreeWayMergeStrategy { return new ResolveMerger(db, inCore); } + /** + * @since 4.8 + */ + @Override + public ThreeWayMerger newMerger(ObjectInserter inserter, Config config) { + return new ResolveMerger(inserter, config); + } + @Override public String getName() { return "resolve"; //$NON-NLS-1$ } -}
\ No newline at end of file +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java index ec903c139d..cd427bd8fb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java @@ -49,6 +49,7 @@ import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.errors.UnmergedPathException; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; @@ -89,6 +90,14 @@ public class StrategySimpleTwoWayInCore extends ThreeWayMergeStrategy { return newMerger(db); } + /** + * @since 4.8 + */ + @Override + public ThreeWayMerger newMerger(ObjectInserter inserter, Config config) { + return new InCoreMerger(inserter); + } + private static class InCoreMerger extends ThreeWayMerger { private static final int T_BASE = 0; @@ -110,6 +119,12 @@ public class StrategySimpleTwoWayInCore extends ThreeWayMergeStrategy { cache = DirCache.newInCore(); } + InCoreMerger(final ObjectInserter inserter) { + super(inserter); + tw = new NameConflictTreeWalk(null, reader); + cache = DirCache.newInCore(); + } + @Override protected boolean mergeImpl() throws IOException { tw.addTree(mergeBase()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java index fbedaef865..b3ef0fb3e4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java @@ -50,6 +50,7 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; @@ -85,6 +86,17 @@ public abstract class ThreeWayMerger extends Merger { } /** + * Create a new in-core merge instance from an inserter. + * + * @param inserter + * the inserter to write objects to. + * @since 4.8 + */ + protected ThreeWayMerger(ObjectInserter inserter) { + super(inserter); + } + + /** * Set the common ancestor tree. * * @param id diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java index 5d7e72dd29..73ce9854e0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java @@ -69,26 +69,21 @@ import org.eclipse.jgit.internal.JGitText; */ class MergeBaseGenerator extends Generator { private static final int PARSED = RevWalk.PARSED; - private static final int IN_PENDING = RevWalk.SEEN; - private static final int POPPED = RevWalk.TEMP_MARK; - private static final int MERGE_BASE = RevWalk.REWRITE; private final RevWalk walker; - private final DateRevQueue pending; private int branchMask; - private int recarryTest; - private int recarryMask; - private int mergeBaseAncestor = -1; private LinkedList<RevCommit> ret = new LinkedList<>(); + private CarryStack stack; + MergeBaseGenerator(final RevWalk w) { walker = w; pending = new DateRevQueue(); @@ -202,29 +197,56 @@ class MergeBaseGenerator extends Generator { return null; } - private void carryOntoHistory(RevCommit c, final int carry) { + private void carryOntoHistory(RevCommit c, int carry) { + stack = null; for (;;) { - final RevCommit[] pList = c.parents; - if (pList == null) - return; - final int n = pList.length; - if (n == 0) - return; - - for (int i = 1; i < n; i++) { - final RevCommit p = pList[i]; - if (!carryOntoOne(p, carry)) - carryOntoHistory(p, carry); + carryOntoHistoryInnerLoop(c, carry); + if (stack == null) { + break; + } + c = stack.c; + carry = stack.carry; + stack = stack.prev; + } + } + + private void carryOntoHistoryInnerLoop(RevCommit c, int carry) { + for (;;) { + RevCommit[] parents = c.parents; + if (parents == null || parents.length == 0) { + break; } - c = pList[0]; - if (carryOntoOne(c, carry)) + int e = parents.length - 1; + for (int i = 0; i < e; i++) { + RevCommit p = parents[i]; + if (carryOntoOne(p, carry) == CONTINUE) { + // Walking p will be required, buffer p on stack. + stack = new CarryStack(stack, p, carry); + } + // For other results from carryOntoOne: + // HAVE_ALL: p has all bits, do nothing to skip that path. + // CONTINUE_ON_STACK: callee pushed StackElement for p. + } + + c = parents[e]; + if (carryOntoOne(c, carry) != CONTINUE) { break; + } } } - private boolean carryOntoOne(final RevCommit p, final int carry) { - final boolean haveAll = (p.flags & carry) == carry; + private static final int CONTINUE = 0; + private static final int HAVE_ALL = 1; + private static final int CONTINUE_ON_STACK = 2; + + private int carryOntoOne(RevCommit p, int carry) { + // If we already had all carried flags, our parents do too. + // Return HAVE_ALL to stop caller from running down this leg + // of the revision graph any further. + // + // Otherwise return CONTINUE to ask the caller to walk history. + int rc = (p.flags & carry) == carry ? HAVE_ALL : CONTINUE; p.flags |= carry; if ((p.flags & recarryMask) == recarryTest) { @@ -232,17 +254,23 @@ class MergeBaseGenerator extends Generator { // voted to be one. Inject ourselves back at the front of the // pending queue and tell all of our ancestors they are within // the merge base now. - // p.flags &= ~POPPED; pending.add(p); - carryOntoHistory(p, branchMask | MERGE_BASE); - return true; + stack = new CarryStack(stack, p, branchMask | MERGE_BASE); + return CONTINUE_ON_STACK; } + return rc; + } - // If we already had all carried flags, our parents do too. - // Return true to stop the caller from running down this leg - // of the revision graph any further. - // - return haveAll; + private static class CarryStack { + final CarryStack prev; + final RevCommit c; + final int carry; + + CarryStack(CarryStack prev, RevCommit c, int carry) { + this.prev = prev; + this.c = c; + this.carry = carry; + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java index 37d70e3a14..0920f21563 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java @@ -58,6 +58,7 @@ import org.eclipse.jgit.internal.storage.pack.PackWriter; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; @@ -84,6 +85,8 @@ import org.eclipse.jgit.storage.pack.PackConfig; public class BundleWriter { private final Repository db; + private final ObjectReader reader; + private final Map<String, ObjectId> include; private final Set<RevCommit> assume; @@ -100,8 +103,26 @@ public class BundleWriter { * @param repo * repository where objects are stored. */ - public BundleWriter(final Repository repo) { + public BundleWriter(Repository repo) { db = repo; + reader = null; + include = new TreeMap<>(); + assume = new HashSet<>(); + tagTargets = new HashSet<>(); + } + + /** + * Create a writer for a bundle. + * + * @param or + * reader for reading objects. Will be closed at the end of {@link + * #writeBundle(ProgressMonitor, OutputStream)}, but readers may be + * reused after closing. + * @since 4.8 + */ + public BundleWriter(ObjectReader or) { + db = null; + reader = or; include = new TreeMap<>(); assume = new HashSet<>(); tagTargets = new HashSet<>(); @@ -112,7 +133,8 @@ public class BundleWriter { * * @param pc * configuration controlling packing parameters. If null the - * source repository's settings will be used. + * source repository's settings will be used, or the default + * settings if constructed without a repo. */ public void setPackConfig(PackConfig pc) { this.packConfig = pc; @@ -196,10 +218,7 @@ public class BundleWriter { */ public void writeBundle(ProgressMonitor monitor, OutputStream os) throws IOException { - PackConfig pc = packConfig; - if (pc == null) - pc = new PackConfig(db); - try (PackWriter packWriter = new PackWriter(pc, db.newObjectReader())) { + try (PackWriter packWriter = newPackWriter()) { packWriter.setObjectCountCallback(callback); final HashSet<ObjectId> inc = new HashSet<>(); @@ -242,6 +261,14 @@ public class BundleWriter { } } + private PackWriter newPackWriter() { + PackConfig pc = packConfig; + if (pc == null) { + pc = db != null ? new PackConfig(db) : new PackConfig(); + } + return new PackWriter(pc, reader != null ? reader : db.newObjectReader()); + } + /** * Set the {@link ObjectCountCallback}. * <p> diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java index 58fdd25745..17af0b9838 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -58,6 +58,7 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK; +import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; @@ -235,7 +236,7 @@ public class UploadPack { private InputStream rawIn; - private OutputStream rawOut; + private ResponseBufferedOutputStream rawOut; private PacketLineIn pckIn; @@ -644,11 +645,10 @@ public class UploadPack { * other network connections this should be null. * @throws IOException */ - public void upload(final InputStream input, final OutputStream output, + public void upload(final InputStream input, OutputStream output, final OutputStream messages) throws IOException { try { rawIn = input; - rawOut = output; if (messages != null) msgOut = messages; @@ -656,11 +656,17 @@ public class UploadPack { final Thread caller = Thread.currentThread(); timer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$ TimeoutInputStream i = new TimeoutInputStream(rawIn, timer); - TimeoutOutputStream o = new TimeoutOutputStream(rawOut, timer); + @SuppressWarnings("resource") + TimeoutOutputStream o = new TimeoutOutputStream(output, timer); i.setTimeout(timeout * 1000); o.setTimeout(timeout * 1000); rawIn = i; - rawOut = o; + output = o; + } + + rawOut = new ResponseBufferedOutputStream(output); + if (biDirectionalPipe) { + rawOut.stopBuffering(); } pckIn = new PacketLineIn(rawIn); @@ -714,6 +720,8 @@ public class UploadPack { private void service() throws IOException { boolean sendPack; + // If it's a non-bidi request, we need to read the entire request before + // writing a response. Buffer the response until then. try { if (biDirectionalPipe) sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut)); @@ -769,6 +777,8 @@ public class UploadPack { throw new UploadPackInternalServerErrorException(err); } throw err; + } finally { + rawOut.stopBuffering(); } if (sendPack) @@ -1513,7 +1523,7 @@ public class UploadPack { walk.reset(); ObjectWalk ow = rw.toObjectWalkWithSameObjects(); - pw.preparePack(pm, ow, wantAll, commonBase); + pw.preparePack(pm, ow, wantAll, commonBase, PackWriter.NONE); rw = ow; } @@ -1572,4 +1582,47 @@ public class UploadPack { adv.addSymref(Constants.HEAD, head.getLeaf().getName()); } } + + private static class ResponseBufferedOutputStream extends OutputStream { + private final OutputStream rawOut; + + private OutputStream out; + + ResponseBufferedOutputStream(OutputStream rawOut) { + this.rawOut = rawOut; + this.out = new ByteArrayOutputStream(); + } + + @Override + public void write(int b) throws IOException { + out.write(b); + } + + @Override + public void write(byte b[]) throws IOException { + out.write(b); + } + + @Override + public void write(byte b[], int off, int len) throws IOException { + out.write(b, off, len); + } + + @Override + public void flush() throws IOException { + out.flush(); + } + + @Override + public void close() throws IOException { + out.close(); + } + + void stopBuffering() throws IOException { + if (out != rawOut) { + ((ByteArrayOutputStream) out).writeTo(rawOut); + out = rawOut; + } + } + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java index c0b29ef930..59cf7989d4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java @@ -45,6 +45,7 @@ package org.eclipse.jgit.treewalk; import java.io.IOException; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.lib.FileMode; @@ -110,7 +111,7 @@ public class NameConflictTreeWalk extends TreeWalk { * the reader the walker will obtain tree data from. * @since 4.3 */ - public NameConflictTreeWalk(Repository repo, final ObjectReader or) { + public NameConflictTreeWalk(@Nullable Repository repo, final ObjectReader or) { super(repo, or); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java index 6b537a4775..ed5b87d5ca 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -516,7 +516,13 @@ public abstract class FS { if (env != null) { pb.environment().putAll(env); } - Process p = pb.start(); + Process p; + try { + p = pb.start(); + } catch (IOException e) { + // Process failed to start + throw new CommandFailedException(-1, e.getMessage(), e); + } p.getOutputStream().close(); GobblerThread gobbler = new GobblerThread(p, command, dir); gobbler.start(); @@ -877,7 +883,7 @@ public abstract class FS { } /** - * See {@link FileUtils#relativize(String, String)}. + * See {@link FileUtils#relativizePath(String, String, String, boolean)}. * * @param base * The path against which <code>other</code> should be @@ -886,11 +892,11 @@ public abstract class FS { * The path that will be made relative to <code>base</code>. * @return A relative path that, when resolved against <code>base</code>, * will yield the original <code>other</code>. - * @see FileUtils#relativize(String, String) + * @see FileUtils#relativizePath(String, String, String, boolean) * @since 3.7 */ public String relativize(String base, String other) { - return FileUtils.relativize(base, other); + return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive()); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java index 1f20e9700d..76dbb8756e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java @@ -468,10 +468,71 @@ public class FileUtils { throw new IOException(JGitText.get().cannotCreateTempDir); } + + /** + * @deprecated Use the more-clearly-named + * {@link FileUtils#relativizeNativePath(String, String)} + * instead, or directly call + * {@link FileUtils#relativizePath(String, String, String, boolean)} + * + * Expresses <code>other</code> as a relative file path from + * <code>base</code>. File-separator and case sensitivity are + * based on the current file system. + * + * See also + * {@link FileUtils#relativizePath(String, String, String, boolean)}. + * + * @param base + * Base path + * @param other + * Destination path + * @return Relative path from <code>base</code> to <code>other</code> + * @since 3.7 + */ + @Deprecated + public static String relativize(String base, String other) { + return relativizeNativePath(base, other); + } + + /** + * Expresses <code>other</code> as a relative file path from <code>base</code>. + * File-separator and case sensitivity are based on the current file system. + * + * See also {@link FileUtils#relativizePath(String, String, String, boolean)}. + * + * @param base + * Base path + * @param other + * Destination path + * @return Relative path from <code>base</code> to <code>other</code> + * @since 4.8 + */ + public static String relativizeNativePath(String base, String other) { + return FS.DETECTED.relativize(base, other); + } + + /** + * Expresses <code>other</code> as a relative file path from <code>base</code>. + * File-separator and case sensitivity are based on Git's internal representation of files (which matches Unix). + * + * See also {@link FileUtils#relativizePath(String, String, String, boolean)}. + * + * @param base + * Base path + * @param other + * Destination path + * @return Relative path from <code>base</code> to <code>other</code> + * @since 4.8 + */ + public static String relativizeGitPath(String base, String other) { + return relativizePath(base, other, "/", false); //$NON-NLS-1$ + } + + /** - * This will try and make a given path relative to another. + * Expresses <code>other</code> as a relative file path from <code>base</code> * <p> - * For example, if this is called with the two following paths : + * For example, if called with the two following paths : * * <pre> * <code>base = "c:\\Users\\jdoe\\eclipse\\git\\project"</code> @@ -480,9 +541,7 @@ public class FileUtils { * * This will return "..\\another_project\\pom.xml". * </p> - * <p> - * This method uses {@link File#separator} to split the paths into segments. - * </p> + * * <p> * <b>Note</b> that this will return the empty String if <code>base</code> * and <code>other</code> are equal. @@ -494,29 +553,32 @@ public class FileUtils { * folder and not a file. * @param other * The path that will be made relative to <code>base</code>. + * @param dirSeparator + * A string that separates components of the path. In practice, this is "/" or "\\". + * @param caseSensitive + * Whether to consider differently-cased directory names as distinct * @return A relative path that, when resolved against <code>base</code>, * will yield the original <code>other</code>. - * @since 3.7 + * @since 4.8 */ - public static String relativize(String base, String other) { + public static String relativizePath(String base, String other, String dirSeparator, boolean caseSensitive) { if (base.equals(other)) return ""; //$NON-NLS-1$ - final boolean ignoreCase = !FS.DETECTED.isCaseSensitive(); - final String[] baseSegments = base.split(Pattern.quote(File.separator)); + final String[] baseSegments = base.split(Pattern.quote(dirSeparator)); final String[] otherSegments = other.split(Pattern - .quote(File.separator)); + .quote(dirSeparator)); int commonPrefix = 0; while (commonPrefix < baseSegments.length && commonPrefix < otherSegments.length) { - if (ignoreCase + if (caseSensitive && baseSegments[commonPrefix] - .equalsIgnoreCase(otherSegments[commonPrefix])) + .equals(otherSegments[commonPrefix])) commonPrefix++; - else if (!ignoreCase + else if (!caseSensitive && baseSegments[commonPrefix] - .equals(otherSegments[commonPrefix])) + .equalsIgnoreCase(otherSegments[commonPrefix])) commonPrefix++; else break; @@ -524,11 +586,11 @@ public class FileUtils { final StringBuilder builder = new StringBuilder(); for (int i = commonPrefix; i < baseSegments.length; i++) - builder.append("..").append(File.separator); //$NON-NLS-1$ + builder.append("..").append(dirSeparator); //$NON-NLS-1$ for (int i = commonPrefix; i < otherSegments.length; i++) { builder.append(otherSegments[i]); if (i < otherSegments.length - 1) - builder.append(File.separator); + builder.append(dirSeparator); } return builder.toString(); } |