Merge branch 'fc/remote-helper-refmap'
authorJunio C Hamano <gitster@pobox.com>
Mon, 16 Jun 2014 19:18:15 +0000 (12:18 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 16 Jun 2014 19:18:15 +0000 (12:18 -0700)
Allow remote-helper/fast-import based transport to rename the refs
while transferring the history.

* fc/remote-helper-refmap:
transport-helper: remove unnecessary strbuf resets
transport-helper: add support to delete branches
fast-export: add support to delete refs
fast-import: add support to delete refs
transport-helper: add support to push symbolic refs
transport-helper: add support for old:new refspec
fast-export: add new --refspec option
fast-export: improve argument parsing

Documentation/git-fast-export.txt
Documentation/git-fast-import.txt
builtin/fast-export.c
fast-import.c
t/t5801-remote-helpers.sh
t/t9300-fast-import.sh
t/t9350-fast-export.sh
transport-helper.c
index 85f1f30fdf5c0a741c3c575bfa393fc7d88d308f..221506b04ba55fac2151d8ac95da7df57eb9d411 100644 (file)
@@ -105,6 +105,10 @@ marks the same across runs.
        in the commit (as opposed to just listing the files which are
        different from the commit's first parent).
 
+--refspec::
+       Apply the specified refspec to each ref exported. Multiple of them can
+       be specified.
+
 [<git-rev-list-args>...]::
        A list of arguments, acceptable to 'git rev-parse' and
        'git rev-list', that specifies the specific objects and references
index 3ffa2fa2c8b38d2657b7ef21dfabbc2d816ade18..377eeaa36db2407f76a9cd21777bb2eda4ff74bc 100644 (file)
@@ -483,6 +483,9 @@ Marks must be declared (via `mark`) before they can be used.
 * Any valid Git SHA-1 expression that resolves to a commit.  See
   ``SPECIFYING REVISIONS'' in linkgit:gitrevisions[7] for details.
 
+* The special null SHA-1 (40 zeros) specifies that the branch is to be
+  removed.
+
 The special case of restarting an incremental import from the
 current branch value should be written as:
 ----
index b8d8a3aaf9ee8294be9a78956a70b476d327ee05..ef4481615fd2f4d60cd3cd6a4bbcceaca93c92d5 100644 (file)
@@ -17,6 +17,7 @@
 #include "utf8.h"
 #include "parse-options.h"
 #include "quote.h"
+#include "remote.h"
 
 static const char *fast_export_usage[] = {
        N_("git fast-export [rev-list-opts]"),
@@ -31,6 +32,8 @@ static int use_done_feature;
 static int no_data;
 static int full_tree;
 static struct string_list extra_refs = STRING_LIST_INIT_NODUP;
+static struct refspec *refspecs;
+static int refspecs_nr;
 
 static int parse_opt_signed_tag_mode(const struct option *opt,
                                     const char *arg, int unset)
@@ -525,6 +528,15 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)
                if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1)
                        continue;
 
+               if (refspecs) {
+                       char *private;
+                       private = apply_refspecs(refspecs, refspecs_nr, full_name);
+                       if (private) {
+                               free(full_name);
+                               full_name = private;
+                       }
+               }
+
                commit = get_commit(e, full_name);
                if (!commit) {
                        warning("%s: Unexpected object of type %s, skipping.",
@@ -661,6 +673,19 @@ static void import_marks(char *input_file)
        fclose(f);
 }
 
+static void handle_deletes(void)
+{
+       int i;
+       for (i = 0; i < refspecs_nr; i++) {
+               struct refspec *refspec = &refspecs[i];
+               if (*refspec->src)
+                       continue;
+
+               printf("reset %s\nfrom %s\n\n",
+                               refspec->dst, sha1_to_hex(null_sha1));
+       }
+}
+
 int cmd_fast_export(int argc, const char **argv, const char *prefix)
 {
        struct rev_info revs;
@@ -668,6 +693,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
        struct commit *commit;
        char *export_filename = NULL, *import_filename = NULL;
        uint32_t lastimportid;
+       struct string_list refspecs_list = STRING_LIST_INIT_NODUP;
        struct option options[] = {
                OPT_INTEGER(0, "progress", &progress,
                            N_("show progress after <n> objects")),
@@ -688,6 +714,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
                OPT_BOOL(0, "use-done-feature", &use_done_feature,
                             N_("Use the done feature to terminate the stream")),
                OPT_BOOL(0, "no-data", &no_data, N_("Skip output of blob data")),
+               OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"),
+                            N_("Apply refspec to exported refs")),
                OPT_END()
        };
 
@@ -701,11 +729,27 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
        revs.topo_order = 1;
        revs.show_source = 1;
        revs.rewrite_parents = 1;
+       argc = parse_options(argc, argv, prefix, options, fast_export_usage,
+                       PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN);
        argc = setup_revisions(argc, argv, &revs, NULL);
-       argc = parse_options(argc, argv, prefix, options, fast_export_usage, 0);
        if (argc > 1)
                usage_with_options (fast_export_usage, options);
 
+       if (refspecs_list.nr) {
+               const char **refspecs_str;
+               int i;
+
+               refspecs_str = xmalloc(sizeof(*refspecs_str) * refspecs_list.nr);
+               for (i = 0; i < refspecs_list.nr; i++)
+                       refspecs_str[i] = refspecs_list.items[i].string;
+
+               refspecs_nr = refspecs_list.nr;
+               refspecs = parse_fetch_refspec(refspecs_nr, refspecs_str);
+
+               string_list_clear(&refspecs_list, 1);
+               free(refspecs_str);
+       }
+
        if (use_done_feature)
                printf("feature done\n");
 
@@ -733,6 +777,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
        }
 
        handle_tags_and_duplicates();
+       handle_deletes();
 
        if (export_filename && lastimportid != last_idnum)
                export_marks(export_filename);
@@ -740,5 +785,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
        if (use_done_feature)
                printf("done\n");
 
+       free_refspec(refspecs_nr, refspecs);
+
        return 0;
 }
