Merge branch 'mr/autoconf-fread'
authorJunio C Hamano <gitster@pobox.com>
Fri, 14 Mar 2008 07:27:59 +0000 (00:27 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 14 Mar 2008 07:27:59 +0000 (00:27 -0700)
* mr/autoconf-fread:
autoconf: Test FREAD_READS_DIRECTORIES

62 files changed:
Documentation/config.txt
Documentation/git-add.txt
Documentation/git-gc.txt
Documentation/git-pull.txt
Documentation/git-rebase.txt
Makefile
builtin-checkout.c
builtin-commit.c
builtin-fetch.c
builtin-gc.c
builtin-merge-file.c
builtin-merge-recursive.c
builtin-pack-objects.c
builtin-read-tree.c
builtin-remote.c [new file with mode: 0644]
builtin-tag.c
builtin.h
cache.h
contrib/completion/git-completion.bash
contrib/examples/git-remote.perl [new file with mode: 0755]
diff-lib.c
git-compat-util.h
git-cvsimport.perl
git-gui/Makefile
git-gui/git-gui.sh
git-gui/lib/blame.tcl
git-gui/lib/browser.tcl
git-gui/lib/choose_font.tcl
git-gui/lib/console.tcl
git-gui/lib/error.tcl
git-gui/lib/option.tcl
git-gui/po/zh_cn.po
git-quiltimport.sh
git-rebase.sh
git-remote.perl [deleted file]
git-svn.perl
git.c
hash.c
hash.h
ll-merge.c [new file with mode: 0644]
ll-merge.h [new file with mode: 0644]
merge-tree.c
parse-options.c
parse-options.h
path-list.c
path-list.h
read-cache.c
remote.c
remote.h
sha1_name.c
t/t0021-conversion.sh
t/t1005-read-tree-reset.sh
t/t1410-reflog.sh
t/t5304-prune.sh
t/t5505-remote.sh
t/t6031-merge-recursive.sh [new file with mode: 0755]
t/t7005-editor.sh
tree-walk.c
tree-walk.h
unpack-trees.c
unpack-trees.h
xdiff-interface.c
index c5e094a9c4a31568e4efb2c70af7e7808ae63528..bfad0e3858047aa4606e48120e16831e6f17c2f7 100644 (file)
@@ -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
index 47799097ce97da34fac60eafabd25576e077e362..c751a17d079dc2a6c5d3160573a42a7b6be3d5e2 100644 (file)
@@ -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
 +
index 2e7be916aa5dede578b181e06457189551c16240..229a7c9b30482eb2d8729b581004bc776fa10edf 100644 (file)
@@ -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]
index 737894390d8033bf470b3bfe3aeade858f38717e..3405ca09e85e9c81b9d48c753a2f117e7edca4eb 100644 (file)
@@ -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
 -------
index 4b10304740f5035095c19be05859ae0b9205e9a0..e0412e0866db8f80e8bbc8f33caaa659461d90aa 100644 (file)
@@ -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
 ----------------
index 7fbb81578264e47c83857e864e58b32c4f81a5fa..e3eaa6a543e7c3eed8a72c00f8b5541524fc9508 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -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 \
index 6b08016228d4d50cfd6863a11f33b2c13084a6eb..7deb504837d252dd43fa9746c5d12342e93a5923 100644 (file)
@@ -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);
 
index f49c22e64255225e492614bb628c1d1776521424..660a3458f7f4ef24dfa4fd5bdf902174da1eefb4 100644 (file)
@@ -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);
index 55f611e3c26e55df43eca1f40df74d335c850cc0..b2b9935ed65cfa80daf9f9071a3dd5d381ef3426 100644 (file)
@@ -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"),
index 045bf0e487a73e98d8d78990fcf85cc25ad5f96d..95917d74a873065bf9f928da37aa40e9e0d1f688 100644 (file)
@@ -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))
index adce6d4635a4153428368073677cd74a9bafc045..3605960c2d9692514a6df0f344f3c3269cf1de3c 100644 (file)
@@ -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);
index 6fe4102c0cfb29031f7fdce68ca4d1937e2fefd7..910c0d20e7ba1128c705a49bfd9966212c5420b2 100644 (file)
 #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");
index f504cff7566159332c89f830a871da0ea05868fe..777f272668c39931b330be555f41399adfbce397 100644 (file)
@@ -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));
index 0138f5a9172034b2ce34222ff077a975f8998005..e9cfd2bbc5539ee0c9c048798383b837ff63991b 100644 (file)
 #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 (file)
index 0000000..24e6929
--- /dev/null
@@ -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;
+}
index 28c36fdcd1658968ff7c3e2f1d6ba6f364f99592..8dd959fe1c74507023f8e82c7f4682c1588ebac6 100644 (file)
@@ -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)
index 674c8a141faf808883c9de283d10da01b3f9c2d5..95126fd0c12149034372afd8eb37b944be684957 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -67,6 +67,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
 extern int cmd_push(int argc, const char **argv, const char *prefix);
 extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_reflog(int argc, const char **argv, const char *prefix);
+extern int cmd_remote(int argc, const char **argv, const char *prefix);
 extern int cmd_config(int argc, const char **argv, const char *prefix);
 extern int cmd_rerere(int argc, const char **argv, const char *prefix);
 extern int cmd_reset(int argc, const char **argv, const char *prefix);
diff --git a/cache.h b/cache.h
index e23030262328761669f4704a3e3731a5e93d5b0c..2a1e7ec6b2bf712af80813936b5aed434d02090e 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -346,12 +346,12 @@ extern void verify_non_filename(const char *prefix, const char *name);
 /* Initialize and use the cache information */
 extern int read_index(struct index_state *);
 extern int read_index_from(struct index_state *, const char *path);
-extern int write_index(struct index_state *, int newfd);
+extern int write_index(const struct index_state *, int newfd);
 extern int discard_index(struct index_state *);
-extern int unmerged_index(struct index_state *);
+extern int unmerged_index(const struct index_state *);
 extern int verify_path(const char *path);
 extern int index_name_exists(struct index_state *istate, const char *name, int namelen);
-extern int index_name_pos(struct index_state *, const char *name, int namelen);
+extern int index_name_pos(const struct index_state *, const char *name, int namelen);
 #define ADD_CACHE_OK_TO_ADD 1          /* Ok to add */
 #define ADD_CACHE_OK_TO_REPLACE 2      /* Ok to replace file/directory */
 #define ADD_CACHE_SKIP_DFCHECK 4       /* Ok to skip DF conflict checks */
