Re: [PATCH v8 42/44] refs.c: pass a skip list to name_conflict_fn
On Thu, May 22, 2014 at 12:27 PM, Jonathan Nieder jrnie...@gmail.com wrote: Ronnie Sahlberg wrote: --- a/refs.c +++ b/refs.c @@ -798,11 +798,19 @@ struct name_conflict_cb { const char *refname; const char *oldrefname; const char *conflicting_refname; + const char **skip; + int skipnum; Would a struct string_list make sense here? (See Documentation/technical/api-string-list.txt.) It would. But it is better to do that change later. Currently we have both repack_without_ref and repack_without_refs that take the same char ** argument. After next series we will have removed one of these functions and have an easier API to modify (once we start tracking the skiplist as part of the transaction instead). Instead of doing this change and then redoing it once we move a lot of the actual work from _commit to _update I will do this change towards the end of the next series. [...] }; static int name_conflict_fn(struct ref_entry *entry, void *cb_data) { struct name_conflict_cb *data = (struct name_conflict_cb *)cb_data; + int i; + for(i = 0; i data-skipnum; i++) { Fixed. (style nit) missing space after 'for'. + if (!strcmp(entry-name, data-skip[i])) { + return 0; + } Style: git tends to avoid braces around a single-line if/for/etc body. Fixed. [...] @@ -817,15 +825,21 @@ static int name_conflict_fn(struct ref_entry *entry, void *cb_data) * conflicting with the name of an existing reference in dir. If * oldrefname is non-NULL, ignore potential conflicts with oldrefname * (e.g., because oldrefname is scheduled for deletion in the same - * operation). + * operation). skip contains a list of refs we want to skip checking for + * conflicts with. Refs may be skipped due to us knowing that it will + * be deleted later during a transaction that deletes one reference and then + * creates a new conflicting reference. For example a rename from m to m/m. This example of Refs may be skipped due to seems overly complicated. Isn't the idea just that skip contains a list of refs scheduled for deletion in this transaction, since they shouldn't be treated as conflicts at all (for example when renamining m to m/m)? I wonder if there's some way to make use of the result of the naive refname_available check to decide what to do when creating a ref. E.g.: if a refname would be available except there's a ref being deleted in the way, we could do one of the following: a. delete all relevant loose refs and perform the transaction in packed-refs, or b. order operations to avoid the D/F conflict, even with loose refs (the hardest case is if the ref being deleted uses a directory and we want to create a file with the same name. But that's still doable if we're willing to rmdir when needed as part of the loop to commit changes) The packed-refs trick (a) seems much simpler, but either should work. This could be done e.g. by checking is_refname_available with an empty list first before doing the real thing with a list of exclusions. [...] @@ -2592,6 +2609,9 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms int log = !lstat(git_path(logs/%s, oldrefname), loginfo); const char *symref = NULL; + if (!strcmp(oldrefname, newrefname)) + return 0; What is the intended result if I try to rename a nonexistent ref or an existent symref to its own name? Yeah, I should get rid of that. I have renoved the rename_ref patch and moved it to the next series. I think we can solve this easier/better once we have the other patches in first. Sorry to be so fussy about this part. It's not that I think that this change is trying to do something bad --- in fact, it's more the opposite, that I'm excited to see git learning to have a better understanding and handling of refname D/F conflicts. That would allow: * git fetch --prune working as a single transaction even if the repository being fetched from removed a refs/heads/topic branch and created refs/heads/topic/1 and refs/heads/topic/2 * git fast-import and git fetch --mirror learning the same trick * fewer code paths having to be touched to be able to (optionally) let git actually tolerate D/F conflicts, for people who want to have 'topic', 'topic/1', and 'topic/2' branches at the same time. This could be turned on by default for remote-tracking refs. It would be especially nice for people on Windows and Mac OS where there can be D/F conflicts that people on Linux didn't notice due to case-sensitivity. Longer term, through a configuration that starts turned off by default and has the default flipped as more people have upgraded git, this could make D/F conflicts in refnames stop being an error altogether. So it's kind of exciting to see, even though it's fussy to get it right.
Re: [PATCH v8 42/44] refs.c: pass a skip list to name_conflict_fn
Ronnie Sahlberg wrote: --- a/refs.c +++ b/refs.c @@ -798,11 +798,19 @@ struct name_conflict_cb { const char *refname; const char *oldrefname; const char *conflicting_refname; + const char **skip; + int skipnum; Would a struct string_list make sense here? (See Documentation/technical/api-string-list.txt.) [...] }; static int name_conflict_fn(struct ref_entry *entry, void *cb_data) { struct name_conflict_cb *data = (struct name_conflict_cb *)cb_data; + int i; + for(i = 0; i data-skipnum; i++) { (style nit) missing space after 'for'. + if (!strcmp(entry-name, data-skip[i])) { + return 0; + } Style: git tends to avoid braces around a single-line if/for/etc body. [...] @@ -817,15 +825,21 @@ static int name_conflict_fn(struct ref_entry *entry, void *cb_data) * conflicting with the name of an existing reference in dir. If * oldrefname is non-NULL, ignore potential conflicts with oldrefname * (e.g., because oldrefname is scheduled for deletion in the same - * operation). + * operation). skip contains a list of refs we want to skip checking for + * conflicts with. Refs may be skipped due to us knowing that it will + * be deleted later during a transaction that deletes one reference and then + * creates a new conflicting reference. For example a rename from m to m/m. This example of Refs may be skipped due to seems overly complicated. Isn't the idea just that skip contains a list of refs scheduled for deletion in this transaction, since they shouldn't be treated as conflicts at all (for example when renamining m to m/m)? I wonder if there's some way to make use of the result of the naive refname_available check to decide what to do when creating a ref. E.g.: if a refname would be available except there's a ref being deleted in the way, we could do one of the following: a. delete all relevant loose refs and perform the transaction in packed-refs, or b. order operations to avoid the D/F conflict, even with loose refs (the hardest case is if the ref being deleted uses a directory and we want to create a file with the same name. But that's still doable if we're willing to rmdir when needed as part of the loop to commit changes) The packed-refs trick (a) seems much simpler, but either should work. This could be done e.g. by checking is_refname_available with an empty list first before doing the real thing with a list of exclusions. [...] @@ -2592,6 +2609,9 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms int log = !lstat(git_path(logs/%s, oldrefname), loginfo); const char *symref = NULL; + if (!strcmp(oldrefname, newrefname)) + return 0; What is the intended result if I try to rename a nonexistent ref or an existent symref to its own name? Sorry to be so fussy about this part. It's not that I think that this change is trying to do something bad --- in fact, it's more the opposite, that I'm excited to see git learning to have a better understanding and handling of refname D/F conflicts. That would allow: * git fetch --prune working as a single transaction even if the repository being fetched from removed a refs/heads/topic branch and created refs/heads/topic/1 and refs/heads/topic/2 * git fast-import and git fetch --mirror learning the same trick * fewer code paths having to be touched to be able to (optionally) let git actually tolerate D/F conflicts, for people who want to have 'topic', 'topic/1', and 'topic/2' branches at the same time. This could be turned on by default for remote-tracking refs. It would be especially nice for people on Windows and Mac OS where there can be D/F conflicts that people on Linux didn't notice due to case-sensitivity. Longer term, through a configuration that starts turned off by default and has the default flipped as more people have upgraded git, this could make D/F conflicts in refnames stop being an error altogether. So it's kind of exciting to see, even though it's fussy to get it right. Thanks, Jonathan -- To unsubscribe from this list: send the line unsubscribe git in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v8 42/44] refs.c: pass a skip list to name_conflict_fn
Allow passing a list of refs to skip checking to name_conflict_fn. There are some conditions where we want to allow a temporary conflict and skip checking those refs. For example if we have a transaction that 1, guarantees that m is a packed refs and there is no loose ref for m 2, the transaction will delete m from the packed ref 3, the transaction will create conflicting m/m For this case we want to be able to lock and create m/m since we know that the conflict is only transient. I.e. the conflict will be automatically resolved by the transaction when it deletes m. Signed-off-by: Ronnie Sahlberg sahlb...@google.com --- refs.c | 43 +-- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/refs.c b/refs.c index f458ff9..c940509 100644 --- a/refs.c +++ b/refs.c @@ -798,11 +798,19 @@ struct name_conflict_cb { const char *refname; const char *oldrefname; const char *conflicting_refname; + const char **skip; + int skipnum; }; static int name_conflict_fn(struct ref_entry *entry, void *cb_data) { struct name_conflict_cb *data = (struct name_conflict_cb *)cb_data; + int i; + for(i = 0; i data-skipnum; i++) { + if (!strcmp(entry-name, data-skip[i])) { + return 0; + } + } if (data-oldrefname !strcmp(data-oldrefname, entry-name)) return 0; if (names_conflict(data-refname, entry-name)) { @@ -817,15 +825,21 @@ static int name_conflict_fn(struct ref_entry *entry, void *cb_data) * conflicting with the name of an existing reference in dir. If * oldrefname is non-NULL, ignore potential conflicts with oldrefname * (e.g., because oldrefname is scheduled for deletion in the same - * operation). + * operation). skip contains a list of refs we want to skip checking for + * conflicts with. Refs may be skipped due to us knowing that it will + * be deleted later during a transaction that deletes one reference and then + * creates a new conflicting reference. For example a rename from m to m/m. */ static int is_refname_available(const char *refname, const char *oldrefname, - struct ref_dir *dir) + struct ref_dir *dir, + const char **skip, int skipnum) { struct name_conflict_cb data; data.refname = refname; data.oldrefname = oldrefname; data.conflicting_refname = NULL; + data.skip = skip; + data.skipnum = skipnum; sort_ref_dir(dir); if (do_for_each_entry_in_dir(dir, 0, name_conflict_fn, data)) { @@ -2037,7 +2051,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) static struct ref_lock *lock_ref_sha1_basic(const char *refname, const unsigned char *old_sha1, - int flags, int *type_p) + int flags, int *type_p, + const char **skip, int skipnum) { char *ref_file; const char *orig_refname = refname; @@ -2084,7 +2099,9 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, * name is a proper prefix of our refname. */ if (missing -!is_refname_available(refname, NULL, get_packed_refs(ref_cache))) { +!is_refname_available(refname, NULL, + get_packed_refs(ref_cache), + skip, skipnum)) { last_errno = ENOTDIR; goto error_return; } @@ -2142,7 +2159,7 @@ struct ref_lock *lock_any_ref_for_update(const char *refname, const unsigned char *old_sha1, int flags, int *type_p) { - return lock_ref_sha1_basic(refname, old_sha1, flags, type_p); + return lock_ref_sha1_basic(refname, old_sha1, flags, type_p, NULL, 0); } /* @@ -2592,6 +2609,9 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms int log = !lstat(git_path(logs/%s, oldrefname), loginfo); const char *symref = NULL; + if (!strcmp(oldrefname, newrefname)) + return 0; + if (log S_ISLNK(loginfo.st_mode)) return error(reflog for %s is a symlink, oldrefname); @@ -2602,10 +2622,12 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms if (!symref) return error(refname %s not found, oldrefname); - if (!is_refname_available(newrefname, oldrefname, get_packed_refs(ref_cache))) + if (!is_refname_available(newrefname, oldrefname, + get_packed_refs(ref_cache), NULL, 0)) return 1; - if (!is_refname_available(newrefname, oldrefname,