index fb4738d373a5a1048c2ff388a729e6088515ca9b..6707a66471f2038a0c826c840a201a7362de95ed 100644 (file)
@@ -248,6 +248,7 @@ struct branch {
        uintmax_t last_commit;
        uintmax_t num_notes;
        unsigned active : 1;
+       unsigned delete : 1;
        unsigned pack_id : PACK_ID_BITS;
        unsigned char sha1[20];
 };
@@ -1681,10 +1682,13 @@ static int update_branch(struct branch *b)
        struct ref_lock *lock;
        unsigned char old_sha1[20];
 
-       if (is_null_sha1(b->sha1))
-               return 0;
        if (read_ref(b->name, old_sha1))
                hashclr(old_sha1);
+       if (is_null_sha1(b->sha1)) {
+               if (b->delete)
+                       delete_ref(b->name, old_sha1, 0);
+               return 0;
+       }
        lock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL);
        if (!lock)
                return error("Unable to lock %s", b->name);
@@ -2611,8 +2615,11 @@ static int parse_from(struct branch *b)
                        free(buf);
                } else
                        parse_from_existing(b);
-       } else if (!get_sha1(from, b->sha1))
+       } else if (!get_sha1(from, b->sha1)) {
                parse_from_existing(b);
+               if (is_null_sha1(b->sha1))
+                       b->delete = 1;
+       }
        else
                die("Invalid ref name or SHA1 expression: %s", from);
 
index a00a660763451d57b2a9537ec1b99fcfd0c9a831..24194075468819881826c24c08b1aa97bacb9897 100755 (executable)
@@ -87,13 +87,29 @@ test_expect_success 'push new branch by name' '
        compare_refs local HEAD server refs/heads/new-name
 '
 
-test_expect_failure 'push new branch with old:new refspec' '
+test_expect_success 'push new branch with old:new refspec' '
        (cd local &&
         git push origin new-name:new-refspec
        ) &&
        compare_refs local HEAD server refs/heads/new-refspec
 '
 
