7

In C++ using libgit2, I'd like to create a new local repository where its master branch is based on specific-branch from another local repository, maintaining its history so I can later synch between the two.

Essentially, I'm attempting the following, except using libgit2:

https://stackoverflow.com/a/9529847/1019385

So if I had files arranged as follows:

./old.git [branches: master, specific-branch]

./old/* [files and clone of ./old.git at specific-branch]

Where the commands would be something like:

git init --bare ./new.git
cd ./old
git push ./new.git +specific-branch:master

And come up with something like (removed error checking to reduce code):

git_libgit2_init();
git_repository* repo = nullptr;
git_repository_init(&repo, "./new.git", true);
git_remote_create(&remote, repo, "origin", "./new.git");
git_remote_add_push(repo, "origin", "+specific-branch:master");
git_push_options optionsPush = GIT_PUSH_OPTIONS_INIT;
git_remote_push(remote, nullptr, &optionsPush);

What I'm not really sure is where to go from here and how to invoke git_remote_push() properly where it actually does something. This currently has no side effects, as ./old.git is not referenced. That is, ./new.git is created properly, but it doesn't contain contents of ./old.git/./old/*.

Help much appreciated.


Based on an answer suggesting a "fetch" approach, I've also attempted the following:

git_repository* repo = nullptr;
if (git_repository_init(&repo, "./new.git", true)) {
    FATAL();
}
git_remote* remote;
git_remote_create_anonymous(&remote, repo, "./old");
char* specs[] = { _strdup("specific-branch:master"), nullptr };
git_strarray refspecs;
refspecs.count = 1;
refspecs.strings = specs;
if (git_remote_download(remote, &refspecs, NULL)) {
    FATAL();
}

This still has no effect.

Community
  • 1
  • 1
Mike Weir
  • 3,094
  • 1
  • 30
  • 46
  • Show the actual debugging results you're looking at, please. mcve plus the specific point at which yo expected a result (returns/effects) and why, and what you got instead and why.. – jthill May 31 '18 at 23:20
  • @jthill expected results are shown. It's a fairly general question that could be done with any repository. I've also linked to a question with several answers that pertain to my problem, without the use of libgit2. The only difference is my desire to use libgit2 to avoid cli. – Mike Weir Jun 01 '18 at 12:51
  • You're making assertions ("This still has no effect") backed by no evidence at all, not bothering to provide a compilable test case or show your diagnostic steps, generally giving no indication of anything but throw-it-against-the-wall-and-see-if-it-sticks coding. It's no wonder your code doesn't work. Took me two hours to go from completely-new-at-libgit2 to working code, to help you, but there's no way I'm rewarding such an uncooperative, low-effort demand. If you'd bothered to do the work you'd have solved this or at least stated whatever incorrect assumption you're making here. – jthill Jun 02 '18 at 16:36
  • There are not a lot of combinations of ways those functions could be used. I just don't think those calls amount to the answer. I've included an example source that could easily be put into a test project, on any repository you have access to. – Mike Weir Jun 02 '18 at 16:39

3 Answers3

2

In straight git the most flexible and direct method (as it doesn't require you to already have the entire repository you only want pieces of) is e.g.

git init --bare new.git; cd $_

git fetch --no-tags ~/src/git next:master    # to fetch and rename a branch
# or
git fetch ~/src/git v2.17.0; git branch master FETCH_HEAD   # full handroll

To do this in libgit2, you can create a repository as usual with git_repository_init and an in-memory "anonymous" remote from a url (i'd hope a path would do as well, check that) with git_remote_create_anonymous, then git_remote_downloadgit_remote_fetch the refspec you want from that.

jthill
  • 55,082
  • 5
  • 77
  • 137
  • Wouldn't this lose its history? This seems like it would only fetch the files, and wouldn't allow any merging between repositories. – Mike Weir May 29 '18 at 20:47
  • No, fetch retrieves complete histories. Try it. – jthill May 29 '18 at 20:49
  • You seem to be right that using `git fetch` seems to maintain history. However, `git_remote_download()` has no effect, despite both `git_remote_create_anonymous()` (both using a path, and a url) and `git_remote_download()` not failing. Could you possibly show an example of usage as I might be doing this incorrectly. – Mike Weir May 30 '18 at 12:58
  • I've updated my answer with code based on your suggestion for clarity. – Mike Weir May 30 '18 at 13:11
  • Hmm, so I took a random guess and tried `git_remote_fetch()` instead of `git_remote_download()` and everything suddenly worked as expected. I am quite unfamiliar with Git - any ideas on what the difference is between the two? – Mike Weir Jun 03 '18 at 22:53
  • Yep. The download worked, it used the refspecs to identify what to fetch and the pack of new history arrived exactly as requested, but unlike fetch it didn't update any local refs to point to it. It does exactly what its docs say it'll do, my own inattentive speedread had me expecting something different too, yay for checking docs and evidence when confused (fess up like a pro, you still had that coming). Straight git doesn't really expose that operation, it's got lower-level ones for downloading over established pipes, and a no-mapping fetch always updates `FETCH_HEAD`. – jthill Jun 04 '18 at 02:50
0

An alternative (IMHO, the way of the API), since what you're after is a "standard" clone step using a custom upstream branch, would be to use git_clone's provided customization points (instead of using all the smaller parts/steps).

So something like this should work (C pseudocode):

int main() {
    git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
    git_repository *repo;

    opts.bare = 1;
    opts.remote_create_cb = (git_remote_create_cb)git_remote_create_with_fetchspec;
    opts.remote_create_payload = "specific-branch:master";

    return git_clone(&repo, "./old", "./new", &opts);
}
tiennou
  • 487
  • 3
  • 13
-1

It looks like you're creating a new repository, and then adding a remote on it and trying to use it to push to itself... If you want to truly emulate your commands, you'll need two repositories:

  1. git_repository_init the new.git, then
  2. git_repository_open the old, and then set up the remote on it, and push it to the new repository.

Something along the lines of:

git_repository *old = NULL, *new = NULL;

git_libgit2_init();
git_repository_init(&new, "./new.git", true);
git_repository_free(new);

git_repository_open(&old, "./old");
git_remote_create(&remote, old, "origin", "./new.git");
git_remote_add_push(old, "origin", "+specific-branch:master");
git_remote_push(remote, NULL, NULL);
git_repository_free(old);
Edward Thomson
  • 74,857
  • 14
  • 158
  • 187
  • 1
    Would you have some guidance on how to do that? I understand the need for two instances and have no issue with it. – Mike Weir May 22 '18 at 11:43
  • I added some code to illustrate my thinking, but it's totally untested thus far. – Edward Thomson May 22 '18 at 11:48
  • `git_remote_create()` fails with `GIT_EEXISTS`. When I just made up a different name instead of "origin", and also used it in the second parameter of `git_remote_add_push()`, it seemed to have no end effect still (no error codes though). – Mike Weir May 22 '18 at 18:14