diff options
61 files changed, 2852 insertions, 1536 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index c5e094a9c4..bfad0e3858 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -590,6 +590,10 @@ gc.packrefs:: at some stage, and setting this to `false` will continue to prevent `git pack-refs` from being run from `git gc`. +gc.pruneexpire:: + When `git gc` is run, it will call `prune --expire 2.weeks.ago`. + Override the grace period with this config variable. + gc.reflogexpire:: `git reflog expire` removes reflog entries older than this time; defaults to 90 days. @@ -860,7 +864,7 @@ pack.indexVersion:: whenever the corresponding pack is larger than 2 GB. Otherwise the default is 1. -pack.packSizeLimit: +pack.packSizeLimit:: The default maximum size of a pack. This setting only affects packing to a file, i.e. the git:// protocol is unaffected. It can be overridden by the `\--max-pack-size` option of diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 47799097ce..c751a17d07 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -207,16 +207,14 @@ patch:: and the working tree file and asks you if you want to stage the change of each hunk. You can say: - y - add the change from that hunk to index - n - do not add the change from that hunk to index - a - add the change from that hunk and all the rest to index - d - do not the change from that hunk nor any of the rest to index - j - do not decide on this hunk now, and view the next - undecided hunk - J - do not decide on this hunk now, and view the next hunk - k - do not decide on this hunk now, and view the previous - undecided hunk - K - do not decide on this hunk now, and view the previous hunk + y - stage this hunk + n - do not stage this hunk + a - stage this and all the remaining hunks in the file + d - do not stage this hunk nor any of the remaining hunks in the file + j - leave this hunk undecided, see next undecided hunk + J - leave this hunk undecided, see next hunk + k - leave this hunk undecided, see previous undecided hunk + K - leave this hunk undecided, see previous hunk s - split the current hunk into smaller hunks ? - print help + diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 2e7be916aa..229a7c9b30 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -8,7 +8,7 @@ git-gc - Cleanup unnecessary files and optimize the local repository SYNOPSIS -------- -'git-gc' [--prune] [--aggressive] [--auto] [--quiet] +'git-gc' [--aggressive] [--auto] [--quiet] DESCRIPTION ----------- @@ -25,17 +25,6 @@ operating performance. Some git commands may automatically run OPTIONS ------- ---prune:: - Usually `git-gc` packs refs, expires old reflog entries, - packs loose objects, - and removes old 'rerere' records. Removal - of unreferenced loose objects is an unsafe operation - while other git operations are in progress, so it is not - done by default. Pass this option if you want it, and only - when you know nobody else is creating new objects in the - repository at the same time (e.g. never use this option - in a cron script). - --aggressive:: Usually 'git-gc' runs very quickly while providing good disk space utilization and performance. This option will cause @@ -104,6 +93,10 @@ the value, the more time is spent optimizing the delta compression. See the documentation for the --window' option in linkgit:git-repack[1] for more details. This defaults to 10. +The optional configuration variable 'gc.pruneExpire' controls how old +the unreferenced loose objects have to be before they are pruned. The +default is "2 weeks ago". + See Also -------- linkgit:git-prune[1] diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 737894390d..3405ca09e8 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -21,6 +21,8 @@ Note that you can use `.` (current directory) as the <repository> 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 ------- 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, <branch> will be the current branch. +Upon completion, <branch> will be the current branch. INTERACTIVE MODE ---------------- @@ -247,7 +247,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)) \ @@ -308,7 +308,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 fsck.h \ + mailmap.h remote.h parse-options.h transport.h diffcore.h hash.h ll-merge.h fsck.h \ pack-revindex.h DIFF_OBJS = \ @@ -333,7 +333,7 @@ LIB_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 branch.o \ - alias.o fsck.o pack-revindex.o + ll-merge.o alias.o fsck.o pack-revindex.o BUILTIN_OBJS = \ builtin-add.o \ @@ -385,6 +385,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-checkout.c b/builtin-checkout.c index 6b08016228..7deb504837 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,8 @@ static int reset_to_new(struct tree *tree, int quiet) opts.merge = 1; opts.fn = oneway_merge; opts.verbose_update = !quiet; + 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)) @@ -170,6 +173,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 +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.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)) @@ -224,8 +230,11 @@ 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.src_index = &the_index; + topts.dst_index = &the_index; refresh_cache(REFRESH_QUIET); diff --git a/builtin-commit.c b/builtin-commit.c index f49c22e642..660a3458f7 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -198,6 +198,8 @@ static void create_base_index(void) opts.head_idx = 1; opts.index_only = 1; opts.merge = 1; + opts.src_index = &the_index; + opts.dst_index = &the_index; opts.fn = oneway_merge; tree = parse_tree_indirect(head_sha1); diff --git a/builtin-fetch.c b/builtin-fetch.c index 55f611e3c2..b2b9935ed6 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -40,6 +40,8 @@ static struct option builtin_fetch_options[] = { "force overwrite of local branch"), OPT_SET_INT('t', "tags", &tags, "fetch all tags and associated objects", TAGS_SET), + OPT_SET_INT('n', NULL, &tags, + "do not fetch all tags (--no-tags)", TAGS_UNSET), OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"), OPT_BOOLEAN('u', "update-head-ok", &update_head_ok, "allow updating of HEAD ref"), diff --git a/builtin-gc.c b/builtin-gc.c index 045bf0e487..95917d74a8 100644 --- a/builtin-gc.c +++ b/builtin-gc.c @@ -26,12 +26,13 @@ static int pack_refs = 1; static int aggressive_window = -1; static int gc_auto_threshold = 6700; static int gc_auto_pack_limit = 20; +static char *prune_expire = "2.weeks.ago"; #define MAX_ADD 10 static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL}; static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL}; static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL}; -static const char *argv_prune[] = {"prune", NULL}; +static const char *argv_prune[] = {"prune", "--expire", NULL, NULL}; static const char *argv_rerere[] = {"rerere", "gc", NULL}; static int gc_config(const char *var, const char *value) @@ -55,6 +56,17 @@ static int gc_config(const char *var, const char *value) gc_auto_pack_limit = git_config_int(var, value); return 0; } + if (!strcmp(var, "gc.pruneexpire")) { + if (!value) + return config_error_nonbool(var); + if (strcmp(value, "now")) { + unsigned long now = approxidate("now"); + if (approxidate(value) >= now) + return error("Invalid %s: '%s'", var, value); + } + prune_expire = xstrdup(value); + return 0; + } return git_default_config(var, value); } @@ -234,7 +246,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (run_command_v_opt(argv_repack, RUN_GIT_CMD)) return error(FAILED_RUN, argv_repack[0]); - if (prune && run_command_v_opt(argv_prune, RUN_GIT_CMD)) + argv_prune[2] = prune_expire; + if (run_command_v_opt(argv_prune, RUN_GIT_CMD)) return error(FAILED_RUN, argv_prune[0]); if (run_command_v_opt(argv_rerere, RUN_GIT_CMD)) diff --git a/builtin-merge-file.c b/builtin-merge-file.c index adce6d4635..3605960c2d 100644 --- a/builtin-merge-file.c +++ b/builtin-merge-file.c @@ -57,7 +57,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) if (!f) ret = error("Could not open %s for writing", filename); - else if (fwrite(result.ptr, result.size, 1, f) != 1) + else if (result.size && + fwrite(result.ptr, result.size, 1, f) != 1) ret = error("Could not write to %s", filename); else if (fclose(f)) ret = error("Could not close %s", filename); diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 6fe4102c0c..910c0d20e7 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -11,11 +11,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 "ll-merge.h" #include "interpolate.h" #include "attr.h" #include "merge-recursive.h" @@ -213,6 +213,8 @@ static int git_merge_trees(int index_only, opts.merge = 1; opts.head_idx = 2; opts.fn = threeway_merge; + 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); @@ -615,364 +617,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.<name>.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.<name>.var2 - * after seeing merge.<name>.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.<name>.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)); @@ -981,14 +635,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); @@ -1019,9 +668,20 @@ static struct merge_file_info merge_file(struct diff_filespec *o, if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1)) result.merge = 1; - result.mode = a->mode == o->mode ? b->mode: a->mode; + /* + * Merge modes + */ + if (a->mode == b->mode || a->mode == o->mode) + result.mode = b->mode; + else { + result.mode = a->mode; + if (b->mode != o->mode) { + result.clean = 0; + result.merge = 1; + } + } - if (sha_eq(a->sha1, o->sha1)) + if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, o->sha1)) hashcpy(result.sha, b->sha1); else if (sha_eq(b->sha1, o->sha1)) hashcpy(result.sha, a->sha1); @@ -1029,8 +689,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"); diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index f504cff756..777f272668 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -454,6 +454,7 @@ static void write_pack_file(void) struct pack_header hdr; int do_progress = progress >> pack_to_stdout; uint32_t nr_remaining = nr_result; + time_t last_mtime = 0; if (do_progress) progress_state = start_progress("Writing objects", nr_result); @@ -504,6 +505,7 @@ static void write_pack_file(void) if (!pack_to_stdout) { mode_t mode = umask(0); + struct stat st; char *idx_tmp_name, tmpname[PATH_MAX]; umask(mode); @@ -511,6 +513,7 @@ static void write_pack_file(void) idx_tmp_name = write_idx_file(NULL, written_list, nr_written, sha1); + snprintf(tmpname, sizeof(tmpname), "%s-%s.pack", base_name, sha1_to_hex(sha1)); if (adjust_perm(pack_tmp_name, mode)) @@ -519,6 +522,28 @@ static void write_pack_file(void) if (rename(pack_tmp_name, tmpname)) die("unable to rename temporary pack file: %s", strerror(errno)); + + /* + * Packs are runtime accessed in their mtime + * order since newer packs are more likely to contain + * younger objects. So if we are creating multiple + * packs then we should modify the mtime of later ones + * to preserve this property. + */ + if (stat(tmpname, &st) < 0) { + warning("failed to stat %s: %s", + tmpname, strerror(errno)); + } else if (!last_mtime) { + last_mtime = st.st_mtime; + } else { + struct utimbuf utb; + utb.actime = st.st_atime; + utb.modtime = --last_mtime; + if (utime(tmpname, &utb) < 0) + warning("failed utime() on %s: %s", + tmpname, strerror(errno)); + } + snprintf(tmpname, sizeof(tmpname), "%s-%s.idx", base_name, sha1_to_hex(sha1)); if (adjust_perm(idx_tmp_name, mode)) @@ -527,6 +552,7 @@ static void write_pack_file(void) if (rename(idx_tmp_name, tmpname)) die("unable to rename temporary index file: %s", strerror(errno)); + free(idx_tmp_name); free(pack_tmp_name); puts(sha1_to_hex(sha1)); diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 0138f5a917..e9cfd2bbc5 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -13,16 +13,15 @@ #include "dir.h" #include "builtin.h" -#define MAX_TREES 8 static int nr_trees; -static struct tree *trees[MAX_TREES]; +static struct tree *trees[MAX_UNPACK_TREES]; static int list_tree(unsigned char *sha1) { struct tree *tree; - if (nr_trees >= MAX_TREES) - die("I cannot read more than %d trees", MAX_TREES); + if (nr_trees >= MAX_UNPACK_TREES) + die("I cannot read more than %d trees", MAX_UNPACK_TREES); tree = parse_tree_indirect(sha1); if (!tree) return -1; @@ -97,11 +96,13 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) { int i, newfd, stage = 0; unsigned char sha1[20]; - struct tree_desc t[MAX_TREES]; + struct tree_desc t[MAX_UNPACK_TREES]; struct unpack_trees_options opts; memset(&opts, 0, sizeof(opts)); opts.head_idx = -1; + opts.src_index = &the_index; + opts.dst_index = &the_index; git_config(git_default_config); @@ -220,27 +221,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/builtin-remote.c b/builtin-remote.c new file mode 100644 index 0000000000..24e692953b --- /dev/null +++ b/builtin-remote.c @@ -0,0 +1,605 @@ +#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 <name> <url>", + "git remote rm <name>", + "git remote show <name>", + "git remote prune <name>", + "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 }; + printf("Updating %s\n", name); + 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; + + /* 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); + } + + 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); + transport_disconnect(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; + 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_setlen(&buf, prefix_len); + strbuf_addstr(&buf, 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 get_one_remote_for_update(struct remote *remote, void *priv) +{ + struct path_list *list = priv; + if (!remote->skip_default_update) + 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, result = 0; + struct path_list list = { NULL, 0, 0, 0 }; + static const char *default_argv[] = { NULL, "default", NULL }; + + if (argc < 2) { + argc = 2; + argv = default_argv; + } + + 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) +{ + 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-tag.c b/builtin-tag.c index 28c36fdcd1..8dd959fe1c 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) @@ -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); @@ -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); @@ -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, @@ -543,6 +544,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/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 848c067b57..5046f69934 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 @@ -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 @@ -384,7 +392,6 @@ __git_commands () show-index) : plumbing;; ssh-*) : transport;; stripspace) : plumbing;; - svn) : import export;; symbolic-ref) : plumbing;; tar-tree) : deprecated;; unpack-file) : plumbing;; @@ -428,6 +435,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 +508,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 [ $c -eq $COMP_CWORD -a -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)" ;; @@ -836,8 +849,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 @@ -956,7 +969,6 @@ _git_config () core.sharedRepository core.warnAmbiguousRefs core.compression - core.legacyHeaders core.packedGitWindowSize core.packedGitLimit clean.requireForce @@ -1033,21 +1045,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 [ $c -eq $COMP_CWORD -a -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,34 +1125,107 @@ _git_show () _git_stash () { - __gitcomp 'list show apply clear' + local subcommands='save list show apply clear drop pop create' + 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 [ $c -eq $COMP_CWORD -a -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 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 @@ -1198,15 +1275,18 @@ _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 " + --paginate --no-pager --git-dir= --bare --version --exec-path + --work-tree= + --help " ;; *) __gitcomp "$(__git_commands) $(__git_aliases)" ;; @@ -1250,6 +1330,7 @@ _git () show-branch) _git_log ;; stash) _git_stash ;; submodule) _git_submodule ;; + svn) _git_svn ;; tag) _git_tag ;; whatchanged) _git_log ;; *) COMPREPLY=() ;; @@ -1300,6 +1381,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 diff --git a/git-remote.perl b/contrib/examples/git-remote.perl index b30ed734e7..b30ed734e7 100755 --- a/git-remote.perl +++ b/contrib/examples/git-remote.perl diff --git a/diff-lib.c b/diff-lib.c index 4581b594d0..52dbac34a4 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,32 +641,19 @@ 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) +static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_options *o) { - 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++; - } + 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++; } - return skip; } /* @@ -685,17 +671,14 @@ 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); + if (idx && ce_stage(idx)) + skip_same_name(idx, o); /* * Unpack-trees generates a DF/conflict entry if @@ -707,9 +690,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,6 +717,8 @@ int run_diff_index(struct rev_info *revs, int cached) opts.merge = 1; opts.fn = oneway_diff; opts.unpack_data = revs; + opts.src_index = &the_index; + opts.dst_index = NULL; init_tree_desc(&t, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts)) @@ -787,6 +772,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.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/git-compat-util.h b/git-compat-util.h index 73968e02b0..a18235e6d0 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -68,6 +68,7 @@ #include <sys/poll.h> #include <sys/socket.h> #include <sys/ioctl.h> +#include <utime.h> #ifndef NO_SYS_SELECT_H #include <sys/select.h> #endif diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 47f116f37e..95c5eec51e 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -735,7 +735,7 @@ sub commit { next unless $logmsg =~ $rx && $1; my $mparent = $1 eq 'HEAD' ? $opt_o : $1; if (my $sha1 = get_headref("$remote/$mparent")) { - push @commit_args, '-p', $mparent; + push @commit_args, '-p', "$remote/$mparent"; print "Merge parent branch: $mparent\n" if $opt_v; } } diff --git a/git-gui/Makefile b/git-gui/Makefile index 4e321742ab..b19fb2d64e 100644 --- a/git-gui/Makefile +++ b/git-gui/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 diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 238a2393ff..3a58cd2c6b 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/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] @@ -2289,8 +2290,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 +2310,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,12 +2419,13 @@ 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 \ -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 \ @@ -2493,15 +2497,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 +2532,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/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl index 00ecf21333..92fac1bad4 100644 --- a/git-gui/lib/blame.tcl +++ b/git-gui/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 <Button-1> " 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/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl index 53d5a62816..ab470d1264 100644 --- a/git-gui/lib/browser.tcl +++ b/git-gui/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/git-gui/lib/choose_font.tcl b/git-gui/lib/choose_font.tcl index 0c4051b375..56443b042c 100644 --- a/git-gui/lib/choose_font.tcl +++ b/git-gui/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/git-gui/lib/console.tcl b/git-gui/lib/console.tcl index 5597188d80..c112464ec3 100644 --- a/git-gui/lib/console.tcl +++ b/git-gui/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 <Destroy> [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 <Destroy> [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/git-gui/lib/error.tcl b/git-gui/lib/error.tcl index 8c27678e3a..75650157e5 100644 --- a/git-gui/lib/error.tcl +++ b/git-gui/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 \ diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl index ea80df0092..9270512582 100644 --- a/git-gui/lib/option.tcl +++ b/git-gui/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] diff --git a/git-gui/po/zh_cn.po b/git-gui/po/zh_cn.po index 621c9479b2..f8697216f7 100644 --- a/git-gui/po/zh_cn.po +++ b/git-gui/po/zh_cn.po @@ -3,46 +3,63 @@ # This file is distributed under the same license as the git-gui package. # Xudong Guan <xudong.guan@gmail.com>, 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 <eric.y.miao@gmail.com> +# 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 <xudong.guan@gmail.com>\n" +"Last-Translator: Eric Miao <eric.y.miao@gmail.com>\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 "" +msgstr "克隆" -#: lib/choose_repository.tcl:299 +#: lib/choose_repository.tcl:476 msgid "URL:" -msgstr "" +msgstr "URL:" -#: lib/choose_repository.tcl:319 +#: lib/choose_repository.tcl:496 msgid "Clone Type:" -msgstr "" +msgstr "克隆类型:" -#: lib/choose_repository.tcl:325 +#: lib/choose_repository.tcl:502 msgid "Standard (Fast, Semi-Redundant, Hardlinks)" -msgstr "" +msgstr "标准方式 (快速, 部分备份, 作硬连接)" -#: lib/choose_repository.tcl:331 +#: lib/choose_repository.tcl:508 msgid "Full Copy (Slower, Redundant Backup)" -msgstr "" +msgstr "全部复制 (较慢, 做备份)" -#: lib/choose_repository.tcl:337 +#: lib/choose_repository.tcl:514 msgid "Shared (Fastest, Not Recommended, No Backup)" -msgstr "" +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 +#: 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 "" +msgstr "不是一个 Git 版本库: %s" -#: lib/choose_repository.tcl:405 +#: lib/choose_repository.tcl:586 msgid "Standard only available for local repository." -msgstr "" +msgstr "标准方式仅当是本地版本库时有效." -#: lib/choose_repository.tcl:409 +#: lib/choose_repository.tcl:590 msgid "Shared only available for local repository." -msgstr "" +msgstr "共享方式仅当是本地版本库时有效." -#: lib/choose_repository.tcl:439 +#: lib/choose_repository.tcl:617 msgid "Failed to configure origin" -msgstr "" +msgstr "无法配置 origin" -#: lib/choose_repository.tcl:451 +#: lib/choose_repository.tcl:629 msgid "Counting objects" -msgstr "" +msgstr "清点对象" -#: lib/choose_repository.tcl:452 +#: lib/choose_repository.tcl:630 +#, fuzzy msgid "buckets" -msgstr "" +msgstr "水桶??" -#: lib/choose_repository.tcl:476 +#: 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 "压缩数据库" +msgstr "复制 objects" -#: lib/choose_repository.tcl:571 +#: lib/choose_repository.tcl:749 msgid "KiB" -msgstr "" +msgstr "KiB" -#: lib/choose_repository.tcl:595 +#: lib/choose_repository.tcl:773 #, tcl-format msgid "Unable to copy object: %s" -msgstr "" +msgstr "无法复制 object: %s" -#: lib/choose_repository.tcl:605 +#: lib/choose_repository.tcl:783 msgid "Linking objects" -msgstr "" +msgstr "链接 objects" -#: lib/choose_repository.tcl:606 +#: lib/choose_repository.tcl:784 msgid "objects" -msgstr "" +msgstr "objects" -#: lib/choose_repository.tcl:614 +#: lib/choose_repository.tcl:792 #, tcl-format msgid "Unable to hardlink object: %s" -msgstr "" +msgstr "无法硬链接 object: %s" -#: lib/choose_repository.tcl:669 +#: lib/choose_repository.tcl:847 msgid "Cannot fetch branches and objects. See console output for details." -msgstr "" +msgstr "无法获取分支和对象. 请查看控制终端的输出." -#: lib/choose_repository.tcl:680 +#: lib/choose_repository.tcl:858 msgid "Cannot fetch tags. See console output for details." -msgstr "" +msgstr "无法获取标签. 请查看控制终端的输出." -#: lib/choose_repository.tcl:704 +#: lib/choose_repository.tcl:882 msgid "Cannot determine HEAD. See console output for details." -msgstr "" +msgstr "无法确定 HEAD. 请查看控制终端的输出." -#: lib/choose_repository.tcl:713 +#: lib/choose_repository.tcl:891 #, tcl-format msgid "Unable to cleanup %s" -msgstr "" +msgstr "无法清理 %s" -#: lib/choose_repository.tcl:719 +#: lib/choose_repository.tcl:897 msgid "Clone failed." -msgstr "" +msgstr "克隆失败." -#: lib/choose_repository.tcl:726 +#: lib/choose_repository.tcl:904 msgid "No default branch obtained." -msgstr "" +msgstr "没有获取缺省分支" -#: lib/choose_repository.tcl:737 +#: lib/choose_repository.tcl:915 #, tcl-format msgid "Cannot resolve %s as a commit." -msgstr "" +msgstr "无法解析 %s 为提交." -#: lib/choose_repository.tcl:749 +#: 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/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:149 +#: 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 "包含标签" diff --git a/git-quiltimport.sh b/git-quiltimport.sh index 233e5eae1d..7cd8f7134e 100755 --- a/git-quiltimport.sh +++ b/git-quiltimport.sh @@ -63,7 +63,23 @@ tmp_info="$tmp_dir/info" commit=$(git rev-parse HEAD) mkdir $tmp_dir || exit 2 -for patch_name in $(grep -v '^#' < "$QUILT_PATCHES/series" ); do +while read patch_name level garbage +do + case "$patch_name" in ''|'#'*) continue;; esac + case "$level" in + -p*) ;; + ''|'#'*) + level=;; + *) + echo "unable to parse patch level, ignoring it." + level=;; + esac + case "$garbage" in + ''|'#'*);; + *) + echo "trailing garbage found in series file: $garbage" + exit 1;; + esac if ! [ -f "$QUILT_PATCHES/$patch_name" ] ; then echo "$patch_name doesn't exist. Skipping." continue @@ -113,10 +129,10 @@ for patch_name in $(grep -v '^#' < "$QUILT_PATCHES/series" ); do fi if [ -z "$dry_run" ] ; then - git apply --index -C1 "$tmp_patch" && + git apply --index -C1 ${level:+"$level"} "$tmp_patch" && tree=$(git write-tree) && commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git commit-tree $tree -p $commit) && git update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4 fi -done +done <"$QUILT_PATCHES/series" rm -rf $tmp_dir || exit 5 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 <branch> and remove the .dotest working files, use the command git rebase --abort instead. Note that if <branch> 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 diff --git a/git-svn.perl b/git-svn.perl index 1195569529..d8b38c9a47 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -522,7 +522,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; @@ -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 }, @@ -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; @@ -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/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.<name>.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.<name>.var2 + * after seeing merge.<name>.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.<name>.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-tree.c b/merge-tree.c index e08324686c..02fc10f7e6 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,41 @@ 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 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)) { if (entry[0].sha1) { - resolve(base, NULL, entry+1); - return; + resolve(info, NULL, entry+1); + return mask; } } if (same_entry(entry+0, entry+1)) { if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) { - resolve(base, entry+1, entry+2); - return; + resolve(info, entry+1, entry+2); + return mask; } } if (same_entry(entry+0, entry+2)) { if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) { - resolve(base, NULL, entry+1); - return; + resolve(info, NULL, entry+1); + return mask; } } - unresolved(base, entry); + unresolved(info, entry); + return mask; } 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/parse-options.c b/parse-options.c index b32c9ea66c..8e64316fe0 100644 --- a/parse-options.c +++ b/parse-options.c @@ -259,6 +259,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; args.out[args.cpidx++] = args.argv[0]; continue; } diff --git a/parse-options.h b/parse-options.h index dc0807834f..1af62b0485 100644 --- a/parse-options.h +++ b/parse-options.h @@ -19,6 +19,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 { 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); +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); -void path_list_clear(struct path_list *list, int free_util); 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 */ diff --git a/read-cache.c b/read-cache.c index 657f0c5894..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; @@ -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; @@ -377,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; @@ -1166,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++) { @@ -1311,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; @@ -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; } @@ -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/sha1_name.c b/sha1_name.c index 8358ba2069..8b6c76f68e 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -422,6 +422,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]; @@ -473,32 +504,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) - return -1; - if (!o->parsed) - if (!parse_object(o->sha1)) - return -1; + o = peel_to_type(name, len, o, expected_type); + if (o) { + hashcpy(sha1, o->sha1); + return 0; } + return -1; } return 0; } 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 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/t/t1410-reflog.sh b/t/t1410-reflog.sh index 24476bede5..73f830db23 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -202,22 +202,4 @@ test_expect_success 'delete' ' ' -test_expect_success 'prune --expire' ' - - before=$(git count-objects | sed "s/ .*//") && - BLOB=$(echo aleph | git hash-object -w --stdin) && - BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && - test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && - test -f $BLOB_FILE && - git reset --hard && - git prune --expire=1.hour.ago && - test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && - test -f $BLOB_FILE && - test-chmtime -86500 $BLOB_FILE && - git prune --expire 1.day && - test $before = $(git count-objects | sed "s/ .*//") && - ! test -f $BLOB_FILE - -' - test_done diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index 6560af756e..47090c4cf5 100644 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -29,4 +29,53 @@ test_expect_success 'prune stale packs' ' ' +test_expect_success 'prune --expire' ' + + before=$(git count-objects | sed "s/ .*//") && + BLOB=$(echo aleph | git hash-object -w --stdin) && + BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && + test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE && + git prune --expire=1.hour.ago && + test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE && + test-chmtime -86500 $BLOB_FILE && + git prune --expire 1.day && + test $before = $(git count-objects | sed "s/ .*//") && + ! test -f $BLOB_FILE + +' + +test_expect_success 'gc: implicit prune --expire' ' + + before=$(git count-objects | sed "s/ .*//") && + BLOB=$(echo aleph_0 | git hash-object -w --stdin) && + BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && + test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE && + test-chmtime -$((86400*14-30)) $BLOB_FILE && + git gc && + test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE && + test-chmtime -$((86400*14+1)) $BLOB_FILE && + git gc && + test $before = $(git count-objects | sed "s/ .*//") && + ! test -f $BLOB_FILE + +' + +test_expect_success 'gc: refuse to start with invalid gc.pruneExpire' ' + + git config gc.pruneExpire invalid && + test_must_fail git gc + +' + +test_expect_success 'gc: start with ok gc.pruneExpire' ' + + git config gc.pruneExpire 2.days.ago && + git gc + +' + test_done diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 4fc62f550c..2822a651b5 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 ) @@ -78,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 ) ' @@ -94,4 +97,144 @@ test_expect_success 'remove remote' ' ) ' +cat > test/expect << EOF +* remote origin + URL: $(pwd)/one/.git + Remote branch merged with 'git pull' while on branch master + master + New remote branch (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 && + test_tick && + 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_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) +' + +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 diff --git a/t/t6031-merge-recursive.sh b/t/t6031-merge-recursive.sh new file mode 100755 index 0000000000..5bb6b93780 --- /dev/null +++ b/t/t6031-merge-recursive.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +test_description='merge-recursive: handle file mode' +. ./test-lib.sh + +test_expect_success 'mode change in one branch: keep changed version' ' + : >file1 && + git add file1 && + git commit -m initial && + git checkout -b a1 master && + : >dummy && + git add dummy && + git commit -m a && + git checkout -b b1 master && + chmod +x file1 && + git add file1 && + git commit -m b1 && + git checkout a1 && + git merge-recursive master -- a1 b1 && + test -x file1 +' + +test_expect_success 'mode change in both branches: expect conflict' ' + git reset --hard HEAD && + git checkout -b a2 master && + : >file2 && + H=$(git hash-object file2) && + chmod +x file2 && + git add file2 && + git commit -m a2 && + git checkout -b b2 master && + : >file2 && + git add file2 && + git commit -m b2 && + git checkout a2 && + ( + git merge-recursive master -- a2 b2 + test $? = 1 + ) && + git ls-files -u >actual && + ( + echo "100755 $H 2 file2" + echo "100644 $H 3 file2" + ) >expect && + diff -u actual expect && + test -x file2 +' + +test_done 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 diff --git a/tree-walk.c b/tree-walk.c index 142205ddc3..02e2aed773 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); } @@ -104,12 +104,48 @@ 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); + static struct traverse_info dummy; + + 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); + if (pathlen) + info->prev = &dummy; +} + +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; +} + +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 (;;) { unsigned long mask = 0; + unsigned long dirmask = 0; int i, last; last = -1; @@ -134,25 +170,35 @@ void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callb mask = 0; } mask |= 1ul << i; + if (S_ISDIR(entry[i].mode)) + dirmask |= 1ul << i; last = i; } if (!mask) break; + dirmask &= mask; /* - * 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); } - callback(n, mask, entry, base); + ret = info->fn(n, mask, dirmask, 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 db0fbdc701..42110a465f 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -33,10 +33,27 @@ 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); - -void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback); +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 { + struct traverse_info *prev; + struct name_entry name; + int pathlen; + + unsigned long conflicts; + 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 diff --git a/unpack-trees.c b/unpack-trees.c index 3e448d8974..91649f31d6 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,268 +8,18 @@ #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) +static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce, + unsigned int set, unsigned int clear) { - 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; - } + unsigned int size = ce_size(ce); + struct cache_entry *new = xmalloc(size); - 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; + clear |= CE_HASHED | CE_UNHASHED; -#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; + 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 @@ -308,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 < active_nr; cnt++) { - struct cache_entry *ce = active_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++; } @@ -323,15 +75,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 < 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_cache_entry_at(i); + remove_index_entry_at(&o->result, i); i--; continue; } @@ -346,21 +98,244 @@ static void check_updates(struct unpack_trees_options *o) stop_progress(&progress); } -int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o) +static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o) +{ + 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) { + add_entry(o, ce, 0, 0); + return 0; + } + } + 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) { - struct tree_entry_list **posns; int i; - struct tree_entry_list df_conflict_list; + struct tree_desc t[MAX_UNPACK_TREES]; + 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); + } + return traverse_trees(n, t, &newinfo); +} + +/* + * 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 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); + + n += o->merge; + for (i = 0; i < n; i++) + add_entry(o, src[i], 0, 0); + 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; + 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 < 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) + return -1; + 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) { + add_entry(o, ce, 0, 0); + return mask; + } + } + src[0] = ce; + } + break; + } + } + + if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0) + return -1; + + /* Now handle any directories.. */ + if (dirmask) { + unsigned long conflicts = mask & ~dirmask; + if (o->merge) { + conflicts <<= 1; + if (src[0]) + conflicts |= 1; + } + if (traverse_trees_recursive(n, dirmask, conflicts, + names, info) < 0) + return -1; + return mask; + } + + return mask; +} + +static int unpack_failed(struct unpack_trees_options *o, const char *message) +{ + discard_index(&o->result); + if (!o->gently) { + if (message) + return error(message); + return -1; + } + return -1; +} + +int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o) +{ static struct cache_entry *dfc; - memset(&df_conflict_list, 0, sizeof(df_conflict_list)); - df_conflict_list.next = &df_conflict_list; + if (len > MAX_UNPACK_TREES) + die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES); memset(&state, 0, sizeof(state)); state.base_dir = ""; state.force = 1; state.quiet = 1; state.refresh_cache = 1; + memset(&o->result, 0, sizeof(o->result)); o->merge_size = len; if (!dfc) @@ -368,30 +343,33 @@ 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); - - if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "", - o, &df_conflict_list)) { - if (o->gently) { - discard_cache(); - read_cache(); - } - return -1; - } + const char *prefix = o->prefix ? o->prefix : ""; + struct traverse_info info; + + setup_traverse_info(&info, prefix); + info.fn = unpack_callback; + info.data = o; + + if (traverse_trees(len, t, &info) < 0) + return unpack_failed(o, NULL); } - if (o->trivial_merges_only && o->nontrivial_merge) { - if (o->gently) { - discard_cache(); - read_cache(); + /* Any left-over entries in the index? */ + if (o->merge) { + 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); } - 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"); + + o->src_index = NULL; check_updates(o); + if (o->dst_index) + *o->dst_index = o->result; return 0; } @@ -427,7 +405,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->src_index, ce, &st, CE_MATCH_IGNORE_VALID); if (!changed) return 0; /* @@ -447,10 +425,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->src_index->cache_tree, ce->name); } /* @@ -495,12 +473,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->src_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->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) || @@ -512,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++; } @@ -598,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 = cache_name_pos(ce->name, strlen(ce->name)); + cnt = index_name_pos(&o->result, ce->name, strlen(ce->name)); if (0 <= cnt) { - struct cache_entry *ce = active_cache[cnt]; + struct cache_entry *ce = o->result.cache[cnt]; if (ce->ce_flags & CE_REMOVE) return 0; } @@ -615,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? @@ -629,38 +606,38 @@ 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_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_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); - invalidate_ce_path(ce); + 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_cache_entry(ce, ADD_CACHE_OK_TO_ADD); + add_entry(o, ce, 0, 0); return 1; } @@ -680,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; @@ -759,10 +734,8 @@ int threeway_merge(struct cache_entry **stages, } /* #1 */ - if (!head && !remote && any_anc_missing) { - remove_entry(remove); + 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. @@ -794,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); 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; } @@ -820,7 +792,6 @@ int threeway_merge(struct cache_entry **stages, return -1; } - remove_entry(remove); o->nontrivial_merge = 1; /* #2, #3, #4, #6, #7, #9, #10, #11. */ @@ -855,9 +826,7 @@ int threeway_merge(struct cache_entry **stages, * "carry forward" rule, please see <Documentation/git-read-tree.txt>. * */ -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]; @@ -885,7 +854,6 @@ int twoway_merge(struct cache_entry **src, } else if (oldtree && !newtree && same(current, oldtree)) { /* 10 or 11 */ - remove_entry(remove); return deleted_entry(oldtree, current, o); } else if (oldtree && newtree && @@ -895,7 +863,6 @@ int twoway_merge(struct cache_entry **src, } else { /* all other failures */ - remove_entry(remove); if (oldtree) return o->gently ? -1 : reject_merge(oldtree); if (current) @@ -907,7 +874,6 @@ int twoway_merge(struct cache_entry **src, } else if (newtree) return merged_entry(newtree, current, o); - remove_entry(remove); return deleted_entry(oldtree, current, o); } @@ -918,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]; @@ -929,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 @@ -942,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]; @@ -953,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); + 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) || - ce_match_stat(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 a2df544d04..50453ed20f 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -1,11 +1,12 @@ #ifndef UNPACK_TREES_H #define UNPACK_TREES_H +#define MAX_UNPACK_TREES 8 + 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,14 +29,18 @@ struct unpack_trees_options { struct cache_entry *df_conflict_entry; void *unpack_data; + + 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 diff --git a/xdiff-interface.c b/xdiff-interface.c index bba236428a..61dc5c5470 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -152,8 +152,8 @@ int read_mmfile(mmfile_t *ptr, const char *filename) if ((f = fopen(filename, "rb")) == NULL) return error("Could not open %s", filename); sz = xsize_t(st.st_size); - ptr->ptr = xmalloc(sz); - if (fread(ptr->ptr, sz, 1, f) != 1) + ptr->ptr = xmalloc(sz ? sz : 1); + if (sz && fread(ptr->ptr, sz, 1, f) != 1) return error("Could not read %s", filename); fclose(f); ptr->size = sz; |