diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp
index 4e022aeb299f25df73cb14cc8032f28093cdf56b..8e1f7775d2c5e992db5b248e776f20061049ed90 100644
--- a/indra/newview/llappearancemgr.cpp
+++ b/indra/newview/llappearancemgr.cpp
@@ -296,15 +296,28 @@ struct LLWearableHoldingPattern
 	bool append;
 };
 
+/* static */ void removeDuplicateItems(LLInventoryModel::item_array_t& items)
+{
+	LLInventoryModel::item_array_t new_items;
+	std::set<LLUUID> items_seen;
+	for (S32 i=0; i<items.count(); i++)
+	{
+		LLViewerInventoryItem *item = items.get(i);
+		LLUUID item_id = item->getLinkedUUID();
+		if (items_seen.find(item_id)!=items_seen.end())
+			continue;
+		items_seen.insert(item_id);
+		new_items.push_back(item);
+	}
+	items = new_items;
+}
 
 void removeDuplicateItems(LLInventoryModel::item_array_t& dst, const LLInventoryModel::item_array_t& src)
 {
 	LLInventoryModel::item_array_t new_dst;
 	std::set<LLUUID> mark_inventory;
-	std::set<LLUUID> mark_asset;
 
 	S32 inventory_dups = 0;
-	S32 asset_dups = 0;
 	
 	for (LLInventoryModel::item_array_t::const_iterator src_pos = src.begin();
 		  src_pos != src.end();
@@ -312,8 +325,6 @@ void removeDuplicateItems(LLInventoryModel::item_array_t& dst, const LLInventory
 	{
 		LLUUID src_item_id = (*src_pos)->getLinkedUUID();
 		mark_inventory.insert(src_item_id);
-		LLUUID src_asset_id = (*src_pos)->getAssetUUID();
-		mark_asset.insert(src_asset_id);
 	}
 
 	for (LLInventoryModel::item_array_t::const_iterator dst_pos = dst.begin();
@@ -323,33 +334,22 @@ void removeDuplicateItems(LLInventoryModel::item_array_t& dst, const LLInventory
 		LLUUID dst_item_id = (*dst_pos)->getLinkedUUID();
 
 		if (mark_inventory.find(dst_item_id) == mark_inventory.end())
-		{
-		}
-		else
-		{
-			inventory_dups++;
-		}
-
-		LLUUID dst_asset_id = (*dst_pos)->getAssetUUID();
-
-		if (mark_asset.find(dst_asset_id) == mark_asset.end())
 		{
 			// Item is not already present in COF.
 			new_dst.put(*dst_pos);
-			mark_asset.insert(dst_item_id);
+			mark_inventory.insert(dst_item_id);
 		}
 		else
 		{
-			asset_dups++;
+			inventory_dups++;
 		}
 	}
 	llinfos << "removeDups, original " << dst.count() << " final " << new_dst.count()
-			<< " inventory dups " << inventory_dups << " asset_dups " << asset_dups << llendl;
+			<< " inventory dups " << inventory_dups << llendl;
 	
 	dst = new_dst;
 }
 
-
 /* static */ 
 LLUUID LLAppearanceManager::getCOF()
 {
@@ -363,6 +363,9 @@ void LLAppearanceManager::changeOutfit(bool proceed, const LLUUID& category, boo
 	if (!proceed)
 		return;
 
+#if 1 
+	updateCOF(category,append);
+#else
 	if (append)
 	{
 		updateCOFFromCategory(category, append); // append is true - add non-duplicates to COF.
@@ -380,6 +383,7 @@ void LLAppearanceManager::changeOutfit(bool proceed, const LLUUID& category, boo
 			rebuildCOFFromOutfit(category);
 		}
 	}
+#endif
 }
 
 // Append to current COF contents by recursively traversing a folder.
@@ -520,6 +524,130 @@ void LLAppearanceManager::shallowCopyCategory(const LLUUID& src_id, const LLUUID
 		}
 	}
 }
+/* static */ void LLAppearanceManager::purgeCategory(const LLUUID& category, bool keep_outfit_links)
+{
+	LLInventoryModel::cat_array_t cats;
+	LLInventoryModel::item_array_t items;
+	gInventory.collectDescendents(getCOF(), cats, items,
+								  LLInventoryModel::EXCLUDE_TRASH);
+	for (S32 i = 0; i < items.count(); ++i)
+	{
+		LLViewerInventoryItem *item = items.get(i);
+		if (keep_outfit_links && (item->getActualType() == LLAssetType::AT_LINK_FOLDER))
+			continue;
+		gInventory.purgeObject(item->getUUID());
+	}
+}
+
+// Keep the last N wearables of each type.  For viewer 2.0, N is 1 for
+// both body parts and clothing items.
+/* static */ void LLAppearanceManager::filterWearableItems(
+	LLInventoryModel::item_array_t& items, S32 max_per_type)
+{
+	// Divvy items into arrays by wearable type.
+	std::vector<LLInventoryModel::item_array_t> items_by_type(WT_COUNT);
+	for (S32 i=0; i<items.count(); i++)
+	{
+		LLViewerInventoryItem *item = items.get(i);
+		// Ignore non-wearables.
+		if (!item->isWearableType())
+			continue;
+		EWearableType type = item->getWearableType();
+		items_by_type[type].push_back(item);
+	}
+
+	// rebuild items list, retaining the last max_per_type of each array
+	items.clear();
+	for (S32 i=0; i<WT_COUNT; i++)
+	{
+		S32 size = items_by_type[i].size();
+		if (size <= 0)
+			continue;
+		S32 start_index = max(0,size-max_per_type);
+		for (S32 j = start_index; j<size; j++)
+		{
+			items.push_back(items_by_type[i][j]);
+		}
+	}
+}
+
+// Create links to all listed items.
+/* static */ void LLAppearanceManager::linkAll(const LLUUID& category,
+											   LLInventoryModel::item_array_t& items,
+											   LLPointer<LLInventoryCallback> cb)
+{
+	for (S32 i=0; i<items.count(); i++)
+	{
+		const LLInventoryItem* item = items.get(i).get();
+		link_inventory_item(gAgent.getID(),
+							item->getLinkedUUID(),
+							category,
+							item->getName(),
+							LLAssetType::AT_LINK,
+							cb);
+	}
+}
+
+/* static */ void LLAppearanceManager::updateCOF(const LLUUID& category, bool append)
+{
+	const LLUUID cof = getCOF();
+
+	// Collect and filter descendents to determine new COF contents.
+
+	// - Body parts: always include COF contents as a fallback in case any
+	// required parts are missing.
+	LLInventoryModel::item_array_t body_items;
+	getDescendentsOfAssetType(cof, body_items, LLAssetType::AT_BODYPART, false);
+	getDescendentsOfAssetType(category, body_items, LLAssetType::AT_BODYPART, false);
+	// Reduce body items to max of one per type.
+	removeDuplicateItems(body_items);
+	filterWearableItems(body_items, 1);
+
+	// - Wearables: include COF contents only if appending.
+	LLInventoryModel::item_array_t wear_items;
+	if (append)
+		getDescendentsOfAssetType(cof, wear_items, LLAssetType::AT_CLOTHING, false);
+	getDescendentsOfAssetType(category, wear_items, LLAssetType::AT_CLOTHING, false);
+	// Reduce wearables to max of one per type.
+	removeDuplicateItems(wear_items);
+	filterWearableItems(wear_items, 1);
+
+	// - Attachments: include COF contents only if appending.
+	LLInventoryModel::item_array_t obj_items;
+	if (append)
+		getDescendentsOfAssetType(cof, obj_items, LLAssetType::AT_OBJECT, false);
+	getDescendentsOfAssetType(category, obj_items, LLAssetType::AT_OBJECT, false);
+	removeDuplicateItems(obj_items);
+
+	// - Gestures: include COF contents only if appending.
+	LLInventoryModel::item_array_t gest_items;
+	if (append)
+		getDescendentsOfAssetType(cof, gest_items, LLAssetType::AT_GESTURE, false);
+	getDescendentsOfAssetType(category, gest_items, LLAssetType::AT_GESTURE, false);
+	removeDuplicateItems(gest_items);
+	
+	// Remove current COF contents.
+	bool keep_outfit_links = append;
+	purgeCategory(cof, keep_outfit_links);
+	gInventory.notifyObservers();
+
+	// Create links to new COF contents.
+	LLPointer<LLInventoryCallback> link_waiter = new LLUpdateAppearanceOnDestroy;
+
+	linkAll(cof, body_items, link_waiter);
+	linkAll(cof, wear_items, link_waiter);
+	linkAll(cof, obj_items, link_waiter);
+	linkAll(cof, gest_items, link_waiter);
+
+	// Add link to outfit if category is an outfit. 
+	LLViewerInventoryCategory* catp = gInventory.getCategory(category);
+	if (!append && catp && catp->getPreferredType() == LLAssetType::AT_OUTFIT)
+	{
+		link_inventory_item(gAgent.getID(), category, cof, catp->getName(),
+							LLAssetType::AT_LINK_FOLDER, link_waiter);
+	}
+							  
+}
 
 /* static */ 
 bool LLAppearanceManager::isMandatoryWearableType(EWearableType type)
@@ -792,6 +920,22 @@ void LLAppearanceManager::getCOFValidDescendents(const LLUUID& category,
 									follow_folder_links);
 }
 
+/* static */
+void LLAppearanceManager::getDescendentsOfAssetType(const LLUUID& category,
+													LLInventoryModel::item_array_t& items,
+													LLAssetType::EType type,
+													bool follow_folder_links)
+{
+	LLInventoryModel::cat_array_t cats;
+	LLIsType is_of_type(type);
+	gInventory.collectDescendentsIf(category,
+									cats,
+									items,
+									LLInventoryModel::EXCLUDE_TRASH,
+									is_of_type,
+									follow_folder_links);
+}
+
 /* static */ 
 void LLAppearanceManager::getUserDescendents(const LLUUID& category, 
 											 LLInventoryModel::item_array_t& wear_items,
@@ -998,14 +1142,16 @@ void LLAppearanceManager::removeItemLinks(const LLUUID& item_id, bool do_update)
 	}
 }
 
