You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by tr...@apache.org on 2013/08/10 07:53:54 UTC

svn commit: r1512568 [36/39] - in /jackrabbit/commons/filevault/trunk: ./ parent/ vault-cli/ vault-cli/src/ vault-cli/src/main/ vault-cli/src/main/appassembler/ vault-cli/src/main/assembly/ vault-cli/src/main/java/ vault-cli/src/main/java/org/ vault-cl...

Added: jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltContext.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltContext.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltContext.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,425 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.vlt;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Credentials;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.vault.fs.Mounter;
+import org.apache.jackrabbit.vault.fs.api.ImportMode;
+import org.apache.jackrabbit.vault.fs.api.PathFilter;
+import org.apache.jackrabbit.vault.fs.api.PathFilterSet;
+import org.apache.jackrabbit.vault.fs.api.RepositoryAddress;
+import org.apache.jackrabbit.vault.fs.api.VaultFileSystem;
+import org.apache.jackrabbit.vault.fs.api.VaultFsConfig;
+import org.apache.jackrabbit.vault.fs.config.ConfigurationException;
+import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
+import org.apache.jackrabbit.vault.fs.config.ExportRoot;
+import org.apache.jackrabbit.vault.fs.config.MetaInf;
+import org.apache.jackrabbit.vault.util.Constants;
+import org.apache.jackrabbit.vault.util.PathUtil;
+import org.apache.jackrabbit.vault.util.RepositoryProvider;
+import org.apache.jackrabbit.vault.vlt.actions.Action;
+import org.apache.jackrabbit.vault.vlt.meta.Ignored;
+import org.apache.jackrabbit.vault.vlt.meta.MetaDirectory;
+import org.apache.jackrabbit.vault.vlt.meta.xml.zip.ZipMetaDir;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>VaultContext</code>...
+ *
+ */
+public class VltContext {
+
+    protected static Logger log = LoggerFactory.getLogger(VltContext.class);
+
+    private final File cwd;
+
+    private final RepositoryProvider repProvider;
+
+    private final CredentialsProvider credsProvider;
+
+    private final ExportRoot exportRoot;
+
+    private Map<RepositoryAddress, VaultFileSystem> fileSystems
+            = new HashMap<RepositoryAddress, VaultFileSystem>();
+
+    private RepositoryAddress mountpoint;
+
+    private boolean verbose;
+
+    private boolean quiet;
+
+    private boolean swallowErrors = true;
+
+    private final PrintStream stdout;
+
+    private String[] defaultFilterRoots = Constants.EMPTY_STRING_ARRAY;
+
+    private String defaultFilter;
+
+    private String fsRoot = "";
+
+    private PathFilter globalIgnored;
+
+    public VltContext(File cwd, File localFile,
+            RepositoryProvider repProvider,
+            CredentialsProvider credsProvider)
+                    throws ConfigurationException, IOException {
+        this(cwd, localFile, repProvider, credsProvider, System.out);
+    }
+
+    public VltContext(File cwd, File localFile,
+                        RepositoryProvider repProvider,
+                        CredentialsProvider credsProvider,
+                        PrintStream out)
+            throws ConfigurationException, IOException {
+        if (!cwd.exists()) {
+            throw new FileNotFoundException(cwd.getAbsolutePath());
+        }
+        if (!localFile.exists()) {
+            throw new FileNotFoundException(localFile.getAbsolutePath());
+        }
+        this.stdout = out;
+        this.cwd = cwd;
+        this.repProvider = repProvider;
+        this.credsProvider = credsProvider;
+        ExportRoot er = ExportRoot.findRoot(localFile);
+        if (er == null) {
+            er = new ExportRoot(localFile);
+        }
+        this.exportRoot = er;
+    }
+
+    public RepositoryAddress getMountpoint() throws VltException {
+        if (mountpoint == null) {
+            File dir = new File(exportRoot.getJcrRoot(), VltDirectory.META_DIR_NAME);
+            MetaDirectory rootMeta = VltContext.createMetaDirectory(dir);
+            try {
+                String addr = rootMeta.getRepositoryUrl();
+                if (addr == null) {
+                    throw new VltException("Root directory must provide a repository url file.");
+                }
+                mountpoint = new RepositoryAddress(addr);
+            } catch (IOException e) {
+                throw new VltException("error while reading repository address.", e);
+            } catch (URISyntaxException e) {
+                throw new VltException("Illegal repository address.", e);
+            }
+        }
+        return mountpoint;
+    }
+
+    public static MetaDirectory createMetaDirectory(File base) throws VltException {
+        //return new FileMetaDir(base);
+        try {
+            //return new TarMetaDir(base);
+            return new ZipMetaDir(base);
+        } catch (IOException e) {
+            throw new VltException("Error creating meta directory.", e);
+        }
+    }
+
+    public String getFsRoot() {
+        return fsRoot;
+    }
+
+    public void setFsRoot(String fsRoot) {
+        if (fsRoot == null || fsRoot.equals("/")) {
+            this.fsRoot = "";
+        } else {
+            this.fsRoot = fsRoot;
+        }
+    }
+
+    public PathFilter getGlobalIgnored() {
+        return globalIgnored;
+    }
+
+    public void setGlobalIgnored(PathFilter globalIgnored) {
+        this.globalIgnored = globalIgnored;
+    }
+
+    /**
+     * Sets the filesystem root to the aggregate path defined by the entries
+     * of the given directory.
+     *
+     * @param dir the directory
+     * @throws VltException if an error occurs
+     */
+    public void setFsRoot(VltDirectory dir) throws VltException {
+        String aPath = dir.getAggregatePath();
+        if (aPath != null) {
+            RepositoryAddress adr = getMountpoint().resolve(aPath);
+            setFsRoot(aPath);
+            setMountpoint(adr);
+        }
+    }
+
+    public void setMountpoint(RepositoryAddress addr) throws VltException {
+        mountpoint = addr;
+        File dir = new File(exportRoot.getJcrRoot(), VltDirectory.META_DIR_NAME);
+        MetaDirectory rootMeta = VltContext.createMetaDirectory(dir);
+        try {
+            String url = addr.toString();
+            if (fsRoot.length() > 0 && url.endsWith(fsRoot)) {
+                url = url.substring(0, url.length() - fsRoot.length());
+            }
+            rootMeta.setRepositoryUrl(url);
+        } catch (IOException e) {
+            throw new VltException("error while writing repository address.", e);
+        }
+    }
+
+    public Session login(RepositoryAddress mountpoint) throws RepositoryException {
+        Repository rep = repProvider.getRepository(mountpoint);
+        Credentials creds = credsProvider.getCredentials(mountpoint);
+        Session s = rep.login(creds);
+        // hack to store credentials
+        credsProvider.storeCredentials(mountpoint, creds);
+        return s;
+    }
+
+    public VaultFileSystem getFileSystem(RepositoryAddress mountpoint)
+            throws VltException {
+        VaultFileSystem fs = fileSystems.get(mountpoint);
+        if (fs == null) {
+            try {
+                // check if export root already defines config and filter
+                DefaultWorkspaceFilter filter = null;
+                VaultFsConfig config = null;
+                if (exportRoot != null && exportRoot.getMetaInf() != null) {
+                    filter = (DefaultWorkspaceFilter) exportRoot.getMetaInf().getFilter();
+                    config = exportRoot.getMetaInf().getConfig();
+                }
+                if (filter == null && defaultFilterRoots.length > 0) {
+                    filter = new DefaultWorkspaceFilter();
+                    for (String root: defaultFilterRoots) {
+                        filter.add(new PathFilterSet(root));
+                    }
+                    stdout.printf("Created default filter:%n%s", filter.getSourceAsString());
+                }
+                if (filter == null && defaultFilter != null) {
+                    filter = new DefaultWorkspaceFilter();
+                    try {
+                        filter.load(new File(defaultFilter));
+                    } catch (ConfigurationException e) {
+                        throw new VltException("Specified filter is not valid.", e);
+                    }
+                }
+                // get .vltignore files
+                if (exportRoot != null && filter != null) {
+                    if (globalIgnored == null) {
+                        globalIgnored = new Ignored(this, cwd);
+                    }
+                    filter.setGlobalIgnored(globalIgnored);
+                }
+                // override any import mode defined in the filter as this is not expected when committing files (GRANITE-XYZ)
+                if (filter != null) {
+                    filter.setImportMode(ImportMode.REPLACE);
+                }
+
+                Repository rep = repProvider.getRepository(mountpoint);
+                Credentials creds = credsProvider.getCredentials(mountpoint);
+                fs = Mounter.mount(config, filter, rep, creds, mountpoint, fsRoot);
+                // hack to store credentials
+                credsProvider.storeCredentials(mountpoint, creds);
+
+            } catch (RepositoryException e) {
+                throw new VltException("Unable to mount filesystem", e);
+            } catch (IOException e) {
+                throw new VltException("Unable to mount filesystem", e);
+            }
+            fileSystems.put(mountpoint, fs);
+        }
+        return fs;
+    }
+
+    public ExportRoot getExportRoot() {
+        return exportRoot;
+    }
+
+    public MetaInf getMetaInf() {
+        return exportRoot.isValid() ? exportRoot.getMetaInf() : null;
+    }
+
+    public String[] getDefaultFilterRoots() {
+        return defaultFilterRoots;
+    }
+
+    public void setDefaultFilterRoots(String[] defaultFilterRoots) {
+        this.defaultFilterRoots = defaultFilterRoots;
+    }
+
+    public String getDefaultFilter() {
+        return defaultFilter;
+    }
+
+    public void setDefaultFilter(String defaultFilter) {
+        this.defaultFilter = defaultFilter;
+    }
+
+    public void close() {
+        for (RepositoryAddress addr: fileSystems.keySet()) {
+            VaultFileSystem fs = fileSystems.get(addr);
+            try {
+                fs.unmount();
+            } catch (RepositoryException e) {
+                log.warn("Error while unmounting fs.", e);
+            }
+        }
+        fileSystems.clear();
+    }
+
+    public boolean execute(Action action) throws VltException {
+        try {
+            action.run(this);
+        } catch (VltException e) {
+            if (swallowErrors && e.isUserError()) {
+                printError(e);
+                return false;
+            } else {
+                throw e;
+            }
+        }
+        return true;
+    }
+
+    public String getCwdRelativePath(String path) {
+        return PathUtil.getRelativeFilePath(cwd.getPath(), path);
+    }
+
+    public VltException error(String path, String msg) {
+        path = getCwdRelativePath(path);
+        return new VltException(path, true, msg, null);
+    }
+
+    public VltException exception(String path, String msg, Throwable cause) {
+        path = getCwdRelativePath(path);
+        return new VltException(path, false, msg, cause);
+    }
+
+    public void printAction(VltFile file, FileAction action) {
+        printAction(file.getPath(), action, file.getContentType());
+    }
+
+    public void printAction(String path, FileAction action, String contentType) {
+        if (!quiet && (verbose || action != FileAction.VOID)) {
+            path = getCwdRelativePath(path);
+            if (action == FileAction.ADDED && contentType != null) {
+                stdout.printf("%s %s (%s)%n", action.letter, path, contentType);
+            } else {
+                stdout.printf("%s %s%n", action.letter, path);
+            }
+            stdout.flush();
+        }
+    }
+
+    public void printError(VltException e) {
+        stdout.println(e.getMessage());
+        stdout.flush();
+    }
+
+    public void printMessage(VltFile file, String msg) {
+        if (!quiet) {
+            String path = getCwdRelativePath(file.getPath());
+            stdout.printf("%s %s%n", path, msg);
+            stdout.flush();
+        }
+    }
+    
+    public void printMessage(String msg, VltFile file) {
+        if (!quiet) {
+            String path = getCwdRelativePath(file.getPath());
+            stdout.printf("%s %s%n", msg, path);
+            stdout.flush();
+        }
+    }
+
+    public void printMessage(String msg) {
+        if (!quiet) {
+            stdout.println(msg);
+            stdout.flush();
+        }
+    }
+
+    public void printStatus(VltFile file)
+            throws VltException {
+        String path = getCwdRelativePath(file.getPath());
+        VltFile.State state = file.getStatus();
+        if (quiet && state == VltFile.State.UNKNOWN) {
+            return;
+        }
+        if (verbose || state != VltFile.State.CLEAN) {
+            if (state == VltFile.State.ADDED && file.getContentType() != null) {
+                stdout.printf("%s %s (%s)%n", state.letter, path, file.getContentType());
+            } else {
+                stdout.printf("%s %s%n", state.letter, path);
+            }
+            stdout.flush();
+        }
+    }
+
+    public void printRemoteStatus(VltFile file, FileAction action)
+            throws VltException {
+        String path = getCwdRelativePath(file.getPath());
+        VltFile.State state = file.getStatus();
+        if (quiet && state == VltFile.State.UNKNOWN && action == FileAction.VOID) {
+            return;
+        }
+        if (verbose || state != VltFile.State.CLEAN || action != FileAction.VOID) {
+            stdout.printf("%s%s %s%n", state.letter, action.letter, path);
+            stdout.flush();
+        }
+    }
+
+    public PrintStream getStdout() {
+        return stdout;
+    }
+
+    public File getCwd() {
+        return cwd;
+    }
+
+    public boolean isVerbose() {
+        return verbose;
+    }
+
+    public void setVerbose(boolean verbose) {
+        this.verbose = verbose;
+    }
+
+    public boolean isQuiet() {
+        return quiet;
+    }
+
+    public void setQuiet(boolean quiet) {
+        this.quiet = quiet;
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltDirectory.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltDirectory.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltDirectory.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltDirectory.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,490 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.vlt;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.vault.fs.api.VaultFile;
+import org.apache.jackrabbit.vault.fs.api.VaultFileOutput;
+import org.apache.jackrabbit.vault.fs.api.VaultFileSystem;
+import org.apache.jackrabbit.vault.fs.api.VaultFsTransaction;
+import org.apache.jackrabbit.vault.util.Constants;
+import org.apache.jackrabbit.vault.util.FileInputSource;
+import org.apache.jackrabbit.vault.util.LineOutputStream;
+import org.apache.jackrabbit.vault.vlt.actions.Action;
+import org.apache.jackrabbit.vault.vlt.meta.MetaDirectory;
+import org.apache.jackrabbit.vault.vlt.meta.VltEntries;
+import org.apache.jackrabbit.vault.vlt.meta.VltEntry;
+
+/**
+ * <code>VltDirectory</code>...
+ *
+ */
+public class VltDirectory {
+
+    public static final String META_DIR_NAME = ".vlt";
+
+    private final VltContext ctx;
+
+    private File dir;
+
+    private MetaDirectory metaDir;
+
+    //private final File entriesFile;
+
+    private VltEntries entries;
+
+    private FileList files;
+
+    public VltDirectory(VltContext ctx, File directory) throws VltException {
+        this.ctx = ctx;
+        this.dir = directory;
+        if (!dir.exists()) {
+            throw ctx.error(dir.getPath(), "no such file or directory.");
+        }
+        if (!dir.isDirectory()) {
+            throw ctx.error(dir.getPath(), "not a directory.");
+        }
+        if (dir.getName().equals(META_DIR_NAME)) {
+            throw ctx.error(dir.getPath(), "meta directory not controllable.");
+        }
+        metaDir = VltContext.createMetaDirectory(new File(dir, META_DIR_NAME));
+        //entriesFile = new File(metaDir, ENTRIES_FILE_NAME);
+
+        init();
+    }
+
+    public VltDirectory getParent() throws VltException {
+        return new VltDirectory(ctx, dir.getParentFile());
+    }
+
+    public String getAggregatePath() throws VltException {
+        VltDirectory parent = getParent();
+        VltEntries es = parent.getEntries();
+        if (es != null) {
+            VltEntry e = es.getEntry(dir.getName());
+            if (e != null) {
+                return e.getAggregatePath();
+            }
+        } else {
+            // fallback if the parent is not controllable. check the
+            // aggregate path of the .content.xml
+            VltEntry e = entries == null ? null : entries.getEntry(Constants.DOT_CONTENT_XML);
+            if (e != null) {
+                return e.getAggregatePath();
+            }
+        }
+        return null;
+    }
+
+    public VltContext getContext() {
+        return ctx;
+    }
+
+    public boolean isControlled() {
+        return entries != null;
+    }
+
+    public void assertControlled() throws VltException {
+        if (entries == null) {
+            throw ctx.exception(dir.getPath(), "Directory is not under vault control.", null);
+        }
+    }
+
+    private void init() throws VltException {
+        entries = metaDir.getEntries();
+        if (entries != null) {
+            files = new FileList(this, entries);
+        }
+    }
+
+    public void close() {
+        if (dir != null) {
+            dir = null;
+        }
+        if (metaDir != null) {
+            try {
+                metaDir.close();
+            } catch (IOException e) {
+                // ignore
+            }
+            metaDir = null;
+        }
+    }
+    
+    public void control(String path, String aPath) throws VltException {
+        if (!metaDir.exists()) {
+            try {
+                metaDir.create(path);
+            } catch (IOException e) {
+                throw ctx.exception(path, "Error while creating.", e);
+            }
+        }
+        entries = metaDir.getEntries();
+        if (entries == null) {
+            throw ctx.error(metaDir.getFile().getPath(), "No entries found.");
+        }
+        try {
+            metaDir.sync();
+        } catch (IOException e) {
+            throw ctx.exception(path, "Error while saving.", e);
+        }
+        files = new FileList(this, entries);
+    }
+
+    public void uncontrol()
+            throws VltException {
+        if (metaDir.exists()) {
+            try {
+                metaDir.delete();
+            } catch (IOException e) {
+                throw ctx.exception(getPath(), "Error while deleting meta directory", e);
+            }
+        }
+        entries = null;
+        files = null;
+    }
+
+    public MetaDirectory getMetaDirectory() {
+        return metaDir;
+    }
+
+    public File getDirectory() {
+        return dir;
+    }
+
+    public String getPath() {
+        return dir.getPath();
+    }
+
+    public VltEntries getEntries() {
+        return entries;
+    }
+
+    public VaultFile getRemoteDirectory(VltContext ctx) throws VltException {
+        assertControlled();
+        try {
+            VaultFileSystem fs = ctx.getFileSystem(ctx.getMountpoint());
+            return fs.getFile(entries.getPath());
+        } catch (IOException e) {
+            throw new VltException("Unable to get remote directory.", e);
+        } catch (RepositoryException e) {
+            throw new VltException("Unable to get remote directory.", e);
+        }
+    }
+
+    public void prepareCommit(VaultFsTransaction tx, Collection<String> names,
+                              boolean nonRecursive, boolean force)
+            throws VltException {
+        assertControlled();
+        VaultFile remoteDir = getRemoteDirectory(ctx);
+        if (remoteDir == null) {
+            throw ctx.error(getPath(), "Remote directory does not exist.");
+        }
+        if (names.isEmpty()) {
+            // add all files in this directory
+            for (VltFile file : getFiles()) {
+                prepareCommit(tx, remoteDir, file, nonRecursive, force);
+            }
+        } else {
+            for (String name: names) {
+                VltFile file = files.getFile(name);
+                if (file == null) {
+                    throw ctx.error(name, "no such file or directory.");
+                }
+                prepareCommit(tx, remoteDir, file, nonRecursive, force);
+            }
+        }
+        saveEntries();
+    }
+
+    public void updateComitted(String path, String fileName) throws VltException {
+        assertControlled();
+        VltFile file = files().getFile(fileName);
+        VaultFile remote;
+        try {
+            VaultFile rd = getRemoteDirectory(ctx);
+            remote = rd == null ? null : rd.getChild(fileName);
+        } catch (RepositoryException e) {
+            throw ctx.exception(dir.getPath(), "Error while retrieving remote directory.", e);
+        }
+        if (file == null && remote == null) {
+            // removed and file gone
+            ctx.printAction(getPath() + Constants.FS_NATIVE + fileName, FileAction.DELETED, null);
+        } else if (file == null) {
+            // added
+            update(remote, fileName);
+        } else {
+            FileAction a = file.commit(remote);
+            if (a != FileAction.VOID) {
+                entries.update(file);
+                saveEntries();
+                ctx.printAction(file, a);
+            }
+        }
+    }
+
+    private void update(VaultFile remote, String name)
+            throws VltException {
+        VltFile file = new VltFile(this, name, null);
+        files.addFile(file);
+
+        FileAction action = file.update(remote, false);
+
+        // write back entries
+        entries.update(file);
+        saveEntries();
+        ctx.printAction(file, action);
+        sync();
+    }
+
+    private void prepareCommit(VaultFsTransaction tx, VaultFile remoteDir,
+                               VltFile file, boolean nonRecursive, boolean force)
+            throws VltException {
+        VaultFile remoteFile;
+        try {
+            remoteFile = remoteDir == null
+                    ? null
+                    : remoteDir.getChild(file.getName());
+        } catch (RepositoryException e) {
+            throw ctx.exception(file.getPath(), "Error while retrieving status", e);
+        }
+
+        if (file.status(remoteFile) != FileAction.VOID && !force) {
+            throw ctx.error(file.getPath(), "Some files need to be updated first." +
+                    " Specify --force to overwrite remote files.");
+        }
+        try {
+            switch (file.getStatus()) {
+                case MODIFIED:
+                    FileInputSource fis = new FileInputSource(file.getFile());
+                    if (file.isBinary()) {
+                        fis.setLineSeparator(LineOutputStream.LS_BINARY);
+                    }
+                    tx.modify(remoteFile, fis);
+                    ctx.printMessage("sending....", file);
+                    break;
+                case DELETED:
+                    tx.delete(remoteFile);
+                    ctx.printMessage("deleting...", file);
+                    break;
+                case ADDED:
+                    String path = this.getEntries().getPath();
+                    if (path.endsWith("/")) {
+                        path += file.getName();
+                    } else {
+                        path += "/" + file.getName();
+                    }
+                    if (file.canDescend()) {
+                        tx.mkdir(path);
+                    } else {
+                        fis = new FileInputSource(file.getFile());
+                        if (file.isBinary()) {
+                            fis.setLineSeparator(LineOutputStream.LS_BINARY);
+                        }
+                        VaultFileOutput out = tx.add(path, fis);
+                        // set the content type hint
+                        out.setContentType(file.getContentType());
+                    }
+                    ctx.printMessage("adding.....", file);
+                    break;
+                default:
+                    // ignore
+            }
+        } catch (IOException e) {
+            ctx.exception(file.getPath(), "Error while preparing commit.", e);
+        } catch (RepositoryException e) {
+            ctx.exception(file.getPath(), "Error while preparing commit.", e);
+        }
+
+        if (file.canDescend() && !nonRecursive) {
+            VltDirectory dir = file.descend();
+            if (dir.isControlled()) {
+                // add all files in this directory
+                VaultFile remDir = dir.getRemoteDirectory(ctx);
+                for (VltFile child: dir.getFiles()) {
+                    dir.prepareCommit(tx, remDir, child, nonRecursive, force);
+                }
+                dir.saveEntries();
+            }
+            dir.close();
+        }
+    }
+
+    public Collection<VltFile> getFiles() {
+        if (files == null) {
+            return Collections.emptySet();
+        } else {
+            return files.getFiles();
+        }
+    }
+
+    public void apply(Action action, String name, boolean nonRecursive)
+            throws VltException {
+        apply(action, Arrays.asList(name), nonRecursive);
+    }
+
+    public void apply(Action action, Collection<String> names,
+                      boolean nonRecursive) throws VltException {
+        if (!action.run(this, null)) {
+            return;
+        }
+        if (names.isEmpty()) {
+            // add all files in this directory
+            for (VltFile file : getFiles()) {
+                apply(action, file, nonRecursive);
+            }
+        } else {
+            for (String name: names) {
+                // special check for jcr_root
+                VltFile file;
+                if (ctx.getExportRoot().getJcrRoot().getParentFile().equals(dir)) {
+                    file = new VltFile(this, name, null);
+                } else {
+                    assertControlled();
+                    file = files.getFile(name);
+                }
+                if (file == null) {
+                    throw ctx.error(name, "no such file or directory.");
+                }
+                apply(action, file, nonRecursive);
+            }
+        }
+    }
+
+    private void apply(Action action, VltFile file, boolean nonRecursive)
+            throws VltException {
+        action.run(this, file, null);
+        if (entries != null) {
+            entries.update(file);
+            sync();
+        }
+        if (file.canDescend() && !nonRecursive) {
+            VltDirectory dir = file.descend();
+            dir.apply(action, Collections.<String>emptyList(), nonRecursive);
+            dir.close();
+        }
+    }
+
+
+    public void sync() throws VltException {
+        saveEntries();
+        // reload files (todo: make better)
+        files = new FileList(this, entries);
+    }
+
+    public void applyWithRemote(Action action, Collection<String> names, boolean nonRecursive)
+            throws VltException {
+        applyWithRemote(action, getRemoteDirectory(ctx), names, nonRecursive);
+    }
+
+    public void applyWithRemote(Action action, String name, boolean nonRecursive)
+            throws VltException {
+        applyWithRemote(action, getRemoteDirectory(ctx), Arrays.asList(name), nonRecursive);
+    }
+
+    public void applyWithRemote(Action action, VaultFile remoteDir, Collection<String> names,
+                                boolean nonRecursive)
+            throws VltException {
+        if (!action.run(this, remoteDir)) {
+            return;
+        }
+        if (names.isEmpty()) {
+            // get the status of remote files
+            Set<String> processed = new HashSet<String>();
+            if (remoteDir != null && files != null) {
+                Collection<? extends VaultFile> remoteFiles;
+                try {
+                    remoteFiles = remoteDir.getChildren();
+                } catch (RepositoryException e) {
+                    throw new VltException("Error while retrieving file.", e);
+                }
+                for (VaultFile remoteFile : remoteFiles) {
+                    String name = remoteFile.getName();
+                    processed.add(name);
+                    applyWithRemote(action, files.getFile(name), remoteFile, nonRecursive);
+                }
+            }
+            // second go over all local ones
+            for (VltFile file: getFiles()) {
+                if (!processed.contains(file.getName())) {
+                    applyWithRemote(action, file, null, nonRecursive);
+                }
+            }
+        } else {
+            try {
+                for (String name: names) {
+                    VltFile file = files.getFile(name);
+                    VaultFile remoteFile = remoteDir.getChild(name);
+                    applyWithRemote(action, file, remoteFile, nonRecursive);
+                }
+            } catch (RepositoryException e) {
+                throw new VltException("Error while retrieving file.", e);
+            }
+        }
+    }
+
+    public void applyWithRemote(Action action, VltFile file, VaultFile remoteFile,
+                                boolean nonRecursive)
+            throws VltException {
+        // if remote file is missing, do depth first
+        if (remoteFile == null && file != null && file.canDescend() && !nonRecursive) {
+            VltDirectory dir = file.descend();
+            dir.applyWithRemote(action, remoteFile, Collections.<String>emptyList(), nonRecursive);
+            dir.close();
+        }
+
+        // run on 'this' file
+        action.run(this, file, remoteFile);
+        saveEntries();
+
+        if (remoteFile != null) {
+            // refetch file
+            if (file == null) {
+                file = files.getFile(remoteFile.getName());
+            }
+
+            // check again deep
+            if (file != null && file.canDescend() && !nonRecursive) {
+                VltDirectory dir = file.descend();
+                dir.applyWithRemote(action, remoteFile, Collections.<String>emptyList(), nonRecursive);
+                dir.close();
+            }
+        }
+    }
+
+    private void saveEntries() throws VltException {
+        try {
+            metaDir.sync();
+        } catch (IOException e) {
+            throw ctx.error(getPath(), "Error while saving entries: " + e);
+        }
+    }
+
+    public FileList files() throws VltException {
+        assertControlled();
+        return files;
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltException.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltException.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltException.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltException.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.vlt;
+
+/**
+ * <code>VaultException</code>...
+ *
+ */
+public class VltException extends Exception {
+
+    static final long serialVersionUID = -4355975803798981445L;
+
+    private final String path;
+
+    private final boolean isUserError;
+
+    public VltException(String message) {
+        this(null, false, message, null);
+    }
+
+    public VltException(String path, String message) {
+        this(path, false, message, null);
+    }
+
+    public VltException(String message, Throwable cause) {
+        this(null, false, message, cause);
+    }
+
+    public VltException(String path, String message, Throwable cause) {
+        this(path, false, message, cause);
+    }
+    
+    public VltException(String path, boolean isUserError, String message, Throwable cause) {
+        super(message, cause);
+        this.path = path;
+        this.isUserError = isUserError;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public boolean isUserError() {
+        return isUserError;
+    }
+
+    public String message() {
+        return super.getMessage();
+    }
+    
+    public String getMessage() {
+        return path == null || path.equals("") || path.equals(".")
+                ? super.getMessage()
+                : path + ": " + super.getMessage();
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltFile.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltFile.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltFile.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/VltFile.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,1025 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.vlt;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Properties;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.vault.fs.VaultFileCopy;
+import org.apache.jackrabbit.vault.fs.api.VaultFile;
+import org.apache.jackrabbit.vault.util.Constants;
+import org.apache.jackrabbit.vault.util.LineOutputStream;
+import org.apache.jackrabbit.vault.util.MD5;
+import org.apache.jackrabbit.vault.util.MimeTypes;
+import org.apache.jackrabbit.vault.util.PathUtil;
+import org.apache.jackrabbit.vault.util.diff.DiffWriter;
+import org.apache.jackrabbit.vault.util.diff.Document;
+import org.apache.jackrabbit.vault.util.diff.DocumentDiff;
+import org.apache.jackrabbit.vault.util.diff.DocumentDiff3;
+import org.apache.jackrabbit.vault.util.diff.DocumentSource;
+import org.apache.jackrabbit.vault.util.diff.FileDocumentSource;
+import org.apache.jackrabbit.vault.util.diff.Hunk3;
+import org.apache.jackrabbit.vault.util.diff.LineElementsFactory;
+import org.apache.jackrabbit.vault.vlt.meta.MetaFile;
+import org.apache.jackrabbit.vault.vlt.meta.MetaFileDocSource;
+import org.apache.jackrabbit.vault.vlt.meta.VltEntry;
+import org.apache.jackrabbit.vault.vlt.meta.VltEntryInfo;
+
+/**
+ * <code>VltFile</code>...
+ *
+ */
+public class VltFile implements DocumentSource {
+
+    public static final String PROP_CONTENT_TYPE = "vlt:mime-type";
+
+    /**
+     * Possible state of this file
+     */
+    public enum State {
+        CLEAN (" "),
+        ADDED ("A"),
+        CONFLICTED ("C"),
+        DELETED ("D"),
+        IGNORED ("I"),
+        MODIFIED ("M"),
+        REPLACED ("R"),
+        UNKNOWN("?"),
+        MISSING ("!"),
+        OBSTRUCTED ("~"),
+        VOID (" ");
+
+        public final String letter;
+
+        private State(String letter) {
+            this.letter = letter;
+        }
+
+        public String toString() {
+            return name().toLowerCase() + " (" + letter + ")";
+        }
+    }
+
+    private final VltDirectory parent;
+
+    private final File file;
+
+    private final String name;
+
+    private VltEntry entry;
+
+    public VltFile(VltDirectory parent, String name, VltEntry entry)
+            throws VltException {
+        this.parent = parent;
+        this.name = name;
+        this.entry = entry;
+        this.file = new File(parent.getDirectory(), name);
+    }
+
+    public Properties getProperties() throws VltException {
+        Properties props = new Properties();
+        if (entry != null) {
+            VltEntryInfo info = entry.work();
+            String ct = info.getContentType();
+            if (ct != null) {
+                props.put(PROP_CONTENT_TYPE, ct);
+            }
+        }
+        return props;
+    }
+
+    public String getProperty(String name) throws VltException {
+        if (entry != null) {
+            VltEntryInfo info = entry.work();
+            if (name.equals(PROP_CONTENT_TYPE)) {
+                return info.getContentType();
+            }
+        }
+        return null;
+    }
+
+    public void setProperty(String name, String value) throws VltException {
+        if (entry == null) {
+            throw error("Can't set property to non controlled file.");
+        }
+        VltEntryInfo info = entry.work();
+        if (info == null) {
+            throw error("Can't set property to non controlled file.");
+        }
+        if (name.equals(PROP_CONTENT_TYPE)) {
+            if (!file.isDirectory()) {
+                // silently ignore directories
+                info.setContentType(value);
+                parent.getContext().printMessage(this, name + "=" + value);
+            }
+        } else {
+            throw error("Generic properies not supported, yet");
+        }
+    }
+    
+    public State getStatus() throws VltException {
+        State state = State.VOID;
+        if (entry == null) {
+            if (file.exists()) {
+                // special check for jcr_root
+                if (file.equals(parent.getContext().getExportRoot().getJcrRoot())) {
+                    state = State.CLEAN;
+                } else {
+                    state = State.UNKNOWN;
+                }
+            } else {
+                state = State.VOID;
+            }
+        } else {
+            switch (entry.getState()) {
+                case CLEAN:
+                    if (file.exists()) {
+                        if (file.isDirectory()) {
+                            VltDirectory dir = descend();
+                            if (dir.isControlled()) {
+                                state = State.CLEAN;
+                            } else {
+                                state = State.OBSTRUCTED;
+                            }
+                        } else {
+                            VltEntryInfo work = entry.work();
+                            VltEntryInfo base = entry.base();
+                            assert work != null;
+                            assert base != null;
+
+                            try {
+                                work.update(file, false);
+                            } catch (IOException e) {
+                                throw exception("Error while calculating status.", e);
+                            }
+                            state = work.isSame(base) ? State.CLEAN : State.MODIFIED;
+                        }
+                    } else {
+                        state = State.MISSING;
+                    }
+                    break;
+                case ADDED:
+                    if (file.exists()) {
+                        state = State.ADDED;
+                    } else {
+                        state = State.MISSING;
+                    }
+                    break;
+                case CONFLICT:
+                    state = State.CONFLICTED;
+                    break;
+                case DELETED:
+                    state = State.DELETED;
+                    break;
+            }
+        }
+        return state;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public String getPath() {
+        return file.getPath();
+    }
+
+    public MetaFile getBaseFile(boolean create) throws VltException {
+        try {
+            return parent.getMetaDirectory().getBaseFile(name, create);
+        } catch (IOException e) {
+            throw new VltException(getPath(), "Error opening base file.", e);
+        }
+    }
+
+    public String getContentType() {
+        if (entry != null && !file.isDirectory()) {
+            VltEntryInfo work = entry.work();
+            if (work != null) {
+                return work.getContentType();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Checks if this file has binary content. It does not actually read the
+     * file data but calls {@link MimeTypes#isBinary(String)} with the content
+     * type of the work file.
+     * @return <code>true</code> if this is binary
+     */
+    public boolean isBinary() {
+        return MimeTypes.isBinary(getContentType());
+    }
+
+
+    public MetaFile getTmpFile() throws VltException {
+        try {
+            return parent.getMetaDirectory().getTmpFile(name, true);
+        } catch (IOException e) {
+            throw new VltException(getPath(), "Error opening tmp file.", e);
+        }
+    }
+
+    public boolean canDescend() {
+        return file.isDirectory();
+    }
+
+    public VltDirectory descend() throws VltException {
+        if (!canDescend()) {
+            throw new VltException("Cannot descend into non directory.");
+        }
+        return new VltDirectory(parent.getContext(), file);
+    }
+
+    public VltEntry getEntry() {
+        return entry;
+    }
+
+    public void diff() throws VltException {
+        State state = getStatus();
+        if (entry == null || entry.isDirectory()) {
+            return;
+        }
+        VltEntryInfo work = entry.work();
+        VltEntryInfo base = entry.base();
+        if (work == null || base == null) {
+            return;
+        }
+        switch (state) {
+            case ADDED:
+            case CONFLICTED:
+            case DELETED:
+            case MODIFIED:
+                break;
+            case IGNORED:
+            case MISSING:
+            case OBSTRUCTED:
+            case REPLACED:
+            case UNKNOWN:
+            case VOID:
+            case CLEAN:
+                return;
+        }
+        if (MimeTypes.isBinary(work.getContentType()) || MimeTypes.isBinary(base.getContentType())) {
+            PrintStream s = parent.getContext().getStdout();
+            s.printf("Index: %s%n", getName());
+            s.println("===================================================================");
+            s.println("Cannot display: file marked as binary type.");
+            s.printf("vlt:mime-type = %s%n", work.getContentType());
+            s.flush();
+            return;
+        }
+        try {
+            // do the actual diff
+            PrintStream s = parent.getContext().getStdout();
+            DiffWriter out = new DiffWriter(new OutputStreamWriter(s, Constants.ENCODING));
+            out.write("Index: ");
+            out.write(getName());
+            out.writeNewLine();
+            out.write("===================================================================");
+            out.writeNewLine();
+
+            Reader r0 = getBaseFile(false) == null ? null : getBaseFile(false).getReader();
+            Document d0 = new Document(this, LineElementsFactory.create(this, r0, false));
+            Reader r1 = file.exists() ? new InputStreamReader(FileUtils.openInputStream(file), Constants.ENCODING) : null;
+            Document d1 = new Document(this, LineElementsFactory.create(this, r1, false));
+
+            DocumentDiff diff;
+            try {
+                diff = d0.diff(d1);
+            } finally {
+                IOUtils.closeQuietly(r0);
+                IOUtils.closeQuietly(r1);
+            }
+            diff.write(out, 3);
+            out.flush();
+        } catch (IOException e) {
+            throw exception("Error while writing diff.", e);
+        }
+
+    }
+
+    public FileAction delete(boolean force) throws VltException {
+        State state = getStatus();
+        switch (state) {
+            case ADDED:
+            case CONFLICTED:
+            case MODIFIED:
+            case REPLACED:
+                if (!force) {
+                    parent.getContext().printMessage(this, "has local modification. use --force to delete anyway");
+                    return FileAction.VOID;
+                }
+                break;
+            case CLEAN:
+            case MISSING:
+            case DELETED:
+                break;
+            case IGNORED:
+            case OBSTRUCTED:
+            case UNKNOWN:
+            case VOID:
+                if (!force) {
+                    parent.getContext().printMessage(this, "is not under version control. use --force to delete anyway");
+                    return FileAction.VOID;
+                }
+                break;
+        }
+        if (entry != null && entry.delete(file)) {
+            entry = null;
+        }
+        return FileAction.DELETED;
+    }
+
+    public FileAction commit(VaultFile remoteFile) throws VltException {
+        if (remoteFile == null) {
+            return doDelete(false);
+        } else {
+            return doUpdate(remoteFile, false);
+        }
+    }
+
+    public boolean revert() throws VltException {
+        State state = getStatus();
+        switch (state) {
+            case ADDED:
+                doDelete(true);
+                entry = null;
+                return true;
+
+            case CONFLICTED:
+                resolved(true);
+                // no break;
+            case DELETED:
+            case MISSING:
+            case MODIFIED:
+                doRevert();
+                return true;
+
+            case IGNORED:
+            case CLEAN:
+            case OBSTRUCTED:
+            case REPLACED:
+            case UNKNOWN:
+            case VOID:
+            default:
+                return false;
+        }
+    }
+
+    public boolean resolved(boolean force) throws VltException {
+        if (getStatus() != State.CONFLICTED) {
+            return false;
+        }
+        if (!force) {
+            // check if the file still contains the diff markers
+            boolean mayContainMarker = false;
+            try {
+                BufferedReader in = new BufferedReader(new FileReader(file));
+                String line;
+                while ((line = in.readLine()) != null) {
+                    if (line.startsWith(Hunk3.MARKER_B[0])
+                            || line.startsWith(Hunk3.MARKER_L[0])
+                            || line.startsWith(Hunk3.MARKER_R[0])
+                            || line.startsWith(Hunk3.MARKER_M[0])) {
+                        mayContainMarker = true;
+                        break;
+                    }
+                }
+            } catch (IOException e) {
+                throw exception("Error while reading file.", e);
+            }
+            if (mayContainMarker) {
+                throw error("File still contains conflict markers. use --force to force resolve.");
+            }
+        }
+
+        // resolve entry
+        try {
+            entry.resolved(getTmpFile(), file, getBaseFile(false));
+        } catch (IOException e) {
+            throw exception("Error while copying files.", e);
+        }
+
+        return true;
+    }
+
+    public FileAction update(VaultFile remoteFile, boolean force)
+            throws VltException {
+        State state  = getStatus();
+        switch (state) {
+            case IGNORED:
+            case OBSTRUCTED:
+            case REPLACED:
+                if (!force || remoteFile == null) {
+                    throw error("update not possible. file is " + state.name().toLowerCase() + ". " +
+                            "Specify --force to overwrite existing files.");
+                }
+                return doUpdate(remoteFile, false);
+
+            case ADDED:
+                if (remoteFile != null) {
+                    if (mergeableWithRemote(remoteFile) != FileAction.VOID) {
+                        throw error("Failed to add file: object of the same name already exists.");
+                    }
+                    return doUpdate(remoteFile, false);
+                } else {
+                    return FileAction.VOID;
+                }
+
+            case CLEAN:
+                if (remoteFile == null) {
+                    return doDelete(false);
+                } else {
+                    if (file.isDirectory()) {
+                        // do nothing
+                        return FileAction.VOID;
+                    } else {
+                        return doUpdate(remoteFile, false);
+                    }
+                }
+
+            case CONFLICTED:
+                if (remoteFile == null) {
+                    try {
+                        if (!entry.revertConflict(file)) {
+                            return FileAction.CONFLICTED;
+                        }
+                    } catch (IOException e) {
+                        throw exception("Error during update.", e);
+                    }
+                    // refetch status, and delete file if clean
+                    return doDelete(getStatus() != State.CLEAN);
+                } else {
+                    try {
+                        if (!entry.revertConflict(file)) {
+                            return doMerge(remoteFile, FileAction.CONFLICTED);
+                        } else {
+                            return doMerge(remoteFile, FileAction.UPDATED);
+                        }
+                    } catch (IOException e) {
+                        throw exception("Error during update.", e);
+                    }
+                }
+
+            case DELETED:
+                if (remoteFile == null) {
+                    // we can delete the entry since someone else deleted it as well
+                    return doDelete(false);
+                } else {
+                    // just update base and entry, in case someone wants to revert
+                    return doUpdate(remoteFile, true);
+                }
+
+            case MISSING:
+                if (remoteFile == null) {
+                    // if file is missing, just delete it
+                    return doDelete(false);
+                } else {
+                    // do update
+                    entry = null;
+                    return doUpdate(remoteFile, false);
+                }
+
+            case MODIFIED:
+                if (remoteFile == null) {
+                    // keep the file
+                    return doDelete(true);
+                } else {
+                    return doMerge(remoteFile, FileAction.VOID);
+                }
+
+            case UNKNOWN:
+                if (remoteFile == null) {
+                    // do nothing
+                    return FileAction.VOID;
+                } else {
+                    // do update
+                    if (file.exists() && !force) {
+                        throw error("Failed to update: object of the same name already exists." +
+                                " Specify --force to overwrite existing files.");
+                    }
+                    return doUpdate(remoteFile, false);
+                }
+
+            case VOID:
+                // do update
+                return doUpdate(remoteFile, false);
+
+            default:
+                throw exception("illegal state: " + state, null);
+        }
+    }
+
+    public FileAction status(VaultFile remoteFile) throws VltException {
+        State state  = getStatus();
+        switch (state) {
+            case IGNORED:
+            case OBSTRUCTED:
+            case REPLACED:
+                return FileAction.CONFLICTED;
+
+            case ADDED:
+                if (remoteFile != null) {
+                    return FileAction.CONFLICTED;
+                } else {
+                    return FileAction.VOID;
+                }
+
+            case CLEAN:
+                if (remoteFile == null) {
+                    return FileAction.DELETED;
+                } else {
+                    if (file.isDirectory()) {
+                        // do nothing
+                        return FileAction.VOID;
+                    } else {
+                        return equalsToRemote(remoteFile)
+                                ? FileAction.VOID
+                                : FileAction.UPDATED;
+                    }
+                }
+
+            case CONFLICTED:
+                // do not probe further
+                return FileAction.CONFLICTED;
+
+            case DELETED:
+                return FileAction.VOID;
+
+            case MISSING:
+                return FileAction.ADDED;
+
+            case MODIFIED:
+                if (remoteFile == null) {
+                    return FileAction.DELETED;
+                } else {
+                    return mergeableWithRemote(remoteFile);
+                }
+
+            case UNKNOWN:
+                if (remoteFile == null) {
+                    // do nothing
+                    return FileAction.VOID;
+                } else {
+                    return FileAction.UPDATED;
+                }
+
+            case VOID:
+                return FileAction.ADDED;
+
+            default:
+                throw exception("illegal state: " + state, null);
+        }
+    }
+
+    public FileAction add(boolean force) throws VltException {
+        State state = getStatus();
+        switch (state) {
+            case ADDED:
+            case CLEAN:
+            case CONFLICTED:
+            case MISSING:
+            case MODIFIED:
+            case OBSTRUCTED:
+            case REPLACED:
+                parent.getContext().printMessage(this, "is already under version control");
+                break;
+            case DELETED:
+                parent.getContext().printMessage(this, "replace not supported yet");
+                break;
+            case IGNORED:
+                parent.getContext().printMessage(this, "failed to add. is ignored.");
+                break;
+            case UNKNOWN:
+            case VOID:
+                return doAdd(force);
+        }
+        return FileAction.VOID;
+    }
+
+    private FileAction doAdd(boolean force) throws VltException {
+        assert entry == null;
+        entry = parent.getEntries().update(getName(), null, null);
+        VltEntryInfo work = entry.create(VltEntryInfo.Type.WORK);
+        try {
+            work.update(file, true);
+        } catch (IOException e) {
+            throw exception("Error while adding file", e);
+        }
+        String contentType = MimeTypes.getMimeType(file.getName(), MimeTypes.APPLICATION_OCTET_STREAM);
+        work.setContentType(contentType);
+        entry.put(work);
+        return FileAction.ADDED;
+    }
+
+    private FileAction doDelete(boolean keepFile)
+            throws VltException {
+        // small hack to remove meta directory. should actually be somewhere else
+        if (file.isDirectory()) {
+            VltDirectory dir = new VltDirectory(parent.getContext(), file);
+            dir.uncontrol();
+        } else {
+            try {
+                if (getBaseFile(false) != null) {
+                    getBaseFile(false).delete();
+                }
+            } catch (IOException e) {
+                throw new VltException(getPath(), "Error while deleting base file.", e);
+            }
+        }
+        if (!keepFile) {
+            file.delete();
+        }
+        entry = null;
+        return FileAction.DELETED;
+    }
+
+    private FileAction doMerge(VaultFile remoteFile, FileAction action)
+            throws VltException {
+        if (remoteFile.isDirectory()) {
+            throw exception("Error while merging. remote is a directory.", null);
+        }
+        // abort merger if actions is already conflict
+        if (action == FileAction.CONFLICTED) {
+            return action;
+        }
+        MetaFile baseFile = getBaseFile(false);
+        MetaFile tmpFile = getTmpFile();
+
+        VltEntryInfo base = entry.base();
+        VltEntryInfo work = entry.work();
+        byte[] lineFeed = MimeTypes.isBinary(remoteFile.getContentType())
+                ? null
+                : LineOutputStream.LS_NATIVE;
+
+        // get the remote file
+        VaultFileCopy copy = null;
+        boolean remoteUpdated = true;
+        try {
+            // first check size and last modified
+            if (!base.checkModified(remoteFile)) {
+                remoteUpdated = false;
+            } else {
+                File temp = tmpFile.openTempFile();
+                copy = VaultFileCopy.copy(remoteFile, temp, lineFeed);
+                // if tmp is equal to the base one, there was no update on the server
+                if (copy.getMd5().equals(base.getMd5())) {
+                    tmpFile.closeTempFile(tmpFile.length() >= 0);
+                    remoteUpdated = false;
+                } else {
+                    tmpFile.closeTempFile(false);
+                }
+            }
+        } catch (IOException e) {
+            throw exception("Error while copying files.", e);
+        }
+        if (!remoteUpdated) {
+            if (work.getMd5().equals(base.getMd5())) {
+                // fix base
+                base.setSize(work.getSize());
+                base.setDate(work.getDate());
+                return FileAction.VOID;
+            } else if (remoteFile.lastModified() > 0) {
+                // normal modification provided
+                return action;
+            }
+        }
+
+        try {
+            // check if binary
+            boolean remoteBT = getRemoteBinaryType(remoteFile, copy);
+            boolean localBT = MimeTypes.isBinary(base.getContentType());
+            if (remoteBT || localBT) {
+                parent.getContext().printMessage(this, "can't merge. binary content");
+                entry.conflict(file, baseFile, tmpFile);
+                return FileAction.CONFLICTED;
+            }
+
+            // do a 3-way diff between the base, the local and the remote one.
+            // we currently do not use document sources, since we don't really have
+            // a label to provide (like rev. num, etc).
+            Reader r0 = baseFile.getReader();
+            Reader r1 = tmpFile.getReader();
+            Document baseDoc = new Document(null, LineElementsFactory.create(new MetaFileDocSource(baseFile), r0, false));
+            Document leftDoc = new Document(null, LineElementsFactory.create(new FileDocumentSource(file), false, Constants.ENCODING));
+            Document rightDoc = new Document(null, LineElementsFactory.create(new MetaFileDocSource(tmpFile), r1, false));
+
+            DocumentDiff3 diff;
+            try {
+                diff = baseDoc.diff3(leftDoc, rightDoc);
+            } finally {
+                IOUtils.closeQuietly(r0);
+                IOUtils.closeQuietly(r1);
+            }
+
+            // save the diff output
+            Writer out = new OutputStreamWriter(FileUtils.openOutputStream(file), Constants.ENCODING);
+            try {
+                diff.write(new DiffWriter(out), false);
+            } catch (IOException e) {
+                IOUtils.closeQuietly(out);
+            }
+
+            if (diff.hasConflicts()) {
+                entry.conflict(file, baseFile, tmpFile);
+                action = FileAction.CONFLICTED;
+            } else {
+                // make the tmp file the new base
+                tmpFile.moveTo(baseFile);
+                base.update(baseFile, true);
+                action = FileAction.MERGED;
+            }
+
+            // and update the 'work'
+            // check if MD5 changes and change action accordingly
+            MD5 oldMd5 = work.getMd5();
+            work.update(file, true);
+            if (oldMd5.equals(work.getMd5())) {
+                action = FileAction.VOID;
+            }
+            // check if remote file provided a last modified
+            if (remoteFile.lastModified() == 0) {
+                if (work.getMd5().equals(base.getMd5())) {
+                    base.setDate(work.getDate());
+                } else {
+                    base.setDate(System.currentTimeMillis());
+                }
+            }
+
+        } catch (IOException e) {
+            throw exception("Error during merge operation.", e);
+        }
+
+        return action;
+    }
+
+    private boolean getRemoteBinaryType(VaultFile remoteFile, VaultFileCopy copy) {
+        // check if binary
+        boolean remoteBT = MimeTypes.isBinary(remoteFile.getContentType());
+        if (copy != null && remoteBT != copy.isBinary()) {
+            parent.getContext().printMessage(this, "Remote Binary type differs from actual data. Content Type: " + remoteFile.getContentType() + " Data is binary: " + copy.isBinary() + ". Using data type.");
+            remoteBT = copy.isBinary();                
+        }
+        return remoteBT;
+    }
+
+    private FileAction mergeableWithRemote(VaultFile remoteFile)
+            throws VltException {
+        if (remoteFile.isDirectory() != file.isDirectory()) {
+            return FileAction.CONFLICTED;
+        }
+        if (file.isDirectory()) {
+            return FileAction.VOID;
+        }
+
+        MetaFile tmpFile = getTmpFile();
+
+        VltEntryInfo base = entry.base();
+
+        // get the remote file
+        byte[] lineFeed = MimeTypes.isBinary(remoteFile.getContentType())
+                ? null
+                : LineOutputStream.LS_NATIVE;
+        VaultFileCopy copy;
+        try {
+            File temp = tmpFile.openTempFile();
+            copy = VaultFileCopy.copy(remoteFile, temp, lineFeed);
+            if (base == null) {
+                tmpFile.closeTempFile(true);
+                // if base is null, file was only added so check the work entry
+                VltEntryInfo work = entry.work();
+                if (copy.getMd5().equals(work.getMd5())) {
+                    return FileAction.VOID;
+                } else {
+                    return FileAction.CONFLICTED;
+                }
+            }
+            
+            // if tmp is equal to the base one, there was not update on the server
+            if (copy.getMd5().equals(base.getMd5())) {
+                tmpFile.closeTempFile(true);
+                return FileAction.VOID;
+            }
+            // keep tmp file
+            tmpFile.closeTempFile(false);
+
+        } catch (IOException e) {
+            throw exception("Error while copying files.", e);
+        }
+
+        // check if binary
+        boolean remoteBT = getRemoteBinaryType(remoteFile, copy);
+        if (remoteBT || MimeTypes.isBinary(base.getContentType())) {
+            return FileAction.CONFLICTED;
+        }
+
+        try {
+            // do a 3-way diff between the base, the local and the remote one.
+            // we currently do not use document sources, since we don't really have
+            // a label to provide (like rev. num, etc).
+
+            MetaFile baseFile = getBaseFile(false);
+            Reader r0 = baseFile.getReader();
+            Reader r1 = tmpFile.getReader();
+            Document baseDoc = new Document(null, LineElementsFactory.create(new MetaFileDocSource(baseFile), r0, false));
+            Document leftDoc = new Document(null, LineElementsFactory.create(new FileDocumentSource(file), false, Constants.ENCODING));
+            Document rightDoc = new Document(null, LineElementsFactory.create(new MetaFileDocSource(tmpFile), r1, false));
+
+            DocumentDiff3 diff;
+            try {
+                diff = baseDoc.diff3(leftDoc, rightDoc);
+            } finally {
+                IOUtils.closeQuietly(r0);
+                IOUtils.closeQuietly(r1);
+            }
+
+            if (diff.hasConflicts()) {
+                return FileAction.CONFLICTED;
+            } else {
+                return FileAction.MERGED;
+            }
+        } catch (IOException e) {
+            throw exception("Error during merge operation.", e);
+        }
+    }
+
+    private void doRevert() throws VltException {
+        if (entry.isDirectory()) {
+            file.mkdir();
+        } else {
+            try {
+                getBaseFile(false).copyTo(getFile(), true);
+            } catch (IOException e) {
+                throw exception("Error while copying files.", e);
+            }
+        }
+        VltEntryInfo base = entry.base();
+        entry.put(base.copyAs(VltEntryInfo.Type.WORK));
+    }
+
+    private boolean equalsToRemote(VaultFile remoteFile)
+            throws VltException {
+        MetaFile tmpFile = getTmpFile();
+
+        // copy file
+        byte[] lineFeed = MimeTypes.isBinary(remoteFile.getContentType())
+                ? null
+                : LineOutputStream.LS_NATIVE;
+        VaultFileCopy copy;
+        File temp = null;
+        try {
+            temp = tmpFile.openTempFile();
+            copy = VaultFileCopy.copy(remoteFile, temp, lineFeed);
+        } catch (IOException e) {
+            throw exception("Error while copying files.", e);
+        } finally {
+            if (tmpFile != null) {
+                try {
+                    tmpFile.closeTempFile(true);
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+        // if md5 is equal, no update
+        VltEntryInfo base = entry.base();
+        return copy.getMd5().equals(base.getMd5());
+    }
+
+    private FileAction doUpdate(VaultFile remoteFile, boolean baseOnly)
+            throws VltException {
+        FileAction action;
+        VltEntryInfo base;
+        if (entry == null || entry.base() == null) {
+            // new entry
+            action = FileAction.ADDED;
+            entry = parent.getEntries().update(getName(), remoteFile.getAggregatePath(), remoteFile.getRepoRelPath());
+            base = entry.create(VltEntryInfo.Type.BASE);
+            entry.put(base);
+        } else {
+            action = FileAction.UPDATED;
+            base = entry.base();
+
+            // quick check if modified
+            if (!base.checkModified(remoteFile)) {
+                return FileAction.VOID;
+            }
+        }
+        long lastMod = remoteFile.lastModified();
+        if (lastMod == 0) {
+            lastMod = System.currentTimeMillis();
+        }
+        base.setDate(lastMod);
+
+        if (remoteFile.isDirectory()) {
+            if (!baseOnly) {
+                // ensure controlled
+                // todo: this does not belong here
+                if (entry.work() != null) {
+                    action = FileAction.VOID;
+                } else {
+                    entry.put(base.copyAs(VltEntryInfo.Type.WORK));
+                }
+                file.mkdir();
+                file.setLastModified(base.getDate());
+                VltDirectory dir = new VltDirectory(parent.getContext(), file);
+                if (!dir.isControlled()) {
+                    dir.control(remoteFile.getPath(), remoteFile.getControllingAggregate().getPath());
+                    action = FileAction.ADDED;
+                }
+            }
+        } else {
+            MetaFile baseFile = getBaseFile(true);
+
+            // copy file
+            byte[] lineFeed = MimeTypes.isBinary(remoteFile.getContentType())
+                    ? null
+                    : LineOutputStream.LS_NATIVE;
+            VaultFileCopy copy;
+            try {
+                File temp = baseFile.openTempFile();
+                copy = VaultFileCopy.copy(remoteFile, temp, lineFeed);
+                baseFile.closeTempFile(false);
+            } catch (IOException e) {
+                throw exception("Error while copying files.", e);
+            }
+            // if md5 is equal, no update
+            if (copy.getMd5().equals(base.getMd5())) {
+                action = FileAction.VOID;
+            }
+
+            if (action == FileAction.VOID
+                    && (base.getContentType() != null || remoteFile.getContentType() != null)
+                    && (base.getContentType() == null || !base.getContentType().equals(remoteFile.getContentType()))) {
+                action = FileAction.UPDATED;
+            }
+
+            // update infos
+            VltEntryInfo work = entry.work();
+            base.setContentType(remoteFile.getContentType());
+            base.setSize(copy.getLength());
+            base.setMd5(copy.getMd5());
+            if (!baseOnly) {
+                // only copy if not equal
+                if (work == null || !work.getMd5().equals(copy.getMd5()) || !getFile().exists()) {
+                    try {
+                        baseFile.copyTo(getFile(), true);
+                        entry.put(base.copyAs(VltEntryInfo.Type.WORK));
+                    } catch (IOException e) {
+                        throw exception("Error while copying files.", e);
+                    }
+                }
+            }
+        }
+        return action;
+    }
+
+    private VltException exception(String msg, Throwable cause) {
+        return parent.getContext().exception(getPath(), msg, cause);
+    }
+
+    private VltException error(String msg) {
+        return parent.getContext().error(getPath(), msg);
+    }
+
+    //-----------------------------------------------------< DocumentSource >---
+
+    public String getLabel() {
+        return getName();
+    }
+
+    public String getLocation() {
+        File cwd = parent.getContext().getCwd();
+        return PathUtil.getRelativeFilePath(cwd.getPath(), file.getPath());
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/AbstractAction.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/AbstractAction.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/AbstractAction.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/AbstractAction.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.vlt.actions;
+
+import org.apache.jackrabbit.vault.fs.api.VaultFile;
+import org.apache.jackrabbit.vault.vlt.VltDirectory;
+import org.apache.jackrabbit.vault.vlt.VltException;
+import org.apache.jackrabbit.vault.vlt.VltFile;
+
+/**
+ * <code>AbstractAction</code>...
+ *
+ */
+abstract public class AbstractAction implements Action {
+
+    private boolean onlyControlled;
+
+    public boolean onlyControlled() {
+        return onlyControlled;
+    }
+
+    public void setOnlyControlled(boolean onlyControlled) {
+        this.onlyControlled = onlyControlled;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void run(VltDirectory dir, VltFile file, VaultFile remoteFile)
+            throws VltException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return <code>true</code> if the given directory is controlled or
+     *         {@link #onlyControlled} is <code>false</code>;
+     */
+    public boolean run(VltDirectory dir, VaultFile remoteDir)
+            throws VltException {
+        return dir.isControlled() || !onlyControlled;
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/Action.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/Action.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/Action.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/Action.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.vlt.actions;
+
+import org.apache.jackrabbit.vault.fs.api.VaultFile;
+import org.apache.jackrabbit.vault.vlt.VltContext;
+import org.apache.jackrabbit.vault.vlt.VltDirectory;
+import org.apache.jackrabbit.vault.vlt.VltException;
+import org.apache.jackrabbit.vault.vlt.VltFile;
+
+/**
+ * <code>Action</code>...
+ *
+ */
+public interface Action {
+
+    /**
+     * Executes this action on the given context.
+     * @param ctx context
+     * @throws VltException if an error occurs
+     */
+    public void run(VltContext ctx) throws VltException;
+
+    /**
+     * Executes this action for the given files.
+     * @param dir vlt directory
+     * @param file local file
+     * @param remoteFile remote file
+     * @throws VltException if an error occurs
+     */
+    public void run(VltDirectory dir, VltFile file, VaultFile remoteFile)
+            throws VltException;
+
+
+    /**
+     * Executes this action on the given directory
+     * @param dir local directory
+     * @param remoteDir remote directory
+     * @throws VltException if an error occurs
+     *
+     * @return <code>true</code> if proceed; <code>false</code> to abort
+     */
+    public boolean run(VltDirectory dir, VaultFile remoteDir) throws VltException;
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/Add.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/Add.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/Add.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/Add.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.vlt.actions;
+
+import java.io.File;
+import java.util.List;
+
+import org.apache.jackrabbit.vault.fs.api.VaultFile;
+import org.apache.jackrabbit.vault.vlt.FileAction;
+import org.apache.jackrabbit.vault.vlt.VltDirectory;
+import org.apache.jackrabbit.vault.vlt.VltException;
+import org.apache.jackrabbit.vault.vlt.VltFile;
+
+/**
+ * <code>Resolved</code>...
+ *
+ */
+public class Add extends BaseAction {
+
+    private boolean force;
+
+    public Add(File localDir, List<File> localFiles, boolean nonRecursive, boolean force) {
+        super(localDir, localFiles, nonRecursive);
+        setDirsAsFiles(true);
+        this.force = force;
+    }
+
+    public void run(VltDirectory dir, VltFile file, VaultFile remoteFile) throws VltException {
+        FileAction action = file.add(force);
+        dir.getContext().printAction(file, action);
+
+        // if file is a directory, put under version control
+        if (file.canDescend()) {
+            VltDirectory child = file.descend();
+            if (!child.isControlled()) {
+                // this is a bit a hack
+                child.control(dir.getEntries().getPath() + "/" + file.getName(), null);
+            }
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/BaseAction.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/BaseAction.java?rev=1512568&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/BaseAction.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-vlt/src/main/java/org/apache/jackrabbit/vault/vlt/actions/BaseAction.java Sat Aug 10 05:53:42 2013
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.vault.vlt.actions;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.jackrabbit.vault.vlt.VltContext;
+import org.apache.jackrabbit.vault.vlt.VltDirectory;
+import org.apache.jackrabbit.vault.vlt.VltException;
+import org.apache.jackrabbit.vault.vlt.meta.Ignored;
+
+/**
+ * Basic implementation of an abstract action
+ */
+public class BaseAction extends AbstractAction {
+
+    private final File localDir;
+
+    private final List<File> localFiles;
+
+    private final boolean nonRecursive;
+
+    private boolean dirsAsFiles;
+
+    private boolean withRemote;
+
+    public BaseAction(File localDir, List<File> localFiles, boolean nonRecursive) {
+        this.localDir = localDir;
+        this.localFiles = localFiles == null ? new LinkedList<File>() : localFiles;
+        this.nonRecursive = nonRecursive;
+    }
+
+    public boolean isWithRemote() {
+        return withRemote;
+    }
+
+    public void setWithRemote(boolean withRemote) {
+        this.withRemote = withRemote;
+    }
+
+    public void setDirsAsFiles(boolean dirsAsFiles) {
+        this.dirsAsFiles = dirsAsFiles;
+    }
+
+    public void run(VltContext ctx, VltTree infos) throws VltException {
+        if (withRemote) {
+            for (VltTree.Info i: infos.infos()) {
+                i.dir.applyWithRemote(this, i.names, nonRecursive);
+            }
+        } else {
+            for (VltTree.Info i: infos.infos()) {
+                // special check for jcr_root
+                if (!ctx.getExportRoot().getJcrRoot().getParentFile().equals(i.dir.getDirectory())) {
+                    i.dir.assertControlled();
+                }
+                i.dir.apply(this, i.names, nonRecursive);
+            }
+        }
+    }
+    
+    public void run(VltContext ctx) throws VltException {
+        VltTree infos = new VltTree(ctx, nonRecursive);
+        infos.setDirsAsFiles(dirsAsFiles);
+        try {
+            if (localFiles.isEmpty()) {
+                infos.add(localDir);
+            } else {
+                infos.addAll(localFiles);
+            }
+        } catch (IOException e) {
+            throw new VltException("Unable to perform command.", e);
+        }
+        // get common ancestor
+        VltDirectory root = new VltDirectory(ctx, new File(infos.getRootPath()));
+        // mount fs at the top most directory
+        if (root.isControlled()) {
+            ctx.setFsRoot(root);
+        }
+        // define globally ignored
+        ctx.setGlobalIgnored(new Ignored(ctx, root.getDirectory()));
+
+        run(ctx, infos);
+    }
+
+}
\ No newline at end of file