Bug 900251 - Add support for add-if-not instruction added by bug 759469 to the mar generation scripts. r=nthomas, a=sylvestre
authorRobert Strong <robert.bugzilla@gmail.com>
Sat, 08 Mar 2014 18:57:34 -0800
changeset 183222 fabe14145cd73b0ed759255b39b778f05c79f1eb
parent 183221 fe8c8f40fea69102882f778fbef67cb328312b1e
child 183223 95e8bd9007a02c94df251110bd69f0c4e7d33b61
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnthomas, sylvestre
bugs900251, 759469
milestone29.0a2
Bug 900251 - Add support for add-if-not instruction added by bug 759469 to the mar generation scripts. r=nthomas, a=sylvestre
config/createprecomplete.py
tools/update-packaging/common.sh
tools/update-packaging/make_full_update.sh
tools/update-packaging/make_incremental_update.sh
tools/update-packaging/make_incremental_updates.py
tools/update-packaging/test/buildrefmars.sh
tools/update-packaging/test/common.sh
tools/update-packaging/test/diffmar.sh
tools/update-packaging/test/from-mac/Contents/MacOS/application.ini
tools/update-packaging/test/from-mac/Contents/MacOS/diff-patch-larger-than-file.txt
tools/update-packaging/test/from-mac/Contents/MacOS/distribution/extensions/diff/diff-patch-larger-than-file.txt
tools/update-packaging/test/from-mac/Contents/MacOS/extensions/diff/diff-patch-larger-than-file.txt
tools/update-packaging/test/from-mac/Contents/MacOS/force.txt
tools/update-packaging/test/from-mac/Contents/MacOS/readme.txt
tools/update-packaging/test/from-mac/Contents/MacOS/removed.txt
tools/update-packaging/test/from-mac/Contents/MacOS/same.bin
tools/update-packaging/test/from-mac/Contents/MacOS/same.txt
tools/update-packaging/test/from-mac/Contents/MacOS/searchplugins/diff/diff-patch-larger-than-file.txt
tools/update-packaging/test/from-mac/Contents/MacOS/update-settings.ini
tools/update-packaging/test/from-mac/Contents/MacOS/update.manifest
tools/update-packaging/test/from-mac/Contents/MacOS/{foodir/channel-prefs.js
tools/update-packaging/test/from-mac/Contents/MacOS/{foodir/diff-patch-larger-than-file.txt
tools/update-packaging/test/from-mac/Contents/MacOS/{foodir/force.txt
tools/update-packaging/test/from-mac/Contents/MacOS/{foodir/readme.txt
tools/update-packaging/test/from-mac/Contents/MacOS/{foodir/removed.txt
tools/update-packaging/test/from-mac/Contents/MacOS/{foodir/same.bin
tools/update-packaging/test/from-mac/Contents/MacOS/{foodir/same.txt
tools/update-packaging/test/from-mac/Contents/MacOS/{foodir/update.manifest
tools/update-packaging/test/from-mac/precomplete
tools/update-packaging/test/from/application.ini
tools/update-packaging/test/from/channel-prefs.js
tools/update-packaging/test/from/distribution/extensions/diff/diff-patch-larger-than-file.txt
tools/update-packaging/test/from/precomplete
tools/update-packaging/test/from/update-settings.ini
tools/update-packaging/test/from/{foodir/channel-prefs.js
tools/update-packaging/test/make_full_update.sh
tools/update-packaging/test/runtests.sh
tools/update-packaging/test/testpatchfile.txt
tools/update-packaging/test/to-mac/Contents/MacOS/addFeedPrefs.js
tools/update-packaging/test/to-mac/Contents/MacOS/added.txt
tools/update-packaging/test/to-mac/Contents/MacOS/application.ini
tools/update-packaging/test/to-mac/Contents/MacOS/diff-patch-larger-than-file.bin
tools/update-packaging/test/to-mac/Contents/MacOS/diff-patch-larger-than-file.txt
tools/update-packaging/test/to-mac/Contents/MacOS/distribution/extensions/added/file.txt
tools/update-packaging/test/to-mac/Contents/MacOS/distribution/extensions/diff/diff-patch-larger-than-file.txt
tools/update-packaging/test/to-mac/Contents/MacOS/extensions/added/file.txt
tools/update-packaging/test/to-mac/Contents/MacOS/extensions/diff/diff-patch-larger-than-file.txt
tools/update-packaging/test/to-mac/Contents/MacOS/force.txt
tools/update-packaging/test/to-mac/Contents/MacOS/precomplete
tools/update-packaging/test/to-mac/Contents/MacOS/readme.txt
tools/update-packaging/test/to-mac/Contents/MacOS/removed-files
tools/update-packaging/test/to-mac/Contents/MacOS/same.bin
tools/update-packaging/test/to-mac/Contents/MacOS/same.txt
tools/update-packaging/test/to-mac/Contents/MacOS/searchplugins/added/file.txt
tools/update-packaging/test/to-mac/Contents/MacOS/searchplugins/diff/diff-patch-larger-than-file.txt
tools/update-packaging/test/to-mac/Contents/MacOS/update-settings.ini
tools/update-packaging/test/to-mac/Contents/MacOS/update.manifest
tools/update-packaging/test/to-mac/Contents/MacOS/{foodir/added.txt
tools/update-packaging/test/to-mac/Contents/MacOS/{foodir/channel-prefs.js
tools/update-packaging/test/to-mac/Contents/MacOS/{foodir/diff-patch-larger-than-file.txt
tools/update-packaging/test/to-mac/Contents/MacOS/{foodir/force.txt
tools/update-packaging/test/to-mac/Contents/MacOS/{foodir/readme.txt
tools/update-packaging/test/to-mac/Contents/MacOS/{foodir/same.bin
tools/update-packaging/test/to-mac/Contents/MacOS/{foodir/same.txt
tools/update-packaging/test/to-mac/Contents/MacOS/{foodir/update.manifest
tools/update-packaging/test/to-mac/precomplete
tools/update-packaging/test/to/application.ini
tools/update-packaging/test/to/channel-prefs.js
tools/update-packaging/test/to/distribution/extensions/added/file.txt
tools/update-packaging/test/to/distribution/extensions/diff/diff-patch-larger-than-file.txt
tools/update-packaging/test/to/precomplete
tools/update-packaging/test/to/removed-files
tools/update-packaging/test/to/update-settings.ini
tools/update-packaging/test/to/{foodir/channel-prefs.js
tools/update-packaging/test_make_incremental_updates.py
--- a/config/createprecomplete.py
+++ b/config/createprecomplete.py
@@ -15,24 +15,27 @@ def get_build_entries(root_path):
     """
     rel_file_path_set = set()
     rel_dir_path_set = set()
     for root, dirs, files in os.walk(root_path):
         for file_name in files:
             parent_dir_rel_path = root[len(root_path)+1:]
             rel_path_file = os.path.join(parent_dir_rel_path, file_name)
             rel_path_file = rel_path_file.replace("\\", "/")
-            if not (rel_path_file.endswith("channel-prefs.js")):
+            if not (rel_path_file.endswith("channel-prefs.js") or
+                    rel_path_file.endswith("update-settings.ini") or
+                    rel_path_file.find("distribution/") != -1):
                 rel_file_path_set.add(rel_path_file)
 
         for dir_name in dirs:
             parent_dir_rel_path = root[len(root_path)+1:]
             rel_path_dir = os.path.join(parent_dir_rel_path, dir_name)
             rel_path_dir = rel_path_dir.replace("\\", "/")+"/"
-            rel_dir_path_set.add(rel_path_dir)
+            if rel_path_dir.find("distribution/") == -1:
+                rel_dir_path_set.add(rel_path_dir)
 
     rel_file_path_list = list(rel_file_path_set)
     rel_file_path_list.sort(reverse=True)
     rel_dir_path_list = list(rel_dir_path_set)
     rel_dir_path_list.sort(reverse=True)
 
     return rel_file_path_list, rel_dir_path_list
 
--- a/tools/update-packaging/common.sh
+++ b/tools/update-packaging/common.sh
@@ -34,55 +34,106 @@ copy_perm() {
     chmod 0755 "$target"
   else
     chmod 0644 "$target"
   fi
 }
 
 make_add_instruction() {
   f="$1"
+  filev2="$2"
+  # The third param will be an empty string when a file add instruction is only
+  # needed in the version 2 manifest. This only happens when the file has an
+  # add-if-not instruction in the version 3 manifest. This is due to the
+  # precomplete file prior to the version 3 manifest having a remove instruction
+  # for this file so the file is removed before applying a complete update.
+  filev3="$3"
 
   # Used to log to the console
-  if [ $2 ]; then
+  if [ $4 ]; then
     forced=" (forced)"
   else
     forced=
   fi
 
   is_extension=$(echo "$f" | grep -c 'distribution/extensions/.*/')
   if [ $is_extension = "1" ]; then
     # Use the subdirectory of the extensions folder as the file to test
     # before performing this add instruction.
     testdir=$(echo "$f" | sed 's/\(.*distribution\/extensions\/[^\/]*\)\/.*/\1/')
-    notice "     add-if: $f$forced"
-    echo "add-if \"$testdir\" \"$f\""
+    notice "     add-if \"$testdir\" \"$f\""
+    echo "add-if \"$testdir\" \"$f\"" >> $filev2
+    if [ ! $filev3 = "" ]; then
+      echo "add-if \"$testdir\" \"$f\"" >> $filev3
+    fi
   else
-    notice "        add: $f$forced"
-    echo "add \"$f\""
+    notice "        add \"$f\"$forced"
+    echo "add \"$f\"" >> $filev2
+    if [ ! $filev3 = "" ]; then
+      echo "add \"$f\"" >> $filev3
+    fi
+  fi
+}
+
+check_for_add_if_not_update() {
+  add_if_not_file_chk="$1"
+
+  if [ `basename $add_if_not_file_chk` = "channel-prefs.js" -o \
+       `basename $add_if_not_file_chk` = "update-settings.ini" ]; then
+    ## "true" *giggle*
+    return 0;
   fi
+  ## 'false'... because this is bash. Oh yay!
+  return 1;
+}
+
+check_for_add_to_manifestv2() {
+  add_if_not_file_chk="$1"
+
+  if [ `basename $add_if_not_file_chk` = "update-settings.ini" ]; then
+    ## "true" *giggle*
+    return 0;
+  fi
+  ## 'false'... because this is bash. Oh yay!
+  return 1;
+}
+
+make_add_if_not_instruction() {
+  f="$1"
+  filev3="$2"
+
+  notice " add-if-not \"$f\" \"$f\""
+  echo "add-if-not \"$f\" \"$f\"" >> $filev3
 }
 
 make_patch_instruction() {
   f="$1"
+  filev2="$2"
+  filev3="$3"
+
   is_extension=$(echo "$f" | grep -c 'distribution/extensions/.*/')
   if [ $is_extension = "1" ]; then
     # Use the subdirectory of the extensions folder as the file to test
     # before performing this add instruction.
     testdir=$(echo "$f" | sed 's/\(.*distribution\/extensions\/[^\/]*\)\/.*/\1/')
-    notice "   patch-if: $f"
-    echo "patch-if \"$testdir\" \"$f.patch\" \"$f\""
+    notice "   patch-if \"$testdir\" \"$f.patch\" \"$f\""
+    echo "patch-if \"$testdir\" \"$f.patch\" \"$f\"" >> $filev2
+    echo "patch-if \"$testdir\" \"$f.patch\" \"$f\"" >> $filev3
   else
-    notice "      patch: $f"
-    echo "patch \"$f.patch\" \"$f\""
+    notice "      patch \"$f.patch\" \"$f\""
+    echo "patch \"$f.patch\" \"$f\"" >> $filev2
+    echo "patch \"$f.patch\" \"$f\"" >> $filev3
   fi
 }
 
 append_remove_instructions() {
   dir="$1"
   filev2="$2"
+  filev3="$3"
+
   if [ -f "$dir/removed-files" ]; then
     prefix=
     listfile="$dir/removed-files"
   elif [ -f "$dir/Contents/MacOS/removed-files" ]; then
     prefix=Contents/MacOS/
     listfile="$dir/Contents/MacOS/removed-files"
   fi
   if [ -n "$listfile" ]; then
@@ -107,44 +158,45 @@ append_remove_instructions() {
                 fixedprefix=""
               else
                 f=$(echo $f | sed -e 's:^\.\.\/::')
                 fixedprefix=$(echo "$prefix" | sed -e 's:[^\/]*\/$::')
               fi
             fi
           fi
           if [ $(echo "$f" | grep -c '\/$') = 1 ]; then
-            notice "      rmdir: $fixedprefix$f"
+            notice "      rmdir \"$fixedprefix$f\""
             echo "rmdir \"$fixedprefix$f\"" >> $filev2
+            echo "rmdir \"$fixedprefix$f\"" >> $filev3
           elif [ $(echo "$f" | grep -c '\/\*$') = 1 ]; then
             # Remove the *
             f=$(echo "$f" | sed -e 's:\*$::')
-            notice "    rmrfdir: $fixedprefix$f"
+            notice "    rmrfdir \"$fixedprefix$f\""
             echo "rmrfdir \"$fixedprefix$f\"" >> $filev2
+            echo "rmrfdir \"$fixedprefix$f\"" >> $filev3
           else
-            notice "     remove: $fixedprefix$f"
+            notice "     remove \"$fixedprefix$f\""
             echo "remove \"$fixedprefix$f\"" >> $filev2
+            echo "remove \"$fixedprefix$f\"" >> $filev3
           fi
         fi
       fi
     done
   fi
 }
 
 # List all files in the current directory, stripping leading "./"
-# Skip the channel-prefs.js file as it should not be included in any
-# generated MAR files (see bug 306077). Pass a variable name and it will be
-# filled as an array.
+# Pass a variable name and it will be filled as an array.
 list_files() {
   count=0
 
   find . -type f \
-    ! -name "channel-prefs.js" \
     ! -name "update.manifest" \
     ! -name "updatev2.manifest" \
+    ! -name "updatev3.manifest" \
     ! -name "temp-dirlist" \
     ! -name "temp-filelist" \
     | sed 's/\.\/\(.*\)/\1/' \
     | sort -r > "temp-filelist"
   while read file; do
     eval "${1}[$count]=\"$file\""
     (( count++ ))
   done < "temp-filelist"
--- a/tools/update-packaging/make_full_update.sh
+++ b/tools/update-packaging/make_full_update.sh
@@ -39,17 +39,18 @@ targetdir="$2"
 # Prevent the workdir from being inside the targetdir so it isn't included in
 # the update mar.
 if [ $(echo "$targetdir" | grep -c '\/$') = 1 ]; then
   # Remove the /
   targetdir=$(echo "$targetdir" | sed -e 's:\/$::')
 fi
 workdir="$targetdir.work"
 updatemanifestv2="$workdir/updatev2.manifest"
-targetfiles="updatev2.manifest"
+updatemanifestv3="$workdir/updatev3.manifest"
+targetfiles="updatev2.manifest updatev3.manifest"
 
 mkdir -p "$workdir"
 
 # On Mac, the precomplete file added by Bug 386760 will cause OS X to reload the
 # Info.plist so it launches the right architecture, bug 600098
 
 # Generate a list of all files in the target directory.
 pushd "$targetdir"
@@ -61,47 +62,58 @@ if [ ! -f "precomplete" ]; then
   notice "precomplete file is missing!"
   exit 1
 fi
 
 list_files files
 
 popd
 
-# Add the type of update to the beginning of version 2 update manifest.
+# Add the type of update to the beginning of the update manifests.
 > $updatemanifestv2
+> $updatemanifestv3
 notice ""
-notice "Adding type instruction to file 'updatev2.manifest'"
-notice "       type: complete"
+notice "Adding type instruction to update manifests"
+notice "       type complete"
 echo "type \"complete\"" >> $updatemanifestv2
+echo "type \"complete\"" >> $updatemanifestv3
 
 notice ""
-notice "Adding file add instructions to file 'updatev2.manifest'"
+notice "Adding file add instructions to update manifests"
 num_files=${#files[*]}
 
 for ((i=0; $i<$num_files; i=$i+1)); do
   f="${files[$i]}"
 
-  make_add_instruction "$f" >> $updatemanifestv2
+  if check_for_add_if_not_update "$f"; then
+    make_add_if_not_instruction "$f" "$updatemanifestv3"
+    if check_for_add_to_manifestv2 "$f"; then
+      make_add_instruction "$f" "$updatemanifestv2" "" 1
+    fi
+  else
+    make_add_instruction "$f" "$updatemanifestv2" "$updatemanifestv3"
+  fi
 
   dir=$(dirname "$f")
   mkdir -p "$workdir/$dir"
   $BZIP2 -cz9 "$targetdir/$f" > "$workdir/$f"
   copy_perm "$targetdir/$f" "$workdir/$f"
 
   targetfiles="$targetfiles \"$f\""
 done
 
 # Append remove instructions for any dead files.
 notice ""
 notice "Adding file and directory remove instructions from file 'removed-files'"
-append_remove_instructions "$targetdir" "$updatemanifestv2"
+append_remove_instructions "$targetdir" "$updatemanifestv2" "$updatemanifestv3"
 
 $BZIP2 -z9 "$updatemanifestv2" && mv -f "$updatemanifestv2.bz2" "$updatemanifestv2"
+$BZIP2 -z9 "$updatemanifestv3" && mv -f "$updatemanifestv3.bz2" "$updatemanifestv3"
 
 eval "$MAR -C \"$workdir\" -c output.mar $targetfiles"
 mv -f "$workdir/output.mar" "$archive"
 
 # cleanup
 rm -fr "$workdir"
 
 notice ""
 notice "Finished"
+notice ""
--- a/tools/update-packaging/make_incremental_update.sh
+++ b/tools/update-packaging/make_incremental_update.sh
@@ -82,17 +82,18 @@ newdir="$3"
 # Prevent the workdir from being inside the targetdir so it isn't included in
 # the update mar.
 if [ $(echo "$newdir" | grep -c '\/$') = 1 ]; then
   # Remove the /
   newdir=$(echo "$newdir" | sed -e 's:\/$::')
 fi
 workdir="$newdir.work"
 updatemanifestv2="$workdir/updatev2.manifest"
-archivefiles="updatev2.manifest"
+updatemanifestv3="$workdir/updatev3.manifest"
+archivefiles="updatev2.manifest updatev3.manifest"
 
 mkdir -p "$workdir"
 
 # On Mac, the precomplete file added by Bug 386760 will cause OS X to reload the
 # Info.plist so it launches the right architecture, bug 600098
 
 # Generate a list of all files in the target directory.
 pushd "$olddir"
@@ -115,25 +116,27 @@ if [ ! -f "precomplete" ]; then
   exit 1
 fi
 
 list_dirs newdirs
 list_files newfiles
 
 popd
 
-# Add the type of update to the beginning of version 2 update manifest.
+# Add the type of update to the beginning of the update manifests.
 notice ""
-notice "Adding type instruction to file 'updatev2.manifest'"
+notice "Adding type instruction to update manifests"
 > $updatemanifestv2
-notice "       type: partial"
+> $updatemanifestv3
+notice "       type partial"
 echo "type \"partial\"" >> $updatemanifestv2
+echo "type \"partial\"" >> $updatemanifestv3
 
 notice ""
-notice "Adding file patch and add instructions to file 'updatev2.manifest'"
+notice "Adding file patch and add instructions to update manifests"
 
 num_oldfiles=${#oldfiles[*]}
 remove_array=
 num_removes=0
 
 for ((i=0; $i<$num_oldfiles; i=$i+1)); do
   f="${oldfiles[$i]}"
 
@@ -146,61 +149,72 @@ for ((i=0; $i<$num_oldfiles; i=$i+1)); d
   # here for consistency.
   if [ `basename $f` = "removed-files" ]; then
     continue 1
   fi
 
   # If this file exists in the new directory as well, then check if it differs.
   if [ -f "$newdir/$f" ]; then
 
+    if check_for_add_if_not_update "$f"; then
+      # The full workdir may not exist yet, so create it if necessary.
+      mkdir -p `dirname "$workdir/$f"`
+      $BZIP2 -cz9 "$newdir/$f" > "$workdir/$f"
+      copy_perm "$newdir/$f" "$workdir/$f"
+      make_add_if_not_instruction "$f" "$updatemanifestv3"
+      archivefiles="$archivefiles \"$f\""
+      continue 1
+    fi
+
     if check_for_forced_update "$requested_forced_updates" "$f"; then
       # The full workdir may not exist yet, so create it if necessary.
       mkdir -p `dirname "$workdir/$f"`
       $BZIP2 -cz9 "$newdir/$f" > "$workdir/$f"
       copy_perm "$newdir/$f" "$workdir/$f"
-      make_add_instruction "$f" 1 >> $updatemanifestv2
+      make_add_instruction "$f" "$updatemanifestv2" "$updatemanifestv3" 1
       archivefiles="$archivefiles \"$f\""
       continue 1
     fi
 
     if ! diff "$olddir/$f" "$newdir/$f" > /dev/null; then
       # Compute both the compressed binary diff and the compressed file, and
       # compare the sizes.  Then choose the smaller of the two to package.
       dir=$(dirname "$workdir/$f")
       mkdir -p "$dir"
+      notice "diffing \"$f\""
       $MBSDIFF "$olddir/$f" "$newdir/$f" "$workdir/$f.patch"
       $BZIP2 -z9 "$workdir/$f.patch"
       $BZIP2 -cz9 "$newdir/$f" > "$workdir/$f"
       copy_perm "$newdir/$f" "$workdir/$f"
       patchfile="$workdir/$f.patch.bz2"
       patchsize=$(get_file_size "$patchfile")
       fullsize=$(get_file_size "$workdir/$f")
 
       if [ $patchsize -lt $fullsize ]; then
-        make_patch_instruction "$f" >> $updatemanifestv2
+        make_patch_instruction "$f" "$updatemanifestv2" "$updatemanifestv3"
         mv -f "$patchfile" "$workdir/$f.patch"
         rm -f "$workdir/$f"
         archivefiles="$archivefiles \"$f.patch\""
       else
-        make_add_instruction "$f" >> $updatemanifestv2
+        make_add_instruction "$f" "$updatemanifestv2" "$updatemanifestv3"
         rm -f "$patchfile"
         archivefiles="$archivefiles \"$f\""
       fi
     fi
   else
     # remove instructions are added after add / patch instructions for
     # consistency with make_incremental_updates.py
     remove_array[$num_removes]=$f
     (( num_removes++ ))
   fi
 done
 
 # Newly added files
 notice ""
-notice "Adding file add instructions to file 'updatev2.manifest'"
+notice "Adding file add instructions to update manifests"
 num_newfiles=${#newfiles[*]}
 
 for ((i=0; $i<$num_newfiles; i=$i+1)); do
   f="${newfiles[$i]}"
 
   # removed-files is excluded by make_incremental_updates.py so it is excluded
   # here for consistency.
   if [ `basename $f` = "removed-files" ]; then
@@ -215,48 +229,58 @@ for ((i=0; $i<$num_newfiles; i=$i+1)); d
   done
 
   dir=$(dirname "$workdir/$f")
   mkdir -p "$dir"
 
   $BZIP2 -cz9 "$newdir/$f" > "$workdir/$f"
   copy_perm "$newdir/$f" "$workdir/$f"
 
-  make_add_instruction "$f" >> "$updatemanifestv2"
+  if check_for_add_if_not_update "$f"; then
+    make_add_if_not_instruction "$f" "$updatemanifestv3"
+  else
+    make_add_instruction "$f" "$updatemanifestv2" "$updatemanifestv3"
+  fi
+
+
   archivefiles="$archivefiles \"$f\""
 done
 
 notice ""
-notice "Adding file remove instructions to file 'updatev2.manifest'"
+notice "Adding file remove instructions to update manifests"
 for ((i=0; $i<$num_removes; i=$i+1)); do
   f="${remove_array[$i]}"
-  notice "     remove: $f"
+  notice "     remove \"$f\""
   echo "remove \"$f\"" >> $updatemanifestv2
+  echo "remove \"$f\"" >> $updatemanifestv3
 done
 
 # Add remove instructions for any dead files.
 notice ""
 notice "Adding file and directory remove instructions from file 'removed-files'"
-append_remove_instructions "$newdir" "$updatemanifestv2"
+append_remove_instructions "$newdir" "$updatemanifestv2" "$updatemanifestv3"
 
 notice ""
 notice "Adding directory remove instructions for directories that no longer exist"
 num_olddirs=${#olddirs[*]}
 
 for ((i=0; $i<$num_olddirs; i=$i+1)); do
   f="${olddirs[$i]}"
   # If this dir doesn't exist in the new directory remove it.
   if [ ! -d "$newdir/$f" ]; then
-    notice "      rmdir: $f/"
+    notice "      rmdir $f/"
     echo "rmdir \"$f/\"" >> $updatemanifestv2
+    echo "rmdir \"$f/\"" >> $updatemanifestv3
   fi
 done
 
 $BZIP2 -z9 "$updatemanifestv2" && mv -f "$updatemanifestv2.bz2" "$updatemanifestv2"
+$BZIP2 -z9 "$updatemanifestv3" && mv -f "$updatemanifestv3.bz2" "$updatemanifestv3"
 
 eval "$MAR -C \"$workdir\" -c output.mar $archivefiles"
 mv -f "$workdir/output.mar" "$archive"
 
 # cleanup
 rm -fr "$workdir"
 
 notice ""
 notice "Finished"
+notice ""
--- a/tools/update-packaging/make_incremental_updates.py
+++ b/tools/update-packaging/make_incremental_updates.py
@@ -16,86 +16,121 @@ import bz2
 import string
 import tempfile
 
 class PatchInfo:
     """ Represents the meta-data associated with a patch
         work_dir = working dir where files are stored for this patch
         archive_files = list of files to include in this patch
         manifestv2 = set of manifest version 2 patch instructions
+        manifestv3 = set of manifest version 3 patch instructions
         file_exclusion_list = 
         files to exclude from this patch. names without slashes will be
-                              excluded anywhere in the directory hiearchy.   names with slashes
-                              will only be excluded at that exact path
+        excluded anywhere in the directory hiearchy.   names with slashes
+        will only be excluded at that exact path
         """
     def __init__(self, work_dir, file_exclusion_list, path_exclusion_list):
         self.work_dir=work_dir
         self.archive_files=[]
         self.manifestv2=[]
+        self.manifestv3=[]
         self.file_exclusion_list=file_exclusion_list
         self.path_exclusion_list=path_exclusion_list
-        
+
     def append_add_instruction(self, filename):
-        """ Appends an add instruction for this patch.   
-            if the filename starts with distribution/extensions/ adds an add-if
-            instruction to test the existence of the subdirectory.  This was
-            ported from mozilla/tools/update-packaging/common.sh's
-            make_add_instruction.
+        """ Appends an add instruction for this patch.
+            if filename starts with distribution/extensions/.*/ this will add an
+            add-if instruction that will add the file if the parent directory
+            of the file exists.  This was ported from
+            mozilla/tools/update-packaging/common.sh's make_add_instruction.
         """
-        m = re.match("((?:|.*/)distribution/extensions)/", filename)
+        m = re.match("((?:|.*/)distribution/extensions/.*)/", filename)
         if m:
             # Directory immediately following extensions is used for the test
             testdir = m.group(1)
+            print '     add-if "'+testdir+'" "'+filename+'"'
             self.manifestv2.append('add-if "'+testdir+'" "'+filename+'"')
+            self.manifestv3.append('add-if "'+testdir+'" "'+filename+'"')
         else:
+            print '        add "'+filename+'"'
             self.manifestv2.append('add "'+filename+'"')
-           
+            self.manifestv3.append('add "'+filename+'"')
+
+    def append_add_if_not_instruction(self, filename):
+        """ Appends an add-if-not instruction to the version 3 manifest for this patch.
+            This was ported from mozilla/tools/update-packaging/common.sh's
+            make_add_if_not_instruction.
+        """
+        print ' add-if-not "'+filename+'" "'+filename+'"'
+        self.manifestv3.append('add-if-not "'+filename+'" "'+filename+'"')
+
     def append_patch_instruction(self, filename, patchname):
-        """ Appends an patch instruction for this patch.   
-            
+        """ Appends a patch instruction for this patch.
+
             filename = file to patch
             patchname = patchfile to apply to file
-            
-            if the filename starts with distribution/extensions/ adds a
-            patch-if instruction to test the existence of the subdirectory.  
-            This was ported from
+
+            if filename starts with distribution/extensions/.*/ this will add a
+            patch-if instruction that will patch the file if the parent
+            directory of the file exists. This was ported from
             mozilla/tools/update-packaging/common.sh's make_patch_instruction.
         """
-        m = re.match("((?:|.*/)distribution/extensions)/", filename)
+        m = re.match("((?:|.*/)distribution/extensions/.*)/", filename)
         if m:
             testdir = m.group(1)
+            print '   patch-if "'+testdir+'" "'+patchname+'" "'+filename+'"'
             self.manifestv2.append('patch-if "'+testdir+'" "'+patchname+'" "'+filename+'"')
+            self.manifestv3.append('patch-if "'+testdir+'" "'+patchname+'" "'+filename+'"')
         else:
+            print '      patch "'+patchname+'" "'+filename+'"'
             self.manifestv2.append('patch "'+patchname+'" "'+filename+'"')
-                
+            self.manifestv3.append('patch "'+patchname+'" "'+filename+'"')
+
     def append_remove_instruction(self, filename):
-        """ Appends an remove instruction for this patch.   
+        """ Appends an remove instruction for this patch.
             This was ported from
             mozilla/tools/update-packaging/common.sh/make_remove_instruction
         """
         if filename.endswith("/"):
+            print '      rmdir "'+filename+'"'
             self.manifestv2.append('rmdir "'+filename+'"')
+            self.manifestv3.append('rmdir "'+filename+'"')
         elif filename.endswith("/*"):
             filename = filename[:-1]
+            print '    rmrfdir "'+filename+'"'
             self.manifestv2.append('rmrfdir "'+filename+'"')
+            self.manifestv3.append('rmrfdir "'+filename+'"')
         else:
+            print '     remove "'+filename+'"'
             self.manifestv2.append('remove "'+filename+'"')
+            self.manifestv3.append('remove "'+filename+'"')
 
     def create_manifest_files(self):
-        """ Createst the v2 manifest file in the root of the work_dir """
+        """ Create the v2 manifest file in the root of the work_dir """
         manifest_file_path = os.path.join(self.work_dir,"updatev2.manifest")
         manifest_file = open(manifest_file_path, "wb")
         manifest_file.writelines("type \"partial\"\n")
         manifest_file.writelines(string.join(self.manifestv2, '\n'))
         manifest_file.writelines("\n")
         manifest_file.close()
 
         bzip_file(manifest_file_path)
         self.archive_files.append('"updatev2.manifest"')
 
+        """ Create the v3 manifest file in the root of the work_dir """
+        manifest_file_path = os.path.join(self.work_dir,"updatev3.manifest")
+        manifest_file = open(manifest_file_path, "wb")
+        manifest_file.writelines("type \"partial\"\n")
+        manifest_file.writelines(string.join(self.manifestv3, '\n'))
+        manifest_file.writelines("\n")
+        manifest_file.close()
+
+        bzip_file(manifest_file_path)
+        self.archive_files.append('"updatev3.manifest"')
+
     def build_marfile_entry_hash(self, root_path):
         """ Iterates through the root_path, creating a MarFileEntry for each file
             and directory in that path.  Excludes any filenames in the file_exclusion_list
         """
         mar_entry_hash = {}
         filename_set = set()
         dirname_set = set()
         for root, dirs, files in os.walk(root_path):
@@ -115,17 +150,17 @@ class PatchInfo:
                     dirname = os.path.join(partial_path, name)
                     if "/"+dirname not in self.path_exclusion_list:
                         dirname = dirname+"/"
                         mar_entry_hash[dirname]=MarFileEntry(root_path, dirname)
                         dirname_set.add(dirname)
 
         return mar_entry_hash, filename_set, dirname_set
  
-       
+
 class MarFileEntry:
     """Represents a file inside a Mozilla Archive Format (MAR)
         abs_path = abspath to the the file
         name =  relative path within the mar.  e.g.
           foo.mar/dir/bar.txt extracted into /tmp/foo:
             abs_path=/tmp/foo/dir/bar.txt
             name = dir/bar.txt
     """ 
@@ -134,17 +169,17 @@ class MarFileEntry:
            name = relative path within the mar"""
         self.name=name.replace("\\", "/")
         self.abs_path=os.path.join(root,name)
         self.sha_cache=None
 
     def __str__(self):
         return 'Name: %s FullPath: %s' %(self.name,self.abs_path)
 
-    def calc_file_sha_digest(self, filename):        
+    def calc_file_sha_digest(self, filename):
         """ Returns sha digest of given filename"""
         file_content = open(filename, 'r').read()
         return sha.new(file_content).digest()
 
     def sha(self):
         """ Returns sha digest of file repreesnted by this _marfile_entry"""
         if not self.sha_cache:
             self.sha_cache=self.calc_file_sha_digest(self.abs_path)
@@ -164,101 +199,104 @@ def copy_file(src_file_abs_path, dst_fil
     # Copy the file over
     shutil.copy2(src_file_abs_path, dst_file_abs_path)
 
 def bzip_file(filename):
     """ Bzip's the file in place.  The original file is replaced with a bzip'd version of itself
         assumes the path is absolute"""
     exec_shell_cmd('bzip2 -z9 "' + filename+'"')
     os.rename(filename+".bz2",filename)
-    
+
 def bunzip_file(filename):
     """ Bzip's the file in palce.   The original file is replaced with a bunzip'd version of itself.
         doesn't matter if the filename ends in .bz2 or not"""
     if not filename.endswith(".bz2"):
         os.rename(filename, filename+".bz2")
         filename=filename+".bz2"
     exec_shell_cmd('bzip2 -d "' + filename+'"') 
 
 
 def extract_mar(filename, work_dir): 
     """ Extracts the marfile intot he work_dir
         assumes work_dir already exists otherwise will throw osError"""
     print "Extracting "+filename+" to "+work_dir
     saved_path = os.getcwd()
     try:
         os.chdir(work_dir)
-        exec_shell_cmd("mar -x "+filename)    
+        exec_shell_cmd("mar -x "+filename)
     finally:
         os.chdir(saved_path)
 
 def create_partial_patch_for_file(from_marfile_entry, to_marfile_entry, shas, patch_info):
     """ Creates the partial patch file and manifest entry for the pair of files passed in
     """
     if not (from_marfile_entry.sha(),to_marfile_entry.sha()) in shas:
-        print "diffing: " + from_marfile_entry.name
-        
+        print 'diffing "'+from_marfile_entry.name+'\"'
         #bunzip to/from
         bunzip_file(from_marfile_entry.abs_path)
         bunzip_file(to_marfile_entry.abs_path)
 
         # The patch file will be created in the working directory with the
         # name of the file in the mar + .patch
         patch_file_abs_path = os.path.join(patch_info.work_dir,from_marfile_entry.name+".patch")
         patch_file_dir=os.path.dirname(patch_file_abs_path)
         if not os.path.exists(patch_file_dir):
             os.makedirs(patch_file_dir)
 
         # Create bzip'd patch file
         exec_shell_cmd("mbsdiff "+from_marfile_entry.abs_path+" "+to_marfile_entry.abs_path+" "+patch_file_abs_path)
         bzip_file(patch_file_abs_path)
 
         # Create bzip's full file
-        full_file_abs_path =  os.path.join(patch_info.work_dir, to_marfile_entry.name)   
+        full_file_abs_path =  os.path.join(patch_info.work_dir, to_marfile_entry.name)
         shutil.copy2(to_marfile_entry.abs_path, full_file_abs_path)
         bzip_file(full_file_abs_path)
-  
-        ## TOODO NEED TO ADD HANDLING FOR FORCED UPDATES
+
         if os.path.getsize(patch_file_abs_path) < os.path.getsize(full_file_abs_path):
             # Patch is smaller than file.  Remove the file and add patch to manifest
             os.remove(full_file_abs_path)
             file_in_manifest_name = from_marfile_entry.name+".patch"
             file_in_manifest_abspath = patch_file_abs_path
             patch_info.append_patch_instruction(to_marfile_entry.name, file_in_manifest_name)
-        else:            
+        else:
             # File is smaller than patch.  Remove the patch and add file to manifest
             os.remove(patch_file_abs_path)
             file_in_manifest_name = from_marfile_entry.name
             file_in_manifest_abspath = full_file_abs_path
             patch_info.append_add_instruction(file_in_manifest_name)
-            
+
         shas[from_marfile_entry.sha(),to_marfile_entry.sha()] = (file_in_manifest_name,file_in_manifest_abspath)
-        patch_info.archive_files.append('"'+file_in_manifest_name+'"')        
+        patch_info.archive_files.append('"'+file_in_manifest_name+'"')
     else:
         filename, src_file_abs_path = shas[from_marfile_entry.sha(),to_marfile_entry.sha()]
-        # We've already calculated the patch for this pair of files.   
+        # We've already calculated the patch for this pair of files.
         if (filename.endswith(".patch")):
-            print "skipping diff: " + from_marfile_entry.name
+            # print "skipping diff: "+from_marfile_entry.name
             # Patch was smaller than file - add patch instruction to manifest
             file_in_manifest_name = to_marfile_entry.name+'.patch';
             patch_info.append_patch_instruction(to_marfile_entry.name, file_in_manifest_name)
         else:
             # File was smaller than file - add file to manifest
             file_in_manifest_name = to_marfile_entry.name
-            patch_info.append_add_instruction(file_in_manifest_name)                
+            patch_info.append_add_instruction(file_in_manifest_name)
         # Copy the pre-calculated file into our new patch work aread
         copy_file(src_file_abs_path, os.path.join(patch_info.work_dir, file_in_manifest_name))
         patch_info.archive_files.append('"'+file_in_manifest_name+'"')
  
-def create_add_patch_for_file(to_marfile_entry, patch_info):           
+def create_add_patch_for_file(to_marfile_entry, patch_info):
     """  Copy the file to the working dir, add the add instruction, and add it to the list of archive files """
-    print "Adding New File " + to_marfile_entry.name    
     copy_file(to_marfile_entry.abs_path, os.path.join(patch_info.work_dir, to_marfile_entry.name))
     patch_info.append_add_instruction(to_marfile_entry.name)
-    patch_info.archive_files.append('"'+to_marfile_entry.name+'"')    
+    patch_info.archive_files.append('"'+to_marfile_entry.name+'"')
+
+def create_add_if_not_patch_for_file(to_marfile_entry, patch_info):
+    """  Copy the file to the working dir, add the add-if-not instruction, and add it to the list of archive files """
+    copy_file(to_marfile_entry.abs_path, os.path.join(patch_info.work_dir, to_marfile_entry.name))
+    patch_info.append_add_if_not_instruction(to_marfile_entry.name)
+    patch_info.archive_files.append('"'+to_marfile_entry.name+'"')
 
 def process_explicit_remove_files(dir_path, patch_info): 
     """ Looks for a 'removed-files' file in the dir_path.  If the removed-files does not exist
     this will throw.  If found adds the removed-files
     found in that file to the patch_info"""
 
     # Windows and linux have this file at the root of the dir
     list_file_path = os.path.join(dir_path, "removed-files")
@@ -285,80 +323,86 @@ def process_explicit_remove_files(dir_pa
                         line = line.replace("../", "Contents/")
                     else:
                         line = os.path.join(prefix,line)
                 # Python on windows uses \ for path separators and the update
                 # manifests expects / for path separators on all platforms.
                 line = line.replace("\\", "/")
                 patch_info.append_remove_instruction(line)
 
-def create_partial_patch(from_dir_path, to_dir_path, patch_filename, shas, patch_info, forced_updates):
+def create_partial_patch(from_dir_path, to_dir_path, patch_filename, shas, patch_info, forced_updates, add_if_not_list):
     """ Builds a partial patch by comparing the files in from_dir_path to those of to_dir_path"""
     # Cannocolize the paths for safey
     from_dir_path = os.path.abspath(from_dir_path)
     to_dir_path = os.path.abspath(to_dir_path)
     # Create a hashtable of the from  and to directories
     from_dir_hash,from_file_set,from_dir_set = patch_info.build_marfile_entry_hash(from_dir_path)
     to_dir_hash,to_file_set,to_dir_set = patch_info.build_marfile_entry_hash(to_dir_path)
-    # Require that the precomplete file is included in the to complete update
+    # Require that the precomplete file is included in the complete update
     if "precomplete" not in to_file_set:
         raise Exception, "missing precomplete file in: "+to_dir_path
     # Create a list of the forced updates 
     forced_list = forced_updates.strip().split('|')
     forced_list.append("precomplete")
 
     # Files which exist in both sets need to be patched
     patch_filenames = list(from_file_set.intersection(to_file_set))
     patch_filenames.sort(reverse=True)
     for filename in patch_filenames:
         from_marfile_entry = from_dir_hash[filename]
         to_marfile_entry = to_dir_hash[filename]
-        if filename in forced_list:
-            print "Forcing "+ filename
+        if os.path.basename(filename) in add_if_not_list:
+            # This filename is in the add if not list, explicitly add-if-not
+            create_add_if_not_patch_for_file(to_dir_hash[filename], patch_info)
+        elif filename in forced_list:
+            print 'Forcing "'+filename+'"'
             # This filename is in the forced list, explicitly add
             create_add_patch_for_file(to_dir_hash[filename], patch_info)
         else: 
           if from_marfile_entry.sha() != to_marfile_entry.sha():
               # Not the same - calculate a patch
               create_partial_patch_for_file(from_marfile_entry, to_marfile_entry, shas, patch_info)
 
     # files in to_dir not in from_dir need to added
     add_filenames = list(to_file_set - from_file_set)
     add_filenames.sort(reverse=True)
     for filename in add_filenames:
-        create_add_patch_for_file(to_dir_hash[filename], patch_info)
+        if os.path.basename(filename) in add_if_not_list:
+            create_add_if_not_patch_for_file(to_dir_hash[filename], patch_info)
+        else:
+            create_add_patch_for_file(to_dir_hash[filename], patch_info)
 
     # files in from_dir not in to_dir need to be removed
     remove_filenames = list(from_file_set - to_file_set)
     remove_filenames.sort(reverse=True)
     for filename in remove_filenames:
         patch_info.append_remove_instruction(from_dir_hash[filename].name)
 
     process_explicit_remove_files(to_dir_path, patch_info)
-    
+
     # directories in from_dir not in to_dir need to be removed
     remove_dirnames = list(from_dir_set - to_dir_set)
     remove_dirnames.sort(reverse=True)
     for dirname in remove_dirnames:
         patch_info.append_remove_instruction(from_dir_hash[dirname].name)
 
     # Construct the Manifest files
     patch_info.create_manifest_files()
-    
+
     # And construct the mar
-    mar_cmd = 'mar -C '+patch_info.work_dir+' -c output.mar '+string.join(patch_info.archive_files, ' ')    
+    mar_cmd = 'mar -C '+patch_info.work_dir+' -c output.mar '+string.join(patch_info.archive_files, ' ')
     exec_shell_cmd(mar_cmd)
 
     # Copy mar to final destination
     patch_file_dir = os.path.split(patch_filename)[0]
     if not os.path.exists(patch_file_dir):
         os.makedirs(patch_file_dir)
     shutil.copy2(os.path.join(patch_info.work_dir,"output.mar"), patch_filename)
     return patch_filename
-    
+
 def usage():
     print "-h for help"
     print "-f for patchlist_file"
 
 def get_buildid(work_dir, platform):
     """ extracts buildid from MAR
         TODO: this should handle 1.8 branch too
     """
@@ -421,29 +465,29 @@ def create_partial_patches(patches):
             # Extract from mar into from dir
             work_dir_from =  os.path.join(work_dir,"from");
             os.mkdir(work_dir_from)
             extract_mar(from_filename,work_dir_from)
             from_decoded = decode_filename(from_filename)
             from_buildid = get_buildid(work_dir_from, from_decoded['platform'])
             from_shasum = sha.sha(open(from_filename).read()).hexdigest()
             from_size = str(os.path.getsize(to_filename))
-            
+
             # Extract to mar into to dir
             work_dir_to =  os.path.join(work_dir,"to")
             os.mkdir(work_dir_to)
             extract_mar(to_filename, work_dir_to)
             to_decoded = decode_filename(from_filename)
             to_buildid = get_buildid(work_dir_to, to_decoded['platform'])
             to_shasum = sha.sha(open(to_filename).read()).hexdigest()
             to_size = str(os.path.getsize(to_filename))
 
             mar_extract_time = time.time()
 
-            partial_filename = create_partial_patch(work_dir_from, work_dir_to, patch_filename, shas, PatchInfo(work_dir, ['channel-prefs.js','update.manifest','updatev2.manifest','removed-files'],['/readme.txt']),forced_updates)
+            partial_filename = create_partial_patch(work_dir_from, work_dir_to, patch_filename, shas, PatchInfo(work_dir, ['update.manifest','updatev2.manifest','updatev3.manifest','removed-files'],['/readme.txt']),forced_updates,['channel-prefs.js','update-settings.ini'])
             partial_buildid = to_buildid
             partial_shasum = sha.sha(open(partial_filename).read()).hexdigest()
             partial_size = str(os.path.getsize(partial_filename))
 
             metadata.append({
              'to_filename': os.path.basename(to_filename),
              'from_filename': os.path.basename(from_filename),
              'partial_filename': os.path.basename(partial_filename),
@@ -461,36 +505,36 @@ def create_partial_patches(patches):
              'platform':from_decoded['platform'],
             })
             print "done with patch %s/%s time (%.2fs/%.2fs/%.2fs) (mar/patch/total)" % (str(patch_num),str(len(patches)),mar_extract_time-startTime,time.time()-mar_extract_time,time.time()-startTime)
             patch_num += 1
         return metadata
     finally:
         # If we fail or get a ctrl-c during run be sure to clean up temp dir
         if (work_dir_root and os.path.exists(work_dir_root)):
-            shutil.rmtree(work_dir_root)        
+            shutil.rmtree(work_dir_root)
 
-def main(argv):                          
+def main(argv):
     patchlist_file = None
-    try:                                
+    try:
          opts, args = getopt.getopt(argv, "hf:", ["help", "patchlist_file="])
-         for opt, arg in opts:                
-            if opt in ("-h", "--help"):      
-                usage()                     
-                sys.exit()       
+         for opt, arg in opts:
+            if opt in ("-h", "--help"):
+                usage()
+                sys.exit()
             elif opt in ("-f", "--patchlist_file"):
-                patchlist_file = arg               
-    except getopt.GetoptError:          
-          usage()                         
-          sys.exit(2)                     
-    
+                patchlist_file = arg
+    except getopt.GetoptError:
+          usage()
+          sys.exit(2)
+
     if not patchlist_file:
         usage()
         sys.exit(2)
-        
+
     patches = []
     f = open(patchlist_file, 'r')
     for line in f.readlines():
         patches.append(line)
     f.close()
     create_partial_patches(patches)
 
 if __name__ == "__main__":
--- a/tools/update-packaging/test/buildrefmars.sh
+++ b/tools/update-packaging/test/buildrefmars.sh
@@ -4,23 +4,24 @@
 if [ -f "ref.mar" ]; then
   rm "ref.mar"
 fi
 if [ -f "ref-mac.mar" ]; then
   rm "ref-mac.mar"
 fi
 
  ../make_incremental_update.sh ref.mar `pwd`/from `pwd`/to
- ../make_incremental_update.sh ref-mac.mar `pwd`/from `pwd`/to-mac
+ ../make_incremental_update.sh ref-mac.mar `pwd`/from-mac `pwd`/to-mac
 
 if [ -f "product-1.0.lang.platform.complete.mar" ]; then
   rm "product-1.0.lang.platform.complete.mar"
 fi
 if [ -f "product-2.0.lang.platform.complete.mar" ]; then
   rm "product-2.0.lang.platform.complete.mar"
 fi
 if [ -f "product-2.0.lang.mac.complete.mar" ]; then
   rm "product-2.0.lang.mac.complete.mar"
 fi
 
 ./make_full_update.sh product-1.0.lang.platform.complete.mar "`pwd`/from"
 ./make_full_update.sh product-2.0.lang.platform.complete.mar "`pwd`/to"
+./make_full_update.sh product-1.0.lang.mac.complete.mar "`pwd`/from-mac"
 ./make_full_update.sh product-2.0.lang.mac.complete.mar "`pwd`/to-mac"
--- a/tools/update-packaging/test/common.sh
+++ b/tools/update-packaging/test/common.sh
@@ -35,55 +35,106 @@ copy_perm() {
     chmod 0755 "$target"
   else
     chmod 0644 "$target"
   fi
 }
 
 make_add_instruction() {
   f="$1"
+  filev2="$2"
+  # The third param will be an empty string when a file add instruction is only
+  # needed in the version 2 manifest. This only happens when the file has an
+  # add-if-not instruction in the version 3 manifest. This is due to the
+  # precomplete file prior to the version 3 manifest having a remove instruction
+  # for this file so the file is removed before applying a complete update.
+  filev3="$3"
 
   # Used to log to the console
-  if [ $2 ]; then
+  if [ $4 ]; then
     forced=" (forced)"
   else
     forced=
   fi
 
   is_extension=$(echo "$f" | grep -c 'distribution/extensions/.*/')
   if [ $is_extension = "1" ]; then
     # Use the subdirectory of the extensions folder as the file to test
     # before performing this add instruction.
     testdir=$(echo "$f" | sed 's/\(.*distribution\/extensions\/[^\/]*\)\/.*/\1/')
-    notice "     add-if: $f$forced"
-    echo "add-if \"$testdir\" \"$f\""
+    notice "     add-if \"$testdir\" \"$f\""
+    echo "add-if \"$testdir\" \"$f\"" >> $filev2
+    if [ ! $filev3 = "" ]; then
+      echo "add-if \"$testdir\" \"$f\"" >> $filev3
+    fi
   else
-    notice "        add: $f$forced"
-    echo "add \"$f\""
+    notice "        add \"$f\"$forced"
+    echo "add \"$f\"" >> $filev2
+    if [ ! $filev3 = "" ]; then
+      echo "add \"$f\"" >> $filev3
+    fi
+  fi
+}
+
+check_for_add_if_not_update() {
+  add_if_not_file_chk="$1"
+
+  if [ `basename $add_if_not_file_chk` = "channel-prefs.js" -o \
+       `basename $add_if_not_file_chk` = "update-settings.ini" ]; then
+    ## "true" *giggle*
+    return 0;
   fi
+  ## 'false'... because this is bash. Oh yay!
+  return 1;
+}
+
+check_for_add_to_manifestv2() {
+  add_if_not_file_chk="$1"
+
+  if [ `basename $add_if_not_file_chk` = "update-settings.ini" ]; then
+    ## "true" *giggle*
+    return 0;
+  fi
+  ## 'false'... because this is bash. Oh yay!
+  return 1;
+}
+
+make_add_if_not_instruction() {
+  f="$1"
+  filev3="$2"
+
+  notice " add-if-not \"$f\" \"$f\""
+  echo "add-if-not \"$f\" \"$f\"" >> $filev3
 }
 
 make_patch_instruction() {
   f="$1"
+  filev2="$2"
+  filev3="$3"
+
   is_extension=$(echo "$f" | grep -c 'distribution/extensions/.*/')
   if [ $is_extension = "1" ]; then
     # Use the subdirectory of the extensions folder as the file to test
     # before performing this add instruction.
     testdir=$(echo "$f" | sed 's/\(.*distribution\/extensions\/[^\/]*\)\/.*/\1/')
-    notice "   patch-if: $f"
-    echo "patch-if \"$testdir\" \"$f.patch\" \"$f\""
+    notice "   patch-if \"$testdir\" \"$f.patch\" \"$f\""
+    echo "patch-if \"$testdir\" \"$f.patch\" \"$f\"" >> $filev2
+    echo "patch-if \"$testdir\" \"$f.patch\" \"$f\"" >> $filev3
   else
-    notice "      patch: $f"
-    echo "patch \"$f.patch\" \"$f\""
+    notice "      patch \"$f.patch\" \"$f\""
+    echo "patch \"$f.patch\" \"$f\"" >> $filev2
+    echo "patch \"$f.patch\" \"$f\"" >> $filev3
   fi
 }
 
 append_remove_instructions() {
   dir="$1"
   filev2="$2"
+  filev3="$3"
+
   if [ -f "$dir/removed-files" ]; then
     prefix=
     listfile="$dir/removed-files"
   elif [ -f "$dir/Contents/MacOS/removed-files" ]; then
     prefix=Contents/MacOS/
     listfile="$dir/Contents/MacOS/removed-files"
   fi
   if [ -n "$listfile" ]; then
@@ -108,37 +159,38 @@ append_remove_instructions() {
                 fixedprefix=""
               else
                 f=$(echo $f | sed -e 's:^\.\.\/::')
                 fixedprefix=$(echo "$prefix" | sed -e 's:[^\/]*\/$::')
               fi
             fi
           fi
           if [ $(echo "$f" | grep -c '\/$') = 1 ]; then
-            notice "      rmdir: $fixedprefix$f"
+            notice "      rmdir \"$fixedprefix$f\""
             echo "rmdir \"$fixedprefix$f\"" >> $filev2
+            echo "rmdir \"$fixedprefix$f\"" >> $filev3
           elif [ $(echo "$f" | grep -c '\/\*$') = 1 ]; then
             # Remove the *
             f=$(echo "$f" | sed -e 's:\*$::')
-            notice "    rmrfdir: $fixedprefix$f"
+            notice "    rmrfdir \"$fixedprefix$f\""
             echo "rmrfdir \"$fixedprefix$f\"" >> $filev2
+            echo "rmrfdir \"$fixedprefix$f\"" >> $filev3
           else
-            notice "     remove: $fixedprefix$f"
+            notice "     remove \"$fixedprefix$f\""
             echo "remove \"$fixedprefix$f\"" >> $filev2
+            echo "remove \"$fixedprefix$f\"" >> $filev3
           fi
         fi
       fi
     done
   fi
 }
 
 # List all files in the current directory, stripping leading "./"
-# Skip the channel-prefs.js file as it should not be included in any
-# generated MAR files (see bug 306077). Pass a variable name and it will be
-# filled as an array.
+# Pass a variable name and it will be filled as an array.
 list_files() {
   count=0
 
   # Removed the exclusion cases here to allow for generation of testing mars
   find . -type f \
     | sed 's/\.\/\(.*\)/\1/' \
     | sort -r > "$workdir/temp-filelist"
   while read file; do
--- a/tools/update-packaging/test/diffmar.sh
+++ b/tools/update-packaging/test/diffmar.sh
@@ -1,14 +1,15 @@
 #!/bin/bash
 # Compares two mars
 
 marA="$1"
 marB="$2"
-workdir="/tmp/diffmar"
+testDir="$3"
+workdir="/tmp/diffmar/$testDir"
 fromdir="$workdir/0"
 todir="$workdir/1"
 
 # On Windows, creation time can be off by a second or more between the files in
 # the fromdir and todir due to them being extracted synchronously so use
 # time-style and exclude seconds from the creation time.
 lsargs="-algR"
 unamestr=`uname`
@@ -26,21 +27,25 @@ mkdir -p "$todir"
 cp "$1" "$fromdir"
 cp "$2" "$todir"
 
 cd "$fromdir"
 mar -x "$1"
 rm "$1"
 mv updatev2.manifest updatev2.manifest.bz2
 bzip2 -d updatev2.manifest.bz2
+mv updatev3.manifest updatev3.manifest.bz2
+bzip2 -d updatev3.manifest.bz2
 ls $lsargs > files.txt
 
 cd "$todir"
 mar -x "$2"
 rm "$2"
 mv updatev2.manifest updatev2.manifest.bz2
 bzip2 -d updatev2.manifest.bz2
+mv updatev3.manifest updatev3.manifest.bz2
+bzip2 -d updatev3.manifest.bz2
 ls $lsargs > files.txt
 
 echo "diffing $fromdir and $todir"
 echo "on linux shell sort and python sort return different results"
-echo "which cause differences in the manifest files"
+echo "which can cause differences in the manifest files"
 diff -ru "$fromdir" "$todir"
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/application.ini
@@ -0,0 +1,5 @@
+[App]
+Vendor=Mozilla
+Name=MarTest
+Version=1
+BuildID=20120101010101
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/diff-patch-larger-than-file.txt
@@ -0,0 +1,1 @@
+from file
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/distribution/extensions/diff/diff-patch-larger-than-file.txt
@@ -0,0 +1,1 @@
+from file
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/extensions/diff/diff-patch-larger-than-file.txt
@@ -0,0 +1,1 @@
+from file
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/force.txt
@@ -0,0 +1,1 @@
+file is same
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/readme.txt
@@ -0,0 +1,2 @@
+This from file should be ignored
+
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/removed.txt
@@ -0,0 +1,1 @@
+removed
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a9ee7258ccc69e5ecd1b4d484ca67b7e28853f70
GIT binary patch
literal 200
zc$^Kxi4uWO006+}&P@_Iik!Lo|DUrvvmu1guxZP-9lQ37>^pGi$gvZr&YT;&FmY+>
i%C(srx9;4VTX^tj>B+Mfuim^{`S9t>w;#X$to;K<I|RxA
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/same.txt
@@ -0,0 +1,1 @@
+file is same
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/searchplugins/diff/diff-patch-larger-than-file.txt
@@ -0,0 +1,1 @@
+from file
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/update-settings.ini
@@ -0,0 +1,1 @@
+add-if-not from complete file
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/update.manifest
@@ -0,0 +1,1 @@
+from file shouldn't go in update
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/{foodir/channel-prefs.js
@@ -0,0 +1,1 @@
+add-if-not from complete file
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/{foodir/diff-patch-larger-than-file.txt
@@ -0,0 +1,1 @@
+from file
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/{foodir/force.txt
@@ -0,0 +1,1 @@
+file is same
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/{foodir/readme.txt
@@ -0,0 +1,1 @@
+This from file should be ignored
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/{foodir/removed.txt
@@ -0,0 +1,1 @@
+removed
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a9ee7258ccc69e5ecd1b4d484ca67b7e28853f70
GIT binary patch
literal 200
zc$^Kxi4uWO006+}&P@_Iik!Lo|DUrvvmu1guxZP-9lQ37>^pGi$gvZr&YT;&FmY+>
i%C(srx9;4VTX^tj>B+Mfuim^{`S9t>w;#X$to;K<I|RxA
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/{foodir/same.txt
@@ -0,0 +1,1 @@
+file is same
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/Contents/MacOS/{foodir/update.manifest
@@ -0,0 +1,1 @@
+from file shouldn't go in update
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from-mac/precomplete
@@ -0,0 +1,25 @@
+remove "precomplete"
+remove "Contents/MacOS/{foodir/update.manifest"
+remove "Contents/MacOS/{foodir/same.txt"
+remove "Contents/MacOS/{foodir/same.bin"
+remove "Contents/MacOS/{foodir/removed.txt"
+remove "Contents/MacOS/{foodir/readme.txt"
+remove "Contents/MacOS/{foodir/force.txt"
+remove "Contents/MacOS/{foodir/diff-patch-larger-than-file.txt"
+remove "Contents/MacOS/update.manifest"
+remove "Contents/MacOS/searchplugins/diff/diff-patch-larger-than-file.txt"
+remove "Contents/MacOS/same.txt"
+remove "Contents/MacOS/same.bin"
+remove "Contents/MacOS/removed.txt"
+remove "Contents/MacOS/readme.txt"
+remove "Contents/MacOS/force.txt"
+remove "Contents/MacOS/extensions/diff/diff-patch-larger-than-file.txt"
+remove "Contents/MacOS/diff-patch-larger-than-file.txt"
+remove "Contents/MacOS/application.ini"
+rmdir "Contents/MacOS/{foodir/"
+rmdir "Contents/MacOS/searchplugins/diff/"
+rmdir "Contents/MacOS/searchplugins/"
+rmdir "Contents/MacOS/extensions/diff/"
+rmdir "Contents/MacOS/extensions/"
+rmdir "Contents/MacOS/"
+rmdir "Contents/"
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from/application.ini
@@ -0,0 +1,5 @@
+[App]
+Vendor=Mozilla
+Name=MarTest
+Version=1
+BuildID=20120101010101
deleted file mode 100644
--- a/tools/update-packaging/test/from/channel-prefs.js
+++ /dev/null
@@ -1,1 +0,0 @@
-from file shouldn't go in update
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from/distribution/extensions/diff/diff-patch-larger-than-file.txt
@@ -0,0 +1,1 @@
+from file
--- a/tools/update-packaging/test/from/precomplete
+++ b/tools/update-packaging/test/from/precomplete
@@ -1,21 +1,23 @@
-remove {foodir/update.manifest
-remove {foodir/same.txt
-remove {foodir/same.bin
-remove {foodir/removed.txt
-remove {foodir/readme.txt
-remove {foodir/force.txt
-remove {foodir/diff-patch-larger-than-file.txt
-remove-cc {foodir/channel-prefs.js
-remove update.manifest
-remove searchplugins/diff/diff-patch-larger-than-file.txt
-remove same.txt
-remove same.bin
-remove removed.txt
-remove readme.txt
-remove precomplete
-remove force.txt
-remove diff-patch-larger-than-file.txt
-remove-cc channel-prefs.js
-rmdir {foodir/
-rmdir searchplugins/diff/
-rmdir searchplugins/
+remove "{foodir/update.manifest"
+remove "{foodir/same.txt"
+remove "{foodir/same.bin"
+remove "{foodir/removed.txt"
+remove "{foodir/readme.txt"
+remove "{foodir/force.txt"
+remove "{foodir/diff-patch-larger-than-file.txt"
+remove "update.manifest"
+remove "searchplugins/diff/diff-patch-larger-than-file.txt"
+remove "same.txt"
+remove "same.bin"
+remove "removed.txt"
+remove "readme.txt"
+remove "precomplete"
+remove "force.txt"
+remove "extensions/diff/diff-patch-larger-than-file.txt"
+remove "diff-patch-larger-than-file.txt"
+remove "application.ini"
+rmdir "{foodir/"
+rmdir "searchplugins/diff/"
+rmdir "searchplugins/"
+rmdir "extensions/diff/"
+rmdir "extensions/"
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/from/update-settings.ini
@@ -0,0 +1,1 @@
+add-if-not from complete file
--- a/tools/update-packaging/test/from/{foodir/channel-prefs.js
+++ b/tools/update-packaging/test/from/{foodir/channel-prefs.js
@@ -1,1 +1,1 @@
-from file shouldn't go in update
+add-if-not from complete file
--- a/tools/update-packaging/test/make_full_update.sh
+++ b/tools/update-packaging/test/make_full_update.sh
@@ -40,17 +40,18 @@ targetdir="$2"
 # Prevent the workdir from being inside the targetdir so it isn't included in
 # the update mar.
 if [ $(echo "$targetdir" | grep -c '\/$') = 1 ]; then
   # Remove the /
   targetdir=$(echo "$targetdir" | sed -e 's:\/$::')
 fi
 workdir="$targetdir.work"
 updatemanifestv2="$workdir/updatev2.manifest"
-targetfiles="updatev2.manifest"
+updatemanifestv3="$workdir/updatev3.manifest"
+targetfiles="updatev2.manifest updatev3.manifest"
 
 mkdir -p "$workdir"
 
 # On Mac, the precomplete file added by Bug 386760 will cause OS X to reload the
 # Info.plist so it launches the right architecture, bug 600098
 
 # Generate a list of all files in the target directory.
 pushd "$targetdir"
@@ -62,47 +63,58 @@ if [ ! -f "precomplete" ]; then
   notice "precomplete file is missing!"
   exit 1
 fi
 
 list_files files
 
 popd
 
-# Add the type of update to the beginning of version 2 update manifest.
+# Add the type of update to the beginning of the update manifests.
 > $updatemanifestv2
+> $updatemanifestv3
 notice ""
-notice "Adding type instruction to file 'updatev2.manifest'"
-notice "       type: complete"
+notice "Adding type instruction to update manifests"
+notice "       type complete"
 echo "type \"complete\"" >> $updatemanifestv2
+echo "type \"complete\"" >> $updatemanifestv3
 
 notice ""
-notice "Adding file add instructions to file 'updatev2.manifest'"
+notice "Adding file add instructions to update manifests"
 num_files=${#files[*]}
 
 for ((i=0; $i<$num_files; i=$i+1)); do
   f="${files[$i]}"
 
-  make_add_instruction "$f" >> $updatemanifestv2
+  if check_for_add_if_not_update "$f"; then
+    make_add_if_not_instruction "$f" "$updatemanifestv3"
+    if check_for_add_to_manifestv2 "$f"; then
+      make_add_instruction "$f" "$updatemanifestv2" "" 1
+    fi
+  else
+    make_add_instruction "$f" "$updatemanifestv2" "$updatemanifestv3"
+  fi
 
   dir=$(dirname "$f")
   mkdir -p "$workdir/$dir"
   $BZIP2 -cz9 "$targetdir/$f" > "$workdir/$f"
   copy_perm "$targetdir/$f" "$workdir/$f"
 
   targetfiles="$targetfiles \"$f\""
 done
 
 # Append remove instructions for any dead files.
 notice ""
 notice "Adding file and directory remove instructions from file 'removed-files'"
-append_remove_instructions "$targetdir" "$updatemanifestv2"
+append_remove_instructions "$targetdir" "$updatemanifestv2" "$updatemanifestv3"
 
 $BZIP2 -z9 "$updatemanifestv2" && mv -f "$updatemanifestv2.bz2" "$updatemanifestv2"
+$BZIP2 -z9 "$updatemanifestv3" && mv -f "$updatemanifestv3.bz2" "$updatemanifestv3"
 
 eval "$MAR -C \"$workdir\" -c output.mar $targetfiles"
 mv -f "$workdir/output.mar" "$archive"
 
 # cleanup
 rm -fr "$workdir"
 
 notice ""
 notice "Finished"
+notice ""
--- a/tools/update-packaging/test/runtests.sh
+++ b/tools/update-packaging/test/runtests.sh
@@ -1,9 +1,12 @@
 #!/bin/bash
 
 echo "testing make_incremental_updates.py"
 python ../make_incremental_updates.py -f testpatchfile.txt
 
+echo ""
 echo "diffing ref.mar and test.mar"
-./diffmar.sh ref.mar test.mar
+./diffmar.sh ref.mar test.mar test
+
+echo ""
 echo "diffing ref-mac.mar and test-mac.mar"
-./diffmar.sh ref-mac.mar test-mac.mar
+./diffmar.sh ref-mac.mar test-mac.mar test-mac
--- a/tools/update-packaging/test/testpatchfile.txt
+++ b/tools/update-packaging/test/testpatchfile.txt
@@ -1,2 +1,2 @@
 product-1.0.lang.platform.complete.mar,product-2.0.lang.platform.complete.mar,test.mar,""
-product-1.0.lang.platform.complete.mar,product-2.0.lang.mac.complete.mar,test-mac.mar,""
+product-1.0.lang.mac.complete.mar,product-2.0.lang.mac.complete.mar,test-mac.mar,""
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/addFeedPrefs.js
@@ -0,0 +1,1 @@
+this is a new file
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/added.txt
@@ -0,0 +1,1 @@
+added file
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/application.ini
@@ -0,0 +1,5 @@
+[App]
+Vendor=Mozilla
+Name=MarTest
+Version=2
+BuildID=20130101010101
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a9ee7258ccc69e5ecd1b4d484ca67b7e28853f70
GIT binary patch
literal 200
zc$^Kxi4uWO006+}&P@_Iik!Lo|DUrvvmu1guxZP-9lQ37>^pGi$gvZr&YT;&FmY+>
i%C(srx9;4VTX^tj>B+Mfuim^{`S9t>w;#X$to;K<I|RxA
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/diff-patch-larger-than-file.txt
@@ -0,0 +1,1 @@
+file to
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/distribution/extensions/added/file.txt
@@ -0,0 +1,1 @@
+extfile
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/distribution/extensions/diff/diff-patch-larger-than-file.txt
@@ -0,0 +1,1 @@
+to file
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/extensions/added/file.txt
@@ -0,0 +1,1 @@
+extfile
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/extensions/diff/diff-patch-larger-than-file.txt
@@ -0,0 +1,1 @@
+to file
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/force.txt
@@ -0,0 +1,1 @@
+file is same
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/precomplete
@@ -0,0 +1,19 @@
+remove "Contents/MacOS/{foodir/update.manifest"
+remove "Contents/MacOS/{foodir/same.txt"
+remove "Contents/MacOS/{foodir/same.bin"
+remove "Contents/MacOS/{foodir/readme.txt"
+remove "Contents/MacOS/{foodir/force.txt"
+remove "Contents/MacOS/{foodir/diff-patch-larger-than-file.txt"
+remove "Contents/MacOS/update.manifest"
+remove "Contents/MacOS/searchplugins/diff/diff-patch-larger-than-file.txt"
+remove "Contents/MacOS/same.txt"
+remove "Contents/MacOS/same.bin"
+remove "Contents/MacOS/removed-files"
+remove "Contents/MacOS/readme.txt"
+remove "Contents/MacOS/precomplete"
+remove "Contents/MacOS/force.txt"
+remove "Contents/MacOS/diff-patch-larger-than-file.txt"
+rmdir "Contents/MacOS/{foodir/"
+rmdir "Contents/MacOS/searchplugins/diff/"
+rmdir "Contents/MacOS/searchplugins/added/"
+rmdir "Contents/MacOS/searchplugins/"
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/readme.txt
@@ -0,0 +1,1 @@
+This to file should be ignored
--- a/tools/update-packaging/test/to-mac/Contents/MacOS/removed-files
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/removed-files
@@ -1,10 +1,11 @@
 removed1.txt
 removed2.bin
+recursivedir/meh/*
 removed3-foo.txt     
 dir/
 this file has spaces
 notherdir/
 
 
 extra-spaces    
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a9ee7258ccc69e5ecd1b4d484ca67b7e28853f70
GIT binary patch
literal 200
zc$^Kxi4uWO006+}&P@_Iik!Lo|DUrvvmu1guxZP-9lQ37>^pGi$gvZr&YT;&FmY+>
i%C(srx9;4VTX^tj>B+Mfuim^{`S9t>w;#X$to;K<I|RxA
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/same.txt
@@ -0,0 +1,1 @@
+file is same
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/searchplugins/added/file.txt
@@ -0,0 +1,1 @@
+extfile
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/searchplugins/diff/diff-patch-larger-than-file.txt
@@ -0,0 +1,1 @@
+to file
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/update-settings.ini
@@ -0,0 +1,1 @@
+add-if-not from complete file
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/update.manifest
@@ -0,0 +1,1 @@
+to file shouldn't go in update
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/{foodir/added.txt
@@ -0,0 +1,1 @@
+added file
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/{foodir/channel-prefs.js
@@ -0,0 +1,1 @@
+add-if-not from partial file
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/{foodir/diff-patch-larger-than-file.txt
@@ -0,0 +1,1 @@
+file to
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/{foodir/force.txt
@@ -0,0 +1,1 @@
+file is same
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/{foodir/readme.txt
@@ -0,0 +1,1 @@
+This to file should be ignored
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a9ee7258ccc69e5ecd1b4d484ca67b7e28853f70
GIT binary patch
literal 200
zc$^Kxi4uWO006+}&P@_Iik!Lo|DUrvvmu1guxZP-9lQ37>^pGi$gvZr&YT;&FmY+>
i%C(srx9;4VTX^tj>B+Mfuim^{`S9t>w;#X$to;K<I|RxA
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/{foodir/same.txt
@@ -0,0 +1,1 @@
+file is same
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to-mac/Contents/MacOS/{foodir/update.manifest
@@ -0,0 +1,1 @@
+to file shouldn't go in update
--- a/tools/update-packaging/test/to-mac/precomplete
+++ b/tools/update-packaging/test/to-mac/precomplete
@@ -1,4 +1,33 @@
-remove precomplete
-remove Contents/MacOS/removed-files
-rmdir Contents/MacOS/
-rmdir Contents/
+remove "precomplete"
+remove "Contents/MacOS/{foodir/update.manifest"
+remove "Contents/MacOS/{foodir/same.txt"
+remove "Contents/MacOS/{foodir/same.bin"
+remove "Contents/MacOS/{foodir/readme.txt"
+remove "Contents/MacOS/{foodir/force.txt"
+remove "Contents/MacOS/{foodir/diff-patch-larger-than-file.txt"
+remove "Contents/MacOS/{foodir/added.txt"
+remove "Contents/MacOS/update.manifest"
+remove "Contents/MacOS/searchplugins/diff/diff-patch-larger-than-file.txt"
+remove "Contents/MacOS/searchplugins/added/file.txt"
+remove "Contents/MacOS/same.txt"
+remove "Contents/MacOS/same.bin"
+remove "Contents/MacOS/removed-files"
+remove "Contents/MacOS/readme.txt"
+remove "Contents/MacOS/precomplete"
+remove "Contents/MacOS/force.txt"
+remove "Contents/MacOS/extensions/diff/diff-patch-larger-than-file.txt"
+remove "Contents/MacOS/extensions/added/file.txt"
+remove "Contents/MacOS/diff-patch-larger-than-file.txt"
+remove "Contents/MacOS/diff-patch-larger-than-file.bin"
+remove "Contents/MacOS/application.ini"
+remove "Contents/MacOS/added.txt"
+remove "Contents/MacOS/addFeedPrefs.js"
+rmdir "Contents/MacOS/{foodir/"
+rmdir "Contents/MacOS/searchplugins/diff/"
+rmdir "Contents/MacOS/searchplugins/added/"
+rmdir "Contents/MacOS/searchplugins/"
+rmdir "Contents/MacOS/extensions/diff/"
+rmdir "Contents/MacOS/extensions/added/"
+rmdir "Contents/MacOS/extensions/"
+rmdir "Contents/MacOS/"
+rmdir "Contents/"
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to/application.ini
@@ -0,0 +1,5 @@
+[App]
+Vendor=Mozilla
+Name=MarTest
+Version=2
+BuildID=20130101010101
deleted file mode 100644
--- a/tools/update-packaging/test/to/channel-prefs.js
+++ /dev/null
@@ -1,1 +0,0 @@
-to file shouldn't go in update
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to/distribution/extensions/added/file.txt
@@ -0,0 +1,1 @@
+extfile
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to/distribution/extensions/diff/diff-patch-larger-than-file.txt
@@ -0,0 +1,1 @@
+to file
--- a/tools/update-packaging/test/to/precomplete
+++ b/tools/update-packaging/test/to/precomplete
@@ -1,21 +1,30 @@
-remove {foodir/update.manifest
-remove {foodir/same.txt
-remove {foodir/same.bin
-remove {foodir/readme.txt
-remove {foodir/force.txt
-remove {foodir/diff-patch-larger-than-file.txt
-remove-cc {foodir/channel-prefs.js
-remove update.manifest
-remove searchplugins/diff/diff-patch-larger-than-file.txt
-remove same.txt
-remove same.bin
-remove removed-files
-remove readme.txt
-remove precomplete
-remove force.txt
-remove diff-patch-larger-than-file.txt
-remove-cc channel-prefs.js
-rmdir {foodir/
-rmdir searchplugins/diff/
-rmdir searchplugins/added/
-rmdir searchplugins/
+remove "{foodir/update.manifest"
+remove "{foodir/same.txt"
+remove "{foodir/same.bin"
+remove "{foodir/readme.txt"
+remove "{foodir/force.txt"
+remove "{foodir/diff-patch-larger-than-file.txt"
+remove "{foodir/added.txt"
+remove "update.manifest"
+remove "searchplugins/diff/diff-patch-larger-than-file.txt"
+remove "searchplugins/added/file.txt"
+remove "same.txt"
+remove "same.bin"
+remove "removed-files"
+remove "readme.txt"
+remove "precomplete"
+remove "force.txt"
+remove "extensions/diff/diff-patch-larger-than-file.txt"
+remove "extensions/added/file.txt"
+remove "diff-patch-larger-than-file.txt"
+remove "diff-patch-larger-than-file.bin"
+remove "application.ini"
+remove "added.txt"
+remove "addFeedPrefs.js"
+rmdir "{foodir/"
+rmdir "searchplugins/diff/"
+rmdir "searchplugins/added/"
+rmdir "searchplugins/"
+rmdir "extensions/diff/"
+rmdir "extensions/added/"
+rmdir "extensions/"
--- a/tools/update-packaging/test/to/removed-files
+++ b/tools/update-packaging/test/to/removed-files
@@ -1,10 +1,11 @@
 removed1.txt
 removed2.bin
+recursivedir/meh/*
 removed3-foo.txt     
 dir/
 this file has spaces
 notherdir/
 
 
 extra-spaces    
 
new file mode 100644
--- /dev/null
+++ b/tools/update-packaging/test/to/update-settings.ini
@@ -0,0 +1,1 @@
+add-if-not from complete file
--- a/tools/update-packaging/test/to/{foodir/channel-prefs.js
+++ b/tools/update-packaging/test/to/{foodir/channel-prefs.js
@@ -1,1 +1,1 @@
-to file shouldn't go in update
+add-if-not from complete file
--- a/tools/update-packaging/test_make_incremental_updates.py
+++ b/tools/update-packaging/test_make_incremental_updates.py
@@ -6,40 +6,69 @@
 
 import unittest
 import make_incremental_updates as mkup
 from make_incremental_updates import PatchInfo, MarFileEntry
 
 class TestPatchInfo(unittest.TestCase):
     def setUp(self):
         self.work_dir = 'work_dir'
-        self.file_exclusion_list = ['channel-prefs.js','update.manifest','updatev2.manifest','removed-files']
+        self.file_exclusion_list = ['update.manifest','updatev2.manifest','updatev3.manifest','removed-files']
         self.path_exclusion_list = ['/readme.txt']
         self.patch_info = PatchInfo(self.work_dir, self.file_exclusion_list, self.path_exclusion_list)
 
     def testPatchInfo(self):
         self.assertEquals(self.work_dir, self.patch_info.work_dir)
         self.assertEquals([], self.patch_info.archive_files)
         self.assertEquals([], self.patch_info.manifestv2)
+        self.assertEquals([], self.patch_info.manifestv3)
         self.assertEquals(self.file_exclusion_list, self.patch_info.file_exclusion_list)
         self.assertEquals(self.path_exclusion_list, self.patch_info.path_exclusion_list)
 
     def test_append_add_instruction(self):
         self.patch_info.append_add_instruction('file.test')
         self.assertEquals(['add "file.test"'], self.patch_info.manifestv2)
+        self.assertEquals(['add "file.test"'], self.patch_info.manifestv3)
+
+    def test_append_add_if_instruction(self):
+        self.patch_info.append_add_instruction('distribution/extensions/extension/file.test')
+        self.assertEquals(['add-if "distribution/extensions/extension" "distribution/extensions/extension/file.test"'], self.patch_info.manifestv2)
+        self.assertEquals(['add-if "distribution/extensions/extension" "distribution/extensions/extension/file.test"'], self.patch_info.manifestv3)
+
+    def test_append_add_if_not_instruction(self):
+        self.patch_info.append_add_if_not_instruction('file.test')
+        self.assertEquals([], self.patch_info.manifestv2)
+        self.assertEquals(['add-if-not "file.test" "file.test"'], self.patch_info.manifestv3)
 
     def test_append_patch_instruction(self):
         self.patch_info.append_patch_instruction('file.test', 'patchname')
         self.assertEquals(['patch "patchname" "file.test"'], self.patch_info.manifestv2)
+        self.assertEquals(['patch "patchname" "file.test"'], self.patch_info.manifestv3)
 
-    """ FIXME touches the filesystem, need refactoring
+    def test_append_patch_if_instruction(self):
+        self.patch_info.append_patch_instruction('distribution/extensions/extension/file.test', 'patchname')
+        self.assertEquals(['patch-if "distribution/extensions/extension" "patchname" "distribution/extensions/extension/file.test"'], self.patch_info.manifestv2)
+        self.assertEquals(['patch-if "distribution/extensions/extension" "patchname" "distribution/extensions/extension/file.test"'], self.patch_info.manifestv3)
+
     def test_append_remove_instruction(self):
         self.patch_info.append_remove_instruction('file.test')
         self.assertEquals(['remove "file.test"'], self.patch_info.manifestv2)
+        self.assertEquals(['remove "file.test"'], self.patch_info.manifestv3)
 
+    def test_append_rmdir_instruction(self):
+        self.patch_info.append_remove_instruction('dirtest/')
+        self.assertEquals(['rmdir "dirtest/"'], self.patch_info.manifestv2)
+        self.assertEquals(['rmdir "dirtest/"'], self.patch_info.manifestv3)
+
+    def test_append_rmrfdir_instruction(self):
+        self.patch_info.append_remove_instruction('dirtest/*')
+        self.assertEquals(['rmrfdir "dirtest/"'], self.patch_info.manifestv2)
+        self.assertEquals(['rmrfdir "dirtest/"'], self.patch_info.manifestv3)
+
+    """ FIXME touches the filesystem, need refactoring
     def test_create_manifest_file(self):
         self.patch_info.create_manifest_file()
     """
 
     def test_build_marfile_entry_hash(self):
         self.assertEquals(({}, set([]), set([])), self.patch_info.build_marfile_entry_hash('root_path'))
 
 """ FIXME touches the filesystem, need refactoring
@@ -65,17 +94,17 @@ class TestMarFileEntry(unittest.TestCase
         f.close()
         sha = self.mar_file_entry.sha()
         self.assertEquals(goodSha, sha)
 """
 
 class TestMakeIncrementalUpdates(unittest.TestCase):
     def setUp(self):
         work_dir = '.'
-        self.patch_info = PatchInfo(work_dir, ['channel-prefs.js','update.manifest','updatev2.manifest','removed-files'],['/readme.txt'])
+        self.patch_info = PatchInfo(work_dir, ['update.manifest','updatev2.manifest','updatev3.manifest','removed-files'],['/readme.txt'])
         root_path = '/'
         filename = 'test.file'
         self.mar_file_entry = MarFileEntry(root_path, filename)
 
     """ FIXME makes direct shell calls, need refactoring
     def test_exec_shell_cmd(self):
         mkup.exec_shell_cmd('echo test')
 
@@ -91,20 +120,20 @@ class TestMakeIncrementalUpdates(unittes
     def test_extract_mar(self): 
         mkup.extract_mar('filename', 'work_dir')
 
     def test_create_partial_patch_for_file(self):
         mkup.create_partial_patch_for_file('from_marfile_entry', 'to_marfile_entry', 'shas', self.patch_info)
 
     def test_create_add_patch_for_file(self):           
         mkup.create_add_patch_for_file('to_marfile_entry', self.patch_info)
-        
+
     def test_process_explicit_remove_files(self): 
         mkup.process_explicit_remove_files('dir_path', self.patch_info)
-    
+
     def test_create_partial_patch(self):
         mkup.create_partial_patch('from_dir_path', 'to_dir_path', 'patch_filename', 'shas', self.patch_info, 'forced_updates')
 
     def test_create_partial_patches(patches):
         mkup.create_partial_patches('patches')
 
     """
 
@@ -112,11 +141,11 @@ class TestMakeIncrementalUpdates(unittes
     def test_get_buildid(self):
         mkup.get_buildid('work_dir', 'platform')
     """
 
     def test_decode_filename(self):
         expected = {'locale': 'lang', 'platform': 'platform', 'product': 'product', 'version': '1.0', 'type': 'complete'}
         self.assertEquals(expected, mkup.decode_filename('product-1.0.lang.platform.complete.mar'))
         self.assertRaises(Exception, mkup.decode_filename, 'fail')
-       
+
 if __name__ == '__main__':
     unittest.main()