/* * Copyright (C) 2010, Christian Halstrick * Copyright (C) 2010, 2025 Stefan Lay and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import static org.eclipse.jgit.lib.FileMode.GITLINK; import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK; import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; import java.time.Instant; import java.util.ArrayList; import java.util.List; import org.eclipse.jgit.api.errors.FilterFailedException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuildIterator; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.NameConflictTreeWalk; import org.eclipse.jgit.treewalk.TreeWalk.OperationType; import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; import org.eclipse.jgit.treewalk.filter.IndexDiffFilter; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; import org.eclipse.jgit.treewalk.filter.TreeFilter; /** * A class used to execute a {@code Add} command. It has setters for all * supported options and arguments of this command and a {@link #call()} method * to finally execute the command. Each instance of this class should only be * used for one invocation of the command (means: one call to {@link #call()}) * * @see Git documentation about Add */ public class AddCommand extends GitCommand { private List filepatterns; private WorkingTreeIterator workingTreeIterator; // Update only known index entries, don't add new ones. If there's no file // for an index entry, remove it: stage deletions. private boolean update = false; // If TRUE, also stage deletions, otherwise only update and add index // entries. // If not set explicitly private Boolean all; // This defaults to true because it's what JGit has been doing // traditionally. The C git default would be false. private boolean renormalize = true; /** * Constructor for AddCommand * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ public AddCommand(Repository repo) { super(repo); filepatterns = new ArrayList<>(); } /** * Add a path to a file/directory whose content should be added. *

* A directory name (e.g. dir to add dir/file1 and * dir/file2) can also be given to add all files in the * directory, recursively. Fileglobs (e.g. *.c) are not yet supported. *

*

* If a pattern {@code "."} is added, all changes in the git repository's * working tree will be added. *

*

* File patterns are required unless {@code isUpdate() == true} or * {@link #setAll(boolean)} is called. If so and no file patterns are given, * all changes will be added (i.e., a file pattern of {@code "."} is * implied). *

* * @param filepattern * repository-relative path of file/directory to add (with * / as separator) * @return {@code this} */ public AddCommand addFilepattern(String filepattern) { checkCallable(); filepatterns.add(filepattern); return this; } /** * Allow clients to provide their own implementation of a FileTreeIterator * * @param f * a {@link org.eclipse.jgit.treewalk.WorkingTreeIterator} * object. * @return {@code this} */ public AddCommand setWorkingTreeIterator(WorkingTreeIterator f) { workingTreeIterator = f; return this; } /** * {@inheritDoc} *

* Executes the {@code Add} command. Each instance of this class should only * be used for one invocation of the command. Don't call this method twice * on an instance. *

* * @throws JGitInternalException * on errors, but also if {@code isUpdate() == true} _and_ * {@link #setAll(boolean)} had been called * @throws NoFilepatternException * if no file patterns are given if {@code isUpdate() == false} * and {@link #setAll(boolean)} was not called */ @Override public DirCache call() throws GitAPIException, NoFilepatternException { checkCallable(); if (update && all != null) { throw new JGitInternalException(MessageFormat.format( JGitText.get().illegalCombinationOfArguments, "--update", "--all/--no-all")); //$NON-NLS-1$ //$NON-NLS-2$ } boolean addAll; if (filepatterns.isEmpty()) { if (update || all != null) { addAll = true; } else { throw new NoFilepatternException( JGitText.get().atLeastOnePatternIsRequired); } } else { addAll = filepatterns.contains("."); //$NON-NLS-1$ if (all == null && !update) { all = Boolean.TRUE; } } boolean stageDeletions = update || (all != null && all.booleanValue()); DirCache dc = null; try (ObjectInserter inserter = repo.newObjectInserter(); NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) { tw.setOperationType(OperationType.CHECKIN_OP); dc = repo.lockDirCache(); DirCacheBuilder builder = dc.builder(); tw.addTree(new DirCacheBuildIterator(builder)); if (workingTreeIterator == null) workingTreeIterator = new FileTreeIterator(repo); workingTreeIterator.setDirCacheIterator(tw, 0); tw.addTree(workingTreeIterator); TreeFilter pathFilter = null; if (!addAll) { pathFilter = PathFilterGroup.createFromStrings(filepatterns); } if (!renormalize) { if (pathFilter == null) { tw.setFilter(new IndexDiffFilter(0, 1)); } else { tw.setFilter(AndTreeFilter.create(new IndexDiffFilter(0, 1), pathFilter)); } } else if (pathFilter != null) { tw.setFilter(pathFilter); } byte[] lastAdded = null; while (tw.next()) { DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); WorkingTreeIterator f = tw.getTree(1, WorkingTreeIterator.class); if (c == null && f != null && f.isEntryIgnored()) { // file is not in index but is ignored, do nothing continue; } else if (c == null && update) { // Only update of existing entries was requested. continue; } DirCacheEntry entry = c != null ? c.getDirCacheEntry() : null; if (entry != null && entry.getStage() > 0 && lastAdded != null && lastAdded.length == tw.getPathLength() && tw.isPathPrefix(lastAdded, lastAdded.length) == 0) { // In case of an existing merge conflict the // DirCacheBuildIterator iterates over all stages of // this path, we however want to add only one // new DirCacheEntry per path. continue; } if (tw.isSubtree() && !tw.isDirectoryFileConflict()) { tw.enterSubtree(); continue; } if (f == null) { // working tree file does not exist if (entry != null && (!stageDeletions || GITLINK == entry.getFileMode())) { builder.add(entry); } continue; } if (entry != null && entry.isAssumeValid()) { // Index entry is marked assume valid. Even though // the user specified the file to be added JGit does // not consider the file for addition. builder.add(entry); continue; } if ((f.getEntryRawMode() == TYPE_TREE && f.getIndexFileMode(c) != FileMode.GITLINK) || (f.getEntryRawMode() == TYPE_GITLINK && f.getIndexFileMode(c) == FileMode.TREE)) { // Index entry exists and is symlink, gitlink or file, // otherwise the tree would have been entered above. // Replace the index entry by diving into tree of files. tw.enterSubtree(); continue; } byte[] path = tw.getRawPath(); if (entry == null || entry.getStage() > 0) { entry = new DirCacheEntry(path); } FileMode mode = f.getIndexFileMode(c); entry.setFileMode(mode); if (GITLINK != mode) { entry.setLength(f.getEntryLength()); entry.setLastModified(f.getEntryLastModifiedInstant()); long len = f.getEntryContentLength(); // We read and filter the content multiple times. // f.getEntryContentLength() reads and filters the input and // inserter.insert(...) does it again. That's because an // ObjectInserter needs to know the length before it starts // inserting. TODO: Fix this by using Buffers. try (InputStream in = f.openEntryStream()) { ObjectId id = inserter.insert(OBJ_BLOB, len, in); entry.setObjectId(id); } } else { entry.setLength(0); entry.setLastModified(Instant.ofEpochSecond(0)); entry.setObjectId(f.getEntryObjectId()); } builder.add(entry); lastAdded = path; } inserter.flush(); builder.commit(); setCallable(false); } catch (IOException e) { Throwable cause = e.getCause(); if (cause != null && cause instanceof FilterFailedException) throw (FilterFailedException) cause; throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfAddCommand, e); } finally { if (dc != null) dc.unlock(); } return dc; } /** * Set whether to only match against already tracked files. If * {@code update == true}, re-sets a previous {@link #setAll(boolean)}. * * @param update * If set to true, the command only matches {@code filepattern} * against already tracked files in the index rather than the * working tree. That means that it will never stage new files, * but that it will stage modified new contents of tracked files * and that it will remove files from the index if the * corresponding files in the working tree have been removed. In * contrast to the git command line a {@code filepattern} must * exist also if update is set to true as there is no concept of * a working directory here. * @return {@code this} */ public AddCommand setUpdate(boolean update) { this.update = update; return this; } /** * Whether to only match against already tracked files * * @return whether to only match against already tracked files */ public boolean isUpdate() { return update; } /** * Defines whether the command will renormalize by re-applying the "clean" * process to tracked files. *

* This does not automatically call {@link #setUpdate(boolean)}. *

* * @param renormalize * whether to renormalize tracked files * @return {@code this} * @since 6.6 */ public AddCommand setRenormalize(boolean renormalize) { this.renormalize = renormalize; return this; } /** * Tells whether the command will renormalize by re-applying the "clean" * process to tracked files. *

* For legacy reasons, this is {@code true} by default. *

*

* This setting is independent of {@link #isUpdate()}. In C git, * command-line option --renormalize implies --update. *

* * @return whether files will be renormalized * @since 6.6 */ public boolean isRenormalize() { return renormalize; } /** * Defines whether the command will use '--all' mode: update existing index * entries, add new entries, and remove index entries for which there is no * file. (In other words: also stage deletions.) *

* The setting is independent of {@link #setUpdate(boolean)}. *

* * @param all * whether to enable '--all' mode * @return {@code this} * @since 7.2 */ public AddCommand setAll(boolean all) { this.all = Boolean.valueOf(all); return this; } /** * Tells whether '--all' has been set for this command. * * @return {@code true} if it was set; {@code false} otherwise * @since 7.2 */ public boolean isAll() { return all != null && all.booleanValue(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4097 Content-Disposition: inline; filename="AddNoteCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "8805ea235370f29e661533fd7dff74e7aa3e915d" /* * Copyright (C) 2011, Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.notes.Note; import org.eclipse.jgit.notes.NoteMap; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; /** * Add object notes. * * @see Git documentation about Notes */ public class AddNoteCommand extends GitCommand { private RevObject id; private String message; private String notesRef = Constants.R_NOTES_COMMITS; /** * Constructor for AddNoteCommand * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected AddNoteCommand(Repository repo) { super(repo); } @Override public Note call() throws GitAPIException { checkCallable(); NoteMap map = NoteMap.newEmptyMap(); RevCommit notesCommit = null; try (RevWalk walk = new RevWalk(repo); ObjectInserter inserter = repo.newObjectInserter()) { Ref ref = repo.findRef(notesRef); // if we have a notes ref, use it if (ref != null) { notesCommit = walk.parseCommit(ref.getObjectId()); map = NoteMap.read(walk.getObjectReader(), notesCommit); } map.set(id, message, inserter); commitNoteMap(repo, notesRef, walk, map, notesCommit, inserter, "Notes added by 'git notes add'"); //$NON-NLS-1$ return map.getNote(id); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } } /** * Sets the object id of object you want a note on. If the object already * has a note, the existing note will be replaced. * * @param id * a {@link org.eclipse.jgit.revwalk.RevObject} * @return {@code this} */ public AddNoteCommand setObjectId(RevObject id) { checkCallable(); this.id = id; return this; } /** * Set the notes message * * @param message * the notes message used when adding a note * @return {@code this} */ public AddNoteCommand setMessage(String message) { checkCallable(); this.message = message; return this; } static void commitNoteMap(Repository r, String ref, RevWalk walk, NoteMap map, RevCommit notesCommit, ObjectInserter inserter, String msg) throws IOException { // commit the note CommitBuilder builder = new CommitBuilder(); builder.setTreeId(map.writeTree(inserter)); builder.setAuthor(new PersonIdent(r)); builder.setCommitter(builder.getAuthor()); builder.setMessage(msg); if (notesCommit != null) builder.setParentIds(notesCommit); ObjectId commit = inserter.insert(builder); inserter.flush(); RefUpdate refUpdate = r.updateRef(ref); if (notesCommit != null) refUpdate.setExpectedOldObjectId(notesCommit); else refUpdate.setExpectedOldObjectId(ObjectId.zeroId()); refUpdate.setNewObjectId(commit); refUpdate.update(walk); } /** * Set name of a {@code Ref} to read notes from * * @param notesRef * the ref to read notes from. Note, the default value of * {@link org.eclipse.jgit.lib.Constants#R_NOTES_COMMITS} will be * used if nothing is set * @return {@code this} * @see Constants#R_NOTES_COMMITS */ public AddNoteCommand setNotesRef(String notesRef) { checkCallable(); this.notesRef = notesRef; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2989 Content-Disposition: inline; filename="ApplyCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "df0f616256551ba3591c5b2d3e73d3388f0b9dd8" /* * Copyright (C) 2011, 2021 IBM Corporation and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.PatchApplyException; import org.eclipse.jgit.api.errors.PatchFormatException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.patch.Patch; import org.eclipse.jgit.patch.PatchApplier; import org.eclipse.jgit.patch.PatchApplier.Result; /** * Apply a patch to files and/or to the index. * * @see Git documentation about apply * @since 2.0 */ public class ApplyCommand extends GitCommand { private InputStream in; /** * Constructs the command. * * @param repo * the repository this command will be used on */ ApplyCommand(Repository repo) { super(repo); if (repo == null) { throw new NullPointerException(JGitText.get().repositoryIsRequired); } } /** * Set patch * * @param in * the patch to apply * @return this instance */ public ApplyCommand setPatch(InputStream in) { checkCallable(); this.in = in; return this; } /** * {@inheritDoc} * *

* Executes the {@code ApplyCommand} command with all the options and * parameters collected by the setter methods (e.g. * {@link #setPatch(InputStream)} of this class. Each instance of this class * should only be used for one invocation of the command. Don't call this * method twice on an instance. */ @Override public ApplyResult call() throws GitAPIException { checkCallable(); setCallable(false); Patch patch = new Patch(); try (InputStream inStream = in) { patch.parse(inStream); if (!patch.getErrors().isEmpty()) { throw new PatchFormatException(patch.getErrors()); } } catch (IOException e) { throw new PatchApplyException(MessageFormat.format( JGitText.get().patchApplyException, e.getMessage()), e); } ApplyResult r = new ApplyResult(); try { PatchApplier patchApplier = new PatchApplier(repo); Result applyResult = patchApplier.applyPatch(patch); if (!applyResult.getErrors().isEmpty()) { throw new PatchApplyException( MessageFormat.format(JGitText.get().patchApplyException, applyResult.getErrors())); } for (String p : applyResult.getPaths()) { r.addUpdatedFile(new File(repo.getWorkTree(), p)); } } catch (IOException e) { throw new PatchApplyException(MessageFormat.format(JGitText.get().patchApplyException, e.getMessage(), e)); } return r; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 933 Content-Disposition: inline; filename="ApplyResult.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "540481b9c8c3180a4fc2c68b24ff399b06759e80" /* * Copyright (C) 2011, 2012 IBM Corporation and others. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.File; import java.util.ArrayList; import java.util.List; /** * Encapsulates the result of a {@link org.eclipse.jgit.api.ApplyCommand} * * @since 2.0 */ public class ApplyResult { private List updatedFiles = new ArrayList<>(); /** * Add updated file * * @param f * an updated file * @return this instance */ public ApplyResult addUpdatedFile(File f) { updatedFiles.add(f); return this; } /** * Get updated files * * @return updated files */ public List getUpdatedFiles() { return updatedFiles; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 15313 Content-Disposition: inline; filename="ArchiveCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "81b361177450d1c5e911504b57de22f37b7fa99d" /* * Copyright (C) 2012 Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.Closeable; import java.io.IOException; import java.io.OutputStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; /** * Create an archive of files from a named tree. *

* Examples (git is a {@link org.eclipse.jgit.api.Git} instance): *

* Create a tarball from HEAD: * *

 * ArchiveCommand.registerFormat("tar", new TarFormat());
 * try {
 * 	git.archive().setTree(db.resolve("HEAD")).setOutputStream(out).call();
 * } finally {
 * 	ArchiveCommand.unregisterFormat("tar");
 * }
 * 
*

* Create a ZIP file from master: * *

 * ArchiveCommand.registerFormat("zip", new ZipFormat());
 * try {
 *	git.archive().
 *		.setTree(db.resolve("master"))
 *		.setFormat("zip")
 *		.setOutputStream(out)
 *		.call();
 * } finally {
 *	ArchiveCommand.unregisterFormat("zip");
 * }
 * 
* * @see Git * documentation about archive * @since 3.1 */ public class ArchiveCommand extends GitCommand { /** * Archival format. * * Usage: * Repository repo = git.getRepository(); * T out = format.createArchiveOutputStream(System.out); * try { * for (...) { * format.putEntry(out, path, mode, repo.open(objectId)); * } * out.close(); * } * * @param * type representing an archive being created. */ public static interface Format { /** * Start a new archive. Entries can be included in the archive using the * putEntry method, and then the archive should be closed using its * close method. * * @param s * underlying output stream to which to write the archive. * @return new archive object for use in putEntry * @throws IOException * thrown by the underlying output stream for I/O errors */ T createArchiveOutputStream(OutputStream s) throws IOException; /** * Start a new archive. Entries can be included in the archive using the * putEntry method, and then the archive should be closed using its * close method. In addition options can be applied to the underlying * stream. E.g. compression level. * * @param s * underlying output stream to which to write the archive. * @param o * options to apply to the underlying output stream. Keys are * option names and values are option values. * @return new archive object for use in putEntry * @throws IOException * thrown by the underlying output stream for I/O errors * @since 4.0 */ T createArchiveOutputStream(OutputStream s, Map o) throws IOException; /** * Write an entry to an archive. * * @param out * archive object from createArchiveOutputStream * @param tree * the tag, commit, or tree object to produce an archive for * @param path * full filename relative to the root of the archive (with * trailing '/' for directories) * @param mode * mode (for example FileMode.REGULAR_FILE or * FileMode.SYMLINK) * @param loader * blob object with data for this entry (null for * directories) * @throws IOException * thrown by the underlying output stream for I/O errors * @since 4.7 */ void putEntry(T out, ObjectId tree, String path, FileMode mode, ObjectLoader loader) throws IOException; /** * Filename suffixes representing this format (e.g., * { ".tar.gz", ".tgz" }). * * The behavior is undefined when suffixes overlap (if * one format claims suffix ".7z", no other format should * take ".tar.7z"). * * @return this format's suffixes */ Iterable suffixes(); } /** * Signals an attempt to use an archival format that ArchiveCommand * doesn't know about (for example due to a typo). */ public static class UnsupportedFormatException extends GitAPIException { private static final long serialVersionUID = 1L; private final String format; /** * @param format the problematic format name */ public UnsupportedFormatException(String format) { super(MessageFormat.format(JGitText.get().unsupportedArchiveFormat, format)); this.format = format; } /** * Get the problematic format name * * @return the problematic format name */ public String getFormat() { return format; } } private static class FormatEntry { final Format format; /** Number of times this format has been registered. */ final int refcnt; public FormatEntry(Format format, int refcnt) { if (format == null) throw new NullPointerException(); this.format = format; this.refcnt = refcnt; } } /** * Available archival formats (corresponding to values for * the --format= option) */ private static final Map formats = new ConcurrentHashMap<>(); /** * Replaces the entry for a key only if currently mapped to a given * value. * * @param map a map * @param key key with which the specified value is associated * @param oldValue expected value for the key (null if should be absent). * @param newValue value to be associated with the key (null to remove). * @return true if the value was replaced */ private static boolean replace(Map map, K key, V oldValue, V newValue) { if (oldValue == null && newValue == null) // Nothing to do. return true; if (oldValue == null) return map.putIfAbsent(key, newValue) == null; else if (newValue == null) return map.remove(key, oldValue); else return map.replace(key, oldValue, newValue); } /** * Adds support for an additional archival format. To avoid * unnecessary dependencies, ArchiveCommand does not have support * for any formats built in; use this function to add them. *

* OSGi plugins providing formats should call this function at * bundle activation time. *

* It is okay to register the same archive format with the same * name multiple times, but don't forget to unregister it that * same number of times, too. *

* Registering multiple formats with different names and the * same or overlapping suffixes results in undefined behavior. * TODO: check that suffixes don't overlap. * * @param name name of a format (e.g., "tar" or "zip"). * @param fmt archiver for that format * @throws JGitInternalException * A different archival format with that name was * already registered. */ public static void registerFormat(String name, Format fmt) { if (fmt == null) throw new NullPointerException(); FormatEntry old, entry; do { old = formats.get(name); if (old == null) { entry = new FormatEntry(fmt, 1); continue; } if (!old.format.equals(fmt)) throw new JGitInternalException(MessageFormat.format( JGitText.get().archiveFormatAlreadyRegistered, name)); entry = new FormatEntry(old.format, old.refcnt + 1); } while (!replace(formats, name, old, entry)); } /** * Marks support for an archival format as no longer needed so its * Format can be garbage collected if no one else is using it either. *

* In other words, this decrements the reference count for an * archival format. If the reference count becomes zero, removes * support for that format. * * @param name name of format (e.g., "tar" or "zip"). * @throws JGitInternalException * No such archival format was registered. */ public static void unregisterFormat(String name) { FormatEntry old, entry; do { old = formats.get(name); if (old == null) throw new JGitInternalException(MessageFormat.format( JGitText.get().archiveFormatAlreadyAbsent, name)); if (old.refcnt == 1) { entry = null; continue; } entry = new FormatEntry(old.format, old.refcnt - 1); } while (!replace(formats, name, old, entry)); } private static Format formatBySuffix(String filenameSuffix) throws UnsupportedFormatException { if (filenameSuffix != null) for (FormatEntry entry : formats.values()) { Format fmt = entry.format; for (String sfx : fmt.suffixes()) if (filenameSuffix.endsWith(sfx)) return fmt; } return lookupFormat("tar"); //$NON-NLS-1$ } private static Format lookupFormat(String formatName) throws UnsupportedFormatException { FormatEntry entry = formats.get(formatName); if (entry == null) throw new UnsupportedFormatException(formatName); return entry.format; } private OutputStream out; private ObjectId tree; private String prefix; private String format; private Map formatOptions = new HashMap<>(); private List paths = new ArrayList<>(); /** Filename suffix, for automatically choosing a format. */ private String suffix; /** * Constructor for ArchiveCommand * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ public ArchiveCommand(Repository repo) { super(repo); setCallable(false); } private OutputStream writeArchive(Format fmt) { try { try (TreeWalk walk = new TreeWalk(repo); RevWalk rw = new RevWalk(walk.getObjectReader()); T outa = fmt.createArchiveOutputStream(out, formatOptions)) { String pfx = prefix == null ? "" : prefix; //$NON-NLS-1$ MutableObjectId idBuf = new MutableObjectId(); ObjectReader reader = walk.getObjectReader(); RevObject o = rw.peel(rw.parseAny(tree)); walk.reset(getTree(o)); if (!paths.isEmpty()) { walk.setFilter(PathFilterGroup.createFromStrings(paths)); } // Put base directory into archive if (pfx.endsWith("/")) { //$NON-NLS-1$ fmt.putEntry(outa, o, pfx.replaceAll("[/]+$", "/"), //$NON-NLS-1$ //$NON-NLS-2$ FileMode.TREE, null); } while (walk.next()) { String name = pfx + walk.getPathString(); FileMode mode = walk.getFileMode(0); if (walk.isSubtree()) walk.enterSubtree(); if (mode == FileMode.GITLINK) { // TODO(jrn): Take a callback to recurse // into submodules. mode = FileMode.TREE; } if (mode == FileMode.TREE) { fmt.putEntry(outa, o, name + "/", mode, null); //$NON-NLS-1$ continue; } walk.getObjectId(idBuf, 0); fmt.putEntry(outa, o, name, mode, reader.open(idBuf)); } return out; } finally { out.close(); } } catch (IOException e) { // TODO(jrn): Throw finer-grained errors. throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfArchiveCommand, e); } } @Override public OutputStream call() throws GitAPIException { checkCallable(); Format fmt; if (format == null) fmt = formatBySuffix(suffix); else fmt = lookupFormat(format); return writeArchive(fmt); } /** * Set the tag, commit, or tree object to produce an archive for * * @param tree * the tag, commit, or tree object to produce an archive for * @return this */ public ArchiveCommand setTree(ObjectId tree) { if (tree == null) throw new IllegalArgumentException(); this.tree = tree; setCallable(true); return this; } /** * Set string prefixed to filenames in archive * * @param prefix * string prefixed to filenames in archive (e.g., "master/"). * null means to not use any leading prefix. * @return this * @since 3.3 */ public ArchiveCommand setPrefix(String prefix) { this.prefix = prefix; return this; } /** * Set the intended filename for the produced archive. Currently the only * effect is to determine the default archive format when none is specified * with {@link #setFormat(String)}. * * @param filename * intended filename for the archive * @return this */ public ArchiveCommand setFilename(String filename) { int slash = filename.lastIndexOf('/'); int dot = filename.indexOf('.', slash + 1); if (dot == -1) this.suffix = ""; //$NON-NLS-1$ else this.suffix = filename.substring(dot); return this; } /** * Set output stream * * @param out * the stream to which to write the archive * @return this */ public ArchiveCommand setOutputStream(OutputStream out) { this.out = out; return this; } /** * Set archive format * * @param fmt * archive format (e.g., "tar" or "zip"). null means to choose * automatically based on the archive filename. * @return this */ public ArchiveCommand setFormat(String fmt) { this.format = fmt; return this; } /** * Set archive format options * * @param options * archive format options (e.g., level=9 for zip compression). * @return this * @since 4.0 */ public ArchiveCommand setFormatOptions(Map options) { this.formatOptions = options; return this; } /** * Set an optional parameter path. without an optional path parameter, all * files and subdirectories of the current working directory are included in * the archive. If one or more paths are specified, only these are included. * * @param paths * file names (e.g file1.c) or directory names (e.g. * dir to add dir/file1 and * dir/file2) can also be given to add all files in * the directory, recursively. Fileglobs (e.g. *.c) are not yet * supported. * @return this * @since 3.4 */ public ArchiveCommand setPaths(String... paths) { this.paths = Arrays.asList(paths); return this; } private RevTree getTree(RevObject o) throws IncorrectObjectTypeException { final RevTree t; if (o instanceof RevCommit) { t = ((RevCommit) o).getTree(); } else if (!(o instanceof RevTree)) { throw new IncorrectObjectTypeException(tree.toObjectId(), Constants.TYPE_TREE); } else { t = (RevTree) o; } return t; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5144 Content-Disposition: inline; filename="BlameCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "513853eaca29b10a76a7f969a8e7811df24f704b" /* * Copyright (C) 2011, 2019 GitHub Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.blame.BlameGenerator; import org.eclipse.jgit.blame.BlameResult; import org.eclipse.jgit.diff.DiffAlgorithm; import org.eclipse.jgit.diff.RawTextComparator; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; /** * Blame command for building a {@link org.eclipse.jgit.blame.BlameResult} for a * file path. */ public class BlameCommand extends GitCommand { private String path; private DiffAlgorithm diffAlgorithm; private RawTextComparator textComparator; private ObjectId startCommit; private Collection reverseEndCommits; private Boolean followFileRenames; /** * Constructor for BlameCommand * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ public BlameCommand(Repository repo) { super(repo); } /** * Set file path. * * @param filePath * file path (with / as separator) * @return this command */ public BlameCommand setFilePath(String filePath) { this.path = filePath; return this; } /** * Set diff algorithm * * @param diffAlgorithm * a {@link org.eclipse.jgit.diff.DiffAlgorithm} object. * @return this command */ public BlameCommand setDiffAlgorithm(DiffAlgorithm diffAlgorithm) { this.diffAlgorithm = diffAlgorithm; return this; } /** * Set raw text comparator * * @param textComparator * a {@link org.eclipse.jgit.diff.RawTextComparator} * @return this command */ public BlameCommand setTextComparator(RawTextComparator textComparator) { this.textComparator = textComparator; return this; } /** * Set start commit id * * @param commit * id of a commit * @return this command */ public BlameCommand setStartCommit(AnyObjectId commit) { this.startCommit = commit.toObjectId(); return this; } /** * Enable (or disable) following file renames. *

* If true renames are followed using the standard FollowFilter behavior * used by RevWalk (which matches {@code git log --follow} in the C * implementation). This is not the same as copy/move detection as * implemented by the C implementation's of {@code git blame -M -C}. * * @param follow * enable following. * @return {@code this} */ public BlameCommand setFollowFileRenames(boolean follow) { followFileRenames = Boolean.valueOf(follow); return this; } /** * Configure the command to compute reverse blame (history of deletes). * * @param start * oldest commit to traverse from. The result file will be loaded * from this commit's tree. * @param end * most recent commit to stop traversal at. Usually an active * branch tip, tag, or HEAD. * @return {@code this} * @throws java.io.IOException * the repository cannot be read. */ public BlameCommand reverse(AnyObjectId start, AnyObjectId end) throws IOException { return reverse(start, Collections.singleton(end.toObjectId())); } /** * Configure the generator to compute reverse blame (history of deletes). * * @param start * oldest commit to traverse from. The result file will be loaded * from this commit's tree. * @param end * most recent commits to stop traversal at. Usually an active * branch tip, tag, or HEAD. * @return {@code this} * @throws java.io.IOException * the repository cannot be read. */ public BlameCommand reverse(AnyObjectId start, Collection end) throws IOException { startCommit = start.toObjectId(); reverseEndCommits = new ArrayList<>(end); return this; } /** * {@inheritDoc} *

* Generate a list of lines with information about when the lines were * introduced into the file path. */ @Override public BlameResult call() throws GitAPIException { checkCallable(); try (BlameGenerator gen = new BlameGenerator(repo, path)) { if (diffAlgorithm != null) gen.setDiffAlgorithm(diffAlgorithm); if (textComparator != null) gen.setTextComparator(textComparator); if (followFileRenames != null) gen.setFollowFileRenames(followFileRenames.booleanValue()); if (reverseEndCommits != null) gen.reverse(startCommit, reverseEndCommits); else if (startCommit != null) gen.push(null, startCommit); else { gen.prepareHead(); } return gen.computeBlameResult(); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 23499 Content-Disposition: inline; filename="CheckoutCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "32c242f27147dd343582a9e8e417f00fc2d073c8" /* * Copyright (C) 2010, Chris Aniszczyk * Copyright (C) 2011, 2023 Matthias Sohn and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.jgit.api.CheckoutResult.Status; import org.eclipse.jgit.api.errors.CheckoutConflictException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.dircache.Checkout; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata; import org.eclipse.jgit.dircache.DirCacheEditor; import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.AmbiguousObjectException; import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig.EolStreamType; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.NullProgressMonitor; 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.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; /** * Checkout a branch to the working tree. *

* Examples (git is a {@link org.eclipse.jgit.api.Git} instance): *

* Check out an existing branch: * *

 * git.checkout().setName("feature").call();
 * 
*

* Check out paths from the index: * *

 * git.checkout().addPath("file1.txt").addPath("file2.txt").call();
 * 
*

* Check out a path from a commit: * *

 * git.checkout().setStartPoint("HEADˆ").addPath("file1.txt").call();
 * 
* *

* Create a new branch and check it out: * *

 * git.checkout().setCreateBranch(true).setName("newbranch").call();
 * 
*

* Create a new tracking branch for a remote branch and check it out: * *

 * git.checkout().setCreateBranch(true).setName("stable")
 * 		.setUpstreamMode(SetupUpstreamMode.SET_UPSTREAM)
 * 		.setStartPoint("origin/stable").call();
 * 
* * @see Git * documentation about Checkout */ public class CheckoutCommand extends GitCommand { /** * Stage to check out, see {@link CheckoutCommand#setStage(Stage)}. */ public enum Stage { /** * Base stage (#1) */ BASE(DirCacheEntry.STAGE_1), /** * Ours stage (#2) */ OURS(DirCacheEntry.STAGE_2), /** * Theirs stage (#3) */ THEIRS(DirCacheEntry.STAGE_3); private final int number; private Stage(int number) { this.number = number; } } private String name; private boolean forceRefUpdate = false; private boolean forced = false; private boolean createBranch = false; private boolean orphan = false; private CreateBranchCommand.SetupUpstreamMode upstreamMode; private String startPoint = null; private RevCommit startCommit; private Stage checkoutStage = null; private CheckoutResult status; private List paths; private boolean checkoutAllPaths; private Set actuallyModifiedPaths; private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; /** * Constructor for CheckoutCommand * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected CheckoutCommand(Repository repo) { super(repo); this.paths = new ArrayList<>(); } @Override public Ref call() throws GitAPIException, RefAlreadyExistsException, RefNotFoundException, InvalidRefNameException, CheckoutConflictException { checkCallable(); try { processOptions(); if (checkoutAllPaths || !paths.isEmpty()) { checkoutPaths(); status = new CheckoutResult(Status.OK, paths); setCallable(false); return null; } if (createBranch) { try (Git git = new Git(repo)) { CreateBranchCommand command = git.branchCreate(); command.setName(name); if (startCommit != null) command.setStartPoint(startCommit); else command.setStartPoint(startPoint); if (upstreamMode != null) command.setUpstreamMode(upstreamMode); command.call(); } } Ref headRef = repo.exactRef(Constants.HEAD); if (headRef == null) { // TODO Git CLI supports checkout from unborn branch, we should // also allow this throw new UnsupportedOperationException( JGitText.get().cannotCheckoutFromUnbornBranch); } String shortHeadRef = getShortBranchName(headRef); String refLogMessage = "checkout: moving from " + shortHeadRef; //$NON-NLS-1$ ObjectId branch; if (orphan) { if (startPoint == null && startCommit == null) { Result r = repo.updateRef(Constants.HEAD).link( getBranchName()); if (!EnumSet.of(Result.NEW, Result.FORCED).contains(r)) throw new JGitInternalException(MessageFormat.format( JGitText.get().checkoutUnexpectedResult, r.name())); this.status = CheckoutResult.NOT_TRIED_RESULT; return repo.exactRef(Constants.HEAD); } branch = getStartPointObjectId(); } else { branch = repo.resolve(name); if (branch == null) throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, name)); } RevCommit headCommit = null; RevCommit newCommit = null; try (RevWalk revWalk = new RevWalk(repo)) { AnyObjectId headId = headRef.getObjectId(); headCommit = headId == null ? null : revWalk.parseCommit(headId); newCommit = revWalk.parseCommit(branch); } RevTree headTree = headCommit == null ? null : headCommit.getTree(); DirCacheCheckout dco; DirCache dc = repo.lockDirCache(); try { dco = new DirCacheCheckout(repo, headTree, dc, newCommit.getTree()); dco.setFailOnConflict(true); dco.setForce(forced); if (forced) { dco.setFailOnConflict(false); } dco.setProgressMonitor(monitor); try { dco.checkout(); } catch (org.eclipse.jgit.errors.CheckoutConflictException e) { status = new CheckoutResult(Status.CONFLICTS, dco.getConflicts()); throw new CheckoutConflictException(dco.getConflicts(), e); } } finally { dc.unlock(); } Ref ref = repo.findRef(name); if (ref != null && !ref.getName().startsWith(Constants.R_HEADS)) ref = null; String toName = Repository.shortenRefName(name); RefUpdate refUpdate = repo.updateRef(Constants.HEAD, ref == null); refUpdate.setForceUpdate(forceRefUpdate); refUpdate.setRefLogMessage(refLogMessage + " to " + toName, false); //$NON-NLS-1$ Result updateResult; if (ref != null) updateResult = refUpdate.link(ref.getName()); else if (orphan) { updateResult = refUpdate.link(getBranchName()); ref = repo.exactRef(Constants.HEAD); } else { refUpdate.setNewObjectId(newCommit); updateResult = refUpdate.forceUpdate(); } setCallable(false); boolean ok = false; switch (updateResult) { case NEW: ok = true; break; case NO_CHANGE: case FAST_FORWARD: case FORCED: ok = true; break; default: break; } if (!ok) throw new JGitInternalException(MessageFormat.format(JGitText .get().checkoutUnexpectedResult, updateResult.name())); if (!dco.getToBeDeleted().isEmpty()) { status = new CheckoutResult(Status.NONDELETED, dco.getToBeDeleted(), new ArrayList<>(dco.getUpdated().keySet()), dco.getRemoved()); } else status = new CheckoutResult(new ArrayList<>(dco .getUpdated().keySet()), dco.getRemoved()); return ref; } catch (IOException ioe) { throw new JGitInternalException(ioe.getMessage(), ioe); } finally { if (status == null) status = CheckoutResult.ERROR_RESULT; } } private String getShortBranchName(Ref headRef) { if (headRef.isSymbolic()) { return Repository.shortenRefName(headRef.getTarget().getName()); } // Detached HEAD. Every non-symbolic ref in the ref database has an // object id, so this cannot be null. ObjectId id = headRef.getObjectId(); if (id == null) { throw new NullPointerException(); } return id.getName(); } /** * Set progress monitor * * @param monitor * a progress monitor * @return this instance * @since 4.11 */ public CheckoutCommand setProgressMonitor(ProgressMonitor monitor) { if (monitor == null) { monitor = NullProgressMonitor.INSTANCE; } this.monitor = monitor; return this; } /** * Add a single slash-separated path to the list of paths to check out. To * check out all paths, use {@link #setAllPaths(boolean)}. *

* If this option is set, neither the {@link #setCreateBranch(boolean)} nor * {@link #setName(String)} option is considered. In other words, these * options are exclusive. * * @param path * path to update in the working tree and index (with * / as separator) * @return {@code this} */ public CheckoutCommand addPath(String path) { checkCallable(); this.paths.add(path); return this; } /** * Add multiple slash-separated paths to the list of paths to check out. To * check out all paths, use {@link #setAllPaths(boolean)}. *

* If this option is set, neither the {@link #setCreateBranch(boolean)} nor * {@link #setName(String)} option is considered. In other words, these * options are exclusive. * * @param p * paths to update in the working tree and index (with * / as separator) * @return {@code this} * @since 4.6 */ public CheckoutCommand addPaths(List p) { checkCallable(); this.paths.addAll(p); return this; } /** * Set whether to checkout all paths. *

* This options should be used when you want to do a path checkout on the * entire repository and so calling {@link #addPath(String)} is not possible * since empty paths are not allowed. *

* If this option is set, neither the {@link #setCreateBranch(boolean)} nor * {@link #setName(String)} option is considered. In other words, these * options are exclusive. * * @param all * true to checkout all paths, false * otherwise * @return {@code this} * @since 2.0 */ public CheckoutCommand setAllPaths(boolean all) { checkoutAllPaths = all; return this; } /** * Checkout paths into index and working directory, firing a * {@link org.eclipse.jgit.events.WorkingTreeModifiedEvent} if the working * tree was modified. * * @return this instance * @throws java.io.IOException * if an IO error occurred * @throws org.eclipse.jgit.api.errors.RefNotFoundException * if {@code Ref} couldn't be resolved */ protected CheckoutCommand checkoutPaths() throws IOException, RefNotFoundException { actuallyModifiedPaths = new HashSet<>(); Checkout checkout = new Checkout(repo).setRecursiveDeletion(true); DirCache dc = repo.lockDirCache(); try (RevWalk revWalk = new RevWalk(repo); TreeWalk treeWalk = new TreeWalk(repo, revWalk.getObjectReader())) { treeWalk.setRecursive(true); if (!checkoutAllPaths) treeWalk.setFilter(PathFilterGroup.createFromStrings(paths)); if (isCheckoutIndex()) checkoutPathsFromIndex(treeWalk, dc, checkout); else { RevCommit commit = revWalk.parseCommit(getStartPointObjectId()); checkoutPathsFromCommit(treeWalk, dc, commit, checkout); } } finally { try { dc.unlock(); } finally { WorkingTreeModifiedEvent event = new WorkingTreeModifiedEvent( actuallyModifiedPaths, null); actuallyModifiedPaths = null; if (!event.isEmpty()) { repo.fireEvent(event); } } } return this; } private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc, Checkout checkout) throws IOException { DirCacheIterator dci = new DirCacheIterator(dc); treeWalk.addTree(dci); String previousPath = null; final ObjectReader r = treeWalk.getObjectReader(); DirCacheEditor editor = dc.editor(); while (treeWalk.next()) { String path = treeWalk.getPathString(); // Only add one edit per path if (path.equals(previousPath)) continue; final EolStreamType eolStreamType = treeWalk .getEolStreamType(CHECKOUT_OP); final String filterCommand = treeWalk .getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE); editor.add(new PathEdit(path) { @Override public void apply(DirCacheEntry ent) { int stage = ent.getStage(); if (stage > DirCacheEntry.STAGE_0) { if (checkoutStage != null) { if (stage == checkoutStage.number) { checkoutPath(ent, r, checkout, path, new CheckoutMetadata(eolStreamType, filterCommand)); actuallyModifiedPaths.add(path); } } else { UnmergedPathException e = new UnmergedPathException( ent); throw new JGitInternalException(e.getMessage(), e); } } else { checkoutPath(ent, r, checkout, path, new CheckoutMetadata(eolStreamType, filterCommand)); actuallyModifiedPaths.add(path); } } }); previousPath = path; } editor.commit(); } private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc, RevCommit commit, Checkout checkout) throws IOException { treeWalk.addTree(commit.getTree()); final ObjectReader r = treeWalk.getObjectReader(); DirCacheEditor editor = dc.editor(); while (treeWalk.next()) { final ObjectId blobId = treeWalk.getObjectId(0); final FileMode mode = treeWalk.getFileMode(0); final EolStreamType eolStreamType = treeWalk .getEolStreamType(CHECKOUT_OP); final String filterCommand = treeWalk .getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE); final String path = treeWalk.getPathString(); editor.add(new PathEdit(path) { @Override public void apply(DirCacheEntry ent) { if (ent.getStage() != DirCacheEntry.STAGE_0) { // A checkout on a conflicting file stages the checked // out file and resolves the conflict. ent.setStage(DirCacheEntry.STAGE_0); } ent.setObjectId(blobId); ent.setFileMode(mode); checkoutPath(ent, r, checkout, path, new CheckoutMetadata(eolStreamType, filterCommand)); actuallyModifiedPaths.add(path); } }); } editor.commit(); } private void checkoutPath(DirCacheEntry entry, ObjectReader reader, Checkout checkout, String path, CheckoutMetadata checkoutMetadata) { try { checkout.checkout(entry, checkoutMetadata, reader, path); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().checkoutConflictWithFile, entry.getPathString()), e); } } private boolean isCheckoutIndex() { return startCommit == null && startPoint == null; } private ObjectId getStartPointObjectId() throws AmbiguousObjectException, RefNotFoundException, IOException { if (startCommit != null) return startCommit.getId(); String startPointOrHead = (startPoint != null) ? startPoint : Constants.HEAD; ObjectId result = repo.resolve(startPointOrHead); if (result == null) throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, startPointOrHead)); return result; } private void processOptions() throws InvalidRefNameException, RefAlreadyExistsException, IOException { if (((!checkoutAllPaths && paths.isEmpty()) || orphan) && (name == null || !Repository .isValidRefName(Constants.R_HEADS + name))) throw new InvalidRefNameException(MessageFormat.format(JGitText .get().branchNameInvalid, name == null ? "" : name)); //$NON-NLS-1$ if (orphan) { Ref refToCheck = repo.exactRef(getBranchName()); if (refToCheck != null) throw new RefAlreadyExistsException(MessageFormat.format( JGitText.get().refAlreadyExists, name)); } } private String getBranchName() { if (name.startsWith(Constants.R_REFS)) return name; return Constants.R_HEADS + name; } /** * Specify the name of the branch or commit to check out, or the new branch * name. *

* When only checking out paths and not switching branches, use * {@link #setStartPoint(String)} or {@link #setStartPoint(RevCommit)} to * specify from which branch or commit to check out files. *

* When {@link #setCreateBranch(boolean)} is set to true, use * this method to set the name of the new branch to create and * {@link #setStartPoint(String)} or {@link #setStartPoint(RevCommit)} to * specify the start point of the branch. * * @param name * the name of the branch or commit * @return this instance */ public CheckoutCommand setName(String name) { checkCallable(); this.name = name; return this; } /** * Specify whether to create a new branch. *

* If true is used, the name of the new branch must be set * using {@link #setName(String)}. The commit at which to start the new * branch can be set using {@link #setStartPoint(String)} or * {@link #setStartPoint(RevCommit)}; if not specified, HEAD is used. Also * see {@link #setUpstreamMode} for setting up branch tracking. * * @param createBranch * if true a branch will be created as part of the * checkout and set to the specified start point * @return this instance */ public CheckoutCommand setCreateBranch(boolean createBranch) { checkCallable(); this.createBranch = createBranch; return this; } /** * Specify whether to create a new orphan branch. *

* If true is used, the name of the new orphan branch must be * set using {@link #setName(String)}. The commit at which to start the new * orphan branch can be set using {@link #setStartPoint(String)} or * {@link #setStartPoint(RevCommit)}; if not specified, HEAD is used. * * @param orphan * if true a orphan branch will be created as part * of the checkout to the specified start point * @return this instance * @since 3.3 */ public CheckoutCommand setOrphan(boolean orphan) { checkCallable(); this.orphan = orphan; return this; } /** * Specify to force the ref update in case of a branch switch. * * In releases prior to 5.2 this method was called setForce() but this name * was misunderstood to implement native git's --force option, which is not * true. * * @param forceRefUpdate * if true and the branch with the given name * already exists, the start-point of an existing branch will be * set to a new start-point; if false, the existing branch will * not be changed * @return this instance * @since 5.3 */ public CheckoutCommand setForceRefUpdate(boolean forceRefUpdate) { checkCallable(); this.forceRefUpdate = forceRefUpdate; return this; } /** * Allow a checkout even if the workingtree or index differs from HEAD. This * matches native git's '--force' option. * * JGit releases before 5.2 had a method setForce() offering * semantics different from this new setForced(). This old * semantic can now be found in {@link #setForceRefUpdate(boolean)} * * @param forced * if set to true then allow the checkout even if * workingtree or index doesn't match HEAD. Overwrite workingtree * files and index content with the new content in this case. * @return this instance * @since 5.3 */ public CheckoutCommand setForced(boolean forced) { checkCallable(); this.forced = forced; return this; } /** * Set the name of the commit that should be checked out. *

* When checking out files and this is not specified or null, * the index is used. *

* When creating a new branch, this will be used as the start point. If not * specified or null, the current HEAD is used. * * @param startPoint * commit name to check out * @return this instance */ public CheckoutCommand setStartPoint(String startPoint) { checkCallable(); this.startPoint = startPoint; this.startCommit = null; checkOptions(); return this; } /** * Set the commit that should be checked out. *

* When creating a new branch, this will be used as the start point. If not * specified or null, the current HEAD is used. *

* When checking out files and this is not specified or null, * the index is used. * * @param startCommit * commit to check out * @return this instance */ public CheckoutCommand setStartPoint(RevCommit startCommit) { checkCallable(); this.startCommit = startCommit; this.startPoint = null; checkOptions(); return this; } /** * When creating a branch with {@link #setCreateBranch(boolean)}, this can * be used to configure branch tracking. * * @param mode * corresponds to the --track/--no-track options; may be * null * @return this instance */ public CheckoutCommand setUpstreamMode( CreateBranchCommand.SetupUpstreamMode mode) { checkCallable(); this.upstreamMode = mode; return this; } /** * When checking out the index, check out the specified stage (ours or * theirs) for unmerged paths. *

* This can not be used when checking out a branch, only when checking out * the index. * * @param stage * the stage to check out * @return this */ public CheckoutCommand setStage(Stage stage) { checkCallable(); this.checkoutStage = stage; checkOptions(); return this; } /** * Get the result, never null * * @return the result, never null */ public CheckoutResult getResult() { if (status == null) return CheckoutResult.NOT_TRIED_RESULT; return status; } private void checkOptions() { if (checkoutStage != null && !isCheckoutIndex()) throw new IllegalStateException( JGitText.get().cannotCheckoutOursSwitchBranch); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5150 Content-Disposition: inline; filename="CheckoutResult.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "c3077730a5afeda66b64a8d58c30cb2744c6610a" /* * Copyright (C) 2010, Mathias Kinzler and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.util.ArrayList; import java.util.List; /** * Encapsulates the result of a {@link org.eclipse.jgit.api.CheckoutCommand} */ public class CheckoutResult { /** * The {@link Status#ERROR} result; */ public static final CheckoutResult ERROR_RESULT = new CheckoutResult( Status.ERROR, null); /** * The {@link Status#NOT_TRIED} result; */ public static final CheckoutResult NOT_TRIED_RESULT = new CheckoutResult( Status.NOT_TRIED, null); /** * The status */ public enum Status { /** * The call() method has not yet been executed */ NOT_TRIED, /** * Checkout completed normally */ OK, /** * Checkout has not completed because of checkout conflicts */ CONFLICTS, /** * Checkout has completed, but some files could not be deleted */ NONDELETED, /** * An Exception occurred during checkout */ ERROR; } private final Status myStatus; private final List conflictList; private final List undeletedList; private final List modifiedList; private final List removedList; /** * Create a new fail result. If status is {@link Status#CONFLICTS}, * fileList is a list of conflicting files, if status is * {@link Status#NONDELETED}, fileList is a list of not deleted * files. All other values ignore fileList. To create a result * for {@link Status#OK}, see {@link #CheckoutResult(List, List)}. * * @param status * the failure status * @param fileList * the list of files to store, status has to be either * {@link Status#CONFLICTS} or {@link Status#NONDELETED}. */ CheckoutResult(Status status, List fileList) { this(status, fileList, null, null); } /** * Create a new fail result. If status is {@link Status#CONFLICTS}, * fileList is a list of conflicting files, if status is * {@link Status#NONDELETED}, fileList is a list of not deleted * files. All other values ignore fileList. To create a result * for {@link Status#OK}, see {@link #CheckoutResult(List, List)}. * * @param status * the failure status * @param fileList * the list of files to store, status has to be either * {@link Status#CONFLICTS} or {@link Status#NONDELETED}. * @param modified * the modified files * @param removed * the removed files. */ CheckoutResult(Status status, List fileList, List modified, List removed) { myStatus = status; if (status == Status.CONFLICTS) this.conflictList = fileList; else this.conflictList = new ArrayList<>(0); if (status == Status.NONDELETED) this.undeletedList = fileList; else this.undeletedList = new ArrayList<>(0); this.modifiedList = modified; this.removedList = removed; } /** * Create a new OK result with modified and removed files. * * @param modified * the modified files * @param removed * the removed files. */ CheckoutResult(List modified, List removed) { myStatus = Status.OK; this.conflictList = new ArrayList<>(0); this.undeletedList = new ArrayList<>(0); this.modifiedList = modified; this.removedList = removed; } /** * Get status * * @return the status */ public Status getStatus() { return myStatus; } /** * Get list of file that created a checkout conflict * * @return the list of files that created a checkout conflict, or an empty * list if {@link #getStatus()} is not * {@link org.eclipse.jgit.api.CheckoutResult.Status#CONFLICTS}; */ public List getConflictList() { return conflictList; } /** * Get the list of files that could not be deleted during checkout * * @return the list of files that could not be deleted during checkout, or * an empty list if {@link #getStatus()} is not * {@link org.eclipse.jgit.api.CheckoutResult.Status#NONDELETED}; */ public List getUndeletedList() { return undeletedList; } /** * Get the list of files that where modified during checkout * * @return the list of files that where modified during checkout, or an * empty list if {@link #getStatus()} is not * {@link org.eclipse.jgit.api.CheckoutResult.Status#OK} */ public List getModifiedList() { return modifiedList; } /** * Get the list of files that where removed during checkout * * @return the list of files that where removed during checkout, or an empty * list if {@link #getStatus()} is not * {@link org.eclipse.jgit.api.CheckoutResult.Status#OK} */ public List getRemovedList() { return removedList; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 13657 Content-Disposition: inline; filename="CherryPickCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "a353d1a1358a5ac8455d9d5864da8b801a4f5807" /* * Copyright (C) 2010, 2021 Christian Halstrick and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static org.eclipse.jgit.api.CherryPickCommitMessageProvider.ORIGINAL; import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.MultipleParentsNotAllowedException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.UnmergedPathsException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.CommitConfig; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.merge.ContentMergeStrategy; import org.eclipse.jgit.merge.MergeMessageFormatter; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.merge.Merger; import org.eclipse.jgit.merge.ResolveMerger; import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.FileTreeIterator; /** * A class used to execute a {@code cherry-pick} command. It has setters for all * supported options and arguments of this command and a {@link #call()} method * to finally execute the command. Each instance of this class should only be * used for one invocation of the command (means: one call to {@link #call()}) * * @see Git documentation about cherry-pick */ public class CherryPickCommand extends GitCommand { private String reflogPrefix = "cherry-pick:"; //$NON-NLS-1$ private List commits = new ArrayList<>(); private String ourCommitName = null; private CherryPickCommitMessageProvider messageProvider = ORIGINAL; private MergeStrategy strategy = MergeStrategy.RECURSIVE; private ContentMergeStrategy contentStrategy; private Integer mainlineParentNumber; private boolean noCommit = false; private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; /** * Constructor for CherryPickCommand * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected CherryPickCommand(Repository repo) { super(repo); } /** * {@inheritDoc} *

* Executes the {@code Cherry-Pick} command with all the options and * parameters collected by the setter methods (e.g. {@link #include(Ref)} of * this class. Each instance of this class should only be used for one * invocation of the command. Don't call this method twice on an instance. */ @Override public CherryPickResult call() throws GitAPIException, NoMessageException, UnmergedPathsException, ConcurrentRefUpdateException, WrongRepositoryStateException, NoHeadException { RevCommit newHead = null; List cherryPickedRefs = new ArrayList<>(); checkCallable(); try (RevWalk revWalk = new RevWalk(repo)) { // get the head commit Ref headRef = repo.exactRef(Constants.HEAD); if (headRef == null) { throw new NoHeadException( JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported); } newHead = revWalk.parseCommit(headRef.getObjectId()); // loop through all refs to be cherry-picked for (Ref src : commits) { // get the commit to be cherry-picked // handle annotated tags ObjectId srcObjectId = src.getPeeledObjectId(); if (srcObjectId == null) { srcObjectId = src.getObjectId(); } RevCommit srcCommit = revWalk.parseCommit(srcObjectId); // get the parent of the commit to cherry-pick final RevCommit srcParent = getParentCommit(srcCommit, revWalk); String ourName = calculateOurName(headRef); String cherryPickName = srcCommit.getId().abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name() + " " + srcCommit.getShortMessage(); //$NON-NLS-1$ Merger merger = strategy.newMerger(repo); merger.setProgressMonitor(monitor); boolean noProblems; Map failingPaths = null; List unmergedPaths = null; if (merger instanceof ResolveMerger) { ResolveMerger resolveMerger = (ResolveMerger) merger; resolveMerger.setContentMergeStrategy(contentStrategy); resolveMerger.setCommitNames( new String[] { "BASE", ourName, cherryPickName }); //$NON-NLS-1$ resolveMerger .setWorkingTreeIterator(new FileTreeIterator(repo)); if (srcParent != null) { resolveMerger.setBase(srcParent.getTree()); } noProblems = merger.merge(newHead, srcCommit); failingPaths = resolveMerger.getFailingPaths(); unmergedPaths = resolveMerger.getUnmergedPaths(); if (!resolveMerger.getModifiedFiles().isEmpty()) { repo.fireEvent(new WorkingTreeModifiedEvent( resolveMerger.getModifiedFiles(), null)); } } else { noProblems = merger.merge(newHead, srcCommit); } if (noProblems) { if (AnyObjectId.isEqual(newHead.getTree().getId(), merger.getResultTreeId())) { continue; } DirCacheCheckout dco = new DirCacheCheckout(repo, newHead.getTree(), repo.lockDirCache(), merger.getResultTreeId()); dco.setFailOnConflict(true); dco.setProgressMonitor(monitor); dco.checkout(); if (!noCommit) { try (Git git = new Git(getRepository())) { String commitMessage = messageProvider .getCherryPickedCommitMessage(srcCommit); newHead = git.commit() .setMessage(commitMessage) .setReflogComment(reflogPrefix + " " //$NON-NLS-1$ + srcCommit.getShortMessage()) .setAuthor(srcCommit.getAuthorIdent()) .setNoVerify(true).call(); } } cherryPickedRefs.add(src); } else { if (failingPaths != null && !failingPaths.isEmpty()) { return new CherryPickResult(failingPaths); } // there are merge conflicts String message; if (unmergedPaths != null) { CommitConfig cfg = repo.getConfig() .get(CommitConfig.KEY); message = srcCommit.getFullMessage(); char commentChar = cfg.getCommentChar(message); message = new MergeMessageFormatter() .formatWithConflicts(message, unmergedPaths, commentChar); } else { message = srcCommit.getFullMessage(); } if (!noCommit) { repo.writeCherryPickHead(srcCommit.getId()); } repo.writeMergeCommitMsg(message); return CherryPickResult.CONFLICT; } } } catch (IOException e) { throw new JGitInternalException( MessageFormat.format( JGitText.get().exceptionCaughtDuringExecutionOfCherryPickCommand, e), e); } return new CherryPickResult(newHead, cherryPickedRefs); } private RevCommit getParentCommit(RevCommit srcCommit, RevWalk revWalk) throws MultipleParentsNotAllowedException, MissingObjectException, IOException { final RevCommit srcParent; if (mainlineParentNumber == null) { int nOfParents = srcCommit.getParentCount(); if (nOfParents == 0) { return null; } else if (nOfParents != 1) { throw new MultipleParentsNotAllowedException( MessageFormat.format( JGitText.get().canOnlyCherryPickCommitsWithOneParent, srcCommit.name(), Integer.valueOf(srcCommit.getParentCount()))); } srcParent = srcCommit.getParent(0); } else { if (mainlineParentNumber.intValue() > srcCommit.getParentCount()) { throw new JGitInternalException(MessageFormat.format( JGitText.get().commitDoesNotHaveGivenParent, srcCommit, mainlineParentNumber)); } srcParent = srcCommit .getParent(mainlineParentNumber.intValue() - 1); } revWalk.parseHeaders(srcParent); return srcParent; } /** * Include a reference to a commit * * @param commit * a reference to a commit which is cherry-picked to the current * head * @return {@code this} */ public CherryPickCommand include(Ref commit) { checkCallable(); commits.add(commit); return this; } /** * Include a commit * * @param commit * the Id of a commit which is cherry-picked to the current head * @return {@code this} */ public CherryPickCommand include(AnyObjectId commit) { return include(commit.getName(), commit); } /** * Include a commit * * @param name * a name given to the commit * @param commit * the Id of a commit which is cherry-picked to the current head * @return {@code this} */ public CherryPickCommand include(String name, AnyObjectId commit) { return include(new ObjectIdRef.Unpeeled(Storage.LOOSE, name, commit.copy())); } /** * Set the name that should be used in the "OURS" place for conflict markers * * @param ourCommitName * the name that should be used in the "OURS" place for conflict * markers * @return {@code this} */ public CherryPickCommand setOurCommitName(String ourCommitName) { this.ourCommitName = ourCommitName; return this; } /** * Set a message provider for a target cherry-picked commit
* By default original commit message is used (see * {@link CherryPickCommitMessageProvider#ORIGINAL}) * * @param messageProvider * the commit message provider * @return {@code this} * @since 6.9 */ public CherryPickCommand setCherryPickCommitMessageProvider( CherryPickCommitMessageProvider messageProvider) { this.messageProvider = messageProvider; return this; } /** * Set the prefix to use in the reflog. *

* This is primarily needed for implementing rebase in terms of * cherry-picking * * @param prefix * including ":" * @return {@code this} * @since 3.1 */ public CherryPickCommand setReflogPrefix(String prefix) { this.reflogPrefix = prefix; return this; } /** * Set the {@code MergeStrategy} * * @param strategy * The merge strategy to use during this Cherry-pick. * @return {@code this} * @since 3.4 */ public CherryPickCommand setStrategy(MergeStrategy strategy) { this.strategy = strategy; return this; } /** * Sets the content merge strategy to use if the * {@link #setStrategy(MergeStrategy) merge strategy} is "resolve" or * "recursive". * * @param strategy * the {@link ContentMergeStrategy} to be used * @return {@code this} * @since 5.12 */ public CherryPickCommand setContentMergeStrategy( ContentMergeStrategy strategy) { this.contentStrategy = strategy; return this; } /** * Set the (1-based) parent number to diff against * * @param mainlineParentNumber * the (1-based) parent number to diff against. This allows * cherry-picking of merges. * @return {@code this} * @since 3.4 */ public CherryPickCommand setMainlineParentNumber(int mainlineParentNumber) { this.mainlineParentNumber = Integer.valueOf(mainlineParentNumber); return this; } /** * Allows cherry-picking changes without committing them. *

* NOTE: The behavior of cherry-pick is undefined if you pick multiple * commits or if HEAD does not match the index state before cherry-picking. * * @param noCommit * true to cherry-pick without committing, false to commit after * each pick (default) * @return {@code this} * @since 3.5 */ public CherryPickCommand setNoCommit(boolean noCommit) { this.noCommit = noCommit; return this; } /** * The progress monitor associated with the cherry-pick operation. By * default, this is set to NullProgressMonitor * * @see NullProgressMonitor * @param monitor * a {@link org.eclipse.jgit.lib.ProgressMonitor} * @return {@code this} * @since 4.11 */ public CherryPickCommand setProgressMonitor(ProgressMonitor monitor) { if (monitor == null) { monitor = NullProgressMonitor.INSTANCE; } this.monitor = monitor; return this; } private String calculateOurName(Ref headRef) { if (ourCommitName != null) return ourCommitName; String targetRefName = headRef.getTarget().getName(); String headName = Repository.shortenRefName(targetRefName); return headName; } @SuppressWarnings("nls") @Override public String toString() { return "CherryPickCommand [repo=" + repo + ",\ncommits=" + commits + ",\nmainlineParentNumber=" + mainlineParentNumber + ", noCommit=" + noCommit + ", ourCommitName=" + ourCommitName + ", reflogPrefix=" + reflogPrefix + ", strategy=" + strategy + "]"; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2446 Content-Disposition: inline; filename="CherryPickCommitMessageProvider.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "50d65b6fa83319cc43b263b0e107e20f32e7120f" /* * Copyright (c) 2023 Dmitrii Naumenko * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is aailable at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.util.List; import org.eclipse.jgit.revwalk.FooterLine; import org.eclipse.jgit.revwalk.RevCommit; /** * The interface is used to construct a cherry-picked commit message based on * the original commit * * @see #ORIGINAL * @see #ORIGINAL_WITH_REFERENCE * @since 6.9 */ public interface CherryPickCommitMessageProvider { /** * This provider returns the original commit message */ static final CherryPickCommitMessageProvider ORIGINAL = RevCommit::getFullMessage; /** * This provider returns the original commit message with original commit * hash in SHA-1 form.
* Example: * *

	 * my original commit message
	 *
	 * (cherry picked from commit 75355897dc28e9975afed028c1a6d8c6b97b2a3c)
	 * 
* * This is similar to -x flag in git-scm (see https://git-scm.com/docs/git-cherry-pick#_options) */ static final CherryPickCommitMessageProvider ORIGINAL_WITH_REFERENCE = srcCommit -> { String fullMessage = srcCommit.getFullMessage(); // Don't add extra new line after footer (aka trailer) // https://stackoverflow.com/questions/70007405/git-log-exclude-cherry-pick-messages-for-trailers // https://lore.kernel.org/git/7vmx136cdc.fsf@alter.siamese.dyndns.org String separator = messageEndsWithFooter(srcCommit) ? "\n" : "\n\n"; //$NON-NLS-1$//$NON-NLS-2$ String revisionString = srcCommit.getName(); return String.format("%s%s(cherry picked from commit %s)", //$NON-NLS-1$ fullMessage, separator, revisionString); }; /** * @param srcCommit * original cherry-picked commit * @return target cherry-picked commit message */ String getCherryPickedCommitMessage(RevCommit srcCommit); private static boolean messageEndsWithFooter(RevCommit srcCommit) { byte[] rawBuffer = srcCommit.getRawBuffer(); List footers = srcCommit.getFooterLines(); int maxFooterEnd = footers.stream().mapToInt(FooterLine::getEndOffset) .max().orElse(-1); return rawBuffer.length == maxFooterEnd; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3851 Content-Disposition: inline; filename="CherryPickResult.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "995f8903896c25d9c6286d08863a0f3739a20724" /* * Copyright (C) 2011, Philipp Thun and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.util.List; import java.util.Map; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; import org.eclipse.jgit.revwalk.RevCommit; /** * Encapsulates the result of a {@link org.eclipse.jgit.api.CherryPickCommand}. */ public class CherryPickResult { /** * The cherry-pick status */ public enum CherryPickStatus { /** Cherry-pick succeeded */ OK { @Override public String toString() { return "Ok"; //$NON-NLS-1$ } }, /** Cherry-pick failed */ FAILED { @Override public String toString() { return "Failed"; //$NON-NLS-1$ } }, /** Cherry-pick found conflicts to be resolved */ CONFLICTING { @Override public String toString() { return "Conflicting"; //$NON-NLS-1$ } } } private final CherryPickStatus status; private final RevCommit newHead; private final List cherryPickedRefs; private final Map failingPaths; /** * Constructor for CherryPickResult * * @param newHead * commit the head points at after this cherry-pick * @param cherryPickedRefs * list of successfully cherry-picked Ref's */ public CherryPickResult(RevCommit newHead, List cherryPickedRefs) { this.status = CherryPickStatus.OK; this.newHead = newHead; this.cherryPickedRefs = cherryPickedRefs; this.failingPaths = null; } /** * Constructor for CherryPickResult * * @param failingPaths * list of paths causing this cherry-pick to fail (see * {@link org.eclipse.jgit.merge.ResolveMerger#getFailingPaths()} * for details) */ public CherryPickResult(Map failingPaths) { this.status = CherryPickStatus.FAILED; this.newHead = null; this.cherryPickedRefs = null; this.failingPaths = failingPaths; } private CherryPickResult(CherryPickStatus status) { this.status = status; this.newHead = null; this.cherryPickedRefs = null; this.failingPaths = null; } /** * A CherryPickResult with status * {@link CherryPickStatus#CONFLICTING} */ public static final CherryPickResult CONFLICT = new CherryPickResult( CherryPickStatus.CONFLICTING); /** * Get status * * @return the status this cherry-pick resulted in */ public CherryPickStatus getStatus() { return status; } /** * Get the new head after this cherry-pick * * @return the commit the head points at after this cherry-pick, * null if {@link #getStatus} is not * {@link org.eclipse.jgit.api.CherryPickResult.CherryPickStatus#OK} */ public RevCommit getNewHead() { return newHead; } /** * Get the cherry-picked {@code Ref}s * * @return the list of successfully cherry-picked Ref's, * null if {@link #getStatus} is not * {@link org.eclipse.jgit.api.CherryPickResult.CherryPickStatus#OK} */ public List getCherryPickedRefs() { return cherryPickedRefs; } /** * Get the list of paths causing this cherry-pick to fail * * @return the list of paths causing this cherry-pick to fail (see * {@link org.eclipse.jgit.merge.ResolveMerger#getFailingPaths()} * for details), null if {@link #getStatus} is not * {@link org.eclipse.jgit.api.CherryPickResult.CherryPickStatus#FAILED} */ public Map getFailingPaths() { return failingPaths; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7217 Content-Disposition: inline; filename="CleanCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "a4a0c49f45321c64ea639c3d7e04b298653cde46" /* * Copyright (C) 2011, Chris Aniszczyk * Copyright (C) 2011, Abhishek Bhatnagar and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static org.eclipse.jgit.lib.Constants.DOT_GIT; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.Set; import java.util.TreeSet; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.Paths; /** * Remove untracked files from the working tree * * @see Git documentation about Clean */ public class CleanCommand extends GitCommand> { private Set paths = Collections.emptySet(); private boolean dryRun; private boolean directories; private boolean ignore = true; private boolean force = false; /** * Constructor for CleanCommand * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected CleanCommand(Repository repo) { super(repo); } /** * {@inheritDoc} *

* Executes the {@code clean} command with all the options and parameters * collected by the setter methods of this class. Each instance of this * class should only be used for one invocation of the command (means: one * call to {@link #call()}) */ @Override public Set call() throws NoWorkTreeException, GitAPIException { Set files = new TreeSet<>(); try { StatusCommand command = new StatusCommand(repo); Status status = command.call(); Set untrackedFiles = new TreeSet<>(status.getUntracked()); Set untrackedDirs = new TreeSet<>( status.getUntrackedFolders()); FS fs = getRepository().getFS(); for (String p : status.getIgnoredNotInIndex()) { File f = new File(repo.getWorkTree(), p); if (fs.isFile(f) || fs.isSymLink(f)) { untrackedFiles.add(p); } else if (fs.isDirectory(f)) { untrackedDirs.add(p); } } Set filtered = filterFolders(untrackedFiles, untrackedDirs); Set notIgnoredFiles = filterIgnorePaths(filtered, status.getIgnoredNotInIndex(), true); Set notIgnoredDirs = filterIgnorePaths(untrackedDirs, status.getIgnoredNotInIndex(), false); for (String file : notIgnoredFiles) { if (paths.isEmpty() || paths.contains(file)) { files = cleanPath(file, files); } } for (String dir : notIgnoredDirs) { if (paths.isEmpty() || paths.contains(dir)) { files = cleanPath(dir, files); } } } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } finally { if (!dryRun && !files.isEmpty()) { repo.fireEvent(new WorkingTreeModifiedEvent(null, files)); } } return files; } /** * When dryRun is false, deletes the specified path from disk. If dryRun is * true, no paths are actually deleted. In both cases, the paths that would * have been deleted are added to inFiles and returned. * * Paths that are directories are recursively deleted when * {@link #directories} is true. Paths that are git repositories are * recursively deleted when {@link #directories} and {@link #force} are both * true. * * @param path * The path to be cleaned * @param inFiles * A set of strings representing the files that have been cleaned * already, the path to be cleaned will be added to this set * before being returned. * * @return a set of strings with the cleaned path added to it * @throws IOException * if an IO error occurred */ private Set cleanPath(String path, Set inFiles) throws IOException { File curFile = new File(repo.getWorkTree(), path); if (curFile.isDirectory()) { if (directories) { // Is this directory a git repository? if (new File(curFile, DOT_GIT).exists()) { if (force) { if (!dryRun) { FileUtils.delete(curFile, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); } inFiles.add(path + '/'); } } else { if (!dryRun) { FileUtils.delete(curFile, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); } inFiles.add(path + '/'); } } } else { if (!dryRun) { FileUtils.delete(curFile, FileUtils.SKIP_MISSING); } inFiles.add(path); } return inFiles; } private Set filterIgnorePaths(Set inputPaths, Set ignoredNotInIndex, boolean exact) { if (ignore) { Set filtered = new TreeSet<>(inputPaths); for (String path : inputPaths) { for (String ignored : ignoredNotInIndex) { if ((exact && path.equals(ignored)) || (!exact && Paths.isEqualOrPrefix(ignored, path))) { filtered.remove(path); break; } } } return filtered; } return inputPaths; } private Set filterFolders(Set untracked, Set untrackedFolders) { Set filtered = new TreeSet<>(untracked); for (String file : untracked) { for (String folder : untrackedFolders) { if (Paths.isEqualOrPrefix(folder, file)) { filtered.remove(file); break; } } } return filtered; } /** * If paths are set, only these paths are affected by the cleaning. * * @param paths * the paths to set (with / as separator) * @return {@code this} */ public CleanCommand setPaths(Set paths) { this.paths = paths; return this; } /** * If dryRun is set, the paths in question will not actually be deleted. * * @param dryRun * whether to do a dry run or not * @return {@code this} */ public CleanCommand setDryRun(boolean dryRun) { this.dryRun = dryRun; return this; } /** * If force is set, directories that are git repositories will also be * deleted. * * @param force * whether or not to delete git repositories * @return {@code this} * @since 4.5 */ public CleanCommand setForce(boolean force) { this.force = force; return this; } /** * If dirs is set, in addition to files, also clean directories. * * @param dirs * whether to clean directories too, or only files. * @return {@code this} */ public CleanCommand setCleanDirectories(boolean dirs) { directories = dirs; return this; } /** * If ignore is set, don't report/clean files/directories that are ignored * by a .gitignore. otherwise do handle them. * * @param ignore * whether to respect .gitignore or not. * @return {@code this} */ public CleanCommand setIgnore(boolean ignore) { this.ignore = ignore; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 26204 Content-Disposition: inline; filename="CloneCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "4a536b9534274431501cb909da455714e680cd79" /* * Copyright (C) 2011, 2022 Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.text.MessageFormat; import java.time.Instant; import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.dircache.DirCache; 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.internal.util.ShutdownHook; 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; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.transport.FetchResult; 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 * * @see Git documentation about Clone */ public class CloneCommand extends TransportCommand { private String uri; private File directory; private File gitDir; private boolean bare; private boolean relativePaths; private FS fs; private String remote = Constants.DEFAULT_REMOTE_NAME; private String branch = Constants.HEAD; private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; private boolean cloneAllBranches; private boolean mirror; private boolean cloneSubmodules; private boolean noCheckout; private Collection branchesToClone; private Callback callback; private boolean directoryExistsInitially; private boolean gitDirExistsInitially; private FETCH_TYPE fetchType; private TagOpt tagOption; private Integer depth; private Instant shallowSince; private List shallowExcludes = new ArrayList<>(); private ShutdownHook.Listener shutdownListener = this::cleanup; private enum FETCH_TYPE { MULTIPLE_BRANCHES, ALL_BRANCHES, MIRROR } /** * Callback for status of clone operation. * * @since 4.8 */ public interface Callback { /** * Notify initialized submodules. * * @param submodules * the submodules * */ void initializedSubmodules(Collection 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 */ public CloneCommand() { super(null); } /** * Get the git directory. This is primarily used for tests. * * @return the git directory */ @Nullable File getDirectory() { return directory; } /** * {@inheritDoc} *

* Executes the {@code Clone} command. * * The Git instance returned by this command needs to be closed by the * caller to free resources held by the underlying {@link Repository} * instance. It is recommended to call this method as soon as you don't need * a reference to this {@link Git} instance and the underlying * {@link Repository} instance anymore. */ @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), e); } setFetchType(); @SuppressWarnings("resource") // Closed by caller Repository repository = init(); FetchResult fetchResult = null; ShutdownHook.INSTANCE.register(shutdownListener); try { 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), e); } catch (GitAPIException | RuntimeException e) { if (repository != null) { repository.close(); } cleanup(); throw e; } finally { ShutdownHook.INSTANCE.unregister(shutdownListener); } 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 void setFetchType() { if (mirror) { fetchType = FETCH_TYPE.MIRROR; setBare(true); } else if (cloneAllBranches) { fetchType = FETCH_TYPE.ALL_BRANCHES; } else if (branchesToClone != null && !branchesToClone.isEmpty()) { fetchType = FETCH_TYPE.MULTIPLE_BRANCHES; } else { // Default: neither mirror nor all nor specific refs given fetchType = FETCH_TYPE.ALL_BRANCHES; } } private static boolean isNonEmptyDirectory(File dir) { if (dir != null && dir.exists()) { File[] files = dir.listFiles(); return files != null && files.length != 0; } return false; } void verifyDirectories(URIish u) { if (directory == null && gitDir == null) { directory = new File(u.getHumanishName() + (bare ? Constants.DOT_GIT_EXT : "")); //$NON-NLS-1$ } directoryExistsInitially = directory != null && directory.exists(); gitDirExistsInitially = gitDir != null && gitDir.exists(); validateDirs(directory, gitDir, bare); if (isNonEmptyDirectory(directory)) { throw new JGitInternalException(MessageFormat.format( JGitText.get().cloneNonEmptyDirectory, directory.getName())); } if (isNonEmptyDirectory(gitDir)) { throw new JGitInternalException(MessageFormat.format( JGitText.get().cloneNonEmptyDirectory, gitDir.getName())); } } private Repository init() throws GitAPIException { InitCommand command = Git.init(); command.setBare(bare); command.setRelativeDirs(relativePaths); if (fs != null) { command.setFs(fs); } if (directory != null) { command.setDirectory(directory); } if (gitDir != null) { command.setGitDir(gitDir); } return command.call().getRepository(); } private FetchResult fetch(Repository clonedRepo, URIish u) throws URISyntaxException, org.eclipse.jgit.api.errors.TransportException, IOException, GitAPIException { // create the remote config and save it RemoteConfig config = new RemoteConfig(clonedRepo.getConfig(), remote); config.addURI(u); boolean fetchAll = fetchType == FETCH_TYPE.ALL_BRANCHES || fetchType == FETCH_TYPE.MIRROR; config.setFetchRefSpecs(calculateRefSpecs(fetchType, config.getName())); config.setMirror(fetchType == FETCH_TYPE.MIRROR); if (tagOption != null) { config.setTagOpt(tagOption); } config.update(clonedRepo.getConfig()); clonedRepo.getConfig().save(); // run the fetch command FetchCommand command = new FetchCommand(clonedRepo); command.setRemote(remote); command.setProgressMonitor(monitor); if (tagOption != null) { command.setTagOpt(tagOption); } else { command.setTagOpt( fetchAll ? TagOpt.FETCH_TAGS : TagOpt.AUTO_FOLLOW); } command.setInitialBranch(branch); if (depth != null) { command.setDepth(depth.intValue()); } if (shallowSince != null) { command.setShallowSince(shallowSince); } command.setShallowExcludes(shallowExcludes); configure(command); return command.call(); } private List calculateRefSpecs(FETCH_TYPE type, String remoteName) { List specs = new ArrayList<>(); if (type == FETCH_TYPE.MIRROR) { specs.add(new RefSpec().setForceUpdate(true).setSourceDestination( Constants.R_REFS + '*', Constants.R_REFS + '*')); } else { RefSpec heads = new RefSpec(); heads = heads.setForceUpdate(true); final String dst = (bare ? Constants.R_HEADS : Constants.R_REMOTES + remoteName + '/') + '*'; heads = heads.setSourceDestination(Constants.R_HEADS + '*', dst); if (type == FETCH_TYPE.MULTIPLE_BRANCHES) { RefSpec tags = new RefSpec().setForceUpdate(true) .setSourceDestination(Constants.R_TAGS + '*', Constants.R_TAGS + '*'); for (String selectedRef : branchesToClone) { if (heads.matchSource(selectedRef)) { specs.add(heads.expandFromSource(selectedRef)); } else if (tags.matchSource(selectedRef)) { specs.add(tags.expandFromSource(selectedRef)); } } } else { // We'll fetch the tags anyway. specs.add(heads); } } return specs; } private void checkout(Repository clonedRepo, FetchResult result) throws MissingObjectException, IncorrectObjectTypeException, IOException, GitAPIException { Ref head = null; if (branch.equals(Constants.HEAD)) { Ref foundBranch = findBranchToCheckout(result); if (foundBranch != null) head = foundBranch; } if (head == null) { head = result.getAdvertisedRef(branch); if (head == null) head = result.getAdvertisedRef(Constants.R_HEADS + branch); if (head == null) head = result.getAdvertisedRef(Constants.R_TAGS + branch); } if (head == null || head.getObjectId() == null) return; // TODO throw exception? if (head.getName().startsWith(Constants.R_HEADS)) { final RefUpdate newHead = clonedRepo.updateRef(Constants.HEAD); newHead.disableRefLog(); newHead.link(head.getName()); addMergeConfig(clonedRepo, head); } final RevCommit commit = parseCommit(clonedRepo, head); boolean detached = !head.getName().startsWith(Constants.R_HEADS); RefUpdate u = clonedRepo.updateRef(Constants.HEAD, detached); u.setNewObjectId(commit.getId()); u.forceUpdate(); if (!bare && !noCheckout) { DirCache dc = clonedRepo.lockDirCache(); DirCacheCheckout co = new DirCacheCheckout(clonedRepo, dc, commit.getTree()); co.setProgressMonitor(monitor); co.checkout(); if (cloneSubmodules) cloneSubmodules(clonedRepo); } } private void cloneSubmodules(Repository clonedRepo) throws IOException, GitAPIException { SubmoduleInitCommand init = new SubmoduleInitCommand(clonedRepo); Collection 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()) { try (Repository subRepo = walk.getRepository()) { if (subRepo != null) { cloneSubmodules(subRepo); } } } } } private Ref findBranchToCheckout(FetchResult result) { final Ref idHEAD = result.getAdvertisedRef(Constants.HEAD); ObjectId headId = idHEAD != null ? idHEAD.getObjectId() : null; if (headId == null) { return null; } if (idHEAD != null && idHEAD.isSymbolic()) { return idHEAD.getTarget(); } Ref master = result.getAdvertisedRef(Constants.R_HEADS + Constants.MASTER); ObjectId objectId = master != null ? master.getObjectId() : null; if (headId.equals(objectId)) { return master; } Ref foundBranch = null; for (Ref r : result.getAdvertisedRefs()) { final String n = r.getName(); if (!n.startsWith(Constants.R_HEADS)) continue; if (headId.equals(r.getObjectId())) { foundBranch = r; break; } } return foundBranch; } private void addMergeConfig(Repository clonedRepo, Ref head) throws IOException { String branchName = Repository.shortenRefName(head.getName()); clonedRepo.getConfig().setString(ConfigConstants.CONFIG_BRANCH_SECTION, branchName, ConfigConstants.CONFIG_KEY_REMOTE, remote); clonedRepo.getConfig().setString(ConfigConstants.CONFIG_BRANCH_SECTION, branchName, ConfigConstants.CONFIG_KEY_MERGE, head.getName()); String autosetupRebase = clonedRepo.getConfig().getString( ConfigConstants.CONFIG_BRANCH_SECTION, null, ConfigConstants.CONFIG_KEY_AUTOSETUPREBASE); if (ConfigConstants.CONFIG_KEY_ALWAYS.equals(autosetupRebase) || ConfigConstants.CONFIG_KEY_REMOTE.equals(autosetupRebase)) clonedRepo.getConfig().setEnum( ConfigConstants.CONFIG_BRANCH_SECTION, branchName, ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.REBASE); clonedRepo.getConfig().save(); } private RevCommit parseCommit(Repository clonedRepo, Ref ref) throws MissingObjectException, IncorrectObjectTypeException, IOException { final RevCommit commit; try (RevWalk rw = new RevWalk(clonedRepo)) { commit = rw.parseCommit(ref.getObjectId()); } return commit; } /** * Set the URI to clone from * * @param uri * the URI to clone from, or {@code null} to unset the URI. The * URI must be set before {@link #call} is called. * @return this instance */ public CloneCommand setURI(String uri) { this.uri = uri; return this; } /** * The optional directory associated with the clone operation. If the * directory isn't set, a name associated with the source uri will be used. * * @see URIish#getHumanishName() * @param directory * the directory to clone to, or {@code null} if the directory * name should be taken from the source uri * @return this instance * @throws java.lang.IllegalStateException * if the combination of directory, gitDir and bare is illegal. * E.g. if for a non-bare repository directory and gitDir point * to the same directory of if for a bare repository both * directory and gitDir are specified */ public CloneCommand setDirectory(File directory) { validateDirs(directory, gitDir, bare); this.directory = directory; return this; } /** * Set the repository meta directory (.git) * * @param gitDir * the repository meta directory, or {@code null} to choose one * automatically at clone time * @return this instance * @throws java.lang.IllegalStateException * if the combination of directory, gitDir and bare is illegal. * E.g. if for a non-bare repository directory and gitDir point * to the same directory of if for a bare repository both * directory and gitDir are specified * @since 3.6 */ public CloneCommand setGitDir(File gitDir) { validateDirs(directory, gitDir, bare); this.gitDir = gitDir; return this; } /** * Set whether the cloned repository shall be bare * * @param bare * whether the cloned repository is bare or not * @return this instance * @throws java.lang.IllegalStateException * if the combination of directory, gitDir and bare is illegal. * E.g. if for a non-bare repository directory and gitDir point * to the same directory of if for a bare repository both * directory and gitDir are specified */ public CloneCommand setBare(boolean bare) throws IllegalStateException { validateDirs(directory, gitDir, bare); this.bare = bare; return this; } /** * Set whether the cloned repository shall use relative paths for GIT_DIR * and GIT_WORK_TREE * * @param relativePaths * if true, use relative paths for GIT_DIR and GIT_WORK_TREE * @return this instance * @since 7.2 */ public CloneCommand setRelativePaths(boolean relativePaths) { this.relativePaths = relativePaths; return this; } /** * Set the file system abstraction to be used for repositories created by * this command. * * @param fs * the abstraction. * @return {@code this} (for chaining calls). * @since 4.10 */ public CloneCommand setFs(FS fs) { this.fs = fs; return this; } /** * The remote name used to keep track of the upstream repository for the * clone operation. If no remote name is set, the default value of * Constants.DEFAULT_REMOTE_NAME will be used. * * @see Constants#DEFAULT_REMOTE_NAME * @param remote * name that keeps track of the upstream repository. * {@code null} means to use DEFAULT_REMOTE_NAME. * @return this instance */ public CloneCommand setRemote(String remote) { if (remote == null) { remote = Constants.DEFAULT_REMOTE_NAME; } this.remote = remote; return this; } /** * Set the initial branch * * @param branch * the initial branch to check out when cloning the repository. * Can be specified as ref name (refs/heads/master), * branch name (master) or tag name * (v1.2.3). The default is to use the branch * pointed to by the cloned repository's HEAD and can be * requested by passing {@code null} or HEAD. * @return this instance */ public CloneCommand setBranch(String branch) { if (branch == null) { branch = Constants.HEAD; } this.branch = branch; return this; } /** * The progress monitor associated with the clone operation. By default, * this is set to NullProgressMonitor * * @see NullProgressMonitor * @param monitor * a {@link org.eclipse.jgit.lib.ProgressMonitor} * @return {@code this} */ public CloneCommand setProgressMonitor(ProgressMonitor monitor) { if (monitor == null) { monitor = NullProgressMonitor.INSTANCE; } this.monitor = monitor; return this; } /** * Set whether all branches have to be fetched. *

* If {@code false}, use {@link #setBranchesToClone(Collection)} to define * what will be cloned. If neither are set, all branches will be cloned. *

* * @param cloneAllBranches * {@code true} when all branches have to be fetched (indicates * wildcard in created fetch refspec), {@code false} otherwise. * @return {@code this} */ public CloneCommand setCloneAllBranches(boolean cloneAllBranches) { this.cloneAllBranches = cloneAllBranches; return this; } /** * Set up a mirror of the source repository. This implies that a bare * repository will be created. Compared to {@link #setBare}, * {@code #setMirror} not only maps local branches of the source to local * branches of the target, it maps all refs (including remote-tracking * branches, notes etc.) and sets up a refspec configuration such that all * these refs are overwritten by a git remote update in the target * repository. * * @param mirror * whether to mirror all refs from the source repository * * @return {@code this} * @since 5.6 */ public CloneCommand setMirror(boolean mirror) { this.mirror = mirror; return this; } /** * Set whether to clone submodules * * @param cloneSubmodules * true to initialize and update submodules. Ignored when * {@link #setBare(boolean)} is set to true. * @return {@code this} */ public CloneCommand setCloneSubmodules(boolean cloneSubmodules) { this.cloneSubmodules = cloneSubmodules; return this; } /** * Set the branches or tags to clone. *

* This is ignored if {@link #setCloneAllBranches(boolean) * setCloneAllBranches(true)} or {@link #setMirror(boolean) setMirror(true)} * is used. If {@code branchesToClone} is {@code null} or empty, it's also * ignored. *

* * @param branchesToClone * collection of branches to clone. Must be specified as full ref * names (e.g. {@code refs/heads/master} or * {@code refs/tags/v1.0.0}). * @return {@code this} */ public CloneCommand setBranchesToClone(Collection branchesToClone) { this.branchesToClone = branchesToClone; return this; } /** * Set the tag option used for the remote configuration explicitly. * * @param tagOption * tag option to be used for the remote config * @return {@code this} * @since 5.8 */ public CloneCommand setTagOption(TagOpt tagOption) { this.tagOption = tagOption; return this; } /** * Set the --no-tags option. Tags are not cloned now and the remote * configuration is initialized with the --no-tags option as well. * * @return {@code this} * @since 5.8 */ public CloneCommand setNoTags() { return setTagOption(TagOpt.NO_TAGS); } /** * Set whether to skip checking out a branch * * @param noCheckout * if set to true no branch will be checked out * after the clone. This enhances performance of the clone * command when there is no need for a checked out branch. * @return {@code this} */ public CloneCommand setNoCheckout(boolean noCheckout) { this.noCheckout = noCheckout; 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; } /** * Creates a shallow clone with a history truncated to the specified number * of commits. * * @param depth * the depth * @return {@code this} * * @since 6.3 */ public CloneCommand setDepth(int depth) { if (depth < 1) { throw new IllegalArgumentException(JGitText.get().depthMustBeAt1); } this.depth = Integer.valueOf(depth); return this; } /** * Creates a shallow clone with a history after the specified time. * * @param shallowSince * the timestammp; must not be {@code null} * @return {@code this} * * @since 6.3 */ public CloneCommand setShallowSince(@NonNull OffsetDateTime shallowSince) { this.shallowSince = shallowSince.toInstant(); return this; } /** * Creates a shallow clone with a history after the specified time. * * @param shallowSince * the timestammp; must not be {@code null} * @return {@code this} * * @since 6.3 */ public CloneCommand setShallowSince(@NonNull Instant shallowSince) { this.shallowSince = shallowSince; return this; } /** * Creates a shallow clone with a history, excluding commits reachable from * a specified remote branch or tag. * * @param shallowExclude * the ref or commit; must not be {@code null} * @return {@code this} * * @since 6.3 */ public CloneCommand addShallowExclude(@NonNull String shallowExclude) { shallowExcludes.add(shallowExclude); return this; } /** * Creates a shallow clone with a history, excluding commits reachable from * a specified remote branch or tag. * * @param shallowExclude * the commit; must not be {@code null} * @return {@code this} * * @since 6.3 */ public CloneCommand addShallowExclude(@NonNull ObjectId shallowExclude) { shallowExcludes.add(shallowExclude.name()); return this; } private static void validateDirs(File directory, File gitDir, boolean bare) throws IllegalStateException { if (directory != null) { if (directory.exists() && !directory.isDirectory()) { throw new IllegalStateException(MessageFormat.format( JGitText.get().initFailedDirIsNoDirectory, directory)); } if (gitDir != null && gitDir.exists() && !gitDir.isDirectory()) { throw new IllegalStateException(MessageFormat.format( JGitText.get().initFailedGitDirIsNoDirectory, gitDir)); } if (bare) { if (gitDir != null && !gitDir.equals(directory)) throw new IllegalStateException(MessageFormat.format( JGitText.get().initFailedBareRepoDifferentDirs, gitDir, directory)); } else { if (gitDir != null && gitDir.equals(directory)) throw new IllegalStateException(MessageFormat.format( JGitText.get().initFailedNonBareRepoSameDirs, gitDir, directory)); } } } 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(gitDir); } } } 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 { File[] files = file.listFiles(); if (files == null) { return; } for (File child : files) { FileUtils.delete(child, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 35813 Content-Disposition: inline; filename="CommitCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "a7d409c3f58be75a24f83b0cf19a5ddba84be500" /* * Copyright (C) 2010-2012, Christian Halstrick and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.api.errors.AbortedByHookException; import org.eclipse.jgit.api.errors.CanceledException; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; import org.eclipse.jgit.api.errors.EmptyCommitException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.ServiceUnavailableException; import org.eclipse.jgit.api.errors.UnmergedPathsException; import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuildIterator; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.hooks.CommitMsgHook; import org.eclipse.jgit.hooks.Hooks; import org.eclipse.jgit.hooks.PostCommitHook; import org.eclipse.jgit.hooks.PreCommitHook; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.CommitConfig; import org.eclipse.jgit.lib.CommitConfig.CleanupMode; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.GpgConfig; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.lib.Signer; import org.eclipse.jgit.lib.Signers; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.TreeWalk.OperationType; import org.eclipse.jgit.util.ChangeIdUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A class used to execute a {@code Commit} command. It has setters for all * supported options and arguments of this command and a {@link #call()} method * to finally execute the command. * * @see Git documentation about Commit */ public class CommitCommand extends GitCommand { private static final Logger log = LoggerFactory .getLogger(CommitCommand.class); private PersonIdent author; private PersonIdent committer; private String message; private boolean all; private List only = new ArrayList<>(); private boolean[] onlyProcessed; private boolean amend; private boolean insertChangeId; /** * parents this commit should have. The current HEAD will be in this list * and also all commits mentioned in .git/MERGE_HEAD */ private List parents = new ArrayList<>(); private String reflogComment; private boolean useDefaultReflogMessage = true; /** * Setting this option bypasses the pre-commit and commit-msg hooks. */ private boolean noVerify; private HashMap hookOutRedirect = new HashMap<>(3); private HashMap hookErrRedirect = new HashMap<>(3); private Boolean allowEmpty; private Boolean signCommit; private String signingKey; private Signer signer; private GpgConfig gpgConfig; private CredentialsProvider credentialsProvider; private @NonNull CleanupMode cleanupMode = CleanupMode.VERBATIM; private boolean cleanDefaultIsStrip = true; private Character commentChar; /** * Constructor for CommitCommand * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected CommitCommand(Repository repo) { super(repo); this.credentialsProvider = CredentialsProvider.getDefault(); } /** * {@inheritDoc} *

* Executes the {@code commit} command with all the options and parameters * collected by the setter methods of this class. Each instance of this * class should only be used for one invocation of the command (means: one * call to {@link #call()}) * * @throws ServiceUnavailableException * if signing service is not available e.g. since it isn't * installed */ @Override public RevCommit call() throws GitAPIException, AbortedByHookException, ConcurrentRefUpdateException, NoHeadException, NoMessageException, ServiceUnavailableException, UnmergedPathsException, WrongRepositoryStateException { checkCallable(); Collections.sort(only); try (RevWalk rw = new RevWalk(repo)) { RepositoryState state = repo.getRepositoryState(); if (!state.canCommit()) throw new WrongRepositoryStateException(MessageFormat.format( JGitText.get().cannotCommitOnARepoWithState, state.name())); if (!noVerify) { Hooks.preCommit(repo, hookOutRedirect.get(PreCommitHook.NAME), hookErrRedirect.get(PreCommitHook.NAME)) .call(); } processOptions(state, rw); if (all && !repo.isBare()) { try (Git git = new Git(repo)) { git.add().addFilepattern(".") //$NON-NLS-1$ .setUpdate(true).call(); } catch (NoFilepatternException e) { // should really not happen throw new JGitInternalException(e.getMessage(), e); } } Ref head = repo.exactRef(Constants.HEAD); if (head == null) throw new NoHeadException( JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported); // determine the current HEAD and the commit it is referring to ObjectId headId = repo.resolve(Constants.HEAD + "^{commit}"); //$NON-NLS-1$ if (headId == null && amend) throw new WrongRepositoryStateException( JGitText.get().commitAmendOnInitialNotPossible); if (headId != null) { if (amend) { RevCommit previousCommit = rw.parseCommit(headId); for (RevCommit p : previousCommit.getParents()) parents.add(p.getId()); if (author == null) author = previousCommit.getAuthorIdent(); } else { parents.add(0, headId); } } if (!noVerify) { message = Hooks .commitMsg(repo, hookOutRedirect.get(CommitMsgHook.NAME), hookErrRedirect.get(CommitMsgHook.NAME)) .setCommitMessage(message).call(); } CommitConfig config = null; if (CleanupMode.DEFAULT.equals(cleanupMode)) { config = repo.getConfig().get(CommitConfig.KEY); cleanupMode = config.resolve(cleanupMode, cleanDefaultIsStrip); } char comments = (char) 0; if (CleanupMode.STRIP.equals(cleanupMode) || CleanupMode.SCISSORS.equals(cleanupMode)) { if (commentChar == null) { if (config == null) { config = repo.getConfig().get(CommitConfig.KEY); } if (config.isAutoCommentChar()) { // We're supposed to pick a character that isn't used, // but then cleaning up won't remove any lines. So don't // bother. comments = (char) 0; cleanupMode = CleanupMode.WHITESPACE; } else { comments = config.getCommentChar(); } } else { comments = commentChar.charValue(); } } message = CommitConfig.cleanText(message, cleanupMode, comments); RevCommit revCommit; DirCache index = repo.lockDirCache(); try (ObjectInserter odi = repo.newObjectInserter()) { if (!only.isEmpty()) index = createTemporaryIndex(headId, index, rw); // Write the index as tree to the object database. This may // fail for example when the index contains unmerged paths // (unresolved conflicts) ObjectId indexTreeId = index.writeTree(odi); if (insertChangeId) insertChangeId(indexTreeId); checkIfEmpty(rw, headId, indexTreeId); // Create a Commit object, populate it and write it CommitBuilder commit = new CommitBuilder(); commit.setCommitter(committer); commit.setAuthor(author); commit.setMessage(message); commit.setParentIds(parents); commit.setTreeId(indexTreeId); if (signCommit.booleanValue()) { sign(commit); } ObjectId commitId = odi.insert(commit); odi.flush(); revCommit = rw.parseCommit(commitId); updateRef(state, headId, revCommit, commitId); } finally { index.unlock(); } try { Hooks.postCommit(repo, hookOutRedirect.get(PostCommitHook.NAME), hookErrRedirect.get(PostCommitHook.NAME)).call(); } catch (Exception e) { log.error(MessageFormat.format( JGitText.get().postCommitHookFailed, e.getMessage()), e); } return revCommit; } catch (UnmergedPathException e) { throw new UnmergedPathsException(e); } catch (IOException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfCommitCommand, e); } } private void checkIfEmpty(RevWalk rw, ObjectId headId, ObjectId indexTreeId) throws EmptyCommitException, MissingObjectException, IncorrectObjectTypeException, IOException { if (headId != null && !allowEmpty.booleanValue()) { RevCommit headCommit = rw.parseCommit(headId); headCommit.getTree(); if (indexTreeId.equals(headCommit.getTree())) { throw new EmptyCommitException(JGitText.get().emptyCommit); } } } private void sign(CommitBuilder commit) throws CanceledException, IOException, UnsupportedSigningFormatException { if (signer == null) { signer = Signers.get(gpgConfig.getKeyFormat()); if (signer == null) { throw new UnsupportedSigningFormatException(MessageFormat .format(JGitText.get().signatureTypeUnknown, gpgConfig.getKeyFormat().toConfigValue())); } } if (signingKey == null) { signingKey = gpgConfig.getSigningKey(); } signer.signObject(repo, gpgConfig, commit, committer, signingKey, credentialsProvider); } private void updateRef(RepositoryState state, ObjectId headId, RevCommit revCommit, ObjectId commitId) throws ConcurrentRefUpdateException, IOException { RefUpdate ru = repo.updateRef(Constants.HEAD); ru.setNewObjectId(commitId); if (!useDefaultReflogMessage) { ru.setRefLogMessage(reflogComment, false); } else { String prefix = amend ? "commit (amend): " //$NON-NLS-1$ : parents.isEmpty() ? "commit (initial): " //$NON-NLS-1$ : "commit: "; //$NON-NLS-1$ ru.setRefLogMessage(prefix + revCommit.getShortMessage(), false); } if (headId != null) { ru.setExpectedOldObjectId(headId); } else { ru.setExpectedOldObjectId(ObjectId.zeroId()); } Result rc = ru.forceUpdate(); switch (rc) { case NEW: case FORCED: case FAST_FORWARD: { setCallable(false); if (state == RepositoryState.MERGING_RESOLVED || isMergeDuringRebase(state)) { // Commit was successful. Now delete the files // used for merge commits repo.writeMergeCommitMsg(null); repo.writeMergeHeads(null); } else if (state == RepositoryState.CHERRY_PICKING_RESOLVED) { repo.writeMergeCommitMsg(null); repo.writeCherryPickHead(null); } else if (state == RepositoryState.REVERTING_RESOLVED) { repo.writeMergeCommitMsg(null); repo.writeRevertHead(null); } break; } case REJECTED: case LOCK_FAILURE: throw new ConcurrentRefUpdateException( JGitText.get().couldNotLockHEAD, ru.getRef(), rc); default: throw new JGitInternalException(MessageFormat.format( JGitText.get().updatingRefFailed, Constants.HEAD, commitId.toString(), rc)); } } private void insertChangeId(ObjectId treeId) { ObjectId firstParentId = null; if (!parents.isEmpty()) firstParentId = parents.get(0); ObjectId changeId = ChangeIdUtil.computeChangeId(treeId, firstParentId, author, committer, message); message = ChangeIdUtil.insertId(message, changeId); if (changeId != null) message = message.replaceAll("\nChange-Id: I" //$NON-NLS-1$ + ObjectId.zeroId().getName() + "\n", "\nChange-Id: I" //$NON-NLS-1$ //$NON-NLS-2$ + changeId.getName() + "\n"); //$NON-NLS-1$ } private DirCache createTemporaryIndex(ObjectId headId, DirCache index, RevWalk rw) throws IOException { ObjectInserter inserter = null; // get DirCacheBuilder for existing index DirCacheBuilder existingBuilder = index.builder(); // get DirCacheBuilder for newly created in-core index to build a // temporary index for this commit DirCache inCoreIndex = DirCache.newInCore(); DirCacheBuilder tempBuilder = inCoreIndex.builder(); onlyProcessed = new boolean[only.size()]; boolean emptyCommit = true; try (TreeWalk treeWalk = new TreeWalk(repo)) { treeWalk.setOperationType(OperationType.CHECKIN_OP); int dcIdx = treeWalk .addTree(new DirCacheBuildIterator(existingBuilder)); FileTreeIterator fti = new FileTreeIterator(repo); fti.setDirCacheIterator(treeWalk, 0); int fIdx = treeWalk.addTree(fti); int hIdx = -1; if (headId != null) hIdx = treeWalk.addTree(rw.parseTree(headId)); treeWalk.setRecursive(true); String lastAddedFile = null; while (treeWalk.next()) { String path = treeWalk.getPathString(); // check if current entry's path matches a specified path int pos = lookupOnly(path); CanonicalTreeParser hTree = null; if (hIdx != -1) hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class); DirCacheIterator dcTree = treeWalk.getTree(dcIdx, DirCacheIterator.class); if (pos >= 0) { // include entry in commit FileTreeIterator fTree = treeWalk.getTree(fIdx, FileTreeIterator.class); // check if entry refers to a tracked file boolean tracked = dcTree != null || hTree != null; if (!tracked) continue; // for an unmerged path, DirCacheBuildIterator will yield 3 // entries, we only want to add one if (path.equals(lastAddedFile)) continue; lastAddedFile = path; if (fTree != null) { // create a new DirCacheEntry with data retrieved from // disk final DirCacheEntry dcEntry = new DirCacheEntry(path); long entryLength = fTree.getEntryLength(); dcEntry.setLength(entryLength); dcEntry.setLastModified(fTree.getEntryLastModifiedInstant()); dcEntry.setFileMode(fTree.getIndexFileMode(dcTree)); boolean objectExists = (dcTree != null && fTree.idEqual(dcTree)) || (hTree != null && fTree.idEqual(hTree)); if (objectExists) { dcEntry.setObjectId(fTree.getEntryObjectId()); } else { if (FileMode.GITLINK.equals(dcEntry.getFileMode())) dcEntry.setObjectId(fTree.getEntryObjectId()); else { // insert object if (inserter == null) inserter = repo.newObjectInserter(); long contentLength = fTree .getEntryContentLength(); try (InputStream inputStream = fTree .openEntryStream()) { dcEntry.setObjectId(inserter.insert( Constants.OBJ_BLOB, contentLength, inputStream)); } } } // add to existing index existingBuilder.add(dcEntry); // add to temporary in-core index tempBuilder.add(dcEntry); if (emptyCommit && (hTree == null || !hTree.idEqual(fTree) || hTree.getEntryRawMode() != fTree .getEntryRawMode())) // this is a change emptyCommit = false; } else { // if no file exists on disk, neither add it to // index nor to temporary in-core index if (emptyCommit && hTree != null) // this is a change emptyCommit = false; } // keep track of processed path onlyProcessed[pos] = true; } else { // add entries from HEAD for all other paths if (hTree != null) { // create a new DirCacheEntry with data retrieved from // HEAD final DirCacheEntry dcEntry = new DirCacheEntry(path); dcEntry.setObjectId(hTree.getEntryObjectId()); dcEntry.setFileMode(hTree.getEntryFileMode()); // add to temporary in-core index tempBuilder.add(dcEntry); } // preserve existing entry in index if (dcTree != null) existingBuilder.add(dcTree.getDirCacheEntry()); } } } // there must be no unprocessed paths left at this point; otherwise an // untracked or unknown path has been specified for (int i = 0; i < onlyProcessed.length; i++) if (!onlyProcessed[i]) throw new JGitInternalException(MessageFormat.format( JGitText.get().entryNotFoundByPath, only.get(i))); // there must be at least one change if (emptyCommit && !allowEmpty.booleanValue()) // Would like to throw a EmptyCommitException. But this would break the API // TODO(ch): Change this in the next release throw new JGitInternalException(JGitText.get().emptyCommit); // update index existingBuilder.commit(); // finish temporary in-core index used for this commit tempBuilder.finish(); return inCoreIndex; } /** * Look an entry's path up in the list of paths specified by the --only/ -o * option * * In case the complete (file) path (e.g. "d1/d2/f1") cannot be found in * only, lookup is also tried with (parent) directory paths * (e.g. "d1/d2" and "d1"). * * @param pathString * entry's path * @return the item's index in only; -1 if no item matches */ private int lookupOnly(String pathString) { String p = pathString; while (true) { int position = Collections.binarySearch(only, p); if (position >= 0) return position; int l = p.lastIndexOf('/'); if (l < 1) break; p = p.substring(0, l); } return -1; } /** * Sets default values for not explicitly specified options. Then validates * that all required data has been provided. * * @param state * the state of the repository we are working on * @param rw * the RevWalk to use * * @throws NoMessageException * if the commit message has not been specified * @throws UnsupportedSigningFormatException * if the configured gpg.format is not supported */ private void processOptions(RepositoryState state, RevWalk rw) throws NoMessageException, UnsupportedSigningFormatException { if (committer == null) committer = new PersonIdent(repo); if (author == null && !amend) author = committer; if (allowEmpty == null) // JGit allows empty commits by default. Only when pathes are // specified the commit should not be empty. This behaviour differs // from native git but can only be adapted in the next release. // TODO(ch) align the defaults with native git allowEmpty = only.isEmpty() ? Boolean.TRUE : Boolean.FALSE; // when doing a merge commit parse MERGE_HEAD and MERGE_MSG files if (state == RepositoryState.MERGING_RESOLVED || isMergeDuringRebase(state)) { try { parents = repo.readMergeHeads(); if (parents != null) for (int i = 0; i < parents.size(); i++) { RevObject ro = rw.parseAny(parents.get(i)); if (ro instanceof RevTag) parents.set(i, rw.peel(ro)); } } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR, Constants.MERGE_HEAD, e), e); } if (message == null) { try { message = repo.readMergeCommitMsg(); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR, Constants.MERGE_MSG, e), e); } } } else if (state == RepositoryState.SAFE && message == null) { try { message = repo.readSquashCommitMsg(); if (message != null) repo.writeSquashCommitMsg(null /* delete */); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR, Constants.MERGE_MSG, e), e); } } if (message == null) // as long as we don't support -C option we have to have // an explicit message throw new NoMessageException(JGitText.get().commitMessageNotSpecified); if (gpgConfig == null) { gpgConfig = new GpgConfig(repo.getConfig()); } if (signCommit == null) { signCommit = gpgConfig.isSignCommits() ? Boolean.TRUE : Boolean.FALSE; } } private boolean isMergeDuringRebase(RepositoryState state) { if (state != RepositoryState.REBASING_INTERACTIVE && state != RepositoryState.REBASING_MERGE) return false; try { return repo.readMergeHeads() != null; } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR, Constants.MERGE_HEAD, e), e); } } /** * Set the commit message * * @param message * the commit message used for the {@code commit} * @return {@code this} */ public CommitCommand setMessage(String message) { checkCallable(); this.message = message; return this; } /** * Sets the {@link CleanupMode} to apply to the commit message. If not * called, {@link CommitCommand} applies {@link CleanupMode#VERBATIM}. * * @param mode * {@link CleanupMode} to set * @return {@code this} * @since 6.1 */ public CommitCommand setCleanupMode(@NonNull CleanupMode mode) { checkCallable(); this.cleanupMode = mode; return this; } /** * Sets the default clean mode if {@link #setCleanupMode(CleanupMode) * setCleanupMode(CleanupMode.DEFAULT)} is set and git config * {@code commit.cleanup = default} or is not set. * * @param strip * if {@code true}, default to {@link CleanupMode#STRIP}; * otherwise default to {@link CleanupMode#WHITESPACE} * @return {@code this} * @since 6.1 */ public CommitCommand setDefaultClean(boolean strip) { checkCallable(); this.cleanDefaultIsStrip = strip; return this; } /** * Sets the comment character to apply when cleaning a commit message. If * {@code null} (the default) and the {@link #setCleanupMode(CleanupMode) * clean-up mode} is {@link CleanupMode#STRIP} or * {@link CleanupMode#SCISSORS}, the value of git config * {@code core.commentChar} will be used. * * @param commentChar * the comment character, or {@code null} to use the value from * the git config * @return {@code this} * @since 6.1 */ public CommitCommand setCommentCharacter(Character commentChar) { checkCallable(); this.commentChar = commentChar; return this; } /** * Set whether to allow to create an empty commit * * @param allowEmpty * whether it should be allowed to create a commit which has the * same tree as it's sole predecessor (a commit which doesn't * change anything). By default when creating standard commits * (without specifying paths) JGit allows to create such commits. * When this flag is set to false an attempt to create an "empty" * standard commit will lead to an EmptyCommitException. *

* By default when creating a commit containing only specified * paths an attempt to create an empty commit leads to a * {@link org.eclipse.jgit.api.errors.JGitInternalException}. By * setting this flag to true this exception will not * be thrown. * @return {@code this} * @since 4.2 */ public CommitCommand setAllowEmpty(boolean allowEmpty) { this.allowEmpty = Boolean.valueOf(allowEmpty); return this; } /** * Get the commit message * * @return the commit message used for the commit */ public String getMessage() { return message; } /** * Sets the committer for this {@code commit}. If no committer is explicitly * specified because this method is never called or called with {@code null} * value then the committer will be deduced from config info in repository, * with current time. * * @param committer * the committer used for the {@code commit} * @return {@code this} */ public CommitCommand setCommitter(PersonIdent committer) { checkCallable(); this.committer = committer; return this; } /** * Sets the committer for this {@code commit}. If no committer is explicitly * specified because this method is never called then the committer will be * deduced from config info in repository, with current time. * * @param name * the name of the committer used for the {@code commit} * @param email * the email of the committer used for the {@code commit} * @return {@code this} */ public CommitCommand setCommitter(String name, String email) { checkCallable(); return setCommitter(new PersonIdent(name, email)); } /** * Get the committer * * @return the committer used for the {@code commit}. If no committer was * specified {@code null} is returned and the default * {@link org.eclipse.jgit.lib.PersonIdent} of this repo is used * during execution of the command */ public PersonIdent getCommitter() { return committer; } /** * Sets the author for this {@code commit}. If no author is explicitly * specified because this method is never called or called with {@code null} * value then the author will be set to the committer or to the original * author when amending. * * @param author * the author used for the {@code commit} * @return {@code this} */ public CommitCommand setAuthor(PersonIdent author) { checkCallable(); this.author = author; return this; } /** * Sets the author for this {@code commit}. If no author is explicitly * specified because this method is never called then the author will be set * to the committer or to the original author when amending. * * @param name * the name of the author used for the {@code commit} * @param email * the email of the author used for the {@code commit} * @return {@code this} */ public CommitCommand setAuthor(String name, String email) { checkCallable(); return setAuthor(new PersonIdent(name, email)); } /** * Get the author * * @return the author used for the {@code commit}. If no author was * specified {@code null} is returned and the default * {@link org.eclipse.jgit.lib.PersonIdent} of this repo is used * during execution of the command */ public PersonIdent getAuthor() { return author; } /** * If set to true the Commit command automatically stages files that have * been modified and deleted, but new files not known by the repository are * not affected. This corresponds to the parameter -a on the command line. * * @param all * whether to auto-stage all files that have been modified and * deleted * @return {@code this} * @throws JGitInternalException * in case of an illegal combination of arguments/ options */ public CommitCommand setAll(boolean all) { checkCallable(); if (all && !only.isEmpty()) throw new JGitInternalException(MessageFormat.format( JGitText.get().illegalCombinationOfArguments, "--all", //$NON-NLS-1$ "--only")); //$NON-NLS-1$ this.all = all; return this; } /** * Used to amend the tip of the current branch. If set to {@code true}, the * previous commit will be amended. This is equivalent to --amend on the * command line. * * @param amend * whether to amend the tip of the current branch * @return {@code this} */ public CommitCommand setAmend(boolean amend) { checkCallable(); this.amend = amend; return this; } /** * Commit dedicated path only. *

* This method can be called several times to add multiple paths. Full file * paths are supported as well as directory paths; in the latter case this * commits all files/directories below the specified path. * * @param only * path to commit (with / as separator) * @return {@code this} */ public CommitCommand setOnly(String only) { checkCallable(); if (all) throw new JGitInternalException(MessageFormat.format( JGitText.get().illegalCombinationOfArguments, "--only", //$NON-NLS-1$ "--all")); //$NON-NLS-1$ String o = only.endsWith("/") ? only.substring(0, only.length() - 1) //$NON-NLS-1$ : only; // ignore duplicates if (!this.only.contains(o)) this.only.add(o); return this; } /** * If set to true a change id will be inserted into the commit message * * An existing change id is not replaced. An initial change id (I000...) * will be replaced by the change id. * * @param insertChangeId * whether to insert a change id * @return {@code this} */ public CommitCommand setInsertChangeId(boolean insertChangeId) { checkCallable(); this.insertChangeId = insertChangeId; return this; } /** * Override the message written to the reflog * * @param reflogComment * the comment to be written into the reflog or null * to specify that no reflog should be written * @return {@code this} */ public CommitCommand setReflogComment(String reflogComment) { this.reflogComment = reflogComment; useDefaultReflogMessage = false; return this; } /** * Sets the {@link #noVerify} option on this commit command. *

* Both the pre-commit and commit-msg hooks can block a commit by their * return value; setting this option to true will bypass these * two hooks. *

* * @param noVerify * Whether this commit should be verified by the pre-commit and * commit-msg hooks. * @return {@code this} * @since 3.7 */ public CommitCommand setNoVerify(boolean noVerify) { this.noVerify = noVerify; return this; } /** * Set the output stream for all hook scripts executed by this command * (pre-commit, commit-msg, post-commit). If not set it defaults to * {@code System.out}. * * @param hookStdOut * the output stream for hook scripts executed by this command * @return {@code this} * @since 3.7 */ public CommitCommand setHookOutputStream(PrintStream hookStdOut) { setHookOutputStream(PreCommitHook.NAME, hookStdOut); setHookOutputStream(CommitMsgHook.NAME, hookStdOut); setHookOutputStream(PostCommitHook.NAME, hookStdOut); return this; } /** * Set the error stream for all hook scripts executed by this command * (pre-commit, commit-msg, post-commit). If not set it defaults to * {@code System.err}. * * @param hookStdErr * the error stream for hook scripts executed by this command * @return {@code this} * @since 5.6 */ public CommitCommand setHookErrorStream(PrintStream hookStdErr) { setHookErrorStream(PreCommitHook.NAME, hookStdErr); setHookErrorStream(CommitMsgHook.NAME, hookStdErr); setHookErrorStream(PostCommitHook.NAME, hookStdErr); return this; } /** * Set the output stream for a selected hook script executed by this command * (pre-commit, commit-msg, post-commit). If not set it defaults to * {@code System.out}. * * @param hookName * name of the hook to set the output stream for * @param hookStdOut * the output stream to use for the selected hook * @return {@code this} * @since 4.5 */ public CommitCommand setHookOutputStream(String hookName, PrintStream hookStdOut) { if (!(PreCommitHook.NAME.equals(hookName) || CommitMsgHook.NAME.equals(hookName) || PostCommitHook.NAME.equals(hookName))) { throw new IllegalArgumentException( MessageFormat.format(JGitText.get().illegalHookName, hookName)); } hookOutRedirect.put(hookName, hookStdOut); return this; } /** * Set the error stream for a selected hook script executed by this command * (pre-commit, commit-msg, post-commit). If not set it defaults to * {@code System.err}. * * @param hookName * name of the hook to set the output stream for * @param hookStdErr * the output stream to use for the selected hook * @return {@code this} * @since 5.6 */ public CommitCommand setHookErrorStream(String hookName, PrintStream hookStdErr) { if (!(PreCommitHook.NAME.equals(hookName) || CommitMsgHook.NAME.equals(hookName) || PostCommitHook.NAME.equals(hookName))) { throw new IllegalArgumentException(MessageFormat .format(JGitText.get().illegalHookName, hookName)); } hookErrRedirect.put(hookName, hookStdErr); return this; } /** * Sets the signing key *

* Per spec of user.signingKey: this will be sent to the GPG program as is, * i.e. can be anything supported by the GPG program. *

*

* Note, if none was set or null is specified a default will be * obtained from the configuration. *

* * @param signingKey * signing key (maybe null) * @return {@code this} * @since 5.3 */ public CommitCommand setSigningKey(String signingKey) { checkCallable(); this.signingKey = signingKey; return this; } /** * Sets whether the commit should be signed. * * @param sign * true to sign, false to not sign and * null for default behavior (read from * configuration) * @return {@code this} * @since 5.3 */ public CommitCommand setSign(Boolean sign) { checkCallable(); this.signCommit = sign; return this; } /** * Sets the {@link Signer} to use if the commit is to be signed. * * @param signer * to use; if {@code null}, the default signer will be used * @return {@code this} * @since 7.0 */ public CommitCommand setSigner(Signer signer) { checkCallable(); this.signer = signer; return this; } /** * Sets an external {@link GpgConfig} to use. Whether it will be used is at * the discretion of the {@link #setSigner(Signer)}. * * @param config * to set; if {@code null}, the config will be loaded from the * git config of the repository * @return {@code this} * @since 5.11 */ public CommitCommand setGpgConfig(GpgConfig config) { checkCallable(); this.gpgConfig = config; return this; } /** * Sets a {@link CredentialsProvider} * * @param credentialsProvider * the provider to use when querying for credentials (eg., during * signing) * @return {@code this} * @since 6.0 */ public CommitCommand setCredentialsProvider( CredentialsProvider credentialsProvider) { this.credentialsProvider = credentialsProvider; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 10220 Content-Disposition: inline; filename="CreateBranchCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "013e0ff131891506b6738180b7a8198d3d1123d6" /* * Copyright (C) 2010, Mathias Kinzler * Copyright (C) 2010, Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static org.eclipse.jgit.lib.Constants.HEAD; import static org.eclipse.jgit.lib.Constants.R_HEADS; import java.io.IOException; import java.text.MessageFormat; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.errors.AmbiguousObjectException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; /** * Used to create a local branch. * * @see Git documentation about Branch */ public class CreateBranchCommand extends GitCommand { private String name; private boolean force = false; private SetupUpstreamMode upstreamMode; private String startPoint = HEAD; private RevCommit startCommit; /** * The modes available for setting up the upstream configuration * (corresponding to the --set-upstream, --track, --no-track options * */ public enum SetupUpstreamMode { /** * Corresponds to the --track option */ TRACK, /** * Corresponds to the --no-track option */ NOTRACK, /** * Corresponds to the --set-upstream option */ SET_UPSTREAM; } /** * Constructor for CreateBranchCommand * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected CreateBranchCommand(Repository repo) { super(repo); } @Override public Ref call() throws GitAPIException, RefAlreadyExistsException, RefNotFoundException, InvalidRefNameException { checkCallable(); processOptions(); try (RevWalk revWalk = new RevWalk(repo)) { boolean exists = repo.findRef(R_HEADS + name) != null; if (!force && exists) throw new RefAlreadyExistsException(MessageFormat.format( JGitText.get().refAlreadyExists1, name)); ObjectId startAt = getStartPointObjectId(); String startPointFullName = null; if (startPoint != null) { Ref baseRef = repo.findRef(startPoint); if (baseRef != null) startPointFullName = baseRef.getName(); } // determine whether we are based on a commit, // a branch, or a tag and compose the reflog message String refLogMessage; String baseBranch = ""; //$NON-NLS-1$ if (startPointFullName == null) { String baseCommit; if (startCommit != null) baseCommit = startCommit.getShortMessage(); else { RevCommit commit = revWalk.parseCommit(repo .resolve(getStartPointOrHead())); baseCommit = commit.getShortMessage(); } if (exists) refLogMessage = "branch: Reset start-point to commit " //$NON-NLS-1$ + baseCommit; else refLogMessage = "branch: Created from commit " + baseCommit; //$NON-NLS-1$ } else if (startPointFullName.startsWith(R_HEADS) || startPointFullName.startsWith(Constants.R_REMOTES)) { baseBranch = startPointFullName; if (exists) refLogMessage = "branch: Reset start-point to branch " //$NON-NLS-1$ + startPointFullName; // TODO else refLogMessage = "branch: Created from branch " + baseBranch; //$NON-NLS-1$ } else { startAt = revWalk.peel(revWalk.parseAny(startAt)); if (exists) refLogMessage = "branch: Reset start-point to tag " //$NON-NLS-1$ + startPointFullName; else refLogMessage = "branch: Created from tag " //$NON-NLS-1$ + startPointFullName; } RefUpdate updateRef = repo.updateRef(R_HEADS + name); updateRef.setNewObjectId(startAt); updateRef.setRefLogMessage(refLogMessage, false); Result updateResult; if (exists && force) updateResult = updateRef.forceUpdate(); else updateResult = updateRef.update(); setCallable(false); boolean ok = false; switch (updateResult) { case NEW: ok = !exists; break; case NO_CHANGE: case FAST_FORWARD: case FORCED: ok = exists; break; default: break; } if (!ok) throw new JGitInternalException(MessageFormat.format(JGitText .get().createBranchUnexpectedResult, updateResult .name())); Ref result = repo.findRef(name); if (result == null) throw new JGitInternalException( JGitText.get().createBranchFailedUnknownReason); if (baseBranch.length() == 0) { return result; } // if we are based on another branch, see // if we need to configure upstream configuration: first check // whether the setting was done explicitly boolean doConfigure; if (upstreamMode == SetupUpstreamMode.SET_UPSTREAM || upstreamMode == SetupUpstreamMode.TRACK) // explicitly set to configure doConfigure = true; else if (upstreamMode == SetupUpstreamMode.NOTRACK) // explicitly set to not configure doConfigure = false; else { // if there was no explicit setting, check the configuration String autosetupflag = repo.getConfig().getString( ConfigConstants.CONFIG_BRANCH_SECTION, null, ConfigConstants.CONFIG_KEY_AUTOSETUPMERGE); if ("false".equals(autosetupflag)) { //$NON-NLS-1$ doConfigure = false; } else if ("always".equals(autosetupflag)) { //$NON-NLS-1$ doConfigure = true; } else { // in this case, the default is to configure // only in case the base branch was a remote branch doConfigure = baseBranch.startsWith(Constants.R_REMOTES); } } if (doConfigure) { StoredConfig config = repo.getConfig(); String remoteName = repo.getRemoteName(baseBranch); if (remoteName != null) { String branchName = repo .shortenRemoteBranchName(baseBranch); config .setString(ConfigConstants.CONFIG_BRANCH_SECTION, name, ConfigConstants.CONFIG_KEY_REMOTE, remoteName); config.setString(ConfigConstants.CONFIG_BRANCH_SECTION, name, ConfigConstants.CONFIG_KEY_MERGE, Constants.R_HEADS + branchName); } else { // set "." as remote config.setString(ConfigConstants.CONFIG_BRANCH_SECTION, name, ConfigConstants.CONFIG_KEY_REMOTE, "."); //$NON-NLS-1$ config.setString(ConfigConstants.CONFIG_BRANCH_SECTION, name, ConfigConstants.CONFIG_KEY_MERGE, baseBranch); } config.save(); } return result; } catch (IOException ioe) { throw new JGitInternalException(ioe.getMessage(), ioe); } } private ObjectId getStartPointObjectId() throws AmbiguousObjectException, RefNotFoundException, IOException { if (startCommit != null) return startCommit.getId(); String startPointOrHead = getStartPointOrHead(); ObjectId result = repo.resolve(startPointOrHead); if (result == null) throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, startPointOrHead)); return result; } private String getStartPointOrHead() { return startPoint != null ? startPoint : HEAD; } private void processOptions() throws InvalidRefNameException { if (name == null || !Repository.isValidRefName(R_HEADS + name) || !isValidBranchName(name)) throw new InvalidRefNameException(MessageFormat.format(JGitText .get().branchNameInvalid, name == null ? "" : name)); //$NON-NLS-1$ } /** * Check if the given branch name is valid * * @param branchName * branch name to check * @return {@code true} if the branch name is valid * * @since 5.0 */ public static boolean isValidBranchName(String branchName) { if (HEAD.equals(branchName)) { return false; } return !branchName.startsWith("-"); //$NON-NLS-1$ } /** * Set the name of the new branch * * @param name * the name of the new branch * @return this instance */ public CreateBranchCommand setName(String name) { checkCallable(); this.name = name; return this; } /** * Set whether to create the branch forcefully * * @param force * if true and the branch with the given name * already exists, the start-point of an existing branch will be * set to a new start-point; if false, the existing branch will * not be changed * @return this instance */ public CreateBranchCommand setForce(boolean force) { checkCallable(); this.force = force; return this; } /** * Set the start point * * @param startPoint * corresponds to the start-point option; if null, * the current HEAD will be used * @return this instance */ public CreateBranchCommand setStartPoint(String startPoint) { checkCallable(); this.startPoint = startPoint; this.startCommit = null; return this; } /** * Set the start point * * @param startPoint * corresponds to the start-point option; if null, * the current HEAD will be used * @return this instance */ public CreateBranchCommand setStartPoint(RevCommit startPoint) { checkCallable(); this.startCommit = startPoint; this.startPoint = null; return this; } /** * Set the upstream mode * * @param mode * corresponds to the --track/--no-track/--set-upstream options; * may be null * @return this instance */ public CreateBranchCommand setUpstreamMode(SetupUpstreamMode mode) { checkCallable(); this.upstreamMode = mode; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8487 Content-Disposition: inline; filename="DeleteBranchCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "a5c535f772df9c20575aa638e1128f1a9ddf335a" /* * Copyright (C) 2010, Mathias Kinzler * Copyright (C) 2010, 2023 Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.jgit.api.errors.CannotDeleteCurrentBranchException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NotMergedException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; /** * Used to delete one or several branches. * * The result of {@link #call()} is a list with the (full) names of the deleted * branches. * * Note that we don't have a setter corresponding to the -r option; remote * tracking branches are simply deleted just like local branches. * * @see Git documentation about Branch */ public class DeleteBranchCommand extends GitCommand> { private final Set branchNames = new HashSet<>(); private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; private boolean force; /** * Constructor for DeleteBranchCommand * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected DeleteBranchCommand(Repository repo) { super(repo); } @Override public List call() throws GitAPIException, NotMergedException, CannotDeleteCurrentBranchException { checkCallable(); List result = new ArrayList<>(); Set shortNames = new HashSet<>(); if (branchNames.isEmpty()) { return result; } Exception error = null; try { deleteBranches(result, shortNames); } catch (Exception e) { error = e; } monitor.beginTask(JGitText.get().updatingConfig, 1); try { updateConfig(shortNames, error); } finally { monitor.update(1); monitor.endTask(); } return result; } private void deleteBranches(List result, Set shortNames) throws GitAPIException, NotMergedException, CannotDeleteCurrentBranchException { try { String currentBranch = repo.getFullBranch(); if (!force) { // check if the branches to be deleted // are all merged into the current branch try (RevWalk walk = new RevWalk(repo)) { RevCommit tip = walk .parseCommit(repo.resolve(Constants.HEAD)); for (String branchName : branchNames) { if (branchName == null) { continue; } Ref currentRef = repo.findRef(branchName); if (currentRef == null) { continue; } RevCommit base = walk .parseCommit(repo.resolve(branchName)); if (!walk.isMergedInto(base, tip)) { throw new NotMergedException(); } } } } setCallable(false); monitor.start(2); monitor.beginTask(JGitText.get().deletingBranches, branchNames.size()); try { for (String branchName : branchNames) { if (branchName == null) { monitor.update(1); continue; } Ref currentRef = repo.findRef(branchName); if (currentRef == null) { monitor.update(1); continue; } String fullName = currentRef.getName(); if (fullName.equals(currentBranch)) { throw new CannotDeleteCurrentBranchException( MessageFormat.format(JGitText .get().cannotDeleteCheckedOutBranch, branchName)); } RefUpdate update = repo.updateRef(fullName); update.setRefLogMessage("branch deleted", false); //$NON-NLS-1$ update.setForceUpdate(true); Result deleteResult = update.delete(); switch (deleteResult) { case IO_FAILURE: case LOCK_FAILURE: case REJECTED: throw new JGitInternalException(MessageFormat.format( JGitText.get().deleteBranchUnexpectedResult, deleteResult.name())); default: result.add(fullName); if (fullName.startsWith(Constants.R_HEADS)) { shortNames.add(fullName .substring(Constants.R_HEADS.length())); } break; } monitor.update(1); if (monitor.isCancelled()) { break; } } } finally { monitor.endTask(); } } catch (IOException ioe) { throw new JGitInternalException(ioe.getMessage(), ioe); } } private void updateConfig(Set shortNames, Exception error) throws GitAPIException { IOException configError = null; if (!shortNames.isEmpty()) { try { // Remove upstream configurations if any StoredConfig cfg = repo.getConfig(); boolean changed = false; for (String branchName : shortNames) { changed |= cfg.removeSection( ConfigConstants.CONFIG_BRANCH_SECTION, branchName); } if (changed) { cfg.save(); } } catch (IOException e) { configError = e; } } if (error == null) { if (configError != null) { throw new JGitInternalException(configError.getMessage(), configError); } } else if (error instanceof GitAPIException) { if (configError != null) { error.addSuppressed(configError); } throw (GitAPIException) error; } else if (error instanceof RuntimeException) { if (configError != null) { error.addSuppressed(configError); } throw (RuntimeException) error; } else { JGitInternalException internal = new JGitInternalException( error.getMessage(), error); if (configError != null) { internal.addSuppressed(configError); } throw internal; } } /** * Set the names of the branches to delete * * @param branchnames * the names of the branches to delete; if not set, this will do * nothing; invalid branch names will simply be ignored * @return this instance */ public DeleteBranchCommand setBranchNames(String... branchnames) { checkCallable(); this.branchNames.clear(); this.branchNames.addAll(Arrays.asList(branchnames)); return this; } /** * Sets the names of the branches to delete * * @param branchNames * the names of the branches to delete; if not set, this will do * nothing; invalid branch names will simply be ignored * @return {@code this} * @since 6.8 */ public DeleteBranchCommand setBranchNames(Collection branchNames) { checkCallable(); this.branchNames.clear(); this.branchNames.addAll(branchNames); return this; } /** * Set whether to forcefully delete branches * * @param force * true corresponds to the -D option, * false to the -d option (default)
* if false a check will be performed whether the * branch to be deleted is already merged into the current branch * and deletion will be refused in this case * @return this instance */ public DeleteBranchCommand setForce(boolean force) { checkCallable(); this.force = force; return this; } /** * Retrieves the progress monitor. * * @return the {@link ProgressMonitor} for the delete operation * @since 6.8 */ public ProgressMonitor getProgressMonitor() { return monitor; } /** * Sets the progress monitor associated with the delete operation. By * default, this is set to NullProgressMonitor * * @see NullProgressMonitor * @param monitor * a {@link ProgressMonitor} * @return {@code this} * @since 6.8 */ public DeleteBranchCommand setProgressMonitor(ProgressMonitor monitor) { checkCallable(); if (monitor == null) { monitor = NullProgressMonitor.INSTANCE; } this.monitor = monitor; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2825 Content-Disposition: inline; filename="DeleteTagCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "92d88aad7d8ef7a8fcd9dd6da16e1b9f05bd7f98" /* * Copyright (C) 2011, Tomasz Zarna and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; /** * Used to delete one or several tags. * * The result of {@link #call()} is a list with the (full) names of the deleted * tags. * * @see Git documentation about Tag */ public class DeleteTagCommand extends GitCommand> { private final Set tags = new HashSet<>(); /** * Constructor for DeleteTagCommand * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected DeleteTagCommand(Repository repo) { super(repo); } @Override public List call() throws GitAPIException { checkCallable(); List result = new ArrayList<>(); if (tags.isEmpty()) return result; try { setCallable(false); for (String tagName : tags) { if (tagName == null) continue; Ref currentRef = repo.findRef(tagName); if (currentRef == null) continue; String fullName = currentRef.getName(); RefUpdate update = repo.updateRef(fullName); update.setForceUpdate(true); Result deleteResult = update.delete(); boolean ok = true; switch (deleteResult) { case IO_FAILURE: case LOCK_FAILURE: case REJECTED: ok = false; break; default: break; } if (ok) { result.add(fullName); } else throw new JGitInternalException(MessageFormat.format( JGitText.get().deleteTagUnexpectedResult, deleteResult.name())); } return result; } catch (IOException ioe) { throw new JGitInternalException(ioe.getMessage(), ioe); } } /** * Set names of the tags to delete * * @param tags * the names of the tags to delete; if not set, this will do * nothing; invalid tag names will simply be ignored * @return this instance */ public DeleteTagCommand setTags(String... tags) { checkCallable(); this.tags.clear(); this.tags.addAll(Arrays.asList(tags)); return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 15860 Content-Disposition: inline; filename="DescribeCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "d2526287f99588620d68c3215d2e8572b10a84a9" /* * Copyright (C) 2013, CloudBees, Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static org.eclipse.jgit.lib.Constants.R_REFS; import static org.eclipse.jgit.lib.Constants.R_TAGS; import static org.eclipse.jgit.lib.TypedConfigGetter.UNSET_INT; import java.io.IOException; import java.text.MessageFormat; import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.InvalidPatternException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.fnmatch.FileNameMatcher; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AbbrevConfig; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevFlagSet; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; /** * Given a commit, show the most recent tag that is reachable from a commit. * * @since 3.2 */ public class DescribeCommand extends GitCommand { private final RevWalk w; /** * Commit to describe. */ private RevCommit target; /** * How many tags we'll consider as candidates. * This can only go up to the number of flags JGit can support in a walk, * which is 24. */ private int maxCandidates = 10; /** * Whether to always use long output format or not. */ private boolean longDesc; /** * Pattern matchers to be applied to tags under consideration. */ private List matchers = new ArrayList<>(); /** * Pattern matchers to be applied to tags for exclusion. */ private List excludeMatchers = new ArrayList<>(); /** * Whether to use all refs in the refs/ namespace */ private boolean useAll; /** * Whether to use all tags (incl. lightweight) or not. */ private boolean useTags; /** * Whether to show a uniquely abbreviated commit hash as a fallback or not. */ private boolean always; /** * The prefix length to use when abbreviating a commit hash. */ private int abbrev = UNSET_INT; /** * Constructor for DescribeCommand. * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected DescribeCommand(Repository repo) { super(repo); w = new RevWalk(repo); w.setRetainBody(false); } /** * Sets the commit to be described. * * @param target * A non-null object ID to be described. * @return {@code this} * @throws MissingObjectException * the supplied commit does not exist. * @throws IncorrectObjectTypeException * the supplied id is not a commit or an annotated tag. * @throws java.io.IOException * a pack file or loose object could not be read. */ public DescribeCommand setTarget(ObjectId target) throws IOException { this.target = w.parseCommit(target); return this; } /** * Sets the commit to be described. * * @param rev * Commit ID, tag, branch, ref, etc. See * {@link org.eclipse.jgit.lib.Repository#resolve(String)} for * allowed syntax. * @return {@code this} * @throws IncorrectObjectTypeException * the supplied id is not a commit or an annotated tag. * @throws org.eclipse.jgit.api.errors.RefNotFoundException * the given rev didn't resolve to any object. * @throws java.io.IOException * a pack file or loose object could not be read. */ public DescribeCommand setTarget(String rev) throws IOException, RefNotFoundException { ObjectId id = repo.resolve(rev); if (id == null) throw new RefNotFoundException(MessageFormat.format(JGitText.get().refNotResolved, rev)); return setTarget(id); } /** * Determine whether always to use the long format or not. When set to * true the long format is used even the commit matches a tag. * * @param longDesc * true if always the long format should be used. * @return {@code this} * @see Git documentation about describe * @since 4.0 */ public DescribeCommand setLong(boolean longDesc) { this.longDesc = longDesc; return this; } /** * Instead of using only the annotated tags, use any ref found in refs/ * namespace. This option enables matching any known branch, * remote-tracking branch, or lightweight tag. * * @param all * true enables matching any ref found in refs/ * like setting option --all in c git * @return {@code this} * @since 5.10 */ public DescribeCommand setAll(boolean all) { this.useAll = all; return this; } /** * Instead of using only the annotated tags, use any tag found in refs/tags * namespace. This option enables matching lightweight (non-annotated) tags * or not. * * @param tags * true enables matching lightweight (non-annotated) * tags like setting option --tags in c git * @return {@code this} * @since 5.0 */ public DescribeCommand setTags(boolean tags) { this.useTags = tags; return this; } /** * Always describe the commit by eventually falling back to a uniquely * abbreviated commit hash if no other name matches. * * @param always * true enables falling back to a uniquely * abbreviated commit hash * @return {@code this} * @since 5.4 */ public DescribeCommand setAlways(boolean always) { this.always = always; return this; } /** * Sets the prefix length to use when abbreviating an object SHA-1. * * @param abbrev * minimum length of the abbreviated string. Must be in the range * [{@value AbbrevConfig#MIN_ABBREV}, * {@value Constants#OBJECT_ID_STRING_LENGTH}]. * @return {@code this} * @since 6.1 */ public DescribeCommand setAbbrev(int abbrev) { if (abbrev == 0) { this.abbrev = 0; } else { this.abbrev = AbbrevConfig.capAbbrev(abbrev); } return this; } private String longDescription(Ref tag, int depth, ObjectId tip) throws IOException { if (abbrev == 0) { return formatRefName(tag.getName()); } return String.format("%s-%d-g%s", formatRefName(tag.getName()), //$NON-NLS-1$ Integer.valueOf(depth), w.getObjectReader().abbreviate(tip, abbrev).name()); } /** * Sets one or more {@code glob(7)} patterns that tags must match to be * considered. If multiple patterns are provided, tags only need match one * of them. * * @param patterns * the {@code glob(7)} pattern or patterns * @return {@code this} * @throws org.eclipse.jgit.errors.InvalidPatternException * if the pattern passed in was invalid. * @see Git documentation about describe * @since 4.9 */ public DescribeCommand setMatch(String... patterns) throws InvalidPatternException { for (String p : patterns) { matchers.add(new FileNameMatcher(p, null)); } return this; } /** * Sets one or more {@code glob(7)} patterns that tags must not match to be * considered. If multiple patterns are provided, they will all be applied. * * @param patterns * the {@code glob(7)} pattern or patterns * @return {@code this} * @throws org.eclipse.jgit.errors.InvalidPatternException * if the pattern passed in was invalid. * @see Git documentation about describe * @since 7.2 */ public DescribeCommand setExclude(String... patterns) throws InvalidPatternException { for (String p : patterns) { excludeMatchers.add(new FileNameMatcher(p, null)); } return this; } private final Comparator TAG_TIE_BREAKER = new Comparator<>() { @Override public int compare(Ref o1, Ref o2) { try { return tagDate(o2).compareTo(tagDate(o1)); } catch (IOException e) { return 0; } } private Instant tagDate(Ref tag) throws IOException { RevTag t = w.parseTag(tag.getObjectId()); w.parseBody(t); return t.getTaggerIdent().getWhenAsInstant(); } }; private Optional getBestMatch(List tags) { if (tags == null || tags.isEmpty()) { return Optional.empty(); } else if (matchers.isEmpty() && excludeMatchers.isEmpty()) { Collections.sort(tags, TAG_TIE_BREAKER); return Optional.of(tags.get(0)); } Stream matchingTags; if (!matchers.isEmpty()) { // Find the first tag that matches in the stream of all tags // filtered by matchers ordered by tie break order matchingTags = Stream.empty(); for (FileNameMatcher matcher : matchers) { Stream m = tags.stream().filter( // tag -> { matcher.append(formatRefName(tag.getName())); boolean result = matcher.isMatch(); matcher.reset(); return result; }); matchingTags = Stream.of(matchingTags, m).flatMap(i -> i); } } else { // If there are no matchers, there are only excluders // Assume all tags match for now before applying excluders matchingTags = tags.stream(); } for (FileNameMatcher matcher : excludeMatchers) { matchingTags = matchingTags.filter( // tag -> { matcher.append(formatRefName(tag.getName())); boolean result = matcher.isMatch(); matcher.reset(); return !result; }); } return matchingTags.sorted(TAG_TIE_BREAKER).findFirst(); } private ObjectId getObjectIdFromRef(Ref r) throws JGitInternalException { try { ObjectId key = repo.getRefDatabase().peel(r).getPeeledObjectId(); if (key == null) { key = r.getObjectId(); } return key; } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } } /** * {@inheritDoc} *

* Describes the specified commit. Target defaults to HEAD if no commit was * set explicitly. */ @Override public String call() throws GitAPIException { try { checkCallable(); if (target == null) { setTarget(Constants.HEAD); } if (abbrev == UNSET_INT) { abbrev = AbbrevConfig.parseFromConfig(repo).get(); } Collection tagList = repo.getRefDatabase() .getRefsByPrefix(useAll ? R_REFS : R_TAGS); Map> tags = tagList.stream() .filter(this::filterLightweightTags) .collect(Collectors.groupingBy(this::getObjectIdFromRef)); // combined flags of all the candidate instances final RevFlagSet allFlags = new RevFlagSet(); /** * Tracks the depth of each tag as we find them. */ class Candidate { final Ref tag; final RevFlag flag; /** * This field counts number of commits that are reachable from * the tip but not reachable from the tag. */ int depth; Candidate(RevCommit commit, Ref tag) { this.tag = tag; this.flag = w.newFlag(tag.getName()); // we'll mark all the nodes reachable from this tag accordingly allFlags.add(flag); w.carry(flag); commit.add(flag); // As of this writing, JGit carries a flag from a child to its parents // right before RevWalk.next() returns, so all the flags that are added // must be manually carried to its parents. If that gets fixed, // this will be unnecessary. commit.carry(flag); } /** * Does this tag contain the given commit? */ boolean reaches(RevCommit c) { return c.has(flag); } String describe(ObjectId tip) throws IOException { return longDescription(tag, depth, tip); } } List candidates = new ArrayList<>(); // all the candidates we find // is the target already pointing to a suitable tag? if so, we are done! Optional bestMatch = getBestMatch(tags.get(target)); if (bestMatch.isPresent()) { return longDesc ? longDescription(bestMatch.get(), 0, target) : formatRefName(bestMatch.get().getName()); } w.markStart(target); int seen = 0; // commit seen thus far RevCommit c; while ((c = w.next()) != null) { if (!c.hasAny(allFlags)) { // if a tag already dominates this commit, // then there's no point in picking a tag on this commit // since the one that dominates it is always more preferable bestMatch = getBestMatch(tags.get(c)); if (bestMatch.isPresent()) { Candidate cd = new Candidate(c, bestMatch.get()); candidates.add(cd); cd.depth = seen; } } // if the newly discovered commit isn't reachable from a tag that we've seen // it counts toward the total depth. for (Candidate cd : candidates) { if (!cd.reaches(c)) cd.depth++; } // if we have search going for enough tags, we will start // closing down. JGit can only give us a finite number of bits, // so we can't track all tags even if we wanted to. if (candidates.size() >= maxCandidates) break; // TODO: if all the commits in the queue of RevWalk has allFlags // there's no point in continuing search as we'll not discover any more // tags. But RevWalk doesn't expose this. seen++; } // at this point we aren't adding any more tags to our search, // but we still need to count all the depths correctly. while ((c = w.next()) != null) { if (c.hasAll(allFlags)) { // no point in visiting further from here, so cut the search here for (RevCommit p : c.getParents()) p.add(RevFlag.SEEN); } else { for (Candidate cd : candidates) { if (!cd.reaches(c)) cd.depth++; } } } // if all the nodes are dominated by all the tags, the walk stops if (candidates.isEmpty()) { return always ? w.getObjectReader() .abbreviate(target, AbbrevConfig.capAbbrev(abbrev)) .name() : null; } Candidate best = Collections.min(candidates, (Candidate o1, Candidate o2) -> o1.depth - o2.depth); return best.describe(target); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } finally { setCallable(false); w.close(); } } /** * Removes the refs/ or refs/tags prefix from tag names * @param name the name of the tag * @return the tag name with its prefix removed */ private String formatRefName(String name) { return name.startsWith(R_TAGS) ? name.substring(R_TAGS.length()) : name.substring(R_REFS.length()); } /** * Whether we use lightweight tags or not for describe Candidates * * @param ref * reference under inspection * @return true if it should be used for describe or not regarding * {@link org.eclipse.jgit.api.DescribeCommand#useTags} */ @SuppressWarnings("null") private boolean filterLightweightTags(Ref ref) { ObjectId id = ref.getObjectId(); try { return this.useAll || this.useTags || (id != null && (w.parseTag(id) != null)); } catch (IOException e) { return false; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7226 Content-Disposition: inline; filename="DiffCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "040b29d8e33b8007c9a4f69cfa688b07036f39c0" /* * Copyright (C) 2011, Tomasz Zarna and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static org.eclipse.jgit.lib.Constants.HEAD; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.util.io.NullOutputStream; /** * Show changes between commits, commit and working tree, etc. * * @see Git documentation about diff */ public class DiffCommand extends GitCommand> { private AbstractTreeIterator oldTree; private AbstractTreeIterator newTree; private boolean cached; private TreeFilter pathFilter = TreeFilter.ALL; private boolean showNameAndStatusOnly; private boolean showNameOnly; private OutputStream out; private int contextLines = -1; private String sourcePrefix; private String destinationPrefix; private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; /** * Constructor for DiffCommand * * @param repo * a {@link org.eclipse.jgit.lib.Repository} object. */ protected DiffCommand(Repository repo) { super(repo); } private DiffFormatter getDiffFormatter() { return out != null && !showNameAndStatusOnly && !showNameOnly ? new DiffFormatter(new BufferedOutputStream(out)) : new DiffFormatter(NullOutputStream.INSTANCE); } /** * {@inheritDoc} *

* Executes the {@code Diff} command with all the options and parameters * collected by the setter methods (e.g. {@link #setCached(boolean)} of this * class. Each instance of this class should only be used for one invocation * of the command. Don't call this method twice on an instance. */ @Override public List call() throws GitAPIException { try (DiffFormatter diffFmt = getDiffFormatter()) { diffFmt.setRepository(repo); diffFmt.setProgressMonitor(monitor); if (cached) { if (oldTree == null) { ObjectId head = repo.resolve(HEAD + "^{tree}"); //$NON-NLS-1$ if (head == null) throw new NoHeadException(JGitText.get().cannotReadTree); CanonicalTreeParser p = new CanonicalTreeParser(); try (ObjectReader reader = repo.newObjectReader()) { p.reset(reader, head); } oldTree = p; } newTree = new DirCacheIterator(repo.readDirCache()); } else { if (oldTree == null) { oldTree = new DirCacheIterator(repo.readDirCache()); } if (newTree == null) { newTree = new FileTreeIterator(repo); } } diffFmt.setPathFilter(pathFilter); List result = diffFmt.scan(oldTree, newTree); if (showNameAndStatusOnly || showNameOnly) { return result; } if (contextLines >= 0) { diffFmt.setContext(contextLines); } if (destinationPrefix != null) { diffFmt.setNewPrefix(destinationPrefix); } if (sourcePrefix != null) { diffFmt.setOldPrefix(sourcePrefix); } diffFmt.format(result); diffFmt.flush(); return result; } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } } /** * Whether to view the changes staged for the next commit * * @param cached * whether to view the changes staged for the next commit * @return this instance */ public DiffCommand setCached(boolean cached) { this.cached = cached; return this; } /** * Set path filter * * @param pathFilter * parameter, used to limit the diff to the named path * @return this instance */ public DiffCommand setPathFilter(TreeFilter pathFilter) { this.pathFilter = pathFilter; return this; } /** * Set old tree * * @param oldTree * the previous state * @return this instance */ public DiffCommand setOldTree(AbstractTreeIterator oldTree) { this.oldTree = oldTree; return this; } /** * Set new tree * * @param newTree * the updated state * @return this instance */ public DiffCommand setNewTree(AbstractTreeIterator newTree) { this.newTree = newTree; return this; } /** * Set whether to return only names and status of changed files * * @param showNameAndStatusOnly * whether to return only names and status of changed files * @return this instance */ public DiffCommand setShowNameAndStatusOnly(boolean showNameAndStatusOnly) { this.showNameAndStatusOnly = showNameAndStatusOnly; return this; } /** * Set whether to return only names of changed files * * @param showNameOnly * whether to return only names files * @return this instance * @since 6.4 */ public DiffCommand setShowNameOnly(boolean showNameOnly) { this.showNameOnly = showNameOnly; return this; } /** * Set output stream * * @param out * the stream to write line data * @return this instance */ public DiffCommand setOutputStream(OutputStream out) { this.out = out; return this; } /** * Set number of context lines instead of the usual three. * * @param contextLines * the number of context lines * @return this instance */ public DiffCommand setContextLines(int contextLines) { this.contextLines = contextLines; return this; } /** * Set the given source prefix instead of "a/". * * @param sourcePrefix * the prefix * @return this instance */ public DiffCommand setSourcePrefix(String sourcePrefix) { this.sourcePrefix = sourcePrefix; return this; } /** * Set the given destination prefix instead of "b/". * * @param destinationPrefix * the prefix * @return this instance */ public DiffCommand setDestinationPrefix(String destinationPrefix) { this.destinationPrefix = destinationPrefix; return this; } /** * The progress monitor associated with the diff operation. By default, this * is set to NullProgressMonitor * * @see NullProgressMonitor * @param monitor * a progress monitor * @return this instance */ public DiffCommand setProgressMonitor(ProgressMonitor monitor) { if (monitor == null) { monitor = NullProgressMonitor.INSTANCE; } this.monitor = monitor; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 18511 Content-Disposition: inline; filename="FetchCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "f24127bd51e72008bb5ce5d1bca8b86918e29c7a" /* * Copyright (C) 2010, 2022 Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static java.util.stream.Collectors.toList; import java.io.IOException; import java.net.URISyntaxException; import java.text.MessageFormat; import java.time.Instant; import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidConfigurationException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.NoRemoteRepositoryException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.transport.FetchResult; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.TagOpt; import org.eclipse.jgit.transport.Transport; /** * A class used to execute a {@code Fetch} command. It has setters for all * supported options and arguments of this command and a {@link #call()} method * to finally execute the command. * * @see Git documentation about Fetch */ public class FetchCommand extends TransportCommand { private String remote = Constants.DEFAULT_REMOTE_NAME; private List refSpecs; private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; private boolean checkFetchedObjects; private Boolean removeDeletedRefs; private boolean dryRun; private boolean thin = Transport.DEFAULT_FETCH_THIN; private TagOpt tagOption; private FetchRecurseSubmodulesMode submoduleRecurseMode = null; private Callback callback; private boolean isForceUpdate; private String initialBranch; private Integer depth; private Instant deepenSince; private List shallowExcludes = new ArrayList<>(); private boolean unshallow; /** * 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); } /** * Constructor for FetchCommand. * * @param repo * a {@link org.eclipse.jgit.lib.Repository} object. */ protected FetchCommand(Repository repo) { super(repo); refSpecs = new ArrayList<>(3); } private FetchRecurseSubmodulesMode getRecurseMode(String path) { // Use the caller-specified mode, if set if (submoduleRecurseMode != null) { return submoduleRecurseMode; } // Fall back to submodule.name.fetchRecurseSubmodules, if set FetchRecurseSubmodulesMode mode = repo.getConfig().getEnum( FetchRecurseSubmodulesMode.values(), ConfigConstants.CONFIG_SUBMODULE_SECTION, path, ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES); if (mode != null) { return mode; } // Fall back to fetch.recurseSubmodules, if set mode = repo.getConfig().getEnum(FetchRecurseSubmodulesMode.values(), ConfigConstants.CONFIG_FETCH_SECTION, null, ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES); if (mode != null) { return mode; } // Default to on-demand mode return FetchRecurseSubmodulesMode.ON_DEMAND; } private void fetchSubmodules(FetchResult results) throws org.eclipse.jgit.api.errors.TransportException, GitAPIException, InvalidConfigurationException { try (SubmoduleWalk walk = new SubmoduleWalk(repo); RevWalk revWalk = new RevWalk(repo)) { // Walk over submodules in the parent repository's FETCH_HEAD. ObjectId fetchHead = repo.resolve(Constants.FETCH_HEAD); if (fetchHead == null) { return; } if (revWalk.parseAny(fetchHead).getType() == Constants.OBJ_BLOB) { return; } walk.setTree(revWalk.parseTree(fetchHead)); while (walk.next()) { try (Repository submoduleRepo = walk.getRepository()) { // Skip submodules that don't exist locally (have not been // cloned), are not registered in the .gitmodules file, or // not registered in the parent repository's config. if (submoduleRepo == null || walk.getModulesPath() == null || walk.getConfigUrl() == null) { continue; } FetchRecurseSubmodulesMode recurseMode = getRecurseMode( walk.getPath()); // When the fetch mode is "yes" we always fetch. When the // mode is "on demand", we only fetch if the submodule's // revision was updated to an object that is not currently // present in the submodule. if ((recurseMode == FetchRecurseSubmodulesMode.ON_DEMAND && !submoduleRepo.getObjectDatabase() .has(walk.getObjectId())) || recurseMode == FetchRecurseSubmodulesMode.YES) { FetchCommand f = new FetchCommand(submoduleRepo) .setProgressMonitor(monitor) .setTagOpt(tagOption) .setCheckFetchedObjects(checkFetchedObjects) .setRemoveDeletedRefs(isRemoveDeletedRefs()) .setThin(thin) .setRefSpecs(applyOptions(refSpecs)) .setDryRun(dryRun) .setRecurseSubmodules(recurseMode); configure(f); if (callback != null) { callback.fetchingSubmodule(walk.getPath()); } results.addSubmodule(walk.getPath(), f.call()); } } } } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } catch (ConfigInvalidException e) { throw new InvalidConfigurationException(e.getMessage(), e); } } /** * {@inheritDoc} *

* Execute the {@code fetch} command with all the options and parameters * collected by the setter methods of this class. Each instance of this * class should only be used for one invocation of the command (means: one * call to {@link #call()}) */ @Override public FetchResult call() throws GitAPIException, InvalidRemoteException, org.eclipse.jgit.api.errors.TransportException { checkCallable(); try (Transport transport = Transport.open(repo, remote)) { transport.setCheckFetchedObjects(checkFetchedObjects); transport.setRemoveDeletedRefs(isRemoveDeletedRefs()); transport.setDryRun(dryRun); if (tagOption != null) transport.setTagOpt(tagOption); transport.setFetchThin(thin); if (depth != null) { transport.setDepth(depth); } if (unshallow) { if (depth != null) { throw new IllegalStateException(JGitText.get().depthWithUnshallow); } transport.setDepth(Constants.INFINITE_DEPTH); } if (deepenSince != null) { transport.setDeepenSince(deepenSince); } transport.setDeepenNots(shallowExcludes); configure(transport); FetchResult result = transport.fetch(monitor, applyOptions(refSpecs), initialBranch); if (!repo.isBare()) { fetchSubmodules(result); } return result; } catch (NoRemoteRepositoryException e) { throw new InvalidRemoteException(MessageFormat.format( JGitText.get().invalidRemote, remote), e); } catch (TransportException e) { throw new org.eclipse.jgit.api.errors.TransportException( e.getMessage(), e); } catch (URISyntaxException e) { throw new InvalidRemoteException(MessageFormat.format( JGitText.get().invalidRemote, remote), e); } catch (NotSupportedException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfFetchCommand, e); } } private List applyOptions(List refSpecs2) { if (!isForceUpdate()) { return refSpecs2; } List updated = new ArrayList<>(3); for (RefSpec refSpec : refSpecs2) { updated.add(refSpec.setForceUpdate(true)); } return updated; } /** * Set the mode to be used for recursing into submodules. * * @param recurse * corresponds to the * --recurse-submodules/--no-recurse-submodules options. If * {@code null} use the value of the * {@code submodule.name.fetchRecurseSubmodules} option * configured per submodule. If not specified there, use the * value of the {@code fetch.recurseSubmodules} option configured * in git config. If not configured in either, "on-demand" is the * built-in default. * @return {@code this} * @since 4.7 */ public FetchCommand setRecurseSubmodules( @Nullable FetchRecurseSubmodulesMode recurse) { checkCallable(); submoduleRecurseMode = recurse; return this; } /** * The remote (uri or name) used for the fetch operation. If no remote is * set, the default value of Constants.DEFAULT_REMOTE_NAME will * be used. * * @see Constants#DEFAULT_REMOTE_NAME * @param remote * name of a remote * @return {@code this} */ public FetchCommand setRemote(String remote) { checkCallable(); this.remote = remote; return this; } /** * Get the remote * * @return the remote used for the remote operation */ public String getRemote() { return remote; } /** * Get timeout * * @return the timeout used for the fetch operation */ public int getTimeout() { return timeout; } /** * Whether to check received objects for validity * * @return whether to check received objects for validity */ public boolean isCheckFetchedObjects() { return checkFetchedObjects; } /** * If set to {@code true}, objects received will be checked for validity * * @param checkFetchedObjects * whether to check objects for validity * @return {@code this} */ public FetchCommand setCheckFetchedObjects(boolean checkFetchedObjects) { checkCallable(); this.checkFetchedObjects = checkFetchedObjects; return this; } /** * Whether to remove refs which no longer exist in the source * * @return whether to remove refs which no longer exist in the source */ public boolean isRemoveDeletedRefs() { if (removeDeletedRefs != null) { return removeDeletedRefs.booleanValue(); } // fall back to configuration boolean result = false; StoredConfig config = repo.getConfig(); result = config.getBoolean(ConfigConstants.CONFIG_FETCH_SECTION, null, ConfigConstants.CONFIG_KEY_PRUNE, result); result = config.getBoolean(ConfigConstants.CONFIG_REMOTE_SECTION, remote, ConfigConstants.CONFIG_KEY_PRUNE, result); return result; } /** * If set to {@code true}, refs are removed which no longer exist in the * source * * @param removeDeletedRefs * whether to remove deleted {@code Ref}s * @return {@code this} */ public FetchCommand setRemoveDeletedRefs(boolean removeDeletedRefs) { checkCallable(); this.removeDeletedRefs = Boolean.valueOf(removeDeletedRefs); return this; } /** * Get progress monitor * * @return the progress monitor for the fetch operation */ public ProgressMonitor getProgressMonitor() { return monitor; } /** * The progress monitor associated with the fetch operation. By default, * this is set to NullProgressMonitor * * @see NullProgressMonitor * @param monitor * a {@link org.eclipse.jgit.lib.ProgressMonitor} * @return {@code this} */ public FetchCommand setProgressMonitor(ProgressMonitor monitor) { checkCallable(); if (monitor == null) { monitor = NullProgressMonitor.INSTANCE; } this.monitor = monitor; return this; } /** * Get list of {@code RefSpec}s * * @return the ref specs */ public List getRefSpecs() { return refSpecs; } /** * The ref specs to be used in the fetch operation * * @param specs * String representation of {@code RefSpec}s * @return {@code this} * @since 4.9 */ public FetchCommand setRefSpecs(String... specs) { return setRefSpecs( Arrays.stream(specs).map(RefSpec::new).collect(toList())); } /** * The ref specs to be used in the fetch operation * * @param specs * one or multiple {@link org.eclipse.jgit.transport.RefSpec}s * @return {@code this} */ public FetchCommand setRefSpecs(RefSpec... specs) { return setRefSpecs(Arrays.asList(specs)); } /** * The ref specs to be used in the fetch operation * * @param specs * list of {@link org.eclipse.jgit.transport.RefSpec}s * @return {@code this} */ public FetchCommand setRefSpecs(List specs) { checkCallable(); this.refSpecs.clear(); this.refSpecs.addAll(specs); return this; } /** * Whether to do a dry run * * @return the dry run preference for the fetch operation */ public boolean isDryRun() { return dryRun; } /** * Sets whether the fetch operation should be a dry run * * @param dryRun * whether to do a dry run * @return {@code this} */ public FetchCommand setDryRun(boolean dryRun) { checkCallable(); this.dryRun = dryRun; return this; } /** * Get thin-pack preference * * @return the thin-pack preference for fetch operation */ public boolean isThin() { return thin; } /** * Sets the thin-pack preference for fetch operation. * * Default setting is Transport.DEFAULT_FETCH_THIN * * @param thinPack * the thin-pack preference * @return {@code this} */ public FetchCommand setThin(boolean thinPack) { checkCallable(); this.thin = thinPack; return this; } /** * Sets the specification of annotated tag behavior during fetch * * @param tagOpt * the {@link org.eclipse.jgit.transport.TagOpt} * @return {@code this} */ public FetchCommand setTagOpt(TagOpt tagOpt) { checkCallable(); this.tagOption = tagOpt; return this; } /** * Set the initial branch * * @param branch * the initial branch to check out when cloning the repository. * Can be specified as ref name (refs/heads/master), * branch name (master) or tag name * (v1.2.3). The default is to use the branch * pointed to by the cloned repository's HEAD and can be * requested by passing {@code null} or HEAD. * @return {@code this} * @since 5.11 */ public FetchCommand setInitialBranch(String branch) { this.initialBranch = branch; 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; } /** * Whether fetch --force option is enabled * * @return whether refs affected by the fetch are updated forcefully * @since 5.0 */ public boolean isForceUpdate() { return this.isForceUpdate; } /** * Set fetch --force option * * @param force * whether to update refs affected by the fetch forcefully * @return this command * @since 5.0 */ public FetchCommand setForceUpdate(boolean force) { this.isForceUpdate = force; return this; } /** * Limits fetching to the specified number of commits from the tip of each * remote branch history. * * @param depth * the depth * @return {@code this} * * @since 6.3 */ public FetchCommand setDepth(int depth) { if (depth < 1) { throw new IllegalArgumentException(JGitText.get().depthMustBeAt1); } this.depth = Integer.valueOf(depth); return this; } /** * Deepens or shortens the history of a shallow repository to include all * reachable commits after a specified time. * * @param shallowSince * the timestammp; must not be {@code null} * @return {@code this} * * @since 6.3 */ public FetchCommand setShallowSince(@NonNull OffsetDateTime shallowSince) { this.deepenSince = shallowSince.toInstant(); return this; } /** * Deepens or shortens the history of a shallow repository to include all * reachable commits after a specified time. * * @param shallowSince * the timestammp; must not be {@code null} * @return {@code this} * * @since 6.3 */ public FetchCommand setShallowSince(@NonNull Instant shallowSince) { this.deepenSince = shallowSince; return this; } /** * Deepens or shortens the history of a shallow repository to exclude * commits reachable from a specified remote branch or tag. * * @param shallowExclude * the ref or commit; must not be {@code null} * @return {@code this} * * @since 6.3 */ public FetchCommand addShallowExclude(@NonNull String shallowExclude) { shallowExcludes.add(shallowExclude); return this; } /** * Creates a shallow clone with a history, excluding commits reachable from * a specified remote branch or tag. * * @param shallowExclude * the commit; must not be {@code null} * @return {@code this} * * @since 6.3 */ public FetchCommand addShallowExclude(@NonNull ObjectId shallowExclude) { shallowExcludes.add(shallowExclude.name()); return this; } /** * If the source repository is complete, converts a shallow repository to a * complete one, removing all the limitations imposed by shallow * repositories. * * If the source repository is shallow, fetches as much as possible so that * the current repository has the same history as the source repository. * * @param unshallow * whether to unshallow or not * @return {@code this} * * @since 6.3 */ public FetchCommand setUnshallow(boolean unshallow) { this.unshallow = unshallow; return this; } void setShallowExcludes(List shallowExcludes) { this.shallowExcludes = shallowExcludes; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8644 Content-Disposition: inline; filename="GarbageCollectCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "f6935e1c672079379dcb83a13bfc77316e9a3b48" /* * Copyright (C) 2012, Matthias Sohn and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; import java.text.ParseException; import java.time.Instant; import java.util.Date; import java.util.Properties; import java.util.concurrent.ExecutionException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.dfs.DfsGarbageCollector; import org.eclipse.jgit.internal.storage.dfs.DfsRepository; import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.internal.storage.file.GC; import org.eclipse.jgit.internal.storage.file.GC.RepoStatistics; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.pack.PackConfig; /** * A class used to execute a {@code gc} command. It has setters for all * supported options and arguments of this command and a {@link #call()} method * to finally execute the command. Each instance of this class should only be * used for one invocation of the command (means: one call to {@link #call()}) * * @since 2.2 * @see Git documentation about gc */ public class GarbageCollectCommand extends GitCommand { /** * Default value of maximum delta chain depth during aggressive garbage * collection: {@value} * * @since 3.6 */ public static final int DEFAULT_GC_AGGRESSIVE_DEPTH = 250; /** * Default window size during packing during aggressive garbage collection: * * {@value} * * @since 3.6 */ public static final int DEFAULT_GC_AGGRESSIVE_WINDOW = 250; private ProgressMonitor monitor; private Instant expire; private PackConfig pconfig; private Boolean packKeptObjects; /** * Constructor for GarbageCollectCommand. * * @param repo * a {@link org.eclipse.jgit.lib.Repository} object. */ protected GarbageCollectCommand(Repository repo) { super(repo); pconfig = new PackConfig(repo); } /** * Set progress monitor * * @param monitor * a progress monitor * @return this instance */ public GarbageCollectCommand setProgressMonitor(ProgressMonitor monitor) { this.monitor = monitor; return this; } /** * During gc() or prune() each unreferenced, loose object which has been * created or modified after expire will not be pruned. Only * older objects may be pruned. If set to null then every object is a * candidate for pruning. Use {@link org.eclipse.jgit.util.GitDateParser} to * parse time formats used by git gc. * * @param expire * minimal age of objects to be pruned. * @return this instance * @deprecated use {@link #setExpire(Instant)} instead */ @Deprecated(since = "7.2") public GarbageCollectCommand setExpire(Date expire) { if (expire != null) { this.expire = expire.toInstant(); } return this; } /** * During gc() or prune() each unreferenced, loose object which has been * created or modified after expire will not be pruned. Only * older objects may be pruned. If set to null then every object is a * candidate for pruning. Use {@link org.eclipse.jgit.util.GitTimeParser} to * parse time formats used by git gc. * * @param expire * minimal age of objects to be pruned. * @return this instance * @since 7.2 */ public GarbageCollectCommand setExpire(Instant expire) { this.expire = expire; return this; } /** * Whether to use aggressive mode or not. If set to true JGit behaves more * similar to native git's "git gc --aggressive". If set to * true compressed objects found in old packs are not reused * but every object is compressed again. Configuration variables pack.window * and pack.depth are set to 250 for this GC. * * @since 3.6 * @param aggressive * whether to turn on or off aggressive mode * @return this instance */ public GarbageCollectCommand setAggressive(boolean aggressive) { if (aggressive) { StoredConfig repoConfig = repo.getConfig(); pconfig.setDeltaSearchWindowSize(repoConfig.getInt( ConfigConstants.CONFIG_GC_SECTION, ConfigConstants.CONFIG_KEY_AGGRESSIVE_WINDOW, DEFAULT_GC_AGGRESSIVE_WINDOW)); pconfig.setMaxDeltaDepth(repoConfig.getInt( ConfigConstants.CONFIG_GC_SECTION, ConfigConstants.CONFIG_KEY_AGGRESSIVE_DEPTH, DEFAULT_GC_AGGRESSIVE_DEPTH)); pconfig.setReuseObjects(false); } else pconfig = new PackConfig(repo); return this; } /** * Whether to include objects in `.keep` packs when repacking. * * @param packKeptObjects * whether to include objects in `.keep` files when repacking. * @return this instance * @since 5.13.3 */ public GarbageCollectCommand setPackKeptObjects(boolean packKeptObjects) { this.packKeptObjects = Boolean.valueOf(packKeptObjects); return this; } /** * Whether to preserve old pack files instead of deleting them. * * @since 4.7 * @param preserveOldPacks * whether to preserve old pack files * @return this instance */ public GarbageCollectCommand setPreserveOldPacks(boolean preserveOldPacks) { if (pconfig == null) pconfig = new PackConfig(repo); pconfig.setPreserveOldPacks(preserveOldPacks); return this; } /** * Whether to prune preserved pack files in the preserved directory. * * @since 4.7 * @param prunePreserved * whether to prune preserved pack files * @return this instance */ public GarbageCollectCommand setPrunePreserved(boolean prunePreserved) { if (pconfig == null) pconfig = new PackConfig(repo); pconfig.setPrunePreserved(prunePreserved); return this; } @Override public Properties call() throws GitAPIException { checkCallable(); try { if (repo instanceof FileRepository) { GC gc = new GC((FileRepository) repo); gc.setPackConfig(pconfig); gc.setProgressMonitor(monitor); if (this.expire != null) gc.setExpire(expire); if (this.packKeptObjects != null) { gc.setPackKeptObjects(packKeptObjects.booleanValue()); } try { gc.gc().get(); return toProperties(gc.getStatistics()); } catch (ParseException | InterruptedException | ExecutionException e) { throw new JGitInternalException(JGitText.get().gcFailed, e); } } else if (repo instanceof DfsRepository) { DfsGarbageCollector gc = new DfsGarbageCollector((DfsRepository) repo); gc.setPackConfig(pconfig); gc.pack(monitor); return new Properties(); } else { throw new UnsupportedOperationException(MessageFormat.format( JGitText.get().unsupportedGC, repo.getClass().toString())); } } catch (IOException e) { throw new JGitInternalException(JGitText.get().gcFailed, e); } } /** * Computes and returns the repository statistics. * * @return the repository statistics * @throws org.eclipse.jgit.api.errors.GitAPIException * thrown if the repository statistics cannot be computed * @since 3.0 */ public Properties getStatistics() throws GitAPIException { try { if (repo instanceof FileRepository) { GC gc = new GC((FileRepository) repo); return toProperties(gc.getStatistics()); } return new Properties(); } catch (IOException e) { throw new JGitInternalException( JGitText.get().couldNotGetRepoStatistics, e); } } @SuppressWarnings("boxing") private static Properties toProperties(RepoStatistics stats) { Properties p = new Properties(); p.put("numberOfBitmaps", stats.numberOfBitmaps); //$NON-NLS-1$ p.put("numberOfLooseObjects", stats.numberOfLooseObjects); //$NON-NLS-1$ p.put("numberOfLooseRefs", stats.numberOfLooseRefs); //$NON-NLS-1$ p.put("numberOfPackedObjects", stats.numberOfPackedObjects); //$NON-NLS-1$ p.put("numberOfPackedRefs", stats.numberOfPackedRefs); //$NON-NLS-1$ p.put("numberOfPackFiles", stats.numberOfPackFiles); //$NON-NLS-1$ p.put("sizeOfLooseObjects", stats.sizeOfLooseObjects); //$NON-NLS-1$ p.put("sizeOfPackedObjects", stats.sizeOfPackedObjects); //$NON-NLS-1$ return p; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 24206 Content-Disposition: inline; filename="Git.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "5bc035a46af60ebf3b130b1e7874486bdfd71490" /* * Copyright (C) 2010, Christian Halstrick * Copyright (C) 2010, 2021 Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static java.util.Objects.requireNonNull; import java.io.File; import java.io.IOException; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryBuilder; import org.eclipse.jgit.lib.RepositoryCache; import org.eclipse.jgit.lib.internal.WorkQueue; import org.eclipse.jgit.nls.NLS; import org.eclipse.jgit.util.FS; /** * Offers a "GitPorcelain"-like API to interact with a git repository. *

* The GitPorcelain commands are described in the Git Documentation. *

* This class only offers methods to construct so-called command classes. Each * GitPorcelain command is represented by one command class.
* Example: this class offers a {@code commit()} method returning an instance of * the {@code CommitCommand} class. The {@code CommitCommand} class has setters * for all the arguments and options. The {@code CommitCommand} class also has a * {@code call} method to actually execute the commit. The following code show's * how to do a simple commit: * *

 * Git git = new Git(myRepo);
 * git.commit().setMessage("Fix393").setAuthor(developerIdent).call();
 * 
* * All mandatory parameters for commands have to be specified in the methods of * this class, the optional parameters have to be specified by the * setter-methods of the Command class. *

* This class is intended to be used internally (e.g. by JGit tests) or by * external components (EGit, third-party tools) when they need exactly the * functionality of a GitPorcelain command. There are use-cases where this class * is not optimal and where you should use the more low-level JGit classes. The * methods in this class may for example offer too much functionality or they * offer the functionality with the wrong arguments. */ public class Git implements AutoCloseable { /** The git repository this class is interacting with */ private final Repository repo; private final boolean closeRepo; /** * Open repository * * @param dir * the repository to open. May be either the GIT_DIR, or the * working tree directory that contains {@code .git}. * @return a {@link org.eclipse.jgit.api.Git} object for the existing git * repository * @throws java.io.IOException * if an IO error occurred */ public static Git open(File dir) throws IOException { return open(dir, FS.DETECTED); } /** * Open repository * * @param dir * the repository to open. May be either the GIT_DIR, or the * working tree directory that contains {@code .git}. * @param fs * filesystem abstraction to use when accessing the repository. * @return a {@link org.eclipse.jgit.api.Git} object for the existing git * repository. Closing this instance will close the repo. * @throws java.io.IOException * if an IO error occurred */ public static Git open(File dir, FS fs) throws IOException { RepositoryCache.FileKey key; key = RepositoryCache.FileKey.lenient(dir, fs); Repository db = new RepositoryBuilder().setFS(fs).setGitDir(key.getFile()) .setMustExist(true).build(); return new Git(db, true); } /** * Wrap repository * * @param repo * the git repository this class is interacting with; * {@code null} is not allowed. * @return a {@link org.eclipse.jgit.api.Git} object for the existing git * repository. The caller is responsible for closing the repository; * {@link #close()} on this instance does not close the repo. */ public static Git wrap(Repository repo) { return new Git(repo); } /** * {@inheritDoc} *

* Free resources associated with this instance. *

* If the repository was opened by a static factory method in this class, * then this method calls {@link Repository#close()} on the underlying * repository instance. (Whether this actually releases underlying * resources, such as file handles, may vary; see {@link Repository} for * more details.) *

* If the repository was created by a caller and passed into * {@link #Git(Repository)} or a static factory method in this class, then * this method does not call close on the underlying repository. *

* In all cases, after calling this method you should not use this * {@link Git} instance anymore. * * @since 3.2 */ @Override public void close() { if (closeRepo) repo.close(); } /** * Return a command object to execute a {@code clone} command * * @see Git documentation about clone * @return a {@link org.eclipse.jgit.api.CloneCommand} used to collect all * optional parameters and to finally execute the {@code clone} * command */ public static CloneCommand cloneRepository() { return new CloneCommand(); } /** * Return a command to list remote branches/tags without a local repository. * * @return a {@link org.eclipse.jgit.api.LsRemoteCommand} * @since 3.1 */ public static LsRemoteCommand lsRemoteRepository() { return new LsRemoteCommand(null); } /** * Return a command object to execute a {@code init} command * * @see Git * documentation about init * @return a {@link org.eclipse.jgit.api.InitCommand} used to collect all * optional parameters and to finally execute the {@code init} * command */ public static InitCommand init() { return new InitCommand(); } /** * Shutdown JGit and release resources it holds like NLS and thread pools * @since 5.8 */ public static void shutdown() { WorkQueue.getExecutor().shutdownNow(); NLS.clear(); } /** * Construct a new {@link org.eclipse.jgit.api.Git} object which can * interact with the specified git repository. *

* All command classes returned by methods of this class will always * interact with this git repository. *

* The caller is responsible for closing the repository; {@link #close()} on * this instance does not close the repo. * * @param repo * the git repository this class is interacting with; * {@code null} is not allowed. */ public Git(Repository repo) { this(repo, false); } Git(Repository repo, boolean closeRepo) { this.repo = requireNonNull(repo); this.closeRepo = closeRepo; } /** * Return a command object to execute a {@code Commit} command * * @see Git documentation about Commit * @return a {@link org.eclipse.jgit.api.CommitCommand} used to collect all * optional parameters and to finally execute the {@code Commit} * command */ public CommitCommand commit() { return new CommitCommand(repo); } /** * Return a command object to execute a {@code Log} command * * @see Git * documentation about Log * @return a {@link org.eclipse.jgit.api.LogCommand} used to collect all * optional parameters and to finally execute the {@code Log} * command */ public LogCommand log() { return new LogCommand(repo); } /** * Return a command object to execute a {@code Merge} command * * @see Git documentation about Merge * @return a {@link org.eclipse.jgit.api.MergeCommand} used to collect all * optional parameters and to finally execute the {@code Merge} * command */ public MergeCommand merge() { return new MergeCommand(repo); } /** * Return a command object to execute a {@code Pull} command * * @return a {@link org.eclipse.jgit.api.PullCommand} */ public PullCommand pull() { return new PullCommand(repo); } /** * Return a command object used to create branches * * @return a {@link org.eclipse.jgit.api.CreateBranchCommand} */ public CreateBranchCommand branchCreate() { return new CreateBranchCommand(repo); } /** * Return a command object used to delete branches * * @return a {@link org.eclipse.jgit.api.DeleteBranchCommand} */ public DeleteBranchCommand branchDelete() { return new DeleteBranchCommand(repo); } /** * Return a command object used to list branches * * @return a {@link org.eclipse.jgit.api.ListBranchCommand} */ public ListBranchCommand branchList() { return new ListBranchCommand(repo); } /** * * Return a command object used to list tags * * @return a {@link org.eclipse.jgit.api.ListTagCommand} */ public ListTagCommand tagList() { return new ListTagCommand(repo); } /** * Return a command object used to rename branches * * @return a {@link org.eclipse.jgit.api.RenameBranchCommand} */ public RenameBranchCommand branchRename() { return new RenameBranchCommand(repo); } /** * Return a command object to execute a {@code Add} command * * @see Git * documentation about Add * @return a {@link org.eclipse.jgit.api.AddCommand} used to collect all * optional parameters and to finally execute the {@code Add} * command */ public AddCommand add() { return new AddCommand(repo); } /** * Return a command object to execute a {@code Tag} command * * @see Git * documentation about Tag * @return a {@link org.eclipse.jgit.api.TagCommand} used to collect all * optional parameters and to finally execute the {@code Tag} * command */ public TagCommand tag() { return new TagCommand(repo); } /** * Return a command object to execute a {@code Fetch} command * * @see Git documentation about Fetch * @return a {@link org.eclipse.jgit.api.FetchCommand} used to collect all * optional parameters and to finally execute the {@code Fetch} * command */ public FetchCommand fetch() { return new FetchCommand(repo); } /** * Return a command object to execute a {@code Push} command * * @see Git * documentation about Push * @return a {@link org.eclipse.jgit.api.PushCommand} used to collect all * optional parameters and to finally execute the {@code Push} * command */ public PushCommand push() { return new PushCommand(repo); } /** * Return a command object to execute a {@code cherry-pick} command * * @see Git documentation about cherry-pick * @return a {@link org.eclipse.jgit.api.CherryPickCommand} used to collect * all optional parameters and to finally execute the * {@code cherry-pick} command */ public CherryPickCommand cherryPick() { return new CherryPickCommand(repo); } /** * Return a command object to execute a {@code revert} command * * @see Git documentation about reverting changes * @return a {@link org.eclipse.jgit.api.RevertCommand} used to collect all * optional parameters and to finally execute the * {@code cherry-pick} command */ public RevertCommand revert() { return new RevertCommand(repo); } /** * Return a command object to execute a {@code Rebase} command * * @see Git documentation about rebase * @return a {@link org.eclipse.jgit.api.RebaseCommand} used to collect all * optional parameters and to finally execute the {@code rebase} * command */ public RebaseCommand rebase() { return new RebaseCommand(repo); } /** * Return a command object to execute a {@code rm} command * * @see Git * documentation about rm * @return a {@link org.eclipse.jgit.api.RmCommand} used to collect all * optional parameters and to finally execute the {@code rm} command */ public RmCommand rm() { return new RmCommand(repo); } /** * Return a command object to execute a {@code checkout} command * * @see Git documentation about checkout * @return a {@link org.eclipse.jgit.api.CheckoutCommand} used to collect * all optional parameters and to finally execute the * {@code checkout} command */ public CheckoutCommand checkout() { return new CheckoutCommand(repo); } /** * Return a command object to execute a {@code reset} command * * @see Git documentation about reset * @return a {@link org.eclipse.jgit.api.ResetCommand} used to collect all * optional parameters and to finally execute the {@code reset} * command */ public ResetCommand reset() { return new ResetCommand(repo); } /** * Return a command object to execute a {@code status} command * * @see Git documentation about status * @return a {@link org.eclipse.jgit.api.StatusCommand} used to collect all * optional parameters and to finally execute the {@code status} * command */ public StatusCommand status() { return new StatusCommand(repo); } /** * Return a command to create an archive from a tree * * @return a {@link org.eclipse.jgit.api.ArchiveCommand} * @since 3.1 */ public ArchiveCommand archive() { return new ArchiveCommand(repo); } /** * Return a command to add notes to an object * * @return a {@link org.eclipse.jgit.api.AddNoteCommand} */ public AddNoteCommand notesAdd() { return new AddNoteCommand(repo); } /** * Return a command to remove notes on an object * * @return a {@link org.eclipse.jgit.api.RemoveNoteCommand} */ public RemoveNoteCommand notesRemove() { return new RemoveNoteCommand(repo); } /** * Return a command to list all notes * * @return a {@link org.eclipse.jgit.api.ListNotesCommand} */ public ListNotesCommand notesList() { return new ListNotesCommand(repo); } /** * Return a command to show notes on an object * * @return a {@link org.eclipse.jgit.api.ShowNoteCommand} */ public ShowNoteCommand notesShow() { return new ShowNoteCommand(repo); } /** * Return a command object to execute a {@code ls-remote} command * * @see Git documentation about ls-remote * @return a {@link org.eclipse.jgit.api.LsRemoteCommand} used to collect * all optional parameters and to finally execute the {@code status} * command */ public LsRemoteCommand lsRemote() { return new LsRemoteCommand(repo); } /** * Return a command object to execute a {@code clean} command * * @see Git documentation about Clean * @return a {@link org.eclipse.jgit.api.CleanCommand} used to collect all * optional parameters and to finally execute the {@code clean} * command */ public CleanCommand clean() { return new CleanCommand(repo); } /** * Return a command object to execute a {@code blame} command * * @see Git documentation about Blame * @return a {@link org.eclipse.jgit.api.BlameCommand} used to collect all * optional parameters and to finally execute the {@code blame} * command */ public BlameCommand blame() { return new BlameCommand(repo); } /** * Return a command object to execute a {@code reflog} command * * @see Git documentation about reflog * @return a {@link org.eclipse.jgit.api.ReflogCommand} used to collect all * optional parameters and to finally execute the {@code reflog} * command */ public ReflogCommand reflog() { return new ReflogCommand(repo); } /** * Return a command object to execute a {@code diff} command * * @see Git * documentation about diff * @return a {@link org.eclipse.jgit.api.DiffCommand} used to collect all * optional parameters and to finally execute the {@code diff} * command */ public DiffCommand diff() { return new DiffCommand(repo); } /** * Return a command object used to delete tags * * @return a {@link org.eclipse.jgit.api.DeleteTagCommand} */ public DeleteTagCommand tagDelete() { return new DeleteTagCommand(repo); } /** * Return a command object to execute a {@code submodule add} command * * @return a {@link org.eclipse.jgit.api.SubmoduleAddCommand} used to add a * new submodule to a parent repository */ public SubmoduleAddCommand submoduleAdd() { return new SubmoduleAddCommand(repo); } /** * Return a command object to execute a {@code submodule init} command * * @return a {@link org.eclipse.jgit.api.SubmoduleInitCommand} used to * initialize the repository's config with settings from the * .gitmodules file in the working tree */ public SubmoduleInitCommand submoduleInit() { return new SubmoduleInitCommand(repo); } /** * Returns a command object to execute a {@code submodule deinit} command * * @return a {@link org.eclipse.jgit.api.SubmoduleDeinitCommand} used to * remove a submodule's working tree manifestation * @since 4.10 */ public SubmoduleDeinitCommand submoduleDeinit() { return new SubmoduleDeinitCommand(repo); } /** * Returns a command object to execute a {@code submodule status} command * * @return a {@link org.eclipse.jgit.api.SubmoduleStatusCommand} used to * report the status of a repository's configured submodules */ public SubmoduleStatusCommand submoduleStatus() { return new SubmoduleStatusCommand(repo); } /** * Return a command object to execute a {@code submodule sync} command * * @return a {@link org.eclipse.jgit.api.SubmoduleSyncCommand} used to * update the URL of a submodule from the parent repository's * .gitmodules file */ public SubmoduleSyncCommand submoduleSync() { return new SubmoduleSyncCommand(repo); } /** * Return a command object to execute a {@code submodule update} command * * @return a {@link org.eclipse.jgit.api.SubmoduleUpdateCommand} used to * update the submodules in a repository to the configured revision */ public SubmoduleUpdateCommand submoduleUpdate() { return new SubmoduleUpdateCommand(repo); } /** * Return a command object used to list stashed commits * * @return a {@link org.eclipse.jgit.api.StashListCommand} */ public StashListCommand stashList() { return new StashListCommand(repo); } /** * Return a command object used to create a stashed commit * * @return a {@link org.eclipse.jgit.api.StashCreateCommand} * @since 2.0 */ public StashCreateCommand stashCreate() { return new StashCreateCommand(repo); } /** * Returs a command object used to apply a stashed commit * * @return a {@link org.eclipse.jgit.api.StashApplyCommand} * @since 2.0 */ public StashApplyCommand stashApply() { return new StashApplyCommand(repo); } /** * Return a command object used to drop a stashed commit * * @return a {@link org.eclipse.jgit.api.StashDropCommand} * @since 2.0 */ public StashDropCommand stashDrop() { return new StashDropCommand(repo); } /** * Return a command object to execute a {@code apply} command * * @see Git documentation about apply * @return a {@link org.eclipse.jgit.api.ApplyCommand} used to collect all * optional parameters and to finally execute the {@code apply} * command * @since 2.0 */ public ApplyCommand apply() { return new ApplyCommand(repo); } /** * Return a command object to execute a {@code gc} command * * @see Git * documentation about gc * @return a {@link org.eclipse.jgit.api.GarbageCollectCommand} used to * collect all optional parameters and to finally execute the * {@code gc} command * @since 2.2 */ public GarbageCollectCommand gc() { return new GarbageCollectCommand(repo); } /** * Return a command object to execute a {@code PackRefs} command * * @return a {@link org.eclipse.jgit.api.PackRefsCommand} * @since 7.1 */ public PackRefsCommand packRefs() { return new PackRefsCommand(repo); } /** * Return a command object to find human-readable names of revisions. * * @return a {@link org.eclipse.jgit.api.NameRevCommand}. * @since 3.0 */ public NameRevCommand nameRev() { return new NameRevCommand(repo); } /** * Return a command object to come up with a short name that describes a * commit in terms of the nearest git tag. * * @return a {@link org.eclipse.jgit.api.DescribeCommand}. * @since 3.2 */ public DescribeCommand describe() { return new DescribeCommand(repo); } /** * Return a command used to list the available remotes. * * @return a {@link org.eclipse.jgit.api.RemoteListCommand} * @since 4.2 */ public RemoteListCommand remoteList() { return new RemoteListCommand(repo); } /** * Return a command used to add a new remote. * * @return a {@link org.eclipse.jgit.api.RemoteAddCommand} * @since 4.2 */ public RemoteAddCommand remoteAdd() { return new RemoteAddCommand(repo); } /** * Return a command used to remove an existing remote. * * @return a {@link org.eclipse.jgit.api.RemoteRemoveCommand} * @since 4.2 */ public RemoteRemoveCommand remoteRemove() { return new RemoteRemoveCommand(repo); } /** * Return a command used to change the URL of an existing remote. * * @return a {@link org.eclipse.jgit.api.RemoteSetUrlCommand} * @since 4.2 */ public RemoteSetUrlCommand remoteSetUrl() { return new RemoteSetUrlCommand(repo); } /** * Return a command to verify signatures of tags or commits. * * @return a {@link VerifySignatureCommand} * @since 5.11 */ public VerifySignatureCommand verifySignature() { return new VerifySignatureCommand(repo); } /** * Get repository * * @return the git repository this class is interacting with; see * {@link #close()} for notes on closing this repository. */ public Repository getRepository() { return repo; } @Override public String toString() { return "Git[" + repo + "]"; //$NON-NLS-1$//$NON-NLS-2$ } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5131 Content-Disposition: inline; filename="GitCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "ee7d75cf9580ce2e08922827fe6acd951e860691" /* * Copyright (C) 2010, Christian Halstrick 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.api; import java.text.MessageFormat; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; /** * Common superclass of all commands in the package {@code org.eclipse.jgit.api} *

* This class ensures that all commands fulfill the * {@link java.util.concurrent.Callable} interface. It also has a property * {@link #repo} holding a reference to the git * {@link org.eclipse.jgit.lib.Repository} this command should work with. *

* Finally this class stores a state telling whether it is allowed to call * {@link #call()} on this instance. Instances of * {@link org.eclipse.jgit.api.GitCommand} can only be used for one single * successful call to {@link #call()}. Afterwards this instance may not be used * anymore to set/modify any properties or to call {@link #call()} again. This * is achieved by setting the {@link #callable} property to false after the * successful execution of {@link #call()} and to check the state (by calling * {@link #checkCallable()}) before setting of properties and inside * {@link #call()}. * * @param * the return type which is expected from {@link #call()} */ public abstract class GitCommand implements Callable { /** The repository this command is working with */ final protected Repository repo; /** * a state which tells whether it is allowed to call {@link #call()} on this * instance. */ private AtomicBoolean callable = new AtomicBoolean(true); /** * Creates a new command which interacts with a single repository * * @param repo * the {@link org.eclipse.jgit.lib.Repository} this command * should interact with */ protected GitCommand(Repository repo) { this.repo = repo; } /** * Get repository this command is working on * * @return the {@link org.eclipse.jgit.lib.Repository} this command is * interacting with */ public Repository getRepository() { return repo; } /** * Set's the state which tells whether it is allowed to call {@link #call()} * on this instance. {@link #checkCallable()} will throw an exception when * called and this property is set to {@code false} * * @param callable * if true it is allowed to call {@link #call()} on * this instance. */ protected void setCallable(boolean callable) { this.callable.set(callable); } /** * Checks that the property {@link #callable} is {@code true}. If not then * an {@link java.lang.IllegalStateException} is thrown * * @throws java.lang.IllegalStateException * when this method is called and the property {@link #callable} * is {@code false} */ protected void checkCallable() { if (!callable.get()) throw new IllegalStateException(MessageFormat.format( JGitText.get().commandWasCalledInTheWrongState , this.getClass().getName())); } /** * {@inheritDoc} *

* Execute the command */ @Override public abstract T call() throws GitAPIException; } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7150 Content-Disposition: inline; filename="InitCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "1da71aa6eb9c89ef55bee4605077d0e71bf42bf8" /* * Copyright (C) 2010, Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.File; import java.io.IOException; import java.text.MessageFormat; import java.util.concurrent.Callable; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryBuilder; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.SystemReader; /** * Create an empty git repository or reinitalize an existing one * * @see Git documentation about init */ public class InitCommand implements Callable { private File directory; private File gitDir; private boolean bare; private FS fs; private String initialBranch; private boolean relativePaths; /** * {@inheritDoc} *

* Executes the {@code Init} command. * * @return a {@code Git} instance that owns the {@code Repository} that it * wraps. */ @Override public Git call() throws GitAPIException { try { RepositoryBuilder builder = new RepositoryBuilder(); if (bare) builder.setBare(); if (fs != null) { builder.setFS(fs); } builder.readEnvironment(); if (gitDir != null) builder.setGitDir(gitDir); else gitDir = builder.getGitDir(); if (directory != null) { if (bare) builder.setGitDir(directory); else { builder.setWorkTree(directory); if (gitDir == null) builder.setGitDir(new File(directory, Constants.DOT_GIT)); } } else if (builder.getGitDir() == null) { String dStr = SystemReader.getInstance() .getProperty("user.dir"); //$NON-NLS-1$ if (dStr == null) dStr = "."; //$NON-NLS-1$ File d = new File(dStr); if (!bare) d = new File(d, Constants.DOT_GIT); builder.setGitDir(d); } else { // directory was not set but gitDir was set if (!bare) { String dStr = SystemReader.getInstance().getProperty( "user.dir"); //$NON-NLS-1$ if (dStr == null) dStr = "."; //$NON-NLS-1$ builder.setWorkTree(new File(dStr)); } } builder.setInitialBranch(StringUtils.isEmptyOrNull(initialBranch) ? SystemReader.getInstance().getUserConfig().getString( ConfigConstants.CONFIG_INIT_SECTION, null, ConfigConstants.CONFIG_KEY_DEFAULT_BRANCH) : initialBranch); Repository repository = builder.build(); if (!repository.getObjectDatabase().exists()) if (repository instanceof FileRepository) { ((FileRepository) repository).create(bare, relativePaths); } else { repository.create(bare); } return new Git(repository, true); } catch (IOException | ConfigInvalidException e) { throw new JGitInternalException(e.getMessage(), e); } } /** * The optional directory associated with the init operation. If no * directory is set, we'll use the current directory * * @param directory * the directory to init to * @return this instance * @throws java.lang.IllegalStateException * if the combination of directory, gitDir and bare is illegal. * E.g. if for a non-bare repository directory and gitDir point * to the same directory of if for a bare repository both * directory and gitDir are specified */ public InitCommand setDirectory(File directory) throws IllegalStateException { validateDirs(directory, gitDir, bare); this.directory = directory; return this; } /** * Set the repository meta directory (.git) * * @param gitDir * the repository meta directory * @return this instance * @throws java.lang.IllegalStateException * if the combination of directory, gitDir and bare is illegal. * E.g. if for a non-bare repository directory and gitDir point * to the same directory of if for a bare repository both * directory and gitDir are specified * @since 3.6 */ public InitCommand setGitDir(File gitDir) throws IllegalStateException { validateDirs(directory, gitDir, bare); this.gitDir = gitDir; return this; } private static void validateDirs(File directory, File gitDir, boolean bare) throws IllegalStateException { if (directory != null) { if (bare) { if (gitDir != null && !gitDir.equals(directory)) throw new IllegalStateException(MessageFormat.format( JGitText.get().initFailedBareRepoDifferentDirs, gitDir, directory)); } else { if (gitDir != null && gitDir.equals(directory)) throw new IllegalStateException(MessageFormat.format( JGitText.get().initFailedNonBareRepoSameDirs, gitDir, directory)); } } } /** * Set whether the repository is bare or not * * @param bare * whether the repository is bare or not * @throws java.lang.IllegalStateException * if the combination of directory, gitDir and bare is illegal. * E.g. if for a non-bare repository directory and gitDir point * to the same directory of if for a bare repository both * directory and gitDir are specified * @return this instance */ public InitCommand setBare(boolean bare) { validateDirs(directory, gitDir, bare); this.bare = bare; return this; } /** * Set the file system abstraction to be used for repositories created by * this command. * * @param fs * the abstraction. * @return {@code this} (for chaining calls). * @since 4.10 */ public InitCommand setFs(FS fs) { this.fs = fs; return this; } /** * Set the initial branch of the new repository. If not specified * ({@code null} or empty), fall back to the default name (currently * master). * * @param branch * initial branch name of the new repository * @return {@code this} * @throws InvalidRefNameException * if the branch name is not valid * * @since 5.11 */ public InitCommand setInitialBranch(String branch) throws InvalidRefNameException { this.initialBranch = branch; return this; } /** * * Set whether the repository shall use relative paths for GIT_DIR and * GIT_WORK_TREE * * @param relativePaths * if true, use relative paths for GIT_DIR and GIT_WORK_TREE * @return {@code this} * @since 7.2 */ public InitCommand setRelativeDirs(boolean relativePaths) { this.relativePaths = relativePaths; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4322 Content-Disposition: inline; filename="ListBranchCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "e3c3c89bcba2c3be4c46da9e09d4e8eb4973db86" /* * Copyright (C) 2010, Mathias Kinzler * Copyright (C) 2010, Chris Aniszczyk * Copyright (C) 2014, Robin Stocker and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static org.eclipse.jgit.lib.Constants.HEAD; import static org.eclipse.jgit.lib.Constants.R_HEADS; import static org.eclipse.jgit.lib.Constants.R_REMOTES; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalkUtils; /** * Used to obtain a list of branches. *

* In case HEAD is detached (it points directly to a commit), it is also * returned in the results. * * @see Git documentation about Branch */ public class ListBranchCommand extends GitCommand> { private ListMode listMode; private String containsCommitish; /** * The modes available for listing branches (corresponding to the -r and -a * options) */ public enum ListMode { /** * Corresponds to the -a option (all branches) */ ALL, /** * Corresponds to the -r option (remote branches only) */ REMOTE; } /** * Constructor for ListBranchCommand. * * @param repo * a {@link org.eclipse.jgit.lib.Repository} object. */ protected ListBranchCommand(Repository repo) { super(repo); } @Override public List call() throws GitAPIException { checkCallable(); List resultRefs; try { Collection refs = new ArrayList<>(); // Also return HEAD if it's detached Ref head = repo.exactRef(HEAD); if (head != null && head.getLeaf().getName().equals(HEAD)) { refs.add(head); } if (listMode == null) { refs.addAll(repo.getRefDatabase().getRefsByPrefix(R_HEADS)); } else if (listMode == ListMode.REMOTE) { refs.addAll(repo.getRefDatabase().getRefsByPrefix(R_REMOTES)); } else { refs.addAll(repo.getRefDatabase().getRefsByPrefix(R_HEADS, R_REMOTES)); } resultRefs = new ArrayList<>(filterRefs(refs)); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } Collections.sort(resultRefs, (Ref o1, Ref o2) -> o1.getName().compareTo(o2.getName())); setCallable(false); return resultRefs; } private Collection filterRefs(Collection refs) throws RefNotFoundException, IOException { if (containsCommitish == null) return refs; try (RevWalk walk = new RevWalk(repo)) { ObjectId resolved = repo.resolve(containsCommitish); if (resolved == null) throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, containsCommitish)); RevCommit containsCommit = walk.parseCommit(resolved); return RevWalkUtils.findBranchesReachableFrom(containsCommit, walk, refs); } } /** * Set the list mode * * @param listMode * optional: corresponds to the -r/-a options; by default, only * local branches will be listed * @return this instance */ public ListBranchCommand setListMode(ListMode listMode) { checkCallable(); this.listMode = listMode; return this; } /** * If this is set, only the branches that contain the specified commit-ish * as an ancestor are returned. * * @param containsCommitish * a commit ID or ref name * @return this instance * @since 3.4 */ public ListBranchCommand setContains(String containsCommitish) { checkCallable(); this.containsCommitish = containsCommitish; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2427 Content-Disposition: inline; filename="ListNotesCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "9eb52866dc51dd5dae2bb3cafece9b77cd2c6e3a" /* * Copyright (C) 2011, Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.notes.Note; import org.eclipse.jgit.notes.NoteMap; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; /** * List object notes. * * @see Git documentation about Notes */ public class ListNotesCommand extends GitCommand> { private String notesRef = Constants.R_NOTES_COMMITS; /** * Constructor for ListNotesCommand. * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected ListNotesCommand(Repository repo) { super(repo); } @Override public List call() throws GitAPIException { checkCallable(); List notes = new ArrayList<>(); NoteMap map = NoteMap.newEmptyMap(); try (RevWalk walk = new RevWalk(repo)) { Ref ref = repo.findRef(notesRef); // if we have a notes ref, use it if (ref != null) { RevCommit notesCommit = walk.parseCommit(ref.getObjectId()); map = NoteMap.read(walk.getObjectReader(), notesCommit); } Iterator i = map.iterator(); while (i.hasNext()) notes.add(i.next()); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } return notes; } /** * Set the {@code Ref} to read notes from * * @param notesRef * the name of the {@code Ref} to read notes from. Note, the * default value of * {@link org.eclipse.jgit.lib.Constants#R_NOTES_COMMITS} will be * used if nothing is set * @return {@code this} * @see Constants#R_NOTES_COMMITS */ public ListNotesCommand setNotesRef(String notesRef) { checkCallable(); this.notesRef = notesRef; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2772 Content-Disposition: inline; filename="ListTagCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "9a4a822b59a561a51088757781570a360b57306c" /* * Copyright (C) 2011, Ketan Padegaonkar and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; /** * Used to obtain a list of tags. * * @see Git documentation about Tag */ public class ListTagCommand extends GitCommand> { private final RevWalk rw; private RevCommit commit; /** * Constructor for ListTagCommand. * * @param repo * a {@link org.eclipse.jgit.lib.Repository} object. */ protected ListTagCommand(Repository repo) { super(repo); rw = new RevWalk(repo); } /** * Only list tags which contain the specified commit. * * @param commit * the specified commit * @return this command * @throws IOException * if an IO error occurred * @throws IncorrectObjectTypeException * if commit has an incorrect object type * @throws MissingObjectException * if the commit is missing * * @since 6.6 */ public ListTagCommand setContains(AnyObjectId commit) throws MissingObjectException, IncorrectObjectTypeException, IOException { checkCallable(); this.commit = rw.parseCommit(commit); return this; } @Override public List call() throws GitAPIException { checkCallable(); List tags; try { List refList = repo.getRefDatabase() .getRefsByPrefix(Constants.R_TAGS); if (commit != null) { // if body is retained #getMergedInto needs to access data not // available in commit graph which is slower rw.setRetainBody(false); tags = rw.getMergedInto(commit, refList); } else { tags = new ArrayList<>(refList); } } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } finally { rw.close(); } Collections.sort(tags, (Ref o1, Ref o2) -> o1.getName().compareTo(o2.getName())); setCallable(false); return tags; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 13190 Content-Disposition: inline; filename="LogCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "2a8d34ed6842671913adc4326dabdd03684e2c44" /* * Copyright (C) 2010, Christian Halstrick and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; 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.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.filter.AndRevFilter; import org.eclipse.jgit.revwalk.filter.MaxCountRevFilter; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.revwalk.filter.SkipRevFilter; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; import org.eclipse.jgit.treewalk.filter.TreeFilter; /** * A class used to execute a {@code Log} command. It has setters for all * supported options and arguments of this command and a {@link #call()} method * to finally execute the command. Each instance of this class should only be * used for one invocation of the command (means: one call to {@link #call()}) *

* Examples (git is a {@link org.eclipse.jgit.api.Git} instance): *

* Get newest 10 commits, starting from the current branch: * *

 * ObjectId head = repository.resolve(Constants.HEAD);
 *
 * Iterable<RevCommit> commits = git.log().add(head).setMaxCount(10).call();
 * 
*

* * Get commits only for a specific file: * *

 * git.log().add(head).addPath("dir/filename.txt").call();
 * 
* * @see Git documentation about Log */ public class LogCommand extends GitCommand> { private RevWalk walk; private boolean startSpecified = false; private RevFilter revFilter; private final List pathFilters = new ArrayList<>(); private final List excludeTreeFilters = new ArrayList<>(); private int maxCount = -1; private int skip = -1; /** * Constructor for LogCommand. * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected LogCommand(Repository repo) { super(repo); walk = new RevWalk(repo); } /** * {@inheritDoc} *

* Executes the {@code Log} command with all the options and parameters * collected by the setter methods (e.g. {@link #add(AnyObjectId)}, * {@link #not(AnyObjectId)}, ..) of this class. Each instance of this class * should only be used for one invocation of the command. Don't call this * method twice on an instance. */ @Override public Iterable call() throws GitAPIException, NoHeadException { checkCallable(); List filters = new ArrayList<>(); if (!pathFilters.isEmpty()) { filters.add(AndTreeFilter.create(PathFilterGroup.create(pathFilters), TreeFilter.ANY_DIFF)); } if (!excludeTreeFilters.isEmpty()) { for (TreeFilter f : excludeTreeFilters) { filters.add(AndTreeFilter.create(f, TreeFilter.ANY_DIFF)); } } if (!filters.isEmpty()) { if (filters.size() == 1) { walk.setTreeFilter(filters.get(0)); } else { walk.setTreeFilter(AndTreeFilter.create(filters)); } } if (skip > -1 && maxCount > -1) walk.setRevFilter(AndRevFilter.create(SkipRevFilter.create(skip), MaxCountRevFilter.create(maxCount))); else if (skip > -1) walk.setRevFilter(SkipRevFilter.create(skip)); else if (maxCount > -1) walk.setRevFilter(MaxCountRevFilter.create(maxCount)); if (!startSpecified) { try { ObjectId headId = repo.resolve(Constants.HEAD); if (headId == null) throw new NoHeadException( JGitText.get().noHEADExistsAndNoExplicitStartingRevisionWasSpecified); add(headId); } catch (IOException e) { // all exceptions thrown by add() shouldn't occur and represent // severe low-level exception which are therefore wrapped throw new JGitInternalException( JGitText.get().anExceptionOccurredWhileTryingToAddTheIdOfHEAD, e); } } if (this.revFilter != null) { walk.setRevFilter(this.revFilter); } setCallable(false); return walk; } /** * Mark a commit to start graph traversal from. * * @see RevWalk#markStart(RevCommit) * @param start * the id of the commit to start from * @return {@code this} * @throws org.eclipse.jgit.errors.MissingObjectException * the commit supplied is not available from the object * database. This usually indicates the supplied commit is * invalid, but the reference was constructed during an earlier * invocation to * {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}. * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object was not parsed yet and it was discovered during * parsing that it is not actually a commit. This usually * indicates the caller supplied a non-commit SHA-1 to * {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}. * @throws JGitInternalException * a low-level exception of JGit has occurred. The original * exception can be retrieved by calling * {@link java.lang.Exception#getCause()}. Expect only * {@code IOException's} to be wrapped. Subclasses of * {@link java.io.IOException} (e.g. * {@link org.eclipse.jgit.errors.MissingObjectException}) are * typically not wrapped here but thrown as original exception */ public LogCommand add(AnyObjectId start) throws MissingObjectException, IncorrectObjectTypeException { return add(true, start); } /** * Same as {@code --not start}, or {@code ^start} * * @param start * a {@link org.eclipse.jgit.lib.AnyObjectId} * @return {@code this} * @throws org.eclipse.jgit.errors.MissingObjectException * the commit supplied is not available from the object * database. This usually indicates the supplied commit is * invalid, but the reference was constructed during an earlier * invocation to * {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}. * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object was not parsed yet and it was discovered during * parsing that it is not actually a commit. This usually * indicates the caller supplied a non-commit SHA-1 to * {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}. * @throws JGitInternalException * a low-level exception of JGit has occurred. The original * exception can be retrieved by calling * {@link java.lang.Exception#getCause()}. Expect only * {@code IOException's} to be wrapped. Subclasses of * {@link java.io.IOException} (e.g. * {@link org.eclipse.jgit.errors.MissingObjectException}) are * typically not wrapped here but thrown as original exception */ public LogCommand not(AnyObjectId start) throws MissingObjectException, IncorrectObjectTypeException { return add(false, start); } /** * Adds the range {@code since..until} * * @param since * a {@link org.eclipse.jgit.lib.AnyObjectId} object. * @param until * a {@link org.eclipse.jgit.lib.AnyObjectId} object. * @return {@code this} * @throws org.eclipse.jgit.errors.MissingObjectException * the commit supplied is not available from the object * database. This usually indicates the supplied commit is * invalid, but the reference was constructed during an earlier * invocation to * {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}. * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException * the object was not parsed yet and it was discovered during * parsing that it is not actually a commit. This usually * indicates the caller supplied a non-commit SHA-1 to * {@link org.eclipse.jgit.revwalk.RevWalk#lookupCommit(AnyObjectId)}. * @throws JGitInternalException * a low-level exception of JGit has occurred. The original * exception can be retrieved by calling * {@link java.lang.Exception#getCause()}. Expect only * {@code IOException's} to be wrapped. Subclasses of * {@link java.io.IOException} (e.g. * {@link org.eclipse.jgit.errors.MissingObjectException}) are * typically not wrapped here but thrown as original exception */ public LogCommand addRange(AnyObjectId since, AnyObjectId until) throws MissingObjectException, IncorrectObjectTypeException { return not(since).add(until); } /** * Add all refs as commits to start the graph traversal from. * * @see #add(AnyObjectId) * @return {@code this} * @throws java.io.IOException * the references could not be accessed */ public LogCommand all() throws IOException { for (Ref ref : getRepository().getRefDatabase().getRefs()) { if(!ref.isPeeled()) ref = getRepository().getRefDatabase().peel(ref); ObjectId objectId = ref.getPeeledObjectId(); if (objectId == null) objectId = ref.getObjectId(); RevCommit commit = null; try { commit = walk.parseCommit(objectId); } catch (MissingObjectException | IncorrectObjectTypeException e) { // ignore as traversal starting point: // - the ref points to an object that does not exist // - the ref points to an object that is not a commit (e.g. a // tree or a blob) } if (commit != null) add(commit); } return this; } /** * Show only commits that affect any of the specified paths. The path must * either name a file or a directory exactly and use / (slash) * as separator. Note that regex expressions or wildcards are not supported. * * @param path * a repository-relative path (with / as separator) * @return {@code this} */ public LogCommand addPath(String path) { checkCallable(); pathFilters.add(PathFilter.create(path)); return this; } /** * Show all commits that are not within any of the specified paths. The path * must either name a file or a directory exactly and use / * (slash) as separator. Note that regular expressions or wildcards are not * yet supported. If a path is both added and excluded from the search, then * the exclusion wins. * * @param path * a repository-relative path (with / as separator) * @return {@code this} * @since 5.6 */ public LogCommand excludePath(String path) { checkCallable(); excludeTreeFilters.add(PathFilter.create(path).negate()); return this; } /** * Skip the number of commits before starting to show the commit output. * * @param skip * the number of commits to skip * @return {@code this} */ public LogCommand setSkip(int skip) { checkCallable(); this.skip = skip; return this; } /** * Limit the number of commits to output. * * @param maxCount * the limit * @return {@code this} */ public LogCommand setMaxCount(int maxCount) { checkCallable(); this.maxCount = maxCount; return this; } private LogCommand add(boolean include, AnyObjectId start) throws MissingObjectException, IncorrectObjectTypeException, JGitInternalException { checkCallable(); try { if (include) { walk.markStart(walk.lookupCommit(start)); startSpecified = true; } else walk.markUninteresting(walk.lookupCommit(start)); return this; } catch (MissingObjectException | IncorrectObjectTypeException e) { throw e; } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().exceptionOccurredDuringAddingOfOptionToALogCommand , start), e); } } /** * Set a filter for the LogCommand. * * @param aFilter * the filter that this instance of LogCommand * should use * @return {@code this} * @since 4.4 */ public LogCommand setRevFilter(RevFilter aFilter) { checkCallable(); this.revFilter = aFilter; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6057 Content-Disposition: inline; filename="LsRemoteCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "c3415581ef96263705f3699683956c9051cfa5e4" /* * Copyright (C) 2011, 2022 Christoph Brill and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.FetchConnection; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.Transport; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.transport.UrlConfig; import org.eclipse.jgit.util.SystemReader; /** * The ls-remote command * * @see Git documentation about ls-remote */ public class LsRemoteCommand extends TransportCommand> { private String remote = Constants.DEFAULT_REMOTE_NAME; private boolean heads; private boolean tags; private String uploadPack; /** * Constructor for LsRemoteCommand * * @param repo * local repository or null for operation without local * repository */ public LsRemoteCommand(Repository repo) { super(repo); } /** * The remote (uri or name) used for the fetch operation. If no remote is * set, the default value of Constants.DEFAULT_REMOTE_NAME will * be used. * * @see Constants#DEFAULT_REMOTE_NAME * @param remote * a {@link java.lang.String} object. * @return {@code this} */ public LsRemoteCommand setRemote(String remote) { checkCallable(); this.remote = remote; return this; } /** * Include refs/heads in references results * * @param heads * whether to include refs/heads * @return {@code this} */ public LsRemoteCommand setHeads(boolean heads) { this.heads = heads; return this; } /** * Include refs/tags in references results * * @param tags * whether to include tags * @return {@code this} */ public LsRemoteCommand setTags(boolean tags) { this.tags = tags; return this; } /** * The full path of git-upload-pack on the remote host * * @param uploadPack * the full path of executable providing the git-upload-pack * service on remote host * @return {@code this} */ public LsRemoteCommand setUploadPack(String uploadPack) { this.uploadPack = uploadPack; return this; } /** * {@inheritDoc} *

* Execute the {@code LsRemote} command with all the options and parameters * collected by the setter methods (e.g. {@link #setHeads(boolean)}) of this * class. Each instance of this class should only be used for one invocation * of the command. Don't call this method twice on an instance. */ @Override public Collection call() throws GitAPIException, InvalidRemoteException, org.eclipse.jgit.api.errors.TransportException { return execute().values(); } /** * Same as {@link #call()}, but return Map instead of Collection. * * @return a map from names to references in the remote repository * @throws org.eclipse.jgit.api.errors.GitAPIException * or subclass thereof when an error occurs * @throws org.eclipse.jgit.api.errors.InvalidRemoteException * when called with an invalid remote uri * @throws org.eclipse.jgit.api.errors.TransportException * for errors that occurs during transport * @since 3.5 */ public Map callAsMap() throws GitAPIException, InvalidRemoteException, org.eclipse.jgit.api.errors.TransportException { return Collections.unmodifiableMap(execute()); } private Map execute() throws GitAPIException, InvalidRemoteException, org.eclipse.jgit.api.errors.TransportException { checkCallable(); try (Transport transport = repo != null ? Transport.open(repo, remote) : Transport.open(new URIish(translate(remote)))) { transport.setOptionUploadPack(uploadPack); configure(transport); Collection refSpecs = new ArrayList<>(1); if (tags) refSpecs.add(new RefSpec( "refs/tags/*:refs/remotes/origin/tags/*")); //$NON-NLS-1$ if (heads) refSpecs.add(new RefSpec("refs/heads/*:refs/remotes/origin/*")); //$NON-NLS-1$ Collection refs; Map refmap = new HashMap<>(); try (FetchConnection fc = transport.openFetch(refSpecs)) { refs = fc.getRefs(); if (refSpecs.isEmpty()) for (Ref r : refs) refmap.put(r.getName(), r); else for (Ref r : refs) for (RefSpec rs : refSpecs) if (rs.matchSource(r)) { refmap.put(r.getName(), r); break; } return refmap; } } catch (URISyntaxException e) { throw new InvalidRemoteException(MessageFormat.format( JGitText.get().invalidRemote, remote), e); } catch (NotSupportedException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfLsRemoteCommand, e); } catch (IOException | ConfigInvalidException e) { throw new org.eclipse.jgit.api.errors.TransportException( e.getMessage(), e); } } private String translate(String uri) throws IOException, ConfigInvalidException { UrlConfig urls = new UrlConfig( SystemReader.getInstance().getUserConfig()); return urls.replace(uri); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 21344 Content-Disposition: inline; filename="MergeCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "7064f5a57a92e262e928f8f3a5dd1a5b870af5a0" /* * Copyright (C) 2010, Christian Halstrick * Copyright (C) 2010, 2014, Stefan Lay * Copyright (C) 2016, 2021 Laurent Delaigue and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.MergeResult.MergeStatus; import org.eclipse.jgit.api.errors.CheckoutConflictException; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidMergeHeadsException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.CommitConfig; import org.eclipse.jgit.lib.Config.ConfigEnum; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.merge.ContentMergeStrategy; import org.eclipse.jgit.merge.MergeConfig; import org.eclipse.jgit.merge.MergeMessageFormatter; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.merge.Merger; import org.eclipse.jgit.merge.ResolveMerger; import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; import org.eclipse.jgit.merge.SquashMessageFormatter; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalkUtils; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.util.StringUtils; /** * A class used to execute a {@code Merge} command. It has setters for all * supported options and arguments of this command and a {@link #call()} method * to finally execute the command. Each instance of this class should only be * used for one invocation of the command (means: one call to {@link #call()}) * * @see Git documentation about Merge */ public class MergeCommand extends GitCommand { private MergeStrategy mergeStrategy = MergeStrategy.RECURSIVE; private ContentMergeStrategy contentStrategy; private List commits = new ArrayList<>(); private Boolean squash; private FastForwardMode fastForwardMode; private String message; private boolean insertChangeId; private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; /** * Values for the "merge.conflictStyle" git config. * * @since 5.12 */ public enum ConflictStyle { /** "merge" style: only ours/theirs. This is the default. */ MERGE, /** "diff3" style: ours/base/theirs. */ DIFF3 } /** * The modes available for fast forward merges corresponding to the * --ff, --no-ff and --ff-only * options under branch.<name>.mergeoptions. */ public enum FastForwardMode implements ConfigEnum { /** * Corresponds to the default --ff option (for a fast forward update the * branch pointer only). */ FF, /** * Corresponds to the --no-ff option (create a merge commit even for a * fast forward). */ NO_FF, /** * Corresponds to the --ff-only option (abort unless the merge is a fast * forward). */ FF_ONLY; @Override public String toConfigValue() { return "--" + name().toLowerCase(Locale.ROOT).replace('_', '-'); //$NON-NLS-1$ } @Override public boolean matchConfigValue(String in) { if (StringUtils.isEmptyOrNull(in)) return false; if (!in.startsWith("--")) //$NON-NLS-1$ return false; return name().equalsIgnoreCase(in.substring(2).replace('-', '_')); } /** * The modes available for fast forward merges corresponding to the * options under merge.ff. */ public enum Merge { /** * {@link FastForwardMode#FF}. */ TRUE, /** * {@link FastForwardMode#NO_FF}. */ FALSE, /** * {@link FastForwardMode#FF_ONLY}. */ ONLY; /** * Map from FastForwardMode to * FastForwardMode.Merge. * * @param ffMode * the FastForwardMode value to be mapped * @return the mapped FastForwardMode.Merge value */ public static Merge valueOf(FastForwardMode ffMode) { switch (ffMode) { case NO_FF: return FALSE; case FF_ONLY: return ONLY; default: return TRUE; } } } /** * Map from FastForwardMode.Merge to * FastForwardMode. * * @param ffMode * the FastForwardMode.Merge value to be mapped * @return the mapped FastForwardMode value */ public static FastForwardMode valueOf(FastForwardMode.Merge ffMode) { switch (ffMode) { case FALSE: return NO_FF; case ONLY: return FF_ONLY; default: return FF; } } } private Boolean commit; /** * Constructor for MergeCommand. * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected MergeCommand(Repository repo) { super(repo); } /** * {@inheritDoc} *

* Execute the {@code Merge} command with all the options and parameters * collected by the setter methods (e.g. {@link #include(Ref)}) of this * class. Each instance of this class should only be used for one invocation * of the command. Don't call this method twice on an instance. */ @Override @SuppressWarnings("boxing") public MergeResult call() throws GitAPIException, NoHeadException, ConcurrentRefUpdateException, CheckoutConflictException, InvalidMergeHeadsException, WrongRepositoryStateException, NoMessageException { checkCallable(); fallBackToConfiguration(); checkParameters(); DirCacheCheckout dco = null; try (RevWalk revWalk = new RevWalk(repo)) { Ref head = repo.exactRef(Constants.HEAD); if (head == null) throw new NoHeadException( JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported); StringBuilder refLogMessage = new StringBuilder("merge "); //$NON-NLS-1$ // Check for FAST_FORWARD, ALREADY_UP_TO_DATE // we know for now there is only one commit Ref ref = commits.get(0); refLogMessage.append(ref.getName()); // handle annotated tags ref = repo.getRefDatabase().peel(ref); ObjectId objectId = ref.getPeeledObjectId(); if (objectId == null) objectId = ref.getObjectId(); RevCommit srcCommit = revWalk.lookupCommit(objectId); ObjectId headId = head.getObjectId(); if (headId == null) { revWalk.parseHeaders(srcCommit); dco = new DirCacheCheckout(repo, repo.lockDirCache(), srcCommit.getTree()); dco.setFailOnConflict(true); dco.setProgressMonitor(monitor); dco.checkout(); RefUpdate refUpdate = repo .updateRef(head.getTarget().getName()); refUpdate.setNewObjectId(objectId); refUpdate.setExpectedOldObjectId(null); refUpdate.setRefLogMessage("initial pull", false); //$NON-NLS-1$ if (refUpdate.update() != Result.NEW) throw new NoHeadException( JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported); setCallable(false); return new MergeResult(srcCommit, srcCommit, new ObjectId[] { null, srcCommit }, MergeStatus.FAST_FORWARD, mergeStrategy, null, null); } RevCommit headCommit = revWalk.lookupCommit(headId); if (revWalk.isMergedInto(srcCommit, headCommit)) { setCallable(false); return new MergeResult(headCommit, srcCommit, new ObjectId[] { headCommit, srcCommit }, MergeStatus.ALREADY_UP_TO_DATE, mergeStrategy, null, null); } else if (revWalk.isMergedInto(headCommit, srcCommit) && fastForwardMode != FastForwardMode.NO_FF) { // FAST_FORWARD detected: skip doing a real merge but only // update HEAD refLogMessage.append(": " + MergeStatus.FAST_FORWARD); //$NON-NLS-1$ dco = new DirCacheCheckout(repo, headCommit.getTree(), repo.lockDirCache(), srcCommit.getTree()); dco.setProgressMonitor(monitor); dco.setFailOnConflict(true); dco.checkout(); String msg = null; ObjectId newHead, base = null; MergeStatus mergeStatus = null; if (!squash) { updateHead(refLogMessage, srcCommit, headId); newHead = base = srcCommit; mergeStatus = MergeStatus.FAST_FORWARD; } else { msg = JGitText.get().squashCommitNotUpdatingHEAD; newHead = base = headId; mergeStatus = MergeStatus.FAST_FORWARD_SQUASHED; List squashedCommits = RevWalkUtils.find( revWalk, srcCommit, headCommit); String squashMessage = new SquashMessageFormatter().format( squashedCommits, head); repo.writeSquashCommitMsg(squashMessage); } setCallable(false); return new MergeResult(newHead, base, new ObjectId[] { headCommit, srcCommit }, mergeStatus, mergeStrategy, null, msg); } else { if (fastForwardMode == FastForwardMode.FF_ONLY) { return new MergeResult(headCommit, srcCommit, new ObjectId[] { headCommit, srcCommit }, MergeStatus.ABORTED, mergeStrategy, null, null); } String mergeMessage = ""; //$NON-NLS-1$ if (!squash) { if (message != null) mergeMessage = message; else mergeMessage = new MergeMessageFormatter().format( commits, head); repo.writeMergeCommitMsg(mergeMessage); repo.writeMergeHeads(Arrays.asList(ref.getObjectId())); } else { List squashedCommits = RevWalkUtils.find( revWalk, srcCommit, headCommit); String squashMessage = new SquashMessageFormatter().format( squashedCommits, head); repo.writeSquashCommitMsg(squashMessage); } Merger merger = mergeStrategy.newMerger(repo); merger.setProgressMonitor(monitor); boolean noProblems; Map> lowLevelResults = null; Map failingPaths = null; List unmergedPaths = null; if (merger instanceof ResolveMerger) { ResolveMerger resolveMerger = (ResolveMerger) merger; resolveMerger.setContentMergeStrategy(contentStrategy); resolveMerger.setCommitNames(new String[] { "BASE", "HEAD", ref.getName() }); //$NON-NLS-1$ //$NON-NLS-2$ resolveMerger.setWorkingTreeIterator(new FileTreeIterator(repo)); noProblems = merger.merge(headCommit, srcCommit); lowLevelResults = resolveMerger .getMergeResults(); failingPaths = resolveMerger.getFailingPaths(); unmergedPaths = resolveMerger.getUnmergedPaths(); if (!resolveMerger.getModifiedFiles().isEmpty()) { repo.fireEvent(new WorkingTreeModifiedEvent( resolveMerger.getModifiedFiles(), null)); } } else noProblems = merger.merge(headCommit, srcCommit); refLogMessage.append(": Merge made by "); //$NON-NLS-1$ if (!revWalk.isMergedInto(headCommit, srcCommit)) refLogMessage.append(mergeStrategy.getName()); else refLogMessage.append("recursive"); //$NON-NLS-1$ refLogMessage.append('.'); if (noProblems) { dco = new DirCacheCheckout(repo, headCommit.getTree(), repo.lockDirCache(), merger.getResultTreeId()); dco.setFailOnConflict(true); dco.setProgressMonitor(monitor); dco.checkout(); String msg = null; ObjectId newHeadId = null; MergeStatus mergeStatus = null; if (!commit && squash) { mergeStatus = MergeStatus.MERGED_SQUASHED_NOT_COMMITTED; } if (!commit && !squash) { mergeStatus = MergeStatus.MERGED_NOT_COMMITTED; } if (commit && !squash) { try (Git git = new Git(getRepository())) { newHeadId = git.commit() .setReflogComment(refLogMessage.toString()) .setInsertChangeId(insertChangeId) .call().getId(); } mergeStatus = MergeStatus.MERGED; getRepository().autoGC(monitor); } if (commit && squash) { msg = JGitText.get().squashCommitNotUpdatingHEAD; newHeadId = headCommit.getId(); mergeStatus = MergeStatus.MERGED_SQUASHED; } return new MergeResult(newHeadId, null, new ObjectId[] { headCommit.getId(), srcCommit.getId() }, mergeStatus, mergeStrategy, null, msg); } if (failingPaths != null) { repo.writeMergeCommitMsg(null); repo.writeMergeHeads(null); return new MergeResult(null, merger.getBaseCommitId(), new ObjectId[] { headCommit.getId(), srcCommit.getId() }, MergeStatus.FAILED, mergeStrategy, lowLevelResults, failingPaths, null); } CommitConfig cfg = repo.getConfig().get(CommitConfig.KEY); char commentChar = cfg.getCommentChar(message); String mergeMessageWithConflicts = new MergeMessageFormatter() .formatWithConflicts(mergeMessage, unmergedPaths, commentChar); repo.writeMergeCommitMsg(mergeMessageWithConflicts); return new MergeResult(null, merger.getBaseCommitId(), new ObjectId[] { headCommit.getId(), srcCommit.getId() }, MergeStatus.CONFLICTING, mergeStrategy, lowLevelResults, null); } } catch (org.eclipse.jgit.errors.CheckoutConflictException e) { List conflicts = (dco == null) ? Collections . emptyList() : dco.getConflicts(); throw new CheckoutConflictException(conflicts, e); } catch (IOException e) { throw new JGitInternalException( MessageFormat.format( JGitText.get().exceptionCaughtDuringExecutionOfMergeCommand, e), e); } } private void checkParameters() throws InvalidMergeHeadsException { if (squash.booleanValue() && fastForwardMode == FastForwardMode.NO_FF) { throw new JGitInternalException( JGitText.get().cannotCombineSquashWithNoff); } if (commits.size() != 1) throw new InvalidMergeHeadsException( commits.isEmpty() ? JGitText.get().noMergeHeadSpecified : MessageFormat.format( JGitText.get().mergeStrategyDoesNotSupportHeads, mergeStrategy.getName(), Integer.valueOf(commits.size()))); } /** * Use values from the configuration if they have not been explicitly * defined via the setters */ private void fallBackToConfiguration() { MergeConfig config = MergeConfig.getConfigForCurrentBranch(repo); if (squash == null) squash = Boolean.valueOf(config.isSquash()); if (commit == null) commit = Boolean.valueOf(config.isCommit()); if (fastForwardMode == null) fastForwardMode = config.getFastForwardMode(); } private void updateHead(StringBuilder refLogMessage, ObjectId newHeadId, ObjectId oldHeadID) throws IOException, ConcurrentRefUpdateException { RefUpdate refUpdate = repo.updateRef(Constants.HEAD); refUpdate.setNewObjectId(newHeadId); refUpdate.setRefLogMessage(refLogMessage.toString(), false); refUpdate.setExpectedOldObjectId(oldHeadID); Result rc = refUpdate.update(); switch (rc) { case NEW: case FAST_FORWARD: return; case REJECTED: case LOCK_FAILURE: throw new ConcurrentRefUpdateException( JGitText.get().couldNotLockHEAD, refUpdate.getRef(), rc); default: throw new JGitInternalException(MessageFormat.format( JGitText.get().updatingRefFailed, Constants.HEAD, newHeadId.toString(), rc)); } } /** * Set merge strategy * * @param mergeStrategy * the {@link org.eclipse.jgit.merge.MergeStrategy} to be used * @return {@code this} */ public MergeCommand setStrategy(MergeStrategy mergeStrategy) { checkCallable(); this.mergeStrategy = mergeStrategy; return this; } /** * Sets the content merge strategy to use if the * {@link #setStrategy(MergeStrategy) merge strategy} is "resolve" or * "recursive". * * @param strategy * the {@link ContentMergeStrategy} to be used * @return {@code this} * @since 5.12 */ public MergeCommand setContentMergeStrategy(ContentMergeStrategy strategy) { checkCallable(); this.contentStrategy = strategy; return this; } /** * Reference to a commit to be merged with the current head * * @param aCommit * a reference to a commit which is merged with the current head * @return {@code this} */ public MergeCommand include(Ref aCommit) { checkCallable(); commits.add(aCommit); return this; } /** * Id of a commit which is to be merged with the current head * * @param aCommit * the Id of a commit which is merged with the current head * @return {@code this} */ public MergeCommand include(AnyObjectId aCommit) { return include(aCommit.getName(), aCommit); } /** * Include a commit * * @param name * a name of a {@code Ref} pointing to the commit * @param aCommit * the Id of a commit which is merged with the current head * @return {@code this} */ public MergeCommand include(String name, AnyObjectId aCommit) { return include(new ObjectIdRef.Unpeeled(Storage.LOOSE, name, aCommit.copy())); } /** * If true, will prepare the next commit in working tree and * index as if a real merge happened, but do not make the commit or move the * HEAD. Otherwise, perform the merge and commit the result. *

* In case the merge was successful but this flag was set to * true a {@link org.eclipse.jgit.api.MergeResult} with status * {@link org.eclipse.jgit.api.MergeResult.MergeStatus#MERGED_SQUASHED} or * {@link org.eclipse.jgit.api.MergeResult.MergeStatus#FAST_FORWARD_SQUASHED} * is returned. * * @param squash * whether to squash commits or not * @return {@code this} * @since 2.0 */ public MergeCommand setSquash(boolean squash) { checkCallable(); this.squash = Boolean.valueOf(squash); return this; } /** * Sets the fast forward mode. * * @param fastForwardMode * corresponds to the --ff/--no-ff/--ff-only options. If * {@code null} use the value of the {@code merge.ff} option * configured in git config. If this option is not configured * --ff is the built-in default. * @return {@code this} * @since 2.2 */ public MergeCommand setFastForward( @Nullable FastForwardMode fastForwardMode) { checkCallable(); this.fastForwardMode = fastForwardMode; return this; } /** * Controls whether the merge command should automatically commit after a * successful merge * * @param commit * true if this command should commit (this is the * default behavior). false if this command should * not commit. In case the merge was successful but this flag was * set to false a * {@link org.eclipse.jgit.api.MergeResult} with type * {@link org.eclipse.jgit.api.MergeResult} with status * {@link org.eclipse.jgit.api.MergeResult.MergeStatus#MERGED_NOT_COMMITTED} * is returned * @return {@code this} * @since 3.0 */ public MergeCommand setCommit(boolean commit) { this.commit = Boolean.valueOf(commit); return this; } /** * Set the commit message to be used for the merge commit (in case one is * created) * * @param message * the message to be used for the merge commit * @return {@code this} * @since 3.5 */ public MergeCommand setMessage(String message) { this.message = message; return this; } /** * If set to true a change id will be inserted into the commit message * * An existing change id is not replaced. An initial change id (I000...) * will be replaced by the change id. * * @param insertChangeId * whether to insert a change id * @return {@code this} * @since 5.0 */ public MergeCommand setInsertChangeId(boolean insertChangeId) { checkCallable(); this.insertChangeId = insertChangeId; return this; } /** * The progress monitor associated with the diff operation. By default, this * is set to NullProgressMonitor * * @see NullProgressMonitor * @param monitor * A progress monitor * @return this instance * @since 4.2 */ public MergeCommand setProgressMonitor(ProgressMonitor monitor) { if (monitor == null) { monitor = NullProgressMonitor.INSTANCE; } this.monitor = monitor; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 14450 Content-Disposition: inline; filename="MergeResult.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "0fa11f2cbbd0c55711aebb171e7f85d2bca21967" /* * Copyright (C) 2010, Stefan Lay * Copyright (C) 2010-2012, Christian Halstrick and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.text.MessageFormat; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.merge.MergeChunk; import org.eclipse.jgit.merge.MergeChunk.ConflictState; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; /** * Encapsulates the result of a {@link org.eclipse.jgit.api.MergeCommand}. */ public class MergeResult { /** * The status the merge resulted in. */ public enum MergeStatus { /** * Merge is a fast-forward */ FAST_FORWARD { @Override public String toString() { return "Fast-forward"; //$NON-NLS-1$ } @Override public boolean isSuccessful() { return true; } }, /** * Merge is a fast-forward, squashed * * @since 2.0 */ FAST_FORWARD_SQUASHED { @Override public String toString() { return "Fast-forward-squashed"; //$NON-NLS-1$ } @Override public boolean isSuccessful() { return true; } }, /** * Already up to date, merge was a no-op */ ALREADY_UP_TO_DATE { @Override public String toString() { return "Already-up-to-date"; //$NON-NLS-1$ } @Override public boolean isSuccessful() { return true; } }, /** * Merge failed */ FAILED { @Override public String toString() { return "Failed"; //$NON-NLS-1$ } @Override public boolean isSuccessful() { return false; } }, /** * Merged */ MERGED { @Override public String toString() { return "Merged"; //$NON-NLS-1$ } @Override public boolean isSuccessful() { return true; } }, /** * Merged, squashed, not updating HEAD * * @since 2.0 */ MERGED_SQUASHED { @Override public String toString() { return "Merged-squashed"; //$NON-NLS-1$ } @Override public boolean isSuccessful() { return true; } }, /** * Merged, squashed, not committed * * @since 3.0 */ MERGED_SQUASHED_NOT_COMMITTED { @Override public String toString() { return "Merged-squashed-not-committed"; //$NON-NLS-1$ } @Override public boolean isSuccessful() { return true; } }, /** * Merge raised conflicts to be resolved */ CONFLICTING { @Override public String toString() { return "Conflicting"; //$NON-NLS-1$ } @Override public boolean isSuccessful() { return false; } }, /** * Merge was aborted * * @since 2.2 */ ABORTED { @Override public String toString() { return "Aborted"; //$NON-NLS-1$ } @Override public boolean isSuccessful() { return false; } }, /** * Merged, not committed * * @since 3.0 **/ MERGED_NOT_COMMITTED { @Override public String toString() { return "Merged-not-committed"; //$NON-NLS-1$ } @Override public boolean isSuccessful() { return true; } }, /** Not yet supported */ NOT_SUPPORTED { @Override public String toString() { return "Not-yet-supported"; //$NON-NLS-1$ } @Override public boolean isSuccessful() { return false; } }, /** * Status representing a checkout conflict, meaning that nothing could * be merged, as the pre-scan for the trees already failed for certain * files (i.e. local modifications prevent checkout of files). */ CHECKOUT_CONFLICT { @Override public String toString() { return "Checkout Conflict"; //$NON-NLS-1$ } @Override public boolean isSuccessful() { return false; } }; /** * Whether the merge was successful * * @return whether the status indicates a successful result */ public abstract boolean isSuccessful(); } private ObjectId[] mergedCommits; private ObjectId base; private ObjectId newHead; private Map conflicts; private MergeStatus mergeStatus; private String description; private MergeStrategy mergeStrategy; private Map failingPaths; private List checkoutConflicts; /** * Constructor for MergeResult. * * @param newHead * the object the head points at after the merge * @param base * the common base which was used to produce a content-merge. May * be null if the merge-result was produced without * computing a common base * @param mergedCommits * all the commits which have been merged together * @param mergeStatus * the status the merge resulted in * @param mergeStrategy * the used {@link org.eclipse.jgit.merge.MergeStrategy} * @param lowLevelResults * merge results as returned by * {@link org.eclipse.jgit.merge.ResolveMerger#getMergeResults()} * @since 2.0 */ public MergeResult(ObjectId newHead, ObjectId base, ObjectId[] mergedCommits, MergeStatus mergeStatus, MergeStrategy mergeStrategy, Map> lowLevelResults) { this(newHead, base, mergedCommits, mergeStatus, mergeStrategy, lowLevelResults, null); } /** * Constructor for MergeResult. * * @param newHead * the object the head points at after the merge * @param base * the common base which was used to produce a content-merge. May * be null if the merge-result was produced without * computing a common base * @param mergedCommits * all the commits which have been merged together * @param mergeStatus * the status the merge resulted in * @param mergeStrategy * the used {@link org.eclipse.jgit.merge.MergeStrategy} * @param lowLevelResults * merge results as returned by * {@link org.eclipse.jgit.merge.ResolveMerger#getMergeResults()} * @param description * a user friendly description of the merge result */ public MergeResult(ObjectId newHead, ObjectId base, ObjectId[] mergedCommits, MergeStatus mergeStatus, MergeStrategy mergeStrategy, Map> lowLevelResults, String description) { this(newHead, base, mergedCommits, mergeStatus, mergeStrategy, lowLevelResults, null, description); } /** * Constructor for MergeResult. * * @param newHead * the object the head points at after the merge * @param base * the common base which was used to produce a content-merge. May * be null if the merge-result was produced without * computing a common base * @param mergedCommits * all the commits which have been merged together * @param mergeStatus * the status the merge resulted in * @param mergeStrategy * the used {@link org.eclipse.jgit.merge.MergeStrategy} * @param lowLevelResults * merge results as returned by * {@link org.eclipse.jgit.merge.ResolveMerger#getMergeResults()} * @param failingPaths * list of paths causing this merge to fail as returned by * {@link org.eclipse.jgit.merge.ResolveMerger#getFailingPaths()} * @param description * a user friendly description of the merge result */ public MergeResult(ObjectId newHead, ObjectId base, ObjectId[] mergedCommits, MergeStatus mergeStatus, MergeStrategy mergeStrategy, Map> lowLevelResults, Map failingPaths, String description) { this.newHead = newHead; this.mergedCommits = mergedCommits; this.base = base; this.mergeStatus = mergeStatus; this.mergeStrategy = mergeStrategy; this.description = description; this.failingPaths = failingPaths; if (lowLevelResults != null) for (Map.Entry> result : lowLevelResults .entrySet()) addConflict(result.getKey(), result.getValue()); } /** * Creates a new result that represents a checkout conflict before the * operation even started for real. * * @param checkoutConflicts * the conflicting files */ public MergeResult(List checkoutConflicts) { this.checkoutConflicts = checkoutConflicts; this.mergeStatus = MergeStatus.CHECKOUT_CONFLICT; } /** * Get the object the head points at after the merge * * @return the object the head points at after the merge */ public ObjectId getNewHead() { return newHead; } /** * Get the merge status * * @return the status the merge resulted in */ public MergeStatus getMergeStatus() { return mergeStatus; } /** * Get the commits which have been merged * * @return all the commits which have been merged together */ public ObjectId[] getMergedCommits() { return mergedCommits; } /** * Get the common base * * @return base the common base which was used to produce a content-merge. * May be null if the merge-result was produced without * computing a common base */ public ObjectId getBase() { return base; } @SuppressWarnings("nls") @Override public String toString() { boolean first = true; StringBuilder commits = new StringBuilder(); for (ObjectId commit : mergedCommits) { if (!first) commits.append(", "); else first = false; commits.append(ObjectId.toString(commit)); } return MessageFormat.format( JGitText.get().mergeUsingStrategyResultedInDescription, commits, ObjectId.toString(base), mergeStrategy.getName(), mergeStatus, (description == null ? "" : ", " + description)); } /** * Set conflicts * * @param conflicts * the conflicts to set */ public void setConflicts(Map conflicts) { this.conflicts = conflicts; } /** * Add a conflict * * @param path * path of the file to add a conflict for * @param conflictingRanges * the conflicts to set */ public void addConflict(String path, int[][] conflictingRanges) { if (conflicts == null) conflicts = new HashMap<>(); conflicts.put(path, conflictingRanges); } /** * Add a conflict * * @param path * path of the file to add a conflict for * @param lowLevelResult * a {@link org.eclipse.jgit.merge.MergeResult} */ public void addConflict(String path, org.eclipse.jgit.merge.MergeResult lowLevelResult) { if (!lowLevelResult.containsConflicts()) return; if (conflicts == null) conflicts = new HashMap<>(); int nrOfConflicts = 0; // just counting for (MergeChunk mergeChunk : lowLevelResult) { if (mergeChunk.getConflictState().equals(ConflictState.FIRST_CONFLICTING_RANGE)) { nrOfConflicts++; } } int currentConflict = -1; int[][] ret=new int[nrOfConflicts][mergedCommits.length+1]; for (MergeChunk mergeChunk : lowLevelResult) { // to store the end of this chunk (end of the last conflicting range) int endOfChunk = 0; if (mergeChunk.getConflictState().equals(ConflictState.FIRST_CONFLICTING_RANGE)) { if (currentConflict > -1) { // there was a previous conflicting range for which the end // is not set yet - set it! ret[currentConflict][mergedCommits.length] = endOfChunk; } currentConflict++; endOfChunk = mergeChunk.getEnd(); ret[currentConflict][mergeChunk.getSequenceIndex()] = mergeChunk.getBegin(); } if (mergeChunk.getConflictState().equals(ConflictState.NEXT_CONFLICTING_RANGE)) { if (mergeChunk.getEnd() > endOfChunk) endOfChunk = mergeChunk.getEnd(); ret[currentConflict][mergeChunk.getSequenceIndex()] = mergeChunk.getBegin(); } } conflicts.put(path, ret); } /** * Returns information about the conflicts which occurred during a * {@link org.eclipse.jgit.api.MergeCommand}. The returned value maps the * path of a conflicting file to a two-dimensional int-array of line-numbers * telling where in the file conflict markers for which merged commit can be * found. *

* If the returned value contains a mapping "path"->[x][y]=z then this * means *

    *
  • the file with path "path" contains conflicts
  • *
  • if y < "number of merged commits": for conflict number x in this * file the chunk which was copied from commit number y starts on line * number z. All numberings and line numbers start with 0.
  • *
  • if y == "number of merged commits": the first non-conflicting line * after conflict number x starts at line number z
  • *
*

* Example code how to parse this data: * *

	 * MergeResult m=...;
	 * Map<String, int[][]> allConflicts = m.getConflicts();
	 * for (String path : allConflicts.keySet()) {
	 * 	int[][] c = allConflicts.get(path);
	 * 	System.out.println("Conflicts in file " + path);
	 * 	for (int i = 0; i < c.length; ++i) {
	 * 		System.out.println("  Conflict #" + i);
	 * 		for (int j = 0; j < (c[i].length) - 1; ++j) {
	 * 			if (c[i][j] >= 0)
	 * 				System.out.println("    Chunk for "
	 * 						+ m.getMergedCommits()[j] + " starts on line #"
	 * 						+ c[i][j]);
	 * 		}
	 * 	}
	 * }
	 * 
* * @return the conflicts or null if no conflict occurred */ public Map getConflicts() { return conflicts; } /** * Returns a list of paths causing this merge to fail as returned by * {@link org.eclipse.jgit.merge.ResolveMerger#getFailingPaths()} * * @return the list of paths causing this merge to fail or null * if no failure occurred */ public Map getFailingPaths() { return failingPaths; } /** * Returns a list of paths that cause a checkout conflict. These paths * prevent the operation from even starting. * * @return the list of files that caused the checkout conflict. */ public List getCheckoutConflicts() { return checkoutConflicts; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 10333 Content-Disposition: inline; filename="NameRevCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "1ff6e98b12aa44055dd670854c05c7d00059600b" /* * Copyright (C) 2013, Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.FIFORevQueue; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; /** * Command to find human-readable names of revisions. * * @see Git documentation about name-rev * @since 3.0 */ public class NameRevCommand extends GitCommand> { /** Amount of slop to allow walking past the earliest requested commit. */ private static final int COMMIT_TIME_SLOP = 60 * 60 * 24; /** Cost of traversing a merge commit compared to a linear history. */ private static final int MERGE_COST = 65535; private static class NameRevCommit extends RevCommit { private String tip; private int distance; private long cost; private NameRevCommit(AnyObjectId id) { super(id); } private StringBuilder format() { StringBuilder sb = new StringBuilder(tip); if (distance > 0) sb.append('~').append(distance); return sb; } @Override public String toString() { StringBuilder sb = new StringBuilder(getClass().getSimpleName()) .append('['); if (tip != null) { sb.append(format()); } else { sb.append((Object) null); } sb.append(',').append(cost).append(']').append(' ') .append(super.toString()); return sb.toString(); } } private final RevWalk walk; private final List prefixes; private final List revs; private List refs; private int mergeCost; /** * Create a new name-rev command. * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected NameRevCommand(Repository repo) { super(repo); mergeCost = MERGE_COST; prefixes = new ArrayList<>(2); revs = new ArrayList<>(2); walk = new RevWalk(repo) { @Override public NameRevCommit createCommit(AnyObjectId id) { return new NameRevCommit(id); } }; } @Override public Map call() throws GitAPIException { try { Map nonCommits = new HashMap<>(); FIFORevQueue pending = new FIFORevQueue(); if (refs != null) { for (Ref ref : refs) addRef(ref, nonCommits, pending); } addPrefixes(nonCommits, pending); int cutoff = minCommitTime() - COMMIT_TIME_SLOP; while (true) { NameRevCommit c = (NameRevCommit) pending.next(); if (c == null) break; if (c.getCommitTime() < cutoff) continue; for (int i = 0; i < c.getParentCount(); i++) { NameRevCommit p = (NameRevCommit) walk.parseCommit(c.getParent(i)); long cost = c.cost + (i > 0 ? mergeCost : 1); if (p.tip == null || compare(c.tip, cost, p.tip, p.cost) < 0) { if (i > 0) { p.tip = c.format().append('^').append(i + 1).toString(); p.distance = 0; } else { p.tip = c.tip; p.distance = c.distance + 1; } p.cost = cost; pending.add(p); } } } Map result = new LinkedHashMap<>(revs.size()); for (ObjectId id : revs) { RevObject o = walk.parseAny(id); if (o instanceof NameRevCommit) { NameRevCommit c = (NameRevCommit) o; if (c.tip != null) result.put(id, simplify(c.format().toString())); } else { String name = nonCommits.get(id); if (name != null) result.put(id, simplify(name)); } } setCallable(false); return result; } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } finally { walk.close(); } } /** * Add an object to search for. * * @param id * object ID to add. * @return {@code this} * @throws org.eclipse.jgit.errors.MissingObjectException * the object supplied is not available from the object * database. * @throws org.eclipse.jgit.api.errors.JGitInternalException * a low-level exception of JGit has occurred. The original * exception can be retrieved by calling * {@link java.lang.Exception#getCause()}. */ public NameRevCommand add(ObjectId id) throws MissingObjectException, JGitInternalException { checkCallable(); try { walk.parseAny(id); } catch (MissingObjectException e) { throw e; } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } revs.add(id.copy()); return this; } /** * Add multiple objects to search for. * * @param ids * object IDs to add. * @return {@code this} * @throws org.eclipse.jgit.errors.MissingObjectException * the object supplied is not available from the object * database. * @throws org.eclipse.jgit.api.errors.JGitInternalException * a low-level exception of JGit has occurred. The original * exception can be retrieved by calling * {@link java.lang.Exception#getCause()}. */ public NameRevCommand add(Iterable ids) throws MissingObjectException, JGitInternalException { for (ObjectId id : ids) add(id); return this; } /** * Add a ref prefix to the set that results must match. *

* If an object matches multiple refs equally well, the first matching ref * added with {@link #addRef(Ref)} is preferred, or else the first matching * prefix added by {@link #addPrefix(String)}. * * @param prefix * prefix to add; the prefix must end with a slash * @return {@code this} */ public NameRevCommand addPrefix(String prefix) { checkCallable(); prefixes.add(prefix); return this; } /** * Add all annotated tags under {@code refs/tags/} to the set that all * results must match. *

* Calls {@link #addRef(Ref)}; see that method for a note on matching * priority. * * @return {@code this} * @throws JGitInternalException * a low-level exception of JGit has occurred. The original * exception can be retrieved by calling * {@link java.lang.Exception#getCause()}. */ public NameRevCommand addAnnotatedTags() { checkCallable(); if (refs == null) refs = new ArrayList<>(); try { for (Ref ref : repo.getRefDatabase() .getRefsByPrefix(Constants.R_TAGS)) { ObjectId id = ref.getObjectId(); if (id != null && (walk.parseAny(id) instanceof RevTag)) addRef(ref); } } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } return this; } /** * Add a ref to the set that all results must match. *

* If an object matches multiple refs equally well, the first matching ref * added with {@link #addRef(Ref)} is preferred, or else the first matching * prefix added by {@link #addPrefix(String)}. * * @param ref * ref to add. * @return {@code this} */ public NameRevCommand addRef(Ref ref) { checkCallable(); if (refs == null) refs = new ArrayList<>(); refs.add(ref); return this; } NameRevCommand setMergeCost(int cost) { mergeCost = cost; return this; } private void addPrefixes(Map nonCommits, FIFORevQueue pending) throws IOException { if (!prefixes.isEmpty()) { for (String prefix : prefixes) addPrefix(prefix, nonCommits, pending); } else if (refs == null) addPrefix(Constants.R_REFS, nonCommits, pending); } private void addPrefix(String prefix, Map nonCommits, FIFORevQueue pending) throws IOException { for (Ref ref : repo.getRefDatabase().getRefsByPrefix(prefix)) addRef(ref, nonCommits, pending); } private void addRef(Ref ref, Map nonCommits, FIFORevQueue pending) throws IOException { if (ref.getObjectId() == null) return; RevObject o = walk.parseAny(ref.getObjectId()); while (o instanceof RevTag) { RevTag t = (RevTag) o; nonCommits.put(o, ref.getName()); o = t.getObject(); walk.parseHeaders(o); } if (o instanceof NameRevCommit) { NameRevCommit c = (NameRevCommit) o; if (c.tip == null) c.tip = ref.getName(); pending.add(c); } else if (!nonCommits.containsKey(o)) nonCommits.put(o, ref.getName()); } private int minCommitTime() throws IOException { int min = Integer.MAX_VALUE; for (ObjectId id : revs) { RevObject o = walk.parseAny(id); while (o instanceof RevTag) { o = ((RevTag) o).getObject(); walk.parseHeaders(o); } if (o instanceof RevCommit) { RevCommit c = (RevCommit) o; if (c.getCommitTime() < min) min = c.getCommitTime(); } } return min; } private long compare(String leftTip, long leftCost, String rightTip, long rightCost) { long c = leftCost - rightCost; if (c != 0 || prefixes.isEmpty()) return c; int li = -1; int ri = -1; for (int i = 0; i < prefixes.size(); i++) { String prefix = prefixes.get(i); if (li < 0 && leftTip.startsWith(prefix)) li = i; if (ri < 0 && rightTip.startsWith(prefix)) ri = i; } // Don't tiebreak if prefixes are the same, in order to prefer first-parent // paths. return (long) li - ri; } private static String simplify(String refName) { if (refName.startsWith(Constants.R_HEADS)) return refName.substring(Constants.R_HEADS.length()); if (refName.startsWith(Constants.R_TAGS)) return refName.substring(Constants.R_TAGS.length()); if (refName.startsWith(Constants.R_REFS)) return refName.substring(Constants.R_REFS.length()); return refName; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2070 Content-Disposition: inline; filename="PackRefsCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "29a69c5ac47e64f0319fb84c039061614c322d44" /* * Copyright (c) 2024 Qualcomm Innovation Center, 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 v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; /** * Optimize storage of references. * * @since 7.1 */ public class PackRefsCommand extends GitCommand { private ProgressMonitor monitor; private boolean all; /** * Creates a new {@link PackRefsCommand} instance with default values. * * @param repo * the repository this command will be used on */ public PackRefsCommand(Repository repo) { super(repo); this.monitor = NullProgressMonitor.INSTANCE; } /** * Set progress monitor * * @param monitor * a progress monitor * @return this instance */ public PackRefsCommand setProgressMonitor(ProgressMonitor monitor) { this.monitor = monitor; return this; } /** * Specify whether to pack all the references. * * @param all * if true all the loose refs will be packed * @return this instance */ public PackRefsCommand setAll(boolean all) { this.all = all; return this; } /** * Whether to pack all the references * * @return whether to pack all the references */ public boolean isAll() { return all; } @Override public String call() throws GitAPIException { checkCallable(); try { repo.getRefDatabase().packRefs(monitor, this); return JGitText.get().packRefsSuccessful; } catch (IOException e) { throw new JGitInternalException(JGitText.get().packRefsFailed, e); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 18102 Content-Disposition: inline; filename="PullCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "4b2cee45c26e276340cd9ca8ebb573e91fc6eb6c" /* * Copyright (C) 2010, Christian Halstrick * Copyright (C) 2010, Mathias Kinzler * Copyright (C) 2016, 2021 Laurent Delaigue and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.MergeCommand.FastForwardMode; import org.eclipse.jgit.api.MergeCommand.FastForwardMode.Merge; import org.eclipse.jgit.api.RebaseCommand.Operation; import org.eclipse.jgit.api.errors.CanceledException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidConfigurationException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.RefNotAdvertisedException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode; import org.eclipse.jgit.merge.ContentMergeStrategy; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.FetchResult; import org.eclipse.jgit.transport.TagOpt; /** * The Pull command * * @see Git documentation about Pull */ public class PullCommand extends TransportCommand { private static final String DOT = "."; //$NON-NLS-1$ private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; private BranchRebaseMode pullRebaseMode = null; private String remote; private String remoteBranchName; private MergeStrategy strategy = MergeStrategy.RECURSIVE; private ContentMergeStrategy contentStrategy; private TagOpt tagOption; private FastForwardMode fastForwardMode; private FetchRecurseSubmodulesMode submoduleRecurseMode = null; /** * Constructor for PullCommand. * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected PullCommand(Repository repo) { super(repo); } /** * Set progress monitor * * @param monitor * a progress monitor * @return this instance */ public PullCommand setProgressMonitor(ProgressMonitor monitor) { if (monitor == null) { monitor = NullProgressMonitor.INSTANCE; } this.monitor = monitor; return this; } /** * Set if rebase should be used after fetching. If set to true, rebase is * used instead of merge. This is equivalent to --rebase on the command * line. *

* If set to false, merge is used after fetching, overriding the * configuration file. This is equivalent to --no-rebase on the command * line. *

* This setting overrides the settings in the configuration file. By * default, the setting in the repository configuration file is used. *

* A branch can be configured to use rebase by default. See * branch.[name].rebase and branch.autosetuprebase. * * @param useRebase * whether to use rebase after fetching * @return {@code this} */ public PullCommand setRebase(boolean useRebase) { checkCallable(); pullRebaseMode = useRebase ? BranchRebaseMode.REBASE : BranchRebaseMode.NONE; return this; } /** * Sets the {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode} to * use after fetching. * *

*
BranchRebaseMode.REBASE
*
Equivalent to {@code --rebase} on the command line: use rebase * instead of merge after fetching.
*
BranchRebaseMode.MERGES
*
Equivalent to {@code --rebase-merges} on the command line: rebase * preserving local merge commits.
*
BranchRebaseMode.INTERACTIVE
*
Equivalent to {@code --interactive} on the command line: use * interactive rebase.
*
BranchRebaseMode.NONE
*
Equivalent to {@code --no-rebase}: merge instead of rebasing. *
{@code null}
*
Use the setting defined in the git configuration, either {@code * branch.[name].rebase} or, if not set, {@code pull.rebase}
*
* * This setting overrides the settings in the configuration file. By * default, the setting in the repository configuration file is used. *

* A branch can be configured to use rebase by default. See * {@code branch.[name].rebase}, {@code branch.autosetuprebase}, and * {@code pull.rebase}. * * @param rebaseMode * the {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode} * to use * @return {@code this} * @since 4.5 */ public PullCommand setRebase(BranchRebaseMode rebaseMode) { checkCallable(); pullRebaseMode = rebaseMode; return this; } /** * {@inheritDoc} *

* Execute the {@code Pull} command with all the options and parameters * collected by the setter methods (e.g. * {@link #setProgressMonitor(ProgressMonitor)}) of this class. Each * instance of this class should only be used for one invocation of the * command. Don't call this method twice on an instance. */ @Override public PullResult call() throws GitAPIException, WrongRepositoryStateException, InvalidConfigurationException, InvalidRemoteException, CanceledException, RefNotFoundException, RefNotAdvertisedException, NoHeadException, org.eclipse.jgit.api.errors.TransportException { checkCallable(); monitor.beginTask(JGitText.get().pullTaskName, 2); Config repoConfig = repo.getConfig(); String branchName = null; try { String fullBranch = repo.getFullBranch(); if (fullBranch != null && fullBranch.startsWith(Constants.R_HEADS)) { 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..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())); if (remote == null && branchName != null) { // get the configured remote for the currently checked out branch // stored in configuration key branch..remote remote = repoConfig.getString( ConfigConstants.CONFIG_BRANCH_SECTION, branchName, ConfigConstants.CONFIG_KEY_REMOTE); } if (remote == null) { // fall back to default remote remote = Constants.DEFAULT_REMOTE_NAME; } // determines whether rebase should be used after fetching if (pullRebaseMode == null && branchName != null) { pullRebaseMode = getRebaseMode(branchName, repoConfig); } final boolean isRemote = !remote.equals("."); //$NON-NLS-1$ String remoteUri; FetchResult fetchRes; if (isRemote) { remoteUri = repoConfig.getString( ConfigConstants.CONFIG_REMOTE_SECTION, remote, ConfigConstants.CONFIG_KEY_URL); if (remoteUri == null) { String missingKey = ConfigConstants.CONFIG_REMOTE_SECTION + DOT + remote + DOT + ConfigConstants.CONFIG_KEY_URL; throw new InvalidConfigurationException(MessageFormat.format( JGitText.get().missingConfigurationForKey, missingKey)); } if (monitor.isCancelled()) throw new CanceledException(MessageFormat.format( JGitText.get().operationCanceled, JGitText.get().pullTaskName)); FetchCommand fetch = new FetchCommand(repo).setRemote(remote) .setProgressMonitor(monitor).setTagOpt(tagOption) .setRecurseSubmodules(submoduleRecurseMode); configure(fetch); fetchRes = fetch.call(); } else { // we can skip the fetch altogether remoteUri = JGitText.get().localRepository; fetchRes = null; } monitor.update(1); if (monitor.isCancelled()) throw new CanceledException(MessageFormat.format( JGitText.get().operationCanceled, JGitText.get().pullTaskName)); // we check the updates to see which of the updated branches // corresponds to the remote branch name AnyObjectId commitToMerge; if (isRemote) { Ref r = null; if (fetchRes != null) { r = fetchRes.getAdvertisedRef(remoteBranchName); if (r == null) { r = fetchRes.getAdvertisedRef(Constants.R_HEADS + remoteBranchName); } } if (r == null) { throw new RefNotAdvertisedException(MessageFormat.format( JGitText.get().couldNotGetAdvertisedRef, remote, remoteBranchName)); } commitToMerge = r.getObjectId(); } else { try { commitToMerge = repo.resolve(remoteBranchName); if (commitToMerge == null) { throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, remoteBranchName)); } } catch (IOException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfPullCommand, e); } } String upstreamName = MessageFormat.format( JGitText.get().upstreamBranchName, Repository.shortenRefName(remoteBranchName), remoteUri); PullResult result; if (pullRebaseMode != BranchRebaseMode.NONE) { try { Ref head = repo.exactRef(Constants.HEAD); if (head == null) { throw new NoHeadException(JGitText .get().commitOnRepoWithoutHEADCurrentlyNotSupported); } ObjectId headId = head.getObjectId(); if (headId == null) { // Pull on an unborn branch: checkout try (RevWalk revWalk = new RevWalk(repo)) { RevCommit srcCommit = revWalk .parseCommit(commitToMerge); DirCacheCheckout dco = new DirCacheCheckout(repo, repo.lockDirCache(), srcCommit.getTree()); dco.setFailOnConflict(true); dco.setProgressMonitor(monitor); dco.checkout(); RefUpdate refUpdate = repo .updateRef(head.getTarget().getName()); refUpdate.setNewObjectId(commitToMerge); refUpdate.setExpectedOldObjectId(null); refUpdate.setRefLogMessage("initial pull", false); //$NON-NLS-1$ if (refUpdate.update() != Result.NEW) { throw new NoHeadException(JGitText .get().commitOnRepoWithoutHEADCurrentlyNotSupported); } monitor.endTask(); return new PullResult(fetchRes, remote, RebaseResult.result( RebaseResult.Status.FAST_FORWARD, srcCommit)); } } } catch (NoHeadException e) { throw e; } catch (IOException e) { throw new JGitInternalException(JGitText .get().exceptionCaughtDuringExecutionOfPullCommand, e); } RebaseCommand rebase = new RebaseCommand(repo); RebaseResult rebaseRes = rebase.setUpstream(commitToMerge) .setProgressMonitor(monitor) .setUpstreamName(upstreamName) .setOperation(Operation.BEGIN) .setStrategy(strategy) .setContentMergeStrategy(contentStrategy) .setPreserveMerges( pullRebaseMode == BranchRebaseMode.MERGES) .call(); result = new PullResult(fetchRes, remote, rebaseRes); } else { MergeCommand merge = new MergeCommand(repo); MergeResult mergeRes = merge.include(upstreamName, commitToMerge) .setProgressMonitor(monitor) .setStrategy(strategy) .setContentMergeStrategy(contentStrategy) .setFastForward(getFastForwardMode()).call(); monitor.update(1); result = new PullResult(fetchRes, remote, mergeRes); } monitor.endTask(); return result; } /** * The remote (uri or name) to be used for the pull operation. If no remote * is set, the branch's configuration will be used. If the branch * configuration is missing the default value of * Constants.DEFAULT_REMOTE_NAME will be used. * * @see Constants#DEFAULT_REMOTE_NAME * @param remote * name of the remote to pull from * @return {@code this} * @since 3.3 */ public PullCommand setRemote(String remote) { checkCallable(); this.remote = remote; return this; } /** * The remote branch name to be used for the pull operation. If no * remoteBranchName is set, the branch's configuration will be used. If the * branch configuration is missing the remote branch with the same name as * the current branch is used. * * @param remoteBranchName * remote branch name to be used for pull operation * @return {@code this} * @since 3.3 */ public PullCommand setRemoteBranchName(String remoteBranchName) { checkCallable(); this.remoteBranchName = remoteBranchName; return this; } /** * Get the remote name used for pull operation * * @return the remote used for the pull operation if it was set explicitly * @since 3.3 */ public String getRemote() { return remote; } /** * Get the remote branch name for the pull operation * * @return the remote branch name used for the pull operation if it was set * explicitly * @since 3.3 */ public String getRemoteBranchName() { return remoteBranchName; } /** * Set the @{code MergeStrategy} * * @param strategy * The merge strategy to use during this pull operation. * @return {@code this} * @since 3.4 */ public PullCommand setStrategy(MergeStrategy strategy) { this.strategy = strategy; return this; } /** * Sets the content merge strategy to use if the * {@link #setStrategy(MergeStrategy) merge strategy} is "resolve" or * "recursive". * * @param strategy * the {@link ContentMergeStrategy} to be used * @return {@code this} * @since 5.12 */ public PullCommand setContentMergeStrategy(ContentMergeStrategy strategy) { this.contentStrategy = strategy; return this; } /** * Set the specification of annotated tag behavior during fetch * * @param tagOpt * the {@link org.eclipse.jgit.transport.TagOpt} * @return {@code this} * @since 4.7 */ public PullCommand setTagOpt(TagOpt tagOpt) { checkCallable(); this.tagOption = tagOpt; return this; } /** * Set the fast forward mode. It is used if pull is configured to do a merge * as opposed to rebase. If non-{@code null} takes precedence over the * fast-forward mode configured in git config. * * @param fastForwardMode * corresponds to the --ff/--no-ff/--ff-only options. If * {@code null} use the value of {@code pull.ff} configured in * git config. If {@code pull.ff} is not configured fall back to * the value of {@code merge.ff}. If {@code merge.ff} is not * configured --ff is the built-in default. * @return {@code this} * @since 4.9 */ public PullCommand setFastForward( @Nullable FastForwardMode fastForwardMode) { checkCallable(); this.fastForwardMode = fastForwardMode; return this; } /** * Set the mode to be used for recursing into submodules. * * @param recurse * the * {@link org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode} * to be used for recursing into submodules * @return {@code this} * @since 4.7 * @see FetchCommand#setRecurseSubmodules(FetchRecurseSubmodulesMode) */ public PullCommand setRecurseSubmodules( @Nullable FetchRecurseSubmodulesMode recurse) { this.submoduleRecurseMode = recurse; return this; } /** * Reads the rebase mode to use for a pull command from the repository * configuration. This is the value defined for the configurations * {@code branch.[branchName].rebase}, or,if not set, {@code pull.rebase}. * If neither is set, yields * {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode#NONE}. * * @param branchName * name of the local branch * @param config * the {@link org.eclipse.jgit.lib.Config} to read the value from * @return the {@link org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode} * @since 4.5 */ public static BranchRebaseMode getRebaseMode(String branchName, Config config) { BranchRebaseMode mode = config.getEnum(BranchRebaseMode.values(), ConfigConstants.CONFIG_BRANCH_SECTION, branchName, ConfigConstants.CONFIG_KEY_REBASE); if (mode == null) { mode = config.getEnum( ConfigConstants.CONFIG_PULL_SECTION, null, ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.NONE); } return mode; } private FastForwardMode getFastForwardMode() { if (fastForwardMode != null) { return fastForwardMode; } Config config = repo.getConfig(); Merge ffMode = config.getEnum(Merge.values(), ConfigConstants.CONFIG_PULL_SECTION, null, ConfigConstants.CONFIG_KEY_FF); return ffMode != null ? FastForwardMode.valueOf(ffMode) : null; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2606 Content-Disposition: inline; filename="PullResult.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "fdc7f3f333dc2fa84a3d8b2b9f092295cb55141c" /* * Copyright (C) 2010, Mathias Kinzler and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import org.eclipse.jgit.transport.FetchResult; /** * Encapsulates the result of a {@link org.eclipse.jgit.api.PullCommand} */ public class PullResult { private final FetchResult fetchResult; private final MergeResult mergeResult; private final RebaseResult rebaseResult; private final String fetchedFrom; PullResult(FetchResult fetchResult, String fetchedFrom, MergeResult mergeResult) { this.fetchResult = fetchResult; this.fetchedFrom = fetchedFrom; this.mergeResult = mergeResult; this.rebaseResult = null; } PullResult(FetchResult fetchResult, String fetchedFrom, RebaseResult rebaseResult) { this.fetchResult = fetchResult; this.fetchedFrom = fetchedFrom; this.mergeResult = null; this.rebaseResult = rebaseResult; } /** * Get fetch result * * @return the fetch result, or null */ public FetchResult getFetchResult() { return this.fetchResult; } /** * Get merge result * * @return the merge result, or null */ public MergeResult getMergeResult() { return this.mergeResult; } /** * Get rebase result * * @return the rebase result, or null */ public RebaseResult getRebaseResult() { return this.rebaseResult; } /** * Get name of the remote configuration from which fetch was tried * * @return the name of the remote configuration from which fetch was tried, * or null */ public String getFetchedFrom() { return this.fetchedFrom; } /** * Whether the pull was successful * * @return whether the pull was successful */ public boolean isSuccessful() { if (mergeResult != null) return mergeResult.getMergeStatus().isSuccessful(); else if (rebaseResult != null) return rebaseResult.getStatus().isSuccessful(); return true; } @SuppressWarnings("nls") @Override public String toString() { StringBuilder sb = new StringBuilder(); if (fetchResult != null) sb.append(fetchResult.toString()); else sb.append("No fetch result"); sb.append("\n"); if (mergeResult != null) sb.append(mergeResult.toString()); else if (rebaseResult != null) sb.append(rebaseResult.toString()); else sb.append("No update result"); return sb.toString(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 19527 Content-Disposition: inline; filename="PushCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "e9d1a3213b05094bf09a3dd56a2e02a3373b977a" /* * Copyright (C) 2010, 2022 Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.jgit.api.errors.DetachedHeadException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TooLargeObjectInPackException; import org.eclipse.jgit.errors.TooLargePackException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.BranchConfig; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.PushConfig; import org.eclipse.jgit.transport.PushConfig.PushDefault; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.RefLeaseSpec; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.Transport; /** * A class used to execute a {@code Push} command. It has setters for all * supported options and arguments of this command and a {@link #call()} method * to finally execute the command. * * @see Git documentation about Push */ public class PushCommand extends TransportCommand> { private String remote; private final List refSpecs; private final Map refLeaseSpecs; private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; private String receivePack = RemoteConfig.DEFAULT_RECEIVE_PACK; private boolean dryRun; private boolean atomic; private boolean force; private boolean thin = Transport.DEFAULT_PUSH_THIN; private boolean useBitmaps = Transport.DEFAULT_PUSH_USE_BITMAPS; private PrintStream hookOutRedirect; private PrintStream hookErrRedirect; private OutputStream out; private List pushOptions; // Legacy behavior as default. Use setPushDefault(null) to determine the // value from the git config. private PushDefault pushDefault = PushDefault.CURRENT; /** *

* Constructor for PushCommand. *

* * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected PushCommand(Repository repo) { super(repo); refSpecs = new ArrayList<>(3); refLeaseSpecs = new HashMap<>(); } /** * {@inheritDoc} *

* Execute the {@code push} command with all the options and parameters * collected by the setter methods of this class. Each instance of this * class should only be used for one invocation of the command (means: one * call to {@link #call()}) */ @Override public Iterable call() throws GitAPIException, InvalidRemoteException, org.eclipse.jgit.api.errors.TransportException { checkCallable(); setCallable(false); ArrayList pushResults = new ArrayList<>(3); try { Config config = repo.getConfig(); remote = determineRemote(config, remote); if (refSpecs.isEmpty()) { RemoteConfig rc = new RemoteConfig(config, getRemote()); refSpecs.addAll(rc.getPushRefSpecs()); if (refSpecs.isEmpty()) { determineDefaultRefSpecs(config); } } if (force) { for (int i = 0; i < refSpecs.size(); i++) refSpecs.set(i, refSpecs.get(i).setForceUpdate(true)); } List transports = Transport.openAll(repo, remote, Transport.Operation.PUSH); for (@SuppressWarnings("resource") // Explicitly closed in finally final Transport transport : transports) { transport.setPushThin(thin); transport.setPushAtomic(atomic); if (receivePack != null) transport.setOptionReceivePack(receivePack); transport.setDryRun(dryRun); transport.setPushOptions(pushOptions); transport.setPushUseBitmaps(useBitmaps); transport.setHookOutputStream(hookOutRedirect); transport.setHookErrorStream(hookErrRedirect); configure(transport); final Collection toPush = transport .findRemoteRefUpdatesFor(refSpecs, refLeaseSpecs); try { PushResult result = transport.push(monitor, toPush, out); pushResults.add(result); } catch (TooLargePackException e) { throw new org.eclipse.jgit.api.errors.TooLargePackException( e.getMessage(), e); } catch (TooLargeObjectInPackException e) { throw new org.eclipse.jgit.api.errors.TooLargeObjectInPackException( e.getMessage(), e); } catch (TransportException e) { throw new org.eclipse.jgit.api.errors.TransportException( e.getMessage(), e); } finally { transport.close(); } } } catch (URISyntaxException e) { throw new InvalidRemoteException( MessageFormat.format(JGitText.get().invalidRemote, remote), e); } catch (TransportException e) { throw new org.eclipse.jgit.api.errors.TransportException( e.getMessage(), e); } catch (NotSupportedException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfPushCommand, e); } catch (IOException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfPushCommand, e); } return pushResults; } private String determineRemote(Config config, String remoteName) throws IOException { if (remoteName != null) { return remoteName; } Ref head = repo.exactRef(Constants.HEAD); String effectiveRemote = null; BranchConfig branchCfg = null; if (head != null && head.isSymbolic()) { String currentBranch = head.getLeaf().getName(); branchCfg = new BranchConfig(config, Repository.shortenRefName(currentBranch)); effectiveRemote = branchCfg.getPushRemote(); } if (effectiveRemote == null) { effectiveRemote = config.getString( ConfigConstants.CONFIG_REMOTE_SECTION, null, ConfigConstants.CONFIG_KEY_PUSH_DEFAULT); if (effectiveRemote == null && branchCfg != null) { effectiveRemote = branchCfg.getRemote(); } } if (effectiveRemote == null) { effectiveRemote = Constants.DEFAULT_REMOTE_NAME; } return effectiveRemote; } private String getCurrentBranch() throws IOException, DetachedHeadException { Ref head = repo.exactRef(Constants.HEAD); if (head != null && head.isSymbolic()) { return head.getLeaf().getName(); } throw new DetachedHeadException(); } private void determineDefaultRefSpecs(Config config) throws IOException, GitAPIException { if (pushDefault == null) { pushDefault = config.get(PushConfig::new).getPushDefault(); } switch (pushDefault) { case CURRENT: refSpecs.add(new RefSpec(getCurrentBranch())); break; case MATCHING: refSpecs.add(new RefSpec(":")); //$NON-NLS-1$ break; case NOTHING: throw new InvalidRefNameException( JGitText.get().pushDefaultNothing); case SIMPLE: case UPSTREAM: String currentBranch = getCurrentBranch(); BranchConfig branchCfg = new BranchConfig(config, Repository.shortenRefName(currentBranch)); String fetchRemote = branchCfg.getRemote(); if (fetchRemote == null) { fetchRemote = Constants.DEFAULT_REMOTE_NAME; } boolean isTriangular = !fetchRemote.equals(remote); if (isTriangular) { if (PushDefault.UPSTREAM.equals(pushDefault)) { throw new InvalidRefNameException(MessageFormat.format( JGitText.get().pushDefaultTriangularUpstream, remote, fetchRemote)); } // Strange, but consistent with C git: "simple" doesn't even // check whether there is a configured upstream, and if so, that // it is equal to the local branch name. It just becomes // "current". refSpecs.add(new RefSpec(currentBranch)); } else { String trackedBranch = branchCfg.getMerge(); if (branchCfg.isRemoteLocal() || trackedBranch == null || !trackedBranch.startsWith(Constants.R_HEADS)) { throw new InvalidRefNameException(MessageFormat.format( JGitText.get().pushDefaultNoUpstream, currentBranch)); } if (PushDefault.SIMPLE.equals(pushDefault) && !trackedBranch.equals(currentBranch)) { throw new InvalidRefNameException(MessageFormat.format( JGitText.get().pushDefaultSimple, currentBranch, trackedBranch)); } refSpecs.add(new RefSpec(currentBranch + ':' + trackedBranch)); } break; default: throw new InvalidRefNameException(MessageFormat .format(JGitText.get().pushDefaultUnknown, pushDefault)); } } /** * The remote (uri or name) used for the push operation. If no remote is * set, the default value of Constants.DEFAULT_REMOTE_NAME will * be used. * * @see Constants#DEFAULT_REMOTE_NAME * @param remote * the remote name * @return {@code this} */ public PushCommand setRemote(String remote) { checkCallable(); this.remote = remote; return this; } /** * Get remote name * * @return the remote used for the remote operation */ public String getRemote() { return remote; } /** * Sets a {@link PrintStream} a "pre-push" hook may write its stdout to. If * not set, {@link System#out} will be used. *

* When pushing to several remote repositories the stream is shared for all * pushes. *

* * @param redirect * {@link PrintStream} to use; if {@code null}, * {@link System#out} will be used * @return {@code this} * @since 6.4 */ public PushCommand setHookOutputStream(PrintStream redirect) { checkCallable(); hookOutRedirect = redirect; return this; } /** * Sets a {@link PrintStream} a "pre-push" hook may write its stderr to. If * not set, {@link System#err} will be used. *

* When pushing to several remote repositories the stream is shared for all * pushes. *

* * @param redirect * {@link PrintStream} to use; if {@code null}, * {@link System#err} will be used * @return {@code this} * @since 6.4 */ public PushCommand setHookErrorStream(PrintStream redirect) { checkCallable(); hookErrRedirect = redirect; return this; } /** * The remote executable providing receive-pack service for pack transports. * If no receive-pack is set, the default value of * RemoteConfig.DEFAULT_RECEIVE_PACK will be used. * * @see RemoteConfig#DEFAULT_RECEIVE_PACK * @param receivePack * name of the remote executable providing the receive-pack * service * @return {@code this} */ public PushCommand setReceivePack(String receivePack) { checkCallable(); this.receivePack = receivePack; return this; } /** * Get the name of the remote executable providing the receive-pack service * * @return the receive-pack used for the remote operation */ public String getReceivePack() { return receivePack; } /** * Get timeout used for push operation * * @return the timeout used for the push operation */ public int getTimeout() { return timeout; } /** * Get the progress monitor * * @return the progress monitor for the push operation */ public ProgressMonitor getProgressMonitor() { return monitor; } /** * The progress monitor associated with the push operation. By default, this * is set to NullProgressMonitor * * @see NullProgressMonitor * @param monitor * a {@link org.eclipse.jgit.lib.ProgressMonitor} * @return {@code this} */ public PushCommand setProgressMonitor(ProgressMonitor monitor) { checkCallable(); if (monitor == null) { monitor = NullProgressMonitor.INSTANCE; } this.monitor = monitor; return this; } /** * Get the RefLeaseSpecs. * * @return the RefLeaseSpecs * @since 4.7 */ public List getRefLeaseSpecs() { return new ArrayList<>(refLeaseSpecs.values()); } /** * The ref lease specs to be used in the push operation, for a * force-with-lease push operation. * * @param specs * a {@link org.eclipse.jgit.transport.RefLeaseSpec} object. * @return {@code this} * @since 4.7 */ public PushCommand setRefLeaseSpecs(RefLeaseSpec... specs) { return setRefLeaseSpecs(Arrays.asList(specs)); } /** * The ref lease specs to be used in the push operation, for a * force-with-lease push operation. * * @param specs * list of {@code RefLeaseSpec}s * @return {@code this} * @since 4.7 */ public PushCommand setRefLeaseSpecs(List specs) { checkCallable(); this.refLeaseSpecs.clear(); for (RefLeaseSpec spec : specs) { refLeaseSpecs.put(spec.getRef(), spec); } return this; } /** * Get {@code RefSpec}s. * * @return the ref specs */ public List getRefSpecs() { return refSpecs; } /** * The ref specs to be used in the push operation * * @param specs a {@link org.eclipse.jgit.transport.RefSpec} object. * @return {@code this} */ public PushCommand setRefSpecs(RefSpec... specs) { checkCallable(); this.refSpecs.clear(); Collections.addAll(refSpecs, specs); return this; } /** * The ref specs to be used in the push operation * * @param specs * list of {@link org.eclipse.jgit.transport.RefSpec}s * @return {@code this} */ public PushCommand setRefSpecs(List specs) { checkCallable(); this.refSpecs.clear(); this.refSpecs.addAll(specs); return this; } /** * Retrieves the {@link PushDefault} currently set. * * @return the {@link PushDefault}, or {@code null} if not set * @since 6.1 */ public PushDefault getPushDefault() { return pushDefault; } /** * Sets an explicit {@link PushDefault}. The default used if this is not * called is {@link PushDefault#CURRENT} for compatibility reasons with * earlier JGit versions. * * @param pushDefault * {@link PushDefault} to set; if {@code null} the value defined * in the git config will be used. * * @return {@code this} * @since 6.1 */ public PushCommand setPushDefault(PushDefault pushDefault) { checkCallable(); this.pushDefault = pushDefault; return this; } /** * Push all branches under refs/heads/*. * * @return {@code this} */ public PushCommand setPushAll() { refSpecs.add(Transport.REFSPEC_PUSH_ALL); return this; } /** * Push all tags under refs/tags/*. * * @return {@code this} */ public PushCommand setPushTags() { refSpecs.add(Transport.REFSPEC_TAGS); return this; } /** * Add a reference to push. * * @param ref * the source reference. The remote name will match. * @return {@code this}. */ public PushCommand add(Ref ref) { refSpecs.add(new RefSpec(ref.getLeaf().getName())); return this; } /** * Add a reference to push. * * @param nameOrSpec * any reference name, or a reference specification. * @return {@code this}. * @throws JGitInternalException * the reference name cannot be resolved. */ public PushCommand add(String nameOrSpec) { if (0 <= nameOrSpec.indexOf(':')) { refSpecs.add(new RefSpec(nameOrSpec)); } else { Ref src; try { src = repo.findRef(nameOrSpec); } catch (IOException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfPushCommand, e); } if (src != null) add(src); } return this; } /** * Whether to run the push operation as a dry run * * @return the dry run preference for the push operation */ public boolean isDryRun() { return dryRun; } /** * Sets whether the push operation should be a dry run * * @param dryRun a boolean. * @return {@code this} */ public PushCommand setDryRun(boolean dryRun) { checkCallable(); this.dryRun = dryRun; return this; } /** * Get the thin-pack preference * * @return the thin-pack preference for push operation */ public boolean isThin() { return thin; } /** * Set the thin-pack preference for push operation. * * Default setting is Transport.DEFAULT_PUSH_THIN * * @param thinPack * the thin-pack preference value * @return {@code this} */ public PushCommand setThin(boolean thinPack) { checkCallable(); this.thin = thinPack; return this; } /** * Whether to use bitmaps for push. * * @return true if push use bitmaps. * @since 6.4 */ public boolean isUseBitmaps() { return useBitmaps; } /** * Set whether to use bitmaps for push. * * Default setting is {@value Transport#DEFAULT_PUSH_USE_BITMAPS} * * @param useBitmaps * false to disable use of bitmaps for push, true otherwise. * @return {@code this} * @since 6.4 */ public PushCommand setUseBitmaps(boolean useBitmaps) { checkCallable(); this.useBitmaps = useBitmaps; return this; } /** * Whether this push should be executed atomically (all references updated, * or none) * * @return true if all-or-nothing behavior is requested. * @since 4.2 */ public boolean isAtomic() { return atomic; } /** * Requests atomic push (all references updated, or no updates). * * Default setting is false. * * @param atomic * whether to run the push atomically * @return {@code this} * @since 4.2 */ public PushCommand setAtomic(boolean atomic) { checkCallable(); this.atomic = atomic; return this; } /** * Whether to push forcefully * * @return the force preference for push operation */ public boolean isForce() { return force; } /** * Sets the force preference for push operation. * * @param force * whether to push forcefully * @return {@code this} */ public PushCommand setForce(boolean force) { checkCallable(); this.force = force; return this; } /** * Sets the output stream to write sideband messages to * * @param out * an {@link java.io.OutputStream} * @return {@code this} * @since 3.0 */ public PushCommand setOutputStream(OutputStream out) { this.out = out; return this; } /** * Get push options * * @return the option strings associated with the push operation * @since 4.5 */ public List getPushOptions() { return pushOptions; } /** * Set the option strings associated with the push operation. * * @param pushOptions * a {@link java.util.List} of push option strings * @return {@code this} * @since 4.5 */ public PushCommand setPushOptions(List pushOptions) { this.pushOptions = pushOptions; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 63095 Content-Disposition: inline; filename="RebaseCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "3ae7a6c81e28ea56b0c3e06e3885da0bf2563f1f" /* * Copyright (C) 2010, 2013 Mathias Kinzler * Copyright (C) 2016, 2021 Laurent Delaigue and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.text.MessageFormat; import java.time.Instant; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.api.RebaseResult.Status; import org.eclipse.jgit.api.ResetCommand.ResetType; import org.eclipse.jgit.api.errors.CheckoutConflictException; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRebaseStepException; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.api.errors.StashApplyFailureException; import org.eclipse.jgit.api.errors.UnmergedPathsException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.RevisionSyntaxException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.CommitConfig; import org.eclipse.jgit.lib.CommitConfig.CleanupMode; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.RebaseTodoLine; import org.eclipse.jgit.lib.RebaseTodoLine.Action; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.merge.ContentMergeStrategy; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevSort; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; /** * A class used to execute a {@code Rebase} command. It has setters for all * supported options and arguments of this command and a {@link #call()} method * to finally execute the command. Each instance of this class should only be * used for one invocation of the command (means: one call to {@link #call()}) * * @see Git documentation about Rebase */ public class RebaseCommand extends GitCommand { /** * The name of the "rebase-merge" folder for interactive rebases. */ public static final String REBASE_MERGE = "rebase-merge"; //$NON-NLS-1$ /** * The name of the "rebase-apply" folder for non-interactive rebases. */ private static final String REBASE_APPLY = "rebase-apply"; //$NON-NLS-1$ /** * The name of the "stopped-sha" file */ public static final String STOPPED_SHA = "stopped-sha"; //$NON-NLS-1$ private static final String AUTHOR_SCRIPT = "author-script"; //$NON-NLS-1$ private static final String DONE = "done"; //$NON-NLS-1$ private static final String GIT_AUTHOR_DATE = "GIT_AUTHOR_DATE"; //$NON-NLS-1$ private static final String GIT_AUTHOR_EMAIL = "GIT_AUTHOR_EMAIL"; //$NON-NLS-1$ private static final String GIT_AUTHOR_NAME = "GIT_AUTHOR_NAME"; //$NON-NLS-1$ private static final String GIT_REBASE_TODO = "git-rebase-todo"; //$NON-NLS-1$ private static final String HEAD_NAME = "head-name"; //$NON-NLS-1$ private static final String INTERACTIVE = "interactive"; //$NON-NLS-1$ private static final String QUIET = "quiet"; //$NON-NLS-1$ private static final String MESSAGE = "message"; //$NON-NLS-1$ private static final String ONTO = "onto"; //$NON-NLS-1$ private static final String ONTO_NAME = "onto_name"; //$NON-NLS-1$ private static final String PATCH = "patch"; //$NON-NLS-1$ private static final String REBASE_HEAD = "orig-head"; //$NON-NLS-1$ /** Pre git 1.7.6 file name for {@link #REBASE_HEAD}. */ private static final String REBASE_HEAD_LEGACY = "head"; //$NON-NLS-1$ private static final String AMEND = "amend"; //$NON-NLS-1$ private static final String MESSAGE_FIXUP = "message-fixup"; //$NON-NLS-1$ private static final String MESSAGE_SQUASH = "message-squash"; //$NON-NLS-1$ private static final String AUTOSTASH = "autostash"; //$NON-NLS-1$ private static final String AUTOSTASH_MSG = "On {0}: autostash"; //$NON-NLS-1$ /** * The folder containing the hashes of (potentially) rewritten commits when * --rebase-merges is used. *

* Native git rebase --merge uses a file of that name to record * commits to copy notes at the end of the whole rebase. *

*/ private static final String REWRITTEN = "rewritten"; //$NON-NLS-1$ /** * File containing the current commit(s) to cherry pick when --rebase-merges * is used. */ private static final String CURRENT_COMMIT = "current-commit"; //$NON-NLS-1$ private static final String REFLOG_PREFIX = "rebase:"; //$NON-NLS-1$ /** * The available operations */ public enum Operation { /** * Initiates rebase */ BEGIN, /** * Continues after a conflict resolution */ CONTINUE, /** * Skips the "current" commit */ SKIP, /** * Aborts and resets the current rebase */ ABORT, /** * Starts processing steps * @since 3.2 */ PROCESS_STEPS; } private Operation operation = Operation.BEGIN; private RevCommit upstreamCommit; private String upstreamCommitName; private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; private final RevWalk walk; private final RebaseState rebaseState; private InteractiveHandler interactiveHandler; private CommitConfig commitConfig; private boolean stopAfterInitialization = false; private RevCommit newHead; private boolean lastStepWasForward; private MergeStrategy strategy = MergeStrategy.RECURSIVE; private ContentMergeStrategy contentStrategy; private boolean preserveMerges = false; /** *

* Constructor for RebaseCommand. *

* * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected RebaseCommand(Repository repo) { super(repo); walk = new RevWalk(repo); rebaseState = new RebaseState(repo.getDirectory()); } /** * {@inheritDoc} *

* Executes the {@code Rebase} command with all the options and parameters * collected by the setter methods of this class. Each instance of this * class should only be used for one invocation of the command. Don't call * this method twice on an instance. */ @Override public RebaseResult call() throws GitAPIException, NoHeadException, RefNotFoundException, WrongRepositoryStateException { newHead = null; lastStepWasForward = false; checkCallable(); checkParameters(); commitConfig = repo.getConfig().get(CommitConfig.KEY); try { switch (operation) { case ABORT: try { return abort(RebaseResult.ABORTED_RESULT); } catch (IOException ioe) { throw new JGitInternalException(ioe.getMessage(), ioe); } case PROCESS_STEPS: case SKIP: case CONTINUE: String upstreamCommitId = rebaseState.readFile(ONTO); try { upstreamCommitName = rebaseState.readFile(ONTO_NAME); } catch (FileNotFoundException e) { // Fall back to commit ID if file doesn't exist (e.g. rebase // was started by C Git) upstreamCommitName = upstreamCommitId; } this.upstreamCommit = walk.parseCommit(repo .resolve(upstreamCommitId)); preserveMerges = rebaseState.getRewrittenDir().isDirectory(); break; case BEGIN: autoStash(); if (stopAfterInitialization || !walk.isMergedInto( walk.parseCommit(repo.resolve(Constants.HEAD)), upstreamCommit)) { org.eclipse.jgit.api.Status status = Git.wrap(repo) .status().setIgnoreSubmodules(IgnoreSubmoduleMode.ALL).call(); if (status.hasUncommittedChanges()) { List list = new ArrayList<>(); list.addAll(status.getUncommittedChanges()); return RebaseResult.uncommittedChanges(list); } } RebaseResult res = initFilesAndRewind(); if (stopAfterInitialization) { return RebaseResult.INTERACTIVE_PREPARED_RESULT; } if (res != null) { if (!autoStashApply()) { res = RebaseResult.STASH_APPLY_CONFLICTS_RESULT; } if (rebaseState.getDir().exists()) { FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE); } return res; } } if (monitor.isCancelled()) return abort(RebaseResult.ABORTED_RESULT); if (operation == Operation.CONTINUE) { newHead = continueRebase(); List doneLines = repo.readRebaseTodo( rebaseState.getPath(DONE), true); RebaseTodoLine step = doneLines.get(doneLines.size() - 1); if (newHead != null && step.getAction() != Action.PICK) { RebaseTodoLine newStep = new RebaseTodoLine( step.getAction(), AbbreviatedObjectId.fromObjectId(newHead), step.getShortMessage()); RebaseResult result = processStep(newStep, false); if (result != null) return result; } File amendFile = rebaseState.getFile(AMEND); boolean amendExists = amendFile.exists(); if (amendExists) { FileUtils.delete(amendFile); } if (newHead == null && !amendExists) { // continueRebase() returns null only if no commit was // neccessary. This means that no changes where left over // after resolving all conflicts. In this case, cgit stops // and displays a nice message to the user, telling him to // either do changes or skip the commit instead of continue. return RebaseResult.NOTHING_TO_COMMIT_RESULT; } } if (operation == Operation.SKIP) newHead = checkoutCurrentHead(); List steps = repo.readRebaseTodo( rebaseState.getPath(GIT_REBASE_TODO), false); if (steps.isEmpty()) { return finishRebase(walk.parseCommit(repo.resolve(Constants.HEAD)), false); } if (isInteractive()) { interactiveHandler.prepareSteps(steps); repo.writeRebaseTodoFile(rebaseState.getPath(GIT_REBASE_TODO), steps, false); } checkSteps(steps); for (RebaseTodoLine step : steps) { popSteps(1); RebaseResult result = processStep(step, true); if (result != null) { return result; } } return finishRebase(newHead, lastStepWasForward); } catch (CheckoutConflictException cce) { return RebaseResult.conflicts(cce.getConflictingPaths()); } catch (IOException ioe) { throw new JGitInternalException(ioe.getMessage(), ioe); } } private void autoStash() throws GitAPIException, IOException { if (repo.getConfig().getBoolean(ConfigConstants.CONFIG_REBASE_SECTION, ConfigConstants.CONFIG_KEY_AUTOSTASH, false)) { String message = MessageFormat.format( AUTOSTASH_MSG, Repository .shortenRefName(getHeadName(getHead()))); RevCommit stashCommit = Git.wrap(repo).stashCreate().setRef(null) .setWorkingDirectoryMessage( message) .call(); if (stashCommit != null) { FileUtils.mkdir(rebaseState.getDir()); rebaseState.createFile(AUTOSTASH, stashCommit.getName()); } } } private boolean autoStashApply() throws IOException, GitAPIException { boolean success = true; if (rebaseState.getFile(AUTOSTASH).exists()) { String stash = rebaseState.readFile(AUTOSTASH); try (Git git = Git.wrap(repo)) { git.stashApply().setStashRef(stash) .ignoreRepositoryState(true).setStrategy(strategy) .call(); } catch (StashApplyFailureException e) { success = false; try (RevWalk rw = new RevWalk(repo)) { ObjectId stashId = repo.resolve(stash); RevCommit commit = rw.parseCommit(stashId); updateStashRef(commit, commit.getAuthorIdent(), commit.getShortMessage()); } } } return success; } private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent, String refLogMessage) throws IOException { Ref currentRef = repo.exactRef(Constants.R_STASH); RefUpdate refUpdate = repo.updateRef(Constants.R_STASH); refUpdate.setNewObjectId(commitId); refUpdate.setRefLogIdent(refLogIdent); refUpdate.setRefLogMessage(refLogMessage, false); refUpdate.setForceRefLog(true); if (currentRef != null) refUpdate.setExpectedOldObjectId(currentRef.getObjectId()); else refUpdate.setExpectedOldObjectId(ObjectId.zeroId()); refUpdate.forceUpdate(); } private RebaseResult processStep(RebaseTodoLine step, boolean shouldPick) throws IOException, GitAPIException { if (Action.COMMENT.equals(step.getAction())) return null; if (preserveMerges && shouldPick && (Action.EDIT.equals(step.getAction()) || Action.PICK .equals(step.getAction()))) { writeRewrittenHashes(); } ObjectReader or = repo.newObjectReader(); Collection ids = or.resolve(step.getCommit()); if (ids.size() != 1) throw new JGitInternalException( JGitText.get().cannotResolveUniquelyAbbrevObjectId); RevCommit commitToPick = walk.parseCommit(ids.iterator().next()); if (shouldPick) { if (monitor.isCancelled()) return RebaseResult.result(Status.STOPPED, commitToPick); RebaseResult result = cherryPickCommit(commitToPick); if (result != null) return result; } boolean isSquash = false; switch (step.getAction()) { case PICK: return null; // continue rebase process on pick command case REWORD: String oldMessage = commitToPick.getFullMessage(); CleanupMode mode = commitConfig.resolve(CleanupMode.DEFAULT, true); boolean[] doChangeId = { false }; String newMessage = editCommitMessage(doChangeId, oldMessage, mode, commitConfig.getCommentChar(oldMessage)); try (Git git = new Git(repo)) { newHead = git.commit() .setMessage(newMessage) .setAmend(true) .setNoVerify(true) .setInsertChangeId(doChangeId[0]) .call(); } return null; case EDIT: rebaseState.createFile(AMEND, commitToPick.name()); return stop(commitToPick, Status.EDIT); case COMMENT: break; case SQUASH: isSquash = true; //$FALL-THROUGH$ case FIXUP: resetSoftToParent(); List steps = repo.readRebaseTodo( rebaseState.getPath(GIT_REBASE_TODO), false); boolean isLast = steps.isEmpty(); if (!isLast) { switch (steps.get(0).getAction()) { case FIXUP: case SQUASH: break; default: isLast = true; break; } } File messageFixupFile = rebaseState.getFile(MESSAGE_FIXUP); File messageSquashFile = rebaseState.getFile(MESSAGE_SQUASH); if (isSquash && messageFixupFile.exists()) { messageFixupFile.delete(); } newHead = doSquashFixup(isSquash, commitToPick, isLast, messageFixupFile, messageSquashFile); } return null; } private String editCommitMessage(boolean[] doChangeId, String message, @NonNull CleanupMode mode, char commentChar) { String newMessage; CommitConfig.CleanupMode cleanup; if (interactiveHandler instanceof InteractiveHandler2) { InteractiveHandler2.ModifyResult modification = ((InteractiveHandler2) interactiveHandler) .editCommitMessage(message, mode, commentChar); newMessage = modification.getMessage(); cleanup = modification.getCleanupMode(); if (CleanupMode.DEFAULT.equals(cleanup)) { cleanup = mode; } doChangeId[0] = modification.shouldAddChangeId(); } else { newMessage = interactiveHandler.modifyCommitMessage(message); cleanup = CommitConfig.CleanupMode.STRIP; doChangeId[0] = false; } return CommitConfig.cleanText(newMessage, cleanup, commentChar); } private RebaseResult cherryPickCommit(RevCommit commitToPick) throws IOException, GitAPIException, NoMessageException, UnmergedPathsException, ConcurrentRefUpdateException, WrongRepositoryStateException, NoHeadException { try { monitor.beginTask(MessageFormat.format( JGitText.get().applyingCommit, commitToPick.getShortMessage()), ProgressMonitor.UNKNOWN); if (preserveMerges) { return cherryPickCommitPreservingMerges(commitToPick); } return cherryPickCommitFlattening(commitToPick); } finally { monitor.endTask(); } } private RebaseResult cherryPickCommitFlattening(RevCommit commitToPick) throws IOException, GitAPIException, NoMessageException, UnmergedPathsException, ConcurrentRefUpdateException, WrongRepositoryStateException, NoHeadException { // If the first parent of commitToPick is the current HEAD, // we do a fast-forward instead of cherry-pick to avoid // unnecessary object rewriting newHead = tryFastForward(commitToPick); lastStepWasForward = newHead != null; if (!lastStepWasForward) { // TODO if the content of this commit is already merged // here we should skip this step in order to avoid // confusing pseudo-changed String ourCommitName = getOurCommitName(); try (Git git = new Git(repo)) { CherryPickResult cherryPickResult = git.cherryPick() .include(commitToPick) .setOurCommitName(ourCommitName) .setReflogPrefix(REFLOG_PREFIX) .setStrategy(strategy) .setContentMergeStrategy(contentStrategy) .call(); switch (cherryPickResult.getStatus()) { case FAILED: if (operation == Operation.BEGIN) { return abort(RebaseResult .failed(cherryPickResult.getFailingPaths())); } return stop(commitToPick, Status.STOPPED); case CONFLICTING: return stop(commitToPick, Status.STOPPED); case OK: newHead = cherryPickResult.getNewHead(); } } } return null; } private RebaseResult cherryPickCommitPreservingMerges(RevCommit commitToPick) throws IOException, GitAPIException, NoMessageException, UnmergedPathsException, ConcurrentRefUpdateException, WrongRepositoryStateException, NoHeadException { writeCurrentCommit(commitToPick); List newParents = getNewParents(commitToPick); boolean otherParentsUnchanged = true; for (int i = 1; i < commitToPick.getParentCount(); i++) otherParentsUnchanged &= newParents.get(i).equals( commitToPick.getParent(i)); // If the first parent of commitToPick is the current HEAD, // we do a fast-forward instead of cherry-pick to avoid // unnecessary object rewriting newHead = otherParentsUnchanged ? tryFastForward(commitToPick) : null; lastStepWasForward = newHead != null; if (!lastStepWasForward) { ObjectId headId = getHead().getObjectId(); // getHead() checks for null assert headId != null; if (!AnyObjectId.isEqual(headId, newParents.get(0))) checkoutCommit(headId.getName(), newParents.get(0)); // Use the cherry-pick strategy if all non-first parents did not // change. This is different from C Git, which always uses the merge // strategy (see below). try (Git git = new Git(repo)) { if (otherParentsUnchanged) { boolean isMerge = commitToPick.getParentCount() > 1; String ourCommitName = getOurCommitName(); CherryPickCommand pickCommand = git.cherryPick() .include(commitToPick) .setOurCommitName(ourCommitName) .setReflogPrefix(REFLOG_PREFIX) .setStrategy(strategy) .setContentMergeStrategy(contentStrategy); if (isMerge) { pickCommand.setMainlineParentNumber(1); // We write a MERGE_HEAD and later commit explicitly pickCommand.setNoCommit(true); writeMergeInfo(commitToPick, newParents); } CherryPickResult cherryPickResult = pickCommand.call(); switch (cherryPickResult.getStatus()) { case FAILED: if (operation == Operation.BEGIN) { return abort(RebaseResult.failed( cherryPickResult.getFailingPaths())); } return stop(commitToPick, Status.STOPPED); case CONFLICTING: return stop(commitToPick, Status.STOPPED); case OK: if (isMerge) { // Commit the merge (setup above using // writeMergeInfo()) CommitCommand commit = git.commit(); commit.setAuthor(commitToPick.getAuthorIdent()); commit.setReflogComment(REFLOG_PREFIX + " " //$NON-NLS-1$ + commitToPick.getShortMessage()); newHead = commit.call(); } else newHead = cherryPickResult.getNewHead(); break; } } else { // Use the merge strategy to redo merges, which had some of // their non-first parents rewritten MergeCommand merge = git.merge() .setFastForward(MergeCommand.FastForwardMode.NO_FF) .setProgressMonitor(monitor) .setStrategy(strategy) .setContentMergeStrategy(contentStrategy) .setCommit(false); for (int i = 1; i < commitToPick.getParentCount(); i++) merge.include(newParents.get(i)); MergeResult mergeResult = merge.call(); if (mergeResult.getMergeStatus().isSuccessful()) { CommitCommand commit = git.commit(); commit.setAuthor(commitToPick.getAuthorIdent()); commit.setMessage(commitToPick.getFullMessage()); commit.setReflogComment(REFLOG_PREFIX + " " //$NON-NLS-1$ + commitToPick.getShortMessage()); newHead = commit.call(); } else { if (operation == Operation.BEGIN && mergeResult .getMergeStatus() == MergeResult.MergeStatus.FAILED) return abort(RebaseResult .failed(mergeResult.getFailingPaths())); return stop(commitToPick, Status.STOPPED); } } } } return null; } // Prepare MERGE_HEAD and message for the next commit private void writeMergeInfo(RevCommit commitToPick, List newParents) throws IOException { repo.writeMergeHeads(newParents.subList(1, newParents.size())); repo.writeMergeCommitMsg(commitToPick.getFullMessage()); } // Get the rewritten equivalents for the parents of the given commit private List getNewParents(RevCommit commitToPick) throws IOException { List newParents = new ArrayList<>(); for (int p = 0; p < commitToPick.getParentCount(); p++) { String parentHash = commitToPick.getParent(p).getName(); if (!new File(rebaseState.getRewrittenDir(), parentHash).exists()) newParents.add(commitToPick.getParent(p)); else { String newParent = RebaseState.readFile( rebaseState.getRewrittenDir(), parentHash); if (newParent.length() == 0) newParents.add(walk.parseCommit(repo .resolve(Constants.HEAD))); else newParents.add(walk.parseCommit(ObjectId .fromString(newParent))); } } return newParents; } private void writeCurrentCommit(RevCommit commit) throws IOException { RebaseState.appendToFile(rebaseState.getFile(CURRENT_COMMIT), commit.name()); } private void writeRewrittenHashes() throws RevisionSyntaxException, IOException, RefNotFoundException { File currentCommitFile = rebaseState.getFile(CURRENT_COMMIT); if (!currentCommitFile.exists()) return; ObjectId headId = getHead().getObjectId(); // getHead() checks for null assert headId != null; String head = headId.getName(); String currentCommits = rebaseState.readFile(CURRENT_COMMIT); for (String current : currentCommits.split("\n")) //$NON-NLS-1$ RebaseState .createFile(rebaseState.getRewrittenDir(), current, head); FileUtils.delete(currentCommitFile); } private RebaseResult finishRebase(RevCommit finalHead, boolean lastStepIsForward) throws IOException, GitAPIException { String headName = rebaseState.readFile(HEAD_NAME); updateHead(headName, finalHead, upstreamCommit); boolean unstashSuccessful = autoStashApply(); getRepository().autoGC(monitor); FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE); if (!unstashSuccessful) { return RebaseResult.STASH_APPLY_CONFLICTS_RESULT; } if (lastStepIsForward || finalHead == null) { return RebaseResult.FAST_FORWARD_RESULT; } return RebaseResult.OK_RESULT; } private void checkSteps(List steps) throws InvalidRebaseStepException, IOException { if (steps.isEmpty()) return; if (RebaseTodoLine.Action.SQUASH.equals(steps.get(0).getAction()) || RebaseTodoLine.Action.FIXUP.equals(steps.get(0).getAction())) { if (!rebaseState.getFile(DONE).exists() || rebaseState.readFile(DONE).trim().length() == 0) { throw new InvalidRebaseStepException(MessageFormat.format( JGitText.get().cannotSquashFixupWithoutPreviousCommit, steps.get(0).getAction().name())); } } } private RevCommit doSquashFixup(boolean isSquash, RevCommit commitToPick, boolean isLast, File messageFixup, File messageSquash) throws IOException, GitAPIException { if (!messageSquash.exists()) { // init squash/fixup sequence ObjectId headId = repo.resolve(Constants.HEAD); RevCommit previousCommit = walk.parseCommit(headId); initializeSquashFixupFile(MESSAGE_SQUASH, previousCommit.getFullMessage()); if (!isSquash) { rebaseState.createFile(MESSAGE_FIXUP, previousCommit.getFullMessage()); } } String currSquashMessage = rebaseState.readFile(MESSAGE_SQUASH); int count = parseSquashFixupSequenceCount(currSquashMessage) + 1; String content = composeSquashMessage(isSquash, commitToPick, currSquashMessage, count); rebaseState.createFile(MESSAGE_SQUASH, content); return squashIntoPrevious(!messageFixup.exists(), isLast); } private void resetSoftToParent() throws IOException, GitAPIException, CheckoutConflictException { Ref ref = repo.exactRef(Constants.ORIG_HEAD); ObjectId orig_head = ref == null ? null : ref.getObjectId(); try (Git git = Git.wrap(repo)) { // we have already committed the cherry-picked commit. // what we need is to have changes introduced by this // commit to be on the index // resetting is a workaround git.reset().setMode(ResetType.SOFT) .setRef("HEAD~1").call(); //$NON-NLS-1$ } finally { // set ORIG_HEAD back to where we started because soft // reset moved it repo.writeOrigHead(orig_head); } } private RevCommit squashIntoPrevious(boolean sequenceContainsSquash, boolean isLast) throws IOException, GitAPIException { RevCommit retNewHead; String commitMessage; if (!isLast || sequenceContainsSquash) { commitMessage = rebaseState.readFile(MESSAGE_SQUASH); } else { commitMessage = rebaseState.readFile(MESSAGE_FIXUP); } try (Git git = new Git(repo)) { if (isLast) { boolean[] doChangeId = { false }; if (sequenceContainsSquash) { char commentChar = commitMessage.charAt(0); commitMessage = editCommitMessage(doChangeId, commitMessage, CleanupMode.STRIP, commentChar); } retNewHead = git.commit() .setMessage(commitMessage) .setAmend(true) .setNoVerify(true) .setInsertChangeId(doChangeId[0]) .call(); rebaseState.getFile(MESSAGE_SQUASH).delete(); rebaseState.getFile(MESSAGE_FIXUP).delete(); } else { // Next step is either Squash or Fixup retNewHead = git.commit().setMessage(commitMessage) .setAmend(true).setNoVerify(true).call(); } } return retNewHead; } @SuppressWarnings("nls") private String composeSquashMessage(boolean isSquash, RevCommit commitToPick, String currSquashMessage, int count) { StringBuilder sb = new StringBuilder(); String ordinal = getOrdinal(count); // currSquashMessage is always non-empty here, and the first character // is the comment character used so far. char commentChar = currSquashMessage.charAt(0); String newMessage = commitToPick.getFullMessage(); if (!isSquash) { sb.append(commentChar).append(" This is a combination of ") .append(count).append(" commits.\n"); // Add the previous message without header (i.e first line) sb.append(currSquashMessage .substring(currSquashMessage.indexOf('\n') + 1)); sb.append('\n'); sb.append(commentChar).append(" The ").append(count).append(ordinal) .append(" commit message will be skipped:\n") .append(commentChar).append(' '); sb.append(newMessage.replaceAll("([\n\r])", "$1" + commentChar + ' ')); } else { String currentMessage = currSquashMessage; if (commitConfig.isAutoCommentChar()) { // Figure out a new comment character taking into account the // new message String cleaned = CommitConfig.cleanText(currentMessage, CommitConfig.CleanupMode.STRIP, commentChar) + '\n' + newMessage; char newCommentChar = commitConfig.getCommentChar(cleaned); if (newCommentChar != commentChar) { currentMessage = replaceCommentChar(currentMessage, commentChar, newCommentChar); commentChar = newCommentChar; } } sb.append(commentChar).append(" This is a combination of ") .append(count).append(" commits.\n"); // Add the previous message without header (i.e first line) sb.append( currentMessage.substring(currentMessage.indexOf('\n') + 1)); sb.append('\n'); sb.append(commentChar).append(" This is the ").append(count) .append(ordinal).append(" commit message:\n"); sb.append(newMessage); } return sb.toString(); } private String replaceCommentChar(String message, char oldChar, char newChar) { // (?m) - Switch on multi-line matching; \h - horizontal whitespace return message.replaceAll("(?m)^(\\h*)" + oldChar, "$1" + newChar); //$NON-NLS-1$ //$NON-NLS-2$ } private static String getOrdinal(int count) { switch (count % 10) { case 1: return "st"; //$NON-NLS-1$ case 2: return "nd"; //$NON-NLS-1$ case 3: return "rd"; //$NON-NLS-1$ default: return "th"; //$NON-NLS-1$ } } /** * Parse the count from squashed commit messages * * @param currSquashMessage * the squashed commit message to be parsed * @return the count of squashed messages in the given string */ static int parseSquashFixupSequenceCount(String currSquashMessage) { String regex = "This is a combination of (.*) commits"; //$NON-NLS-1$ String firstLine = currSquashMessage.substring(0, currSquashMessage.indexOf('\n')); Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(firstLine); if (!matcher.find()) throw new IllegalArgumentException(); return Integer.parseInt(matcher.group(1)); } private void initializeSquashFixupFile(String messageFile, String fullMessage) throws IOException { char commentChar = commitConfig.getCommentChar(fullMessage); rebaseState.createFile(messageFile, commentChar + " This is a combination of 1 commits.\n" //$NON-NLS-1$ + commentChar + " The first commit's message is:\n" //$NON-NLS-1$ + fullMessage); } private String getOurCommitName() { // If onto is different from upstream, this should say "onto", but // RebaseCommand doesn't support a different "onto" at the moment. String ourCommitName = "Upstream, based on " //$NON-NLS-1$ + Repository.shortenRefName(upstreamCommitName); return ourCommitName; } private void updateHead(String headName, RevCommit aNewHead, RevCommit onto) throws IOException { // point the previous head (if any) to the new commit if (headName.startsWith(Constants.R_REFS)) { RefUpdate rup = repo.updateRef(headName); rup.setNewObjectId(aNewHead); rup.setRefLogMessage("rebase finished: " + headName + " onto " //$NON-NLS-1$ //$NON-NLS-2$ + onto.getName(), false); Result res = rup.forceUpdate(); switch (res) { case FAST_FORWARD: case FORCED: case NO_CHANGE: break; default: throw new JGitInternalException( JGitText.get().updatingHeadFailed); } rup = repo.updateRef(Constants.HEAD); rup.setRefLogMessage("rebase finished: returning to " + headName, //$NON-NLS-1$ false); res = rup.link(headName); switch (res) { case FAST_FORWARD: case FORCED: case NO_CHANGE: break; default: throw new JGitInternalException( JGitText.get().updatingHeadFailed); } } } private RevCommit checkoutCurrentHead() throws IOException, NoHeadException { ObjectId headTree = repo.resolve(Constants.HEAD + "^{tree}"); //$NON-NLS-1$ if (headTree == null) throw new NoHeadException( JGitText.get().cannotRebaseWithoutCurrentHead); DirCache dc = repo.lockDirCache(); try { DirCacheCheckout dco = new DirCacheCheckout(repo, dc, headTree); dco.setFailOnConflict(false); dco.setProgressMonitor(monitor); boolean needsDeleteFiles = dco.checkout(); if (needsDeleteFiles) { List fileList = dco.getToBeDeleted(); for (String filePath : fileList) { File fileToDelete = new File(repo.getWorkTree(), filePath); if (repo.getFS().exists(fileToDelete)) FileUtils.delete(fileToDelete, FileUtils.RECURSIVE | FileUtils.RETRY); } } } finally { dc.unlock(); } try (RevWalk rw = new RevWalk(repo)) { RevCommit commit = rw.parseCommit(repo.resolve(Constants.HEAD)); return commit; } } /** * @return the commit if we had to do a commit, otherwise null * @throws GitAPIException * if JGit API failed * @throws IOException * if an IO error occurred */ private RevCommit continueRebase() throws GitAPIException, IOException { // if there are still conflicts, we throw a specific Exception DirCache dc = repo.readDirCache(); boolean hasUnmergedPaths = dc.hasUnmergedPaths(); if (hasUnmergedPaths) throw new UnmergedPathsException(); // determine whether we need to commit boolean needsCommit; try (TreeWalk treeWalk = new TreeWalk(repo)) { treeWalk.reset(); treeWalk.setRecursive(true); treeWalk.addTree(new DirCacheIterator(dc)); ObjectId id = repo.resolve(Constants.HEAD + "^{tree}"); //$NON-NLS-1$ if (id == null) throw new NoHeadException( JGitText.get().cannotRebaseWithoutCurrentHead); treeWalk.addTree(id); treeWalk.setFilter(TreeFilter.ANY_DIFF); needsCommit = treeWalk.next(); } if (needsCommit) { try (Git git = new Git(repo)) { CommitCommand commit = git.commit(); commit.setMessage(rebaseState.readFile(MESSAGE)); commit.setAuthor(parseAuthor()); return commit.call(); } } return null; } private PersonIdent parseAuthor() throws IOException { File authorScriptFile = rebaseState.getFile(AUTHOR_SCRIPT); byte[] raw; try { raw = IO.readFully(authorScriptFile); } catch (FileNotFoundException notFound) { if (authorScriptFile.exists()) { throw notFound; } return null; } return parseAuthor(raw); } private RebaseResult stop(RevCommit commitToPick, RebaseResult.Status status) throws IOException { PersonIdent author = commitToPick.getAuthorIdent(); String authorScript = toAuthorScript(author); rebaseState.createFile(AUTHOR_SCRIPT, authorScript); rebaseState.createFile(MESSAGE, commitToPick.getFullMessage()); if (commitToPick.getParentCount() > 0) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try (DiffFormatter df = new DiffFormatter(bos)) { df.setRepository(repo); df.format(commitToPick.getParent(0), commitToPick); } rebaseState.createFile(PATCH, new String(bos.toByteArray(), UTF_8)); } else { rebaseState.createFile(PATCH, ""); //$NON-NLS-1$ } rebaseState.createFile(STOPPED_SHA, repo.newObjectReader() .abbreviate( commitToPick).name()); // Remove cherry pick state file created by CherryPickCommand, it's not // needed for rebase repo.writeCherryPickHead(null); return RebaseResult.result(status, commitToPick); } String toAuthorScript(PersonIdent author) { StringBuilder sb = new StringBuilder(100); sb.append(GIT_AUTHOR_NAME); sb.append("='"); //$NON-NLS-1$ sb.append(author.getName()); sb.append("'\n"); //$NON-NLS-1$ sb.append(GIT_AUTHOR_EMAIL); sb.append("='"); //$NON-NLS-1$ sb.append(author.getEmailAddress()); sb.append("'\n"); //$NON-NLS-1$ // the command line uses the "external String" // representation for date and timezone sb.append(GIT_AUTHOR_DATE); sb.append("='"); //$NON-NLS-1$ sb.append("@"); // @ for time in seconds since 1970 //$NON-NLS-1$ String externalString = author.toExternalString(); sb .append(externalString.substring(externalString .lastIndexOf('>') + 2)); sb.append("'\n"); //$NON-NLS-1$ return sb.toString(); } /** * Removes the number of lines given in the parameter from the * git-rebase-todo file but preserves comments and other lines * that can not be parsed as steps * * @param numSteps * number of steps to remove * @throws IOException * if an IO error occurred */ private void popSteps(int numSteps) throws IOException { if (numSteps == 0) return; List todoLines = new ArrayList<>(); List poppedLines = new ArrayList<>(); for (RebaseTodoLine line : repo.readRebaseTodo( rebaseState.getPath(GIT_REBASE_TODO), true)) { if (poppedLines.size() >= numSteps || RebaseTodoLine.Action.COMMENT.equals(line.getAction())) todoLines.add(line); else poppedLines.add(line); } repo.writeRebaseTodoFile(rebaseState.getPath(GIT_REBASE_TODO), todoLines, false); if (!poppedLines.isEmpty()) { repo.writeRebaseTodoFile(rebaseState.getPath(DONE), poppedLines, true); } } private RebaseResult initFilesAndRewind() throws IOException, GitAPIException { // we need to store everything into files so that we can implement // --skip, --continue, and --abort Ref head = getHead(); ObjectId headId = head.getObjectId(); if (headId == null) { throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, Constants.HEAD)); } String headName = getHeadName(head); RevCommit headCommit = walk.lookupCommit(headId); RevCommit upstream = walk.lookupCommit(upstreamCommit.getId()); if (!isInteractive() && walk.isMergedInto(upstream, headCommit)) return RebaseResult.UP_TO_DATE_RESULT; else if (!isInteractive() && walk.isMergedInto(headCommit, upstream)) { // head is already merged into upstream, fast-forward monitor.beginTask(MessageFormat.format( JGitText.get().resettingHead, upstreamCommit.getShortMessage()), ProgressMonitor.UNKNOWN); checkoutCommit(headName, upstreamCommit); monitor.endTask(); updateHead(headName, upstreamCommit, upstream); return RebaseResult.FAST_FORWARD_RESULT; } monitor.beginTask(JGitText.get().obtainingCommitsForCherryPick, ProgressMonitor.UNKNOWN); // create the folder for the meta information FileUtils.mkdir(rebaseState.getDir(), true); repo.writeOrigHead(headId); rebaseState.createFile(REBASE_HEAD, headId.name()); rebaseState.createFile(REBASE_HEAD_LEGACY, headId.name()); rebaseState.createFile(HEAD_NAME, headName); rebaseState.createFile(ONTO, upstreamCommit.name()); rebaseState.createFile(ONTO_NAME, upstreamCommitName); if (isInteractive() || preserveMerges) { // --preserve-merges is an interactive mode for native git. Without // this, native git rebase --continue after a conflict would fall // into merge mode. rebaseState.createFile(INTERACTIVE, ""); //$NON-NLS-1$ } rebaseState.createFile(QUIET, ""); //$NON-NLS-1$ ArrayList toDoSteps = new ArrayList<>(); toDoSteps.add(new RebaseTodoLine("# Created by EGit: rebasing " + headId.name() //$NON-NLS-1$ + " onto " + upstreamCommit.name())); //$NON-NLS-1$ // determine the commits to be applied List cherryPickList = calculatePickList(headCommit); ObjectReader reader = walk.getObjectReader(); for (RevCommit commit : cherryPickList) toDoSteps.add(new RebaseTodoLine(Action.PICK, reader .abbreviate(commit), commit.getShortMessage())); repo.writeRebaseTodoFile(rebaseState.getPath(GIT_REBASE_TODO), toDoSteps, false); monitor.endTask(); // we rewind to the upstream commit monitor.beginTask(MessageFormat.format(JGitText.get().rewinding, upstreamCommit.getShortMessage()), ProgressMonitor.UNKNOWN); boolean checkoutOk = false; try { checkoutOk = checkoutCommit(headName, upstreamCommit); } finally { if (!checkoutOk) FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE); } monitor.endTask(); return null; } private List calculatePickList(RevCommit headCommit) throws IOException { List cherryPickList = new ArrayList<>(); try (RevWalk r = new RevWalk(repo)) { r.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER, true); r.sort(RevSort.COMMIT_TIME_DESC, true); r.markUninteresting(r.lookupCommit(upstreamCommit)); r.markStart(r.lookupCommit(headCommit)); Iterator commitsToUse = r.iterator(); while (commitsToUse.hasNext()) { RevCommit commit = commitsToUse.next(); if (preserveMerges || commit.getParentCount() <= 1) { cherryPickList.add(commit); } } } Collections.reverse(cherryPickList); if (preserveMerges) { // When preserving merges we only rewrite commits which have at // least one parent that is itself rewritten (or a merge base) File rewrittenDir = rebaseState.getRewrittenDir(); FileUtils.mkdir(rewrittenDir, false); walk.reset(); walk.setRevFilter(RevFilter.MERGE_BASE); walk.markStart(upstreamCommit); walk.markStart(headCommit); RevCommit base; while ((base = walk.next()) != null) { RebaseState.createFile(rewrittenDir, base.getName(), upstreamCommit.getName()); } Iterator iterator = cherryPickList.iterator(); pickLoop: while(iterator.hasNext()){ RevCommit commit = iterator.next(); int nOfParents = commit.getParentCount(); if (nOfParents == 0) { // Must be the very first commit in the cherryPickList. We // have independent branches. new File(rewrittenDir, commit.getName()).createNewFile(); } else { for (int i = 0; i < nOfParents; i++) { boolean parentRewritten = new File(rewrittenDir, commit.getParent(i).getName()).exists(); if (parentRewritten) { new File(rewrittenDir, commit.getName()) .createNewFile(); continue pickLoop; } } // commit is only merged in, needs not be rewritten iterator.remove(); } } } return cherryPickList; } private static String getHeadName(Ref head) { String headName; if (head.isSymbolic()) { headName = head.getTarget().getName(); } else { ObjectId headId = head.getObjectId(); // the callers are checking this already assert headId != null; headName = headId.getName(); } return headName; } private Ref getHead() throws IOException, RefNotFoundException { Ref head = repo.exactRef(Constants.HEAD); if (head == null || head.getObjectId() == null) throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, Constants.HEAD)); return head; } private boolean isInteractive() { return interactiveHandler != null; } /** * Check if we can fast-forward and returns the new head if it is possible * * @param newCommit * a {@link org.eclipse.jgit.revwalk.RevCommit} object to check * if we can fast-forward to. * @return the new head, or null * @throws java.io.IOException * if an IO error occurred * @throws org.eclipse.jgit.api.errors.GitAPIException * if a JGit API exception occurred */ public RevCommit tryFastForward(RevCommit newCommit) throws IOException, GitAPIException { Ref head = getHead(); ObjectId headId = head.getObjectId(); if (headId == null) throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, Constants.HEAD)); RevCommit headCommit = walk.lookupCommit(headId); if (walk.isMergedInto(newCommit, headCommit)) return newCommit; String headName = getHeadName(head); return tryFastForward(headName, headCommit, newCommit); } private RevCommit tryFastForward(String headName, RevCommit oldCommit, RevCommit newCommit) throws IOException, GitAPIException { boolean tryRebase = false; for (RevCommit parentCommit : newCommit.getParents()) if (parentCommit.equals(oldCommit)) tryRebase = true; if (!tryRebase) return null; CheckoutCommand co = new CheckoutCommand(repo); try { co.setProgressMonitor(monitor); co.setName(newCommit.name()).call(); if (headName.startsWith(Constants.R_HEADS)) { RefUpdate rup = repo.updateRef(headName); rup.setExpectedOldObjectId(oldCommit); rup.setNewObjectId(newCommit); rup.setRefLogMessage("Fast-forward from " + oldCommit.name() //$NON-NLS-1$ + " to " + newCommit.name(), false); //$NON-NLS-1$ Result res = rup.update(walk); switch (res) { case FAST_FORWARD: case NO_CHANGE: case FORCED: break; default: throw new IOException("Could not fast-forward"); //$NON-NLS-1$ } } return newCommit; } catch (RefAlreadyExistsException | RefNotFoundException | InvalidRefNameException | CheckoutConflictException e) { throw new JGitInternalException(e.getMessage(), e); } } private void checkParameters() throws WrongRepositoryStateException { if (this.operation == Operation.PROCESS_STEPS) { if (rebaseState.getFile(DONE).exists()) throw new WrongRepositoryStateException(MessageFormat.format( JGitText.get().wrongRepositoryState, repo .getRepositoryState().name())); } if (this.operation != Operation.BEGIN) { // these operations are only possible while in a rebasing state switch (repo.getRepositoryState()) { case REBASING_INTERACTIVE: case REBASING: case REBASING_REBASING: case REBASING_MERGE: break; default: throw new WrongRepositoryStateException(MessageFormat.format( JGitText.get().wrongRepositoryState, repo .getRepositoryState().name())); } } else switch (repo.getRepositoryState()) { case SAFE: if (this.upstreamCommit == null) throw new JGitInternalException(MessageFormat .format(JGitText.get().missingRequiredParameter, "upstream")); //$NON-NLS-1$ return; default: throw new WrongRepositoryStateException(MessageFormat.format( JGitText.get().wrongRepositoryState, repo .getRepositoryState().name())); } } private RebaseResult abort(RebaseResult result) throws IOException, GitAPIException { ObjectId origHead = getOriginalHead(); try { String commitId = origHead != null ? origHead.name() : null; monitor.beginTask(MessageFormat.format( JGitText.get().abortingRebase, commitId), ProgressMonitor.UNKNOWN); DirCacheCheckout dco; if (commitId == null) throw new JGitInternalException( JGitText.get().abortingRebaseFailedNoOrigHead); ObjectId id = repo.resolve(commitId); RevCommit commit = walk.parseCommit(id); if (result.getStatus().equals(Status.FAILED)) { RevCommit head = walk.parseCommit(repo.resolve(Constants.HEAD)); dco = new DirCacheCheckout(repo, head.getTree(), repo.lockDirCache(), commit.getTree()); } else { dco = new DirCacheCheckout(repo, repo.lockDirCache(), commit.getTree()); } dco.setFailOnConflict(false); dco.checkout(); walk.close(); } finally { monitor.endTask(); } try { String headName = rebaseState.readFile(HEAD_NAME); monitor.beginTask(MessageFormat.format( JGitText.get().resettingHead, headName), ProgressMonitor.UNKNOWN); Result res = null; RefUpdate refUpdate = repo.updateRef(Constants.HEAD, false); refUpdate.setRefLogMessage("rebase: aborting", false); //$NON-NLS-1$ if (headName.startsWith(Constants.R_REFS)) { // update the HEAD res = refUpdate.link(headName); } else { refUpdate.setNewObjectId(origHead); res = refUpdate.forceUpdate(); } switch (res) { case FAST_FORWARD: case FORCED: case NO_CHANGE: break; default: throw new JGitInternalException( JGitText.get().abortingRebaseFailed); } boolean unstashSuccessful = autoStashApply(); // cleanup the files FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE); repo.writeCherryPickHead(null); repo.writeMergeHeads(null); if (!unstashSuccessful) { return RebaseResult.STASH_APPLY_CONFLICTS_RESULT; } return result; } finally { monitor.endTask(); } } private ObjectId getOriginalHead() throws IOException { try { return ObjectId.fromString(rebaseState.readFile(REBASE_HEAD)); } catch (FileNotFoundException e) { try { return ObjectId .fromString(rebaseState.readFile(REBASE_HEAD_LEGACY)); } catch (FileNotFoundException ex) { return repo.readOrigHead(); } } } private boolean checkoutCommit(String headName, RevCommit commit) throws IOException, CheckoutConflictException { try { RevCommit head = walk.parseCommit(repo.resolve(Constants.HEAD)); DirCacheCheckout dco = new DirCacheCheckout(repo, head.getTree(), repo.lockDirCache(), commit.getTree()); dco.setFailOnConflict(true); dco.setProgressMonitor(monitor); try { dco.checkout(); } catch (org.eclipse.jgit.errors.CheckoutConflictException cce) { throw new CheckoutConflictException(dco.getConflicts(), cce); } // update the HEAD RefUpdate refUpdate = repo.updateRef(Constants.HEAD, true); refUpdate.setExpectedOldObjectId(head); refUpdate.setNewObjectId(commit); refUpdate.setRefLogMessage( "checkout: moving from " //$NON-NLS-1$ + Repository.shortenRefName(headName) + " to " + commit.getName(), false); //$NON-NLS-1$ Result res = refUpdate.forceUpdate(); switch (res) { case FAST_FORWARD: case NO_CHANGE: case FORCED: break; default: throw new IOException( JGitText.get().couldNotRewindToUpstreamCommit); } } finally { walk.close(); monitor.endTask(); } return true; } /** * Set upstream {@code RevCommit} * * @param upstream * the upstream commit * @return {@code this} */ public RebaseCommand setUpstream(RevCommit upstream) { this.upstreamCommit = upstream; this.upstreamCommitName = upstream.name(); return this; } /** * Set the upstream commit * * @param upstream * id of the upstream commit * @return {@code this} */ public RebaseCommand setUpstream(AnyObjectId upstream) { try { this.upstreamCommit = walk.parseCommit(upstream); this.upstreamCommitName = upstream.name(); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().couldNotReadObjectWhileParsingCommit, upstream.name()), e); } return this; } /** * Set the upstream branch * * @param upstream * the name of the upstream branch * @return {@code this} * @throws org.eclipse.jgit.api.errors.RefNotFoundException * if {@code upstream} Ref couldn't be resolved */ public RebaseCommand setUpstream(String upstream) throws RefNotFoundException { try { ObjectId upstreamId = repo.resolve(upstream); if (upstreamId == null) throw new RefNotFoundException(MessageFormat.format(JGitText .get().refNotResolved, upstream)); upstreamCommit = walk.parseCommit(repo.resolve(upstream)); upstreamCommitName = upstream; return this; } catch (IOException ioe) { throw new JGitInternalException(ioe.getMessage(), ioe); } } /** * Optionally override the name of the upstream. If this is used, it has to * come after any {@link #setUpstream} call. * * @param upstreamName * the name which will be used to refer to upstream in conflicts * @return {@code this} */ public RebaseCommand setUpstreamName(String upstreamName) { if (upstreamCommit == null) { throw new IllegalStateException( "setUpstreamName must be called after setUpstream."); //$NON-NLS-1$ } this.upstreamCommitName = upstreamName; return this; } /** * Set the operation to execute during rebase * * @param operation * the operation to perform * @return {@code this} */ public RebaseCommand setOperation(Operation operation) { this.operation = operation; return this; } /** * Set progress monitor * * @param monitor * a progress monitor * @return this instance */ public RebaseCommand setProgressMonitor(ProgressMonitor monitor) { if (monitor == null) { monitor = NullProgressMonitor.INSTANCE; } this.monitor = monitor; return this; } /** * Enable interactive rebase *

* Does not stop after initialization of interactive rebase. This is * equivalent to * {@link org.eclipse.jgit.api.RebaseCommand#runInteractively(InteractiveHandler, boolean) * runInteractively(handler, false)}; *

* * @param handler * the * {@link org.eclipse.jgit.api.RebaseCommand.InteractiveHandler} * to use * @return this */ public RebaseCommand runInteractively(InteractiveHandler handler) { return runInteractively(handler, false); } /** * Enable interactive rebase *

* If stopAfterRebaseInteractiveInitialization is {@code true} the rebase * stops after initialization of interactive rebase returning * {@link org.eclipse.jgit.api.RebaseResult#INTERACTIVE_PREPARED_RESULT} *

* * @param handler * the * {@link org.eclipse.jgit.api.RebaseCommand.InteractiveHandler} * to use * @param stopAfterRebaseInteractiveInitialization * if {@code true} the rebase stops after initialization * @return this instance * @since 3.2 */ public RebaseCommand runInteractively(InteractiveHandler handler, final boolean stopAfterRebaseInteractiveInitialization) { this.stopAfterInitialization = stopAfterRebaseInteractiveInitialization; this.interactiveHandler = handler; return this; } /** * Set the MergeStrategy. * * @param strategy * The merge strategy to use during this rebase operation. * @return {@code this} * @since 3.4 */ public RebaseCommand setStrategy(MergeStrategy strategy) { this.strategy = strategy; return this; } /** * Sets the content merge strategy to use if the * {@link #setStrategy(MergeStrategy) merge strategy} is "resolve" or * "recursive". * * @param strategy * the {@link ContentMergeStrategy} to be used * @return {@code this} * @since 5.12 */ public RebaseCommand setContentMergeStrategy(ContentMergeStrategy strategy) { this.contentStrategy = strategy; return this; } /** * Whether to preserve merges during rebase * * @param preserve * {@code true} to re-create merges during rebase. Defaults to * {@code false}, a flattening rebase. * @return {@code this} * @since 3.5 */ public RebaseCommand setPreserveMerges(boolean preserve) { this.preserveMerges = preserve; return this; } /** * Allows to configure the interactive rebase process steps and to modify * commit messages. */ public interface InteractiveHandler { /** * Callback API to modify the initial list of interactive rebase steps. * * @param steps * initial configuration of interactive rebase */ void prepareSteps(List steps); /** * Used for editing commit message on REWORD or SQUASH. * * @param message * existing commit message * @return new commit message */ String modifyCommitMessage(String message); } /** * Extends {@link InteractiveHandler} with an enhanced callback for editing * commit messages. * * @since 6.1 */ public interface InteractiveHandler2 extends InteractiveHandler { /** * Callback API for editing a commit message on REWORD or SQUASH. *

* The callback gets the comment character currently set, and the * clean-up mode. It can use this information when presenting the * message to the user, and it also has the possibility to clean the * message itself (in which case the returned {@link ModifyResult} * should have {@link CleanupMode#VERBATIM} set lest JGit cleans the * message again). It can also override the initial clean-up mode by * returning clean-up mode other than {@link CleanupMode#DEFAULT}. If it * does return {@code DEFAULT}, the passed-in {@code mode} will be * applied. *

* * @param message * existing commit message * @param mode * {@link CleanupMode} currently set * @param commentChar * comment character used * @return a {@link ModifyResult} */ @NonNull ModifyResult editCommitMessage(@NonNull String message, @NonNull CleanupMode mode, char commentChar); @Override default String modifyCommitMessage(String message) { // Should actually not be called; but do something reasonable anyway ModifyResult result = editCommitMessage( message == null ? "" : message, CleanupMode.STRIP, //$NON-NLS-1$ '#'); return result.getMessage(); } /** * Describes the result of editing a commit message: the new message, * and how it should be cleaned. */ interface ModifyResult { /** * Retrieves the new commit message. * * @return the message */ @NonNull String getMessage(); /** * Tells how the message returned by {@link #getMessage()} should be * cleaned. * * @return the {@link CleanupMode} */ @NonNull CleanupMode getCleanupMode(); /** * Tells whether a Gerrit Change-Id should be computed and added to * the commit message, as with * {@link CommitCommand#setInsertChangeId(boolean)}. * * @return {@code true} if a Change-Id should be handled, * {@code false} otherwise */ boolean shouldAddChangeId(); } } PersonIdent parseAuthor(byte[] raw) { if (raw.length == 0) return null; Map keyValueMap = new HashMap<>(); for (int p = 0; p < raw.length;) { int end = RawParseUtils.nextLF(raw, p); if (end == p) break; int equalsIndex = RawParseUtils.next(raw, p, '='); if (equalsIndex == end) break; String key = RawParseUtils.decode(raw, p, equalsIndex - 1); String value = RawParseUtils.decode(raw, equalsIndex + 1, end - 2); p = end; keyValueMap.put(key, value); } String name = keyValueMap.get(GIT_AUTHOR_NAME); String email = keyValueMap.get(GIT_AUTHOR_EMAIL); String time = keyValueMap.get(GIT_AUTHOR_DATE); // the time is saved as int timeStart = 0; if (time.startsWith("@")) { //$NON-NLS-1$ timeStart = 1; } else { timeStart = 0; } Instant when = Instant.ofEpochSecond( Long.parseLong(time.substring(timeStart, time.indexOf(' ')))); String tzOffsetString = time.substring(time.indexOf(' ') + 1); int multiplier = -1; if (tzOffsetString.charAt(0) == '+') { multiplier = 1; } int hours = Integer.parseInt(tzOffsetString.substring(1, 3)); int minutes = Integer.parseInt(tzOffsetString.substring(3, 5)); // this is in format (+/-)HHMM (hours and minutes) ZoneOffset tz = ZoneOffset.ofHoursMinutes(hours * multiplier, minutes * multiplier); if (name != null && email != null) { return new PersonIdent(name, email, when, tz); } return null; } private static class RebaseState { private final File repoDirectory; private File dir; public RebaseState(File repoDirectory) { this.repoDirectory = repoDirectory; } public File getDir() { if (dir == null) { File rebaseApply = new File(repoDirectory, REBASE_APPLY); if (rebaseApply.exists()) { dir = rebaseApply; } else { File rebaseMerge = new File(repoDirectory, REBASE_MERGE); dir = rebaseMerge; } } return dir; } /** * @return Directory with rewritten commit hashes, usually exists if * {@link RebaseCommand#preserveMerges} is true **/ public File getRewrittenDir() { return new File(getDir(), REWRITTEN); } public String readFile(String name) throws IOException { try { return readFile(getDir(), name); } catch (FileNotFoundException e) { if (ONTO_NAME.equals(name)) { // Older JGit mistakenly wrote a file "onto-name" instead of // "onto_name". Try that wrong name just in case somebody // upgraded while a rebase started by JGit was in progress. File oldFile = getFile(ONTO_NAME.replace('_', '-')); if (oldFile.exists()) { return readFile(oldFile); } } throw e; } } public void createFile(String name, String content) throws IOException { createFile(getDir(), name, content); } public File getFile(String name) { return new File(getDir(), name); } public String getPath(String name) { return (getDir().getName() + "/" + name); //$NON-NLS-1$ } private static String readFile(File file) throws IOException { byte[] content = IO.readFully(file); // strip off the last LF int end = RawParseUtils.prevLF(content, content.length); return RawParseUtils.decode(content, 0, end + 1); } private static String readFile(File directory, String fileName) throws IOException { return readFile(new File(directory, fileName)); } private static void createFile(File parentDir, String name, String content) throws IOException { File file = new File(parentDir, name); try (FileOutputStream fos = new FileOutputStream(file)) { fos.write(content.getBytes(UTF_8)); fos.write('\n'); } } private static void appendToFile(File file, String content) throws IOException { try (FileOutputStream fos = new FileOutputStream(file, true)) { fos.write(content.getBytes(UTF_8)); fos.write('\n'); } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6831 Content-Disposition: inline; filename="RebaseResult.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "2aa64df46fb6dcef585c7e59d08d1d2280efdaa9" /* * Copyright (C) 2010, 2013, Mathias Kinzler and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.util.List; import java.util.Map; import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; import org.eclipse.jgit.revwalk.RevCommit; /** * The result of a {@link org.eclipse.jgit.api.RebaseCommand} execution */ public class RebaseResult { /** * The overall status */ public enum Status { /** * Rebase was successful, HEAD points to the new commit */ OK { @Override public boolean isSuccessful() { return true; } }, /** * Aborted; the original HEAD was restored */ ABORTED { @Override public boolean isSuccessful() { return false; } }, /** * Stopped due to a conflict; must either abort or resolve or skip */ STOPPED { @Override public boolean isSuccessful() { return false; } }, /** * Stopped for editing in the context of an interactive rebase * * @since 3.2 */ EDIT { @Override public boolean isSuccessful() { return false; } }, /** * Failed; the original HEAD was restored */ FAILED { @Override public boolean isSuccessful() { return false; } }, /** * The repository contains uncommitted changes and the rebase is not a * fast-forward * * @since 3.2 */ UNCOMMITTED_CHANGES { @Override public boolean isSuccessful() { return false; } }, /** * Conflicts: checkout of target HEAD failed */ CONFLICTS { @Override public boolean isSuccessful() { return false; } }, /** * Already up-to-date */ UP_TO_DATE { @Override public boolean isSuccessful() { return true; } }, /** * Fast-forward, HEAD points to the new commit */ FAST_FORWARD { @Override public boolean isSuccessful() { return true; } }, /** * Continue with nothing left to commit (possibly want skip). * * @since 2.0 */ NOTHING_TO_COMMIT { @Override public boolean isSuccessful() { return false; } }, /** * Interactive rebase has been prepared * @since 3.2 */ INTERACTIVE_PREPARED { @Override public boolean isSuccessful() { return false; } }, /** * Applying stash resulted in conflicts * * @since 3.2 */ STASH_APPLY_CONFLICTS { @Override public boolean isSuccessful() { return true; } }; /** * Whether the rebase was successful * * @return whether the status indicates a successful result */ public abstract boolean isSuccessful(); } static final RebaseResult OK_RESULT = new RebaseResult(Status.OK); static final RebaseResult ABORTED_RESULT = new RebaseResult(Status.ABORTED); static final RebaseResult UP_TO_DATE_RESULT = new RebaseResult( Status.UP_TO_DATE); static final RebaseResult FAST_FORWARD_RESULT = new RebaseResult( Status.FAST_FORWARD); static final RebaseResult NOTHING_TO_COMMIT_RESULT = new RebaseResult( Status.NOTHING_TO_COMMIT); static final RebaseResult INTERACTIVE_PREPARED_RESULT = new RebaseResult( Status.INTERACTIVE_PREPARED); static final RebaseResult STASH_APPLY_CONFLICTS_RESULT = new RebaseResult( Status.STASH_APPLY_CONFLICTS); private final Status status; private final RevCommit currentCommit; private Map failingPaths; private List conflicts; private List uncommittedChanges; private RebaseResult(Status status) { this.status = status; currentCommit = null; } private RebaseResult(Status status, RevCommit commit) { this.status = status; currentCommit = commit; } /** * Create RebaseResult * * @param status * the overall rebase status * @param commit * current commit * @return the RebaseResult */ static RebaseResult result(RebaseResult.Status status, RevCommit commit) { return new RebaseResult(status, commit); } /** * Create RebaseResult with status {@link Status#FAILED} * * @param failingPaths * list of paths causing this rebase to fail * @return the RebaseResult */ static RebaseResult failed( Map failingPaths) { RebaseResult result = new RebaseResult(Status.FAILED); result.failingPaths = failingPaths; return result; } /** * Create RebaseResult with status {@link Status#CONFLICTS} * * @param conflicts * the list of conflicting paths * @return the RebaseResult */ static RebaseResult conflicts(List conflicts) { RebaseResult result = new RebaseResult(Status.CONFLICTS); result.conflicts = conflicts; return result; } /** * Create RebaseResult with status * {@link Status#UNCOMMITTED_CHANGES} * * @param uncommittedChanges * the list of paths * @return the RebaseResult */ static RebaseResult uncommittedChanges(List uncommittedChanges) { RebaseResult result = new RebaseResult(Status.UNCOMMITTED_CHANGES); result.uncommittedChanges = uncommittedChanges; return result; } /** * Get the status * * @return the overall status */ public Status getStatus() { return status; } /** * Get the current commit if status is * {@link org.eclipse.jgit.api.RebaseResult.Status#STOPPED}, otherwise * null * * @return the current commit if status is * {@link org.eclipse.jgit.api.RebaseResult.Status#STOPPED}, * otherwise null */ public RevCommit getCurrentCommit() { return currentCommit; } /** * Get the list of paths causing this rebase to fail * * @return the list of paths causing this rebase to fail (see * {@link org.eclipse.jgit.merge.ResolveMerger#getFailingPaths()} * for details) if status is * {@link org.eclipse.jgit.api.RebaseResult.Status#FAILED}, * otherwise null */ public Map getFailingPaths() { return failingPaths; } /** * Get the list of conflicts * * @return the list of conflicts if status is * {@link org.eclipse.jgit.api.RebaseResult.Status#CONFLICTS} */ public List getConflicts() { return conflicts; } /** *

Getter for the field uncommittedChanges.

* * @return the list of uncommitted changes if status is * {@link org.eclipse.jgit.api.RebaseResult.Status#UNCOMMITTED_CHANGES} * @since 3.2 */ public List getUncommittedChanges() { return uncommittedChanges; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2163 Content-Disposition: inline; filename="ReflogCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "a1496490044d11eb16642cee5a3aa75871882470" /* * Copyright (C) 2011, Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; import java.util.Collection; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ReflogEntry; import org.eclipse.jgit.lib.ReflogReader; import org.eclipse.jgit.lib.Repository; /** * The reflog command * * @see Git documentation about reflog */ public class ReflogCommand extends GitCommand> { private String ref = Constants.HEAD; /** * Constructor for ReflogCommand. * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ public ReflogCommand(Repository repo) { super(repo); } /** * The ref used for the reflog operation. If no ref is set, the default * value of HEAD will be used. * * @param ref * the name of the {@code Ref} to log * @return {@code this} */ public ReflogCommand setRef(String ref) { checkCallable(); this.ref = ref; return this; } /** * {@inheritDoc} *

* Run the reflog command */ @Override public Collection call() throws GitAPIException, InvalidRefNameException { checkCallable(); try { ReflogReader reader = repo.getRefDatabase().getReflogReader(ref); if (reader == null) throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, ref)); return reader.getReverseEntries(); } catch (IOException e) { throw new InvalidRefNameException(MessageFormat.format( JGitText.get().cannotRead, ref), e); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2705 Content-Disposition: inline; filename="RemoteAddCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "9268ff1e19243f5d815892672528b991528d2b7a" /* * Copyright (C) 2015, Kaloyan Raev and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.net.URISyntaxException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.URIish; /** * Used to add a new remote. * * This class has setters for all supported options and arguments of this * command and a {@link #call()} method to finally execute the command. * * @see Git * documentation about Remote * @since 4.2 */ public class RemoteAddCommand extends GitCommand { private String name; private URIish uri; /** * Constructor for RemoteAddCommand. * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected RemoteAddCommand(Repository repo) { super(repo); } /** * The name of the remote to add. * * @param name * a remote name * @return this instance * @since 5.0 */ public RemoteAddCommand setName(String name) { this.name = name; return this; } /** * The URL of the repository for the new remote. * * @param uri * an URL for the remote * @return this instance * @since 5.0 */ public RemoteAddCommand setUri(URIish uri) { this.uri = uri; return this; } /** * {@inheritDoc} *

* Executes the {@code remote add} command with all the options and * parameters collected by the setter methods of this class. */ @Override public RemoteConfig call() throws GitAPIException { checkCallable(); try { StoredConfig config = repo.getConfig(); RemoteConfig remote = new RemoteConfig(config, name); RefSpec refSpec = new RefSpec(); refSpec = refSpec.setForceUpdate(true); refSpec = refSpec.setSourceDestination(Constants.R_HEADS + "*", //$NON-NLS-1$ Constants.R_REMOTES + name + "/*"); //$NON-NLS-1$ remote.addFetchRefSpec(refSpec); remote.addURI(uri); remote.update(config); config.save(); return remote; } catch (IOException | URISyntaxException e) { throw new JGitInternalException(e.getMessage(), e); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1673 Content-Disposition: inline; filename="RemoteListCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "d790e50ea1496ae33858ede80942a7ddc8c52572" /* * Copyright (C) 2015, Kaloyan Raev and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.net.URISyntaxException; import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.RemoteConfig; /** * Used to obtain the list of remotes. * * This class has setters for all supported options and arguments of this * command and a {@link #call()} method to finally execute the command. * * @see Git * documentation about Remote * @since 4.2 */ public class RemoteListCommand extends GitCommand> { /** *

* Constructor for RemoteListCommand. *

* * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected RemoteListCommand(Repository repo) { super(repo); } /** * {@inheritDoc} *

* Executes the {@code remote} command with all the options and parameters * collected by the setter methods of this class. */ @Override public List call() throws GitAPIException { checkCallable(); try { return RemoteConfig.getAllRemoteConfigs(repo.getConfig()); } catch (URISyntaxException e) { throw new JGitInternalException(e.getMessage(), e); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2210 Content-Disposition: inline; filename="RemoteRemoveCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "ad553f07a9a9ef8c06134c419af72ac07f09c8e2" /* * Copyright (C) 2015, Kaloyan Raev and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.net.URISyntaxException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.transport.RemoteConfig; /** * Used to remove an existing remote. * * This class has setters for all supported options and arguments of this * command and a {@link #call()} method to finally execute the command. * * @see Git * documentation about Remote * @since 4.2 */ public class RemoteRemoveCommand extends GitCommand { private String remoteName; /** *

* Constructor for RemoteRemoveCommand. *

* * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected RemoteRemoveCommand(Repository repo) { super(repo); } /** * The name of the remote to remove. * * @param remoteName * a remote name * @return {@code this} * @since 5.3 */ public RemoteRemoveCommand setRemoteName(String remoteName) { this.remoteName = remoteName; return this; } /** * {@inheritDoc} *

* Executes the {@code remote} command with all the options and parameters * collected by the setter methods of this class. */ @Override public RemoteConfig call() throws GitAPIException { checkCallable(); try { StoredConfig config = repo.getConfig(); RemoteConfig remote = new RemoteConfig(config, remoteName); config.unsetSection(ConfigConstants.CONFIG_KEY_REMOTE, remoteName); config.save(); return remote; } catch (IOException | URISyntaxException e) { throw new JGitInternalException(e.getMessage(), e); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3624 Content-Disposition: inline; filename="RemoteSetUrlCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "68ddce361f492d234905965e466b7134e5e8b5e8" /* * Copyright (C) 2015, Kaloyan Raev and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.net.URISyntaxException; import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.URIish; /** * Used to change the URL of a remote. * * This class has setters for all supported options and arguments of this * command and a {@link #call()} method to finally execute the command. * * @see Git * documentation about Remote * @since 4.2 */ public class RemoteSetUrlCommand extends GitCommand { /** * The available URI types for the remote. * * @since 5.3 */ public enum UriType { /** * Fetch URL for the remote. */ FETCH, /** * Push URL for the remote. */ PUSH } private String remoteName; private URIish remoteUri; private UriType type; /** *

* Constructor for RemoteSetUrlCommand. *

* * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected RemoteSetUrlCommand(Repository repo) { super(repo); } /** * The name of the remote to change the URL for. * * @param remoteName * a remote remoteName * @return {@code this} * @since 5.3 */ public RemoteSetUrlCommand setRemoteName(String remoteName) { this.remoteName = remoteName; return this; } /** * The new URL for the remote. * * @param remoteUri * an URL for the remote * @return {@code this} * @since 5.3 */ public RemoteSetUrlCommand setRemoteUri(URIish remoteUri) { this.remoteUri = remoteUri; return this; } /** * Whether to change the push URL of the remote instead of the fetch URL. * * @param type * the UriType value to set * @return {@code this} * @since 5.3 */ public RemoteSetUrlCommand setUriType(UriType type) { this.type = type; return this; } /** * {@inheritDoc} *

* Executes the {@code remote} command with all the options and parameters * collected by the setter methods of this class. */ @Override public RemoteConfig call() throws GitAPIException { checkCallable(); try { StoredConfig config = repo.getConfig(); RemoteConfig remote = new RemoteConfig(config, remoteName); if (type == UriType.PUSH) { List uris = remote.getPushURIs(); if (uris.size() > 1) { throw new JGitInternalException( "remote.newtest.pushurl has multiple values"); //$NON-NLS-1$ } else if (uris.size() == 1) { remote.removePushURI(uris.get(0)); } remote.addPushURI(remoteUri); } else { List uris = remote.getURIs(); if (uris.size() > 1) { throw new JGitInternalException( "remote.newtest.url has multiple values"); //$NON-NLS-1$ } else if (uris.size() == 1) { remote.removeURI(uris.get(0)); } remote.addURI(remoteUri); } remote.update(config); config.save(); return remote; } catch (IOException | URISyntaxException e) { throw new JGitInternalException(e.getMessage(), e); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2927 Content-Disposition: inline; filename="RemoveNoteCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "c65cbf160f13b75855b93a6739ac908586f00acd" /* * Copyright (C) 2011, Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.notes.Note; import org.eclipse.jgit.notes.NoteMap; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; /** * Remove object notes. * * @see Git documentation about Notes */ public class RemoveNoteCommand extends GitCommand { private RevObject id; private String notesRef = Constants.R_NOTES_COMMITS; /** *

* Constructor for RemoveNoteCommand. *

* * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected RemoveNoteCommand(Repository repo) { super(repo); } @Override public Note call() throws GitAPIException { checkCallable(); try (RevWalk walk = new RevWalk(repo); ObjectInserter inserter = repo.newObjectInserter()) { NoteMap map = NoteMap.newEmptyMap(); RevCommit notesCommit = null; Ref ref = repo.exactRef(notesRef); // if we have a notes ref, use it if (ref != null) { notesCommit = walk.parseCommit(ref.getObjectId()); map = NoteMap.read(walk.getObjectReader(), notesCommit); } map.set(id, null, inserter); AddNoteCommand.commitNoteMap(repo, notesRef, walk, map, notesCommit, inserter, "Notes removed by 'git notes remove'"); //$NON-NLS-1$ return map.getNote(id); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } } /** * Sets the object id of object you want to remove a note * * @param id * the {@link org.eclipse.jgit.revwalk.RevObject} to remove a * note from. * @return {@code this} */ public RemoveNoteCommand setObjectId(RevObject id) { checkCallable(); this.id = id; return this; } /** * Set the name of the Ref to remove a note from. * * @param notesRef * the {@code Ref} to read notes from. Note, the default value of * {@link org.eclipse.jgit.lib.Constants#R_NOTES_COMMITS} will be * used if nothing is set * @return {@code this} * @see Constants#R_NOTES_COMMITS */ public RemoveNoteCommand setNotesRef(String notesRef) { checkCallable(); this.notesRef = notesRef; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7102 Content-Disposition: inline; filename="RenameBranchCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "029f9d02d633b2420d42ad44306e4767c6054c2e" /* * Copyright (C) 2010, Mathias Kinzler * Copyright (C) 2010, Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; import java.util.Arrays; import org.eclipse.jgit.api.errors.DetachedHeadException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefRename; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; /** * Used to rename branches. * * @see Git documentation about Branch */ public class RenameBranchCommand extends GitCommand { private String oldName; private String newName; /** *

* Constructor for RenameBranchCommand. *

* * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected RenameBranchCommand(Repository repo) { super(repo); } @Override public Ref call() throws GitAPIException, RefNotFoundException, InvalidRefNameException, RefAlreadyExistsException, DetachedHeadException { checkCallable(); if (newName == null) { throw new InvalidRefNameException(MessageFormat.format(JGitText .get().branchNameInvalid, "")); //$NON-NLS-1$ } try { String fullOldName; String fullNewName; if (oldName != null) { // Don't just rely on findRef -- if there are local and remote // branches with the same name, and oldName is a short name, it // does not uniquely identify the ref and we might end up // renaming the wrong branch or finding a tag instead even // if a unique branch for the name exists! // // OldName may be a either a short or a full name. Ref ref = repo.exactRef(oldName); if (ref == null) { ref = repo.exactRef(Constants.R_HEADS + oldName); Ref ref2 = repo.exactRef(Constants.R_REMOTES + oldName); if (ref != null && ref2 != null) { throw new RefNotFoundException(MessageFormat.format( JGitText.get().renameBranchFailedAmbiguous, oldName, ref.getName(), ref2.getName())); } else if (ref == null) { if (ref2 != null) { ref = ref2; } else { throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, oldName)); } } } fullOldName = ref.getName(); } else { fullOldName = repo.getFullBranch(); if (fullOldName == null) { throw new NoHeadException( JGitText.get().invalidRepositoryStateNoHead); } if (ObjectId.isId(fullOldName)) throw new DetachedHeadException(); } if (fullOldName.startsWith(Constants.R_REMOTES)) { fullNewName = Constants.R_REMOTES + newName; } else if (fullOldName.startsWith(Constants.R_HEADS)) { fullNewName = Constants.R_HEADS + newName; } else { throw new RefNotFoundException(MessageFormat.format( JGitText.get().renameBranchFailedNotABranch, fullOldName)); } if (!Repository.isValidRefName(fullNewName)) { throw new InvalidRefNameException(MessageFormat.format(JGitText .get().branchNameInvalid, fullNewName)); } if (repo.exactRef(fullNewName) != null) { throw new RefAlreadyExistsException(MessageFormat .format(JGitText.get().refAlreadyExists1, fullNewName)); } RefRename rename = repo.renameRef(fullOldName, fullNewName); Result renameResult = rename.rename(); setCallable(false); if (Result.RENAMED != renameResult) { throw new JGitInternalException(MessageFormat.format(JGitText .get().renameBranchUnexpectedResult, renameResult .name())); } if (fullNewName.startsWith(Constants.R_HEADS)) { String shortOldName = fullOldName.substring(Constants.R_HEADS .length()); final StoredConfig repoConfig = repo.getConfig(); // Copy all configuration values over to the new branch for (String name : repoConfig.getNames( ConfigConstants.CONFIG_BRANCH_SECTION, shortOldName)) { String[] values = repoConfig.getStringList( ConfigConstants.CONFIG_BRANCH_SECTION, shortOldName, name); if (values.length == 0) { continue; } // Keep any existing values already configured for the // new branch name String[] existing = repoConfig.getStringList( ConfigConstants.CONFIG_BRANCH_SECTION, newName, name); if (existing.length > 0) { String[] newValues = new String[values.length + existing.length]; System.arraycopy(existing, 0, newValues, 0, existing.length); System.arraycopy(values, 0, newValues, existing.length, values.length); values = newValues; } repoConfig.setStringList( ConfigConstants.CONFIG_BRANCH_SECTION, newName, name, Arrays.asList(values)); } repoConfig.unsetSection(ConfigConstants.CONFIG_BRANCH_SECTION, shortOldName); repoConfig.save(); } Ref resultRef = repo.exactRef(fullNewName); if (resultRef == null) { throw new JGitInternalException( JGitText.get().renameBranchFailedUnknownReason); } return resultRef; } catch (IOException ioe) { throw new JGitInternalException(ioe.getMessage(), ioe); } } /** * Sets the new short name of the branch. *

* The full name is constructed using the prefix of the branch to be renamed * defined by either {@link #setOldName(String)} or HEAD. If that old branch * is a local branch, the renamed branch also will be, and if the old branch * is a remote branch, so will be the renamed branch. *

* * @param newName * the new name * @return this instance */ public RenameBranchCommand setNewName(String newName) { checkCallable(); this.newName = newName; return this; } /** * Sets the old name of the branch. *

* {@code oldName} may be a short or a full name. Using a full name is * recommended to unambiguously identify the branch to be renamed. *

* * @param oldName * the name of the branch to rename; if not set, the currently * checked out branch (if any) will be renamed * @return this instance */ public RenameBranchCommand setOldName(String oldName) { checkCallable(); this.oldName = oldName; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 12587 Content-Disposition: inline; filename="ResetCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "47145a0563de988bb4b808974c6277b6b3dd8251" /* * Copyright (C) 2011-2013, Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import org.eclipse.jgit.api.errors.CheckoutConflictException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuildIterator; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.EmptyTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; /** * A class used to execute a {@code Reset} command. It has setters for all * supported options and arguments of this command and a {@link #call()} method * to finally execute the command. Each instance of this class should only be * used for one invocation of the command (means: one call to {@link #call()}) * * @see Git documentation about Reset */ public class ResetCommand extends GitCommand { /** * Kind of reset */ public enum ResetType { /** * Just change the ref, the index and workdir are not changed. */ SOFT, /** * Change the ref and the index, the workdir is not changed. */ MIXED, /** * Change the ref, the index and the workdir */ HARD, /** * Resets the index and updates the files in the working tree that are * different between respective commit and HEAD, but keeps those which * are different between the index and working tree */ MERGE, // TODO not implemented yet /** * Change the ref, the index and the workdir that are different between * respective commit and HEAD */ KEEP // TODO not implemented yet } // We need to be able to distinguish whether the caller set the ref // explicitly or not, so we apply the default (HEAD) only later. private String ref = null; private ResetType mode; private Collection filepaths = new ArrayList<>(); private boolean isReflogDisabled; private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; /** *

* Constructor for ResetCommand. *

* * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ public ResetCommand(Repository repo) { super(repo); } /** * {@inheritDoc} *

* Executes the {@code Reset} command. Each instance of this class should * only be used for one invocation of the command. Don't call this method * twice on an instance. */ @Override public Ref call() throws GitAPIException, CheckoutConflictException { checkCallable(); try { RepositoryState state = repo.getRepositoryState(); final boolean merging = state.equals(RepositoryState.MERGING) || state.equals(RepositoryState.MERGING_RESOLVED); final boolean cherryPicking = state .equals(RepositoryState.CHERRY_PICKING) || state.equals(RepositoryState.CHERRY_PICKING_RESOLVED); final boolean reverting = state.equals(RepositoryState.REVERTING) || state.equals(RepositoryState.REVERTING_RESOLVED); final ObjectId commitId = resolveRefToCommitId(); // When ref is explicitly specified, it has to resolve if (ref != null && commitId == null) { // @TODO throw an InvalidRefNameException. We can't do that // now because this would break the API throw new JGitInternalException(MessageFormat .format(JGitText.get().invalidRefName, ref)); } final ObjectId commitTree; if (commitId != null) commitTree = parseCommit(commitId).getTree(); else commitTree = null; if (!filepaths.isEmpty()) { // reset [commit] -- paths resetIndexForPaths(commitTree); setCallable(false); return repo.exactRef(Constants.HEAD); } final Ref result; if (commitId != null) { // write the ref final RefUpdate ru = repo.updateRef(Constants.HEAD); ru.setNewObjectId(commitId); String refName = Repository.shortenRefName(getRefOrHEAD()); if (isReflogDisabled) { ru.disableRefLog(); } else { String message = refName + ": updating " + Constants.HEAD; //$NON-NLS-1$ ru.setRefLogMessage(message, false); } if (ru.forceUpdate() == RefUpdate.Result.LOCK_FAILURE) throw new JGitInternalException(MessageFormat.format( JGitText.get().cannotLock, ru.getName())); ObjectId origHead = ru.getOldObjectId(); if (origHead != null) repo.writeOrigHead(origHead); } result = repo.exactRef(Constants.HEAD); if (mode == null) mode = ResetType.MIXED; switch (mode) { case HARD: checkoutIndex(commitTree); break; case MIXED: resetIndex(commitTree); break; case SOFT: // do nothing, only the ref was changed break; case KEEP: // TODO case MERGE: // TODO throw new UnsupportedOperationException(); } if (mode != ResetType.SOFT) { if (merging) resetMerge(); else if (cherryPicking) resetCherryPick(); else if (reverting) resetRevert(); else if (repo.readSquashCommitMsg() != null) repo.writeSquashCommitMsg(null /* delete */); } setCallable(false); return result; } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().exceptionCaughtDuringExecutionOfResetCommand, e.getMessage()), e); } } private RevCommit parseCommit(ObjectId commitId) { try (RevWalk rw = new RevWalk(repo)) { return rw.parseCommit(commitId); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().cannotReadCommit, commitId.toString()), e); } } private ObjectId resolveRefToCommitId() { try { return repo.resolve(getRefOrHEAD() + "^{commit}"); //$NON-NLS-1$ } catch (IOException e) { throw new JGitInternalException( MessageFormat.format(JGitText.get().cannotRead, getRefOrHEAD()), e); } } /** * Set the name of the Ref to reset to * * @param ref * the ref to reset to, defaults to HEAD if not specified * @return this instance */ public ResetCommand setRef(String ref) { this.ref = ref; return this; } /** * Set the reset mode * * @param mode * the mode of the reset command * @return this instance */ public ResetCommand setMode(ResetType mode) { if (!filepaths.isEmpty()) throw new JGitInternalException(MessageFormat.format( JGitText.get().illegalCombinationOfArguments, "[--mixed | --soft | --hard]", "...")); //$NON-NLS-1$ //$NON-NLS-2$ this.mode = mode; return this; } /** * Repository relative path of file or directory to reset * * @param path * repository-relative path of file/directory to reset (with * / as separator) * @return this instance */ public ResetCommand addPath(String path) { if (mode != null) throw new JGitInternalException(MessageFormat.format( JGitText.get().illegalCombinationOfArguments, "...", //$NON-NLS-1$ "[--mixed | --soft | --hard]")); //$NON-NLS-1$ filepaths.add(path); return this; } /** * Whether to disable reflog * * @param disable * if {@code true} disables writing a reflog entry for this reset * command * @return this instance * @since 4.5 */ public ResetCommand disableRefLog(boolean disable) { this.isReflogDisabled = disable; return this; } /** * Whether reflog is disabled * * @return {@code true} if writing reflog is disabled for this reset command * @since 4.5 */ public boolean isReflogDisabled() { return this.isReflogDisabled; } private String getRefOrHEAD() { if (ref != null) { return ref; } return Constants.HEAD; } /** * The progress monitor associated with the reset operation. By default, * this is set to NullProgressMonitor * * @see NullProgressMonitor * @param monitor * a {@link org.eclipse.jgit.lib.ProgressMonitor} * @return {@code this} * @since 4.11 */ public ResetCommand setProgressMonitor(ProgressMonitor monitor) { if (monitor == null) { monitor = NullProgressMonitor.INSTANCE; } this.monitor = monitor; return this; } private void resetIndexForPaths(ObjectId commitTree) { DirCache dc = null; try (TreeWalk tw = new TreeWalk(repo)) { dc = repo.lockDirCache(); DirCacheBuilder builder = dc.builder(); tw.addTree(new DirCacheBuildIterator(builder)); if (commitTree != null) tw.addTree(commitTree); else tw.addTree(new EmptyTreeIterator()); tw.setFilter(PathFilterGroup.createFromStrings(filepaths)); tw.setRecursive(true); while (tw.next()) { final CanonicalTreeParser tree = tw.getTree(1, CanonicalTreeParser.class); // only keep file in index if it's in the commit if (tree != null) { // revert index to commit DirCacheEntry entry = new DirCacheEntry(tw.getRawPath()); entry.setFileMode(tree.getEntryFileMode()); entry.setObjectId(tree.getEntryObjectId()); builder.add(entry); } } builder.commit(); } catch (IOException e) { throw new RuntimeException(e); } finally { if (dc != null) dc.unlock(); } } private void resetIndex(ObjectId commitTree) throws IOException { DirCache dc = repo.lockDirCache(); try (TreeWalk walk = new TreeWalk(repo)) { DirCacheBuilder builder = dc.builder(); if (commitTree != null) walk.addTree(commitTree); else walk.addTree(new EmptyTreeIterator()); walk.addTree(new DirCacheIterator(dc)); walk.setRecursive(true); while (walk.next()) { AbstractTreeIterator cIter = walk.getTree(0, AbstractTreeIterator.class); if (cIter == null) { // Not in commit, don't add to new index continue; } final DirCacheEntry entry = new DirCacheEntry(walk.getRawPath()); entry.setFileMode(cIter.getEntryFileMode()); entry.setObjectIdFromRaw(cIter.idBuffer(), cIter.idOffset()); DirCacheIterator dcIter = walk.getTree(1, DirCacheIterator.class); if (dcIter != null && dcIter.idEqual(cIter)) { DirCacheEntry indexEntry = dcIter.getDirCacheEntry(); entry.setLastModified(indexEntry.getLastModifiedInstant()); entry.setLength(indexEntry.getLength()); } builder.add(entry); } builder.commit(); } finally { dc.unlock(); } } private void checkoutIndex(ObjectId commitTree) throws IOException, GitAPIException { DirCache dc = repo.lockDirCache(); try { DirCacheCheckout checkout = new DirCacheCheckout(repo, dc, commitTree); checkout.setFailOnConflict(false); checkout.setProgressMonitor(monitor); try { checkout.checkout(); } catch (org.eclipse.jgit.errors.CheckoutConflictException cce) { throw new CheckoutConflictException(checkout.getConflicts(), cce); } } finally { dc.unlock(); } } private void resetMerge() throws IOException { repo.writeMergeHeads(null); repo.writeMergeCommitMsg(null); } private void resetCherryPick() throws IOException { repo.writeCherryPickHead(null); repo.writeMergeCommitMsg(null); } private void resetRevert() throws IOException { repo.writeRevertHead(null); repo.writeMergeCommitMsg(null); } @SuppressWarnings("nls") @Override public String toString() { return "ResetCommand [repo=" + repo + ", ref=" + ref + ", mode=" + mode + ", isReflogDisabled=" + isReflogDisabled + ", filepaths=" + filepaths + "]"; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 11055 Content-Disposition: inline; filename="RevertCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "6643c83662757176f129cfa2fe51e9683202cd0b" /* * Copyright (C) 2010, 2024 Christian Halstrick and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.eclipse.jgit.api.MergeResult.MergeStatus; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.MultipleParentsNotAllowedException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.UnmergedPathsException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.CommitConfig; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.merge.MergeMessageFormatter; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.merge.ResolveMerger; import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.FileTreeIterator; /** * A class used to execute a {@code revert} command. It has setters for all * supported options and arguments of this command and a {@link #call()} method * to finally execute the command. Each instance of this class should only be * used for one invocation of the command (means: one call to {@link #call()}) * * @see Git documentation about revert */ public class RevertCommand extends GitCommand { private List commits = new ArrayList<>(); private String ourCommitName = null; private boolean insertChangeId; private List revertedRefs = new ArrayList<>(); private MergeResult failingResult; private List unmergedPaths; private MergeStrategy strategy = MergeStrategy.RECURSIVE; private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; /** *

* Constructor for RevertCommand. *

* * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected RevertCommand(Repository repo) { super(repo); } /** * {@inheritDoc} *

* Executes the {@code revert} command with all the options and parameters * collected by the setter methods (e.g. {@link #include(Ref)} of this * class. Each instance of this class should only be used for one invocation * of the command. Don't call this method twice on an instance. */ @Override public RevCommit call() throws NoMessageException, UnmergedPathsException, ConcurrentRefUpdateException, WrongRepositoryStateException, GitAPIException { RevCommit newHead = null; checkCallable(); try (RevWalk revWalk = new RevWalk(repo)) { // get the head commit Ref headRef = repo.exactRef(Constants.HEAD); if (headRef == null) throw new NoHeadException( JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported); RevCommit headCommit = revWalk.parseCommit(headRef.getObjectId()); newHead = headCommit; // loop through all refs to be reverted for (Ref src : commits) { // get the commit to be reverted // handle annotated tags ObjectId srcObjectId = src.getPeeledObjectId(); if (srcObjectId == null) srcObjectId = src.getObjectId(); RevCommit srcCommit = revWalk.parseCommit(srcObjectId); // get the parent of the commit to revert if (srcCommit.getParentCount() != 1) throw new MultipleParentsNotAllowedException( MessageFormat.format( JGitText.get().canOnlyRevertCommitsWithOneParent, srcCommit.name(), Integer.valueOf(srcCommit.getParentCount()))); RevCommit srcParent = srcCommit.getParent(0); revWalk.parseHeaders(srcParent); String ourName = calculateOurName(headRef); String revertName = srcCommit.getId() .abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name() + ' ' + srcCommit.getShortMessage(); ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo); merger.setWorkingTreeIterator(new FileTreeIterator(repo)); merger.setBase(srcCommit.getTree()); merger.setCommitNames(new String[] { "BASE", ourName, revertName }); //$NON-NLS-1$ String shortMessage = "Revert \"" //$NON-NLS-1$ + srcCommit.getFirstMessageLine() + '"'; String newMessage = shortMessage + "\n\n" //$NON-NLS-1$ + "This reverts commit " + srcCommit.getId().getName() //$NON-NLS-1$ + ".\n"; //$NON-NLS-1$ if (merger.merge(headCommit, srcParent)) { if (!merger.getModifiedFiles().isEmpty()) { repo.fireEvent(new WorkingTreeModifiedEvent( merger.getModifiedFiles(), null)); } if (AnyObjectId.isEqual(headCommit.getTree().getId(), merger.getResultTreeId())) continue; DirCacheCheckout dco = new DirCacheCheckout(repo, headCommit.getTree(), repo.lockDirCache(), merger.getResultTreeId()); dco.setFailOnConflict(true); dco.setProgressMonitor(monitor); dco.checkout(); try (Git git = new Git(getRepository())) { newHead = git.commit().setMessage(newMessage) .setInsertChangeId(insertChangeId) .setReflogComment("revert: " + shortMessage) //$NON-NLS-1$ .call(); } revertedRefs.add(src); headCommit = newHead; } else { unmergedPaths = merger.getUnmergedPaths(); Map failingPaths = merger .getFailingPaths(); if (failingPaths != null) failingResult = new MergeResult(null, merger.getBaseCommitId(), new ObjectId[] { headCommit.getId(), srcParent.getId() }, MergeStatus.FAILED, strategy, merger.getMergeResults(), failingPaths, null); else failingResult = new MergeResult(null, merger.getBaseCommitId(), new ObjectId[] { headCommit.getId(), srcParent.getId() }, MergeStatus.CONFLICTING, strategy, merger.getMergeResults(), failingPaths, null); if (!merger.failed() && !unmergedPaths.isEmpty()) { CommitConfig config = repo.getConfig() .get(CommitConfig.KEY); char commentChar = config.getCommentChar(newMessage); String message = new MergeMessageFormatter() .formatWithConflicts(newMessage, merger.getUnmergedPaths(), commentChar); repo.writeRevertHead(srcCommit.getId()); repo.writeMergeCommitMsg(message); } return null; } } } catch (IOException e) { throw new JGitInternalException( MessageFormat.format( JGitText.get().exceptionCaughtDuringExecutionOfRevertCommand, e), e); } return newHead; } /** * Include a {@code Ref} to a commit to be reverted * * @param commit * a reference to a commit to be reverted into the current head * @return {@code this} */ public RevertCommand include(Ref commit) { checkCallable(); commits.add(commit); return this; } /** * Include a commit to be reverted * * @param commit * the Id of a commit to be reverted into the current head * @return {@code this} */ public RevertCommand include(AnyObjectId commit) { return include(commit.getName(), commit); } /** * Include a commit to be reverted * * @param name * name of a {@code Ref} referring to the commit * @param commit * the Id of a commit which is reverted into the current head * @return {@code this} */ public RevertCommand include(String name, AnyObjectId commit) { return include(new ObjectIdRef.Unpeeled(Storage.LOOSE, name, commit.copy())); } /** * Set the name to be used in the "OURS" place for conflict markers * * @param ourCommitName * the name that should be used in the "OURS" place for conflict * markers * @return {@code this} */ public RevertCommand setOurCommitName(String ourCommitName) { this.ourCommitName = ourCommitName; return this; } private String calculateOurName(Ref headRef) { if (ourCommitName != null) return ourCommitName; String targetRefName = headRef.getTarget().getName(); String headName = Repository.shortenRefName(targetRefName); return headName; } /** * Get the list of successfully reverted {@link org.eclipse.jgit.lib.Ref}'s. * * @return the list of successfully reverted * {@link org.eclipse.jgit.lib.Ref}'s. Never null but * maybe an empty list if no commit was successfully cherry-picked */ public List getRevertedRefs() { return revertedRefs; } /** * Get the result of a merge failure * * @return the result of a merge failure, null if no merge * failure occurred during the revert */ public MergeResult getFailingResult() { return failingResult; } /** * Get unmerged paths * * @return the unmerged paths, will be null if no merge conflicts */ public List getUnmergedPaths() { return unmergedPaths; } /** * Set the merge strategy to use for this revert command * * @param strategy * The merge strategy to use for this revert command. * @return {@code this} * @since 3.4 */ public RevertCommand setStrategy(MergeStrategy strategy) { this.strategy = strategy; return this; } /** * The progress monitor associated with the revert operation. By default, * this is set to NullProgressMonitor * * @see NullProgressMonitor * @param monitor * a {@link org.eclipse.jgit.lib.ProgressMonitor} * @return {@code this} * @since 4.11 */ public RevertCommand setProgressMonitor(ProgressMonitor monitor) { if (monitor == null) { monitor = NullProgressMonitor.INSTANCE; } this.monitor = monitor; return this; } /** * Defines whether to add a Gerrit change ID to each revert commit message. * * @param insertChangeId * whether to insert a change ID * @return {@code this} * @since 6.8 */ public RevertCommand setInsertChangeId(boolean insertChangeId) { this.insertChangeId = insertChangeId; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5140 Content-Disposition: inline; filename="RmCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "7459e7298f1a957a524baa70b6a5622d249f2322" /* * Copyright (C) 2010, 2012 Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuildIterator; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; /** * Remove files from the index and working directory (or optionally only from * the index). *

* It has setters for all supported options and arguments of this command and a * {@link #call()} method to finally execute the command. Each instance of this * class should only be used for one invocation of the command (means: one call * to {@link #call()}). *

* Examples (git is a {@link org.eclipse.jgit.api.Git} instance): *

* Remove file "test.txt" from both index and working directory: * *

 * git.rm().addFilepattern("test.txt").call();
 * 
*

* Remove file "new.txt" from the index (but not from the working directory): * *

 * git.rm().setCached(true).addFilepattern("new.txt").call();
 * 
* * @see Git documentation about Rm */ public class RmCommand extends GitCommand { private Collection filepatterns; /** Only remove files from index, not from working directory */ private boolean cached = false; /** * Constructor for RmCommand. * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ public RmCommand(Repository repo) { super(repo); filepatterns = new ArrayList<>(); } /** * Add file name pattern of files to be removed * * @param filepattern * repository-relative path of file to remove (with * / as separator) * @return {@code this} */ public RmCommand addFilepattern(String filepattern) { checkCallable(); filepatterns.add(filepattern); return this; } /** * Only remove the specified files from the index. * * @param cached * {@code true} if files should only be removed from index, * {@code false} if files should also be deleted from the working * directory * @return {@code this} * @since 2.2 */ public RmCommand setCached(boolean cached) { checkCallable(); this.cached = cached; return this; } /** * {@inheritDoc} *

* Executes the {@code Rm} command. Each instance of this class should only * be used for one invocation of the command. Don't call this method twice * on an instance. */ @Override public DirCache call() throws GitAPIException, NoFilepatternException { if (filepatterns.isEmpty()) throw new NoFilepatternException(JGitText.get().atLeastOnePatternIsRequired); checkCallable(); DirCache dc = null; List actuallyDeletedFiles = new ArrayList<>(); try (TreeWalk tw = new TreeWalk(repo)) { dc = repo.lockDirCache(); DirCacheBuilder builder = dc.builder(); tw.reset(); // drop the first empty tree, which we do not need here tw.setRecursive(true); tw.setFilter(PathFilterGroup.createFromStrings(filepatterns)); tw.addTree(new DirCacheBuildIterator(builder)); while (tw.next()) { if (!cached) { final FileMode mode = tw.getFileMode(0); if (mode.getObjectType() == Constants.OBJ_BLOB) { String relativePath = tw.getPathString(); final File path = new File(repo.getWorkTree(), relativePath); // Deleting a blob is simply a matter of removing // the file or symlink named by the tree entry. if (delete(path)) { actuallyDeletedFiles.add(relativePath); } } } } builder.commit(); setCallable(false); } catch (IOException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfRmCommand, e); } finally { try { if (dc != null) { dc.unlock(); } } finally { if (!actuallyDeletedFiles.isEmpty()) { repo.fireEvent(new WorkingTreeModifiedEvent(null, actuallyDeletedFiles)); } } } return dc; } private boolean delete(File p) { boolean deleted = false; while (p != null && !p.equals(repo.getWorkTree()) && p.delete()) { deleted = true; p = p.getParentFile(); } return deleted; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2581 Content-Disposition: inline; filename="ShowNoteCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "7bb9de0ef4867f4146c02713671ae880ef7a22de" /* * Copyright (C) 2011, Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.notes.Note; import org.eclipse.jgit.notes.NoteMap; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; /** * Show an object note. * * @see Git documentation about Notes */ public class ShowNoteCommand extends GitCommand { private RevObject id; private String notesRef = Constants.R_NOTES_COMMITS; /** * Constructor for ShowNoteCommand. * * @param repo * the {@link org.eclipse.jgit.lib.Repository} */ protected ShowNoteCommand(Repository repo) { super(repo); } @Override public Note call() throws GitAPIException { checkCallable(); NoteMap map = NoteMap.newEmptyMap(); RevCommit notesCommit = null; try (RevWalk walk = new RevWalk(repo)) { Ref ref = repo.exactRef(notesRef); // if we have a notes ref, use it if (ref != null) { notesCommit = walk.parseCommit(ref.getObjectId()); map = NoteMap.read(walk.getObjectReader(), notesCommit); } return map.getNote(id); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } } /** * Sets the object id of object you want a note on * * @param id * the {@link org.eclipse.jgit.revwalk.RevObject} to show notes * for. * @return {@code this} */ public ShowNoteCommand setObjectId(RevObject id) { checkCallable(); this.id = id; return this; } /** * Set the {@code Ref} to read notes from. * * @param notesRef * the ref to read notes from. Note, the default value of * {@link org.eclipse.jgit.lib.Constants#R_NOTES_COMMITS} will be * used if nothing is set * @return {@code this} * @see Constants#R_NOTES_COMMITS */ public ShowNoteCommand setNotesRef(String notesRef) { checkCallable(); this.notesRef = notesRef; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 13644 Content-Disposition: inline; filename="StashApplyCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "b0b715e06b4bc0cece092c7b590fa74cbaa6e97f" /* * Copyright (C) 2012, 2023 GitHub Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP; import java.io.IOException; import java.text.MessageFormat; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.StashApplyFailureException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.dircache.Checkout; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.CheckoutConflictException; import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig.EolStreamType; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.merge.ContentMergeStrategy; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.merge.Merger; import org.eclipse.jgit.merge.ResolveMerger; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; /** * Command class to apply a stashed commit. * * This class behaves like git stash apply --index, i.e. it tries to * recover the stashed index state in addition to the working tree state. * * @see Git documentation about Stash * @since 2.0 */ public class StashApplyCommand extends GitCommand { private static final String DEFAULT_REF = Constants.STASH + "@{0}"; //$NON-NLS-1$ private String stashRef; private boolean restoreIndex = true; private boolean restoreUntracked = true; private boolean ignoreRepositoryState; private MergeStrategy strategy = MergeStrategy.RECURSIVE; private ContentMergeStrategy contentStrategy; /** * Create command to apply the changes of a stashed commit * * @param repo * the {@link org.eclipse.jgit.lib.Repository} to apply the stash * to */ public StashApplyCommand(Repository repo) { super(repo); } /** * Set the stash reference to apply *

* This will default to apply the latest stashed commit (stash@{0}) if * unspecified * * @param stashRef * name of the stash {@code Ref} to apply * @return {@code this} */ public StashApplyCommand setStashRef(String stashRef) { this.stashRef = stashRef; return this; } /** * Whether to ignore the repository state when applying the stash * * @param willIgnoreRepositoryState * whether to ignore the repository state when applying the stash * @return {@code this} * @since 3.2 */ public StashApplyCommand ignoreRepositoryState(boolean willIgnoreRepositoryState) { this.ignoreRepositoryState = willIgnoreRepositoryState; return this; } private ObjectId getStashId() throws GitAPIException { final String revision = stashRef != null ? stashRef : DEFAULT_REF; final ObjectId stashId; try { stashId = repo.resolve(revision); } catch (IOException e) { throw new InvalidRefNameException(MessageFormat.format( JGitText.get().stashResolveFailed, revision), e); } if (stashId == null) throw new InvalidRefNameException(MessageFormat.format( JGitText.get().stashResolveFailed, revision)); return stashId; } /** * {@inheritDoc} *

* Apply the changes in a stashed commit to the working directory and index */ @Override public ObjectId call() throws GitAPIException, WrongRepositoryStateException, NoHeadException, StashApplyFailureException { checkCallable(); if (!ignoreRepositoryState && repo.getRepositoryState() != RepositoryState.SAFE) throw new WrongRepositoryStateException(MessageFormat.format( JGitText.get().stashApplyOnUnsafeRepository, repo.getRepositoryState())); try (ObjectReader reader = repo.newObjectReader(); RevWalk revWalk = new RevWalk(reader)) { ObjectId headCommit = repo.resolve(Constants.HEAD); if (headCommit == null) throw new NoHeadException(JGitText.get().stashApplyWithoutHead); final ObjectId stashId = getStashId(); RevCommit stashCommit = revWalk.parseCommit(stashId); if (stashCommit.getParentCount() < 2 || stashCommit.getParentCount() > 3) throw new JGitInternalException(MessageFormat.format( JGitText.get().stashCommitIncorrectNumberOfParents, stashId.name(), Integer.valueOf(stashCommit.getParentCount()))); ObjectId headTree = repo.resolve(Constants.HEAD + "^{tree}"); //$NON-NLS-1$ ObjectId stashIndexCommit = revWalk.parseCommit(stashCommit .getParent(1)); ObjectId stashHeadCommit = stashCommit.getParent(0); ObjectId untrackedCommit = null; if (restoreUntracked && stashCommit.getParentCount() == 3) untrackedCommit = revWalk.parseCommit(stashCommit.getParent(2)); Merger merger = strategy.newMerger(repo); boolean mergeSucceeded; if (merger instanceof ResolveMerger) { ResolveMerger resolveMerger = (ResolveMerger) merger; resolveMerger .setCommitNames(new String[] { "stashed HEAD", "HEAD", //$NON-NLS-1$ //$NON-NLS-2$ "stash" }); //$NON-NLS-1$ resolveMerger.setBase(stashHeadCommit); resolveMerger .setWorkingTreeIterator(new FileTreeIterator(repo)); resolveMerger.setContentMergeStrategy(contentStrategy); mergeSucceeded = resolveMerger.merge(headCommit, stashCommit); List modifiedByMerge = resolveMerger.getModifiedFiles(); if (!modifiedByMerge.isEmpty()) { repo.fireEvent(new WorkingTreeModifiedEvent(modifiedByMerge, null)); } } else { mergeSucceeded = merger.merge(headCommit, stashCommit); } if (mergeSucceeded) { DirCache dc = repo.lockDirCache(); DirCacheCheckout dco = new DirCacheCheckout(repo, headTree, dc, merger.getResultTreeId()); dco.setFailOnConflict(true); dco.checkout(); // Ignoring failed deletes.... if (restoreIndex) { Merger ixMerger = strategy.newMerger(repo, true); if (ixMerger instanceof ResolveMerger) { ResolveMerger resolveMerger = (ResolveMerger) ixMerger; resolveMerger.setCommitNames(new String[] { "stashed HEAD", //$NON-NLS-1$ "HEAD", "stashed index" }); //$NON-NLS-1$//$NON-NLS-2$ resolveMerger.setBase(stashHeadCommit); resolveMerger.setContentMergeStrategy(contentStrategy); } boolean ok = ixMerger.merge(headCommit, stashIndexCommit); if (ok) { resetIndex(revWalk .parseTree(ixMerger.getResultTreeId())); } else { throw new StashApplyFailureException( JGitText.get().stashApplyConflict); } } if (untrackedCommit != null) { Merger untrackedMerger = strategy.newMerger(repo, true); if (untrackedMerger instanceof ResolveMerger) { ResolveMerger resolveMerger = (ResolveMerger) untrackedMerger; resolveMerger.setCommitNames(new String[] { "null", "HEAD", //$NON-NLS-1$//$NON-NLS-2$ "untracked files" }); //$NON-NLS-1$ // There is no common base for HEAD & untracked files // because the commit for untracked files has no parent. // If we use stashHeadCommit as common base (as in the // other merges) we potentially report conflicts for // files which are not even member of untracked files // commit. resolveMerger.setBase(null); resolveMerger.setContentMergeStrategy(contentStrategy); } boolean ok = untrackedMerger.merge(headCommit, untrackedCommit); if (ok) { try { RevTree untrackedTree = revWalk .parseTree(untrackedCommit); resetUntracked(untrackedTree); } catch (CheckoutConflictException e) { throw new StashApplyFailureException( JGitText.get().stashApplyConflict, e); } } else { throw new StashApplyFailureException( JGitText.get().stashApplyConflict); } } } else { throw new StashApplyFailureException( JGitText.get().stashApplyConflict); } return stashId; } catch (JGitInternalException e) { throw e; } catch (IOException e) { throw new JGitInternalException(JGitText.get().stashApplyFailed, e); } } /** * Whether to restore the index state * * @param restoreIndex * true (default) if the command should restore the index state * @return {@code this} * @since 5.3 */ public StashApplyCommand setRestoreIndex(boolean restoreIndex) { this.restoreIndex = restoreIndex; return this; } /** * Set the MergeStrategy to use. * * @param strategy * The merge strategy to use in order to merge during this * command execution. * @return {@code this} * @since 3.4 */ public StashApplyCommand setStrategy(MergeStrategy strategy) { this.strategy = strategy; return this; } /** * Sets the content merge strategy to use if the * {@link #setStrategy(MergeStrategy) merge strategy} is "resolve" or * "recursive". * * @param strategy * the {@link ContentMergeStrategy} to be used * @return {@code this} * @since 5.12 */ public StashApplyCommand setContentMergeStrategy( ContentMergeStrategy strategy) { checkCallable(); this.contentStrategy = strategy; return this; } /** * Whether the command should restore untracked files * * @param restoreUntracked * true (default) if the command should restore untracked files * @return {@code this} * @since 5.3 */ public StashApplyCommand setRestoreUntracked(boolean restoreUntracked) { this.restoreUntracked = restoreUntracked; return this; } private void resetIndex(RevTree tree) throws IOException { DirCache dc = repo.lockDirCache(); try (TreeWalk walk = new TreeWalk(repo)) { DirCacheBuilder builder = dc.builder(); walk.addTree(tree); walk.addTree(new DirCacheIterator(dc)); walk.setRecursive(true); while (walk.next()) { AbstractTreeIterator cIter = walk.getTree(0, AbstractTreeIterator.class); if (cIter == null) { // Not in commit, don't add to new index continue; } final DirCacheEntry entry = new DirCacheEntry(walk.getRawPath()); entry.setFileMode(cIter.getEntryFileMode()); entry.setObjectIdFromRaw(cIter.idBuffer(), cIter.idOffset()); DirCacheIterator dcIter = walk.getTree(1, DirCacheIterator.class); if (dcIter != null && dcIter.idEqual(cIter)) { DirCacheEntry indexEntry = dcIter.getDirCacheEntry(); entry.setLastModified(indexEntry.getLastModifiedInstant()); entry.setLength(indexEntry.getLength()); } builder.add(entry); } builder.commit(); } finally { dc.unlock(); } } private void resetUntracked(RevTree tree) throws CheckoutConflictException, IOException { Set actuallyModifiedPaths = new HashSet<>(); Checkout checkout = new Checkout(repo).setRecursiveDeletion(true); // TODO maybe NameConflictTreeWalk ? try (TreeWalk walk = new TreeWalk(repo)) { walk.addTree(tree); walk.addTree(new FileTreeIterator(repo)); walk.setRecursive(true); final ObjectReader reader = walk.getObjectReader(); while (walk.next()) { final AbstractTreeIterator cIter = walk.getTree(0, AbstractTreeIterator.class); if (cIter == null) // Not in commit, don't create untracked continue; final EolStreamType eolStreamType = walk .getEolStreamType(CHECKOUT_OP); final DirCacheEntry entry = new DirCacheEntry(walk.getRawPath()); entry.setFileMode(cIter.getEntryFileMode()); entry.setObjectIdFromRaw(cIter.idBuffer(), cIter.idOffset()); FileTreeIterator fIter = walk .getTree(1, FileTreeIterator.class); String gitPath = entry.getPathString(); if (fIter != null) { if (fIter.isModified(entry, true, reader)) { // file exists and is dirty throw new CheckoutConflictException(gitPath); } } checkoutPath(entry, gitPath, reader, checkout, new CheckoutMetadata(eolStreamType, null)); actuallyModifiedPaths.add(gitPath); } } finally { if (!actuallyModifiedPaths.isEmpty()) { repo.fireEvent(new WorkingTreeModifiedEvent( actuallyModifiedPaths, null)); } } } private void checkoutPath(DirCacheEntry entry, String gitPath, ObjectReader reader, Checkout checkout, CheckoutMetadata checkoutMetadata) { try { checkout.checkout(entry, checkoutMetadata, reader, gitPath); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().checkoutConflictWithFile, entry.getPathString()), e); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 12344 Content-Disposition: inline; filename="StashCreateCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "f7a1f4eff82ec7013a23a6a9bca73a51d6135bd2" /* * Copyright (C) 2012, GitHub Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import org.eclipse.jgit.api.ResetCommand.ResetType; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.UnmergedPathsException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEditor; import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath; import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; import org.eclipse.jgit.treewalk.filter.IndexDiffFilter; import org.eclipse.jgit.treewalk.filter.SkipWorkTreeFilter; import org.eclipse.jgit.util.FileUtils; /** * Command class to stash changes in the working directory and index in a * commit. * * @see Git documentation about Stash * @since 2.0 */ public class StashCreateCommand extends GitCommand { private static final String MSG_INDEX = "index on {0}: {1} {2}"; //$NON-NLS-1$ private static final String MSG_UNTRACKED = "untracked files on {0}: {1} {2}"; //$NON-NLS-1$ private static final String MSG_WORKING_DIR = "WIP on {0}: {1} {2}"; //$NON-NLS-1$ private String indexMessage = MSG_INDEX; private String workingDirectoryMessage = MSG_WORKING_DIR; private String ref = Constants.R_STASH; private PersonIdent person; private boolean includeUntracked; /** * Create a command to stash changes in the working directory and index * * @param repo * a {@link org.eclipse.jgit.lib.Repository} object. */ public StashCreateCommand(Repository repo) { super(repo); person = new PersonIdent(repo); } /** * Set the message used when committing index changes *

* The message will be formatted with the current branch, abbreviated commit * id, and short commit message when used. * * @param message * the stash message * @return {@code this} */ public StashCreateCommand setIndexMessage(String message) { indexMessage = message; return this; } /** * Set the message used when committing working directory changes *

* The message will be formatted with the current branch, abbreviated commit * id, and short commit message when used. * * @param message * the working directory message * @return {@code this} */ public StashCreateCommand setWorkingDirectoryMessage(String message) { workingDirectoryMessage = message; return this; } /** * Set the person to use as the author and committer in the commits made * * @param person * the {@link org.eclipse.jgit.lib.PersonIdent} of the person who * creates the stash. * @return {@code this} */ public StashCreateCommand setPerson(PersonIdent person) { this.person = person; return this; } /** * Set the reference to update with the stashed commit id If null, no * reference is updated *

* This value defaults to {@link org.eclipse.jgit.lib.Constants#R_STASH} * * @param ref * the name of the {@code Ref} to update * @return {@code this} */ public StashCreateCommand setRef(String ref) { this.ref = ref; return this; } /** * Whether to include untracked files in the stash. * * @param includeUntracked * whether to include untracked files in the stash * @return {@code this} * @since 3.4 */ public StashCreateCommand setIncludeUntracked(boolean includeUntracked) { this.includeUntracked = includeUntracked; return this; } private RevCommit parseCommit(final ObjectReader reader, final ObjectId headId) throws IOException { try (RevWalk walk = new RevWalk(reader)) { return walk.parseCommit(headId); } } private CommitBuilder createBuilder() { CommitBuilder builder = new CommitBuilder(); PersonIdent author = person; if (author == null) author = new PersonIdent(repo); builder.setAuthor(author); builder.setCommitter(author); return builder; } private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent, String refLogMessage) throws IOException { if (ref == null) return; Ref currentRef = repo.findRef(ref); RefUpdate refUpdate = repo.updateRef(ref); refUpdate.setNewObjectId(commitId); refUpdate.setRefLogIdent(refLogIdent); refUpdate.setRefLogMessage(refLogMessage, false); refUpdate.setForceRefLog(true); if (currentRef != null) refUpdate.setExpectedOldObjectId(currentRef.getObjectId()); else refUpdate.setExpectedOldObjectId(ObjectId.zeroId()); refUpdate.forceUpdate(); } private Ref getHead() throws GitAPIException { try { Ref head = repo.exactRef(Constants.HEAD); if (head == null || head.getObjectId() == null) throw new NoHeadException(JGitText.get().headRequiredToStash); return head; } catch (IOException e) { throw new JGitInternalException(JGitText.get().stashFailed, e); } } /** * {@inheritDoc} *

* Stash the contents on the working directory and index in separate commits * and reset to the current HEAD commit. */ @Override public RevCommit call() throws GitAPIException { checkCallable(); List deletedFiles = new ArrayList<>(); Ref head = getHead(); try (ObjectReader reader = repo.newObjectReader()) { RevCommit headCommit = parseCommit(reader, head.getObjectId()); DirCache cache = repo.lockDirCache(); ObjectId commitId; try (ObjectInserter inserter = repo.newObjectInserter(); TreeWalk treeWalk = new TreeWalk(repo, reader)) { treeWalk.setRecursive(true); treeWalk.addTree(headCommit.getTree()); treeWalk.addTree(new DirCacheIterator(cache)); treeWalk.addTree(new FileTreeIterator(repo)); treeWalk.getTree(2, FileTreeIterator.class) .setDirCacheIterator(treeWalk, 1); treeWalk.setFilter(AndTreeFilter.create(new SkipWorkTreeFilter( 1), new IndexDiffFilter(1, 2))); // Return null if no local changes to stash if (!treeWalk.next()) return null; MutableObjectId id = new MutableObjectId(); List wtEdits = new ArrayList<>(); List wtDeletes = new ArrayList<>(); List untracked = new ArrayList<>(); boolean hasChanges = false; do { AbstractTreeIterator headIter = treeWalk.getTree(0, AbstractTreeIterator.class); DirCacheIterator indexIter = treeWalk.getTree(1, DirCacheIterator.class); WorkingTreeIterator wtIter = treeWalk.getTree(2, WorkingTreeIterator.class); if (indexIter != null && !indexIter.getDirCacheEntry().isMerged()) throw new UnmergedPathsException( new UnmergedPathException( indexIter.getDirCacheEntry())); if (wtIter != null) { if (indexIter == null && headIter == null && !includeUntracked) continue; hasChanges = true; if (indexIter != null && wtIter.idEqual(indexIter)) continue; if (headIter != null && wtIter.idEqual(headIter)) continue; treeWalk.getObjectId(id, 0); final DirCacheEntry entry = new DirCacheEntry( treeWalk.getRawPath()); entry.setLength(wtIter.getEntryLength()); entry.setLastModified( wtIter.getEntryLastModifiedInstant()); entry.setFileMode(wtIter.getEntryFileMode()); long contentLength = wtIter.getEntryContentLength(); try (InputStream in = wtIter.openEntryStream()) { entry.setObjectId(inserter.insert( Constants.OBJ_BLOB, contentLength, in)); } if (indexIter == null && headIter == null) untracked.add(entry); else wtEdits.add(new PathEdit(entry) { @Override public void apply(DirCacheEntry ent) { ent.copyMetaData(entry); } }); } hasChanges = true; if (wtIter == null && headIter != null) wtDeletes.add(treeWalk.getPathString()); } while (treeWalk.next()); if (!hasChanges) return null; String branch = Repository.shortenRefName(head.getTarget() .getName()); // Commit index changes CommitBuilder builder = createBuilder(); builder.setParentId(headCommit); builder.setTreeId(cache.writeTree(inserter)); builder.setMessage(MessageFormat.format(indexMessage, branch, headCommit.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH) .name(), headCommit.getShortMessage())); ObjectId indexCommit = inserter.insert(builder); // Commit untracked changes ObjectId untrackedCommit = null; if (!untracked.isEmpty()) { DirCache untrackedDirCache = DirCache.newInCore(); DirCacheBuilder untrackedBuilder = untrackedDirCache .builder(); for (DirCacheEntry entry : untracked) untrackedBuilder.add(entry); untrackedBuilder.finish(); builder.setParentIds(new ObjectId[0]); builder.setTreeId(untrackedDirCache.writeTree(inserter)); builder.setMessage(MessageFormat.format(MSG_UNTRACKED, branch, headCommit .abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH) .name(), headCommit.getShortMessage())); untrackedCommit = inserter.insert(builder); } // Commit working tree changes if (!wtEdits.isEmpty() || !wtDeletes.isEmpty()) { DirCacheEditor editor = cache.editor(); for (PathEdit edit : wtEdits) editor.add(edit); for (String path : wtDeletes) editor.add(new DeletePath(path)); editor.finish(); } builder.setParentId(headCommit); builder.addParentId(indexCommit); if (untrackedCommit != null) builder.addParentId(untrackedCommit); builder.setMessage(MessageFormat.format( workingDirectoryMessage, branch, headCommit.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH) .name(), headCommit.getShortMessage())); builder.setTreeId(cache.writeTree(inserter)); commitId = inserter.insert(builder); inserter.flush(); updateStashRef(commitId, builder.getAuthor(), builder.getMessage()); // Remove untracked files if (includeUntracked) { for (DirCacheEntry entry : untracked) { String repoRelativePath = entry.getPathString(); File file = new File(repo.getWorkTree(), repoRelativePath); FileUtils.delete(file); deletedFiles.add(repoRelativePath); } } } finally { cache.unlock(); } // Hard reset to HEAD new ResetCommand(repo).setMode(ResetType.HARD).call(); // Return stashed commit return parseCommit(reader, commitId); } catch (IOException e) { throw new JGitInternalException(JGitText.get().stashFailed, e); } finally { if (!deletedFiles.isEmpty()) { repo.fireEvent( new WorkingTreeModifiedEvent(null, deletedFiles)); } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6552 Content-Disposition: inline; filename="StashDropCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "2dba0ef0f2f9e7045b3a3936f7be5a238b9c950c" /* * Copyright (C) 2012, GitHub Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static org.eclipse.jgit.lib.Constants.R_STASH; import java.io.File; import java.io.IOException; import java.nio.file.StandardCopyOption; import java.text.MessageFormat; import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.errors.LockFailedException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.RefDirectory; import org.eclipse.jgit.internal.storage.file.ReflogWriter; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.ReflogEntry; import org.eclipse.jgit.lib.ReflogReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.FileUtils; /** * Command class to delete a stashed commit reference *

* Currently only supported on a traditional file repository using * one-file-per-ref reflogs. * * @see Git documentation about Stash * @since 2.0 */ public class StashDropCommand extends GitCommand { private int stashRefEntry; private boolean all; /** * Constructor for StashDropCommand. * * @param repo * a {@link org.eclipse.jgit.lib.Repository} object. */ public StashDropCommand(Repository repo) { super(repo); if (!(repo.getRefDatabase() instanceof RefDirectory)) { throw new UnsupportedOperationException( JGitText.get().stashDropNotSupported); } } /** * Set the stash reference to drop (0-based). *

* This will default to drop the latest stashed commit (stash@{0}) if * unspecified * * @param stashRef * the 0-based index of the stash reference * @return {@code this} */ public StashDropCommand setStashRef(int stashRef) { if (stashRef < 0) throw new IllegalArgumentException(); stashRefEntry = stashRef; return this; } /** * Set whether to drop all stashed commits * * @param all * {@code true} to drop all stashed commits, {@code false} to * drop only the stashed commit set via calling * {@link #setStashRef(int)} * @return {@code this} */ public StashDropCommand setAll(boolean all) { this.all = all; return this; } private Ref getRef() throws GitAPIException { try { return repo.exactRef(R_STASH); } catch (IOException e) { throw new InvalidRefNameException(MessageFormat.format( JGitText.get().cannotRead, R_STASH), e); } } private RefUpdate createRefUpdate(Ref stashRef) throws IOException { RefUpdate update = repo.updateRef(R_STASH); update.disableRefLog(); update.setExpectedOldObjectId(stashRef.getObjectId()); update.setForceUpdate(true); return update; } private void deleteRef(Ref stashRef) { try { Result result = createRefUpdate(stashRef).delete(); if (Result.FORCED != result) throw new JGitInternalException(MessageFormat.format( JGitText.get().stashDropDeleteRefFailed, result)); } catch (IOException e) { throw new JGitInternalException(JGitText.get().stashDropFailed, e); } } private void updateRef(Ref stashRef, ObjectId newId) { try { RefUpdate update = createRefUpdate(stashRef); update.setNewObjectId(newId); Result result = update.update(); switch (result) { case FORCED: case NEW: case NO_CHANGE: return; default: throw new JGitInternalException(MessageFormat.format( JGitText.get().updatingRefFailed, R_STASH, newId, result)); } } catch (IOException e) { throw new JGitInternalException(JGitText.get().stashDropFailed, e); } } /** * {@inheritDoc} *

* Drop the configured entry from the stash reflog and return value of the * stash reference after the drop occurs */ @Override public ObjectId call() throws GitAPIException { checkCallable(); Ref stashRef = getRef(); if (stashRef == null) return null; if (all) { deleteRef(stashRef); return null; } List entries; try { ReflogReader reader = repo.getRefDatabase() .getReflogReader(R_STASH); if (reader == null) { throw new RefNotFoundException(MessageFormat .format(JGitText.get().refNotResolved, stashRef)); } entries = reader.getReverseEntries(); } catch (IOException e) { throw new JGitInternalException(JGitText.get().stashDropFailed, e); } if (stashRefEntry >= entries.size()) throw new JGitInternalException( JGitText.get().stashDropMissingReflog); if (entries.size() == 1) { deleteRef(stashRef); return null; } RefDirectory refdb = (RefDirectory) repo.getRefDatabase(); ReflogWriter writer = new ReflogWriter(refdb, true); String stashLockRef = ReflogWriter.refLockFor(R_STASH); File stashLockFile = refdb.logFor(stashLockRef); File stashFile = refdb.logFor(R_STASH); if (stashLockFile.exists()) throw new JGitInternalException(JGitText.get().stashDropFailed, new LockFailedException(stashFile)); entries.remove(stashRefEntry); ObjectId entryId = ObjectId.zeroId(); try { for (int i = entries.size() - 1; i >= 0; i--) { ReflogEntry entry = entries.get(i); writer.log(stashLockRef, entryId, entry.getNewId(), entry.getWho(), entry.getComment()); entryId = entry.getNewId(); } try { FileUtils.rename(stashLockFile, stashFile, StandardCopyOption.ATOMIC_MOVE); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().renameFileFailed, stashLockFile.getPath(), stashFile.getPath()), e); } } catch (IOException e) { throw new JGitInternalException(JGitText.get().stashDropFailed, e); } updateRef(stashRef, entryId); try { Ref newStashRef = repo.exactRef(R_STASH); return newStashRef != null ? newStashRef.getObjectId() : null; } catch (IOException e) { throw new InvalidRefNameException(MessageFormat.format( JGitText.get().cannotRead, R_STASH), e); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2397 Content-Disposition: inline; filename="StashListCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "828a3020f81a5e784602ca002e2975fe5a2fcbc6" /* * Copyright (C) 2011, GitHub Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ReflogEntry; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; /** * Command class to list the stashed commits in a repository. * * @see Git documentation about Stash */ public class StashListCommand extends GitCommand> { /** * Create a new stash list command * * @param repo a {@link org.eclipse.jgit.lib.Repository} object. */ public StashListCommand(Repository repo) { super(repo); } @Override public Collection call() throws GitAPIException, InvalidRefNameException { checkCallable(); try { if (repo.exactRef(Constants.R_STASH) == null) return Collections.emptyList(); } catch (IOException e) { throw new InvalidRefNameException(MessageFormat.format( JGitText.get().cannotRead, Constants.R_STASH), e); } final ReflogCommand refLog = new ReflogCommand(repo); refLog.setRef(Constants.R_STASH); final Collection stashEntries = refLog.call(); if (stashEntries.isEmpty()) return Collections.emptyList(); final List stashCommits = new ArrayList<>( stashEntries.size()); try (RevWalk walk = new RevWalk(repo)) { for (ReflogEntry entry : stashEntries) { try { stashCommits.add(walk.parseCommit(entry.getNewId())); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().cannotReadCommit, entry.getNewId()), e); } } } return stashCommits; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5811 Content-Disposition: inline; filename="Status.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "f01158fa79b51d7a7ab0e060c471843f9fe4fc51" /* * Copyright (C) 2011, 2013 Christian Halstrick and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.eclipse.jgit.lib.IndexDiff; import org.eclipse.jgit.lib.IndexDiff.StageState; /** * A class telling where the working-tree, the index and the current HEAD differ * from each other. Collections are exposed containing the paths of the modified * files. E.g. to find out which files are dirty in the working tree (modified * but not added) you would inspect the collection returned by * {@link #getModified()}. *

* The same path can be returned by multiple getters. E.g. if a modification has * been added to the index and afterwards the corresponding working tree file is * again modified this path will be returned by {@link #getModified()} and * {@link #getChanged()} */ public class Status { private final IndexDiff diff; private final boolean clean; private final boolean hasUncommittedChanges; /** * Constructor for Status. * * @param diff * the {@link org.eclipse.jgit.lib.IndexDiff} having the status */ public Status(IndexDiff diff) { super(); this.diff = diff; hasUncommittedChanges = !diff.getAdded().isEmpty() // || !diff.getChanged().isEmpty() // || !diff.getRemoved().isEmpty() // || !diff.getMissing().isEmpty() // || !diff.getModified().isEmpty() // || !diff.getConflicting().isEmpty(); clean = !hasUncommittedChanges // && diff.getUntracked().isEmpty(); } /** * Whether the status is clean * * @return {@code true} if no differences exist between the working-tree, * the index, and the current HEAD, {@code false} if differences do * exist */ public boolean isClean() { return clean; } /** * Whether there are uncommitted changes * * @return {@code true} if any tracked file is changed * @since 3.2 */ public boolean hasUncommittedChanges() { return hasUncommittedChanges; } /** * Get files added to the index * * @return list of files added to the index, not in HEAD (e.g. what you get * if you call {@code git add ...} on a newly created file) */ public Set getAdded() { return Collections.unmodifiableSet(diff.getAdded()); } /** * Get changed files from HEAD to index * * @return list of files changed from HEAD to index (e.g. what you get if * you modify an existing file and call 'git add ...' on it) */ public Set getChanged() { return Collections.unmodifiableSet(diff.getChanged()); } /** * Get removed files * * @return list of files removed from index, but in HEAD (e.g. what you get * if you call 'git rm ...' on a existing file) */ public Set getRemoved() { return Collections.unmodifiableSet(diff.getRemoved()); } /** * Get missing files * * @return list of files in index, but not filesystem (e.g. what you get if * you call 'rm ...' on a existing file) */ public Set getMissing() { return Collections.unmodifiableSet(diff.getMissing()); } /** * Get modified files relative to the index * * @return list of files modified on disk relative to the index (e.g. what * you get if you modify an existing file without adding it to the * index) */ public Set getModified() { return Collections.unmodifiableSet(diff.getModified()); } /** * Get untracked files * * @return list of files that are not ignored, and not in the index. (e.g. * what you get if you create a new file without adding it to the * index) */ public Set getUntracked() { return Collections.unmodifiableSet(diff.getUntracked()); } /** * Get untracked folders * * @return set of directories that are not ignored, and not in the index. */ public Set getUntrackedFolders() { return Collections.unmodifiableSet(diff.getUntrackedFolders()); } /** * Get conflicting files * * @return list of files that are in conflict. (e.g what you get if you * modify file that was modified by someone else in the meantime) */ public Set getConflicting() { return Collections.unmodifiableSet(diff.getConflicting()); } /** * Get StageState of conflicting files * * @return a map from conflicting path to its * {@link org.eclipse.jgit.lib.IndexDiff.StageState}. * @since 3.0 */ public Map getConflictingStageState() { return Collections.unmodifiableMap(diff.getConflictingStageStates()); } /** * Get ignored files which are not in the index * * @return set of files and folders that are ignored and not in the index. */ public Set getIgnoredNotInIndex() { return Collections.unmodifiableSet(diff.getIgnoredNotInIndex()); } /** * Get uncommitted changes, i.e. all files changed in the index or working * tree * * @return set of files and folders that are known to the repo and changed * either in the index or in the working tree. * @since 3.2 */ public Set getUncommittedChanges() { Set uncommittedChanges = new HashSet<>(); uncommittedChanges.addAll(diff.getAdded()); uncommittedChanges.addAll(diff.getChanged()); uncommittedChanges.addAll(diff.getRemoved()); uncommittedChanges.addAll(diff.getMissing()); uncommittedChanges.addAll(diff.getModified()); uncommittedChanges.addAll(diff.getConflicting()); return uncommittedChanges; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5163 Content-Disposition: inline; filename="StatusCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "cdd078ea250de981b766c163b2235700dc38d053" /* * Copyright (C) 2011, Christian Halstrick and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.IndexDiff; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; /** * A class used to execute a {@code Status} command. It has setters for all * supported options and arguments of this command and a {@link #call()} method * to finally execute the command. Each instance of this class should only be * used for one invocation of the command (means: one call to {@link #call()}) * * @see Git * documentation about Status */ public class StatusCommand extends GitCommand { private WorkingTreeIterator workingTreeIt; private List paths = null; private ProgressMonitor progressMonitor = null; private IgnoreSubmoduleMode ignoreSubmoduleMode = null; /** * Constructor for StatusCommand. * * @param repo * a {@link org.eclipse.jgit.lib.Repository} object. */ protected StatusCommand(Repository repo) { super(repo); } /** * Whether to ignore submodules * * @param mode * the * {@link org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode} * @return {@code this} * @since 3.6 */ public StatusCommand setIgnoreSubmodules(IgnoreSubmoduleMode mode) { ignoreSubmoduleMode = mode; return this; } /** * Show only the status of files which match the given paths. The path must * either name a file or a directory exactly. All paths are always relative * to the repository root. If a directory is specified all files recursively * underneath that directory are matched. If this method is called multiple * times then the status of those files is reported which match at least one * of the given paths. Note that regex expressions or wildcards are not * supported. * * @param path * repository-relative path of file/directory to show status for * (with / as separator) * @return {@code this} * @since 3.1 */ public StatusCommand addPath(String path) { if (paths == null) paths = new ArrayList<>(); paths.add(path); return this; } /** * Returns the paths filtering this status. * * @return the paths for which the status is shown or null if * the complete status for the whole repo is shown. * @since 3.1 */ public List getPaths() { return paths; } /** * {@inheritDoc} *

* Executes the {@code Status} command with all the options and parameters * collected by the setter methods of this class. Each instance of this * class should only be used for one invocation of the command. Don't call * this method twice on an instance. */ @Override public Status call() throws GitAPIException, NoWorkTreeException { if (workingTreeIt == null) workingTreeIt = new FileTreeIterator(repo); try { IndexDiff diff = new IndexDiff(repo, Constants.HEAD, workingTreeIt); if (ignoreSubmoduleMode != null) diff.setIgnoreSubmoduleMode(ignoreSubmoduleMode); if (paths != null) diff.setFilter(PathFilterGroup.createFromStrings(paths)); if (progressMonitor == null) diff.diff(); else diff.diff(progressMonitor, ProgressMonitor.UNKNOWN, ProgressMonitor.UNKNOWN, ""); //$NON-NLS-1$ return new Status(diff); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } } /** * To set the {@link org.eclipse.jgit.treewalk.WorkingTreeIterator} which * should be used. If this method is not called a standard * {@link org.eclipse.jgit.treewalk.FileTreeIterator} is used. * * @param workingTreeIt * a working tree iterator * @return {@code this} */ public StatusCommand setWorkingTreeIt(WorkingTreeIterator workingTreeIt) { this.workingTreeIt = workingTreeIt; return this; } /** * To set the {@link org.eclipse.jgit.lib.ProgressMonitor} which contains * callback methods to inform you about the progress of this command. * * @param progressMonitor * a {@link org.eclipse.jgit.lib.ProgressMonitor} object. * @return {@code this} * @since 3.1 */ public StatusCommand setProgressMonitor(ProgressMonitor progressMonitor) { this.progressMonitor = progressMonitor; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6871 Content-Disposition: inline; filename="SubmoduleAddCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "5105dfc2e5ac287334d7fc978cc549bf0452975c" /* * Copyright (C) 2011, GitHub Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.File; import java.io.IOException; import java.text.MessageFormat; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.submodule.SubmoduleValidator; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; /** * A class used to execute a submodule add command. * * This will clone the configured submodule, register the submodule in the * .gitmodules file and the repository config file, and also add the submodule * and .gitmodules file to the index. * * @see Git documentation about submodules */ public class SubmoduleAddCommand extends TransportCommand { private String name; private String path; private String uri; private ProgressMonitor monitor; /** * Constructor for SubmoduleAddCommand. * * @param repo * a {@link org.eclipse.jgit.lib.Repository} object. */ public SubmoduleAddCommand(Repository repo) { super(repo); } /** * Set the submodule name * * @param name * name of the submodule * @return this command * @since 5.1 */ public SubmoduleAddCommand setName(String name) { this.name = name; return this; } /** * Set repository-relative path of submodule * * @param path * (with / as separator) * @return this command */ public SubmoduleAddCommand setPath(String path) { this.path = path; return this; } /** * Set URI to clone submodule from * * @param uri * a {@link java.lang.String} object. * @return this command */ public SubmoduleAddCommand setURI(String uri) { this.uri = uri; return this; } /** * The progress monitor associated with the clone operation. By default, * this is set to NullProgressMonitor * * @see NullProgressMonitor * @param monitor * a {@link org.eclipse.jgit.lib.ProgressMonitor} object. * @return this command */ public SubmoduleAddCommand setProgressMonitor(ProgressMonitor monitor) { this.monitor = monitor; return this; } /** * Is the configured already a submodule in the index? * * @return true if submodule exists in index, false otherwise * @throws java.io.IOException * if an IO error occurred */ protected boolean submoduleExists() throws IOException { TreeFilter filter = PathFilter.create(path); try (SubmoduleWalk w = SubmoduleWalk.forIndex(repo)) { return w.setFilter(filter).next(); } } /** * {@inheritDoc} *

* Executes the {@code SubmoduleAddCommand} * * The {@code Repository} instance returned by this command needs to be * closed by the caller to free resources held by the {@code Repository} * instance. It is recommended to call this method as soon as you don't need * a reference to this {@code Repository} instance anymore. */ @Override public Repository call() throws GitAPIException { checkCallable(); if (path == null || path.length() == 0) throw new IllegalArgumentException(JGitText.get().pathNotConfigured); if (uri == null || uri.length() == 0) throw new IllegalArgumentException(JGitText.get().uriNotConfigured); if (name == null || name.length() == 0) { // Use the path as the default. name = path; } try { SubmoduleValidator.assertValidSubmoduleName(name); SubmoduleValidator.assertValidSubmodulePath(path); SubmoduleValidator.assertValidSubmoduleUri(uri); } catch (SubmoduleValidator.SubmoduleValidationException e) { throw new IllegalArgumentException(e.getMessage()); } try { if (submoduleExists()) throw new JGitInternalException(MessageFormat.format( JGitText.get().submoduleExists, path)); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } final String resolvedUri; try { resolvedUri = SubmoduleWalk.getSubmoduleRemoteUrl(repo, uri); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } // Clone submodule repository File moduleDirectory = SubmoduleWalk.getSubmoduleDirectory(repo, path); CloneCommand clone = Git.cloneRepository(); configure(clone); clone.setDirectory(moduleDirectory); clone.setGitDir(new File( new File(repo.getCommonDirectory(), Constants.MODULES), path)); clone.setRelativePaths(true); clone.setURI(resolvedUri); if (monitor != null) clone.setProgressMonitor(monitor); Repository subRepo = null; try (Git git = clone.call()) { subRepo = git.getRepository(); subRepo.incrementOpen(); } // Save submodule URL to parent repository's config StoredConfig config = repo.getConfig(); config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, name, ConfigConstants.CONFIG_KEY_URL, resolvedUri); try { config.save(); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } // Save path and URL to parent repository's .gitmodules file FileBasedConfig modulesConfig = new FileBasedConfig(new File( repo.getWorkTree(), Constants.DOT_GIT_MODULES), repo.getFS()); try { modulesConfig.load(); modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, name, ConfigConstants.CONFIG_KEY_PATH, path); modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, name, ConfigConstants.CONFIG_KEY_URL, uri); modulesConfig.save(); } catch (IOException | ConfigInvalidException e) { throw new JGitInternalException(e.getMessage(), e); } AddCommand add = new AddCommand(repo); // Add .gitmodules file to parent repository's index add.addFilepattern(Constants.DOT_GIT_MODULES); // Add submodule directory to parent repository's index add.addFilepattern(path); try { add.call(); } catch (NoFilepatternException e) { throw new JGitInternalException(e.getMessage(), e); } return subRepo; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8662 Content-Disposition: inline; filename="SubmoduleDeinitCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "0aa151533405349b291b1edb9f6b50bfe4a09ea9" /* * Copyright (C) 2017, Two Sigma Open Source and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import static org.eclipse.jgit.util.FileUtils.RECURSIVE; import java.io.File; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidConfigurationException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.util.FileUtils; /** * A class used to execute a submodule deinit command. *

* This will remove the module(s) from the working tree, but won't affect * .git/modules. * * @since 4.10 * @see Git documentation about submodules */ public class SubmoduleDeinitCommand extends GitCommand> { private final Collection paths; private boolean force; /** * Constructor of SubmoduleDeinitCommand * * @param repo * repository this command works on */ public SubmoduleDeinitCommand(Repository repo) { super(repo); paths = new ArrayList<>(); } /** * {@inheritDoc} * * @return the set of repositories successfully deinitialized. * @throws NoSuchSubmoduleException * if any of the submodules which we might want to deinitialize * don't exist */ @Override public Collection call() throws GitAPIException { checkCallable(); try { if (paths.isEmpty()) { return Collections.emptyList(); } for (String path : paths) { if (!submoduleExists(path)) { throw new NoSuchSubmoduleException(path); } } List results = new ArrayList<>(paths.size()); try (RevWalk revWalk = new RevWalk(repo); SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) { generator.setFilter(PathFilterGroup.createFromStrings(paths)); StoredConfig config = repo.getConfig(); while (generator.next()) { String path = generator.getPath(); String name = generator.getModuleName(); SubmoduleDeinitStatus status = checkDirty(revWalk, path); switch (status) { case SUCCESS: deinit(path); break; case ALREADY_DEINITIALIZED: break; case DIRTY: if (force) { deinit(path); status = SubmoduleDeinitStatus.FORCED; } break; default: throw new JGitInternalException(MessageFormat.format( JGitText.get().unexpectedSubmoduleStatus, status)); } config.unsetSection( ConfigConstants.CONFIG_SUBMODULE_SECTION, name); results.add(new SubmoduleDeinitResult(path, status)); } } return results; } catch (ConfigInvalidException e) { throw new InvalidConfigurationException(e.getMessage(), e); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } } /** * Recursively delete the *contents* of path, but leave path as an empty * directory * * @param path * the path to clean * @throws IOException * if an IO error occurred */ private void deinit(String path) throws IOException { File dir = new File(repo.getWorkTree(), path); if (!dir.isDirectory()) { throw new JGitInternalException(MessageFormat.format( JGitText.get().expectedDirectoryNotSubmodule, path)); } final File[] ls = dir.listFiles(); if (ls != null) { for (File f : ls) { FileUtils.delete(f, RECURSIVE); } } } /** * Check if a submodule is dirty. A submodule is dirty if there are local * changes to the submodule relative to its HEAD, including untracked files. * It is also dirty if the HEAD of the submodule does not match the value in * the parent repo's index or HEAD. * * @param revWalk * used to walk commit graph * @param path * path of the submodule * @return status of the command * @throws GitAPIException * if JGit API failed * @throws IOException * if an IO error occurred */ private SubmoduleDeinitStatus checkDirty(RevWalk revWalk, String path) throws GitAPIException, IOException { Ref head = repo.exactRef("HEAD"); //$NON-NLS-1$ if (head == null) { throw new NoHeadException( JGitText.get().invalidRepositoryStateNoHead); } RevCommit headCommit = revWalk.parseCommit(head.getObjectId()); RevTree tree = headCommit.getTree(); ObjectId submoduleHead; try (SubmoduleWalk w = SubmoduleWalk.forPath(repo, tree, path)) { submoduleHead = w.getHead(); if (submoduleHead == null) { // The submodule is not checked out. return SubmoduleDeinitStatus.ALREADY_DEINITIALIZED; } if (!submoduleHead.equals(w.getObjectId())) { // The submodule's current HEAD doesn't match the value in the // outer repo's HEAD. return SubmoduleDeinitStatus.DIRTY; } } try (SubmoduleWalk w = SubmoduleWalk.forIndex(repo)) { if (!w.next()) { // The submodule does not exist in the index (shouldn't happen // since we check this earlier) return SubmoduleDeinitStatus.DIRTY; } if (!submoduleHead.equals(w.getObjectId())) { // The submodule's current HEAD doesn't match the value in the // outer repo's index. return SubmoduleDeinitStatus.DIRTY; } try (Repository submoduleRepo = w.getRepository()) { Status status = Git.wrap(submoduleRepo).status().call(); return status.isClean() ? SubmoduleDeinitStatus.SUCCESS : SubmoduleDeinitStatus.DIRTY; } } } /** * Check if this path is a submodule by checking the index, which is what * git submodule deinit checks. * * @param path * path of the submodule * * @return {@code true} if path exists and is a submodule in index, * {@code false} otherwise * @throws IOException * if an IO error occurred */ private boolean submoduleExists(String path) throws IOException { TreeFilter filter = PathFilter.create(path); try (SubmoduleWalk w = SubmoduleWalk.forIndex(repo)) { return w.setFilter(filter).next(); } } /** * Add repository-relative submodule path to deinitialize * * @param path * (with / as separator) * @return this command */ public SubmoduleDeinitCommand addPath(String path) { paths.add(path); return this; } /** * If {@code true}, call() will deinitialize modules with local changes; * else it will refuse to do so. * * @param force * execute the command forcefully if there are local modifications * @return {@code this} */ public SubmoduleDeinitCommand setForce(boolean force) { this.force = force; return this; } /** * The user tried to deinitialize a submodule that doesn't exist in the * index. */ public static class NoSuchSubmoduleException extends GitAPIException { private static final long serialVersionUID = 1L; /** * Constructor of NoSuchSubmoduleException * * @param path * path of non-existing submodule */ public NoSuchSubmoduleException(String path) { super(MessageFormat.format(JGitText.get().noSuchSubmodule, path)); } } /** * The effect of a submodule deinit command for a given path */ public enum SubmoduleDeinitStatus { /** * The submodule was not initialized in the first place */ ALREADY_DEINITIALIZED, /** * The submodule was deinitialized */ SUCCESS, /** * The submodule had local changes, but was deinitialized successfully */ FORCED, /** * The submodule had local changes and force was false */ DIRTY, } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1575 Content-Disposition: inline; filename="SubmoduleDeinitResult.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "b7d7b66333f9ed8390e9305963b7969c2e7811ac" /* * Copyright (C) 2017, Two Sigma Open Source and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; /** * The result of a submodule deinit command for a particular path * * @since 4.10 */ public class SubmoduleDeinitResult { private String path; private SubmoduleDeinitCommand.SubmoduleDeinitStatus status; /** * Constructor for SubmoduleDeinitResult * * @param path * path of the submodule * @param status * effect of a SubmoduleDeinitCommand's execution */ public SubmoduleDeinitResult(String path, SubmoduleDeinitCommand.SubmoduleDeinitStatus status) { this.path = path; this.status = status; } /** * Get the path of the submodule * * @return path of the submodule */ public String getPath() { return path; } /** * Set the path of the submodule * * @param path * path of the submodule */ public void setPath(String path) { this.path = path; } /** * Get the status of the command * * @return the status of the command */ public SubmoduleDeinitCommand.SubmoduleDeinitStatus getStatus() { return status; } /** * Set the status of the command * * @param status * the status of the command */ public void setStatus(SubmoduleDeinitCommand.SubmoduleDeinitStatus status) { this.status = status; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3193 Content-Disposition: inline; filename="SubmoduleInitCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "03b4a000ad404dbea56deedef102234787b5f437" /* * Copyright (C) 2011, GitHub Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; /** * A class used to execute a submodule init command. * * This will copy the 'url' and 'update' fields from the working tree * .gitmodules file to a repository's config file for each submodule not * currently present in the repository's config file. * * @see Git documentation about submodules */ public class SubmoduleInitCommand extends GitCommand> { private final Collection paths; /** * Constructor for SubmoduleInitCommand. * * @param repo * a {@link org.eclipse.jgit.lib.Repository} object. */ public SubmoduleInitCommand(Repository repo) { super(repo); paths = new ArrayList<>(); } /** * Add repository-relative submodule path to initialize * * @param path * (with / as separator) * @return this command */ public SubmoduleInitCommand addPath(String path) { paths.add(path); return this; } @Override public Collection call() throws GitAPIException { checkCallable(); try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) { if (!paths.isEmpty()) generator.setFilter(PathFilterGroup.createFromStrings(paths)); StoredConfig config = repo.getConfig(); List initialized = new ArrayList<>(); while (generator.next()) { // Ignore entry if URL is already present in config file if (generator.getConfigUrl() != null) continue; String path = generator.getPath(); String name = generator.getModuleName(); // Copy 'url' and 'update' fields from .gitmodules to config // file String url = generator.getRemoteUrl(); String update = generator.getModulesUpdate(); if (url != null) config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, name, ConfigConstants.CONFIG_KEY_URL, url); if (update != null) config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, name, ConfigConstants.CONFIG_KEY_UPDATE, update); if (url != null || update != null) initialized.add(path); } // Save repository config if any values were updated if (!initialized.isEmpty()) config.save(); return initialized; } catch (IOException | ConfigInvalidException e) { throw new JGitInternalException(e.getMessage(), e); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3747 Content-Disposition: inline; filename="SubmoduleStatusCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "d5bc0dda98a11fc0a0328be715257110397c7a09" /* * Copyright (C) 2011, GitHub Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.submodule.SubmoduleStatus; import org.eclipse.jgit.submodule.SubmoduleStatusType; import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; /** * A class used to execute a submodule status command. * * @see Git documentation about submodules */ public class SubmoduleStatusCommand extends GitCommand> { private final Collection paths; /** * Constructor for SubmoduleStatusCommand. * * @param repo * a {@link org.eclipse.jgit.lib.Repository} object. */ public SubmoduleStatusCommand(Repository repo) { super(repo); paths = new ArrayList<>(); } /** * Add repository-relative submodule path to limit status reporting to * * @param path * (with / as separator) * @return this command */ public SubmoduleStatusCommand addPath(String path) { paths.add(path); return this; } @Override public Map call() throws GitAPIException { checkCallable(); try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) { if (!paths.isEmpty()) generator.setFilter(PathFilterGroup.createFromStrings(paths)); Map statuses = new HashMap<>(); while (generator.next()) { SubmoduleStatus status = getStatus(generator); statuses.put(status.getPath(), status); } return statuses; } catch (IOException | ConfigInvalidException e) { throw new JGitInternalException(e.getMessage(), e); } } private SubmoduleStatus getStatus(SubmoduleWalk generator) throws IOException, ConfigInvalidException { ObjectId id = generator.getObjectId(); String path = generator.getPath(); // Report missing if no path in .gitmodules file if (generator.getModulesPath() == null) return new SubmoduleStatus(SubmoduleStatusType.MISSING, path, id); // Report uninitialized if no URL in config file if (generator.getConfigUrl() == null) return new SubmoduleStatus(SubmoduleStatusType.UNINITIALIZED, path, id); // Report uninitialized if no submodule repository ObjectId headId = null; try (Repository subRepo = generator.getRepository()) { if (subRepo == null) { return new SubmoduleStatus(SubmoduleStatusType.UNINITIALIZED, path, id); } headId = subRepo.resolve(Constants.HEAD); } // Report uninitialized if no HEAD commit in submodule repository if (headId == null) return new SubmoduleStatus(SubmoduleStatusType.UNINITIALIZED, path, id, headId); // Report checked out if HEAD commit is different than index commit if (!headId.equals(id)) return new SubmoduleStatus(SubmoduleStatusType.REV_CHECKED_OUT, path, id, headId); // Report initialized if HEAD commit is the same as the index commit return new SubmoduleStatus(SubmoduleStatusType.INITIALIZED, path, id, headId); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4002 Content-Disposition: inline; filename="SubmoduleSyncCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "4f3e8512c3517153ddb5dbb6db8040b1ecf7202c" /* * Copyright (C) 2011, GitHub Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; /** * A class used to execute a submodule sync command. * * This will set the remote URL in a submodule's repository to the current value * in the .gitmodules file. * * @see Git documentation about submodules */ public class SubmoduleSyncCommand extends GitCommand> { private final Collection paths; /** * Constructor for SubmoduleSyncCommand. * * @param repo * a {@link org.eclipse.jgit.lib.Repository} object. */ public SubmoduleSyncCommand(Repository repo) { super(repo); paths = new ArrayList<>(); } /** * Add repository-relative submodule path to synchronize * * @param path * (with / as separator) * @return this command */ public SubmoduleSyncCommand addPath(String path) { paths.add(path); return this; } /** * Get branch that HEAD currently points to * * @param subRepo * a {@link org.eclipse.jgit.lib.Repository} object. * @return shortened branch name, null on failures * @throws java.io.IOException * if an IO error occurred */ protected String getHeadBranch(Repository subRepo) throws IOException { Ref head = subRepo.exactRef(Constants.HEAD); if (head != null && head.isSymbolic()) { return Repository.shortenRefName(head.getLeaf().getName()); } return null; } @Override public Map call() throws GitAPIException { checkCallable(); try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) { if (!paths.isEmpty()) generator.setFilter(PathFilterGroup.createFromStrings(paths)); Map synced = new HashMap<>(); StoredConfig config = repo.getConfig(); while (generator.next()) { String remoteUrl = generator.getRemoteUrl(); if (remoteUrl == null) continue; String path = generator.getPath(); config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path, ConfigConstants.CONFIG_KEY_URL, remoteUrl); synced.put(path, remoteUrl); try (Repository subRepo = generator.getRepository()) { if (subRepo == null) { continue; } StoredConfig subConfig; String branch; subConfig = subRepo.getConfig(); // Get name of remote associated with current branch and // fall back to default remote name as last resort branch = getHeadBranch(subRepo); String remote = null; if (branch != null) { remote = subConfig.getString( ConfigConstants.CONFIG_BRANCH_SECTION, branch, ConfigConstants.CONFIG_KEY_REMOTE); } if (remote == null) { remote = Constants.DEFAULT_REMOTE_NAME; } subConfig.setString(ConfigConstants.CONFIG_REMOTE_SECTION, remote, ConfigConstants.CONFIG_KEY_URL, remoteUrl); subConfig.save(); } } if (!synced.isEmpty()) config.save(); return synced; } catch (IOException | ConfigInvalidException e) { throw new JGitInternalException(e.getMessage(), e); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 9570 Content-Disposition: inline; filename="SubmoduleUpdateCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "5e4b2ee0b7e269096bc75195f31d777d66875b74" /* * Copyright (C) 2011, GitHub Inc. * Copyright (C) 2016, Laurent Delaigue and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.jgit.api.errors.CheckoutConflictException; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidConfigurationException; import org.eclipse.jgit.api.errors.InvalidMergeHeadsException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.internal.storage.file.LockFile; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; import org.eclipse.jgit.util.FileUtils; /** * A class used to execute a submodule update command. * * @see Git documentation about submodules */ public class SubmoduleUpdateCommand extends TransportCommand> { private ProgressMonitor monitor; private final Collection paths; private MergeStrategy strategy = MergeStrategy.RECURSIVE; private CloneCommand.Callback callback; private FetchCommand.Callback fetchCallback; private boolean fetch = false; private boolean clonedRestored; /** *

* Constructor for SubmoduleUpdateCommand. *

* * @param repo * a {@link org.eclipse.jgit.lib.Repository} object. */ public SubmoduleUpdateCommand(Repository repo) { super(repo); paths = new ArrayList<>(); } /** * The progress monitor associated with the clone operation. By default, * this is set to NullProgressMonitor * * @see NullProgressMonitor * @param monitor * a {@link org.eclipse.jgit.lib.ProgressMonitor} object. * @return this command */ public SubmoduleUpdateCommand setProgressMonitor( final ProgressMonitor monitor) { this.monitor = monitor; return this; } /** * Whether to fetch the submodules before we update them. By default, this * is set to false * * @param fetch * whether to fetch the submodules before we update them * @return this command * @since 4.9 */ public SubmoduleUpdateCommand setFetch(boolean fetch) { this.fetch = fetch; return this; } /** * Add repository-relative submodule path to initialize * * @param path * (with / as separator) * @return this command */ public SubmoduleUpdateCommand addPath(String path) { paths.add(path); return this; } private static boolean submoduleExists(File gitDir) { if (gitDir != null && gitDir.isDirectory()) { File[] files = gitDir.listFiles(); return files != null && files.length != 0; } return false; } private static void restoreSubmodule(File gitDir, File workingTree) throws IOException { LockFile dotGitLock = new LockFile( new File(workingTree, Constants.DOT_GIT)); if (dotGitLock.lock()) { String content = Constants.GITDIR + getRelativePath(gitDir, workingTree); dotGitLock.write(Constants.encode(content)); dotGitLock.commit(); } } private static String getRelativePath(File gitDir, File workingTree) { File relPath; try { relPath = workingTree.toPath().relativize(gitDir.toPath()) .toFile(); } catch (IllegalArgumentException e) { relPath = gitDir; } return FileUtils.pathToString(relPath); } private String determineUpdateMode(String mode) { if (clonedRestored) { return ConfigConstants.CONFIG_KEY_CHECKOUT; } return mode; } private Repository getOrCloneSubmodule(SubmoduleWalk generator, String url) throws IOException, GitAPIException { Repository repository = generator.getRepository(); boolean restored = false; boolean cloned = false; if (repository == null) { File gitDir = new File( new File(repo.getCommonDirectory(), Constants.MODULES), generator.getPath()); if (submoduleExists(gitDir)) { restoreSubmodule(gitDir, generator.getDirectory()); restored = true; clonedRestored = true; repository = generator.getRepository(); } else { if (callback != null) { callback.cloningSubmodule(generator.getPath()); } CloneCommand clone = Git.cloneRepository(); configure(clone); clone.setURI(url); clone.setDirectory(generator.getDirectory()); clone.setGitDir(gitDir); clone.setRelativePaths(true); if (monitor != null) { clone.setProgressMonitor(monitor); } repository = clone.call().getRepository(); cloned = true; clonedRestored = true; } } if ((this.fetch || restored) && !cloned) { if (fetchCallback != null) { fetchCallback.fetchingSubmodule(generator.getPath()); } FetchCommand fetchCommand = Git.wrap(repository).fetch(); if (monitor != null) { fetchCommand.setProgressMonitor(monitor); } configure(fetchCommand); fetchCommand.call(); } return repository; } /** * {@inheritDoc} * * Execute the SubmoduleUpdateCommand command. */ @Override public Collection call() throws InvalidConfigurationException, NoHeadException, ConcurrentRefUpdateException, CheckoutConflictException, InvalidMergeHeadsException, WrongRepositoryStateException, NoMessageException, NoHeadException, RefNotFoundException, GitAPIException { checkCallable(); try (SubmoduleWalk generator = SubmoduleWalk.forIndex(repo)) { if (!paths.isEmpty()) generator.setFilter(PathFilterGroup.createFromStrings(paths)); List updated = new ArrayList<>(); while (generator.next()) { // Skip submodules not registered in .gitmodules file if (generator.getModulesPath() == null) continue; // Skip submodules not registered in parent repository's config String url = generator.getConfigUrl(); if (url == null) { continue; } clonedRestored = false; try (Repository submoduleRepo = getOrCloneSubmodule(generator, url); RevWalk walk = new RevWalk(submoduleRepo)) { RevCommit commit = walk .parseCommit(generator.getObjectId()); String update = determineUpdateMode( generator.getConfigUpdate()); if (ConfigConstants.CONFIG_KEY_MERGE.equals(update)) { MergeCommand merge = new MergeCommand(submoduleRepo); merge.include(commit); merge.setProgressMonitor(monitor); merge.setStrategy(strategy); merge.call(); } else if (ConfigConstants.CONFIG_KEY_REBASE.equals(update)) { RebaseCommand rebase = new RebaseCommand(submoduleRepo); rebase.setUpstream(commit); rebase.setProgressMonitor(monitor); rebase.setStrategy(strategy); rebase.call(); } else { // Checkout commit referenced in parent repository's // index as a detached HEAD DirCacheCheckout co = new DirCacheCheckout( submoduleRepo, submoduleRepo.lockDirCache(), commit.getTree()); co.setFailOnConflict(true); co.setProgressMonitor(monitor); co.checkout(); RefUpdate refUpdate = submoduleRepo.updateRef( Constants.HEAD, true); refUpdate.setNewObjectId(commit); refUpdate.forceUpdate(); if (callback != null) { callback.checkingOut(commit, generator.getPath()); } } } updated.add(generator.getPath()); } return updated; } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } catch (ConfigInvalidException e) { throw new InvalidConfigurationException(e.getMessage(), e); } } /** * Setter for the field strategy. * * @param strategy * The merge strategy to use during this update operation. * @return {@code this} * @since 3.4 */ public SubmoduleUpdateCommand setStrategy(MergeStrategy strategy) { 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; } /** * Set status callback for submodule fetch operation. * * @param callback * the callback * @return {@code this} * @since 4.9 */ public SubmoduleUpdateCommand setFetchCallback( FetchCommand.Callback callback) { this.fetchCallback = callback; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 13506 Content-Disposition: inline; filename="TagCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "cc8589fa1c5acf68ab12239a6419e199ad16c799" /* * Copyright (C) 2010, 2020 Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidTagNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.GpgConfig; import org.eclipse.jgit.lib.GpgConfig.GpgFormat; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Signer; import org.eclipse.jgit.lib.Signers; import org.eclipse.jgit.lib.TagBuilder; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.CredentialsProvider; /** * Create/update an annotated tag object or a simple unannotated tag *

* Examples (git is a {@link org.eclipse.jgit.api.Git} instance): *

* Create a new tag for the current commit: * *

 * git.tag().setName("v1.0").setMessage("First stable release").call();
 * 
*

* * Create a new unannotated tag for the current commit: * *

 * git.tag().setName("v1.0").setAnnotated(false).call();
 * 
* * @see Git documentation about Tag */ public class TagCommand extends GitCommand { private RevObject id; private String name; private String message; private PersonIdent tagger; private Boolean signed; private boolean forceUpdate; private Boolean annotated; private String signingKey; private GpgConfig gpgConfig; private Signer signer; private CredentialsProvider credentialsProvider; /** *

Constructor for TagCommand.

* * @param repo a {@link org.eclipse.jgit.lib.Repository} object. */ protected TagCommand(Repository repo) { super(repo); this.credentialsProvider = CredentialsProvider.getDefault(); } /** * {@inheritDoc} *

* Executes the {@code tag} command with all the options and parameters * collected by the setter methods of this class. Each instance of this * class should only be used for one invocation of the command (means: one * call to {@link #call()}) * * @since 2.0 */ @Override public Ref call() throws GitAPIException, ConcurrentRefUpdateException, InvalidTagNameException, NoHeadException { checkCallable(); processOptions(); try (RevWalk revWalk = new RevWalk(repo)) { // if no id is set, we should attempt to use HEAD if (id == null) { ObjectId objectId = repo.resolve(Constants.HEAD + "^{commit}"); //$NON-NLS-1$ if (objectId == null) throw new NoHeadException( JGitText.get().tagOnRepoWithoutHEADCurrentlyNotSupported); id = revWalk.parseCommit(objectId); } if (!isAnnotated()) { return updateTagRef(id, revWalk, name, "SimpleTag[" + name + " : " + id //$NON-NLS-1$ //$NON-NLS-2$ + "]"); //$NON-NLS-1$ } // create the tag object TagBuilder newTag = new TagBuilder(); newTag.setTag(name); newTag.setMessage(message); newTag.setTagger(tagger); newTag.setObjectId(id); if (signer != null) { signer.signObject(repo, gpgConfig, newTag, tagger, signingKey, credentialsProvider); } // write the tag object try (ObjectInserter inserter = repo.newObjectInserter()) { ObjectId tagId = inserter.insert(newTag); inserter.flush(); String tag = newTag.getTag(); return updateTagRef(tagId, revWalk, tag, newTag.toString()); } } catch (IOException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfTagCommand, e); } } private Ref updateTagRef(ObjectId tagId, RevWalk revWalk, String tagName, String newTagToString) throws IOException, ConcurrentRefUpdateException, RefAlreadyExistsException { String refName = Constants.R_TAGS + tagName; RefUpdate tagRef = repo.updateRef(refName); tagRef.setNewObjectId(tagId); tagRef.setForceUpdate(forceUpdate); tagRef.setRefLogMessage("tagged " + name, false); //$NON-NLS-1$ Result updateResult = tagRef.update(revWalk); switch (updateResult) { case NEW: case FORCED: return repo.exactRef(refName); case LOCK_FAILURE: throw new ConcurrentRefUpdateException( JGitText.get().couldNotLockHEAD, tagRef.getRef(), updateResult); case NO_CHANGE: if (forceUpdate) { return repo.exactRef(refName); } throw new RefAlreadyExistsException(MessageFormat .format(JGitText.get().tagAlreadyExists, newTagToString), updateResult); case REJECTED: throw new RefAlreadyExistsException(MessageFormat.format( JGitText.get().tagAlreadyExists, newTagToString), updateResult); default: throw new JGitInternalException(MessageFormat.format( JGitText.get().updatingRefFailed, refName, newTagToString, updateResult)); } } /** * Sets default values for not explicitly specified options. Then validates * that all required data has been provided. * * @throws InvalidTagNameException * if the tag name is null or invalid * @throws UnsupportedSigningFormatException * if the tag should be signed but {@code gpg.format} is not * {@link GpgFormat#OPENPGP} */ private void processOptions() throws InvalidTagNameException, UnsupportedSigningFormatException { if (name == null || !Repository.isValidRefName(Constants.R_TAGS + name)) { throw new InvalidTagNameException( MessageFormat.format(JGitText.get().tagNameInvalid, name == null ? "" : name)); //$NON-NLS-1$ } if (!isAnnotated()) { if ((message != null && !message.isEmpty()) || tagger != null) { throw new JGitInternalException(JGitText .get().messageAndTaggerNotAllowedInUnannotatedTags); } } else { if (tagger == null) { tagger = new PersonIdent(repo); } // Figure out whether to sign. if (!(Boolean.FALSE.equals(signed) && signingKey == null)) { if (gpgConfig == null) { gpgConfig = new GpgConfig(repo.getConfig()); } boolean doSign = isSigned() || gpgConfig.isSignAllTags(); if (!Boolean.TRUE.equals(annotated) && !doSign) { doSign = gpgConfig.isSignAnnotated(); } if (doSign) { if (signer == null) { signer = Signers.get(gpgConfig.getKeyFormat()); if (signer == null) { throw new UnsupportedSigningFormatException( MessageFormat.format( JGitText.get().signatureTypeUnknown, gpgConfig.getKeyFormat() .toConfigValue())); } } // The message of a signed tag must end in a newline because // the signature will be appended. if (message != null && !message.isEmpty() && !message.endsWith("\n")) { //$NON-NLS-1$ message += '\n'; } } } } } /** * Set the tag name. * * @param name * the tag name used for the {@code tag} * @return {@code this} */ public TagCommand setName(String name) { checkCallable(); this.name = name; return this; } /** * Get the tag name. * * @return the tag name used for the tag */ public String getName() { return name; } /** * Get the tag message. * * @return the tag message used for the tag */ public String getMessage() { return message; } /** * Set the tag message. * * @param message * the tag message used for the {@code tag} * @return {@code this} */ public TagCommand setMessage(String message) { checkCallable(); this.message = message; return this; } /** * Whether {@link #setSigned(boolean) setSigned(true)} has been called or * whether a {@link #setSigningKey(String) signing key ID} has been set; * i.e., whether -s or -u was specified explicitly. * * @return whether the tag is signed */ public boolean isSigned() { return Boolean.TRUE.equals(signed) || signingKey != null; } /** * If set to true the Tag command creates a signed tag object. This * corresponds to the parameter -s (--sign or --no-sign) on the command * line. *

* If {@code true}, the tag will be a signed annotated tag. *

* * @param signed * whether to sign * @return {@code this} */ public TagCommand setSigned(boolean signed) { checkCallable(); this.signed = Boolean.valueOf(signed); return this; } /** * Sets the {@link Signer} to use if the commit is to be signed. * * @param signer * to use; if {@code null}, the default signer will be used * @return {@code this} * @since 7.0 */ public TagCommand setSigner(Signer signer) { checkCallable(); this.signer = signer; return this; } /** * Sets an external {@link GpgConfig} to use. Whether it will be used is at * the discretion of the {@link #setSigner(Signer)}. * * @param config * to set; if {@code null}, the config will be loaded from the * git config of the repository * @return {@code this} * @since 5.11 */ public TagCommand setGpgConfig(GpgConfig config) { checkCallable(); this.gpgConfig = config; return this; } /** * Sets the tagger of the tag. If the tagger is null, a PersonIdent will be * created from the info in the repository. * * @param tagger * a {@link org.eclipse.jgit.lib.PersonIdent} object. * @return {@code this} */ public TagCommand setTagger(PersonIdent tagger) { checkCallable(); this.tagger = tagger; return this; } /** * Get the tagger who created the tag. * * @return the tagger of the tag */ public PersonIdent getTagger() { return tagger; } /** * Get the tag's object id * * @return the object id of the tag */ public RevObject getObjectId() { return id; } /** * Sets the object id of the tag. If the object id is {@code null}, the * commit pointed to from HEAD will be used. * * @param id * a {@link org.eclipse.jgit.revwalk.RevObject} object. * @return {@code this} */ public TagCommand setObjectId(RevObject id) { checkCallable(); this.id = id; return this; } /** * Whether this is a forced update * * @return is this a force update */ public boolean isForceUpdate() { return forceUpdate; } /** * If set to true the Tag command may replace an existing tag object. This * corresponds to the parameter -f on the command line. * * @param forceUpdate * whether this is a forced update * @return {@code this} */ public TagCommand setForceUpdate(boolean forceUpdate) { checkCallable(); this.forceUpdate = forceUpdate; return this; } /** * Configure this tag to be created as an annotated tag * * @param annotated * whether this shall be an annotated tag * @return {@code this} * @since 3.0 */ public TagCommand setAnnotated(boolean annotated) { checkCallable(); this.annotated = Boolean.valueOf(annotated); return this; } /** * Whether this will create an annotated tag. * * @return true if this command will create an annotated tag (default is * true) * @since 3.0 */ public boolean isAnnotated() { boolean setExplicitly = Boolean.TRUE.equals(annotated) || isSigned(); if (setExplicitly) { return true; } // Annotated at default (not set explicitly) return annotated == null; } /** * Sets the signing key. *

* Per spec of {@code user.signingKey}: this will be sent to the GPG program * as is, i.e. can be anything supported by the GPG program. *

*

* Note, if none was set or {@code null} is specified a default will be * obtained from the configuration. *

*

* If set to a non-{@code null} value, the tag will be a signed annotated * tag. *

* * @param signingKey * signing key; {@code null} allowed * @return {@code this} * @since 5.11 */ public TagCommand setSigningKey(String signingKey) { checkCallable(); this.signingKey = signingKey; return this; } /** * Retrieves the signing key ID. * * @return the key ID set, or {@code null} if none is set * @since 5.11 */ public String getSigningKey() { return signingKey; } /** * Sets a {@link CredentialsProvider} * * @param credentialsProvider * the provider to use when querying for credentials (eg., during * signing) * @return {@code this} * @since 5.11 */ public TagCommand setCredentialsProvider( CredentialsProvider credentialsProvider) { checkCallable(); this.credentialsProvider = credentialsProvider; return this; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4074 Content-Disposition: inline; filename="TransportCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "30f1bc9cc6915d8d3374d77493dddc6fd7a00f33" /* * Copyright (C) 2011, GitHub Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.Transport; /** * Base class for commands that use a * {@link org.eclipse.jgit.transport.Transport} during execution. *

* This class provides standard configuration of a transport for options such as * a {@link org.eclipse.jgit.transport.CredentialsProvider}, a timeout, and a * {@link org.eclipse.jgit.api.TransportConfigCallback}. * * @param * concrete type of this {@code GitCommand} * @param * the return type of the {@code GitCommand}'s {@code call()} method */ public abstract class TransportCommand extends GitCommand { /** * Configured credentials provider */ protected CredentialsProvider credentialsProvider; /** * Configured transport timeout */ protected int timeout; /** * Configured callback for transport configuration */ protected TransportConfigCallback transportConfigCallback; /** *

Constructor for TransportCommand.

* * @param repo a {@link org.eclipse.jgit.lib.Repository} object. */ protected TransportCommand(Repository repo) { super(repo); setCredentialsProvider(CredentialsProvider.getDefault()); } /** * Set the credentialsProvider. * * @param credentialsProvider * the {@link org.eclipse.jgit.transport.CredentialsProvider} to * use * @return {@code this} */ public C setCredentialsProvider( final CredentialsProvider credentialsProvider) { this.credentialsProvider = credentialsProvider; return self(); } /** * Set timeout. * * @param timeout * the timeout (in seconds) used for the transport step * @return {@code this} */ public C setTimeout(int timeout) { this.timeout = timeout; return self(); } /** * Set the TransportConfigCallback. * * @param transportConfigCallback * if set, the callback will be invoked after the * {@link org.eclipse.jgit.transport.Transport} has created, but * before the {@link org.eclipse.jgit.transport.Transport} is * used. The callback can use this opportunity to set additional * type-specific configuration on the * {@link org.eclipse.jgit.transport.Transport} instance. * @return {@code this} */ public C setTransportConfigCallback( final TransportConfigCallback transportConfigCallback) { this.transportConfigCallback = transportConfigCallback; return self(); } /** * Return this command cast to {@code C} * * @return {@code this} cast to {@code C} */ @SuppressWarnings("unchecked") protected final C self() { return (C) this; } /** * Configure transport with credentials provider, timeout, and config * callback * * @param transport * a {@link org.eclipse.jgit.transport.Transport} object. * @return {@code this} */ protected C configure(Transport transport) { if (credentialsProvider != null) transport.setCredentialsProvider(credentialsProvider); transport.setTimeout(timeout); if (transportConfigCallback != null) transportConfigCallback.configure(transport); return self(); } /** * Configure a child command with the current configuration set in * {@code this} command * * @param childCommand * a {@link org.eclipse.jgit.api.TransportCommand} object. * @return {@code this} */ protected C configure(TransportCommand childCommand) { childCommand.setCredentialsProvider(credentialsProvider); childCommand.setTimeout(timeout); childCommand.setTransportConfigCallback(transportConfigCallback); return self(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1369 Content-Disposition: inline; filename="TransportConfigCallback.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "889dc2f426a77d9a610e52ced1f60943b362ddbe" /* * Copyright (C) 2011, Roberto Tyley and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import org.eclipse.jgit.transport.Transport; /** * Receives a callback allowing type-specific configuration to be set * on the Transport instance after it's been created. *

* This allows consumers of the JGit command API to perform custom * configuration that would be difficult anticipate and expose on the * API command builders. *

* For instance, if a client needs to replace the SshSessionFactorys * on any SSHTransport used (eg to control available SSH identities), * they can set the TransportConfigCallback on the JGit API command - * once the transport has been created by the command, the callback * will be invoked and passed the transport instance, which the * client can then inspect and configure as necessary. */ public interface TransportConfigCallback { /** * Add any additional transport-specific configuration required. * * @param transport * a {@link org.eclipse.jgit.transport.Transport} object. */ void configure(Transport transport); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1162 Content-Disposition: inline; filename="VerificationResult.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "f5f4b06e4598ee9c17f1e90810ea6d0c9670a15f" /* * Copyright (C) 2021, Thomas Wolf and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import org.eclipse.jgit.lib.SignatureVerifier; import org.eclipse.jgit.revwalk.RevObject; /** * A {@code VerificationResult} describes the outcome of a signature * verification. * * @see VerifySignatureCommand * * @since 5.11 */ public interface VerificationResult { /** * If an error occurred during signature verification, this retrieves the * exception. * * @return the exception, or {@code null} if none occurred */ Throwable getException(); /** * Retrieves the signature verification result. * * @return the result, or {@code null} if none was computed * @since 7.0 */ SignatureVerifier.SignatureVerification getVerification(); /** * Retrieves the git object of which the signature was verified. * * @return the git object */ RevObject getObject(); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7074 Content-Disposition: inline; filename="VerifySignatureCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "487ff043237ebadbfb54bf8232534970a6f509d0" /* * Copyright (C) 2021, Thomas Wolf and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.api; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.ServiceUnavailableException; import org.eclipse.jgit.api.errors.WrongObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.GpgConfig; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.SignatureVerifier.SignatureVerification; import org.eclipse.jgit.lib.SignatureVerifiers; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; /** * A command to verify GPG signatures on tags or commits. * * @since 5.11 */ public class VerifySignatureCommand extends GitCommand> { /** * Describes what kind of objects shall be handled by a * {@link VerifySignatureCommand}. */ public enum VerifyMode { /** * Handle any object type, ignore anything that is not a commit or tag. */ ANY, /** * Handle only commits; throw a {@link WrongObjectTypeException} for * anything else. */ COMMITS, /** * Handle only tags; throw a {@link WrongObjectTypeException} for * anything else. */ TAGS } private final Set namesToCheck = new HashSet<>(); private VerifyMode mode = VerifyMode.ANY; private GpgConfig config; /** * Creates a new {@link VerifySignatureCommand} for the given {@link Repository}. * * @param repo * to operate on */ public VerifySignatureCommand(Repository repo) { super(repo); } /** * Add a name of an object (SHA-1, ref name; anything that can be * {@link Repository#resolve(String) resolved}) to the command to have its * signature verified. * * @param name * to add * @return {@code this} */ public VerifySignatureCommand addName(String name) { checkCallable(); namesToCheck.add(name); return this; } /** * Add names of objects (SHA-1, ref name; anything that can be * {@link Repository#resolve(String) resolved}) to the command to have their * signatures verified. * * @param names * to add; duplicates will be ignored * @return {@code this} */ public VerifySignatureCommand addNames(String... names) { checkCallable(); namesToCheck.addAll(Arrays.asList(names)); return this; } /** * Add names of objects (SHA-1, ref name; anything that can be * {@link Repository#resolve(String) resolved}) to the command to have their * signatures verified. * * @param names * to add; duplicates will be ignored * @return {@code this} */ public VerifySignatureCommand addNames(Collection names) { checkCallable(); namesToCheck.addAll(names); return this; } /** * Sets the mode of operation for this command. * * @param mode * the {@link VerifyMode} to set * @return {@code this} */ public VerifySignatureCommand setMode(@NonNull VerifyMode mode) { checkCallable(); this.mode = mode; return this; } /** * Sets an external {@link GpgConfig} to use. * * @param config * to set; if {@code null}, the config will be loaded from the * git config of the repository * @return {@code this} * @since 5.11 */ public VerifySignatureCommand setGpgConfig(GpgConfig config) { checkCallable(); this.config = config; return this; } /** * {@link Repository#resolve(String) Resolves} all names added to the * command to git objects and verifies their signature. Non-existing objects * are ignored. *

* Depending on the {@link #setMode(VerifyMode)}, only tags or commits or * any kind of objects are allowed. *

*

* Unsigned objects are silently skipped. *

* * @return a map of the given names to the corresponding * {@link VerificationResult}, excluding ignored or skipped objects. * @throws WrongObjectTypeException * if a name resolves to an object of a type not allowed by the * {@link #setMode(VerifyMode)} mode */ @Override @NonNull public Map call() throws ServiceUnavailableException, WrongObjectTypeException { checkCallable(); setCallable(false); Map result = new HashMap<>(); if (config == null) { config = new GpgConfig(repo.getConfig()); } try (RevWalk walk = new RevWalk(repo)) { for (String toCheck : namesToCheck) { ObjectId id = repo.resolve(toCheck); if (id != null && !ObjectId.zeroId().equals(id)) { RevObject object; try { object = walk.parseAny(id); } catch (MissingObjectException e) { continue; } VerificationResult verification = verifyOne(object); if (verification != null) { result.put(toCheck, verification); } } } } catch (IOException e) { throw new JGitInternalException( JGitText.get().signatureVerificationError, e); } return result; } private VerificationResult verifyOne(RevObject object) throws WrongObjectTypeException, IOException { int type = object.getType(); if (VerifyMode.TAGS.equals(mode) && type != Constants.OBJ_TAG) { throw new WrongObjectTypeException(object, Constants.OBJ_TAG); } else if (VerifyMode.COMMITS.equals(mode) && type != Constants.OBJ_COMMIT) { throw new WrongObjectTypeException(object, Constants.OBJ_COMMIT); } if (type == Constants.OBJ_COMMIT || type == Constants.OBJ_TAG) { try { SignatureVerification verification = SignatureVerifiers .verify(repo, config, object); if (verification == null) { // Not signed return null; } // Create new result return new Result(object, verification, null); } catch (JGitInternalException e) { return new Result(object, null, e); } } return null; } private static class Result implements VerificationResult { private final Throwable throwable; private final SignatureVerification verification; private final RevObject object; public Result(RevObject object, SignatureVerification verification, Throwable throwable) { this.object = object; this.verification = verification; this.throwable = throwable; } @Override public Throwable getException() { return throwable; } @Override public SignatureVerification getVerification() { return verification; } @Override public RevObject getObject() { return object; } } } Content-Type: text/plain; charset=UTF-8 Content-Length: 7074 Content-Disposition: inline; filename="VerifySignatureCommand.java" Last-Modified: Wed, 02 Jul 2025 12:20:13 GMT Expires: Wed, 02 Jul 2025 12:25:13 GMT ETag: "6661f1cc1b0be7d5baf2f4251e504621e1d36aec" /org.eclipse.jgit/src/org/eclipse/jgit/api/errors/

/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/