diff --git a/src/bin/cargo/commands/vendor.rs b/src/bin/cargo/commands/vendor.rs
index efa1f1bb7b6..363b60b7cd5 100644
--- a/src/bin/cargo/commands/vendor.rs
+++ b/src/bin/cargo/commands/vendor.rs
@@ -32,7 +32,7 @@ pub fn cli() -> Command {
             "versioned-dirs",
             "Always include version in subdir name",
         ))
-        .arg(unsupported("no-merge-sources"))
+        .arg(flag("no-merge-sources", "Keep sources separate"))
         .arg(unsupported("relative-path"))
         .arg(unsupported("only-git-deps"))
         .arg(unsupported("disallow-duplicates"))
@@ -79,6 +79,7 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
                 .unwrap_or_default()
                 .cloned()
                 .collect(),
+            no_merge_sources: args.flag("no-merge-sources"),
         },
     )?;
     Ok(())
diff --git a/src/cargo/ops/vendor.rs b/src/cargo/ops/vendor.rs
index e785c6c4096..ba3734253bb 100644
--- a/src/cargo/ops/vendor.rs
+++ b/src/cargo/ops/vendor.rs
@@ -1,6 +1,6 @@
 use crate::core::package::MANIFEST_PREAMBLE;
 use crate::core::shell::Verbosity;
-use crate::core::{GitReference, Package, Workspace};
+use crate::core::{GitReference, Package, SourceId, Workspace};
 use crate::ops;
 use crate::sources::path::PathSource;
 use crate::sources::CRATES_IO_REGISTRY;
@@ -9,18 +9,23 @@ use crate::util::{try_canonicalize, CargoResult, GlobalContext};
 use anyhow::{bail, Context as _};
 use cargo_util::{paths, Sha256};
 use serde::Serialize;
+use std::collections::hash_map::DefaultHasher;
 use std::collections::HashSet;
 use std::collections::{BTreeMap, BTreeSet, HashMap};
 use std::ffi::OsStr;
 use std::fs::{self, File, OpenOptions};
+use std::hash::Hasher;
 use std::io::{Read, Write};
 use std::path::{Path, PathBuf};
 
+const SOURCES_FILE_NAME: &str = ".sources";
+
 pub struct VendorOptions<'a> {
     pub no_delete: bool,
     pub versioned_dirs: bool,
     pub destination: &'a Path,
     pub extra: Vec<PathBuf>,