@@ -368,8 +368,8 @@ extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
 #define CE_MATCH_IGNORE_VALID          01
 /* do not check the contents but report dirty on racily-clean entries */
 #define CE_MATCH_RACY_IS_DIRTY 02
-extern int ie_match_stat(struct index_state *, struct cache_entry *, struct stat *, unsigned int);
-extern int ie_modified(struct index_state *, struct cache_entry *, struct stat *, unsigned int);
+extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
+extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 
 extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
 extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
@@ -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,
index 848c067b57398154de6dc5287648b4103841217a..5046f699349aa87bedbc79a997dfcb9ea54e7618 100755 (executable)
@@ -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/contrib/examples/git-remote.perl b/contrib/examples/git-remote.perl
new file mode 100755 (executable)
index 0000000..b30ed73
--- /dev/null
@@ -0,0 +1,477 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Git;
+my $git = Git->repository();
+
+sub add_remote_config {
+       my ($hash, $name, $what, $value) = @_;
+       if ($what eq 'url') {
+               # Having more than one is Ok -- it is used for push.
+               if (! exists $hash->{'URL'}) {
+                       $hash->{$name}{'URL'} = $value;
+               }
+       }
+       elsif ($what eq 'fetch') {
+               $hash->{$name}{'FETCH'} ||= [];
+               push @{$hash->{$name}{'FETCH'}}, $value;
+       }
+       elsif ($what eq 'push') {
+               $hash->{$name}{'PUSH'} ||= [];
+               push @{$hash->{$name}{'PUSH'}}, $value;
+       }
+       if (!exists $hash->{$name}{'SOURCE'}) {
+               $hash->{$name}{'SOURCE'} = 'config';
+       }
+}
+
+sub add_remote_remotes {
+       my ($hash, $file, $name) = @_;
+
+       if (exists $hash->{$name}) {
+               $hash->{$name}{'WARNING'} = 'ignored due to config';
+               return;
+       }
+
+       my $fh;
+       if (!open($fh, '<', $file)) {
+               print STDERR "Warning: cannot open $file\n";
+               return;
+       }
+       my $it = { 'SOURCE' => 'remotes' };
+       $hash->{$name} = $it;
+       while (<$fh>) {
+               chomp;
+               if (/^URL:\s*(.*)$/) {
+                       # Having more than one is Ok -- it is used for push.
+                       if (! exists $it->{'URL'}) {
+                               $it->{'URL'} = $1;
+                       }
+               }
+               elsif (/^Push:\s*(.*)$/) {
+                       $it->{'PUSH'} ||= [];
+                       push @{$it->{'PUSH'}}, $1;
+               }
+               elsif (/^Pull:\s*(.*)$/) {
+                       $it->{'FETCH'} ||= [];
+                       push @{$it->{'FETCH'}}, $1;
+               }
+               elsif (/^\#/) {
+                       ; # ignore
+               }
+               else {
+                       print STDERR "Warning: funny line in $file: $_\n";
+               }
+       }
+       close($fh);
+}
+
+sub list_remote {
+       my ($git) = @_;
+       my %seen = ();
+       my @remotes = eval {
+               $git->command(qw(config --get-regexp), '^remote\.');
+       };
+       for (@remotes) {
+               if (/^remote\.(\S+?)\.([^.\s]+)\s+(.*)$/) {
+                       add_remote_config(\%seen, $1, $2, $3);
+               }
+       }
+
+       my $dir = $git->repo_path() . "/remotes";
+       if (opendir(my $dh, $dir)) {
+               local $_;
+               while ($_ = readdir($dh)) {
+                       chomp;
+                       next if (! -f "$dir/$_" || ! -r _);
+                       add_remote_remotes(\%seen, "$dir/$_", $_);
+               }
+       }
+
+       return \%seen;
+}
+
+sub add_branch_config {
+       my ($hash, $name, $what, $value) = @_;
+       if ($what eq 'remote') {
+               if (exists $hash->{$name}{'REMOTE'}) {
+                       print STDERR "Warning: more than one branch.$name.remote\n";
+               }
+               $hash->{$name}{'REMOTE'} = $value;
+       }
+       elsif ($what eq 'merge') {
+               $hash->{$name}{'MERGE'} ||= [];
+               push @{$hash->{$name}{'MERGE'}}, $value;
+       }
+}
+
+sub list_branch {
+       my ($git) = @_;
+       my %seen = ();
+       my @branches = eval {
+               $git->command(qw(config --get-regexp), '^branch\.');
+       };
+       for (@branches) {
+               if (/^branch\.([^.]*)\.(\S*)\s+(.*)$/) {
+                       add_branch_config(\%seen, $1, $2, $3);
+               }
+       }
+
+       return \%seen;
+}
+
+my $remote = list_remote($git);
+my $branch = list_branch($git);
+
+sub update_ls_remote {
+       my ($harder, $info) = @_;
+
+       return if (($harder == 0) ||
+                  (($harder == 1) && exists $info->{'LS_REMOTE'}));
+
+       my @ref = map {
+               s|^[0-9a-f]{40}\s+refs/heads/||;
+               $_;
+       } $git->command(qw(ls-remote --heads), $info->{'URL'});
+       $info->{'LS_REMOTE'} = \@ref;
+}
+
+sub list_wildcard_mapping {
+       my ($forced, $ours, $ls) = @_;
+       my %refs;
+       for (@$ls) {
+               $refs{$_} = 01; # bit #0 to say "they have"
+       }
+       for ($git->command('for-each-ref', "refs/remotes/$ours")) {
+               chomp;
+               next unless (s|^[0-9a-f]{40}\s[a-z]+\srefs/remotes/$ours/||);
+               next if ($_ eq 'HEAD');
+               $refs{$_} ||= 0;
+               $refs{$_} |= 02; # bit #1 to say "we have"
+       }
+       my (@new, @stale, @tracked);
+       for (sort keys %refs) {
+               my $have = $refs{$_};
+               if ($have == 1) {
+                       push @new, $_;
+               }
+               elsif ($have == 2) {
+                       push @stale, $_;
+               }
+               elsif ($have == 3) {
+                       push @tracked, $_;
+               }
+       }
+       return \@new, \@stale, \@tracked;
+}
+
+sub list_mapping {
+       my ($name, $info) = @_;
+       my $fetch = $info->{'FETCH'};
+       my $ls = $info->{'LS_REMOTE'};
+       my (@new, @stale, @tracked);
+
+       for (@$fetch) {
+               next unless (/(\+)?([^:]+):(.*)/);
+               my ($forced, $theirs, $ours) = ($1, $2, $3);
+               if ($theirs eq 'refs/heads/*' &&
+                   $ours =~ /^refs\/remotes\/(.*)\/\*$/) {
+                       # wildcard mapping
+                       my ($w_new, $w_stale, $w_tracked)
+                               = list_wildcard_mapping($forced, $1, $ls);
+                       push @new, @$w_new;
+                       push @stale, @$w_stale;
+                       push @tracked, @$w_tracked;
+               }
+               elsif ($theirs =~ /\*/ || $ours =~ /\*/) {
+                       print STDERR "Warning: unrecognized mapping in remotes.$name.fetch: $_\n";
+               }
+               elsif ($theirs =~ s|^refs/heads/||) {
+                       if (!grep { $_ eq $theirs } @$ls) {
+                               push @stale, $theirs;
+                       }
+                       elsif ($ours ne '') {
+                               push @tracked, $theirs;
+                       }
+               }
+       }
+       return \@new, \@stale, \@tracked;
+}
+
+sub show_mapping {
+       my ($name, $info) = @_;
+       my ($new, $stale, $tracked) = list_mapping($name, $info);
+       if (@$new) {
+               print "  New remote branches (next fetch will store in remotes/$name)\n";
+               print "    @$new\n";
+       }
+       if (@$stale) {
+               print "  Stale tracking branches in remotes/$name (use 'git remote prune')\n";
+               print "    @$stale\n";
+       }
+       if (@$tracked) {
+               print "  Tracked remote branches\n";
+               print "    @$tracked\n";
+       }
+}
+
+sub prune_remote {
+       my ($name, $ls_remote) = @_;
+       if (!exists $remote->{$name}) {
+               print STDERR "No such remote $name\n";
+               return 1;
+       }
+       my $info = $remote->{$name};
+       update_ls_remote($ls_remote, $info);
+
+       my ($new, $stale, $tracked) = list_mapping($name, $info);
+       my $prefix = "refs/remotes/$name";
+       foreach my $to_prune (@$stale) {
+               my @v = $git->command(qw(rev-parse --verify), "$prefix/$to_prune");
+               $git->command(qw(update-ref -d), "$prefix/$to_prune", $v[0]);
+       }
+       return 0;
+}
+
+sub show_remote {
+       my ($name, $ls_remote) = @_;
+       if (!exists $remote->{$name}) {
+               print STDERR "No such remote $name\n";
+               return 1;
+       }
+       my $info = $remote->{$name};
+       update_ls_remote($ls_remote, $info);
+
+       print "* remote $name\n";
+       print "  URL: $info->{'URL'}\n";
+       for my $branchname (sort keys %$branch) {
+               next unless (defined $branch->{$branchname}{'REMOTE'} &&
+                            $branch->{$branchname}{'REMOTE'} eq $name);
+               my @merged = map {
+                       s|^refs/heads/||;
+                       $_;
+               } split(' ',"@{$branch->{$branchname}{'MERGE'}}");
+               next unless (@merged);
+               print "  Remote branch(es) merged with 'git pull' while on branch $branchname\n";
+               print "    @merged\n";
+       }
+       if ($info->{'LS_REMOTE'}) {
+               show_mapping($name, $info);
+       }
+       if ($info->{'PUSH'}) {
+               my @pushed = map {
+                       s|^refs/heads/||;
+                       s|^\+refs/heads/|+|;
+                       s|:refs/heads/|:|;
+                       $_;
+               } @{$info->{'PUSH'}};
+               print "  Local branch(es) pushed with 'git push'\n";
+               print "    @pushed\n";
+       }
+       return 0;
+}
+
+sub add_remote {
+       my ($name, $url, $opts) = @_;
+       if (exists $remote->{$name}) {
+               print STDERR "remote $name already exists.\n";
+               exit(1);
+       }
+       $git->command('config', "remote.$name.url", $url);
+       my $track = $opts->{'track'} || ["*"];
+
+       for (@$track) {
+               $git->command('config', '--add', "remote.$name.fetch",
+                               $opts->{'mirror'} ?
+                               "+refs/$_:refs/$_" :
+                               "+refs/heads/$_:refs/remotes/$name/$_");
+       }
+       if ($opts->{'fetch'}) {
+               $git->command('fetch', $name);
+       }
+       if (exists $opts->{'master'}) {
+               $git->command('symbolic-ref', "refs/remotes/$name/HEAD",
+                             "refs/remotes/$name/$opts->{'master'}");
+       }
+}
+
+sub update_remote {
+       my ($name) = @_;
+       my @remotes;
+
+        my $conf = $git->config("remotes." . $name);
+       if (defined($conf)) {
+               @remotes = split(' ', $conf);
+       } elsif ($name eq 'default') {
+               @remotes = ();
+               for (sort keys %$remote) {
+                       my $do_fetch = $git->config_bool("remote." . $_ .
+                                                   ".skipDefaultUpdate");
+                       unless ($do_fetch) {
+                               push @remotes, $_;
+                       }
+               }
+       } else {
+               print STDERR "Remote group $name does not exists.\n";
+               exit(1);
+       }
+       for (@remotes) {
+               print "Updating $_\n";
+               $git->command('fetch', "$_");
+       }
+}
+
+sub rm_remote {
+       my ($name) = @_;
+       if (!exists $remote->{$name}) {
+               print STDERR "No such remote $name\n";
+               return 1;
+       }
+
+       $git->command('config', '--remove-section', "remote.$name");
+
+       eval {
+           my @trackers = $git->command('config', '--get-regexp',
+                       'branch.*.remote', $name);
+               for (@trackers) {
+                       /^branch\.(.*)?\.remote/;
+                       $git->config('--unset', "branch.$1.remote");
+                       $git->config('--unset', "branch.$1.merge");
+               }
+       };
+
+       my @refs = $git->command('for-each-ref',
+               '--format=%(refname) %(objectname)', "refs/remotes/$name");
+       for (@refs) {
+               my ($ref, $object) = split;
+               $git->command(qw(update-ref -d), $ref, $object);
+       }
+       return 0;
+}
+
+sub add_usage {
+       print STDERR "Usage: git remote add [-f] [-t track]* [-m master] <name> <url>\n";
+       exit(1);
+}
+
+my $VERBOSE = 0;
+@ARGV = grep {
+       if ($_ eq '-v' or $_ eq '--verbose') {
+               $VERBOSE=1;
+               0
+       } else {
+               1
+       }
+} @ARGV;
+
+if (!@ARGV) {
+       for (sort keys %$remote) {
+               print "$_";
+               print "\t$remote->{$_}->{URL}" if $VERBOSE;
+               print "\n";
+       }
+}
+elsif ($ARGV[0] eq 'show') {
+       my $ls_remote = 1;
+       my $i;
+       for ($i = 1; $i < @ARGV; $i++) {
+               if ($ARGV[$i] eq '-n') {
+                       $ls_remote = 0;
+               }
+               else {
+                       last;
+               }
+       }
+       if ($i >= @ARGV) {
+               print STDERR "Usage: git remote show <remote>\n";
+               exit(1);
+       }
+       my $status = 0;
+       for (; $i < @ARGV; $i++) {
+               $status |= show_remote($ARGV[$i], $ls_remote);
+       }
+       exit($status);
+}
+elsif ($ARGV[0] eq 'update') {
+       if (@ARGV <= 1) {
+               update_remote("default");
+               exit(1);
+       }
+       for (my $i = 1; $i < @ARGV; $i++) {
+               update_remote($ARGV[$i]);
+       }
+}
+elsif ($ARGV[0] eq 'prune') {
+       my $ls_remote = 1;
+       my $i;
+       for ($i = 1; $i < @ARGV; $i++) {
+               if ($ARGV[$i] eq '-n') {
+                       $ls_remote = 0;
+               }
+               else {
+                       last;
+               }
+       }
+       if ($i >= @ARGV) {
+               print STDERR "Usage: git remote prune <remote>\n";
+               exit(1);
+       }
+       my $status = 0;
+       for (; $i < @ARGV; $i++) {
+               $status |= prune_remote($ARGV[$i], $ls_remote);
+       }
+        exit($status);
+}
+elsif ($ARGV[0] eq 'add') {
+       my %opts = ();
+       while (1 < @ARGV && $ARGV[1] =~ /^-/) {
+               my $opt = $ARGV[1];
+               shift @ARGV;
+               if ($opt eq '-f' || $opt eq '--fetch') {
+                       $opts{'fetch'} = 1;
+                       next;
+               }
+               if ($opt eq '-t' || $opt eq '--track') {
+                       if (@ARGV < 1) {
+                               add_usage();
+                       }
+                       $opts{'track'} ||= [];
+                       push @{$opts{'track'}}, $ARGV[1];
+                       shift @ARGV;
+                       next;
+               }
+               if ($opt eq '-m' || $opt eq '--master') {
+                       if ((@ARGV < 1) || exists $opts{'master'}) {
+                               add_usage();
+                       }
+                       $opts{'master'} = $ARGV[1];
+                       shift @ARGV;
+                       next;
+               }
+               if ($opt eq '--mirror') {
+                       $opts{'mirror'} = 1;
+                       next;
+               }
+               add_usage();
+       }
+       if (@ARGV != 3) {
+               add_usage();
+       }
+       add_remote($ARGV[1], $ARGV[2], \%opts);
+}
+elsif ($ARGV[0] eq 'rm') {
+       if (@ARGV <= 1) {
+               print STDERR "Usage: git remote rm <remote>\n";
+               exit(1);
+       }
+       exit(rm_remote($ARGV[1]));
+}
+else {
+       print STDERR "Usage: git remote\n";
+       print STDERR "       git remote add <name> <url>\n";
+       print STDERR "       git remote rm <name>\n";
+       print STDERR "       git remote show <name>\n";
+       print STDERR "       git remote prune <name>\n";
+       print STDERR "       git remote update [group]\n";
+       exit(1);
+}
index 4581b594d0a65d5bbe782b1db5d69f33189a0e50..52dbac34a40b2ca7bcbb7c2343a0487e5cb90ccf 100644 (file)
@@ -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))
index 73968e02b024f22068eb5500033f7a2f41d31e8f..a18235e6d053322a85805d1b07b631c6739deb93 100644 (file)
@@ -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
index 47f116f37ee1030ac0cab1f91feec04d673d94bd..95c5eec51ecc6ab6f142cef51eb4bd3b5842debb 100755 (executable)
@@ -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;
                }
        }