+test_expect_success 'push new branch with HEAD:new refspec' '
+       (cd local &&
+        git checkout new-name
+        git push origin HEAD:new-refspec-2
+       ) &&
+       compare_refs local HEAD server refs/heads/new-refspec-2
+'
+
+test_expect_success 'push delete branch' '
+       (cd local &&
+        git push origin :new-name
+       ) &&
+       test_must_fail git --git-dir="server/.git" \
+        rev-parse --verify refs/heads/new-name
+'
+
 test_expect_success 'forced push' '
        (cd local &&
        git checkout -b force-test &&
index 27263dfb80420a417bb0f33aa8731ef2fb7bc7b6..5fc9ef262ac1297d9442ed08208cb183ecc0e5c1 100755 (executable)
@@ -2999,4 +2999,22 @@ test_expect_success 'T: ls root tree' '
        test_cmp expect actual
 '
 
+test_expect_success 'T: delete branch' '
+       git branch to-delete &&
+       git fast-import <<-EOF &&
+       reset refs/heads/to-delete
+       from 0000000000000000000000000000000000000000
+       EOF
+       test_must_fail git rev-parse --verify refs/heads/to-delete
+'
+
+test_expect_success 'T: empty reset doesnt delete branch' '
+       git branch not-to-delete &&
+       git fast-import <<-EOF &&
+       reset refs/heads/not-to-delete
+       EOF
+       git show-ref &&
+       git rev-parse --verify refs/heads/not-to-delete
+'
+
 test_done
index 2312dec8f096fe7e36feeabd0e1cd85b5e421d4a..66c8b0a371e37f90cff8da0c92f09c8784ec45a2 100755 (executable)
@@ -504,4 +504,22 @@ test_expect_success 'refs are updated even if no commits need to be exported' '
        test_cmp expected actual
 '
 
+test_expect_success 'use refspec' '
+       git fast-export --refspec refs/heads/master:refs/heads/foobar master | \
+               grep "^commit " | sort | uniq > actual &&
+       echo "commit refs/heads/foobar" > expected &&
+       test_cmp expected actual
+'
+
+test_expect_success 'delete refspec' '
+       git branch to-delete &&
+       git fast-export --refspec :refs/heads/to-delete to-delete ^to-delete > actual &&
+       cat > expected <<-EOF &&
+       reset refs/heads/to-delete
+       from 0000000000000000000000000000000000000000
+
+       EOF
+       test_cmp expected actual
+'
+
 test_done
index 4cfb8f3a4acc36681774e165f92de7fbd948fde5..84c616f180bebc730d05f306906558dd0abad2a4 100644 (file)
@@ -850,7 +850,7 @@ static int push_refs_with_export(struct transport *transport,
        struct ref *ref;
        struct child_process *helper, exporter;
        struct helper_data *data = transport->data;
-       struct string_list revlist_args = STRING_LIST_INIT_NODUP;
+       struct string_list revlist_args = STRING_LIST_INIT_DUP;
        struct strbuf buf = STRBUF_INIT;
 
        if (!data->refspecs)
@@ -870,15 +870,10 @@ static int push_refs_with_export(struct transport *transport,
 
        write_constant(helper->in, "export\n");
 
-       strbuf_reset(&buf);
-
        for (ref = remote_refs; ref; ref = ref->next) {
                char *private;
                unsigned char sha1[20];
 
-               if (ref->deletion)
-                       die("remote-helpers do not support ref deletion");
-
                private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name);
                if (private && !get_sha1(private, sha1)) {
                        strbuf_addf(&buf, "^%s", private);
@@ -888,15 +883,34 @@ static int push_refs_with_export(struct transport *transport,
                free(private);
 
                if (ref->peer_ref) {
-                       if (strcmp(ref->peer_ref->name, ref->name))
-                               die("remote-helpers do not support old:new syntax");
-                       string_list_append(&revlist_args, ref->peer_ref->name);
+                       if (strcmp(ref->name, ref->peer_ref->name)) {
+                               if (!ref->deletion) {
+                                       const char *name;
+                                       int flag;
+
+                                       /* Follow symbolic refs (mainly for HEAD). */
+                                       name = resolve_ref_unsafe(ref->peer_ref->name, sha1, 1, &flag);
+                                       if (!name || !(flag & REF_ISSYMREF))
+                                               name = ref->peer_ref->name;
+
+                                       strbuf_addf(&buf, "%s:%s", name, ref->name);
+                               } else
+                                       strbuf_addf(&buf, ":%s", ref->name);
+
+                               string_list_append(&revlist_args, "--refspec");
+                               string_list_append(&revlist_args, buf.buf);
+                               strbuf_release(&buf);
+                       }
+                       if (!ref->deletion)
+                               string_list_append(&revlist_args, ref->peer_ref->name);
                }
        }
 
        if (get_exporter(transport, &exporter, &revlist_args))
                die("Couldn't run fast-export");
 
+       string_list_clear(&revlist_args, 1);
+
        if (finish_command(&exporter))
                die("Error while running fast-export");
        if (push_update_refs_status(data, remote_refs, flags))