aboutsummaryrefslogtreecommitdiff
path: root/config.c
diff options
context:
space:
mode:
authorSahil Dua <sahildua2305@gmail.com>2017-06-18 23:19:16 +0200
committerJunio C Hamano <gitster@pobox.com>2017-06-18 21:47:59 -0700
commit52d59cc6452ed1aeec91f4c168a853ea8d9d5496 (patch)
treec373ca88d5a0480912ff23aea032ab55389f9933 /config.c
parentc8b2cec09e548df7c948e5f4453381f0a3b0f6a9 (diff)
downloadgit-52d59cc6452ed1aeec91f4c168a853ea8d9d5496.tar.gz
git-52d59cc6452ed1aeec91f4c168a853ea8d9d5496.tar.xz
branch: add a --copy (-c) option to go with --move (-m)
Add the ability to --copy a branch and its reflog and configuration, this uses the same underlying machinery as the --move (-m) option except the reflog and configuration is copied instead of being moved. This is useful for e.g. copying a topic branch to a new version, e.g. work to work-2 after submitting the work topic to the list, while preserving all the tracking info and other configuration that goes with the branch, and unlike --move keeping the other already-submitted branch around for reference. Like --move, when the source branch is the currently checked out branch the HEAD is moved to the destination branch. In the case of --move we don't really have a choice (other than remaining on a detached HEAD) and in order to keep the functionality consistent, we are doing it in similar way for --copy too. The most common usage of this feature is expected to be moving to a new topic branch which is a copy of the current one, in that case moving to the target branch is what the user wants, and doesn't unexpectedly behave differently than --move would. One outstanding caveat of this implementation is that: git checkout maint && git checkout master && git branch -c topic && git checkout - Will check out 'maint' instead of 'master'. This is because the @{-N} feature (or its -1 shorthand "-") relies on HEAD reflogs created by the checkout command, so in this case we'll checkout maint instead of master, as the user might expect. What to do about that is left to a future change. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Sahil Dua <sahildua2305@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'config.c')
-rw-r--r--config.c102
1 files changed, 82 insertions, 20 deletions
diff --git a/config.c b/config.c
index 856e42e4d..d0d5aa1dc 100644
--- a/config.c
+++ b/config.c
@@ -2638,8 +2638,8 @@ static int section_name_is_ok(const char *name)
}
/* if new_name == NULL, the section is removed instead */
-int git_config_rename_section_in_file(const char *config_filename,
- const char *old_name, const char *new_name)
+static int git_config_copy_or_rename_section_in_file(const char *config_filename,
+ const char *old_name, const char *new_name, int copy)
{
int ret = 0, remove = 0;
char *filename_buf = NULL;
@@ -2648,6 +2648,7 @@ int git_config_rename_section_in_file(const char *config_filename,
char buf[1024];
FILE *config_file = NULL;
struct stat st;
+ struct strbuf copystr = STRBUF_INIT;
if (new_name && !section_name_is_ok(new_name)) {
ret = error("invalid section name: %s", new_name);
@@ -2683,12 +2684,30 @@ int git_config_rename_section_in_file(const char *config_filename,
while (fgets(buf, sizeof(buf), config_file)) {
int i;
int length;
+ int is_section = 0;
char *output = buf;
for (i = 0; buf[i] && isspace(buf[i]); i++)
; /* do nothing */
if (buf[i] == '[') {
/* it's a section */
- int offset = section_name_match(&buf[i], old_name);
+ int offset;
+ is_section = 1;
+
+ /*
+ * When encountering a new section under -c we
+ * need to flush out any section we're already
+ * coping and begin anew. There might be
+ * multiple [branch "$name"] sections.
+ */
+ if (copystr.len > 0) {
+ if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
+ ret = write_error(get_lock_file_path(lock));
+ goto out;
+ }
+ strbuf_reset(&copystr);
+ }
+
+ offset = section_name_match(&buf[i], old_name);
if (offset > 0) {
ret++;
if (new_name == NULL) {
@@ -2696,25 +2715,30 @@ int git_config_rename_section_in_file(const char *config_filename,
continue;
}
store.baselen = strlen(new_name);
- if (!store_write_section(out_fd, new_name)) {
- ret = write_error(get_lock_file_path(lock));
- goto out;
- }
- /*
- * We wrote out the new section, with
- * a newline, now skip the old
- * section's length
- */
- output += offset + i;
- if (strlen(output) > 0) {
+ if (!copy) {
+ if (!store_write_section(out_fd, new_name)) {
+ ret = write_error(get_lock_file_path(lock));
+ goto out;
+ }
+
/*
- * More content means there's
- * a declaration to put on the
- * next line; indent with a
- * tab
+ * We wrote out the new section, with
+ * a newline, now skip the old
+ * section's length
*/
- output -= 1;
- output[0] = '\t';
+ output += offset + i;
+ if (strlen(output) > 0) {
+ /*
+ * More content means there's
+ * a declaration to put on the
+ * next line; indent with a
+ * tab
+ */
+ output -= 1;
+ output[0] = '\t';
+ }
+ } else {
+ copystr = store_create_section(new_name);
}
}
remove = 0;
@@ -2722,11 +2746,30 @@ int git_config_rename_section_in_file(const char *config_filename,
if (remove)
continue;
length = strlen(output);
+
+ if (!is_section && copystr.len > 0) {
+ strbuf_add(&copystr, output, length);
+ }
+
if (write_in_full(out_fd, output, length) != length) {
ret = write_error(get_lock_file_path(lock));
goto out;
}
}
+
+ /*
+ * Copy a trailing section at the end of the config, won't be
+ * flushed by the usual "flush because we have a new section
+ * logic in the loop above.
+ */
+ if (copystr.len > 0) {
+ if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
+ ret = write_error(get_lock_file_path(lock));
+ goto out;
+ }
+ strbuf_reset(&copystr);
+ }
+
fclose(config_file);
config_file = NULL;
commit_and_out:
@@ -2742,11 +2785,30 @@ out_no_rollback:
return ret;
}
+int git_config_rename_section_in_file(const char *config_filename,
+ const char *old_name, const char *new_name)
+{
+ return git_config_copy_or_rename_section_in_file(config_filename,
+ old_name, new_name, 0);
+}
+
int git_config_rename_section(const char *old_name, const char *new_name)
{
return git_config_rename_section_in_file(NULL, old_name, new_name);
}
+int git_config_copy_section_in_file(const char *config_filename,
+ const char *old_name, const char *new_name)
+{
+ return git_config_copy_or_rename_section_in_file(config_filename,
+ old_name, new_name, 1);
+}
+
+int git_config_copy_section(const char *old_name, const char *new_name)
+{
+ return git_config_copy_section_in_file(NULL, old_name, new_name);
+}
+
/*
* Call this to report error for your variable that should not
* get a boolean value (i.e. "[my] var" means "true").