index 4e321742ab8f103d829f33ab4d6ab5430fa132ba..b19fb2d64e777102fed94d485f869ad6695bbb62 100644 (file)
@@ -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
index 238a2393ff7811675003e0e846f3d9409fb04e0c..3a58cd2c6b49b8734234c6fb5957c035df9787d4 100755 (executable)
@@ -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} \
index 00ecf21333c976d4c6002cd66a055803362f3523..92fac1bad402a0d7772af0b8817217555b61b6c7 100644 (file)
@@ -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 \
index 53d5a628165a29c592ab14c234a000a56d7c6a12..ab470d12648c1dd3560b411e70ea210cc4381e3e 100644 (file)
@@ -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 \
index 0c4051b3752b4f88e50e1f4f22189d826234668a..56443b042c62bc10765aaf6484ad1077a843cb30 100644 (file)
@@ -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 \
index 5597188d803a1c8217011412a39c14fbbeaf0b3a..c112464ec367a2db707a3f28ff6c588aefe7985f 100644 (file)
@@ -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
index 8c27678e3a45c011cfc6a21f5d53b0d87c66b4a6..75650157e551e34dab650d89f3fa6d25afc91d6a 100644 (file)
@@ -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 \
index ea80df009226d0ff14e8fe423abfe3d7a94ef56b..9270512582034a6629c4ff15abb1f30889f76903 100644 (file)
@@ -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]
index 621c9479b2b90c13119985df889af70b206e3cbd..f8697216f71a56fcda47056fe5b5c287839ef7f0 100644 (file)
@@ -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 "ä»\8eæ\9c¬æ¬¡æ\8f\90交移é\99¤"
+msgstr "ç¼\93å­\98为æ\8f\90交"
 
