Index: subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/Status.java =================================================================== --- subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/Status.java (revision 21889) +++ subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/Status.java (working copy) @@ -638,6 +638,16 @@ } /** + * @return The node kind of the last commit, or {@link NodeKind#none} + * if up to date. + * @since 1.5 + */ + public int getReposLastCmtKind() + { + return reposKind; + } + + /** * class for kind status of the item or its properties * the constants are defined in the interface StatusKind for building * reasons Index: subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/BasicTests.java =================================================================== --- subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/BasicTests.java (revision 21889) +++ subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/BasicTests.java (working copy) @@ -105,6 +105,370 @@ } /** + * test the out of date SVNClient.status functionality + * @throws Throwable + */ + public void testOODStatus() throws Throwable + { + // build the test setup + OneTest thisTest = new OneTest(); + try + { + // obstructed checkout must fail + client.checkout(thisTest.getUrl() + "/A", thisTest.getWCPath(), + null, true); + fail("missing exception"); + } + catch (ClientException expected) + { + } + + /* Make a whole slew of changes to a WC: + * + * (root) r7 - prop change + * iota + * A + * |__mu + * | + * |__B + * | |__lambda + * | | + * | |__E r12 - deleted + * | | |__alpha + * | | |__beta + * | | + * | |__F r9 - prop change + * | |__I r6 - added dir + * | + * |__C r5 - deleted + * | + * |__D + * |__gamma + * | + * |__G + * | |__pi r3 - deleted + * | |__rho r2 - modify text + * | |__tau r4 - modify text + * | + * |__H + * |__chi r10-11 replaced with file + * |__psi r13-14 replaced with dir + * |__omega + * |__nu r8 - added file + */ + File file, dir; + PrintWriter pw; + Status status; + long rev; // Resulting rev from co or update + long revCounter = 2; // Keeps track of the latest rev committed + + // ----- r2: modify file A/D/G/rho -------------------------- + file = new File(thisTest.getWorkingCopy(), "A/D/G/rho"); + pw = new PrintWriter(new FileOutputStream(file, true)); + pw.print("modification to rho"); + pw.close(); + addExpectedCommitItem(thisTest.getWCPath(), + thisTest.getUrl(), "A/D/G/rho", NodeKind.file, + CommitItemStateFlags.TextMods); + assertEquals("wrong revision number from commit", + rev = client.commit(new String[]{thisTest.getWCPath()}, + "log msg", true), revCounter++); + thisTest.getWc().setItemWorkingCopyRevision("A/D/G/rho", rev); + thisTest.getWc().setItemContent("A/D/G/rho", + thisTest.getWc().getItemContent("A/D/G/rho") + + "modification to rho"); + + status = client.singleStatus(thisTest.getWCPath() + "/A/D/G/rho", + false); + long rhoCommitDate = status.getLastChangedDate().getTime(); + long rhoCommitRev = rev; + String rhoAuthor = status.getLastCommitAuthor(); + + // ----- r3: delete file A/D/G/pi --------------------------- + client.remove(new String[]{thisTest.getWCPath() + "/A/D/G/pi"}, null, + false); + addExpectedCommitItem(thisTest.getWCPath(), + thisTest.getUrl(), "A/D/G/pi", NodeKind.file, + CommitItemStateFlags.Delete); + assertEquals("wrong revision number from commit", + rev = client.commit(new String[]{thisTest.getWCPath()}, + "log msg", true), revCounter++); + thisTest.getWc().removeItem("A/D/G/pi"); + + thisTest.getWc().setItemWorkingCopyRevision("A/D/G", rev); + assertEquals("wrong revision from update", + client.update(thisTest.getWCPath() + "/A/D/G", null, true), + rev); + Info Ginfo = client.info(thisTest.getWCPath() + "/A/D/G"); + long GCommitRev = rev; + + // ----- r4: modify file A/D/G/tau -------------------------- + file = new File(thisTest.getWorkingCopy(), "A/D/G/tau"); + pw = new PrintWriter(new FileOutputStream(file, true)); + pw.print("modification to tau"); + pw.close(); + addExpectedCommitItem(thisTest.getWCPath(), + thisTest.getUrl(), "A/D/G/tau",NodeKind.file, + CommitItemStateFlags.TextMods); + assertEquals("wrong revision number from commit", + rev = client.commit(new String[]{thisTest.getWCPath()}, + "log msg", true), revCounter++); + thisTest.getWc().setItemWorkingCopyRevision("A/D/G/tau", rev); + thisTest.getWc().setItemContent("A/D/G/tau", + thisTest.getWc().getItemContent("A/D/G/tau") + + "modification to tau"); + status = client.singleStatus(thisTest.getWCPath() + "/A/D/G/tau", + false); + long tauCommitDate = status.getLastChangedDate().getTime(); + long tauCommitRev = rev; + String tauAuthor = status.getLastCommitAuthor(); + + // ----- r5: delete dir with no children A/C --------------- + client.remove(new String[]{thisTest.getWCPath() + "/A/C"}, null, + false); + addExpectedCommitItem(thisTest.getWCPath(), + thisTest.getUrl(), "A/C", NodeKind.dir, + CommitItemStateFlags.Delete); + assertEquals("wrong revision number from commit", + rev = client.commit(new String[]{thisTest.getWCPath()}, + "log msg", true), revCounter++); + thisTest.getWc().removeItem("A/C"); + Info Cinfo = client.info(thisTest.getWCPath() + "/A/B"); + long CCommitRev = rev; + + // ----- r6: Add dir A/B/I ---------------------------------- + dir = new File(thisTest.getWorkingCopy(), "A/B/I"); + dir.mkdir(); + + client.add(dir.getAbsolutePath(), true); + addExpectedCommitItem(thisTest.getWCPath(), + thisTest.getUrl(), "A/B/I", NodeKind.dir, + CommitItemStateFlags.Add); + assertEquals("wrong revision number from commit", + rev = client.commit(new String[]{thisTest.getWCPath()}, + "log msg", true), revCounter++); + thisTest.getWc().addItem("A/B/I", null); + status = client.singleStatus(thisTest.getWCPath() + "/A/B/I", + false); + long ICommitDate = status.getLastChangedDate().getTime(); + long ICommitRev = rev; + String IAuthor = status.getLastCommitAuthor(); + + // ----- r7: Update then commit prop change on root dir ----- + thisTest.getWc().setAllWorkingCopyRevision(rev); + assertEquals("wrong revision from update", + client.update(thisTest.getWCPath(), null, true), rev); + thisTest.checkStatus(); + client.propertySet(thisTest.getWCPath(), "propname", "propval", + false); + thisTest.getWc().setItemPropStatus("", Status.Kind.modified); + addExpectedCommitItem(thisTest.getWCPath(), + thisTest.getUrl(), null, NodeKind.dir, + CommitItemStateFlags.PropMods); + assertEquals("wrong revision number from commit", + rev = client.commit(new String[]{thisTest.getWCPath()}, + "log msg", true), revCounter++); + thisTest.getWc().setItemWorkingCopyRevision("", rev); + thisTest.getWc().setItemPropStatus("", Status.Kind.normal); + + // ----- r8: Add a file A/D/H/nu ---------------------------- + file = new File(thisTest.getWorkingCopy(), "A/D/H/nu"); + pw = new PrintWriter(new FileOutputStream(file)); + pw.print("This is the file 'nu'."); + pw.close(); + client.add(file.getAbsolutePath(), false); + addExpectedCommitItem(thisTest.getWCPath(), + thisTest.getUrl(), "A/D/H/nu", NodeKind.file, + CommitItemStateFlags.TextMods + CommitItemStateFlags.Add); + assertEquals("wrong revision number from commit", + rev = client.commit(new String[]{thisTest.getWCPath()}, + "log msg", true), revCounter++); + thisTest.getWc().addItem("A/D/H/nu", "This is the file 'nu'."); + status = client.singleStatus(thisTest.getWCPath() + "/A/D/H/nu", false); + long nuCommitDate = status.getLastChangedDate().getTime(); + long nuCommitRev = rev; + String nuAuthor = status.getLastCommitAuthor(); + + // ----- r9: Prop change on A/B/F --------------------------- + client.propertySet(thisTest.getWCPath() + "/A/B/F", "propname", + "propval", false); + addExpectedCommitItem(thisTest.getWCPath(), + thisTest.getUrl(), "A/B/F", NodeKind.dir, + CommitItemStateFlags.PropMods); + assertEquals("wrong revision number from commit", + rev = client.commit(new String[]{thisTest.getWCPath()}, + "log msg", true), revCounter++); + thisTest.getWc().setItemPropStatus("A/B/F", Status.Kind.normal); + thisTest.getWc().setItemWorkingCopyRevision("A/B/F", rev); + status = client.singleStatus(thisTest.getWCPath() + "/A/B/F", false); + long FCommitDate = status.getLastChangedDate().getTime(); + long FCommitRev = rev; + String FAuthor = status.getLastCommitAuthor(); + + // ----- r10-11: Replace file A/D/H/chi with file ----------- + client.remove(new String[]{thisTest.getWCPath() + "/A/D/H/chi"}, null, + false); + addExpectedCommitItem(thisTest.getWCPath(), + thisTest.getUrl(), "A/D/H/chi", NodeKind.file, + CommitItemStateFlags.Delete); + assertEquals("wrong revision number from commit", + rev = client.commit(new String[]{thisTest.getWCPath()}, + "log msg", true), revCounter++); + thisTest.getWc().removeItem("A/D/G/pi"); + + file = new File(thisTest.getWorkingCopy(), "A/D/H/chi"); + pw = new PrintWriter(new FileOutputStream(file)); + pw.print("This is the replacement file 'chi'."); + pw.close(); + client.add(file.getAbsolutePath(), false); + addExpectedCommitItem(thisTest.getWCPath(), + thisTest.getUrl(), "A/D/H/chi", NodeKind.file, + CommitItemStateFlags.TextMods + CommitItemStateFlags.Add); + assertEquals("wrong revision number from commit", + rev = client.commit(new String[]{thisTest.getWCPath()}, + "log msg", true), revCounter++); + thisTest.getWc().addItem("A/D/H/chi", + "This is the replacement file 'chi'."); + status = client.singleStatus(thisTest.getWCPath() + "/A/D/H/chi", + false); + long chiCommitDate = status.getLastChangedDate().getTime(); + long chiCommitRev = rev; + String chiAuthor = status.getLastCommitAuthor(); + + // ----- r12: Delete dir A/B/E with children ---------------- + client.remove(new String[]{thisTest.getWCPath() + "/A/B/E"}, null, + false); + addExpectedCommitItem(thisTest.getWCPath(), + thisTest.getUrl(), "A/B/E", NodeKind.dir, + CommitItemStateFlags.Delete); + assertEquals("wrong revision number from commit", + rev = client.commit(new String[]{thisTest.getWCPath()}, + "log msg", true), revCounter++); + thisTest.getWc().removeItem("A/B/E/alpha"); + thisTest.getWc().removeItem("A/B/E/beta"); + thisTest.getWc().removeItem("A/B/E"); + + thisTest.getWc().setItemWorkingCopyRevision("A/B", rev); + assertEquals("wrong revision from update", + client.update(thisTest.getWCPath() + "/A/B", null, true), rev); + Info Binfo = client.info(thisTest.getWCPath() + "/A/B"); + long BCommitDate = Binfo.getLastChangedDate().getTime(); + long BCommitRev = rev; + long ECommitRev = BCommitRev; + String BAuthor = Binfo.getAuthor(); + + // ----- r13-14: Replace file A/D/H/psi with dir ------------ + client.remove(new String[]{thisTest.getWCPath() + "/A/D/H/psi"}, null, + false); + addExpectedCommitItem(thisTest.getWCPath(), + thisTest.getUrl(), "A/D/H/psi", NodeKind.file, + CommitItemStateFlags.Delete); + assertEquals("wrong revision number from commit", + rev = client.commit(new String[]{thisTest.getWCPath()}, + "log msg", true), revCounter++); + thisTest.getWc().removeItem("A/D/H/psi"); + thisTest.getWc().setAllWorkingCopyRevision(rev); + assertEquals("wrong revision from update", + client.update(thisTest.getWCPath(), null, true), rev); + thisTest.getWc().addItem("A/D/H/psi", null); + dir = new File(thisTest.getWorkingCopy(), "A/D/H/psi"); + dir.mkdir(); + client.add(dir.getAbsolutePath(), true); + addExpectedCommitItem(thisTest.getWCPath(), + thisTest.getUrl(), "A/D/H/psi", NodeKind.dir, + CommitItemStateFlags.Add); + assertEquals("wrong revision number from commit", + rev = client.commit(new String[]{thisTest.getWCPath()}, + "log msg", true), revCounter++); + status = client.singleStatus(thisTest.getWCPath() + "/A/D/H/psi", false); + long psiCommitDate = status.getLastChangedDate().getTime(); + long psiCommitRev = rev; + String psiAuthor = status.getLastCommitAuthor(); + + // ----- Check status of modfied WC then update it back + // ----- to rev 1 so it's out of date + thisTest.checkStatus(); + + assertEquals("wrong revision from update", + client.update(thisTest.getWCPath(), Revision.getInstance(1), + true), 1); + thisTest.getWc().setAllWorkingCopyRevision(1); + + thisTest.getWc().setItemOODInfo("A", psiCommitRev, psiAuthor, + psiCommitDate, NodeKind.dir); + + thisTest.getWc().setItemOODInfo("A/B", BCommitRev, BAuthor, + BCommitDate, NodeKind.dir); + + + thisTest.getWc().addItem("A/B/I", null); + thisTest.getWc().setItemOODInfo("A/B/I", ICommitRev, IAuthor, + ICommitDate, NodeKind.dir); + thisTest.getWc().setItemTextStatus("A/B/I", Status.Kind.none); + thisTest.getWc().setItemNodeKind("A/B/I", NodeKind.unknown); + + thisTest.getWc().addItem("A/C", null); + thisTest.getWc().setItemReposLastCmtRevision("A/C", CCommitRev); + thisTest.getWc().setItemReposKind("A/C", NodeKind.dir); + + thisTest.getWc().addItem("A/B/E", null); + thisTest.getWc().setItemReposLastCmtRevision("A/B/E", ECommitRev); + thisTest.getWc().setItemReposKind("A/B/E", NodeKind.dir); + thisTest.getWc().addItem("A/B/E/alpha", "This is the file 'alpha'."); + thisTest.getWc().addItem("A/B/E/beta", "This is the file 'beta'."); + + thisTest.getWc().setItemPropStatus("A/B/F", Status.Kind.none); + thisTest.getWc().setItemOODInfo("A/B/F", FCommitRev, FAuthor, + FCommitDate, NodeKind.dir); + + thisTest.getWc().setItemOODInfo("A/D", psiCommitRev, psiAuthor, + psiCommitDate, NodeKind.dir); + + thisTest.getWc().setItemOODInfo("A/D/G", tauCommitRev, tauAuthor, + tauCommitDate, NodeKind.dir); + + thisTest.getWc().addItem("A/D/G/pi", "This is the file 'pi'."); + thisTest.getWc().setItemReposLastCmtRevision("A/D/G/pi", GCommitRev); + thisTest.getWc().setItemReposKind("A/D/G/pi", NodeKind.file); + + thisTest.getWc().setItemContent("A/D/G/rho", + "This is the file 'rho'."); + thisTest.getWc().setItemOODInfo("A/D/G/rho", rhoCommitRev, rhoAuthor, + rhoCommitDate, NodeKind.file); + + thisTest.getWc().setItemContent("A/D/G/tau", + "This is the file 'tau'."); + thisTest.getWc().setItemOODInfo("A/D/G/tau", tauCommitRev, tauAuthor, + tauCommitDate, NodeKind.file); + + thisTest.getWc().setItemOODInfo("A/D/H", psiCommitRev, psiAuthor, + psiCommitDate, NodeKind.dir); + + thisTest.getWc().setItemWorkingCopyRevision("A/D/H/nu", + Revision.SVN_INVALID_REVNUM); + thisTest.getWc().setItemTextStatus("A/D/H/nu", Status.Kind.none); + thisTest.getWc().setItemNodeKind("A/D/H/nu", NodeKind.unknown); + thisTest.getWc().setItemOODInfo("A/D/H/nu", nuCommitRev, nuAuthor, + nuCommitDate, NodeKind.file); + + thisTest.getWc().setItemContent("A/D/H/chi", + "This is the file 'chi'."); + thisTest.getWc().setItemOODInfo("A/D/H/chi", chiCommitRev, chiAuthor, + chiCommitDate, NodeKind.file); + + thisTest.getWc().removeItem("A/D/H/psi"); + thisTest.getWc().addItem("A/D/H/psi", "This is the file 'psi'."); + thisTest.getWc().setItemOODInfo("A/D/H/psi", psiCommitRev, psiAuthor, + psiCommitDate, NodeKind.dir); // Yes a dir, psi was replaced + + thisTest.getWc().setItemPropStatus("", Status.Kind.none); + thisTest.getWc().setItemOODInfo("", psiCommitRev, psiAuthor, + psiCommitDate, NodeKind.dir); + + thisTest.checkStatus(true); + } + + /** * test the basic SVNClient.checkout functionality * @throws Throwable */ Index: subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/SVNTests.java =================================================================== --- subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/SVNTests.java (revision 21889) +++ subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/SVNTests.java (working copy) @@ -663,9 +663,23 @@ public void checkStatus() throws SubversionException, IOException { + checkStatus(false); + } + + /** + * Check if the working copy has the expected status + * @param checkServer Check server out of date info if true + * @exception SubversionException If there's a problem getting + * WC status. + * @exception IOException If there's a problem comparing the + * WC to the expected state. + */ + public void checkStatus(boolean checkServer) + throws SubversionException, IOException + { Status[] states = client.status(workingCopy.getAbsolutePath(), - true, false, true, true); - wc.check(states, workingCopy.getAbsolutePath()); + true, checkServer, true, true); + wc.check(states, workingCopy.getAbsolutePath(), checkServer); } } Index: subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/WC.java =================================================================== --- subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/WC.java (revision 21889) +++ subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/WC.java (working copy) @@ -18,6 +18,7 @@ * @endcopyright */ +import org.tigris.subversion.javahl.Revision; import org.tigris.subversion.javahl.Status; import org.tigris.subversion.javahl.NodeKind; import org.tigris.subversion.javahl.DirEntry; @@ -26,6 +27,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.Date; import junit.framework.Assert; /** @@ -134,6 +136,21 @@ } /** + * Set the revision number of all paths in a working copy + * @param revision the new revision number + */ + public void setAllWorkingCopyRevision(long revision) + { + Iterator it = this.items.values().iterator(); + + while (it.hasNext()) + { + Item item = (Item) it.next(); + item.workingCopyRev = revision; + } + } + + /** * Returns the file content of the item at a path * @param path the path, where the content is retrieved * @return the content of the file @@ -204,6 +221,65 @@ } /** + * Set the youngest committed revision of an out of date item at path + * @param path the path where the repos last revision is set + * @param revision the repos last revision number + */ + public void setItemReposLastCmtRevision(String path, long revision) + { + ((Item) items.get(path)).reposLastCmtRevision = revision; + } + + /** + * Set the youngest committed revision of an out of date item at path + * @param path the path where the repos last author is set + * @param revision the respos last author + */ + public void setItemReposLastCmtAuthor(String path, String author) + { + ((Item) items.get(path)).reposLastCmtAuthor = author; + } + + /** + * Set the youngest committed revision of an out of date item at path + * @param path the path where the repos last date is set + * @param date the repos last date + */ + public void setItemReposLastCmtDate(String path, long date) + { + ((Item) items.get(path)).reposLastCmtDate = date; + } + + /** + * Set the youngest committed node kind of an out of date item at path + * @param path the path where the repos node kind is set + * @param nodeKind the repos last node kind + */ + public void setItemReposKind(String path, int nodeKind) + { + ((Item) items.get(path)).reposKind = nodeKind; + } + + /** + * Set the youngest committed rev, author, date, and node kind of an + * out of date item at path + * @param path the path where the repos info is set + * @param revision the repos last revision number + * @param revision the respos last author + * @param date the repos last date + * @param nodeKind the repos last node kind + */ + + public void setItemOODInfo(String path, long revision, String author, + long date, int nodeKind) + { + this.setItemReposLastCmtRevision(path, revision); + this.setItemReposLastCmtAuthor(path, author); + this.setItemReposLastCmtDate(path, date); + this.setItemReposKind(path, nodeKind); + } + + /** * Copy an expected working copy state * @return the copy of the exiting object */ @@ -322,6 +398,20 @@ void check(Status[] tested, String workingCopyPath) throws IOException { + check(tested, workingCopyPath, false); + } + + /** + * Check the result of a SVNClient.status versus the expected state + * @param tested the result to be tested + * @param workingCopyPath the path of the working copy + * @param checkServer compare out of date statii if true + * @exception IOException If there is a problem finding or reading + * the WC. + */ + void check(Status[] tested, String workingCopyPath, boolean checkServer) + throws IOException + { // clear the touched flag of all items Iterator it = items.values().iterator(); while (it.hasNext()) @@ -392,6 +482,32 @@ tested[i].getNodeKind(), item.nodeKind == -1 ? NodeKind.dir : item.nodeKind); } + if (checkServer) + { + Assert.assertEquals("Last commit revisions for OOD path '" + + item.myPath + "' don't match:", + item.reposLastCmtRevision, + tested[i].getReposLastCmtRevisionNumber()); + Assert.assertEquals("Last commit kinds for OOD path '" + + item.myPath + "' don't match:", + item.reposKind, + tested[i].getReposKind()); + + // Only the last committed rev and kind is available for + // paths deleted in the repos. + if (tested[i].getRepositoryTextStatus() != Status.Kind.deleted) + { + Assert.assertEquals("Last commit dates for OOD path '" + + item.myPath + "' don't match:", + new Date(item.reposLastCmtDate), + new Date(tested[i].getReposLastCmtDate() == null + ? 0 : tested[i].getReposLastCmtDate().getTime())); + Assert.assertEquals("Last commit authors for OOD path '" + + item.myPath + "' don't match:", + item.reposLastCmtAuthor, + tested[i].getReposLastCmtAuthor()); + } + } item.touched = true; } @@ -451,6 +567,22 @@ * expected switched status */ boolean isSwitched; + /** + * youngest committed revision on repos if out of date + */ + long reposLastCmtRevision = Revision.SVN_INVALID_REVNUM; + /** + * most recent commit date on repos if out of date + */ + long reposLastCmtDate = 0; + /** + * node kind of the youngest commit if out of date + */ + int reposKind = NodeKind.none; + /** + * author of the youngest commit if out of date. + */ + String reposLastCmtAuthor; /** * create a new item Index: subversion/include/svn_repos.h =================================================================== --- subversion/include/svn_repos.h (revision 21889) +++ subversion/include/svn_repos.h (working copy) @@ -835,6 +835,24 @@ apr_pool_t *pool); +/** + * Set @a *deleted to the revision @a path was most recently deleted + * in @a fs, within the inclusive revision bounds set by @a start and + * @a end. If @a path does not exist in @a root within the @a start and + * @a end bounds, or is not deleted within those bounds, set @a *deleted + * to SVN_INVALID_REVNUM. Use @a pool for memory allocation. + * + * @since New in 1.5. + */ +svn_error_t * +svn_repos_deleted_rev(svn_fs_t *fs, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_revnum_t *deleted, + apr_pool_t *pool); + + /** Callback type for use with svn_repos_history(). @a path and @a * revision represent interesting history locations in the lifetime * of the path passed to svn_repos_history(). @a baton is the same Index: subversion/libsvn_repos/reporter.c =================================================================== --- subversion/libsvn_repos/reporter.c (revision 21889) +++ subversion/libsvn_repos/reporter.c (working copy) @@ -625,7 +625,12 @@ /* If there's a source and it's not related to the target, nuke it. */ if (s_entry && !related) { - SVN_ERR(b->editor->delete_entry(e_path, SVN_INVALID_REVNUM, dir_baton, + svn_revnum_t deleted_rev; + + SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b->t_root), t_path, + s_rev, b->t_rev, &deleted_rev, + pool)); + SVN_ERR(b->editor->delete_entry(e_path, deleted_rev, dir_baton, pool)); s_path = NULL; } @@ -765,11 +770,19 @@ if (apr_hash_get(t_entries, s_entry->name, APR_HASH_KEY_STRING) == NULL) { + svn_revnum_t deleted_rev; + /* There is no corresponding target entry, so delete. */ e_fullpath = svn_path_join(e_path, s_entry->name, subpool); + SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b->t_root), + svn_path_join(t_path, + s_entry->name, + subpool), + s_rev, b->t_rev, + &deleted_rev, subpool)); if (b->recurse || s_entry->kind != svn_node_dir) SVN_ERR(b->editor->delete_entry(e_fullpath, - SVN_INVALID_REVNUM, + deleted_rev, dir_baton, subpool)); } } Index: subversion/libsvn_repos/rev_hunt.c =================================================================== --- subversion/libsvn_repos/rev_hunt.c (revision 21889) +++ subversion/libsvn_repos/rev_hunt.c (working copy) @@ -299,6 +299,68 @@ } +svn_error_t * +svn_repos_deleted_rev(svn_fs_t *fs, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_revnum_t *deleted, + apr_pool_t *pool) +{ + apr_pool_t *subpool; + svn_fs_root_t *root; + svn_revnum_t curr_rev; + *deleted = SVN_INVALID_REVNUM; + + /* Validate the revision range. */ + if (! SVN_IS_VALID_REVNUM(start)) + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_REVISION, 0, + _("Invalid start revision %ld"), start); + if (! SVN_IS_VALID_REVNUM(end)) + return svn_error_createf + (SVN_ERR_FS_NO_SUCH_REVISION, 0, + _("Invalid end revision %ld"), end); + + subpool = svn_pool_create(pool); + + /* Ensure that the input is ordered. */ + if (start > end) + { + svn_revnum_t tmprev = start; + start = end; + end = tmprev; + } + + curr_rev = end; + + while (curr_rev >= start) + { + svn_node_kind_t kind_p; + svn_pool_clear(subpool); + + /* Get a revision root for curr_rev. */ + SVN_ERR(svn_fs_revision_root(&root, fs, curr_rev, subpool)); + + SVN_ERR(svn_fs_check_path(&kind_p, root, path, subpool)); + + if (kind_p == svn_node_none) + { + curr_rev--; + } + else + { + if (curr_rev != end) + *deleted = curr_rev + 1; + break; + } + } + + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + + /* Helper func: return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is unreadable. */ static svn_error_t * Index: subversion/libsvn_wc/status.c =================================================================== --- subversion/libsvn_wc/status.c (revision 21889) +++ subversion/libsvn_wc/status.c (working copy) @@ -999,18 +999,42 @@ /* Look up the key PATH in BATON->STATII. IS_DIR_BATON indicates whether baton is a struct *dir_baton or struct *file_baton. If the value doesn't yet exist, and the REPOS_TEXT_STATUS indicates that this is an - addition, create a new status struct using the hash's pool. Merge - REPOS_TEXT_STATUS and REPOS_PROP_STATUS into the status structure's + addition, create a new status struct using the hash's pool. + + If IS_DIR_BATON is true, THIS_DIR_BATON is a *dir_baton cotaining the out + of date (ood) information we want to set in BATON. This is necessary + because this function tweaks the status of out of date directories + (BATON == THIS_DIR_BATON) and out of date directories' parents + (BATON == THIS_DIR_BATON->parent_baton). In the latter case THIS_DIR_BATON + contains the ood info we want to bubble up to ancestor directories so these + accurately reflect the fact they have an ood descendent. + + Merge REPOS_TEXT_STATUS and REPOS_PROP_STATUS into the status structure's "network" fields. + + Iff IS_DIR_BATON is true, DELETED_REV is used as follows, otherwise it + is ignored: + + If REPOS_TEXT_STATUS is svn_wc_status_deleted then DELETED_REV is + optionally the revision path was deleted, in all other cases it must + be set to SVN_INVALID_REVNUM. If DELETED_REV is not + SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is svn_wc_status_deleted, + then use DELETED_REV to set PATH's ood_last_cmt_rev field in BATON. + If DELETED_REV is SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is + svn_wc_status_deleted, set PATH's ood_last_cmt_rev to it's parent's + ood_last_cmt_rev value - see comment below. + If a new struct was added, set the repos_lock to REPOS_LOCK. */ static svn_error_t * tweak_statushash(void *baton, + void *this_dir_baton, svn_boolean_t is_dir_baton, svn_wc_adm_access_t *adm_access, const char *path, svn_boolean_t is_dir, enum svn_wc_status_kind repos_text_status, enum svn_wc_status_kind repos_prop_status, + svn_revnum_t deleted_rev, svn_lock_t *repos_lock) { svn_wc_status2_t *statstruct; @@ -1064,20 +1088,50 @@ /* Copy out of date info. */ if (is_dir_baton) { - struct dir_baton *b = baton; + struct dir_baton *b = this_dir_baton; + if (b->url) - statstruct->url = apr_pstrdup(pool, b->url); - statstruct->ood_kind = b->ood_kind; - /* The last committed rev, date, and author for deleted items + { + if (statstruct->repos_text_status == svn_wc_status_deleted) + { + /* When deleting PATH, BATON is for PATH's parent, + so we must construct PATH's real statstruct->url. */ + statstruct->url = + svn_path_url_add_component(b->url, + svn_path_basename(path, pool), + pool); + } + else + statstruct->url = apr_pstrdup(pool, b->url); + } + + /* The last committed date, and author for deleted items isn't available. */ - if (statstruct->repos_text_status != svn_wc_status_deleted) + if (statstruct->repos_text_status == svn_wc_status_deleted) { + statstruct->ood_kind = is_dir ? svn_node_dir : svn_node_file; + + /* Pre 1.5 servers don't provide the revision a path was deleted. + So we punt and use the last committed revision of the path's + parent, which has some chance of being correct. At worse it + is a higher revision than the path was deleted, but this is + better than nothing... */ + if (deleted_rev == SVN_INVALID_REVNUM) + statstruct->ood_last_cmt_rev = + ((struct dir_baton *) baton)->ood_last_cmt_rev; + else + statstruct->ood_last_cmt_rev = deleted_rev; + } + else + { + statstruct->ood_kind = b->ood_kind; statstruct->ood_last_cmt_rev = b->ood_last_cmt_rev; statstruct->ood_last_cmt_date = b->ood_last_cmt_date; if (b->ood_last_cmt_author) statstruct->ood_last_cmt_author = apr_pstrdup(pool, b->ood_last_cmt_author); } + } else { @@ -1468,17 +1522,18 @@ SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool)); if (apr_hash_get(entries, hash_key, APR_HASH_KEY_STRING)) - SVN_ERR(tweak_statushash(db, TRUE, eb->adm_access, + SVN_ERR(tweak_statushash(db, db, TRUE, eb->adm_access, full_path, kind == svn_node_dir, - svn_wc_status_deleted, 0, NULL)); + svn_wc_status_deleted, 0, revision, NULL)); /* Mark the parent dir -- it lost an entry (unless that parent dir is the root node and we're not supposed to report on the root node). */ if (db->parent_baton && (! *eb->target)) - SVN_ERR(tweak_statushash(db->parent_baton, TRUE, eb->adm_access, + SVN_ERR(tweak_statushash(db->parent_baton, db, TRUE, eb->adm_access, db->path, kind == svn_node_dir, - svn_wc_status_modified, 0, NULL)); + svn_wc_status_modified, 0, SVN_INVALID_REVNUM, + NULL)); return SVN_NO_ERROR; } @@ -1560,8 +1615,10 @@ struct edit_baton *eb = db->edit_baton; svn_wc_status2_t *dir_status = NULL; - /* If nothing has changed, return. */ - if (db->added || db->prop_changed || db->text_changed) + /* If nothing has changed and directory has no out of + date descendents, return. */ + if (db->added || db->prop_changed || db->text_changed + || db->ood_last_cmt_rev != SVN_INVALID_REVNUM) { enum svn_wc_status_kind repos_text_status; enum svn_wc_status_kind repos_prop_status; @@ -1588,11 +1645,12 @@ { /* ### When we add directory locking, we need to find a ### directory lock here. */ - SVN_ERR(tweak_statushash(pb, TRUE, + SVN_ERR(tweak_statushash(pb, db, TRUE, eb->adm_access, db->path, TRUE, repos_text_status, - repos_prop_status, NULL)); + repos_prop_status, SVN_INVALID_REVNUM, + NULL)); } else { @@ -1601,6 +1659,16 @@ trigger invocation of the status callback below. */ eb->anchor_status->repos_prop_status = repos_prop_status; eb->anchor_status->repos_text_status = repos_text_status; + + /* If the root dir is out of date set the ood info directly too. */ + if (db->ood_last_cmt_rev != eb->anchor_status->entry->revision) + { + eb->anchor_status->ood_last_cmt_rev = db->ood_last_cmt_rev; + eb->anchor_status->ood_last_cmt_date = db->ood_last_cmt_date; + eb->anchor_status->ood_kind = db->ood_kind; + eb->anchor_status->ood_last_cmt_author = + apr_pstrdup(pool, db->ood_last_cmt_author); + } } } @@ -1799,11 +1867,11 @@ repos_prop_status = fb->prop_changed ? svn_wc_status_modified : 0; } - SVN_ERR(tweak_statushash(fb, FALSE, + SVN_ERR(tweak_statushash(fb, NULL, FALSE, fb->edit_baton->adm_access, fb->path, FALSE, repos_text_status, - repos_prop_status, + repos_prop_status, SVN_INVALID_REVNUM, repos_lock)); return SVN_NO_ERROR;