+//#define DUMP_CAT_VERBOSE
+
 /* static */
-void LLAppearanceManager::dumpCat(const LLUUID& cat_id, std::string str)
+void LLAppearanceManager::dumpCat(const LLUUID& cat_id, const std::string& msg)
 {
 	LLInventoryModel::cat_array_t cats;
 	LLInventoryModel::item_array_t items;
 	gInventory.collectDescendents(cat_id, cats, items, LLInventoryModel::EXCLUDE_TRASH);
 
-#if 0
+#ifdef DUMP_CAT_VERBOSE
 	llinfos << llendl;
 	llinfos << str << llendl;
 	S32 hitcount = 0;
@@ -1017,6 +1163,18 @@ void LLAppearanceManager::dumpCat(const LLUUID& cat_id, std::string str)
 		llinfos << i <<" "<< item->getName() <<llendl;
 	}
 #endif
-	llinfos << str << " count " << items.count() << llendl;
+	llinfos << msg << " count " << items.count() << llendl;
 }
 
+/* static */
+void LLAppearanceManager::dumpItemArray(const LLInventoryModel::item_array_t& items,
+										const std::string& msg)
+{
+	llinfos << msg << llendl;
+	for (S32 i=0; i<items.count(); i++)
+	{
+		LLViewerInventoryItem *item = items.get(i);
+		llinfos << i <<" " << item->getName() << llendl;
+	}
+	llinfos << llendl;
+}
diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h
index 828af321013a86a904b1ce4824335be363c93c25..ea3ebdf4ea71826f6863f7be365230fdb6b413d0 100644
--- a/indra/newview/llappearancemgr.h
+++ b/indra/newview/llappearancemgr.h
@@ -46,6 +46,7 @@ class LLAppearanceManager: public LLSingleton<LLAppearanceManager>
 	static void updateAppearanceFromCOF();
 	static bool needToSaveCOF();
 	static void changeOutfit(bool proceed, const LLUUID& category, bool append);
