Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote creation API #4667

Merged
merged 18 commits into from
Nov 13, 2018
Merged

Remote creation API #4667

merged 18 commits into from
Nov 13, 2018

Conversation

tiennou
Copy link
Contributor

@tiennou tiennou commented Jun 1, 2018

This is the PR for the code I've written for #4658.

It adds a git_remote_create_ext with an option structure for handling the 3 other _create calls (ie. _with_fetchspec, _anonymous, _detached).

I've not pondered too much about the difference between anonymous & detached remotes. AFAICT, both are in-memory, and anonymous ones can make use of an underlying repository, so config will be respected.

@vors
Copy link

vors commented Jun 1, 2018

😍 thank you for taking a stub at it!

Copy link
Member

@pks-t pks-t left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this PR is great. It fixes our remote creation API and consolidates all available functions into one extensible one. Thanks for working on it!

src/remote.c Outdated

giterr_set(
GITERR_CONFIG,
"remote '%s' already exists", name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation here is a bit funny.

#define GIT_REMOTE_CREATE_OPTIONS_VERSION 1
#define GIT_REMOTE_CREATE_OPTIONS_INIT {GIT_REMOTE_CREATE_OPTIONS_VERSION}

GIT_EXTERN(int) git_remote_create_init_options(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is missing its documentation.

typedef struct git_remote_create_options {
unsigned int version;

/** The repository that should own the remote.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our struct member doc comment style is to either have a single /** foobar */ line or start with an empty /** line.

* @param opts the remote creation options
* @return 0 on success, WIP
*/
GIT_EXTERN(int) git_remote_create_ext(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bikeshedding: create_ext doesn't make it clear what the "extended" nature of this function is. Instead, I'd vote for git_remote_create_with_opts, which clearly states that it takes an opts structure.

*
* @param out the resulting remote
* @param url the remote's url
* @param opts the remote creation options
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should mention that opts is optional and what will happen in that case. (It is optional, isn't it? If not, I'd propose to just create an anonymous remote in that case)

src/remote.c Outdated
if (repo && (error = lookup_remote_prune_config(remote, config_ro, name)) < 0)
if (!is_anonymous &&
((error = write_add_refspec(opts->repository, opts->name, opts->fetchspec, true)) < 0 ||
(error = lookup_remote_prune_config(remote, config_ro, opts->name)) < 0))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, this is different from what we did before. Now we only call lookup_remote_prune_config if both opts->repository and opts->name are set. Previously, we were also calling it in case opts->name is unset. Is this intentional and, if so, why can we avoid calling lookup_remote_prune_config now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the tests around detached/anonymous/realized remotes are confusing here. My is_anonymous variable should be named is_not_realized maybe (bikeshedding welcome on the name).

The original code would attempt to load a remote..prune config option, which IMHO doesn't make sense (and I remember a test broke before I changed it to this).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. Should I keep the test as is ?

  1) Failure:
network::remote::remotes::cannot_add_a_nameless_remote [/Users/tiennou/Projects/libgit2/tests/network/remote/remotes.c:321]
  GIT_EINVALIDSPEC != git_remote_create(&remote, _repo, NULL, "git://github.com/libgit2/libgit2")
  -12 != 0

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, the original code doesn't make a lot of sense. So this change looks correct to me

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that most of what I said above about the broken test is wrong. The test failure was caused when I removed the name & repo existence checks from git_remote_create (since it allows NULL).

AFAICT, the original code is extra-confusing about what actually needs a repo/name because it indirectly uses the config, so I hope this is better. I'm still dubious of the whole config_ro/config_rw dance that's happening, because we start by doing a snapshot, then we re-grab a "live" config. I might be just paranoid/unaware of the semantics of config__weakptr, but I'm tempted to change it to : grab a "live" version IF we have a repo, snapshot it so it doesn't change when applying url.insteadof, and set the various other settings on the first live one.

src/remote.c Outdated
@@ -271,7 +292,7 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n
}

/* A remote without a name doesn't download tags */
if (!name)
if (is_anonymous)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also different from what we did before, as now an unnamed remote with an associated repository will not fetch tags anymore. Is this intended?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I went too far trying to make sense of the 3 types of remotes. Reverting.

return 0;
opts.repository = repo;
opts.name = name;
opts.fetchspec = fetch;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conversion is great

src/remote.c Outdated
int error;
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;

if ((error = ensure_remote_name_is_valid(name)) < 0)
return error;

if ((error = ensure_remote_doesnot_exist(repo, name)) < 0)
return error;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two calls are already done in create_remote_internal now. I'd like to avoid this duplication

@@ -63,6 +73,9 @@ typedef struct git_remote_create_options {

/** The fetchspec the remote should use. */
const char *fetchspec;

/** Additional flags for the remote. See git_remote_create_flags. */
int flags;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be unsigned, shouldn't it?

src/remote.c Outdated
return error;

remote = git__calloc(1, sizeof(git_remote));
GITERR_CHECK_ALLOC(remote);

remote->repo = repo;
remote->repo = opts->repository;

if ((error = git_vector_init(&remote->refs, 32, NULL)) < 0 ||
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a heads up, I was pretty surprised about that 32 here. Should I lower that to something like 4 ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think 4 is really rather low, and you said that 8 is the minimum size for vectors anyway. So we could lower it to 8, but I'm rather indifferent.

@tiennou
Copy link
Contributor Author

tiennou commented Jun 11, 2018

Rebased, with all review comments taken care of apart from the change of behavior here.

@@ -318,7 +318,7 @@ void test_network_remote_remotes__cannot_add_a_nameless_remote(void)

cl_assert_equal_i(
GIT_EINVALIDSPEC,
git_remote_create(&remote, _repo, NULL, "git://github.com/libgit2/libgit2"));
git_remote_create(&remote, _repo, "", "git://github.com/libgit2/libgit2"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the test you've been talking about, right? I'd say we should just keep this test as it was originally, just to be sure we're not altering behaviour.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm not fan of changing that test, but keeping it that way makes the implementation waaay harder to follow, as I now have to accept a NULL argument here instead of assert()-ing, which means I need the generic call to differentiate a git_remote_create call with an unexpected NULL or a git_remote_create_detached/anonymous().

Your call, I just chose the easy way first.

* Remote creation options flags
*/
typedef enum {
GIT_REMOTE_CREATE_DEFAULT = 0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this flag necessary? We don't have default flags for all the other option enums, do we?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@tiennou
Copy link
Contributor Author

tiennou commented Jun 17, 2018

So, major rework of this. I've taken the time to strengthen the test suite because I kept discovering "edge-cases" (like with_fetchspec not building a default fetchspec), so all 3 basic calls are now more cautiously tested (for invalid parameters, config changes, and whatever differing behavior I stumbled upon).

New from last time :

  • I've added a flag to prevent building a default fetchspec. I've also re-duplicated code either for backward-compatibility reasons (git_remote_create actually shadows its error codes) or simplicity in create_internal.
  • I've switched the config-management code to use transactions (so the setup/teardown cycle is simpler).

Left to do/open questions :

  • check that we set error messages correctly. I noticed a few <no message> failures.
  • maybe have the const char *fetchspec option be a size_t count, const char fetchspecs[] ? I didn't notice at first, but using git_remote_add_fetch will need a relookup so the refspecs are "in the correct place" (see dwim_refspecs at the end of create_internal).

@tiennou tiennou force-pushed the feature/remote-create-api branch 2 times, most recently from 21238e7 to c784d47 Compare June 20, 2018 00:27
Copy link
Member

@pks-t pks-t left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just skipped quickly through the reworked commits, as they still seem to be WIP (at least CI is segfaulting on all platforms).


void test_remote_create__named_fail_on_invalid_name(void)
{
cl_git_assert_cannot_create_remote(GIT_EINVALIDSPEC, git_remote_create(&r, _repo, NULL, TEST_URL));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd actually prefer the same as in the other PR. Instead of using a macro and repeating that multiple times, you should just use an array of strings here and iterate through them. That'd be a lot simpler to read and produce less code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@@ -232,7 +232,7 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n

remote->repo = repo;

if ((error = git_vector_init(&remote->refs, 32, NULL)) < 0 ||
if ((error = git_vector_init(&remote->refs, 8, NULL)) < 0 ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd really like to see some reasoning in the commit message as to why this is a good thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memory bloat ? That's enough memory for 32 different refspecs. I expect this is far larger than any "normal" usage of a remote. 8 is selected because it's the minimum allocation size of git_vector.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The minimum allocation size is the interesting information I've been looking for :) I wasn't really wondering why the count was removed, but rather why 8 was picked. Thanks for clarifying

src/remote.c Outdated
*out = remote;
error = 0;

on_error:
if (error)
git_remote_free(remote);

git_config_free(config_ro);
git_config_free(config);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have to unlock the config on error, too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix, I was misusing the transaction config API (hence the CI failure). The transaction handles unlocking when it's freed.

@tiennou tiennou force-pushed the feature/remote-create-api branch 3 times, most recently from 0484a27 to 66bcc02 Compare June 24, 2018 16:39
Copy link
Member

@pks-t pks-t left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And another feedback round. Comments are getting less and more focussed on style, so this might be the last round of comments from me. Thanks for your work!

src/remote.c Outdated
if (repo) {
if ((error = git_repository_config__weakptr(&config, repo)) < 0 ||
(error = git_config_lock(&txn, config)) < 0)
goto on_error;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This indentation is really confusing. The goto on_error doesn't belong to the repo conditional, but to the other one. When fixing you should also indent the git_config_lock by four spaces instead of a tab :)

src/remote.c Outdated
return error;

remote = git__calloc(1, sizeof(git_remote));
GITERR_CHECK_ALLOC(remote);

remote->repo = repo;
remote->repo = opts->repository;

if ((error = git_vector_init(&remote->refs, 32, NULL)) < 0 ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think 4 is really rather low, and you said that 8 is the minimum size for vectors anyway. So we could lower it to 8, but I'm rather indifferent.

return error;

if (canonicalize_url(&buf, url) < 0)
return GIT_ERROR;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of backwards compatibility do you refer to? Do callers expect a specific error code here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found out while writing this test that this was "misbehaving", in that it never returns what create_internal says when it fails. It depends on what kind of backward compatibility we want (I realize I may be too zealous, but maybe this should be discussed separately)…

The ensure_remote_name_is_valid is duplicated in case name is NULL (a NULL name in the with_opts version would cause an anonymous remote to be created instead, ie. it's not a failure anymore). An alternative would be to assert on the mandatory parameters to be correct…

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I guess we should fix this, but that can be done in a later PR if you wish.

src/remote.c Outdated
@@ -263,7 +263,7 @@ static int create_internal(git_remote **out, const char *url, const git_remote_c
(error = canonicalize_url(&canonical_url, url)) < 0)
goto on_error;

if (config) {
if (config && (opts->flags & GIT_REMOTE_CREATE_SKIP_INSTEADOF) != GIT_REMOTE_CREATE_SKIP_INSTEADOF) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (config && !(opts->flags & GIT_REMOTE_CREATE_SKIP_INSTEADOF))

src/remote.c Outdated
if (opts->fetchspec != NULL) {
if ((error = add_refspec(remote, opts->fetchspec, true)) < 0)
if (opts->fetchspec != NULL ||
(opts->name && (opts->flags & GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC) != GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(opts->name && !(opts->flags & GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC))

@tiennou tiennou force-pushed the feature/remote-create-api branch from 66bcc02 to eb72511 Compare July 6, 2018 19:40
@tiennou
Copy link
Contributor Author

tiennou commented Jul 6, 2018

Should be good now. I've lowered the vector size again, because (if my sizeof math is correct), we have 32 * sizeof(void *) => 256bytes on 64bits, which makes 1Mb lost to mostly empty vectors for every 4 remote you create.

Thanks @pks-t for the reviews and ever helpful comments.

src/remote.c Outdated
git_buf_dispose(&var);

if (txn)
git_transaction_commit(txn);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to not have catched this earlier, but you need to do error checking here. Otherwise we might end up silently not writing the config changes.

return error;

if (canonicalize_url(&buf, url) < 0)
return GIT_ERROR;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I guess we should fix this, but that can be done in a later PR if you wish.

@tiennou
Copy link
Contributor Author

tiennou commented Jul 28, 2018

Just a thought : per https://stackoverflow.com/a/51570151/444323, if I kept the new function's prototype compatible with clone's git_remote_create_cb, that would make a nice one-shot customization function, since git_remote_create_opts could be passed as payload.

EDIT To elaborate on that, that would make the function's prototype & options look like this :

typedef enum {
    GIT_REMOTE_CREATE_ANONYMOUS,
    GIT_REMOTE_CREATE_DETACHED,
    GIT_REMOTE_CREATE_NO_DEFAULT_FETCHSPEC,
    GIT_REMOTE_CREATE_SKIP_INSTEAD_OF,
} git_remote_create_flags;

typedef struct {
    int version;
    const char *fetchspec;
    int flags;
} git_remote_create_opts;

/**
 * repo mandatory unless CREATE_DETACHED
 * name mandatory unless CREATE_ANONYMOUS
 * no opts means mandatory
 */
int git_remote_create_with_opts(git_remote **remote,
    git_repository *repo,
    const char *name,
    const char *url,
    const git_remote_create_opts *opts);

@tiennou
Copy link
Contributor Author

tiennou commented Sep 11, 2018

@pks-t If you don't mind (and your workqueue is not saturated), I'd like to have this one reviewed 😉.

Copy link
Member

@pks-t pks-t left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I've finally found some time to do another review. Everything looks alright to me, except for one potential memory leak. Please take the other three comments as nice to have -- feel free to fix them if you want, but they won't keep this from getting merged.

include/git2/remote.h Outdated Show resolved Hide resolved
goto on_error;

fetch = git_buf_cstr(&specbuf);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this leak memory? In case where we create the fetchspec ourselves, we need to allocate memory, in the other case we just us the pointer from opts->fetchspec witouth allocating memory. So in the former case we'd have to also free it, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't expect it does : the on_error "handler" unconditionally frees the buffer, so it doesn't matter if it's a self-built fetchspec or not. Arguably, I could rename that to cleanup

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah. That was my mistake, I somehow expected a git_buf_detach here. git_buf_cstr doesn't transfer memory ownership, so this is fine

src/remote.c Outdated Show resolved Hide resolved
tests/remote/create.c Outdated Show resolved Hide resolved
@tiennou tiennou force-pushed the feature/remote-create-api branch 2 times, most recently from 31fcfc8 to b61a7bf Compare October 8, 2018 19:15
@tiennou
Copy link
Contributor Author

tiennou commented Oct 8, 2018

/rebuild

@libgit2-azure-pipelines
Copy link

Okay, @tiennou, I started to rebuild this pull request.

@tiennou
Copy link
Contributor Author

tiennou commented Oct 8, 2018

Okay, something reaally weird is going on after rebasing to master… Just to be sure, I'll submit the rebased version as another "dummy" PR, and I'm restoring this one to the pre-rebase version (though the review comments have been taken care of) because I'd like to make sure it's still green (IIRC it was, but 🤷‍♂️ ). Please don't merge this while investigations are in progress…

src/remote.c Outdated Show resolved Hide resolved
@pks-t pks-t merged commit 20cb30b into libgit2:master Nov 13, 2018
@pks-t
Copy link
Member

pks-t commented Nov 13, 2018

Thanks a lot @tiennou for this awesome work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants