From 525ab63950e324823dd45423104cdcf5e46e2610 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 24 Dec 2007 00:36:00 -0800 Subject: [PATCH 01/42] merge-recursive: split low-level merge functions out. This moves low-level merge functions out of merge-recursive.c and places them in a new separate file, ll-merge.c Signed-off-by: Junio C Hamano --- Makefile | 4 +- ll-merge.c | 379 ++++++++++++++++++++++++++++++++++++++++++++++ ll-merge.h | 15 ++ merge-recursive.c | 379 ++-------------------------------------------- 4 files changed, 408 insertions(+), 369 deletions(-) create mode 100644 ll-merge.c create mode 100644 ll-merge.h diff --git a/Makefile b/Makefile index 83c359acde..64f67afb8d 100644 --- a/Makefile +++ b/Makefile @@ -301,7 +301,7 @@ LIB_H = \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \ utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h \ - mailmap.h remote.h parse-options.h transport.h diffcore.h hash.h + mailmap.h remote.h parse-options.h transport.h diffcore.h hash.h ll-merge.h DIFF_OBJS = \ diff.o diff-lib.o diffcore-break.o diffcore-order.o \ @@ -324,7 +324,7 @@ LIB_OBJS = \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \ convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \ - transport.o bundle.o walker.o parse-options.o ws.o archive.o + transport.o bundle.o walker.o parse-options.o ws.o archive.o ll-merge.o BUILTIN_OBJS = \ builtin-add.o \ diff --git a/ll-merge.c b/ll-merge.c new file mode 100644 index 0000000000..5ae74331bc --- /dev/null +++ b/ll-merge.c @@ -0,0 +1,379 @@ +/* + * Low level 3-way in-core file merge. + * + * Copyright (c) 2007 Junio C Hamano + */ + +#include "cache.h" +#include "attr.h" +#include "xdiff-interface.h" +#include "run-command.h" +#include "interpolate.h" +#include "ll-merge.h" + +struct ll_merge_driver; + +typedef int (*ll_merge_fn)(const struct ll_merge_driver *, + mmbuffer_t *result, + const char *path, + mmfile_t *orig, + mmfile_t *src1, const char *name1, + mmfile_t *src2, const char *name2, + int virtual_ancestor); + +struct ll_merge_driver { + const char *name; + const char *description; + ll_merge_fn fn; + const char *recursive; + struct ll_merge_driver *next; + char *cmdline; +}; + +/* + * Built-in low-levels + */ +static int ll_binary_merge(const struct ll_merge_driver *drv_unused, + mmbuffer_t *result, + const char *path_unused, + mmfile_t *orig, + mmfile_t *src1, const char *name1, + mmfile_t *src2, const char *name2, + int virtual_ancestor) +{ + /* + * The tentative merge result is "ours" for the final round, + * or common ancestor for an internal merge. Still return + * "conflicted merge" status. + */ + mmfile_t *stolen = virtual_ancestor ? orig : src1; + + result->ptr = stolen->ptr; + result->size = stolen->size; + stolen->ptr = NULL; + return 1; +} + +static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, + mmbuffer_t *result, + const char *path_unused, + mmfile_t *orig, + mmfile_t *src1, const char *name1, + mmfile_t *src2, const char *name2, + int virtual_ancestor) +{ + xpparam_t xpp; + + if (buffer_is_binary(orig->ptr, orig->size) || + buffer_is_binary(src1->ptr, src1->size) || + buffer_is_binary(src2->ptr, src2->size)) { + warning("Cannot merge binary files: %s vs. %s\n", + name1, name2); + return ll_binary_merge(drv_unused, result, + path_unused, + orig, src1, name1, + src2, name2, + virtual_ancestor); + } + + memset(&xpp, 0, sizeof(xpp)); + return xdl_merge(orig, + src1, name1, + src2, name2, + &xpp, XDL_MERGE_ZEALOUS, + result); +} + +static int ll_union_merge(const struct ll_merge_driver *drv_unused, + mmbuffer_t *result, + const char *path_unused, + mmfile_t *orig, + mmfile_t *src1, const char *name1, + mmfile_t *src2, const char *name2, + int virtual_ancestor) +{ + char *src, *dst; + long size; + const int marker_size = 7; + + int status = ll_xdl_merge(drv_unused, result, path_unused, + orig, src1, NULL, src2, NULL, + virtual_ancestor); + if (status <= 0) + return status; + size = result->size; + src = dst = result->ptr; + while (size) { + char ch; + if ((marker_size < size) && + (*src == '<' || *src == '=' || *src == '>')) { + int i; + ch = *src; + for (i = 0; i < marker_size; i++) + if (src[i] != ch) + goto not_a_marker; + if (src[marker_size] != '\n') + goto not_a_marker; + src += marker_size + 1; + size -= marker_size + 1; + continue; + } + not_a_marker: + do { + ch = *src++; + *dst++ = ch; + size--; + } while (ch != '\n' && size); + } + result->size = dst - result->ptr; + return 0; +} + +#define LL_BINARY_MERGE 0 +#define LL_TEXT_MERGE 1 +#define LL_UNION_MERGE 2 +static struct ll_merge_driver ll_merge_drv[] = { + { "binary", "built-in binary merge", ll_binary_merge }, + { "text", "built-in 3-way text merge", ll_xdl_merge }, + { "union", "built-in union merge", ll_union_merge }, +}; + +static void create_temp(mmfile_t *src, char *path) +{ + int fd; + + strcpy(path, ".merge_file_XXXXXX"); + fd = xmkstemp(path); + if (write_in_full(fd, src->ptr, src->size) != src->size) + die("unable to write temp-file"); + close(fd); +} + +/* + * User defined low-level merge driver support. + */ +static int ll_ext_merge(const struct ll_merge_driver *fn, + mmbuffer_t *result, + const char *path, + mmfile_t *orig, + mmfile_t *src1, const char *name1, + mmfile_t *src2, const char *name2, + int virtual_ancestor) +{ + char temp[3][50]; + char cmdbuf[2048]; + struct interp table[] = { + { "%O" }, + { "%A" }, + { "%B" }, + }; + struct child_process child; + const char *args[20]; + int status, fd, i; + struct stat st; + + if (fn->cmdline == NULL) + die("custom merge driver %s lacks command line.", fn->name); + + result->ptr = NULL; + result->size = 0; + create_temp(orig, temp[0]); + create_temp(src1, temp[1]); + create_temp(src2, temp[2]); + + interp_set_entry(table, 0, temp[0]); + interp_set_entry(table, 1, temp[1]); + interp_set_entry(table, 2, temp[2]); + + interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3); + + memset(&child, 0, sizeof(child)); + child.argv = args; + args[0] = "sh"; + args[1] = "-c"; + args[2] = cmdbuf; + args[3] = NULL; + + status = run_command(&child); + if (status < -ERR_RUN_COMMAND_FORK) + ; /* failure in run-command */ + else + status = -status; + fd = open(temp[1], O_RDONLY); + if (fd < 0) + goto bad; + if (fstat(fd, &st)) + goto close_bad; + result->size = st.st_size; + result->ptr = xmalloc(result->size + 1); + if (read_in_full(fd, result->ptr, result->size) != result->size) { + free(result->ptr); + result->ptr = NULL; + result->size = 0; + } + close_bad: + close(fd); + bad: + for (i = 0; i < 3; i++) + unlink(temp[i]); + return status; +} + +/* + * merge.default and merge.driver configuration items + */ +static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail; +static const char *default_ll_merge; + +static int read_merge_config(const char *var, const char *value) +{ + struct ll_merge_driver *fn; + const char *ep, *name; + int namelen; + + if (!strcmp(var, "merge.default")) { + if (value) + default_ll_merge = strdup(value); + return 0; + } + + /* + * We are not interested in anything but "merge..variable"; + * especially, we do not want to look at variables such as + * "merge.summary", "merge.tool", and "merge.verbosity". + */ + if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5) + return 0; + + /* + * Find existing one as we might be processing merge..var2 + * after seeing merge..var1. + */ + name = var + 6; + namelen = ep - name; + for (fn = ll_user_merge; fn; fn = fn->next) + if (!strncmp(fn->name, name, namelen) && !fn->name[namelen]) + break; + if (!fn) { + fn = xcalloc(1, sizeof(struct ll_merge_driver)); + fn->name = xmemdupz(name, namelen); + fn->fn = ll_ext_merge; + *ll_user_merge_tail = fn; + ll_user_merge_tail = &(fn->next); + } + + ep++; + + if (!strcmp("name", ep)) { + if (!value) + return error("%s: lacks value", var); + fn->description = strdup(value); + return 0; + } + + if (!strcmp("driver", ep)) { + if (!value) + return error("%s: lacks value", var); + /* + * merge..driver specifies the command line: + * + * command-line + * + * The command-line will be interpolated with the following + * tokens and is given to the shell: + * + * %O - temporary file name for the merge base. + * %A - temporary file name for our version. + * %B - temporary file name for the other branches' version. + * + * The external merge driver should write the results in the + * file named by %A, and signal that it has done with zero exit + * status. + */ + fn->cmdline = strdup(value); + return 0; + } + + if (!strcmp("recursive", ep)) { + if (!value) + return error("%s: lacks value", var); + fn->recursive = strdup(value); + return 0; + } + + return 0; +} + +static void initialize_ll_merge(void) +{ + if (ll_user_merge_tail) + return; + ll_user_merge_tail = &ll_user_merge; + git_config(read_merge_config); +} + +static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr) +{ + struct ll_merge_driver *fn; + const char *name; + int i; + + initialize_ll_merge(); + + if (ATTR_TRUE(merge_attr)) + return &ll_merge_drv[LL_TEXT_MERGE]; + else if (ATTR_FALSE(merge_attr)) + return &ll_merge_drv[LL_BINARY_MERGE]; + else if (ATTR_UNSET(merge_attr)) { + if (!default_ll_merge) + return &ll_merge_drv[LL_TEXT_MERGE]; + else + name = default_ll_merge; + } + else + name = merge_attr; + + for (fn = ll_user_merge; fn; fn = fn->next) + if (!strcmp(fn->name, name)) + return fn; + + for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++) + if (!strcmp(ll_merge_drv[i].name, name)) + return &ll_merge_drv[i]; + + /* default to the 3-way */ + return &ll_merge_drv[LL_TEXT_MERGE]; +} + +static const char *git_path_check_merge(const char *path) +{ + static struct git_attr_check attr_merge_check; + + if (!attr_merge_check.attr) + attr_merge_check.attr = git_attr("merge", 5); + + if (git_checkattr(path, 1, &attr_merge_check)) + return NULL; + return attr_merge_check.value; +} + +int ll_merge(mmbuffer_t *result_buf, + const char *path, + mmfile_t *ancestor, + mmfile_t *ours, const char *our_label, + mmfile_t *theirs, const char *their_label, + int virtual_ancestor) +{ + const char *ll_driver_name; + const struct ll_merge_driver *driver; + + ll_driver_name = git_path_check_merge(path); + driver = find_ll_merge_driver(ll_driver_name); + + if (virtual_ancestor && driver->recursive) + driver = find_ll_merge_driver(driver->recursive); + return driver->fn(driver, result_buf, path, + ancestor, + ours, our_label, + theirs, their_label, virtual_ancestor); +} diff --git a/ll-merge.h b/ll-merge.h new file mode 100644 index 0000000000..5388422d09 --- /dev/null +++ b/ll-merge.h @@ -0,0 +1,15 @@ +/* + * Low level 3-way in-core file merge. + */ + +#ifndef LL_MERGE_H +#define LL_MERGE_H + +int ll_merge(mmbuffer_t *result_buf, + const char *path, + mmfile_t *ancestor, + mmfile_t *ours, const char *our_label, + mmfile_t *theirs, const char *their_label, + int virtual_ancestor); + +#endif diff --git a/merge-recursive.c b/merge-recursive.c index 55ef76f5a5..d09f66e5f3 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -10,13 +10,11 @@ #include "tree-walk.h" #include "diff.h" #include "diffcore.h" -#include "run-command.h" #include "tag.h" #include "unpack-trees.h" #include "path-list.h" #include "xdiff-interface.h" -#include "interpolate.h" -#include "attr.h" +#include "ll-merge.h" static int subtree_merge; @@ -624,364 +622,16 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm) mm->size = size; } -/* - * Customizable low-level merge drivers support. - */ - -struct ll_merge_driver; -typedef int (*ll_merge_fn)(const struct ll_merge_driver *, - const char *path, - mmfile_t *orig, - mmfile_t *src1, const char *name1, - mmfile_t *src2, const char *name2, - mmbuffer_t *result); - -struct ll_merge_driver { - const char *name; - const char *description; - ll_merge_fn fn; - const char *recursive; - struct ll_merge_driver *next; - char *cmdline; -}; - -/* - * Built-in low-levels - */ -static int ll_binary_merge(const struct ll_merge_driver *drv_unused, - const char *path_unused, - mmfile_t *orig, - mmfile_t *src1, const char *name1, - mmfile_t *src2, const char *name2, - mmbuffer_t *result) -{ - /* - * The tentative merge result is "ours" for the final round, - * or common ancestor for an internal merge. Still return - * "conflicted merge" status. - */ - mmfile_t *stolen = index_only ? orig : src1; - - result->ptr = stolen->ptr; - result->size = stolen->size; - stolen->ptr = NULL; - return 1; -} - -static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, - const char *path_unused, - mmfile_t *orig, - mmfile_t *src1, const char *name1, - mmfile_t *src2, const char *name2, - mmbuffer_t *result) -{ - xpparam_t xpp; - - if (buffer_is_binary(orig->ptr, orig->size) || - buffer_is_binary(src1->ptr, src1->size) || - buffer_is_binary(src2->ptr, src2->size)) { - warning("Cannot merge binary files: %s vs. %s\n", - name1, name2); - return ll_binary_merge(drv_unused, path_unused, - orig, src1, name1, - src2, name2, - result); - } - - memset(&xpp, 0, sizeof(xpp)); - return xdl_merge(orig, - src1, name1, - src2, name2, - &xpp, XDL_MERGE_ZEALOUS, - result); -} - -static int ll_union_merge(const struct ll_merge_driver *drv_unused, - const char *path_unused, - mmfile_t *orig, - mmfile_t *src1, const char *name1, - mmfile_t *src2, const char *name2, - mmbuffer_t *result) -{ - char *src, *dst; - long size; - const int marker_size = 7; - - int status = ll_xdl_merge(drv_unused, path_unused, - orig, src1, NULL, src2, NULL, result); - if (status <= 0) - return status; - size = result->size; - src = dst = result->ptr; - while (size) { - char ch; - if ((marker_size < size) && - (*src == '<' || *src == '=' || *src == '>')) { - int i; - ch = *src; - for (i = 0; i < marker_size; i++) - if (src[i] != ch) - goto not_a_marker; - if (src[marker_size] != '\n') - goto not_a_marker; - src += marker_size + 1; - size -= marker_size + 1; - continue; - } - not_a_marker: - do { - ch = *src++; - *dst++ = ch; - size--; - } while (ch != '\n' && size); - } - result->size = dst - result->ptr; - return 0; -} - -#define LL_BINARY_MERGE 0 -#define LL_TEXT_MERGE 1 -#define LL_UNION_MERGE 2 -static struct ll_merge_driver ll_merge_drv[] = { - { "binary", "built-in binary merge", ll_binary_merge }, - { "text", "built-in 3-way text merge", ll_xdl_merge }, - { "union", "built-in union merge", ll_union_merge }, -}; - -static void create_temp(mmfile_t *src, char *path) -{ - int fd; - - strcpy(path, ".merge_file_XXXXXX"); - fd = xmkstemp(path); - if (write_in_full(fd, src->ptr, src->size) != src->size) - die("unable to write temp-file"); - close(fd); -} - -/* - * User defined low-level merge driver support. - */ -static int ll_ext_merge(const struct ll_merge_driver *fn, - const char *path, - mmfile_t *orig, - mmfile_t *src1, const char *name1, - mmfile_t *src2, const char *name2, - mmbuffer_t *result) -{ - char temp[3][50]; - char cmdbuf[2048]; - struct interp table[] = { - { "%O" }, - { "%A" }, - { "%B" }, - }; - struct child_process child; - const char *args[20]; - int status, fd, i; - struct stat st; - - if (fn->cmdline == NULL) - die("custom merge driver %s lacks command line.", fn->name); - - result->ptr = NULL; - result->size = 0; - create_temp(orig, temp[0]); - create_temp(src1, temp[1]); - create_temp(src2, temp[2]); - - interp_set_entry(table, 0, temp[0]); - interp_set_entry(table, 1, temp[1]); - interp_set_entry(table, 2, temp[2]); - - output(1, "merging %s using %s", path, - fn->description ? fn->description : fn->name); - - interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3); - - memset(&child, 0, sizeof(child)); - child.argv = args; - args[0] = "sh"; - args[1] = "-c"; - args[2] = cmdbuf; - args[3] = NULL; - - status = run_command(&child); - if (status < -ERR_RUN_COMMAND_FORK) - ; /* failure in run-command */ - else - status = -status; - fd = open(temp[1], O_RDONLY); - if (fd < 0) - goto bad; - if (fstat(fd, &st)) - goto close_bad; - result->size = st.st_size; - result->ptr = xmalloc(result->size + 1); - if (read_in_full(fd, result->ptr, result->size) != result->size) { - free(result->ptr); - result->ptr = NULL; - result->size = 0; - } - close_bad: - close(fd); - bad: - for (i = 0; i < 3; i++) - unlink(temp[i]); - return status; -} - -/* - * merge.default and merge.driver configuration items - */ -static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail; -static const char *default_ll_merge; - -static int read_merge_config(const char *var, const char *value) -{ - struct ll_merge_driver *fn; - const char *ep, *name; - int namelen; - - if (!strcmp(var, "merge.default")) { - if (!value) - return config_error_nonbool(var); - default_ll_merge = strdup(value); - return 0; - } - - /* - * We are not interested in anything but "merge..variable"; - * especially, we do not want to look at variables such as - * "merge.summary", "merge.tool", and "merge.verbosity". - */ - if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5) - return 0; - - /* - * Find existing one as we might be processing merge..var2 - * after seeing merge..var1. - */ - name = var + 6; - namelen = ep - name; - for (fn = ll_user_merge; fn; fn = fn->next) - if (!strncmp(fn->name, name, namelen) && !fn->name[namelen]) - break; - if (!fn) { - fn = xcalloc(1, sizeof(struct ll_merge_driver)); - fn->name = xmemdupz(name, namelen); - fn->fn = ll_ext_merge; - *ll_user_merge_tail = fn; - ll_user_merge_tail = &(fn->next); - } - - ep++; - - if (!strcmp("name", ep)) { - if (!value) - return config_error_nonbool(var); - fn->description = strdup(value); - return 0; - } - - if (!strcmp("driver", ep)) { - if (!value) - return config_error_nonbool(var); - /* - * merge..driver specifies the command line: - * - * command-line - * - * The command-line will be interpolated with the following - * tokens and is given to the shell: - * - * %O - temporary file name for the merge base. - * %A - temporary file name for our version. - * %B - temporary file name for the other branches' version. - * - * The external merge driver should write the results in the - * file named by %A, and signal that it has done with zero exit - * status. - */ - fn->cmdline = strdup(value); - return 0; - } - - if (!strcmp("recursive", ep)) { - if (!value) - return config_error_nonbool(var); - fn->recursive = strdup(value); - return 0; - } - - return 0; -} - -static void initialize_ll_merge(void) -{ - if (ll_user_merge_tail) - return; - ll_user_merge_tail = &ll_user_merge; - git_config(read_merge_config); -} - -static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr) -{ - struct ll_merge_driver *fn; - const char *name; - int i; - - initialize_ll_merge(); - - if (ATTR_TRUE(merge_attr)) - return &ll_merge_drv[LL_TEXT_MERGE]; - else if (ATTR_FALSE(merge_attr)) - return &ll_merge_drv[LL_BINARY_MERGE]; - else if (ATTR_UNSET(merge_attr)) { - if (!default_ll_merge) - return &ll_merge_drv[LL_TEXT_MERGE]; - else - name = default_ll_merge; - } - else - name = merge_attr; - - for (fn = ll_user_merge; fn; fn = fn->next) - if (!strcmp(fn->name, name)) - return fn; - - for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++) - if (!strcmp(ll_merge_drv[i].name, name)) - return &ll_merge_drv[i]; - - /* default to the 3-way */ - return &ll_merge_drv[LL_TEXT_MERGE]; -} - -static const char *git_path_check_merge(const char *path) -{ - static struct git_attr_check attr_merge_check; - - if (!attr_merge_check.attr) - attr_merge_check.attr = git_attr("merge", 5); - - if (git_checkattr(path, 1, &attr_merge_check)) - return NULL; - return attr_merge_check.value; -} - -static int ll_merge(mmbuffer_t *result_buf, - struct diff_filespec *o, - struct diff_filespec *a, - struct diff_filespec *b, - const char *branch1, - const char *branch2) +static int merge_3way(mmbuffer_t *result_buf, + struct diff_filespec *o, + struct diff_filespec *a, + struct diff_filespec *b, + const char *branch1, + const char *branch2) { mmfile_t orig, src1, src2; char *name1, *name2; int merge_status; - const char *ll_driver_name; - const struct ll_merge_driver *driver; name1 = xstrdup(mkpath("%s:%s", branch1, a->path)); name2 = xstrdup(mkpath("%s:%s", branch2, b->path)); @@ -990,14 +640,9 @@ static int ll_merge(mmbuffer_t *result_buf, fill_mm(a->sha1, &src1); fill_mm(b->sha1, &src2); - ll_driver_name = git_path_check_merge(a->path); - driver = find_ll_merge_driver(ll_driver_name); - - if (index_only && driver->recursive) - driver = find_ll_merge_driver(driver->recursive); - merge_status = driver->fn(driver, a->path, - &orig, &src1, name1, &src2, name2, - result_buf); + merge_status = ll_merge(result_buf, a->path, &orig, + &src1, name1, &src2, name2, + index_only); free(name1); free(name2); @@ -1038,8 +683,8 @@ static struct merge_file_info merge_file(struct diff_filespec *o, mmbuffer_t result_buf; int merge_status; - merge_status = ll_merge(&result_buf, o, a, b, - branch1, branch2); + merge_status = merge_3way(&result_buf, o, a, b, + branch1, branch2); if ((merge_status < 0) || !result_buf.ptr) die("Failed to execute internal merge"); From 8177631547e4d10ae071def07eff83e056092bfa Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 24 Dec 2007 00:51:01 -0800 Subject: [PATCH 02/42] expose a helper function peel_to_type(). This helper function is the core of "$object^{type}" parser. Now it is made available to callers outside sha1_name.c --- cache.h | 3 +++ sha1_name.c | 57 +++++++++++++++++++++++++++++++++++------------------ 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/cache.h b/cache.h index e1000bccb2..4bfb1ba499 100644 --- a/cache.h +++ b/cache.h @@ -499,6 +499,9 @@ extern void *read_object_with_reference(const unsigned char *sha1, unsigned long *size, unsigned char *sha1_ret); +extern struct object *peel_to_type(const char *name, int namelen, + struct object *o, enum object_type); + enum date_mode { DATE_NORMAL = 0, DATE_RELATIVE, diff --git a/sha1_name.c b/sha1_name.c index ed3c867d6a..739f1b2d49 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -423,6 +423,37 @@ static int get_nth_ancestor(const char *name, int len, return 0; } +struct object *peel_to_type(const char *name, int namelen, + struct object *o, enum object_type expected_type) +{ + if (name && !namelen) + namelen = strlen(name); + if (!o) { + unsigned char sha1[20]; + if (get_sha1_1(name, namelen, sha1)) + return NULL; + o = parse_object(sha1); + } + while (1) { + if (!o || (!o->parsed && !parse_object(o->sha1))) + return NULL; + if (o->type == expected_type) + return o; + if (o->type == OBJ_TAG) + o = ((struct tag*) o)->tagged; + else if (o->type == OBJ_COMMIT) + o = &(((struct commit *) o)->tree->object); + else { + if (name) + error("%.*s: expected %s type, but the object " + "dereferences to %s type", + namelen, name, typename(expected_type), + typename(o->type)); + return NULL; + } + } +} + static int peel_onion(const char *name, int len, unsigned char *sha1) { unsigned char outer[20]; @@ -474,29 +505,17 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) hashcpy(sha1, o->sha1); } else { - /* At this point, the syntax look correct, so + /* + * At this point, the syntax look correct, so * if we do not get the needed object, we should * barf. */ - - while (1) { - if (!o || (!o->parsed && !parse_object(o->sha1))) - return -1; - if (o->type == expected_type) { - hashcpy(sha1, o->sha1); - return 0; - } - if (o->type == OBJ_TAG) - o = ((struct tag*) o)->tagged; - else if (o->type == OBJ_COMMIT) - o = &(((struct commit *) o)->tree->object); - else - return error("%.*s: expected %s type, but the object dereferences to %s type", - len, name, typename(expected_type), - typename(o->type)); - if (!o->parsed) - parse_object(o->sha1); + o = peel_to_type(name, len, o, expected_type); + if (o) { + hashcpy(sha1, o->sha1); + return 0; } + return -1; } return 0; } From 363d59df1a773039d0c69ad8c3109a56bb1b491d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 29 Feb 2008 01:44:56 +0000 Subject: [PATCH 03/42] path-list: add functions to work with unsorted lists Up to now, path-lists were sorted at all times. But sometimes it is much more convenient to build the list and sort it at the end, or sort it not at all. Add path_list_append() and sort_path_list() to allow that. Also, add the unsorted_path_list_has_path() function, to do a linear search. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- path-list.c | 30 ++++++++++++++++++++++++++++++ path-list.h | 10 ++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/path-list.c b/path-list.c index 3d83b7ba9e..92e5cf20fe 100644 --- a/path-list.c +++ b/path-list.c @@ -102,3 +102,33 @@ void print_path_list(const char *text, const struct path_list *p) for (i = 0; i < p->nr; i++) printf("%s:%p\n", p->items[i].path, p->items[i].util); } + +struct path_list_item *path_list_append(const char *path, struct path_list *list) +{ + ALLOC_GROW(list->items, list->nr + 1, list->alloc); + list->items[list->nr].path = + list->strdup_paths ? xstrdup(path) : (char *)path; + return list->items + list->nr++; +} + +static int cmp_items(const void *a, const void *b) +{ + const struct path_list_item *one = a; + const struct path_list_item *two = b; + return strcmp(one->path, two->path); +} + +void sort_path_list(struct path_list *list) +{ + qsort(list->items, list->nr, sizeof(*list->items), cmp_items); +} + +int unsorted_path_list_has_path(struct path_list *list, const char *path) +{ + int i; + for (i = 0; i < list->nr; i++) + if (!strcmp(path, list->items[i].path)) + return 1; + return 0; +} + diff --git a/path-list.h b/path-list.h index 5931e2cc0c..ca2cbbaa4d 100644 --- a/path-list.h +++ b/path-list.h @@ -13,10 +13,16 @@ struct path_list }; void print_path_list(const char *text, const struct path_list *p); - -int path_list_has_path(const struct path_list *list, const char *path); void path_list_clear(struct path_list *list, int free_util); + +/* Use these functions only on sorted lists: */ +int path_list_has_path(const struct path_list *list, const char *path); struct path_list_item *path_list_insert(const char *path, struct path_list *list); struct path_list_item *path_list_lookup(const char *path, struct path_list *list); +/* Use these functions only on unsorted lists: */ +struct path_list_item *path_list_append(const char *path, struct path_list *list); +void sort_path_list(struct path_list *list); +int unsorted_path_list_has_path(struct path_list *list, const char *path); + #endif /* PATH_LIST_H */ From a0ec9d25d9f1c1469ec375169557a1c17d486993 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 29 Feb 2008 01:45:09 +0000 Subject: [PATCH 04/42] parseopt: add flag to stop on first non option Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- parse-options.c | 2 ++ parse-options.h | 1 + 2 files changed, 3 insertions(+) diff --git a/parse-options.c b/parse-options.c index d9562ba504..be35785ce8 100644 --- a/parse-options.c +++ b/parse-options.c @@ -249,6 +249,8 @@ int parse_options(int argc, const char **argv, const struct option *options, const char *arg = args.argv[0]; if (*arg != '-' || !arg[1]) { + if (flags & PARSE_OPT_STOP_AT_NON_OPTION) + break; argv[j++] = args.argv[0]; continue; } diff --git a/parse-options.h b/parse-options.h index 102ac31fb7..0d40cd2f12 100644 --- a/parse-options.h +++ b/parse-options.h @@ -18,6 +18,7 @@ enum parse_opt_type { enum parse_opt_flags { PARSE_OPT_KEEP_DASHDASH = 1, + PARSE_OPT_STOP_AT_NON_OPTION = 2, }; enum parse_opt_option_flags { From 4704640b611dfb275f6a74c9a9be5e309260a71c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 29 Feb 2008 01:45:24 +0000 Subject: [PATCH 05/42] Test "git remote show" and "git remote prune" While at it, also fix a few instances where a cd was done outside of a subshell. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/t5505-remote.sh | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 4fc62f550c..e1e0a18f9d 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -94,4 +94,38 @@ test_expect_success 'remove remote' ' ) ' +cat > test/expect << EOF +* remote origin + URL: $(pwd)/one/.git + Remote branch(es) merged with 'git pull' while on branch master + master + New remote branches (next fetch will store in remotes/origin) + master + Tracked remote branches + side master +EOF + +test_expect_success 'show' ' + (cd test && + git config --add remote.origin.fetch \ + refs/heads/master:refs/heads/upstream && + git fetch && + git branch -d -r origin/master && + (cd ../one && + echo 1 > file && + git commit -m update file) && + git remote show origin > output && + git diff expect output) +' + +test_expect_success 'prune' ' + (cd one && + git branch -m side side2) && + (cd test && + git fetch origin && + git remote prune origin && + git rev-parse refs/remotes/origin/side2 && + ! git rev-parse refs/remotes/origin/side) +' + test_done From 211c89682eeef310f39022b91e88d07cd5784953 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 29 Feb 2008 01:45:45 +0000 Subject: [PATCH 06/42] Make git-remote a builtin Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Makefile | 3 +- builtin-remote.c | 547 ++++++++++++++++++ builtin.h | 1 + .../examples/git-remote.perl | 0 git.c | 1 + remote.c | 3 +- remote.h | 1 + t/t5505-remote.sh | 4 +- 8 files changed, 556 insertions(+), 4 deletions(-) create mode 100644 builtin-remote.c rename git-remote.perl => contrib/examples/git-remote.perl (100%) diff --git a/Makefile b/Makefile index a5b6eebf1a..f9ea96a101 100644 --- a/Makefile +++ b/Makefile @@ -243,7 +243,7 @@ SCRIPT_SH = \ SCRIPT_PERL = \ git-add--interactive.perl \ git-archimport.perl git-cvsimport.perl git-relink.perl \ - git-cvsserver.perl git-remote.perl git-cvsexportcommit.perl \ + git-cvsserver.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ @@ -380,6 +380,7 @@ BUILTIN_OBJS = \ builtin-push.o \ builtin-read-tree.o \ builtin-reflog.o \ + builtin-remote.o \ builtin-send-pack.o \ builtin-config.o \ builtin-rerere.o \ diff --git a/builtin-remote.c b/builtin-remote.c new file mode 100644 index 0000000000..25b02275da --- /dev/null +++ b/builtin-remote.c @@ -0,0 +1,547 @@ +#include "cache.h" +#include "parse-options.h" +#include "transport.h" +#include "remote.h" +#include "path-list.h" +#include "strbuf.h" +#include "run-command.h" +#include "refs.h" + +static const char * const builtin_remote_usage[] = { + "git remote", + "git remote add ", + "git remote rm ", + "git remote show ", + "git remote prune ", + "git remote update [group]", + NULL +}; + +static int verbose; + +static inline int postfixcmp(const char *string, const char *postfix) +{ + int len1 = strlen(string), len2 = strlen(postfix); + if (len1 < len2) + return 1; + return strcmp(string + len1 - len2, postfix); +} + +static inline const char *skip_prefix(const char *name, const char *prefix) +{ + return !name ? "" : + prefixcmp(name, prefix) ? name : name + strlen(prefix); +} + +static int opt_parse_track(const struct option *opt, const char *arg, int not) +{ + struct path_list *list = opt->value; + if (not) + path_list_clear(list, 0); + else + path_list_append(arg, list); + return 0; +} + +static int fetch_remote(const char *name) +{ + const char *argv[] = { "fetch", name, NULL }; + if (run_command_v_opt(argv, RUN_GIT_CMD)) + return error("Could not fetch %s", name); + return 0; +} + +static int add(int argc, const char **argv) +{ + int fetch = 0, mirror = 0; + struct path_list track = { NULL, 0, 0 }; + const char *master = NULL; + struct remote *remote; + struct strbuf buf, buf2; + const char *name, *url; + int i; + + struct option options[] = { + OPT_GROUP("add specific options"), + OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"), + OPT_CALLBACK('t', "track", &track, "branch", + "branch(es) to track", opt_parse_track), + OPT_STRING('m', "master", &master, "branch", "master branch"), + OPT_BOOLEAN(0, "mirror", &mirror, "no separate remotes"), + OPT_END() + }; + + argc = parse_options(argc, argv, options, builtin_remote_usage, 0); + + if (argc < 2) + usage_with_options(builtin_remote_usage, options); + + name = argv[0]; + url = argv[1]; + + remote = remote_get(name); + if (remote && (remote->url_nr > 1 || strcmp(name, remote->url[0]) || + remote->fetch_refspec_nr)) + die("remote %s already exists.", name); + + strbuf_init(&buf, 0); + strbuf_init(&buf2, 0); + + strbuf_addf(&buf, "remote.%s.url", name); + if (git_config_set(buf.buf, url)) + return 1; + + if (track.nr == 0) + path_list_append("*", &track); + for (i = 0; i < track.nr; i++) { + struct path_list_item *item = track.items + i; + + strbuf_reset(&buf); + strbuf_addf(&buf, "remote.%s.fetch", name); + + strbuf_reset(&buf2); + if (mirror) + strbuf_addf(&buf2, "refs/%s:refs/%s", + item->path, item->path); + else + strbuf_addf(&buf2, "refs/heads/%s:refs/remotes/%s/%s", + item->path, name, item->path); + if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0)) + return 1; + } + + if (fetch && fetch_remote(name)) + return 1; + + if (master) { + strbuf_reset(&buf); + strbuf_addf(&buf, "refs/remotes/%s/HEAD", name); + + strbuf_reset(&buf2); + strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master); + + if (create_symref(buf.buf, buf2.buf, "remote add")) + return error("Could not setup master '%s'", master); + } + + strbuf_release(&buf); + strbuf_release(&buf2); + path_list_clear(&track, 0); + + return 0; +} + +struct branch_info { + char *remote; + struct path_list merge; +}; + +static struct path_list branch_list; + +static int config_read_branches(const char *key, const char *value) +{ + if (!prefixcmp(key, "branch.")) { + char *name; + struct path_list_item *item; + struct branch_info *info; + enum { REMOTE, MERGE } type; + + key += 7; + if (!postfixcmp(key, ".remote")) { + name = xstrndup(key, strlen(key) - 7); + type = REMOTE; + } else if (!postfixcmp(key, ".merge")) { + name = xstrndup(key, strlen(key) - 6); + type = MERGE; + } else + return 0; + + item = path_list_insert(name, &branch_list); + + if (!item->util) + item->util = xcalloc(sizeof(struct branch_info), 1); + info = item->util; + if (type == REMOTE) { + if (info->remote) + warning("more than one branch.%s", key); + info->remote = xstrdup(value); + } else { + char *space = strchr(value, ' '); + value = skip_prefix(value, "refs/heads/"); + while (space) { + char *merge; + merge = xstrndup(value, space - value); + path_list_append(merge, &info->merge); + value = skip_prefix(space + 1, "refs/heads/"); + space = strchr(value, ' '); + } + path_list_append(xstrdup(value), &info->merge); + } + } + return 0; +} + +static void read_branches(void) +{ + if (branch_list.nr) + return; + git_config(config_read_branches); + sort_path_list(&branch_list); +} + +struct ref_states { + struct remote *remote; + struct strbuf remote_prefix; + struct path_list new, stale, tracked; +}; + +static int handle_one_branch(const char *refname, + const unsigned char *sha1, int flags, void *cb_data) +{ + struct ref_states *states = cb_data; + struct refspec refspec; + + memset(&refspec, 0, sizeof(refspec)); + refspec.dst = (char *)refname; + if (!remote_find_tracking(states->remote, &refspec)) { + struct path_list_item *item; + const char *name = skip_prefix(refspec.src, "refs/heads/"); + if (unsorted_path_list_has_path(&states->tracked, name) || + unsorted_path_list_has_path(&states->new, + name)) + return 0; + item = path_list_append(name, &states->stale); + item->util = xstrdup(refname); + } + return 0; +} + +static int get_ref_states(const struct ref *ref, struct ref_states *states) +{ + struct ref *fetch_map = NULL, **tail = &fetch_map; + int i; + + for (i = 0; i < states->remote->fetch_refspec_nr; i++) + if (get_fetch_map(ref, states->remote->fetch + i, &tail, 1)) + die("Could not get fetch map for refspec %s", + states->remote->fetch_refspec[i]); + + states->new.strdup_paths = states->tracked.strdup_paths = 1; + for (ref = fetch_map; ref; ref = ref->next) { + struct path_list *target = &states->tracked; + unsigned char sha1[20]; + void *util = NULL; + + if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1)) + target = &states->new; + else { + target = &states->tracked; + if (hashcmp(sha1, ref->new_sha1)) + util = &states; + } + path_list_append(skip_prefix(ref->name, "refs/heads/"), + target)->util = util; + } + free_refs(fetch_map); + + strbuf_addf(&states->remote_prefix, + "refs/remotes/%s/", states->remote->name); + for_each_ref(handle_one_branch, states); + sort_path_list(&states->stale); + + return 0; +} + +struct branches_for_remote { + const char *prefix; + struct path_list *branches; +}; + +static int add_branch_for_removal(const char *refname, + const unsigned char *sha1, int flags, void *cb_data) +{ + struct branches_for_remote *branches = cb_data; + + if (!prefixcmp(refname, branches->prefix)) { + struct path_list_item *item; + item = path_list_append(refname, branches->branches); + item->util = xmalloc(20); + hashcpy(item->util, sha1); + } + + return 0; +} + +static int remove_branches(struct path_list *branches) +{ + int i, result = 0; + for (i = 0; i < branches->nr; i++) { + struct path_list_item *item = branches->items + i; + const char *refname = item->path; + unsigned char *sha1 = item->util; + + if (delete_ref(refname, sha1)) + result |= error("Could not remove branch %s", refname); + } + return result; +} + +static int rm(int argc, const char **argv) +{ + struct option options[] = { + OPT_END() + }; + struct remote *remote; + struct strbuf buf; + struct path_list branches = { NULL, 0, 0, 1 }; + struct branches_for_remote cb_data = { NULL, &branches }; + int i; + + if (argc != 2) + usage_with_options(builtin_remote_usage, options); + + remote = remote_get(argv[1]); + if (!remote) + die("No such remote: %s", argv[1]); + + strbuf_init(&buf, 0); + strbuf_addf(&buf, "remote.%s", remote->name); + if (git_config_rename_section(buf.buf, NULL) < 1) + return error("Could not remove config section '%s'", buf.buf); + + read_branches(); + for (i = 0; i < branch_list.nr; i++) { + struct path_list_item *item = branch_list.items + i; + struct branch_info *info = item->util; + if (info->remote && !strcmp(info->remote, remote->name)) { + const char *keys[] = { "remote", "merge", NULL }, **k; + for (k = keys; *k; k++) { + strbuf_reset(&buf); + strbuf_addf(&buf, "branch.%s.%s", + item->path, *k); + if (git_config_set(buf.buf, NULL)) { + strbuf_release(&buf); + return -1; + } + } + } + } + + /* + * We cannot just pass a function to for_each_ref() which deletes + * the branches one by one, since for_each_ref() relies on cached + * refs, which are invalidated when deleting a branch. + */ + strbuf_reset(&buf); + strbuf_addf(&buf, "refs/remotes/%s/", remote->name); + cb_data.prefix = buf.buf; + i = for_each_ref(add_branch_for_removal, &cb_data); + strbuf_release(&buf); + + if (!i) + i = remove_branches(&branches); + path_list_clear(&branches, 1); + + return i; +} + +static void show_list(const char *title, struct path_list *list) +{ + int i; + + if (!list->nr) + return; + + printf(title, list->nr > 1 ? "es" : ""); + printf("\n "); + for (i = 0; i < list->nr; i++) + printf("%s%s", i ? " " : "", list->items[i].path); + printf("\n"); +} + +static int show_or_prune(int argc, const char **argv, int prune) +{ + int dry_run = 0, result = 0; + struct option options[] = { + OPT_GROUP("show specific options"), + OPT__DRY_RUN(&dry_run), + OPT_END() + }; + struct ref_states states; + + argc = parse_options(argc, argv, options, builtin_remote_usage, 0); + + if (argc < 1) + usage_with_options(builtin_remote_usage, options); + + memset(&states, 0, sizeof(states)); + for (; argc; argc--, argv++) { + struct transport *transport; + const struct ref *ref; + struct strbuf buf; + int i, got_states; + + states.remote = remote_get(*argv); + if (!states.remote) + return error("No such remote: %s", *argv); + transport = transport_get(NULL, states.remote->url_nr > 0 ? + states.remote->url[0] : NULL); + ref = transport_get_remote_refs(transport); + + read_branches(); + got_states = get_ref_states(ref, &states); + if (got_states) + result = error("Error getting local info for '%s'", + states.remote->name); + + if (prune) { + struct strbuf buf; + + strbuf_init(&buf, 0); + for (i = 0; i < states.stale.nr; i++) { + strbuf_reset(&buf); + strbuf_addf(&buf, "refs/remotes/%s/%s", *argv, + states.stale.items[i].path); + result |= delete_ref(buf.buf, NULL); + } + + strbuf_release(&buf); + goto cleanup_states; + } + + printf("* remote %s\n URL: %s\n", *argv, + states.remote->url_nr > 0 ? + states.remote->url[0] : "(no URL)"); + + for (i = 0; i < branch_list.nr; i++) { + struct path_list_item *branch = branch_list.items + i; + struct branch_info *info = branch->util; + int j; + + if (!info->merge.nr || strcmp(*argv, info->remote)) + continue; + printf(" Remote branch%s merged with 'git pull' " + "while on branch %s\n ", + info->merge.nr > 1 ? "es" : "", + branch->path); + for (j = 0; j < info->merge.nr; j++) + printf(" %s", info->merge.items[j].path); + printf("\n"); + } + + if (got_states) + continue; + strbuf_init(&buf, 0); + strbuf_addf(&buf, " New remote branch%%s (next fetch will " + "store in remotes/%s)", states.remote->name); + show_list(buf.buf, &states.new); + strbuf_release(&buf); + show_list(" Stale tracking branch%s (use 'git remote prune')", + &states.stale); + show_list(" Tracked remote branch%s", + &states.tracked); + + if (states.remote->push_refspec_nr) { + printf(" Local branch%s pushed with 'git push'\n ", + states.remote->push_refspec_nr > 1 ? + "es" : ""); + for (i = 0; i < states.remote->push_refspec_nr; i++) { + struct refspec *spec = states.remote->push + i; + printf(" %s%s%s%s", spec->force ? "+" : "", + skip_prefix(spec->src, "refs/heads/"), + spec->dst ? ":" : "", + skip_prefix(spec->dst, "refs/heads/")); + } + } +cleanup_states: + /* NEEDSWORK: free remote */ + path_list_clear(&states.new, 0); + path_list_clear(&states.stale, 0); + path_list_clear(&states.tracked, 0); + } + + return result; +} + +static int update_one(struct remote *remote, void *priv) +{ + if (!remote->skip_default_update) + return fetch_remote(remote->name); + return 0; +} + +static int update(int argc, const char **argv) +{ + int i; + + if (argc < 2) + return for_each_remote(update_one, NULL); + + for (i = 1; i < argc; i++) + if (fetch_remote(argv[i])) + return 1; + return 0; +} + +static int get_one_entry(struct remote *remote, void *priv) +{ + struct path_list *list = priv; + + path_list_append(remote->name, list)->util = remote->url_nr ? + (void *)remote->url[0] : NULL; + if (remote->url_nr > 1) + warning("Remote %s has more than one URL", remote->name); + + return 0; +} + +static int show_all(void) +{ + struct path_list list = { NULL, 0, 0 }; + int result = for_each_remote(get_one_entry, &list); + + if (!result) { + int i; + + sort_path_list(&list); + for (i = 0; i < list.nr; i++) { + struct path_list_item *item = list.items + i; + printf("%s%s%s\n", item->path, + verbose ? "\t" : "", + verbose && item->util ? + (const char *)item->util : ""); + } + } + return result; +} + +int cmd_remote(int argc, const char **argv, const char *prefix) +{ + struct option options[] = { + OPT__VERBOSE(&verbose), + OPT_END() + }; + int result; + + argc = parse_options(argc, argv, options, builtin_remote_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (argc < 1) + result = show_all(); + else if (!strcmp(argv[0], "add")) + result = add(argc, argv); + else if (!strcmp(argv[0], "rm")) + result = rm(argc, argv); + else if (!strcmp(argv[0], "show")) + result = show_or_prune(argc, argv, 0); + else if (!strcmp(argv[0], "prune")) + result = show_or_prune(argc, argv, 1); + else if (!strcmp(argv[0], "update")) + result = update(argc, argv); + else { + error("Unknown subcommand: %s", argv[0]); + usage_with_options(builtin_remote_usage, options); + } + + return result ? 1 : 0; +} diff --git a/builtin.h b/builtin.h index 674c8a141f..95126fd0c1 100644 --- a/builtin.h +++ b/builtin.h @@ -67,6 +67,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); extern int cmd_push(int argc, const char **argv, const char *prefix); extern int cmd_read_tree(int argc, const char **argv, const char *prefix); extern int cmd_reflog(int argc, const char **argv, const char *prefix); +extern int cmd_remote(int argc, const char **argv, const char *prefix); extern int cmd_config(int argc, const char **argv, const char *prefix); extern int cmd_rerere(int argc, const char **argv, const char *prefix); extern int cmd_reset(int argc, const char **argv, const char *prefix); diff --git a/git-remote.perl b/contrib/examples/git-remote.perl similarity index 100% rename from git-remote.perl rename to contrib/examples/git-remote.perl diff --git a/git.c b/git.c index 9cca81a60e..1e3eb1065f 100644 --- a/git.c +++ b/git.c @@ -334,6 +334,7 @@ static void handle_internal_command(int argc, const char **argv) { "push", cmd_push, RUN_SETUP }, { "read-tree", cmd_read_tree, RUN_SETUP }, { "reflog", cmd_reflog, RUN_SETUP }, + { "remote", cmd_remote, RUN_SETUP }, { "repo-config", cmd_config }, { "rerere", cmd_rerere, RUN_SETUP }, { "reset", cmd_reset, RUN_SETUP }, diff --git a/remote.c b/remote.c index 7e1937286b..f3f7375eea 100644 --- a/remote.c +++ b/remote.c @@ -357,7 +357,8 @@ static int handle_config(const char *key, const char *value) remote->fetch_tags = -1; } else if (!strcmp(subkey, ".proxy")) { remote->http_proxy = xstrdup(value); - } + } else if (!strcmp(subkey, ".skipdefaultupdate")) + remote->skip_default_update = 1; return 0; } diff --git a/remote.h b/remote.h index 0f6033fb25..f1dedf15f6 100644 --- a/remote.h +++ b/remote.h @@ -25,6 +25,7 @@ struct remote { * 2 to always fetch tags */ int fetch_tags; + int skip_default_update; const char *receivepack; const char *uploadpack; diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index e1e0a18f9d..5986982adf 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -97,9 +97,9 @@ test_expect_success 'remove remote' ' cat > test/expect << EOF * remote origin URL: $(pwd)/one/.git - Remote branch(es) merged with 'git pull' while on branch master + Remote branch merged with 'git pull' while on branch master master - New remote branches (next fetch will store in remotes/origin) + New remote branch (next fetch will store in remotes/origin) master Tracked remote branches side master From 4ebc914c880cf724222a6e5097a21a85ed8e5951 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 29 Feb 2008 01:46:07 +0000 Subject: [PATCH 07/42] builtin-remote: prune remotes correctly that were added with --mirror This adds special handling for mirror remotes. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-remote.c | 16 +++++++++++++--- t/t5505-remote.sh | 16 ++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/builtin-remote.c b/builtin-remote.c index 25b02275da..d0c07c7a0b 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -396,12 +396,22 @@ static int show_or_prune(int argc, const char **argv, int prune) if (prune) { struct strbuf buf; + int prefix_len; strbuf_init(&buf, 0); + if (states.remote->fetch_refspec_nr == 1 && + states.remote->fetch->pattern && + !strcmp(states.remote->fetch->src, + states.remote->fetch->dst)) + /* handle --mirror remote */ + strbuf_addstr(&buf, "refs/heads/"); + else + strbuf_addf(&buf, "refs/remotes/%s/", *argv); + prefix_len = buf.len; + for (i = 0; i < states.stale.nr; i++) { - strbuf_reset(&buf); - strbuf_addf(&buf, "refs/remotes/%s/%s", *argv, - states.stale.items[i].path); + strbuf_setlen(&buf, prefix_len); + strbuf_addstr(&buf, states.stale.items[i].path); result |= delete_ref(buf.buf, NULL); } diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 5986982adf..0a25c8b71c 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -128,4 +128,20 @@ test_expect_success 'prune' ' ! git rev-parse refs/remotes/origin/side) ' +test_expect_success 'add --mirror && prune' ' + (mkdir mirror && + cd mirror && + git init && + git remote add --mirror -f origin ../one) && + (cd one && + git branch -m side2 side) && + (cd mirror && + git rev-parse --verify refs/heads/side2 && + ! git rev-parse --verify refs/heads/side && + git fetch origin && + git remote prune origin && + ! git rev-parse --verify refs/heads/side2 && + git rev-parse --verify refs/heads/side) +' + test_done From 6217367859e92aa0bd67f02162d1f53e290e15e8 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 2 Mar 2008 05:31:59 +0000 Subject: [PATCH 08/42] remote show: Clean up connection correctly if object fetch wasn't done Like in ls-remote, we have to disconnect the transport after getting the remote refs. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-remote.c | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin-remote.c b/builtin-remote.c index d0c07c7a0b..98ff1b727e 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -387,6 +387,7 @@ static int show_or_prune(int argc, const char **argv, int prune) transport = transport_get(NULL, states.remote->url_nr > 0 ? states.remote->url[0] : NULL); ref = transport_get_remote_refs(transport); + transport_disconnect(transport); read_branches(); got_states = get_ref_states(ref, &states); From 84521ed6f2ccfd8dfe001806f83bb28e6a8934be Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 4 Mar 2008 11:23:53 +0000 Subject: [PATCH 09/42] remote: fix "update [group...]" The rewrite in C inadvertently broke updating with remote groups: when you pass parameters to "git remote update", it used to look up "remotes." for every parameter, and interpret the value as a list of remotes to update. Also, no parameter, or a single parameter "default" should update all remotes that have not been marked with "skipDefaultUpdate". Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-remote.c | 59 +++++++++++++++++++++++++----- t/t5505-remote.sh | 92 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 9 deletions(-) diff --git a/builtin-remote.c b/builtin-remote.c index 98ff1b727e..ca3bf265a9 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -474,24 +474,65 @@ cleanup_states: return result; } -static int update_one(struct remote *remote, void *priv) +static int get_one_remote_for_update(struct remote *remote, void *priv) { + struct path_list *list = priv; if (!remote->skip_default_update) - return fetch_remote(remote->name); + path_list_append(xstrdup(remote->name), list); + return 0; +} + +struct remote_group { + const char *name; + struct path_list *list; +} remote_group; + +static int get_remote_group(const char *key, const char *value) +{ + if (!prefixcmp(key, "remotes.") && + !strcmp(key + 8, remote_group.name)) { + /* split list by white space */ + int space = strcspn(value, " \t\n"); + while (*value) { + if (space > 1) + path_list_append(xstrndup(value, space), + remote_group.list); + value += space + (value[space] != '\0'); + space = strcspn(value, " \t\n"); + } + } + return 0; } static int update(int argc, const char **argv) { - int i; + int i, result = 0; + struct path_list list = { NULL, 0, 0, 0 }; + static const char *default_argv[] = { NULL, "default", NULL }; - if (argc < 2) - return for_each_remote(update_one, NULL); + if (argc < 2) { + argc = 2; + argv = default_argv; + } - for (i = 1; i < argc; i++) - if (fetch_remote(argv[i])) - return 1; - return 0; + remote_group.list = &list; + for (i = 1; i < argc; i++) { + remote_group.name = argv[i]; + result = git_config(get_remote_group); + } + + if (!result && !list.nr && argc == 2 && !strcmp(argv[1], "default")) + result = for_each_remote(get_one_remote_for_update, &list); + + for (i = 0; i < list.nr; i++) + result |= fetch_remote(list.items[i].path); + + /* all names were strdup()ed or strndup()ed */ + list.strdup_paths = 1; + path_list_clear(&list, 0); + + return result; } static int get_one_entry(struct remote *remote, void *priv) diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 0a25c8b71c..f45ea68f63 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -10,10 +10,12 @@ setup_repository () { git init && >file && git add file && + test_tick && git commit -m "Initial" && git checkout -b side && >elif && git add elif && + test_tick && git commit -m "Second" && git checkout master ) @@ -113,6 +115,7 @@ test_expect_success 'show' ' git branch -d -r origin/master && (cd ../one && echo 1 > file && + test_tick && git commit -m update file) && git remote show origin > output && git diff expect output) @@ -144,4 +147,93 @@ test_expect_success 'add --mirror && prune' ' git rev-parse --verify refs/heads/side) ' +cat > one/expect << EOF + apis/master + apis/side + drosophila/another + drosophila/master + drosophila/side +EOF + +test_expect_success 'update' ' + + (cd one && + git remote add drosophila ../two && + git remote add apis ../mirror && + git remote update && + git branch -r > output && + git diff expect output) + +' + +cat > one/expect << EOF + drosophila/another + drosophila/master + drosophila/side + manduca/master + manduca/side + megaloprepus/master + megaloprepus/side +EOF + +test_expect_success 'update with arguments' ' + + (cd one && + for b in $(git branch -r) + do + git branch -r -d $b || break + done && + git remote add manduca ../mirror && + git remote add megaloprepus ../mirror && + git config remotes.phobaeticus "drosophila megaloprepus" && + git config remotes.titanus manduca && + git remote update phobaeticus titanus && + git branch -r > output && + git diff expect output) + +' + +cat > one/expect << EOF + apis/master + apis/side + manduca/master + manduca/side + megaloprepus/master + megaloprepus/side +EOF + +test_expect_success 'update default' ' + + (cd one && + for b in $(git branch -r) + do + git branch -r -d $b || break + done && + git config remote.drosophila.skipDefaultUpdate true && + git remote update default && + git branch -r > output && + git diff expect output) + +' + +cat > one/expect << EOF + drosophila/another + drosophila/master + drosophila/side +EOF + +test_expect_success 'update default (overridden, with funny whitespace)' ' + + (cd one && + for b in $(git branch -r) + do + git branch -r -d $b || break + done && + git config remotes.default "$(printf "\t drosophila \n")" && + git remote update default && + git branch -r > output && + git diff expect output) + +' + test_done From 312fd92b069d26dce20f53e8fa4efea04b12e009 Mon Sep 17 00:00:00 2001 From: eric miao Date: Sun, 6 Jan 2008 17:47:05 +0800 Subject: [PATCH 10/42] git-gui: translate the remaining messages in zh_cn.po to chinese 'make' shows: MSGFMT po/zh_cn.msg 368 translated, 2 fuzzy, 1 untranslated message. 1. update the zh_cn.po and translate the remaining messages in chinese 2. correct some of the previously mis-translated messages 3. add a list of word interpretation in the head as a guideline for subsequent updatings and translations Signed-off-by: eric miao Acked-by: Xudong Guan Signed-off-by: Shawn O. Pearce --- po/zh_cn.po | 1321 ++++++++++++++++++++++++++++----------------------- 1 file changed, 722 insertions(+), 599 deletions(-) diff --git a/po/zh_cn.po b/po/zh_cn.po index 621c9479b2..f8697216f7 100644 --- a/po/zh_cn.po +++ b/po/zh_cn.po @@ -3,46 +3,63 @@ # This file is distributed under the same license as the git-gui package. # Xudong Guan , 2007. # +# Please use the following translation throughout the file for consistence: +# +# repository 版本库 +# commit 提交 +# revision 版本 +# branch 分支 +# tag 标签 +# annotation 标注 +# merge 合并 +# fast forward 快速合并(??) +# stage 缓存 (译自 index/cache) +# amend 修正 +# reset 复位 +# +# 2008-01-06 Eric Miao +# FIXME: checkout 的标准翻译 +# #, fuzzy msgid "" msgstr "" "Project-Id-Version: git-gui\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2007-10-10 04:04-0400\n" +"POT-Creation-Date: 2007-11-24 10:36+0100\n" "PO-Revision-Date: 2007-07-21 01:23-0700\n" -"Last-Translator: Xudong Guan \n" +"Last-Translator: Eric Miao \n" "Language-Team: Chinese\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: git-gui.sh:41 git-gui.sh:634 git-gui.sh:648 git-gui.sh:661 git-gui.sh:744 -#: git-gui.sh:763 +#: git-gui.sh:41 git-gui.sh:604 git-gui.sh:618 git-gui.sh:631 git-gui.sh:714 +#: git-gui.sh:733 msgid "git-gui: fatal error" -msgstr "" +msgstr "git-gui: 致命错误" -#: git-gui.sh:595 +#: git-gui.sh:565 #, tcl-format msgid "Invalid font specified in %s:" -msgstr "" +msgstr "%s 中指定的字体无效:" -#: git-gui.sh:620 +#: git-gui.sh:590 msgid "Main Font" -msgstr "" +msgstr "主要字体" -#: git-gui.sh:621 +#: git-gui.sh:591 msgid "Diff/Console Font" -msgstr "" +msgstr "Diff/控制终端字体" -#: git-gui.sh:635 +#: git-gui.sh:605 msgid "Cannot find git in PATH." -msgstr "" +msgstr "PATH 中没有找到 git" -#: git-gui.sh:662 +#: git-gui.sh:632 msgid "Cannot parse Git version string:" -msgstr "" +msgstr "无法解析 Git 的版本信息:" -#: git-gui.sh:680 +#: git-gui.sh:650 #, tcl-format msgid "" "Git version cannot be determined.\n" @@ -53,388 +70,386 @@ msgid "" "\n" "Assume '%s' is version 1.5.0?\n" msgstr "" +"无法确定 Git 的版本.\n" +"\n" +"%s 声明其版本为 '%s'.\n" +"\n" +"而 %s 需要 1.5.0 或这以后的 Git 版本.\n" +"\n" +"是否假定 '%s' 为版本 1.5.0?\n" -#: git-gui.sh:853 +#: git-gui.sh:888 msgid "Git directory not found:" -msgstr "" +msgstr "Git 目录无法找到:" -#: git-gui.sh:860 +#: git-gui.sh:895 msgid "Cannot move to top of working directory:" -msgstr "" +msgstr "无法移动到工作根目录:" -#: git-gui.sh:867 +#: git-gui.sh:902 msgid "Cannot use funny .git directory:" -msgstr "" +msgstr "无法使用 .git 目录:" -#: git-gui.sh:872 +#: git-gui.sh:907 msgid "No working directory" -msgstr "" +msgstr "没有工作目录" -#: git-gui.sh:1019 +#: git-gui.sh:1054 msgid "Refreshing file status..." -msgstr "" +msgstr "更新文件状态..." -#: git-gui.sh:1084 +#: git-gui.sh:1119 msgid "Scanning for modified files ..." -msgstr "" +msgstr "扫描修改过的文件 ..." -#: git-gui.sh:1259 lib/browser.tcl:245 -#, fuzzy +#: git-gui.sh:1294 lib/browser.tcl:245 msgid "Ready." -msgstr "重做" +msgstr "就绪" -#: git-gui.sh:1525 +#: git-gui.sh:1560 msgid "Unmodified" -msgstr "" +msgstr "未修改" -#: git-gui.sh:1527 +#: git-gui.sh:1562 msgid "Modified, not staged" -msgstr "" +msgstr "修改但未缓存" -#: git-gui.sh:1528 git-gui.sh:1533 -#, fuzzy +#: git-gui.sh:1563 git-gui.sh:1568 msgid "Staged for commit" -msgstr "从本次提交移除" +msgstr "缓存为提交" -#: git-gui.sh:1529 git-gui.sh:1534 -#, fuzzy +#: git-gui.sh:1564 git-gui.sh:1569 msgid "Portions staged for commit" -msgstr "从本次提交移除" +msgstr "部分缓存为提交" -#: git-gui.sh:1530 git-gui.sh:1535 +#: git-gui.sh:1565 git-gui.sh:1570 msgid "Staged for commit, missing" -msgstr "" +msgstr "缓存为提交, 不存在" -#: git-gui.sh:1532 +#: git-gui.sh:1567 msgid "Untracked, not staged" -msgstr "" +msgstr "未跟踪, 未缓存" -#: git-gui.sh:1537 +#: git-gui.sh:1572 msgid "Missing" -msgstr "" +msgstr "不存在" -#: git-gui.sh:1538 +#: git-gui.sh:1573 msgid "Staged for removal" -msgstr "" +msgstr "缓存为删除" -#: git-gui.sh:1539 +#: git-gui.sh:1574 msgid "Staged for removal, still present" -msgstr "" +msgstr "缓存为删除, 但仍存在" -#: git-gui.sh:1541 git-gui.sh:1542 git-gui.sh:1543 git-gui.sh:1544 +#: git-gui.sh:1576 git-gui.sh:1577 git-gui.sh:1578 git-gui.sh:1579 msgid "Requires merge resolution" -msgstr "" +msgstr "需要解决合并冲突" -#: git-gui.sh:1579 +#: git-gui.sh:1614 msgid "Starting gitk... please wait..." -msgstr "" +msgstr "启动 gitk... 请等待..." -#: git-gui.sh:1588 +#: git-gui.sh:1623 #, tcl-format msgid "" "Unable to start gitk:\n" "\n" "%s does not exist" msgstr "" +"无法启动 gitk:\n" +"\n" +"%s 不存在" -#: git-gui.sh:1788 lib/choose_repository.tcl:32 +#: git-gui.sh:1823 lib/choose_repository.tcl:35 msgid "Repository" -msgstr "版本树" +msgstr "版本库(repository)" -#: git-gui.sh:1789 +#: git-gui.sh:1824 msgid "Edit" msgstr "编辑" -#: git-gui.sh:1791 lib/choose_rev.tcl:560 +#: git-gui.sh:1826 lib/choose_rev.tcl:560 msgid "Branch" -msgstr "分支" +msgstr "分支(branch)" -#: git-gui.sh:1794 lib/choose_rev.tcl:547 -#, fuzzy +#: git-gui.sh:1829 lib/choose_rev.tcl:547 msgid "Commit@@noun" -msgstr "提交" +msgstr "提交(commit)" -#: git-gui.sh:1797 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 +#: git-gui.sh:1832 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 msgid "Merge" -msgstr "合并" +msgstr "合并(merge)" -#: git-gui.sh:1798 lib/choose_rev.tcl:556 -#, fuzzy +#: git-gui.sh:1833 lib/choose_rev.tcl:556 msgid "Remote" -msgstr "改名..." +msgstr "远端(remote)" -#: git-gui.sh:1807 +#: git-gui.sh:1842 msgid "Browse Current Branch's Files" -msgstr "浏览当前分支文件" +msgstr "浏览当前分支上的文件" -#: git-gui.sh:1811 -#, fuzzy +#: git-gui.sh:1846 msgid "Browse Branch Files..." -msgstr "浏览当前分支文件" +msgstr "浏览分支上的文件..." -#: git-gui.sh:1816 +#: git-gui.sh:1851 msgid "Visualize Current Branch's History" -msgstr "调用gitk显示当前分支" +msgstr "图示当前分支的历史" -#: git-gui.sh:1820 +#: git-gui.sh:1855 msgid "Visualize All Branch History" -msgstr "调用gitk显示所有分支" +msgstr "图示所有分支的历史" -#: git-gui.sh:1827 -#, fuzzy, tcl-format +#: git-gui.sh:1862 +#, tcl-format msgid "Browse %s's Files" -msgstr "浏览当前分支文件" +msgstr "浏览 %s 上的文件" -#: git-gui.sh:1829 -#, fuzzy, tcl-format +#: git-gui.sh:1864 +#, tcl-format msgid "Visualize %s's History" -msgstr "调用gitk显示所有分支" +msgstr "图示 %s 分支的历史" -#: git-gui.sh:1834 lib/database.tcl:27 lib/database.tcl:67 +#: git-gui.sh:1869 lib/database.tcl:27 lib/database.tcl:67 msgid "Database Statistics" -msgstr "数据库统计数据" +msgstr "数据库统计信息" -#: git-gui.sh:1837 lib/database.tcl:34 +#: git-gui.sh:1872 lib/database.tcl:34 msgid "Compress Database" msgstr "压缩数据库" -#: git-gui.sh:1840 +#: git-gui.sh:1875 msgid "Verify Database" msgstr "验证数据库" -#: git-gui.sh:1847 git-gui.sh:1851 git-gui.sh:1855 lib/shortcut.tcl:9 -#: lib/shortcut.tcl:45 lib/shortcut.tcl:84 +#: git-gui.sh:1882 git-gui.sh:1886 git-gui.sh:1890 lib/shortcut.tcl:7 +#: lib/shortcut.tcl:39 lib/shortcut.tcl:71 msgid "Create Desktop Icon" msgstr "创建桌面图标" -#: git-gui.sh:1860 lib/choose_repository.tcl:36 lib/choose_repository.tcl:95 +#: git-gui.sh:1895 lib/choose_repository.tcl:176 lib/choose_repository.tcl:184 msgid "Quit" msgstr "退出" -#: git-gui.sh:1867 +#: git-gui.sh:1902 msgid "Undo" msgstr "撤销" -#: git-gui.sh:1870 +#: git-gui.sh:1905 msgid "Redo" msgstr "重做" -#: git-gui.sh:1874 git-gui.sh:2366 +#: git-gui.sh:1909 git-gui.sh:2403 msgid "Cut" msgstr "剪切" -#: git-gui.sh:1877 git-gui.sh:2369 git-gui.sh:2440 git-gui.sh:2512 +#: git-gui.sh:1912 git-gui.sh:2406 git-gui.sh:2477 git-gui.sh:2549 #: lib/console.tcl:67 msgid "Copy" msgstr "复制" -#: git-gui.sh:1880 git-gui.sh:2372 +#: git-gui.sh:1915 git-gui.sh:2409 msgid "Paste" msgstr "粘贴" -#: git-gui.sh:1883 git-gui.sh:2375 lib/branch_delete.tcl:26 +#: git-gui.sh:1918 git-gui.sh:2412 lib/branch_delete.tcl:26 #: lib/remote_branch_delete.tcl:38 msgid "Delete" msgstr "删除" -#: git-gui.sh:1887 git-gui.sh:2379 git-gui.sh:2516 lib/console.tcl:69 +#: git-gui.sh:1922 git-gui.sh:2416 git-gui.sh:2553 lib/console.tcl:69 msgid "Select All" msgstr "全选" -#: git-gui.sh:1896 +#: git-gui.sh:1931 msgid "Create..." msgstr "新建..." -#: git-gui.sh:1902 +#: git-gui.sh:1937 msgid "Checkout..." -msgstr "切换..." +msgstr "Checkout..." -#: git-gui.sh:1908 +#: git-gui.sh:1943 msgid "Rename..." -msgstr "改名..." +msgstr "更名..." -#: git-gui.sh:1913 git-gui.sh:2012 +#: git-gui.sh:1948 git-gui.sh:2048 msgid "Delete..." msgstr "删除..." -#: git-gui.sh:1918 +#: git-gui.sh:1953 msgid "Reset..." -msgstr "重置所有修动..." +msgstr "复位(Reset)..." -#: git-gui.sh:1930 git-gui.sh:2313 +#: git-gui.sh:1965 git-gui.sh:2350 msgid "New Commit" -msgstr "新提交" +msgstr "新建提交" -#: git-gui.sh:1938 git-gui.sh:2320 +#: git-gui.sh:1973 git-gui.sh:2357 msgid "Amend Last Commit" -msgstr "修订上次提交" +msgstr "修正上次提交" -#: git-gui.sh:1947 git-gui.sh:2280 lib/remote_branch_delete.tcl:99 +#: git-gui.sh:1982 git-gui.sh:2317 lib/remote_branch_delete.tcl:99 msgid "Rescan" msgstr "重新扫描" -#: git-gui.sh:1953 -#, fuzzy +#: git-gui.sh:1988 msgid "Stage To Commit" -msgstr "从本次提交移除" +msgstr "缓存为提交" -#: git-gui.sh:1958 -#, fuzzy +#: git-gui.sh:1994 msgid "Stage Changed Files To Commit" -msgstr "将被提交的修改" +msgstr "缓存修改的文件为提交" -#: git-gui.sh:1964 +#: git-gui.sh:2000 msgid "Unstage From Commit" -msgstr "从本次提交移除" +msgstr "从本次提交撤除" -#: git-gui.sh:1969 lib/index.tcl:352 +#: git-gui.sh:2005 lib/index.tcl:393 msgid "Revert Changes" -msgstr "恢复修改" +msgstr "撤销修改" -#: git-gui.sh:1976 git-gui.sh:2292 git-gui.sh:2390 +#: git-gui.sh:2012 git-gui.sh:2329 git-gui.sh:2427 msgid "Sign Off" -msgstr "签名" +msgstr "签名(Sign Off)" -#: git-gui.sh:1980 git-gui.sh:2296 -#, fuzzy +#: git-gui.sh:2016 git-gui.sh:2333 msgid "Commit@@verb" msgstr "提交" -#: git-gui.sh:1991 +#: git-gui.sh:2027 msgid "Local Merge..." msgstr "本地合并..." -#: git-gui.sh:1996 +#: git-gui.sh:2032 msgid "Abort Merge..." -msgstr "取消合并..." +msgstr "中止合并..." -#: git-gui.sh:2008 +#: git-gui.sh:2044 msgid "Push..." msgstr "上传..." -#: git-gui.sh:2019 lib/choose_repository.tcl:41 +#: git-gui.sh:2055 lib/choose_repository.tcl:40 msgid "Apple" msgstr "苹果" -#: git-gui.sh:2022 git-gui.sh:2044 lib/about.tcl:13 -#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50 +#: git-gui.sh:2058 git-gui.sh:2080 lib/about.tcl:13 +#: lib/choose_repository.tcl:43 lib/choose_repository.tcl:49 #, tcl-format msgid "About %s" -msgstr "关于%s" +msgstr "关于 %s" -#: git-gui.sh:2026 +#: git-gui.sh:2062 msgid "Preferences..." -msgstr "" +msgstr "首选项..." -#: git-gui.sh:2034 git-gui.sh:2558 +#: git-gui.sh:2070 git-gui.sh:2595 msgid "Options..." msgstr "选项..." -#: git-gui.sh:2040 lib/choose_repository.tcl:47 +#: git-gui.sh:2076 lib/choose_repository.tcl:46 msgid "Help" msgstr "帮助" -#: git-gui.sh:2081 +#: git-gui.sh:2117 msgid "Online Documentation" msgstr "在线文档" -#: git-gui.sh:2165 +#: git-gui.sh:2201 #, tcl-format msgid "fatal: cannot stat path %s: No such file or directory" -msgstr "" +msgstr "致命错误: 无法获取路径 %s 的信息: 该文件或目录不存在" -#: git-gui.sh:2198 +#: git-gui.sh:2234 msgid "Current Branch:" msgstr "当前分支:" -#: git-gui.sh:2219 -#, fuzzy +#: git-gui.sh:2255 msgid "Staged Changes (Will Commit)" -msgstr "将被提交的修改" +msgstr "已缓存的改动 (将被提交)" -#: git-gui.sh:2239 +#: git-gui.sh:2274 msgid "Unstaged Changes" -msgstr "" +msgstr "未缓存的改动" -#: git-gui.sh:2286 +#: git-gui.sh:2323 msgid "Stage Changed" -msgstr "" +msgstr "缓存改动" -#: git-gui.sh:2302 lib/transport.tcl:93 lib/transport.tcl:182 +#: git-gui.sh:2339 lib/transport.tcl:93 lib/transport.tcl:182 msgid "Push" msgstr "上传" -#: git-gui.sh:2332 +#: git-gui.sh:2369 msgid "Initial Commit Message:" -msgstr "初始提交描述:" +msgstr "初始的提交描述:" -#: git-gui.sh:2333 +#: git-gui.sh:2370 msgid "Amended Commit Message:" -msgstr "修订提交描述:" +msgstr "修正的提交描述:" -#: git-gui.sh:2334 +#: git-gui.sh:2371 msgid "Amended Initial Commit Message:" -msgstr "修订初始提交描述:" +msgstr "修正的初始提交描述:" -#: git-gui.sh:2335 +#: git-gui.sh:2372 msgid "Amended Merge Commit Message:" -msgstr "修订合并提交描述:" +msgstr "修正的合并提交描述:" -#: git-gui.sh:2336 +#: git-gui.sh:2373 msgid "Merge Commit Message:" msgstr "合并提交描述:" -#: git-gui.sh:2337 +#: git-gui.sh:2374 msgid "Commit Message:" msgstr "提交描述:" -#: git-gui.sh:2382 git-gui.sh:2520 lib/console.tcl:71 +#: git-gui.sh:2419 git-gui.sh:2557 lib/console.tcl:71 msgid "Copy All" msgstr "全部复制" -#: git-gui.sh:2406 lib/blame.tcl:104 +#: git-gui.sh:2443 lib/blame.tcl:104 msgid "File:" -msgstr "" +msgstr "文件:" -#: git-gui.sh:2508 +#: git-gui.sh:2545 msgid "Refresh" msgstr "刷新" -#: git-gui.sh:2529 +#: git-gui.sh:2566 msgid "Apply/Reverse Hunk" msgstr "应用/撤消此修改块" -#: git-gui.sh:2535 +#: git-gui.sh:2572 msgid "Decrease Font Size" msgstr "缩小字体" -#: git-gui.sh:2539 +#: git-gui.sh:2576 msgid "Increase Font Size" msgstr "放大字体" -#: git-gui.sh:2544 +#: git-gui.sh:2581 msgid "Show Less Context" -msgstr "显示更多diff上下文" +msgstr "显示更少上下文" -#: git-gui.sh:2551 +#: git-gui.sh:2588 msgid "Show More Context" -msgstr "显示更少diff上下文" +msgstr "显示更多上下文" -#: git-gui.sh:2565 -#, fuzzy +#: git-gui.sh:2602 msgid "Unstage Hunk From Commit" -msgstr "从本次提交移除" +msgstr "从提交中撤除修改块" -#: git-gui.sh:2567 -#, fuzzy +#: git-gui.sh:2604 msgid "Stage Hunk For Commit" -msgstr "从本次提交移除" +msgstr "缓存修改块为提交" -#: git-gui.sh:2586 +#: git-gui.sh:2623 msgid "Initializing..." -msgstr "" +msgstr "初始化..." -#: git-gui.sh:2677 +#: git-gui.sh:2718 #, tcl-format msgid "" "Possible environment issues exist.\n" @@ -444,15 +459,22 @@ msgid "" "by %s:\n" "\n" msgstr "" +"可能存在环境变量的问题.\n" +"\n" +"由 %s 执行的 Git 子进程可能忽略下列环境变量:\n" +"\n" -#: git-gui.sh:2707 +#: git-gui.sh:2748 msgid "" "\n" "This is due to a known issue with the\n" "Tcl binary distributed by Cygwin." msgstr "" +"\n" +"这是由 Cygwin 发布的 Tcl 代码中一个\n" +"已知问题所引起." -#: git-gui.sh:2712 +#: git-gui.sh:2753 #, tcl-format msgid "" "\n" @@ -462,206 +484,197 @@ msgid "" "user.email settings into your personal\n" "~/.gitconfig file.\n" msgstr "" +"\n" +"\n" +"%s 的一个很好的替代方案是将 user.name 以及\n" +"user.email 设置放在你的个人 ~/.gitconfig 文件中.\n" #: lib/about.tcl:25 msgid "git-gui - a graphical user interface for Git." -msgstr "" +msgstr "git-gui - Git 的图形化用户界面" #: lib/blame.tcl:77 msgid "File Viewer" -msgstr "" +msgstr "文件查看器" #: lib/blame.tcl:81 -#, fuzzy msgid "Commit:" -msgstr "提交" +msgstr "提交:" #: lib/blame.tcl:249 -#, fuzzy msgid "Copy Commit" -msgstr "提交" +msgstr "复制提交" #: lib/blame.tcl:369 #, tcl-format msgid "Reading %s..." -msgstr "" +msgstr "读取 %s..." #: lib/blame.tcl:473 msgid "Loading copy/move tracking annotations..." -msgstr "" +msgstr "装载复制/移动跟踪标注..." #: lib/blame.tcl:493 msgid "lines annotated" -msgstr "" +msgstr "标注行" #: lib/blame.tcl:674 msgid "Loading original location annotations..." -msgstr "" +msgstr "装载原始位置标注..." #: lib/blame.tcl:677 msgid "Annotation complete." -msgstr "" +msgstr "标注完成." #: lib/blame.tcl:731 msgid "Loading annotation..." -msgstr "" +msgstr "裝載标注..." #: lib/blame.tcl:787 msgid "Author:" -msgstr "" +msgstr "作者:" #: lib/blame.tcl:791 -#, fuzzy msgid "Committer:" -msgstr "提交" +msgstr "提交者:" #: lib/blame.tcl:796 msgid "Original File:" -msgstr "" +msgstr "原始文件:" #: lib/blame.tcl:910 msgid "Originally By:" -msgstr "" +msgstr "最初由:" #: lib/blame.tcl:916 msgid "In File:" -msgstr "" +msgstr "在文件:" #: lib/blame.tcl:921 msgid "Copied Or Moved Here By:" -msgstr "" +msgstr "由复制或移动至此:" #: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 -#, fuzzy msgid "Checkout Branch" -msgstr "当前分支:" +msgstr "Checkout 分支" #: lib/branch_checkout.tcl:23 -#, fuzzy msgid "Checkout" -msgstr "切换..." +msgstr "Checkout" #: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 #: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281 #: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172 #: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97 msgid "Cancel" -msgstr "" +msgstr "取消" #: lib/branch_checkout.tcl:32 lib/browser.tcl:286 msgid "Revision" -msgstr "" +msgstr "版本" #: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202 -#, fuzzy msgid "Options" msgstr "选项..." #: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92 msgid "Fetch Tracking Branch" -msgstr "" +msgstr "获取跟踪分支" #: lib/branch_checkout.tcl:44 msgid "Detach From Local Branch" -msgstr "" +msgstr "从本地分支脱离" #: lib/branch_create.tcl:22 -#, fuzzy msgid "Create Branch" -msgstr "当前分支:" +msgstr "创建分支" #: lib/branch_create.tcl:27 -#, fuzzy msgid "Create New Branch" -msgstr "当前分支:" +msgstr "新建分支" -#: lib/branch_create.tcl:31 lib/choose_repository.tcl:199 -#, fuzzy +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:375 msgid "Create" -msgstr "新建..." +msgstr "新建" #: lib/branch_create.tcl:40 -#, fuzzy msgid "Branch Name" -msgstr "分支" +msgstr "分支名" #: lib/branch_create.tcl:43 msgid "Name:" -msgstr "" +msgstr "名字:" #: lib/branch_create.tcl:58 msgid "Match Tracking Branch Name" -msgstr "" +msgstr "匹配跟踪分支名字" #: lib/branch_create.tcl:66 msgid "Starting Revision" -msgstr "" +msgstr "起始版本" #: lib/branch_create.tcl:72 msgid "Update Existing Branch:" -msgstr "" +msgstr "更新已有分支:" #: lib/branch_create.tcl:75 msgid "No" -msgstr "" +msgstr "号码" #: lib/branch_create.tcl:80 msgid "Fast Forward Only" -msgstr "" +msgstr "仅快速合并" #: lib/branch_create.tcl:85 lib/checkout_op.tcl:514 -#, fuzzy msgid "Reset" -msgstr "重置所有修动..." +msgstr "复位" #: lib/branch_create.tcl:97 msgid "Checkout After Creation" -msgstr "" +msgstr "在创建后Checkout" #: lib/branch_create.tcl:131 msgid "Please select a tracking branch." -msgstr "" +msgstr "请选择某个跟踪分支." #: lib/branch_create.tcl:140 #, tcl-format msgid "Tracking branch %s is not a branch in the remote repository." -msgstr "" +msgstr "跟踪分支 %s 并不是远端版本库中的一个分支" #: lib/branch_create.tcl:153 lib/branch_rename.tcl:86 msgid "Please supply a branch name." -msgstr "" +msgstr "请提供分支名字." #: lib/branch_create.tcl:164 lib/branch_rename.tcl:106 #, tcl-format msgid "'%s' is not an acceptable branch name." -msgstr "" +msgstr "'%s'不是一个可接受的分支名." #: lib/branch_delete.tcl:15 -#, fuzzy msgid "Delete Branch" -msgstr "当前分支:" +msgstr "删除分支" #: lib/branch_delete.tcl:20 msgid "Delete Local Branch" -msgstr "" +msgstr "删除本地分支" #: lib/branch_delete.tcl:37 -#, fuzzy msgid "Local Branches" -msgstr "分支" +msgstr "本地分支" #: lib/branch_delete.tcl:52 msgid "Delete Only If Merged Into" -msgstr "" +msgstr "仅在合并后删除" #: lib/branch_delete.tcl:54 msgid "Always (Do not perform merge test.)" -msgstr "" +msgstr "总是合并 (不作合并测试.)" #: lib/branch_delete.tcl:103 #, tcl-format msgid "The following branches are not completely merged into %s:" -msgstr "" +msgstr "下列分支没有完全被合并到 %s:" #: lib/branch_delete.tcl:115 msgid "" @@ -669,6 +682,9 @@ msgid "" "\n" " Delete the selected branches?" msgstr "" +"恢复被删除的分支非常困难.\n" +"\n" +"是否要删除所选分支?" #: lib/branch_delete.tcl:141 #, tcl-format @@ -676,86 +692,84 @@ msgid "" "Failed to delete branches:\n" "%s" msgstr "" +"无法删除分支:\n" +"%s" #: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22 -#, fuzzy msgid "Rename Branch" -msgstr "当前分支:" +msgstr "更改分支名:" #: lib/branch_rename.tcl:26 -#, fuzzy msgid "Rename" -msgstr "改名..." +msgstr "更名..." #: lib/branch_rename.tcl:36 -#, fuzzy msgid "Branch:" -msgstr "分支" +msgstr "分支:" #: lib/branch_rename.tcl:39 msgid "New Name:" -msgstr "" +msgstr "新名字:" #: lib/branch_rename.tcl:75 msgid "Please select a branch to rename." -msgstr "" +msgstr "请选择分支更名." #: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179 #, tcl-format msgid "Branch '%s' already exists." -msgstr "" +msgstr "分支 '%s' 已经存在." #: lib/branch_rename.tcl:117 #, tcl-format msgid "Failed to rename '%s'." -msgstr "" +msgstr "无法更名 '%s'." #: lib/browser.tcl:17 msgid "Starting..." -msgstr "" +msgstr "开始..." #: lib/browser.tcl:26 msgid "File Browser" -msgstr "" +msgstr "文件浏览器" #: lib/browser.tcl:125 lib/browser.tcl:142 #, tcl-format msgid "Loading %s..." -msgstr "" +msgstr "装载 %s..." #: lib/browser.tcl:186 msgid "[Up To Parent]" -msgstr "" +msgstr "[上层目录]" #: lib/browser.tcl:266 lib/browser.tcl:272 -#, fuzzy msgid "Browse Branch Files" -msgstr "浏览当前分支文件" +msgstr "浏览分支文件" -#: lib/browser.tcl:277 lib/choose_repository.tcl:215 -#: lib/choose_repository.tcl:305 lib/choose_repository.tcl:315 -#: lib/choose_repository.tcl:811 +#: lib/browser.tcl:277 lib/choose_repository.tcl:391 +#: lib/choose_repository.tcl:482 lib/choose_repository.tcl:492 +#: lib/choose_repository.tcl:989 msgid "Browse" -msgstr "" +msgstr "浏览" #: lib/checkout_op.tcl:79 #, tcl-format msgid "Fetching %s from %s" -msgstr "" +msgstr "获取 %s 自 %s" #: lib/checkout_op.tcl:127 #, tcl-format msgid "fatal: Cannot resolve %s" -msgstr "" +msgstr "致命错误: 无法解决 %s" #: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31 msgid "Close" -msgstr "" +msgstr "关闭" #: lib/checkout_op.tcl:169 #, tcl-format msgid "Branch '%s' does not exist." -msgstr "" +msgstr "分支 '%s' 并不存在." #: lib/checkout_op.tcl:206 #, tcl-format @@ -765,20 +779,24 @@ msgid "" "It cannot fast-forward to %s.\n" "A merge is required." msgstr "" +"分支 '%s' 已经存在.\n" +"\n" +"无法快速合并到 %s.\n" +"需要普通合并." #: lib/checkout_op.tcl:220 #, tcl-format msgid "Merge strategy '%s' not supported." -msgstr "" +msgstr "合并策略 '%s' 不支持." #: lib/checkout_op.tcl:239 #, tcl-format msgid "Failed to update '%s'." -msgstr "" +msgstr "无法更新 '%s'." #: lib/checkout_op.tcl:251 msgid "Staging area (index) is already locked." -msgstr "" +msgstr "缓存区域 (index) 已被锁定." #: lib/checkout_op.tcl:266 msgid "" @@ -789,25 +807,31 @@ msgid "" "\n" "The rescan will be automatically started now.\n" msgstr "" +"最后一次扫描的状态和当前版本库状态不符.\n" +"\n" +"另一 Git 程序自上次扫描后修改了本版本库. 在修改当前分支之前需要重新做一次扫" +"描.\n" +"\n" +"重新扫描将自动开始.\n" #: lib/checkout_op.tcl:322 #, tcl-format msgid "Updating working directory to '%s'..." -msgstr "" +msgstr "更新工作目录到 '%s'..." #: lib/checkout_op.tcl:353 #, tcl-format msgid "Aborted checkout of '%s' (file level merging is required)." -msgstr "" +msgstr "中止 '%s' 的 checkout 操作 (需要做文件级合并)." #: lib/checkout_op.tcl:354 msgid "File level merge required." -msgstr "" +msgstr "需要文件级合并." #: lib/checkout_op.tcl:358 #, tcl-format msgid "Staying on branch '%s'." -msgstr "" +msgstr "停留在分支 '%s'." #: lib/checkout_op.tcl:429 msgid "" @@ -816,29 +840,32 @@ msgid "" "If you wanted to be on a branch, create one now starting from 'This Detached " "Checkout'." msgstr "" +"你不在某个本地分支上.\n" +"\n" +"如果你想位于某分支上, 从当前脱节的Checkout中创建一个新分支." #: lib/checkout_op.tcl:446 -#, fuzzy, tcl-format +#, tcl-format msgid "Checked out '%s'." -msgstr "切换..." +msgstr "'%s' 已被 checkout" #: lib/checkout_op.tcl:478 #, tcl-format msgid "Resetting '%s' to '%s' will lose the following commits:" -msgstr "" +msgstr "复位 '%s' 到 '%s' 将导致下列提交的丢失:" #: lib/checkout_op.tcl:500 msgid "Recovering lost commits may not be easy." -msgstr "" +msgstr "恢复丢失的提交是比较困难的." #: lib/checkout_op.tcl:505 #, tcl-format msgid "Reset '%s'?" -msgstr "" +msgstr "复位 '%s'?" #: lib/checkout_op.tcl:510 lib/merge.tcl:164 msgid "Visualize" -msgstr "" +msgstr "图示" #: lib/checkout_op.tcl:578 #, tcl-format @@ -850,286 +877,301 @@ msgid "" "\n" "This should not have occurred. %s will now close and give up." msgstr "" +"无法设定当前分支.\n" +"\n" +"当前工作目录仅有部分被切换出, 我们已成功的更新了您的文件但是无法更新某个内部" +"的Git文件.\n" +"\n" +"这本不该发生, %s 将关闭并放弃." #: lib/choose_font.tcl:39 -#, fuzzy msgid "Select" -msgstr "全选" +msgstr "选择" #: lib/choose_font.tcl:53 msgid "Font Family" -msgstr "" +msgstr "字体族" #: lib/choose_font.tcl:73 -#, fuzzy msgid "Font Size" -msgstr "缩小字体" +msgstr "字体大小" #: lib/choose_font.tcl:90 msgid "Font Example" -msgstr "" +msgstr "字体样例" #: lib/choose_font.tcl:101 msgid "" "This is example text.\n" "If you like this text, it can be your font." msgstr "" +"这是样例文本.\n" +"如果你喜欢, 你可以设置该字体." -#: lib/choose_repository.tcl:25 +#: lib/choose_repository.tcl:27 msgid "Git Gui" -msgstr "" +msgstr "Git Gui" -#: lib/choose_repository.tcl:69 lib/choose_repository.tcl:204 -#, fuzzy +#: lib/choose_repository.tcl:80 lib/choose_repository.tcl:380 msgid "Create New Repository" -msgstr "版本树" +msgstr "创建新的版本库" -#: lib/choose_repository.tcl:74 lib/choose_repository.tcl:291 -#, fuzzy +#: lib/choose_repository.tcl:86 +msgid "New..." +msgstr "新建..." + +#: lib/choose_repository.tcl:93 lib/choose_repository.tcl:468 msgid "Clone Existing Repository" -msgstr "版本树" +msgstr "克隆已有版本库" -#: lib/choose_repository.tcl:79 lib/choose_repository.tcl:800 -#, fuzzy +#: lib/choose_repository.tcl:99 +msgid "Clone..." +msgstr "克隆..." + +#: lib/choose_repository.tcl:106 lib/choose_repository.tcl:978 msgid "Open Existing Repository" -msgstr "版本树" +msgstr "打开已有版本库" -#: lib/choose_repository.tcl:91 -msgid "Next >" -msgstr "" +#: lib/choose_repository.tcl:112 +msgid "Open..." +msgstr "打开..." -#: lib/choose_repository.tcl:152 +#: lib/choose_repository.tcl:125 +msgid "Recent Repositories" +msgstr "最近版本库" + +#: lib/choose_repository.tcl:131 +msgid "Open Recent Repository:" +msgstr "打开最近版本库" + +#: lib/choose_repository.tcl:294 #, tcl-format msgid "Location %s already exists." -msgstr "" +msgstr "位置 %s 已经存在." -#: lib/choose_repository.tcl:158 lib/choose_repository.tcl:165 -#: lib/choose_repository.tcl:172 +#: lib/choose_repository.tcl:300 lib/choose_repository.tcl:307 +#: lib/choose_repository.tcl:314 #, tcl-format msgid "Failed to create repository %s:" -msgstr "" +msgstr "无法创建版本库 %s:" -#: lib/choose_repository.tcl:209 lib/choose_repository.tcl:309 +#: lib/choose_repository.tcl:385 lib/choose_repository.tcl:486 msgid "Directory:" -msgstr "" +msgstr "目录:" -#: lib/choose_repository.tcl:238 lib/choose_repository.tcl:363 -#: lib/choose_repository.tcl:834 -#, fuzzy +#: lib/choose_repository.tcl:415 lib/choose_repository.tcl:544 +#: lib/choose_repository.tcl:1013 msgid "Git Repository" -msgstr "版本树" +msgstr "Git 版本库" -#: lib/choose_repository.tcl:253 lib/choose_repository.tcl:260 +#: lib/choose_repository.tcl:430 lib/choose_repository.tcl:437 #, tcl-format msgid "Directory %s already exists." -msgstr "" +msgstr "目录 %s 已经存在." -#: lib/choose_repository.tcl:265 +#: lib/choose_repository.tcl:442 #, tcl-format msgid "File %s already exists." -msgstr "" +msgstr "文件 %s 已经存在." -#: lib/choose_repository.tcl:286 +#: lib/choose_repository.tcl:463 msgid "Clone" -msgstr "" - -#: lib/choose_repository.tcl:299 -msgid "URL:" -msgstr "" - -#: lib/choose_repository.tcl:319 -msgid "Clone Type:" -msgstr "" - -#: lib/choose_repository.tcl:325 -msgid "Standard (Fast, Semi-Redundant, Hardlinks)" -msgstr "" - -#: lib/choose_repository.tcl:331 -msgid "Full Copy (Slower, Redundant Backup)" -msgstr "" - -#: lib/choose_repository.tcl:337 -msgid "Shared (Fastest, Not Recommended, No Backup)" -msgstr "" - -#: lib/choose_repository.tcl:369 lib/choose_repository.tcl:418 -#: lib/choose_repository.tcl:560 lib/choose_repository.tcl:630 -#: lib/choose_repository.tcl:840 lib/choose_repository.tcl:848 -#, tcl-format -msgid "Not a Git repository: %s" -msgstr "" - -#: lib/choose_repository.tcl:405 -msgid "Standard only available for local repository." -msgstr "" - -#: lib/choose_repository.tcl:409 -msgid "Shared only available for local repository." -msgstr "" - -#: lib/choose_repository.tcl:439 -msgid "Failed to configure origin" -msgstr "" - -#: lib/choose_repository.tcl:451 -msgid "Counting objects" -msgstr "" - -#: lib/choose_repository.tcl:452 -msgid "buckets" -msgstr "" +msgstr "克隆" #: lib/choose_repository.tcl:476 +msgid "URL:" +msgstr "URL:" + +#: lib/choose_repository.tcl:496 +msgid "Clone Type:" +msgstr "克隆类型:" + +#: lib/choose_repository.tcl:502 +msgid "Standard (Fast, Semi-Redundant, Hardlinks)" +msgstr "标准方式 (快速, 部分备份, 作硬连接)" + +#: lib/choose_repository.tcl:508 +msgid "Full Copy (Slower, Redundant Backup)" +msgstr "全部复制 (较慢, 做备份)" + +#: lib/choose_repository.tcl:514 +msgid "Shared (Fastest, Not Recommended, No Backup)" +msgstr "共享方式 (最快, 不推荐, 不做备份)" + +#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597 +#: lib/choose_repository.tcl:738 lib/choose_repository.tcl:808 +#: lib/choose_repository.tcl:1019 lib/choose_repository.tcl:1027 +#, tcl-format +msgid "Not a Git repository: %s" +msgstr "不是一个 Git 版本库: %s" + +#: lib/choose_repository.tcl:586 +msgid "Standard only available for local repository." +msgstr "标准方式仅当是本地版本库时有效." + +#: lib/choose_repository.tcl:590 +msgid "Shared only available for local repository." +msgstr "共享方式仅当是本地版本库时有效." + +#: lib/choose_repository.tcl:617 +msgid "Failed to configure origin" +msgstr "无法配置 origin" + +#: lib/choose_repository.tcl:629 +msgid "Counting objects" +msgstr "清点对象" + +#: lib/choose_repository.tcl:630 +#, fuzzy +msgid "buckets" +msgstr "水桶??" + +#: lib/choose_repository.tcl:654 #, tcl-format msgid "Unable to copy objects/info/alternates: %s" -msgstr "" +msgstr "无法复制 objects/info/alternates: %s" -#: lib/choose_repository.tcl:512 +#: lib/choose_repository.tcl:690 #, tcl-format msgid "Nothing to clone from %s." -msgstr "" +msgstr "没有东西可从 %s 克隆." -#: lib/choose_repository.tcl:514 lib/choose_repository.tcl:728 -#: lib/choose_repository.tcl:740 +#: lib/choose_repository.tcl:692 lib/choose_repository.tcl:906 +#: lib/choose_repository.tcl:918 msgid "The 'master' branch has not been initialized." -msgstr "" +msgstr "'master'分支尚未初始化." -#: lib/choose_repository.tcl:527 +#: lib/choose_repository.tcl:705 msgid "Hardlinks are unavailable. Falling back to copying." -msgstr "" +msgstr "硬连接不可用. 使用复制." -#: lib/choose_repository.tcl:539 +#: lib/choose_repository.tcl:717 #, tcl-format msgid "Cloning from %s" -msgstr "" +msgstr "从 %s 克隆" -#: lib/choose_repository.tcl:570 -#, fuzzy +#: lib/choose_repository.tcl:748 msgid "Copying objects" -msgstr "压缩数据库" - -#: lib/choose_repository.tcl:571 -msgid "KiB" -msgstr "" - -#: lib/choose_repository.tcl:595 -#, tcl-format -msgid "Unable to copy object: %s" -msgstr "" - -#: lib/choose_repository.tcl:605 -msgid "Linking objects" -msgstr "" - -#: lib/choose_repository.tcl:606 -msgid "objects" -msgstr "" - -#: lib/choose_repository.tcl:614 -#, tcl-format -msgid "Unable to hardlink object: %s" -msgstr "" - -#: lib/choose_repository.tcl:669 -msgid "Cannot fetch branches and objects. See console output for details." -msgstr "" - -#: lib/choose_repository.tcl:680 -msgid "Cannot fetch tags. See console output for details." -msgstr "" - -#: lib/choose_repository.tcl:704 -msgid "Cannot determine HEAD. See console output for details." -msgstr "" - -#: lib/choose_repository.tcl:713 -#, tcl-format -msgid "Unable to cleanup %s" -msgstr "" - -#: lib/choose_repository.tcl:719 -msgid "Clone failed." -msgstr "" - -#: lib/choose_repository.tcl:726 -msgid "No default branch obtained." -msgstr "" - -#: lib/choose_repository.tcl:737 -#, tcl-format -msgid "Cannot resolve %s as a commit." -msgstr "" +msgstr "复制 objects" #: lib/choose_repository.tcl:749 +msgid "KiB" +msgstr "KiB" + +#: lib/choose_repository.tcl:773 +#, tcl-format +msgid "Unable to copy object: %s" +msgstr "无法复制 object: %s" + +#: lib/choose_repository.tcl:783 +msgid "Linking objects" +msgstr "链接 objects" + +#: lib/choose_repository.tcl:784 +msgid "objects" +msgstr "objects" + +#: lib/choose_repository.tcl:792 +#, tcl-format +msgid "Unable to hardlink object: %s" +msgstr "无法硬链接 object: %s" + +#: lib/choose_repository.tcl:847 +msgid "Cannot fetch branches and objects. See console output for details." +msgstr "无法获取分支和对象. 请查看控制终端的输出." + +#: lib/choose_repository.tcl:858 +msgid "Cannot fetch tags. See console output for details." +msgstr "无法获取标签. 请查看控制终端的输出." + +#: lib/choose_repository.tcl:882 +msgid "Cannot determine HEAD. See console output for details." +msgstr "无法确定 HEAD. 请查看控制终端的输出." + +#: lib/choose_repository.tcl:891 +#, tcl-format +msgid "Unable to cleanup %s" +msgstr "无法清理 %s" + +#: lib/choose_repository.tcl:897 +msgid "Clone failed." +msgstr "克隆失败." + +#: lib/choose_repository.tcl:904 +msgid "No default branch obtained." +msgstr "没有获取缺省分支" + +#: lib/choose_repository.tcl:915 +#, tcl-format +msgid "Cannot resolve %s as a commit." +msgstr "无法解析 %s 为提交." + +#: lib/choose_repository.tcl:927 msgid "Creating working directory" -msgstr "" +msgstr "创建工作目录" -#: lib/choose_repository.tcl:750 lib/index.tcl:15 lib/index.tcl:80 -#: lib/index.tcl:149 +#: lib/choose_repository.tcl:928 lib/index.tcl:65 lib/index.tcl:127 +#: lib/index.tcl:193 msgid "files" -msgstr "" +msgstr "文件" -#: lib/choose_repository.tcl:779 +#: lib/choose_repository.tcl:957 msgid "Initial file checkout failed." -msgstr "" +msgstr "初始的文件checkout失败" -#: lib/choose_repository.tcl:795 +#: lib/choose_repository.tcl:973 msgid "Open" -msgstr "" +msgstr "打开" -#: lib/choose_repository.tcl:805 -#, fuzzy +#: lib/choose_repository.tcl:983 msgid "Repository:" -msgstr "版本树" +msgstr "版本库" -#: lib/choose_repository.tcl:854 +#: lib/choose_repository.tcl:1033 #, tcl-format msgid "Failed to open repository %s:" -msgstr "" +msgstr "无法打开版本库 %s:" #: lib/choose_rev.tcl:53 msgid "This Detached Checkout" -msgstr "" +msgstr "该脱节的Checkout" #: lib/choose_rev.tcl:60 msgid "Revision Expression:" -msgstr "" +msgstr "版本表达式:" #: lib/choose_rev.tcl:74 -#, fuzzy msgid "Local Branch" -msgstr "分支" +msgstr "本地分支" #: lib/choose_rev.tcl:79 -#, fuzzy msgid "Tracking Branch" -msgstr "当前分支:" +msgstr "跟踪分支:" #: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537 msgid "Tag" -msgstr "" +msgstr "标签" #: lib/choose_rev.tcl:317 #, tcl-format msgid "Invalid revision: %s" -msgstr "" +msgstr "无效版本: %s" #: lib/choose_rev.tcl:338 msgid "No revision selected." -msgstr "" +msgstr "没有选择版本." #: lib/choose_rev.tcl:346 msgid "Revision expression is empty." -msgstr "" +msgstr "版本表达式为空." #: lib/choose_rev.tcl:530 msgid "Updated" -msgstr "" +msgstr "已更新" #: lib/choose_rev.tcl:558 msgid "URL" -msgstr "" +msgstr "URL" #: lib/commit.tcl:9 msgid "" @@ -1138,6 +1180,9 @@ msgid "" "You are about to create the initial commit. There is no commit before this " "to amend.\n" msgstr "" +"没有改动需要修正.\n" +"\n" +"你正在创建最初的提交. 在此之前没有提交可以修正.\n" #: lib/commit.tcl:18 msgid "" @@ -1147,18 +1192,22 @@ msgid "" "completed. You cannot amend the prior commit unless you first abort the " "current merge activity.\n" msgstr "" +"在合并时无法修正.\n" +"\n" +"你当前正在一次尚未完成的合并操作过程中. 除非中止当前合并活动,\n" +"否则无法修正之前的提交.\n" #: lib/commit.tcl:49 msgid "Error loading commit data for amend:" -msgstr "" +msgstr "为修正装载提交数据出错:" #: lib/commit.tcl:76 msgid "Unable to obtain your identity:" -msgstr "" +msgstr "无法获知你的身份:" #: lib/commit.tcl:81 msgid "Invalid GIT_COMMITTER_IDENT:" -msgstr "" +msgstr "无效的 GIT_COMMITTER_IDENT" #: lib/commit.tcl:133 msgid "" @@ -1169,6 +1218,12 @@ msgid "" "\n" "The rescan will be automatically started now.\n" msgstr "" +"最后一次扫描的状态和当前版本库状态不符.\n" +"\n" +"另一 Git 程序自上次扫描后修改了本版本库. 在修改当前分支之前需要重新做一次扫" +"描.\n" +"\n" +"重新扫描将自动开始.\n" #: lib/commit.tcl:154 #, tcl-format @@ -1178,6 +1233,9 @@ msgid "" "File %s has merge conflicts. You must resolve them and stage the file " "before committing.\n" msgstr "" +"尚未合并的文件没有办法提交.\n" +"\n" +"文件 %s 有合并冲突, 你必须解决这些冲突并缓存该文件作提交.\n" #: lib/commit.tcl:162 #, tcl-format @@ -1186,6 +1244,9 @@ msgid "" "\n" "File %s cannot be committed by this program.\n" msgstr "" +"检测到未知文件状态 %s.\n" +"\n" +"文件 %s 无法由该程序提交.\n" #: lib/commit.tcl:170 msgid "" @@ -1193,6 +1254,9 @@ msgid "" "\n" "You must stage at least 1 file before you can commit.\n" msgstr "" +"没有需要提交的变动.\n" +"\n" +"提交前你必须首先缓存至少一个文件.\n" #: lib/commit.tcl:183 msgid "" @@ -1200,19 +1264,26 @@ msgid "" "\n" "A good commit message has the following format:\n" "\n" -"- First line: Describe in one sentance what you did.\n" +"- First line: Describe in one sentence what you did.\n" "- Second line: Blank\n" "- Remaining lines: Describe why this change is good.\n" msgstr "" +"请提供一条提交信息.\n" +"\n" +"一条好的提交信息有下列格式:\n" +"\n" +"- 第一行: 一句话概括你做的修改.\n" +"- 第二行: 空行\n" +"- 剩余行: 请描述为什么你做的这些改动是好的.\n" #: lib/commit.tcl:257 msgid "write-tree failed:" -msgstr "" +msgstr "write-tree 失败:" #: lib/commit.tcl:275 #, tcl-format msgid "Commit %s appears to be corrupt" -msgstr "" +msgstr "提交 %s 似乎已损坏" #: lib/commit.tcl:279 msgid "" @@ -1222,77 +1293,81 @@ msgid "" "\n" "A rescan will be automatically started now.\n" msgstr "" +"没有改动提交.\n" +"\n" +"该提交没有改动任何文件也不是一个合并提交.\n" +"\n" +"重新扫描将自动开始.\n" #: lib/commit.tcl:286 msgid "No changes to commit." -msgstr "" +msgstr "没有改动要提交." #: lib/commit.tcl:303 #, tcl-format msgid "warning: Tcl does not support encoding '%s'." -msgstr "" +msgstr "警告: Tcl 不支持编码方式 '%s'." #: lib/commit.tcl:317 msgid "commit-tree failed:" -msgstr "" +msgstr "commit-tree 失败:" #: lib/commit.tcl:339 msgid "update-ref failed:" -msgstr "" +msgstr "update-ref 失败:" #: lib/commit.tcl:430 #, tcl-format msgid "Created commit %s: %s" -msgstr "" +msgstr "创建了 commit %s: %s" #: lib/console.tcl:57 msgid "Working... please wait..." -msgstr "" +msgstr "工作中... 请等待..." #: lib/console.tcl:183 msgid "Success" -msgstr "" +msgstr "成功" #: lib/console.tcl:196 msgid "Error: Command Failed" -msgstr "" +msgstr "错误: 命令失败" #: lib/database.tcl:43 msgid "Number of loose objects" -msgstr "" +msgstr "松散对象的数量" #: lib/database.tcl:44 msgid "Disk space used by loose objects" -msgstr "" +msgstr "松散对象所使用的磁盘空间" #: lib/database.tcl:45 msgid "Number of packed objects" -msgstr "" +msgstr "压缩对象数量" #: lib/database.tcl:46 msgid "Number of packs" -msgstr "" +msgstr "压缩包数量" #: lib/database.tcl:47 msgid "Disk space used by packed objects" -msgstr "" +msgstr "压缩对象所使用的磁盘空间" #: lib/database.tcl:48 msgid "Packed objects waiting for pruning" -msgstr "" +msgstr "压缩对象等待清理" #: lib/database.tcl:49 msgid "Garbage files" -msgstr "" +msgstr "垃圾文件" #: lib/database.tcl:72 -#, fuzzy msgid "Compressing the object database" -msgstr "压缩数据库" +msgstr "压缩对象数据库" #: lib/database.tcl:83 msgid "Verifying the object database with fsck-objects" -msgstr "" +msgstr "使用 fsck-objects 验证对象数据库" #: lib/database.tcl:108 #, tcl-format @@ -1304,11 +1379,16 @@ msgid "" "\n" "Compress the database now?" msgstr "" +"该版本库当前约有 %i 个松散对象.\n" +"\n" +"为达到较优的性能,强烈建议你在松散对象多于 %i 时压缩数据库.\n" +"\n" +"现在就压缩数据库么?" #: lib/date.tcl:25 #, tcl-format msgid "Invalid date from Git: %s" -msgstr "" +msgstr "无效的日期: %s" #: lib/diff.tcl:42 #, tcl-format @@ -1323,80 +1403,107 @@ msgid "" "A rescan will be automatically started to find other files which may have " "the same state." msgstr "" +"未检测到改动.\n" +"\n" +"该文件的修改日期被另一个程序所更新, 但其内容并没有变化.\n" +"\n" +"对于类似情况的其他文件的重新扫描将自动开始." #: lib/diff.tcl:81 -#, tcl-format +#, fuzzy, tcl-format msgid "Loading diff of %s..." -msgstr "" +msgstr "装载 %s 的 diff ..." #: lib/diff.tcl:114 lib/diff.tcl:184 #, tcl-format msgid "Unable to display %s" -msgstr "" +msgstr "无法显示 %s" #: lib/diff.tcl:115 msgid "Error loading file:" -msgstr "" +msgstr "装载文件出错:" #: lib/diff.tcl:122 msgid "Git Repository (subproject)" -msgstr "" +msgstr "Git 版本库 (子项目)" #: lib/diff.tcl:134 msgid "* Binary file (not showing content)." -msgstr "" +msgstr "* 二进制文件 (不显示内容)." #: lib/diff.tcl:185 msgid "Error loading diff:" -msgstr "" +msgstr "装载 diff 错误:" #: lib/diff.tcl:302 msgid "Failed to unstage selected hunk." -msgstr "" +msgstr "无法将选择的代码段从缓存中删除." #: lib/diff.tcl:309 msgid "Failed to stage selected hunk." -msgstr "" +msgstr "无法缓存所选代码段." #: lib/error.tcl:12 lib/error.tcl:102 msgid "error" -msgstr "" +msgstr "错误" #: lib/error.tcl:28 msgid "warning" -msgstr "" +msgstr "警告" #: lib/error.tcl:81 msgid "You must correct the above errors before committing." -msgstr "" +msgstr "你必须在提交前修正上述错误." -#: lib/index.tcl:241 -#, fuzzy, tcl-format +#: lib/index.tcl:6 +msgid "Unable to unlock the index." +msgstr "无法解锁缓存 (index)" + +#: lib/index.tcl:15 +msgid "Index Error" +msgstr "缓存(Index)错误" + +#: lib/index.tcl:21 +msgid "" +"Updating the Git index failed. A rescan will be automatically started to " +"resynchronize git-gui." +msgstr "更新 Git 缓存(Index)失败, 重新扫描将自动开始以重新同步 git-gui." + +#: lib/index.tcl:27 +msgid "Continue" +msgstr "继续" + +#: lib/index.tcl:31 +msgid "Unlock Index" +msgstr "解锁 Index" + +#: lib/index.tcl:282 +#, tcl-format msgid "Unstaging %s from commit" -msgstr "从本次提交移除" +msgstr "从提交缓存中删除 %s" -#: lib/index.tcl:285 +#: lib/index.tcl:326 #, tcl-format msgid "Adding %s" -msgstr "" +msgstr "添加 %s" -#: lib/index.tcl:340 -#, fuzzy, tcl-format +#: lib/index.tcl:381 +#, tcl-format msgid "Revert changes in file %s?" -msgstr "恢复修改" +msgstr "撤销文件 %s 中的改动?" -#: lib/index.tcl:342 +#: lib/index.tcl:383 #, tcl-format msgid "Revert changes in these %i files?" -msgstr "" +msgstr "撤销这些 (%i个) 文件的改动?" -#: lib/index.tcl:348 +#: lib/index.tcl:389 msgid "Any unstaged changes will be permanently lost by the revert." -msgstr "" +msgstr "任何未缓存的改动将在这次撤销中永久丢失." -#: lib/index.tcl:351 +#: lib/index.tcl:392 msgid "Do Nothing" -msgstr "" +msgstr "不做操作" #: lib/merge.tcl:13 msgid "" @@ -1404,6 +1511,9 @@ msgid "" "\n" "You must finish amending this commit before starting any type of merge.\n" msgstr "" +"修正时无法做合并.\n" +"\n" +"你必须完成对该提交的修正才能继续任何类型的合并操作.\n" #: lib/merge.tcl:27 msgid "" @@ -1414,6 +1524,12 @@ msgid "" "\n" "The rescan will be automatically started now.\n" msgstr "" +"最后一次扫描的状态和当前版本库状态不符.\n" +"\n" +"另一 Git 程序自上次扫描后修改了本版本库. 在修改当前分支之前需要重新做一次扫" +"描.\n" +"\n" +"重新扫描将自动开始.\n" #: lib/merge.tcl:44 #, tcl-format @@ -1425,6 +1541,12 @@ msgid "" "You must resolve them, stage the file, and commit to complete the current " "merge. Only then can you begin another merge.\n" msgstr "" +"你正处在一个有冲突的合并操作中.\n" +"\n" +"文件 %s 有合并冲突.\n" +"\n" +"你必须解决这些冲突, 缓存该文件, 并提交来完成当前的合并.仅当这样后才能开始下一" +"个合并操作.\n" #: lib/merge.tcl:54 #, tcl-format @@ -1436,6 +1558,12 @@ msgid "" "You should complete the current commit before starting a merge. Doing so " "will help you abort a failed merge, should the need arise.\n" msgstr "" +"你正处在一个改动当中.\n" +"\n" +"文件 %s 已被修改.\n" +"\n" +"你必须完成当前的提交后才能开始合并. 如果需要, 这么做将有助于" +"中止一次失败的合并.\n" #: lib/merge.tcl:106 #, tcl-format @@ -1445,24 +1573,24 @@ msgstr "" #: lib/merge.tcl:119 #, tcl-format msgid "Merging %s and %s" -msgstr "" +msgstr "合并 %s 和 %s" #: lib/merge.tcl:131 msgid "Merge completed successfully." -msgstr "" +msgstr "合并成功完成." #: lib/merge.tcl:133 msgid "Merge failed. Conflict resolution is required." -msgstr "" +msgstr "合并失败. 需要解决冲突." #: lib/merge.tcl:158 #, tcl-format msgid "Merge Into %s" -msgstr "" +msgstr "合并到 %s" #: lib/merge.tcl:177 msgid "Revision To Merge" -msgstr "" +msgstr "要合并的版本" #: lib/merge.tcl:212 msgid "" @@ -1470,6 +1598,9 @@ msgid "" "\n" "You must finish amending this commit.\n" msgstr "" +"修正操作中无法中止.\n" +"\n" +"你必须先完成本次修正操作.\n" #: lib/merge.tcl:222 msgid "" @@ -1479,6 +1610,11 @@ msgid "" "\n" "Continue with aborting the current merge?" msgstr "" +"中止合并?\n" +"\n" +"中止当前的合并操作将导致 *所有* 尚未提交的改动丢失.\n" +"\n" +"是否要继续中止当前的合并操作?" #: lib/merge.tcl:228 msgid "" @@ -1488,150 +1624,137 @@ msgid "" "\n" "Continue with resetting the current changes?" msgstr "" +"是否复位当前改动?\n" +"\n" +"复位当前的改动将导致 *所有* 未提交的改动丢失.\n" +"\n" +"是否要继续复位当前的改动?" #: lib/merge.tcl:239 msgid "Aborting" -msgstr "" +msgstr "中止" #: lib/merge.tcl:266 msgid "Abort failed." -msgstr "" +msgstr "中止失败" #: lib/merge.tcl:268 msgid "Abort completed. Ready." -msgstr "" +msgstr "中止完成. 就绪." #: lib/option.tcl:82 msgid "Restore Defaults" -msgstr "" +msgstr "恢复默认值" #: lib/option.tcl:86 msgid "Save" -msgstr "" +msgstr "保存" #: lib/option.tcl:96 -#, fuzzy, tcl-format +#, tcl-format msgid "%s Repository" -msgstr "版本树" +msgstr "%s 版本库" #: lib/option.tcl:97 msgid "Global (All Repositories)" -msgstr "" +msgstr "全局 (所有版本库)" #: lib/option.tcl:103 msgid "User Name" -msgstr "" +msgstr "用户名" #: lib/option.tcl:104 msgid "Email Address" -msgstr "" +msgstr "Email 地址" #: lib/option.tcl:106 -#, fuzzy msgid "Summarize Merge Commits" -msgstr "修订合并提交描述:" +msgstr "概述合并提交:" #: lib/option.tcl:107 msgid "Merge Verbosity" -msgstr "" +msgstr "合并冗余度" #: lib/option.tcl:108 msgid "Show Diffstat After Merge" -msgstr "" +msgstr "在合并后显示 Diffstat" #: lib/option.tcl:110 msgid "Trust File Modification Timestamps" -msgstr "" +msgstr "相信文件的改动时间" #: lib/option.tcl:111 msgid "Prune Tracking Branches During Fetch" -msgstr "" +msgstr "获取时清除跟踪分支" #: lib/option.tcl:112 msgid "Match Tracking Branches" -msgstr "" +msgstr "匹配跟踪分支" #: lib/option.tcl:113 msgid "Number of Diff Context Lines" -msgstr "" +msgstr "Diff 上下文行数" #: lib/option.tcl:114 msgid "New Branch Name Template" -msgstr "" +msgstr "新建分支命名模板" #: lib/option.tcl:176 msgid "Change Font" -msgstr "" +msgstr "更改字体" #: lib/option.tcl:180 #, tcl-format msgid "Choose %s" -msgstr "" +msgstr "选择 %s" #: lib/option.tcl:186 msgid "pt." -msgstr "" +msgstr "磅" #: lib/option.tcl:200 msgid "Preferences" -msgstr "" +msgstr "首选项" #: lib/option.tcl:235 msgid "Failed to completely save options:" -msgstr "" - -#: lib/remote.tcl:165 -msgid "Prune from" -msgstr "" - -#: lib/remote.tcl:170 -#, fuzzy -msgid "Fetch from" -msgstr "导入" - -#: lib/remote.tcl:213 -#, fuzzy -msgid "Push to" -msgstr "上传" +msgstr "无法完全保存选项:" #: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 msgid "Delete Remote Branch" -msgstr "" +msgstr "删除远端分支" #: lib/remote_branch_delete.tcl:47 -#, fuzzy msgid "From Repository" -msgstr "版本树" +msgstr "从版本库" #: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 msgid "Remote:" -msgstr "" +msgstr "Remote:" #: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138 msgid "Arbitrary URL:" -msgstr "" +msgstr "任意 URL:" #: lib/remote_branch_delete.tcl:84 -#, fuzzy msgid "Branches" msgstr "分支" #: lib/remote_branch_delete.tcl:109 -#, fuzzy msgid "Delete Only If" -msgstr "删除" +msgstr "删除仅当" #: lib/remote_branch_delete.tcl:111 msgid "Merged Into:" -msgstr "" +msgstr "合并到" #: lib/remote_branch_delete.tcl:119 msgid "Always (Do not perform merge checks)" -msgstr "" +msgstr "总是合并 (不作合并检查)" #: lib/remote_branch_delete.tcl:152 msgid "A branch is required for 'Merged Into'." -msgstr "" +msgstr "'合并到' 需要指定某个分支" #: lib/remote_branch_delete.tcl:184 #, tcl-format @@ -1640,6 +1763,9 @@ msgid "" "\n" " - %s" msgstr "" +"下列分支没有被全部合并到 %s 中:\n" +"\n" +" - %s" #: lib/remote_branch_delete.tcl:189 #, tcl-format @@ -1647,10 +1773,11 @@ msgid "" "One or more of the merge tests failed because you have not fetched the " "necessary commits. Try fetching from %s first." msgstr "" +"由于没有获取到必要的提交,一个或多个合并测试失败。请尝试从 %s 处先获取。" #: lib/remote_branch_delete.tcl:207 msgid "Please select one or more branches to delete." -msgstr "" +msgstr "请选择某个或多个分支来删除" #: lib/remote_branch_delete.tcl:216 msgid "" @@ -1658,112 +1785,108 @@ msgid "" "\n" "Delete the selected branches?" msgstr "" +"恢复被删除的分支非常困难.\n" +"\n" +"是否要删除所选分支?" #: lib/remote_branch_delete.tcl:226 #, tcl-format msgid "Deleting branches from %s" -msgstr "" +msgstr "从 %s 中删除分支" #: lib/remote_branch_delete.tcl:286 msgid "No repository selected." -msgstr "" +msgstr "没有选择版本库" #: lib/remote_branch_delete.tcl:291 #, tcl-format msgid "Scanning %s..." -msgstr "" +msgstr "正在扫描 %s..." -#: lib/shortcut.tcl:26 lib/shortcut.tcl:74 -msgid "Cannot write script:" -msgstr "" +#: lib/remote.tcl:165 +msgid "Prune from" +msgstr "从..清除(prune)" -#: lib/shortcut.tcl:149 +#: lib/remote.tcl:170 +msgid "Fetch from" +msgstr "从..获取(fetch)" + +#: lib/remote.tcl:213 +msgid "Push to" +msgstr "上传到(push)" + +#: lib/shortcut.tcl:20 lib/shortcut.tcl:61 +msgid "Cannot write shortcut:" +msgstr "无法修改快捷方式:" + +#: lib/shortcut.tcl:136 msgid "Cannot write icon:" -msgstr "" +msgstr "无法修改图标:" #: lib/status_bar.tcl:83 #, tcl-format msgid "%s ... %*i of %*i %s (%3i%%)" -msgstr "" +msgstr "%s ... %*i of %*i %s (%3i%%)" #: lib/transport.tcl:6 -#, fuzzy, tcl-format +#, tcl-format msgid "fetch %s" -msgstr "导入" +msgstr "获取(fetch)" #: lib/transport.tcl:7 #, tcl-format msgid "Fetching new changes from %s" -msgstr "" +msgstr "从 %s 处获取新的改动" #: lib/transport.tcl:18 #, tcl-format msgid "remote prune %s" -msgstr "" +msgstr "清除远端 %s" #: lib/transport.tcl:19 #, tcl-format msgid "Pruning tracking branches deleted from %s" -msgstr "" +msgstr "清除" #: lib/transport.tcl:25 lib/transport.tcl:71 #, tcl-format msgid "push %s" -msgstr "" +msgstr "上传 %s" #: lib/transport.tcl:26 #, tcl-format msgid "Pushing changes to %s" -msgstr "" +msgstr "上传改动到 %s" #: lib/transport.tcl:72 #, tcl-format msgid "Pushing %s %s to %s" -msgstr "" +msgstr "上传 %s %s 到 %s" #: lib/transport.tcl:89 -#, fuzzy msgid "Push Branches" -msgstr "分支" +msgstr "上传分支" #: lib/transport.tcl:103 -#, fuzzy msgid "Source Branches" -msgstr "当前分支:" +msgstr "源端分支:" #: lib/transport.tcl:120 -#, fuzzy msgid "Destination Repository" -msgstr "版本树" +msgstr "目标版本库" #: lib/transport.tcl:158 msgid "Transfer Options" -msgstr "" +msgstr "传输选项" #: lib/transport.tcl:160 msgid "Force overwrite existing branch (may discard changes)" -msgstr "" +msgstr "强制覆盖已有的分支 (可能会丢失改动)" #: lib/transport.tcl:164 msgid "Use thin pack (for slow network connections)" -msgstr "" +msgstr "使用 thin pack (适用于低速网络连接)" #: lib/transport.tcl:168 msgid "Include tags" -msgstr "" - -#~ msgid "Add To Commit" -#~ msgstr "添加到本次提交" - -#~ msgid "Add Existing To Commit" -#~ msgstr "添加默认修改文件" - -#~ msgid "Unstaged Changes (Will Not Be Committed)" -#~ msgstr "不被提交的修改" - -#~ msgid "Add Existing" -#~ msgstr "添加默认修改文件" - -#, fuzzy -#~ msgid "Push to %s..." -#~ msgstr "上传..." +msgstr "包含标签" From c382fdd79548a56fadf8f34d3484cf020eda3966 Mon Sep 17 00:00:00 2001 From: "Philipp A. Hartmann" Date: Wed, 5 Mar 2008 17:54:22 +0100 Subject: [PATCH 11/42] git-gui: if a background colour is set, set foreground colour as well In several places, only the background colour is set to an explicit value, sometimes even "white". This does not work well with dark colour themes. This patch tries to set the foreground colour to "black" in those situations, where an explicit background colour is set without defining any foreground colour. Signed-off-by: Philipp A. Hartmann Signed-off-by: Shawn O. Pearce --- git-gui.sh | 19 +++++++++++++------ lib/blame.tcl | 25 ++++++++++++++++++++----- lib/browser.tcl | 3 ++- lib/choose_font.tcl | 2 ++ lib/console.tcl | 10 +++++++--- lib/error.tcl | 4 +++- 6 files changed, 47 insertions(+), 16 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 238a2393ff..874144397c 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2289,8 +2289,9 @@ pack .vpane -anchor n -side top -fill both -expand 1 # frame .vpane.files.index -height 100 -width 200 label .vpane.files.index.title -text [mc "Staged Changes (Will Commit)"] \ - -background lightgreen -text $ui_index -background white -borderwidth 0 \ + -background lightgreen -foreground black +text $ui_index -background white -foreground black \ + -borderwidth 0 \ -width 20 -height 10 \ -wrap none \ -cursor $cursor_ptr \ @@ -2308,8 +2309,9 @@ pack $ui_index -side left -fill both -expand 1 # frame .vpane.files.workdir -height 100 -width 200 label .vpane.files.workdir.title -text [mc "Unstaged Changes"] \ - -background lightsalmon -text $ui_workdir -background white -borderwidth 0 \ + -background lightsalmon -foreground black +text $ui_workdir -background white -foreground black \ + -borderwidth 0 \ -width 20 -height 10 \ -wrap none \ -cursor $cursor_ptr \ @@ -2416,7 +2418,8 @@ pack $ui_coml -side left -fill x pack .vpane.lower.commarea.buffer.header.amend -side right pack .vpane.lower.commarea.buffer.header.new -side right -text $ui_comm -background white -borderwidth 1 \ +text $ui_comm -background white -foreground black \ + -borderwidth 1 \ -undo true \ -maxundo 20 \ -autoseparators true \ @@ -2493,15 +2496,18 @@ trace add variable current_diff_path write trace_current_diff_path frame .vpane.lower.diff.header -background gold label .vpane.lower.diff.header.status \ -background gold \ + -foreground black \ -width $max_status_desc \ -anchor w \ -justify left label .vpane.lower.diff.header.file \ -background gold \ + -foreground black \ -anchor w \ -justify left label .vpane.lower.diff.header.path \ -background gold \ + -foreground black \ -anchor w \ -justify left pack .vpane.lower.diff.header.status -side left @@ -2525,7 +2531,8 @@ bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y" # frame .vpane.lower.diff.body set ui_diff .vpane.lower.diff.body.t -text $ui_diff -background white -borderwidth 0 \ +text $ui_diff -background white -foreground black \ + -borderwidth 0 \ -width 80 -height 15 -wrap none \ -font font_diff \ -xscrollcommand {.vpane.lower.diff.body.sbx set} \ diff --git a/lib/blame.tcl b/lib/blame.tcl index 00ecf21333..92fac1bad4 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -80,6 +80,7 @@ constructor new {i_commit i_path} { label $w.header.commit_l \ -text [mc "Commit:"] \ -background gold \ + -foreground black \ -anchor w \ -justify left set w_back $w.header.commit_b @@ -89,6 +90,7 @@ constructor new {i_commit i_path} { -relief flat \ -state disabled \ -background gold \ + -foreground black \ -activebackground gold bind $w_back " if {\[$w_back cget -state\] eq {normal}} { @@ -98,16 +100,19 @@ constructor new {i_commit i_path} { label $w.header.commit \ -textvariable @commit \ -background gold \ + -foreground black \ -anchor w \ -justify left label $w.header.path_l \ -text [mc "File:"] \ -background gold \ + -foreground black \ -anchor w \ -justify left set w_path $w.header.path label $w_path \ -background gold \ + -foreground black \ -anchor w \ -justify left pack $w.header.commit_l -side left @@ -135,7 +140,9 @@ constructor new {i_commit i_path} { -takefocus 0 \ -highlightthickness 0 \ -padx 0 -pady 0 \ - -background white -borderwidth 0 \ + -background white \ + -foreground black \ + -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ @@ -148,7 +155,9 @@ constructor new {i_commit i_path} { -takefocus 0 \ -highlightthickness 0 \ -padx 0 -pady 0 \ - -background white -borderwidth 0 \ + -background white \ + -foreground black \ + -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ @@ -166,7 +175,9 @@ constructor new {i_commit i_path} { -takefocus 0 \ -highlightthickness 0 \ -padx 0 -pady 0 \ - -background white -borderwidth 0 \ + -background white \ + -foreground black \ + -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ @@ -184,7 +195,9 @@ constructor new {i_commit i_path} { -takefocus 0 \ -highlightthickness 0 \ -padx 0 -pady 0 \ - -background white -borderwidth 0 \ + -background white \ + -foreground black \ + -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ @@ -213,7 +226,9 @@ constructor new {i_commit i_path} { set w_cviewer $w.file_pane.cm.t text $w_cviewer \ - -background white -borderwidth 0 \ + -background white \ + -foreground black \ + -borderwidth 0 \ -state disabled \ -wrap none \ -height 10 \ diff --git a/lib/browser.tcl b/lib/browser.tcl index 53d5a62816..ab470d1264 100644 --- a/lib/browser.tcl +++ b/lib/browser.tcl @@ -39,7 +39,8 @@ constructor new {commit {path {}}} { frame $w.list set w_list $w.list.l - text $w_list -background white -borderwidth 0 \ + text $w_list -background white -foreground black \ + -borderwidth 0 \ -cursor $cursor_ptr \ -state disabled \ -wrap none \ diff --git a/lib/choose_font.tcl b/lib/choose_font.tcl index 0c4051b375..56443b042c 100644 --- a/lib/choose_font.tcl +++ b/lib/choose_font.tcl @@ -55,6 +55,7 @@ constructor pick {path title a_family a_size} { set w_family $w.inner.family.v text $w_family \ -background white \ + -foreground black \ -borderwidth 1 \ -relief sunken \ -cursor $::cursor_ptr \ @@ -92,6 +93,7 @@ constructor pick {path title a_family a_size} { set w_example $w.example.t text $w_example \ -background white \ + -foreground black \ -borderwidth 1 \ -relief sunken \ -height 3 \ diff --git a/lib/console.tcl b/lib/console.tcl index 5597188d80..c112464ec3 100644 --- a/lib/console.tcl +++ b/lib/console.tcl @@ -46,7 +46,9 @@ method _init {} { -justify left \ -font font_uibold text $w_t \ - -background white -borderwidth 1 \ + -background white \ + -foreground black \ + -borderwidth 1 \ -relief sunken \ -width 80 -height 10 \ -wrap none \ @@ -180,7 +182,8 @@ method done {ok} { if {$ok} { if {[winfo exists $w.m.s]} { bind $w.m.s [list delete_this $this] - $w.m.s conf -background green -text [mc "Success"] + $w.m.s conf -background green -foreground black \ + -text [mc "Success"] if {$is_toplevel} { $w.ok conf -state normal focus $w.ok @@ -193,7 +196,8 @@ method done {ok} { _init $this } bind $w.m.s [list delete_this $this] - $w.m.s conf -background red -text [mc "Error: Command Failed"] + $w.m.s conf -background red -foreground black \ + -text [mc "Error: Command Failed"] if {$is_toplevel} { $w.ok conf -state normal focus $w.ok diff --git a/lib/error.tcl b/lib/error.tcl index 8c27678e3a..75650157e5 100644 --- a/lib/error.tcl +++ b/lib/error.tcl @@ -80,7 +80,9 @@ proc hook_failed_popup {hook msg {is_fatal 1}} { -justify left \ -font font_uibold text $w.m.t \ - -background white -borderwidth 1 \ + -background white \ + -foreground black \ + -borderwidth 1 \ -relief sunken \ -width 80 -height 10 \ -font font_diff \ From 11027d544b4f6aed0b84111a2122224cd201a182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Pi=C4=85tyszek?= Date: Thu, 6 Mar 2008 20:38:40 +0100 Subject: [PATCH 12/42] git-gui: Add option for changing the width of the commit message text box MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The width of the commit message text area is currently hard-coded to 75 characters. This value might be not optimal for some projects. For instance users who would like to generate GNU-style ChangeLog file from git commit message might prefer commit messages of width no longer than 70 characters. This patch adds a global and per repository option "Commit Message Text Width", which could be used to change the width of the commit message text area. Signed-off-by: Adam Piątyszek Signed-off-by: Shawn O. Pearce --- git-gui.sh | 3 ++- lib/option.tcl | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index 874144397c..3a58cd2c6b 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -611,6 +611,7 @@ set default_config(gui.matchtrackingbranch) false set default_config(gui.pruneduringfetch) false set default_config(gui.trustmtime) false set default_config(gui.diffcontext) 5 +set default_config(gui.commitmsgwidth) 75 set default_config(gui.newbranchtemplate) {} set default_config(gui.spellingdictionary) {} set default_config(gui.fontui) [font configure font_ui] @@ -2424,7 +2425,7 @@ text $ui_comm -background white -foreground black \ -maxundo 20 \ -autoseparators true \ -relief sunken \ - -width 75 -height 9 -wrap none \ + -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \ -font font_diff \ -yscrollcommand {.vpane.lower.commarea.buffer.sby set} scrollbar .vpane.lower.commarea.buffer.sby \ diff --git a/lib/option.tcl b/lib/option.tcl index ea80df0092..9270512582 100644 --- a/lib/option.tcl +++ b/lib/option.tcl @@ -124,6 +124,7 @@ proc do_options {} { {b gui.pruneduringfetch {mc "Prune Tracking Branches During Fetch"}} {b gui.matchtrackingbranch {mc "Match Tracking Branches"}} {i-0..99 gui.diffcontext {mc "Number of Diff Context Lines"}} + {i-0..99 gui.commitmsgwidth {mc "Commit Message Text Width"}} {t gui.newbranchtemplate {mc "New Branch Name Template"}} } { set type [lindex $option 0] From 3b9dcff5df97ab642de48bc1ce781961686081aa Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 8 Mar 2008 23:40:42 +0100 Subject: [PATCH 13/42] builtin remote rm: remove symbolic refs, too "git remote add" can add a symbolic ref "HEAD", and "rm" should delete it, too. Noticed by Teemu Likonen. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-remote.c | 5 +++++ t/t5505-remote.sh | 1 + 2 files changed, 6 insertions(+) diff --git a/builtin-remote.c b/builtin-remote.c index ca3bf265a9..637b90425e 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -264,6 +264,11 @@ static int add_branch_for_removal(const char *refname, if (!prefixcmp(refname, branches->prefix)) { struct path_list_item *item; + + /* make sure that symrefs are deleted */ + if (flags & REF_ISSYMREF) + return unlink(git_path(refname)); + item = path_list_append(refname, branches->branches); item->util = xmalloc(20); hashcpy(item->util, sha1); diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index f45ea68f63..2822a651b5 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -80,6 +80,7 @@ test_expect_success 'add another remote' ' test_expect_success 'remove remote' ' ( cd test && + git symbolic-ref refs/remotes/second/HEAD refs/remotes/second/master && git remote rm second ) ' From 0ab9e1e8cdaefdd33bf24bb0be0ec766483f8bbe Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 5 Mar 2008 18:25:10 -0800 Subject: [PATCH 14/42] Add 'df_name_compare()' helper function This new helper is identical to base_name_compare(), except it compares conflicting directory/file entries as equal in order to help handling DF conflicts (thus the name). Note that while a directory name compares as equal to a regular file with the new helper, they then individually compare _differently_ to a filename that has a dot after the basename (because '\0' < '.' < '/'). So a directory called "foo/" will compare equal to a file "foo", even though "foo.c" will compare after "foo" and before "foo/" This will be used by routines that want to traverse the git namespace but then handle conflicting entries together when possible. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- cache.h | 1 + read-cache.c | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/cache.h b/cache.h index e230302623..6eb16cb0df 100644 --- a/cache.h +++ b/cache.h @@ -536,6 +536,7 @@ extern int create_symref(const char *ref, const char *refs_heads_master, const c extern int validate_headref(const char *ref); extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); +extern int df_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2); extern void *read_object_with_reference(const unsigned char *sha1, diff --git a/read-cache.c b/read-cache.c index 657f0c5894..bf649a31a8 100644 --- a/read-cache.c +++ b/read-cache.c @@ -351,6 +351,41 @@ int base_name_compare(const char *name1, int len1, int mode1, return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; } +/* + * df_name_compare() is identical to base_name_compare(), except it + * compares conflicting directory/file entries as equal. Note that + * while a directory name compares as equal to a regular file, they + * then individually compare _differently_ to a filename that has + * a dot after the basename (because '\0' < '.' < '/'). + * + * This is used by routines that want to traverse the git namespace + * but then handle conflicting entries together when possible. + */ +int df_name_compare(const char *name1, int len1, int mode1, + const char *name2, int len2, int mode2) +{ + int len = len1 < len2 ? len1 : len2, cmp; + unsigned char c1, c2; + + cmp = memcmp(name1, name2, len); + if (cmp) + return cmp; + /* Directories and files compare equal (same length, same name) */ + if (len1 == len2) + return 0; + c1 = name1[len]; + if (!c1 && S_ISDIR(mode1)) + c1 = '/'; + c2 = name2[len]; + if (!c2 && S_ISDIR(mode2)) + c2 = '/'; + if (c1 == '/' && !c2) + return 0; + if (c2 == '/' && !c1) + return 0; + return c1 - c2; +} + int cache_name_compare(const char *name1, int flags1, const char *name2, int flags2) { int len1 = flags1 & CE_NAMEMASK; From 40d934df72eaf244c826d5c26da0896ce7185cb6 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 5 Mar 2008 18:59:29 -0800 Subject: [PATCH 15/42] Make 'traverse_tree()' use linked structure rather than 'const char *base' This makes the calling convention a bit less obvious, but a lot more flexible. Instead of allocating and extending a new 'base' string, we just link the top-most name into a linked list of the 'info' structure when traversing a subdirectory, and we can generate the basename by following the list. Perhaps even more importantly, the linked list of info structures also gives us a place to naturally save off other information than just the directory name. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- merge-tree.c | 51 +++++++++++++++++++++++++++------------------------ tree-walk.c | 35 +++++++++++++++++++++++++++++++++-- tree-walk.h | 20 ++++++++++++++++++-- 3 files changed, 78 insertions(+), 28 deletions(-) diff --git a/merge-tree.c b/merge-tree.c index e08324686c..a3511b76ca 100644 --- a/merge-tree.c +++ b/merge-tree.c @@ -168,7 +168,13 @@ static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsi return res; } -static void resolve(const char *base, struct name_entry *branch1, struct name_entry *result) +static char *traverse_path(const struct traverse_info *info, const struct name_entry *n) +{ + char *path = xmalloc(traverse_path_len(info, n) + 1); + return make_traverse_path(path, info, n); +} + +static void resolve(const struct traverse_info *info, struct name_entry *branch1, struct name_entry *result) { struct merge_list *orig, *final; const char *path; @@ -177,7 +183,7 @@ static void resolve(const char *base, struct name_entry *branch1, struct name_en if (!branch1) return; - path = xstrdup(mkpath("%s%s", base, result->path)); + path = traverse_path(info, result); orig = create_entry(2, branch1->mode, branch1->sha1, path); final = create_entry(0, result->mode, result->sha1, path); @@ -186,9 +192,8 @@ static void resolve(const char *base, struct name_entry *branch1, struct name_en add_merge_entry(final); } -static int unresolved_directory(const char *base, struct name_entry n[3]) +static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3]) { - int baselen, pathlen; char *newbase; struct name_entry *p; struct tree_desc t[3]; @@ -204,13 +209,7 @@ static int unresolved_directory(const char *base, struct name_entry n[3]) } if (!S_ISDIR(p->mode)) return 0; - baselen = strlen(base); - pathlen = tree_entry_len(p->path, p->sha1); - newbase = xmalloc(baselen + pathlen + 2); - memcpy(newbase, base, baselen); - memcpy(newbase + baselen, p->path, pathlen); - memcpy(newbase + baselen + pathlen, "/", 2); - + newbase = traverse_path(info, p); buf0 = fill_tree_descriptor(t+0, n[0].sha1); buf1 = fill_tree_descriptor(t+1, n[1].sha1); buf2 = fill_tree_descriptor(t+2, n[2].sha1); @@ -224,7 +223,7 @@ static int unresolved_directory(const char *base, struct name_entry n[3]) } -static struct merge_list *link_entry(unsigned stage, const char *base, struct name_entry *n, struct merge_list *entry) +static struct merge_list *link_entry(unsigned stage, const struct traverse_info *info, struct name_entry *n, struct merge_list *entry) { const char *path; struct merge_list *link; @@ -234,17 +233,17 @@ static struct merge_list *link_entry(unsigned stage, const char *base, struct na if (entry) path = entry->path; else - path = xstrdup(mkpath("%s%s", base, n->path)); + path = traverse_path(info, n); link = create_entry(stage, n->mode, n->sha1, path); link->link = entry; return link; } -static void unresolved(const char *base, struct name_entry n[3]) +static void unresolved(const struct traverse_info *info, struct name_entry n[3]) { struct merge_list *entry = NULL; - if (unresolved_directory(base, n)) + if (unresolved_directory(info, n)) return; /* @@ -252,9 +251,9 @@ static void unresolved(const char *base, struct name_entry n[3]) * list has the stages in order - link_entry adds new * links at the front. */ - entry = link_entry(3, base, n + 2, entry); - entry = link_entry(2, base, n + 1, entry); - entry = link_entry(1, base, n + 0, entry); + entry = link_entry(3, info, n + 2, entry); + entry = link_entry(2, info, n + 1, entry); + entry = link_entry(1, info, n + 0, entry); add_merge_entry(entry); } @@ -288,36 +287,40 @@ static void unresolved(const char *base, struct name_entry n[3]) * The successful merge rules are the same as for the three-way merge * in git-read-tree. */ -static void threeway_callback(int n, unsigned long mask, struct name_entry *entry, const char *base) +static void threeway_callback(int n, unsigned long mask, struct name_entry *entry, struct traverse_info *info) { /* Same in both? */ if (same_entry(entry+1, entry+2)) { if (entry[0].sha1) { - resolve(base, NULL, entry+1); + resolve(info, NULL, entry+1); return; } } if (same_entry(entry+0, entry+1)) { if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) { - resolve(base, entry+1, entry+2); + resolve(info, entry+1, entry+2); return; } } if (same_entry(entry+0, entry+2)) { if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) { - resolve(base, NULL, entry+1); + resolve(info, NULL, entry+1); return; } } - unresolved(base, entry); + unresolved(info, entry); } static void merge_trees(struct tree_desc t[3], const char *base) { - traverse_trees(3, t, base, threeway_callback); + struct traverse_info info; + + setup_traverse_info(&info, base); + info.fn = threeway_callback; + traverse_trees(3, t, &info); } static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) diff --git a/tree-walk.c b/tree-walk.c index 142205ddc3..f9f7d225e9 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -104,7 +104,38 @@ int tree_entry(struct tree_desc *desc, struct name_entry *entry) return 1; } -void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback) +void setup_traverse_info(struct traverse_info *info, const char *base) +{ + int pathlen = strlen(base); + + memset(info, 0, sizeof(*info)); + if (pathlen && base[pathlen-1] == '/') + pathlen--; + info->pathlen = pathlen ? pathlen + 1 : 0; + info->name.path = base; + info->name.sha1 = (void *)(base + pathlen + 1); +} + +char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n) +{ + int len = tree_entry_len(n->path, n->sha1); + int pathlen = info->pathlen; + + path[pathlen + len] = 0; + for (;;) { + memcpy(path + pathlen, n->path, len); + if (!pathlen) + break; + path[--pathlen] = '/'; + n = &info->name; + len = tree_entry_len(n->path, n->sha1); + info = info->prev; + pathlen -= len; + } + return path; +} + +void traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) { struct name_entry *entry = xmalloc(n*sizeof(*entry)); @@ -150,7 +181,7 @@ void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callb } entry_clear(entry + i); } - callback(n, mask, entry, base); + info->fn(n, mask, entry, info); } free(entry); } diff --git a/tree-walk.h b/tree-walk.h index db0fbdc701..7c4ae646be 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -33,10 +33,26 @@ int tree_entry(struct tree_desc *, struct name_entry *); void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1); -typedef void (*traverse_callback_t)(int n, unsigned long mask, struct name_entry *entry, const char *base); +struct traverse_info; +typedef void (*traverse_callback_t)(int n, unsigned long mask, struct name_entry *entry, struct traverse_info *); +void traverse_trees(int n, struct tree_desc *t, struct traverse_info *info); -void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback); +struct traverse_info { + struct traverse_info *prev; + struct name_entry name; + int pathlen; + + traverse_callback_t fn; + void *data; +}; int get_tree_entry(const unsigned char *, const char *, unsigned char *, unsigned *); +extern char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n); +extern void setup_traverse_info(struct traverse_info *info, const char *base); + +static inline int traverse_path_len(const struct traverse_info *info, const struct name_entry *n) +{ + return info->pathlen + tree_entry_len(n->path, n->sha1); +} #endif From 5803c6f8a2faf8cfbbd046d9ebd682b82bb2b086 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 5 Mar 2008 19:44:06 -0800 Subject: [PATCH 16/42] Add return value to 'traverse_tree()' callback This allows the callback to return an error value, but it can also specify which of the tree entries that it actually used up by returning a positive mask value. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- merge-tree.c | 9 +++++---- tree-walk.c | 22 +++++++++++++++------- tree-walk.h | 4 ++-- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/merge-tree.c b/merge-tree.c index a3511b76ca..8be0b9f910 100644 --- a/merge-tree.c +++ b/merge-tree.c @@ -287,31 +287,32 @@ static void unresolved(const struct traverse_info *info, struct name_entry n[3]) * The successful merge rules are the same as for the three-way merge * in git-read-tree. */ -static void threeway_callback(int n, unsigned long mask, struct name_entry *entry, struct traverse_info *info) +static int threeway_callback(int n, unsigned long mask, struct name_entry *entry, struct traverse_info *info) { /* Same in both? */ if (same_entry(entry+1, entry+2)) { if (entry[0].sha1) { resolve(info, NULL, entry+1); - return; + return mask; } } if (same_entry(entry+0, entry+1)) { if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) { resolve(info, entry+1, entry+2); - return; + return mask; } } if (same_entry(entry+0, entry+2)) { if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) { resolve(info, NULL, entry+1); - return; + return mask; } } unresolved(info, entry); + return mask; } static void merge_trees(struct tree_desc t[3], const char *base) diff --git a/tree-walk.c b/tree-walk.c index f9f7d225e9..7170e375b3 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -135,8 +135,9 @@ char *make_traverse_path(char *path, const struct traverse_info *info, const str return path; } -void traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) +int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) { + int ret = 0; struct name_entry *entry = xmalloc(n*sizeof(*entry)); for (;;) { @@ -171,19 +172,26 @@ void traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) break; /* - * Update the tree entries we've walked, and clear - * all the unused name-entries. + * Clear all the unused name-entries. */ for (i = 0; i < n; i++) { - if (mask & (1ul << i)) { - update_tree_entry(t+i); + if (mask & (1ul << i)) continue; - } entry_clear(entry + i); } - info->fn(n, mask, entry, info); + ret = info->fn(n, mask, entry, info); + if (ret < 0) + break; + if (ret) + mask &= ret; + ret = 0; + for (i = 0; i < n; i++) { + if (mask & (1ul << i)) + update_tree_entry(t + i); + } } free(entry); + return ret; } static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode) diff --git a/tree-walk.h b/tree-walk.h index 7c4ae646be..c123cfeef4 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -34,8 +34,8 @@ int tree_entry(struct tree_desc *, struct name_entry *); void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1); struct traverse_info; -typedef void (*traverse_callback_t)(int n, unsigned long mask, struct name_entry *entry, struct traverse_info *); -void traverse_trees(int n, struct tree_desc *t, struct traverse_info *info); +typedef int (*traverse_callback_t)(int n, unsigned long mask, struct name_entry *entry, struct traverse_info *); +int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info); struct traverse_info { struct traverse_info *prev; From 91e4f03604bd089e09154e95294d5d08c805ea49 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 5 Mar 2008 20:06:18 -0800 Subject: [PATCH 17/42] Make 'traverse_trees()' traverse conflicting DF entries in parallel This makes the traverse_trees() entry comparator routine use the more relaxed form of name comparison that considers files and directories with the same name identical. We pass in a separate mask for just the directory entries, so that the callback routine can decide (if it wants to) to only handle one or the other type, but generally most (all?) users are expected to really want to see the case of a name 'foo' showing up in one tree as a file and in another as a directory at the same time. In particular, moving 'unpack_trees()' over to use this tree traversal mechanism requires this. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- merge-tree.c | 2 +- tree-walk.c | 8 ++++++-- tree-walk.h | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/merge-tree.c b/merge-tree.c index 8be0b9f910..02fc10f7e6 100644 --- a/merge-tree.c +++ b/merge-tree.c @@ -287,7 +287,7 @@ static void unresolved(const struct traverse_info *info, struct name_entry n[3]) * The successful merge rules are the same as for the three-way merge * in git-read-tree. */ -static int threeway_callback(int n, unsigned long mask, struct name_entry *entry, struct traverse_info *info) +static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info) { /* Same in both? */ if (same_entry(entry+1, entry+2)) { diff --git a/tree-walk.c b/tree-walk.c index 7170e375b3..842cb6ad2e 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -62,7 +62,7 @@ void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1) static int entry_compare(struct name_entry *a, struct name_entry *b) { - return base_name_compare( + return df_name_compare( a->path, tree_entry_len(a->path, a->sha1), a->mode, b->path, tree_entry_len(b->path, b->sha1), b->mode); } @@ -142,6 +142,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) for (;;) { unsigned long mask = 0; + unsigned long dirmask = 0; int i, last; last = -1; @@ -166,10 +167,13 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) mask = 0; } mask |= 1ul << i; + if (S_ISDIR(entry[i].mode)) + dirmask |= 1ul << i; last = i; } if (!mask) break; + dirmask &= mask; /* * Clear all the unused name-entries. @@ -179,7 +183,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info) continue; entry_clear(entry + i); } - ret = info->fn(n, mask, entry, info); + ret = info->fn(n, mask, dirmask, entry, info); if (ret < 0) break; if (ret) diff --git a/tree-walk.h b/tree-walk.h index c123cfeef4..42110a465f 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -34,7 +34,7 @@ int tree_entry(struct tree_desc *, struct name_entry *); void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1); struct traverse_info; -typedef int (*traverse_callback_t)(int n, unsigned long mask, struct name_entry *entry, struct traverse_info *); +typedef int (*traverse_callback_t)(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *); int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info); struct traverse_info { @@ -42,6 +42,7 @@ struct traverse_info { struct name_entry name; int pathlen; + unsigned long conflicts; traverse_callback_t fn; void *data; }; From 01904572a5cf869983fab454f552542f16b1fe1f Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 5 Mar 2008 20:15:44 -0800 Subject: [PATCH 18/42] Move 'unpack_trees()' over to 'traverse_trees()' interface This not only deletes more code than it adds, it gets rid of a singularly hard-to-understand function (unpack_trees_rec()), and replaces it with a set of smaller and simpler functions that use the generic tree traversal mechanism to walk over one or more git trees in parallel. It's still not the most wonderful interface, and by no means is the new code easy to understand either, but it's at least a bit less opaque. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- unpack-trees.c | 530 +++++++++++++++++++++++-------------------------- 1 file changed, 249 insertions(+), 281 deletions(-) diff --git a/unpack-trees.c b/unpack-trees.c index 3e448d8974..ee9be29374 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -7,270 +7,12 @@ #include "progress.h" #include "refs.h" -#define DBRT_DEBUG 1 - -struct tree_entry_list { - struct tree_entry_list *next; - unsigned int mode; - const char *name; - const unsigned char *sha1; -}; - -static struct tree_entry_list *create_tree_entry_list(struct tree_desc *desc) -{ - struct name_entry one; - struct tree_entry_list *ret = NULL; - struct tree_entry_list **list_p = &ret; - - while (tree_entry(desc, &one)) { - struct tree_entry_list *entry; - - entry = xmalloc(sizeof(struct tree_entry_list)); - entry->name = one.path; - entry->sha1 = one.sha1; - entry->mode = one.mode; - entry->next = NULL; - - *list_p = entry; - list_p = &entry->next; - } - return ret; -} - -static int entcmp(const char *name1, int dir1, const char *name2, int dir2) -{ - int len1 = strlen(name1); - int len2 = strlen(name2); - int len = len1 < len2 ? len1 : len2; - int ret = memcmp(name1, name2, len); - unsigned char c1, c2; - if (ret) - return ret; - c1 = name1[len]; - c2 = name2[len]; - if (!c1 && dir1) - c1 = '/'; - if (!c2 && dir2) - c2 = '/'; - ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; - if (c1 && c2 && !ret) - ret = len1 - len2; - return ret; -} - static inline void remove_entry(int remove) { if (remove >= 0) remove_cache_entry_at(remove); } -static int unpack_trees_rec(struct tree_entry_list **posns, int len, - const char *base, struct unpack_trees_options *o, - struct tree_entry_list *df_conflict_list) -{ - int remove; - int baselen = strlen(base); - int src_size = len + 1; - int retval = 0; - - do { - int i; - const char *first; - int firstdir = 0; - int pathlen; - unsigned ce_size; - struct tree_entry_list **subposns; - struct cache_entry **src; - int any_files = 0; - int any_dirs = 0; - char *cache_name; - int ce_stage; - int skip_entry = 0; - - /* Find the first name in the input. */ - - first = NULL; - cache_name = NULL; - - /* Check the cache */ - if (o->merge && o->pos < active_nr) { - /* This is a bit tricky: */ - /* If the index has a subdirectory (with - * contents) as the first name, it'll get a - * filename like "foo/bar". But that's after - * "foo", so the entry in trees will get - * handled first, at which point we'll go into - * "foo", and deal with "bar" from the index, - * because the base will be "foo/". The only - * way we can actually have "foo/bar" first of - * all the things is if the trees don't - * contain "foo" at all, in which case we'll - * handle "foo/bar" without going into the - * directory, but that's fine (and will return - * an error anyway, with the added unknown - * file case. - */ - - cache_name = active_cache[o->pos]->name; - if (strlen(cache_name) > baselen && - !memcmp(cache_name, base, baselen)) { - cache_name += baselen; - first = cache_name; - } else { - cache_name = NULL; - } - } - -#if DBRT_DEBUG > 1 - if (first) - fprintf(stderr, "index %s\n", first); -#endif - for (i = 0; i < len; i++) { - if (!posns[i] || posns[i] == df_conflict_list) - continue; -#if DBRT_DEBUG > 1 - fprintf(stderr, "%d %s\n", i + 1, posns[i]->name); -#endif - if (!first || entcmp(first, firstdir, - posns[i]->name, - S_ISDIR(posns[i]->mode)) > 0) { - first = posns[i]->name; - firstdir = S_ISDIR(posns[i]->mode); - } - } - /* No name means we're done */ - if (!first) - goto leave_directory; - - pathlen = strlen(first); - ce_size = cache_entry_size(baselen + pathlen); - - src = xcalloc(src_size, sizeof(struct cache_entry *)); - - subposns = xcalloc(len, sizeof(struct tree_list_entry *)); - - remove = -1; - if (cache_name && !strcmp(cache_name, first)) { - any_files = 1; - src[0] = active_cache[o->pos]; - remove = o->pos; - if (o->skip_unmerged && ce_stage(src[0])) - skip_entry = 1; - } - - for (i = 0; i < len; i++) { - struct cache_entry *ce; - - if (!posns[i] || - (posns[i] != df_conflict_list && - strcmp(first, posns[i]->name))) { - continue; - } - - if (posns[i] == df_conflict_list) { - src[i + o->merge] = o->df_conflict_entry; - continue; - } - - if (S_ISDIR(posns[i]->mode)) { - struct tree *tree = lookup_tree(posns[i]->sha1); - struct tree_desc t; - any_dirs = 1; - parse_tree(tree); - init_tree_desc(&t, tree->buffer, tree->size); - subposns[i] = create_tree_entry_list(&t); - posns[i] = posns[i]->next; - src[i + o->merge] = o->df_conflict_entry; - continue; - } - - if (skip_entry) { - subposns[i] = df_conflict_list; - posns[i] = posns[i]->next; - continue; - } - - if (!o->merge) - ce_stage = 0; - else if (i + 1 < o->head_idx) - ce_stage = 1; - else if (i + 1 > o->head_idx) - ce_stage = 3; - else - ce_stage = 2; - - ce = xcalloc(1, ce_size); - ce->ce_mode = create_ce_mode(posns[i]->mode); - ce->ce_flags = create_ce_flags(baselen + pathlen, - ce_stage); - memcpy(ce->name, base, baselen); - memcpy(ce->name + baselen, first, pathlen + 1); - - any_files = 1; - - hashcpy(ce->sha1, posns[i]->sha1); - src[i + o->merge] = ce; - subposns[i] = df_conflict_list; - posns[i] = posns[i]->next; - } - if (any_files) { - if (skip_entry) { - o->pos++; - while (o->pos < active_nr && - !strcmp(active_cache[o->pos]->name, - src[0]->name)) - o->pos++; - } else if (o->merge) { - int ret; - -#if DBRT_DEBUG > 1 - fprintf(stderr, "%s:\n", first); - for (i = 0; i < src_size; i++) { - fprintf(stderr, " %d ", i); - if (src[i]) - fprintf(stderr, "%06x %s\n", src[i]->ce_mode, sha1_to_hex(src[i]->sha1)); - else - fprintf(stderr, "\n"); - } -#endif - ret = o->fn(src, o, remove); - if (ret < 0) - return ret; - -#if DBRT_DEBUG > 1 - fprintf(stderr, "Added %d entries\n", ret); -#endif - o->pos += ret; - } else { - remove_entry(remove); - for (i = 0; i < src_size; i++) { - if (src[i]) { - add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); - } - } - } - } - if (any_dirs) { - char *newbase = xmalloc(baselen + 2 + pathlen); - memcpy(newbase, base, baselen); - memcpy(newbase + baselen, first, pathlen); - newbase[baselen + pathlen] = '/'; - newbase[baselen + pathlen + 1] = '\0'; - if (unpack_trees_rec(subposns, len, newbase, o, - df_conflict_list)) { - retval = -1; - goto leave_directory; - } - free(newbase); - } - free(subposns); - free(src); - } while (1); - - leave_directory: - return retval; -} - /* Unlink the last component and attempt to remove leading * directories, in case this unlink is the removal of the * last entry in the directory -- empty directories are removed. @@ -346,15 +88,241 @@ static void check_updates(struct unpack_trees_options *o) stop_progress(&progress); } +static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o, int remove) +{ + int ret = o->fn(src, o, remove); + if (ret > 0) { + o->pos += ret; + ret = 0; + } + return ret; +} + +static int unpack_index_entry(struct cache_entry *ce, struct unpack_trees_options *o) +{ + struct cache_entry *src[5] = { ce, }; + if (ce_stage(ce)) { + if (o->skip_unmerged) { + o->pos++; + } else { + remove_entry(o->pos); + } + return 0; + } + return call_unpack_fn(src, o, o->pos); +} + +int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info) +{ + int i; + struct tree_desc t[3]; + struct traverse_info newinfo; + struct name_entry *p; + + p = names; + while (!p->mode) + p++; + + newinfo = *info; + newinfo.prev = info; + newinfo.name = *p; + newinfo.pathlen += tree_entry_len(p->path, p->sha1) + 1; + newinfo.conflicts |= df_conflicts; + + for (i = 0; i < n; i++, dirmask >>= 1) { + const unsigned char *sha1 = NULL; + if (dirmask & 1) + sha1 = names[i].sha1; + fill_tree_descriptor(t+i, sha1); + } + traverse_trees(n, t, &newinfo); + return 0; +} + +/* + * Compare the traverse-path to the cache entry without actually + * having to generate the textual representation of the traverse + * path. + * + * NOTE! This *only* compares up to the size of the traverse path + * itself - the caller needs to do the final check for the cache + * entry having more data at the end! + */ +static int do_compare_entry(const struct cache_entry *ce, const struct traverse_info *info, const struct name_entry *n) +{ + int len, pathlen, ce_len; + const char *ce_name; + + if (info->prev) { + int cmp = do_compare_entry(ce, info->prev, &info->name); + if (cmp) + return cmp; + } + pathlen = info->pathlen; + ce_len = ce_namelen(ce); + + /* If ce_len < pathlen then we must have previously hit "name == directory" entry */ + if (ce_len < pathlen) + return -1; + + ce_len -= pathlen; + ce_name = ce->name + pathlen; + + len = tree_entry_len(n->path, n->sha1); + return df_name_compare(ce_name, ce_len, S_IFREG, n->path, len, n->mode); +} + +static int compare_entry(const struct cache_entry *ce, const struct traverse_info *info, const struct name_entry *n) +{ + int cmp = do_compare_entry(ce, info, n); + if (cmp) + return cmp; + + /* + * Even if the beginning compared identically, the ce should + * compare as bigger than a directory leading up to it! + */ + return ce_namelen(ce) > traverse_path_len(info, n); +} + +static struct cache_entry *create_ce_entry(const struct traverse_info *info, const struct name_entry *n, int stage) +{ + int len = traverse_path_len(info, n); + struct cache_entry *ce = xcalloc(1, cache_entry_size(len)); + + ce->ce_mode = create_ce_mode(n->mode); + ce->ce_flags = create_ce_flags(len, stage); + hashcpy(ce->sha1, n->sha1); + make_traverse_path(ce->name, info, n); + + return ce; +} + +static int unpack_nondirectories(int n, unsigned long mask, unsigned long dirmask, struct cache_entry *src[5], + const struct name_entry *names, const struct traverse_info *info, int remove) +{ + int i; + struct unpack_trees_options *o = info->data; + unsigned long conflicts; + + /* Do we have *only* directories? Nothing to do */ + if (mask == dirmask && !src[0]) + return 0; + + conflicts = info->conflicts; + if (o->merge) + conflicts >>= 1; + conflicts |= dirmask; + + /* + * Ok, we've filled in up to any potential index entry in src[0], + * now do the rest. + */ + for (i = 0; i < n; i++) { + int stage; + unsigned int bit = 1ul << i; + if (conflicts & bit) { + src[i + o->merge] = o->df_conflict_entry; + continue; + } + if (!(mask & bit)) + continue; + if (!o->merge) + stage = 0; + else if (i + 1 < o->head_idx) + stage = 1; + else if (i + 1 > o->head_idx) + stage = 3; + else + stage = 2; + src[i + o->merge] = create_ce_entry(info, names + i, stage); + } + + if (o->merge) + return call_unpack_fn(src, o, remove); + + n += o->merge; + remove_entry(remove); + for (i = 0; i < n; i++) + add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); + return 0; +} + +static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info) +{ + struct cache_entry *src[5] = { NULL, }; + struct unpack_trees_options *o = info->data; + int remove = -1; + const struct name_entry *p = names; + + /* Find first entry with a real name (we could use "mask" too) */ + while (!p->mode) + p++; + + /* Are we supposed to look at the index too? */ + if (o->merge) { + while (o->pos < active_nr) { + struct cache_entry *ce = active_cache[o->pos]; + int cmp = compare_entry(ce, info, p); + if (cmp < 0) { + if (unpack_index_entry(ce, o) < 0) + return -1; + continue; + } + if (!cmp) { + if (ce_stage(ce)) { + /* + * If we skip unmerged index entries, we'll skip this + * entry *and* the tree entries associated with it! + */ + if (o->skip_unmerged) + return mask; + remove_entry(o->pos); + continue; + } + src[0] = ce; + remove = o->pos; + } + break; + } + } + + if (unpack_nondirectories(n, mask, dirmask, src, names, info, remove) < 0) + return -1; + + /* Now handle any directories.. */ + if (dirmask) { + unsigned long conflicts = mask & ~dirmask; + if (o->merge) { + conflicts <<= 1; + if (src[0]) + conflicts |= 1; + } + traverse_trees_recursive(n, dirmask, conflicts, names, info); + return mask; + } + + return mask; +} + +static int unpack_failed(struct unpack_trees_options *o, const char *message) +{ + if (!o->gently) { + if (message) + return error(message); + return -1; + } + discard_cache(); + read_cache(); + return -1; +} + int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o) { - struct tree_entry_list **posns; - int i; - struct tree_entry_list df_conflict_list; static struct cache_entry *dfc; - memset(&df_conflict_list, 0, sizeof(df_conflict_list)); - df_conflict_list.next = &df_conflict_list; + if (len > 4) + die("unpack_trees takes at most four trees"); memset(&state, 0, sizeof(state)); state.base_dir = ""; state.force = 1; @@ -368,28 +336,28 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options o->df_conflict_entry = dfc; if (len) { - posns = xmalloc(len * sizeof(struct tree_entry_list *)); - for (i = 0; i < len; i++) - posns[i] = create_tree_entry_list(t+i); + const char *prefix = o->prefix ? o->prefix : ""; + struct traverse_info info; - if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "", - o, &df_conflict_list)) { - if (o->gently) { - discard_cache(); - read_cache(); - } - return -1; + setup_traverse_info(&info, prefix); + info.fn = unpack_callback; + info.data = o; + + if (traverse_trees(len, t, &info) < 0) + return unpack_failed(o, NULL); + } + + /* Any left-over entries in the index? */ + if (o->merge) { + while (o->pos < active_nr) { + struct cache_entry *ce = active_cache[o->pos]; + if (unpack_index_entry(ce, o) < 0) + return unpack_failed(o, NULL); } } - if (o->trivial_merges_only && o->nontrivial_merge) { - if (o->gently) { - discard_cache(); - read_cache(); - } - return o->gently ? -1 : - error("Merge requires file-level merging"); - } + if (o->trivial_merges_only && o->nontrivial_merge) + return unpack_failed(o, "Merge requires file-level merging"); check_updates(o); return 0; From bcbe5a515e837873f24dc9a764fa31e3ade45bb5 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 6 Mar 2008 15:44:48 -0800 Subject: [PATCH 19/42] Fix tree-walking compare_entry() in the presense of --prefix When we make the "root" tree-walk info entry have a pathname in it, we need to have a ->prev pointer so that compare_entry will actually notice and traverse into the root. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- tree-walk.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tree-walk.c b/tree-walk.c index 842cb6ad2e..02e2aed773 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -107,6 +107,7 @@ int tree_entry(struct tree_desc *desc, struct name_entry *entry) void setup_traverse_info(struct traverse_info *info, const char *base) { int pathlen = strlen(base); + static struct traverse_info dummy; memset(info, 0, sizeof(*info)); if (pathlen && base[pathlen-1] == '/') @@ -114,6 +115,8 @@ void setup_traverse_info(struct traverse_info *info, const char *base) info->pathlen = pathlen ? pathlen + 1 : 0; info->name.path = base; info->name.sha1 = (void *)(base + pathlen + 1); + if (pathlen) + info->prev = &dummy; } char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n) From d1f128b0509dce8b492c233b36131f096fd71274 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 6 Mar 2008 12:46:09 -0800 Subject: [PATCH 20/42] Add 'const' where appropriate to index handling functions This is in an effort to make the source index of 'unpack_trees()' as being const, and thus making the compiler help us verify that we only access it for reading. The constification also extended to some of the hashing helpers that get called indirectly. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- cache.h | 10 +++++----- hash.c | 6 +++--- hash.h | 4 ++-- read-cache.c | 12 ++++++------ 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cache.h b/cache.h index 6eb16cb0df..b519b7a0c3 100644 --- a/cache.h +++ b/cache.h @@ -346,12 +346,12 @@ extern void verify_non_filename(const char *prefix, const char *name); /* Initialize and use the cache information */ extern int read_index(struct index_state *); extern int read_index_from(struct index_state *, const char *path); -extern int write_index(struct index_state *, int newfd); +extern int write_index(const struct index_state *, int newfd); extern int discard_index(struct index_state *); -extern int unmerged_index(struct index_state *); +extern int unmerged_index(const struct index_state *); extern int verify_path(const char *path); extern int index_name_exists(struct index_state *istate, const char *name, int namelen); -extern int index_name_pos(struct index_state *, const char *name, int namelen); +extern int index_name_pos(const struct index_state *, const char *name, int namelen); #define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */ #define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */ #define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */ @@ -368,8 +368,8 @@ extern int ce_same_name(struct cache_entry *a, struct cache_entry *b); #define CE_MATCH_IGNORE_VALID 01 /* do not check the contents but report dirty on racily-clean entries */ #define CE_MATCH_RACY_IS_DIRTY 02 -extern int ie_match_stat(struct index_state *, struct cache_entry *, struct stat *, unsigned int); -extern int ie_modified(struct index_state *, struct cache_entry *, struct stat *, unsigned int); +extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); +extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); extern int ce_path_match(const struct cache_entry *ce, const char **pathspec); extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path); diff --git a/hash.c b/hash.c index d9ec82fa66..1cd4c9d5c0 100644 --- a/hash.c +++ b/hash.c @@ -9,7 +9,7 @@ * the existing entry, or the empty slot if none existed. The caller * can then look at the (*ptr) to see whether it existed or not. */ -static struct hash_table_entry *lookup_hash_entry(unsigned int hash, struct hash_table *table) +static struct hash_table_entry *lookup_hash_entry(unsigned int hash, const struct hash_table *table) { unsigned int size = table->size, nr = hash % size; struct hash_table_entry *array = table->array; @@ -66,7 +66,7 @@ static void grow_hash_table(struct hash_table *table) free(old_array); } -void *lookup_hash(unsigned int hash, struct hash_table *table) +void *lookup_hash(unsigned int hash, const struct hash_table *table) { if (!table->array) return NULL; @@ -81,7 +81,7 @@ void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table) return insert_hash_entry(hash, ptr, table); } -int for_each_hash(struct hash_table *table, int (*fn)(void *)) +int for_each_hash(const struct hash_table *table, int (*fn)(void *)) { int sum = 0; unsigned int i; diff --git a/hash.h b/hash.h index a8b0fbb5b5..69e33a47b9 100644 --- a/hash.h +++ b/hash.h @@ -28,9 +28,9 @@ struct hash_table { struct hash_table_entry *array; }; -extern void *lookup_hash(unsigned int hash, struct hash_table *table); +extern void *lookup_hash(unsigned int hash, const struct hash_table *table); extern void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table); -extern int for_each_hash(struct hash_table *table, int (*fn)(void *)); +extern int for_each_hash(const struct hash_table *table, int (*fn)(void *)); extern void free_hash(struct hash_table *table); static inline void init_hash(struct hash_table *table) diff --git a/read-cache.c b/read-cache.c index bf649a31a8..a92b25b59b 100644 --- a/read-cache.c +++ b/read-cache.c @@ -255,13 +255,13 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st) return changed; } -static int is_racy_timestamp(struct index_state *istate, struct cache_entry *ce) +static int is_racy_timestamp(const struct index_state *istate, struct cache_entry *ce) { return (istate->timestamp && ((unsigned int)istate->timestamp) <= ce->ce_mtime); } -int ie_match_stat(struct index_state *istate, +int ie_match_stat(const struct index_state *istate, struct cache_entry *ce, struct stat *st, unsigned int options) { @@ -304,7 +304,7 @@ int ie_match_stat(struct index_state *istate, return changed; } -int ie_modified(struct index_state *istate, +int ie_modified(const struct index_state *istate, struct cache_entry *ce, struct stat *st, unsigned int options) { int changed, changed_fs; @@ -412,7 +412,7 @@ int cache_name_compare(const char *name1, int flags1, const char *name2, int fla return 0; } -int index_name_pos(struct index_state *istate, const char *name, int namelen) +int index_name_pos(const struct index_state *istate, const char *name, int namelen) { int first, last; @@ -1201,7 +1201,7 @@ int discard_index(struct index_state *istate) return 0; } -int unmerged_index(struct index_state *istate) +int unmerged_index(const struct index_state *istate) { int i; for (i = 0; i < istate->cache_nr; i++) { @@ -1346,7 +1346,7 @@ static int ce_write_entry(SHA_CTX *c, int fd, struct cache_entry *ce) return ce_write(c, fd, ondisk, size); } -int write_index(struct index_state *istate, int newfd) +int write_index(const struct index_state *istate, int newfd) { SHA_CTX c; struct cache_header hdr; From bc052d7f435f8f729127cc4790484865c1a974b9 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 6 Mar 2008 12:26:14 -0800 Subject: [PATCH 21/42] Make 'unpack_trees()' take the index to work on as an argument This is just a very mechanical conversion, and makes everybody set it to '&the_index' before calling, but at least it makes it more explicit where we work with the index. The next stage would be to split that index usage up into a 'source' and a 'destination' index, so that we can unpack into a different index than we started out from. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- builtin-checkout.c | 6 +++ builtin-commit.c | 1 + builtin-merge-recursive.c | 1 + builtin-read-tree.c | 1 + diff-lib.c | 2 + unpack-trees.c | 79 ++++++++++++++++++++------------------- unpack-trees.h | 1 + 7 files changed, 52 insertions(+), 39 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index 6b08016228..9bdb6233f0 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -152,6 +152,7 @@ static int reset_to_new(struct tree *tree, int quiet) { struct unpack_trees_options opts; struct tree_desc tree_desc; + memset(&opts, 0, sizeof(opts)); opts.head_idx = -1; opts.update = 1; @@ -159,6 +160,7 @@ static int reset_to_new(struct tree *tree, int quiet) opts.merge = 1; opts.fn = oneway_merge; opts.verbose_update = !quiet; + opts.index = &the_index; parse_tree(tree); init_tree_desc(&tree_desc, tree->buffer, tree->size); if (unpack_trees(1, &tree_desc, &opts)) @@ -170,6 +172,7 @@ static void reset_clean_to_new(struct tree *tree, int quiet) { struct unpack_trees_options opts; struct tree_desc tree_desc; + memset(&opts, 0, sizeof(opts)); opts.head_idx = -1; opts.skip_unmerged = 1; @@ -177,6 +180,7 @@ static void reset_clean_to_new(struct tree *tree, int quiet) opts.merge = 1; opts.fn = oneway_merge; opts.verbose_update = !quiet; + opts.index = &the_index; parse_tree(tree); init_tree_desc(&tree_desc, tree->buffer, tree->size); if (unpack_trees(1, &tree_desc, &opts)) @@ -224,8 +228,10 @@ static int merge_working_tree(struct checkout_opts *opts, struct tree_desc trees[2]; struct tree *tree; struct unpack_trees_options topts; + memset(&topts, 0, sizeof(topts)); topts.head_idx = -1; + topts.index = &the_index; refresh_cache(REFRESH_QUIET); diff --git a/builtin-commit.c b/builtin-commit.c index f49c22e642..38a542258a 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -198,6 +198,7 @@ static void create_base_index(void) opts.head_idx = 1; opts.index_only = 1; opts.merge = 1; + opts.index = &the_index; opts.fn = oneway_merge; tree = parse_tree_indirect(head_sha1); diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 6fe4102c0c..50b389623c 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -213,6 +213,7 @@ static int git_merge_trees(int index_only, opts.merge = 1; opts.head_idx = 2; opts.fn = threeway_merge; + opts.index = &the_index; init_tree_desc_from_tree(t+0, common); init_tree_desc_from_tree(t+1, head); diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 0138f5a917..d004e90431 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -102,6 +102,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) memset(&opts, 0, sizeof(opts)); opts.head_idx = -1; + opts.index = &the_index; git_config(git_default_config); diff --git a/diff-lib.c b/diff-lib.c index 4581b594d0..e359058d0b 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -734,6 +734,7 @@ int run_diff_index(struct rev_info *revs, int cached) opts.merge = 1; opts.fn = oneway_diff; opts.unpack_data = revs; + opts.index = &the_index; init_tree_desc(&t, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts)) @@ -787,6 +788,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) opts.merge = 1; opts.fn = oneway_diff; opts.unpack_data = &revs; + opts.index = &the_index; init_tree_desc(&t, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts)) diff --git a/unpack-trees.c b/unpack-trees.c index ee9be29374..cb8f847968 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1,3 +1,4 @@ +#define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "dir.h" #include "tree.h" @@ -7,10 +8,10 @@ #include "progress.h" #include "refs.h" -static inline void remove_entry(int remove) +static inline void remove_entry(int remove, struct unpack_trees_options *o) { if (remove >= 0) - remove_cache_entry_at(remove); + remove_index_entry_at(o->index, remove); } /* Unlink the last component and attempt to remove leading @@ -53,8 +54,8 @@ static void check_updates(struct unpack_trees_options *o) int i; if (o->update && o->verbose_update) { - for (total = cnt = 0; cnt < active_nr; cnt++) { - struct cache_entry *ce = active_cache[cnt]; + for (total = cnt = 0; cnt < o->index->cache_nr; cnt++) { + struct cache_entry *ce = o->index->cache[cnt]; if (ce->ce_flags & (CE_UPDATE | CE_REMOVE)) total++; } @@ -65,15 +66,15 @@ static void check_updates(struct unpack_trees_options *o) } *last_symlink = '\0'; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + for (i = 0; i < o->index->cache_nr; i++) { + struct cache_entry *ce = o->index->cache[i]; if (ce->ce_flags & (CE_UPDATE | CE_REMOVE)) display_progress(progress, ++cnt); if (ce->ce_flags & CE_REMOVE) { if (o->update) unlink_entry(ce->name, last_symlink); - remove_cache_entry_at(i); + remove_index_entry_at(o->index, i); i--; continue; } @@ -105,7 +106,7 @@ static int unpack_index_entry(struct cache_entry *ce, struct unpack_trees_option if (o->skip_unmerged) { o->pos++; } else { - remove_entry(o->pos); + remove_entry(o->pos, o); } return 0; } @@ -242,9 +243,9 @@ static int unpack_nondirectories(int n, unsigned long mask, unsigned long dirmas return call_unpack_fn(src, o, remove); n += o->merge; - remove_entry(remove); + remove_entry(remove, o); for (i = 0; i < n; i++) - add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); + add_index_entry(o->index, src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); return 0; } @@ -261,8 +262,8 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str /* Are we supposed to look at the index too? */ if (o->merge) { - while (o->pos < active_nr) { - struct cache_entry *ce = active_cache[o->pos]; + while (o->pos < o->index->cache_nr) { + struct cache_entry *ce = o->index->cache[o->pos]; int cmp = compare_entry(ce, info, p); if (cmp < 0) { if (unpack_index_entry(ce, o) < 0) @@ -277,7 +278,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str */ if (o->skip_unmerged) return mask; - remove_entry(o->pos); + remove_entry(o->pos, o); continue; } src[0] = ce; @@ -312,8 +313,8 @@ static int unpack_failed(struct unpack_trees_options *o, const char *message) return error(message); return -1; } - discard_cache(); - read_cache(); + discard_index(o->index); + read_index(o->index); return -1; } @@ -349,8 +350,8 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options /* Any left-over entries in the index? */ if (o->merge) { - while (o->pos < active_nr) { - struct cache_entry *ce = active_cache[o->pos]; + while (o->pos < o->index->cache_nr) { + struct cache_entry *ce = o->index->cache[o->pos]; if (unpack_index_entry(ce, o) < 0) return unpack_failed(o, NULL); } @@ -395,7 +396,7 @@ static int verify_uptodate(struct cache_entry *ce, return 0; if (!lstat(ce->name, &st)) { - unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID); + unsigned changed = ie_match_stat(o->index, ce, &st, CE_MATCH_IGNORE_VALID); if (!changed) return 0; /* @@ -415,10 +416,10 @@ static int verify_uptodate(struct cache_entry *ce, error("Entry '%s' not uptodate. Cannot merge.", ce->name); } -static void invalidate_ce_path(struct cache_entry *ce) +static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o) { if (ce) - cache_tree_invalidate_path(active_cache_tree, ce->name); + cache_tree_invalidate_path(o->index->cache_tree, ce->name); } /* @@ -463,12 +464,12 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, * in that directory. */ namelen = strlen(ce->name); - pos = cache_name_pos(ce->name, namelen); + pos = index_name_pos(o->index, ce->name, namelen); if (0 <= pos) return cnt; /* we have it as nondirectory */ pos = -pos - 1; - for (i = pos; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + for (i = pos; i < o->index->cache_nr; i++) { + struct cache_entry *ce = o->index->cache[i]; int len = ce_namelen(ce); if (len < namelen || strncmp(ce->name, ce->name, namelen) || @@ -566,9 +567,9 @@ static int verify_absent(struct cache_entry *ce, const char *action, * delete this path, which is in a subdirectory that * is being replaced with a blob. */ - cnt = cache_name_pos(ce->name, strlen(ce->name)); + cnt = index_name_pos(o->index, ce->name, strlen(ce->name)); if (0 <= cnt) { - struct cache_entry *ce = active_cache[cnt]; + struct cache_entry *ce = o->index->cache[cnt]; if (ce->ce_flags & CE_REMOVE) return 0; } @@ -597,17 +598,17 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old, } else { if (verify_uptodate(old, o)) return -1; - invalidate_ce_path(old); + invalidate_ce_path(old, o); } } else { if (verify_absent(merge, "overwritten", o)) return -1; - invalidate_ce_path(merge); + invalidate_ce_path(merge, o); } merge->ce_flags &= ~CE_STAGEMASK; - add_cache_entry(merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + add_index_entry(o->index, merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); return 1; } @@ -621,14 +622,14 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old, if (verify_absent(ce, "removed", o)) return -1; ce->ce_flags |= CE_REMOVE; - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); - invalidate_ce_path(ce); + add_index_entry(o->index, ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + invalidate_ce_path(ce, o); return 1; } static int keep_entry(struct cache_entry *ce, struct unpack_trees_options *o) { - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); + add_index_entry(o->index, ce, ADD_CACHE_OK_TO_ADD); return 1; } @@ -728,7 +729,7 @@ int threeway_merge(struct cache_entry **stages, /* #1 */ if (!head && !remote && any_anc_missing) { - remove_entry(remove); + remove_entry(remove, o); return 0; } @@ -762,7 +763,7 @@ int threeway_merge(struct cache_entry **stages, if ((head_deleted && remote_deleted) || (head_deleted && remote && remote_match) || (remote_deleted && head && head_match)) { - remove_entry(remove); + remove_entry(remove, o); if (index) return deleted_entry(index, index, o); else if (ce && !head_deleted) { @@ -788,7 +789,7 @@ int threeway_merge(struct cache_entry **stages, return -1; } - remove_entry(remove); + remove_entry(remove, o); o->nontrivial_merge = 1; /* #2, #3, #4, #6, #7, #9, #10, #11. */ @@ -853,7 +854,7 @@ int twoway_merge(struct cache_entry **src, } else if (oldtree && !newtree && same(current, oldtree)) { /* 10 or 11 */ - remove_entry(remove); + remove_entry(remove, o); return deleted_entry(oldtree, current, o); } else if (oldtree && newtree && @@ -863,7 +864,7 @@ int twoway_merge(struct cache_entry **src, } else { /* all other failures */ - remove_entry(remove); + remove_entry(remove, o); if (oldtree) return o->gently ? -1 : reject_merge(oldtree); if (current) @@ -875,7 +876,7 @@ int twoway_merge(struct cache_entry **src, } else if (newtree) return merged_entry(newtree, current, o); - remove_entry(remove); + remove_entry(remove, o); return deleted_entry(oldtree, current, o); } @@ -922,14 +923,14 @@ int oneway_merge(struct cache_entry **src, o->merge_size); if (!a) { - remove_entry(remove); + remove_entry(remove, o); return deleted_entry(old, old, o); } if (old && same(old, a)) { if (o->reset) { struct stat st; if (lstat(old->name, &st) || - ce_match_stat(old, &st, CE_MATCH_IGNORE_VALID)) + ie_match_stat(o->index, old, &st, CE_MATCH_IGNORE_VALID)) old->ce_flags |= CE_UPDATE; } return keep_entry(old, o); diff --git a/unpack-trees.h b/unpack-trees.h index a2df544d04..65add1652f 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -28,6 +28,7 @@ struct unpack_trees_options { struct cache_entry *df_conflict_entry; void *unpack_data; + struct index_state *index; }; extern int unpack_trees(unsigned n, struct tree_desc *t, From 34110cd4e394e3f92c01a4709689b384c34645d8 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 6 Mar 2008 18:12:28 -0800 Subject: [PATCH 22/42] Make 'unpack_trees()' have a separate source and destination index We will always unpack into our own internal index, but we will take the source from wherever specified, and we will optionally write the result to a specified index (optionally, because not everybody even _wants_ any result: the index diffing really wants to just walk the tree and index in parallel). This ends up removing a fair number more lines than it adds, for the simple reason that we can now skip all the crud that tried to be oh-so-careful about maintaining our position in the index as we were traversing and modifying it. Since we don't actually modify the source index any more, we can just update the 'o->pos' pointer without worrying about whether an index entry got removed or replaced or added to. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- builtin-checkout.c | 9 ++- builtin-commit.c | 3 +- builtin-merge-recursive.c | 3 +- builtin-read-tree.c | 24 +----- diff-lib.c | 49 ++---------- t/t1005-read-tree-reset.sh | 2 +- unpack-trees.c | 149 ++++++++++++++++++------------------- unpack-trees.h | 16 ++-- 8 files changed, 102 insertions(+), 153 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index 9bdb6233f0..7deb504837 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -160,7 +160,8 @@ static int reset_to_new(struct tree *tree, int quiet) opts.merge = 1; opts.fn = oneway_merge; opts.verbose_update = !quiet; - opts.index = &the_index; + opts.src_index = &the_index; + opts.dst_index = &the_index; parse_tree(tree); init_tree_desc(&tree_desc, tree->buffer, tree->size); if (unpack_trees(1, &tree_desc, &opts)) @@ -180,7 +181,8 @@ static void reset_clean_to_new(struct tree *tree, int quiet) opts.merge = 1; opts.fn = oneway_merge; opts.verbose_update = !quiet; - opts.index = &the_index; + opts.src_index = &the_index; + opts.dst_index = &the_index; parse_tree(tree); init_tree_desc(&tree_desc, tree->buffer, tree->size); if (unpack_trees(1, &tree_desc, &opts)) @@ -231,7 +233,8 @@ static int merge_working_tree(struct checkout_opts *opts, memset(&topts, 0, sizeof(topts)); topts.head_idx = -1; - topts.index = &the_index; + topts.src_index = &the_index; + topts.dst_index = &the_index; refresh_cache(REFRESH_QUIET); diff --git a/builtin-commit.c b/builtin-commit.c index 38a542258a..660a3458f7 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -198,7 +198,8 @@ static void create_base_index(void) opts.head_idx = 1; opts.index_only = 1; opts.merge = 1; - opts.index = &the_index; + opts.src_index = &the_index; + opts.dst_index = &the_index; opts.fn = oneway_merge; tree = parse_tree_indirect(head_sha1); diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 50b389623c..fa02bb5a80 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -213,7 +213,8 @@ static int git_merge_trees(int index_only, opts.merge = 1; opts.head_idx = 2; opts.fn = threeway_merge; - opts.index = &the_index; + opts.src_index = &the_index; + opts.dst_index = &the_index; init_tree_desc_from_tree(t+0, common); init_tree_desc_from_tree(t+1, head); diff --git a/builtin-read-tree.c b/builtin-read-tree.c index d004e90431..160456dad1 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -102,7 +102,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) memset(&opts, 0, sizeof(opts)); opts.head_idx = -1; - opts.index = &the_index; + opts.src_index = &the_index; + opts.dst_index = &the_index; git_config(git_default_config); @@ -221,27 +222,6 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) if ((opts.dir && !opts.update)) die("--exclude-per-directory is meaningless unless -u"); - if (opts.prefix) { - int pfxlen = strlen(opts.prefix); - int pos; - if (opts.prefix[pfxlen-1] != '/') - die("prefix must end with /"); - if (stage != 2) - die("binding merge takes only one tree"); - pos = cache_name_pos(opts.prefix, pfxlen); - if (0 <= pos) - die("corrupt index file"); - pos = -pos-1; - if (pos < active_nr && - !strncmp(active_cache[pos]->name, opts.prefix, pfxlen)) - die("subdirectory '%s' already exists.", opts.prefix); - pos = cache_name_pos(opts.prefix, pfxlen-1); - if (0 <= pos) - die("file '%.*s' already exists.", - pfxlen-1, opts.prefix); - opts.pos = -1 - pos; - } - if (opts.merge) { if (stage < 2) die("just how do you expect me to merge %d trees?", stage-1); diff --git a/diff-lib.c b/diff-lib.c index e359058d0b..9520773f3b 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -600,8 +600,7 @@ static void mark_merge_entries(void) */ static void do_oneway_diff(struct unpack_trees_options *o, struct cache_entry *idx, - struct cache_entry *tree, - int idx_pos, int idx_nr) + struct cache_entry *tree) { struct rev_info *revs = o->unpack_data; int match_missing, cached; @@ -642,34 +641,6 @@ static void do_oneway_diff(struct unpack_trees_options *o, show_modified(revs, tree, idx, 1, cached, match_missing); } -/* - * Count how many index entries go with the first one - */ -static inline int count_skip(const struct cache_entry *src, int pos) -{ - int skip = 1; - - /* We can only have multiple entries if the first one is not stage-0 */ - if (ce_stage(src)) { - struct cache_entry **p = active_cache + pos; - int namelen = ce_namelen(src); - - for (;;) { - const struct cache_entry *ce; - pos++; - if (pos >= active_nr) - break; - ce = *++p; - if (ce_namelen(ce) != namelen) - break; - if (memcmp(ce->name, src->name, namelen)) - break; - skip++; - } - } - return skip; -} - /* * The unpack_trees() interface is designed for merging, so * the different source entries are designed primarily for @@ -685,18 +656,12 @@ static inline int count_skip(const struct cache_entry *src, int pos) * the fairly complex unpack_trees() semantic requirements, including * the skipping, the path matching, the type conflict cases etc. */ -static int oneway_diff(struct cache_entry **src, - struct unpack_trees_options *o, - int index_pos) +static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o) { - int skip = 0; struct cache_entry *idx = src[0]; struct cache_entry *tree = src[1]; struct rev_info *revs = o->unpack_data; - if (index_pos >= 0) - skip = count_skip(idx, index_pos); - /* * Unpack-trees generates a DF/conflict entry if * there was a directory in the index and a tree @@ -707,9 +672,9 @@ static int oneway_diff(struct cache_entry **src, tree = NULL; if (ce_path_match(idx ? idx : tree, revs->prune_data)) - do_oneway_diff(o, idx, tree, index_pos, skip); + do_oneway_diff(o, idx, tree); - return skip; + return 0; } int run_diff_index(struct rev_info *revs, int cached) @@ -734,7 +699,8 @@ int run_diff_index(struct rev_info *revs, int cached) opts.merge = 1; opts.fn = oneway_diff; opts.unpack_data = revs; - opts.index = &the_index; + opts.src_index = &the_index; + opts.dst_index = NULL; init_tree_desc(&t, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts)) @@ -788,7 +754,8 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) opts.merge = 1; opts.fn = oneway_diff; opts.unpack_data = &revs; - opts.index = &the_index; + opts.src_index = &the_index; + opts.dst_index = &the_index; init_tree_desc(&t, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts)) diff --git a/t/t1005-read-tree-reset.sh b/t/t1005-read-tree-reset.sh index f1b12167b8..8c4556408e 100755 --- a/t/t1005-read-tree-reset.sh +++ b/t/t1005-read-tree-reset.sh @@ -21,7 +21,7 @@ test_expect_success 'setup' ' git commit -m two ' -test_expect_failure 'reset should work' ' +test_expect_success 'reset should work' ' git read-tree -u --reset HEAD^ && git ls-files >actual && diff -u expect actual diff --git a/unpack-trees.c b/unpack-trees.c index cb8f847968..0cdf19817d 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -8,10 +8,18 @@ #include "progress.h" #include "refs.h" -static inline void remove_entry(int remove, struct unpack_trees_options *o) +static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce, + unsigned int set, unsigned int clear) { - if (remove >= 0) - remove_index_entry_at(o->index, remove); + unsigned int size = ce_size(ce); + struct cache_entry *new = xmalloc(size); + + clear |= CE_HASHED | CE_UNHASHED; + + memcpy(new, ce, size); + new->next = NULL; + new->ce_flags = (new->ce_flags & ~clear) | set; + add_index_entry(&o->result, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE|ADD_CACHE_SKIP_DFCHECK); } /* Unlink the last component and attempt to remove leading @@ -51,11 +59,12 @@ static void check_updates(struct unpack_trees_options *o) unsigned cnt = 0, total = 0; struct progress *progress = NULL; char last_symlink[PATH_MAX]; + struct index_state *index = &o->result; int i; if (o->update && o->verbose_update) { - for (total = cnt = 0; cnt < o->index->cache_nr; cnt++) { - struct cache_entry *ce = o->index->cache[cnt]; + for (total = cnt = 0; cnt < index->cache_nr; cnt++) { + struct cache_entry *ce = index->cache[cnt]; if (ce->ce_flags & (CE_UPDATE | CE_REMOVE)) total++; } @@ -66,15 +75,15 @@ static void check_updates(struct unpack_trees_options *o) } *last_symlink = '\0'; - for (i = 0; i < o->index->cache_nr; i++) { - struct cache_entry *ce = o->index->cache[i]; + for (i = 0; i < index->cache_nr; i++) { + struct cache_entry *ce = index->cache[i]; if (ce->ce_flags & (CE_UPDATE | CE_REMOVE)) display_progress(progress, ++cnt); if (ce->ce_flags & CE_REMOVE) { if (o->update) unlink_entry(ce->name, last_symlink); - remove_index_entry_at(o->index, i); + remove_index_entry_at(&o->result, i); i--; continue; } @@ -89,28 +98,27 @@ static void check_updates(struct unpack_trees_options *o) stop_progress(&progress); } -static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o, int remove) +static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o) { - int ret = o->fn(src, o, remove); - if (ret > 0) { - o->pos += ret; + int ret = o->fn(src, o); + if (ret > 0) ret = 0; - } return ret; } static int unpack_index_entry(struct cache_entry *ce, struct unpack_trees_options *o) { struct cache_entry *src[5] = { ce, }; + + o->pos++; if (ce_stage(ce)) { if (o->skip_unmerged) { - o->pos++; - } else { - remove_entry(o->pos, o); + add_entry(o, ce, 0, 0); + return 0; } return 0; } - return call_unpack_fn(src, o, o->pos); + return call_unpack_fn(src, o); } int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info) @@ -200,7 +208,7 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info, con } static int unpack_nondirectories(int n, unsigned long mask, unsigned long dirmask, struct cache_entry *src[5], - const struct name_entry *names, const struct traverse_info *info, int remove) + const struct name_entry *names, const struct traverse_info *info) { int i; struct unpack_trees_options *o = info->data; @@ -240,12 +248,11 @@ static int unpack_nondirectories(int n, unsigned long mask, unsigned long dirmas } if (o->merge) - return call_unpack_fn(src, o, remove); + return call_unpack_fn(src, o); n += o->merge; - remove_entry(remove, o); for (i = 0; i < n; i++) - add_index_entry(o->index, src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); + add_entry(o, src[i], 0, 0); return 0; } @@ -253,7 +260,6 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str { struct cache_entry *src[5] = { NULL, }; struct unpack_trees_options *o = info->data; - int remove = -1; const struct name_entry *p = names; /* Find first entry with a real name (we could use "mask" too) */ @@ -262,8 +268,8 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str /* Are we supposed to look at the index too? */ if (o->merge) { - while (o->pos < o->index->cache_nr) { - struct cache_entry *ce = o->index->cache[o->pos]; + while (o->pos < o->src_index->cache_nr) { + struct cache_entry *ce = o->src_index->cache[o->pos]; int cmp = compare_entry(ce, info, p); if (cmp < 0) { if (unpack_index_entry(ce, o) < 0) @@ -271,24 +277,25 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str continue; } if (!cmp) { + o->pos++; if (ce_stage(ce)) { /* * If we skip unmerged index entries, we'll skip this * entry *and* the tree entries associated with it! */ - if (o->skip_unmerged) + if (o->skip_unmerged) { + add_entry(o, ce, 0, 0); return mask; - remove_entry(o->pos, o); + } continue; } src[0] = ce; - remove = o->pos; } break; } } - if (unpack_nondirectories(n, mask, dirmask, src, names, info, remove) < 0) + if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0) return -1; /* Now handle any directories.. */ @@ -313,8 +320,6 @@ static int unpack_failed(struct unpack_trees_options *o, const char *message) return error(message); return -1; } - discard_index(o->index); - read_index(o->index); return -1; } @@ -330,6 +335,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options state.quiet = 1; state.refresh_cache = 1; + memset(&o->result, 0, sizeof(o->result)); o->merge_size = len; if (!dfc) @@ -350,8 +356,8 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options /* Any left-over entries in the index? */ if (o->merge) { - while (o->pos < o->index->cache_nr) { - struct cache_entry *ce = o->index->cache[o->pos]; + while (o->pos < o->src_index->cache_nr) { + struct cache_entry *ce = o->src_index->cache[o->pos]; if (unpack_index_entry(ce, o) < 0) return unpack_failed(o, NULL); } @@ -360,7 +366,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options if (o->trivial_merges_only && o->nontrivial_merge) return unpack_failed(o, "Merge requires file-level merging"); + o->src_index = NULL; check_updates(o); + if (o->dst_index) + *o->dst_index = o->result; return 0; } @@ -396,7 +405,7 @@ static int verify_uptodate(struct cache_entry *ce, return 0; if (!lstat(ce->name, &st)) { - unsigned changed = ie_match_stat(o->index, ce, &st, CE_MATCH_IGNORE_VALID); + unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID); if (!changed) return 0; /* @@ -419,7 +428,7 @@ static int verify_uptodate(struct cache_entry *ce, static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o) { if (ce) - cache_tree_invalidate_path(o->index->cache_tree, ce->name); + cache_tree_invalidate_path(o->src_index->cache_tree, ce->name); } /* @@ -464,12 +473,12 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, * in that directory. */ namelen = strlen(ce->name); - pos = index_name_pos(o->index, ce->name, namelen); + pos = index_name_pos(o->src_index, ce->name, namelen); if (0 <= pos) return cnt; /* we have it as nondirectory */ pos = -pos - 1; - for (i = pos; i < o->index->cache_nr; i++) { - struct cache_entry *ce = o->index->cache[i]; + for (i = pos; i < o->src_index->cache_nr; i++) { + struct cache_entry *ce = o->src_index->cache[i]; int len = ce_namelen(ce); if (len < namelen || strncmp(ce->name, ce->name, namelen) || @@ -481,7 +490,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action, if (!ce_stage(ce)) { if (verify_uptodate(ce, o)) return -1; - ce->ce_flags |= CE_REMOVE; + add_entry(o, ce, CE_REMOVE, 0); } cnt++; } @@ -567,9 +576,9 @@ static int verify_absent(struct cache_entry *ce, const char *action, * delete this path, which is in a subdirectory that * is being replaced with a blob. */ - cnt = index_name_pos(o->index, ce->name, strlen(ce->name)); + cnt = index_name_pos(&o->result, ce->name, strlen(ce->name)); if (0 <= cnt) { - struct cache_entry *ce = o->index->cache[cnt]; + struct cache_entry *ce = o->result.cache[cnt]; if (ce->ce_flags & CE_REMOVE) return 0; } @@ -584,7 +593,6 @@ static int verify_absent(struct cache_entry *ce, const char *action, static int merged_entry(struct cache_entry *merge, struct cache_entry *old, struct unpack_trees_options *o) { - merge->ce_flags |= CE_UPDATE; if (old) { /* * See if we can re-use the old CE directly? @@ -607,29 +615,29 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old, invalidate_ce_path(merge, o); } - merge->ce_flags &= ~CE_STAGEMASK; - add_index_entry(o->index, merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + add_entry(o, merge, CE_UPDATE, CE_STAGEMASK); return 1; } static int deleted_entry(struct cache_entry *ce, struct cache_entry *old, struct unpack_trees_options *o) { - if (old) { - if (verify_uptodate(old, o)) - return -1; - } else + /* Did it exist in the index? */ + if (!old) { if (verify_absent(ce, "removed", o)) return -1; - ce->ce_flags |= CE_REMOVE; - add_index_entry(o->index, ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + return 0; + } + if (verify_uptodate(old, o)) + return -1; + add_entry(o, ce, CE_REMOVE, 0); invalidate_ce_path(ce, o); return 1; } static int keep_entry(struct cache_entry *ce, struct unpack_trees_options *o) { - add_index_entry(o->index, ce, ADD_CACHE_OK_TO_ADD); + add_entry(o, ce, 0, 0); return 1; } @@ -649,9 +657,7 @@ static void show_stage_entry(FILE *o, } #endif -int threeway_merge(struct cache_entry **stages, - struct unpack_trees_options *o, - int remove) +int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o) { struct cache_entry *index; struct cache_entry *head; @@ -728,10 +734,8 @@ int threeway_merge(struct cache_entry **stages, } /* #1 */ - if (!head && !remote && any_anc_missing) { - remove_entry(remove, o); + if (!head && !remote && any_anc_missing) return 0; - } /* Under the new "aggressive" rule, we resolve mostly trivial * cases that we historically had git-merge-one-file resolve. @@ -763,10 +767,9 @@ int threeway_merge(struct cache_entry **stages, if ((head_deleted && remote_deleted) || (head_deleted && remote && remote_match) || (remote_deleted && head && head_match)) { - remove_entry(remove, o); if (index) return deleted_entry(index, index, o); - else if (ce && !head_deleted) { + if (ce && !head_deleted) { if (verify_absent(ce, "removed", o)) return -1; } @@ -789,7 +792,6 @@ int threeway_merge(struct cache_entry **stages, return -1; } - remove_entry(remove, o); o->nontrivial_merge = 1; /* #2, #3, #4, #6, #7, #9, #10, #11. */ @@ -824,9 +826,7 @@ int threeway_merge(struct cache_entry **stages, * "carry forward" rule, please see . * */ -int twoway_merge(struct cache_entry **src, - struct unpack_trees_options *o, - int remove) +int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o) { struct cache_entry *current = src[0]; struct cache_entry *oldtree = src[1]; @@ -854,7 +854,6 @@ int twoway_merge(struct cache_entry **src, } else if (oldtree && !newtree && same(current, oldtree)) { /* 10 or 11 */ - remove_entry(remove, o); return deleted_entry(oldtree, current, o); } else if (oldtree && newtree && @@ -864,7 +863,6 @@ int twoway_merge(struct cache_entry **src, } else { /* all other failures */ - remove_entry(remove, o); if (oldtree) return o->gently ? -1 : reject_merge(oldtree); if (current) @@ -876,7 +874,6 @@ int twoway_merge(struct cache_entry **src, } else if (newtree) return merged_entry(newtree, current, o); - remove_entry(remove, o); return deleted_entry(oldtree, current, o); } @@ -887,8 +884,7 @@ int twoway_merge(struct cache_entry **src, * stage0 does not have anything there. */ int bind_merge(struct cache_entry **src, - struct unpack_trees_options *o, - int remove) + struct unpack_trees_options *o) { struct cache_entry *old = src[0]; struct cache_entry *a = src[1]; @@ -898,7 +894,7 @@ int bind_merge(struct cache_entry **src, o->merge_size); if (a && old) return o->gently ? -1 : - error("Entry '%s' overlaps. Cannot bind.", a->name); + error("Entry '%s' overlaps with '%s'. Cannot bind.", a->name, old->name); if (!a) return keep_entry(old, o); else @@ -911,9 +907,7 @@ int bind_merge(struct cache_entry **src, * The rule is: * - take the stat information from stage0, take the data from stage1 */ -int oneway_merge(struct cache_entry **src, - struct unpack_trees_options *o, - int remove) +int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o) { struct cache_entry *old = src[0]; struct cache_entry *a = src[1]; @@ -922,18 +916,19 @@ int oneway_merge(struct cache_entry **src, return error("Cannot do a oneway merge of %d trees", o->merge_size); - if (!a) { - remove_entry(remove, o); + if (!a) return deleted_entry(old, old, o); - } + if (old && same(old, a)) { + int update = 0; if (o->reset) { struct stat st; if (lstat(old->name, &st) || - ie_match_stat(o->index, old, &st, CE_MATCH_IGNORE_VALID)) - old->ce_flags |= CE_UPDATE; + ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID)) + update |= CE_UPDATE; } - return keep_entry(old, o); + add_entry(o, old, update, 0); + return 0; } return merged_entry(a, old, o); } diff --git a/unpack-trees.h b/unpack-trees.h index 65add1652f..e8abbcd037 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -4,8 +4,7 @@ struct unpack_trees_options; typedef int (*merge_fn_t)(struct cache_entry **src, - struct unpack_trees_options *options, - int remove); + struct unpack_trees_options *options); struct unpack_trees_options { int reset; @@ -28,15 +27,18 @@ struct unpack_trees_options { struct cache_entry *df_conflict_entry; void *unpack_data; - struct index_state *index; + + struct index_state *dst_index; + const struct index_state *src_index; + struct index_state result; }; extern int unpack_trees(unsigned n, struct tree_desc *t, struct unpack_trees_options *options); -int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o, int); -int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o, int); -int bind_merge(struct cache_entry **src, struct unpack_trees_options *o, int); -int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o, int); +int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o); +int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o); +int bind_merge(struct cache_entry **src, struct unpack_trees_options *o); +int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o); #endif From 1caeacc1f2973cecf7919a141adc4759acae94d0 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 7 Mar 2008 13:48:40 -0800 Subject: [PATCH 23/42] unpack_trees(): minor memory leak fix in unused destination index This adds a "discard_index(&o->result)" to the failure path, to reclaim memory from an in-core index we built but ended up not using. The *big* memory leak comes from the fact that we leak the cache_entry things left and right. That's a very traditional and deliberate leak: because we used to build up the cache entries by just mapping them directly in from the index file (and we emulate that in modern times by allocating them from one big array), we can't actually free them one-by-one. So doing the "discard_index()" will free the hash tables etc, which is good, and it will free the "istate->alloc" but that is never set on the result because we don't get the result from the index read. So we don't actually free the individual cache entries themselves that got created from the trees. That's not something new, btw. We never did. But some day we should just add a flag to the cache_entry() that it's a "free one by one" kind, and then we could/should do it. In the meantime, this one-liner will fix *some* of the memory leaks, but not that old traditional one. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- unpack-trees.c | 1 + 1 file changed, 1 insertion(+) diff --git a/unpack-trees.c b/unpack-trees.c index 0cdf19817d..da68557b17 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -315,6 +315,7 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str static int unpack_failed(struct unpack_trees_options *o, const char *message) { + discard_index(&o->result); if (!o->gently) { if (message) return error(message); From 5b10a3c12439b262ccebb528a2db3945e7ebd061 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 10 Mar 2008 01:22:03 -0700 Subject: [PATCH 24/42] git-pull documentation: warn about the option order We might eventually be loosening this rule, but there is a longstanding restriction that the users currently need to be aware of. Signed-off-by: Junio C Hamano --- Documentation/git-pull.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 179bdfc69d..f7b90a326f 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -20,6 +20,8 @@ Note that you can use `.` (current directory) as the to pull from the local repository -- this is useful when merging local branches into the current branch. +Also note that options meant for `git-pull` itself and underlying +`git-merge` must be given before the options meant for `git-fetch`. OPTIONS ------- From 542c264b01ac551dca0697d6577ec71ad4c245ab Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 10 Mar 2008 01:26:23 -0700 Subject: [PATCH 25/42] traverse_trees_recursive(): propagate merge errors up There were few places where merge errors detected deeper in the call chain were ignored and not propagated up the callchain to the caller. Most notably, this caused switching branches with "git checkout" to ignore a path modified in a work tree are different between the HEAD version and the commit being switched to, which it internally notices but ignores it, resulting in an incorrect two-way merge and loss of the change in the work tree. Signed-off-by: Junio C Hamano --- unpack-trees.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/unpack-trees.c b/unpack-trees.c index da68557b17..5a0f0382b8 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -144,8 +144,7 @@ int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conf sha1 = names[i].sha1; fill_tree_descriptor(t+i, sha1); } - traverse_trees(n, t, &newinfo); - return 0; + return traverse_trees(n, t, &newinfo); } /* @@ -306,7 +305,9 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str if (src[0]) conflicts |= 1; } - traverse_trees_recursive(n, dirmask, conflicts, names, info); + if (traverse_trees_recursive(n, dirmask, conflicts, + names, info) < 0) + return -1; return mask; } From a5c4f85b1685aeb01f4dac88075f465c71a6c82d Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Sat, 8 Mar 2008 22:10:48 -0500 Subject: [PATCH 26/42] bash: Properly quote the GIT_DIR at all times to fix subdirectory paths with spaces Signed-off-by: Kevin Ballard Signed-off-by: Shawn O. Pearce --- contrib/completion/git-completion.bash | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 848c067b57..c29569c624 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -83,17 +83,17 @@ __git_ps1 () elif [ -f "$g/.dotest-merge/interactive" ] then r="|REBASE-i" - b="$(cat $g/.dotest-merge/head-name)" + b="$(cat "$g/.dotest-merge/head-name")" elif [ -d "$g/.dotest-merge" ] then r="|REBASE-m" - b="$(cat $g/.dotest-merge/head-name)" + b="$(cat "$g/.dotest-merge/head-name")" elif [ -f "$g/MERGE_HEAD" ] then r="|MERGING" b="$(git symbolic-ref HEAD 2>/dev/null)" else - if [ -f $g/BISECT_LOG ] + if [ -f "$g/BISECT_LOG" ] then r="|BISECTING" fi @@ -101,7 +101,7 @@ __git_ps1 () then if ! b="$(git describe --exact-match HEAD 2>/dev/null)" then - b="$(cut -c1-7 $g/HEAD)..." + b="$(cut -c1-7 "$g/HEAD")..." fi fi fi From 1d17b22ebf9d894cef393e3e062f383aad8817e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Mon, 10 Mar 2008 16:02:22 +0100 Subject: [PATCH 27/42] bash: remove unnecessary conditions when checking for subcommands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Checking emptyness of $command is sufficient. Signed-off-by: SZEDER Gábor Signed-off-by: Shawn O. Pearce --- contrib/completion/git-completion.bash | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index c29569c624..a94dc88fdc 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -497,7 +497,7 @@ _git_bisect () c=$((++c)) done - if [ $c -eq $COMP_CWORD -a -z "$command" ]; then + if [ -z "$command" ]; then __gitcomp "start bad good reset visualize replay log" return fi @@ -1042,7 +1042,7 @@ _git_remote () c=$((++c)) done - if [ $c -eq $COMP_CWORD -a -z "$command" ]; then + if [ -z "$command" ]; then __gitcomp "add rm show prune update" return fi @@ -1135,7 +1135,7 @@ _git_submodule () c=$((++c)) done - if [ $c -eq $COMP_CWORD -a -z "$command" ]; then + if [ -z "$command" ]; then local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --*) @@ -1198,7 +1198,7 @@ _git () c=$((++c)) done - if [ $c -eq $COMP_CWORD -a -z "$command" ]; then + if [ -z "$command" ]; then case "${COMP_WORDS[COMP_CWORD]}" in --*=*) COMPREPLY=() ;; --*) __gitcomp " From 3ff1320d4be164e35a685d8d00b6f44084be76e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Mon, 10 Mar 2008 16:02:23 +0100 Subject: [PATCH 28/42] bash: refactor searching for subcommands on the command line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds the __git_find_subcommand function, which takes one argument: a string containing all subcommands separated by spaces. The function searches through the command line whether a subcommand is already present. The first found subcommand will be printed to standard output. This enables us to remove code duplications from completion functions for commands having subcommands. Signed-off-by: SZEDER Gábor Signed-off-by: Shawn O. Pearce --- contrib/completion/git-completion.bash | 71 ++++++++++++-------------- 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index a94dc88fdc..438e193bd0 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -428,6 +428,22 @@ __git_aliased_command () done } +__git_find_subcommand () +{ + local word subcommand c=1 + + while [ $c -lt $COMP_CWORD ]; do + word="${COMP_WORDS[c]}" + for subcommand in $1; do + if [ "$subcommand" = "$word" ]; then + echo "$subcommand" + return + fi + done + c=$((++c)) + done +} + __git_whitespacelist="nowarn warn error error-all strip" _git_am () @@ -485,24 +501,14 @@ _git_add () _git_bisect () { - local i c=1 command - while [ $c -lt $COMP_CWORD ]; do - i="${COMP_WORDS[c]}" - case "$i" in - start|bad|good|reset|visualize|replay|log) - command="$i" - break - ;; - esac - c=$((++c)) - done - - if [ -z "$command" ]; then - __gitcomp "start bad good reset visualize replay log" + local subcommands="start bad good reset visualize replay log" + local subcommand="$(__git_find_subcommand "$subcommands")" + if [ -z "$subcommand" ]; then + __gitcomp "$subcommands" return fi - case "$command" in + case "$subcommand" in bad|good|reset) __gitcomp "$(__git_refs)" ;; @@ -1033,21 +1039,13 @@ _git_config () _git_remote () { - local i c=1 command - while [ $c -lt $COMP_CWORD ]; do - i="${COMP_WORDS[c]}" - case "$i" in - add|rm|show|prune|update) command="$i"; break ;; - esac - c=$((++c)) - done - - if [ -z "$command" ]; then - __gitcomp "add rm show prune update" + local subcommands="add rm show prune update" + local subcommand="$(__git_find_subcommand "$subcommands")" + if [ -z "$subcommand" ]; then return fi - case "$command" in + case "$subcommand" in rm|show|prune) __gitcomp "$(__git_remotes)" ;; @@ -1121,28 +1119,23 @@ _git_show () _git_stash () { - __gitcomp 'list show apply clear' + local subcommands='list show apply clear' + if [ -z "$(__git_find_subcommand "$subcommands")" ]; then + __gitcomp "$subcommands" + fi } _git_submodule () { - local i c=1 command - while [ $c -lt $COMP_CWORD ]; do - i="${COMP_WORDS[c]}" - case "$i" in - add|status|init|update) command="$i"; break ;; - esac - c=$((++c)) - done - - if [ -z "$command" ]; then + local subcommands="add status init update" + if [ -z "$(__git_find_subcommand "$subcommands")" ]; then local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --*) __gitcomp "--quiet --cached" ;; *) - __gitcomp "add status init update" + __gitcomp "$subcommands" ;; esac return From 88b302f5e2e0a6f6610912df49c473967fe59c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Mon, 10 Mar 2008 16:02:24 +0100 Subject: [PATCH 29/42] bash: add new 'git stash' subcommands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Namely 'save', 'drop', 'pop' and 'create' Signed-off-by: SZEDER Gábor Signed-off-by: Shawn O. Pearce --- contrib/completion/git-completion.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 438e193bd0..c62b877aeb 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1119,7 +1119,7 @@ _git_show () _git_stash () { - local subcommands='list show apply clear' + local subcommands='save list show apply clear drop pop create' if [ -z "$(__git_find_subcommand "$subcommands")" ]; then __gitcomp "$subcommands" fi From 47f6ee283891b757069c82922a88d1393be25dac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Mon, 10 Mar 2008 16:02:25 +0100 Subject: [PATCH 30/42] bash: add 'git svn' subcommands and options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: SZEDER Gábor Signed-off-by: Shawn O. Pearce --- contrib/completion/git-completion.bash | 81 +++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index c62b877aeb..2c4bf4e1d2 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -384,7 +384,6 @@ __git_commands () show-index) : plumbing;; ssh-*) : transport;; stripspace) : plumbing;; - svn) : import export;; symbolic-ref) : plumbing;; tar-tree) : deprecated;; unpack-file) : plumbing;; @@ -1142,6 +1141,84 @@ _git_submodule () fi } +_git_svn () +{ + local subcommands=" + init fetch clone rebase dcommit log find-rev + set-tree commit-diff info create-ignore propget + proplist show-ignore show-externals + " + local subcommand="$(__git_find_subcommand "$subcommands")" + if [ -z "$subcommand" ]; then + __gitcomp "$subcommands" + else + local remote_opts="--username= --config-dir= --no-auth-cache" + local fc_opts=" + --follow-parent --authors-file= --repack= + --no-metadata --use-svm-props --use-svnsync-props + --log-window-size= --no-checkout --quiet + --repack-flags --user-log-author $remote_opts + " + local init_opts=" + --template= --shared= --trunk= --tags= + --branches= --stdlayout --minimize-url + --no-metadata --use-svm-props --use-svnsync-props + --rewrite-root= $remote_opts + " + local cmt_opts=" + --edit --rmdir --find-copies-harder --copy-similarity= + " + + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$subcommand,$cur" in + fetch,--*) + __gitcomp "--revision= --fetch-all $fc_opts" + ;; + clone,--*) + __gitcomp "--revision= $fc_opts $init_opts" + ;; + init,--*) + __gitcomp "$init_opts" + ;; + dcommit,--*) + __gitcomp " + --merge --strategy= --verbose --dry-run + --fetch-all --no-rebase $cmt_opts $fc_opts + " + ;; + set-tree,--*) + __gitcomp "--stdin $cmt_opts $fc_opts" + ;; + create-ignore,--*|propget,--*|proplist,--*|show-ignore,--*|\ + show-externals,--*) + __gitcomp "--revision=" + ;; + log,--*) + __gitcomp " + --limit= --revision= --verbose --incremental + --oneline --show-commit --non-recursive + --authors-file= + " + ;; + rebase,--*) + __gitcomp " + --merge --verbose --strategy= --local + --fetch-all $fc_opts + " + ;; + commit-diff,--*) + __gitcomp "--message= --file= --revision= $cmt_opts" + ;; + info,--*) + __gitcomp "--url" + ;; + *) + COMPREPLY=() + ;; + esac + fi +} + _git_tag () { local i c=1 f=0 @@ -1243,6 +1320,7 @@ _git () show-branch) _git_log ;; stash) _git_stash ;; submodule) _git_submodule ;; + svn) _git_svn ;; tag) _git_tag ;; whatchanged) _git_log ;; *) COMPREPLY=() ;; @@ -1293,6 +1371,7 @@ complete -o default -o nospace -F _git_shortlog git-shortlog complete -o default -o nospace -F _git_show git-show complete -o default -o nospace -F _git_stash git-stash complete -o default -o nospace -F _git_submodule git-submodule +complete -o default -o nospace -F _git_svn git-svn complete -o default -o nospace -F _git_log git-show-branch complete -o default -o nospace -F _git_tag git-tag complete -o default -o nospace -F _git_log git-whatchanged From 6753f2aa55280a0fef1cbdcee71c1b529cb0c910 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 10 Mar 2008 19:49:19 -0400 Subject: [PATCH 31/42] bash: Remove completion of core.legacyheaders option This option is no longer recognized by git. Completing it is not worthwhile. Signed-off-by: Shawn O. Pearce --- contrib/completion/git-completion.bash | 1 - 1 file changed, 1 deletion(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 2c4bf4e1d2..73ed095487 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -961,7 +961,6 @@ _git_config () core.sharedRepository core.warnAmbiguousRefs core.compression - core.legacyHeaders core.packedGitWindowSize core.packedGitLimit clean.requireForce From 51fe120903370ca8bf384c839c8cfb70ee563eb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Thu, 6 Mar 2008 22:37:36 +0100 Subject: [PATCH 32/42] bash: use __gitdir when completing 'git rebase' options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When doing completion of rebase options in a subdirectory of the work tree during an ongoing rebase, wrong options were offered because of the hardcoded .git/.dotest-merge path. Signed-off-by: SZEDER Gábor Signed-off-by: Shawn O. Pearce --- contrib/completion/git-completion.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 73ed095487..fc108e4828 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -841,8 +841,8 @@ _git_push () _git_rebase () { - local cur="${COMP_WORDS[COMP_CWORD]}" - if [ -d .dotest ] || [ -d .git/.dotest-merge ]; then + local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)" + if [ -d .dotest ] || [ -d "$dir"/.dotest-merge ]; then __gitcomp "--continue --skip --abort" return fi From ce5a2c956f10f84d8403fc4e94106a6b33a5024a Mon Sep 17 00:00:00 2001 From: Teemu Likonen Date: Thu, 6 Mar 2008 18:52:37 +0200 Subject: [PATCH 33/42] bash: Add more long options to be completed with "git --" Add the following long options to be completed with command "git": --paginate --work-tree= --help Signed-off-by: Teemu Likonen Signed-off-by: Shawn O. Pearce --- contrib/completion/git-completion.bash | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index fc108e4828..2d11d0a97f 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1271,11 +1271,14 @@ _git () case "${COMP_WORDS[COMP_CWORD]}" in --*=*) COMPREPLY=() ;; --*) __gitcomp " + --paginate --no-pager --git-dir= --bare --version --exec-path + --work-tree= + --help " ;; *) __gitcomp "$(__git_commands) $(__git_aliases)" ;; From 5447aac755e5cfb948c627762b4665801811b026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Wed, 5 Mar 2008 20:07:49 +0100 Subject: [PATCH 34/42] bash: fix long option with argument double completion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pressing TAB right after 'git command --long-option=' results in 'git command --long-option=--long-option=' when the long option requires an argument, but we don't provide completion for its arguments (e.g. commit --author=, apply --exclude=). This patch detects these long options and provides empty completion array for them. Signed-off-by: SZEDER Gábor Signed-off-by: Shawn O. Pearce --- contrib/completion/git-completion.bash | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 2d11d0a97f..5046f69934 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -121,13 +121,21 @@ __gitcomp () if [ $# -gt 2 ]; then cur="$3" fi - for c in $1; do - case "$c$4" in - --*=*) all="$all$c$4$s" ;; - *.) all="$all$c$4$s" ;; - *) all="$all$c$4 $s" ;; - esac - done + case "$cur" in + --*=) + COMPREPLY=() + return + ;; + *) + for c in $1; do + case "$c$4" in + --*=*) all="$all$c$4$s" ;; + *.) all="$all$c$4$s" ;; + *) all="$all$c$4 $s" ;; + esac + done + ;; + esac IFS=$s COMPREPLY=($(compgen -P "$2" -W "$all" -- "$cur")) return From 702088afc680afef231d4a24bb5890f1d96a2cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Mon, 10 Mar 2008 15:38:33 +0100 Subject: [PATCH 35/42] update 'git rebase' documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Being in the project's top directory when starting or continuing a rebase is not necessary since 533b703 (Allow whole-tree operations to be started from a subdirectory, 2007-01-12). Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- Documentation/git-rebase.txt | 3 +-- git-rebase.sh | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 4b10304740..e0412e0866 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -262,8 +262,7 @@ hook if one exists. You can use this hook to do sanity checks and reject the rebase if it isn't appropriate. Please see the template pre-rebase hook script for an example. -You must be in the top directory of your project to start (or continue) -a rebase. Upon completion, will be the current branch. +Upon completion, will be the current branch. INTERACTIVE MODE ---------------- diff --git a/git-rebase.sh b/git-rebase.sh index 452c5e7e01..ff66af3ba8 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -18,8 +18,7 @@ original and remove the .dotest working files, use the command git rebase --abort instead. Note that if is not specified on the command line, the -currently checked out branch is used. You must be in the top -directory of your project to start (or continue) a rebase. +currently checked out branch is used. Example: git-rebase master~1 topic From 20a16eb33eee99fd3eab00c72f012b98d4eeee76 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 10 Mar 2008 23:51:13 -0700 Subject: [PATCH 36/42] unpack_trees(): fix diff-index regression. When skip_unmerged option is not given, unpack_trees() should not just skip unmerged cache entries but keep them in the result for the caller to sort them out. For callers other than diff-index, the incoming index should never be unmerged, but diff-index is a special case caller. Signed-off-by: Junio C Hamano --- diff-lib.c | 18 ++++++++++++++++++ unpack-trees.c | 2 -- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/diff-lib.c b/diff-lib.c index 9520773f3b..52dbac34a4 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -641,6 +641,21 @@ static void do_oneway_diff(struct unpack_trees_options *o, show_modified(revs, tree, idx, 1, cached, match_missing); } +static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_options *o) +{ + int len = ce_namelen(ce); + const struct index_state *index = o->src_index; + + while (o->pos < index->cache_nr) { + struct cache_entry *next = index->cache[o->pos]; + if (len != ce_namelen(next)) + break; + if (memcmp(ce->name, next->name, len)) + break; + o->pos++; + } +} + /* * The unpack_trees() interface is designed for merging, so * the different source entries are designed primarily for @@ -662,6 +677,9 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o) struct cache_entry *tree = src[1]; struct rev_info *revs = o->unpack_data; + if (idx && ce_stage(idx)) + skip_same_name(idx, o); + /* * Unpack-trees generates a DF/conflict entry if * there was a directory in the index and a tree diff --git a/unpack-trees.c b/unpack-trees.c index 5a0f0382b8..be89d52e8c 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -116,7 +116,6 @@ static int unpack_index_entry(struct cache_entry *ce, struct unpack_trees_option add_entry(o, ce, 0, 0); return 0; } - return 0; } return call_unpack_fn(src, o); } @@ -286,7 +285,6 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str add_entry(o, ce, 0, 0); return mask; } - continue; } src[0] = ce; } From 4201bb5f7e408143dc5bf35497cc82bc803fe9fb Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Sat, 1 Mar 2008 11:32:14 +0100 Subject: [PATCH 37/42] git rebase --abort: always restore the right commit Previously, --abort would end by git resetting to ORIG_HEAD, but some commands, such as git reset --hard (which happened in git rebase --skip, but could just as well be typed by the user), would have already modified ORIG_HEAD. Just use the orig-head we store in $dotest instead. [jc: cherry-picked from 48411d and 4947cf9 on 'master'] Signed-off-by: Mike Hommey Signed-off-by: Junio C Hamano --- git-rebase.sh | 5 ++- t/t3407-rebase-abort.sh | 71 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) create mode 100755 t/t3407-rebase-abort.sh diff --git a/git-rebase.sh b/git-rebase.sh index bdcea0ed70..6b9af962a9 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -208,16 +208,15 @@ do if test -d "$dotest" then move_to_original_branch - rm -r "$dotest" elif test -d .dotest then dotest=.dotest move_to_original_branch - rm -r .dotest else die "No rebase in progress?" fi - git reset --hard ORIG_HEAD + git reset --hard $(cat $dotest/orig-head) + rm -r "$dotest" exit ;; --onto) diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh new file mode 100755 index 0000000000..37944c39a3 --- /dev/null +++ b/t/t3407-rebase-abort.sh @@ -0,0 +1,71 @@ +#!/bin/sh + +test_description='git rebase --abort tests' + +. ./test-lib.sh + +test_expect_success setup ' + echo a > a && + git add a && + git commit -m a && + git branch to-rebase && + + echo b > a && + git commit -a -m b && + echo c > a && + git commit -a -m c && + + git checkout to-rebase && + echo d > a && + git commit -a -m "merge should fail on this" && + echo e > a && + git commit -a -m "merge should fail on this, too" && + git branch pre-rebase +' + +testrebase() { + type=$1 + dotest=$2 + + test_expect_success "rebase$type --abort" ' + # Clean up the state from the previous one + git reset --hard pre-rebase + test_must_fail git rebase'"$type"' master && + test -d '$dotest' && + git rebase --abort && + test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) && + test ! -d '$dotest' + ' + + test_expect_success "rebase$type --abort after --skip" ' + # Clean up the state from the previous one + git reset --hard pre-rebase + test_must_fail git rebase'"$type"' master && + test -d '$dotest' && + test_must_fail git rebase --skip && + test $(git rev-parse HEAD) = $(git rev-parse master) && + git-rebase --abort && + test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) && + test ! -d '$dotest' + ' + + test_expect_success "rebase$type --abort after --continue" ' + # Clean up the state from the previous one + git reset --hard pre-rebase + test_must_fail git rebase'"$type"' master && + test -d '$dotest' && + echo c > a && + echo d >> a && + git add a && + test_must_fail git rebase --continue && + test $(git rev-parse HEAD) != $(git rev-parse master) && + git rebase --abort && + test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) && + test ! -d '$dotest' + ' +} + +testrebase "" .dotest +testrebase " --merge" .git/.dotest-merge + +test_done From fc99469a2b786676ffe48d5966d71cea3613b716 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 11 Mar 2008 10:56:30 +0100 Subject: [PATCH 38/42] launch_editor(): allow spaces in the filename The construct sh -c "$0 \"$@\"" does not pick up quotes in , so you cannot give path to the editor that has a shell IFS whitespace in it, and also give it initial set of parameters and flags. Replace $0 with to fix this issue. This fixes git config core.editor '"c:/Program Files/What/Ever.exe"' In other words, you can specify an editor with spaces in its path using a config containing something like this: [core] editor = \"c:/Program Files/Darn/Spaces.exe\" NOTE: we cannot just replace the $0 with \"$0\", because we still want this to work: [core] editor = emacs -nw Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-tag.c | 6 +++++- t/t7005-editor.sh | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/builtin-tag.c b/builtin-tag.c index 4a4a88c10b..9a59caf70d 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -50,12 +50,15 @@ void launch_editor(const char *path, struct strbuf *buffer, const char *const *e size_t len = strlen(editor); int i = 0; const char *args[6]; + struct strbuf arg0; + strbuf_init(&arg0, 0); if (strcspn(editor, "$ \t'") != len) { /* there are specials */ + strbuf_addf(&arg0, "%s \"$@\"", editor); args[i++] = "sh"; args[i++] = "-c"; - args[i++] = "$0 \"$@\""; + args[i++] = arg0.buf; } args[i++] = editor; args[i++] = path; @@ -63,6 +66,7 @@ void launch_editor(const char *path, struct strbuf *buffer, const char *const *e if (run_command_v_opt_cd_env(args, 0, NULL, env)) die("There was a problem with the editor %s.", editor); + strbuf_release(&arg0); } if (!buffer) diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh index c1cec55306..6a74b3acfd 100755 --- a/t/t7005-editor.sh +++ b/t/t7005-editor.sh @@ -89,6 +89,33 @@ do ' done +test_expect_success 'editor with a space' ' + + if echo "echo space > \"\$1\"" > "e space.sh" + then + chmod a+x "e space.sh" && + GIT_EDITOR="./e\ space.sh" git commit --amend && + test space = "$(git show -s --pretty=format:%s)" + else + say "Skipping; FS does not support spaces in filenames" + fi + +' + +unset GIT_EDITOR +test_expect_success 'core.editor with a space' ' + + if test -f "e space.sh" + then + git config core.editor \"./e\ space.sh\" && + git commit --amend && + test space = "$(git show -s --pretty=format:%s)" + else + say "Skipping; FS does not support spaces in filenames" + fi + +' + TERM="$OLD_TERM" test_done From 7339eb082343df556c27fb0df5dd36a21714284e Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 11 Mar 2008 13:40:45 -0400 Subject: [PATCH 39/42] t0021: tr portability fix for Solaris Solaris' /usr/bin/tr doesn't seem to like multiple character ranges in brackets (it simply prints "Bad string"). Instead, let's just enumerate the transformation we want. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- t/t0021-conversion.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index cb860296ed..8fc39d77ce 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -5,7 +5,9 @@ test_description='blob conversion via gitattributes' . ./test-lib.sh cat <<\EOF >rot13.sh -tr '[a-zA-Z]' '[n-za-mN-ZA-M]' +tr \ + 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \ + 'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM' EOF chmod +x rot13.sh From ea14e6c55427f50f78fe47187cd4edb9845943a1 Mon Sep 17 00:00:00 2001 From: Marc-Andre Lureau Date: Tue, 11 Mar 2008 10:00:45 +0200 Subject: [PATCH 40/42] git-svn: fix find-rev error message when missing arg Just let the user know that a revision argument is missing instead of a perl error. This error message mimic the "init" error message, but could be improved. Signed-off-by: Marc-Andre Lureau Acked-by: Eric Wong Signed-off-by: Junio C Hamano --- git-svn.perl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index 29f39c0831..38e1d5944d 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -519,7 +519,8 @@ sub cmd_dcommit { } sub cmd_find_rev { - my $revision_or_hash = shift; + my $revision_or_hash = shift or die "SVN or git revision required ", + "as a command-line argument\n"; my $result; if ($revision_or_hash =~ /^r\d+$/) { my $head = shift; From 3000658f7c15c880f976aac0ade73efd3b1e9790 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sun, 9 Mar 2008 13:37:55 +0100 Subject: [PATCH 41/42] "remote update": print remote name being fetched from When the other end has dangling symref, "git fetch" issues an error message but that is not grave enough to cause the fetch process to fail. As the result, the user will see something like this: $ git remote update error: refs/heads/2.0-uobjects points nowhere! "remote update" used to report which remote it is fetching from, like this: $ git remote update Updating core Updating matthieu error: refs/heads/2.0-uobjects points nowhere! Updating origin This reinstates the message "Updating " in "git remote update". Signed-off-by: Samuel Tardieu Signed-off-by: Junio C Hamano --- builtin-remote.c | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin-remote.c b/builtin-remote.c index 637b90425e..24e692953b 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -46,6 +46,7 @@ static int opt_parse_track(const struct option *opt, const char *arg, int not) static int fetch_remote(const char *name) { const char *argv[] = { "fetch", name, NULL }; + printf("Updating %s\n", name); if (run_command_v_opt(argv, RUN_GIT_CMD)) return error("Could not fetch %s", name); return 0; From 7276607886e99fa3bb5f0ddadb9f0daeb3958d2d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 11 Mar 2008 22:29:52 -0700 Subject: [PATCH 42/42] git-gui: Simplify MSGFMT setting in Makefile To prepare msg files for Tcl scripts, the command that is set to MSGFMT make variable needs to be able to grok "--tcl -l -d " options correctly. This patch simplifies the tests done in git-gui's Makefile to directly test this condition. If the test run does not exit properly with zero status (either because you do not have "msgfmt" itself, or your "msgfmt" is too old to grok --tcl option --- the reason does not matter), have it fall back to po/po2msg.sh Signed-off-by: Junio C Hamano Signed-off-by: Shawn O. Pearce --- Makefile | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 4e321742ab..b19fb2d64e 100644 --- a/Makefile +++ b/Makefile @@ -221,14 +221,9 @@ ifdef NO_MSGFMT MSGFMT ?= $(TCL_PATH) po/po2msg.sh else MSGFMT ?= msgfmt - ifeq ($(shell $(MSGFMT) >/dev/null 2>&1 || echo $$?),127) + ifneq ($(shell $(MSGFMT) --tcl -l C -d . /dev/null 2>/dev/null; echo $$?),0) MSGFMT := $(TCL_PATH) po/po2msg.sh endif - ifeq (msgfmt,$(MSGFMT)) - ifeq ($(shell $(MSGFMT) --tcl -l C -d . /dev/null 2>/dev/null || echo $?),1) - MSGFMT := $(TCL_PATH) po/po2msg.sh - endif - endif endif msgsdir = $(gg_libdir)/msgs