public void testNoPostOrder() throws Exception { final DirCache tree = db.readDirCache(); { final DirCacheBuilder b = tree.builder(); b.add(makeFile("a")); b.add(makeFile("b/c")); b.add(makeFile("b/d")); b.add(makeFile("q")); b.finish(); assertEquals(4, tree.getEntryCount()); } final TreeWalk tw = new TreeWalk(db); tw.reset(); tw.setPostOrderTraversal(false); tw.addTree(new DirCacheIterator(tree)); assertModes("a", REGULAR_FILE, tw); assertModes("b", TREE, tw); assertTrue(tw.isSubtree()); assertFalse(tw.isPostChildren()); tw.enterSubtree(); assertModes("b/c", REGULAR_FILE, tw); assertModes("b/d", REGULAR_FILE, tw); assertModes("q", REGULAR_FILE, tw); }
@Test public void testPathsResetWithUnmerged() throws Exception { setupRepository(); String file = "a.txt"; writeTrashFile(file, "data"); git.add().addFilepattern(file).call(); git.commit().setMessage("commit").call(); DirCache index = db.lockDirCache(); DirCacheBuilder builder = index.builder(); builder.add(createEntry(file, FileMode.REGULAR_FILE, 1, "")); builder.add(createEntry(file, FileMode.REGULAR_FILE, 2, "")); builder.add(createEntry(file, FileMode.REGULAR_FILE, 3, "")); builder.add(createEntry("b.txt", FileMode.REGULAR_FILE)); assertTrue(builder.commit()); assertEquals( "[a.txt, mode:100644, stage:1]" + "[a.txt, mode:100644, stage:2]" + "[a.txt, mode:100644, stage:3]" + "[b.txt, mode:100644]", indexState(0)); git.reset().addPath(file).call(); assertEquals("[a.txt, mode:100644]" + "[b.txt, mode:100644]", indexState(0)); }
/** * Creates an in-memory index of the ticket change. * * @param changeId * @param change * @return an in-memory index * @throws IOException */ private DirCache createIndex(Repository db, long ticketId, Change change) throws IOException, ClassNotFoundException, NoSuchFieldException { String ticketPath = toTicketPath(ticketId); DirCache newIndex = DirCache.newInCore(); DirCacheBuilder builder = newIndex.builder(); Set<String> ignorePaths = new TreeSet<String>(); try (ObjectInserter inserter = db.newObjectInserter()) { // create/update the journal // exclude the attachment content List<Change> changes = getJournal(db, ticketId); changes.add(change); String journal = TicketSerializer.serializeJournal(changes).trim(); byte[] journalBytes = journal.getBytes(Constants.ENCODING); String journalPath = ticketPath + "/" + JOURNAL; final DirCacheEntry journalEntry = new DirCacheEntry(journalPath); journalEntry.setLength(journalBytes.length); journalEntry.setLastModified(change.date.getTime()); journalEntry.setFileMode(FileMode.REGULAR_FILE); journalEntry.setObjectId( inserter.insert(org.eclipse.jgit.lib.Constants.OBJ_BLOB, journalBytes)); // add journal to index builder.add(journalEntry); ignorePaths.add(journalEntry.getPathString()); // Add any attachments to the index if (change.hasAttachments()) { for (Attachment attachment : change.attachments) { // build a path name for the attachment and mark as ignored String path = toAttachmentPath(ticketId, attachment.name); ignorePaths.add(path); // create an index entry for this attachment final DirCacheEntry entry = new DirCacheEntry(path); entry.setLength(attachment.content.length); entry.setLastModified(change.date.getTime()); entry.setFileMode(FileMode.REGULAR_FILE); // insert object entry.setObjectId( inserter.insert(org.eclipse.jgit.lib.Constants.OBJ_BLOB, attachment.content)); // add to temporary in-core index builder.add(entry); } } for (DirCacheEntry entry : getTreeEntries(db, ignorePaths)) { builder.add(entry); } // finish the index builder.finish(); } return newIndex; }
CommitBuilder(CommitBuilder prior) throws Exception { branch = prior.branch; DirCacheBuilder b = tree.builder(); for (int i = 0; i < prior.tree.getEntryCount(); i++) b.add(prior.tree.getEntry(i)); b.finish(); parents.add(prior.create()); }
public CommitBuilder parent(RevCommit p) throws Exception { if (parents.isEmpty()) { DirCacheBuilder b = tree.builder(); parseBody(p); b.addTree(new byte[0], DirCacheEntry.STAGE_0, pool.getObjectReader(), p.getTree()); b.finish(); } parents.add(p); return this; }
protected DirCache readTree(RevTree tree) throws IOException, MissingObjectException, IncorrectObjectTypeException { DirCache dc = DirCache.newInCore(); if (tree != null) { DirCacheBuilder b = dc.builder(); b.addTree(new byte[0], DirCacheEntry.STAGE_0, reader, tree); b.finish(); } return dc; }
/** * The resolve conflict way of three way merging * * @param baseTree * @param headTree * @param mergeTree * @param ignoreConflicts Controls what to do in case a content-merge is done and a conflict is * detected. The default setting for this should be <code>false</code>. In this case the * working tree file is filled with new content (containing conflict markers) and the index is * filled with multiple stages containing BASE, OURS and THEIRS content. Having such non-0 * stages is the sign to git tools that there are still conflicts for that path. * <p>If <code>true</code> is specified the behavior is different. In case a conflict is * detected the working tree file is again filled with new content (containing conflict * markers). But also stage 0 of the index is filled with that content. No other stages are * filled. Means: there is no conflict on that path but the new content (including conflict * markers) is stored as successful merge result. This is needed in the context of {@link * RecursiveMerger} where when determining merge bases we don't want to deal with * content-merge conflicts. * @return whether the trees merged cleanly * @throws IOException * @since 3.5 */ protected boolean mergeTrees( AbstractTreeIterator baseTree, RevTree headTree, RevTree mergeTree, boolean ignoreConflicts) throws IOException { builder = dircache.builder(); DirCacheBuildIterator buildIt = new DirCacheBuildIterator(builder); tw = new NameConflictTreeWalk(reader); tw.addTree(baseTree); tw.addTree(headTree); tw.addTree(mergeTree); tw.addTree(buildIt); if (workingTreeIterator != null) { tw.addTree(workingTreeIterator); } else { tw.setFilter(TreeFilter.ANY_DIFF); } if (!mergeTreeWalk(tw, ignoreConflicts)) { return false; } if (!inCore) { // No problem found. The only thing left to be done is to // checkout all files from "theirs" which have been selected to // go into the new index. checkout(); // All content-merges are successfully done. If we can now write the // new index we are on quite safe ground. Even if the checkout of // files coming from "theirs" fails the user can work around such // failures by checking out the index again. if (!builder.commit()) { cleanUp(); throw new IndexWriteException(); } builder = null; } else { builder.finish(); builder = null; } if (getUnmergedPaths().isEmpty() && !failed()) { resultTree = dircache.writeTree(getObjectInserter()); return true; } else { resultTree = null; return false; } }
private static DirCache readTree(final Repository pdb, final Ref branch) throws MissingObjectException, IncorrectObjectTypeException, IOException { try (RevWalk rw = new RevWalk(pdb)) { final DirCache dc = DirCache.newInCore(); final DirCacheBuilder b = dc.builder(); b.addTree( new byte[0], // no prefix path DirCacheEntry.STAGE_0, // standard stage pdb.newObjectReader(), rw.parseTree(branch.getObjectId())); b.finish(); return dc; } }
/** * Construct a tree from a specific listing of file entries. * * @param entries the files to include in the tree. The collection does not need to be sorted * properly and may be empty. * @return reference to the tree specified by the entry list. * @throws Exception */ public RevTree tree(final DirCacheEntry... entries) throws Exception { final DirCache dc = DirCache.newInCore(); final DirCacheBuilder b = dc.builder(); for (final DirCacheEntry e : entries) b.add(e); b.finish(); ObjectId root; try { root = dc.writeTree(inserter); inserter.flush(); } finally { inserter.release(); } return pool.lookupTree(root); }
private void verifyStageState(StageState expected, int... stages) throws IOException { DirCacheBuilder builder = db.lockDirCache().builder(); for (int stage : stages) { DirCacheEntry entry = createEntry("a", FileMode.REGULAR_FILE, stage, "content"); builder.add(entry); } builder.commit(); IndexDiff diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); diff.diff(); assertEquals( "Conflict for entries in stages " + Arrays.toString(stages), expected, diff.getConflictingStageStates().get("a")); }
@Test public void testStageState_simulated_bug() throws Exception { try (Git git = new Git(db)) { writeTrashFile("a", "content"); git.add().addFilepattern("a").call(); RevCommit initialCommit = git.commit().setMessage("initial commit").call(); // create branch and add a new file final String branchName = Constants.R_HEADS + "branch"; createBranch(initialCommit, branchName); checkoutBranch(branchName); writeTrashFile("b", "second file content - branch"); git.add().addFilepattern("b").call(); git.commit().setMessage("branch commit").call(); // checkout master and add the same new file checkoutBranch(Constants.R_HEADS + Constants.MASTER); writeTrashFile("b", "second file content - master"); git.add().addFilepattern("b").call(); git.commit().setMessage("master commit").call(); // Simulate a failed merge of branch into master DirCacheBuilder builder = db.lockDirCache().builder(); DirCacheEntry entry = createEntry("a", FileMode.REGULAR_FILE, 0, "content"); builder.add(entry); entry = createEntry("b", FileMode.REGULAR_FILE, 2, "second file content - master"); builder.add(entry); entry = createEntry("b", FileMode.REGULAR_FILE, 3, "second file content - branch"); builder.add(entry); builder.commit(); FileTreeIterator iterator = new FileTreeIterator(db); IndexDiff diff = new IndexDiff(db, Constants.HEAD, iterator); diff.diff(); assertTrue(diff.getChanged().isEmpty()); assertTrue(diff.getAdded().isEmpty()); assertTrue(diff.getRemoved().isEmpty()); assertTrue(diff.getMissing().isEmpty()); assertTrue(diff.getModified().isEmpty()); assertEquals(1, diff.getConflicting().size()); assertTrue(diff.getConflicting().contains("b")); assertEquals(StageState.BOTH_ADDED, diff.getConflictingStageStates().get("b")); assertTrue(diff.getUntrackedFolders().isEmpty()); } }
/** * adds a entry to the index builder which is a copy of the specified DirCacheEntry * * @param e the entry which should be copied * @return the entry which was added to the index */ private DirCacheEntry keep(DirCacheEntry e) { DirCacheEntry newEntry = new DirCacheEntry(e.getPathString(), e.getStage()); newEntry.setFileMode(e.getFileMode()); newEntry.setObjectId(e.getObjectId()); newEntry.setLastModified(e.getLastModified()); newEntry.setLength(e.getLength()); builder.add(newEntry); return newEntry; }
/** * Writes a file to the tickets branch. * * @param db * @param file * @param content * @param createdBy * @param msg */ private void writeTicketsFile( Repository db, String file, String content, String createdBy, String msg) { if (getTicketsBranch(db) == null) { createTicketsBranch(db); } DirCache newIndex = DirCache.newInCore(); DirCacheBuilder builder = newIndex.builder(); try (ObjectInserter inserter = db.newObjectInserter()) { // create an index entry for the revised index final DirCacheEntry idIndexEntry = new DirCacheEntry(file); idIndexEntry.setLength(content.length()); idIndexEntry.setLastModified(System.currentTimeMillis()); idIndexEntry.setFileMode(FileMode.REGULAR_FILE); // insert new ticket index idIndexEntry.setObjectId( inserter.insert( org.eclipse.jgit.lib.Constants.OBJ_BLOB, content.getBytes(Constants.ENCODING))); // add to temporary in-core index builder.add(idIndexEntry); Set<String> ignorePaths = new HashSet<String>(); ignorePaths.add(file); for (DirCacheEntry entry : getTreeEntries(db, ignorePaths)) { builder.add(entry); } // finish temporary in-core index used for this commit builder.finish(); // commit the change commitIndex(db, newIndex, createdBy, msg); } catch (ConcurrentRefUpdateException e) { log.error("", e); } catch (IOException e) { log.error("", e); } }
/** * adds a new path with the specified stage to the index builder * * @param path * @param p * @param stage * @param lastMod * @param len * @return the entry which was added to the index */ private DirCacheEntry add(byte[] path, CanonicalTreeParser p, int stage, long lastMod, long len) { if (p != null && !p.getEntryFileMode().equals(FileMode.TREE)) { DirCacheEntry e = new DirCacheEntry(path, stage); e.setFileMode(p.getEntryFileMode()); e.setObjectId(p.getEntryObjectId()); e.setLastModified(lastMod); e.setLength(len); builder.add(e); return e; } return null; }
/** * Updates the index after a content merge has happened. If no conflict has occurred this includes * persisting the merged content to the object database. In case of conflicts this method takes * care to write the correct stages to the index. * * @param base * @param ours * @param theirs * @param result * @throws FileNotFoundException * @throws IOException */ private void updateIndex( CanonicalTreeParser base, CanonicalTreeParser ours, CanonicalTreeParser theirs, MergeResult<RawText> result) throws FileNotFoundException, IOException { File mergedFile = !inCore ? writeMergedFile(result) : null; if (result.containsConflicts()) { // A conflict occurred, the file will contain conflict markers // the index will be populated with the three stages and the // workdir (if used) contains the halfway merged content. add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); mergeResults.put(tw.getPathString(), result); return; } // No conflict occurred, the file will contain fully merged content. // The index will be populated with the new merged version. DirCacheEntry dce = new DirCacheEntry(tw.getPathString()); // Set the mode for the new content. Fall back to REGULAR_FILE if // we can't merge modes of OURS and THEIRS. int newMode = mergeFileModes(tw.getRawMode(0), tw.getRawMode(1), tw.getRawMode(2)); dce.setFileMode( newMode == FileMode.MISSING.getBits() ? FileMode.REGULAR_FILE : FileMode.fromBits(newMode)); if (mergedFile != null) { long len = mergedFile.length(); dce.setLastModified(mergedFile.lastModified()); dce.setLength((int) len); InputStream is = new FileInputStream(mergedFile); try { dce.setObjectId(getObjectInserter().insert(OBJ_BLOB, len, is)); } finally { is.close(); } } else dce.setObjectId(insertMergeResult(result)); builder.add(dce); }
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.getEntryLastModified()); 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(); InputStream inputStream = fTree.openEntryStream(); try { dcEntry.setObjectId( inserter.insert(Constants.OBJ_BLOB, contentLength, inputStream)); } finally { inputStream.close(); } } } // 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) throw new JGitInternalException(JGitText.get().emptyCommit); // update index existingBuilder.commit(); // finish temporary in-core index used for this commit tempBuilder.finish(); return inCoreIndex; }
/** * Deletes a ticket from the repository. * * @param ticket * @return true if successful */ @Override protected synchronized boolean deleteTicketImpl( RepositoryModel repository, TicketModel ticket, String deletedBy) { if (ticket == null) { throw new RuntimeException("must specify a ticket!"); } boolean success = false; Repository db = repositoryManager.getRepository(ticket.repository); try { RefModel ticketsBranch = getTicketsBranch(db); if (ticketsBranch == null) { throw new RuntimeException(BRANCH + " does not exist!"); } String ticketPath = toTicketPath(ticket.number); try { ObjectId treeId = db.resolve(BRANCH + "^{tree}"); // Create the in-memory index of the new/updated ticket DirCache index = DirCache.newInCore(); DirCacheBuilder builder = index.builder(); // Traverse HEAD to add all other paths try (TreeWalk treeWalk = new TreeWalk(db)) { int hIdx = -1; if (treeId != null) { hIdx = treeWalk.addTree(treeId); } treeWalk.setRecursive(true); while (treeWalk.next()) { String path = treeWalk.getPathString(); CanonicalTreeParser hTree = null; if (hIdx != -1) { hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class); } if (!path.startsWith(ticketPath)) { // add entries from HEAD for all other paths if (hTree != null) { final DirCacheEntry entry = new DirCacheEntry(path); entry.setObjectId(hTree.getEntryObjectId()); entry.setFileMode(hTree.getEntryFileMode()); // add to temporary in-core index builder.add(entry); } } } } // finish temporary in-core index used for this commit builder.finish(); success = commitIndex(db, index, deletedBy, "- " + ticket.number); } catch (Throwable t) { log.error( MessageFormat.format( "Failed to delete ticket {0,number,0} from {1}", ticket.number, db.getDirectory()), t); } } finally { db.close(); } return success; }