我正在构建一个内部使用git2.rs来管理项目的应用程序.

我正在try 实现基本用例的测试,例如git init、git添加、提交和推送到遥控器,但推送部分遇到了问题.

我使用本地裸远程存储库实现了我的测试用例.我首先创建一个源存储库,在其中初始化git,然后创建一个哑文本文件,将其添加到索引中并提交.

在此之前,一切似乎都很顺利.

然后,我创建一个本地裸repo,将其设置为源repo的"源"远程,并在远程repo实例上调用Push.我没有错误,但源回购的内容似乎没有被推送.

文档对学习者不是很友好,所以我很难理解我在做什么.

我希望在远程repo目录中可能会看到我的文本文件,但那里只有git struct .

当我在按下后试图通过将遥控器克隆到新目录中来进行断言时,我会判断文本文件是否在那里,但它不在,它只会创建一个空的存储库.


以下是我的代码的相关部分,这只是我在Test子模块中实现的一个特征.

源头trait

use git2::Repository;
use std::path::PathBuf;

pub trait Git {
    // ... other methods...

    fn _set_remote<'a, T: Into<PathBuf>>(
        repo_dir: T,
        name: &str,
        url: &str,
    ) -> Result<(), git2::Error> {
        let repo = Self::_repo(repo_dir)?;
        repo.remote(name, url)?;
        Ok(())
    }

    fn git_init(&self) -> Result<Repository, git2::Error>;
    fn git_add<'a, E: Into<&'a str>>(&self, expr: E) -> Result<git2::Index, git2::Error>;
    fn git_commit<'a, M: Into<&'a str>>(&self, message: M) -> Result<git2::Oid, git2::Error>;
    fn git_set_remote(&self, name: &str, url: &str) -> Result<(), git2::Error>;
}

测试实现

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Write;

    struct TestGit {
        pub dir: PathBuf,
        pub state: String,
    }

   // Impl TestGit ...

    impl Git for TestGit {
        fn git_init(&self) -> Result<Repository, git2::Error> {
            // ... 
        }

        fn git_add<'a, E: Into<&'a str>>(&self, expr: E) -> Result<git2::Index, git2::Error> {
            // ...
        }

        fn git_commit<'a, M: Into<&'a str>>(&self, message: M) -> Result<git2::Oid, git2::Error> {
            // ...
        }

        fn git_set_remote(&self, name: &str, url: &str) -> Result<(), git2::Error> {
            Self::_set_remote(&self.dir, name, url)
        }
    }

   // Some first tests for init, add, commit, write file, etc.
   // ...

    #[test]
    fn test_push() {
        let testgit = TestGit {
            dir: std::env::current_dir().unwrap().join("test/base"),
            state: String::from("Hello"),
        };

        let base_repo = testgit.git_init().unwrap();

        let testgitremote = create_testgit_instance("test/remote");
        <TestGit as Git>::_init::<&PathBuf>(&testgitremote.dir, true).unwrap();

        testgit
            .git_set_remote(
                "origin",
                format!("file://{}", testgitremote.dir.to_str().unwrap()).as_str(),
            )
            .unwrap();

        testgit.write_file("test.txt").unwrap(); // This creates a test.txt file with "Hello" in it at the root of the repo. 

        testgit.git_add(".").unwrap();
        testgit.git_commit("test commit").unwrap();
        // This works find until there becauses I tested it elsewhere, the index contains one more element after the commit.

        let mut remote = base_repo.find_remote("origin").unwrap();

        remote.push::<&str>(&[], None).unwrap(); // This is what I'm having troubles to understand, I'm guessing I'm just pushing nothing but I don't find anything clear in the docs and there is no "push" example it the git2.rs sources.

        let mut clonebuilder = git2::build::RepoBuilder::new();

        let clonerepo_dir = testgit.dir.parent().unwrap().join("clone");

        clonebuilder
            .clone(remote.url().unwrap(), &clonerepo_dir)
            .unwrap();

        assert!(clonerepo_dir.join("test.txt").exists()); // This fails...

        std::fs::remove_dir_all(&testgit.dir.parent().unwrap()).unwrap();
    }
}