+    pub no_merge_sources: bool,
 }
 
 pub fn vendor(ws: &Workspace<'_>, opts: &VendorOptions<'_>) -> CargoResult<()> {
@@ -84,8 +89,16 @@ fn sync(
     let canonical_destination = try_canonicalize(opts.destination);
     let canonical_destination = canonical_destination.as_deref().unwrap_or(opts.destination);
     let dest_dir_already_exists = canonical_destination.exists();
+    let merge_sources = !opts.no_merge_sources;
+    let sources_file = canonical_destination.join(SOURCES_FILE_NAME);
 
     paths::create_dir_all(&canonical_destination)?;
+
+    if !merge_sources {
+        let mut file = File::create(sources_file)?;
+        file.write_all(serde_json::json!([]).to_string().as_bytes())?;
+    }
+
     let mut to_remove = HashSet::new();
     if !opts.no_delete {
         for entry in canonical_destination.read_dir()? {
@@ -172,8 +185,9 @@ fn sync(
     let mut versions = HashMap::new();
     for id in ids.keys() {
         let map = versions.entry(id.name()).or_insert_with(BTreeMap::default);
-        if let Some(prev) = map.get(&id.version()) {
-            bail!(
+
+        match map.get(&id.version()) {
+            Some(prev) if merge_sources => bail!(
                 "found duplicate version of package `{} v{}` \
                  vendored from two sources:\n\
                  \n\
@@ -183,7 +197,8 @@ fn sync(
                 id.version(),
                 prev,
                 id.source_id()
-            );
+            ),
+            _ => {}
         }
         map.insert(id.version(), id.source_id());
     }
@@ -207,7 +222,17 @@ fn sync(
         };
 
         sources.insert(id.source_id());
-        let dst = canonical_destination.join(&dst_name);
+        let source_dir = if merge_sources {
+            PathBuf::from(canonical_destination).clone()
+        } else {
+            PathBuf::from(canonical_destination).join(source_id_to_dir_name(id.source_id()))
+        };
+        if sources.insert(id.source_id()) && !merge_sources {
+            if fs::create_dir_all(&source_dir).is_err() {
+                panic!("failed to create: `{}`", source_dir.display())
+            }
+        }
+        let dst = source_dir.join(&dst_name);
         to_remove.remove(&dst);
         let cksum = dst.join(".cargo-checksum.json");
         if dir_has_version_suffix && cksum.exists() {
@@ -244,6 +269,31 @@ fn sync(
         }
     }
 
+    if !merge_sources {
+        let sources_file = PathBuf::from(canonical_destination).join(SOURCES_FILE_NAME);
+        let file = File::open(&sources_file)?;
+        let mut new_sources: BTreeSet<String> = sources
+            .iter()
+            .map(|src_id| source_id_to_dir_name(*src_id))
+            .collect();
+        let old_sources: BTreeSet<String> = serde_json::from_reader::<_, BTreeSet<String>>(file)?
+            .difference(&new_sources)
+            .map(|e| e.clone())
+            .collect();
+        for dir_name in old_sources {
+            let path = PathBuf::from(canonical_destination).join(dir_name.clone());
+            if path.is_dir() {
+                if path.read_dir()?.next().is_none() {
+                    fs::remove_dir(path)?;
+                } else {
+                    new_sources.insert(dir_name.clone());
+                }
+            }
+        }
+        let file = File::create(sources_file)?;
+        serde_json::to_writer(file, &new_sources)?;
+    }
+
     // add our vendored source
     let mut config = BTreeMap::new();
 
@@ -259,16 +309,32 @@ fn sync(
             source_id.without_precise().as_url().to_string()
         };
 
+        let replace_name = if !merge_sources {
+            format!("vendor+{}", name)
+        } else {
+            merged_source_name.to_string()
+        };
+
+        if !merge_sources {
+            let src_id_string = source_id_to_dir_name(source_id);
+            let src_dir = PathBuf::from(canonical_destination).join(src_id_string.clone());
+            let string = src_dir.to_str().unwrap().to_string();
+            config.insert(
+                replace_name.clone(),
+                VendorSource::Directory { directory: string },
+            );
+        }
+
         let source = if source_id.is_crates_io() {
             VendorSource::Registry {
                 registry: None,
-                replace_with: merged_source_name.to_string(),
+                replace_with: replace_name,
             }
         } else if source_id.is_remote_registry() {
             let registry = source_id.url().to_string();
             VendorSource::Registry {
                 registry: Some(registry),
-                replace_with: merged_source_name.to_string(),
+                replace_with: replace_name,
             }
         } else if source_id.is_git() {
             let mut branch = None;
@@ -287,7 +353,7 @@ fn sync(
                 branch,
                 tag,
                 rev,
-                replace_with: merged_source_name.to_string(),
+                replace_with: replace_name,
             }
         } else {
             panic!("Invalid source ID: {}", source_id)
@@ -396,6 +462,42 @@ fn cp_sources(
     Ok(())
 }
 
+fn source_id_to_dir_name(src_id: SourceId) -> String {
+    let src_type = if src_id.is_registry() {
+        "registry"
+    } else if src_id.is_git() {
+        "git"
+    } else {
+        panic!()
+    };
+    let mut hasher = DefaultHasher::new();
+    src_id.stable_hash(Path::new(""), &mut hasher);
+    let src_hash = hasher.finish();
+    let mut bytes = [0; 8];
+    for i in 0..7 {
+        bytes[i] = (src_hash >> i * 8) as u8
+    }
+    format!("{}-{}", src_type, hex(&bytes))
+}
+
+fn hex(bytes: &[u8]) -> String {
+    let mut s = String::with_capacity(bytes.len() * 2);
+    for &byte in bytes {
+        s.push(hex((byte >> 4) & 0xf));
+        s.push(hex((byte >> 0) & 0xf));
+    }
+
+    return s;
+
+    fn hex(b: u8) -> char {
+        if b < 10 {
+            (b'0' + b) as char
+        } else {
+            (b'a' + b - 10) as char
+        }
+    }
+}
+
 fn copy_and_checksum<T: Read>(
     dst_path: &Path,
     dst_opts: &mut OpenOptions,
diff --git a/tests/testsuite/cargo_vendor/help/stdout.term.svg b/tests/testsuite/cargo_vendor/help/stdout.term.svg
index 71c13b4d3fa..b0f4c9e66be 100644
--- a/tests/testsuite/cargo_vendor/help/stdout.term.svg
+++ b/tests/testsuite/cargo_vendor/help/stdout.term.svg
@@ -1,4 +1,4 @@
-<svg width="810px" height="524px" xmlns="http://www.w3.org/2000/svg">
+<svg width="810px" height="542px" xmlns="http://www.w3.org/2000/svg">
   <style>
     .fg { fill: #AAAAAA }
     .bg { background: #000000 }
@@ -43,37 +43,39 @@
 </tspan>
     <tspan x="10px" y="226px"><tspan>      </tspan><tspan class="fg-cyan bold">--versioned-dirs</tspan><tspan>         Always include version in subdir name</tspan>
 </tspan>
-    <tspan x="10px" y="244px"><tspan>  </tspan><tspan class="fg-cyan bold">-v</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--verbose</tspan><tspan class="fg-cyan">...</tspan><tspan>             Use verbose output (-vv very verbose/build.rs output)</tspan>
+    <tspan x="10px" y="244px"><tspan>      </tspan><tspan class="fg-cyan bold">--no-merge-sources</tspan><tspan>       Keep sources separate</tspan>
 </tspan>
-    <tspan x="10px" y="262px"><tspan>  </tspan><tspan class="fg-cyan bold">-q</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--quiet</tspan><tspan>                  Do not print cargo log messages</tspan>
+    <tspan x="10px" y="262px"><tspan>  </tspan><tspan class="fg-cyan bold">-v</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--verbose</tspan><tspan class="fg-cyan">...</tspan><tspan>             Use verbose output (-vv very verbose/build.rs output)</tspan>
 </tspan>
-    <tspan x="10px" y="280px"><tspan>      </tspan><tspan class="fg-cyan bold">--color</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;WHEN&gt;</tspan><tspan>           Coloring: auto, always, never</tspan>
+    <tspan x="10px" y="280px"><tspan>  </tspan><tspan class="fg-cyan bold">-q</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--quiet</tspan><tspan>                  Do not print cargo log messages</tspan>
 </tspan>
-    <tspan x="10px" y="298px"><tspan>      </tspan><tspan class="fg-cyan bold">--config</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;KEY=VALUE&gt;</tspan><tspan>     Override a configuration value</tspan>
+    <tspan x="10px" y="298px"><tspan>      </tspan><tspan class="fg-cyan bold">--color</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;WHEN&gt;</tspan><tspan>           Coloring: auto, always, never</tspan>
 </tspan>
-    <tspan x="10px" y="316px"><tspan>  </tspan><tspan class="fg-cyan bold">-Z</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FLAG&gt;</tspan><tspan>                    Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for</tspan>
+    <tspan x="10px" y="316px"><tspan>      </tspan><tspan class="fg-cyan bold">--config</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;KEY=VALUE&gt;</tspan><tspan>     Override a configuration value</tspan>
 </tspan>
-    <tspan x="10px" y="334px"><tspan>                               details</tspan>
+    <tspan x="10px" y="334px"><tspan>  </tspan><tspan class="fg-cyan bold">-Z</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FLAG&gt;</tspan><tspan>                    Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for</tspan>
 </tspan>
-    <tspan x="10px" y="352px"><tspan>  </tspan><tspan class="fg-cyan bold">-h</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--help</tspan><tspan>                   Print help</tspan>
+    <tspan x="10px" y="352px"><tspan>                               details</tspan>
 </tspan>
-    <tspan x="10px" y="370px">
+    <tspan x="10px" y="370px"><tspan>  </tspan><tspan class="fg-cyan bold">-h</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--help</tspan><tspan>                   Print help</tspan>
 </tspan>
-    <tspan x="10px" y="388px"><tspan class="fg-green bold">Manifest Options:</tspan>
+    <tspan x="10px" y="388px">
 </tspan>
-    <tspan x="10px" y="406px"><tspan>      </tspan><tspan class="fg-cyan bold">--manifest-path</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;PATH&gt;</tspan><tspan>  Path to Cargo.toml</tspan>
+    <tspan x="10px" y="406px"><tspan class="fg-green bold">Manifest Options:</tspan>
 </tspan>
-    <tspan x="10px" y="424px"><tspan>      </tspan><tspan class="fg-cyan bold">--frozen</tspan><tspan>                Require Cargo.lock and cache are up to date</tspan>
+    <tspan x="10px" y="424px"><tspan>      </tspan><tspan class="fg-cyan bold">--manifest-path</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;PATH&gt;</tspan><tspan>  Path to Cargo.toml</tspan>
 </tspan>
-    <tspan x="10px" y="442px"><tspan>      </tspan><tspan class="fg-cyan bold">--locked</tspan><tspan>                Require Cargo.lock is up to date</tspan>
+    <tspan x="10px" y="442px"><tspan>      </tspan><tspan class="fg-cyan bold">--frozen</tspan><tspan>                Require Cargo.lock and cache are up to date</tspan>
 </tspan>
-    <tspan x="10px" y="460px"><tspan>      </tspan><tspan class="fg-cyan bold">--offline</tspan><tspan>               Run without accessing the network</tspan>
+    <tspan x="10px" y="460px"><tspan>      </tspan><tspan class="fg-cyan bold">--locked</tspan><tspan>                Require Cargo.lock is up to date</tspan>
 </tspan>
-    <tspan x="10px" y="478px">
+    <tspan x="10px" y="478px"><tspan>      </tspan><tspan class="fg-cyan bold">--offline</tspan><tspan>               Run without accessing the network</tspan>
 </tspan>
-    <tspan x="10px" y="496px"><tspan>Run `</tspan><tspan class="fg-cyan bold">cargo help vendor</tspan><tspan class="bold">` for more detailed information.</tspan>
+    <tspan x="10px" y="496px">
 </tspan>
-    <tspan x="10px" y="514px">
+    <tspan x="10px" y="514px"><tspan>Run `</tspan><tspan class="fg-cyan bold">cargo help vendor</tspan><tspan class="bold">` for more detailed information.</tspan>
+</tspan>
+    <tspan x="10px" y="532px">
 </tspan>
   </text>
 
diff --git a/tests/testsuite/vendor.rs b/tests/testsuite/vendor.rs
index c13df9cd868..9ad79369dce 100644
--- a/tests/testsuite/vendor.rs
+++ b/tests/testsuite/vendor.rs
@@ -1151,3 +1151,76 @@ fn vendor_crate_with_ws_inherit() {
         .with_stderr_contains("[..]foo/vendor/bar/src/lib.rs[..]")
         .run();
 }
+
+#[cargo_test]
+fn replace_section() {
+    let p = project()
+        .file(
+            "Cargo.toml",
+            r#"
+                [package]
+                name = "foo"
+                version = "0.1.0"
+
+                [dependencies]
+                libc = "0.2.43"
+                [replace."libc:0.2.43"]
+                git = "https://github.com/rust-lang/libc"
+                rev = "add1a320b4e1b454794a034e3f4218f877c393fc"
+            "#,
+        )
+        .file("src/lib.rs", "")
+        .build();
+
+    Package::new("libc", "0.2.43").publish();
+
+    let output = p
+        .cargo("vendor --no-merge-sources")
+        .exec_with_output()
+        .unwrap();
+    p.change_file(".cargo/config", &String::from_utf8(output.stdout).unwrap());
+    assert!(p.root().join("vendor/.sources").exists());
+    p.cargo("check").run();
+}
+
+#[cargo_test]
+fn switch_merged_source() {
+    let p = project()
+        .file(
+            "Cargo.toml",
+            r#"
+                [package]
+                name = "foo"
+                version = "0.1.0"
+                [dependencies]
+                log = "0.3.5"
+            "#,
+        )
+        .file("src/lib.rs", "")
+        .build();
+
+    Package::new("log", "0.3.5").publish();
+
+    // Start with multi sources
+    let output = p
+        .cargo("vendor --no-merge-sources")
+        .exec_with_output()
+        .unwrap();
+    assert!(p.root().join("vendor/.sources").exists());
+    p.change_file(".cargo/config", &String::from_utf8(output.stdout).unwrap());
+    p.cargo("check").run();
+
+    // Switch to merged source
+    let output = p.cargo("vendor").exec_with_output().unwrap();
+    p.change_file(".cargo/config", &String::from_utf8(output.stdout).unwrap());
+    p.cargo("check").run();
+
+    // Switch back to multi sources
+    let output = p
+        .cargo("vendor --no-merge-sources")
+        .exec_with_output()
+        .unwrap();
+    p.change_file(".cargo/config", &String::from_utf8(output.stdout).unwrap());
+    assert!(p.root().join("vendor/.sources").exists());
+    p.cargo("check").run();
+}