-#: git-gui.sh:1529 git-gui.sh:1534
-#, fuzzy
+#: git-gui.sh:1564 git-gui.sh:1569
 msgid "Portions staged for commit"
-msgstr "ä»\8eæ\9c¬æ¬¡æ\8f\90交移é\99¤"
+msgstr "é\83¨å\88\86ç¼\93å­\98为æ\8f\90交"
 
-#: 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 "æµ\8fè§\88å½\93å\89\8då\88\86æ\94¯æ\96\87ä»¶"
+msgstr "æµ\8fè§\88å\88\86æ\94¯ä¸\8aç\9a\84æ\96\87ä»¶..."
 
-#: 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 "æ\94¹名..."
+msgstr "æ\9b´名..."
 
-#: 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 "ä»\8eæ\9c¬æ¬¡æ\8f\90交移é\99¤"
+msgstr "ç¼\93å­\98为æ\8f\90交"
 
-#: 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 "æ\81¢å¤\8d修改"
+msgstr "æ\92¤é\94\80修改"
 
-#: 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 "å°\86被æ\8f\90交ç\9a\84ä¿®æ\94¹"
+msgstr "å·²ç¼\93å­\98ç\9a\84æ\94¹å\8a¨ (å°\86被æ\8f\90交)"
 
-#: 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 "æ\98¾ç¤ºæ\9b´å¤\9adiff上下文"
+msgstr "æ\98¾ç¤ºæ\9b´å°\91上下文"
 
