I'd rather remove the complete section (However, that means that you have to introduce a function to do that in config.c)
No problem.
Also, the second part of your patch would be clearer (methinks) if you replaced the call to get_sha1() with dwim_ref(). You'd get the real refname for free...
Not only that, my previous version did not work if somebody specified remotes/remote/upstreambranch instead of remote/upstreambranch. Updated version attached. Paolo
* git-branch: register where to merge from, when branching off a remote branch. A rather standard (in 1.5) procedure for branching off a remote archive is: git checkout -b branchname remote/upstreambranch git config --add branch.branchname.remote remote git config --add branch.branchname.merge refs/heads/upstreambranch In this case, we can save the user some effort if "git branch" (and "git checkout -b") automatically do the two "git-config --add"s when the source branch is remote. There is a good chance that some user wants to merge something different, but in that case they have to specify what to merge _anyway_. Dually, we remove the branch.branchname section when the user does "git branch -d". diff --git a/builtin-branch.c b/builtin-branch.c index d0179b0..c1563cf 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -147,8 +147,16 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) error("Error deleting %sbranch '%s'", remote, argv[i]); ret = 1; - } else + } else { + /* Remove git-config keys. */ + if (kinds == REF_LOCAL_BRANCH) { + char *config_key = xmalloc(strlen(argv[i]) + 8); + sprintf(config_key, "branch.%s", argv[i]); + git_config_remove_section(config_key); + } + printf("Deleted %sbranch %s.\n", remote, argv[i]); + } } @@ -308,6 +316,27 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev) free_ref_list(&ref_list); } +static void register_branch_pull (const char *name, const char *remote_name) +{ + char *slash = strchr(remote_name, '/'); + + char *config_key = xmalloc(strlen(name) + 15); + char *merge_value = xmalloc(strlen(remote_name) + 10); + + char *remote_value = xstrdup(remote_name); + remote_value[slash - remote_name] = 0; + sprintf(config_key, "branch.%s.remote", name); + git_config_set(config_key, remote_value); + + sprintf(merge_value, "refs/heads/%s", slash + 1); + sprintf(config_key, "branch.%s.merge", name); + git_config_set(config_key, merge_value); + + free (config_key); + free (remote_value); + free (merge_value); +} + static void create_branch(const char *name, const char *start_name, unsigned char *start_sha1, int force, int reflog) @@ -315,8 +344,8 @@ static void create_branch(const char *name, const char *start_name, struct ref_lock *lock; struct commit *commit; unsigned char sha1[20]; - char ref[PATH_MAX], msg[PATH_MAX + 20]; - int forcing = 0; + char *real_ref = NULL, ref[PATH_MAX], msg[PATH_MAX + 20]; + int forcing = 0, remote = 0; snprintf(ref, sizeof ref, "refs/heads/%s", name); if (check_ref_format(ref)) @@ -333,7 +362,9 @@ static void create_branch(const char *name, const char *start_name, if (start_sha1) /* detached HEAD */ hashcpy(sha1, start_sha1); - else if (get_sha1(start_name, sha1)) + else if (dwim_ref(start_name, strlen (start_name), sha1, &real_ref)) + remote = !prefixcmp(real_ref, "refs/remotes/"); + else die("Not a valid object name: '%s'.", start_name); if ((commit = lookup_commit_reference(sha1)) == NULL) @@ -354,8 +385,16 @@ static void create_branch(const char *name, const char *start_name, snprintf(msg, sizeof msg, "branch: Created from %s", start_name); + /* When branching off a remote branch, set up so that git-pull + automatically merges from there. */ + if (remote) + register_branch_pull (name, real_ref + 13); + if (write_ref_sha1(lock, sha1, msg) < 0) die("Failed to write ref: %s.", strerror(errno)); + + if (real_ref) + free (real_ref); } static void rename_branch(const char *oldname, const char *newname, int force) diff --git a/cache.h b/cache.h index 8bbc142..797483b 100644 --- a/cache.h +++ b/cache.h @@ -438,6 +438,7 @@ extern int git_config_bool(const char *, const char *); extern int git_config_set(const char *, const char *); extern int git_config_set_multivar(const char *, const char *, const char *, int); extern int git_config_rename_section(const char *, const char *); +extern int git_config_remove_section(const char *); extern int check_repository_format_version(const char *var, const char *value); #define MAX_GITNAME (1000) diff --git a/config.c b/config.c index 0ff413b..acd83aa 100644 --- a/config.c +++ b/config.c @@ -854,6 +854,33 @@ write_err_out: } +static int section_name_match (const char *buf, const char *name) +{ + int i = 0, j = 0, dot = 0; + for (; buf[i] && buf[i] != ']'; i++) { + if (!dot && isspace(buf[i])) { + dot = 1; + if (name[j++] != '.') + break; + for (i++; isspace(buf[i]); i++) + ; /* do nothing */ + if (buf[i] != '"') + break; + continue; + } + if (buf[i] == '\\' && dot) + i++; + else if (buf[i] == '"' && dot) { + for (i++; isspace(buf[i]); i++) + ; /* do_nothing */ + break; + } + if (buf[i] != name[j++]) + break; + } + return (buf[i] == ']' && name[j] == 0); +} + int git_config_rename_section(const char *old_name, const char *new_name) { int ret = 0; @@ -885,40 +912,15 @@ int git_config_rename_section(const char *old_name, const char *new_name) int length; for (i = 0; buf[i] && isspace(buf[i]); i++) ; /* do nothing */ - if (buf[i] == '[') { + if (buf[i] == '[' && section_name_match (&buf[i+1], old_name)) { /* it's a section */ - int j = 0, dot = 0; - for (i++; buf[i] && buf[i] != ']'; i++) { - if (!dot && isspace(buf[i])) { - dot = 1; - if (old_name[j++] != '.') - break; - for (i++; isspace(buf[i]); i++) - ; /* do nothing */ - if (buf[i] != '"') - break; - continue; - } - if (buf[i] == '\\' && dot) - i++; - else if (buf[i] == '"' && dot) { - for (i++; isspace(buf[i]); i++) - ; /* do_nothing */ - break; - } - if (buf[i] != old_name[j++]) - break; - } - if (buf[i] == ']' && old_name[j] == 0) { - /* old_name matches */ - ret++; - store.baselen = strlen(new_name); - if (!store_write_section(out_fd, new_name)) { - ret = write_error(); - goto out; - } - continue; + ret++; + store.baselen = strlen(new_name); + if (!store_write_section(out_fd, new_name)) { + ret = write_error(); + goto out; } + continue; } length = strlen(buf); if (write_in_full(out_fd, buf, length) != length) { @@ -934,3 +936,58 @@ int git_config_rename_section(const char *old_name, const char *new_name) return ret; } +int git_config_remove_section(const char *name) +{ + int ret = 0; + char *config_filename; + struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1); + int out_fd; + int removing = 0; + char buf[1024]; + + config_filename = getenv(CONFIG_ENVIRONMENT); + if (!config_filename) { + config_filename = getenv(CONFIG_LOCAL_ENVIRONMENT); + if (!config_filename) + config_filename = git_path("config"); + } + config_filename = xstrdup(config_filename); + out_fd = hold_lock_file_for_update(lock, config_filename, 0); + if (out_fd < 0) { + ret = error("Could not lock config file!"); + goto out; + } + + if (!(config_file = fopen(config_filename, "rb"))) { + ret = error("Could not open config file!"); + goto out; + } + + while (fgets(buf, sizeof(buf), config_file)) { + int i; + int length; + for (i = 0; buf[i] && isspace(buf[i]); i++) + ; /* do nothing */ + if (buf[i] == '[') { + if (section_name_match (&buf[i + 1], name)) { + /* name matches */ + ret++; + removing = 1; + } else + removing = 0; + } + if (removing) + continue; + length = strlen(buf); + if (write_in_full(out_fd, buf, length) != length) { + ret = write_error(); + goto out; + } + } + fclose(config_file); + if (close(out_fd) || commit_lock_file(lock) < 0) + ret = error("Cannot commit config file!"); + out: + free(config_filename); + return ret; +}