]> source.dussan.org Git - jgit.git/commitdiff
Add path support to checkout command. 80/3280/18
authorKevin Sawicki <kevin@github.com>
Tue, 3 May 2011 15:07:15 +0000 (08:07 -0700)
committerKevin Sawicki <kevin@github.com>
Tue, 3 May 2011 15:07:15 +0000 (08:07 -0700)
Change-Id: I89e8edfc6dd87d5bf8fd08704df2432720084330
Signed-off-by: Kevin Sawicki <kevin@github.com>
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java

diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java
new file mode 100644 (file)
index 0000000..1ec2787
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2011, Kevin Sawicki <kevin@github.com>
+ * 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 static org.junit.Assert.assertEquals;
+
+import java.io.File;
+
+import org.eclipse.jgit.lib.RepositoryTestCase;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests of path-based uses of {@link CheckoutCommand}
+ */
+public class PathCheckoutCommandTest extends RepositoryTestCase {
+
+       private static final String FILE1 = "f/Test.txt";
+
+       private static final String FILE2 = "Test2.txt";
+
+       Git git;
+
+       RevCommit initialCommit;
+
+       RevCommit secondCommit;
+
+       @Override
+       @Before
+       public void setUp() throws Exception {
+               super.setUp();
+               git = new Git(db);
+               writeTrashFile(FILE1, "1");
+               writeTrashFile(FILE2, "a");
+               git.add().addFilepattern(FILE1).addFilepattern(FILE2).call();
+               initialCommit = git.commit().setMessage("Initial commit").call();
+               writeTrashFile(FILE1, "2");
+               writeTrashFile(FILE2, "b");
+               git.add().addFilepattern(FILE1).addFilepattern(FILE2).call();
+               secondCommit = git.commit().setMessage("Second commit").call();
+               writeTrashFile(FILE1, "3");
+               writeTrashFile(FILE2, "c");
+               git.add().addFilepattern(FILE1).addFilepattern(FILE2).call();
+               git.commit().setMessage("Third commit").call();
+       }
+
+       @Test
+       public void testUpdateWorkingDirectory() throws Exception {
+               CheckoutCommand co = git.checkout();
+               File written = writeTrashFile(FILE1, "");
+               assertEquals("", read(written));
+               co.addPath(FILE1).call();
+               assertEquals("3", read(written));
+               assertEquals("c", read(new File(db.getWorkTree(), FILE2)));
+       }
+
+       @Test
+       public void testCheckoutFirst() throws Exception {
+               CheckoutCommand co = git.checkout();
+               File written = writeTrashFile(FILE1, "");
+               co.setStartPoint(initialCommit).addPath(FILE1).call();
+               assertEquals("1", read(written));
+               assertEquals("c", read(new File(db.getWorkTree(), FILE2)));
+       }
+
+       @Test
+       public void testCheckoutSecond() throws Exception {
+               CheckoutCommand co = git.checkout();
+               File written = writeTrashFile(FILE1, "");
+               co.setStartPoint("HEAD~1").addPath(FILE1).call();
+               assertEquals("2", read(written));
+               assertEquals("c", read(new File(db.getWorkTree(), FILE2)));
+       }
+
+       @Test
+       public void testCheckoutMultiple() throws Exception {
+               CheckoutCommand co = git.checkout();
+               File test = writeTrashFile(FILE1, "");
+               File test2 = writeTrashFile(FILE2, "");
+               co.setStartPoint("HEAD~2").addPath(FILE1).addPath(FILE2).call();
+               assertEquals("1", read(test));
+               assertEquals("a", read(test2));
+       }
+
+       @Test
+       public void testUpdateWorkingDirectoryFromIndex() throws Exception {
+               CheckoutCommand co = git.checkout();
+               File written = writeTrashFile(FILE1, "3a");
+               git.add().addFilepattern(FILE1).call();
+               written = writeTrashFile(FILE1, "");
+               assertEquals("", read(written));
+               co.addPath(FILE1).call();
+               assertEquals("3a", read(written));
+               assertEquals("c", read(new File(db.getWorkTree(), FILE2)));
+       }
+
+       @Test
+       public void testUpdateWorkingDirectoryFromHeadWithIndexChange()
+                       throws Exception {
+               CheckoutCommand co = git.checkout();
+               File written = writeTrashFile(FILE1, "3a");
+               git.add().addFilepattern(FILE1).call();
+               written = writeTrashFile(FILE1, "");
+               assertEquals("", read(written));
+               co.addPath(FILE1).setStartPoint("HEAD").call();
+               assertEquals("3", read(written));
+               assertEquals("c", read(new File(db.getWorkTree(), FILE2)));
+       }
+
+}
index 16c1fcfa33897685bcd6f9b9ac1b4e2135c22908..f3a2e536022f71d52be97a85359222054103c355 100644 (file)
  */
 package org.eclipse.jgit.api;
 
