diff --git a/include/cli/modules/deploy.php b/include/cli/modules/deploy.php
index 74a11f02c3b103a106ac495eb225f86f7acad818..eb76a165f0f9784d8b9ad1c7debf4edb735861d3 100644
--- a/include/cli/modules/deploy.php
+++ b/include/cli/modules/deploy.php
@@ -27,6 +27,9 @@ class Deployment extends Unpacker {
             'action'=>'store_true',
             'help'=>'Use `git ls-files -s` as files source. Eliminates
                 possibility of deploying untracked files');
+        $this->options['force'] = array('-f', '--force',
+            'action'=>'store_true',
+            'help'=>'Deploy all files, even if they have not changed');
         # super(*args);
         call_user_func_array(array('parent', '__construct'), func_get_args());
     }
@@ -45,13 +48,14 @@ class Deployment extends Unpacker {
      * Removes files from the deployment location that no longer exist in
      * the local repository
      */
-    function clean($local, $destination, $recurse=0, $exclude=false) {
+    function clean($local, $destination, $root, $recurse=0, $exclude=false) {
         $dryrun = $this->getOption('dry-run', false);
         $verbose = $dryrun || $this->getOption('verbose');
         $destination = rtrim($destination, '/') . '/';
         $contents = glob($destination.'{,.}*', GLOB_BRACE|GLOB_NOSORT);
         foreach ($contents as $i=>$file) {
-            if ($this->exclude($exclude, $file))
+            $relative = str_replace($root, "", $file);
+            if ($this->exclude($exclude, $relative))
                 continue;
             if (is_file($file)) {
                 $ltarget = $local . '/' . basename($file);
@@ -74,12 +78,13 @@ class Deployment extends Unpacker {
             foreach ($folders as $dir) {
                 if (in_array(basename($dir), array('.','..')))
                     continue;
-                elseif ($this->exclude($exclude, $dir))
+                $relative = str_replace($root, "", $dir);
+                if ($this->exclude($exclude, "$relative/"))
                     continue;
                 $this->clean(
                     $local.'/'.basename($dir),
                     $destination.basename($dir),
-                    $recurse - 1, $exclude);
+                    $root, $recurse - 1, $exclude);
             }
         }
         if (!$contents || !glob($destination.'{,.}*', GLOB_BRACE|GLOB_NOSORT)) {
@@ -123,6 +128,7 @@ class Deployment extends Unpacker {
             return false;
 
         $source = file_get_contents($src);
+        $original = crc32($source);
         $source = preg_replace(':<script(.*) src="([^"]+)\.js"></script>:',
             '<script$1 src="$2.js?'.$short.'"></script>',
             $source);
@@ -142,11 +148,20 @@ class Deployment extends Unpacker {
             "$1ini_set('$2', '0'); // Set by installer",
             $source);
 
-        return $source;
+        // return FALSE if the edited contents do not differ from the
+        // original contents
+        return $original != crc32($source) ? $source : false;
+    }
+
+    function isChanged($source, $hash=false) {
+        $local = str_replace($this->source.'/', '', $source);
+        $hash = $hash ?: $this->hashFile($source);
+        list($shash, $flag) = explode(':', $this->readManifest($local));
+        return ($flag === 'rewrite') ? $flag : $shash != $hash;
     }
 
-    function copyFile($source, $dest, $hash=false, $mode=0644) {
-        $contents = $this->getEditedContents($source);
+    function copyFile($source, $dest, $hash=false, $mode=0644, $contents=false) {
+        $contents = $contents ?: $this->getEditedContents($source);
         if ($contents === false)
             // Regular file
             return parent::copyFile($source, $dest, $hash, $mode);
@@ -154,7 +169,7 @@ class Deployment extends Unpacker {
         if (!file_put_contents($dest, $contents))
             $this->fail($dest.": Unable to apply rewrite rules");
 
-        $this->updateManifest($source, $hash);
+        $this->updateManifest($source, "$hash:rewrite");
         return chmod($dest, $mode);
     }
 
@@ -188,16 +203,21 @@ class Deployment extends Unpacker {
 
         $dryrun = $this->getOption('dry-run', false);
         $verbose = $this->getOption('verbose') || $dryrun;
+        $force = $this->getOption('force');
         while ($line = stream_get_line($pipes[1], 255, "\x00")) {
             list($mode, $hash, , $path) = preg_split('/\s+/', $line);
             $src = $source.$local.$path;
             if ($this->exclude($exclude, $src))
                 continue;
-            if (!$this->isChanged($src, $hash))
+            if (!$force && false === ($flag = $this->isChanged($src, $hash)))
                 continue;
             $dst = $destination.$path;
-            if ($verbose)
-                $this->stdout->write($dst."\n");
+            if ($verbose) {
+                $msg = $dst;
+                if (is_string($flag))
+                    $msg = "$msg ({$flag})";
+                $this->stdout->write("$msg\n");
+            }
             if ($dryrun)
                 continue;
             if (!is_dir(dirname($dst)))
@@ -241,7 +261,7 @@ class Deployment extends Unpacker {
             $exclusions);
         # Unpack the include folder
         $this->unpackage("$root/include/{,.}*", $include, -1,
-            array("*/include/ost-config.php"));
+            array("*/include/ost-config.php", "*.sw[a-z]"));
         if (!$options['dry-run']) {
             if ($include != "{$this->destination}/include/")
                 $this->change_include_dir($include);
@@ -249,14 +269,16 @@ class Deployment extends Unpacker {
 
         if ($options['clean']) {
             // Clean everything but include folder first
-            $this->clean($root, $this->destination, -1,
-                array($include, "setup/"));
-            $this->clean("$root/include", $include, -1,
+            $local_include = str_replace($this->destination, "", $include);
+            $this->clean($root, $this->destination, $this->destination, -1,
+                array($local_include, "setup/"));
+            $this->clean("$root/include", $include, $include, -1,
                 array("ost-config.php","settings.php","plugins/",
-                "*/.htaccess"));
+                "*/.htaccess", ".MANIFEST"));
         }
 
-        $this->writeManifest($this->destination);
+        if (!$options['dry-run'])
+            $this->writeManifest($this->destination);
     }
 }
 
diff --git a/include/cli/modules/unpack.php b/include/cli/modules/unpack.php
index 75b73353deb0f212bed2a65cb420707c5b3eaedd..75fa1090f3790f7eefed5fac82581fd075de8a60 100644
--- a/include/cli/modules/unpack.php
+++ b/include/cli/modules/unpack.php
@@ -99,7 +99,7 @@ class Unpacker extends Module {
         if (!is_file($path))
             return null;
 
-        if (!preg_match_all('/^(\w+) (.+)$/mu', file_get_contents($path),
+        if (!preg_match_all('/^([\w:,]+) (.+)$/mu', file_get_contents($path),
             $lines, PREG_PATTERN_ORDER)
         ) {
             return null;
@@ -156,6 +156,7 @@ class Unpacker extends Module {
     function unpackage($folder, $destination, $recurse=0, $exclude=false) {
         $dryrun = $this->getOption('dry-run', false);
         $verbose = $this->getOption('verbose') || $dryrun;
+        $force = $this->getOption('force', false);
         if (substr($destination, -1) !== '/')
             $destination .= '/';
         foreach (glob($folder, GLOB_BRACE|GLOB_NOSORT) as $file) {
@@ -164,10 +165,15 @@ class Unpacker extends Module {
             if (is_file($file)) {
                 $target = $destination . basename($file);
                 $hash = $this->hashFile($file);
-                if (is_file($target) && !$this->isChanged($file, $hash))
+                if (!$force && is_file($target)
+                        && false === ($flag = $this->isChanged($file, $hash)))
                     continue;
-                if ($verbose)
-                    $this->stdout->write($target."\n");
+                if ($verbose) {
+                    $msg = $target;
+                    if (is_string($flag))
+                        $msg = "$msg ({$flag})";
+                    $this->stdout->write("$msg\n");
+                }
                 if ($dryrun)
                     continue;
                 if (!is_dir($destination))