我也试着添加这样的参考规范,但它没有改变任何事情

let mut remote = base_repo.find_remote("origin").unwrap();

remote.push::<&str>(&["refs/heads/master:refs/heads/master")], None).unwrap();

或者像这样,同样的结果.

let mut remote = base_repo.find_remote("origin").unwrap();

base_repo
    .remote_add_push("origin", "refs/heads/master:refs/heads/master")
    .unwrap();

remote.push::<&str>(&[], None).unwrap();

非常感谢你的帮助.

推荐答案

我在这个线程https://users.rust-lang.org/t/how-to-use-git2-push-correctly/97202/6中得到了一个解决方案,我在这里依赖它,以防它可能有用.

事实证明,问题出在我的git commit实现中.我忘了用新的提交更新分支指针.这就是为什么什么都没有推.

这就是给我提供解决方案的片段

use std::{fs, path};

use git2::build::RepoBuilder;
use git2::{IndexAddOption, Repository, Signature};


fn main() {
    let root_dir = path::Path::new("Z:/Temp");
    let base_path = root_dir.join("base");
    let remote_path = root_dir.join("remote");
    let clone_path = root_dir.join("clone");
    let author = Signature::now("user", "user@example.com").unwrap();

    // create base repo and remote bare repo
    let base_repo = Repository::init(&base_path).unwrap();
    let remote_repo = Repository::init_bare(&remote_path).unwrap();
    let remote_url = format!("file:///{}", remote_repo.path().display());

    // create a text file and add it to index
    fs::write(base_path.join("hello.txt"), "hello world!\n").unwrap();
    let mut base_index = base_repo.index().unwrap();
    base_index
        .add_all(["."], IndexAddOption::DEFAULT, None)
        .unwrap();
    base_index.write().unwrap();

    // make the commit, since it's the initial commit, there's no parent
    let tree = base_repo
        .find_tree(base_index.write_tree().unwrap())
        .unwrap();
    let commit_oid = base_repo
        .commit(None, &author, &author, "initial", &tree, &[])
        .unwrap();

    // update branch pointer
    let branch = base_repo
        .branch("main", &base_repo.find_commit(commit_oid).unwrap(), true)
        .unwrap();
    let branch_ref = branch.into_reference();
    let branch_ref_name = branch_ref.name().unwrap();
    base_repo.set_head(branch_ref_name).unwrap();

    // add remote as "origin" and push the branch
    let mut origin = base_repo.remote("origin", &remote_url).unwrap();
    origin.push(&[branch_ref_name], None).unwrap();

    // clone from remote
    let clone_repo = RepoBuilder::new()
        .branch("main")
        .clone(&remote_url, &clone_path)
        .unwrap();

    // examine the commit message:
    println!(
        "short commit message: {}",
        clone_repo
            .head()
            .unwrap()
            .peel_to_commit()
            .unwrap()
            .summary()
            .unwrap()
    );
}

