diff --git a/doc/contributions.txt b/doc/contributions.txt
index 0fb6110adb5c2b1e32063ccdec40b86a70dd37be..2272ec79227bb7ce193c1bef60e8345b046cd7b1 100755
--- a/doc/contributions.txt
+++ b/doc/contributions.txt
@@ -214,6 +214,7 @@ Ansariel Hiller
 	MAINT-6953
 	MAINT-7028
 	MAINT-7059
+	MAINT-6519
 Aralara Rajal
 Arare Chantilly
 	CHUIBUG-191
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 4241ede365a6b1c5d3c4d1857f9f8d6eb7ee4ad7..b4e930d0625fb33d84a08e0f2bd137e8f8715e3d 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -254,6 +254,7 @@ set(viewer_SOURCE_FILES
     llfloaterlagmeter.cpp
     llfloaterland.cpp
     llfloaterlandholdings.cpp
+    llfloaterlinkreplace.cpp
     llfloaterloadprefpreset.cpp
     llfloatermarketplacelistings.cpp
     llfloatermap.cpp
@@ -876,6 +877,7 @@ set(viewer_HEADER_FILES
     llfloaterlagmeter.h
     llfloaterland.h
     llfloaterlandholdings.h
+    llfloaterlinkreplace.h
     llfloaterloadprefpreset.h
     llfloatermap.h
     llfloatermarketplacelistings.h
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 962537bc12ae6e0b5193aee695bb562e6f83c1ef..f4905514065710557e387b8ad40e5a5190ff817f 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -5450,6 +5450,28 @@
       <key>Value</key>
       <integer>1</integer>
     </map>
+    <key>LinkReplaceBatchSize</key>
+    <map>
+      <key>Comment</key>
+      <string>The maximum size of a batch in a link replace operation</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>U32</string>
+      <key>Value</key>
+      <integer>25</integer>
+    </map>
+    <key>LinkReplaceBatchPauseTime</key>
+    <map>
+      <key>Comment</key>
+      <string>The time in seconds between two batches in a link replace operation</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>F32</string>
+      <key>Value</key>
+      <real>1.0</real>
+    </map>
     <key>LipSyncAah</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/llfloaterlinkreplace.cpp b/indra/newview/llfloaterlinkreplace.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6576af2ea3528ec8add2d38c5ef25980cf4b889f
--- /dev/null
+++ b/indra/newview/llfloaterlinkreplace.cpp
@@ -0,0 +1,396 @@
+/**
+ * @file llfloaterlinkreplace.cpp
+ * @brief Allows replacing link targets in inventory links
+ * @author Ansariel Hiller
+ *
+ * $LicenseInfo:firstyear=2017&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2017, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llfloaterlinkreplace.h"
+
+#include "llagent.h"
+#include "llappearancemgr.h"
+#include "lllineeditor.h"
+#include "lltextbox.h"
+#include "llviewercontrol.h"
+
+LLFloaterLinkReplace::LLFloaterLinkReplace(const LLSD& key)
+	: LLFloater(key),
+	LLEventTimer(gSavedSettings.getF32("LinkReplaceBatchPauseTime")),
+	mRemainingItems(0),
+	mSourceUUID(LLUUID::null),
+	mTargetUUID(LLUUID::null),
+	mInstance(NULL),
+	mBatchSize(gSavedSettings.getU32("LinkReplaceBatchSize"))
+{
+	mEventTimer.stop();
+	mInstance = this;
+}
+
+LLFloaterLinkReplace::~LLFloaterLinkReplace()
+{
+	mInstance = NULL;
+}
+
+BOOL LLFloaterLinkReplace::postBuild()
+{
+	mStartBtn = getChild<LLButton>("btn_start");
+	mStartBtn->setCommitCallback(boost::bind(&LLFloaterLinkReplace::onStartClicked, this));
+
+	mRefreshBtn = getChild<LLButton>("btn_refresh");
+	mRefreshBtn->setCommitCallback(boost::bind(&LLFloaterLinkReplace::checkEnableStart, this));
+
+	mSourceEditor = getChild<LLInventoryLinkReplaceDropTarget>("source_uuid_editor");
+	mTargetEditor = getChild<LLInventoryLinkReplaceDropTarget>("target_uuid_editor");
+
+	mSourceEditor->setDADCallback(boost::bind(&LLFloaterLinkReplace::onSourceItemDrop, this, _1));
+	mTargetEditor->setDADCallback(boost::bind(&LLFloaterLinkReplace::onTargetItemDrop, this, _1));
+
+	mStatusText = getChild<LLTextBox>("status_text");
+
+	return TRUE;
+}
+
+void LLFloaterLinkReplace::onOpen(const LLSD& key)
+{
+	if (key.asUUID().notNull())
+	{
+		LLUUID item_id = key.asUUID();
+		LLViewerInventoryItem* item = gInventory.getItem(item_id);
+		mSourceEditor->setItem(item);
+		onSourceItemDrop(item->getLinkedUUID());
+	}
+	else
+	{
+		checkEnableStart();
+	}
+}
+
+void LLFloaterLinkReplace::onSourceItemDrop(const LLUUID& source_item_id)
+{
+	mSourceUUID = source_item_id;
+	checkEnableStart();
+}
+
+void LLFloaterLinkReplace::onTargetItemDrop(const LLUUID& target_item_id)
+{
+	mTargetUUID = target_item_id;
+	checkEnableStart();
+}
+
+void LLFloaterLinkReplace::updateFoundLinks()
+{
+	LLInventoryModel::item_array_t items;
+	LLInventoryModel::cat_array_t cat_array;
+	LLLinkedItemIDMatches is_linked_item_match(mSourceUUID);
+	gInventory.collectDescendentsIf(gInventory.getRootFolderID(),
+									cat_array,
+									items,
+									LLInventoryModel::INCLUDE_TRASH,
+									is_linked_item_match);
+	mRemainingItems = (U32)items.size();
+
+	LLStringUtil::format_map_t args;
+	args["NUM"] = llformat("%d", mRemainingItems);
+	mStatusText->setText(getString("ItemsFound", args));
+}
+
+void LLFloaterLinkReplace::checkEnableStart()
+{
+	if (mSourceUUID.notNull() && mTargetUUID.notNull() && mSourceUUID == mTargetUUID)
+	{
+		mStatusText->setText(getString("ItemsIdentical"));
+	}
+	else if (mSourceUUID.notNull())
+	{
+		updateFoundLinks();
+	}
+
+	mStartBtn->setEnabled(mRemainingItems > 0 && mSourceUUID.notNull() && mTargetUUID.notNull() && mSourceUUID != mTargetUUID);
+}
+
+void LLFloaterLinkReplace::onStartClicked()
+{
+	LL_INFOS() << "Starting inventory link replace" << LL_ENDL;
+
+	if (mSourceUUID.isNull() || mTargetUUID.isNull())
+	{
+		LL_WARNS() << "Cannot replace. Either source or target UUID is null." << LL_ENDL;
+		return;
+	}
+
+	if (mSourceUUID == mTargetUUID)
+	{
+		LL_WARNS() << "Cannot replace. Source and target are identical." << LL_ENDL;
+		return;
+	}
+
+	LLInventoryModel::cat_array_t cat_array;
+	LLLinkedItemIDMatches is_linked_item_match(mSourceUUID);
+	gInventory.collectDescendentsIf(gInventory.getRootFolderID(),
+									cat_array,
+									mRemainingInventoryItems,
+									LLInventoryModel::INCLUDE_TRASH,
+									is_linked_item_match);
+	LL_INFOS() << "Found " << mRemainingInventoryItems.size() << " inventory links that need to be replaced." << LL_ENDL;
+
+	if (mRemainingInventoryItems.size() > 0)
+	{
+		LLViewerInventoryItem* target_item = gInventory.getItem(mTargetUUID);
+		if (target_item)
+		{
+			mRemainingItems = (U32)mRemainingInventoryItems.size();
+
+			LLStringUtil::format_map_t args;
+			args["NUM"] = llformat("%d", mRemainingItems);
+			mStatusText->setText(getString("ItemsRemaining", args));
+
+			mStartBtn->setEnabled(FALSE);
+			mRefreshBtn->setEnabled(FALSE);
+
+			mEventTimer.start();
+			tick();
+		}
+		else
+		{
+			mStatusText->setText(getString("TargetNotFound"));
+			LL_WARNS() << "Link replace target not found." << LL_ENDL;
+		}
+	}
+}
+
+void LLFloaterLinkReplace::linkCreatedCallback(const LLUUID& old_item_id,
+												const LLUUID& target_item_id,
+												bool needs_wearable_ordering_update,
+												bool needs_description_update,
+												const LLUUID& outfit_folder_id)
+{
+	LL_DEBUGS() << "Inventory link replace:" << LL_NEWLINE
+		<< " - old_item_id = " << old_item_id.asString() << LL_NEWLINE
+		<< " - target_item_id = " << target_item_id.asString() << LL_NEWLINE
+		<< " - order update = " << (needs_wearable_ordering_update ? "true" : "false") << LL_NEWLINE
+		<< " - description update = " << (needs_description_update ? "true" : "false") << LL_NEWLINE
+		<< " - outfit_folder_id = " << outfit_folder_id.asString() << LL_ENDL;
+
+	// If we are replacing an object, bodypart or gesture link within an outfit folder,
+	// we need to change the actual description of the link itself. LLAppearanceMgr *should*
+	// have created COF links that will be used to save the outfit with an empty description.
+	// Since link_inventory_array() will set the description of the linked item for the link
+	// itself, this will lead to a dirty outfit state when the outfit with the replaced
+	// link is worn. So we have to correct this.
+	if (needs_description_update && outfit_folder_id.notNull())
+	{
+		LLInventoryModel::item_array_t items;
+		LLInventoryModel::cat_array_t cats;
+		LLLinkedItemIDMatches is_target_link(target_item_id);
+		gInventory.collectDescendentsIf(outfit_folder_id,
+										cats,
+										items,
+										LLInventoryModel::EXCLUDE_TRASH,
+										is_target_link);
+
+		for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); ++it)
+		{
+			LLPointer<LLViewerInventoryItem> item = *it;
+
+			if ((item->getType() == LLAssetType::AT_BODYPART ||
+				item->getType() == LLAssetType::AT_OBJECT ||
+				item->getType() == LLAssetType::AT_GESTURE)
+				&& !item->getActualDescription().empty())
+			{
+				LL_DEBUGS() << "Updating description for " << item->getName() << LL_ENDL;
+
+				LLSD updates;
+				updates["desc"] = "";
+				update_inventory_item(item->getUUID(), updates, LLPointer<LLInventoryCallback>(NULL));
+			}
+		}
+	}
+
+	LLUUID outfit_update_folder = LLUUID::null;
+	if (needs_wearable_ordering_update && outfit_folder_id.notNull())
+	{
+		// If a wearable item was involved in the link replace operation and replaced
+		// a link in an outfit folder, we need to update the clothing ordering information
+		// *after* the original link has been removed. LLAppearanceMgr abuses the actual link
+		// description to store the clothing ordering information it. We will have to update
+		// the clothing ordering information or the outfit will be in dirty state when worn.
+		outfit_update_folder = outfit_folder_id;
+	}
+
+	LLPointer<LLInventoryCallback> cb = new LLBoostFuncInventoryCallback(boost::bind(&LLFloaterLinkReplace::itemRemovedCallback, this, outfit_update_folder));
+	remove_inventory_object(old_item_id, cb);
+}
+
+void LLFloaterLinkReplace::itemRemovedCallback(const LLUUID& outfit_folder_id)
+{
+	if (outfit_folder_id.notNull())
+	{
+		LLAppearanceMgr::getInstance()->updateClothingOrderingInfo(outfit_folder_id);
+	}
+
+	if (mInstance)
+	{
+		decreaseOpenItemCount();
+	}
+}
+
+void LLFloaterLinkReplace::decreaseOpenItemCount()
+{
+	mRemainingItems--;
+
+	if (mRemainingItems == 0)
+	{
+		mStatusText->setText(getString("ReplaceFinished"));
+		mStartBtn->setEnabled(TRUE);
+		mRefreshBtn->setEnabled(TRUE);
+		mEventTimer.stop();
+		LL_INFOS() << "Inventory link replace finished." << LL_ENDL;
+	}
+	else
+	{
+		LLStringUtil::format_map_t args;
+		args["NUM"] = llformat("%d", mRemainingItems);
+		mStatusText->setText(getString("ItemsRemaining", args));
+		LL_DEBUGS() << "Inventory link replace: " << mRemainingItems << " links remaining..." << LL_ENDL;
+	}
+}
+
+BOOL LLFloaterLinkReplace::tick()
+{
+	LL_DEBUGS() << "Calling tick - remaining items = " << mRemainingInventoryItems.size() << LL_ENDL;
+
+	LLInventoryModel::item_array_t current_batch;
+
+	for (U32 i = 0; i < mBatchSize; ++i)
+	{
+		if (!mRemainingInventoryItems.size())
+		{
+			mEventTimer.stop();
+			break;
+		}
+
+		current_batch.push_back(mRemainingInventoryItems.back());
+		mRemainingInventoryItems.pop_back();
+	}
+	processBatch(current_batch);
+
+	return FALSE;
+}
+
+void LLFloaterLinkReplace::processBatch(LLInventoryModel::item_array_t items)
+{
+	const LLViewerInventoryItem* target_item = gInventory.getItem(mTargetUUID);
+	const LLUUID cof_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, false);
+	const LLUUID outfit_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS, false);
+
+	for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); ++it)
+	{
+		LLPointer<LLInventoryItem> source_item = *it;
+
+		if (source_item->getParentUUID() != cof_folder_id)
+		{
+			bool is_outfit_folder = gInventory.isObjectDescendentOf(source_item->getParentUUID(), outfit_folder_id);
+			// If either the new or old item in the COF is a wearable, we need to update wearable ordering after the link has been replaced
+			bool needs_wearable_ordering_update = is_outfit_folder && source_item->getType() == LLAssetType::AT_CLOTHING || target_item->getType() == LLAssetType::AT_CLOTHING;
+			// Other items in the COF need a description update (description of the actual link item must be empty)
+			bool needs_description_update = is_outfit_folder && target_item->getType() != LLAssetType::AT_CLOTHING;
+
+			LL_DEBUGS() << "is_outfit_folder = " << (is_outfit_folder ? "true" : "false") << LL_NEWLINE
+				<< "needs_wearable_ordering_update = " << (needs_wearable_ordering_update ? "true" : "false") << LL_NEWLINE
+				<< "needs_description_update = " << (needs_description_update ? "true" : "false") << LL_ENDL;
+
+			LLInventoryObject::const_object_list_t obj_array;
+			obj_array.push_back(LLConstPointer<LLInventoryObject>(target_item));
+			LLPointer<LLInventoryCallback> cb = new LLBoostFuncInventoryCallback(boost::bind(&LLFloaterLinkReplace::linkCreatedCallback,
+																											this,
+																											source_item->getUUID(),
+																											target_item->getUUID(),
+																											needs_wearable_ordering_update,
+																											needs_description_update,
+																											(is_outfit_folder ? source_item->getParentUUID() : LLUUID::null) ));
+			link_inventory_array(source_item->getParentUUID(), obj_array, cb);
+		}
+		else
+		{
+			decreaseOpenItemCount();
+		}
+	}
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// LLInventoryLinkReplaceDropTarget
+
+static LLDefaultChildRegistry::Register<LLInventoryLinkReplaceDropTarget> r("inventory_link_replace_drop_target");
+
+BOOL LLInventoryLinkReplaceDropTarget::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
+														   EDragAndDropType cargo_type,
+														   void* cargo_data,
+														   EAcceptance* accept,
+														   std::string& tooltip_msg)
+{
+	LLInventoryItem* item = (LLInventoryItem*)cargo_data;
+
+	if (cargo_type >= DAD_TEXTURE && cargo_type <= DAD_LINK &&
+		item && item->getActualType() != LLAssetType::AT_LINK_FOLDER && item->getType() != LLAssetType::AT_CATEGORY &&
+		(
+			LLAssetType::lookupCanLink(item->getType()) ||
+			(item->getType() == LLAssetType::AT_LINK && !gInventory.getObject(item->getLinkedUUID())) // Broken Link!
+		))
+	{
+		if (drop)
+		{
+			setItem(item);
+			if (!mDADSignal.empty())
+			{
+				mDADSignal(mItemID);
+			}
+		}
+		else
+		{
+			*accept = ACCEPT_YES_SINGLE;
+		}
+	}
+	else
+	{
+		*accept = ACCEPT_NO;
+	}
+
+	return TRUE;
+}
+
+void LLInventoryLinkReplaceDropTarget::setItem(LLInventoryItem* item)
+{
+	if (item)
+	{
+		mItemID = item->getLinkedUUID();
+		setText(item->getName());
+	}
+	else
+	{
+		mItemID.setNull();
+		setText(LLStringExplicit(""));
+	}
+}
diff --git a/indra/newview/llfloaterlinkreplace.h b/indra/newview/llfloaterlinkreplace.h
new file mode 100644
index 0000000000000000000000000000000000000000..377dd1d4505ffc73571aa0e1af04d0bd807f85ac
--- /dev/null
+++ b/indra/newview/llfloaterlinkreplace.h
@@ -0,0 +1,127 @@
+/**
+ * @file llfloaterlinkreplace.h
+ * @brief Allows replacing link targets in inventory links
+ * @author Ansariel Hiller
+ *
+ * $LicenseInfo:firstyear=2017&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2017, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_FLOATERLINKREPLACE_H
+#define LL_FLOATERLINKREPLACE_H
+
+#include "llfloater.h"
+#include "lleventtimer.h"
+#include "lllineeditor.h"
+#include "llinventoryfunctions.h"
+#include "llviewerinventory.h"
+
+class LLButton;
+class LLTextBox;
+
+class LLInventoryLinkReplaceDropTarget : public LLLineEditor
+{
+public:
+	struct Params : public LLInitParam::Block<Params, LLLineEditor::Params>
+	{
+		Params()
+		{}
+	};
+
+	LLInventoryLinkReplaceDropTarget(const Params& p)
+		: LLLineEditor(p) {}
+	~LLInventoryLinkReplaceDropTarget() {}
+
+	typedef boost::signals2::signal<void(const LLUUID& id)> item_dad_callback_t;
+	boost::signals2::connection setDADCallback(const item_dad_callback_t::slot_type& cb)
+	{
+		return mDADSignal.connect(cb);
+	}
+
+	virtual BOOL postBuild()
+	{
+		setEnabled(FALSE);
+		return LLLineEditor::postBuild();
+	}
+
+	virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
+								   EDragAndDropType cargo_type,
+								   void* cargo_data,
+								   EAcceptance* accept,
+								   std::string& tooltip_msg);
+
+	LLUUID getItemID() const { return mItemID; }
+	void setItem(LLInventoryItem* item);
+
+private:
+	LLUUID mItemID;
+
+	item_dad_callback_t mDADSignal;
+};
+
+
+class LLFloaterLinkReplace : public LLFloater, LLEventTimer
+{
+	LOG_CLASS(LLFloaterLinkReplace);
+
+public:
+	LLFloaterLinkReplace(const LLSD& key);
+	virtual ~LLFloaterLinkReplace();
+
+	BOOL postBuild();
+	virtual void onOpen(const LLSD& key);
+
+	virtual BOOL tick();
+
+private:
+	void checkEnableStart();
+	void onStartClicked();
+	void decreaseOpenItemCount();
+	void updateFoundLinks();
+	void processBatch(LLInventoryModel::item_array_t items);
+
+	void linkCreatedCallback(const LLUUID& old_item_id,
+								const LLUUID& target_item_id,
+								bool needs_wearable_ordering_update,
+								bool needs_description_update,
+								const LLUUID& outfit_folder_id);
+	void itemRemovedCallback(const LLUUID& outfit_folder_id);
+
+	void onSourceItemDrop(const LLUUID& source_item_id);
+	void onTargetItemDrop(const LLUUID& target_item_id);
+
+	LLInventoryLinkReplaceDropTarget*	mSourceEditor;
+	LLInventoryLinkReplaceDropTarget*	mTargetEditor;
+	LLButton*							mStartBtn;
+	LLButton*							mRefreshBtn;
+	LLTextBox*							mStatusText;
+
+	LLUUID	mSourceUUID;
+	LLUUID	mTargetUUID;
+	U32		mRemainingItems;
+	U32		mBatchSize;
+
+	LLInventoryModel::item_array_t	mRemainingInventoryItems;
+
+	LLFloaterLinkReplace* mInstance;
+};
+
+#endif // LL_FLOATERLINKREPLACE_H
diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp
index 555c19baac4fcd146cd7f46dd3b6e56b619c4841..bf4a2301ae2704e65c4570a102b51089e45f9a21 100644
--- a/indra/newview/llinventorybridge.cpp
+++ b/indra/newview/llinventorybridge.cpp
@@ -851,6 +851,7 @@ void LLInvFVBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
 
 		getClipboardEntries(true, items, disabled_items, flags);
 	}
+	addLinkReplaceMenuOption(items, disabled_items);
 	hide_context_entries(menu, items, disabled_items);
 }
 
@@ -1051,6 +1052,20 @@ void LLInvFVBridge::addMarketplaceContextMenuOptions(U32 flags,
     items.push_back(std::string("Marketplace Listings Separator"));
 }
 
+void LLInvFVBridge::addLinkReplaceMenuOption(menuentry_vec_t& items, menuentry_vec_t& disabled_items)
+{
+	const LLInventoryObject* obj = getInventoryObject();
+
+	if (isAgentInventory() && obj && obj->getType() != LLAssetType::AT_CATEGORY && obj->getType() != LLAssetType::AT_LINK_FOLDER)
+	{
+		items.push_back(std::string("Replace Links"));
+
+		if (mRoot->getSelectedCount() != 1)
+		{
+			disabled_items.push_back(std::string("Replace Links"));
+		}
+	}
+}
 
 // *TODO: remove this
 BOOL LLInvFVBridge::startDrag(EDragAndDropType* type, LLUUID* id) const
@@ -5179,6 +5194,7 @@ void LLTextureBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
 			disabled_items.push_back(std::string("Save As"));
 		}
 	}
+	addLinkReplaceMenuOption(items, disabled_items);
 	hide_context_entries(menu, items, disabled_items);	
 }
 
@@ -5251,6 +5267,7 @@ void LLSoundBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
 		items.push_back(std::string("Sound Play"));
 	}
 
+	addLinkReplaceMenuOption(items, disabled_items);
 	hide_context_entries(menu, items, disabled_items);
 }
 
@@ -5339,6 +5356,7 @@ void LLLandmarkBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
 		disabled_items.push_back(std::string("About Landmark"));
 	}
 
+	addLinkReplaceMenuOption(items, disabled_items);
 	hide_context_entries(menu, items, disabled_items);
 }
 
@@ -5641,6 +5659,7 @@ void LLCallingCardBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
 			disabled_items.push_back(std::string("Conference Chat"));
 		}
 	}
+	addLinkReplaceMenuOption(items, disabled_items);
 	hide_context_entries(menu, items, disabled_items);
 }
 
@@ -5910,6 +5929,7 @@ void LLGestureBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
 			items.push_back(std::string("Activate"));
 		}
 	}
+	addLinkReplaceMenuOption(items, disabled_items);
 	hide_context_entries(menu, items, disabled_items);
 }
 
@@ -5967,6 +5987,7 @@ void LLAnimationBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
 		items.push_back(std::string("Animation Audition"));
 	}
 
+	addLinkReplaceMenuOption(items, disabled_items);
 	hide_context_entries(menu, items, disabled_items);
 }
 
@@ -6283,6 +6304,7 @@ void LLObjectBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
 			}
 		}
 	}
+	addLinkReplaceMenuOption(items, disabled_items);
 	hide_context_entries(menu, items, disabled_items);
 }
 
@@ -6511,6 +6533,7 @@ void LLWearableBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
 			}
 		}
 	}
+	addLinkReplaceMenuOption(items, disabled_items);
 	hide_context_entries(menu, items, disabled_items);
 }
 
@@ -6682,6 +6705,7 @@ void LLLinkItemBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
 		items.push_back(std::string("Properties"));
 		addDeleteContextMenuOptions(items, disabled_items);
 	}
+	addLinkReplaceMenuOption(items, disabled_items);
 	hide_context_entries(menu, items, disabled_items);
 }
 
@@ -6733,6 +6757,7 @@ void LLMeshBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
 		getClipboardEntries(true, items, disabled_items, flags);
 	}
 
+	addLinkReplaceMenuOption(items, disabled_items);
 	hide_context_entries(menu, items, disabled_items);
 }
 
diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h
index b7d8c9d034a8f6918496c56f525d26ee78532832..e6fcb6be96871a13d1ea377be71def0fe1760700 100644
--- a/indra/newview/llinventorybridge.h
+++ b/indra/newview/llinventorybridge.h
@@ -148,6 +148,9 @@ class LLInvFVBridge : public LLFolderViewModelItemInventory
 	virtual void addMarketplaceContextMenuOptions(U32 flags,
 											 menuentry_vec_t &items,
 											 menuentry_vec_t &disabled_items);
+	virtual void addLinkReplaceMenuOption(menuentry_vec_t& items,
+										  menuentry_vec_t& disabled_items);
+
 protected:
 	LLInvFVBridge(LLInventoryPanel* inventory, LLFolderView* root, const LLUUID& uuid);
 
diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp
index f04d6cc753cf526a6f7f1bb475933085a9e3becb..bccc654fbf2e2310f3ddc96ff0248ed041956952 100644
--- a/indra/newview/llinventoryfunctions.cpp
+++ b/indra/newview/llinventoryfunctions.cpp
@@ -2306,6 +2306,26 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root
 		// Clear the clipboard before we start adding things on it
 		LLClipboard::instance().reset();
 	}
+	if ("replace_links" == action)
+	{
+		LLSD params;
+		if (root->getSelectedCount() == 1)
+		{
+			LLFolderViewItem* folder_item = root->getSelectedItems().front();
+			LLInvFVBridge* bridge = (LLInvFVBridge*)folder_item->getViewModelItem();
+
+			if (bridge)
+			{
+				LLInventoryObject* obj = bridge->getInventoryObject();
+				if (obj && obj->getType() != LLAssetType::AT_CATEGORY && obj->getActualType() != LLAssetType::AT_LINK_FOLDER)
+				{
+					params = LLSD(obj->getUUID());
+				}
+			}
+		}
+		LLFloaterReg::showInstance("linkreplace", params);
+		return;
+	}
 
 	static const std::string change_folder_string = "change_folder_type_";
 	if (action.length() > change_folder_string.length() && 
diff --git a/indra/newview/llpanelmaininventory.cpp b/indra/newview/llpanelmaininventory.cpp
index adbcf377c0ce595b3cebb2daaa01a9231839fca2..ff54f830166f39897dcc5b5b97bae25d13334866 100644
--- a/indra/newview/llpanelmaininventory.cpp
+++ b/indra/newview/llpanelmaininventory.cpp
@@ -1159,6 +1159,26 @@ void LLPanelMainInventory::onCustomAction(const LLSD& userdata)
 		mFilterEditor->setText(item_name);
 		mFilterEditor->setFocus(TRUE);
 	}
+
+	if (command_name == "replace_links")
+	{
+		LLSD params;
+		LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem();
+		if (current_item)
+		{
+			LLInvFVBridge* bridge = (LLInvFVBridge*)current_item->getViewModelItem();
+
+			if (bridge)
+			{
+				LLInventoryObject* obj = bridge->getInventoryObject();
+				if (obj && obj->getType() != LLAssetType::AT_CATEGORY && obj->getActualType() != LLAssetType::AT_LINK_FOLDER)
+				{
+					params = LLSD(obj->getUUID());
+				}
+			}
+		}
+		LLFloaterReg::showInstance("linkreplace", params);
+	}
 }
 
 void LLPanelMainInventory::onVisibilityChange( BOOL new_visibility )
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 4c146679db88c27ca832ff85c1b9d1aec3495237..0ebacddd9b175f98017d7bc58cc51b158e41f21c 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -83,6 +83,7 @@
 #include "llfloaterlagmeter.h"
 #include "llfloaterland.h"
 #include "llfloaterlandholdings.h"
+#include "llfloaterlinkreplace.h"
 #include "llfloaterloadprefpreset.h"
 #include "llfloatermap.h"
 #include "llfloatermarketplacelistings.h"
@@ -258,6 +259,7 @@ void LLViewerFloaterReg::registerFloaters()
 	
 	LLFloaterReg::add("lagmeter", "floater_lagmeter.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLagMeter>);
 	LLFloaterReg::add("land_holdings", "floater_land_holdings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLandHoldings>);
+	LLFloaterReg::add("linkreplace", "floater_linkreplace.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLinkReplace>);
 	LLFloaterReg::add("load_pref_preset", "floater_load_pref_preset.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLoadPrefPreset>);
 	
 	LLFloaterReg::add("mem_leaking", "floater_mem_leaking.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMemLeak>);
diff --git a/indra/newview/skins/default/xui/en/floater_linkreplace.xml b/indra/newview/skins/default/xui/en/floater_linkreplace.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ece75e2576457c7dd7018660f27fb3f53c4b0cf0
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_linkreplace.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ name="linkreplace"
+ help_topic="linkreplace"
+ positioning="centered"
+ title="REPLACE INVENTORY LINKS"
+ width="333"
+ height="130"
+ save_rect="true"
+ can_minimize="true"
+ can_close="true">
+	<string name="Ready">
+		Ready...
+	</string>
+	<string name="TargetNotFound">
+		Target item not found.
+	</string>
+	<string name="ItemsIdentical">
+		Source and target are identical.
+	</string>
+	<string name="ItemsFound">
+		Found [NUM] inventory links.
+	</string>
+	<string name="ItemsRemaining">
+		Links remaining: [NUM]
+	</string>
+	<string name="ReplaceFinished">
+		Finished replacing inventory links.
+	</string>
+	<text
+	 type="string"
+	 follows="left|top"
+	 font="SansSerif"
+	 height="23"
+	 layout="topleft"
+	 left="10"
+	 width="35"
+	 name="source_label"
+	 top="10">
+		Old:
+	</text>
+	<inventory_link_replace_drop_target
+	 name="source_uuid_editor"
+	 follows="left|top|right"
+	 height="23"
+	 layout="topleft"
+	 left_pad="10"
+	 max_length_bytes="255"
+	 top_delta="-3"
+	 right="-10"
+	 tool_tip="Drag and drop the current inventory item here that should be replaced."/>
+	<text
+	 type="string"
+	 follows="left|top"
+	 font="SansSerif"
+	 height="23"
+	 layout="topleft"
+	 left="10"
+	 width="35"
+	 name="target_label"
+	 top_pad="10">
+		New:
+	</text>
+	<inventory_link_replace_drop_target
+	 name="target_uuid_editor"
+	 follows="left|top|right"
+	 height="23"
+	 layout="topleft"
+	 left_pad="10"
+	 max_length_bytes="255"
+	 top_delta="-3"
+	 right="-10"
+	 tool_tip="Drag and drop new inventory item here."/>
+	<text
+	 type="string"
+	 follows="left|top|right"
+	 font="SansSerif"
+	 height="20"
+	 layout="topleft"
+	 left="10"
+	 right="-10"
+	 name="status_text"
+	 top_pad="10">
+		Ready...
+	</text>
+	<button
+	 top_pad="5"
+	 left="10"
+	 height="22"
+	 width="90"
+	 follows="left|top"
+	 mouse_opaque="true"
+	 halign="center"
+	 name="btn_refresh"
+	 label="Refresh"/>
+	<button
+	 top_delta="0"
+	 right="-10"
+	 height="22"
+	 width="90"
+	 follows="right|top"
+	 mouse_opaque="true"
+	 halign="center"
+	 name="btn_start"
+	 label="Start"/>
+</floater>
diff --git a/indra/newview/skins/default/xui/en/menu_inventory.xml b/indra/newview/skins/default/xui/en/menu_inventory.xml
index 7b3a9a2e3ec9e4611dd6424eec248a30f29d4401..847218545740053531f08f883cb66d437a790d43 100644
--- a/indra/newview/skins/default/xui/en/menu_inventory.xml
+++ b/indra/newview/skins/default/xui/en/menu_inventory.xml
@@ -603,6 +603,14 @@
          function="Inventory.DoToSelected"
          parameter="paste_link" />
     </menu_item_call>
+    <menu_item_call
+     label="Replace Links"
+     layout="topleft"
+     name="Replace Links">
+        <menu_item_call.on_click
+         function="Inventory.DoToSelected"
+         parameter="replace_links" />
+    </menu_item_call>
     <menu_item_separator
      layout="topleft" 
      name="Paste Separator" />
diff --git a/indra/newview/skins/default/xui/en/menu_inventory_gear_default.xml b/indra/newview/skins/default/xui/en/menu_inventory_gear_default.xml
index d95541df805ad1562efce96461dd4b2ed4928120..3eacdbc781f361ed657befc2a385299495129674 100644
--- a/indra/newview/skins/default/xui/en/menu_inventory_gear_default.xml
+++ b/indra/newview/skins/default/xui/en/menu_inventory_gear_default.xml
@@ -146,6 +146,14 @@
 			 function="Inventory.GearDefault.Enable"
 			 parameter="find_links" />
         </menu_item_call>
+    <menu_item_call 
+         label="Replace Links"
+         layout="topleft"
+         name="Replace Links">
+            <on_click
+             function="Inventory.GearDefault.Custom.Action"
+             parameter="replace_links" />
+        </menu_item_call>
     <menu_item_separator
      layout="topleft" />