-#: git-gui.sh:2551
+#: git-gui.sh:2588
 msgid "Show More Context"
-msgstr "æ\98¾ç¤ºæ\9b´å°\91diff上下文"
+msgstr "æ\98¾ç¤ºæ\9b´å¤\9a上下文"
 
-#: git-gui.sh:2565
-#, fuzzy
+#: git-gui.sh:2602
 msgid "Unstage Hunk From Commit"
-msgstr "ä»\8eæ\9c¬æ¬¡æ\8f\90交移é\99¤"
+msgstr "ä»\8eæ\8f\90交中æ\92¤é\99¤ä¿®æ\94¹å\9d\97"
 
-#: git-gui.sh:2567
-#, fuzzy
+#: git-gui.sh:2604
 msgid "Stage Hunk For Commit"
-msgstr "ä»\8eæ\9c¬æ¬¡æ\8f\90交移é\99¤"
+msgstr "ç¼\93å­\98ä¿®æ\94¹å\9d\97为æ\8f\90交"
 
-#: 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 "å½\93å\89\8då\88\86æ\94¯:"
+msgstr "å\88\9b建å\88\86æ\94¯"
 
 #: 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 "å½\93å\89\8då\88\86æ\94¯:"
+msgstr "å\88 é\99¤å\88\86æ\94¯"
 
 #: 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 "æ\94¹名..."
+msgstr "æ\9b´名..."
 
 #: 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 "æµ\8fè§\88å½\93å\89\8då\88\86æ\94¯æ\96\87ä»¶"
+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 "å\8e\8b缩æ\95°æ\8d®åº\93"
+msgstr "å¤\8då\88¶ 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 "ä»\8eæ\9c¬æ¬¡æ\8f\90交移é\99¤"
+msgstr "ä»\8eæ\8f\90交ç¼\93å­\98中å\88 é\99¤ %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 "æ\81¢å¤\8dä¿®æ\94¹"
+msgstr "æ\92¤é\94\80æ\96\87ä»¶ %s ä¸­ç\9a\84æ\94¹å\8a¨?"
 
-#: 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 "ç\89\88æ\9c¬æ \91"
+msgstr "ç\9b®æ \87ç\89\88æ\9c¬åº\93"
 
 #: 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 "包含标签"
