{"thread":{"id":"7136","subject":"[PATCH] git-fetch: Split fetch and merge logic","startedAt":"2007-03-07T12:25:28Z","lastAt":"2007-03-07T12:25:28Z","messageCount":1,"participants":["Santi Béjar"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"36545","messageId":"87fy8h5jgn.fsf@gmail.com","threadId":"7136","inReplyTo":null,"subject":"[PATCH] git-fetch: Split fetch and merge logic","fromName":"Santi Béjar","fromEmail":"sbejar@gmail.com","sentAt":"2007-03-07T12:25:28Z","receivedAt":"2007-03-07T12:25:28Z","isPatch":true,"sender":{"key":"santi@agolina.net","avatar":null},"body":"\nIt makes git-parse-remote.sh, git-fetch--tool and almost all git-fetch\nindependent of the merge logic.\n\ngit-fetch fetches the branches from the remote and saves this\ninformation in .git/FETCH_FETCHED (a temporary file), and at the end it\ngenerates the file .git/FETCH_HEAD.\n\nThe current merge behaviour is unchanged.\n\nSigned-off-by: Santi Béjar <sbejar@gmail.com>\n---\nHi *,\n\n this time the net effect is to add 10 lines :(\n\n This applies to next.\n\n Regards,\n\n Santi\n\n builtin-fetch--tool.c |   49 ++++++++++--------------------\n git-fetch.sh          |   78 +++++++++++++++++++++++++++++++++++++++++-------\n git-parse-remote.sh   |   65 ++++++++++++-----------------------------\n 3 files changed, 101 insertions(+), 91 deletions(-)\n\ndiff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c\nindex e9d6764..2cee058 100644\n--- a/builtin-fetch--tool.c\n+++ b/builtin-fetch--tool.c\n@@ -138,8 +138,7 @@ static int update_local_ref(const char *name,\n \n static int append_fetch_head(FILE *fp,\n \t\t\t     const char *head, const char *remote,\n-\t\t\t     const char *remote_name, const char *remote_nick,\n-\t\t\t     const char *local_name, int not_for_merge,\n+\t\t\t     const char *remote_name, const char *local_name,\n \t\t\t     int verbose, int force)\n {\n \tstruct commit *commit;\n@@ -151,8 +150,6 @@ static int append_fetch_head(FILE *fp,\n \tif (get_sha1(head, sha1))\n \t\treturn error(\"Not a valid object name: %s\", head);\n \tcommit = lookup_commit_reference(sha1);\n-\tif (!commit)\n-\t\tnot_for_merge = 1;\n \n \tif (!strcmp(remote_name, \"HEAD\")) {\n \t\tkind = \"\";\n@@ -189,9 +186,9 @@ static int append_fetch_head(FILE *fp,\n \t\tnote_len += sprintf(note + note_len, \"'%s' of \", what);\n \t}\n \tnote_len += sprintf(note + note_len, \"%.*s\", remote_len, remote);\n-\tfprintf(fp, \"%s\\t%s\\t%s\\n\",\n+\tfprintf(fp, \"%s\\t%s:%s\\t%s\\n\",\n \t\tsha1_to_hex(commit ? commit->object.sha1 : sha1),\n-\t\tnot_for_merge ? \"not-for-merge\" : \"\",\n+\t\tremote_name, local_name,\n \t\tnote);\n \treturn update_local_ref(local_name, head, note, verbose, force);\n }\n@@ -211,14 +208,14 @@ static void remove_keep_on_signal(int signo)\n }\n \n static char *find_local_name(const char *remote_name, const char *refs,\n-\t\t\t     int *force_p, int *not_for_merge_p)\n+\t\t\t     int *force_p)\n {\n \tconst char *ref = refs;\n \tint len = strlen(remote_name);\n \n \twhile (ref) {\n \t\tconst char *next;\n-\t\tint single_force, not_for_merge;\n+\t\tint single_force;\n \n \t\twhile (*ref == '\\n')\n \t\t\tref++;\n@@ -226,19 +223,11 @@ static char *find_local_name(const char *remote_name, const char *refs,\n \t\t\tbreak;\n \t\tnext = strchr(ref, '\\n');\n \n-\t\tsingle_force = not_for_merge = 0;\n+\t\tsingle_force = 0;\n \t\tif (*ref == '+') {\n \t\t\tsingle_force = 1;\n \t\t\tref++;\n \t\t}\n-\t\tif (*ref == '.') {\n-\t\t\tnot_for_merge = 1;\n-\t\t\tref++;\n-\t\t\tif (*ref == '+') {\n-\t\t\t\tsingle_force = 1;\n-\t\t\t\tref++;\n-\t\t\t}\n-\t\t}\n \t\tif (!strncmp(remote_name, ref, len) && ref[len] == ':') {\n \t\t\tconst char *local_part = ref + len + 1;\n \t\t\tchar *ret;\n@@ -252,7 +241,6 @@ static char *find_local_name(const char *remote_name, const char *refs,\n \t\t\tmemcpy(ret, local_part, retlen);\n \t\t\tret[retlen] = 0;\n \t\t\t*force_p = single_force;\n-\t\t\t*not_for_merge_p = not_for_merge;\n \t\t\treturn ret;\n \t\t}\n \t\tref = next;\n@@ -262,7 +250,6 @@ static char *find_local_name(const char *remote_name, const char *refs,\n \n static int fetch_native_store(FILE *fp,\n \t\t\t      const char *remote,\n-\t\t\t      const char *remote_nick,\n \t\t\t      const char *refs,\n \t\t\t      int verbose, int force)\n {\n@@ -276,7 +263,7 @@ static int fetch_native_store(FILE *fp,\n \t\tint len;\n \t\tchar *cp;\n \t\tchar *local_name;\n-\t\tint single_force, not_for_merge;\n+\t\tint single_force;\n \n \t\tfor (cp = buffer; *cp && !isspace(*cp); cp++)\n \t\t\t;\n@@ -298,12 +285,11 @@ static int fetch_native_store(FILE *fp,\n \t\t}\n \n \t\tlocal_name = find_local_name(cp, refs,\n-\t\t\t\t\t     &single_force, &not_for_merge);\n+\t\t\t\t\t     &single_force);\n \t\tif (!local_name)\n \t\t\tcontinue;\n \t\terr |= append_fetch_head(fp,\n-\t\t\t\t\t buffer, remote, cp, remote_nick,\n-\t\t\t\t\t local_name, not_for_merge,\n+\t\t\t\t\t buffer, remote, cp, local_name,\n \t\t\t\t\t verbose, force || single_force);\n \t}\n \treturn err;\n@@ -336,8 +322,6 @@ static int parse_reflist(const char *reflist)\n \t\t\tbreak;\n \t\tfor (next = ref; *next && !isspace(*next); next++)\n \t\t\t;\n-\t\tif (*ref == '.')\n-\t\t\tref++;\n \t\tif (*ref == '+')\n \t\t\tref++;\n \t\tcolon = strchr(ref, ':');\n@@ -460,12 +444,11 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)\n \t\tint result;\n \t\tFILE *fp;\n \n-\t\tif (argc != 8)\n-\t\t\treturn error(\"append-fetch-head takes 6 args\");\n-\t\tfp = fopen(git_path(\"FETCH_HEAD\"), \"a\");\n+\t\tif (argc != 6)\n+\t\t\treturn error(\"append-fetch-head takes 4 args\");\n+\t\tfp = fopen(git_path(\"FETCH_FETCHED\"), \"a\");\n \t\tresult = append_fetch_head(fp, argv[2], argv[3],\n \t\t\t\t\t   argv[4], argv[5],\n-\t\t\t\t\t   argv[6], !!argv[7][0],\n \t\t\t\t\t   verbose, force);\n \t\tfclose(fp);\n \t\treturn result;\n@@ -474,10 +457,10 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)\n \t\tint result;\n \t\tFILE *fp;\n \n-\t\tif (argc != 5)\n-\t\t\treturn error(\"fetch-native-store takes 3 args\");\n-\t\tfp = fopen(git_path(\"FETCH_HEAD\"), \"a\");\n-\t\tresult = fetch_native_store(fp, argv[2], argv[3], argv[4],\n+\t\tif (argc != 4)\n+\t\t\treturn error(\"fetch-native-store takes 2 args\");\n+\t\tfp = fopen(git_path(\"FETCH_FETCHED\"), \"a\");\n+\t\tresult = fetch_native_store(fp, argv[2], argv[3],\n \t\t\t\t\t    verbose, force);\n \t\tfclose(fp);\n \t\treturn result;\ndiff --git a/git-fetch.sh b/git-fetch.sh\nindex 9d45dd2..58cbef1 100755\n--- a/git-fetch.sh\n+++ b/git-fetch.sh\n@@ -79,9 +79,9 @@ do\n \tshift\n done\n \n+origin=$(get_default_remote)\n case \"$#\" in\n 0)\n-\torigin=$(get_default_remote)\n \ttest -n \"$(get_remote_url ${origin})\" ||\n \t\tdie \"Where do you want to fetch from today?\"\n \tset x $origin ; shift ;;\n@@ -103,6 +103,8 @@ if test \"\" = \"$append\"\n then\n \t: >\"$GIT_DIR/FETCH_HEAD\"\n fi\n+: >\"$GIT_DIR/FETCH_FETCHED\"\n+trap 'rm -f \"$GIT_DIR\"/FETCH_FETCHED' 0 1 2 3 15\n \n # Global that is reused later\n ls_remote_result=$(git ls-remote $exec \"$remote\") ||\n@@ -145,7 +147,7 @@ then\n \t\t  git-show-ref --exclude-existing=refs/tags/ |\n \t          while read sha1 name\n \t\t  do\n-\t\t\techo \".${name}:${name}\"\n+\t\t\techo \"${name}:${name}\"\n \t\t  done` || exit\n \tif test \"$#\" -gt 1\n \tthen\n@@ -182,7 +184,7 @@ fetch_native () {\n \ttest -n \"$force\" && flags=\"$flags -f\"\n \tGIT_REFLOG_ACTION=\"$GIT_REFLOG_ACTION\" \\\n \t\tgit-fetch--tool $flags native-store \\\n-\t\t\t\"$remote\" \"$remote_nick\" \"$refs\"\n+\t\t\t\"$remote\" \"$refs\"\n       )\n     ) || exit\n \n@@ -199,13 +201,6 @@ fetch_dumb () {\n \n       # These are relative path from $GIT_DIR, typically starting at refs/\n       # but may be HEAD\n-      if expr \"z$ref\" : 'z\\.' >/dev/null\n-      then\n-\t  not_for_merge=t\n-\t  ref=$(expr \"z$ref\" : 'z\\.\\(.*\\)')\n-      else\n-\t  not_for_merge=\n-      fi\n       if expr \"z$ref\" : 'z+' >/dev/null\n       then\n \t  single_force=t\n@@ -283,7 +278,7 @@ fetch_dumb () {\n       esac\n \n       append_fetch_head \"$head\" \"$remote\" \\\n-\t  \"$remote_name\" \"$remote_nick\" \"$local_name\" \"$not_for_merge\" || exit\n+\t  \"$remote_name\" \"$remote_nick\" \"$local_name\" || exit\n \n   done\n \n@@ -316,7 +311,7 @@ case \"$no_tags$tags\" in\n \t\tdo\n \t\t\tgit-cat-file -t \"$sha1\" >/dev/null 2>&1 || continue\n \t\t\techo >&2 \"Auto-following $name\"\n-\t\t\techo \".${name}:${name}\"\n+\t\t\techo \"${name}:${name}\"\n \t\tdone)\n \tesac\n \tcase \"$taglist\" in\n@@ -344,3 +339,62 @@ case \"$orig_head\" in\n \tfi\n \t;;\n esac\n+# Generate $GIT_DIR/FETCH_HEAD\n+case \",$#,$remote_nick,\" in\n+,1,$origin,)\n+\t# Fetch default: merge the branches in branch.*.merge that match\n+\t#                the fetched ones or the first one\n+\tcurr_branch=$(git-symbolic-ref -q HEAD | sed -e 's|^refs/heads/||')\n+\tmerge_branches=$(git-config \\\n+\t\t--get-all \"branch.${curr_branch}.merge\" | sort -u)\n+\tfetch_branches=$(get_remote_default_refs_for_fetch -n $remote_nick |\n+\t\tsed -e 's/:.*$//g' -e 's/^+//' | sort -u)\n+\ttest -n \"$merge_branches\" && test -n \"$fetch_branches\" &&\n+\tmerge_branches=$(printf '%s\\n%s' \"$merge_branches\" \"$fetch_branches\" |\n+\t\tsort | uniq -d)\n+\n+\t[ -z \"$merge_branches\" ] ||\n+\ttest \"$(get_data_source $remote_nick)\" = branches && merge_first=yes;;\n+,1,$remote,)\n+\t# Remote is a URL, merge the default branch HEAD\n+\tmerge_branches=HEAD;;\n+,1,*)\n+\t# Remote is a shorthand diferent from the default,\n+\t# merge the first fetched branch\n+\tmerge_first=yes ;;\n+*)\n+\t# Merge the branches given in the command line\n+\tmerge_branches=$(for ref in $(get_remote_refs_for_fetch \"$@\") ; do\n+\t\texpr \"z$ref\" : 'z.*:' >/dev/null || ref=\"${ref}:\"\n+\t\techo \"$(expr \"z$ref\" : 'z\\([^:]*\\):')\"; done);;\n+esac\n+\n+if test \"$merge_first\" = \"yes\" ; then\n+    merge_branches=\n+    # Do not merge the first branch if it was specified with a glob\n+    test \"$(get_remote_default_refs_for_fetch -t $remote_nick)\" != \"explicit\" &&\n+    merge_first=\n+else\n+    merge_branches=$(canon_refs_list_for_fetch $merge_branches | sed 's/:.*$//g')\n+fi\n+\n+while IFS='\t' read hash ref note ; do\n+\t# 2.6.11-tree tag would not be happy to be fed to resolve.\n+\tif git-cat-file commit \"$hash\" >/dev/null 2>&1\n+\tthen\n+\t\thashc=$(git-rev-parse --verify \"$hash^0\") || exit\n+\t\tremote_branch=$(expr \"z$ref\" : 'z\\([^:]*\\):')\n+\t\tfor merge_branch in $merge_branches ; do\n+\t\t\t[ \"$merge_branch\" = \"$remote_branch\" ] &&\n+\t\t\techo \"$hashc\t\t$note\" && continue 2\n+\t\tdone\n+\telse\n+\t\techo \"$hash\tnot-for-merge\t$note\" && continue\n+\tfi\n+\tif ! test \"$merge_first\" || test \"$merge_first\" = \"done\" ; then\n+\t\techo \"$hash\tnot-for-merge\t$note\"\n+\telse\n+\t\techo \"$hash\t\t$note\"\n+\t\tmerge_first=done\n+\tfi\n+done < \"$GIT_DIR\"/FETCH_FETCHED >> \"$GIT_DIR/FETCH_HEAD\"\ndiff --git a/git-parse-remote.sh b/git-parse-remote.sh\nindex c46131f..99e7184 100755\n--- a/git-parse-remote.sh\n+++ b/git-parse-remote.sh\n@@ -70,8 +70,7 @@ get_remote_default_refs_for_push () {\n \tesac\n }\n \n-# Called from canon_refs_list_for_fetch -d \"$remote\", which\n-# is called from get_remote_default_refs_for_fetch to grok\n+# Called from get_remote_default_refs_for_fetch to grok\n # refspecs that are retrieved from the configuration, but not\n # from get_remote_refs_for_fetch when it deals with refspecs\n # supplied on the command line.  $ls_remote_result has the list\n@@ -87,30 +86,6 @@ expand_refs_wildcard () {\n \n # Subroutine to canonicalize remote:local notation.\n canon_refs_list_for_fetch () {\n-\t# If called from get_remote_default_refs_for_fetch\n-\t# leave the branches in branch.${curr_branch}.merge alone,\n-\t# or the first one otherwise; add prefix . to the rest\n-\t# to prevent the secondary branches to be merged by default.\n-\tmerge_branches=\n-\tcurr_branch=\n-\tif test \"$1\" = \"-d\"\n-\tthen\n-\t\tshift ; remote=\"$1\" ; shift\n-\t\tset $(expand_refs_wildcard \"$remote\" \"$@\")\n-\t\tis_explicit=\"$1\"\n-\t\tshift\n-\t\tif test \"$remote\" = \"$(get_default_remote)\"\n-\t\tthen\n-\t\t\tcurr_branch=$(git-symbolic-ref -q HEAD | \\\n-\t\t\t    sed -e 's|^refs/heads/||')\n-\t\t\tmerge_branches=$(git-config \\\n-\t\t\t    --get-all \"branch.${curr_branch}.merge\")\n-\t\tfi\n-\t\tif test -z \"$merge_branches\" && test $is_explicit != explicit\n-\t\tthen\n-\t\t\tmerge_branches=..this.will.never.match.any.ref..\n-\t\tfi\n-\tfi\n \tfor ref\n \tdo\n \t\tforce=\n@@ -123,18 +98,6 @@ canon_refs_list_for_fetch () {\n \t\texpr \"z$ref\" : 'z.*:' >/dev/null || ref=\"${ref}:\"\n \t\tremote=$(expr \"z$ref\" : 'z\\([^:]*\\):')\n \t\tlocal=$(expr \"z$ref\" : 'z[^:]*:\\(.*\\)')\n-\t\tdot_prefix=.\n-\t\tif test -z \"$merge_branches\"\n-\t\tthen\n-\t\t\tmerge_branches=$remote\n-\t\t\tdot_prefix=\n-\t\telse\n-\t\t\tfor merge_branch in $merge_branches\n-\t\t\tdo\n-\t\t\t    [ \"$remote\" = \"$merge_branch\" ] &&\n-\t\t\t    dot_prefix= && break\n-\t\t\tdone\n-\t\tfi\n \t\tcase \"$remote\" in\n \t\t'' | HEAD ) remote=HEAD ;;\n \t\trefs/heads/* | refs/tags/* | refs/remotes/*) ;;\n@@ -153,32 +116,42 @@ canon_refs_list_for_fetch () {\n \t\t   git-check-ref-format \"$local_ref_name\" ||\n \t\t   die \"* refusing to create funny ref '$local_ref_name' locally\"\n \t\tfi\n-\t\techo \"${dot_prefix}${force}${remote}:${local}\"\n+\t\techo \"${force}${remote}:${local}\"\n \tdone\n }\n \n # Returns list of src: (no store), or src:dst (store)\n get_remote_default_refs_for_fetch () {\n+\ttest \"$1\" = -t && type=yes && shift\n+\ttest \"$1\" = -n && canon=no && shift\n \tdata_source=$(get_data_source \"$1\")\n \tcase \"$data_source\" in\n \t'')\n-\t\techo \"HEAD:\" ;;\n+\t\tset explicit \"HEAD:\" ;;\n \tconfig)\n-\t\tcanon_refs_list_for_fetch -d \"$1\" \\\n-\t\t\t$(git-config --get-all \"remote.$1.fetch\") ;;\n+\t\tset $(expand_refs_wildcard \"$1\" \\\n+\t\t\t$(git-config --get-all \"remote.$1.fetch\")) ;;\n \tbranches)\n \t\tremote_branch=$(sed -ne '/#/s/.*#//p' \"$GIT_DIR/branches/$1\")\n \t\tcase \"$remote_branch\" in '') remote_branch=master ;; esac\n-\t\techo \"refs/heads/${remote_branch}:refs/heads/$1\"\n+\t\tset explicit \"refs/heads/${remote_branch}:refs/heads/$1\"\n \t\t;;\n \tremotes)\n-\t\tcanon_refs_list_for_fetch -d \"$1\" $(sed -ne '/^Pull: */{\n-\t\t\t\t\t\ts///p\n-\t\t\t\t\t}' \"$GIT_DIR/remotes/$1\")\n+\t\tset $(expand_refs_wildcard \"$1\" $(sed -ne '/^Pull: */s///p'\\\n+\t\t\t\t\t\"$GIT_DIR/remotes/$1\"))\n \t\t;;\n \t*)\n \t\tdie \"internal error: get-remote-default-ref-for-push $1\" ;;\n \tesac\n+\tif test \"$type\" = yes ; then\n+\t\techo $1\n+\telif test \"$canon\" = no ; then\n+\t\tshift\n+\t\tfor ref ; do echo \"$ref\" ; done\n+\telse\n+\t\tshift\n+\t\tcanon_refs_list_for_fetch \"$@\"\n+\tfi\n }\n \n get_remote_refs_for_push () {\n-- \n1.5.0.3.897.g91a70-dirty\n"}]}