如果有用,下面是我的添加和提交的固定实现,以及推送测试.

    fn _add_all<'a, T: Into<PathBuf>, E: Into<&'a str>>(
        repo_dir: T,
        expr: E,
    ) -> Result<git2::Index, git2::Error> {
        let repo = Self::_repo(repo_dir)?;
        let mut index = repo.index()?;
        index.add_all([expr.into()], git2::IndexAddOption::DEFAULT, None)?;
        index.write()?;
        index.write_tree()?;
        Ok(index)
    }

    fn _update_branch<'a, T: Into<PathBuf>, Str: Into<&'a str>>(
        repo_dir: T,
        name: Str,
        commit_oid: &git2::Oid,
    ) -> Result<(), git2::Error> {
        let repo = Self::_repo(repo_dir)?;
        let branch = repo.branch(name.into(), &repo.find_commit(commit_oid.clone())?, true)?;
        let branch_ref = branch.into_reference();
        let branch_ref_name = branch_ref.name().unwrap();
        repo.set_head(branch_ref_name)?;
        Ok(())
    }

    fn _commit<'a, T: Into<PathBuf>, Str: Into<&'a str>>(
        repo_dir: T,
        message: Str,
        update_branch: Str,
    ) -> Result<git2::Oid, git2::Error> {
        let repo_dir: PathBuf = repo_dir.into();
        let repo = Self::_repo(&repo_dir)?;
        let mut index = repo.index()?;
        let sign = Self::_signature(&repo)?;
        let tree = repo.find_tree(index.write_tree()?)?;

        let mut parents = vec![];
        let mut update_ref = Some("HEAD");

        if let Ok(head) = repo.head() {
            parents.push(head.peel_to_commit()?);
        } else {
            update_ref = None; // no HEAD = first commit
        }

        let oid = repo.commit(
            update_ref,
            &sign,
            &sign,
            message.into(),
            &tree,
            &parents.iter().collect::<Vec<&git2::Commit>>()[..],
        )?;

        Self::_update_branch(repo_dir, update_branch.into(), &oid)?;

        Ok(oid)
    }
    #[test]
    fn test_push() {
        let testgit = TestGit {
            dir: std::env::current_dir().unwrap().join("test/base"),
            state: String::from("Hello"),
        };
        let base_repo = testgit.git_init().unwrap();

        let testgitremote = TestGit {
            dir: std::env::current_dir().unwrap().join("test/remote"),
            state: String::from("Hello"),
        };
        <TestGit as Git>::_init::<&PathBuf>(&testgitremote.dir, true).unwrap();

        testgit
            .git_set_remote(
                "origin",
                format!("file://{}", testgitremote.dir.to_str().unwrap()).as_str(),
            )
            .unwrap();

        testgit.write_file("test.txt").unwrap();

        testgit.git_add(".").unwrap();

        testgit.git_commit("test commit", "master").unwrap();

        let master = base_repo
            .find_branch("master", git2::BranchType::Local)
            .unwrap();

        let mut remote = base_repo.find_remote("origin").unwrap();

        remote
            .push::<&str>(&[master.into_reference().name().unwrap()], None)
            .unwrap();

        let mut clonebuilder = git2::build::RepoBuilder::new();

        let clonerepo_dir = testgit.dir.parent().unwrap().join("clone");

        clonebuilder
            .clone(remote.url().unwrap(), &clonerepo_dir)
            .unwrap();

        assert!(clonerepo_dir.join("test.txt").exists());

        std::fs::remove_dir_all(&testgit.dir.parent().unwrap()).unwrap();
    }

Rust相关问答推荐

在泛型 struct 的字段声明中访问关联的Conant

如何为utoipa中的可选查询参数生成OpenAPI模式?

通过使用光标拖动角来绕其中心旋转矩形

如何导入crate-type=[";cdylib;]库?

正则表达式中的重叠匹配?(铁 rust 正则式发动机)

如何从ruust中的fig.toml中读取?

如何go 除多余的(0..)在迭代中,当它不被使用时?

如果变量本身不是None,如何返回;如果没有,则返回None&Quot;?

Windows 上 ndarray-linalg 与 mkl-stats 的链接时间错误

仅发布工作区的二进制 crate

如何处理闭包中的生命周期以及作为参数和返回类型的闭包?

Nom 解析器无法消耗无效输入

Rust:`sort_by` 多个条件,冗长的模式匹配

如何将这些测试放在一个单独的文件中?

类型判断模式匹配panic

如何在 Rust 中将 UTF-8 十六进制值转换为 char?

发生移动是因为 `data` 的类型为 `Vec`,它没有实现 `Copy` 特性

如何获取包裹在 Arc<> 和 RwLock<> 中的 Rust HashMap<> 的长度?

将数据序列化为 struct 模型,其中两个字段的数据是根据 struct 中的其他字段计算的

函数参数的 Rust 功能标志