index 233e5eae1d337bff40d0adba4bbb117bbd4b5dee..7cd8f7134e696312d243d73acebb6ecfe07d1e13 100755 (executable)
@@ -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
index 452c5e7e01e168ec4fcdc5bb87caac3f326f95ea..ff66af3ba8348ff86b25c91a1875b93f885a048c 100755 (executable)
@@ -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-remote.perl b/git-remote.perl
deleted file mode 100755 (executable)
index b30ed73..0000000
+++ /dev/null
@@ -1,477 +0,0 @@
-#!/usr/bin/perl -w
-
-use strict;
-use Git;
-my $git = Git->repository();
-
-sub add_remote_config {
-       my ($hash, $name, $what, $value) = @_;
-       if ($what eq 'url') {
-               # Having more than one is Ok -- it is used for push.
-               if (! exists $hash->{'URL'}) {
-                       $hash->{$name}{'URL'} = $value;
-               }
-       }
-       elsif ($what eq 'fetch') {
-               $hash->{$name}{'FETCH'} ||= [];
-               push @{$hash->{$name}{'FETCH'}}, $value;
-       }
-       elsif ($what eq 'push') {
-               $hash->{$name}{'PUSH'} ||= [];
-               push @{$hash->{$name}{'PUSH'}}, $value;
-       }
-       if (!exists $hash->{$name}{'SOURCE'}) {
-               $hash->{$name}{'SOURCE'} = 'config';
-       }
-}
-
-sub add_remote_remotes {
-       my ($hash, $file, $name) = @_;
-
-       if (exists $hash->{$name}) {
-               $hash->{$name}{'WARNING'} = 'ignored due to config';
-               return;
-       }
-
-       my $fh;
-       if (!open($fh, '<', $file)) {
-               print STDERR "Warning: cannot open $file\n";
-               return;
-       }
-       my $it = { 'SOURCE' => 'remotes' };
-       $hash->{$name} = $it;
-       while (<$fh>) {
-               chomp;
-               if (/^URL:\s*(.*)$/) {
-                       # Having more than one is Ok -- it is used for push.
-                       if (! exists $it->{'URL'}) {
-                               $it->{'URL'} = $1;
-                       }
-               }
-               elsif (/^Push:\s*(.*)$/) {
-                       $it->{'PUSH'} ||= [];
-                       push @{$it->{'PUSH'}}, $1;
-               }
-               elsif (/^Pull:\s*(.*)$/) {
-                       $it->{'FETCH'} ||= [];
-                       push @{$it->{'FETCH'}}, $1;
-               }
-               elsif (/^\#/) {
-                       ; # ignore
-               }
-               else {
-                       print STDERR "Warning: funny line in $file: $_\n";
-               }
-       }
-       close($fh);
-}
-
-sub list_remote {
-       my ($git) = @_;
-       my %seen = ();
-       my @remotes = eval {
-               $git->command(qw(config --get-regexp), '^remote\.');
-       };
-       for (@remotes) {
-               if (/^remote\.(\S+?)\.([^.\s]+)\s+(.*)$/) {
-                       add_remote_config(\%seen, $1, $2, $3);
-               }
-       }
-
-       my $dir = $git->repo_path() . "/remotes";
-       if (opendir(my $dh, $dir)) {
-               local $_;
-               while ($_ = readdir($dh)) {
-                       chomp;
-                       next if (! -f "$dir/$_" || ! -r _);
-                       add_remote_remotes(\%seen, "$dir/$_", $_);
-               }
-       }
-
-       return \%seen;
-}
-
-sub add_branch_config {
-       my ($hash, $name, $what, $value) = @_;
-       if ($what eq 'remote') {
-               if (exists $hash->{$name}{'REMOTE'}) {
-                       print STDERR "Warning: more than one branch.$name.remote\n";
-               }
-               $hash->{$name}{'REMOTE'} = $value;
-       }
-       elsif ($what eq 'merge') {
-               $hash->{$name}{'MERGE'} ||= [];
-               push @{$hash->{$name}{'MERGE'}}, $value;
-       }
-}
-
-sub list_branch {
-       my ($git) = @_;
-       my %seen = ();
-       my @branches = eval {
-               $git->command(qw(config --get-regexp), '^branch\.');
-       };
-       for (@branches) {
-               if (/^branch\.([^.]*)\.(\S*)\s+(.*)$/) {
-                       add_branch_config(\%seen, $1, $2, $3);
-               }
-       }
-
-       return \%seen;
-}
-
-my $remote = list_remote($git);
-my $branch = list_branch($git);
-
-sub update_ls_remote {
-       my ($harder, $info) = @_;
-
-       return if (($harder == 0) ||
-                  (($harder == 1) && exists $info->{'LS_REMOTE'}));
-
-       my @ref = map {
-               s|^[0-9a-f]{40}\s+refs/heads/||;
-               $_;
-       } $git->command(qw(ls-remote --heads), $info->{'URL'});
-       $info->{'LS_REMOTE'} = \@ref;
-}
-
-sub list_wildcard_mapping {
-       my ($forced, $ours, $ls) = @_;
-       my %refs;
-       for (@$ls) {
-               $refs{$_} = 01; # bit #0 to say "they have"
-       }
-       for ($git->command('for-each-ref', "refs/remotes/$ours")) {
-               chomp;
-               next unless (s|^[0-9a-f]{40}\s[a-z]+\srefs/remotes/$ours/||);
-               next if ($_ eq 'HEAD');
-               $refs{$_} ||= 0;
-               $refs{$_} |= 02; # bit #1 to say "we have"
-       }
-       my (@new, @stale, @tracked);
-       for (sort keys %refs) {
-               my $have = $refs{$_};
-               if ($have == 1) {
-                       push @new, $_;
-               }
-               elsif ($have == 2) {
-                       push @stale, $_;
-               }
-               elsif ($have == 3) {
-                       push @tracked, $_;
-               }
-       }
-       return \@new, \@stale, \@tracked;
-}
-
-sub list_mapping {
-       my ($name, $info) = @_;
-       my $fetch = $info->{'FETCH'};
-       my $ls = $info->{'LS_REMOTE'};
-       my (@new, @stale, @tracked);
-
-       for (@$fetch) {
-               next unless (/(\+)?([^:]+):(.*)/);
-               my ($forced, $theirs, $ours) = ($1, $2, $3);
-               if ($theirs eq 'refs/heads/*' &&
-                   $ours =~ /^refs\/remotes\/(.*)\/\*$/) {
-                       # wildcard mapping
-                       my ($w_new, $w_stale, $w_tracked)
-                               = list_wildcard_mapping($forced, $1, $ls);
-                       push @new, @$w_new;
-                       push @stale, @$w_stale;
-                       push @tracked, @$w_tracked;
-               }
-               elsif ($theirs =~ /\*/ || $ours =~ /\*/) {
-                       print STDERR "Warning: unrecognized mapping in remotes.$name.fetch: $_\n";
-               }
-               elsif ($theirs =~ s|^refs/heads/||) {
-                       if (!grep { $_ eq $theirs } @$ls) {
-                               push @stale, $theirs;
-                       }
-                       elsif ($ours ne '') {
-                               push @tracked, $theirs;
-                       }
-               }
-       }
-       return \@new, \@stale, \@tracked;
-}
-
-sub show_mapping {
-       my ($name, $info) = @_;
-       my ($new, $stale, $tracked) = list_mapping($name, $info);
-       if (@$new) {
-               print "  New remote branches (next fetch will store in remotes/$name)\n";
-               print "    @$new\n";
-       }
-       if (@$stale) {
-               print "  Stale tracking branches in remotes/$name (use 'git remote prune')\n";
-               print "    @$stale\n";
-       }
-       if (@$tracked) {
-               print "  Tracked remote branches\n";
-               print "    @$tracked\n";
-       }
-}
-
-sub prune_remote {
-       my ($name, $ls_remote) = @_;
-       if (!exists $remote->{$name}) {
-               print STDERR "No such remote $name\n";
-               return 1;
-       }
-       my $info = $remote->{$name};
-       update_ls_remote($ls_remote, $info);
-
-       my ($new, $stale, $tracked) = list_mapping($name, $info);
-       my $prefix = "refs/remotes/$name";
-       foreach my $to_prune (@$stale) {
-               my @v = $git->command(qw(rev-parse --verify), "$prefix/$to_prune");
-               $git->command(qw(update-ref -d), "$prefix/$to_prune", $v[0]);
-       }
-       return 0;
-}
-
-sub show_remote {
-       my ($name, $ls_remote) = @_;
-       if (!exists $remote->{$name}) {
-               print STDERR "No such remote $name\n";
-               return 1;
-       }
-       my $info = $remote->{$name};
-       update_ls_remote($ls_remote, $info);
-
-       print "* remote $name\n";
-       print "  URL: $info->{'URL'}\n";
-       for my $branchname (sort keys %$branch) {
-               next unless (defined $branch->{$branchname}{'REMOTE'} &&
-                            $branch->{$branchname}{'REMOTE'} eq $name);
-               my @merged = map {
-                       s|^refs/heads/||;
-                       $_;
-               } split(' ',"@{$branch->{$branchname}{'MERGE'}}");
-               next unless (@merged);
-               print "  Remote branch(es) merged with 'git pull' while on branch $branchname\n";
-               print "    @merged\n";
-       }
-       if ($info->{'LS_REMOTE'}) {
-               show_mapping($name, $info);
-       }
-       if ($info->{'PUSH'}) {
-               my @pushed = map {
-                       s|^refs/heads/||;
-                       s|^\+refs/heads/|+|;
-                       s|:refs/heads/|:|;
-                       $_;
-               } @{$info->{'PUSH'}};
-               print "  Local branch(es) pushed with 'git push'\n";
-               print "    @pushed\n";
-       }
-       return 0;
-}
-
-sub add_remote {
-       my ($name, $url, $opts) = @_;
-       if (exists $remote->{$name}) {
-               print STDERR "remote $name already exists.\n";
-               exit(1);
-       }
-       $git->command('config', "remote.$name.url", $url);
-       my $track = $opts->{'track'} || ["*"];
-
-       for (@$track) {
-               $git->command('config', '--add', "remote.$name.fetch",
-                               $opts->{'mirror'} ?
-                               "+refs/$_:refs/$_" :
-                               "+refs/heads/$_:refs/remotes/$name/$_");
-       }
-       if ($opts->{'fetch'}) {
-               $git->command('fetch', $name);
-       }
-       if (exists $opts->{'master'}) {
-               $git->command('symbolic-ref', "refs/remotes/$name/HEAD",
-                             "refs/remotes/$name/$opts->{'master'}");
-       }
-}
-
-sub update_remote {
-       my ($name) = @_;
-       my @remotes;
-
-        my $conf = $git->config("remotes." . $name);
-       if (defined($conf)) {
-               @remotes = split(' ', $conf);
-       } elsif ($name eq 'default') {
-               @remotes = ();
-               for (sort keys %$remote) {
-                       my $do_fetch = $git->config_bool("remote." . $_ .
-                                                   ".skipDefaultUpdate");
-                       unless ($do_fetch) {
-                               push @remotes, $_;
-                       }
-               }
-       } else {
-               print STDERR "Remote group $name does not exists.\n";
-               exit(1);
-       }
-       for (@remotes) {
-               print "Updating $_\n";
-               $git->command('fetch', "$_");
-       }
-}
-
-sub rm_remote {
-       my ($name) = @_;
-       if (!exists $remote->{$name}) {
-               print STDERR "No such remote $name\n";
-               return 1;
-       }
-
-       $git->command('config', '--remove-section', "remote.$name");
-
-       eval {
-           my @trackers = $git->command('config', '--get-regexp',
-                       'branch.*.remote', $name);
-               for (@trackers) {
-                       /^branch\.(.*)?\.remote/;
-                       $git->config('--unset', "branch.$1.remote");
-                       $git->config('--unset', "branch.$1.merge");
-               }
-       };
-
-       my @refs = $git->command('for-each-ref',
-               '--format=%(refname) %(objectname)', "refs/remotes/$name");
-       for (@refs) {
-               my ($ref, $object) = split;
-               $git->command(qw(update-ref -d), $ref, $object);
-       }
-       return 0;
-}
-
-sub add_usage {
-       print STDERR "Usage: git remote add [-f] [-t track]* [-m master] <name> <url>\n";
-       exit(1);
-}
-
-my $VERBOSE = 0;
-@ARGV = grep {
-       if ($_ eq '-v' or $_ eq '--verbose') {
-               $VERBOSE=1;
-               0
-       } else {
-               1
-       }
-} @ARGV;
-
-if (!@ARGV) {
-       for (sort keys %$remote) {
-               print "$_";
-               print "\t$remote->{$_}->{URL}" if $VERBOSE;
-               print "\n";
-       }
-}
-elsif ($ARGV[0] eq 'show') {
-       my $ls_remote = 1;
-       my $i;
-       for ($i = 1; $i < @ARGV; $i++) {
-               if ($ARGV[$i] eq '-n') {
-                       $ls_remote = 0;
-               }
-               else {
-                       last;
-               }
-       }
-       if ($i >= @ARGV) {
-               print STDERR "Usage: git remote show <remote>\n";
-               exit(1);
-       }
-       my $status = 0;
-       for (; $i < @ARGV; $i++) {
-               $status |= show_remote($ARGV[$i], $ls_remote);
-       }
-       exit($status);
-}
-elsif ($ARGV[0] eq 'update') {
-       if (@ARGV <= 1) {
-               update_remote("default");
-               exit(1);
-       }
-       for (my $i = 1; $i < @ARGV; $i++) {
-               update_remote($ARGV[$i]);
-       }
-}
-elsif ($ARGV[0] eq 'prune') {
-       my $ls_remote = 1;
-       my $i;
-       for ($i = 1; $i < @ARGV; $i++) {
-               if ($ARGV[$i] eq '-n') {
-                       $ls_remote = 0;
-               }
-               else {
-                       last;
-               }
-       }
-       if ($i >= @ARGV) {
-               print STDERR "Usage: git remote prune <remote>\n";
-               exit(1);
-       }
-       my $status = 0;
-       for (; $i < @ARGV; $i++) {
-               $status |= prune_remote($ARGV[$i], $ls_remote);
-       }
-        exit($status);
-}
-elsif ($ARGV[0] eq 'add') {
-       my %opts = ();
-       while (1 < @ARGV && $ARGV[1] =~ /^-/) {
-               my $opt = $ARGV[1];
-               shift @ARGV;
-               if ($opt eq '-f' || $opt eq '--fetch') {
-                       $opts{'fetch'} = 1;
-                       next;
-               }
-               if ($opt eq '-t' || $opt eq '--track') {
-                       if (@ARGV < 1) {
-                               add_usage();
-                       }
-                       $opts{'track'} ||= [];
-                       push @{$opts{'track'}}, $ARGV[1];
-                       shift @ARGV;
-                       next;
-               }
-               if ($opt eq '-m' || $opt eq '--master') {
-                       if ((@ARGV < 1) || exists $opts{'master'}) {
-                               add_usage();
-                       }
-                       $opts{'master'} = $ARGV[1];
-                       shift @ARGV;
-                       next;
-               }
-               if ($opt eq '--mirror') {
-                       $opts{'mirror'} = 1;
-                       next;
-               }
-               add_usage();
-       }
-       if (@ARGV != 3) {
-               add_usage();
-       }
-       add_remote($ARGV[1], $ARGV[2], \%opts);
-}
-elsif ($ARGV[0] eq 'rm') {
-       if (@ARGV <= 1) {
-               print STDERR "Usage: git remote rm <remote>\n";
-               exit(1);
-       }
-       exit(rm_remote($ARGV[1]));
-}
-else {
-       print STDERR "Usage: git remote\n";
-       print STDERR "       git remote add <name> <url>\n";
-       print STDERR "       git remote rm <name>\n";
-       print STDERR "       git remote show <name>\n";
-       print STDERR "       git remote prune <name>\n";
-       print STDERR "       git remote update [group]\n";
-       exit(1);
-}
index 1195569529401fd1c62414c8683a6b577721c1c7..d8b38c9a47c0b77dd55781a66e42e4dec26fd4b7 100755 (executable)
@@ -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;
diff --git a/git.c b/git.c
index 9cca81a60e6d4f93cf3132b76fd8f147a6a5d98f..1e3eb1065fe3fe52361c2feaeadfab85780877ee 100644 (file)
--- a/git.c
+++ b/git.c
@@ -334,6 +334,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "push", cmd_push, RUN_SETUP },
                { "read-tree", cmd_read_tree, RUN_SETUP },
                { "reflog", cmd_reflog, RUN_SETUP },
