use std::fs::{self, File};
use std::io::prelude::*;
use std::path::Path;
use std::thread;
use git2;

use support::{git, project, execs, main_file, path2url};
use support::{COMPILING, UPDATING, RUNNING};
use support::paths::{self, CargoPathExt};
use hamcrest::{assert_that,existing_file};
use cargo;
use cargo::util::process;

fn setup() {
}

test!(cargo_compile_simple_git_dep {
    let project = project("foo");
    let git_project = git::new("dep1", |project| {
        project
            .file("Cargo.toml", r#"
                [project]

                name = "dep1"
                version = "0.5.0"
                authors = ["carlhuda@example.com"]

                [lib]

                name = "dep1"
            "#)
            .file("src/dep1.rs", r#"
                pub fn hello() -> &'static str {
                    "hello world"
                }
            "#)
    }).unwrap();

    let project = project
        .file("Cargo.toml", &format!(r#"
            [project]

            name = "foo"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.dep1]

            git = '{}'

            [[bin]]

            name = "foo"
        "#, git_project.url()))
        .file("src/foo.rs", &main_file(r#""{}", dep1::hello()"#, &["dep1"]));

    let root = project.root();
    let git_root = git_project.root();

    assert_that(project.cargo_process("build"),
        execs()
        .with_stdout(&format!("{} git repository `{}`\n\
                              {} dep1 v0.5.0 ({}#[..])\n\
                              {} foo v0.5.0 ({})\n",
                             UPDATING, path2url(git_root.clone()),
                             COMPILING, path2url(git_root),
                             COMPILING, path2url(root)))
        .with_stderr(""));

    assert_that(&project.bin("foo"), existing_file());

    assert_that(
      cargo::util::process(&project.bin("foo")).unwrap(),
      execs().with_stdout("hello world\n"));
});

test!(cargo_compile_git_dep_branch {
    let project = project("foo");
    let git_project = git::new("dep1", |project| {
        project
            .file("Cargo.toml", r#"
                [project]

                name = "dep1"
                version = "0.5.0"
                authors = ["carlhuda@example.com"]

                [lib]

                name = "dep1"
            "#)
            .file("src/dep1.rs", r#"
                pub fn hello() -> &'static str {
                    "hello world"
                }
            "#)
    }).unwrap();

    // Make a new branch based on the current HEAD commit
    let repo = git2::Repository::open(&git_project.root()).unwrap();
    let head = repo.head().unwrap().target().unwrap();
    let head = repo.find_commit(head).unwrap();
    repo.branch("branchy", &head, true).unwrap();

    let project = project
        .file("Cargo.toml", &format!(r#"
            [project]

            name = "foo"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.dep1]

            git = '{}'
            branch = "branchy"

            [[bin]]

            name = "foo"
        "#, git_project.url()))
        .file("src/foo.rs", &main_file(r#""{}", dep1::hello()"#, &["dep1"]));

    let root = project.root();
    let git_root = git_project.root();

    assert_that(project.cargo_process("build"),
        execs()
        .with_stdout(&format!("{} git repository `{}`\n\
                              {} dep1 v0.5.0 ({}?branch=branchy#[..])\n\
                              {} foo v0.5.0 ({})\n",
                             UPDATING, path2url(git_root.clone()),
                             COMPILING, path2url(git_root),
                             COMPILING, path2url(root)))
        .with_stderr(""));

    assert_that(&project.bin("foo"), existing_file());

    assert_that(
      cargo::util::process(&project.bin("foo")).unwrap(),
      execs().with_stdout("hello world\n"));
});

test!(cargo_compile_git_dep_tag {
    let project = project("foo");
    let git_project = git::new("dep1", |project| {
        project
            .file("Cargo.toml", r#"
                [project]

                name = "dep1"
                version = "0.5.0"
                authors = ["carlhuda@example.com"]

                [lib]

                name = "dep1"
            "#)
            .file("src/dep1.rs", r#"
                pub fn hello() -> &'static str {
                    "hello world"
                }
            "#)
    }).unwrap();

    // Make a tag corresponding to the current HEAD
    let repo = git2::Repository::open(&git_project.root()).unwrap();
    let head = repo.head().unwrap().target().unwrap();
    repo.tag("v0.1.0",
             &repo.find_object(head, None).unwrap(),
             &repo.signature().unwrap(),
             "make a new tag",
             false).unwrap();

    let project = project
        .file("Cargo.toml", &format!(r#"
            [project]

            name = "foo"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.dep1]

            git = '{}'
            tag = "v0.1.0"

            [[bin]]

            name = "foo"
        "#, git_project.url()))
        .file("src/foo.rs", &main_file(r#""{}", dep1::hello()"#, &["dep1"]));

    let root = project.root();
    let git_root = git_project.root();

    assert_that(project.cargo_process("build"),
        execs()
        .with_stdout(&format!("{} git repository `{}`\n\
                              {} dep1 v0.5.0 ({}?tag=v0.1.0#[..])\n\
                              {} foo v0.5.0 ({})\n",
                             UPDATING, path2url(git_root.clone()),
                             COMPILING, path2url(git_root),
                             COMPILING, path2url(root))));

    assert_that(&project.bin("foo"), existing_file());

    assert_that(cargo::util::process(&project.bin("foo")).unwrap(),
                execs().with_stdout("hello world\n"));

    assert_that(project.cargo("build"),
                execs().with_status(0));
});

test!(cargo_compile_with_nested_paths {
    let git_project = git::new("dep1", |project| {
        project
            .file("Cargo.toml", r#"
                [project]

                name = "dep1"
                version = "0.5.0"
                authors = ["carlhuda@example.com"]

                [dependencies.dep2]

                version = "0.5.0"
                path = "vendor/dep2"

                [lib]

                name = "dep1"
            "#)
            .file("src/dep1.rs", r#"
                extern crate dep2;

                pub fn hello() -> &'static str {
                    dep2::hello()
                }
            "#)
            .file("vendor/dep2/Cargo.toml", r#"
                [project]

                name = "dep2"
                version = "0.5.0"
                authors = ["carlhuda@example.com"]

                [lib]

                name = "dep2"
            "#)
            .file("vendor/dep2/src/dep2.rs", r#"
                pub fn hello() -> &'static str {
                    "hello world"
                }
            "#)
    }).unwrap();

    let p = project("parent")
        .file("Cargo.toml", &format!(r#"
            [project]

            name = "parent"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.dep1]

            version = "0.5.0"
            git = '{}'

            [[bin]]

            name = "parent"
        "#, git_project.url()))
        .file("src/parent.rs",
              &main_file(r#""{}", dep1::hello()"#, &["dep1"]));

    p.cargo_process("build")
        .exec_with_output()
        .unwrap();

    assert_that(&p.bin("parent"), existing_file());

    assert_that(cargo::util::process(&p.bin("parent")).unwrap(),
                execs().with_stdout("hello world\n"));
});

test!(cargo_compile_with_meta_package {
    let git_project = git::new("meta-dep", |project| {
        project
            .file("dep1/Cargo.toml", r#"
                [project]

                name = "dep1"
                version = "0.5.0"
                authors = ["carlhuda@example.com"]

                [lib]

                name = "dep1"
            "#)
            .file("dep1/src/dep1.rs", r#"
                pub fn hello() -> &'static str {
                    "this is dep1"
                }
            "#)
            .file("dep2/Cargo.toml", r#"
                [project]

                name = "dep2"
                version = "0.5.0"
                authors = ["carlhuda@example.com"]

                [lib]

                name = "dep2"
            "#)
            .file("dep2/src/dep2.rs", r#"
                pub fn hello() -> &'static str {
                    "this is dep2"
                }
            "#)
    }).unwrap();

    let p = project("parent")
        .file("Cargo.toml", &format!(r#"
            [project]

            name = "parent"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.dep1]

            version = "0.5.0"
            git = '{}'

            [dependencies.dep2]

            version = "0.5.0"
            git = '{}'

            [[bin]]

            name = "parent"
        "#, git_project.url(), git_project.url()))
        .file("src/parent.rs",
              &main_file(r#""{} {}", dep1::hello(), dep2::hello()"#, &["dep1", "dep2"]));

    p.cargo_process("build")
        .exec_with_output()
        .unwrap();

    assert_that(&p.bin("parent"), existing_file());

    assert_that(cargo::util::process(&p.bin("parent")).unwrap(),
                execs().with_stdout("this is dep1 this is dep2\n"));
});

test!(cargo_compile_with_short_ssh_git {
    let url = "git@github.com:a/dep";

    let project = project("project")
        .file("Cargo.toml", &format!(r#"
            [project]

            name = "foo"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.dep]

            git = "{}"

            [[bin]]

            name = "foo"
        "#, url))
        .file("src/foo.rs", &main_file(r#""{}", dep1::hello()"#, &["dep1"]));

    assert_that(project.cargo_process("build"),
        execs()
        .with_stdout("")
        .with_stderr(&format!("\
failed to parse manifest at `[..]`

Caused by:
  invalid url `{}`: relative URL without a base
", url)));
});

test!(two_revs_same_deps {
    let bar = git::new("meta-dep", |project| {
        project.file("Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.0"
            authors = []
        "#)
        .file("src/lib.rs", "pub fn bar() -> i32 { 1 }")
    }).unwrap();

    let repo = git2::Repository::open(&bar.root()).unwrap();
    let rev1 = repo.revparse_single("HEAD").unwrap().id();

    // Commit the changes and make sure we trigger a recompile
    File::create(&bar.root().join("src/lib.rs")).unwrap().write_all(br#"
        pub fn bar() -> i32 { 2 }
    "#).unwrap();
    git::add(&repo);
    let rev2 = git::commit(&repo);

    let foo = project("foo")
        .file("Cargo.toml", &format!(r#"
            [project]
            name = "foo"
            version = "0.0.0"
            authors = []

            [dependencies.bar]
            git = '{}'
            rev = "{}"

            [dependencies.baz]
            path = "../baz"
        "#, bar.url(), rev1))
        .file("src/main.rs", r#"
            extern crate bar;
            extern crate baz;

            fn main() {
                assert_eq!(bar::bar(), 1);
                assert_eq!(baz::baz(), 2);
            }
        "#);

    let baz = project("baz")
        .file("Cargo.toml", &format!(r#"
            [package]
            name = "baz"
            version = "0.0.0"
            authors = []

            [dependencies.bar]
            git = '{}'
            rev = "{}"
        "#, bar.url(), rev2))
        .file("src/lib.rs", r#"
            extern crate bar;
            pub fn baz() -> i32 { bar::bar() }
        "#);

    baz.build();

    assert_that(foo.cargo_process("build").arg("-v"),
                execs().with_status(0));
    assert_that(&foo.bin("foo"), existing_file());
    assert_that(foo.process(&foo.bin("foo")), execs().with_status(0));
});

test!(recompilation {
    let git_project = git::new("bar", |project| {
        project
            .file("Cargo.toml", r#"
                [project]

                name = "bar"
                version = "0.5.0"
                authors = ["carlhuda@example.com"]

                [lib]
                name = "bar"
            "#)
            .file("src/bar.rs", r#"
                pub fn bar() {}
            "#)
    }).unwrap();

    let p = project("foo")
        .file("Cargo.toml", &format!(r#"
            [project]

            name = "foo"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.bar]

            version = "0.5.0"
            git = '{}'

            [[bin]]

            name = "foo"
        "#, git_project.url()))
        .file("src/foo.rs",
              &main_file(r#""{:?}", bar::bar()"#, &["bar"]));

    // First time around we should compile both foo and bar
    assert_that(p.cargo_process("build"),
                execs().with_stdout(&format!("{} git repository `{}`\n\
                                             {} bar v0.5.0 ({}#[..])\n\
                                             {} foo v0.5.0 ({})\n",
                                            UPDATING, git_project.url(),
                                            COMPILING, git_project.url(),
                                            COMPILING, p.url())));

    // Don't recompile the second time
    assert_that(p.cargo("build"),
                execs().with_stdout(""));

    // Modify a file manually, shouldn't trigger a recompile
    File::create(&git_project.root().join("src/bar.rs")).unwrap().write_all(br#"
        pub fn bar() { println!("hello!"); }
    "#).unwrap();

    assert_that(p.cargo("build"),
                execs().with_stdout(""));

    assert_that(p.cargo("update"),
                execs().with_stdout(&format!("{} git repository `{}`",
                                            UPDATING,
                                            git_project.url())));

    assert_that(p.cargo("build"),
                execs().with_stdout(""));

    // Commit the changes and make sure we don't trigger a recompile because the
    // lockfile says not to change
    let repo = git2::Repository::open(&git_project.root()).unwrap();
    git::add(&repo);
    git::commit(&repo);

    println!("compile after commit");
    assert_that(p.cargo("build"),
                execs().with_stdout(""));
    p.root().move_into_the_past().unwrap();

    // Update the dependency and carry on!
    assert_that(p.cargo("update"),
                execs().with_stdout(&format!("{} git repository `{}`",
                                            UPDATING,
                                            git_project.url())));
    println!("going for the last compile");
    assert_that(p.cargo("build"),
                execs().with_stdout(&format!("{} bar v0.5.0 ({}#[..])\n\
                                             {} foo v0.5.0 ({})\n",
                                            COMPILING, git_project.url(),
                                            COMPILING, p.url())));

    // Make sure clean only cleans one dep
    assert_that(p.cargo("clean")
                 .arg("-p").arg("foo"),
                execs().with_stdout(""));
    assert_that(p.cargo("build"),
                execs().with_stdout(&format!("{} foo v0.5.0 ({})\n",
                                            COMPILING, p.url())));
});

test!(update_with_shared_deps {
    let git_project = git::new("bar", |project| {
        project
            .file("Cargo.toml", r#"
                [project]

                name = "bar"
                version = "0.5.0"
                authors = ["carlhuda@example.com"]

                [lib]
                name = "bar"
            "#)
            .file("src/bar.rs", r#"
                pub fn bar() {}
            "#)
    }).unwrap();

    let p = project("foo")
        .file("Cargo.toml", r#"
            [package]
            name = "foo"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.dep1]
            path = "dep1"
            [dependencies.dep2]
            path = "dep2"
        "#)
        .file("src/main.rs", r#"
            extern crate dep1;
            extern crate dep2;
            fn main() {}
        "#)
        .file("dep1/Cargo.toml", &format!(r#"
            [package]
            name = "dep1"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.bar]
            version = "0.5.0"
            git = '{}'
        "#, git_project.url()))
        .file("dep1/src/lib.rs", "")
        .file("dep2/Cargo.toml", &format!(r#"
            [package]
            name = "dep2"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.bar]
            version = "0.5.0"
            git = '{}'
        "#, git_project.url()))
        .file("dep2/src/lib.rs", "");

    // First time around we should compile both foo and bar
    assert_that(p.cargo_process("build"),
                execs().with_stdout(&format!("\
{updating} git repository `{git}`
{compiling} bar v0.5.0 ({git}#[..])
{compiling} [..] v0.5.0 ({dir})
{compiling} [..] v0.5.0 ({dir})
{compiling} foo v0.5.0 ({dir})\n",
                    updating = UPDATING, git = git_project.url(),
                    compiling = COMPILING, dir = p.url())));

    // Modify a file manually, and commit it
    File::create(&git_project.root().join("src/bar.rs")).unwrap().write_all(br#"
        pub fn bar() { println!("hello!"); }
    "#).unwrap();
    let repo = git2::Repository::open(&git_project.root()).unwrap();
    let old_head = repo.head().unwrap().target().unwrap();
    git::add(&repo);
    git::commit(&repo);

    thread::sleep_ms(1000);

    // By default, not transitive updates
    println!("dep1 update");
    assert_that(p.cargo("update")
                 .arg("-p").arg("dep1"),
                execs().with_stdout(""));

    // Specifying a precise rev to the old rev shouldn't actually update
    // anything because we already have the rev in the db.
    println!("bar precise update");
    assert_that(p.cargo("update")
                 .arg("-p").arg("bar")
                 .arg("--precise").arg(&old_head.to_string()),
                execs().with_stdout(""));

    // Updating aggressively should, however, update the repo.
    println!("dep1 aggressive update");
    assert_that(p.cargo("update")
                 .arg("-p").arg("dep1")
                 .arg("--aggressive"),
                execs().with_stdout(&format!("{} git repository `{}`",
                                            UPDATING,
                                            git_project.url())));

    // Make sure we still only compile one version of the git repo
    println!("build");
    assert_that(p.cargo("build"),
                execs().with_stdout(&format!("\
{compiling} bar v0.5.0 ({git}#[..])
{compiling} [..] v0.5.0 ({dir})
{compiling} [..] v0.5.0 ({dir})
{compiling} foo v0.5.0 ({dir})\n",
                    git = git_project.url(),
                    compiling = COMPILING, dir = p.url())));

    // We should be able to update transitive deps
    assert_that(p.cargo("update").arg("-p").arg("bar"),
                execs().with_stdout(&format!("{} git repository `{}`",
                                            UPDATING,
                                            git_project.url())));
});

test!(dep_with_submodule {
    let project = project("foo");
    let git_project = git::new("dep1", |project| {
        project
            .file("Cargo.toml", r#"
                [package]
                name = "dep1"
                version = "0.5.0"
                authors = ["carlhuda@example.com"]
            "#)
    }).unwrap();
    let git_project2 = git::new("dep2", |project| {
        project.file("lib.rs", "pub fn dep() {}")
    }).unwrap();

    let repo = git2::Repository::open(&git_project.root()).unwrap();
    let url = path2url(git_project2.root()).to_string();
    git::add_submodule(&repo, &url, Path::new("src"));
    git::commit(&repo);

    let project = project
        .file("Cargo.toml", &format!(r#"
            [project]

            name = "foo"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.dep1]

            git = '{}'
        "#, git_project.url()))
        .file("src/lib.rs", "
            extern crate dep1;
            pub fn foo() { dep1::dep() }
        ");

    assert_that(project.cargo_process("build"),
                execs().with_stderr("").with_status(0));
});

test!(two_deps_only_update_one {
    let project = project("foo");
    let git1 = git::new("dep1", |project| {
        project
            .file("Cargo.toml", r#"
                [package]
                name = "dep1"
                version = "0.5.0"
                authors = ["carlhuda@example.com"]
            "#)
            .file("src/lib.rs", "")
    }).unwrap();
    let git2 = git::new("dep2", |project| {
        project
            .file("Cargo.toml", r#"
                [package]
                name = "dep2"
                version = "0.5.0"
                authors = ["carlhuda@example.com"]
            "#)
            .file("src/lib.rs", "")
    }).unwrap();

    let project = project
        .file("Cargo.toml", &format!(r#"
            [project]

            name = "foo"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.dep1]
            git = '{}'
            [dependencies.dep2]
            git = '{}'
        "#, git1.url(), git2.url()))
        .file("src/main.rs", "fn main() {}");

    assert_that(project.cargo_process("build"),
        execs()
        .with_stdout(&format!("{} git repository `[..]`\n\
                              {} git repository `[..]`\n\
                              {} [..] v0.5.0 ([..])\n\
                              {} [..] v0.5.0 ([..])\n\
                              {} foo v0.5.0 ({})\n",
                             UPDATING,
                             UPDATING,
                             COMPILING,
                             COMPILING,
                             COMPILING, project.url()))
        .with_stderr(""));

    File::create(&git1.root().join("src/lib.rs")).unwrap().write_all(br#"
        pub fn foo() {}
    "#).unwrap();
    let repo = git2::Repository::open(&git1.root()).unwrap();
    git::add(&repo);
    git::commit(&repo);

    assert_that(project.cargo("update")
                       .arg("-p").arg("dep1"),
        execs()
        .with_stdout(&format!("{} git repository `{}`\n",
                             UPDATING, git1.url()))
        .with_stderr(""));
});

test!(stale_cached_version {
    let bar = git::new("meta-dep", |project| {
        project.file("Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.0"
            authors = []
        "#)
        .file("src/lib.rs", "pub fn bar() -> i32 { 1 }")
    }).unwrap();

    // Update the git database in the cache with the current state of the git
    // repo
    let foo = project("foo")
        .file("Cargo.toml", &format!(r#"
            [project]
            name = "foo"
            version = "0.0.0"
            authors = []

            [dependencies.bar]
            git = '{}'
        "#, bar.url()))
        .file("src/main.rs", r#"
            extern crate bar;

            fn main() { assert_eq!(bar::bar(), 1) }
        "#);

    assert_that(foo.cargo_process("build"), execs().with_status(0));
    assert_that(foo.process(&foo.bin("foo")), execs().with_status(0));

    // Update the repo, and simulate someone else updating the lockfile and then
    // us pulling it down.
    File::create(&bar.root().join("src/lib.rs")).unwrap().write_all(br#"
        pub fn bar() -> i32 { 1 + 0 }
    "#).unwrap();
    let repo = git2::Repository::open(&bar.root()).unwrap();
    git::add(&repo);
    git::commit(&repo);

    thread::sleep_ms(1000);

    let rev = repo.revparse_single("HEAD").unwrap().id();

    File::create(&foo.root().join("Cargo.lock")).unwrap().write_all(format!(r#"
        [root]
        name = "foo"
        version = "0.0.0"
        dependencies = [
         'bar 0.0.0 (git+{url}#{hash})'
        ]

        [[package]]
        name = "bar"
        version = "0.0.0"
        source = 'git+{url}#{hash}'
    "#, url = bar.url(), hash = rev).as_bytes()).unwrap();

    // Now build!
    assert_that(foo.cargo("build"),
                execs().with_status(0)
                       .with_stdout(&format!("\
{updating} git repository `{bar}`
{compiling} bar v0.0.0 ({bar}#[..])
{compiling} foo v0.0.0 ({foo})
", updating = UPDATING, compiling = COMPILING, bar = bar.url(), foo = foo.url())));
    assert_that(foo.process(&foo.bin("foo")), execs().with_status(0));
});

test!(dep_with_changed_submodule {
    let project = project("foo");
    let git_project = git::new("dep1", |project| {
        project
            .file("Cargo.toml", r#"
                [package]
                name = "dep1"
                version = "0.5.0"
                authors = ["carlhuda@example.com"]
            "#)
    }).unwrap();

    let git_project2 = git::new("dep2", |project| {
        project
            .file("lib.rs", "pub fn dep() -> &'static str { \"project2\" }")
    }).unwrap();

    let git_project3 = git::new("dep3", |project| {
        project
            .file("lib.rs", "pub fn dep() -> &'static str { \"project3\" }")
    }).unwrap();

    let repo = git2::Repository::open(&git_project.root()).unwrap();
    let mut sub = git::add_submodule(&repo, &git_project2.url().to_string(),
                                     &Path::new("src"));
    git::commit(&repo);

    let project = project
        .file("Cargo.toml", &format!(r#"
            [project]
            name = "foo"
            version = "0.5.0"
            authors = ["wycats@example.com"]
            [dependencies.dep1]
            git = '{}'
        "#, git_project.url()))
        .file("src/main.rs", "
            extern crate dep1;
            pub fn main() { println!(\"{}\", dep1::dep()) }
        ");

    println!("first run");
    assert_that(project.cargo_process("run"), execs()
                .with_stdout(&format!("{} git repository `[..]`\n\
                                      {} dep1 v0.5.0 ([..])\n\
                                      {} foo v0.5.0 ([..])\n\
                                      {} `target[..]foo[..]`\n\
                                      project2\
                                      ",
                                      UPDATING,
                                      COMPILING,
                                      COMPILING,
                                      RUNNING))
                .with_stderr("")
                .with_status(0));

    File::create(&git_project.root().join(".gitmodules")).unwrap()
         .write_all(format!("[submodule \"src\"]\n\tpath = src\n\turl={}",
                            git_project3.url()).as_bytes()).unwrap();

    // Sync the submodule and reset it to the new remote.
    sub.sync().unwrap();
    {
        let subrepo = sub.open().unwrap();
        let mut origin = subrepo.find_remote("origin").unwrap();
        origin.set_url(&git_project3.url().to_string()).unwrap();
        origin.add_fetch("refs/heads/*:refs/heads/*").unwrap();;
        origin.fetch(&[], None).unwrap();
        origin.save().unwrap();
        let id = subrepo.refname_to_id("refs/remotes/origin/master").unwrap();
        let obj = subrepo.find_object(id, None).unwrap();
        subrepo.reset(&obj, git2::ResetType::Hard, None).unwrap();
    }
    sub.add_to_index(true).unwrap();
    git::add(&repo);
    git::commit(&repo);

    thread::sleep_ms(1000);
    // Update the dependency and carry on!
    println!("update");
    assert_that(project.cargo("update").arg("-v"),
                execs()
                .with_stderr("")
                .with_stdout(&format!("{} git repository `{}`",
                                     UPDATING,
                                     git_project.url())));

    println!("last run");
    assert_that(project.cargo("run"), execs()
                .with_stdout(&format!("{compiling} dep1 v0.5.0 ([..])\n\
                                      {compiling} foo v0.5.0 ([..])\n\
                                      {running} `target[..]foo[..]`\n\
                                      project3\
                                      ",
                                      compiling = COMPILING, running = RUNNING))
                .with_stderr("")
                .with_status(0));
});

test!(dev_deps_with_testing {
    let p2 = git::new("bar", |project| {
        project.file("Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.5.0"
            authors = ["wycats@example.com"]
        "#)
        .file("src/lib.rs", r#"
            pub fn gimme() -> &'static str { "zoidberg" }
        "#)
    }).unwrap();

    let p = project("foo")
        .file("Cargo.toml", &format!(r#"
            [project]

            name = "foo"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dev-dependencies.bar]
            version = "0.5.0"
            git = '{}'
        "#, p2.url()))
        .file("src/main.rs", r#"
            fn main() {}

            #[cfg(test)]
            mod tests {
                extern crate bar;
                #[test] fn foo() { bar::gimme(); }
            }
        "#);

    // Generate a lockfile which did not use `bar` to compile, but had to update
    // `bar` to generate the lockfile
    assert_that(p.cargo_process("build"),
        execs().with_stdout(&format!("\
{updating} git repository `{bar}`
{compiling} foo v0.5.0 ({url})
", updating = UPDATING, compiling = COMPILING, url = p.url(), bar = p2.url())));

    // Make sure we use the previous resolution of `bar` instead of updating it
    // a second time.
    assert_that(p.cargo("test"),
        execs().with_stdout(&format!("\
{compiling} [..] v0.5.0 ([..])
{compiling} [..] v0.5.0 ([..]
{running} target[..]foo-[..]

running 1 test
test tests::foo ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

", compiling = COMPILING, running = RUNNING)));
});

test!(git_build_cmd_freshness {
    let foo = git::new("foo", |project| {
        project.file("Cargo.toml", r#"
            [package]
            name = "foo"
            version = "0.0.0"
            authors = []
            build = "build.rs"
        "#)
        .file("build.rs", "fn main() {}")
        .file("src/lib.rs", "pub fn bar() -> i32 { 1 }")
        .file(".gitignore", "
            src/bar.rs
        ")
    }).unwrap();
    foo.root().move_into_the_past().unwrap();

    thread::sleep_ms(1000);

    assert_that(foo.cargo("build"),
                execs().with_status(0)
                       .with_stdout(&format!("\
{compiling} foo v0.0.0 ({url})
", compiling = COMPILING, url = foo.url())));

    // Smoke test to make sure it doesn't compile again
    println!("first pass");
    assert_that(foo.cargo("build"),
                execs().with_status(0)
                       .with_stdout(""));

    // Modify an ignored file and make sure we don't rebuild
    println!("second pass");
    File::create(&foo.root().join("src/bar.rs")).unwrap();
    assert_that(foo.cargo("build"),
                execs().with_status(0)
                       .with_stdout(""));
});

test!(git_name_not_always_needed {
    let p2 = git::new("bar", |project| {
        project.file("Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.5.0"
            authors = ["wycats@example.com"]
        "#)
        .file("src/lib.rs", r#"
            pub fn gimme() -> &'static str { "zoidberg" }
        "#)
    }).unwrap();

    let repo = git2::Repository::open(&p2.root()).unwrap();
    let mut cfg = repo.config().unwrap();
    let _ = cfg.remove("user.name");
    let _ = cfg.remove("user.email");

    let p = project("foo")
        .file("Cargo.toml", &format!(r#"
            [project]
            name = "foo"
            version = "0.5.0"
            authors = []

            [dev-dependencies.bar]
            git = '{}'
        "#, p2.url()))
        .file("src/main.rs", "fn main() {}");

    // Generate a lockfile which did not use `bar` to compile, but had to update
    // `bar` to generate the lockfile
    assert_that(p.cargo_process("build"),
        execs().with_stdout(&format!("\
{updating} git repository `{bar}`
{compiling} foo v0.5.0 ({url})
", updating = UPDATING, compiling = COMPILING, url = p.url(), bar = p2.url())));
});

test!(git_repo_changing_no_rebuild {
    let bar = git::new("bar", |project| {
        project.file("Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.5.0"
            authors = ["wycats@example.com"]
        "#)
        .file("src/lib.rs", "pub fn bar() -> i32 { 1 }")
    }).unwrap();

    // Lock p1 to the first rev in the git repo
    let p1 = project("p1")
        .file("Cargo.toml", &format!(r#"
            [project]
            name = "p1"
            version = "0.5.0"
            authors = []
            build = 'build.rs'
            [dependencies.bar]
            git = '{}'
        "#, bar.url()))
        .file("src/main.rs", "fn main() {}")
        .file("build.rs", "fn main() {}");
    p1.build();
    p1.root().move_into_the_past().unwrap();
    assert_that(p1.cargo("build"),
                execs().with_stdout(&format!("\
{updating} git repository `{bar}`
{compiling} [..]
{compiling} [..]
", updating = UPDATING, compiling = COMPILING, bar = bar.url())));

    // Make a commit to lock p2 to a different rev
    File::create(&bar.root().join("src/lib.rs")).unwrap().write_all(br#"
        pub fn bar() -> i32 { 2 }
    "#).unwrap();
    let repo = git2::Repository::open(&bar.root()).unwrap();
    git::add(&repo);
    git::commit(&repo);

    // Lock p2 to the second rev
    let p2 = project("p2")
        .file("Cargo.toml", &format!(r#"
            [project]
            name = "p2"
            version = "0.5.0"
            authors = []
            [dependencies.bar]
            git = '{}'
        "#, bar.url()))
        .file("src/main.rs", "fn main() {}");
    assert_that(p2.cargo_process("build"),
                execs().with_stdout(&format!("\
{updating} git repository `{bar}`
{compiling} [..]
{compiling} [..]
", updating = UPDATING, compiling = COMPILING, bar = bar.url())));

    // And now for the real test! Make sure that p1 doesn't get rebuilt
    // even though the git repo has changed.
    assert_that(p1.cargo("build"),
                execs().with_stdout(""));
});

test!(git_dep_build_cmd {
    let p = git::new("foo", |project| {
        project.file("Cargo.toml", r#"
            [project]

            name = "foo"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.bar]

            version = "0.5.0"
            path = "bar"

            [[bin]]

            name = "foo"
        "#)
        .file("src/foo.rs",
              &main_file(r#""{}", bar::gimme()"#, &["bar"]))
        .file("bar/Cargo.toml", r#"
            [project]

            name = "bar"
            version = "0.5.0"
            authors = ["wycats@example.com"]
            build = "build.rs"

            [lib]

            name = "bar"
        "#)
        .file("bar/src/bar.rs.in", r#"
            pub fn gimme() -> i32 { 0 }
        "#)
        .file("bar/build.rs", r#"
            use std::fs;
            fn main() {
                fs::copy("src/bar.rs.in", "src/bar.rs").unwrap();
            }
        "#)
    }).unwrap();

    p.root().join("bar").move_into_the_past().unwrap();

    assert_that(p.cargo("build"),
                execs().with_status(0));

    assert_that(cargo::util::process(&p.bin("foo")).unwrap(),
                execs().with_stdout("0\n"));

    // Touching bar.rs.in should cause the `build` command to run again.
    fs::File::create(&p.root().join("bar/src/bar.rs.in")).unwrap()
             .write_all(b"pub fn gimme() -> i32 { 1 }").unwrap();

    assert_that(p.cargo("build"),
                execs().with_status(0));

    assert_that(cargo::util::process(&p.bin("foo")).unwrap(),
                execs().with_stdout("1\n"));
});

test!(fetch_downloads {
    let bar = git::new("bar", |project| {
        project.file("Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.5.0"
            authors = ["wycats@example.com"]
        "#)
        .file("src/lib.rs", "pub fn bar() -> i32 { 1 }")
    }).unwrap();

    let p = project("p1")
        .file("Cargo.toml", &format!(r#"
            [project]
            name = "p1"
            version = "0.5.0"
            authors = []
            [dependencies.bar]
            git = '{}'
        "#, bar.url()))
        .file("src/main.rs", "fn main() {}");
    assert_that(p.cargo_process("fetch"),
                execs().with_status(0).with_stdout(&format!("\
{updating} git repository `{url}`
", updating = UPDATING, url = bar.url())));

    assert_that(p.cargo("fetch"),
                execs().with_status(0).with_stdout(""));
});

test!(warnings_in_git_dep {
    let bar = git::new("bar", |project| {
        project.file("Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.5.0"
            authors = ["wycats@example.com"]
        "#)
        .file("src/lib.rs", "fn unused() {}")
    }).unwrap();

    let p = project("foo")
        .file("Cargo.toml", &format!(r#"
            [project]
            name = "foo"
            version = "0.5.0"
            authors = []
            [dependencies.bar]
            git = '{}'
        "#, bar.url()))
        .file("src/main.rs", "fn main() {}");

    assert_that(p.cargo_process("build"),
        execs()
        .with_stdout(&format!("{} git repository `{}`\n\
                              {} bar v0.5.0 ({}#[..])\n\
                              {} foo v0.5.0 ({})\n",
                             UPDATING, bar.url(),
                             COMPILING, bar.url(),
                             COMPILING, p.url()))
        .with_stderr(""));
});

test!(update_ambiguous {
    let foo1 = git::new("foo1", |project| {
        project.file("Cargo.toml", r#"
            [package]
            name = "foo"
            version = "0.5.0"
            authors = ["wycats@example.com"]
        "#)
        .file("src/lib.rs", "")
    }).unwrap();
    let foo2 = git::new("foo2", |project| {
        project.file("Cargo.toml", r#"
            [package]
            name = "foo"
            version = "0.6.0"
            authors = ["wycats@example.com"]
        "#)
        .file("src/lib.rs", "")
    }).unwrap();
    let bar = git::new("bar", |project| {
        project.file("Cargo.toml", &format!(r#"
            [package]
            name = "bar"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.foo]
            git = '{}'
        "#, foo2.url()))
        .file("src/lib.rs", "")
    }).unwrap();

    let p = project("project")
        .file("Cargo.toml", &format!(r#"
            [project]
            name = "project"
            version = "0.5.0"
            authors = []
            [dependencies.foo]
            git = '{}'
            [dependencies.bar]
            git = '{}'
        "#, foo1.url(), bar.url()))
        .file("src/main.rs", "fn main() {}");

    assert_that(p.cargo_process("generate-lockfile"), execs().with_status(0));
    assert_that(p.cargo("update")
                 .arg("-p").arg("foo"),
                execs().with_status(101)
                       .with_stderr("\
There are multiple `foo` packages in your project, and the specification `foo` \
is ambiguous.
Please re-run this command with `-p <spec>` where `<spec>` is one of the \
following:
  foo:0.[..].0
  foo:0.[..].0
"));
});

test!(update_one_dep_in_repo_with_many_deps {
    let foo = git::new("foo", |project| {
        project.file("Cargo.toml", r#"
            [package]
            name = "foo"
            version = "0.5.0"
            authors = ["wycats@example.com"]
        "#)
        .file("src/lib.rs", "")
        .file("a/Cargo.toml", r#"
            [package]
            name = "a"
            version = "0.5.0"
            authors = ["wycats@example.com"]
        "#)
        .file("a/src/lib.rs", "")
    }).unwrap();

    let p = project("project")
        .file("Cargo.toml", &format!(r#"
            [project]
            name = "project"
            version = "0.5.0"
            authors = []
            [dependencies.foo]
            git = '{}'
            [dependencies.a]
            git = '{}'
        "#, foo.url(), foo.url()))
        .file("src/main.rs", "fn main() {}");

    assert_that(p.cargo_process("generate-lockfile"), execs().with_status(0));
    assert_that(p.cargo("update")
                 .arg("-p").arg("foo"),
                execs().with_status(0)
                       .with_stdout(&format!("\
Updating git repository `{}`
", foo.url())));
});

test!(switch_deps_does_not_update_transitive {
    let transitive = git::new("transitive", |project| {
        project.file("Cargo.toml", r#"
            [package]
            name = "transitive"
            version = "0.5.0"
            authors = ["wycats@example.com"]
        "#)
        .file("src/lib.rs", "")
    }).unwrap();
    let dep1 = git::new("dep1", |project| {
        project.file("Cargo.toml", &format!(r#"
            [package]
            name = "dep"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.transitive]
            git = '{}'
        "#, transitive.url()))
        .file("src/lib.rs", "")
    }).unwrap();
    let dep2 = git::new("dep2", |project| {
        project.file("Cargo.toml", &format!(r#"
            [package]
            name = "dep"
            version = "0.5.0"
            authors = ["wycats@example.com"]

            [dependencies.transitive]
            git = '{}'
        "#, transitive.url()))
        .file("src/lib.rs", "")
    }).unwrap();

    let p = project("project")
        .file("Cargo.toml", &format!(r#"
            [project]
            name = "project"
            version = "0.5.0"
            authors = []
            [dependencies.dep]
            git = '{}'
        "#, dep1.url()))
        .file("src/main.rs", "fn main() {}");

    p.build();
    assert_that(p.cargo("build"),
                execs().with_status(0)
                       .with_stdout(&format!("\
Updating git repository `{}`
Updating git repository `{}`
{compiling} transitive [..]
{compiling} dep [..]
{compiling} project [..]
", dep1.url(), transitive.url(), compiling = COMPILING)));

    // Update the dependency to point to the second repository, but this
    // shouldn't update the transitive dependency which is the same.
    File::create(&p.root().join("Cargo.toml")).unwrap().write_all(format!(r#"
            [project]
            name = "project"
            version = "0.5.0"
            authors = []
            [dependencies.dep]
            git = '{}'
    "#, dep2.url()).as_bytes()).unwrap();

    assert_that(p.cargo("build"),
                execs().with_status(0)
                       .with_stdout(&format!("\
Updating git repository `{}`
{compiling} dep [..]
{compiling} project [..]
", dep2.url(), compiling = COMPILING)));
});

test!(update_one_source_updates_all_packages_in_that_git_source {
    let dep = git::new("dep", |project| {
        project.file("Cargo.toml", r#"
            [package]
            name = "dep"
            version = "0.5.0"
            authors = []

            [dependencies.a]
            path = "a"
        "#)
        .file("src/lib.rs", "")
        .file("a/Cargo.toml", r#"
            [package]
            name = "a"
            version = "0.5.0"
            authors = []
        "#)
        .file("a/src/lib.rs", "")
    }).unwrap();

    let p = project("project")
        .file("Cargo.toml", &format!(r#"
            [project]
            name = "project"
            version = "0.5.0"
            authors = []
            [dependencies.dep]
            git = '{}'
        "#, dep.url()))
        .file("src/main.rs", "fn main() {}");

    p.build();
    assert_that(p.cargo("build"),
                execs().with_status(0));

    let repo = git2::Repository::open(&dep.root()).unwrap();
    let rev1 = repo.revparse_single("HEAD").unwrap().id();

    // Just be sure to change a file
    File::create(&dep.root().join("src/lib.rs")).unwrap().write_all(br#"
        pub fn bar() -> i32 { 2 }
    "#).unwrap();
    git::add(&repo);
    git::commit(&repo);

    assert_that(p.cargo("update").arg("-p").arg("dep"),
                execs().with_status(0));
    let mut lockfile = String::new();
    File::open(&p.root().join("Cargo.lock")).unwrap()
         .read_to_string(&mut lockfile).unwrap();
    assert!(!lockfile.contains(&rev1.to_string()),
            "{} in {}", rev1, lockfile);
});

test!(switch_sources {
    let a1 = git::new("a1", |project| {
        project.file("Cargo.toml", r#"
            [package]
            name = "a"
            version = "0.5.0"
            authors = []
        "#)
        .file("src/lib.rs", "")
    }).unwrap();
    let a2 = git::new("a2", |project| {
        project.file("Cargo.toml", r#"
            [package]
            name = "a"
            version = "0.5.1"
            authors = []
        "#)
        .file("src/lib.rs", "")
    }).unwrap();

    let p = project("project")
        .file("Cargo.toml", r#"
            [project]
            name = "project"
            version = "0.5.0"
            authors = []
            [dependencies.b]
            path = "b"
        "#)
        .file("src/main.rs", "fn main() {}")
        .file("b/Cargo.toml", &format!(r#"
            [project]
            name = "b"
            version = "0.5.0"
            authors = []
            [dependencies.a]
            git = '{}'
        "#, a1.url()))
        .file("b/src/lib.rs", "fn main() {}");

    p.build();
    assert_that(p.cargo("build"),
                execs().with_status(0)
                       .with_stdout(&format!("\
{updating} git repository `file://[..]a1`
{compiling} a v0.5.0 ([..]a1#[..]
{compiling} b v0.5.0 ([..])
{compiling} project v0.5.0 ([..])
", updating = UPDATING, compiling = COMPILING)));

    File::create(&p.root().join("b/Cargo.toml")).unwrap().write_all(format!(r#"
        [project]
        name = "b"
        version = "0.5.0"
        authors = []
        [dependencies.a]
        git = '{}'
    "#, a2.url()).as_bytes()).unwrap();

    assert_that(p.cargo("build"),
                execs().with_status(0)
                       .with_stdout(&format!("\
{updating} git repository `file://[..]a2`
{compiling} a v0.5.1 ([..]a2#[..]
{compiling} b v0.5.0 ([..])
{compiling} project v0.5.0 ([..])
", updating = UPDATING, compiling = COMPILING)));
});

test!(dont_require_submodules_are_checked_out {
    let project = project("foo");
    let git1 = git::new("dep1", |p| {
        p.file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.5.0"
            authors = []
            build = "build.rs"
        "#)
        .file("build.rs", "fn main() {}")
        .file("src/lib.rs", "")
        .file("a/foo", "")
    }).unwrap();
    let git2 = git::new("dep2", |p| p).unwrap();

    let repo = git2::Repository::open(&git1.root()).unwrap();
    let url = path2url(git2.root()).to_string();
    git::add_submodule(&repo, &url, &Path::new("a/submodule"));
    git::commit(&repo);

    git2::Repository::init(&project.root()).unwrap();
    let url = path2url(git1.root()).to_string();
    let dst = paths::home().join("foo");
    git2::Repository::clone(&url, &dst).unwrap();

    assert_that(git1.cargo("build").arg("-v").cwd(&dst),
                execs().with_status(0));
});

test!(doctest_same_name {
    let a2 = git::new("a2", |p| {
        p.file("Cargo.toml", r#"
            [project]
            name = "a"
            version = "0.5.0"
            authors = []
        "#)
        .file("src/lib.rs", "pub fn a2() {}")
    }).unwrap();

    let a1 = git::new("a1", |p| {
        p.file("Cargo.toml", &format!(r#"
            [project]
            name = "a"
            version = "0.5.0"
            authors = []
            [dependencies]
            a = {{ git = '{}' }}
        "#, a2.url()))
        .file("src/lib.rs", "extern crate a; pub fn a1() {}")
    }).unwrap();

    let p = project("foo")
        .file("Cargo.toml", &format!(r#"
            [package]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies]
            a = {{ git = '{}' }}
        "#, a1.url()))
        .file("src/lib.rs", r#"
            #[macro_use]
            extern crate a;
        "#);

    assert_that(p.cargo_process("test").arg("-v"),
                execs().with_status(0));
});
