summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/config.txt6
-rw-r--r--Documentation/git-add.txt18
-rw-r--r--Documentation/git-gc.txt17
-rw-r--r--Documentation/git-pull.txt2
-rw-r--r--Documentation/git-rebase.txt3
-rw-r--r--Makefile7
-rw-r--r--builtin-checkout.c9
-rw-r--r--builtin-commit.c2
-rw-r--r--builtin-fetch.c2
-rw-r--r--builtin-gc.c17
-rw-r--r--builtin-merge-file.c3
-rw-r--r--builtin-merge-recursive.c394
-rw-r--r--builtin-pack-objects.c26
-rw-r--r--builtin-read-tree.c32
-rw-r--r--builtin-remote.c605
-rw-r--r--builtin-tag.c6
-rw-r--r--builtin.h1
-rw-r--r--cache.h14
-rwxr-xr-xcontrib/completion/git-completion.bash192
-rwxr-xr-xcontrib/examples/git-remote.perl (renamed from git-remote.perl)0
-rw-r--r--diff-lib.c55
-rw-r--r--git-compat-util.h1
-rwxr-xr-xgit-cvsimport.perl2
-rw-r--r--git-gui/Makefile7
-rwxr-xr-xgit-gui/git-gui.sh22
-rw-r--r--git-gui/lib/blame.tcl25
-rw-r--r--git-gui/lib/browser.tcl3
-rw-r--r--git-gui/lib/choose_font.tcl2
-rw-r--r--git-gui/lib/console.tcl10
-rw-r--r--git-gui/lib/error.tcl4
-rw-r--r--git-gui/lib/option.tcl1
-rw-r--r--git-gui/po/zh_cn.po1223
-rwxr-xr-xgit-quiltimport.sh22
-rwxr-xr-xgit-rebase.sh3
-rwxr-xr-xgit-svn.perl3
-rw-r--r--git.c1
-rw-r--r--hash.c6
-rw-r--r--hash.h4
-rw-r--r--ll-merge.c379
-rw-r--r--ll-merge.h15
-rw-r--r--merge-tree.c58
-rw-r--r--parse-options.c2
-rw-r--r--parse-options.h1
-rw-r--r--path-list.c30
-rw-r--r--path-list.h8
-rw-r--r--read-cache.c47
-rw-r--r--remote.c3
-rw-r--r--remote.h1
-rw-r--r--sha1_name.c60
-rwxr-xr-xt/t0021-conversion.sh4
-rwxr-xr-xt/t1005-read-tree-reset.sh2
-rwxr-xr-xt/t1410-reflog.sh18
-rw-r--r--t/t5304-prune.sh49
-rwxr-xr-xt/t5505-remote.sh143
-rwxr-xr-xt/t6031-merge-recursive.sh49
-rwxr-xr-xt/t7005-editor.sh27
-rw-r--r--tree-walk.c62
-rw-r--r--tree-walk.h23
-rw-r--r--unpack-trees.c636
-rw-r--r--unpack-trees.h17
-rw-r--r--xdiff-interface.c4
61 files changed, 2852 insertions, 1536 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt
index c5e094a..bfad0e3 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -590,6 +590,10 @@ gc.packrefs::
at some stage, and setting this to `false` will continue to
prevent `git pack-refs` from being run from `git gc`.
+gc.pruneexpire::
+ When `git gc` is run, it will call `prune --expire 2.weeks.ago`.
+ Override the grace period with this config variable.
+
gc.reflogexpire::
`git reflog expire` removes reflog entries older than
this time; defaults to 90 days.
@@ -860,7 +864,7 @@ pack.indexVersion::
whenever the corresponding pack is larger than 2 GB. Otherwise
the default is 1.
-pack.packSizeLimit:
+pack.packSizeLimit::
The default maximum size of a pack. This setting only affects
packing to a file, i.e. the git:// protocol is unaffected. It
can be overridden by the `\--max-pack-size` option of
diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt
index 4779909..c751a17 100644
--- a/Documentation/git-add.txt
+++ b/Documentation/git-add.txt
@@ -207,16 +207,14 @@ patch::
and the working tree file and asks you if you want to stage
the change of each hunk. You can say:
- y - add the change from that hunk to index
- n - do not add the change from that hunk to index
- a - add the change from that hunk and all the rest to index
- d - do not the change from that hunk nor any of the rest to index
- j - do not decide on this hunk now, and view the next
- undecided hunk
- J - do not decide on this hunk now, and view the next hunk
- k - do not decide on this hunk now, and view the previous
- undecided hunk
- K - do not decide on this hunk now, and view the previous hunk
+ y - stage this hunk
+ n - do not stage this hunk
+ a - stage this and all the remaining hunks in the file
+ d - do not stage this hunk nor any of the remaining hunks in the file
+ j - leave this hunk undecided, see next undecided hunk
+ J - leave this hunk undecided, see next hunk
+ k - leave this hunk undecided, see previous undecided hunk
+ K - leave this hunk undecided, see previous hunk
s - split the current hunk into smaller hunks
? - print help
+
diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt
index 2e7be91..229a7c9 100644
--- a/Documentation/git-gc.txt
+++ b/Documentation/git-gc.txt
@@ -8,7 +8,7 @@ git-gc - Cleanup unnecessary files and optimize the local repository
SYNOPSIS
--------
-'git-gc' [--prune] [--aggressive] [--auto] [--quiet]
+'git-gc' [--aggressive] [--auto] [--quiet]
DESCRIPTION
-----------
@@ -25,17 +25,6 @@ operating performance. Some git commands may automatically run
OPTIONS
-------
---prune::
- Usually `git-gc` packs refs, expires old reflog entries,
- packs loose objects,
- and removes old 'rerere' records. Removal
- of unreferenced loose objects is an unsafe operation
- while other git operations are in progress, so it is not
- done by default. Pass this option if you want it, and only
- when you know nobody else is creating new objects in the
- repository at the same time (e.g. never use this option
- in a cron script).
-
--aggressive::
Usually 'git-gc' runs very quickly while providing good disk
space utilization and performance. This option will cause
@@ -104,6 +93,10 @@ the value, the more time is spent optimizing the delta compression. See
the documentation for the --window' option in linkgit:git-repack[1] for
more details. This defaults to 10.
+The optional configuration variable 'gc.pruneExpire' controls how old
+the unreferenced loose objects have to be before they are pruned. The
+default is "2 weeks ago".
+
See Also
--------
linkgit:git-prune[1]
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
index 7378943..3405ca0 100644
--- a/Documentation/git-pull.txt
+++ b/Documentation/git-pull.txt
@@ -21,6 +21,8 @@ Note that you can use `.` (current directory) as the
<repository> to pull from the local repository -- this is useful
when merging local branches into the current branch.
+Also note that options meant for `git-pull` itself and underlying
+`git-merge` must be given before the options meant for `git-fetch`.
OPTIONS
-------
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 4b10304..e0412e0 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -262,8 +262,7 @@ hook if one exists. You can use this hook to do sanity checks and
reject the rebase if it isn't appropriate. Please see the template
pre-rebase hook script for an example.
-You must be in the top directory of your project to start (or continue)
-a rebase. Upon completion, <branch> will be the current branch.
+Upon completion, <branch> will be the current branch.
INTERACTIVE MODE
----------------
diff --git a/Makefile b/Makefile
index 7fbb815..e3eaa6a 100644
--- 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 \
diff --git a/builtin-checkout.c b/builtin-checkout.c
index 6b08016..7deb504 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -152,6 +152,7 @@ static int reset_to_new(struct tree *tree, int quiet)
{
struct unpack_trees_options opts;
struct tree_desc tree_desc;
+
memset(&opts, 0, sizeof(opts));
opts.head_idx = -1;
opts.update = 1;
@@ -159,6 +160,8 @@ static int reset_to_new(struct tree *tree, int quiet)
opts.merge = 1;
opts.fn = oneway_merge;
opts.verbose_update = !quiet;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
parse_tree(tree);
init_tree_desc(&tree_desc, tree->buffer, tree->size);
if (unpack_trees(1, &tree_desc, &opts))
@@ -170,6 +173,7 @@ static void reset_clean_to_new(struct tree *tree, int quiet)
{
struct unpack_trees_options opts;
struct tree_desc tree_desc;
+
memset(&opts, 0, sizeof(opts));
opts.head_idx = -1;
opts.skip_unmerged = 1;
@@ -177,6 +181,8 @@ static void reset_clean_to_new(struct tree *tree, int quiet)
opts.merge = 1;
opts.fn = oneway_merge;
opts.verbose_update = !quiet;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
parse_tree(tree);
init_tree_desc(&tree_desc, tree->buffer, tree->size);
if (unpack_trees(1, &tree_desc, &opts))
@@ -224,8 +230,11 @@ static int merge_working_tree(struct checkout_opts *opts,
struct tree_desc trees[2];
struct tree *tree;
struct unpack_trees_options topts;
+
memset(&topts, 0, sizeof(topts));
topts.head_idx = -1;
+ topts.src_index = &the_index;
+ topts.dst_index = &the_index;
refresh_cache(REFRESH_QUIET);
diff --git a/builtin-commit.c b/builtin-commit.c
index f49c22e..660a345 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -198,6 +198,8 @@ static void create_base_index(void)
opts.head_idx = 1;
opts.index_only = 1;
opts.merge = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
opts.fn = oneway_merge;
tree = parse_tree_indirect(head_sha1);
diff --git a/builtin-fetch.c b/builtin-fetch.c
index 55f611e..b2b9935 100644
--- a/builtin-fetch.c
+++ b/builtin-fetch.c
@@ -40,6 +40,8 @@ static struct option builtin_fetch_options[] = {
"force overwrite of local branch"),
OPT_SET_INT('t', "tags", &tags,
"fetch all tags and associated objects", TAGS_SET),
+ OPT_SET_INT('n', NULL, &tags,
+ "do not fetch all tags (--no-tags)", TAGS_UNSET),
OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
"allow updating of HEAD ref"),
diff --git a/builtin-gc.c b/builtin-gc.c
index 045bf0e..95917d7 100644
--- a/builtin-gc.c
+++ b/builtin-gc.c
@@ -26,12 +26,13 @@ static int pack_refs = 1;
static int aggressive_window = -1;
static int gc_auto_threshold = 6700;
static int gc_auto_pack_limit = 20;
+static char *prune_expire = "2.weeks.ago";
#define MAX_ADD 10
static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL};
static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL};
static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL};
-static const char *argv_prune[] = {"prune", NULL};
+static const char *argv_prune[] = {"prune", "--expire", NULL, NULL};
static const char *argv_rerere[] = {"rerere", "gc", NULL};
static int gc_config(const char *var, const char *value)
@@ -55,6 +56,17 @@ static int gc_config(const char *var, const char *value)
gc_auto_pack_limit = git_config_int(var, value);
return 0;
}
+ if (!strcmp(var, "gc.pruneexpire")) {
+ if (!value)
+ return config_error_nonbool(var);
+ if (strcmp(value, "now")) {
+ unsigned long now = approxidate("now");
+ if (approxidate(value) >= now)
+ return error("Invalid %s: '%s'", var, value);
+ }
+ prune_expire = xstrdup(value);
+ return 0;
+ }
return git_default_config(var, value);
}
@@ -234,7 +246,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
if (run_command_v_opt(argv_repack, RUN_GIT_CMD))
return error(FAILED_RUN, argv_repack[0]);
- if (prune && run_command_v_opt(argv_prune, RUN_GIT_CMD))
+ argv_prune[2] = prune_expire;
+ if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
return error(FAILED_RUN, argv_prune[0]);
if (run_command_v_opt(argv_rerere, RUN_GIT_CMD))
diff --git a/builtin-merge-file.c b/builtin-merge-file.c
index adce6d4..3605960 100644
--- a/builtin-merge-file.c
+++ b/builtin-merge-file.c
@@ -57,7 +57,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
if (!f)
ret = error("Could not open %s for writing", filename);
- else if (fwrite(result.ptr, result.size, 1, f) != 1)
+ else if (result.size &&
+ fwrite(result.ptr, result.size, 1, f) != 1)
ret = error("Could not write to %s", filename);
else if (fclose(f))
ret = error("Could not close %s", filename);
diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c
index 6fe4102..910c0d2 100644
--- a/builtin-merge-recursive.c
+++ b/builtin-merge-recursive.c
@@ -11,11 +11,11 @@
#include "tree-walk.h"
#include "diff.h"
#include "diffcore.h"
-#include "run-command.h"
#include "tag.h"
#include "unpack-trees.h"
#include "path-list.h"
#include "xdiff-interface.h"
+#include "ll-merge.h"
#include "interpolate.h"
#include "attr.h"
#include "merge-recursive.h"
@@ -213,6 +213,8 @@ static int git_merge_trees(int index_only,
opts.merge = 1;
opts.head_idx = 2;
opts.fn = threeway_merge;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
init_tree_desc_from_tree(t+0, common);
init_tree_desc_from_tree(t+1, head);
@@ -615,364 +617,16 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
mm->size = size;
}
-/*
- * Customizable low-level merge drivers support.
- */
-
-struct ll_merge_driver;
-typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
- const char *path,
- mmfile_t *orig,
- mmfile_t *src1, const char *name1,
- mmfile_t *src2, const char *name2,
- mmbuffer_t *result);
-
-struct ll_merge_driver {
- const char *name;
- const char *description;
- ll_merge_fn fn;
- const char *recursive;
- struct ll_merge_driver *next;
- char *cmdline;
-};
-
-/*
- * Built-in low-levels
- */
-static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
- const char *path_unused,
- mmfile_t *orig,
- mmfile_t *src1, const char *name1,
- mmfile_t *src2, const char *name2,
- mmbuffer_t *result)
-{
- /*
- * The tentative merge result is "ours" for the final round,
- * or common ancestor for an internal merge. Still return
- * "conflicted merge" status.
- */
- mmfile_t *stolen = index_only ? orig : src1;
-
- result->ptr = stolen->ptr;
- result->size = stolen->size;
- stolen->ptr = NULL;
- return 1;
-}
-
-static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
- const char *path_unused,
- mmfile_t *orig,
- mmfile_t *src1, const char *name1,
- mmfile_t *src2, const char *name2,
- mmbuffer_t *result)
-{
- xpparam_t xpp;
-
- if (buffer_is_binary(orig->ptr, orig->size) ||
- buffer_is_binary(src1->ptr, src1->size) ||
- buffer_is_binary(src2->ptr, src2->size)) {
- warning("Cannot merge binary files: %s vs. %s\n",
- name1, name2);
- return ll_binary_merge(drv_unused, path_unused,
- orig, src1, name1,
- src2, name2,
- result);
- }
-
- memset(&xpp, 0, sizeof(xpp));
- return xdl_merge(orig,
- src1, name1,
- src2, name2,
- &xpp, XDL_MERGE_ZEALOUS,
- result);
-}
-
-static int ll_union_merge(const struct ll_merge_driver *drv_unused,
- const char *path_unused,
- mmfile_t *orig,
- mmfile_t *src1, const char *name1,
- mmfile_t *src2, const char *name2,
- mmbuffer_t *result)
-{
- char *src, *dst;
- long size;
- const int marker_size = 7;
-
- int status = ll_xdl_merge(drv_unused, path_unused,
- orig, src1, NULL, src2, NULL, result);
- if (status <= 0)
- return status;
- size = result->size;
- src = dst = result->ptr;
- while (size) {
- char ch;
- if ((marker_size < size) &&
- (*src == '<' || *src == '=' || *src == '>')) {
- int i;
- ch = *src;
- for (i = 0; i < marker_size; i++)
- if (src[i] != ch)
- goto not_a_marker;
- if (src[marker_size] != '\n')
- goto not_a_marker;
- src += marker_size + 1;
- size -= marker_size + 1;
- continue;
- }
- not_a_marker:
- do {
- ch = *src++;
- *dst++ = ch;
- size--;
- } while (ch != '\n' && size);
- }
- result->size = dst - result->ptr;
- return 0;
-}
-
-#define LL_BINARY_MERGE 0
-#define LL_TEXT_MERGE 1
-#define LL_UNION_MERGE 2
-static struct ll_merge_driver ll_merge_drv[] = {
- { "binary", "built-in binary merge", ll_binary_merge },
- { "text", "built-in 3-way text merge", ll_xdl_merge },
- { "union", "built-in union merge", ll_union_merge },
-};
-
-static void create_temp(mmfile_t *src, char *path)
-{
- int fd;
-
- strcpy(path, ".merge_file_XXXXXX");
- fd = xmkstemp(path);
- if (write_in_full(fd, src->ptr, src->size) != src->size)
- die("unable to write temp-file");
- close(fd);
-}
-
-/*
- * User defined low-level merge driver support.
- */
-static int ll_ext_merge(const struct ll_merge_driver *fn,
- const char *path,
- mmfile_t *orig,
- mmfile_t *src1, const char *name1,
- mmfile_t *src2, const char *name2,
- mmbuffer_t *result)
-{
- char temp[3][50];
- char cmdbuf[2048];
- struct interp table[] = {
- { "%O" },
- { "%A" },
- { "%B" },
- };
- struct child_process child;
- const char *args[20];
- int status, fd, i;
- struct stat st;
-
- if (fn->cmdline == NULL)
- die("custom merge driver %s lacks command line.", fn->name);
-
- result->ptr = NULL;
- result->size = 0;
- create_temp(orig, temp[0]);
- create_temp(src1, temp[1]);
- create_temp(src2, temp[2]);
-
- interp_set_entry(table, 0, temp[0]);
- interp_set_entry(table, 1, temp[1]);
- interp_set_entry(table, 2, temp[2]);
-
- output(1, "merging %s using %s", path,
- fn->description ? fn->description : fn->name);
-
- interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3);
-
- memset(&child, 0, sizeof(child));
- child.argv = args;
- args[0] = "sh";
- args[1] = "-c";
- args[2] = cmdbuf;
- args[3] = NULL;
-
- status = run_command(&child);
- if (status < -ERR_RUN_COMMAND_FORK)
- ; /* failure in run-command */
- else
- status = -status;
- fd = open(temp[1], O_RDONLY);
- if (fd < 0)
- goto bad;
- if (fstat(fd, &st))
- goto close_bad;
- result->size = st.st_size;
- result->ptr = xmalloc(result->size + 1);
- if (read_in_full(fd, result->ptr, result->size) != result->size) {
- free(result->ptr);
- result->ptr = NULL;
- result->size = 0;
- }
- close_bad:
- close(fd);
- bad:
- for (i = 0; i < 3; i++)
- unlink(temp[i]);
- return status;
-}
-
-/*
- * merge.default and merge.driver configuration items
- */
-static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
-static const char *default_ll_merge;
-
-static int read_merge_config(const char *var, const char *value)
-{
- struct ll_merge_driver *fn;
- const char *ep, *name;
- int namelen;
-
- if (!strcmp(var, "merge.default")) {
- if (!value)
- return config_error_nonbool(var);
- default_ll_merge = strdup(value);
- return 0;
- }
-
- /*
- * We are not interested in anything but "merge.<name>.variable";
- * especially, we do not want to look at variables such as
- * "merge.summary", "merge.tool", and "merge.verbosity".
- */
- if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5)
- return 0;
-
- /*
- * Find existing one as we might be processing merge.<name>.var2
- * after seeing merge.<name>.var1.
- */
- name = var + 6;
- namelen = ep - name;
- for (fn = ll_user_merge; fn; fn = fn->next)
- if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
- break;
- if (!fn) {
- fn = xcalloc(1, sizeof(struct ll_merge_driver));
- fn->name = xmemdupz(name, namelen);
- fn->fn = ll_ext_merge;
- *ll_user_merge_tail = fn;
- ll_user_merge_tail = &(fn->next);
- }
-
- ep++;
-
- if (!strcmp("name", ep)) {
- if (!value)
- return config_error_nonbool(var);
- fn->description = strdup(value);
- return 0;
- }
-
- if (!strcmp("driver", ep)) {
- if (!value)
- return config_error_nonbool(var);
- /*
- * merge.<name>.driver specifies the command line:
- *
- * command-line
- *
- * The command-line will be interpolated with the following
- * tokens and is given to the shell:
- *
- * %O - temporary file name for the merge base.
- * %A - temporary file name for our version.
- * %B - temporary file name for the other branches' version.
- *
- * The external merge driver should write the results in the
- * file named by %A, and signal that it has done with zero exit
- * status.
- */
- fn->cmdline = strdup(value);
- return 0;
- }
-
- if (!strcmp("recursive", ep)) {
- if (!value)
- return config_error_nonbool(var);
- fn->recursive = strdup(value);
- return 0;
- }
-
- return 0;
-}
-
-static void initialize_ll_merge(void)
-{
- if (ll_user_merge_tail)
- return;
- ll_user_merge_tail = &ll_user_merge;
- git_config(read_merge_config);
-}
-
-static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr)
-{
- struct ll_merge_driver *fn;
- const char *name;
- int i;
-
- initialize_ll_merge();
-
- if (ATTR_TRUE(merge_attr))
- return &ll_merge_drv[LL_TEXT_MERGE];
- else if (ATTR_FALSE(merge_attr))
- return &ll_merge_drv[LL_BINARY_MERGE];
- else if (ATTR_UNSET(merge_attr)) {
- if (!default_ll_merge)
- return &ll_merge_drv[LL_TEXT_MERGE];
- else
- name = default_ll_merge;
- }
- else
- name = merge_attr;
-
- for (fn = ll_user_merge; fn; fn = fn->next)
- if (!strcmp(fn->name, name))
- return fn;
-
- for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
- if (!strcmp(ll_merge_drv[i].name, name))
- return &ll_merge_drv[i];
-
- /* default to the 3-way */
- return &ll_merge_drv[LL_TEXT_MERGE];
-}
-
-static const char *git_path_check_merge(const char *path)
-{
- static struct git_attr_check attr_merge_check;
-
- if (!attr_merge_check.attr)
- attr_merge_check.attr = git_attr("merge", 5);
-
- if (git_checkattr(path, 1, &attr_merge_check))
- return NULL;
- return attr_merge_check.value;
-}
-
-static int ll_merge(mmbuffer_t *result_buf,
- struct diff_filespec *o,
- struct diff_filespec *a,
- struct diff_filespec *b,
- const char *branch1,
- const char *branch2)
+static int merge_3way(mmbuffer_t *result_buf,
+ struct diff_filespec *o,
+ struct diff_filespec *a,
+ struct diff_filespec *b,
+ const char *branch1,
+ const char *branch2)
{
mmfile_t orig, src1, src2;
char *name1, *name2;
int merge_status;
- const char *ll_driver_name;
- const struct ll_merge_driver *driver;
name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
@@ -981,14 +635,9 @@ static int ll_merge(mmbuffer_t *result_buf,
fill_mm(a->sha1, &src1);
fill_mm(b->sha1, &src2);
- ll_driver_name = git_path_check_merge(a->path);
- driver = find_ll_merge_driver(ll_driver_name);
-
- if (index_only && driver->recursive)
- driver = find_ll_merge_driver(driver->recursive);
- merge_status = driver->fn(driver, a->path,
- &orig, &src1, name1, &src2, name2,
- result_buf);
+ merge_status = ll_merge(result_buf, a->path, &orig,
+ &src1, name1, &src2, name2,
+ index_only);
free(name1);
free(name2);
@@ -1019,9 +668,20 @@ static struct merge_file_info merge_file(struct diff_filespec *o,
if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1))
result.merge = 1;
- result.mode = a->mode == o->mode ? b->mode: a->mode;
+ /*
+ * Merge modes
+ */
+ if (a->mode == b->mode || a->mode == o->mode)
+ result.mode = b->mode;
+ else {
+ result.mode = a->mode;
+ if (b->mode != o->mode) {
+ result.clean = 0;
+ result.merge = 1;
+ }
+ }
- if (sha_eq(a->sha1, o->sha1))
+ if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, o->sha1))
hashcpy(result.sha, b->sha1);
else if (sha_eq(b->sha1, o->sha1))
hashcpy(result.sha, a->sha1);
@@ -1029,8 +689,8 @@ static struct merge_file_info merge_file(struct diff_filespec *o,
mmbuffer_t result_buf;
int merge_status;
- merge_status = ll_merge(&result_buf, o, a, b,
- branch1, branch2);
+ merge_status = merge_3way(&result_buf, o, a, b,
+ branch1, branch2);
if ((merge_status < 0) || !result_buf.ptr)
die("Failed to execute internal merge");
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index f504cff..777f272 100644
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
@@ -454,6 +454,7 @@ static void write_pack_file(void)
struct pack_header hdr;
int do_progress = progress >> pack_to_stdout;
uint32_t nr_remaining = nr_result;
+ time_t last_mtime = 0;
if (do_progress)
progress_state = start_progress("Writing objects", nr_result);
@@ -504,6 +505,7 @@ static void write_pack_file(void)
if (!pack_to_stdout) {
mode_t mode = umask(0);
+ struct stat st;
char *idx_tmp_name, tmpname[PATH_MAX];
umask(mode);
@@ -511,6 +513,7 @@ static void write_pack_file(void)
idx_tmp_name = write_idx_file(NULL, written_list,
nr_written, sha1);
+
snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
base_name, sha1_to_hex(sha1));
if (adjust_perm(pack_tmp_name, mode))
@@ -519,6 +522,28 @@ static void write_pack_file(void)
if (rename(pack_tmp_name, tmpname))
die("unable to rename temporary pack file: %s",
strerror(errno));
+
+ /*
+ * Packs are runtime accessed in their mtime
+ * order since newer packs are more likely to contain
+ * younger objects. So if we are creating multiple
+ * packs then we should modify the mtime of later ones
+ * to preserve this property.
+ */
+ if (stat(tmpname, &st) < 0) {
+ warning("failed to stat %s: %s",
+ tmpname, strerror(errno));
+ } else if (!last_mtime) {
+ last_mtime = st.st_mtime;
+ } else {
+ struct utimbuf utb;
+ utb.actime = st.st_atime;
+ utb.modtime = --last_mtime;
+ if (utime(tmpname, &utb) < 0)
+ warning("failed utime() on %s: %s",
+ tmpname, strerror(errno));
+ }
+
snprintf(tmpname, sizeof(tmpname), "%s-%s.idx",
base_name, sha1_to_hex(sha1));
if (adjust_perm(idx_tmp_name, mode))
@@ -527,6 +552,7 @@ static void write_pack_file(void)
if (rename(idx_tmp_name, tmpname))
die("unable to rename temporary index file: %s",
strerror(errno));
+
free(idx_tmp_name);
free(pack_tmp_name);
puts(sha1_to_hex(sha1));
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 0138f5a..e9cfd2b 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -13,16 +13,15 @@
#include "dir.h"
#include "builtin.h"
-#define MAX_TREES 8
static int nr_trees;
-static struct tree *trees[MAX_TREES];
+static struct tree *trees[MAX_UNPACK_TREES];
static int list_tree(unsigned char *sha1)
{
struct tree *tree;
- if (nr_trees >= MAX_TREES)
- die("I cannot read more than %d trees", MAX_TREES);
+ if (nr_trees >= MAX_UNPACK_TREES)
+ die("I cannot read more than %d trees", MAX_UNPACK_TREES);
tree = parse_tree_indirect(sha1);
if (!tree)
return -1;
@@ -97,11 +96,13 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
{
int i, newfd, stage = 0;
unsigned char sha1[20];
- struct tree_desc t[MAX_TREES];
+ struct tree_desc t[MAX_UNPACK_TREES];
struct unpack_trees_options opts;
memset(&opts, 0, sizeof(opts));
opts.head_idx = -1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
git_config(git_default_config);
@@ -220,27 +221,6 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
if ((opts.dir && !opts.update))
die("--exclude-per-directory is meaningless unless -u");
- if (opts.prefix) {
- int pfxlen = strlen(opts.prefix);
- int pos;
- if (opts.prefix[pfxlen-1] != '/')
- die("prefix must end with /");
- if (stage != 2)
- die("binding merge takes only one tree");
- pos = cache_name_pos(opts.prefix, pfxlen);
- if (0 <= pos)
- die("corrupt index file");
- pos = -pos-1;
- if (pos < active_nr &&
- !strncmp(active_cache[pos]->name, opts.prefix, pfxlen))
- die("subdirectory '%s' already exists.", opts.prefix);
- pos = cache_name_pos(opts.prefix, pfxlen-1);
- if (0 <= pos)
- die("file '%.*s' already exists.",
- pfxlen-1, opts.prefix);
- opts.pos = -1 - pos;
- }
-
if (opts.merge) {
if (stage < 2)
die("just how do you expect me to merge %d trees?", stage-1);
diff --git a/builtin-remote.c b/builtin-remote.c
new file mode 100644
index 0000000..24e6929
--- /dev/null
+++ b/builtin-remote.c
@@ -0,0 +1,605 @@
+#include "cache.h"
+#include "parse-options.h"
+#include "transport.h"
+#include "remote.h"
+#include "path-list.h"
+#include "strbuf.h"
+#include "run-command.h"
+#include "refs.h"
+
+static const char * const builtin_remote_usage[] = {
+ "git remote",
+ "git remote add <name> <url>",
+ "git remote rm <name>",
+ "git remote show <name>",
+ "git remote prune <name>",
+ "git remote update [group]",
+ NULL
+};
+
+static int verbose;
+
+static inline int postfixcmp(const char *string, const char *postfix)
+{
+ int len1 = strlen(string), len2 = strlen(postfix);
+ if (len1 < len2)
+ return 1;
+ return strcmp(string + len1 - len2, postfix);
+}
+
+static inline const char *skip_prefix(const char *name, const char *prefix)
+{
+ return !name ? "" :
+ prefixcmp(name, prefix) ? name : name + strlen(prefix);
+}
+
+static int opt_parse_track(const struct option *opt, const char *arg, int not)
+{
+ struct path_list *list = opt->value;
+ if (not)
+ path_list_clear(list, 0);
+ else
+ path_list_append(arg, list);
+ return 0;
+}
+
+static int fetch_remote(const char *name)
+{
+ const char *argv[] = { "fetch", name, NULL };
+ printf("Updating %s\n", name);
+ if (run_command_v_opt(argv, RUN_GIT_CMD))
+ return error("Could not fetch %s", name);
+ return 0;
+}
+
+static int add(int argc, const char **argv)
+{
+ int fetch = 0, mirror = 0;
+ struct path_list track = { NULL, 0, 0 };
+ const char *master = NULL;
+ struct remote *remote;
+ struct strbuf buf, buf2;
+ const char *name, *url;
+ int i;
+
+ struct option options[] = {
+ OPT_GROUP("add specific options"),
+ OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"),
+ OPT_CALLBACK('t', "track", &track, "branch",
+ "branch(es) to track", opt_parse_track),
+ OPT_STRING('m', "master", &master, "branch", "master branch"),
+ OPT_BOOLEAN(0, "mirror", &mirror, "no separate remotes"),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
+
+ if (argc < 2)
+ usage_with_options(builtin_remote_usage, options);
+
+ name = argv[0];
+ url = argv[1];
+
+ remote = remote_get(name);
+ if (remote && (remote->url_nr > 1 || strcmp(name, remote->url[0]) ||
+ remote->fetch_refspec_nr))
+ die("remote %s already exists.", name);
+
+ strbuf_init(&buf, 0);
+ strbuf_init(&buf2, 0);
+
+ strbuf_addf(&buf, "remote.%s.url", name);
+ if (git_config_set(buf.buf, url))
+ return 1;
+
+ if (track.nr == 0)
+ path_list_append("*", &track);
+ for (i = 0; i < track.nr; i++) {
+ struct path_list_item *item = track.items + i;
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "remote.%s.fetch", name);
+
+ strbuf_reset(&buf2);
+ if (mirror)
+ strbuf_addf(&buf2, "refs/%s:refs/%s",
+ item->path, item->path);
+ else
+ strbuf_addf(&buf2, "refs/heads/%s:refs/remotes/%s/%s",
+ item->path, name, item->path);
+ if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
+ return 1;
+ }
+
+ if (fetch && fetch_remote(name))
+ return 1;
+
+ if (master) {
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "refs/remotes/%s/HEAD", name);
+
+ strbuf_reset(&buf2);
+ strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master);
+
+ if (create_symref(buf.buf, buf2.buf, "remote add"))
+ return error("Could not setup master '%s'", master);
+ }
+
+ strbuf_release(&buf);
+ strbuf_release(&buf2);
+ path_list_clear(&track, 0);
+
+ return 0;
+}
+
+struct branch_info {
+ char *remote;
+ struct path_list merge;
+};
+
+static struct path_list branch_list;
+
+static int config_read_branches(const char *key, const char *value)
+{
+ if (!prefixcmp(key, "branch.")) {
+ char *name;
+ struct path_list_item *item;
+ struct branch_info *info;
+ enum { REMOTE, MERGE } type;
+
+ key += 7;
+ if (!postfixcmp(key, ".remote")) {
+ name = xstrndup(key, strlen(key) - 7);
+ type = REMOTE;
+ } else if (!postfixcmp(key, ".merge")) {
+ name = xstrndup(key, strlen(key) - 6);
+ type = MERGE;
+ } else
+ return 0;
+
+ item = path_list_insert(name, &branch_list);
+
+ if (!item->util)
+ item->util = xcalloc(sizeof(struct branch_info), 1);
+ info = item->util;
+ if (type == REMOTE) {
+ if (info->remote)
+ warning("more than one branch.%s", key);
+ info->remote = xstrdup(value);
+ } else {
+ char *space = strchr(value, ' ');
+ value = skip_prefix(value, "refs/heads/");
+ while (space) {
+ char *merge;
+ merge = xstrndup(value, space - value);
+ path_list_append(merge, &info->merge);
+ value = skip_prefix(space + 1, "refs/heads/");
+ space = strchr(value, ' ');
+ }
+ path_list_append(xstrdup(value), &info->merge);
+ }
+ }
+ return 0;
+}
+
+static void read_branches(void)
+{
+ if (branch_list.nr)
+ return;
+ git_config(config_read_branches);
+ sort_path_list(&branch_list);
+}
+
+struct ref_states {
+ struct remote *remote;
+ struct strbuf remote_prefix;
+ struct path_list new, stale, tracked;
+};
+
+static int handle_one_branch(const char *refname,
+ const unsigned char *sha1, int flags, void *cb_data)
+{
+ struct ref_states *states = cb_data;
+ struct refspec refspec;
+
+ memset(&refspec, 0, sizeof(refspec));
+ refspec.dst = (char *)refname;
+ if (!remote_find_tracking(states->remote, &refspec)) {
+ struct path_list_item *item;
+ const char *name = skip_prefix(refspec.src, "refs/heads/");
+ if (unsorted_path_list_has_path(&states->tracked, name) ||
+ unsorted_path_list_has_path(&states->new,
+ name))
+ return 0;
+ item = path_list_append(name, &states->stale);
+ item->util = xstrdup(refname);
+ }
+ return 0;
+}
+
+static int get_ref_states(const struct ref *ref, struct ref_states *states)
+{
+ struct ref *fetch_map = NULL, **tail = &fetch_map;
+ int i;
+
+ for (i = 0; i < states->remote->fetch_refspec_nr; i++)
+ if (get_fetch_map(ref, states->remote->fetch + i, &tail, 1))
+ die("Could not get fetch map for refspec %s",
+ states->remote->fetch_refspec[i]);
+
+ states->new.strdup_paths = states->tracked.strdup_paths = 1;
+ for (ref = fetch_map; ref; ref = ref->next) {
+ struct path_list *target = &states->tracked;
+ unsigned char sha1[20];
+ void *util = NULL;
+
+ if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
+ target = &states->new;
+ else {
+ target = &states->tracked;
+ if (hashcmp(sha1, ref->new_sha1))
+ util = &states;
+ }
+ path_list_append(skip_prefix(ref->name, "refs/heads/"),
+ target)->util = util;
+ }
+ free_refs(fetch_map);
+
+ strbuf_addf(&states->remote_prefix,
+ "refs/remotes/%s/", states->remote->name);
+ for_each_ref(handle_one_branch, states);
+ sort_path_list(&states->stale);
+
+ return 0;
+}
+
+struct branches_for_remote {
+ const char *prefix;
+ struct path_list *branches;
+};
+
+static int add_branch_for_removal(const char *refname,
+ const unsigned char *sha1, int flags, void *cb_data)
+{
+ struct branches_for_remote *branches = cb_data;
+
+ if (!prefixcmp(refname, branches->prefix)) {
+ struct path_list_item *item;
+
+ /* make sure that symrefs are deleted */
+ if (flags & REF_ISSYMREF)
+ return unlink(git_path(refname));
+
+ item = path_list_append(refname, branches->branches);
+ item->util = xmalloc(20);
+ hashcpy(item->util, sha1);
+ }
+
+ return 0;
+}
+
+static int remove_branches(struct path_list *branches)
+{
+ int i, result = 0;
+ for (i = 0; i < branches->nr; i++) {
+ struct path_list_item *item = branches->items + i;
+ const char *refname = item->path;
+ unsigned char *sha1 = item->util;
+
+ if (delete_ref(refname, sha1))
+ result |= error("Could not remove branch %s", refname);
+ }
+ return result;
+}
+
+static int rm(int argc, const char **argv)
+{
+ struct option options[] = {
+ OPT_END()
+ };
+ struct remote *remote;
+ struct strbuf buf;
+ struct path_list branches = { NULL, 0, 0, 1 };
+ struct branches_for_remote cb_data = { NULL, &branches };
+ int i;
+
+ if (argc != 2)
+ usage_with_options(builtin_remote_usage, options);
+
+ remote = remote_get(argv[1]);
+ if (!remote)
+ die("No such remote: %s", argv[1]);
+
+ strbuf_init(&buf, 0);
+ strbuf_addf(&buf, "remote.%s", remote->name);
+ if (git_config_rename_section(buf.buf, NULL) < 1)
+ return error("Could not remove config section '%s'", buf.buf);
+
+ read_branches();
+ for (i = 0; i < branch_list.nr; i++) {
+ struct path_list_item *item = branch_list.items + i;
+ struct branch_info *info = item->util;
+ if (info->remote && !strcmp(info->remote, remote->name)) {
+ const char *keys[] = { "remote", "merge", NULL }, **k;
+ for (k = keys; *k; k++) {
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "branch.%s.%s",
+ item->path, *k);
+ if (git_config_set(buf.buf, NULL)) {
+ strbuf_release(&buf);
+ return -1;
+ }
+ }
+ }
+ }
+
+ /*
+ * We cannot just pass a function to for_each_ref() which deletes
+ * the branches one by one, since for_each_ref() relies on cached
+ * refs, which are invalidated when deleting a branch.
+ */
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "refs/remotes/%s/", remote->name);
+ cb_data.prefix = buf.buf;
+ i = for_each_ref(add_branch_for_removal, &cb_data);
+ strbuf_release(&buf);
+
+ if (!i)
+ i = remove_branches(&branches);
+ path_list_clear(&branches, 1);
+
+ return i;
+}
+
+static void show_list(const char *title, struct path_list *list)
+{
+ int i;
+
+ if (!list->nr)
+ return;
+
+ printf(title, list->nr > 1 ? "es" : "");
+ printf("\n ");
+ for (i = 0; i < list->nr; i++)
+ printf("%s%s", i ? " " : "", list->items[i].path);
+ printf("\n");
+}
+
+static int show_or_prune(int argc, const char **argv, int prune)
+{
+ int dry_run = 0, result = 0;
+ struct option options[] = {
+ OPT_GROUP("show specific options"),
+ OPT__DRY_RUN(&dry_run),
+ OPT_END()
+ };
+ struct ref_states states;
+
+ argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
+
+ if (argc < 1)
+ usage_with_options(builtin_remote_usage, options);
+
+ memset(&states, 0, sizeof(states));
+ for (; argc; argc--, argv++) {
+ struct transport *transport;
+ const struct ref *ref;
+ struct strbuf buf;
+ int i, got_states;
+
+ states.remote = remote_get(*argv);
+ if (!states.remote)
+ return error("No such remote: %s", *argv);
+ transport = transport_get(NULL, states.remote->url_nr > 0 ?
+ states.remote->url[0] : NULL);
+ ref = transport_get_remote_refs(transport);
+ transport_disconnect(transport);
+
+ read_branches();
+ got_states = get_ref_states(ref, &states);
+ if (got_states)
+ result = error("Error getting local info for '%s'",
+ states.remote->name);
+
+ if (prune) {
+ struct strbuf buf;
+ int prefix_len;
+
+ strbuf_init(&buf, 0);
+ if (states.remote->fetch_refspec_nr == 1 &&
+ states.remote->fetch->pattern &&
+ !strcmp(states.remote->fetch->src,
+ states.remote->fetch->dst))
+ /* handle --mirror remote */
+ strbuf_addstr(&buf, "refs/heads/");
+ else
+ strbuf_addf(&buf, "refs/remotes/%s/", *argv);
+ prefix_len = buf.len;
+
+ for (i = 0; i < states.stale.nr; i++) {
+ strbuf_setlen(&buf, prefix_len);
+ strbuf_addstr(&buf, states.stale.items[i].path);
+ result |= delete_ref(buf.buf, NULL);
+ }
+
+ strbuf_release(&buf);
+ goto cleanup_states;
+ }
+
+ printf("* remote %s\n URL: %s\n", *argv,
+ states.remote->url_nr > 0 ?
+ states.remote->url[0] : "(no URL)");
+
+ for (i = 0; i < branch_list.nr; i++) {
+ struct path_list_item *branch = branch_list.items + i;
+ struct branch_info *info = branch->util;
+ int j;
+
+ if (!info->merge.nr || strcmp(*argv, info->remote))
+ continue;
+ printf(" Remote branch%s merged with 'git pull' "
+ "while on branch %s\n ",
+ info->merge.nr > 1 ? "es" : "",
+ branch->path);
+ for (j = 0; j < info->merge.nr; j++)
+ printf(" %s", info->merge.items[j].path);
+ printf("\n");
+ }
+
+ if (got_states)
+ continue;
+ strbuf_init(&buf, 0);
+ strbuf_addf(&buf, " New remote branch%%s (next fetch will "
+ "store in remotes/%s)", states.remote->name);
+ show_list(buf.buf, &states.new);
+ strbuf_release(&buf);
+ show_list(" Stale tracking branch%s (use 'git remote prune')",
+ &states.stale);
+ show_list(" Tracked remote branch%s",
+ &states.tracked);
+
+ if (states.remote->push_refspec_nr) {
+ printf(" Local branch%s pushed with 'git push'\n ",
+ states.remote->push_refspec_nr > 1 ?
+ "es" : "");
+ for (i = 0; i < states.remote->push_refspec_nr; i++) {
+ struct refspec *spec = states.remote->push + i;
+ printf(" %s%s%s%s", spec->force ? "+" : "",
+ skip_prefix(spec->src, "refs/heads/"),
+ spec->dst ? ":" : "",
+ skip_prefix(spec->dst, "refs/heads/"));
+ }
+ }
+cleanup_states:
+ /* NEEDSWORK: free remote */
+ path_list_clear(&states.new, 0);
+ path_list_clear(&states.stale, 0);
+ path_list_clear(&states.tracked, 0);
+ }
+
+ return result;
+}
+
+static int get_one_remote_for_update(struct remote *remote, void *priv)
+{
+ struct path_list *list = priv;
+ if (!remote->skip_default_update)
+ path_list_append(xstrdup(remote->name), list);
+ return 0;
+}
+
+struct remote_group {
+ const char *name;
+ struct path_list *list;
+} remote_group;
+
+static int get_remote_group(const char *key, const char *value)
+{
+ if (!prefixcmp(key, "remotes.") &&
+ !strcmp(key + 8, remote_group.name)) {
+ /* split list by white space */
+ int space = strcspn(value, " \t\n");
+ while (*value) {
+ if (space > 1)
+ path_list_append(xstrndup(value, space),
+ remote_group.list);
+ value += space + (value[space] != '\0');
+ space = strcspn(value, " \t\n");
+ }
+ }
+
+ return 0;
+}
+
+static int update(int argc, const char **argv)
+{
+ int i, result = 0;
+ struct path_list list = { NULL, 0, 0, 0 };
+ static const char *default_argv[] = { NULL, "default", NULL };
+
+ if (argc < 2) {
+ argc = 2;
+ argv = default_argv;
+ }
+
+ remote_group.list = &list;
+ for (i = 1; i < argc; i++) {
+ remote_group.name = argv[i];
+ result = git_config(get_remote_group);
+ }
+
+ if (!result && !list.nr && argc == 2 && !strcmp(argv[1], "default"))
+ result = for_each_remote(get_one_remote_for_update, &list);
+
+ for (i = 0; i < list.nr; i++)
+ result |= fetch_remote(list.items[i].path);
+
+ /* all names were strdup()ed or strndup()ed */
+ list.strdup_paths = 1;
+ path_list_clear(&list, 0);
+
+ return result;
+}
+
+static int get_one_entry(struct remote *remote, void *priv)
+{
+ struct path_list *list = priv;
+
+ path_list_append(remote->name, list)->util = remote->url_nr ?
+ (void *)remote->url[0] : NULL;
+ if (remote->url_nr > 1)
+ warning("Remote %s has more than one URL", remote->name);
+
+ return 0;
+}
+
+static int show_all(void)
+{
+ struct path_list list = { NULL, 0, 0 };
+ int result = for_each_remote(get_one_entry, &list);
+
+ if (!result) {
+ int i;
+
+ sort_path_list(&list);
+ for (i = 0; i < list.nr; i++) {
+ struct path_list_item *item = list.items + i;
+ printf("%s%s%s\n", item->path,
+ verbose ? "\t" : "",
+ verbose && item->util ?
+ (const char *)item->util : "");
+ }
+ }
+ return result;
+}
+
+int cmd_remote(int argc, const char **argv, const char *prefix)
+{
+ struct option options[] = {
+ OPT__VERBOSE(&verbose),
+ OPT_END()
+ };
+ int result;
+
+ argc = parse_options(argc, argv, options, builtin_remote_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ if (argc < 1)
+ result = show_all();
+ else if (!strcmp(argv[0], "add"))
+ result = add(argc, argv);
+ else if (!strcmp(argv[0], "rm"))
+ result = rm(argc, argv);
+ else if (!strcmp(argv[0], "show"))
+ result = show_or_prune(argc, argv, 0);
+ else if (!strcmp(argv[0], "prune"))
+ result = show_or_prune(argc, argv, 1);
+ else if (!strcmp(argv[0], "update"))
+ result = update(argc, argv);
+ else {
+ error("Unknown subcommand: %s", argv[0]);
+ usage_with_options(builtin_remote_usage, options);
+ }
+
+ return result ? 1 : 0;
+}
diff --git a/builtin-tag.c b/builtin-tag.c
index 28c36fd..8dd959f 100644
--- a/builtin-tag.c
+++ b/builtin-tag.c
@@ -50,12 +50,15 @@ void launch_editor(const char *path, struct strbuf *buffer, const char *const *e
size_t len = strlen(editor);
int i = 0;
const char *args[6];
+ struct strbuf arg0;
+ strbuf_init(&arg0, 0);
if (strcspn(editor, "$ \t'") != len) {
/* there are specials */
+ strbuf_addf(&arg0, "%s \"$@\"", editor);
args[i++] = "sh";
args[i++] = "-c";
- args[i++] = "$0 \"$@\"";
+ args[i++] = arg0.buf;
}
args[i++] = editor;
args[i++] = path;
@@ -63,6 +66,7 @@ void launch_editor(const char *path, struct strbuf *buffer, const char *const *e
if (run_command_v_opt_cd_env(args, 0, NULL, env))
die("There was a problem with the editor %s.", editor);
+ strbuf_release(&arg0);
}
if (!buffer)
diff --git a/builtin.h b/builtin.h
index 674c8a1..95126fd 100644
--- a/builtin.h
+++ b/builtin.h
@@ -67,6 +67,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
extern int cmd_push(int argc, const char **argv, const char *prefix);
extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
extern int cmd_reflog(int argc, const char **argv, const char *prefix);
+extern int cmd_remote(int argc, const char **argv, const char *prefix);
extern int cmd_config(int argc, const char **argv, const char *prefix);
extern int cmd_rerere(int argc, const char **argv, const char *prefix);
extern int cmd_reset(int argc, const char **argv, const char *prefix);
diff --git a/cache.h b/cache.h
index e230302..2a1e7ec 100644
--- a/cache.h
+++ b/cache.h
@@ -346,12 +346,12 @@ extern void verify_non_filename(const char *prefix, const char *name);
/* Initialize and use the cache information */
extern int read_index(struct index_state *);
extern int read_index_from(struct index_state *, const char *path);
-extern int write_index(struct index_state *, int newfd);
+extern int write_index(const struct index_state *, int newfd);
extern int discard_index(struct index_state *);
-extern int unmerged_index(struct index_state *);
+extern int unmerged_index(const struct index_state *);
extern int verify_path(const char *path);
extern int index_name_exists(struct index_state *istate, const char *name, int namelen);
-extern int index_name_pos(struct index_state *, const char *name, int namelen);
+extern int index_name_pos(const struct index_state *, const char *name, int namelen);
#define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */
#define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */
#define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */
@@ -368,8 +368,8 @@ extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
#define CE_MATCH_IGNORE_VALID 01
/* do not check the contents but report dirty on racily-clean entries */
#define CE_MATCH_RACY_IS_DIRTY 02
-extern int ie_match_stat(struct index_state *, struct cache_entry *, struct stat *, unsigned int);
-extern int ie_modified(struct index_state *, struct cache_entry *, struct stat *, unsigned int);
+extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
+extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
@@ -536,6 +536,7 @@ extern int create_symref(const char *ref, const char *refs_heads_master, const c
extern int validate_headref(const char *ref);
extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
+extern int df_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);
extern void *read_object_with_reference(const unsigned char *sha1,
@@ -543,6 +544,9 @@ extern void *read_object_with_reference(const unsigned char *sha1,
unsigned long *size,
unsigned char *sha1_ret);
+extern struct object *peel_to_type(const char *name, int namelen,
+ struct object *o, enum object_type);
+
enum date_mode {
DATE_NORMAL = 0,
DATE_RELATIVE,
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 848c067..5046f69 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -83,17 +83,17 @@ __git_ps1 ()
elif [ -f "$g/.dotest-merge/interactive" ]
then
r="|REBASE-i"
- b="$(cat $g/.dotest-merge/head-name)"
+ b="$(cat "$g/.dotest-merge/head-name")"
elif [ -d "$g/.dotest-merge" ]
then
r="|REBASE-m"
- b="$(cat $g/.dotest-merge/head-name)"
+ b="$(cat "$g/.dotest-merge/head-name")"
elif [ -f "$g/MERGE_HEAD" ]
then
r="|MERGING"
b="$(git symbolic-ref HEAD 2>/dev/null)"
else
- if [ -f $g/BISECT_LOG ]
+ if [ -f "$g/BISECT_LOG" ]
then
r="|BISECTING"
fi
@@ -101,7 +101,7 @@ __git_ps1 ()
then
if ! b="$(git describe --exact-match HEAD 2>/dev/null)"
then
- b="$(cut -c1-7 $g/HEAD)..."
+ b="$(cut -c1-7 "$g/HEAD")..."
fi
fi
fi
@@ -121,13 +121,21 @@ __gitcomp ()
if [ $# -gt 2 ]; then
cur="$3"
fi
- for c in $1; do
- case "$c$4" in
- --*=*) all="$all$c$4$s" ;;
- *.) all="$all$c$4$s" ;;
- *) all="$all$c$4 $s" ;;
- esac
- done
+ case "$cur" in
+ --*=)
+ COMPREPLY=()
+ return
+ ;;
+ *)
+ for c in $1; do
+ case "$c$4" in
+ --*=*) all="$all$c$4$s" ;;
+ *.) all="$all$c$4$s" ;;
+ *) all="$all$c$4 $s" ;;
+ esac
+ done
+ ;;
+ esac
IFS=$s
COMPREPLY=($(compgen -P "$2" -W "$all" -- "$cur"))
return
@@ -384,7 +392,6 @@ __git_commands ()
show-index) : plumbing;;
ssh-*) : transport;;
stripspace) : plumbing;;
- svn) : import export;;
symbolic-ref) : plumbing;;
tar-tree) : deprecated;;
unpack-file) : plumbing;;
@@ -428,6 +435,22 @@ __git_aliased_command ()
done
}
+__git_find_subcommand ()
+{
+ local word subcommand c=1
+
+ while [ $c -lt $COMP_CWORD ]; do
+ word="${COMP_WORDS[c]}"
+ for subcommand in $1; do
+ if [ "$subcommand" = "$word" ]; then
+ echo "$subcommand"
+ return
+ fi
+ done
+ c=$((++c))
+ done
+}
+
__git_whitespacelist="nowarn warn error error-all strip"
_git_am ()
@@ -485,24 +508,14 @@ _git_add ()
_git_bisect ()
{
- local i c=1 command
- while [ $c -lt $COMP_CWORD ]; do
- i="${COMP_WORDS[c]}"
- case "$i" in
- start|bad|good|reset|visualize|replay|log)
- command="$i"
- break
- ;;
- esac
- c=$((++c))
- done
-
- if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
- __gitcomp "start bad good reset visualize replay log"
+ local subcommands="start bad good reset visualize replay log"
+ local subcommand="$(__git_find_subcommand "$subcommands")"
+ if [ -z "$subcommand" ]; then
+ __gitcomp "$subcommands"
return
fi
- case "$command" in
+ case "$subcommand" in
bad|good|reset)
__gitcomp "$(__git_refs)"
;;
@@ -836,8 +849,8 @@ _git_push ()
_git_rebase ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
- if [ -d .dotest ] || [ -d .git/.dotest-merge ]; then
+ local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
+ if [ -d .dotest ] || [ -d "$dir"/.dotest-merge ]; then
__gitcomp "--continue --skip --abort"
return
fi
@@ -956,7 +969,6 @@ _git_config ()
core.sharedRepository
core.warnAmbiguousRefs
core.compression
- core.legacyHeaders
core.packedGitWindowSize
core.packedGitLimit
clean.requireForce
@@ -1033,21 +1045,13 @@ _git_config ()
_git_remote ()
{
- local i c=1 command
- while [ $c -lt $COMP_CWORD ]; do
- i="${COMP_WORDS[c]}"
- case "$i" in
- add|rm|show|prune|update) command="$i"; break ;;
- esac
- c=$((++c))
- done
-
- if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
- __gitcomp "add rm show prune update"
+ local subcommands="add rm show prune update"
+ local subcommand="$(__git_find_subcommand "$subcommands")"
+ if [ -z "$subcommand" ]; then
return
fi
- case "$command" in
+ case "$subcommand" in
rm|show|prune)
__gitcomp "$(__git_remotes)"
;;
@@ -1121,34 +1125,107 @@ _git_show ()
_git_stash ()
{
- __gitcomp 'list show apply clear'
+ local subcommands='save list show apply clear drop pop create'
+ if [ -z "$(__git_find_subcommand "$subcommands")" ]; then
+ __gitcomp "$subcommands"
+ fi
}
_git_submodule ()
{
- local i c=1 command
- while [ $c -lt $COMP_CWORD ]; do
- i="${COMP_WORDS[c]}"
- case "$i" in
- add|status|init|update) command="$i"; break ;;
- esac
- c=$((++c))
- done
-
- if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
+ local subcommands="add status init update"
+ if [ -z "$(__git_find_subcommand "$subcommands")" ]; then
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--quiet --cached"
;;
*)
- __gitcomp "add status init update"
+ __gitcomp "$subcommands"
;;
esac
return
fi
}
+_git_svn ()
+{
+ local subcommands="
+ init fetch clone rebase dcommit log find-rev
+ set-tree commit-diff info create-ignore propget
+ proplist show-ignore show-externals
+ "
+ local subcommand="$(__git_find_subcommand "$subcommands")"
+ if [ -z "$subcommand" ]; then
+ __gitcomp "$subcommands"
+ else
+ local remote_opts="--username= --config-dir= --no-auth-cache"
+ local fc_opts="
+ --follow-parent --authors-file= --repack=
+ --no-metadata --use-svm-props --use-svnsync-props
+ --log-window-size= --no-checkout --quiet
+ --repack-flags --user-log-author $remote_opts
+ "
+ local init_opts="
+ --template= --shared= --trunk= --tags=
+ --branches= --stdlayout --minimize-url
+ --no-metadata --use-svm-props --use-svnsync-props
+ --rewrite-root= $remote_opts
+ "
+ local cmt_opts="
+ --edit --rmdir --find-copies-harder --copy-similarity=
+ "
+
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$subcommand,$cur" in
+ fetch,--*)
+ __gitcomp "--revision= --fetch-all $fc_opts"
+ ;;
+ clone,--*)
+ __gitcomp "--revision= $fc_opts $init_opts"
+ ;;
+ init,--*)
+ __gitcomp "$init_opts"
+ ;;
+ dcommit,--*)
+ __gitcomp "
+ --merge --strategy= --verbose --dry-run
+ --fetch-all --no-rebase $cmt_opts $fc_opts
+ "
+ ;;
+ set-tree,--*)
+ __gitcomp "--stdin $cmt_opts $fc_opts"
+ ;;
+ create-ignore,--*|propget,--*|proplist,--*|show-ignore,--*|\
+ show-externals,--*)
+ __gitcomp "--revision="
+ ;;
+ log,--*)
+ __gitcomp "
+ --limit= --revision= --verbose --incremental
+ --oneline --show-commit --non-recursive
+ --authors-file=
+ "
+ ;;
+ rebase,--*)
+ __gitcomp "
+ --merge --verbose --strategy= --local
+ --fetch-all $fc_opts
+ "
+ ;;
+ commit-diff,--*)
+ __gitcomp "--message= --file= --revision= $cmt_opts"
+ ;;
+ info,--*)
+ __gitcomp "--url"
+ ;;
+ *)
+ COMPREPLY=()
+ ;;
+ esac
+ fi
+}
+
_git_tag ()
{
local i c=1 f=0
@@ -1198,15 +1275,18 @@ _git ()
c=$((++c))
done
- if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
+ if [ -z "$command" ]; then
case "${COMP_WORDS[COMP_CWORD]}" in
--*=*) COMPREPLY=() ;;
--*) __gitcomp "
+ --paginate
--no-pager
--git-dir=
--bare
--version
--exec-path
+ --work-tree=
+ --help
"
;;
*) __gitcomp "$(__git_commands) $(__git_aliases)" ;;
@@ -1250,6 +1330,7 @@ _git ()
show-branch) _git_log ;;
stash) _git_stash ;;
submodule) _git_submodule ;;
+ svn) _git_svn ;;
tag) _git_tag ;;
whatchanged) _git_log ;;
*) COMPREPLY=() ;;
@@ -1300,6 +1381,7 @@ complete -o default -o nospace -F _git_shortlog git-shortlog
complete -o default -o nospace -F _git_show git-show
complete -o default -o nospace -F _git_stash git-stash
complete -o default -o nospace -F _git_submodule git-submodule
+complete -o default -o nospace -F _git_svn git-svn
complete -o default -o nospace -F _git_log git-show-branch
complete -o default -o nospace -F _git_tag git-tag
complete -o default -o nospace -F _git_log git-whatchanged
diff --git a/git-remote.perl b/contrib/examples/git-remote.perl
index b30ed73..b30ed73 100755
--- a/git-remote.perl
+++ b/contrib/examples/git-remote.perl
diff --git a/diff-lib.c b/diff-lib.c
index 4581b59..52dbac3 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -600,8 +600,7 @@ static void mark_merge_entries(void)
*/
static void do_oneway_diff(struct unpack_trees_options *o,
struct cache_entry *idx,
- struct cache_entry *tree,
- int idx_pos, int idx_nr)
+ struct cache_entry *tree)
{
struct rev_info *revs = o->unpack_data;
int match_missing, cached;
@@ -642,32 +641,19 @@ static void do_oneway_diff(struct unpack_trees_options *o,
show_modified(revs, tree, idx, 1, cached, match_missing);
}
-/*
- * Count how many index entries go with the first one
- */
-static inline int count_skip(const struct cache_entry *src, int pos)
+static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_options *o)
{
- int skip = 1;
-
- /* We can only have multiple entries if the first one is not stage-0 */
- if (ce_stage(src)) {
- struct cache_entry **p = active_cache + pos;
- int namelen = ce_namelen(src);
-
- for (;;) {
- const struct cache_entry *ce;
- pos++;
- if (pos >= active_nr)
- break;
- ce = *++p;
- if (ce_namelen(ce) != namelen)
- break;
- if (memcmp(ce->name, src->name, namelen))
- break;
- skip++;
- }
+ int len = ce_namelen(ce);
+ const struct index_state *index = o->src_index;
+
+ while (o->pos < index->cache_nr) {
+ struct cache_entry *next = index->cache[o->pos];
+ if (len != ce_namelen(next))
+ break;
+ if (memcmp(ce->name, next->name, len))
+ break;
+ o->pos++;
}
- return skip;
}
/*
@@ -685,17 +671,14 @@ static inline int count_skip(const struct cache_entry *src, int pos)
* the fairly complex unpack_trees() semantic requirements, including
* the skipping, the path matching, the type conflict cases etc.
*/
-static int oneway_diff(struct cache_entry **src,
- struct unpack_trees_options *o,
- int index_pos)
+static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
{
- int skip = 0;
struct cache_entry *idx = src[0];
struct cache_entry *tree = src[1];
struct rev_info *revs = o->unpack_data;
- if (index_pos >= 0)
- skip = count_skip(idx, index_pos);
+ if (idx && ce_stage(idx))
+ skip_same_name(idx, o);
/*
* Unpack-trees generates a DF/conflict entry if
@@ -707,9 +690,9 @@ static int oneway_diff(struct cache_entry **src,
tree = NULL;
if (ce_path_match(idx ? idx : tree, revs->prune_data))
- do_oneway_diff(o, idx, tree, index_pos, skip);
+ do_oneway_diff(o, idx, tree);
- return skip;
+ return 0;
}
int run_diff_index(struct rev_info *revs, int cached)
@@ -734,6 +717,8 @@ int run_diff_index(struct rev_info *revs, int cached)
opts.merge = 1;
opts.fn = oneway_diff;
opts.unpack_data = revs;
+ opts.src_index = &the_index;
+ opts.dst_index = NULL;
init_tree_desc(&t, tree->buffer, tree->size);
if (unpack_trees(1, &t, &opts))
@@ -787,6 +772,8 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
opts.merge = 1;
opts.fn = oneway_diff;
opts.unpack_data = &revs;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
init_tree_desc(&t, tree->buffer, tree->size);
if (unpack_trees(1, &t, &opts))
diff --git a/git-compat-util.h b/git-compat-util.h
index 73968e0..a18235e 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -68,6 +68,7 @@
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
+#include <utime.h>
#ifndef NO_SYS_SELECT_H
#include <sys/select.h>
#endif
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index 47f116f..95c5eec 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -735,7 +735,7 @@ sub commit {
next unless $logmsg =~ $rx && $1;
my $mparent = $1 eq 'HEAD' ? $opt_o : $1;
if (my $sha1 = get_headref("$remote/$mparent")) {
- push @commit_args, '-p', $mparent;
+ push @commit_args, '-p', "$remote/$mparent";
print "Merge parent branch: $mparent\n" if $opt_v;
}
}
diff --git a/git-gui/Makefile b/git-gui/Makefile
index 4e32174..b19fb2d 100644
--- a/git-gui/Makefile
+++ b/git-gui/Makefile
@@ -221,14 +221,9 @@ ifdef NO_MSGFMT
MSGFMT ?= $(TCL_PATH) po/po2msg.sh
else
MSGFMT ?= msgfmt
- ifeq ($(shell $(MSGFMT) >/dev/null 2>&1 || echo $$?),127)
+ ifneq ($(shell $(MSGFMT) --tcl -l C -d . /dev/null 2>/dev/null; echo $$?),0)
MSGFMT := $(TCL_PATH) po/po2msg.sh
endif
- ifeq (msgfmt,$(MSGFMT))
- ifeq ($(shell $(MSGFMT) --tcl -l C -d . /dev/null 2>/dev/null || echo $?),1)
- MSGFMT := $(TCL_PATH) po/po2msg.sh
- endif
- endif
endif
msgsdir = $(gg_libdir)/msgs
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 238a239..3a58cd2 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -611,6 +611,7 @@ set default_config(gui.matchtrackingbranch) false
set default_config(gui.pruneduringfetch) false
set default_config(gui.trustmtime) false
set default_config(gui.diffcontext) 5
+set default_config(gui.commitmsgwidth) 75
set default_config(gui.newbranchtemplate) {}
set default_config(gui.spellingdictionary) {}
set default_config(gui.fontui) [font configure font_ui]
@@ -2289,8 +2290,9 @@ pack .vpane -anchor n -side top -fill both -expand 1
#
frame .vpane.files.index -height 100 -width 200
label .vpane.files.index.title -text [mc "Staged Changes (Will Commit)"] \
- -background lightgreen
-text $ui_index -background white -borderwidth 0 \
+ -background lightgreen -foreground black
+text $ui_index -background white -foreground black \
+ -borderwidth 0 \
-width 20 -height 10 \
-wrap none \
-cursor $cursor_ptr \
@@ -2308,8 +2310,9 @@ pack $ui_index -side left -fill both -expand 1
#
frame .vpane.files.workdir -height 100 -width 200
label .vpane.files.workdir.title -text [mc "Unstaged Changes"] \
- -background lightsalmon
-text $ui_workdir -background white -borderwidth 0 \
+ -background lightsalmon -foreground black
+text $ui_workdir -background white -foreground black \
+ -borderwidth 0 \
-width 20 -height 10 \
-wrap none \
-cursor $cursor_ptr \
@@ -2416,12 +2419,13 @@ pack $ui_coml -side left -fill x
pack .vpane.lower.commarea.buffer.header.amend -side right
pack .vpane.lower.commarea.buffer.header.new -side right
-text $ui_comm -background white -borderwidth 1 \
+text $ui_comm -background white -foreground black \
+ -borderwidth 1 \
-undo true \
-maxundo 20 \
-autoseparators true \
-relief sunken \
- -width 75 -height 9 -wrap none \
+ -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \
-font font_diff \
-yscrollcommand {.vpane.lower.commarea.buffer.sby set}
scrollbar .vpane.lower.commarea.buffer.sby \
@@ -2493,15 +2497,18 @@ trace add variable current_diff_path write trace_current_diff_path
frame .vpane.lower.diff.header -background gold
label .vpane.lower.diff.header.status \
-background gold \
+ -foreground black \
-width $max_status_desc \
-anchor w \
-justify left
label .vpane.lower.diff.header.file \
-background gold \
+ -foreground black \
-anchor w \
-justify left
label .vpane.lower.diff.header.path \
-background gold \
+ -foreground black \
-anchor w \
-justify left
pack .vpane.lower.diff.header.status -side left
@@ -2525,7 +2532,8 @@ bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
#
frame .vpane.lower.diff.body
set ui_diff .vpane.lower.diff.body.t
-text $ui_diff -background white -borderwidth 0 \
+text $ui_diff -background white -foreground black \
+ -borderwidth 0 \
-width 80 -height 15 -wrap none \
-font font_diff \
-xscrollcommand {.vpane.lower.diff.body.sbx set} \
diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl
index 00ecf21..92fac1b 100644
--- a/git-gui/lib/blame.tcl
+++ b/git-gui/lib/blame.tcl
@@ -80,6 +80,7 @@ constructor new {i_commit i_path} {
label $w.header.commit_l \
-text [mc "Commit:"] \
-background gold \
+ -foreground black \
-anchor w \
-justify left
set w_back $w.header.commit_b
@@ -89,6 +90,7 @@ constructor new {i_commit i_path} {
-relief flat \
-state disabled \
-background gold \
+ -foreground black \
-activebackground gold
bind $w_back <Button-1> "
if {\[$w_back cget -state\] eq {normal}} {
@@ -98,16 +100,19 @@ constructor new {i_commit i_path} {
label $w.header.commit \
-textvariable @commit \
-background gold \
+ -foreground black \
-anchor w \
-justify left
label $w.header.path_l \
-text [mc "File:"] \
-background gold \
+ -foreground black \
-anchor w \
-justify left
set w_path $w.header.path
label $w_path \
-background gold \
+ -foreground black \
-anchor w \
-justify left
pack $w.header.commit_l -side left
@@ -135,7 +140,9 @@ constructor new {i_commit i_path} {
-takefocus 0 \
-highlightthickness 0 \
-padx 0 -pady 0 \
- -background white -borderwidth 0 \
+ -background white \
+ -foreground black \
+ -borderwidth 0 \
-state disabled \
-wrap none \
-height 40 \
@@ -148,7 +155,9 @@ constructor new {i_commit i_path} {
-takefocus 0 \
-highlightthickness 0 \
-padx 0 -pady 0 \
- -background white -borderwidth 0 \
+ -background white \
+ -foreground black \
+ -borderwidth 0 \
-state disabled \
-wrap none \
-height 40 \
@@ -166,7 +175,9 @@ constructor new {i_commit i_path} {
-takefocus 0 \
-highlightthickness 0 \
-padx 0 -pady 0 \
- -background white -borderwidth 0 \
+ -background white \
+ -foreground black \
+ -borderwidth 0 \
-state disabled \
-wrap none \
-height 40 \
@@ -184,7 +195,9 @@ constructor new {i_commit i_path} {
-takefocus 0 \
-highlightthickness 0 \
-padx 0 -pady 0 \
- -background white -borderwidth 0 \
+ -background white \
+ -foreground black \
+ -borderwidth 0 \
-state disabled \
-wrap none \
-height 40 \
@@ -213,7 +226,9 @@ constructor new {i_commit i_path} {
set w_cviewer $w.file_pane.cm.t
text $w_cviewer \
- -background white -borderwidth 0 \
+ -background white \
+ -foreground black \
+ -borderwidth 0 \
-state disabled \
-wrap none \
-height 10 \
diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl
index 53d5a62..ab470d1 100644
--- a/git-gui/lib/browser.tcl
+++ b/git-gui/lib/browser.tcl
@@ -39,7 +39,8 @@ constructor new {commit {path {}}} {
frame $w.list
set w_list $w.list.l
- text $w_list -background white -borderwidth 0 \
+ text $w_list -background white -foreground black \
+ -borderwidth 0 \
-cursor $cursor_ptr \
-state disabled \
-wrap none \
diff --git a/git-gui/lib/choose_font.tcl b/git-gui/lib/choose_font.tcl
index 0c4051b..56443b0 100644
--- a/git-gui/lib/choose_font.tcl
+++ b/git-gui/lib/choose_font.tcl
@@ -55,6 +55,7 @@ constructor pick {path title a_family a_size} {
set w_family $w.inner.family.v
text $w_family \
-background white \
+ -foreground black \
-borderwidth 1 \
-relief sunken \
-cursor $::cursor_ptr \
@@ -92,6 +93,7 @@ constructor pick {path title a_family a_size} {
set w_example $w.example.t
text $w_example \
-background white \
+ -foreground black \
-borderwidth 1 \
-relief sunken \
-height 3 \
diff --git a/git-gui/lib/console.tcl b/git-gui/lib/console.tcl
index 5597188..c112464 100644
--- a/git-gui/lib/console.tcl
+++ b/git-gui/lib/console.tcl
@@ -46,7 +46,9 @@ method _init {} {
-justify left \
-font font_uibold
text $w_t \
- -background white -borderwidth 1 \
+ -background white \
+ -foreground black \
+ -borderwidth 1 \
-relief sunken \
-width 80 -height 10 \
-wrap none \
@@ -180,7 +182,8 @@ method done {ok} {
if {$ok} {
if {[winfo exists $w.m.s]} {
bind $w.m.s <Destroy> [list delete_this $this]
- $w.m.s conf -background green -text [mc "Success"]
+ $w.m.s conf -background green -foreground black \
+ -text [mc "Success"]
if {$is_toplevel} {
$w.ok conf -state normal
focus $w.ok
@@ -193,7 +196,8 @@ method done {ok} {
_init $this
}
bind $w.m.s <Destroy> [list delete_this $this]
- $w.m.s conf -background red -text [mc "Error: Command Failed"]
+ $w.m.s conf -background red -foreground black \
+ -text [mc "Error: Command Failed"]
if {$is_toplevel} {
$w.ok conf -state normal
focus $w.ok
diff --git a/git-gui/lib/error.tcl b/git-gui/lib/error.tcl
index 8c27678..7565015 100644
--- a/git-gui/lib/error.tcl
+++ b/git-gui/lib/error.tcl
@@ -80,7 +80,9 @@ proc hook_failed_popup {hook msg {is_fatal 1}} {
-justify left \
-font font_uibold
text $w.m.t \
- -background white -borderwidth 1 \
+ -background white \
+ -foreground black \
+ -borderwidth 1 \
-relief sunken \
-width 80 -height 10 \
-font font_diff \
diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl
index ea80df0..9270512 100644
--- a/git-gui/lib/option.tcl
+++ b/git-gui/lib/option.tcl
@@ -124,6 +124,7 @@ proc do_options {} {
{b gui.pruneduringfetch {mc "Prune Tracking Branches During Fetch"}}
{b gui.matchtrackingbranch {mc "Match Tracking Branches"}}
{i-0..99 gui.diffcontext {mc "Number of Diff Context Lines"}}
+ {i-0..99 gui.commitmsgwidth {mc "Commit Message Text Width"}}
{t gui.newbranchtemplate {mc "New Branch Name Template"}}
} {
set type [lindex $option 0]
diff --git a/git-gui/po/zh_cn.po b/git-gui/po/zh_cn.po
index 621c947..f869721 100644
--- a/git-gui/po/zh_cn.po
+++ b/git-gui/po/zh_cn.po
@@ -3,46 +3,63 @@
# This file is distributed under the same license as the git-gui package.
# Xudong Guan <xudong.guan@gmail.com>, 2007.
#
+# Please use the following translation throughout the file for consistence:
+#
+# repository 版本库
+# commit 提交
+# revision 版本
+# branch 分支
+# tag 标签
+# annotation 标注
+# merge 合并
+# fast forward 快速合并(??)
+# stage 缓存 (译自 index/cache)
+# amend 修正
+# reset 复位
+#
+# 2008-01-06 Eric Miao <eric.y.miao@gmail.com>
+# FIXME: checkout 的标准翻译
+#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: git-gui\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-10-10 04:04-0400\n"
+"POT-Creation-Date: 2007-11-24 10:36+0100\n"
"PO-Revision-Date: 2007-07-21 01:23-0700\n"
-"Last-Translator: Xudong Guan <xudong.guan@gmail.com>\n"
+"Last-Translator: Eric Miao <eric.y.miao@gmail.com>\n"
"Language-Team: Chinese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: git-gui.sh:41 git-gui.sh:634 git-gui.sh:648 git-gui.sh:661 git-gui.sh:744
-#: git-gui.sh:763
+#: git-gui.sh:41 git-gui.sh:604 git-gui.sh:618 git-gui.sh:631 git-gui.sh:714
+#: git-gui.sh:733
msgid "git-gui: fatal error"
-msgstr ""
+msgstr "git-gui: 致命错误"
-#: git-gui.sh:595
+#: git-gui.sh:565
#, tcl-format
msgid "Invalid font specified in %s:"
-msgstr ""
+msgstr "%s 中指定的字体无效:"
-#: git-gui.sh:620
+#: git-gui.sh:590
msgid "Main Font"
-msgstr ""
+msgstr "主要字体"
-#: git-gui.sh:621
+#: git-gui.sh:591
msgid "Diff/Console Font"
-msgstr ""
+msgstr "Diff/控制终端字体"
-#: git-gui.sh:635
+#: git-gui.sh:605
msgid "Cannot find git in PATH."
-msgstr ""
+msgstr "PATH 中没有找到 git"
-#: git-gui.sh:662
+#: git-gui.sh:632
msgid "Cannot parse Git version string:"
-msgstr ""
+msgstr "无法解析 Git 的版本信息:"
-#: git-gui.sh:680
+#: git-gui.sh:650
#, tcl-format
msgid ""
"Git version cannot be determined.\n"
@@ -53,388 +70,386 @@ msgid ""
"\n"
"Assume '%s' is version 1.5.0?\n"
msgstr ""
+"无法确定 Git 的版本.\n"
+"\n"
+"%s 声明其版本为 '%s'.\n"
+"\n"
+"而 %s 需要 1.5.0 或这以后的 Git 版本.\n"
+"\n"
+"是否假定 '%s' 为版本 1.5.0?\n"
-#: git-gui.sh:853
+#: git-gui.sh:888
msgid "Git directory not found:"
-msgstr ""
+msgstr "Git 目录无法找到:"
-#: git-gui.sh:860
+#: git-gui.sh:895
msgid "Cannot move to top of working directory:"
-msgstr ""
+msgstr "无法移动到工作根目录:"
-#: git-gui.sh:867
+#: git-gui.sh:902
msgid "Cannot use funny .git directory:"
-msgstr ""
+msgstr "无法使用 .git 目录:"
-#: git-gui.sh:872
+#: git-gui.sh:907
msgid "No working directory"
-msgstr ""
+msgstr "没有工作目录"
-#: git-gui.sh:1019
+#: git-gui.sh:1054
msgid "Refreshing file status..."
-msgstr ""
+msgstr "更新文件状态..."
-#: git-gui.sh:1084
+#: git-gui.sh:1119
msgid "Scanning for modified files ..."
-msgstr ""
+msgstr "扫描修改过的文件 ..."
-#: git-gui.sh:1259 lib/browser.tcl:245
-#, fuzzy
+#: git-gui.sh:1294 lib/browser.tcl:245
msgid "Ready."
-msgstr "重做"
+msgstr "就绪"
-#: git-gui.sh:1525
+#: git-gui.sh:1560
msgid "Unmodified"
-msgstr ""
+msgstr "未修改"
-#: git-gui.sh:1527
+#: git-gui.sh:1562
msgid "Modified, not staged"
-msgstr ""
+msgstr "修改但未缓存"
-#: git-gui.sh:1528 git-gui.sh:1533
-#, fuzzy
+#: git-gui.sh:1563 git-gui.sh:1568
msgid "Staged for commit"
-msgstr "从本次提交移除"
+msgstr "缓存为提交"
-#: git-gui.sh:1529 git-gui.sh:1534
-#, fuzzy
+#: git-gui.sh:1564 git-gui.sh:1569
msgid "Portions staged for commit"
-msgstr "从本次提交移除"
+msgstr "部分缓存为提交"
-#: git-gui.sh:1530 git-gui.sh:1535
+#: git-gui.sh:1565 git-gui.sh:1570
msgid "Staged for commit, missing"
-msgstr ""
+msgstr "缓存为提交, 不存在"
-#: git-gui.sh:1532
+#: git-gui.sh:1567
msgid "Untracked, not staged"
-msgstr ""
+msgstr "未跟踪, 未缓存"
-#: git-gui.sh:1537
+#: git-gui.sh:1572
msgid "Missing"
-msgstr ""
+msgstr "不存在"
-#: git-gui.sh:1538
+#: git-gui.sh:1573
msgid "Staged for removal"
-msgstr ""
+msgstr "缓存为删除"
-#: git-gui.sh:1539
+#: git-gui.sh:1574
msgid "Staged for removal, still present"
-msgstr ""
+msgstr "缓存为删除, 但仍存在"
-#: git-gui.sh:1541 git-gui.sh:1542 git-gui.sh:1543 git-gui.sh:1544
+#: git-gui.sh:1576 git-gui.sh:1577 git-gui.sh:1578 git-gui.sh:1579
msgid "Requires merge resolution"
-msgstr ""
+msgstr "需要解决合并冲突"
-#: git-gui.sh:1579
+#: git-gui.sh:1614
msgid "Starting gitk... please wait..."
-msgstr ""
+msgstr "启动 gitk... 请等待..."
-#: git-gui.sh:1588
+#: git-gui.sh:1623
#, tcl-format
msgid ""
"Unable to start gitk:\n"
"\n"
"%s does not exist"
msgstr ""
+"无法启动 gitk:\n"
+"\n"
+"%s 不存在"
-#: git-gui.sh:1788 lib/choose_repository.tcl:32
+#: git-gui.sh:1823 lib/choose_repository.tcl:35
msgid "Repository"
-msgstr "版本树"
+msgstr "版本库(repository)"
-#: git-gui.sh:1789
+#: git-gui.sh:1824
msgid "Edit"
msgstr "编辑"
-#: git-gui.sh:1791 lib/choose_rev.tcl:560
+#: git-gui.sh:1826 lib/choose_rev.tcl:560
msgid "Branch"
-msgstr "分支"
+msgstr "分支(branch)"
-#: git-gui.sh:1794 lib/choose_rev.tcl:547
-#, fuzzy
+#: git-gui.sh:1829 lib/choose_rev.tcl:547
msgid "Commit@@noun"
-msgstr "提交"
+msgstr "提交(commit)"
-#: git-gui.sh:1797 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
+#: git-gui.sh:1832 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
msgid "Merge"
-msgstr "合并"
+msgstr "合并(merge)"
-#: git-gui.sh:1798 lib/choose_rev.tcl:556
-#, fuzzy
+#: git-gui.sh:1833 lib/choose_rev.tcl:556
msgid "Remote"
-msgstr "改名..."
+msgstr "远端(remote)"
-#: git-gui.sh:1807
+#: git-gui.sh:1842
msgid "Browse Current Branch's Files"
-msgstr "浏览当前分支文件"
+msgstr "浏览当前分支上的文件"
-#: git-gui.sh:1811
-#, fuzzy
+#: git-gui.sh:1846
msgid "Browse Branch Files..."
-msgstr "浏览当前分支文件"
+msgstr "浏览分支上的文件..."
-#: git-gui.sh:1816
+#: git-gui.sh:1851
msgid "Visualize Current Branch's History"
-msgstr "调用gitk显示当前分支"
+msgstr "图示当前分支的历史"
-#: git-gui.sh:1820
+#: git-gui.sh:1855
msgid "Visualize All Branch History"
-msgstr "调用gitk显示所有分支"
+msgstr "图示所有分支的历史"
-#: git-gui.sh:1827
-#, fuzzy, tcl-format
+#: git-gui.sh:1862
+#, tcl-format
msgid "Browse %s's Files"
-msgstr "浏览当前分支文件"
+msgstr "浏览 %s 上的文件"
-#: git-gui.sh:1829
-#, fuzzy, tcl-format
+#: git-gui.sh:1864
+#, tcl-format
msgid "Visualize %s's History"
-msgstr "调用gitk显示所有分支"
+msgstr "图示 %s 分支的历史"
-#: git-gui.sh:1834 lib/database.tcl:27 lib/database.tcl:67
+#: git-gui.sh:1869 lib/database.tcl:27 lib/database.tcl:67
msgid "Database Statistics"
-msgstr "数据库统计数据"
+msgstr "数据库统计信息"
-#: git-gui.sh:1837 lib/database.tcl:34
+#: git-gui.sh:1872 lib/database.tcl:34
msgid "Compress Database"
msgstr "压缩数据库"
-#: git-gui.sh:1840
+#: git-gui.sh:1875
msgid "Verify Database"
msgstr "验证数据库"
-#: git-gui.sh:1847 git-gui.sh:1851 git-gui.sh:1855 lib/shortcut.tcl:9
-#: lib/shortcut.tcl:45 lib/shortcut.tcl:84
+#: git-gui.sh:1882 git-gui.sh:1886 git-gui.sh:1890 lib/shortcut.tcl:7
+#: lib/shortcut.tcl:39 lib/shortcut.tcl:71
msgid "Create Desktop Icon"
msgstr "创建桌面图标"
-#: git-gui.sh:1860 lib/choose_repository.tcl:36 lib/choose_repository.tcl:95
+#: git-gui.sh:1895 lib/choose_repository.tcl:176 lib/choose_repository.tcl:184
msgid "Quit"
msgstr "退出"
-#: git-gui.sh:1867
+#: git-gui.sh:1902
msgid "Undo"
msgstr "撤销"
-#: git-gui.sh:1870
+#: git-gui.sh:1905
msgid "Redo"
msgstr "重做"
-#: git-gui.sh:1874 git-gui.sh:2366
+#: git-gui.sh:1909 git-gui.sh:2403
msgid "Cut"
msgstr "剪切"
-#: git-gui.sh:1877 git-gui.sh:2369 git-gui.sh:2440 git-gui.sh:2512
+#: git-gui.sh:1912 git-gui.sh:2406 git-gui.sh:2477 git-gui.sh:2549
#: lib/console.tcl:67
msgid "Copy"
msgstr "复制"
-#: git-gui.sh:1880 git-gui.sh:2372
+#: git-gui.sh:1915 git-gui.sh:2409
msgid "Paste"
msgstr "粘贴"
-#: git-gui.sh:1883 git-gui.sh:2375 lib/branch_delete.tcl:26
+#: git-gui.sh:1918 git-gui.sh:2412 lib/branch_delete.tcl:26
#: lib/remote_branch_delete.tcl:38
msgid "Delete"
msgstr "删除"
-#: git-gui.sh:1887 git-gui.sh:2379 git-gui.sh:2516 lib/console.tcl:69
+#: git-gui.sh:1922 git-gui.sh:2416 git-gui.sh:2553 lib/console.tcl:69
msgid "Select All"
msgstr "全选"
-#: git-gui.sh:1896
+#: git-gui.sh:1931
msgid "Create..."
msgstr "新建..."
-#: git-gui.sh:1902
+#: git-gui.sh:1937
msgid "Checkout..."
-msgstr "切换..."
+msgstr "Checkout..."
-#: git-gui.sh:1908
+#: git-gui.sh:1943
msgid "Rename..."
-msgstr "改名..."
+msgstr "更名..."
-#: git-gui.sh:1913 git-gui.sh:2012
+#: git-gui.sh:1948 git-gui.sh:2048
msgid "Delete..."
msgstr "删除..."
-#: git-gui.sh:1918
+#: git-gui.sh:1953
msgid "Reset..."
-msgstr "重置所有修动..."
+msgstr "复位(Reset)..."
-#: git-gui.sh:1930 git-gui.sh:2313
+#: git-gui.sh:1965 git-gui.sh:2350
msgid "New Commit"
-msgstr "新提交"
+msgstr "新建提交"
-#: git-gui.sh:1938 git-gui.sh:2320
+#: git-gui.sh:1973 git-gui.sh:2357
msgid "Amend Last Commit"
-msgstr "修订上次提交"
+msgstr "修正上次提交"
-#: git-gui.sh:1947 git-gui.sh:2280 lib/remote_branch_delete.tcl:99
+#: git-gui.sh:1982 git-gui.sh:2317 lib/remote_branch_delete.tcl:99
msgid "Rescan"
msgstr "重新扫描"
-#: git-gui.sh:1953
-#, fuzzy
+#: git-gui.sh:1988
msgid "Stage To Commit"
-msgstr "从本次提交移除"
+msgstr "缓存为提交"
-#: git-gui.sh:1958
-#, fuzzy
+#: git-gui.sh:1994
msgid "Stage Changed Files To Commit"
-msgstr "将被提交的修改"
+msgstr "缓存修改的文件为提交"
-#: git-gui.sh:1964
+#: git-gui.sh:2000
msgid "Unstage From Commit"
-msgstr "从本次提交移除"
+msgstr "从本次提交撤除"
-#: git-gui.sh:1969 lib/index.tcl:352
+#: git-gui.sh:2005 lib/index.tcl:393
msgid "Revert Changes"
-msgstr "恢复修改"
+msgstr "撤销修改"
-#: git-gui.sh:1976 git-gui.sh:2292 git-gui.sh:2390
+#: git-gui.sh:2012 git-gui.sh:2329 git-gui.sh:2427
msgid "Sign Off"
-msgstr "签名"
+msgstr "签名(Sign Off)"
-#: git-gui.sh:1980 git-gui.sh:2296
-#, fuzzy
+#: git-gui.sh:2016 git-gui.sh:2333
msgid "Commit@@verb"
msgstr "提交"
-#: git-gui.sh:1991
+#: git-gui.sh:2027
msgid "Local Merge..."
msgstr "本地合并..."
-#: git-gui.sh:1996
+#: git-gui.sh:2032
msgid "Abort Merge..."
-msgstr "取消合并..."
+msgstr "中止合并..."
-#: git-gui.sh:2008
+#: git-gui.sh:2044
msgid "Push..."
msgstr "上传..."
-#: git-gui.sh:2019 lib/choose_repository.tcl:41
+#: git-gui.sh:2055 lib/choose_repository.tcl:40
msgid "Apple"
msgstr "苹果"
-#: git-gui.sh:2022 git-gui.sh:2044 lib/about.tcl:13
-#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50
+#: git-gui.sh:2058 git-gui.sh:2080 lib/about.tcl:13
+#: lib/choose_repository.tcl:43 lib/choose_repository.tcl:49
#, tcl-format
msgid "About %s"
-msgstr "关于%s"
+msgstr "关于 %s"
-#: git-gui.sh:2026
+#: git-gui.sh:2062
msgid "Preferences..."
-msgstr ""
+msgstr "首选项..."
-#: git-gui.sh:2034 git-gui.sh:2558
+#: git-gui.sh:2070 git-gui.sh:2595
msgid "Options..."
msgstr "选项..."
-#: git-gui.sh:2040 lib/choose_repository.tcl:47
+#: git-gui.sh:2076 lib/choose_repository.tcl:46
msgid "Help"
msgstr "帮助"
-#: git-gui.sh:2081
+#: git-gui.sh:2117
msgid "Online Documentation"
msgstr "在线文档"
-#: git-gui.sh:2165
+#: git-gui.sh:2201
#, tcl-format
msgid "fatal: cannot stat path %s: No such file or directory"
-msgstr ""
+msgstr "致命错误: 无法获取路径 %s 的信息: 该文件或目录不存在"
-#: git-gui.sh:2198
+#: git-gui.sh:2234
msgid "Current Branch:"
msgstr "当前分支:"
-#: git-gui.sh:2219
-#, fuzzy
+#: git-gui.sh:2255
msgid "Staged Changes (Will Commit)"
-msgstr "将被提交的修改"
+msgstr "已缓存的改动 (将被提交)"
-#: git-gui.sh:2239
+#: git-gui.sh:2274
msgid "Unstaged Changes"
-msgstr ""
+msgstr "未缓存的改动"
-#: git-gui.sh:2286
+#: git-gui.sh:2323
msgid "Stage Changed"
-msgstr ""
+msgstr "缓存改动"
-#: git-gui.sh:2302 lib/transport.tcl:93 lib/transport.tcl:182
+#: git-gui.sh:2339 lib/transport.tcl:93 lib/transport.tcl:182
msgid "Push"
msgstr "上传"
-#: git-gui.sh:2332
+#: git-gui.sh:2369
msgid "Initial Commit Message:"
-msgstr "初始提交描述:"
+msgstr "初始的提交描述:"
-#: git-gui.sh:2333
+#: git-gui.sh:2370
msgid "Amended Commit Message:"
-msgstr "修订提交描述:"
+msgstr "修正的提交描述:"
-#: git-gui.sh:2334
+#: git-gui.sh:2371
msgid "Amended Initial Commit Message:"
-msgstr "修订初始提交描述:"
+msgstr "修正的初始提交描述:"
-#: git-gui.sh:2335
+#: git-gui.sh:2372
msgid "Amended Merge Commit Message:"
-msgstr "修订合并提交描述:"
+msgstr "修正的合并提交描述:"
-#: git-gui.sh:2336
+#: git-gui.sh:2373
msgid "Merge Commit Message:"
msgstr "合并提交描述:"
-#: git-gui.sh:2337
+#: git-gui.sh:2374
msgid "Commit Message:"
msgstr "提交描述:"
-#: git-gui.sh:2382 git-gui.sh:2520 lib/console.tcl:71
+#: git-gui.sh:2419 git-gui.sh:2557 lib/console.tcl:71
msgid "Copy All"
msgstr "全部复制"
-#: git-gui.sh:2406 lib/blame.tcl:104
+#: git-gui.sh:2443 lib/blame.tcl:104
msgid "File:"
-msgstr ""
+msgstr "文件:"
-#: git-gui.sh:2508
+#: git-gui.sh:2545
msgid "Refresh"
msgstr "刷新"
-#: git-gui.sh:2529
+#: git-gui.sh:2566
msgid "Apply/Reverse Hunk"
msgstr "应用/撤消此修改块"
-#: git-gui.sh:2535
+#: git-gui.sh:2572
msgid "Decrease Font Size"
msgstr "缩小字体"
-#: git-gui.sh:2539
+#: git-gui.sh:2576
msgid "Increase Font Size"
msgstr "放大字体"
-#: git-gui.sh:2544
+#: git-gui.sh:2581
msgid "Show Less Context"
-msgstr "显示更多diff上下文"
+msgstr "显示更少上下文"
-#: git-gui.sh:2551
+#: git-gui.sh:2588
msgid "Show More Context"
-msgstr "显示更少diff上下文"
+msgstr "显示更多上下文"
-#: git-gui.sh:2565
-#, fuzzy
+#: git-gui.sh:2602
msgid "Unstage Hunk From Commit"
-msgstr "从本次提交移除"
+msgstr "从提交中撤除修改块"
-#: git-gui.sh:2567
-#, fuzzy
+#: git-gui.sh:2604
msgid "Stage Hunk For Commit"
-msgstr "从本次提交移除"
+msgstr "缓存修改块为提交"
-#: git-gui.sh:2586
+#: git-gui.sh:2623
msgid "Initializing..."
-msgstr ""
+msgstr "初始化..."
-#: git-gui.sh:2677
+#: git-gui.sh:2718
#, tcl-format
msgid ""
"Possible environment issues exist.\n"
@@ -444,15 +459,22 @@ msgid ""
"by %s:\n"
"\n"
msgstr ""
+"可能存在环境变量的问题.\n"
+"\n"
+"由 %s 执行的 Git 子进程可能忽略下列环境变量:\n"
+"\n"
-#: git-gui.sh:2707
+#: git-gui.sh:2748
msgid ""
"\n"
"This is due to a known issue with the\n"
"Tcl binary distributed by Cygwin."
msgstr ""
+"\n"
+"这是由 Cygwin 发布的 Tcl 代码中一个\n"
+"已知问题所引起."
-#: git-gui.sh:2712
+#: git-gui.sh:2753
#, tcl-format
msgid ""
"\n"
@@ -462,206 +484,197 @@ msgid ""
"user.email settings into your personal\n"
"~/.gitconfig file.\n"
msgstr ""
+"\n"
+"\n"
+"%s 的一个很好的替代方案是将 user.name 以及\n"
+"user.email 设置放在你的个人 ~/.gitconfig 文件中.\n"
#: lib/about.tcl:25
msgid "git-gui - a graphical user interface for Git."
-msgstr ""
+msgstr "git-gui - Git 的图形化用户界面"
#: lib/blame.tcl:77
msgid "File Viewer"
-msgstr ""
+msgstr "文件查看器"
#: lib/blame.tcl:81
-#, fuzzy
msgid "Commit:"
-msgstr "提交"
+msgstr "提交:"
#: lib/blame.tcl:249
-#, fuzzy
msgid "Copy Commit"
-msgstr "提交"
+msgstr "复制提交"
#: lib/blame.tcl:369
#, tcl-format
msgid "Reading %s..."
-msgstr ""
+msgstr "读取 %s..."
#: lib/blame.tcl:473
msgid "Loading copy/move tracking annotations..."
-msgstr ""
+msgstr "装载复制/移动跟踪标注..."
#: lib/blame.tcl:493
msgid "lines annotated"
-msgstr ""
+msgstr "标注行"
#: lib/blame.tcl:674
msgid "Loading original location annotations..."
-msgstr ""
+msgstr "装载原始位置标注..."
#: lib/blame.tcl:677
msgid "Annotation complete."
-msgstr ""
+msgstr "标注完成."
#: lib/blame.tcl:731
msgid "Loading annotation..."
-msgstr ""
+msgstr "裝載标注..."
#: lib/blame.tcl:787
msgid "Author:"
-msgstr ""
+msgstr "作者:"
#: lib/blame.tcl:791
-#, fuzzy
msgid "Committer:"
-msgstr "提交"
+msgstr "提交者:"
#: lib/blame.tcl:796
msgid "Original File:"
-msgstr ""
+msgstr "原始文件:"
#: lib/blame.tcl:910
msgid "Originally By:"
-msgstr ""
+msgstr "最初由:"
#: lib/blame.tcl:916
msgid "In File:"
-msgstr ""
+msgstr "在文件:"
#: lib/blame.tcl:921
msgid "Copied Or Moved Here By:"
-msgstr ""
+msgstr "由复制或移动至此:"
#: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19
-#, fuzzy
msgid "Checkout Branch"
-msgstr "当前分支:"
+msgstr "Checkout 分支"
#: lib/branch_checkout.tcl:23
-#, fuzzy
msgid "Checkout"
-msgstr "切换..."
+msgstr "Checkout"
#: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
#: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:281
#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:172
#: lib/option.tcl:90 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97
msgid "Cancel"
-msgstr ""
+msgstr "取消"
#: lib/branch_checkout.tcl:32 lib/browser.tcl:286
msgid "Revision"
-msgstr ""
+msgstr "版本"
#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:202
-#, fuzzy
msgid "Options"
msgstr "选项..."
#: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92
msgid "Fetch Tracking Branch"
-msgstr ""
+msgstr "获取跟踪分支"
#: lib/branch_checkout.tcl:44
msgid "Detach From Local Branch"
-msgstr ""
+msgstr "从本地分支脱离"
#: lib/branch_create.tcl:22
-#, fuzzy
msgid "Create Branch"
-msgstr "当前分支:"
+msgstr "创建分支"
#: lib/branch_create.tcl:27
-#, fuzzy
msgid "Create New Branch"
-msgstr "当前分支:"
+msgstr "新建分支"
-#: lib/branch_create.tcl:31 lib/choose_repository.tcl:199
-#, fuzzy
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:375
msgid "Create"
-msgstr "新建..."
+msgstr "新建"
#: lib/branch_create.tcl:40
-#, fuzzy
msgid "Branch Name"
-msgstr "分支"
+msgstr "分支名"
#: lib/branch_create.tcl:43
msgid "Name:"
-msgstr ""
+msgstr "名字:"
#: lib/branch_create.tcl:58
msgid "Match Tracking Branch Name"
-msgstr ""
+msgstr "匹配跟踪分支名字"
#: lib/branch_create.tcl:66
msgid "Starting Revision"
-msgstr ""
+msgstr "起始版本"
#: lib/branch_create.tcl:72
msgid "Update Existing Branch:"
-msgstr ""
+msgstr "更新已有分支:"
#: lib/branch_create.tcl:75
msgid "No"
-msgstr ""
+msgstr "号码"
#: lib/branch_create.tcl:80
msgid "Fast Forward Only"
-msgstr ""
+msgstr "仅快速合并"
#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514
-#, fuzzy
msgid "Reset"
-msgstr "重置所有修动..."
+msgstr "复位"
#: lib/branch_create.tcl:97
msgid "Checkout After Creation"
-msgstr ""
+msgstr "在创建后Checkout"
#: lib/branch_create.tcl:131
msgid "Please select a tracking branch."
-msgstr ""
+msgstr "请选择某个跟踪分支."
#: lib/branch_create.tcl:140
#, tcl-format
msgid "Tracking branch %s is not a branch in the remote repository."
-msgstr ""
+msgstr "跟踪分支 %s 并不是远端版本库中的一个分支"
#: lib/branch_create.tcl:153 lib/branch_rename.tcl:86
msgid "Please supply a branch name."
-msgstr ""
+msgstr "请提供分支名字."
#: lib/branch_create.tcl:164 lib/branch_rename.tcl:106
#, tcl-format
msgid "'%s' is not an acceptable branch name."
-msgstr ""
+msgstr "'%s'不是一个可接受的分支名."
#: lib/branch_delete.tcl:15
-#, fuzzy
msgid "Delete Branch"
-msgstr "当前分支:"
+msgstr "删除分支"
#: lib/branch_delete.tcl:20
msgid "Delete Local Branch"
-msgstr ""
+msgstr "删除本地分支"
#: lib/branch_delete.tcl:37
-#, fuzzy
msgid "Local Branches"
-msgstr "分支"
+msgstr "本地分支"
#: lib/branch_delete.tcl:52
msgid "Delete Only If Merged Into"
-msgstr ""
+msgstr "仅在合并后删除"
#: lib/branch_delete.tcl:54
msgid "Always (Do not perform merge test.)"
-msgstr ""
+msgstr "总是合并 (不作合并测试.)"
#: lib/branch_delete.tcl:103
#, tcl-format
msgid "The following branches are not completely merged into %s:"
-msgstr ""
+msgstr "下列分支没有完全被合并到 %s:"
#: lib/branch_delete.tcl:115
msgid ""
@@ -669,6 +682,9 @@ msgid ""
"\n"
" Delete the selected branches?"
msgstr ""
+"恢复被删除的分支非常困难.\n"
+"\n"
+"是否要删除所选分支?"
#: lib/branch_delete.tcl:141
#, tcl-format
@@ -676,86 +692,84 @@ msgid ""
"Failed to delete branches:\n"
"%s"
msgstr ""
+"无法删除分支:\n"
+"%s"
#: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
-#, fuzzy
msgid "Rename Branch"
-msgstr "当前分支:"
+msgstr "更改分支名:"
#: lib/branch_rename.tcl:26
-#, fuzzy
msgid "Rename"
-msgstr "改名..."
+msgstr "更名..."
#: lib/branch_rename.tcl:36
-#, fuzzy
msgid "Branch:"
-msgstr "分支"
+msgstr "分支:"
#: lib/branch_rename.tcl:39
msgid "New Name:"
-msgstr ""
+msgstr "新名字:"
#: lib/branch_rename.tcl:75
msgid "Please select a branch to rename."
-msgstr ""
+msgstr "请选择分支更名."
#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179
#, tcl-format
msgid "Branch '%s' already exists."
-msgstr ""
+msgstr "分支 '%s' 已经存在."
#: lib/branch_rename.tcl:117
#, tcl-format
msgid "Failed to rename '%s'."
-msgstr ""
+msgstr "无法更名 '%s'."
#: lib/browser.tcl:17
msgid "Starting..."
-msgstr ""
+msgstr "开始..."
#: lib/browser.tcl:26
msgid "File Browser"
-msgstr ""
+msgstr "文件浏览器"
#: lib/browser.tcl:125 lib/browser.tcl:142
#, tcl-format
msgid "Loading %s..."
-msgstr ""
+msgstr "装载 %s..."
#: lib/browser.tcl:186
msgid "[Up To Parent]"
-msgstr ""
+msgstr "[上层目录]"
#: lib/browser.tcl:266 lib/browser.tcl:272
-#, fuzzy
msgid "Browse Branch Files"
-msgstr "浏览当前分支文件"
+msgstr "浏览分支文件"
-#: lib/browser.tcl:277 lib/choose_repository.tcl:215
-#: lib/choose_repository.tcl:305 lib/choose_repository.tcl:315
-#: lib/choose_repository.tcl:811
+#: lib/browser.tcl:277 lib/choose_repository.tcl:391
+#: lib/choose_repository.tcl:482 lib/choose_repository.tcl:492
+#: lib/choose_repository.tcl:989
msgid "Browse"
-msgstr ""
+msgstr "浏览"
#: lib/checkout_op.tcl:79
#, tcl-format
msgid "Fetching %s from %s"
-msgstr ""
+msgstr "获取 %s 自 %s"
#: lib/checkout_op.tcl:127
#, tcl-format
msgid "fatal: Cannot resolve %s"
-msgstr ""
+msgstr "致命错误: 无法解决 %s"
#: lib/checkout_op.tcl:140 lib/console.tcl:79 lib/database.tcl:31
msgid "Close"
-msgstr ""
+msgstr "关闭"
#: lib/checkout_op.tcl:169
#, tcl-format
msgid "Branch '%s' does not exist."
-msgstr ""
+msgstr "分支 '%s' 并不存在."
#: lib/checkout_op.tcl:206
#, tcl-format
@@ -765,20 +779,24 @@ msgid ""
"It cannot fast-forward to %s.\n"
"A merge is required."
msgstr ""
+"分支 '%s' 已经存在.\n"
+"\n"
+"无法快速合并到 %s.\n"
+"需要普通合并."
#: lib/checkout_op.tcl:220
#, tcl-format
msgid "Merge strategy '%s' not supported."
-msgstr ""
+msgstr "合并策略 '%s' 不支持."
#: lib/checkout_op.tcl:239
#, tcl-format
msgid "Failed to update '%s'."
-msgstr ""
+msgstr "无法更新 '%s'."
#: lib/checkout_op.tcl:251
msgid "Staging area (index) is already locked."
-msgstr ""
+msgstr "缓存区域 (index) 已被锁定."
#: lib/checkout_op.tcl:266
msgid ""
@@ -789,25 +807,31 @@ msgid ""
"\n"
"The rescan will be automatically started now.\n"
msgstr ""
+"最后一次扫描的状态和当前版本库状态不符.\n"
+"\n"
+"另一 Git 程序自上次扫描后修改了本版本库. 在修改当前分支之前需要重新做一次扫"
+"描.\n"
+"\n"
+"重新扫描将自动开始.\n"
#: lib/checkout_op.tcl:322
#, tcl-format
msgid "Updating working directory to '%s'..."
-msgstr ""
+msgstr "更新工作目录到 '%s'..."
#: lib/checkout_op.tcl:353
#, tcl-format
msgid "Aborted checkout of '%s' (file level merging is required)."
-msgstr ""
+msgstr "中止 '%s' 的 checkout 操作 (需要做文件级合并)."
#: lib/checkout_op.tcl:354
msgid "File level merge required."
-msgstr ""
+msgstr "需要文件级合并."
#: lib/checkout_op.tcl:358
#, tcl-format
msgid "Staying on branch '%s'."
-msgstr ""
+msgstr "停留在分支 '%s'."
#: lib/checkout_op.tcl:429
msgid ""
@@ -816,29 +840,32 @@ msgid ""
"If you wanted to be on a branch, create one now starting from 'This Detached "
"Checkout'."
msgstr ""
+"你不在某个本地分支上.\n"
+"\n"
+"如果你想位于某分支上, 从当前脱节的Checkout中创建一个新分支."
#: lib/checkout_op.tcl:446
-#, fuzzy, tcl-format
+#, tcl-format
msgid "Checked out '%s'."
-msgstr "切换..."
+msgstr "'%s' 已被 checkout"
#: lib/checkout_op.tcl:478
#, tcl-format
msgid "Resetting '%s' to '%s' will lose the following commits:"
-msgstr ""
+msgstr "复位 '%s' 到 '%s' 将导致下列提交的丢失:"
#: lib/checkout_op.tcl:500
msgid "Recovering lost commits may not be easy."
-msgstr ""
+msgstr "恢复丢失的提交是比较困难的."
#: lib/checkout_op.tcl:505
#, tcl-format
msgid "Reset '%s'?"
-msgstr ""
+msgstr "复位 '%s'?"
#: lib/checkout_op.tcl:510 lib/merge.tcl:164
msgid "Visualize"
-msgstr ""
+msgstr "图示"
#: lib/checkout_op.tcl:578
#, tcl-format
@@ -850,286 +877,301 @@ msgid ""
"\n"
"This should not have occurred. %s will now close and give up."
msgstr ""
+"无法设定当前分支.\n"
+"\n"
+"当前工作目录仅有部分被切换出, 我们已成功的更新了您的文件但是无法更新某个内部"
+"的Git文件.\n"
+"\n"
+"这本不该发生, %s 将关闭并放弃."
#: lib/choose_font.tcl:39
-#, fuzzy
msgid "Select"
-msgstr "全选"
+msgstr "选择"
#: lib/choose_font.tcl:53
msgid "Font Family"
-msgstr ""
+msgstr "字体族"
#: lib/choose_font.tcl:73
-#, fuzzy
msgid "Font Size"
-msgstr "缩小字体"
+msgstr "字体大小"
#: lib/choose_font.tcl:90
msgid "Font Example"
-msgstr ""
+msgstr "字体样例"
#: lib/choose_font.tcl:101
msgid ""
"This is example text.\n"
"If you like this text, it can be your font."
msgstr ""
+"这是样例文本.\n"
+"如果你喜欢, 你可以设置该字体."
-#: lib/choose_repository.tcl:25
+#: lib/choose_repository.tcl:27
msgid "Git Gui"
-msgstr ""
+msgstr "Git Gui"
-#: lib/choose_repository.tcl:69 lib/choose_repository.tcl:204
-#, fuzzy
+#: lib/choose_repository.tcl:80 lib/choose_repository.tcl:380
msgid "Create New Repository"
-msgstr "版本树"
+msgstr "创建新的版本库"
-#: lib/choose_repository.tcl:74 lib/choose_repository.tcl:291
-#, fuzzy
+#: lib/choose_repository.tcl:86
+msgid "New..."
+msgstr "新建..."
+
+#: lib/choose_repository.tcl:93 lib/choose_repository.tcl:468
msgid "Clone Existing Repository"
-msgstr "版本树"
+msgstr "克隆已有版本库"
-#: lib/choose_repository.tcl:79 lib/choose_repository.tcl:800
-#, fuzzy
+#: lib/choose_repository.tcl:99
+msgid "Clone..."
+msgstr "克隆..."
+
+#: lib/choose_repository.tcl:106 lib/choose_repository.tcl:978
msgid "Open Existing Repository"
-msgstr "版本树"
+msgstr "打开已有版本库"
-#: lib/choose_repository.tcl:91
-msgid "Next >"
-msgstr ""
+#: lib/choose_repository.tcl:112
+msgid "Open..."
+msgstr "打开..."
-#: lib/choose_repository.tcl:152
+#: lib/choose_repository.tcl:125
+msgid "Recent Repositories"
+msgstr "最近版本库"
+
+#: lib/choose_repository.tcl:131
+msgid "Open Recent Repository:"
+msgstr "打开最近版本库"
+
+#: lib/choose_repository.tcl:294
#, tcl-format
msgid "Location %s already exists."
-msgstr ""
+msgstr "位置 %s 已经存在."
-#: lib/choose_repository.tcl:158 lib/choose_repository.tcl:165
-#: lib/choose_repository.tcl:172
+#: lib/choose_repository.tcl:300 lib/choose_repository.tcl:307
+#: lib/choose_repository.tcl:314
#, tcl-format
msgid "Failed to create repository %s:"
-msgstr ""
+msgstr "无法创建版本库 %s:"
-#: lib/choose_repository.tcl:209 lib/choose_repository.tcl:309
+#: lib/choose_repository.tcl:385 lib/choose_repository.tcl:486
msgid "Directory:"
-msgstr ""
+msgstr "目录:"
-#: lib/choose_repository.tcl:238 lib/choose_repository.tcl:363
-#: lib/choose_repository.tcl:834
-#, fuzzy
+#: lib/choose_repository.tcl:415 lib/choose_repository.tcl:544
+#: lib/choose_repository.tcl:1013
msgid "Git Repository"
-msgstr "版本树"
+msgstr "Git 版本库"
-#: lib/choose_repository.tcl:253 lib/choose_repository.tcl:260
+#: lib/choose_repository.tcl:430 lib/choose_repository.tcl:437
#, tcl-format
msgid "Directory %s already exists."
-msgstr ""
+msgstr "目录 %s 已经存在."
-#: lib/choose_repository.tcl:265
+#: lib/choose_repository.tcl:442
#, tcl-format
msgid "File %s already exists."
-msgstr ""
+msgstr "文件 %s 已经存在."
-#: lib/choose_repository.tcl:286
+#: lib/choose_repository.tcl:463
msgid "Clone"
-msgstr ""
+msgstr "克隆"
-#: lib/choose_repository.tcl:299
+#: lib/choose_repository.tcl:476
msgid "URL:"
-msgstr ""
+msgstr "URL:"
-#: lib/choose_repository.tcl:319
+#: lib/choose_repository.tcl:496
msgid "Clone Type:"
-msgstr ""
+msgstr "克隆类型:"
-#: lib/choose_repository.tcl:325
+#: lib/choose_repository.tcl:502
msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
-msgstr ""
+msgstr "标准方式 (快速, 部分备份, 作硬连接)"
-#: lib/choose_repository.tcl:331
+#: lib/choose_repository.tcl:508
msgid "Full Copy (Slower, Redundant Backup)"
-msgstr ""
+msgstr "全部复制 (较慢, 做备份)"
-#: lib/choose_repository.tcl:337
+#: lib/choose_repository.tcl:514
msgid "Shared (Fastest, Not Recommended, No Backup)"
-msgstr ""
+msgstr "共享方式 (最快, 不推荐, 不做备份)"
-#: lib/choose_repository.tcl:369 lib/choose_repository.tcl:418
-#: lib/choose_repository.tcl:560 lib/choose_repository.tcl:630
-#: lib/choose_repository.tcl:840 lib/choose_repository.tcl:848
+#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
+#: lib/choose_repository.tcl:738 lib/choose_repository.tcl:808
+#: lib/choose_repository.tcl:1019 lib/choose_repository.tcl:1027
#, tcl-format
msgid "Not a Git repository: %s"
-msgstr ""
+msgstr "不是一个 Git 版本库: %s"
-#: lib/choose_repository.tcl:405
+#: lib/choose_repository.tcl:586
msgid "Standard only available for local repository."
-msgstr ""
+msgstr "标准方式仅当是本地版本库时有效."
-#: lib/choose_repository.tcl:409
+#: lib/choose_repository.tcl:590
msgid "Shared only available for local repository."
-msgstr ""
+msgstr "共享方式仅当是本地版本库时有效."
-#: lib/choose_repository.tcl:439
+#: lib/choose_repository.tcl:617
msgid "Failed to configure origin"
-msgstr ""
+msgstr "无法配置 origin"
-#: lib/choose_repository.tcl:451
+#: lib/choose_repository.tcl:629
msgid "Counting objects"
-msgstr ""
+msgstr "清点对象"
-#: lib/choose_repository.tcl:452
+#: lib/choose_repository.tcl:630
+#, fuzzy
msgid "buckets"
-msgstr ""
+msgstr "水桶??"
-#: lib/choose_repository.tcl:476
+#: lib/choose_repository.tcl:654
#, tcl-format
msgid "Unable to copy objects/info/alternates: %s"
-msgstr ""
+msgstr "无法复制 objects/info/alternates: %s"
-#: lib/choose_repository.tcl:512
+#: lib/choose_repository.tcl:690
#, tcl-format
msgid "Nothing to clone from %s."
-msgstr ""
+msgstr "没有东西可从 %s 克隆."
-#: lib/choose_repository.tcl:514 lib/choose_repository.tcl:728
-#: lib/choose_repository.tcl:740
+#: lib/choose_repository.tcl:692 lib/choose_repository.tcl:906
+#: lib/choose_repository.tcl:918
msgid "The 'master' branch has not been initialized."
-msgstr ""
+msgstr "'master'分支尚未初始化."
-#: lib/choose_repository.tcl:527
+#: lib/choose_repository.tcl:705
msgid "Hardlinks are unavailable. Falling back to copying."
-msgstr ""
+msgstr "硬连接不可用. 使用复制."
-#: lib/choose_repository.tcl:539
+#: lib/choose_repository.tcl:717
#, tcl-format
msgid "Cloning from %s"
-msgstr ""
+msgstr "从 %s 克隆"
-#: lib/choose_repository.tcl:570
-#, fuzzy
+#: lib/choose_repository.tcl:748
msgid "Copying objects"
-msgstr "压缩数据库"
+msgstr "复制 objects"
-#: lib/choose_repository.tcl:571
+#: lib/choose_repository.tcl:749
msgid "KiB"
-msgstr ""
+msgstr "KiB"
-#: lib/choose_repository.tcl:595
+#: lib/choose_repository.tcl:773
#, tcl-format
msgid "Unable to copy object: %s"
-msgstr ""
+msgstr "无法复制 object: %s"
-#: lib/choose_repository.tcl:605
+#: lib/choose_repository.tcl:783
msgid "Linking objects"
-msgstr ""
+msgstr "链接 objects"
-#: lib/choose_repository.tcl:606
+#: lib/choose_repository.tcl:784
msgid "objects"
-msgstr ""
+msgstr "objects"
-#: lib/choose_repository.tcl:614
+#: lib/choose_repository.tcl:792
#, tcl-format
msgid "Unable to hardlink object: %s"
-msgstr ""
+msgstr "无法硬链接 object: %s"
-#: lib/choose_repository.tcl:669
+#: lib/choose_repository.tcl:847
msgid "Cannot fetch branches and objects. See console output for details."
-msgstr ""
+msgstr "无法获取分支和对象. 请查看控制终端的输出."
-#: lib/choose_repository.tcl:680
+#: lib/choose_repository.tcl:858
msgid "Cannot fetch tags. See console output for details."
-msgstr ""
+msgstr "无法获取标签. 请查看控制终端的输出."
-#: lib/choose_repository.tcl:704
+#: lib/choose_repository.tcl:882
msgid "Cannot determine HEAD. See console output for details."
-msgstr ""
+msgstr "无法确定 HEAD. 请查看控制终端的输出."
-#: lib/choose_repository.tcl:713
+#: lib/choose_repository.tcl:891
#, tcl-format
msgid "Unable to cleanup %s"
-msgstr ""
+msgstr "无法清理 %s"
-#: lib/choose_repository.tcl:719
+#: lib/choose_repository.tcl:897
msgid "Clone failed."
-msgstr ""
+msgstr "克隆失败."
-#: lib/choose_repository.tcl:726
+#: lib/choose_repository.tcl:904
msgid "No default branch obtained."
-msgstr ""
+msgstr "没有获取缺省分支"
-#: lib/choose_repository.tcl:737
+#: lib/choose_repository.tcl:915
#, tcl-format
msgid "Cannot resolve %s as a commit."
-msgstr ""
+msgstr "无法解析 %s 为提交."
-#: lib/choose_repository.tcl:749
+#: lib/choose_repository.tcl:927
msgid "Creating working directory"
-msgstr ""
+msgstr "创建工作目录"
-#: lib/choose_repository.tcl:750 lib/index.tcl:15 lib/index.tcl:80
-#: lib/index.tcl:149
+#: lib/choose_repository.tcl:928 lib/index.tcl:65 lib/index.tcl:127
+#: lib/index.tcl:193
msgid "files"
-msgstr ""
+msgstr "文件"
-#: lib/choose_repository.tcl:779
+#: lib/choose_repository.tcl:957
msgid "Initial file checkout failed."
-msgstr ""
+msgstr "初始的文件checkout失败"
-#: lib/choose_repository.tcl:795
+#: lib/choose_repository.tcl:973
msgid "Open"
-msgstr ""
+msgstr "打开"
-#: lib/choose_repository.tcl:805
-#, fuzzy
+#: lib/choose_repository.tcl:983
msgid "Repository:"
-msgstr "版本树"
+msgstr "版本库"
-#: lib/choose_repository.tcl:854
+#: lib/choose_repository.tcl:1033
#, tcl-format
msgid "Failed to open repository %s:"
-msgstr ""
+msgstr "无法打开版本库 %s:"
#: lib/choose_rev.tcl:53
msgid "This Detached Checkout"
-msgstr ""
+msgstr "该脱节的Checkout"
#: lib/choose_rev.tcl:60
msgid "Revision Expression:"
-msgstr ""
+msgstr "版本表达式:"
#: lib/choose_rev.tcl:74
-#, fuzzy
msgid "Local Branch"
-msgstr "分支"
+msgstr "本地分支"
#: lib/choose_rev.tcl:79
-#, fuzzy
msgid "Tracking Branch"
-msgstr "当前分支:"
+msgstr "跟踪分支:"
#: lib/choose_rev.tcl:84 lib/choose_rev.tcl:537
msgid "Tag"
-msgstr ""
+msgstr "标签"
#: lib/choose_rev.tcl:317
#, tcl-format
msgid "Invalid revision: %s"
-msgstr ""
+msgstr "无效版本: %s"
#: lib/choose_rev.tcl:338
msgid "No revision selected."
-msgstr ""
+msgstr "没有选择版本."
#: lib/choose_rev.tcl:346
msgid "Revision expression is empty."
-msgstr ""
+msgstr "版本表达式为空."
#: lib/choose_rev.tcl:530
msgid "Updated"
-msgstr ""
+msgstr "已更新"
#: lib/choose_rev.tcl:558
msgid "URL"
-msgstr ""
+msgstr "URL"
#: lib/commit.tcl:9
msgid ""
@@ -1138,6 +1180,9 @@ msgid ""
"You are about to create the initial commit. There is no commit before this "
"to amend.\n"
msgstr ""
+"没有改动需要修正.\n"
+"\n"
+"你正在创建最初的提交. 在此之前没有提交可以修正.\n"
#: lib/commit.tcl:18
msgid ""
@@ -1147,18 +1192,22 @@ msgid ""
"completed. You cannot amend the prior commit unless you first abort the "
"current merge activity.\n"
msgstr ""
+"在合并时无法修正.\n"
+"\n"
+"你当前正在一次尚未完成的合并操作过程中. 除非中止当前合并活动,\n"
+"否则无法修正之前的提交.\n"
#: lib/commit.tcl:49
msgid "Error loading commit data for amend:"
-msgstr ""
+msgstr "为修正装载提交数据出错:"
#: lib/commit.tcl:76
msgid "Unable to obtain your identity:"
-msgstr ""
+msgstr "无法获知你的身份:"
#: lib/commit.tcl:81
msgid "Invalid GIT_COMMITTER_IDENT:"
-msgstr ""
+msgstr "无效的 GIT_COMMITTER_IDENT"
#: lib/commit.tcl:133
msgid ""
@@ -1169,6 +1218,12 @@ msgid ""
"\n"
"The rescan will be automatically started now.\n"
msgstr ""
+"最后一次扫描的状态和当前版本库状态不符.\n"
+"\n"
+"另一 Git 程序自上次扫描后修改了本版本库. 在修改当前分支之前需要重新做一次扫"
+"描.\n"
+"\n"
+"重新扫描将自动开始.\n"
#: lib/commit.tcl:154
#, tcl-format
@@ -1178,6 +1233,9 @@ msgid ""
"File %s has merge conflicts. You must resolve them and stage the file "
"before committing.\n"
msgstr ""
+"尚未合并的文件没有办法提交.\n"
+"\n"
+"文件 %s 有合并冲突, 你必须解决这些冲突并缓存该文件作提交.\n"
#: lib/commit.tcl:162
#, tcl-format
@@ -1186,6 +1244,9 @@ msgid ""
"\n"
"File %s cannot be committed by this program.\n"
msgstr ""
+"检测到未知文件状态 %s.\n"
+"\n"
+"文件 %s 无法由该程序提交.\n"
#: lib/commit.tcl:170
msgid ""
@@ -1193,6 +1254,9 @@ msgid ""
"\n"
"You must stage at least 1 file before you can commit.\n"
msgstr ""
+"没有需要提交的变动.\n"
+"\n"
+"提交前你必须首先缓存至少一个文件.\n"
#: lib/commit.tcl:183
msgid ""
@@ -1200,19 +1264,26 @@ msgid ""
"\n"
"A good commit message has the following format:\n"
"\n"
-"- First line: Describe in one sentance what you did.\n"
+"- First line: Describe in one sentence what you did.\n"
"- Second line: Blank\n"
"- Remaining lines: Describe why this change is good.\n"
msgstr ""
+"请提供一条提交信息.\n"
+"\n"
+"一条好的提交信息有下列格式:\n"
+"\n"
+"- 第一行: 一句话概括你做的修改.\n"
+"- 第二行: 空行\n"
+"- 剩余行: 请描述为什么你做的这些改动是好的.\n"
#: lib/commit.tcl:257
msgid "write-tree failed:"
-msgstr ""
+msgstr "write-tree 失败:"
#: lib/commit.tcl:275
#, tcl-format
msgid "Commit %s appears to be corrupt"
-msgstr ""
+msgstr "提交 %s 似乎已损坏"
#: lib/commit.tcl:279
msgid ""
@@ -1222,77 +1293,81 @@ msgid ""
"\n"
"A rescan will be automatically started now.\n"
msgstr ""
+"没有改动提交.\n"
+"\n"
+"该提交没有改动任何文件也不是一个合并提交.\n"
+"\n"
+"重新扫描将自动开始.\n"
#: lib/commit.tcl:286
msgid "No changes to commit."
-msgstr ""
+msgstr "没有改动要提交."
#: lib/commit.tcl:303
#, tcl-format
msgid "warning: Tcl does not support encoding '%s'."
-msgstr ""
+msgstr "警告: Tcl 不支持编码方式 '%s'."
#: lib/commit.tcl:317
msgid "commit-tree failed:"
-msgstr ""
+msgstr "commit-tree 失败:"
#: lib/commit.tcl:339
msgid "update-ref failed:"
-msgstr ""
+msgstr "update-ref 失败:"
#: lib/commit.tcl:430
#, tcl-format
msgid "Created commit %s: %s"
-msgstr ""
+msgstr "创建了 commit %s: %s"
#: lib/console.tcl:57
msgid "Working... please wait..."
-msgstr ""
+msgstr "工作中... 请等待..."
#: lib/console.tcl:183
msgid "Success"
-msgstr ""
+msgstr "成功"
#: lib/console.tcl:196
msgid "Error: Command Failed"
-msgstr ""
+msgstr "错误: 命令失败"
#: lib/database.tcl:43
msgid "Number of loose objects"
-msgstr ""
+msgstr "松散对象的数量"
#: lib/database.tcl:44
msgid "Disk space used by loose objects"
-msgstr ""
+msgstr "松散对象所使用的磁盘空间"
#: lib/database.tcl:45
msgid "Number of packed objects"
-msgstr ""
+msgstr "压缩对象数量"
#: lib/database.tcl:46
msgid "Number of packs"
-msgstr ""
+msgstr "压缩包数量"
#: lib/database.tcl:47
msgid "Disk space used by packed objects"
-msgstr ""
+msgstr "压缩对象所使用的磁盘空间"
#: lib/database.tcl:48
msgid "Packed objects waiting for pruning"
-msgstr ""
+msgstr "压缩对象等待清理"
#: lib/database.tcl:49
msgid "Garbage files"
-msgstr ""
+msgstr "垃圾文件"
#: lib/database.tcl:72
-#, fuzzy
msgid "Compressing the object database"
-msgstr "压缩数据库"
+msgstr "压缩对象数据库"
#: lib/database.tcl:83
msgid "Verifying the object database with fsck-objects"
-msgstr ""
+msgstr "使用 fsck-objects 验证对象数据库"
#: lib/database.tcl:108
#, tcl-format
@@ -1304,11 +1379,16 @@ msgid ""
"\n"
"Compress the database now?"
msgstr ""
+"该版本库当前约有 %i 个松散对象.\n"
+"\n"
+"为达到较优的性能,强烈建议你在松散对象多于 %i 时压缩数据库.\n"
+"\n"
+"现在就压缩数据库么?"
#: lib/date.tcl:25
#, tcl-format
msgid "Invalid date from Git: %s"
-msgstr ""
+msgstr "无效的日期: %s"
#: lib/diff.tcl:42
#, tcl-format
@@ -1323,80 +1403,107 @@ msgid ""
"A rescan will be automatically started to find other files which may have "
"the same state."
msgstr ""
+"未检测到改动.\n"
+"\n"
+"该文件的修改日期被另一个程序所更新, 但其内容并没有变化.\n"
+"\n"
+"对于类似情况的其他文件的重新扫描将自动开始."
#: lib/diff.tcl:81
-#, tcl-format
+#, fuzzy, tcl-format
msgid "Loading diff of %s..."
-msgstr ""
+msgstr "装载 %s 的 diff ..."
#: lib/diff.tcl:114 lib/diff.tcl:184
#, tcl-format
msgid "Unable to display %s"
-msgstr ""
+msgstr "无法显示 %s"
#: lib/diff.tcl:115
msgid "Error loading file:"
-msgstr ""
+msgstr "装载文件出错:"
#: lib/diff.tcl:122
msgid "Git Repository (subproject)"
-msgstr ""
+msgstr "Git 版本库 (子项目)"
#: lib/diff.tcl:134
msgid "* Binary file (not showing content)."
-msgstr ""
+msgstr "* 二进制文件 (不显示内容)."
#: lib/diff.tcl:185
msgid "Error loading diff:"
-msgstr ""
+msgstr "装载 diff 错误:"
#: lib/diff.tcl:302
msgid "Failed to unstage selected hunk."
-msgstr ""
+msgstr "无法将选择的代码段从缓存中删除."
#: lib/diff.tcl:309
msgid "Failed to stage selected hunk."
-msgstr ""
+msgstr "无法缓存所选代码段."
#: lib/error.tcl:12 lib/error.tcl:102
msgid "error"
-msgstr ""
+msgstr "错误"
#: lib/error.tcl:28
msgid "warning"
-msgstr ""
+msgstr "警告"
#: lib/error.tcl:81
msgid "You must correct the above errors before committing."
-msgstr ""
+msgstr "你必须在提交前修正上述错误."
-#: lib/index.tcl:241
-#, fuzzy, tcl-format
+#: lib/index.tcl:6
+msgid "Unable to unlock the index."
+msgstr "无法解锁缓存 (index)"
+
+#: lib/index.tcl:15
+msgid "Index Error"
+msgstr "缓存(Index)错误"
+
+#: lib/index.tcl:21
+msgid ""
+"Updating the Git index failed. A rescan will be automatically started to "
+"resynchronize git-gui."
+msgstr "更新 Git 缓存(Index)失败, 重新扫描将自动开始以重新同步 git-gui."
+
+#: lib/index.tcl:27
+msgid "Continue"
+msgstr "继续"
+
+#: lib/index.tcl:31
+msgid "Unlock Index"
+msgstr "解锁 Index"
+
+#: lib/index.tcl:282
+#, tcl-format
msgid "Unstaging %s from commit"
-msgstr "从本次提交移除"
+msgstr "从提交缓存中删除 %s"
-#: lib/index.tcl:285
+#: lib/index.tcl:326
#, tcl-format
msgid "Adding %s"
-msgstr ""
+msgstr "添加 %s"
-#: lib/index.tcl:340
-#, fuzzy, tcl-format
+#: lib/index.tcl:381
+#, tcl-format
msgid "Revert changes in file %s?"
-msgstr "恢复修改"
+msgstr "撤销文件 %s 中的改动?"
-#: lib/index.tcl:342
+#: lib/index.tcl:383
#, tcl-format
msgid "Revert changes in these %i files?"
-msgstr ""
+msgstr "撤销这些 (%i个) 文件的改动?"
-#: lib/index.tcl:348
+#: lib/index.tcl:389
msgid "Any unstaged changes will be permanently lost by the revert."
-msgstr ""
+msgstr "任何未缓存的改动将在这次撤销中永久丢失."
-#: lib/index.tcl:351
+#: lib/index.tcl:392
msgid "Do Nothing"
-msgstr ""
+msgstr "不做操作"
#: lib/merge.tcl:13
msgid ""
@@ -1404,6 +1511,9 @@ msgid ""
"\n"
"You must finish amending this commit before starting any type of merge.\n"
msgstr ""
+"修正时无法做合并.\n"
+"\n"
+"你必须完成对该提交的修正才能继续任何类型的合并操作.\n"
#: lib/merge.tcl:27
msgid ""
@@ -1414,6 +1524,12 @@ msgid ""
"\n"
"The rescan will be automatically started now.\n"
msgstr ""
+"最后一次扫描的状态和当前版本库状态不符.\n"
+"\n"
+"另一 Git 程序自上次扫描后修改了本版本库. 在修改当前分支之前需要重新做一次扫"
+"描.\n"
+"\n"
+"重新扫描将自动开始.\n"
#: lib/merge.tcl:44
#, tcl-format
@@ -1425,6 +1541,12 @@ msgid ""
"You must resolve them, stage the file, and commit to complete the current "
"merge. Only then can you begin another merge.\n"
msgstr ""
+"你正处在一个有冲突的合并操作中.\n"
+"\n"
+"文件 %s 有合并冲突.\n"
+"\n"
+"你必须解决这些冲突, 缓存该文件, 并提交来完成当前的合并.仅当这样后才能开始下一"
+"个合并操作.\n"
#: lib/merge.tcl:54
#, tcl-format
@@ -1436,6 +1558,12 @@ msgid ""
"You should complete the current commit before starting a merge. Doing so "
"will help you abort a failed merge, should the need arise.\n"
msgstr ""
+"你正处在一个改动当中.\n"
+"\n"
+"文件 %s 已被修改.\n"
+"\n"
+"你必须完成当前的提交后才能开始合并. 如果需要, 这么做将有助于"
+"中止一次失败的合并.\n"
#: lib/merge.tcl:106
#, tcl-format
@@ -1445,24 +1573,24 @@ msgstr ""
#: lib/merge.tcl:119
#, tcl-format
msgid "Merging %s and %s"
-msgstr ""
+msgstr "合并 %s 和 %s"
#: lib/merge.tcl:131
msgid "Merge completed successfully."
-msgstr ""
+msgstr "合并成功完成."
#: lib/merge.tcl:133
msgid "Merge failed. Conflict resolution is required."
-msgstr ""
+msgstr "合并失败. 需要解决冲突."
#: lib/merge.tcl:158
#, tcl-format
msgid "Merge Into %s"
-msgstr ""
+msgstr "合并到 %s"
#: lib/merge.tcl:177
msgid "Revision To Merge"
-msgstr ""
+msgstr "要合并的版本"
#: lib/merge.tcl:212
msgid ""
@@ -1470,6 +1598,9 @@ msgid ""
"\n"
"You must finish amending this commit.\n"
msgstr ""
+"修正操作中无法中止.\n"
+"\n"
+"你必须先完成本次修正操作.\n"
#: lib/merge.tcl:222
msgid ""
@@ -1479,6 +1610,11 @@ msgid ""
"\n"
"Continue with aborting the current merge?"
msgstr ""
+"中止合并?\n"
+"\n"
+"中止当前的合并操作将导致 *所有* 尚未提交的改动丢失.\n"
+"\n"
+"是否要继续中止当前的合并操作?"
#: lib/merge.tcl:228
msgid ""
@@ -1488,150 +1624,137 @@ msgid ""
"\n"
"Continue with resetting the current changes?"
msgstr ""
+"是否复位当前改动?\n"
+"\n"
+"复位当前的改动将导致 *所有* 未提交的改动丢失.\n"
+"\n"
+"是否要继续复位当前的改动?"
#: lib/merge.tcl:239
msgid "Aborting"
-msgstr ""
+msgstr "中止"
#: lib/merge.tcl:266
msgid "Abort failed."
-msgstr ""
+msgstr "中止失败"
#: lib/merge.tcl:268
msgid "Abort completed. Ready."
-msgstr ""
+msgstr "中止完成. 就绪."
#: lib/option.tcl:82
msgid "Restore Defaults"
-msgstr ""
+msgstr "恢复默认值"
#: lib/option.tcl:86
msgid "Save"
-msgstr ""
+msgstr "保存"
#: lib/option.tcl:96
-#, fuzzy, tcl-format
+#, tcl-format
msgid "%s Repository"
-msgstr "版本树"
+msgstr "%s 版本库"
#: lib/option.tcl:97
msgid "Global (All Repositories)"
-msgstr ""
+msgstr "全局 (所有版本库)"
#: lib/option.tcl:103
msgid "User Name"
-msgstr ""
+msgstr "用户名"
#: lib/option.tcl:104
msgid "Email Address"
-msgstr ""
+msgstr "Email 地址"
#: lib/option.tcl:106
-#, fuzzy
msgid "Summarize Merge Commits"
-msgstr "修订合并提交描述:"
+msgstr "概述合并提交:"
#: lib/option.tcl:107
msgid "Merge Verbosity"
-msgstr ""
+msgstr "合并冗余度"
#: lib/option.tcl:108
msgid "Show Diffstat After Merge"
-msgstr ""
+msgstr "在合并后显示 Diffstat"
#: lib/option.tcl:110
msgid "Trust File Modification Timestamps"
-msgstr ""
+msgstr "相信文件的改动时间"
#: lib/option.tcl:111
msgid "Prune Tracking Branches During Fetch"
-msgstr ""
+msgstr "获取时清除跟踪分支"
#: lib/option.tcl:112
msgid "Match Tracking Branches"
-msgstr ""
+msgstr "匹配跟踪分支"
#: lib/option.tcl:113
msgid "Number of Diff Context Lines"
-msgstr ""
+msgstr "Diff 上下文行数"
#: lib/option.tcl:114
msgid "New Branch Name Template"
-msgstr ""
+msgstr "新建分支命名模板"
#: lib/option.tcl:176
msgid "Change Font"
-msgstr ""
+msgstr "更改字体"
#: lib/option.tcl:180
#, tcl-format
msgid "Choose %s"
-msgstr ""
+msgstr "选择 %s"
#: lib/option.tcl:186
msgid "pt."
-msgstr ""
+msgstr "磅"
#: lib/option.tcl:200
msgid "Preferences"
-msgstr ""
+msgstr "首选项"
#: lib/option.tcl:235
msgid "Failed to completely save options:"
-msgstr ""
-
-#: lib/remote.tcl:165
-msgid "Prune from"
-msgstr ""
-
-#: lib/remote.tcl:170
-#, fuzzy
-msgid "Fetch from"
-msgstr "导入"
-
-#: lib/remote.tcl:213
-#, fuzzy
-msgid "Push to"
-msgstr "上传"
+msgstr "无法完全保存选项:"
#: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
msgid "Delete Remote Branch"
-msgstr ""
+msgstr "删除远端分支"
#: lib/remote_branch_delete.tcl:47
-#, fuzzy
msgid "From Repository"
-msgstr "版本树"
+msgstr "从版本库"
#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
msgid "Remote:"
-msgstr ""
+msgstr "Remote:"
#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
msgid "Arbitrary URL:"
-msgstr ""
+msgstr "任意 URL:"
#: lib/remote_branch_delete.tcl:84
-#, fuzzy
msgid "Branches"
msgstr "分支"
#: lib/remote_branch_delete.tcl:109
-#, fuzzy
msgid "Delete Only If"
-msgstr "删除"
+msgstr "删除仅当"
#: lib/remote_branch_delete.tcl:111
msgid "Merged Into:"
-msgstr ""
+msgstr "合并到"
#: lib/remote_branch_delete.tcl:119
msgid "Always (Do not perform merge checks)"
-msgstr ""
+msgstr "总是合并 (不作合并检查)"
#: lib/remote_branch_delete.tcl:152
msgid "A branch is required for 'Merged Into'."
-msgstr ""
+msgstr "'合并到' 需要指定某个分支"
#: lib/remote_branch_delete.tcl:184
#, tcl-format
@@ -1640,6 +1763,9 @@ msgid ""
"\n"
" - %s"
msgstr ""
+"下列分支没有被全部合并到 %s 中:\n"
+"\n"
+" - %s"
#: lib/remote_branch_delete.tcl:189
#, tcl-format
@@ -1647,10 +1773,11 @@ msgid ""
"One or more of the merge tests failed because you have not fetched the "
"necessary commits. Try fetching from %s first."
msgstr ""
+"由于没有获取到必要的提交,一个或多个合并测试失败。请尝试从 %s 处先获取。"
#: lib/remote_branch_delete.tcl:207
msgid "Please select one or more branches to delete."
-msgstr ""
+msgstr "请选择某个或多个分支来删除"
#: lib/remote_branch_delete.tcl:216
msgid ""
@@ -1658,112 +1785,108 @@ msgid ""
"\n"
"Delete the selected branches?"
msgstr ""
+"恢复被删除的分支非常困难.\n"
+"\n"
+"是否要删除所选分支?"
#: lib/remote_branch_delete.tcl:226
#, tcl-format
msgid "Deleting branches from %s"
-msgstr ""
+msgstr "从 %s 中删除分支"
#: lib/remote_branch_delete.tcl:286
msgid "No repository selected."
-msgstr ""
+msgstr "没有选择版本库"
#: lib/remote_branch_delete.tcl:291
#, tcl-format
msgid "Scanning %s..."
-msgstr ""
+msgstr "正在扫描 %s..."
-#: lib/shortcut.tcl:26 lib/shortcut.tcl:74
-msgid "Cannot write script:"
-msgstr ""
+#: lib/remote.tcl:165
+msgid "Prune from"
+msgstr "从..清除(prune)"
+
+#: lib/remote.tcl:170
+msgid "Fetch from"
+msgstr "从..获取(fetch)"
+
+#: lib/remote.tcl:213
+msgid "Push to"
+msgstr "上传到(push)"
+
+#: lib/shortcut.tcl:20 lib/shortcut.tcl:61
+msgid "Cannot write shortcut:"
+msgstr "无法修改快捷方式:"
-#: lib/shortcut.tcl:149
+#: lib/shortcut.tcl:136
msgid "Cannot write icon:"
-msgstr ""
+msgstr "无法修改图标:"
#: lib/status_bar.tcl:83
#, tcl-format
msgid "%s ... %*i of %*i %s (%3i%%)"
-msgstr ""
+msgstr "%s ... %*i of %*i %s (%3i%%)"
#: lib/transport.tcl:6
-#, fuzzy, tcl-format
+#, tcl-format
msgid "fetch %s"
-msgstr "导入"
+msgstr "获取(fetch)"
#: lib/transport.tcl:7
#, tcl-format
msgid "Fetching new changes from %s"
-msgstr ""
+msgstr "从 %s 处获取新的改动"
#: lib/transport.tcl:18
#, tcl-format
msgid "remote prune %s"
-msgstr ""
+msgstr "清除远端 %s"
#: lib/transport.tcl:19
#, tcl-format
msgid "Pruning tracking branches deleted from %s"
-msgstr ""
+msgstr "清除"
#: lib/transport.tcl:25 lib/transport.tcl:71
#, tcl-format
msgid "push %s"
-msgstr ""
+msgstr "上传 %s"
#: lib/transport.tcl:26
#, tcl-format
msgid "Pushing changes to %s"
-msgstr ""
+msgstr "上传改动到 %s"
#: lib/transport.tcl:72
#, tcl-format
msgid "Pushing %s %s to %s"
-msgstr ""
+msgstr "上传 %s %s 到 %s"
#: lib/transport.tcl:89
-#, fuzzy
msgid "Push Branches"
-msgstr "分支"
+msgstr "上传分支"
#: lib/transport.tcl:103
-#, fuzzy
msgid "Source Branches"
-msgstr "当前分支:"
+msgstr "源端分支:"
#: lib/transport.tcl:120
-#, fuzzy
msgid "Destination Repository"
-msgstr "版本树"
+msgstr "目标版本库"
#: lib/transport.tcl:158
msgid "Transfer Options"
-msgstr ""
+msgstr "传输选项"
#: lib/transport.tcl:160
msgid "Force overwrite existing branch (may discard changes)"
-msgstr ""
+msgstr "强制覆盖已有的分支 (可能会丢失改动)"
#: lib/transport.tcl:164
msgid "Use thin pack (for slow network connections)"
-msgstr ""
+msgstr "使用 thin pack (适用于低速网络连接)"
#: lib/transport.tcl:168
msgid "Include tags"
-msgstr ""
-
-#~ msgid "Add To Commit"
-#~ msgstr "添加到本次提交"
-
-#~ msgid "Add Existing To Commit"
-#~ msgstr "添加默认修改文件"
-
-#~ msgid "Unstaged Changes (Will Not Be Committed)"
-#~ msgstr "不被提交的修改"
-
-#~ msgid "Add Existing"
-#~ msgstr "添加默认修改文件"
-
-#, fuzzy
-#~ msgid "Push to %s..."
-#~ msgstr "上传..."
+msgstr "包含标签"
diff --git a/git-quiltimport.sh b/git-quiltimport.sh
index 233e5ea..7cd8f71 100755
--- a/git-quiltimport.sh
+++ b/git-quiltimport.sh
@@ -63,7 +63,23 @@ tmp_info="$tmp_dir/info"
commit=$(git rev-parse HEAD)
mkdir $tmp_dir || exit 2
-for patch_name in $(grep -v '^#' < "$QUILT_PATCHES/series" ); do
+while read patch_name level garbage
+do
+ case "$patch_name" in ''|'#'*) continue;; esac
+ case "$level" in
+ -p*) ;;
+ ''|'#'*)
+ level=;;
+ *)
+ echo "unable to parse patch level, ignoring it."
+ level=;;
+ esac
+ case "$garbage" in
+ ''|'#'*);;
+ *)
+ echo "trailing garbage found in series file: $garbage"
+ exit 1;;
+ esac
if ! [ -f "$QUILT_PATCHES/$patch_name" ] ; then
echo "$patch_name doesn't exist. Skipping."
continue
@@ -113,10 +129,10 @@ for patch_name in $(grep -v '^#' < "$QUILT_PATCHES/series" ); do
fi
if [ -z "$dry_run" ] ; then
- git apply --index -C1 "$tmp_patch" &&
+ git apply --index -C1 ${level:+"$level"} "$tmp_patch" &&
tree=$(git write-tree) &&
commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git commit-tree $tree -p $commit) &&
git update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4
fi
-done
+done <"$QUILT_PATCHES/series"
rm -rf $tmp_dir || exit 5
diff --git a/git-rebase.sh b/git-rebase.sh
index 452c5e7..ff66af3 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -18,8 +18,7 @@ original <branch> and remove the .dotest working files, use the command
git rebase --abort instead.
Note that if <branch> is not specified on the command line, the
-currently checked out branch is used. You must be in the top
-directory of your project to start (or continue) a rebase.
+currently checked out branch is used.
Example: git-rebase master~1 topic
diff --git a/git-svn.perl b/git-svn.perl
index 1195569..d8b38c9 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -522,7 +522,8 @@ sub cmd_dcommit {
}
sub cmd_find_rev {
- my $revision_or_hash = shift;
+ my $revision_or_hash = shift or die "SVN or git revision required ",
+ "as a command-line argument\n";
my $result;
if ($revision_or_hash =~ /^r\d+$/) {
my $head = shift;
diff --git a/git.c b/git.c
index 9cca81a..1e3eb10 100644
--- a/git.c
+++ b/git.c
@@ -334,6 +334,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "push", cmd_push, RUN_SETUP },
{ "read-tree", cmd_read_tree, RUN_SETUP },
{ "reflog", cmd_reflog, RUN_SETUP },
+ { "remote", cmd_remote, RUN_SETUP },
{ "repo-config", cmd_config },
{ "rerere", cmd_rerere, RUN_SETUP },
{ "reset", cmd_reset, RUN_SETUP },
diff --git a/hash.c b/hash.c
index d9ec82f..1cd4c9d 100644
--- a/hash.c
+++ b/hash.c
@@ -9,7 +9,7 @@
* the existing entry, or the empty slot if none existed. The caller
* can then look at the (*ptr) to see whether it existed or not.
*/
-static struct hash_table_entry *lookup_hash_entry(unsigned int hash, struct hash_table *table)
+static struct hash_table_entry *lookup_hash_entry(unsigned int hash, const struct hash_table *table)
{
unsigned int size = table->size, nr = hash % size;
struct hash_table_entry *array = table->array;
@@ -66,7 +66,7 @@ static void grow_hash_table(struct hash_table *table)
free(old_array);
}
-void *lookup_hash(unsigned int hash, struct hash_table *table)
+void *lookup_hash(unsigned int hash, const struct hash_table *table)
{
if (!table->array)
return NULL;
@@ -81,7 +81,7 @@ void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table)
return insert_hash_entry(hash, ptr, table);
}
-int for_each_hash(struct hash_table *table, int (*fn)(void *))
+int for_each_hash(const struct hash_table *table, int (*fn)(void *))
{
int sum = 0;
unsigned int i;
diff --git a/hash.h b/hash.h
index a8b0fbb..69e33a4 100644
--- a/hash.h
+++ b/hash.h
@@ -28,9 +28,9 @@ struct hash_table {
struct hash_table_entry *array;
};
-extern void *lookup_hash(unsigned int hash, struct hash_table *table);
+extern void *lookup_hash(unsigned int hash, const struct hash_table *table);
extern void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table);
-extern int for_each_hash(struct hash_table *table, int (*fn)(void *));
+extern int for_each_hash(const struct hash_table *table, int (*fn)(void *));
extern void free_hash(struct hash_table *table);
static inline void init_hash(struct hash_table *table)
diff --git a/ll-merge.c b/ll-merge.c
new file mode 100644
index 0000000..5ae7433
--- /dev/null
+++ b/ll-merge.c
@@ -0,0 +1,379 @@
+/*
+ * Low level 3-way in-core file merge.
+ *
+ * Copyright (c) 2007 Junio C Hamano
+ */
+
+#include "cache.h"
+#include "attr.h"
+#include "xdiff-interface.h"
+#include "run-command.h"
+#include "interpolate.h"
+#include "ll-merge.h"
+
+struct ll_merge_driver;
+
+typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
+ mmbuffer_t *result,
+ const char *path,
+ mmfile_t *orig,
+ mmfile_t *src1, const char *name1,
+ mmfile_t *src2, const char *name2,
+ int virtual_ancestor);
+
+struct ll_merge_driver {
+ const char *name;
+ const char *description;
+ ll_merge_fn fn;
+ const char *recursive;
+ struct ll_merge_driver *next;
+ char *cmdline;
+};
+
+/*
+ * Built-in low-levels
+ */
+static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
+ mmbuffer_t *result,
+ const char *path_unused,
+ mmfile_t *orig,
+ mmfile_t *src1, const char *name1,
+ mmfile_t *src2, const char *name2,
+ int virtual_ancestor)
+{
+ /*
+ * The tentative merge result is "ours" for the final round,
+ * or common ancestor for an internal merge. Still return
+ * "conflicted merge" status.
+ */
+ mmfile_t *stolen = virtual_ancestor ? orig : src1;
+
+ result->ptr = stolen->ptr;
+ result->size = stolen->size;
+ stolen->ptr = NULL;
+ return 1;
+}
+
+static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
+ mmbuffer_t *result,
+ const char *path_unused,
+ mmfile_t *orig,
+ mmfile_t *src1, const char *name1,
+ mmfile_t *src2, const char *name2,
+ int virtual_ancestor)
+{
+ xpparam_t xpp;
+
+ if (buffer_is_binary(orig->ptr, orig->size) ||
+ buffer_is_binary(src1->ptr, src1->size) ||
+ buffer_is_binary(src2->ptr, src2->size)) {
+ warning("Cannot merge binary files: %s vs. %s\n",
+ name1, name2);
+ return ll_binary_merge(drv_unused, result,
+ path_unused,
+ orig, src1, name1,
+ src2, name2,
+ virtual_ancestor);
+ }
+
+ memset(&xpp, 0, sizeof(xpp));
+ return xdl_merge(orig,
+ src1, name1,
+ src2, name2,
+ &xpp, XDL_MERGE_ZEALOUS,
+ result);
+}
+
+static int ll_union_merge(const struct ll_merge_driver *drv_unused,
+ mmbuffer_t *result,
+ const char *path_unused,
+ mmfile_t *orig,
+ mmfile_t *src1, const char *name1,
+ mmfile_t *src2, const char *name2,
+ int virtual_ancestor)
+{
+ char *src, *dst;
+ long size;
+ const int marker_size = 7;
+
+ int status = ll_xdl_merge(drv_unused, result, path_unused,
+ orig, src1, NULL, src2, NULL,
+ virtual_ancestor);
+ if (status <= 0)
+ return status;
+ size = result->size;
+ src = dst = result->ptr;
+ while (size) {
+ char ch;
+ if ((marker_size < size) &&
+ (*src == '<' || *src == '=' || *src == '>')) {
+ int i;
+ ch = *src;
+ for (i = 0; i < marker_size; i++)
+ if (src[i] != ch)
+ goto not_a_marker;
+ if (src[marker_size] != '\n')
+ goto not_a_marker;
+ src += marker_size + 1;
+ size -= marker_size + 1;
+ continue;
+ }
+ not_a_marker:
+ do {
+ ch = *src++;
+ *dst++ = ch;
+ size--;
+ } while (ch != '\n' && size);
+ }
+ result->size = dst - result->ptr;
+ return 0;
+}
+
+#define LL_BINARY_MERGE 0
+#define LL_TEXT_MERGE 1
+#define LL_UNION_MERGE 2
+static struct ll_merge_driver ll_merge_drv[] = {
+ { "binary", "built-in binary merge", ll_binary_merge },
+ { "text", "built-in 3-way text merge", ll_xdl_merge },
+ { "union", "built-in union merge", ll_union_merge },
+};
+
+static void create_temp(mmfile_t *src, char *path)
+{
+ int fd;
+
+ strcpy(path, ".merge_file_XXXXXX");
+ fd = xmkstemp(path);
+ if (write_in_full(fd, src->ptr, src->size) != src->size)
+ die("unable to write temp-file");
+ close(fd);
+}
+
+/*
+ * User defined low-level merge driver support.
+ */
+static int ll_ext_merge(const struct ll_merge_driver *fn,
+ mmbuffer_t *result,
+ const char *path,
+ mmfile_t *orig,
+ mmfile_t *src1, const char *name1,
+ mmfile_t *src2, const char *name2,
+ int virtual_ancestor)
+{
+ char temp[3][50];
+ char cmdbuf[2048];
+ struct interp table[] = {
+ { "%O" },
+ { "%A" },
+ { "%B" },
+ };
+ struct child_process child;
+ const char *args[20];
+ int status, fd, i;
+ struct stat st;
+
+ if (fn->cmdline == NULL)
+ die("custom merge driver %s lacks command line.", fn->name);
+
+ result->ptr = NULL;
+ result->size = 0;
+ create_temp(orig, temp[0]);
+ create_temp(src1, temp[1]);
+ create_temp(src2, temp[2]);
+
+ interp_set_entry(table, 0, temp[0]);
+ interp_set_entry(table, 1, temp[1]);
+ interp_set_entry(table, 2, temp[2]);
+
+ interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3);
+
+ memset(&child, 0, sizeof(child));
+ child.argv = args;
+ args[0] = "sh";
+ args[1] = "-c";
+ args[2] = cmdbuf;
+ args[3] = NULL;
+
+ status = run_command(&child);
+ if (status < -ERR_RUN_COMMAND_FORK)
+ ; /* failure in run-command */
+ else
+ status = -status;
+ fd = open(temp[1], O_RDONLY);
+ if (fd < 0)
+ goto bad;
+ if (fstat(fd, &st))
+ goto close_bad;
+ result->size = st.st_size;
+ result->ptr = xmalloc(result->size + 1);
+ if (read_in_full(fd, result->ptr, result->size) != result->size) {
+ free(result->ptr);
+ result->ptr = NULL;
+ result->size = 0;
+ }
+ close_bad:
+ close(fd);
+ bad:
+ for (i = 0; i < 3; i++)
+ unlink(temp[i]);
+ return status;
+}
+
+/*
+ * merge.default and merge.driver configuration items
+ */
+static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
+static const char *default_ll_merge;
+
+static int read_merge_config(const char *var, const char *value)
+{
+ struct ll_merge_driver *fn;
+ const char *ep, *name;
+ int namelen;
+
+ if (!strcmp(var, "merge.default")) {
+ if (value)
+ default_ll_merge = strdup(value);
+ return 0;
+ }
+
+ /*
+ * We are not interested in anything but "merge.<name>.variable";
+ * especially, we do not want to look at variables such as
+ * "merge.summary", "merge.tool", and "merge.verbosity".
+ */
+ if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5)
+ return 0;
+
+ /*
+ * Find existing one as we might be processing merge.<name>.var2
+ * after seeing merge.<name>.var1.
+ */
+ name = var + 6;
+ namelen = ep - name;
+ for (fn = ll_user_merge; fn; fn = fn->next)
+ if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
+ break;
+ if (!fn) {
+ fn = xcalloc(1, sizeof(struct ll_merge_driver));
+ fn->name = xmemdupz(name, namelen);
+ fn->fn = ll_ext_merge;
+ *ll_user_merge_tail = fn;
+ ll_user_merge_tail = &(fn->next);
+ }
+
+ ep++;
+
+ if (!strcmp("name", ep)) {
+ if (!value)
+ return error("%s: lacks value", var);
+ fn->description = strdup(value);
+ return 0;
+ }
+
+ if (!strcmp("driver", ep)) {
+ if (!value)
+ return error("%s: lacks value", var);
+ /*
+ * merge.<name>.driver specifies the command line:
+ *
+ * command-line
+ *
+ * The command-line will be interpolated with the following
+ * tokens and is given to the shell:
+ *
+ * %O - temporary file name for the merge base.
+ * %A - temporary file name for our version.
+ * %B - temporary file name for the other branches' version.
+ *
+ * The external merge driver should write the results in the
+ * file named by %A, and signal that it has done with zero exit
+ * status.
+ */
+ fn->cmdline = strdup(value);
+ return 0;
+ }
+
+ if (!strcmp("recursive", ep)) {
+ if (!value)
+ return error("%s: lacks value", var);
+ fn->recursive = strdup(value);
+ return 0;
+ }
+
+ return 0;
+}
+
+static void initialize_ll_merge(void)
+{
+ if (ll_user_merge_tail)
+ return;
+ ll_user_merge_tail = &ll_user_merge;
+ git_config(read_merge_config);
+}
+
+static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr)
+{
+ struct ll_merge_driver *fn;
+ const char *name;
+ int i;
+
+ initialize_ll_merge();
+
+ if (ATTR_TRUE(merge_attr))
+ return &ll_merge_drv[LL_TEXT_MERGE];
+ else if (ATTR_FALSE(merge_attr))
+ return &ll_merge_drv[LL_BINARY_MERGE];
+ else if (ATTR_UNSET(merge_attr)) {
+ if (!default_ll_merge)
+ return &ll_merge_drv[LL_TEXT_MERGE];
+ else
+ name = default_ll_merge;
+ }
+ else
+ name = merge_attr;
+
+ for (fn = ll_user_merge; fn; fn = fn->next)
+ if (!strcmp(fn->name, name))
+ return fn;
+
+ for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
+ if (!strcmp(ll_merge_drv[i].name, name))
+ return &ll_merge_drv[i];
+
+ /* default to the 3-way */
+ return &ll_merge_drv[LL_TEXT_MERGE];
+}
+
+static const char *git_path_check_merge(const char *path)
+{
+ static struct git_attr_check attr_merge_check;
+
+ if (!attr_merge_check.attr)
+ attr_merge_check.attr = git_attr("merge", 5);
+
+ if (git_checkattr(path, 1, &attr_merge_check))
+ return NULL;
+ return attr_merge_check.value;
+}
+
+int ll_merge(mmbuffer_t *result_buf,
+ const char *path,
+ mmfile_t *ancestor,
+ mmfile_t *ours, const char *our_label,
+ mmfile_t *theirs, const char *their_label,
+ int virtual_ancestor)
+{
+ const char *ll_driver_name;
+ const struct ll_merge_driver *driver;
+
+ ll_driver_name = git_path_check_merge(path);
+ driver = find_ll_merge_driver(ll_driver_name);
+
+ if (virtual_ancestor && driver->recursive)
+ driver = find_ll_merge_driver(driver->recursive);
+ return driver->fn(driver, result_buf, path,
+ ancestor,
+ ours, our_label,
+ theirs, their_label, virtual_ancestor);
+}
diff --git a/ll-merge.h b/ll-merge.h
new file mode 100644
index 0000000..5388422
--- /dev/null
+++ b/ll-merge.h
@@ -0,0 +1,15 @@
+/*
+ * Low level 3-way in-core file merge.
+ */
+
+#ifndef LL_MERGE_H
+#define LL_MERGE_H
+
+int ll_merge(mmbuffer_t *result_buf,
+ const char *path,
+ mmfile_t *ancestor,
+ mmfile_t *ours, const char *our_label,
+ mmfile_t *theirs, const char *their_label,
+ int virtual_ancestor);
+
+#endif
diff --git a/merge-tree.c b/merge-tree.c
index e083246..02fc10f 100644
--- a/merge-tree.c
+++ b/merge-tree.c
@@ -168,7 +168,13 @@ static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsi
return res;
}
-static void resolve(const char *base, struct name_entry *branch1, struct name_entry *result)
+static char *traverse_path(const struct traverse_info *info, const struct name_entry *n)
+{
+ char *path = xmalloc(traverse_path_len(info, n) + 1);
+ return make_traverse_path(path, info, n);
+}
+
+static void resolve(const struct traverse_info *info, struct name_entry *branch1, struct name_entry *result)
{
struct merge_list *orig, *final;
const char *path;
@@ -177,7 +183,7 @@ static void resolve(const char *base, struct name_entry *branch1, struct name_en
if (!branch1)
return;
- path = xstrdup(mkpath("%s%s", base, result->path));
+ path = traverse_path(info, result);
orig = create_entry(2, branch1->mode, branch1->sha1, path);
final = create_entry(0, result->mode, result->sha1, path);
@@ -186,9 +192,8 @@ static void resolve(const char *base, struct name_entry *branch1, struct name_en
add_merge_entry(final);
}
-static int unresolved_directory(const char *base, struct name_entry n[3])
+static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3])
{
- int baselen, pathlen;
char *newbase;
struct name_entry *p;
struct tree_desc t[3];
@@ -204,13 +209,7 @@ static int unresolved_directory(const char *base, struct name_entry n[3])
}
if (!S_ISDIR(p->mode))
return 0;
- baselen = strlen(base);
- pathlen = tree_entry_len(p->path, p->sha1);
- newbase = xmalloc(baselen + pathlen + 2);
- memcpy(newbase, base, baselen);
- memcpy(newbase + baselen, p->path, pathlen);
- memcpy(newbase + baselen + pathlen, "/", 2);
-
+ newbase = traverse_path(info, p);
buf0 = fill_tree_descriptor(t+0, n[0].sha1);
buf1 = fill_tree_descriptor(t+1, n[1].sha1);
buf2 = fill_tree_descriptor(t+2, n[2].sha1);
@@ -224,7 +223,7 @@ static int unresolved_directory(const char *base, struct name_entry n[3])
}
-static struct merge_list *link_entry(unsigned stage, const char *base, struct name_entry *n, struct merge_list *entry)
+static struct merge_list *link_entry(unsigned stage, const struct traverse_info *info, struct name_entry *n, struct merge_list *entry)
{
const char *path;
struct merge_list *link;
@@ -234,17 +233,17 @@ static struct merge_list *link_entry(unsigned stage, const char *base, struct na
if (entry)
path = entry->path;
else
- path = xstrdup(mkpath("%s%s", base, n->path));
+ path = traverse_path(info, n);
link = create_entry(stage, n->mode, n->sha1, path);
link->link = entry;
return link;
}
-static void unresolved(const char *base, struct name_entry n[3])
+static void unresolved(const struct traverse_info *info, struct name_entry n[3])
{
struct merge_list *entry = NULL;
- if (unresolved_directory(base, n))
+ if (unresolved_directory(info, n))
return;
/*
@@ -252,9 +251,9 @@ static void unresolved(const char *base, struct name_entry n[3])
* list has the stages in order - link_entry adds new
* links at the front.
*/
- entry = link_entry(3, base, n + 2, entry);
- entry = link_entry(2, base, n + 1, entry);
- entry = link_entry(1, base, n + 0, entry);
+ entry = link_entry(3, info, n + 2, entry);
+ entry = link_entry(2, info, n + 1, entry);
+ entry = link_entry(1, info, n + 0, entry);
add_merge_entry(entry);
}
@@ -288,36 +287,41 @@ static void unresolved(const char *base, struct name_entry n[3])
* The successful merge rules are the same as for the three-way merge
* in git-read-tree.
*/
-static void threeway_callback(int n, unsigned long mask, struct name_entry *entry, const char *base)
+static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info)
{
/* Same in both? */
if (same_entry(entry+1, entry+2)) {
if (entry[0].sha1) {
- resolve(base, NULL, entry+1);
- return;
+ resolve(info, NULL, entry+1);
+ return mask;
}
}
if (same_entry(entry+0, entry+1)) {
if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) {
- resolve(base, entry+1, entry+2);
- return;
+ resolve(info, entry+1, entry+2);
+ return mask;
}
}
if (same_entry(entry+0, entry+2)) {
if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) {
- resolve(base, NULL, entry+1);
- return;
+ resolve(info, NULL, entry+1);
+ return mask;
}
}
- unresolved(base, entry);
+ unresolved(info, entry);
+ return mask;
}
static void merge_trees(struct tree_desc t[3], const char *base)
{
- traverse_trees(3, t, base, threeway_callback);
+ struct traverse_info info;
+
+ setup_traverse_info(&info, base);
+ info.fn = threeway_callback;
+ traverse_trees(3, t, &info);
}
static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
diff --git a/parse-options.c b/parse-options.c
index b32c9ea..8e64316 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -259,6 +259,8 @@ int parse_options(int argc, const char **argv, const struct option *options,
const char *arg = args.argv[0];
if (*arg != '-' || !arg[1]) {
+ if (flags & PARSE_OPT_STOP_AT_NON_OPTION)
+ break;
args.out[args.cpidx++] = args.argv[0];
continue;
}
diff --git a/parse-options.h b/parse-options.h
index dc08078..1af62b0 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -19,6 +19,7 @@ enum parse_opt_type {
enum parse_opt_flags {
PARSE_OPT_KEEP_DASHDASH = 1,
+ PARSE_OPT_STOP_AT_NON_OPTION = 2,
};
enum parse_opt_option_flags {
diff --git a/path-list.c b/path-list.c
index 3d83b7b..92e5cf2 100644
--- a/path-list.c
+++ b/path-list.c
@@ -102,3 +102,33 @@ void print_path_list(const char *text, const struct path_list *p)
for (i = 0; i < p->nr; i++)
printf("%s:%p\n", p->items[i].path, p->items[i].util);
}
+
+struct path_list_item *path_list_append(const char *path, struct path_list *list)
+{
+ ALLOC_GROW(list->items, list->nr + 1, list->alloc);
+ list->items[list->nr].path =
+ list->strdup_paths ? xstrdup(path) : (char *)path;
+ return list->items + list->nr++;
+}
+
+static int cmp_items(const void *a, const void *b)
+{
+ const struct path_list_item *one = a;
+ const struct path_list_item *two = b;
+ return strcmp(one->path, two->path);
+}
+
+void sort_path_list(struct path_list *list)
+{
+ qsort(list->items, list->nr, sizeof(*list->items), cmp_items);
+}
+
+int unsorted_path_list_has_path(struct path_list *list, const char *path)
+{
+ int i;
+ for (i = 0; i < list->nr; i++)
+ if (!strcmp(path, list->items[i].path))
+ return 1;
+ return 0;
+}
+
diff --git a/path-list.h b/path-list.h
index 5931e2c..ca2cbba 100644
--- a/path-list.h
+++ b/path-list.h
@@ -13,10 +13,16 @@ struct path_list
};
void print_path_list(const char *text, const struct path_list *p);
+void path_list_clear(struct path_list *list, int free_util);
+/* Use these functions only on sorted lists: */
int path_list_has_path(const struct path_list *list, const char *path);
-void path_list_clear(struct path_list *list, int free_util);
struct path_list_item *path_list_insert(const char *path, struct path_list *list);
struct path_list_item *path_list_lookup(const char *path, struct path_list *list);
+/* Use these functions only on unsorted lists: */
+struct path_list_item *path_list_append(const char *path, struct path_list *list);
+void sort_path_list(struct path_list *list);
+int unsorted_path_list_has_path(struct path_list *list, const char *path);
+
#endif /* PATH_LIST_H */
diff --git a/read-cache.c b/read-cache.c
index 657f0c5..a92b25b 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -255,13 +255,13 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
return changed;
}
-static int is_racy_timestamp(struct index_state *istate, struct cache_entry *ce)
+static int is_racy_timestamp(const struct index_state *istate, struct cache_entry *ce)
{
return (istate->timestamp &&
((unsigned int)istate->timestamp) <= ce->ce_mtime);
}
-int ie_match_stat(struct index_state *istate,
+int ie_match_stat(const struct index_state *istate,
struct cache_entry *ce, struct stat *st,
unsigned int options)
{
@@ -304,7 +304,7 @@ int ie_match_stat(struct index_state *istate,
return changed;
}
-int ie_modified(struct index_state *istate,
+int ie_modified(const struct index_state *istate,
struct cache_entry *ce, struct stat *st, unsigned int options)
{
int changed, changed_fs;
@@ -351,6 +351,41 @@ int base_name_compare(const char *name1, int len1, int mode1,
return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
}
+/*
+ * df_name_compare() is identical to base_name_compare(), except it
+ * compares conflicting directory/file entries as equal. Note that
+ * while a directory name compares as equal to a regular file, they
+ * then individually compare _differently_ to a filename that has
+ * a dot after the basename (because '\0' < '.' < '/').
+ *
+ * This is used by routines that want to traverse the git namespace
+ * but then handle conflicting entries together when possible.
+ */
+int df_name_compare(const char *name1, int len1, int mode1,
+ const char *name2, int len2, int mode2)
+{
+ int len = len1 < len2 ? len1 : len2, cmp;
+ unsigned char c1, c2;
+
+ cmp = memcmp(name1, name2, len);
+ if (cmp)
+ return cmp;
+ /* Directories and files compare equal (same length, same name) */
+ if (len1 == len2)
+ return 0;
+ c1 = name1[len];
+ if (!c1 && S_ISDIR(mode1))
+ c1 = '/';
+ c2 = name2[len];
+ if (!c2 && S_ISDIR(mode2))
+ c2 = '/';
+ if (c1 == '/' && !c2)
+ return 0;
+ if (c2 == '/' && !c1)
+ return 0;
+ return c1 - c2;
+}
+
int cache_name_compare(const char *name1, int flags1, const char *name2, int flags2)
{
int len1 = flags1 & CE_NAMEMASK;
@@ -377,7 +412,7 @@ int cache_name_compare(const char *name1, int flags1, const char *name2, int fla
return 0;
}
-int index_name_pos(struct index_state *istate, const char *name, int namelen)
+int index_name_pos(const struct index_state *istate, const char *name, int namelen)
{
int first, last;
@@ -1166,7 +1201,7 @@ int discard_index(struct index_state *istate)
return 0;
}
-int unmerged_index(struct index_state *istate)
+int unmerged_index(const struct index_state *istate)
{
int i;
for (i = 0; i < istate->cache_nr; i++) {
@@ -1311,7 +1346,7 @@ static int ce_write_entry(SHA_CTX *c, int fd, struct cache_entry *ce)
return ce_write(c, fd, ondisk, size);
}
-int write_index(struct index_state *istate, int newfd)
+int write_index(const struct index_state *istate, int newfd)
{
SHA_CTX c;
struct cache_header hdr;
diff --git a/remote.c b/remote.c
index 7e19372..f3f7375 100644
--- a/remote.c
+++ b/remote.c
@@ -357,7 +357,8 @@ static int handle_config(const char *key, const char *value)
remote->fetch_tags = -1;
} else if (!strcmp(subkey, ".proxy")) {
remote->http_proxy = xstrdup(value);
- }
+ } else if (!strcmp(subkey, ".skipdefaultupdate"))
+ remote->skip_default_update = 1;
return 0;
}
diff --git a/remote.h b/remote.h
index 0f6033f..f1dedf1 100644
--- a/remote.h
+++ b/remote.h
@@ -25,6 +25,7 @@ struct remote {
* 2 to always fetch tags
*/
int fetch_tags;
+ int skip_default_update;
const char *receivepack;
const char *uploadpack;
diff --git a/sha1_name.c b/sha1_name.c
index 8358ba2..8b6c76f 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -422,6 +422,37 @@ static int get_nth_ancestor(const char *name, int len,
return 0;
}
+struct object *peel_to_type(const char *name, int namelen,
+ struct object *o, enum object_type expected_type)
+{
+ if (name && !namelen)
+ namelen = strlen(name);
+ if (!o) {
+ unsigned char sha1[20];
+ if (get_sha1_1(name, namelen, sha1))
+ return NULL;
+ o = parse_object(sha1);
+ }
+ while (1) {
+ if (!o || (!o->parsed && !parse_object(o->sha1)))
+ return NULL;
+ if (o->type == expected_type)
+ return o;
+ if (o->type == OBJ_TAG)
+ o = ((struct tag*) o)->tagged;
+ else if (o->type == OBJ_COMMIT)
+ o = &(((struct commit *) o)->tree->object);
+ else {
+ if (name)
+ error("%.*s: expected %s type, but the object "
+ "dereferences to %s type",
+ namelen, name, typename(expected_type),
+ typename(o->type));
+ return NULL;
+ }
+ }
+}
+
static int peel_onion(const char *name, int len, unsigned char *sha1)
{
unsigned char outer[20];
@@ -473,32 +504,17 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
hashcpy(sha1, o->sha1);
}
else {
- /* At this point, the syntax look correct, so
+ /*
+ * At this point, the syntax look correct, so
* if we do not get the needed object, we should
* barf.
*/
-
- while (1) {
- if (!o || (!o->parsed && !parse_object(o->sha1)))
- return -1;
- if (o->type == expected_type) {
- hashcpy(sha1, o->sha1);
- return 0;
- }
- if (o->type == OBJ_TAG)
- o = ((struct tag*) o)->tagged;
- else if (o->type == OBJ_COMMIT)
- o = &(((struct commit *) o)->tree->object);
- else
- return error("%.*s: expected %s type, but the object dereferences to %s type",
- len, name, typename(expected_type),
- typename(o->type));
- if (!o)
- return -1;
- if (!o->parsed)
- if (!parse_object(o->sha1))
- return -1;
+ o = peel_to_type(name, len, o, expected_type);
+ if (o) {
+ hashcpy(sha1, o->sha1);
+ return 0;
}
+ return -1;
}
return 0;
}
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index cb86029..8fc39d7 100755
--- a/t/t0021-conversion.sh
+++ b/t/t0021-conversion.sh
@@ -5,7 +5,9 @@ test_description='blob conversion via gitattributes'
. ./test-lib.sh
cat <<\EOF >rot13.sh
-tr '[a-zA-Z]' '[n-za-mN-ZA-M]'
+tr \
+ 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \
+ 'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
EOF
chmod +x rot13.sh
diff --git a/t/t1005-read-tree-reset.sh b/t/t1005-read-tree-reset.sh
index f1b1216..8c45564 100755
--- a/t/t1005-read-tree-reset.sh
+++ b/t/t1005-read-tree-reset.sh
@@ -21,7 +21,7 @@ test_expect_success 'setup' '
git commit -m two
'
-test_expect_failure 'reset should work' '
+test_expect_success 'reset should work' '
git read-tree -u --reset HEAD^ &&
git ls-files >actual &&
diff -u expect actual
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
index 24476be..73f830d 100755
--- a/t/t1410-reflog.sh
+++ b/t/t1410-reflog.sh
@@ -202,22 +202,4 @@ test_expect_success 'delete' '
'
-test_expect_success 'prune --expire' '
-
- before=$(git count-objects | sed "s/ .*//") &&
- BLOB=$(echo aleph | git hash-object -w --stdin) &&
- BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
- test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
- test -f $BLOB_FILE &&
- git reset --hard &&
- git prune --expire=1.hour.ago &&
- test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
- test -f $BLOB_FILE &&
- test-chmtime -86500 $BLOB_FILE &&
- git prune --expire 1.day &&
- test $before = $(git count-objects | sed "s/ .*//") &&
- ! test -f $BLOB_FILE
-
-'
-
test_done
diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh
index 6560af7..47090c4 100644
--- a/t/t5304-prune.sh
+++ b/t/t5304-prune.sh
@@ -29,4 +29,53 @@ test_expect_success 'prune stale packs' '
'
+test_expect_success 'prune --expire' '
+
+ before=$(git count-objects | sed "s/ .*//") &&
+ BLOB=$(echo aleph | git hash-object -w --stdin) &&
+ BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
+ test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
+ test -f $BLOB_FILE &&
+ git prune --expire=1.hour.ago &&
+ test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
+ test -f $BLOB_FILE &&
+ test-chmtime -86500 $BLOB_FILE &&
+ git prune --expire 1.day &&
+ test $before = $(git count-objects | sed "s/ .*//") &&
+ ! test -f $BLOB_FILE
+
+'
+
+test_expect_success 'gc: implicit prune --expire' '
+
+ before=$(git count-objects | sed "s/ .*//") &&
+ BLOB=$(echo aleph_0 | git hash-object -w --stdin) &&
+ BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
+ test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
+ test -f $BLOB_FILE &&
+ test-chmtime -$((86400*14-30)) $BLOB_FILE &&
+ git gc &&
+ test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
+ test -f $BLOB_FILE &&
+ test-chmtime -$((86400*14+1)) $BLOB_FILE &&
+ git gc &&
+ test $before = $(git count-objects | sed "s/ .*//") &&
+ ! test -f $BLOB_FILE
+
+'
+
+test_expect_success 'gc: refuse to start with invalid gc.pruneExpire' '
+
+ git config gc.pruneExpire invalid &&
+ test_must_fail git gc
+
+'
+
+test_expect_success 'gc: start with ok gc.pruneExpire' '
+
+ git config gc.pruneExpire 2.days.ago &&
+ git gc
+
+'
+
test_done
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 4fc62f5..2822a65 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -10,10 +10,12 @@ setup_repository () {
git init &&
>file &&
git add file &&
+ test_tick &&
git commit -m "Initial" &&
git checkout -b side &&
>elif &&
git add elif &&
+ test_tick &&
git commit -m "Second" &&
git checkout master
)
@@ -78,6 +80,7 @@ test_expect_success 'add another remote' '
test_expect_success 'remove remote' '
(
cd test &&
+ git symbolic-ref refs/remotes/second/HEAD refs/remotes/second/master &&
git remote rm second
)
'
@@ -94,4 +97,144 @@ test_expect_success 'remove remote' '
)
'
+cat > test/expect << EOF
+* remote origin
+ URL: $(pwd)/one/.git
+ Remote branch merged with 'git pull' while on branch master
+ master
+ New remote branch (next fetch will store in remotes/origin)
+ master
+ Tracked remote branches
+ side master
+EOF
+
+test_expect_success 'show' '
+ (cd test &&
+ git config --add remote.origin.fetch \
+ refs/heads/master:refs/heads/upstream &&
+ git fetch &&
+ git branch -d -r origin/master &&
+ (cd ../one &&
+ echo 1 > file &&
+ test_tick &&
+ git commit -m update file) &&
+ git remote show origin > output &&
+ git diff expect output)
+'
+
+test_expect_success 'prune' '
+ (cd one &&
+ git branch -m side side2) &&
+ (cd test &&
+ git fetch origin &&
+ git remote prune origin &&
+ git rev-parse refs/remotes/origin/side2 &&
+ ! git rev-parse refs/remotes/origin/side)
+'
+
+test_expect_success 'add --mirror && prune' '
+ (mkdir mirror &&
+ cd mirror &&
+ git init &&
+ git remote add --mirror -f origin ../one) &&
+ (cd one &&
+ git branch -m side2 side) &&
+ (cd mirror &&
+ git rev-parse --verify refs/heads/side2 &&
+ ! git rev-parse --verify refs/heads/side &&
+ git fetch origin &&
+ git remote prune origin &&
+ ! git rev-parse --verify refs/heads/side2 &&
+ git rev-parse --verify refs/heads/side)
+'
+
+cat > one/expect << EOF
+ apis/master
+ apis/side
+ drosophila/another
+ drosophila/master
+ drosophila/side
+EOF
+
+test_expect_success 'update' '
+
+ (cd one &&
+ git remote add drosophila ../two &&
+ git remote add apis ../mirror &&
+ git remote update &&
+ git branch -r > output &&
+ git diff expect output)
+
+'
+
+cat > one/expect << EOF
+ drosophila/another
+ drosophila/master
+ drosophila/side
+ manduca/master
+ manduca/side
+ megaloprepus/master
+ megaloprepus/side
+EOF
+
+test_expect_success 'update with arguments' '
+
+ (cd one &&
+ for b in $(git branch -r)
+ do
+ git branch -r -d $b || break
+ done &&
+ git remote add manduca ../mirror &&
+ git remote add megaloprepus ../mirror &&
+ git config remotes.phobaeticus "drosophila megaloprepus" &&
+ git config remotes.titanus manduca &&
+ git remote update phobaeticus titanus &&
+ git branch -r > output &&
+ git diff expect output)
+
+'
+
+cat > one/expect << EOF
+ apis/master
+ apis/side
+ manduca/master
+ manduca/side
+ megaloprepus/master
+ megaloprepus/side
+EOF
+
+test_expect_success 'update default' '
+
+ (cd one &&
+ for b in $(git branch -r)
+ do
+ git branch -r -d $b || break
+ done &&
+ git config remote.drosophila.skipDefaultUpdate true &&
+ git remote update default &&
+ git branch -r > output &&
+ git diff expect output)
+
+'
+
+cat > one/expect << EOF
+ drosophila/another
+ drosophila/master
+ drosophila/side
+EOF
+
+test_expect_success 'update default (overridden, with funny whitespace)' '
+
+ (cd one &&
+ for b in $(git branch -r)
+ do
+ git branch -r -d $b || break
+ done &&
+ git config remotes.default "$(printf "\t drosophila \n")" &&
+ git remote update default &&
+ git branch -r > output &&
+ git diff expect output)
+
+'
+
test_done
diff --git a/t/t6031-merge-recursive.sh b/t/t6031-merge-recursive.sh
new file mode 100755
index 0000000..5bb6b93
--- /dev/null
+++ b/t/t6031-merge-recursive.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+test_description='merge-recursive: handle file mode'
+. ./test-lib.sh
+
+test_expect_success 'mode change in one branch: keep changed version' '
+ : >file1 &&
+ git add file1 &&
+ git commit -m initial &&
+ git checkout -b a1 master &&
+ : >dummy &&
+ git add dummy &&
+ git commit -m a &&
+ git checkout -b b1 master &&
+ chmod +x file1 &&
+ git add file1 &&
+ git commit -m b1 &&
+ git checkout a1 &&
+ git merge-recursive master -- a1 b1 &&
+ test -x file1
+'
+
+test_expect_success 'mode change in both branches: expect conflict' '
+ git reset --hard HEAD &&
+ git checkout -b a2 master &&
+ : >file2 &&
+ H=$(git hash-object file2) &&
+ chmod +x file2 &&
+ git add file2 &&
+ git commit -m a2 &&
+ git checkout -b b2 master &&
+ : >file2 &&
+ git add file2 &&
+ git commit -m b2 &&
+ git checkout a2 &&
+ (
+ git merge-recursive master -- a2 b2
+ test $? = 1
+ ) &&
+ git ls-files -u >actual &&
+ (
+ echo "100755 $H 2 file2"
+ echo "100644 $H 3 file2"
+ ) >expect &&
+ diff -u actual expect &&
+ test -x file2
+'
+
+test_done
diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh
index c1cec55..6a74b3a 100755
--- a/t/t7005-editor.sh
+++ b/t/t7005-editor.sh
@@ -89,6 +89,33 @@ do
'
done
+test_expect_success 'editor with a space' '
+
+ if echo "echo space > \"\$1\"" > "e space.sh"
+ then
+ chmod a+x "e space.sh" &&
+ GIT_EDITOR="./e\ space.sh" git commit --amend &&
+ test space = "$(git show -s --pretty=format:%s)"
+ else
+ say "Skipping; FS does not support spaces in filenames"
+ fi
+
+'
+
+unset GIT_EDITOR
+test_expect_success 'core.editor with a space' '
+
+ if test -f "e space.sh"
+ then
+ git config core.editor \"./e\ space.sh\" &&
+ git commit --amend &&
+ test space = "$(git show -s --pretty=format:%s)"
+ else
+ say "Skipping; FS does not support spaces in filenames"
+ fi
+
+'
+
TERM="$OLD_TERM"
test_done
diff --git a/tree-walk.c b/tree-walk.c
index 142205d..02e2aed 100644
--- a/tree-walk.c
+++ b/tree-walk.c
@@ -62,7 +62,7 @@ void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1)
static int entry_compare(struct name_entry *a, struct name_entry *b)
{
- return base_name_compare(
+ return df_name_compare(
a->path, tree_entry_len(a->path, a->sha1), a->mode,
b->path, tree_entry_len(b->path, b->sha1), b->mode);
}
@@ -104,12 +104,48 @@ int tree_entry(struct tree_desc *desc, struct name_entry *entry)
return 1;
}
-void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback)
+void setup_traverse_info(struct traverse_info *info, const char *base)
{
+ int pathlen = strlen(base);
+ static struct traverse_info dummy;
+
+ memset(info, 0, sizeof(*info));
+ if (pathlen && base[pathlen-1] == '/')
+ pathlen--;
+ info->pathlen = pathlen ? pathlen + 1 : 0;
+ info->name.path = base;
+ info->name.sha1 = (void *)(base + pathlen + 1);
+ if (pathlen)
+ info->prev = &dummy;
+}
+
+char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n)
+{
+ int len = tree_entry_len(n->path, n->sha1);
+ int pathlen = info->pathlen;
+
+ path[pathlen + len] = 0;
+ for (;;) {
+ memcpy(path + pathlen, n->path, len);
+ if (!pathlen)
+ break;
+ path[--pathlen] = '/';
+ n = &info->name;
+ len = tree_entry_len(n->path, n->sha1);
+ info = info->prev;
+ pathlen -= len;
+ }
+ return path;
+}
+
+int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
+{
+ int ret = 0;
struct name_entry *entry = xmalloc(n*sizeof(*entry));
for (;;) {
unsigned long mask = 0;
+ unsigned long dirmask = 0;
int i, last;
last = -1;
@@ -134,25 +170,35 @@ void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callb
mask = 0;
}
mask |= 1ul << i;
+ if (S_ISDIR(entry[i].mode))
+ dirmask |= 1ul << i;
last = i;
}
if (!mask)
break;
+ dirmask &= mask;
/*
- * Update the tree entries we've walked, and clear
- * all the unused name-entries.
+ * Clear all the unused name-entries.
*/
for (i = 0; i < n; i++) {
- if (mask & (1ul << i)) {
- update_tree_entry(t+i);
+ if (mask & (1ul << i))
continue;
- }
entry_clear(entry + i);
}
- callback(n, mask, entry, base);
+ ret = info->fn(n, mask, dirmask, entry, info);
+ if (ret < 0)
+ break;
+ if (ret)
+ mask &= ret;
+ ret = 0;
+ for (i = 0; i < n; i++) {
+ if (mask & (1ul << i))
+ update_tree_entry(t + i);
+ }
}
free(entry);
+ return ret;
}
static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode)
diff --git a/tree-walk.h b/tree-walk.h
index db0fbdc..42110a4 100644
--- a/tree-walk.h
+++ b/tree-walk.h
@@ -33,10 +33,27 @@ int tree_entry(struct tree_desc *, struct name_entry *);
void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1);
-typedef void (*traverse_callback_t)(int n, unsigned long mask, struct name_entry *entry, const char *base);
-
-void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback);
+struct traverse_info;
+typedef int (*traverse_callback_t)(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *);
+int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info);
+
+struct traverse_info {
+ struct traverse_info *prev;
+ struct name_entry name;
+ int pathlen;
+
+ unsigned long conflicts;
+ traverse_callback_t fn;
+ void *data;
+};
int get_tree_entry(const unsigned char *, const char *, unsigned char *, unsigned *);
+extern char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n);
+extern void setup_traverse_info(struct traverse_info *info, const char *base);
+
+static inline int traverse_path_len(const struct traverse_info *info, const struct name_entry *n)
+{
+ return info->pathlen + tree_entry_len(n->path, n->sha1);
+}
#endif
diff --git a/unpack-trees.c b/unpack-trees.c
index 3e448d8..91649f3 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -1,3 +1,4 @@
+#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "dir.h"
#include "tree.h"
@@ -7,268 +8,18 @@
#include "progress.h"
#include "refs.h"
-#define DBRT_DEBUG 1
-
-struct tree_entry_list {
- struct tree_entry_list *next;
- unsigned int mode;
- const char *name;
- const unsigned char *sha1;
-};
-
-static struct tree_entry_list *create_tree_entry_list(struct tree_desc *desc)
-{
- struct name_entry one;
- struct tree_entry_list *ret = NULL;
- struct tree_entry_list **list_p = &ret;
-
- while (tree_entry(desc, &one)) {
- struct tree_entry_list *entry;
-
- entry = xmalloc(sizeof(struct tree_entry_list));
- entry->name = one.path;
- entry->sha1 = one.sha1;
- entry->mode = one.mode;
- entry->next = NULL;
-
- *list_p = entry;
- list_p = &entry->next;
- }
- return ret;
-}
-
-static int entcmp(const char *name1, int dir1, const char *name2, int dir2)
+static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
+ unsigned int set, unsigned int clear)
{
- int len1 = strlen(name1);
- int len2 = strlen(name2);
- int len = len1 < len2 ? len1 : len2;
- int ret = memcmp(name1, name2, len);
- unsigned char c1, c2;
- if (ret)
- return ret;
- c1 = name1[len];
- c2 = name2[len];
- if (!c1 && dir1)
- c1 = '/';
- if (!c2 && dir2)
- c2 = '/';
- ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
- if (c1 && c2 && !ret)
- ret = len1 - len2;
- return ret;
-}
-
-static inline void remove_entry(int remove)
-{
- if (remove >= 0)
- remove_cache_entry_at(remove);
-}
-
-static int unpack_trees_rec(struct tree_entry_list **posns, int len,
- const char *base, struct unpack_trees_options *o,
- struct tree_entry_list *df_conflict_list)
-{
- int remove;
- int baselen = strlen(base);
- int src_size = len + 1;
- int retval = 0;
-
- do {
- int i;
- const char *first;
- int firstdir = 0;
- int pathlen;
- unsigned ce_size;
- struct tree_entry_list **subposns;
- struct cache_entry **src;
- int any_files = 0;
- int any_dirs = 0;
- char *cache_name;
- int ce_stage;
- int skip_entry = 0;
-
- /* Find the first name in the input. */
-
- first = NULL;
- cache_name = NULL;
-
- /* Check the cache */
- if (o->merge && o->pos < active_nr) {
- /* This is a bit tricky: */
- /* If the index has a subdirectory (with
- * contents) as the first name, it'll get a
- * filename like "foo/bar". But that's after
- * "foo", so the entry in trees will get
- * handled first, at which point we'll go into
- * "foo", and deal with "bar" from the index,
- * because the base will be "foo/". The only
- * way we can actually have "foo/bar" first of
- * all the things is if the trees don't
- * contain "foo" at all, in which case we'll
- * handle "foo/bar" without going into the
- * directory, but that's fine (and will return
- * an error anyway, with the added unknown
- * file case.
- */
-
- cache_name = active_cache[o->pos]->name;
- if (strlen(cache_name) > baselen &&
- !memcmp(cache_name, base, baselen)) {
- cache_name += baselen;
- first = cache_name;
- } else {
- cache_name = NULL;
- }
- }
-
-#if DBRT_DEBUG > 1
- if (first)
- fprintf(stderr, "index %s\n", first);
-#endif
- for (i = 0; i < len; i++) {
- if (!posns[i] || posns[i] == df_conflict_list)
- continue;
-#if DBRT_DEBUG > 1
- fprintf(stderr, "%d %s\n", i + 1, posns[i]->name);
-#endif
- if (!first || entcmp(first, firstdir,
- posns[i]->name,
- S_ISDIR(posns[i]->mode)) > 0) {
- first = posns[i]->name;
- firstdir = S_ISDIR(posns[i]->mode);
- }
- }
- /* No name means we're done */
- if (!first)
- goto leave_directory;
-
- pathlen = strlen(first);
- ce_size = cache_entry_size(baselen + pathlen);
-
- src = xcalloc(src_size, sizeof(struct cache_entry *));
-
- subposns = xcalloc(len, sizeof(struct tree_list_entry *));
-
- remove = -1;
- if (cache_name && !strcmp(cache_name, first)) {
- any_files = 1;
- src[0] = active_cache[o->pos];
- remove = o->pos;
- if (o->skip_unmerged && ce_stage(src[0]))
- skip_entry = 1;
- }
-
- for (i = 0; i < len; i++) {
- struct cache_entry *ce;
-
- if (!posns[i] ||
- (posns[i] != df_conflict_list &&
- strcmp(first, posns[i]->name))) {
- continue;
- }
-
- if (posns[i] == df_conflict_list) {
- src[i + o->merge] = o->df_conflict_entry;
- continue;
- }
+ unsigned int size = ce_size(ce);
+ struct cache_entry *new = xmalloc(size);
- if (S_ISDIR(posns[i]->mode)) {
- struct tree *tree = lookup_tree(posns[i]->sha1);
- struct tree_desc t;
- any_dirs = 1;
- parse_tree(tree);
- init_tree_desc(&t, tree->buffer, tree->size);
- subposns[i] = create_tree_entry_list(&t);
- posns[i] = posns[i]->next;
- src[i + o->merge] = o->df_conflict_entry;
- continue;
- }
-
- if (skip_entry) {
- subposns[i] = df_conflict_list;
- posns[i] = posns[i]->next;
- continue;
- }
-
- if (!o->merge)
- ce_stage = 0;
- else if (i + 1 < o->head_idx)
- ce_stage = 1;
- else if (i + 1 > o->head_idx)
- ce_stage = 3;
- else
- ce_stage = 2;
-
- ce = xcalloc(1, ce_size);
- ce->ce_mode = create_ce_mode(posns[i]->mode);
- ce->ce_flags = create_ce_flags(baselen + pathlen,
- ce_stage);
- memcpy(ce->name, base, baselen);
- memcpy(ce->name + baselen, first, pathlen + 1);
-
- any_files = 1;
-
- hashcpy(ce->sha1, posns[i]->sha1);
- src[i + o->merge] = ce;
- subposns[i] = df_conflict_list;
- posns[i] = posns[i]->next;
- }
- if (any_files) {
- if (skip_entry) {
- o->pos++;
- while (o->pos < active_nr &&
- !strcmp(active_cache[o->pos]->name,
- src[0]->name))
- o->pos++;
- } else if (o->merge) {
- int ret;
-
-#if DBRT_DEBUG > 1
- fprintf(stderr, "%s:\n", first);
- for (i = 0; i < src_size; i++) {
- fprintf(stderr, " %d ", i);
- if (src[i])
- fprintf(stderr, "%06x %s\n", src[i]->ce_mode, sha1_to_hex(src[i]->sha1));
- else
- fprintf(stderr, "\n");
- }
-#endif
- ret = o->fn(src, o, remove);
- if (ret < 0)
- return ret;
+ clear |= CE_HASHED | CE_UNHASHED;
-#if DBRT_DEBUG > 1
- fprintf(stderr, "Added %d entries\n", ret);
-#endif
- o->pos += ret;
- } else {
- remove_entry(remove);
- for (i = 0; i < src_size; i++) {
- if (src[i]) {
- add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
- }
- }
- }
- }
- if (any_dirs) {
- char *newbase = xmalloc(baselen + 2 + pathlen);
- memcpy(newbase, base, baselen);
- memcpy(newbase + baselen, first, pathlen);
- newbase[baselen + pathlen] = '/';
- newbase[baselen + pathlen + 1] = '\0';
- if (unpack_trees_rec(subposns, len, newbase, o,
- df_conflict_list)) {
- retval = -1;
- goto leave_directory;
- }
- free(newbase);
- }
- free(subposns);
- free(src);
- } while (1);
-
- leave_directory:
- return retval;
+ memcpy(new, ce, size);
+ new->next = NULL;
+ new->ce_flags = (new->ce_flags & ~clear) | set;
+ add_index_entry(&o->result, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE|ADD_CACHE_SKIP_DFCHECK);
}
/* Unlink the last component and attempt to remove leading
@@ -308,11 +59,12 @@ static void check_updates(struct unpack_trees_options *o)
unsigned cnt = 0, total = 0;
struct progress *progress = NULL;
char last_symlink[PATH_MAX];
+ struct index_state *index = &o->result;
int i;
if (o->update && o->verbose_update) {
- for (total = cnt = 0; cnt < active_nr; cnt++) {
- struct cache_entry *ce = active_cache[cnt];
+ for (total = cnt = 0; cnt < index->cache_nr; cnt++) {
+ struct cache_entry *ce = index->cache[cnt];
if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
total++;
}
@@ -323,15 +75,15 @@ static void check_updates(struct unpack_trees_options *o)
}
*last_symlink = '\0';
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
+ for (i = 0; i < index->cache_nr; i++) {
+ struct cache_entry *ce = index->cache[i];
if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
display_progress(progress, ++cnt);
if (ce->ce_flags & CE_REMOVE) {
if (o->update)
unlink_entry(ce->name, last_symlink);
- remove_cache_entry_at(i);
+ remove_index_entry_at(&o->result, i);
i--;
continue;
}
@@ -346,21 +98,244 @@ static void check_updates(struct unpack_trees_options *o)
stop_progress(&progress);
}
-int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)
+static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_options *o)
+{
+ int ret = o->fn(src, o);
+ if (ret > 0)
+ ret = 0;
+ return ret;
+}
+
+static int unpack_index_entry(struct cache_entry *ce, struct unpack_trees_options *o)
+{
+ struct cache_entry *src[5] = { ce, };
+
+ o->pos++;
+ if (ce_stage(ce)) {
+ if (o->skip_unmerged) {
+ add_entry(o, ce, 0, 0);
+ return 0;
+ }
+ }
+ return call_unpack_fn(src, o);
+}
+
+int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info)
{
- struct tree_entry_list **posns;
int i;
- struct tree_entry_list df_conflict_list;
+ struct tree_desc t[MAX_UNPACK_TREES];
+ struct traverse_info newinfo;
+ struct name_entry *p;
+
+ p = names;
+ while (!p->mode)
+ p++;
+
+ newinfo = *info;
+ newinfo.prev = info;
+ newinfo.name = *p;
+ newinfo.pathlen += tree_entry_len(p->path, p->sha1) + 1;
+ newinfo.conflicts |= df_conflicts;
+
+ for (i = 0; i < n; i++, dirmask >>= 1) {
+ const unsigned char *sha1 = NULL;
+ if (dirmask & 1)
+ sha1 = names[i].sha1;
+ fill_tree_descriptor(t+i, sha1);
+ }
+ return traverse_trees(n, t, &newinfo);
+}
+
+/*
+ * Compare the traverse-path to the cache entry without actually
+ * having to generate the textual representation of the traverse
+ * path.
+ *
+ * NOTE! This *only* compares up to the size of the traverse path
+ * itself - the caller needs to do the final check for the cache
+ * entry having more data at the end!
+ */
+static int do_compare_entry(const struct cache_entry *ce, const struct traverse_info *info, const struct name_entry *n)
+{
+ int len, pathlen, ce_len;
+ const char *ce_name;
+
+ if (info->prev) {
+ int cmp = do_compare_entry(ce, info->prev, &info->name);
+ if (cmp)
+ return cmp;
+ }
+ pathlen = info->pathlen;
+ ce_len = ce_namelen(ce);
+
+ /* If ce_len < pathlen then we must have previously hit "name == directory" entry */
+ if (ce_len < pathlen)
+ return -1;
+
+ ce_len -= pathlen;
+ ce_name = ce->name + pathlen;
+
+ len = tree_entry_len(n->path, n->sha1);
+ return df_name_compare(ce_name, ce_len, S_IFREG, n->path, len, n->mode);
+}
+
+static int compare_entry(const struct cache_entry *ce, const struct traverse_info *info, const struct name_entry *n)
+{
+ int cmp = do_compare_entry(ce, info, n);
+ if (cmp)
+ return cmp;
+
+ /*
+ * Even if the beginning compared identically, the ce should
+ * compare as bigger than a directory leading up to it!
+ */
+ return ce_namelen(ce) > traverse_path_len(info, n);
+}
+
+static struct cache_entry *create_ce_entry(const struct traverse_info *info, const struct name_entry *n, int stage)
+{
+ int len = traverse_path_len(info, n);
+ struct cache_entry *ce = xcalloc(1, cache_entry_size(len));
+
+ ce->ce_mode = create_ce_mode(n->mode);
+ ce->ce_flags = create_ce_flags(len, stage);
+ hashcpy(ce->sha1, n->sha1);
+ make_traverse_path(ce->name, info, n);
+
+ return ce;
+}
+
+static int unpack_nondirectories(int n, unsigned long mask, unsigned long dirmask, struct cache_entry *src[5],
+ const struct name_entry *names, const struct traverse_info *info)
+{
+ int i;
+ struct unpack_trees_options *o = info->data;
+ unsigned long conflicts;
+
+ /* Do we have *only* directories? Nothing to do */
+ if (mask == dirmask && !src[0])
+ return 0;
+
+ conflicts = info->conflicts;
+ if (o->merge)
+ conflicts >>= 1;
+ conflicts |= dirmask;
+
+ /*
+ * Ok, we've filled in up to any potential index entry in src[0],
+ * now do the rest.
+ */
+ for (i = 0; i < n; i++) {
+ int stage;
+ unsigned int bit = 1ul << i;
+ if (conflicts & bit) {
+ src[i + o->merge] = o->df_conflict_entry;
+ continue;
+ }
+ if (!(mask & bit))
+ continue;
+ if (!o->merge)
+ stage = 0;
+ else if (i + 1 < o->head_idx)
+ stage = 1;
+ else if (i + 1 > o->head_idx)
+ stage = 3;
+ else
+ stage = 2;
+ src[i + o->merge] = create_ce_entry(info, names + i, stage);
+ }
+
+ if (o->merge)
+ return call_unpack_fn(src, o);
+
+ n += o->merge;
+ for (i = 0; i < n; i++)
+ add_entry(o, src[i], 0, 0);
+ return 0;
+}
+
+static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info)
+{
+ struct cache_entry *src[5] = { NULL, };
+ struct unpack_trees_options *o = info->data;
+ const struct name_entry *p = names;
+
+ /* Find first entry with a real name (we could use "mask" too) */
+ while (!p->mode)
+ p++;
+
+ /* Are we supposed to look at the index too? */
+ if (o->merge) {
+ while (o->pos < o->src_index->cache_nr) {
+ struct cache_entry *ce = o->src_index->cache[o->pos];
+ int cmp = compare_entry(ce, info, p);
+ if (cmp < 0) {
+ if (unpack_index_entry(ce, o) < 0)
+ return -1;
+ continue;
+ }
+ if (!cmp) {
+ o->pos++;
+ if (ce_stage(ce)) {
+ /*
+ * If we skip unmerged index entries, we'll skip this
+ * entry *and* the tree entries associated with it!
+ */
+ if (o->skip_unmerged) {
+ add_entry(o, ce, 0, 0);
+ return mask;
+ }
+ }
+ src[0] = ce;
+ }
+ break;
+ }
+ }
+
+ if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0)
+ return -1;
+
+ /* Now handle any directories.. */
+ if (dirmask) {
+ unsigned long conflicts = mask & ~dirmask;
+ if (o->merge) {
+ conflicts <<= 1;
+ if (src[0])
+ conflicts |= 1;
+ }
+ if (traverse_trees_recursive(n, dirmask, conflicts,
+ names, info) < 0)
+ return -1;
+ return mask;
+ }
+
+ return mask;
+}
+
+static int unpack_failed(struct unpack_trees_options *o, const char *message)
+{
+ discard_index(&o->result);
+ if (!o->gently) {
+ if (message)
+ return error(message);
+ return -1;
+ }
+ return -1;
+}
+
+int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)
+{
static struct cache_entry *dfc;
- memset(&df_conflict_list, 0, sizeof(df_conflict_list));
- df_conflict_list.next = &df_conflict_list;
+ if (len > MAX_UNPACK_TREES)
+ die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
memset(&state, 0, sizeof(state));
state.base_dir = "";
state.force = 1;
state.quiet = 1;
state.refresh_cache = 1;
+ memset(&o->result, 0, sizeof(o->result));
o->merge_size = len;
if (!dfc)
@@ -368,30 +343,33 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
o->df_conflict_entry = dfc;
if (len) {
- posns = xmalloc(len * sizeof(struct tree_entry_list *));
- for (i = 0; i < len; i++)
- posns[i] = create_tree_entry_list(t+i);
-
- if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "",
- o, &df_conflict_list)) {
- if (o->gently) {
- discard_cache();
- read_cache();
- }
- return -1;
- }
+ const char *prefix = o->prefix ? o->prefix : "";
+ struct traverse_info info;
+
+ setup_traverse_info(&info, prefix);
+ info.fn = unpack_callback;
+ info.data = o;
+
+ if (traverse_trees(len, t, &info) < 0)
+ return unpack_failed(o, NULL);
}
- if (o->trivial_merges_only && o->nontrivial_merge) {
- if (o->gently) {
- discard_cache();
- read_cache();
+ /* Any left-over entries in the index? */
+ if (o->merge) {
+ while (o->pos < o->src_index->cache_nr) {
+ struct cache_entry *ce = o->src_index->cache[o->pos];
+ if (unpack_index_entry(ce, o) < 0)
+ return unpack_failed(o, NULL);
}
- return o->gently ? -1 :
- error("Merge requires file-level merging");
}
+ if (o->trivial_merges_only && o->nontrivial_merge)
+ return unpack_failed(o, "Merge requires file-level merging");
+
+ o->src_index = NULL;
check_updates(o);
+ if (o->dst_index)
+ *o->dst_index = o->result;
return 0;
}
@@ -427,7 +405,7 @@ static int verify_uptodate(struct cache_entry *ce,
return 0;
if (!lstat(ce->name, &st)) {
- unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
+ unsigned changed = ie_match_stat(o->src_index, ce, &st, CE_MATCH_IGNORE_VALID);
if (!changed)
return 0;
/*
@@ -447,10 +425,10 @@ static int verify_uptodate(struct cache_entry *ce,
error("Entry '%s' not uptodate. Cannot merge.", ce->name);
}
-static void invalidate_ce_path(struct cache_entry *ce)
+static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o)
{
if (ce)
- cache_tree_invalidate_path(active_cache_tree, ce->name);
+ cache_tree_invalidate_path(o->src_index->cache_tree, ce->name);
}
/*
@@ -495,12 +473,12 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
* in that directory.
*/
namelen = strlen(ce->name);
- pos = cache_name_pos(ce->name, namelen);
+ pos = index_name_pos(o->src_index, ce->name, namelen);
if (0 <= pos)
return cnt; /* we have it as nondirectory */
pos = -pos - 1;
- for (i = pos; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
+ for (i = pos; i < o->src_index->cache_nr; i++) {
+ struct cache_entry *ce = o->src_index->cache[i];
int len = ce_namelen(ce);
if (len < namelen ||
strncmp(ce->name, ce->name, namelen) ||
@@ -512,7 +490,7 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
if (!ce_stage(ce)) {
if (verify_uptodate(ce, o))
return -1;
- ce->ce_flags |= CE_REMOVE;
+ add_entry(o, ce, CE_REMOVE, 0);
}
cnt++;
}
@@ -598,9 +576,9 @@ static int verify_absent(struct cache_entry *ce, const char *action,
* delete this path, which is in a subdirectory that
* is being replaced with a blob.
*/
- cnt = cache_name_pos(ce->name, strlen(ce->name));
+ cnt = index_name_pos(&o->result, ce->name, strlen(ce->name));
if (0 <= cnt) {
- struct cache_entry *ce = active_cache[cnt];
+ struct cache_entry *ce = o->result.cache[cnt];
if (ce->ce_flags & CE_REMOVE)
return 0;
}
@@ -615,7 +593,6 @@ static int verify_absent(struct cache_entry *ce, const char *action,
static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
struct unpack_trees_options *o)
{
- merge->ce_flags |= CE_UPDATE;
if (old) {
/*
* See if we can re-use the old CE directly?
@@ -629,38 +606,38 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
} else {
if (verify_uptodate(old, o))
return -1;
- invalidate_ce_path(old);
+ invalidate_ce_path(old, o);
}
}
else {
if (verify_absent(merge, "overwritten", o))
return -1;
- invalidate_ce_path(merge);
+ invalidate_ce_path(merge, o);
}
- merge->ce_flags &= ~CE_STAGEMASK;
- add_cache_entry(merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
+ add_entry(o, merge, CE_UPDATE, CE_STAGEMASK);
return 1;
}
static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
struct unpack_trees_options *o)
{
- if (old) {
- if (verify_uptodate(old, o))
- return -1;
- } else
+ /* Did it exist in the index? */
+ if (!old) {
if (verify_absent(ce, "removed", o))
return -1;
- ce->ce_flags |= CE_REMOVE;
- add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
- invalidate_ce_path(ce);
+ return 0;
+ }
+ if (verify_uptodate(old, o))
+ return -1;
+ add_entry(o, ce, CE_REMOVE, 0);
+ invalidate_ce_path(ce, o);
return 1;
}
static int keep_entry(struct cache_entry *ce, struct unpack_trees_options *o)
{
- add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
+ add_entry(o, ce, 0, 0);
return 1;
}
@@ -680,9 +657,7 @@ static void show_stage_entry(FILE *o,
}
#endif
-int threeway_merge(struct cache_entry **stages,
- struct unpack_trees_options *o,
- int remove)
+int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o)
{
struct cache_entry *index;
struct cache_entry *head;
@@ -759,10 +734,8 @@ int threeway_merge(struct cache_entry **stages,
}
/* #1 */
- if (!head && !remote && any_anc_missing) {
- remove_entry(remove);
+ if (!head && !remote && any_anc_missing)
return 0;
- }
/* Under the new "aggressive" rule, we resolve mostly trivial
* cases that we historically had git-merge-one-file resolve.
@@ -794,10 +767,9 @@ int threeway_merge(struct cache_entry **stages,
if ((head_deleted && remote_deleted) ||
(head_deleted && remote && remote_match) ||
(remote_deleted && head && head_match)) {
- remove_entry(remove);
if (index)
return deleted_entry(index, index, o);
- else if (ce && !head_deleted) {
+ if (ce && !head_deleted) {
if (verify_absent(ce, "removed", o))
return -1;
}
@@ -820,7 +792,6 @@ int threeway_merge(struct cache_entry **stages,
return -1;
}
- remove_entry(remove);
o->nontrivial_merge = 1;
/* #2, #3, #4, #6, #7, #9, #10, #11. */
@@ -855,9 +826,7 @@ int threeway_merge(struct cache_entry **stages,
* "carry forward" rule, please see <Documentation/git-read-tree.txt>.
*
*/
-int twoway_merge(struct cache_entry **src,
- struct unpack_trees_options *o,
- int remove)
+int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o)
{
struct cache_entry *current = src[0];
struct cache_entry *oldtree = src[1];
@@ -885,7 +854,6 @@ int twoway_merge(struct cache_entry **src,
}
else if (oldtree && !newtree && same(current, oldtree)) {
/* 10 or 11 */
- remove_entry(remove);
return deleted_entry(oldtree, current, o);
}
else if (oldtree && newtree &&
@@ -895,7 +863,6 @@ int twoway_merge(struct cache_entry **src,
}
else {
/* all other failures */
- remove_entry(remove);
if (oldtree)
return o->gently ? -1 : reject_merge(oldtree);
if (current)
@@ -907,7 +874,6 @@ int twoway_merge(struct cache_entry **src,
}
else if (newtree)
return merged_entry(newtree, current, o);
- remove_entry(remove);
return deleted_entry(oldtree, current, o);
}
@@ -918,8 +884,7 @@ int twoway_merge(struct cache_entry **src,
* stage0 does not have anything there.
*/
int bind_merge(struct cache_entry **src,
- struct unpack_trees_options *o,
- int remove)
+ struct unpack_trees_options *o)
{
struct cache_entry *old = src[0];
struct cache_entry *a = src[1];
@@ -929,7 +894,7 @@ int bind_merge(struct cache_entry **src,
o->merge_size);
if (a && old)
return o->gently ? -1 :
- error("Entry '%s' overlaps. Cannot bind.", a->name);
+ error("Entry '%s' overlaps with '%s'. Cannot bind.", a->name, old->name);
if (!a)
return keep_entry(old, o);
else
@@ -942,9 +907,7 @@ int bind_merge(struct cache_entry **src,
* The rule is:
* - take the stat information from stage0, take the data from stage1
*/
-int oneway_merge(struct cache_entry **src,
- struct unpack_trees_options *o,
- int remove)
+int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o)
{
struct cache_entry *old = src[0];
struct cache_entry *a = src[1];
@@ -953,18 +916,19 @@ int oneway_merge(struct cache_entry **src,
return error("Cannot do a oneway merge of %d trees",
o->merge_size);
- if (!a) {
- remove_entry(remove);
+ if (!a)
return deleted_entry(old, old, o);
- }
+
if (old && same(old, a)) {
+ int update = 0;
if (o->reset) {
struct stat st;
if (lstat(old->name, &st) ||
- ce_match_stat(old, &st, CE_MATCH_IGNORE_VALID))
- old->ce_flags |= CE_UPDATE;
+ ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID))
+ update |= CE_UPDATE;
}
- return keep_entry(old, o);
+ add_entry(o, old, update, 0);
+ return 0;
}
return merged_entry(a, old, o);
}
diff --git a/unpack-trees.h b/unpack-trees.h
index a2df544..50453ed 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -1,11 +1,12 @@
#ifndef UNPACK_TREES_H
#define UNPACK_TREES_H
+#define MAX_UNPACK_TREES 8
+
struct unpack_trees_options;
typedef int (*merge_fn_t)(struct cache_entry **src,
- struct unpack_trees_options *options,
- int remove);
+ struct unpack_trees_options *options);
struct unpack_trees_options {
int reset;
@@ -28,14 +29,18 @@ struct unpack_trees_options {
struct cache_entry *df_conflict_entry;
void *unpack_data;
+
+ struct index_state *dst_index;
+ const struct index_state *src_index;
+ struct index_state result;
};
extern int unpack_trees(unsigned n, struct tree_desc *t,
struct unpack_trees_options *options);
-int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o, int);
-int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o, int);
-int bind_merge(struct cache_entry **src, struct unpack_trees_options *o, int);
-int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o, int);
+int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o);
+int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o);
+int bind_merge(struct cache_entry **src, struct unpack_trees_options *o);
+int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o);
#endif
diff --git a/xdiff-interface.c b/xdiff-interface.c
index bba2364..61dc5c5 100644
--- a/xdiff-interface.c
+++ b/xdiff-interface.c
@@ -152,8 +152,8 @@ int read_mmfile(mmfile_t *ptr, const char *filename)
if ((f = fopen(filename, "rb")) == NULL)
return error("Could not open %s", filename);
sz = xsize_t(st.st_size);
- ptr->ptr = xmalloc(sz);
- if (fread(ptr->ptr, sz, 1, f) != 1)
+ ptr->ptr = xmalloc(sz ? sz : 1);
+ if (sz && fread(ptr->ptr, sz, 1, f) != 1)
return error("Could not read %s", filename);
fclose(f);
ptr->size = sz;