It is possible to have two branches which are the same but for case. This works great on the case-sensitive filesystems, but not so well on case-insensitive filesystems. It is fairly typical to have case-insensitive clients (Macs, say) with a case-sensitive server (GNU/Linux). Should a user attempt to pull on a Mac when there are case-clone branches with differing contents, they'll get an error message containing something like "Ref refs/remotes/origin/lower is at [sha-of-lowercase-branch] but expected [sha-of-uppercase-branch].... (unable to update local ref)" With a case-insensitive git server, if a branch called capital-M Master (that differs from lowercase-m-master) is pushed, nobody else can push to (lowercase-m) master until the branch is removed. Create the option receive.denycaseclonebranches, which checks pushed branches to ensure that they are not case-clones of an existing branch. This setting is turned on by default if core.ignorecase is set, but not otherwise. Signed-off-by: David Turner <dturner@xxxxxxxxxxx> --- builtin/receive-pack.c | 29 ++++++++++++++++++++++++++++- t/t5400-send-pack.sh | 20 ++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index c323081..0894ded 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -27,6 +27,7 @@ enum deny_action { static int deny_deletes; static int deny_non_fast_forwards; +static int deny_case_clone_branches = -1; static enum deny_action deny_current_branch = DENY_UNCONFIGURED; static enum deny_action deny_delete_current = DENY_UNCONFIGURED; static int receive_fsck_objects = -1; @@ -69,6 +70,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb) if (status) return status; + if (strcmp(var, "receive.denycaseclonebranches") == 0) { + deny_case_clone_branches = git_config_bool(var, value); + return 0; + } + if (strcmp(var, "receive.denydeletes") == 0) { deny_deletes = git_config_bool(var, value); return 0; @@ -468,6 +474,24 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) return 0; } +static int is_case_clone(const char *refname, const unsigned char *sha1, + int flags, void *cb_data) +{ + const char* incoming_refname = cb_data; + return !strcasecmp(refname, incoming_refname) && + strcmp(refname, incoming_refname); +} + +static int ref_is_denied_case_clone(const char *name) +{ + + if (!deny_case_clone_branches) + return 0; + + return for_each_ref(is_case_clone, (void *) name); + +} + static const char *update(struct command *cmd, struct shallow_info *si) { const char *name = cmd->ref_name; @@ -478,7 +502,8 @@ static const char *update(struct command *cmd, struct shallow_info *si) struct ref_lock *lock; /* only refs/... are allowed */ - if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) { + if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0) || + ref_is_denied_case_clone(name)) { rp_error("refusing to create funny ref '%s' remotely", name); return "funny refname"; } @@ -1171,6 +1196,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) die("'%s' does not appear to be a git repository", dir); git_config(receive_pack_config, NULL); + if (deny_case_clone_branches == -1) + deny_case_clone_branches = ignore_case; if (0 <= transfer_unpack_limit) unpack_limit = transfer_unpack_limit; diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 0736bcb..099c0e3 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -129,6 +129,26 @@ test_expect_success 'denyNonFastforwards trumps --force' ' test "$victim_orig" = "$victim_head" ' +if ! test_have_prereq CASE_INSENSITIVE_FS +then +test_expect_success 'denyCaseCloneBranches works' ' + ( + cd victim && + git config receive.denyCaseCloneBranches true + git config receive.denyDeletes false + ) && + git checkout -b caseclone && + git send-pack ./victim caseclone && + git checkout -b CaseClone && + test_must_fail git send-pack ./victim CaseClone && + git checkout -b notacaseclone && + git send-pack ./victim notacaseclone && + test_must_fail git send-pack ./victim :CaseClone && + git send-pack ./victim :caseclone && + git send-pack ./victim CaseClone +' +fi + test_expect_success 'push --all excludes remote-tracking hierarchy' ' mkdir parent && ( -- 2.0.0.rc1.18.gf763c0f -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html