+	static void updateCOF(const LLUUID& category, bool append = false);
 	static void updateCOFFromCategory(const LLUUID& category, bool append);
 	static void rebuildCOFFromOutfit(const LLUUID& category);
 	static void wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append);
@@ -65,9 +66,20 @@ class LLAppearanceManager: public LLSingleton<LLAppearanceManager>
 	static void removeItemLinks(const LLUUID& item_id, bool do_update = true);
 
 	// For debugging - could be moved elsewhere.
-	static void dumpCat(const LLUUID& cat_id, std::string str);
+	static void dumpCat(const LLUUID& cat_id, const std::string& msg);
+	static void dumpItemArray(const LLInventoryModel::item_array_t& items, const std::string& msg);
 
 private:
+	static void filterWearableItems(LLInventoryModel::item_array_t& items, S32 max_per_type);
+	static void linkAll(const LLUUID& category,
+						LLInventoryModel::item_array_t& items,
+						LLPointer<LLInventoryCallback> cb);
+	
+	static void getDescendentsOfAssetType(const LLUUID& category, 
+										  LLInventoryModel::item_array_t& items,
+										  LLAssetType::EType type,
+										  bool follow_folder_links);
+
 	static void getCOFValidDescendents(const LLUUID& category, 
 									   LLInventoryModel::item_array_t& items);
 									   
@@ -81,6 +93,7 @@ class LLAppearanceManager: public LLSingleton<LLAppearanceManager>
 	static bool isMandatoryWearableType(EWearableType type);
 	static void checkMandatoryWearableTypes(const LLUUID& category, std::set<EWearableType>& types_found);
 	static void purgeCOFBeforeRebuild(const LLUUID& category);
+	static void purgeCategory(const LLUUID& category, bool keep_outfit_links);
 };
 
 #define SUPPORT_ENSEMBLES 0