+import java.io.File;
 import java.io.IOException;
 import java.text.MessageFormat;
+import java.util.LinkedList;
+import java.util.List;
 
 import org.eclipse.jgit.JGitText;
 import org.eclipse.jgit.api.CheckoutResult.Status;
@@ -52,7 +55,12 @@ 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.DirCache;
 import org.eclipse.jgit.dircache.DirCacheCheckout;
+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.CheckoutConflictException;
 import org.eclipse.jgit.lib.AnyObjectId;
@@ -65,6 +73,8 @@ 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
@@ -82,17 +92,20 @@ public class CheckoutCommand extends GitCommand<Ref> {
 
        private CreateBranchCommand.SetupUpstreamMode upstreamMode;
 
-       private String startPoint = Constants.HEAD;
+       private String startPoint = null;
 
        private RevCommit startCommit;
 
        private CheckoutResult status;
 
+       private List<String> paths;
+
        /**
         * @param repo
         */
        protected CheckoutCommand(Repository repo) {
                super(repo);
+               this.paths = new LinkedList<String>();
        }
 
        /**
@@ -111,6 +124,12 @@ public class CheckoutCommand extends GitCommand<Ref> {
                checkCallable();
                processOptions();
                try {
+                       if (!paths.isEmpty()) {
+                               checkoutPaths();
+                               status = CheckoutResult.OK_RESULT;
+                               setCallable(false);
+                               return null;
+                       }
 
                        if (createBranch) {
                                Git git = new Git(repo);
@@ -196,6 +215,67 @@ public class CheckoutCommand extends GitCommand<Ref> {
                }
        }
 
+       /**
+        * @param path
+        *            Path to update in the working tree and index.
+        * @return {@code this}
+        */
+       public CheckoutCommand addPath(String path) {
+               checkCallable();
+               this.paths.add(path);
+               return this;
+       }
+
+       /**
+        * Checkout paths into index and working directory
+        *
+        * @return this instance
+        * @throws IOException
+        * @throws RefNotFoundException
+        */
+       protected CheckoutCommand checkoutPaths() throws IOException,
+                       RefNotFoundException {
+               RevWalk revWalk = new RevWalk(repo);
+               DirCache dc = repo.lockDirCache();
+               try {
+                       TreeWalk treeWalk = new TreeWalk(revWalk.getObjectReader());
+                       treeWalk.setRecursive(true);
+                       treeWalk.addTree(new DirCacheIterator(dc));
+                       treeWalk.setFilter(PathFilterGroup.createFromStrings(paths));
+                       List<String> files = new LinkedList<String>();
+                       while (treeWalk.next())
+                               files.add(treeWalk.getPathString());
+
+                       if (startCommit != null || startPoint != null) {
+                               DirCacheEditor editor = dc.editor();
+                               TreeWalk startWalk = new TreeWalk(revWalk.getObjectReader());
+                               startWalk.setRecursive(true);
+                               startWalk.setFilter(treeWalk.getFilter());
+                               startWalk.addTree(revWalk.parseCommit(getStartPoint())
+                                               .getTree());
+                               while (startWalk.next()) {
+                                       final ObjectId blobId = startWalk.getObjectId(0);
+                                       editor.add(new PathEdit(startWalk.getPathString()) {
+
+                                               public void apply(DirCacheEntry ent) {
+                                                       ent.setObjectId(blobId);
+                                               }
+                                       });
+                               }
+                               editor.commit();
+                       }
+
+                       File workTree = repo.getWorkTree();
+                       for (String file : files)
+                               DirCacheCheckout.checkoutEntry(repo, new File(workTree, file),
+                                               dc.getEntry(file));
+               } finally {
+                       dc.unlock();
+                       revWalk.release();
+               }
+               return this;
+       }
+
        private ObjectId getStartPoint() throws AmbiguousObjectException,
                        RefNotFoundException, IOException {
                if (startCommit != null)
@@ -215,8 +295,9 @@ public class CheckoutCommand extends GitCommand<Ref> {
        }
 
        private void processOptions() throws InvalidRefNameException {
-               if (name == null
-                               || !Repository.isValidRefName(Constants.R_HEADS + name))
+               if (paths.isEmpty()
+                               && (name == null || !Repository
+                                               .isValidRefName(Constants.R_HEADS + name)))
                        throw new InvalidRefNameException(MessageFormat.format(JGitText
                                        .get().branchNameInvalid, name == null ? "<null>" : name));
        }