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 <dtur...@twitter.com>
---
 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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to