-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Remote creation API #4667
Conversation
😍 thank you for taking a stub at it! |
There was a problem hiding this 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); |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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.
include/git2/remote.h
Outdated
typedef struct git_remote_create_options { | ||
unsigned int version; | ||
|
||
/** The repository that should own the remote. |
There was a problem hiding this comment.
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.
include/git2/remote.h
Outdated
* @param opts the remote creation options | ||
* @return 0 on success, WIP | ||
*/ | ||
GIT_EXTERN(int) git_remote_create_ext( |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
include/git2/remote.h
Outdated
@@ -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; |
There was a problem hiding this comment.
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 || |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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.
9206aae
to
64a153c
Compare
Rebased, with all review comments taken care of apart from the change of behavior here. |
64a153c
to
9cdd275
Compare
tests/network/remote/remotes.c
Outdated
@@ -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")); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
include/git2/remote.h
Outdated
* Remote creation options flags | ||
*/ | ||
typedef enum { | ||
GIT_REMOTE_CREATE_DEFAULT = 0, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
9cdd275
to
5ee8b5d
Compare
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 :
Left to do/open questions :
|
21238e7
to
c784d47
Compare
There was a problem hiding this 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).
tests/remote/create.c
Outdated
|
||
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)); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 || |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
0484a27
to
66bcc02
Compare
There was a problem hiding this 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; |
There was a problem hiding this comment.
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 || |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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…
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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))
66bcc02
to
eb72511
Compare
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); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
Just a thought : per https://stackoverflow.com/a/51570151/444323, if I kept the new function's prototype compatible with clone's 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); |
70f392d
to
a0f91ab
Compare
@pks-t If you don't mind (and your workqueue is not saturated), I'd like to have this one reviewed 😉. |
There was a problem hiding this 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.
goto on_error; | ||
|
||
fetch = git_buf_cstr(&specbuf); | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
…
There was a problem hiding this comment.
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
31fcfc8
to
b61a7bf
Compare
/rebuild |
Okay, @tiennou, I started to rebuild this pull request. |
b61a7bf
to
1d990c5
Compare
Okay, something reaally weird is going on after rebasing to |
We need a repo/config and a name to be able to do anything to the configuration. As such, those two tests can be merged so their conditions are shared.
This supersedes the functionality of remote_create_with_fetchspec, remote_create_anonymous and remote_create_detached.
As it is, this is space for 32 refs pointers, which feels a little much. Lower it to 8, as it is the minimum vector size anyway.
68f0ce5
to
666c7bd
Compare
Thanks a lot @tiennou for this awesome work! |
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.