+               { "remote", cmd_remote, RUN_SETUP },
                { "repo-config", cmd_config },
                { "rerere", cmd_rerere, RUN_SETUP },
                { "reset", cmd_reset, RUN_SETUP },
diff --git a/hash.c b/hash.c
index d9ec82fa663804210e8ef200f2f300e914662f22..1cd4c9d5c0945994b84bb25edd6e4685cf76b5c5 100644 (file)
--- a/hash.c
+++ b/hash.c
@@ -9,7 +9,7 @@
  * the existing entry, or the empty slot if none existed. The caller
  * can then look at the (*ptr) to see whether it existed or not.
  */
-static struct hash_table_entry *lookup_hash_entry(unsigned int hash, struct hash_table *table)
+static struct hash_table_entry *lookup_hash_entry(unsigned int hash, const struct hash_table *table)
 {
        unsigned int size = table->size, nr = hash % size;
        struct hash_table_entry *array = table->array;
@@ -66,7 +66,7 @@ static void grow_hash_table(struct hash_table *table)
        free(old_array);
 }
 
-void *lookup_hash(unsigned int hash, struct hash_table *table)
+void *lookup_hash(unsigned int hash, const struct hash_table *table)
 {
        if (!table->array)
                return NULL;
@@ -81,7 +81,7 @@ void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table)
        return insert_hash_entry(hash, ptr, table);
 }
 
-int for_each_hash(struct hash_table *table, int (*fn)(void *))
+int for_each_hash(const struct hash_table *table, int (*fn)(void *))
 {
        int sum = 0;
        unsigned int i;
diff --git a/hash.h b/hash.h
index a8b0fbb5b502669f0e6f5db55e4573b8277c04d2..69e33a47b9861df9ac12c354eae180b4f8fea857 100644 (file)
--- a/hash.h
+++ b/hash.h
@@ -28,9 +28,9 @@ struct hash_table {
        struct hash_table_entry *array;
 };
 
-extern void *lookup_hash(unsigned int hash, struct hash_table *table);
+extern void *lookup_hash(unsigned int hash, const struct hash_table *table);
 extern void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table);
-extern int for_each_hash(struct hash_table *table, int (*fn)(void *));
+extern int for_each_hash(const struct hash_table *table, int (*fn)(void *));
 extern void free_hash(struct hash_table *table);
 
 static inline void init_hash(struct hash_table *table)
diff --git a/ll-merge.c b/ll-merge.c
new file mode 100644 (file)
index 0000000..5ae7433
--- /dev/null
@@ -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 (file)
index 0000000..5388422
--- /dev/null
@@ -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
index e08324686cc090fa9bd94d7f069b025454c7acdf..02fc10f7e622ba1c53065e7cf4563ff10af0c41f 100644 (file)
@@ -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)
index b32c9ea66c7ae1e83564cd25d63d5cfd6c1589b5..8e64316fe002fe90c58de61e4d6558ee4ad87157 100644 (file)
@@ -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;
                }
index dc0807834f9135b82c289b429a8d2ea13ee53be8..1af62b0485f2760607b7d3bc3e0ebdca09a53ea4 100644 (file)
@@ -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 {
index 3d83b7ba9e8c934db8d8ecc9d545cab25be89837..92e5cf20fe6e0891eb6cc8f230a0fcd238f2b9f8 100644 (file)
@@ -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;
+}
+
index 5931e2cc0ca23bd8b7f7f90d471cf3b8e95df456..ca2cbbaa4dccbf3359155e429231f35f55c5f7e0 100644 (file)
@@ -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 */
index 657f0c5894c65831b80ceee54d161d0beac1d733..a92b25b59bf0e096942bca126542a1ea411b525b 100644 (file)
@@ -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;
index 7e1937286b1cabec84865c7e07e2635feb8bbc70..f3f7375eea6d8c09e4ab6f561be22b08e545efcb 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -357,7 +357,8 @@ static int handle_config(const char *key, const char *value)
                        remote->fetch_tags = -1;
        } else if (!strcmp(subkey, ".proxy")) {
                remote->http_proxy = xstrdup(value);
-       }
+       } else if (!strcmp(subkey, ".skipdefaultupdate"))
+               remote->skip_default_update = 1;
        return 0;
 }
 
index 0f6033fb258c5a44971c7479e1dfe938393ce398..f1dedf15f6ffe607c4ab123286c9c15fa5522790 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -25,6 +25,7 @@ struct remote {
         * 2 to always fetch tags
         */
        int fetch_tags;
+       int skip_default_update;
 
        const char *receivepack;
        const char *uploadpack;
index 8358ba2069746943fcf45d35222d95fab7a8231d..8b6c76f68ed1433caddfa8aab6132db667197433 100644 (file)
@@ -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;
 }
index cb860296edfab2d117fc8b62505df00a51baed02..8fc39d77cec6168dae930beef785597dace24aa3 100755 (executable)
@@ -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
 
index f1b12167b8b5d03634e6f1803b1388b07884bd13..8c4556408ecc81985acd7b8237921330c326e787 100755 (executable)
@@ -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
index 24476bede5ce8f8720bf3d9eba7f7a944182dbf4..73f830db2374e751fb46e25b345e860979b9dd05 100755 (executable)
@@ -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
index 6560af756e7b4df0c7777e818edc86854f74b973..47090c4cf528016bf864116232baf29aeb1e4bfe 100644 (file)
@@ -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
index 4fc62f550cbced0f1e257fa5057e43f4c54ed5dd..2822a651b5c434bb9243a6f8f9069a8fee1ad590 100755 (executable)
@@ -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 (executable)
index 0000000..5bb6b93
--- /dev/null
@@ -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
index c1cec553060a2fd6a4d27191a866b3bf53ba3335..6a74b3acfda6774d7996cf5787e9858654357328 100755 (executable)
@@ -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
index 142205ddc3e33fb8024171daf4c6b1bee1dba476..02e2aed7737207225f1b96eed774a1b75dd6d8d9 100644 (file)
@@ -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)
index db0fbdc701f1ef63cdc1a8b7d5c5e72322f91426..42110a465f9a8c91d1bc643dfae7a9b9c32e3719 100644 (file)
@@ -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
index 3e448d8974eb6d738fec2c35cc5a9ffbc8764411..91649f31d6e73cac256a272b4e5eba0dc871d8ce 100644 (file)
@@ -1,3 +1,4 @@
+#define NO_THE_INDEX_COMPATIBILITY_MACROS
 #include "cache.h"
 #include "dir.h"
 #include "tree.h"
 #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);
 }
index a2df544d040adc21f7d854ad50c53e61cf74c9ae..50453ed20f755fea2e7138d7f01300b318f28dce 100644 (file)
@@ -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
index bba236428adb0b34421b9f1b5a3a1728911ee406..61dc5c547019776b971dc89d009f628bbac134fd 100644 (file)
@@ -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;