aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/RelNotes-1.5.3.txt101
-rw-r--r--Documentation/config.txt7
-rw-r--r--Documentation/git-config.txt25
-rw-r--r--Documentation/git-remote.txt7
-rw-r--r--Documentation/git-repack.txt11
-rw-r--r--Documentation/git-rev-parse.txt11
-rw-r--r--Documentation/git-send-email.txt23
-rw-r--r--Documentation/git.txt28
-rw-r--r--Makefile6
-rw-r--r--builtin-blame.c1
-rw-r--r--builtin-config.c79
-rw-r--r--builtin-init-db.c3
-rw-r--r--builtin-log.c2
-rw-r--r--builtin-ls-files.c2
-rw-r--r--builtin-pack-refs.c2
-rw-r--r--builtin-rerere.c3
-rw-r--r--builtin-rev-list.c2
-rw-r--r--builtin-rev-parse.c10
-rw-r--r--builtin-stripspace.c69
-rw-r--r--builtin.h1
-rw-r--r--cache.h4
-rw-r--r--connect.c1
-rwxr-xr-xcontrib/completion/git-completion.bash3
-rwxr-xr-xcontrib/workdir/git-new-workdir2
-rw-r--r--fetch.c3
-rw-r--r--git-compat-util.h16
-rwxr-xr-xgit-cvsimport.perl2
-rw-r--r--git-filter-branch.sh3
-rw-r--r--git-gui/Makefile52
-rwxr-xr-xgit-gui/git-gui.sh16
-rw-r--r--git-gui/lib/blame.tcl12
-rwxr-xr-xgit-send-email.perl43
-rwxr-xr-xgit-sh-setup.sh8
-rwxr-xr-xgit-svn.perl44
-rwxr-xr-xgit-tag.sh45
-rwxr-xr-xgit-verify-tag.sh7
-rw-r--r--git.c121
-rw-r--r--git.spec.in7
-rwxr-xr-xgitk3017
-rw-r--r--index-pack.c3
-rw-r--r--log-tree.c1
-rw-r--r--read-cache.c30
-rw-r--r--refs.c6
-rw-r--r--setup.c218
-rw-r--r--sha1_file.c5
-rwxr-xr-xt/t0030-stripspace.sh355
-rwxr-xr-xt/t1300-repo-config.sh84
-rwxr-xr-xt/t1500-rev-parse.sh77
-rwxr-xr-xt/t1501-worktree.sh92
-rwxr-xr-xt/t3700-add.sh26
-rwxr-xr-xt/t5516-fetch-push.sh4
-rwxr-xr-xt/t6010-merge-base.sh30
-rwxr-xr-xt/t7004-tag.sh686
-rw-r--r--t/t7004/pubring.gpgbin0 -> 1164 bytes
-rw-r--r--t/t7004/random_seedbin0 -> 600 bytes
-rw-r--r--t/t7004/secring.gpgbin0 -> 1237 bytes
-rw-r--r--t/t7004/trustdb.gpgbin0 -> 1280 bytes
-rwxr-xr-xt/t9400-git-cvsserver-server.sh4
-rw-r--r--t/test-lib.sh1
-rw-r--r--write_or_die.c40
60 files changed, 4239 insertions, 1222 deletions
diff --git a/Documentation/RelNotes-1.5.3.txt b/Documentation/RelNotes-1.5.3.txt
index d111661a7..ef2f95b3c 100644
--- a/Documentation/RelNotes-1.5.3.txt
+++ b/Documentation/RelNotes-1.5.3.txt
@@ -1,4 +1,4 @@
-GIT v1.5.3 Release Notes (draft)
+GIT v1.5.3 Release Notes
========================
Updates since v1.5.2
@@ -10,8 +10,23 @@ Updates since v1.5.2
* Thee are a handful pack-objects changes to help you cope better with
repositories with pathologically large blobs in them.
+* For people who need to import from Perforce, a front-end for
+ fast-import is in contrib/fast-import/ now.
+
+* Comes with git-gui 0.8.0.
+
+* Comes with updated gitk.
+
* New commands and options.
+ - "git log" learned a new option '--follow', to follow
+ renaming history of a single file.
+
+ - "git-filter-branch" is a reborn cg-admin-rewritehist.
+
+ - "git-cvsserver" learned new options (--base-path, --export-all,
+ --strict-paths) inspired by git-daemon.
+
- "git-submodule" command helps you manage the projects from
the superproject that contain them.
@@ -36,9 +51,45 @@ Updates since v1.5.2
- "git repack" can be told to split resulting packs to avoid
exceeding limit specified with "--max-pack-size".
+ - "git fsck" gained --verbose option. This is really really
+ verbose but it might help you identify exact commit that is
+ corrupt in your repository.
+
+ - "git format-patch" learned --numbered-files option. This
+ may be useful for MH users.
+
+ - "git tag -n -l" shows tag annotations while listing tags.
+
+ - "git cvsimport" can optionally use the separate-remote layout.
+
+ - "git blame" can be told to see through commits that changes
+ whitespaces and indentation levels with "-w" option.
+
+ - "git send-email" can be told not to thread the messages when
+ sending out more than one patches.
+
+ - "git config" learned NUL terminated output format via -z to
+ help scripts.
+
* Updated behavior of existing commands.
- - "git push" pretends that you immediately fetched back from
+ - "git mergetool" chooses its backend more wisely, taking
+ notice of its environment such as use of X, Gnome/KDE, etc.
+
+ - "gitweb" shows merge commits a lot nicer than before. The
+ default view uses more compact --cc format, while the UI
+ allows to choose normal diff with any parent.
+
+ - snapshot files "gitweb" creates from a repository at
+ $path/$project/.git are more useful. We use $project part
+ in the filename, which we used to discard.
+
+ - "git cvsimort" creates lightweight tag; there is not any
+ interesting information we can record in an annotated tag,
+ and the handcrafted ones the old code created was not
+ properly formed anyway.
+
+ - "git-push" pretends that you immediately fetched back from
the remote by updating corresponding remote tracking
branches if you have any.
@@ -48,17 +99,25 @@ Updates since v1.5.2
- "git-apply --whitespace=strip" removes blank lines added at
the end of the file.
- - fetch over git native protocols with -v shows connection
+ - "git-fetch" over git native protocols with -v shows connection
status, and the IP address of the other end, to help
diagnosing problems.
- - core.legacyheaders is no more, although we still can read
- objects created in a new loose object format.
+ - We used to have core.legacyheaders configuration, when
+ set to false, allowed git to write loose objects in a format
+ that mimicks the format used by objects stored in packs. It
+ turns out that this was not so useful. Although we will
+ continue to read objects written in that format, we do not
+ honor that configuration anymore and create loose objects in
+ the legacy/traditional format.
+
+ - "--find-copies-harder" option to diff family can now be
+ spelled as "-C -C" for brevity.
- "git-mailsplit" (hence "git-am") can read from Maildir
formatted mailboxes.
- - "git cvsserver" does not barf upon seeing "cvs login"
+ - "git-cvsserver" does not barf upon seeing "cvs login"
request.
- "pack-objects" honors "delta" attribute set in
@@ -68,10 +127,25 @@ Updates since v1.5.2
- new-workdir script (in contrib) can now be used with a bare
repository.
+ - "git-mergetool" learned to use gvimdiff.
+
+ - "gitview" (in contrib) has a better blame interface.
+
+ - "git log" and friends did not handle a commit log message
+ that is larger than 16kB; they do now.
+
+ - "--pretty=oneline" output format for "git log" and friends
+ deals with "malformed" commit log messages that have more
+ than one lines in the first paragraph better. We used to
+ show the first line, cutting the title at mid-sentence; we
+ concatenate them into a single line and treat the result as
+ "oneline".
* Builds
- -
+ - old-style function definitions (most notably, a function
+ without parameter defined with "func()", not "func(void)")
+ have been eradicated.
* Performance Tweaks
@@ -88,6 +162,10 @@ Updates since v1.5.2
the object requested the last time, which exploits the
locality of references.
+ - verifying pack contents done by "git fsck --full" got boost
+ by carefully choosing the order to verify objects in them.
+
+
Fixes since v1.5.2
------------------
@@ -96,14 +174,11 @@ this release, unless otherwise noted.
* Bugfixes
- - .... This has not
- been backported to 1.5.2.x series, as it is rather an
- intrusive change.
-
+ - "gitweb" had trouble handling non UTF-8 text with older
+ Encode.pm Perl module.
--
exec >/var/tmp/1
-O=v1.5.2-45-ged82edc
-O=v1.5.2-172-g1a8b769
+O=v1.5.2.2-603-g7c85173
echo O=`git describe refs/heads/master`
git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 34b1c9710..50503e84b 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -184,6 +184,13 @@ repository that ends in "/.git" is assumed to be not bare (bare =
false), while all other repositories are assumed to be bare (bare
= true).
+core.worktree::
+ Set the path to the working tree. The value will not be
+ used in combination with repositories found automatically in
+ a .git directory (i.e. $GIT_DIR is not set).
+ This can be overriden by the GIT_WORK_TREE environment
+ variable and the '--work-tree' command line option.
+
core.logAllRefUpdates::
Updates to a ref <ref> is logged to the file
"$GIT_DIR/logs/<ref>", by appending the new and old
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index f2c67176f..5f66a7fcd 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -9,16 +9,17 @@ git-config - Get and set repository or global options
SYNOPSIS
--------
[verse]
-'git-config' [--system | --global] name [value [value_regex]]
-'git-config' [--system | --global] --add name value
-'git-config' [--system | --global] --replace-all name [value [value_regex]]
-'git-config' [--system | --global] [type] --get name [value_regex]
-'git-config' [--system | --global] [type] --get-all name [value_regex]
+'git-config' [--system | --global] [type] [-z|--null] name [value [value_regex]]
+'git-config' [--system | --global] [type] --add name value
+'git-config' [--system | --global] [type] --replace-all name [value [value_regex]]
+'git-config' [--system | --global] [type] [-z|--null] --get name [value_regex]
+'git-config' [--system | --global] [type] [-z|--null] --get-all name [value_regex]
+'git-config' [--system | --global] [type] [-z|--null] --get-regexp name_regex [value_regex]
'git-config' [--system | --global] --unset name [value_regex]
'git-config' [--system | --global] --unset-all name [value_regex]
'git-config' [--system | --global] --rename-section old_name new_name
'git-config' [--system | --global] --remove-section name
-'git-config' [--system | --global] -l | --list
+'git-config' [--system | --global] [-z|--null] -l | --list
DESCRIPTION
-----------
@@ -36,8 +37,7 @@ prepend a single exclamation mark in front (see also <<EXAMPLES>>).
The type specifier can be either '--int' or '--bool', which will make
'git-config' ensure that the variable(s) are of the given type and
convert the value to the canonical form (simple decimal number for int,
-a "true" or "false" string for bool). Type specifiers currently only
-take effect for reading operations. If no type specifier is passed,
+a "true" or "false" string for bool). If no type specifier is passed,
no checks or transformations are performed on the value.
This command will fail if:
@@ -73,6 +73,7 @@ OPTIONS
--get-regexp::
Like --get-all, but interprets the name as a regular expression.
+ Also outputs the key names.
--global::
For writing options: write to global ~/.gitconfig file rather than
@@ -116,6 +117,14 @@ See also <<FILES>>.
in the config file will cause the value to be multiplied
by 1024, 1048576, or 1073741824 prior to output.
+-z, --null::
+ For all options that output values and/or keys, always
+ end values with with the null character (instead of a
+ newline). Use newline instead as a delimiter between
+ key and value. This allows for secure parsing of the
+ output without getting confused e.g. by values that
+ contain line breaks.
+
[[FILES]]
FILES
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index ab232c2f6..61a6022ce 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -49,6 +49,9 @@ branch the `HEAD` at the remote repository actually points at.
'show'::
Gives some information about the remote <name>.
++
+With `-n` option, the remote heads are not queried first with
+`git ls-remote <name>`; cached information is used instead.
'prune'::
@@ -56,6 +59,10 @@ Deletes all stale tracking branches under <name>.
These stale branches have already been removed from the remote repository
referenced by <name>, but are still locally available in
"remotes/<name>".
++
+With `-n` option, the remote heads are not confirmed first with `git
+ls-remote <name>`; cached information is used instead. Use with
+caution.
'update'::
diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt
index c33a512ff..28949397c 100644
--- a/Documentation/git-repack.txt
+++ b/Documentation/git-repack.txt
@@ -14,7 +14,8 @@ DESCRIPTION
-----------
This script is used to combine all objects that do not currently
-reside in a "pack", into a pack.
+reside in a "pack", into a pack. It can also be used to re-organise
+existing packs into a single, more efficient pack.
A pack is a collection of objects, individually compressed, with
delta compression applied, stored in a single file, with an
@@ -28,11 +29,13 @@ OPTIONS
-a::
Instead of incrementally packing the unpacked objects,
- pack everything available into a single pack.
+ pack everything referenced into a single pack.
Especially useful when packing a repository that is used
for private development and there is no need to worry
- about people fetching via dumb file transfer protocols
- from it. Use with '-d'.
+ about people fetching via dumb protocols from it. Use
+ with '-d'. This will clean up the objects that `git prune`
+ leaves behind, but `git fsck --full` shows as
+ dangling.
-d::
After packing, if the newly created packs make some
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 87771b832..eea9c9cfe 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -90,8 +90,15 @@ OPTIONS
Show `$GIT_DIR` if defined else show the path to the .git directory.
--is-inside-git-dir::
- Return "true" if we are in the git directory, otherwise "false".
- Some commands require to be run in a working directory.
+ When the current working directory is below the repository
+ directory print "true", otherwise "false".
+
+--is-inside-work-tree::
+ When the current working directory is inside the work tree of the
+ repository print "true", otherwise "false".
+
+--is-bare-repository::
+ When the repository is bare print "true", otherwise "false".
--short, --short=number::
Instead of outputting the full SHA1 values of object names try to
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index 946bd76af..293686c31 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -59,9 +59,11 @@ The --cc option must be repeated for each user you want on the cc list.
Only necessary if --compose is also set. If --compose
is not set, this will be prompted for.
---no-signed-off-by-cc::
- Do not add emails found in Signed-off-by: or Cc: lines to the
- cc list.
+--signed-off-by-cc, --no-signed-off-by-cc::
+ If this is set, add emails found in Signed-off-by: or Cc: lines to the
+ cc list.
+ Default is the value of 'sendemail.signedoffbycc' configuration value;
+ if that is unspecified, default to --signed-off-by-cc.
--quiet::
Make git-send-email less verbose. One line per email should be
@@ -82,9 +84,18 @@ The --cc option must be repeated for each user you want on the cc list.
Only necessary if --compose is also set. If --compose
is not set, this will be prompted for.
---suppress-from::
- Do not add the From: address to the cc: list, if it shows up in a From:
- line.
+--suppress-from, --no-suppress-from::
+ If this is set, do not add the From: address to the cc: list, if it
+ shows up in a From: line.
+ Default is the value of 'sendemail.suppressfrom' configuration value;
+ if that is unspecified, default to --no-supress-from.
+
+--thread, --no-thread::
+ If this is set, the In-Reply-To header will be set on each email sent.
+ If disabled with "--no-thread", no emails will have the In-Reply-To
+ header set.
+ Default is the value of the 'sendemail.thread' configuration value;
+ if that is unspecified, default to --thread.
--dry-run::
Do everything except actually send the emails.
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 20b5b7bb4..2cc0b214d 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -10,7 +10,8 @@ SYNOPSIS
--------
[verse]
'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate]
- [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]
+ [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]
+ [--help] COMMAND [ARGS]
DESCRIPTION
-----------
@@ -103,6 +104,14 @@ OPTIONS
Set the path to the repository. This can also be controlled by
setting the GIT_DIR environment variable.
+--work-tree=<path>::
+ Set the path to the working tree. The value will not be
+ used in combination with repositories found automatically in
+ a .git directory (i.e. $GIT_DIR is not set).
+ This can also be controlled by setting the GIT_WORK_TREE
+ environment variable and the core.worktree configuration
+ variable.
+
--bare::
Same as --git-dir=`pwd`.
@@ -347,6 +356,13 @@ git so take care if using Cogito etc.
specifies a path to use instead of the default `.git`
for the base of the repository.
+'GIT_WORK_TREE'::
+ Set the path to the working tree. The value will not be
+ used in combination with repositories found automatically in
+ a .git directory (i.e. $GIT_DIR is not set).
+ This can also be controlled by the '--work-tree' command line
+ option and the core.worktree configuration variable.
+
git Commits
~~~~~~~~~~~
'GIT_AUTHOR_NAME'::
@@ -396,6 +412,16 @@ other
'GIT_PAGER'::
This environment variable overrides `$PAGER`.
+'GIT_FLUSH'::
+ If this environment variable is set to "1", then commands such
+ as git-blame (in incremental mode), git-rev-list, git-log,
+ git-whatchanged, etc., will force a flush of the output stream
+ after each commit-oriented record have been flushed. If this
+ variable is set to "0", the output of these commands will be done
+ using completely buffered I/O. If this environment variable is
+ not set, git will choose buffered or record-oriented flushing
+ based on whether stdout appears to be redirected to a file or not.
+
'GIT_TRACE'::
If this variable is set to "1", "2" or "true" (comparison
is case insensitive), git will print `trace:` messages on
diff --git a/Makefile b/Makefile
index a98e27aa7..5d60dc8e1 100644
--- a/Makefile
+++ b/Makefile
@@ -94,9 +94,9 @@ all::
# Define OLD_ICONV if your library has an old iconv(), where the second
# (input buffer pointer) parameter is declared with type (const char **).
#
-# Define NO_R_TO_GCC if your gcc does not like "-R/path/lib" that
-# tells runtime paths to dynamic libraries; "-Wl,-rpath=/path/lib"
-# is used instead.
+# Define NO_R_TO_GCC_LINKER if your gcc does not like "-R/path/lib"
+# that tells runtime paths to dynamic libraries;
+# "-Wl,-rpath=/path/lib" is used instead.
#
# Define USE_NSEC below if you want git to care about sub-second file mtimes
# and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
diff --git a/builtin-blame.c b/builtin-blame.c
index f7e2c1388..da23a6f9c 100644
--- a/builtin-blame.c
+++ b/builtin-blame.c
@@ -1459,6 +1459,7 @@ static void found_guilty_entry(struct blame_entry *ent)
printf("boundary\n");
}
write_filename_info(suspect->path);
+ maybe_flush_or_die(stdout, "stdout");
}
}
diff --git a/builtin-config.c b/builtin-config.c
index b2515f7e6..3f7cab16d 100644
--- a/builtin-config.c
+++ b/builtin-config.c
@@ -2,7 +2,7 @@
#include "cache.h"
static const char git_config_set_usage[] =
-"git-config [ --global | --system ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list";
+"git-config [ --global | --system ] [ --bool | --int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list";
static char *key;
static regex_t *key_regexp;
@@ -12,14 +12,17 @@ static int use_key_regexp;
static int do_all;
static int do_not_match;
static int seen;
+static char delim = '=';
+static char key_delim = ' ';
+static char term = '\n';
static enum { T_RAW, T_INT, T_BOOL } type = T_RAW;
static int show_all_config(const char *key_, const char *value_)
{
if (value_)
- printf("%s=%s\n", key_, value_);
+ printf("%s%c%s%c", key_, delim, value_, term);
else
- printf("%s\n", key_);
+ printf("%s%c", key_, term);
return 0;
}
@@ -38,8 +41,12 @@ static int show_config(const char* key_, const char* value_)
regexec(regexp, (value_?value_:""), 0, NULL, 0)))
return 0;
- if (show_keys)
- printf("%s ", key_);
+ if (show_keys) {
+ if (value_)
+ printf("%s%c", key_, key_delim);
+ else
+ printf("%s", key_);
+ }
if (seen && !do_all)
dup_error = 1;
if (type == T_INT)
@@ -54,7 +61,7 @@ static int show_config(const char* key_, const char* value_)
key_, vptr);
}
else
- printf("%s\n", vptr);
+ printf("%s%c", vptr, term);
return 0;
}
@@ -131,9 +138,33 @@ free_strings:
return ret;
}
+char *normalize_value(const char *key, const char *value)
+{
+ char *normalized;
+
+ if (!value)
+ return NULL;
+
+ if (type == T_RAW)
+ normalized = xstrdup(value);
+ else {
+ normalized = xmalloc(64);
+ if (type == T_INT) {
+ int v = git_config_int(key, value);
+ sprintf(normalized, "%d", v);
+ }
+ else if (type == T_BOOL)
+ sprintf(normalized, "%s",
+ git_config_bool(key, value) ? "true" : "false");
+ }
+
+ return normalized;
+}
+
int cmd_config(int argc, const char **argv, const char *prefix)
{
int nongit = 0;
+ char* value;
setup_git_directory_gently(&nongit);
while (1 < argc) {
@@ -155,6 +186,11 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
else if (!strcmp(argv[1], "--system"))
setenv("GIT_CONFIG", ETC_GITCONFIG, 1);
+ else if (!strcmp(argv[1], "--null") || !strcmp(argv[1], "-z")) {
+ term = '\0';
+ delim = '\n';
+ key_delim = '\n';
+ }
else if (!strcmp(argv[1], "--rename-section")) {
int ret;
if (argc != 4)
@@ -205,9 +241,10 @@ int cmd_config(int argc, const char **argv, const char *prefix)
use_key_regexp = 1;
do_all = 1;
return get_value(argv[2], NULL);
- } else
-
- return git_config_set(argv[1], argv[2]);
+ } else {
+ value = normalize_value(argv[1], argv[2]);
+ return git_config_set(argv[1], value);
+ }
case 4:
if (!strcmp(argv[1], "--unset"))
return git_config_set_multivar(argv[2], NULL, argv[3], 0);
@@ -223,17 +260,21 @@ int cmd_config(int argc, const char **argv, const char *prefix)
use_key_regexp = 1;
do_all = 1;
return get_value(argv[2], argv[3]);
- } else if (!strcmp(argv[1], "--add"))
- return git_config_set_multivar(argv[2], argv[3], "^$", 0);
- else if (!strcmp(argv[1], "--replace-all"))
-
- return git_config_set_multivar(argv[2], argv[3], NULL, 1);
- else
-
- return git_config_set_multivar(argv[1], argv[2], argv[3], 0);
+ } else if (!strcmp(argv[1], "--add")) {
+ value = normalize_value(argv[2], argv[3]);
+ return git_config_set_multivar(argv[2], value, "^$", 0);
+ } else if (!strcmp(argv[1], "--replace-all")) {
+ value = normalize_value(argv[2], argv[3]);
+ return git_config_set_multivar(argv[2], value, NULL, 1);
+ } else {
+ value = normalize_value(argv[1], argv[2]);
+ return git_config_set_multivar(argv[1], value, argv[3], 0);
+ }
case 5:
- if (!strcmp(argv[1], "--replace-all"))
- return git_config_set_multivar(argv[2], argv[3], argv[4], 1);
+ if (!strcmp(argv[1], "--replace-all")) {
+ value = normalize_value(argv[2], argv[3]);
+ return git_config_set_multivar(argv[2], value, argv[4], 1);
+ }
case 1:
default:
usage(git_config_set_usage);
diff --git a/builtin-init-db.c b/builtin-init-db.c
index 0be2d2ef6..976f47b32 100644
--- a/builtin-init-db.c
+++ b/builtin-init-db.c
@@ -40,7 +40,8 @@ static int copy_file(const char *dst, const char *src, int mode)
return fdo;
}
status = copy_fd(fdi, fdo);
- close(fdo);
+ if (close(fdo) != 0)
+ return error("%s: write error: %s", dst, strerror(errno));
if (!status && adjust_shared_perm(dst))
return -1;
diff --git a/builtin-log.c b/builtin-log.c
index 073a2a16a..a4186eac8 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -589,7 +589,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
get_patch_ids(&rev, &ids, prefix);
if (!use_stdout)
- realstdout = fdopen(dup(1), "w");
+ realstdout = xfdopen(xdup(1), "w");
prepare_revision_walk(&rev);
while ((commit = get_revision(&rev)) != NULL) {
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index 5398a4141..61577ea13 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -470,7 +470,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
}
if (require_work_tree &&
- (is_bare_repository() || is_inside_git_dir()))
+ (!is_inside_work_tree() || is_inside_git_dir()))
die("This operation must be run in a work tree");
pathspec = get_pathspec(prefix, argv + i);
diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c
index 1952950c9..758499238 100644
--- a/builtin-pack-refs.c
+++ b/builtin-pack-refs.c
@@ -105,6 +105,8 @@ static int pack_refs(unsigned int flags)
fprintf(cbdata.refs_file, "# pack-refs with: peeled \n");
for_each_ref(handle_one_ref, &cbdata);
+ if (ferror(cbdata.refs_file))
+ die("failed to write ref-pack file");
if (fflush(cbdata.refs_file) || fsync(fd) || fclose(cbdata.refs_file))
die("failed to write ref-pack file (%s)", strerror(errno));
if (commit_lock_file(&packed) < 0)
diff --git a/builtin-rerere.c b/builtin-rerere.c
index f6409b93c..29fb075d2 100644
--- a/builtin-rerere.c
+++ b/builtin-rerere.c
@@ -57,7 +57,8 @@ static int write_rr(struct path_list *rr, int out_fd)
write_in_full(out_fd, path, length) != length)
die("unable to write rerere record");
}
- close(out_fd);
+ if (close(out_fd) != 0)
+ die("unable to write rerere record");
return commit_lock_file(&write_lock);
}
diff --git a/builtin-rev-list.c b/builtin-rev-list.c
index 813aadf59..86db8b03f 100644
--- a/builtin-rev-list.c
+++ b/builtin-rev-list.c
@@ -100,7 +100,7 @@ static void show_commit(struct commit *commit)
printf("%s%c", buf, hdr_termination);
free(buf);
}
- fflush(stdout);
+ maybe_flush_or_die(stdout, "stdout");
if (commit->parents) {
free_commit_list(commit->parents);
commit->parents = NULL;
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index 37addb25f..497903a85 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -352,6 +352,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
: "false");
continue;
}
+ if (!strcmp(arg, "--is-inside-work-tree")) {
+ printf("%s\n", is_inside_work_tree() ? "true"
+ : "false");
+ continue;
+ }
+ if (!strcmp(arg, "--is-bare-repository")) {
+ printf("%s\n", is_bare_repository() ? "true"
+ : "false");
+ continue;
+ }
if (!prefixcmp(arg, "--since=")) {
show_datestring("--max-age=", arg+8);
continue;
diff --git a/builtin-stripspace.c b/builtin-stripspace.c
index 62bd4b547..d8358e28f 100644
--- a/builtin-stripspace.c
+++ b/builtin-stripspace.c
@@ -1,58 +1,79 @@
#include "builtin.h"
+#include "cache.h"
/*
- * Remove empty lines from the beginning and end.
+ * Remove trailing spaces from a line.
*
- * Turn multiple consecutive empty lines into just one
- * empty line. Return true if it is an incomplete line.
+ * If the line ends with newline, it will be removed too.
+ * Returns the new length of the string.
*/
-static int cleanup(char *line)
+static int cleanup(char *line, int len)
{
- int len = strlen(line);
+ if (len) {
+ if (line[len - 1] == '\n')
+ len--;
- if (len && line[len-1] == '\n') {
- if (len == 1)
- return 0;
- do {
- unsigned char c = line[len-2];
+ while (len) {
+ unsigned char c = line[len - 1];
if (!isspace(c))
break;
- line[len-2] = '\n';
len--;
- line[len] = 0;
- } while (len > 1);
- return 0;
+ }
+ line[len] = 0;
}
- return 1;
+ return len;
}
-static void stripspace(FILE *in, FILE *out)
+/*
+ * Remove empty lines from the beginning and end
+ * and also trailing spaces from every line.
+ *
+ * Turn multiple consecutive empty lines between paragraphs
+ * into just one empty line.
+ *
+ * If the input has only empty lines and spaces,
+ * no output will be produced.
+ *
+ * Enable skip_comments to skip every line starting with "#".
+ */
+void stripspace(FILE *in, FILE *out, int skip_comments)
{
int empties = -1;
- int incomplete = 0;
- char line[1024];
+ int alloc = 1024;
+ char *line = xmalloc(alloc);
+
+ while (fgets(line, alloc, in)) {
+ int len = strlen(line);
- while (fgets(line, sizeof(line), in)) {
- incomplete = cleanup(line);
+ while (len == alloc - 1 && line[len - 1] != '\n') {
+ alloc = alloc_nr(alloc);
+ line = xrealloc(line, alloc);
+ fgets(line + len, alloc - len, in);
+ len += strlen(line + len);
+ }
+
+ if (skip_comments && line[0] == '#')
+ continue;
+ len = cleanup(line, len);
/* Not just an empty line? */
- if (line[0] != '\n') {
+ if (len) {
if (empties > 0)
fputc('\n', out);
empties = 0;
fputs(line, out);
+ fputc('\n', out);
continue;
}
if (empties < 0)
continue;
empties++;
}
- if (incomplete)
- fputc('\n', out);
+ free(line);
}
int cmd_stripspace(int argc, const char **argv, const char *prefix)
{
- stripspace(stdin, stdout);
+ stripspace(stdin, stdout, 0);
return 0;
}
diff --git a/builtin.h b/builtin.h
index da4834c31..661a92f78 100644
--- a/builtin.h
+++ b/builtin.h
@@ -7,6 +7,7 @@ extern const char git_version_string[];
extern const char git_usage_string[];
extern void help_unknown_cmd(const char *cmd);
+extern void stripspace(FILE *in, FILE *out, int skip_comments);
extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
extern void prune_packed_objects(int);
diff --git a/cache.h b/cache.h
index 67763571b..0d23a25b1 100644
--- a/cache.h
+++ b/cache.h
@@ -192,6 +192,7 @@ enum object_type {
};
#define GIT_DIR_ENVIRONMENT "GIT_DIR"
+#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
#define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
#define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
@@ -207,6 +208,7 @@ enum object_type {
extern int is_bare_repository_cfg;
extern int is_bare_repository(void);
extern int is_inside_git_dir(void);
+extern int is_inside_work_tree(void);
extern const char *get_git_dir(void);
extern char *get_object_directory(void);
extern char *get_refs_directory(void);
@@ -533,6 +535,8 @@ extern char git_default_name[MAX_GITNAME];
extern const char *git_commit_encoding;
extern const char *git_log_output_encoding;
+/* IO helper functions */
+extern void maybe_flush_or_die(FILE *, const char *);
extern int copy_fd(int ifd, int ofd);
extern int read_in_full(int fd, void *buf, size_t count);
extern int write_in_full(int fd, const void *buf, size_t count);
diff --git a/connect.c b/connect.c
index a5afd2a53..65e79edc7 100644
--- a/connect.c
+++ b/connect.c
@@ -587,6 +587,7 @@ pid_t git_connect(int fd[2], char *url, const char *prog, int flags)
unsetenv(ALTERNATE_DB_ENVIRONMENT);
unsetenv(DB_ENVIRONMENT);
unsetenv(GIT_DIR_ENVIRONMENT);
+ unsetenv(GIT_WORK_TREE_ENVIRONMENT);
unsetenv(GRAFT_ENVIRONMENT);
unsetenv(INDEX_ENVIRONMENT);
execlp("sh", "sh", "-c", command, NULL);
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index c7c996334..f2b10fa5f 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -682,6 +682,9 @@ _git_push ()
esac
__gitcomp "$(__git_refs "$remote")" "" "${cur#*:}"
;;
+ +*)
+ __gitcomp "$(__git_refs)" + "${cur#+}"
+ ;;
*)
__gitcomp "$(__git_refs)"
;;
diff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir
index 709b2a3ac..3ff6bd166 100755
--- a/contrib/workdir/git-new-workdir
+++ b/contrib/workdir/git-new-workdir
@@ -24,7 +24,7 @@ git_dir=$(cd "$orig_git" 2>/dev/null &&
git rev-parse --git-dir 2>/dev/null) ||
die "\"$orig_git\" is not a git repository!"
-if test "$git_dir" == ".git"
+if test "$git_dir" = ".git"
then
git_dir="$orig_git/.git"
fi
diff --git a/fetch.c b/fetch.c
index dda33e548..811be87a3 100644
--- a/fetch.c
+++ b/fetch.c
@@ -46,6 +46,9 @@ static int process_tree(struct tree *tree)
while (tree_entry(&desc, &entry)) {
struct object *obj = NULL;
+ /* submodule commits are not stored in the superproject */
+ if (S_ISGITLINK(entry.mode))
+ continue;
if (S_ISDIR(entry.mode)) {
struct tree *tree = lookup_tree(entry.sha1);
if (tree)
diff --git a/git-compat-util.h b/git-compat-util.h
index b2ab3f825..362e040f5 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -287,6 +287,22 @@ static inline ssize_t xwrite(int fd, const void *buf, size_t len)
}
}
+static inline int xdup(int fd)
+{
+ int ret = dup(fd);
+ if (ret < 0)
+ die("dup failed: %s", strerror(errno));
+ return ret;
+}
+
+static inline FILE *xfdopen(int fd, const char *mode)
+{
+ FILE *stream = fdopen(fd, mode);
+ if (stream == NULL)
+ die("Out of memory? fdopen failed: %s", strerror(errno));
+ return stream;
+}
+
static inline size_t xsize_t(off_t len)
{
return (size_t)len;
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index 69ccb88dd..ba23eb8ee 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -1007,7 +1007,7 @@ if ($orig_branch) {
if ($opt_r && $opt_o ne 'HEAD');
system('git-update-ref', 'HEAD', "$orig_branch");
unless ($opt_i) {
- system('git checkout');
+ system('git checkout -f');
die "checkout failed: $?\n" if $?;
}
}
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index 8fa5ce646..a2fcebc1c 100644
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -312,9 +312,10 @@ case "$GIT_DIR" in
/*)
;;
*)
- export GIT_DIR="$(pwd)/../../$GIT_DIR"
+ GIT_DIR="$(pwd)/../../$GIT_DIR"
;;
esac
+export GIT_DIR GIT_WORK_TREE=.
export GIT_INDEX_FILE="$(pwd)/../index"
git-read-tree # seed the index file
diff --git a/git-gui/Makefile b/git-gui/Makefile
index 3de0de1a2..1bac6fed4 100644
--- a/git-gui/Makefile
+++ b/git-gui/Makefile
@@ -7,6 +7,8 @@ GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
@$(SHELL_PATH) ./GIT-VERSION-GEN
-include GIT-VERSION-FILE
+uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
+
SCRIPT_SH = git-gui.sh
GITGUI_BUILT_INS = git-citool
ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH))
@@ -29,11 +31,35 @@ ifndef INSTALL
INSTALL = install
endif
+INSTALL_D0 = $(INSTALL) -d -m755 # space is required here
+INSTALL_D1 =
+INSTALL_R0 = $(INSTALL) -m644 # space is required here
+INSTALL_R1 =
+INSTALL_X0 = $(INSTALL) -m755 # space is required here
+INSTALL_X1 =
+INSTALL_L0 = rm -f # space is required here
+INSTALL_L1 = && ln # space is required here
+INSTALL_L2 =
+INSTALL_L3 =
+
ifndef V
- QUIET_GEN = @echo ' ' GEN $@;
- QUIET_BUILT_IN = @echo ' ' BUILTIN $@;
- QUIET_INDEX = @echo ' ' INDEX $(dir $@);
+ QUIET = @
+ QUIET_GEN = $(QUIET)echo ' ' GEN $@ &&
+ QUIET_BUILT_IN = $(QUIET)echo ' ' BUILTIN $@ &&
+ QUIET_INDEX = $(QUIET)echo ' ' INDEX $(dir $@) &&
QUIET_2DEVNULL = 2>/dev/null
+
+ INSTALL_D0 = dir=
+ INSTALL_D1 = && echo ' ' DEST $$dir && $(INSTALL) -d -m755 "$$dir"
+ INSTALL_R0 = src=
+ INSTALL_R1 = && echo ' ' INSTALL 644 `basename $$src` && $(INSTALL) -m644 $$src
+ INSTALL_X0 = src=
+ INSTALL_X1 = && echo ' ' INSTALL 755 `basename $$src` && $(INSTALL) -m755 $$src
+
+ INSTALL_L0 = dst=
+ INSTALL_L1 = && src=
+ INSTALL_L2 = && dst=
+ INSTALL_L3 = && echo ' ' 'LINK ' `basename "$$dst"` '->' `basename "$$src"` && rm -f "$$dst" && ln "$$src" "$$dst"
endif
TCL_PATH ?= tclsh
@@ -58,11 +84,15 @@ exedir_SQ = $(subst ','\'',$(exedir))
$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
$(QUIET_GEN)rm -f $@ $@+ && \
+ GITGUI_RELATIVE= && \
if test '$(exedir_SQ)' = '$(libdir_SQ)'; then \
- GITGUI_RELATIVE=1; \
+ if test "$(uname_O)" = Cygwin; \
+ then GITGUI_RELATIVE= ; \
+ else GITGUI_RELATIVE=1; \
+ fi; \
fi && \
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
- -e 's|^exec wish "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' \
+ -e 's|^ exec wish "$$0"| exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' \
-e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \
-e 's|@@GITGUI_RELATIVE@@|'$$GITGUI_RELATIVE'|' \
-e $$GITGUI_RELATIVE's|@@GITGUI_LIBDIR@@|$(libdir_SQ)|' \
@@ -109,12 +139,12 @@ GIT-GUI-VARS: .FORCE-GIT-GUI-VARS
all:: $(ALL_PROGRAMS) lib/tclIndex
install: all
- $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)'
- $(INSTALL) git-gui '$(DESTDIR_SQ)$(gitexecdir_SQ)'
- $(foreach p,$(GITGUI_BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;)
- $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(libdir_SQ)'
- $(INSTALL) -m644 lib/tclIndex '$(DESTDIR_SQ)$(libdir_SQ)'
- $(foreach p,$(ALL_LIBFILES), $(INSTALL) -m644 $p '$(DESTDIR_SQ)$(libdir_SQ)' ;)
+ $(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL_D1)
+ $(QUIET)$(INSTALL_X0)git-gui $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
+ $(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(INSTALL_L0)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L1)'$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' $(INSTALL_L2)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L3) &&) true
+ $(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(libdir_SQ)' $(INSTALL_D1)
+ $(QUIET)$(INSTALL_R0)lib/tclIndex $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)'
+ $(QUIET)$(foreach p,$(ALL_LIBFILES), $(INSTALL_R0)$p $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)' &&) true
dist-version:
@mkdir -p $(TARDIR)
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 3237f3d59..9df2e4702 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -1,6 +1,12 @@
#!/bin/sh
# Tcl ignores the next line -*- tcl -*- \
-exec wish "$0" -- "$@"
+ if test "z$*" = zversion \
+ || test "z$*" = z--version; \
+ then \
+ echo 'git-gui version @@GITGUI_VERSION@@'; \
+ exit; \
+ fi; \
+ exec wish "$0" -- "$@"
set appvers {@@GITGUI_VERSION@@}
set copyright {
@@ -302,11 +308,6 @@ proc tk_optionMenu {w varName args} {
##
## version check
-if {{--version} eq $argv || {version} eq $argv} {
- puts "git-gui version $appvers"
- exit
-}
-
set req_maj 1
set req_min 5
@@ -1580,8 +1581,7 @@ if {[is_MacOSX]} {
# -- Tools Menu
#
- if {[file exists /usr/local/miga/lib/gui-miga]
- && [file exists .pvcsrc]} {
+ if {[is_Cygwin] && [file exists /usr/local/miga/lib/gui-miga]} {
proc do_miga {} {
global ui_status_value
if {![lock_index update]} return
diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl
index 139171d39..b52365481 100644
--- a/git-gui/lib/blame.tcl
+++ b/git-gui/lib/blame.tcl
@@ -272,6 +272,8 @@ constructor new {i_commit i_path} {
set cursorW %W
tk_popup $w.ctxm %X %Y
"
+ bind $i <Shift-Tab> "[list focus $w_cviewer];break"
+ bind $i <Tab> "[list focus $w_cviewer];break"
}
foreach i [concat $w_columns $w_cviewer] {
@@ -287,8 +289,10 @@ constructor new {i_commit i_path} {
bind $i <Control-Key-f> {catch {%W yview scroll 1 pages};break}
}
+ bind $w_cviewer <Shift-Tab> "[list focus $w_file];break"
+ bind $w_cviewer <Tab> "[list focus $w_file];break"
bind $w_cviewer <Button-1> [list focus $w_cviewer]
- bind $top <Visibility> [list focus $top]
+ bind $w_file <Visibility> [list focus $w_file]
grid configure $w.header -sticky ew
grid configure $w.file_pane -sticky nsew
@@ -483,7 +487,11 @@ method _read_file {fd jump} {
} ifdeleted { catch {close $fd} }
method _exec_blame {cur_w cur_d options cur_s} {
- set cmd [list nice git blame]
+ set cmd [list]
+ if {![is_Windows] || [is_Cygwin]} {
+ lappend cmd nice
+ }
+ lappend cmd git blame
set cmd [concat $cmd $options]
lappend cmd --incremental
if {$commit eq {}} {
diff --git a/git-send-email.perl b/git-send-email.perl
index 9f7555167..87f59fa31 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -64,15 +64,17 @@ Options:
email sent, rather than to the first email sent.
Defaults to on.
- --no-signed-off-cc Suppress the automatic addition of email addresses
- that appear in Signed-off-by: or Cc: lines to the cc:
- list. Note: Using this option is not recommended.
+ --signed-off-cc Automatically add email addresses that appear in
+ Signed-off-by: or Cc: lines to the cc: list. Defaults to on.
--smtp-server If set, specifies the outgoing SMTP server to use.
Defaults to localhost.
--suppress-from Suppress sending emails to yourself if your address
- appears in a From: line.
+ appears in a From: line. Defaults to off.
+
+ --thread Specify that the "In-Reply-To:" header should be set on all
+ emails. Defaults to on.
--quiet Make git-send-email less verbose. One line per email
should be all that is output.
@@ -137,9 +139,6 @@ my $compose_filename = ".msg.$$";
my (@to,@cc,@initial_cc,@bcclist,@xh,
$initial_reply_to,$initial_subject,@files,$from,$compose,$time);
-# Behavior modification variables
-my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc,
- $dry_run) = (1, 0, 0, 0, 0);
my $smtp_server;
my $envelope_sender;
@@ -154,9 +153,22 @@ if ($@) {
$term = new FakeTerm "$@: going non-interactive";
}
-my $def_chain = $repo->config_bool('sendemail.chainreplyto');
-if (defined $def_chain and not $def_chain) {
- $chain_reply_to = 0;
+# Behavior modification variables
+my ($quiet, $dry_run) = (0, 0);
+
+# Variables with corresponding config settings
+my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc);
+
+my %config_settings = (
+ "thread" => [\$thread, 1],
+ "chainreplyto" => [\$chain_reply_to, 1],
+ "suppressfrom" => [\$suppress_from, 0],
+ "signedoffcc" => [\$signed_off_cc, 1],
+);
+
+foreach my $setting (keys %config_settings) {
+ my $config = $repo->config_bool("sendemail.$setting");
+ ${$config_settings{$setting}->[0]} = (defined $config) ? $config : $config_settings{$setting}->[1];
}
@bcclist = $repo->config('sendemail.bcc');
@@ -177,10 +189,11 @@ my $rc = GetOptions("from=s" => \$from,
"smtp-server=s" => \$smtp_server,
"compose" => \$compose,
"quiet" => \$quiet,
- "suppress-from" => \$suppress_from,
- "no-signed-off-cc|no-signed-off-by-cc" => \$no_signed_off_cc,
+ "suppress-from!" => \$suppress_from,
+ "signed-off-cc|signed-off-by-cc!" => \$signed_off_cc,
"dry-run" => \$dry_run,
"envelope-sender=s" => \$envelope_sender,
+ "thread!" => \$thread,
);
unless ($rc) {
@@ -287,7 +300,7 @@ if (!defined $initial_subject && $compose) {
$prompting++;
}
-if (!defined $initial_reply_to && $prompting) {
+if ($thread && !defined $initial_reply_to && $prompting) {
do {
$_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ",
$initial_reply_to);
@@ -484,7 +497,7 @@ Date: $date
Message-Id: $message_id
X-Mailer: git-send-email $gitversion
";
- if ($reply_to) {
+ if ($thread && $reply_to) {
$header .= "In-Reply-To: $reply_to\n";
$header .= "References: $references\n";
@@ -609,7 +622,7 @@ foreach my $t (@files) {
}
} else {
$message .= $_;
- if (/^(Signed-off-by|Cc): (.*)$/i && !$no_signed_off_cc) {
+ if (/^(Signed-off-by|Cc): (.*)$/i && $signed_off_cc) {
my $c = $2;
chomp $c;
push @cc, $c;
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index f24c7f2d2..0de49e845 100755
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -29,11 +29,7 @@ set_reflog_action() {
}
is_bare_repository () {
- git-config --bool --get core.bare ||
- case "$GIT_DIR" in
- .git | */.git) echo false ;;
- *) echo true ;;
- esac
+ git-rev-parse --is-bare-repository
}
cd_to_toplevel () {
@@ -48,7 +44,7 @@ cd_to_toplevel () {
}
require_work_tree () {
- test $(is_bare_repository) = false &&
+ test $(git-rev-parse --is-inside-work-tree) = true &&
test $(git-rev-parse --is-inside-git-dir) = false ||
die "fatal: $0 cannot be used without a working tree."
}
diff --git a/git-svn.perl b/git-svn.perl
index 50128d728..03d5e2d97 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -596,8 +596,7 @@ sub post_fetch_checkout {
my $index = $ENV{GIT_INDEX_FILE} || "$ENV{GIT_DIR}/index";
return if -f $index;
- chomp(my $bare = `git config --bool --get core.bare`);
- return if $bare eq 'true';
+ return if command_oneline(qw/rev-parse --is-inside-work-tree/) eq 'false';
return if command_oneline(qw/rev-parse --is-inside-git-dir/) eq 'true';
command_noisy(qw/read-tree -m -u -v HEAD HEAD/);
print STDERR "Checked out HEAD:\n ",
@@ -787,12 +786,12 @@ sub read_repo_config {
sub extract_metadata {
my $id = shift or return (undef, undef, undef);
- my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
+ my ($url, $rev, $uuid) = ($id =~ /^\s*git-svn-id:\s+(.*)\@(\d+)
\s([a-f\d\-]+)$/x);
if (!defined $rev || !$uuid || !$url) {
# some of the original repositories I made had
# identifiers like this:
- ($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
+ ($rev, $uuid) = ($id =~/^\s*git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
}
return ($url, $rev, $uuid);
}
@@ -804,20 +803,29 @@ sub cmt_metadata {
sub working_head_info {
my ($head, $refs) = @_;
- my ($fh, $ctx) = command_output_pipe('rev-list', $head);
- while (my $hash = <$fh>) {
- chomp($hash);
- my ($url, $rev, $uuid) = cmt_metadata($hash);
+ my ($fh, $ctx) = command_output_pipe('log', $head);
+ my $hash;
+ my %max;
+ while (<$fh>) {
+ if ( m{^commit ($::sha1)$} ) {
+ unshift @$refs, $hash if $hash and $refs;
+ $hash = $1;
+ next;
+ }
+ next unless s{^\s*(git-svn-id:)}{$1};
+ my ($url, $rev, $uuid) = extract_metadata($_);
if (defined $url && defined $rev) {
+ next if $max{$url} and $max{$url} < $rev;
if (my $gs = Git::SVN->find_by_url($url)) {
my $c = $gs->rev_db_get($rev);
if ($c && $c eq $hash) {
close $fh; # break the pipe
return ($url, $rev, $uuid, $gs);
+ } else {
+ $max{$url} ||= $gs->rev_db_max;
}
}
}
- unshift @$refs, $hash if $refs;
}
command_close_pipe($fh, $ctx);
(undef, undef, undef, undef);
@@ -1966,16 +1974,19 @@ sub rebuild {
return;
}
print "Rebuilding $db_path ...\n";
- my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname);
+ my ($log, $ctx) = command_output_pipe("log", $self->refname);
my $latest;
my $full_url = $self->full_url;
remove_username($full_url);
my $svn_uuid;
- while (<$rev_list>) {
- chomp;
- my $c = $_;
- die "Non-SHA1: $c\n" unless $c =~ /^$::sha1$/o;
- my ($url, $rev, $uuid) = ::cmt_metadata($c);
+ my $c;
+ while (<$log>) {
+ if ( m{^commit ($::sha1)$} ) {
+ $c = $1;
+ next;
+ }
+ next unless s{^\s*(git-svn-id:)}{$1};
+ my ($url, $rev, $uuid) = ::extract_metadata($_);
remove_username($url);
# ignore merges (from set-tree)
@@ -1993,7 +2004,7 @@ sub rebuild {
$self->rev_db_set($rev, $c);
print "r$rev = $c\n";
}
- command_close_pipe($rev_list, $ctx);
+ command_close_pipe($log, $ctx);
print "Done rebuilding $db_path\n";
}
@@ -2925,6 +2936,7 @@ sub new {
SVN::Client::get_ssl_server_trust_file_provider(),
SVN::Client::get_simple_prompt_provider(
\&Git::SVN::Prompt::simple, 2),
+ SVN::Client::get_ssl_client_cert_file_provider(),
SVN::Client::get_ssl_client_cert_prompt_provider(
\&Git::SVN::Prompt::ssl_client_cert, 2),
SVN::Client::get_ssl_client_cert_pw_prompt_provider(
diff --git a/git-tag.sh b/git-tag.sh
index c84043902..1ff5b41e7 100755
--- a/git-tag.sh
+++ b/git-tag.sh
@@ -19,28 +19,40 @@ do
case "$1" in
-a)
annotate=1
+ shift
;;
-s)
annotate=1
signed=1
+ shift
;;
-f)
force=1
+ shift
;;
-n)
- case $2 in
- -*) LINES=1 # no argument
+ case "$#,$2" in
+ 1,* | *,-*)
+ LINES=1 # no argument
;;
*) shift
LINES=$(expr "$1" : '\([0-9]*\)')
[ -z "$LINES" ] && LINES=1 # 1 line is default when -n is used
;;
esac
+ shift
;;
-l)
list=1
shift
- PATTERN="$1" # select tags by shell pattern, not re
+ case $# in
+ 0) PATTERN=
+ ;;
+ *)
+ PATTERN="$1" # select tags by shell pattern, not re
+ shift
+ ;;
+ esac
git rev-parse --symbolic --tags | sort |
while read TAG
do
@@ -51,12 +63,16 @@ do
[ "$LINES" -le 0 ] && { echo "$TAG"; continue ;}
OBJTYPE=$(git cat-file -t "$TAG")
case $OBJTYPE in
- tag) ANNOTATION=$(git cat-file tag "$TAG" |
- sed -e '1,/^$/d' \
- -e '/^-----BEGIN PGP SIGNATURE-----$/Q' )
- printf "%-15s %s\n" "$TAG" "$ANNOTATION" |
- sed -e '2,$s/^/ /' \
- -e "${LINES}q"
+ tag)
+ ANNOTATION=$(git cat-file tag "$TAG" |
+ sed -e '1,/^$/d' |
+ sed -n -e "
+ /^-----BEGIN PGP SIGNATURE-----\$/q
+ 2,\$s/^/ /
+ p
+ ${LINES}q
+ ")
+ printf "%-15s %s\n" "$TAG" "$ANNOTATION"
;;
*) echo "$TAG"
;;
@@ -70,7 +86,9 @@ do
if test "$#" = "0"; then
die "error: option -m needs an argument"
else
+ message="$1"
message_given=1
+ shift
fi
;;
-F)
@@ -81,13 +99,19 @@ do
else
message="$(cat "$1")"
message_given=1
+ shift
fi
;;
-u)
annotate=1
signed=1
shift
- username="$1"
+ if test "$#" = "0"; then
+ die "error: option -u needs an argument"
+ else
+ username="$1"
+ shift
+ fi
;;
-d)
shift
@@ -122,7 +146,6 @@ do
break
;;
esac
- shift
done
[ -n "$list" ] && exit 0
diff --git a/git-verify-tag.sh b/git-verify-tag.sh
index f2d5597db..68858b694 100755
--- a/git-verify-tag.sh
+++ b/git-verify-tag.sh
@@ -37,8 +37,9 @@ esac
trap 'rm -f "$GIT_DIR/.tmp-vtag"' 0
git-cat-file tag "$1" >"$GIT_DIR/.tmp-vtag" || exit 1
-
-cat "$GIT_DIR/.tmp-vtag" |
-sed '/-----BEGIN PGP/Q' |
+sed -n -e '
+ /^-----BEGIN PGP SIGNATURE-----$/q
+ p
+' <"$GIT_DIR/.tmp-vtag" |
gpg --verify "$GIT_DIR/.tmp-vtag" - || exit 1
rm -f "$GIT_DIR/.tmp-vtag"
diff --git a/git.c b/git.c
index 29b55a160..696a97edc 100644
--- a/git.c
+++ b/git.c
@@ -4,7 +4,7 @@
#include "quote.h"
const char git_usage_string[] =
- "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]";
+ "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]";
static void prepend_to_path(const char *dir, int len)
{
@@ -28,7 +28,7 @@ static void prepend_to_path(const char *dir, int len)
free(path);
}
-static int handle_options(const char*** argv, int* argc)
+static int handle_options(const char*** argv, int* argc, int* envchanged)
{
int handled = 0;
@@ -64,14 +64,34 @@ static int handle_options(const char*** argv, int* argc)
usage(git_usage_string);
}
setenv(GIT_DIR_ENVIRONMENT, (*argv)[1], 1);
+ if (envchanged)
+ *envchanged = 1;
(*argv)++;
(*argc)--;
handled++;
} else if (!prefixcmp(cmd, "--git-dir=")) {
setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
+ if (envchanged)
+ *envchanged = 1;
+ } else if (!strcmp(cmd, "--work-tree")) {
+ if (*argc < 2) {
+ fprintf(stderr, "No directory given for --work-tree.\n" );
+ usage(git_usage_string);
+ }
+ setenv(GIT_WORK_TREE_ENVIRONMENT, (*argv)[1], 1);
+ if (envchanged)
+ *envchanged = 1;
+ (*argv)++;
+ (*argc)--;
+ } else if (!prefixcmp(cmd, "--work-tree=")) {
+ setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1);
+ if (envchanged)
+ *envchanged = 1;
} else if (!strcmp(cmd, "--bare")) {
static char git_dir[PATH_MAX+1];
setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 1);
+ if (envchanged)
+ *envchanged = 1;
} else {
fprintf(stderr, "Unknown option: %s\n", cmd);
usage(git_usage_string);
@@ -150,7 +170,7 @@ static int split_cmdline(char *cmdline, const char ***argv)
static int handle_alias(int *argcp, const char ***argv)
{
- int nongit = 0, ret = 0, saved_errno = errno;
+ int nongit = 0, envchanged = 0, ret = 0, saved_errno = errno;
const char *subdir;
int count, option_count;
const char** new_argv;
@@ -171,7 +191,11 @@ static int handle_alias(int *argcp, const char ***argv)
alias_string + 1, alias_command);
}
count = split_cmdline(alias_string, &new_argv);
- option_count = handle_options(&new_argv, &count);
+ option_count = handle_options(&new_argv, &count, &envchanged);
+ if (envchanged)
+ die("alias '%s' changes environment variables\n"
+ "You can use '!git' in the alias to do this.",
+ alias_command);
memmove(new_argv - option_count, new_argv,
count * sizeof(char *));
new_argv -= option_count;
@@ -214,17 +238,56 @@ const char git_version_string[] = GIT_VERSION;
* require working tree to be present -- anything uses this needs
* RUN_SETUP for reading from the configuration file.
*/
-#define NOT_BARE (1<<2)
+#define NEED_WORK_TREE (1<<2)
+
+struct cmd_struct {
+ const char *cmd;
+ int (*fn)(int, const char **, const char *);
+ int option;
+};
-static void handle_internal_command(int argc, const char **argv, char **envp)
+static int run_command(struct cmd_struct *p, int argc, const char **argv)
+{
+ int status;
+ struct stat st;
+ const char *prefix;
+
+ prefix = NULL;
+ if (p->option & RUN_SETUP)
+ prefix = setup_git_directory();
+ if (p->option & USE_PAGER)
+ setup_pager();
+ if ((p->option & NEED_WORK_TREE) &&
+ (!is_inside_work_tree() || is_inside_git_dir()))
+ die("%s must be run in a work tree", p->cmd);
+ trace_argv_printf(argv, argc, "trace: built-in: git");
+
+ status = p->fn(argc, argv, prefix);
+ if (status)
+ return status;
+
+ /* Somebody closed stdout? */
+ if (fstat(fileno(stdout), &st))
+ return 0;
+ /* Ignore write errors for pipes and sockets.. */
+ if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode))
+ return 0;
+
+ /* Check for ENOSPC and EIO errors.. */
+ if (fflush(stdout))
+ die("write failure on standard output: %s", strerror(errno));
+ if (ferror(stdout))
+ die("unknown write failure on standard output");
+ if (fclose(stdout))
+ die("close failed on standard output: %s", strerror(errno));
+ return 0;
+}
+
+static void handle_internal_command(int argc, const char **argv)
{
const char *cmd = argv[0];
- static struct cmd_struct {
- const char *cmd;
- int (*fn)(int, const char **, const char *);
- int option;
- } commands[] = {
- { "add", cmd_add, RUN_SETUP | NOT_BARE },
+ static struct cmd_struct commands[] = {
+ { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
{ "annotate", cmd_annotate, RUN_SETUP | USE_PAGER },
{ "apply", cmd_apply },
{ "archive", cmd_archive },
@@ -234,9 +297,9 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
{ "cat-file", cmd_cat_file, RUN_SETUP },
{ "checkout-index", cmd_checkout_index, RUN_SETUP },
{ "check-ref-format", cmd_check_ref_format },
- { "check-attr", cmd_check_attr, RUN_SETUP | NOT_BARE },
+ { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },
{ "cherry", cmd_cherry, RUN_SETUP },
- { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NOT_BARE },
+ { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
{ "config", cmd_config },
{ "count-objects", cmd_count_objects, RUN_SETUP },
@@ -264,7 +327,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
{ "mailsplit", cmd_mailsplit },
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file },
- { "mv", cmd_mv, RUN_SETUP | NOT_BARE },
+ { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
{ "name-rev", cmd_name_rev, RUN_SETUP },
{ "pack-objects", cmd_pack_objects, RUN_SETUP },
{ "pickaxe", cmd_blame, RUN_SETUP | USE_PAGER },
@@ -277,9 +340,9 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
{ "rerere", cmd_rerere, RUN_SETUP },
{ "rev-list", cmd_rev_list, RUN_SETUP },
{ "rev-parse", cmd_rev_parse, RUN_SETUP },
- { "revert", cmd_revert, RUN_SETUP | NOT_BARE },
- { "rm", cmd_rm, RUN_SETUP | NOT_BARE },
- { "runstatus", cmd_runstatus, RUN_SETUP | NOT_BARE },
+ { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
+ { "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE },
+ { "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
{ "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
{ "show-branch", cmd_show_branch, RUN_SETUP },
{ "show", cmd_show, RUN_SETUP | USE_PAGER },
@@ -307,25 +370,13 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
for (i = 0; i < ARRAY_SIZE(commands); i++) {
struct cmd_struct *p = commands+i;
- const char *prefix;
if (strcmp(p->cmd, cmd))
continue;
-
- prefix = NULL;
- if (p->option & RUN_SETUP)
- prefix = setup_git_directory();
- if (p->option & USE_PAGER)
- setup_pager();
- if ((p->option & NOT_BARE) &&
- (is_bare_repository() || is_inside_git_dir()))
- die("%s must be run in a work tree", cmd);
- trace_argv_printf(argv, argc, "trace: built-in: git");
-
- exit(p->fn(argc, argv, prefix));
+ exit(run_command(p, argc, argv));
}
}
-int main(int argc, const char **argv, char **envp)
+int main(int argc, const char **argv)
{
const char *cmd = argv[0] ? argv[0] : "git-help";
char *slash = strrchr(cmd, '/');
@@ -358,14 +409,14 @@ int main(int argc, const char **argv, char **envp)
if (!prefixcmp(cmd, "git-")) {
cmd += 4;
argv[0] = cmd;
- handle_internal_command(argc, argv, envp);
+ handle_internal_command(argc, argv);
die("cannot handle %s internally", cmd);
}
/* Look for flags.. */
argv++;
argc--;
- handle_options(&argv, &argc);
+ handle_options(&argv, &argc, NULL);
if (argc > 0) {
if (!prefixcmp(argv[0], "--"))
argv[0] += 2;
@@ -390,7 +441,7 @@ int main(int argc, const char **argv, char **envp)
while (1) {
/* See if it's an internal command */
- handle_internal_command(argc, argv, envp);
+ handle_internal_command(argc, argv);
/* .. then try the external ones */
execv_git_cmd(argv);
diff --git a/git.spec.in b/git.spec.in
index 287057e81..27182baa8 100644
--- a/git.spec.in
+++ b/git.spec.in
@@ -164,9 +164,9 @@ rm -rf $RPM_BUILD_ROOT
%{_bindir}/git-gui
%{_bindir}/git-citool
%{_datadir}/git-gui/
-%{!?_without_docs: %{_mandir}/man1/git-gui.1}
+%{!?_without_docs: %{_mandir}/man1/git-gui.1*}
%{!?_without_docs: %doc Documentation/git-gui.html}
-%{!?_without_docs: %{_mandir}/man1/git-citool.1}
+%{!?_without_docs: %{_mandir}/man1/git-citool.1*}
%{!?_without_docs: %doc Documentation/git-citool.html}
%files -n gitk
@@ -187,6 +187,9 @@ rm -rf $RPM_BUILD_ROOT
%{!?_without_docs: %doc Documentation/technical}
%changelog
+* Tue Jun 26 2007 Quy Tonthat <qtonthat@gmail.com>
+- Fixed problems looking for wrong manpages.
+
* Thu Jun 21 2007 Shawn O. Pearce <spearce@spearce.org>
- Added documentation files for git-gui
diff --git a/gitk b/gitk
index 87c3690ff..2d6a6ef9c 100755
--- a/gitk
+++ b/gitk
@@ -16,13 +16,76 @@ proc gitdir {} {
}
}
+# A simple scheduler for compute-intensive stuff.
+# The aim is to make sure that event handlers for GUI actions can
+# run at least every 50-100 ms. Unfortunately fileevent handlers are
+# run before X event handlers, so reading from a fast source can
+# make the GUI completely unresponsive.
+proc run args {
+ global isonrunq runq
+
+ set script $args
+ if {[info exists isonrunq($script)]} return
+ if {$runq eq {}} {
+ after idle dorunq
+ }
+ lappend runq [list {} $script]
+ set isonrunq($script) 1
+}
+
+proc filerun {fd script} {
+ fileevent $fd readable [list filereadable $fd $script]
+}
+
+proc filereadable {fd script} {
+ global runq
+
+ fileevent $fd readable {}
+ if {$runq eq {}} {
+ after idle dorunq
+ }
+ lappend runq [list $fd $script]
+}
+
+proc dorunq {} {
+ global isonrunq runq
+
+ set tstart [clock clicks -milliseconds]
+ set t0 $tstart
+ while {$runq ne {}} {
+ set fd [lindex $runq 0 0]
+ set script [lindex $runq 0 1]
+ set repeat [eval $script]
+ set t1 [clock clicks -milliseconds]
+ set t [expr {$t1 - $t0}]
+ set runq [lrange $runq 1 end]
+ if {$repeat ne {} && $repeat} {
+ if {$fd eq {} || $repeat == 2} {
+ # script returns 1 if it wants to be readded
+ # file readers return 2 if they could do more straight away
+ lappend runq [list $fd $script]
+ } else {
+ fileevent $fd readable [list filereadable $fd $script]
+ }
+ } elseif {$fd eq {}} {
+ unset isonrunq($script)
+ }
+ set t0 $t1
+ if {$t1 - $tstart >= 80} break
+ }
+ if {$runq ne {}} {
+ after idle dorunq
+ }
+}
+
+# Start off a git rev-list process and arrange to read its output
proc start_rev_list {view} {
- global startmsecs nextupdate
+ global startmsecs
global commfd leftover tclencoding datemode
global viewargs viewfiles commitidx
+ global lookingforhead showlocalchanges
set startmsecs [clock clicks -milliseconds]
- set nextupdate [expr {$startmsecs + 100}]
set commitidx($view) 0
set args $viewargs($view)
if {$viewfiles($view) ne {}} {
@@ -41,11 +104,12 @@ proc start_rev_list {view} {
}
set commfd($view) $fd
set leftover($view) {}
+ set lookingforhead $showlocalchanges
fconfigure $fd -blocking 0 -translation lf
if {$tclencoding != {}} {
fconfigure $fd -encoding $tclencoding
}
- fileevent $fd readable [list getcommitlines $fd $view]
+ filerun $fd [list getcommitlines $fd $view]
nowbusy $view
}
@@ -72,15 +136,17 @@ proc getcommits {} {
}
proc getcommitlines {fd view} {
- global commitlisted nextupdate
+ global commitlisted
global leftover commfd
global displayorder commitidx commitrow commitdata
- global parentlist childlist children curview hlview
- global vparentlist vchildlist vdisporder vcmitlisted
+ global parentlist children curview hlview
+ global vparentlist vdisporder vcmitlisted
set stuff [read $fd 500000]
if {$stuff == {}} {
- if {![eof $fd]} return
+ if {![eof $fd]} {
+ return 1
+ }
global viewname
unset commfd($view)
notbusy $view
@@ -105,9 +171,9 @@ proc getcommitlines {fd view} {
error_popup $err
}
if {$view == $curview} {
- after idle finishcommits
+ run chewcommits $view
}
- return
+ return 0
}
set start 0
set gotsome 0
@@ -171,41 +237,52 @@ proc getcommitlines {fd view} {
incr commitidx($view)
if {$view == $curview} {
lappend parentlist $olds
- lappend childlist $children($view,$id)
lappend displayorder $id
lappend commitlisted $listed
} else {
lappend vparentlist($view) $olds
- lappend vchildlist($view) $children($view,$id)
lappend vdisporder($view) $id
lappend vcmitlisted($view) $listed
}
set gotsome 1
}
if {$gotsome} {
- if {$view == $curview} {
- while {[layoutmore $nextupdate]} doupdate
- } elseif {[info exists hlview] && $view == $hlview} {
- vhighlightmore
- }
- }
- if {[clock clicks -milliseconds] >= $nextupdate} {
- doupdate
+ run chewcommits $view
}
+ return 2
}
-proc doupdate {} {
- global commfd nextupdate numcommits
+proc chewcommits {view} {
+ global curview hlview commfd
+ global selectedline pending_select
+
+ set more 0
+ if {$view == $curview} {
+ set allread [expr {![info exists commfd($view)]}]
+ set tlimit [expr {[clock clicks -milliseconds] + 50}]
+ set more [layoutmore $tlimit $allread]
+ if {$allread && !$more} {
+ global displayorder nullid commitidx phase
+ global numcommits startmsecs
- foreach v [array names commfd] {
- fileevent $commfd($v) readable {}
+ if {[info exists pending_select]} {
+ set row [expr {[lindex $displayorder 0] eq $nullid}]
+ selectline $row 1
+ }
+ if {$commitidx($curview) > 0} {
+ #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
+ #puts "overall $ms ms for $numcommits commits"
+ } else {
+ show_status "No commits selected"
+ }
+ notbusy layout
+ set phase {}
+ }
}
- update
- set nextupdate [expr {[clock clicks -milliseconds] + 100}]
- foreach v [array names commfd] {
- set fd $commfd($v)
- fileevent $fd readable [list getcommitlines $fd $v]
+ if {[info exists hlview] && $view == $hlview} {
+ vhighlightmore
}
+ return $more
}
proc readcommit {id} {
@@ -230,8 +307,9 @@ proc updatecommits {} {
catch {unset selectedline}
catch {unset thickerline}
catch {unset viewdata($n)}
- discardallcommits
readrefs
+ changedrefs
+ regetallcommits
showview $n
}
@@ -263,12 +341,16 @@ proc parsecommit {id contents listed} {
}
}
set headline {}
- # take the first line of the comment as the headline
- set i [string first "\n" $comment]
+ # take the first non-blank line of the comment as the headline
+ set headline [string trimleft $comment]
+ set i [string first "\n" $headline]
if {$i >= 0} {
- set headline [string trim [string range $comment 0 $i]]
- } else {
- set headline $comment
+ set headline [string range $headline 0 $i]
+ }
+ set headline [string trimright $headline]
+ set i [string first "\r" $headline]
+ if {$i >= 0} {
+ set headline [string trimright [string range $headline 0 $i]]
}
if {!$listed} {
# git rev-list indents the comment by 4 spaces;
@@ -303,47 +385,39 @@ proc getcommit {id} {
}
proc readrefs {} {
- global tagids idtags headids idheads tagcontents
- global otherrefids idotherrefs mainhead
+ global tagids idtags headids idheads tagobjid
+ global otherrefids idotherrefs mainhead mainheadid
foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
catch {unset $v}
}
- set refd [open [list | git show-ref] r]
- while {0 <= [set n [gets $refd line]]} {
- if {![regexp {^([0-9a-f]{40}) refs/([^^]*)$} $line \
- match id path]} {
- continue
- }
- if {[regexp {^remotes/.*/HEAD$} $path match]} {
- continue
- }
- if {![regexp {^(tags|heads)/(.*)$} $path match type name]} {
- set type others
- set name $path
- }
- if {[regexp {^remotes/} $path match]} {
- set type heads
- }
- if {$type == "tags"} {
- set tagids($name) $id
- lappend idtags($id) $name
- set obj {}
- set type {}
- set tag {}
- catch {
- set commit [exec git rev-parse "$id^0"]
- if {$commit != $id} {
- set tagids($name) $commit
- lappend idtags($commit) $name
- }
- }
- catch {
- set tagcontents($name) [exec git cat-file tag $id]
+ set refd [open [list | git show-ref -d] r]
+ while {[gets $refd line] >= 0} {
+ if {[string index $line 40] ne " "} continue
+ set id [string range $line 0 39]
+ set ref [string range $line 41 end]
+ if {![string match "refs/*" $ref]} continue
+ set name [string range $ref 5 end]
+ if {[string match "remotes/*" $name]} {
+ if {![string match "*/HEAD" $name]} {
+ set headids($name) $id
+ lappend idheads($id) $name
}
- } elseif { $type == "heads" } {
+ } elseif {[string match "heads/*" $name]} {
+ set name [string range $name 6 end]
set headids($name) $id
lappend idheads($id) $name
+ } elseif {[string match "tags/*" $name]} {
+ # this lets refs/tags/foo^{} overwrite refs/tags/foo,
+ # which is what we want since the former is the commit ID
+ set name [string range $name 5 end]
+ if {[string match "*^{}" $name]} {
+ set name [string range $name 0 end-3]
+ } else {
+ set tagobjid($name) $id
+ }
+ set tagids($name) $id
+ lappend idtags($id) $name
} else {
set otherrefids($name) $id
lappend idotherrefs($id) $name
@@ -351,12 +425,40 @@ proc readrefs {} {
}
close $refd
set mainhead {}
+ set mainheadid {}
catch {
set thehead [exec git symbolic-ref HEAD]
if {[string match "refs/heads/*" $thehead]} {
set mainhead [string range $thehead 11 end]
+ if {[info exists headids($mainhead)]} {
+ set mainheadid $headids($mainhead)
+ }
+ }
+ }
+}
+
+# update things for a head moved to a child of its previous location
+proc movehead {id name} {
+ global headids idheads
+
+ removehead $headids($name) $name
+ set headids($name) $id
+ lappend idheads($id) $name
+}
+
+# update things when a head has been removed
+proc removehead {id name} {
+ global headids idheads
+
+ if {$idheads($id) eq $name} {
+ unset idheads($id)
+ } else {
+ set i [lsearch -exact $idheads($id) $name]
+ if {$i >= 0} {
+ set idheads($id) [lreplace $idheads($id) $i $i]
}
}
+ unset headids($name)
}
proc show_error {w top msg} {
@@ -395,14 +497,14 @@ proc confirm_popup msg {
proc makewindow {} {
global canv canv2 canv3 linespc charspc ctext cflist
- global textfont mainfont uifont
+ global textfont mainfont uifont tabstop
global findtype findtypemenu findloc findstring fstring geometry
global entries sha1entry sha1string sha1but
global maincursor textcursor curtextcursor
- global rowctxmenu mergemax wrapcomment
+ global rowctxmenu fakerowmenu mergemax wrapcomment
global highlight_files gdttype
global searchstring sstring
- global bgcolor fgcolor bglist fglist diffcolors
+ global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
global headctxmenu
menu .bar
@@ -457,15 +559,18 @@ proc makewindow {} {
set cscroll .tf.histframe.csb
set canv .tf.histframe.pwclist.canv
canvas $canv \
+ -selectbackground $selectbgcolor \
-background $bgcolor -bd 0 \
-yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
.tf.histframe.pwclist add $canv
set canv2 .tf.histframe.pwclist.canv2
canvas $canv2 \
+ -selectbackground $selectbgcolor \
-background $bgcolor -bd 0 -yscrollincr $linespc
.tf.histframe.pwclist add $canv2
set canv3 .tf.histframe.pwclist.canv3
canvas $canv3 \
+ -selectbackground $selectbgcolor \
-background $bgcolor -bd 0 -yscrollincr $linespc
.tf.histframe.pwclist add $canv3
eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
@@ -612,6 +717,7 @@ proc makewindow {} {
pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
set ctext .bleft.ctext
text $ctext -background $bgcolor -foreground $fgcolor \
+ -tabs "[expr {$tabstop * $charspc}]" \
-state disabled -font $textfont \
-yscrollcommand scrolltext -wrap none
scrollbar .bleft.sb -command "$ctext yview"
@@ -666,6 +772,7 @@ proc makewindow {} {
set cflist .bright.cfiles
set indent [font measure $mainfont "nn"]
text $cflist \
+ -selectbackground $selectbgcolor \
-background $bgcolor -foreground $fgcolor \
-font $mainfont \
-tabs [list $indent [expr {2 * $indent}]] \
@@ -766,6 +873,19 @@ proc makewindow {} {
$rowctxmenu add command -label "Create new branch" -command mkbranch
$rowctxmenu add command -label "Cherry-pick this commit" \
-command cherrypick
+ $rowctxmenu add command -label "Reset HEAD branch to here" \
+ -command resethead
+
+ set fakerowmenu .fakerowmenu
+ menu $fakerowmenu -tearoff 0
+ $fakerowmenu add command -label "Diff this -> selected" \
+ -command {diffvssel 0}
+ $fakerowmenu add command -label "Diff selected -> this" \
+ -command {diffvssel 1}
+ $fakerowmenu add command -label "Make patch" -command mkpatch
+# $fakerowmenu add command -label "Commit" -command {mkcommit 0}
+# $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
+# $fakerowmenu add command -label "Revert local changes" -command revertlocal
set headctxmenu .headctxmenu
menu $headctxmenu -tearoff 0
@@ -820,12 +940,12 @@ proc click {w} {
}
proc savestuff {w} {
- global canv canv2 canv3 ctext cflist mainfont textfont uifont
+ global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop
global stuffsaved findmergefiles maxgraphpct
- global maxwidth showneartags
+ global maxwidth showneartags showlocalchanges
global viewname viewfiles viewargs viewperm nextviewnum
global cmitmode wrapcomment
- global colors bgcolor fgcolor diffcolors
+ global colors bgcolor fgcolor diffcolors selectbgcolor
if {$stuffsaved} return
if {![winfo viewable .]} return
@@ -834,16 +954,19 @@ proc savestuff {w} {
puts $f [list set mainfont $mainfont]
puts $f [list set textfont $textfont]
puts $f [list set uifont $uifont]
+ puts $f [list set tabstop $tabstop]
puts $f [list set findmergefiles $findmergefiles]
puts $f [list set maxgraphpct $maxgraphpct]
puts $f [list set maxwidth $maxwidth]
puts $f [list set cmitmode $cmitmode]
puts $f [list set wrapcomment $wrapcomment]
puts $f [list set showneartags $showneartags]
+ puts $f [list set showlocalchanges $showlocalchanges]
puts $f [list set bgcolor $bgcolor]
puts $f [list set fgcolor $fgcolor]
puts $f [list set colors $colors]
puts $f [list set diffcolors $diffcolors]
+ puts $f [list set selectbgcolor $selectbgcolor]
puts $f "set geometry(main) [wm geometry .]"
puts $f "set geometry(topwidth) [winfo width .tf]"
@@ -1562,9 +1685,9 @@ proc newviewok {top n} {
set viewargs($n) $newargs
addviewmenu $n
if {!$newishighlight} {
- after idle showview $n
+ run showview $n
} else {
- after idle addvhighlight $n
+ run addvhighlight $n
}
} else {
# editing an existing view
@@ -1580,7 +1703,7 @@ proc newviewok {top n} {
set viewfiles($n) $files
set viewargs($n) $newargs
if {$curview == $n} {
- after idle updatecommits
+ run updatecommits
}
}
}
@@ -1631,16 +1754,16 @@ proc unflatten {var l} {
proc showview {n} {
global curview viewdata viewfiles
- global displayorder parentlist childlist rowidlist rowoffsets
+ global displayorder parentlist rowidlist rowoffsets
global colormap rowtextx commitrow nextcolor canvxmax
- global numcommits rowrangelist commitlisted idrowranges
+ global numcommits rowrangelist commitlisted idrowranges rowchk
global selectedline currentid canv canvy0
global matchinglines treediffs
global pending_select phase
- global commitidx rowlaidout rowoptim linesegends
- global commfd nextupdate
- global selectedview
- global vparentlist vchildlist vdisporder vcmitlisted
+ global commitidx rowlaidout rowoptim
+ global commfd
+ global selectedview selectfirst
+ global vparentlist vdisporder vcmitlisted
global hlview selectedhlview
if {$n == $curview} return
@@ -1657,20 +1780,22 @@ proc showview {n} {
} else {
set yscreen [expr {($ybot - $ytop) / 2}]
}
+ } elseif {[info exists pending_select]} {
+ set selid $pending_select
+ unset pending_select
}
unselectline
normalline
stopfindproc
if {$curview >= 0} {
set vparentlist($curview) $parentlist
- set vchildlist($curview) $childlist
set vdisporder($curview) $displayorder
set vcmitlisted($curview) $commitlisted
if {$phase ne {}} {
set viewdata($curview) \
[list $phase $rowidlist $rowoffsets $rowrangelist \
[flatten idrowranges] [flatten idinlist] \
- $rowlaidout $rowoptim $numcommits $linesegends]
+ $rowlaidout $rowoptim $numcommits]
} elseif {![info exists viewdata($curview)]
|| [lindex $viewdata($curview) 0] ne {}} {
set viewdata($curview) \
@@ -1691,7 +1816,9 @@ proc showview {n} {
.bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
if {![info exists viewdata($n)]} {
- set pending_select $selid
+ if {$selid ne {}} {
+ set pending_select $selid
+ }
getcommits
return
}
@@ -1700,7 +1827,6 @@ proc showview {n} {
set phase [lindex $v 0]
set displayorder $vdisporder($n)
set parentlist $vparentlist($n)
- set childlist $vchildlist($n)
set commitlisted $vcmitlisted($n)
set rowidlist [lindex $v 1]
set rowoffsets [lindex $v 2]
@@ -1714,7 +1840,7 @@ proc showview {n} {
set rowlaidout [lindex $v 6]
set rowoptim [lindex $v 7]
set numcommits [lindex $v 8]
- set linesegends [lindex $v 9]
+ catch {unset rowchk}
}
catch {unset colormap}
@@ -1725,7 +1851,8 @@ proc showview {n} {
set row 0
setcanvscroll
set yf 0
- set row 0
+ set row {}
+ set selectfirst 0
if {$selid ne {} && [info exists commitrow($n,$selid)]} {
set row $commitrow($n,$selid)
# try to get the selected row in the same position on the screen
@@ -1738,16 +1865,23 @@ proc showview {n} {
}
allcanvs yview moveto $yf
drawvisible
- selectline $row 0
+ if {$row ne {}} {
+ selectline $row 0
+ } elseif {$selid ne {}} {
+ set pending_select $selid
+ } else {
+ set row [expr {[lindex $displayorder 0] eq $nullid}]
+ if {$row < $numcommits} {
+ selectline $row 0
+ } else {
+ set selectfirst 1
+ }
+ }
if {$phase ne {}} {
if {$phase eq "getcommits"} {
show_status "Reading commits..."
}
- if {[info exists commfd($n)]} {
- layoutmore {}
- } else {
- finishcommits
- }
+ run chewcommits $n
} elseif {$numcommits == 0} {
show_status "No commits selected"
}
@@ -1825,7 +1959,6 @@ proc addvhighlight {n} {
if {$n != $curview && ![info exists viewdata($n)]} {
set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
set vparentlist($n) {}
- set vchildlist($n) {}
set vdisporder($n) {}
set vcmitlisted($n) {}
start_rev_list $n
@@ -1935,7 +2068,7 @@ proc do_file_hl {serial} {
set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
set filehighlight [open $cmd r+]
fconfigure $filehighlight -blocking 0
- fileevent $filehighlight readable readfhighlight
+ filerun $filehighlight readfhighlight
set fhl_list {}
drawvisible
flushhighlights
@@ -1963,7 +2096,11 @@ proc readfhighlight {} {
global filehighlight fhighlights commitrow curview mainfont iddrawn
global fhl_list
- while {[gets $filehighlight line] >= 0} {
+ if {![info exists filehighlight]} {
+ return 0
+ }
+ set nr 0
+ while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
set line [string trim $line]
set i [lsearch -exact $fhl_list $line]
if {$i < 0} continue
@@ -1987,8 +2124,10 @@ proc readfhighlight {} {
puts "oops, git diff-tree died"
catch {close $filehighlight}
unset filehighlight
+ return 0
}
next_hlcont
+ return 1
}
proc find_change {name ix op} {
@@ -2055,7 +2194,7 @@ proc vrel_change {name ix op} {
rhighlight_none
if {$highlight_related ne "None"} {
- after idle drawvisible
+ run drawvisible
}
}
@@ -2070,7 +2209,7 @@ proc rhighlight_sel {a} {
set anc_todo [list $a]
if {$highlight_related ne "None"} {
rhighlight_none
- after idle drawvisible
+ run drawvisible
}
}
@@ -2288,17 +2427,15 @@ proc ntimes {n o} {
}
proc usedinrange {id l1 l2} {
- global children commitrow childlist curview
+ global children commitrow curview
if {[info exists commitrow($curview,$id)]} {
set r $commitrow($curview,$id)
if {$l1 <= $r && $r <= $l2} {
return [expr {$r - $l1 + 1}]
}
- set kids [lindex $childlist $r]
- } else {
- set kids $children($curview,$id)
}
+ set kids $children($curview,$id)
foreach c $kids {
set r $commitrow($curview,$c)
if {$l1 <= $r && $r <= $l2} {
@@ -2341,7 +2478,7 @@ proc sanity {row {full 0}} {
}
proc makeuparrow {oid x y z} {
- global rowidlist rowoffsets uparrowlen idrowranges
+ global rowidlist rowoffsets uparrowlen idrowranges displayorder
for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} {
incr y -1
@@ -2364,7 +2501,7 @@ proc makeuparrow {oid x y z} {
}
set tmp [lreplace [lindex $rowoffsets $y] $x $x {}]
lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1]
- lappend idrowranges($oid) $y
+ lappend idrowranges($oid) [lindex $displayorder $y]
}
proc initlayout {} {
@@ -2373,15 +2510,14 @@ proc initlayout {} {
global idinlist rowchk rowrangelist idrowranges
global numcommits canvxmax canv
global nextcolor
- global parentlist childlist children
+ global parentlist
global colormap rowtextx
- global linesegends
+ global selectfirst
set numcommits 0
set displayorder {}
set commitlisted {}
set parentlist {}
- set childlist {}
set rowrangelist {}
set nextcolor 0
set rowidlist {{}}
@@ -2394,7 +2530,7 @@ proc initlayout {} {
catch {unset colormap}
catch {unset rowtextx}
catch {unset idrowranges}
- set linesegends {}
+ set selectfirst 1
}
proc setcanvscroll {} {
@@ -2425,15 +2561,18 @@ proc visiblerows {} {
return [list $r0 $r1]
}
-proc layoutmore {tmax} {
+proc layoutmore {tmax allread} {
global rowlaidout rowoptim commitidx numcommits optim_delay
- global uparrowlen curview
+ global uparrowlen curview rowidlist idinlist
+ set showlast 0
+ set showdelay $optim_delay
+ set optdelay [expr {$uparrowlen + 1}]
while {1} {
- if {$rowoptim - $optim_delay > $numcommits} {
- showstuff [expr {$rowoptim - $optim_delay}]
- } elseif {$rowlaidout - $uparrowlen - 1 > $rowoptim} {
- set nr [expr {$rowlaidout - $uparrowlen - 1 - $rowoptim}]
+ if {$rowoptim - $showdelay > $numcommits} {
+ showstuff [expr {$rowoptim - $showdelay}] $showlast
+ } elseif {$rowlaidout - $optdelay > $rowoptim} {
+ set nr [expr {$rowlaidout - $optdelay - $rowoptim}]
if {$nr > 100} {
set nr 100
}
@@ -2447,10 +2586,24 @@ proc layoutmore {tmax} {
set nr 150
}
set row $rowlaidout
- set rowlaidout [layoutrows $row [expr {$row + $nr}] 0]
+ set rowlaidout [layoutrows $row [expr {$row + $nr}] $allread]
if {$rowlaidout == $row} {
return 0
}
+ } elseif {$allread} {
+ set optdelay 0
+ set nrows $commitidx($curview)
+ if {[lindex $rowidlist $nrows] ne {} ||
+ [array names idinlist] ne {}} {
+ layouttail
+ set rowlaidout $commitidx($curview)
+ } elseif {$rowoptim == $nrows} {
+ set showdelay 0
+ set showlast 1
+ if {$numcommits == $nrows} {
+ return 0
+ }
+ }
} else {
return 0
}
@@ -2460,57 +2613,116 @@ proc layoutmore {tmax} {
}
}
-proc showstuff {canshow} {
- global numcommits commitrow pending_select selectedline
- global linesegends idrowranges idrangedrawn curview
+proc showstuff {canshow last} {
+ global numcommits commitrow pending_select selectedline curview
+ global lookingforhead mainheadid displayorder nullid selectfirst
+ global lastscrollset
if {$numcommits == 0} {
global phase
set phase "incrdraw"
allcanvs delete all
}
- set row $numcommits
+ set r0 $numcommits
+ set prev $numcommits
set numcommits $canshow
- setcanvscroll
+ set t [clock clicks -milliseconds]
+ if {$prev < 100 || $last || $t - $lastscrollset > 500} {
+ set lastscrollset $t
+ setcanvscroll
+ }
set rows [visiblerows]
- set r0 [lindex $rows 0]
set r1 [lindex $rows 1]
- set selrow -1
- for {set r $row} {$r < $canshow} {incr r} {
- foreach id [lindex $linesegends [expr {$r+1}]] {
- set i -1
- foreach {s e} [rowranges $id] {
- incr i
- if {$e ne {} && $e < $numcommits && $s <= $r1 && $e >= $r0
- && ![info exists idrangedrawn($id,$i)]} {
- drawlineseg $id $i
- set idrangedrawn($id,$i) 1
- }
- }
- }
- }
- if {$canshow > $r1} {
- set canshow $r1
+ if {$r1 >= $canshow} {
+ set r1 [expr {$canshow - 1}]
}
- while {$row < $canshow} {
- drawcmitrow $row
- incr row
+ if {$r0 <= $r1} {
+ drawcommits $r0 $r1
}
if {[info exists pending_select] &&
[info exists commitrow($curview,$pending_select)] &&
$commitrow($curview,$pending_select) < $numcommits} {
selectline $commitrow($curview,$pending_select) 1
}
- if {![info exists selectedline] && ![info exists pending_select]} {
- selectline 0 1
+ if {$selectfirst} {
+ if {[info exists selectedline] || [info exists pending_select]} {
+ set selectfirst 0
+ } else {
+ set l [expr {[lindex $displayorder 0] eq $nullid}]
+ selectline $l 1
+ set selectfirst 0
+ }
+ }
+ if {$lookingforhead && [info exists commitrow($curview,$mainheadid)]
+ && ($last || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
+ set lookingforhead 0
+ dodiffindex
+ }
+}
+
+proc doshowlocalchanges {} {
+ global lookingforhead curview mainheadid phase commitrow
+
+ if {[info exists commitrow($curview,$mainheadid)] &&
+ ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
+ dodiffindex
+ } elseif {$phase ne {}} {
+ set lookingforhead 1
+ }
+}
+
+proc dohidelocalchanges {} {
+ global lookingforhead localrow lserial
+
+ set lookingforhead 0
+ if {$localrow >= 0} {
+ removerow $localrow
+ set localrow -1
+ }
+ incr lserial
+}
+
+# spawn off a process to do git diff-index HEAD
+proc dodiffindex {} {
+ global localrow lserial
+
+ incr lserial
+ set localrow -1
+ set fd [open "|git diff-index HEAD" r]
+ fconfigure $fd -blocking 0
+ filerun $fd [list readdiffindex $fd $lserial]
+}
+
+proc readdiffindex {fd serial} {
+ global localrow commitrow mainheadid nullid curview
+ global commitinfo commitdata lserial
+
+ if {[gets $fd line] < 0} {
+ if {[eof $fd]} {
+ close $fd
+ return 0
+ }
+ return 1
+ }
+ # we only need to see one line and we don't really care what it says...
+ close $fd
+
+ if {$serial == $lserial && $localrow == -1} {
+ # add the line for the local diff to the graph
+ set localrow $commitrow($curview,$mainheadid)
+ set hl "Local uncommitted changes"
+ set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
+ set commitdata($nullid) "\n $hl\n"
+ insertrow $localrow $nullid
}
+ return 0
}
proc layoutrows {row endrow last} {
global rowidlist rowoffsets displayorder
global uparrowlen downarrowlen maxwidth mingaplen
- global childlist parentlist
- global idrowranges linesegends
+ global children parentlist
+ global idrowranges
global commitidx curview
global idinlist rowchk rowrangelist
@@ -2527,7 +2739,6 @@ proc layoutrows {row endrow last} {
lappend oldolds $p
}
}
- set lse {}
set nev [expr {[llength $idlist] + [llength $newolds]
+ [llength $oldolds] - $maxwidth + 1}]
if {$nev > 0} {
@@ -2544,8 +2755,7 @@ proc layoutrows {row endrow last} {
set offs [incrange $offs $x 1]
set idinlist($i) 0
set rm1 [expr {$row - 1}]
- lappend lse $i
- lappend idrowranges($i) $rm1
+ lappend idrowranges($i) [lindex $displayorder $rm1]
if {[incr nev -1] <= 0} break
continue
}
@@ -2555,14 +2765,13 @@ proc layoutrows {row endrow last} {
lset rowidlist $row $idlist
lset rowoffsets $row $offs
}
- lappend linesegends $lse
set col [lsearch -exact $idlist $id]
if {$col < 0} {
set col [llength $idlist]
lappend idlist $id
lset rowidlist $row $idlist
set z {}
- if {[lindex $childlist $row] ne {}} {
+ if {$children($curview,$id) ne {}} {
set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}]
unset idinlist($id)
}
@@ -2577,7 +2786,7 @@ proc layoutrows {row endrow last} {
set ranges {}
if {[info exists idrowranges($id)]} {
set ranges $idrowranges($id)
- lappend ranges $row
+ lappend ranges $id
unset idrowranges($id)
}
lappend rowrangelist $ranges
@@ -2602,7 +2811,7 @@ proc layoutrows {row endrow last} {
}
foreach i $newolds {
set idinlist($i) 1
- set idrowranges($i) $row
+ set idrowranges($i) $id
}
incr col $l
foreach oid $oldolds {
@@ -2621,7 +2830,7 @@ proc layoutrows {row endrow last} {
proc addextraid {id row} {
global displayorder commitrow commitinfo
global commitidx commitlisted
- global parentlist childlist children curview
+ global parentlist children curview
incr commitidx($curview)
lappend displayorder $id
@@ -2635,7 +2844,6 @@ proc addextraid {id row} {
if {![info exists children($curview,$id)]} {
set children($curview,$id) {}
}
- lappend childlist $children($curview,$id)
}
proc layouttail {} {
@@ -2660,6 +2868,7 @@ proc layouttail {} {
}
foreach id [array names idinlist] {
+ unset idinlist($id)
addextraid $id $row
lset rowidlist $row [list $id]
lset rowoffsets $row 0
@@ -2683,7 +2892,7 @@ proc insert_pad {row col npad} {
}
proc optimize_rows {row col endrow} {
- global rowidlist rowoffsets idrowranges displayorder
+ global rowidlist rowoffsets displayorder
for {} {$row < $endrow} {incr row} {
set idlist [lindex $rowidlist $row]
@@ -2707,7 +2916,13 @@ proc optimize_rows {row col endrow} {
set isarrow 1
}
}
+ # Looking at lines from this row to the previous row,
+ # make them go straight up if they end in an arrow on
+ # the previous row; otherwise make them go straight up
+ # or at 45 degrees.
if {$z < -1 || ($z < 0 && $isarrow)} {
+ # Line currently goes left too much;
+ # insert pads in the previous row, then optimize it
set npad [expr {-1 - $z + $isarrow}]
set offs [incrange $offs $col $npad]
insert_pad $y0 $x0 $npad
@@ -2718,6 +2933,8 @@ proc optimize_rows {row col endrow} {
set x0 [expr {$col + $z}]
set z0 [lindex $rowoffsets $y0 $x0]
} elseif {$z > 1 || ($z > 0 && $isarrow)} {
+ # Line currently goes right too much;
+ # insert pads in this line and adjust the next's rowoffsets
set npad [expr {$z - 1 + $isarrow}]
set y1 [expr {$row + 1}]
set offs2 [lindex $rowoffsets $y1]
@@ -2748,6 +2965,7 @@ proc optimize_rows {row col endrow} {
set z0 [expr {$xc - $x0}]
}
}
+ # avoid lines jigging left then immediately right
if {$z0 ne {} && $z < 0 && $z0 > 0} {
insert_pad $y0 $x0 1
set offs [incrange $offs $col 1]
@@ -2756,6 +2974,7 @@ proc optimize_rows {row col endrow} {
}
if {!$haspad} {
set o {}
+ # Find the first column that doesn't have a line going right
for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
set o [lindex $offs $col]
if {$o eq {}} {
@@ -2774,6 +2993,8 @@ proc optimize_rows {row col endrow} {
}
if {$o eq {} || $o <= 0} break
}
+ # Insert a pad at that column as long as it has a line and
+ # isn't the last column, and adjust the next row' offsets
if {$o ne {} && [incr col] < [llength $idlist]} {
set y1 [expr {$row + 1}]
set offs2 [lindex $rowoffsets $y1]
@@ -2827,99 +3048,216 @@ proc rowranges {id} {
} elseif {[info exists idrowranges($id)]} {
set ranges $idrowranges($id)
}
- return $ranges
+ set linenos {}
+ foreach rid $ranges {
+ lappend linenos $commitrow($curview,$rid)
+ }
+ if {$linenos ne {}} {
+ lset linenos 0 [expr {[lindex $linenos 0] + 1}]
+ }
+ return $linenos
}
-proc drawlineseg {id i} {
- global rowoffsets rowidlist
- global displayorder
- global canv colormap linespc
- global numcommits commitrow curview
+# work around tk8.4 refusal to draw arrows on diagonal segments
+proc adjarrowhigh {coords} {
+ global linespc
- set ranges [rowranges $id]
- set downarrow 1
- if {[info exists commitrow($curview,$id)]
- && $commitrow($curview,$id) < $numcommits} {
- set downarrow [expr {$i < [llength $ranges] / 2 - 1}]
- } else {
- set downarrow 1
- }
- set startrow [lindex $ranges [expr {2 * $i}]]
- set row [lindex $ranges [expr {2 * $i + 1}]]
- if {$startrow == $row} return
- assigncolor $id
- set coords {}
- set col [lsearch -exact [lindex $rowidlist $row] $id]
- if {$col < 0} {
- puts "oops: drawline: id $id not on row $row"
- return
+ set x0 [lindex $coords 0]
+ set x1 [lindex $coords 2]
+ if {$x0 != $x1} {
+ set y0 [lindex $coords 1]
+ set y1 [lindex $coords 3]
+ if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
+ # we have a nearby vertical segment, just trim off the diag bit
+ set coords [lrange $coords 2 end]
+ } else {
+ set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
+ set xi [expr {$x0 - $slope * $linespc / 2}]
+ set yi [expr {$y0 - $linespc / 2}]
+ set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
+ }
}
- set lasto {}
- set ns 0
+ return $coords
+}
+
+proc drawlineseg {id row endrow arrowlow} {
+ global rowidlist displayorder iddrawn linesegs
+ global canv colormap linespc curview maxlinelen
+
+ set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
+ set le [expr {$row + 1}]
+ set arrowhigh 1
while {1} {
- set o [lindex $rowoffsets $row $col]
- if {$o eq {}} break
- if {$o ne $lasto} {
- # changing direction
- set x [xc $row $col]
- set y [yc $row]
- lappend coords $x $y
- set lasto $o
+ set c [lsearch -exact [lindex $rowidlist $le] $id]
+ if {$c < 0} {
+ incr le -1
+ break
+ }
+ lappend cols $c
+ set x [lindex $displayorder $le]
+ if {$x eq $id} {
+ set arrowhigh 0
+ break
}
- incr col $o
- incr row -1
+ if {[info exists iddrawn($x)] || $le == $endrow} {
+ set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
+ if {$c >= 0} {
+ lappend cols $c
+ set arrowhigh 0
+ }
+ break
+ }
+ incr le
}
- set x [xc $row $col]
- set y [yc $row]
- lappend coords $x $y
- if {$i == 0} {
- # draw the link to the first child as part of this line
- incr row -1
- set child [lindex $displayorder $row]
- set ccol [lsearch -exact [lindex $rowidlist $row] $child]
- if {$ccol >= 0} {
- set x [xc $row $ccol]
- set y [yc $row]
- if {$ccol < $col - 1} {
- lappend coords [xc $row [expr {$col - 1}]] [yc $row]
- } elseif {$ccol > $col + 1} {
- lappend coords [xc $row [expr {$col + 1}]] [yc $row]
+ if {$le <= $row} {
+ return $row
+ }
+
+ set lines {}
+ set i 0
+ set joinhigh 0
+ if {[info exists linesegs($id)]} {
+ set lines $linesegs($id)
+ foreach li $lines {
+ set r0 [lindex $li 0]
+ if {$r0 > $row} {
+ if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
+ set joinhigh 1
+ }
+ break
}
- lappend coords $x $y
- }
- }
- if {[llength $coords] < 4} return
- if {$downarrow} {
- # This line has an arrow at the lower end: check if the arrow is
- # on a diagonal segment, and if so, work around the Tk 8.4
- # refusal to draw arrows on diagonal lines.
- set x0 [lindex $coords 0]
- set x1 [lindex $coords 2]
- if {$x0 != $x1} {
- set y0 [lindex $coords 1]
- set y1 [lindex $coords 3]
- if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
- # we have a nearby vertical segment, just trim off the diag bit
- set coords [lrange $coords 2 end]
+ incr i
+ }
+ }
+ set joinlow 0
+ if {$i > 0} {
+ set li [lindex $lines [expr {$i-1}]]
+ set r1 [lindex $li 1]
+ if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
+ set joinlow 1
+ }
+ }
+
+ set x [lindex $cols [expr {$le - $row}]]
+ set xp [lindex $cols [expr {$le - 1 - $row}]]
+ set dir [expr {$xp - $x}]
+ if {$joinhigh} {
+ set ith [lindex $lines $i 2]
+ set coords [$canv coords $ith]
+ set ah [$canv itemcget $ith -arrow]
+ set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
+ set x2 [lindex $cols [expr {$le + 1 - $row}]]
+ if {$x2 ne {} && $x - $x2 == $dir} {
+ set coords [lrange $coords 0 end-2]
+ }
+ } else {
+ set coords [list [xc $le $x] [yc $le]]
+ }
+ if {$joinlow} {
+ set itl [lindex $lines [expr {$i-1}] 2]
+ set al [$canv itemcget $itl -arrow]
+ set arrowlow [expr {$al eq "last" || $al eq "both"}]
+ } elseif {$arrowlow &&
+ [lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0} {
+ set arrowlow 0
+ }
+ set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
+ for {set y $le} {[incr y -1] > $row} {} {
+ set x $xp
+ set xp [lindex $cols [expr {$y - 1 - $row}]]
+ set ndir [expr {$xp - $x}]
+ if {$dir != $ndir || $xp < 0} {
+ lappend coords [xc $y $x] [yc $y]
+ }
+ set dir $ndir
+ }
+ if {!$joinlow} {
+ if {$xp < 0} {
+ # join parent line to first child
+ set ch [lindex $displayorder $row]
+ set xc [lsearch -exact [lindex $rowidlist $row] $ch]
+ if {$xc < 0} {
+ puts "oops: drawlineseg: child $ch not on row $row"
+ } else {
+ if {$xc < $x - 1} {
+ lappend coords [xc $row [expr {$x-1}]] [yc $row]
+ } elseif {$xc > $x + 1} {
+ lappend coords [xc $row [expr {$x+1}]] [yc $row]
+ }
+ set x $xc
+ }
+ lappend coords [xc $row $x] [yc $row]
+ } else {
+ set xn [xc $row $xp]
+ set yn [yc $row]
+ # work around tk8.4 refusal to draw arrows on diagonal segments
+ if {$arrowlow && $xn != [lindex $coords end-1]} {
+ if {[llength $coords] < 4 ||
+ [lindex $coords end-3] != [lindex $coords end-1] ||
+ [lindex $coords end] - $yn > 2 * $linespc} {
+ set xn [xc $row [expr {$xp - 0.5 * $dir}]]
+ set yo [yc [expr {$row + 0.5}]]
+ lappend coords $xn $yo $xn $yn
+ }
} else {
- set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
- set xi [expr {$x0 - $slope * $linespc / 2}]
- set yi [expr {$y0 - $linespc / 2}]
- set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
+ lappend coords $xn $yn
+ }
+ }
+ if {!$joinhigh} {
+ if {$arrowhigh} {
+ set coords [adjarrowhigh $coords]
+ }
+ assigncolor $id
+ set t [$canv create line $coords -width [linewidth $id] \
+ -fill $colormap($id) -tags lines.$id -arrow $arrow]
+ $canv lower $t
+ bindline $t $id
+ set lines [linsert $lines $i [list $row $le $t]]
+ } else {
+ $canv coords $ith $coords
+ if {$arrow ne $ah} {
+ $canv itemconf $ith -arrow $arrow
}
+ lset lines $i 0 $row
+ }
+ } else {
+ set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
+ set ndir [expr {$xo - $xp}]
+ set clow [$canv coords $itl]
+ if {$dir == $ndir} {
+ set clow [lrange $clow 2 end]
+ }
+ set coords [concat $coords $clow]
+ if {!$joinhigh} {
+ lset lines [expr {$i-1}] 1 $le
+ if {$arrowhigh} {
+ set coords [adjarrowhigh $coords]
+ }
+ } else {
+ # coalesce two pieces
+ $canv delete $ith
+ set b [lindex $lines [expr {$i-1}] 0]
+ set e [lindex $lines $i 1]
+ set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
+ }
+ $canv coords $itl $coords
+ if {$arrow ne $al} {
+ $canv itemconf $itl -arrow $arrow
}
}
- set arrow [expr {2 * ($i > 0) + $downarrow}]
- set arrow [lindex {none first last both} $arrow]
- set t [$canv create line $coords -width [linewidth $id] \
- -fill $colormap($id) -tags lines.$id -arrow $arrow]
- $canv lower $t
- bindline $t $id
+
+ set linesegs($id) $lines
+ return $le
}
-proc drawparentlinks {id row col olds} {
- global rowidlist canv colormap
+proc drawparentlinks {id row} {
+ global rowidlist canv colormap curview parentlist
+ global idpos
+ set rowids [lindex $rowidlist $row]
+ set col [lsearch -exact $rowids $id]
+ if {$col < 0} return
+ set olds [lindex $parentlist $row]
set row2 [expr {$row + 1}]
set x [xc $row $col]
set y [yc $row]
@@ -2937,9 +3275,7 @@ proc drawparentlinks {id row col olds} {
if {$x2 > $rmx} {
set rmx $x2
}
- set ranges [rowranges $p]
- if {$ranges ne {} && $row2 == [lindex $ranges 0]
- && $row2 < [lindex $ranges 1]} {
+ if {[lsearch -exact $rowids $p] < 0} {
# drawlineseg will do this one for us
continue
}
@@ -2957,40 +3293,30 @@ proc drawparentlinks {id row col olds} {
$canv lower $t
bindline $t $p
}
- return $rmx
+ if {$rmx > [lindex $idpos($id) 1]} {
+ lset idpos($id) 1 $rmx
+ redrawtags $id
+ }
}
proc drawlines {id} {
- global colormap canv
- global idrangedrawn
- global children iddrawn commitrow rowidlist curview
-
- $canv delete lines.$id
- set nr [expr {[llength [rowranges $id]] / 2}]
- for {set i 0} {$i < $nr} {incr i} {
- if {[info exists idrangedrawn($id,$i)]} {
- drawlineseg $id $i
- }
- }
- foreach child $children($curview,$id) {
- if {[info exists iddrawn($child)]} {
- set row $commitrow($curview,$child)
- set col [lsearch -exact [lindex $rowidlist $row] $child]
- if {$col >= 0} {
- drawparentlinks $child $row $col [list $id]
- }
- }
- }
+ global canv
+
+ $canv itemconf lines.$id -width [linewidth $id]
}
-proc drawcmittext {id row col rmx} {
+proc drawcmittext {id row col} {
global linespc canv canv2 canv3 canvy0 fgcolor
- global commitlisted commitinfo rowidlist
+ global commitlisted commitinfo rowidlist parentlist
global rowtextx idpos idtags idheads idotherrefs
global linehtag linentag linedtag
- global mainfont canvxmax boldrows boldnamerows fgcolor
+ global mainfont canvxmax boldrows boldnamerows fgcolor nullid
- set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}]
+ if {$id eq $nullid} {
+ set ofill red
+ } else {
+ set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}]
+ }
set x [xc $row $col]
set y [yc $row]
set orad [expr {$linespc / 3}]
@@ -2999,10 +3325,18 @@ proc drawcmittext {id row col rmx} {
-fill $ofill -outline $fgcolor -width 1 -tags circle]
$canv raise $t
$canv bind $t <1> {selcanvline {} %x %y}
- set xt [xc $row [llength [lindex $rowidlist $row]]]
- if {$xt < $rmx} {
- set xt $rmx
+ set rmx [llength [lindex $rowidlist $row]]
+ set olds [lindex $parentlist $row]
+ if {$olds ne {}} {
+ set nextids [lindex $rowidlist [expr {$row + 1}]]
+ foreach p $olds {
+ set i [lsearch -exact $nextids $p]
+ if {$i > $rmx} {
+ set rmx $i
+ }
+ }
}
+ set xt [xc $row $rmx]
set rowtextx($row) $xt
set idpos($id) [list $x $xt $y]
if {[info exists idtags($id)] || [info exists idheads($id)]
@@ -3040,29 +3374,13 @@ proc drawcmittext {id row col rmx} {
proc drawcmitrow {row} {
global displayorder rowidlist
- global idrangedrawn iddrawn
+ global iddrawn
global commitinfo parentlist numcommits
global filehighlight fhighlights findstring nhighlights
global hlview vhighlights
global highlight_related rhighlights
if {$row >= $numcommits} return
- foreach id [lindex $rowidlist $row] {
- if {$id eq {}} continue
- set i -1
- foreach {s e} [rowranges $id] {
- incr i
- if {$row < $s} continue
- if {$e eq {}} break
- if {$row <= $e} {
- if {$e < $numcommits && ![info exists idrangedrawn($id,$i)]} {
- drawlineseg $id $i
- set idrangedrawn($id,$i) 1
- }
- break
- }
- }
- }
set id [lindex $displayorder $row]
if {[info exists hlview] && ![info exists vhighlights($row)]} {
@@ -3087,49 +3405,99 @@ proc drawcmitrow {row} {
getcommit $id
}
assigncolor $id
- set olds [lindex $parentlist $row]
- if {$olds ne {}} {
- set rmx [drawparentlinks $id $row $col $olds]
- } else {
- set rmx 0
- }
- drawcmittext $id $row $col $rmx
+ drawcmittext $id $row $col
set iddrawn($id) 1
}
-proc drawfrac {f0 f1} {
- global numcommits canv
- global linespc
+proc drawcommits {row {endrow {}}} {
+ global numcommits iddrawn displayorder curview
+ global parentlist rowidlist
- set ymax [lindex [$canv cget -scrollregion] 3]
- if {$ymax eq {} || $ymax == 0} return
- set y0 [expr {int($f0 * $ymax)}]
- set row [expr {int(($y0 - 3) / $linespc) - 1}]
if {$row < 0} {
set row 0
}
- set y1 [expr {int($f1 * $ymax)}]
- set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
+ if {$endrow eq {}} {
+ set endrow $row
+ }
if {$endrow >= $numcommits} {
set endrow [expr {$numcommits - 1}]
}
- for {} {$row <= $endrow} {incr row} {
- drawcmitrow $row
+
+ # make the lines join to already-drawn rows either side
+ set r [expr {$row - 1}]
+ if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
+ set r $row
+ }
+ set er [expr {$endrow + 1}]
+ if {$er >= $numcommits ||
+ ![info exists iddrawn([lindex $displayorder $er])]} {
+ set er $endrow
+ }
+ for {} {$r <= $er} {incr r} {
+ set id [lindex $displayorder $r]
+ set wasdrawn [info exists iddrawn($id)]
+ if {!$wasdrawn} {
+ drawcmitrow $r
+ }
+ if {$r == $er} break
+ set nextid [lindex $displayorder [expr {$r + 1}]]
+ if {$wasdrawn && [info exists iddrawn($nextid)]} {
+ catch {unset prevlines}
+ continue
+ }
+ drawparentlinks $id $r
+
+ if {[info exists lineends($r)]} {
+ foreach lid $lineends($r) {
+ unset prevlines($lid)
+ }
+ }
+ set rowids [lindex $rowidlist $r]
+ foreach lid $rowids {
+ if {$lid eq {}} continue
+ if {$lid eq $id} {
+ # see if this is the first child of any of its parents
+ foreach p [lindex $parentlist $r] {
+ if {[lsearch -exact $rowids $p] < 0} {
+ # make this line extend up to the child
+ set le [drawlineseg $p $r $er 0]
+ lappend lineends($le) $p
+ set prevlines($p) 1
+ }
+ }
+ } elseif {![info exists prevlines($lid)]} {
+ set le [drawlineseg $lid $r $er 1]
+ lappend lineends($le) $lid
+ set prevlines($lid) 1
+ }
+ }
}
}
+proc drawfrac {f0 f1} {
+ global canv linespc
+
+ set ymax [lindex [$canv cget -scrollregion] 3]
+ if {$ymax eq {} || $ymax == 0} return
+ set y0 [expr {int($f0 * $ymax)}]
+ set row [expr {int(($y0 - 3) / $linespc) - 1}]
+ set y1 [expr {int($f1 * $ymax)}]
+ set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
+ drawcommits $row $endrow
+}
+
proc drawvisible {} {
global canv
eval drawfrac [$canv yview]
}
proc clear_display {} {
- global iddrawn idrangedrawn
+ global iddrawn linesegs
global vhighlights fhighlights nhighlights rhighlights
allcanvs delete all
catch {unset iddrawn}
- catch {unset idrangedrawn}
+ catch {unset linesegs}
catch {unset vhighlights}
catch {unset fhighlights}
catch {unset nhighlights}
@@ -3356,27 +3724,14 @@ proc show_status {msg} {
-tags text -fill $fgcolor
}
-proc finishcommits {} {
- global commitidx phase curview
- global pending_select
-
- if {$commitidx($curview) > 0} {
- drawrest
- } else {
- show_status "No commits selected"
- }
- set phase {}
- catch {unset pending_select}
-}
-
# Insert a new commit as the child of the commit on row $row.
# The new commit will be displayed on row $row and the commits
# on that row and below will move down one row.
proc insertrow {row newcmit} {
- global displayorder parentlist childlist commitlisted
+ global displayorder parentlist commitlisted children
global commitrow curview rowidlist rowoffsets numcommits
- global rowrangelist idrowranges rowlaidout rowoptim numcommits
- global linesegends selectedline
+ global rowrangelist rowlaidout rowoptim numcommits
+ global selectedline rowchk commitidx
if {$row >= $numcommits} {
puts "oops, inserting new row $row but only have $numcommits rows"
@@ -3385,16 +3740,17 @@ proc insertrow {row newcmit} {
set p [lindex $displayorder $row]
set displayorder [linsert $displayorder $row $newcmit]
set parentlist [linsert $parentlist $row $p]
- set kids [lindex $childlist $row]
+ set kids $children($curview,$p)
lappend kids $newcmit
- lset childlist $row $kids
- set childlist [linsert $childlist $row {}]
+ set children($curview,$p) $kids
+ set children($curview,$newcmit) {}
set commitlisted [linsert $commitlisted $row 1]
set l [llength $displayorder]
for {set r $row} {$r < $l} {incr r} {
set id [lindex $displayorder $r]
set commitrow($curview,$id) $r
}
+ incr commitidx($curview)
set idlist [lindex $rowidlist $row]
set offs [lindex $rowoffsets $row]
@@ -3419,47 +3775,18 @@ proc insertrow {row newcmit} {
set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs]
set rowrangelist [linsert $rowrangelist $row {}]
- set l [llength $rowrangelist]
- for {set r 0} {$r < $l} {incr r} {
- set ranges [lindex $rowrangelist $r]
- if {$ranges ne {} && [lindex $ranges end] >= $row} {
- set newranges {}
- foreach x $ranges {
- if {$x >= $row} {
- lappend newranges [expr {$x + 1}]
- } else {
- lappend newranges $x
- }
- }
- lset rowrangelist $r $newranges
- }
- }
if {[llength $kids] > 1} {
set rp1 [expr {$row + 1}]
set ranges [lindex $rowrangelist $rp1]
if {$ranges eq {}} {
- set ranges [list $row $rp1]
- } elseif {[lindex $ranges end-1] == $rp1} {
- lset ranges end-1 $row
+ set ranges [list $newcmit $p]
+ } elseif {[lindex $ranges end-1] eq $p} {
+ lset ranges end-1 $newcmit
}
lset rowrangelist $rp1 $ranges
}
- foreach id [array names idrowranges] {
- set ranges $idrowranges($id)
- if {$ranges ne {} && [lindex $ranges end] >= $row} {
- set newranges {}
- foreach x $ranges {
- if {$x >= $row} {
- lappend newranges [expr {$x + 1}]
- } else {
- lappend newranges $x
- }
- }
- set idrowranges($id) $newranges
- }
- }
- set linesegends [linsert $linesegends $row {}]
+ catch {unset rowchk}
incr rowlaidout
incr rowoptim
@@ -3471,6 +3798,65 @@ proc insertrow {row newcmit} {
redisplay
}
+# Remove a commit that was inserted with insertrow on row $row.
+proc removerow {row} {
+ global displayorder parentlist commitlisted children
+ global commitrow curview rowidlist rowoffsets numcommits
+ global rowrangelist idrowranges rowlaidout rowoptim numcommits
+ global linesegends selectedline rowchk commitidx
+
+ if {$row >= $numcommits} {
+ puts "oops, removing row $row but only have $numcommits rows"
+ return
+ }
+ set rp1 [expr {$row + 1}]
+ set id [lindex $displayorder $row]
+ set p [lindex $parentlist $row]
+ set displayorder [lreplace $displayorder $row $row]
+ set parentlist [lreplace $parentlist $row $row]
+ set commitlisted [lreplace $commitlisted $row $row]
+ set kids $children($curview,$p)
+ set i [lsearch -exact $kids $id]
+ if {$i >= 0} {
+ set kids [lreplace $kids $i $i]
+ set children($curview,$p) $kids
+ }
+ set l [llength $displayorder]
+ for {set r $row} {$r < $l} {incr r} {
+ set id [lindex $displayorder $r]
+ set commitrow($curview,$id) $r
+ }
+ incr commitidx($curview) -1
+
+ set rowidlist [lreplace $rowidlist $row $row]
+ set rowoffsets [lreplace $rowoffsets $rp1 $rp1]
+ if {$kids ne {}} {
+ set offs [lindex $rowoffsets $row]
+ set offs [lreplace $offs end end]
+ lset rowoffsets $row $offs
+ }
+
+ set rowrangelist [lreplace $rowrangelist $row $row]
+ if {[llength $kids] > 0} {
+ set ranges [lindex $rowrangelist $row]
+ if {[lindex $ranges end-1] eq $id} {
+ set ranges [lreplace $ranges end-1 end]
+ lset rowrangelist $row $ranges
+ }
+ }
+
+ catch {unset rowchk}
+
+ incr rowlaidout -1
+ incr rowoptim -1
+ incr numcommits -1
+
+ if {[info exists selectedline] && $selectedline > $row} {
+ incr selectedline -1
+ }
+ redisplay
+}
+
# Don't change the text pane cursor if it is currently the hand cursor,
# showing that we are over a sha1 ID link.
proc settextcursor {c} {
@@ -3502,25 +3888,6 @@ proc notbusy {what} {
}
}
-proc drawrest {} {
- global startmsecs
- global rowlaidout commitidx curview
- global pending_select
-
- set row $rowlaidout
- layoutrows $rowlaidout $commitidx($curview) 1
- layouttail
- optimize_rows $row 0 $commitidx($curview)
- showstuff $commitidx($curview)
- if {[info exists pending_select]} {
- selectline 0 1
- }
-
- set drawmsecs [expr {[clock clicks -milliseconds] - $startmsecs}]
- #global numcommits
- #puts "overall $drawmsecs ms for $numcommits commits"
-}
-
proc findmatches {f} {
global findtype foundstring foundstrlen
if {$findtype == "Regexp"} {
@@ -3594,13 +3961,13 @@ proc dofind {} {
if {$matches == {}} continue
set doesmatch 1
if {$ty == "Headline"} {
- drawcmitrow $l
+ drawcommits $l
markmatches $canv $l $f $linehtag($l) $matches $mainfont
} elseif {$ty == "Author"} {
- drawcmitrow $l
+ drawcommits $l
markmatches $canv2 $l $f $linentag($l) $matches $mainfont
} elseif {$ty == "Date"} {
- drawcmitrow $l
+ drawcommits $l
markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
}
}
@@ -3693,7 +4060,7 @@ proc stopfindproc {{done 0}} {
proc markheadline {l id} {
global canv mainfont linehtag
- drawcmitrow $l
+ drawcommits $l
set bbox [$canv bbox $linehtag($l)]
set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
$canv lower $t
@@ -3798,75 +4165,106 @@ proc viewnextline {dir} {
# add a list of tag or branch names at position pos
# returns the number of names inserted
-proc appendrefs {pos tags var} {
- global ctext commitrow linknum curview $var
+proc appendrefs {pos ids var} {
+ global ctext commitrow linknum curview $var maxrefs
if {[catch {$ctext index $pos}]} {
return 0
}
- set tags [lsort $tags]
- set sep {}
- foreach tag $tags {
- set id [set $var\($tag\)]
- set lk link$linknum
- incr linknum
- $ctext insert $pos $sep
- $ctext insert $pos $tag $lk
- $ctext tag conf $lk -foreground blue
- if {[info exists commitrow($curview,$id)]} {
- $ctext tag bind $lk <1> \
- [list selectline $commitrow($curview,$id) 1]
- $ctext tag conf $lk -underline 1
- $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
- $ctext tag bind $lk <Leave> { %W configure -cursor $curtextcursor }
+ $ctext conf -state normal
+ $ctext delete $pos "$pos lineend"
+ set tags {}
+ foreach id $ids {
+ foreach tag [set $var\($id\)] {
+ lappend tags [list $tag $id]
+ }
+ }
+ if {[llength $tags] > $maxrefs} {
+ $ctext insert $pos "many ([llength $tags])"
+ } else {
+ set tags [lsort -index 0 -decreasing $tags]
+ set sep {}
+ foreach ti $tags {
+ set id [lindex $ti 1]
+ set lk link$linknum
+ incr linknum
+ $ctext tag delete $lk
+ $ctext insert $pos $sep
+ $ctext insert $pos [lindex $ti 0] $lk
+ if {[info exists commitrow($curview,$id)]} {
+ $ctext tag conf $lk -foreground blue
+ $ctext tag bind $lk <1> \
+ [list selectline $commitrow($curview,$id) 1]
+ $ctext tag conf $lk -underline 1
+ $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
+ $ctext tag bind $lk <Leave> \
+ { %W configure -cursor $curtextcursor }
+ }
+ set sep ", "
}
- set sep ", "
}
+ $ctext conf -state disabled
return [llength $tags]
}
-proc taglist {ids} {
- global idtags
+# called when we have finished computing the nearby tags
+proc dispneartags {delay} {
+ global selectedline currentid showneartags tagphase
- set tags {}
- foreach id $ids {
- foreach tag $idtags($id) {
- lappend tags $tag
- }
+ if {![info exists selectedline] || !$showneartags} return
+ after cancel dispnexttag
+ if {$delay} {
+ after 200 dispnexttag
+ set tagphase -1
+ } else {
+ after idle dispnexttag
+ set tagphase 0
}
- return $tags
}
-# called when we have finished computing the nearby tags
-proc dispneartags {} {
- global selectedline currentid ctext anc_tags desc_tags showneartags
- global desc_heads
+proc dispnexttag {} {
+ global selectedline currentid showneartags tagphase ctext
if {![info exists selectedline] || !$showneartags} return
- set id $currentid
- $ctext conf -state normal
- if {[info exists desc_heads($id)]} {
- if {[appendrefs branch $desc_heads($id) headids] > 1} {
- $ctext insert "branch -2c" "es"
+ switch -- $tagphase {
+ 0 {
+ set dtags [desctags $currentid]
+ if {$dtags ne {}} {
+ appendrefs precedes $dtags idtags
+ }
+ }
+ 1 {
+ set atags [anctags $currentid]
+ if {$atags ne {}} {
+ appendrefs follows $atags idtags
+ }
+ }
+ 2 {
+ set dheads [descheads $currentid]
+ if {$dheads ne {}} {
+ if {[appendrefs branch $dheads idheads] > 1
+ && [$ctext get "branch -3c"] eq "h"} {
+ # turn "Branch" into "Branches"
+ $ctext conf -state normal
+ $ctext insert "branch -2c" "es"
+ $ctext conf -state disabled
+ }
+ }
}
}
- if {[info exists anc_tags($id)]} {
- appendrefs follows [taglist $anc_tags($id)] tagids
- }
- if {[info exists desc_tags($id)]} {
- appendrefs precedes [taglist $desc_tags($id)] tagids
+ if {[incr tagphase] <= 2} {
+ after idle dispnexttag
}
- $ctext conf -state disabled
}
proc selectline {l isnew} {
global canv canv2 canv3 ctext commitinfo selectedline
global displayorder linehtag linentag linedtag
- global canvy0 linespc parentlist childlist
+ global canvy0 linespc parentlist children curview
global currentid sha1entry
global commentend idtags linknum
global mergemax numcommits pending_select
- global cmitmode desc_tags anc_tags showneartags allcommits desc_heads
+ global cmitmode showneartags allcommits
catch {unset pending_select}
$canv delete hover
@@ -3973,7 +4371,7 @@ proc selectline {l isnew} {
}
}
- foreach c [lindex $childlist $l] {
+ foreach c $children($curview,$id) {
append headers "Child: [commit_descriptor $c]"
}
@@ -3986,30 +4384,22 @@ proc selectline {l isnew} {
$ctext insert end "Branch: "
$ctext mark set branch "end -1c"
$ctext mark gravity branch left
- if {[info exists desc_heads($id)]} {
- if {[appendrefs branch $desc_heads($id) headids] > 1} {
- # turn "Branch" into "Branches"
- $ctext insert "branch -2c" "es"
- }
- }
$ctext insert end "\nFollows: "
$ctext mark set follows "end -1c"
$ctext mark gravity follows left
- if {[info exists anc_tags($id)]} {
- appendrefs follows [taglist $anc_tags($id)] tagids
- }
$ctext insert end "\nPrecedes: "
$ctext mark set precedes "end -1c"
$ctext mark gravity precedes left
- if {[info exists desc_tags($id)]} {
- appendrefs precedes [taglist $desc_tags($id)] tagids
- }
$ctext insert end "\n"
+ dispneartags 1
}
$ctext insert end "\n"
- appendwithlinks [lindex $info 5] {comment}
+ set comment [lindex $info 5]
+ if {[string first "\r" $comment] >= 0} {
+ set comment [string map {"\r" "\n "} $comment]
+ }
+ appendwithlinks $comment {comment}
- $ctext tag delete Comments
$ctext tag remove found 1.0 end
$ctext conf -state disabled
set commentend [$ctext index "end - 1c"]
@@ -4144,20 +4534,25 @@ proc goforw {} {
}
proc gettree {id} {
- global treefilelist treeidlist diffids diffmergeid treepending
+ global treefilelist treeidlist diffids diffmergeid treepending nullid
set diffids $id
catch {unset diffmergeid}
if {![info exists treefilelist($id)]} {
if {![info exists treepending]} {
- if {[catch {set gtf [open [concat | git ls-tree -r $id] r]}]} {
+ if {$id ne $nullid} {
+ set cmd [concat | git ls-tree -r $id]
+ } else {
+ set cmd [concat | git ls-files]
+ }
+ if {[catch {set gtf [open $cmd r]}]} {
return
}
set treepending $id
set treefilelist($id) {}
set treeidlist($id) {}
fconfigure $gtf -blocking 0
- fileevent $gtf readable [list gettreeline $gtf $id]
+ filerun $gtf [list gettreeline $gtf $id]
}
} else {
setfilelist $id
@@ -4165,16 +4560,28 @@ proc gettree {id} {
}
proc gettreeline {gtf id} {
- global treefilelist treeidlist treepending cmitmode diffids
-
- while {[gets $gtf line] >= 0} {
- if {[lindex $line 1] ne "blob"} continue
- set sha1 [lindex $line 2]
- set fname [lindex $line 3]
+ global treefilelist treeidlist treepending cmitmode diffids nullid
+
+ set nl 0
+ while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
+ if {$diffids ne $nullid} {
+ if {[lindex $line 1] ne "blob"} continue
+ set i [string first "\t" $line]
+ if {$i < 0} continue
+ set sha1 [lindex $line 2]
+ set fname [string range $line [expr {$i+1}] end]
+ if {[string index $fname 0] eq "\""} {
+ set fname [lindex $fname 0]
+ }
+ lappend treeidlist($id) $sha1
+ } else {
+ set fname $line
+ }
lappend treefilelist($id) $fname
- lappend treeidlist($id) $sha1
}
- if {![eof $gtf]} return
+ if {![eof $gtf]} {
+ return [expr {$nl >= 1000? 2: 1}]
+ }
close $gtf
unset treepending
if {$cmitmode ne "tree"} {
@@ -4186,10 +4593,11 @@ proc gettreeline {gtf id} {
} else {
setfilelist $id
}
+ return 0
}
proc showfile {f} {
- global treefilelist treeidlist diffids
+ global treefilelist treeidlist diffids nullid
global ctext commentend
set i [lsearch -exact $treefilelist($diffids) $f]
@@ -4197,13 +4605,20 @@ proc showfile {f} {
puts "oops, $f not in list for id $diffids"
return
}
- set blob [lindex $treeidlist($diffids) $i]
- if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
- puts "oops, error reading blob $blob: $err"
- return
+ if {$diffids ne $nullid} {
+ set blob [lindex $treeidlist($diffids) $i]
+ if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
+ puts "oops, error reading blob $blob: $err"
+ return
+ }
+ } else {
+ if {[catch {set bf [open $f r]} err]} {
+ puts "oops, can't read $f: $err"
+ return
+ }
}
fconfigure $bf -blocking 0
- fileevent $bf readable [list getblobline $bf $diffids]
+ filerun $bf [list getblobline $bf $diffids]
$ctext config -state normal
clear_ctext $commentend
$ctext insert end "\n"
@@ -4217,18 +4632,21 @@ proc getblobline {bf id} {
if {$id ne $diffids || $cmitmode ne "tree"} {
catch {close $bf}
- return
+ return 0
}
$ctext config -state normal
- while {[gets $bf line] >= 0} {
+ set nl 0
+ while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
$ctext insert end "$line\n"
}
if {[eof $bf]} {
# delete last newline
$ctext delete "end - 2c" "end - 1c"
close $bf
+ return 0
}
$ctext config -state disabled
+ return [expr {$nl >= 1000? 2: 1}]
}
proc mergediff {id l} {
@@ -4248,91 +4666,86 @@ proc mergediff {id l} {
fconfigure $mdf -blocking 0
set mdifffd($id) $mdf
set np [llength [lindex $parentlist $l]]
- fileevent $mdf readable [list getmergediffline $mdf $id $np]
- set nextupdate [expr {[clock clicks -milliseconds] + 100}]
+ filerun $mdf [list getmergediffline $mdf $id $np]
}
proc getmergediffline {mdf id np} {
- global diffmergeid ctext cflist nextupdate mergemax
+ global diffmergeid ctext cflist mergemax
global difffilestart mdifffd
- set n [gets $mdf line]
- if {$n < 0} {
- if {[eof $mdf]} {
+ $ctext conf -state normal
+ set nr 0
+ while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
+ if {![info exists diffmergeid] || $id != $diffmergeid
+ || $mdf != $mdifffd($id)} {
close $mdf
+ return 0
}
- return
- }
- if {![info exists diffmergeid] || $id != $diffmergeid
- || $mdf != $mdifffd($id)} {
- return
- }
- $ctext conf -state normal
- if {[regexp {^diff --cc (.*)} $line match fname]} {
- # start of a new file
- $ctext insert end "\n"
- set here [$ctext index "end - 1c"]
- lappend difffilestart $here
- add_flist [list $fname]
- set l [expr {(78 - [string length $fname]) / 2}]
- set pad [string range "----------------------------------------" 1 $l]
- $ctext insert end "$pad $fname $pad\n" filesep
- } elseif {[regexp {^@@} $line]} {
- $ctext insert end "$line\n" hunksep
- } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
- # do nothing
- } else {
- # parse the prefix - one ' ', '-' or '+' for each parent
- set spaces {}
- set minuses {}
- set pluses {}
- set isbad 0
- for {set j 0} {$j < $np} {incr j} {
- set c [string range $line $j $j]
- if {$c == " "} {
- lappend spaces $j
- } elseif {$c == "-"} {
- lappend minuses $j
- } elseif {$c == "+"} {
- lappend pluses $j
- } else {
- set isbad 1
- break
+ if {[regexp {^diff --cc (.*)} $line match fname]} {
+ # start of a new file
+ $ctext insert end "\n"
+ set here [$ctext index "end - 1c"]
+ lappend difffilestart $here
+ add_flist [list $fname]
+ set l [expr {(78 - [string length $fname]) / 2}]
+ set pad [string range "----------------------------------------" 1 $l]
+ $ctext insert end "$pad $fname $pad\n" filesep
+ } elseif {[regexp {^@@} $line]} {
+ $ctext insert end "$line\n" hunksep
+ } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
+ # do nothing
+ } else {
+ # parse the prefix - one ' ', '-' or '+' for each parent
+ set spaces {}
+ set minuses {}
+ set pluses {}
+ set isbad 0
+ for {set j 0} {$j < $np} {incr j} {
+ set c [string range $line $j $j]
+ if {$c == " "} {
+ lappend spaces $j
+ } elseif {$c == "-"} {
+ lappend minuses $j
+ } elseif {$c == "+"} {
+ lappend pluses $j
+ } else {
+ set isbad 1
+ break
+ }
}
- }
- set tags {}
- set num {}
- if {!$isbad && $minuses ne {} && $pluses eq {}} {
- # line doesn't appear in result, parents in $minuses have the line
- set num [lindex $minuses 0]
- } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
- # line appears in result, parents in $pluses don't have the line
- lappend tags mresult
- set num [lindex $spaces 0]
- }
- if {$num ne {}} {
- if {$num >= $mergemax} {
- set num "max"
+ set tags {}
+ set num {}
+ if {!$isbad && $minuses ne {} && $pluses eq {}} {
+ # line doesn't appear in result, parents in $minuses have the line
+ set num [lindex $minuses 0]
+ } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
+ # line appears in result, parents in $pluses don't have the line
+ lappend tags mresult
+ set num [lindex $spaces 0]
+ }
+ if {$num ne {}} {
+ if {$num >= $mergemax} {
+ set num "max"
+ }
+ lappend tags m$num
}
- lappend tags m$num
+ $ctext insert end "$line\n" $tags
}
- $ctext insert end "$line\n" $tags
}
$ctext conf -state disabled
- if {[clock clicks -milliseconds] >= $nextupdate} {
- incr nextupdate 100
- fileevent $mdf readable {}
- update
- fileevent $mdf readable [list getmergediffline $mdf $id $np]
+ if {[eof $mdf]} {
+ close $mdf
+ return 0
}
+ return [expr {$nr >= 1000? 2: 1}]
}
proc startdiff {ids} {
- global treediffs diffids treepending diffmergeid
+ global treediffs diffids treepending diffmergeid nullid
set diffids $ids
catch {unset diffmergeid}
- if {![info exists treediffs($ids)]} {
+ if {![info exists treediffs($ids)] || [lsearch -exact $ids $nullid] >= 0} {
if {![info exists treepending]} {
gettreediffs $ids
}
@@ -4347,59 +4760,83 @@ proc addtocflist {ids} {
getblobdiffs $ids
}
+proc diffcmd {ids flags} {
+ global nullid
+
+ set i [lsearch -exact $ids $nullid]
+ if {$i >= 0} {
+ set cmd [concat | git diff-index $flags]
+ if {[llength $ids] > 1} {
+ if {$i == 0} {
+ lappend cmd -R [lindex $ids 1]
+ } else {
+ lappend cmd [lindex $ids 0]
+ }
+ } else {
+ lappend cmd HEAD
+ }
+ } else {
+ set cmd [concat | git diff-tree --no-commit-id -r $flags $ids]
+ }
+ return $cmd
+}
+
proc gettreediffs {ids} {
global treediff treepending
+
set treepending $ids
set treediff {}
- if {[catch \
- {set gdtf [open [concat | git diff-tree --no-commit-id -r $ids] r]} \
- ]} return
+ if {[catch {set gdtf [open [diffcmd $ids {}] r]}]} return
fconfigure $gdtf -blocking 0
- fileevent $gdtf readable [list gettreediffline $gdtf $ids]
+ filerun $gdtf [list gettreediffline $gdtf $ids]
}
proc gettreediffline {gdtf ids} {
global treediff treediffs treepending diffids diffmergeid
global cmitmode
- set n [gets $gdtf line]
- if {$n < 0} {
- if {![eof $gdtf]} return
- close $gdtf
- set treediffs($ids) $treediff
- unset treepending
- if {$cmitmode eq "tree"} {
- gettree $diffids
- } elseif {$ids != $diffids} {
- if {![info exists diffmergeid]} {
- gettreediffs $diffids
+ set nr 0
+ while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
+ set i [string first "\t" $line]
+ if {$i >= 0} {
+ set file [string range $line [expr {$i+1}] end]
+ if {[string index $file 0] eq "\""} {
+ set file [lindex $file 0]
}
- } else {
- addtocflist $ids
+ lappend treediff $file
}
- return
}
- set file [lindex $line 5]
- lappend treediff $file
+ if {![eof $gdtf]} {
+ return [expr {$nr >= 1000? 2: 1}]
+ }
+ close $gdtf
+ set treediffs($ids) $treediff
+ unset treepending
+ if {$cmitmode eq "tree"} {
+ gettree $diffids
+ } elseif {$ids != $diffids} {
+ if {![info exists diffmergeid]} {
+ gettreediffs $diffids
+ }
+ } else {
+ addtocflist $ids
+ }
+ return 0
}
proc getblobdiffs {ids} {
- global diffopts blobdifffd diffids env curdifftag curtagstart
- global nextupdate diffinhdr treediffs
+ global diffopts blobdifffd diffids env
+ global diffinhdr treediffs
set env(GIT_DIFF_OPTS) $diffopts
- set cmd [concat | git diff-tree --no-commit-id -r -p -C $ids]
- if {[catch {set bdf [open $cmd r]} err]} {
+ if {[catch {set bdf [open [diffcmd $ids {-p -C}] r]} err]} {
puts "error getting diffs: $err"
return
}
set diffinhdr 0
fconfigure $bdf -blocking 0
set blobdifffd($ids) $bdf
- set curdifftag Comments
- set curtagstart 0.0
- fileevent $bdf readable [list getblobdiffline $bdf $diffids]
- set nextupdate [expr {[clock clicks -milliseconds] + 100}]
+ filerun $bdf [list getblobdiffline $bdf $diffids]
}
proc setinlist {var i val} {
@@ -4415,84 +4852,111 @@ proc setinlist {var i val} {
}
}
+proc makediffhdr {fname ids} {
+ global ctext curdiffstart treediffs
+
+ set i [lsearch -exact $treediffs($ids) $fname]
+ if {$i >= 0} {
+ setinlist difffilestart $i $curdiffstart
+ }
+ set l [expr {(78 - [string length $fname]) / 2}]
+ set pad [string range "----------------------------------------" 1 $l]
+ $ctext insert $curdiffstart "$pad $fname $pad" filesep
+}
+
proc getblobdiffline {bdf ids} {
- global diffids blobdifffd ctext curdifftag curtagstart
+ global diffids blobdifffd ctext curdiffstart
global diffnexthead diffnextnote difffilestart
- global nextupdate diffinhdr treediffs
+ global diffinhdr treediffs
- set n [gets $bdf line]
- if {$n < 0} {
- if {[eof $bdf]} {
- close $bdf
- if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
- $ctext tag add $curdifftag $curtagstart end
- }
- }
- return
- }
- if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
- return
- }
+ set nr 0
$ctext conf -state normal
- if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
- # start of a new file
- $ctext insert end "\n"
- $ctext tag add $curdifftag $curtagstart end
- set here [$ctext index "end - 1c"]
- set curtagstart $here
- set header $newname
- set i [lsearch -exact $treediffs($ids) $fname]
- if {$i >= 0} {
- setinlist difffilestart $i $here
+ while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
+ if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
+ close $bdf
+ return 0
}
- if {$newname ne $fname} {
- set i [lsearch -exact $treediffs($ids) $newname]
- if {$i >= 0} {
- setinlist difffilestart $i $here
+ if {![string compare -length 11 "diff --git " $line]} {
+ # trim off "diff --git "
+ set line [string range $line 11 end]
+ set diffinhdr 1
+ # start of a new file
+ $ctext insert end "\n"
+ set curdiffstart [$ctext index "end - 1c"]
+ $ctext insert end "\n" filesep
+ # If the name hasn't changed the length will be odd,
+ # the middle char will be a space, and the two bits either
+ # side will be a/name and b/name, or "a/name" and "b/name".
+ # If the name has changed we'll get "rename from" and
+ # "rename to" lines following this, and we'll use them
+ # to get the filenames.
+ # This complexity is necessary because spaces in the filename(s)
+ # don't get escaped.
+ set l [string length $line]
+ set i [expr {$l / 2}]
+ if {!(($l & 1) && [string index $line $i] eq " " &&
+ [string range $line 2 [expr {$i - 1}]] eq \
+ [string range $line [expr {$i + 3}] end])} {
+ continue
+ }
+ # unescape if quoted and chop off the a/ from the front
+ if {[string index $line 0] eq "\""} {
+ set fname [string range [lindex $line 0] 2 end]
+ } else {
+ set fname [string range $line 2 [expr {$i - 1}]]
+ }
+ makediffhdr $fname $ids
+
+ } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
+ $line match f1l f1c f2l f2c rest]} {
+ $ctext insert end "$line\n" hunksep
+ set diffinhdr 0
+
+ } elseif {$diffinhdr} {
+ if {![string compare -length 12 "rename from " $line]} {
+ set fname [string range $line 12 end]
+ if {[string index $fname 0] eq "\""} {
+ set fname [lindex $fname 0]
+ }
+ set i [lsearch -exact $treediffs($ids) $fname]
+ if {$i >= 0} {
+ setinlist difffilestart $i $curdiffstart
+ }
+ } elseif {![string compare -length 10 $line "rename to "]} {
+ set fname [string range $line 10 end]
+ if {[string index $fname 0] eq "\""} {
+ set fname [lindex $fname 0]
+ }
+ makediffhdr $fname $ids
+ } elseif {[string compare -length 3 $line "---"] == 0} {
+ # do nothing
+ continue
+ } elseif {[string compare -length 3 $line "+++"] == 0} {
+ set diffinhdr 0
+ continue
}
- }
- set curdifftag "f:$fname"
- $ctext tag delete $curdifftag
- set l [expr {(78 - [string length $header]) / 2}]
- set pad [string range "----------------------------------------" 1 $l]
- $ctext insert end "$pad $header $pad\n" filesep
- set diffinhdr 1
- } elseif {$diffinhdr && [string compare -length 3 $line "---"] == 0} {
- # do nothing
- } elseif {$diffinhdr && [string compare -length 3 $line "+++"] == 0} {
- set diffinhdr 0
- } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
- $line match f1l f1c f2l f2c rest]} {
- $ctext insert end "$line\n" hunksep
- set diffinhdr 0
- } else {
- set x [string range $line 0 0]
- if {$x == "-" || $x == "+"} {
- set tag [expr {$x == "+"}]
- $ctext insert end "$line\n" d$tag
- } elseif {$x == " "} {
- $ctext insert end "$line\n"
- } elseif {$diffinhdr || $x == "\\"} {
- # e.g. "\ No newline at end of file"
$ctext insert end "$line\n" filesep
+
} else {
- # Something else we don't recognize
- if {$curdifftag != "Comments"} {
- $ctext insert end "\n"
- $ctext tag add $curdifftag $curtagstart end
- set curtagstart [$ctext index "end - 1c"]
- set curdifftag Comments
+ set x [string range $line 0 0]
+ if {$x == "-" || $x == "+"} {
+ set tag [expr {$x == "+"}]
+ $ctext insert end "$line\n" d$tag
+ } elseif {$x == " "} {
+ $ctext insert end "$line\n"
+ } else {
+ # "\ No newline at end of file",
+ # or something else we don't recognize
+ $ctext insert end "$line\n" hunksep
}
- $ctext insert end "$line\n" filesep
}
}
$ctext conf -state disabled
- if {[clock clicks -milliseconds] >= $nextupdate} {
- incr nextupdate 100
- fileevent $bdf readable {}
- update
- fileevent $bdf readable "getblobdiffline $bdf {$ids}"
+ if {[eof $bdf]} {
+ close $bdf
+ return 0
}
+ return [expr {$nr >= 1000? 2: 1}]
}
proc changediffdisp {} {
@@ -4690,13 +5154,15 @@ proc redisplay {} {
}
proc incrfont {inc} {
- global mainfont textfont ctext canv phase
+ global mainfont textfont ctext canv phase cflist
+ global charspc tabstop
global stopped entries
unmarkmatches
set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
setcoords
- $ctext conf -font $textfont
+ $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
+ $cflist conf -font $textfont
$ctext tag conf filesep -font [concat $textfont bold]
foreach e $entries {
$e conf -font $mainfont
@@ -4954,18 +5420,25 @@ proc mstime {} {
proc rowmenu {x y id} {
global rowctxmenu commitrow selectedline rowmenuid curview
+ global nullid fakerowmenu mainhead
+ set rowmenuid $id
if {![info exists selectedline]
|| $commitrow($curview,$id) eq $selectedline} {
set state disabled
} else {
set state normal
}
- $rowctxmenu entryconfigure "Diff this*" -state $state
- $rowctxmenu entryconfigure "Diff selected*" -state $state
- $rowctxmenu entryconfigure "Make patch" -state $state
- set rowmenuid $id
- tk_popup $rowctxmenu $x $y
+ if {$id ne $nullid} {
+ set menu $rowctxmenu
+ $menu entryconfigure 7 -label "Reset $mainhead branch to here"
+ } else {
+ set menu $fakerowmenu
+ }
+ $menu entryconfigure "Diff this*" -state $state
+ $menu entryconfigure "Diff selected*" -state $state
+ $menu entryconfigure "Make patch" -state $state
+ tk_popup $menu $x $y
}
proc diffvssel {dirn} {
@@ -5005,7 +5478,6 @@ proc doseldiff {oldid newid} {
$ctext insert end [lindex $commitinfo($newid) 0]
$ctext insert end "\n"
$ctext conf -state disabled
- $ctext tag delete Comments
$ctext tag remove found 1.0 end
startdiff [list $oldid $newid]
}
@@ -5076,12 +5548,20 @@ proc mkpatchrev {} {
}
proc mkpatchgo {} {
- global patchtop
+ global patchtop nullid
set oldid [$patchtop.fromsha1 get]
set newid [$patchtop.tosha1 get]
set fname [$patchtop.fname get]
- if {[catch {exec git diff-tree -p $oldid $newid >$fname &} err]} {
+ if {$newid eq $nullid} {
+ set cmd [list git diff-index -p $oldid]
+ } elseif {$oldid eq $nullid} {
+ set cmd [list git diff-index -p -R $newid]
+ } else {
+ set cmd [list git diff-tree -p $oldid $newid]
+ }
+ lappend cmd >$fname &
+ if {[catch {eval exec $cmd} err]} {
error_popup "Error creating patch: $err"
}
catch {destroy $patchtop}
@@ -5158,10 +5638,11 @@ proc domktag {} {
proc redrawtags {id} {
global canv linehtag commitrow idpos selectedline curview
- global mainfont canvxmax
+ global mainfont canvxmax iddrawn
if {![info exists commitrow($curview,$id)]} return
- drawcmitrow $commitrow($curview,$id)
+ if {![info exists iddrawn($id)]} return
+ drawcommits $commitrow($curview,$id)
$canv delete tag.$id
set xt [eval drawtags $id $idpos($id)]
$canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
@@ -5288,26 +5769,28 @@ proc mkbrgo {top} {
notbusy newbranch
error_popup $err
} else {
+ set headids($name) $id
+ lappend idheads($id) $name
addedhead $id $name
- # XXX should update list of heads displayed for selected commit
notbusy newbranch
redrawtags $id
+ dispneartags 0
}
}
proc cherrypick {} {
global rowmenuid curview commitrow
- global mainhead desc_heads anc_tags desc_tags allparents allchildren
+ global mainhead
- if {[info exists desc_heads($rowmenuid)]
- && [lsearch -exact $desc_heads($rowmenuid) $mainhead] >= 0} {
+ set oldhead [exec git rev-parse HEAD]
+ set dheads [descheads $rowmenuid]
+ if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
included in branch $mainhead -- really re-apply it?"]
if {!$ok} return
}
nowbusy cherrypick
update
- set oldhead [exec git rev-parse HEAD]
# Unfortunately git-cherry-pick writes stuff to stderr even when
# no error occurs, and exec takes that as an indication of error...
if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
@@ -5321,16 +5804,11 @@ proc cherrypick {} {
error_popup "No changes committed"
return
}
- set allparents($newhead) $oldhead
- lappend allchildren($oldhead) $newhead
- set desc_heads($newhead) $mainhead
- if {[info exists anc_tags($oldhead)]} {
- set anc_tags($newhead) $anc_tags($oldhead)
- }
- set desc_tags($newhead) {}
+ addnewchild $newhead $oldhead
if {[info exists commitrow($curview,$oldhead)]} {
insertrow $commitrow($curview,$oldhead) $newhead
if {$mainhead ne {}} {
+ movehead $newhead $mainhead
movedhead $newhead $mainhead
}
redrawtags $oldhead
@@ -5339,48 +5817,146 @@ proc cherrypick {} {
notbusy cherrypick
}
+proc resethead {} {
+ global mainheadid mainhead rowmenuid confirm_ok resettype
+ global showlocalchanges
+
+ set confirm_ok 0
+ set w ".confirmreset"
+ toplevel $w
+ wm transient $w .
+ wm title $w "Confirm reset"
+ message $w.m -text \
+ "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
+ -justify center -aspect 1000
+ pack $w.m -side top -fill x -padx 20 -pady 20
+ frame $w.f -relief sunken -border 2
+ message $w.f.rt -text "Reset type:" -aspect 1000
+ grid $w.f.rt -sticky w
+ set resettype mixed
+ radiobutton $w.f.soft -value soft -variable resettype -justify left \
+ -text "Soft: Leave working tree and index untouched"
+ grid $w.f.soft -sticky w
+ radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
+ -text "Mixed: Leave working tree untouched, reset index"
+ grid $w.f.mixed -sticky w
+ radiobutton $w.f.hard -value hard -variable resettype -justify left \
+ -text "Hard: Reset working tree and index\n(discard ALL local changes)"
+ grid $w.f.hard -sticky w
+ pack $w.f -side top -fill x
+ button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
+ pack $w.ok -side left -fill x -padx 20 -pady 20
+ button $w.cancel -text Cancel -command "destroy $w"
+ pack $w.cancel -side right -fill x -padx 20 -pady 20
+ bind $w <Visibility> "grab $w; focus $w"
+ tkwait window $w
+ if {!$confirm_ok} return
+ if {[catch {set fd [open \
+ [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
+ error_popup $err
+ } else {
+ dohidelocalchanges
+ set w ".resetprogress"
+ filerun $fd [list readresetstat $fd $w]
+ toplevel $w
+ wm transient $w
+ wm title $w "Reset progress"
+ message $w.m -text "Reset in progress, please wait..." \
+ -justify center -aspect 1000
+ pack $w.m -side top -fill x -padx 20 -pady 5
+ canvas $w.c -width 150 -height 20 -bg white
+ $w.c create rect 0 0 0 20 -fill green -tags rect
+ pack $w.c -side top -fill x -padx 20 -pady 5 -expand 1
+ nowbusy reset
+ }
+}
+
+proc readresetstat {fd w} {
+ global mainhead mainheadid showlocalchanges
+
+ if {[gets $fd line] >= 0} {
+ if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
+ set x [expr {($m * 150) / $n}]
+ $w.c coords rect 0 0 $x 20
+ }
+ return 1
+ }
+ destroy $w
+ notbusy reset
+ if {[catch {close $fd} err]} {
+ error_popup $err
+ }
+ set oldhead $mainheadid
+ set newhead [exec git rev-parse HEAD]
+ if {$newhead ne $oldhead} {
+ movehead $newhead $mainhead
+ movedhead $newhead $mainhead
+ set mainheadid $newhead
+ redrawtags $oldhead
+ redrawtags $newhead
+ }
+ if {$showlocalchanges} {
+ doshowlocalchanges
+ }
+ return 0
+}
+
# context menu for a head
proc headmenu {x y id head} {
- global headmenuid headmenuhead headctxmenu
+ global headmenuid headmenuhead headctxmenu mainhead
set headmenuid $id
set headmenuhead $head
+ set state normal
+ if {$head eq $mainhead} {
+ set state disabled
+ }
+ $headctxmenu entryconfigure 0 -state $state
+ $headctxmenu entryconfigure 1 -state $state
tk_popup $headctxmenu $x $y
}
proc cobranch {} {
global headmenuid headmenuhead mainhead headids
+ global showlocalchanges mainheadid
# check the tree is clean first??
set oldmainhead $mainhead
nowbusy checkout
update
+ dohidelocalchanges
if {[catch {
- exec git checkout $headmenuhead
+ exec git checkout -q $headmenuhead
} err]} {
notbusy checkout
error_popup $err
} else {
notbusy checkout
set mainhead $headmenuhead
+ set mainheadid $headmenuid
if {[info exists headids($oldmainhead)]} {
redrawtags $headids($oldmainhead)
}
redrawtags $headmenuid
}
+ if {$showlocalchanges} {
+ dodiffindex
+ }
}
proc rmbranch {} {
- global desc_heads headmenuid headmenuhead mainhead
+ global headmenuid headmenuhead mainhead
global headids idheads
set head $headmenuhead
set id $headmenuid
+ # this check shouldn't be needed any more...
if {$head eq $mainhead} {
error_popup "Cannot delete the currently checked-out branch"
return
}
- if {$desc_heads($id) eq $head} {
+ set dheads [descheads $id]
+ if {$dheads eq $headids($head)} {
# the stuff on this branch isn't on any other branch
if {![confirm_popup "The commits on branch $head aren't on any other\
branch.\nReally delete branch $head?"]} return
@@ -5392,385 +5968,886 @@ proc rmbranch {} {
error_popup $err
return
}
+ removehead $id $head
removedhead $id $head
redrawtags $id
notbusy rmbranch
+ dispneartags 0
}
# Stuff for finding nearby tags
proc getallcommits {} {
- global allcstart allcommits allcfd allids
+ global allcommits allids nbmp nextarc seeds
set allids {}
- set fd [open [concat | git rev-list --all --topo-order --parents] r]
- set allcfd $fd
- fconfigure $fd -blocking 0
- set allcommits "reading"
- nowbusy allcommits
- restartgetall $fd
+ set nbmp 0
+ set nextarc 0
+ set allcommits 0
+ set seeds {}
+ regetallcommits
}
-proc discardallcommits {} {
- global allparents allchildren allcommits allcfd
- global desc_tags anc_tags alldtags tagisdesc allids desc_heads
+# Called when the graph might have changed
+proc regetallcommits {} {
+ global allcommits seeds
- if {![info exists allcommits]} return
- if {$allcommits eq "reading"} {
- catch {close $allcfd}
+ set cmd [concat | git rev-list --all --parents]
+ foreach id $seeds {
+ lappend cmd "^$id"
}
- foreach v {allcommits allchildren allparents allids desc_tags anc_tags
- alldtags tagisdesc desc_heads} {
- catch {unset $v}
- }
-}
-
-proc restartgetall {fd} {
- global allcstart
+ set fd [open $cmd r]
+ fconfigure $fd -blocking 0
+ incr allcommits
+ nowbusy allcommits
+ filerun $fd [list getallclines $fd]
+}
+
+# Since most commits have 1 parent and 1 child, we group strings of
+# such commits into "arcs" joining branch/merge points (BMPs), which
+# are commits that either don't have 1 parent or don't have 1 child.
+#
+# arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
+# arcout(id) - outgoing arcs for BMP
+# arcids(a) - list of IDs on arc including end but not start
+# arcstart(a) - BMP ID at start of arc
+# arcend(a) - BMP ID at end of arc
+# growing(a) - arc a is still growing
+# arctags(a) - IDs out of arcids (excluding end) that have tags
+# archeads(a) - IDs out of arcids (excluding end) that have heads
+# The start of an arc is at the descendent end, so "incoming" means
+# coming from descendents, and "outgoing" means going towards ancestors.
- fileevent $fd readable [list getallclines $fd]
- set allcstart [clock clicks -milliseconds]
-}
+proc getallclines {fd} {
+ global allids allparents allchildren idtags idheads nextarc nbmp
+ global arcnos arcids arctags arcout arcend arcstart archeads growing
+ global seeds allcommits
-proc combine_dtags {l1 l2} {
- global tagisdesc notfirstd
+ set nid 0
+ while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
+ set id [lindex $line 0]
+ if {[info exists allparents($id)]} {
+ # seen it already
+ continue
+ }
+ lappend allids $id
+ set olds [lrange $line 1 end]
+ set allparents($id) $olds
+ if {![info exists allchildren($id)]} {
+ set allchildren($id) {}
+ set arcnos($id) {}
+ lappend seeds $id
+ } else {
+ set a $arcnos($id)
+ if {[llength $olds] == 1 && [llength $a] == 1} {
+ lappend arcids($a) $id
+ if {[info exists idtags($id)]} {
+ lappend arctags($a) $id
+ }
+ if {[info exists idheads($id)]} {
+ lappend archeads($a) $id
+ }
+ if {[info exists allparents($olds)]} {
+ # seen parent already
+ if {![info exists arcout($olds)]} {
+ splitarc $olds
+ }
+ lappend arcids($a) $olds
+ set arcend($a) $olds
+ unset growing($a)
+ }
+ lappend allchildren($olds) $id
+ lappend arcnos($olds) $a
+ continue
+ }
+ }
+ incr nbmp
+ foreach a $arcnos($id) {
+ lappend arcids($a) $id
+ set arcend($a) $id
+ unset growing($a)
+ }
- set res [lsort -unique [concat $l1 $l2]]
- for {set i 0} {$i < [llength $res]} {incr i} {
- set x [lindex $res $i]
- for {set j [expr {$i+1}]} {$j < [llength $res]} {} {
- set y [lindex $res $j]
- if {[info exists tagisdesc($x,$y)]} {
- if {$tagisdesc($x,$y) > 0} {
- # x is a descendent of y, exclude x
- set res [lreplace $res $i $i]
- incr i -1
- break
- } else {
- # y is a descendent of x, exclude y
- set res [lreplace $res $j $j]
+ set ao {}
+ foreach p $olds {
+ lappend allchildren($p) $id
+ set a [incr nextarc]
+ set arcstart($a) $id
+ set archeads($a) {}
+ set arctags($a) {}
+ set archeads($a) {}
+ set arcids($a) {}
+ lappend ao $a
+ set growing($a) 1
+ if {[info exists allparents($p)]} {
+ # seen it already, may need to make a new branch
+ if {![info exists arcout($p)]} {
+ splitarc $p
}
- } else {
- # no relation, keep going
- incr j
+ lappend arcids($a) $p
+ set arcend($a) $p
+ unset growing($a)
}
+ lappend arcnos($p) $a
}
+ set arcout($id) $ao
}
- return $res
+ if {$nid > 0} {
+ global cached_dheads cached_dtags cached_atags
+ catch {unset cached_dheads}
+ catch {unset cached_dtags}
+ catch {unset cached_atags}
+ }
+ if {![eof $fd]} {
+ return [expr {$nid >= 1000? 2: 1}]
+ }
+ close $fd
+ if {[incr allcommits -1] == 0} {
+ notbusy allcommits
+ }
+ dispneartags 0
+ return 0
}
-proc combine_atags {l1 l2} {
- global tagisdesc
+proc recalcarc {a} {
+ global arctags archeads arcids idtags idheads
- set res [lsort -unique [concat $l1 $l2]]
- for {set i 0} {$i < [llength $res]} {incr i} {
- set x [lindex $res $i]
- for {set j [expr {$i+1}]} {$j < [llength $res]} {} {
- set y [lindex $res $j]
- if {[info exists tagisdesc($x,$y)]} {
- if {$tagisdesc($x,$y) < 0} {
- # x is an ancestor of y, exclude x
- set res [lreplace $res $i $i]
- incr i -1
- break
- } else {
- # y is an ancestor of x, exclude y
- set res [lreplace $res $j $j]
- }
- } else {
- # no relation, keep going
- incr j
- }
+ set at {}
+ set ah {}
+ foreach id [lrange $arcids($a) 0 end-1] {
+ if {[info exists idtags($id)]} {
+ lappend at $id
+ }
+ if {[info exists idheads($id)]} {
+ lappend ah $id
}
}
- return $res
+ set arctags($a) $at
+ set archeads($a) $ah
}
-proc forward_pass {id children} {
- global idtags desc_tags idheads desc_heads alldtags tagisdesc
+proc splitarc {p} {
+ global arcnos arcids nextarc nbmp arctags archeads idtags idheads
+ global arcstart arcend arcout allparents growing
- set dtags {}
- set dheads {}
- foreach child $children {
- if {[info exists idtags($child)]} {
- set ctags [list $child]
+ set a $arcnos($p)
+ if {[llength $a] != 1} {
+ puts "oops splitarc called but [llength $a] arcs already"
+ return
+ }
+ set a [lindex $a 0]
+ set i [lsearch -exact $arcids($a) $p]
+ if {$i < 0} {
+ puts "oops splitarc $p not in arc $a"
+ return
+ }
+ set na [incr nextarc]
+ if {[info exists arcend($a)]} {
+ set arcend($na) $arcend($a)
+ } else {
+ set l [lindex $allparents([lindex $arcids($a) end]) 0]
+ set j [lsearch -exact $arcnos($l) $a]
+ set arcnos($l) [lreplace $arcnos($l) $j $j $na]
+ }
+ set tail [lrange $arcids($a) [expr {$i+1}] end]
+ set arcids($a) [lrange $arcids($a) 0 $i]
+ set arcend($a) $p
+ set arcstart($na) $p
+ set arcout($p) $na
+ set arcids($na) $tail
+ if {[info exists growing($a)]} {
+ set growing($na) 1
+ unset growing($a)
+ }
+ incr nbmp
+
+ foreach id $tail {
+ if {[llength $arcnos($id)] == 1} {
+ set arcnos($id) $na
} else {
- set ctags $desc_tags($child)
+ set j [lsearch -exact $arcnos($id) $a]
+ set arcnos($id) [lreplace $arcnos($id) $j $j $na]
}
- if {$dtags eq {}} {
- set dtags $ctags
- } elseif {$ctags ne $dtags} {
- set dtags [combine_dtags $dtags $ctags]
+ }
+
+ # reconstruct tags and heads lists
+ if {$arctags($a) ne {} || $archeads($a) ne {}} {
+ recalcarc $a
+ recalcarc $na
+ } else {
+ set arctags($na) {}
+ set archeads($na) {}
+ }
+}
+
+# Update things for a new commit added that is a child of one
+# existing commit. Used when cherry-picking.
+proc addnewchild {id p} {
+ global allids allparents allchildren idtags nextarc nbmp
+ global arcnos arcids arctags arcout arcend arcstart archeads growing
+ global seeds
+
+ lappend allids $id
+ set allparents($id) [list $p]
+ set allchildren($id) {}
+ set arcnos($id) {}
+ lappend seeds $id
+ incr nbmp
+ lappend allchildren($p) $id
+ set a [incr nextarc]
+ set arcstart($a) $id
+ set archeads($a) {}
+ set arctags($a) {}
+ set arcids($a) [list $p]
+ set arcend($a) $p
+ if {![info exists arcout($p)]} {
+ splitarc $p
+ }
+ lappend arcnos($p) $a
+ set arcout($id) [list $a]
+}
+
+# Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
+# or 0 if neither is true.
+proc anc_or_desc {a b} {
+ global arcout arcstart arcend arcnos cached_isanc
+
+ if {$arcnos($a) eq $arcnos($b)} {
+ # Both are on the same arc(s); either both are the same BMP,
+ # or if one is not a BMP, the other is also not a BMP or is
+ # the BMP at end of the arc (and it only has 1 incoming arc).
+ if {$a eq $b} {
+ return 0
}
- set cheads $desc_heads($child)
- if {$dheads eq {}} {
- set dheads $cheads
- } elseif {$cheads ne $dheads} {
- set dheads [lsort -unique [concat $dheads $cheads]]
+ # assert {[llength $arcnos($a)] == 1}
+ set arc [lindex $arcnos($a) 0]
+ set i [lsearch -exact $arcids($arc) $a]
+ set j [lsearch -exact $arcids($arc) $b]
+ if {$i < 0 || $i > $j} {
+ return 1
+ } else {
+ return -1
}
}
- set desc_tags($id) $dtags
- if {[info exists idtags($id)]} {
- set adt $dtags
- foreach tag $dtags {
- set adt [concat $adt $alldtags($tag)]
+
+ if {![info exists arcout($a)]} {
+ set arc [lindex $arcnos($a) 0]
+ if {[info exists arcend($arc)]} {
+ set aend $arcend($arc)
+ } else {
+ set aend {}
}
- set adt [lsort -unique $adt]
- set alldtags($id) $adt
- foreach tag $adt {
- set tagisdesc($id,$tag) -1
- set tagisdesc($tag,$id) 1
+ set a $arcstart($arc)
+ } else {
+ set aend $a
+ }
+ if {![info exists arcout($b)]} {
+ set arc [lindex $arcnos($b) 0]
+ if {[info exists arcend($arc)]} {
+ set bend $arcend($arc)
+ } else {
+ set bend {}
}
+ set b $arcstart($arc)
+ } else {
+ set bend $b
}
- if {[info exists idheads($id)]} {
- set dheads [concat $dheads $idheads($id)]
+ if {$a eq $bend} {
+ return 1
+ }
+ if {$b eq $aend} {
+ return -1
+ }
+ if {[info exists cached_isanc($a,$bend)]} {
+ if {$cached_isanc($a,$bend)} {
+ return 1
+ }
+ }
+ if {[info exists cached_isanc($b,$aend)]} {
+ if {$cached_isanc($b,$aend)} {
+ return -1
+ }
+ if {[info exists cached_isanc($a,$bend)]} {
+ return 0
+ }
}
- set desc_heads($id) $dheads
-}
-proc getallclines {fd} {
- global allparents allchildren allcommits allcstart
- global desc_tags anc_tags idtags tagisdesc allids
- global idheads travindex
+ set todo [list $a $b]
+ set anc($a) a
+ set anc($b) b
+ for {set i 0} {$i < [llength $todo]} {incr i} {
+ set x [lindex $todo $i]
+ if {$anc($x) eq {}} {
+ continue
+ }
+ foreach arc $arcnos($x) {
+ set xd $arcstart($arc)
+ if {$xd eq $bend} {
+ set cached_isanc($a,$bend) 1
+ set cached_isanc($b,$aend) 0
+ return 1
+ } elseif {$xd eq $aend} {
+ set cached_isanc($b,$aend) 1
+ set cached_isanc($a,$bend) 0
+ return -1
+ }
+ if {![info exists anc($xd)]} {
+ set anc($xd) $anc($x)
+ lappend todo $xd
+ } elseif {$anc($xd) ne $anc($x)} {
+ set anc($xd) {}
+ }
+ }
+ }
+ set cached_isanc($a,$bend) 0
+ set cached_isanc($b,$aend) 0
+ return 0
+}
- while {[gets $fd line] >= 0} {
- set id [lindex $line 0]
- lappend allids $id
- set olds [lrange $line 1 end]
- set allparents($id) $olds
- if {![info exists allchildren($id)]} {
- set allchildren($id) {}
+# This identifies whether $desc has an ancestor that is
+# a growing tip of the graph and which is not an ancestor of $anc
+# and returns 0 if so and 1 if not.
+# If we subsequently discover a tag on such a growing tip, and that
+# turns out to be a descendent of $anc (which it could, since we
+# don't necessarily see children before parents), then $desc
+# isn't a good choice to display as a descendent tag of
+# $anc (since it is the descendent of another tag which is
+# a descendent of $anc). Similarly, $anc isn't a good choice to
+# display as a ancestor tag of $desc.
+#
+proc is_certain {desc anc} {
+ global arcnos arcout arcstart arcend growing problems
+
+ set certain {}
+ if {[llength $arcnos($anc)] == 1} {
+ # tags on the same arc are certain
+ if {$arcnos($desc) eq $arcnos($anc)} {
+ return 1
}
- foreach p $olds {
- lappend allchildren($p) $id
+ if {![info exists arcout($anc)]} {
+ # if $anc is partway along an arc, use the start of the arc instead
+ set a [lindex $arcnos($anc) 0]
+ set anc $arcstart($a)
}
- # compute nearest tagged descendents as we go
- # also compute descendent heads
- forward_pass $id $allchildren($id)
- if {[clock clicks -milliseconds] - $allcstart >= 50} {
- fileevent $fd readable {}
- after idle restartgetall $fd
- return
+ }
+ if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
+ set x $desc
+ } else {
+ set a [lindex $arcnos($desc) 0]
+ set x $arcend($a)
+ }
+ if {$x == $anc} {
+ return 1
+ }
+ set anclist [list $x]
+ set dl($x) 1
+ set nnh 1
+ set ngrowanc 0
+ for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
+ set x [lindex $anclist $i]
+ if {$dl($x)} {
+ incr nnh -1
+ }
+ set done($x) 1
+ foreach a $arcout($x) {
+ if {[info exists growing($a)]} {
+ if {![info exists growanc($x)] && $dl($x)} {
+ set growanc($x) 1
+ incr ngrowanc
+ }
+ } else {
+ set y $arcend($a)
+ if {[info exists dl($y)]} {
+ if {$dl($y)} {
+ if {!$dl($x)} {
+ set dl($y) 0
+ if {![info exists done($y)]} {
+ incr nnh -1
+ }
+ if {[info exists growanc($x)]} {
+ incr ngrowanc -1
+ }
+ set xl [list $y]
+ for {set k 0} {$k < [llength $xl]} {incr k} {
+ set z [lindex $xl $k]
+ foreach c $arcout($z) {
+ if {[info exists arcend($c)]} {
+ set v $arcend($c)
+ if {[info exists dl($v)] && $dl($v)} {
+ set dl($v) 0
+ if {![info exists done($v)]} {
+ incr nnh -1
+ }
+ if {[info exists growanc($v)]} {
+ incr ngrowanc -1
+ }
+ lappend xl $v
+ }
+ }
+ }
+ }
+ }
+ }
+ } elseif {$y eq $anc || !$dl($x)} {
+ set dl($y) 0
+ lappend anclist $y
+ } else {
+ set dl($y) 1
+ lappend anclist $y
+ incr nnh
+ }
+ }
}
}
- if {[eof $fd]} {
- set travindex [llength $allids]
- set allcommits "traversing"
- after idle restartatags
- if {[catch {close $fd} err]} {
- error_popup "Error reading full commit graph: $err.\n\
- Results may be incomplete."
+ foreach x [array names growanc] {
+ if {$dl($x)} {
+ return 0
}
+ return 0
}
+ return 1
}
-# walk backward through the tree and compute nearest tagged ancestors
-proc restartatags {} {
- global allids allparents idtags anc_tags travindex
+proc validate_arctags {a} {
+ global arctags idtags
- set t0 [clock clicks -milliseconds]
- set i $travindex
- while {[incr i -1] >= 0} {
- set id [lindex $allids $i]
- set atags {}
- foreach p $allparents($id) {
- if {[info exists idtags($p)]} {
- set ptags [list $p]
- } else {
- set ptags $anc_tags($p)
+ set i -1
+ set na $arctags($a)
+ foreach id $arctags($a) {
+ incr i
+ if {![info exists idtags($id)]} {
+ set na [lreplace $na $i $i]
+ incr i -1
+ }
+ }
+ set arctags($a) $na
+}
+
+proc validate_archeads {a} {
+ global archeads idheads
+
+ set i -1
+ set na $archeads($a)
+ foreach id $archeads($a) {
+ incr i
+ if {![info exists idheads($id)]} {
+ set na [lreplace $na $i $i]
+ incr i -1
+ }
+ }
+ set archeads($a) $na
+}
+
+# Return the list of IDs that have tags that are descendents of id,
+# ignoring IDs that are descendents of IDs already reported.
+proc desctags {id} {
+ global arcnos arcstart arcids arctags idtags allparents
+ global growing cached_dtags
+
+ if {![info exists allparents($id)]} {
+ return {}
+ }
+ set t1 [clock clicks -milliseconds]
+ set argid $id
+ if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
+ # part-way along an arc; check that arc first
+ set a [lindex $arcnos($id) 0]
+ if {$arctags($a) ne {}} {
+ validate_arctags $a
+ set i [lsearch -exact $arcids($a) $id]
+ set tid {}
+ foreach t $arctags($a) {
+ set j [lsearch -exact $arcids($a) $t]
+ if {$j >= $i} break
+ set tid $t
}
- if {$atags eq {}} {
- set atags $ptags
- } elseif {$ptags ne $atags} {
- set atags [combine_atags $atags $ptags]
+ if {$tid ne {}} {
+ return $tid
}
}
- set anc_tags($id) $atags
- if {[clock clicks -milliseconds] - $t0 >= 50} {
- set travindex $i
- after idle restartatags
- return
+ set id $arcstart($a)
+ if {[info exists idtags($id)]} {
+ return $id
+ }
+ }
+ if {[info exists cached_dtags($id)]} {
+ return $cached_dtags($id)
+ }
+
+ set origid $id
+ set todo [list $id]
+ set queued($id) 1
+ set nc 1
+ for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
+ set id [lindex $todo $i]
+ set done($id) 1
+ set ta [info exists hastaggedancestor($id)]
+ if {!$ta} {
+ incr nc -1
+ }
+ # ignore tags on starting node
+ if {!$ta && $i > 0} {
+ if {[info exists idtags($id)]} {
+ set tagloc($id) $id
+ set ta 1
+ } elseif {[info exists cached_dtags($id)]} {
+ set tagloc($id) $cached_dtags($id)
+ set ta 1
+ }
+ }
+ foreach a $arcnos($id) {
+ set d $arcstart($a)
+ if {!$ta && $arctags($a) ne {}} {
+ validate_arctags $a
+ if {$arctags($a) ne {}} {
+ lappend tagloc($id) [lindex $arctags($a) end]
+ }
+ }
+ if {$ta || $arctags($a) ne {}} {
+ set tomark [list $d]
+ for {set j 0} {$j < [llength $tomark]} {incr j} {
+ set dd [lindex $tomark $j]
+ if {![info exists hastaggedancestor($dd)]} {
+ if {[info exists done($dd)]} {
+ foreach b $arcnos($dd) {
+ lappend tomark $arcstart($b)
+ }
+ if {[info exists tagloc($dd)]} {
+ unset tagloc($dd)
+ }
+ } elseif {[info exists queued($dd)]} {
+ incr nc -1
+ }
+ set hastaggedancestor($dd) 1
+ }
+ }
+ }
+ if {![info exists queued($d)]} {
+ lappend todo $d
+ set queued($d) 1
+ if {![info exists hastaggedancestor($d)]} {
+ incr nc
+ }
+ }
}
}
- set allcommits "done"
- set travindex 0
- notbusy allcommits
- dispneartags
-}
-
-# update the desc_tags and anc_tags arrays for a new tag just added
-proc addedtag {id} {
- global desc_tags anc_tags allparents allchildren allcommits
- global idtags tagisdesc alldtags
-
- if {![info exists desc_tags($id)]} return
- set adt $desc_tags($id)
- foreach t $desc_tags($id) {
- set adt [concat $adt $alldtags($t)]
- }
- set adt [lsort -unique $adt]
- set alldtags($id) $adt
- foreach t $adt {
- set tagisdesc($id,$t) -1
- set tagisdesc($t,$id) 1
- }
- if {[info exists anc_tags($id)]} {
- set todo $anc_tags($id)
- while {$todo ne {}} {
- set do [lindex $todo 0]
- set todo [lrange $todo 1 end]
- if {[info exists tagisdesc($id,$do)]} continue
- set tagisdesc($do,$id) -1
- set tagisdesc($id,$do) 1
- if {[info exists anc_tags($do)]} {
- set todo [concat $todo $anc_tags($do)]
+ set tags {}
+ foreach id [array names tagloc] {
+ if {![info exists hastaggedancestor($id)]} {
+ foreach t $tagloc($id) {
+ if {[lsearch -exact $tags $t] < 0} {
+ lappend tags $t
+ }
}
}
}
+ set t2 [clock clicks -milliseconds]
+ set loopix $i
- set lastold $desc_tags($id)
- set lastnew [list $id]
- set nup 0
- set nch 0
- set todo $allparents($id)
- while {$todo ne {}} {
- set do [lindex $todo 0]
- set todo [lrange $todo 1 end]
- if {![info exists desc_tags($do)]} continue
- if {$desc_tags($do) ne $lastold} {
- set lastold $desc_tags($do)
- set lastnew [combine_dtags $lastold [list $id]]
- incr nch
- }
- if {$lastold eq $lastnew} continue
- set desc_tags($do) $lastnew
- incr nup
- if {![info exists idtags($do)]} {
- set todo [concat $todo $allparents($do)]
+ # remove tags that are descendents of other tags
+ for {set i 0} {$i < [llength $tags]} {incr i} {
+ set a [lindex $tags $i]
+ for {set j 0} {$j < $i} {incr j} {
+ set b [lindex $tags $j]
+ set r [anc_or_desc $a $b]
+ if {$r == 1} {
+ set tags [lreplace $tags $j $j]
+ incr j -1
+ incr i -1
+ } elseif {$r == -1} {
+ set tags [lreplace $tags $i $i]
+ incr i -1
+ break
+ }
}
}
- if {![info exists anc_tags($id)]} return
- set lastold $anc_tags($id)
- set lastnew [list $id]
- set nup 0
- set nch 0
- set todo $allchildren($id)
- while {$todo ne {}} {
- set do [lindex $todo 0]
- set todo [lrange $todo 1 end]
- if {![info exists anc_tags($do)]} continue
- if {$anc_tags($do) ne $lastold} {
- set lastold $anc_tags($do)
- set lastnew [combine_atags $lastold [list $id]]
- incr nch
+ if {[array names growing] ne {}} {
+ # graph isn't finished, need to check if any tag could get
+ # eclipsed by another tag coming later. Simply ignore any
+ # tags that could later get eclipsed.
+ set ctags {}
+ foreach t $tags {
+ if {[is_certain $t $origid]} {
+ lappend ctags $t
+ }
}
- if {$lastold eq $lastnew} continue
- set anc_tags($do) $lastnew
- incr nup
- if {![info exists idtags($do)]} {
- set todo [concat $todo $allchildren($do)]
+ if {$tags eq $ctags} {
+ set cached_dtags($origid) $tags
+ } else {
+ set tags $ctags
}
+ } else {
+ set cached_dtags($origid) $tags
+ }
+ set t3 [clock clicks -milliseconds]
+ if {0 && $t3 - $t1 >= 100} {
+ puts "iterating descendents ($loopix/[llength $todo] nodes) took\
+ [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
}
+ return $tags
}
-# update the desc_heads array for a new head just added
-proc addedhead {hid head} {
- global desc_heads allparents headids idheads
-
- set headids($head) $hid
- lappend idheads($hid) $head
-
- set todo [list $hid]
- while {$todo ne {}} {
- set do [lindex $todo 0]
- set todo [lrange $todo 1 end]
- if {![info exists desc_heads($do)] ||
- [lsearch -exact $desc_heads($do) $head] >= 0} continue
- set oldheads $desc_heads($do)
- lappend desc_heads($do) $head
- set heads $desc_heads($do)
- while {1} {
- set p $allparents($do)
- if {[llength $p] != 1 || ![info exists desc_heads($p)] ||
- $desc_heads($p) ne $oldheads} break
- set do $p
- set desc_heads($do) $heads
+proc anctags {id} {
+ global arcnos arcids arcout arcend arctags idtags allparents
+ global growing cached_atags
+
+ if {![info exists allparents($id)]} {
+ return {}
+ }
+ set t1 [clock clicks -milliseconds]
+ set argid $id
+ if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
+ # part-way along an arc; check that arc first
+ set a [lindex $arcnos($id) 0]
+ if {$arctags($a) ne {}} {
+ validate_arctags $a
+ set i [lsearch -exact $arcids($a) $id]
+ foreach t $arctags($a) {
+ set j [lsearch -exact $arcids($a) $t]
+ if {$j > $i} {
+ return $t
+ }
+ }
+ }
+ if {![info exists arcend($a)]} {
+ return {}
+ }
+ set id $arcend($a)
+ if {[info exists idtags($id)]} {
+ return $id
+ }
+ }
+ if {[info exists cached_atags($id)]} {
+ return $cached_atags($id)
+ }
+
+ set origid $id
+ set todo [list $id]
+ set queued($id) 1
+ set taglist {}
+ set nc 1
+ for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
+ set id [lindex $todo $i]
+ set done($id) 1
+ set td [info exists hastaggeddescendent($id)]
+ if {!$td} {
+ incr nc -1
+ }
+ # ignore tags on starting node
+ if {!$td && $i > 0} {
+ if {[info exists idtags($id)]} {
+ set tagloc($id) $id
+ set td 1
+ } elseif {[info exists cached_atags($id)]} {
+ set tagloc($id) $cached_atags($id)
+ set td 1
+ }
+ }
+ foreach a $arcout($id) {
+ if {!$td && $arctags($a) ne {}} {
+ validate_arctags $a
+ if {$arctags($a) ne {}} {
+ lappend tagloc($id) [lindex $arctags($a) 0]
+ }
+ }
+ if {![info exists arcend($a)]} continue
+ set d $arcend($a)
+ if {$td || $arctags($a) ne {}} {
+ set tomark [list $d]
+ for {set j 0} {$j < [llength $tomark]} {incr j} {
+ set dd [lindex $tomark $j]
+ if {![info exists hastaggeddescendent($dd)]} {
+ if {[info exists done($dd)]} {
+ foreach b $arcout($dd) {
+ if {[info exists arcend($b)]} {
+ lappend tomark $arcend($b)
+ }
+ }
+ if {[info exists tagloc($dd)]} {
+ unset tagloc($dd)
+ }
+ } elseif {[info exists queued($dd)]} {
+ incr nc -1
+ }
+ set hastaggeddescendent($dd) 1
+ }
+ }
+ }
+ if {![info exists queued($d)]} {
+ lappend todo $d
+ set queued($d) 1
+ if {![info exists hastaggeddescendent($d)]} {
+ incr nc
+ }
+ }
+ }
+ }
+ set t2 [clock clicks -milliseconds]
+ set loopix $i
+ set tags {}
+ foreach id [array names tagloc] {
+ if {![info exists hastaggeddescendent($id)]} {
+ foreach t $tagloc($id) {
+ if {[lsearch -exact $tags $t] < 0} {
+ lappend tags $t
+ }
+ }
}
- set todo [concat $todo $p]
}
-}
-# update the desc_heads array for a head just removed
-proc removedhead {hid head} {
- global desc_heads allparents headids idheads
+ # remove tags that are ancestors of other tags
+ for {set i 0} {$i < [llength $tags]} {incr i} {
+ set a [lindex $tags $i]
+ for {set j 0} {$j < $i} {incr j} {
+ set b [lindex $tags $j]
+ set r [anc_or_desc $a $b]
+ if {$r == -1} {
+ set tags [lreplace $tags $j $j]
+ incr j -1
+ incr i -1
+ } elseif {$r == 1} {
+ set tags [lreplace $tags $i $i]
+ incr i -1
+ break
+ }
+ }
+ }
- unset headids($head)
- if {$idheads($hid) eq $head} {
- unset idheads($hid)
- } else {
- set i [lsearch -exact $idheads($hid) $head]
- if {$i >= 0} {
- set idheads($hid) [lreplace $idheads($hid) $i $i]
+ if {[array names growing] ne {}} {
+ # graph isn't finished, need to check if any tag could get
+ # eclipsed by another tag coming later. Simply ignore any
+ # tags that could later get eclipsed.
+ set ctags {}
+ foreach t $tags {
+ if {[is_certain $origid $t]} {
+ lappend ctags $t
+ }
+ }
+ if {$tags eq $ctags} {
+ set cached_atags($origid) $tags
+ } else {
+ set tags $ctags
}
+ } else {
+ set cached_atags($origid) $tags
+ }
+ set t3 [clock clicks -milliseconds]
+ if {0 && $t3 - $t1 >= 100} {
+ puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
+ [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
}
+ return $tags
+}
- set todo [list $hid]
- while {$todo ne {}} {
- set do [lindex $todo 0]
- set todo [lrange $todo 1 end]
- if {![info exists desc_heads($do)]} continue
- set i [lsearch -exact $desc_heads($do) $head]
- if {$i < 0} continue
- set oldheads $desc_heads($do)
- set heads [lreplace $desc_heads($do) $i $i]
- while {1} {
- set desc_heads($do) $heads
- set p $allparents($do)
- if {[llength $p] != 1 || ![info exists desc_heads($p)] ||
- $desc_heads($p) ne $oldheads} break
- set do $p
+# Return the list of IDs that have heads that are descendents of id,
+# including id itself if it has a head.
+proc descheads {id} {
+ global arcnos arcstart arcids archeads idheads cached_dheads
+ global allparents
+
+ if {![info exists allparents($id)]} {
+ return {}
+ }
+ set aret {}
+ if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
+ # part-way along an arc; check it first
+ set a [lindex $arcnos($id) 0]
+ if {$archeads($a) ne {}} {
+ validate_archeads $a
+ set i [lsearch -exact $arcids($a) $id]
+ foreach t $archeads($a) {
+ set j [lsearch -exact $arcids($a) $t]
+ if {$j > $i} break
+ lappend aret $t
+ }
}
- set todo [concat $todo $p]
+ set id $arcstart($a)
}
+ set origid $id
+ set todo [list $id]
+ set seen($id) 1
+ set ret {}
+ for {set i 0} {$i < [llength $todo]} {incr i} {
+ set id [lindex $todo $i]
+ if {[info exists cached_dheads($id)]} {
+ set ret [concat $ret $cached_dheads($id)]
+ } else {
+ if {[info exists idheads($id)]} {
+ lappend ret $id
+ }
+ foreach a $arcnos($id) {
+ if {$archeads($a) ne {}} {
+ validate_archeads $a
+ if {$archeads($a) ne {}} {
+ set ret [concat $ret $archeads($a)]
+ }
+ }
+ set d $arcstart($a)
+ if {![info exists seen($d)]} {
+ lappend todo $d
+ set seen($d) 1
+ }
+ }
+ }
+ }
+ set ret [lsort -unique $ret]
+ set cached_dheads($origid) $ret
+ return [concat $ret $aret]
}
-# update things for a head moved to a child of its previous location
-proc movedhead {id name} {
- global headids idheads
+proc addedtag {id} {
+ global arcnos arcout cached_dtags cached_atags
- set oldid $headids($name)
- set headids($name) $id
- if {$idheads($oldid) eq $name} {
- unset idheads($oldid)
- } else {
- set i [lsearch -exact $idheads($oldid) $name]
- if {$i >= 0} {
- set idheads($oldid) [lreplace $idheads($oldid) $i $i]
- }
+ if {![info exists arcnos($id)]} return
+ if {![info exists arcout($id)]} {
+ recalcarc [lindex $arcnos($id) 0]
}
- lappend idheads($id) $name
+ catch {unset cached_dtags}
+ catch {unset cached_atags}
}
-proc changedrefs {} {
- global desc_heads desc_tags anc_tags allcommits allids
- global allchildren allparents idtags travindex
+proc addedhead {hid head} {
+ global arcnos arcout cached_dheads
- if {![info exists allcommits]} return
- catch {unset desc_heads}
- catch {unset desc_tags}
- catch {unset anc_tags}
- catch {unset alldtags}
- catch {unset tagisdesc}
- foreach id $allids {
- forward_pass $id $allchildren($id)
+ if {![info exists arcnos($hid)]} return
+ if {![info exists arcout($hid)]} {
+ recalcarc [lindex $arcnos($hid) 0]
}
- if {$allcommits ne "reading"} {
- set travindex [llength $allids]
- if {$allcommits ne "traversing"} {
- set allcommits "traversing"
- after idle restartatags
+ catch {unset cached_dheads}
+}
+
+proc removedhead {hid head} {
+ global cached_dheads
+
+ catch {unset cached_dheads}
+}
+
+proc movedhead {hid head} {
+ global arcnos arcout cached_dheads
+
+ if {![info exists arcnos($hid)]} return
+ if {![info exists arcout($hid)]} {
+ recalcarc [lindex $arcnos($hid) 0]
+ }
+ catch {unset cached_dheads}
+}
+
+proc changedrefs {} {
+ global cached_dheads cached_dtags cached_atags
+ global arctags archeads arcnos arcout idheads idtags
+
+ foreach id [concat [array names idheads] [array names idtags]] {
+ if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
+ set a [lindex $arcnos($id) 0]
+ if {![info exists donearc($a)]} {
+ recalcarc $a
+ set donearc($a) 1
+ }
}
}
+ catch {unset cached_dtags}
+ catch {unset cached_atags}
+ catch {unset cached_dheads}
}
proc rereadrefs {} {
@@ -5817,7 +6894,7 @@ proc listrefs {id} {
}
proc showtag {tag isnew} {
- global ctext tagcontents tagids linknum
+ global ctext tagcontents tagids linknum tagobjid
if {$isnew} {
addtohistory [list showtag $tag 0]
@@ -5825,6 +6902,11 @@ proc showtag {tag isnew} {
$ctext conf -state normal
clear_ctext
set linknum 0
+ if {![info exists tagcontents($tag)]} {
+ catch {
+ set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
+ }
+ }
if {[info exists tagcontents($tag)]} {
set text $tagcontents($tag)
} else {
@@ -5844,9 +6926,9 @@ proc doquit {} {
proc doprefs {} {
global maxwidth maxgraphpct diffopts
- global oldprefs prefstop showneartags
- global bgcolor fgcolor ctext diffcolors
- global uifont
+ global oldprefs prefstop showneartags showlocalchanges
+ global bgcolor fgcolor ctext diffcolors selectbgcolor
+ global uifont tabstop
set top .gitkprefs
set prefstop $top
@@ -5854,7 +6936,7 @@ proc doprefs {} {
raise $top
return
}
- foreach v {maxwidth maxgraphpct diffopts showneartags} {
+ foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
set oldprefs($v) [set $v]
}
toplevel $top
@@ -5871,6 +6953,11 @@ proc doprefs {} {
-font optionfont
spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
grid x $top.maxpctl $top.maxpct -sticky w
+ frame $top.showlocal
+ label $top.showlocal.l -text "Show local changes" -font optionfont
+ checkbutton $top.showlocal.b -variable showlocalchanges
+ pack $top.showlocal.b $top.showlocal.l -side left
+ grid x $top.showlocal -sticky w
label $top.ddisp -text "Diff display options"
$top.ddisp configure -font $uifont
@@ -5884,6 +6971,9 @@ proc doprefs {} {
checkbutton $top.ntag.b -variable showneartags
pack $top.ntag.b $top.ntag.l -side left
grid x $top.ntag -sticky w
+ label $top.tabstopl -text "tabstop" -font optionfont
+ spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
+ grid x $top.tabstopl $top.tabstop -sticky w
label $top.cdisp -text "Colors: press to choose"
$top.cdisp configure -font $uifont
@@ -5912,6 +7002,10 @@ proc doprefs {} {
"diff hunk header" \
[list $ctext tag conf hunksep -foreground]]
grid x $top.hunksepbut $top.hunksep -sticky w
+ label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
+ button $top.selbgbut -text "Select bg" -font optionfont \
+ -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
+ grid x $top.selbgbut $top.selbgsep -sticky w
frame $top.buts
button $top.buts.ok -text "OK" -command prefsok -default active
@@ -5936,6 +7030,16 @@ proc choosecolor {v vi w x cmd} {
eval $cmd $c
}
+proc setselbg {c} {
+ global bglist cflist
+ foreach w $bglist {
+ $w configure -selectbackground $c
+ }
+ $cflist tag configure highlight \
+ -background [$cflist cget -selectbackground]
+ allcanvs itemconf secsel -fill $c
+}
+
proc setbg {c} {
global bglist
@@ -5956,9 +7060,9 @@ proc setfg {c} {
proc prefscan {} {
global maxwidth maxgraphpct diffopts
- global oldprefs prefstop showneartags
+ global oldprefs prefstop showneartags showlocalchanges
- foreach v {maxwidth maxgraphpct diffopts showneartags} {
+ foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
set $v $oldprefs($v)
}
catch {destroy $prefstop}
@@ -5967,10 +7071,19 @@ proc prefscan {} {
proc prefsok {} {
global maxwidth maxgraphpct
- global oldprefs prefstop showneartags
+ global oldprefs prefstop showneartags showlocalchanges
+ global charspc ctext tabstop
catch {destroy $prefstop}
unset prefstop
+ $ctext configure -tabs "[expr {$tabstop * $charspc}]"
+ if {$showlocalchanges != $oldprefs(showlocalchanges)} {
+ if {$showlocalchanges} {
+ doshowlocalchanges
+ } else {
+ dohidelocalchanges
+ }
+ }
if {$maxwidth != $oldprefs(maxwidth)
|| $maxgraphpct != $oldprefs(maxgraphpct)} {
redisplay
@@ -5980,7 +7093,10 @@ proc prefsok {} {
}
proc formatdate {d} {
- return [clock format $d -format "%Y-%m-%d %H:%M:%S"]
+ if {$d ne {}} {
+ set d [clock format $d -format "%Y-%m-%d %H:%M:%S"]
+ }
+ return $d
}
# This list of encoding names and aliases is distilled from
@@ -6276,6 +7392,7 @@ if {$tclencoding == {}} {
set mainfont {Helvetica 9}
set textfont {Courier 9}
set uifont {Helvetica 9 bold}
+set tabstop 8
set findmergefiles 0
set maxgraphpct 50
set maxwidth 16
@@ -6287,11 +7404,15 @@ set mingaplen 30
set cmitmode "patch"
set wrapcomment "none"
set showneartags 1
+set maxrefs 20
+set maxlinelen 200
+set showlocalchanges 1
set colors {green red blue magenta darkgrey brown orange}
set bgcolor white
set fgcolor black
set diffcolors {red "#00a000" blue}
+set selectbgcolor gray85
catch {source ~/.gitk}
@@ -6338,6 +7459,9 @@ if {$i >= 0} {
}
}
+set nullid "0000000000000000000000000000000000000000"
+
+set runq {}
set history {}
set historyindex 0
set fh_serial 0
@@ -6362,6 +7486,9 @@ set cmdlineok 0
set stopped 0
set stuffsaved 0
set patchnum 0
+set lookingforhead 0
+set localrow -1
+set lserial 0
setcoords
makewindow
wm title . "[file tail $argv0]: [file tail [pwd]]"
diff --git a/index-pack.c b/index-pack.c
index 82c8da368..8403c36b6 100644
--- a/index-pack.c
+++ b/index-pack.c
@@ -634,7 +634,8 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
write_or_die(keep_fd, keep_msg, keep_msg_len);
write_or_die(keep_fd, "\n", 1);
}
- close(keep_fd);
+ if (close(keep_fd) != 0)
+ die("cannot write keep file");
report = "keep";
}
}
diff --git a/log-tree.c b/log-tree.c
index 0cf21bc05..ced3f332e 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -408,5 +408,6 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit)
shown = 1;
}
opt->loginfo = NULL;
+ maybe_flush_or_die(stdout, "stdout");
return shown;
}
diff --git a/read-cache.c b/read-cache.c
index 4362b11f4..a363f312c 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -350,6 +350,34 @@ int remove_file_from_index(struct index_state *istate, const char *path)
return 0;
}
+static int compare_name(struct cache_entry *ce, const char *path, int namelen)
+{
+ return namelen != ce_namelen(ce) || memcmp(path, ce->name, namelen);
+}
+
+static int index_name_pos_also_unmerged(struct index_state *istate,
+ const char *path, int namelen)
+{
+ int pos = index_name_pos(istate, path, namelen);
+ struct cache_entry *ce;
+
+ if (pos >= 0)
+ return pos;
+
+ /* maybe unmerged? */
+ pos = -1 - pos;
+ if (pos >= istate->cache_nr ||
+ compare_name((ce = istate->cache[pos]), path, namelen))
+ return -1;
+
+ /* order of preference: stage 2, 1, 3 */
+ if (ce_stage(ce) == 1 && pos + 1 < istate->cache_nr &&
+ ce_stage((ce = istate->cache[pos + 1])) == 2 &&
+ !compare_name(ce, path, namelen))
+ pos++;
+ return pos;
+}
+
int add_file_to_index(struct index_state *istate, const char *path, int verbose)
{
int size, namelen;
@@ -380,7 +408,7 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose)
* from it, otherwise assume unexecutable regular file.
*/
struct cache_entry *ent;
- int pos = index_name_pos(istate, path, namelen);
+ int pos = index_name_pos_also_unmerged(istate, path, namelen);
ent = (0 <= pos) ? istate->cache[pos] : NULL;
ce->ce_mode = ce_mode_from_stat(ent, st.st_mode);
diff --git a/refs.c b/refs.c
index 67ac97c71..4dc7e8b47 100644
--- a/refs.c
+++ b/refs.c
@@ -1106,8 +1106,7 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
len += sprintf(logrec + len - 1, "\t%.*s\n", msglen, msg) - 1;
written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;
free(logrec);
- close(logfd);
- if (written != len)
+ if (close(logfd) != 0 || written != len)
return error("Unable to append to %s", log_file);
return 0;
}
@@ -1204,8 +1203,7 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
goto error_free_return;
}
written = write_in_full(fd, ref, len);
- close(fd);
- if (written != len) {
+ if (close(fd) != 0 || written != len) {
error("Unable to write to %s", lockpath);
goto error_unlink_return;
}
diff --git a/setup.c b/setup.c
index 14f62c42e..01f74d464 100644
--- a/setup.c
+++ b/setup.c
@@ -95,7 +95,7 @@ void verify_non_filename(const char *prefix, const char *arg)
const char *name;
struct stat st;
- if (is_inside_git_dir())
+ if (!is_inside_work_tree() || is_inside_git_dir())
return;
if (*arg == '-')
return; /* flag */
@@ -174,41 +174,96 @@ static int inside_git_dir = -1;
int is_inside_git_dir(void)
{
- if (inside_git_dir < 0) {
- char buffer[1024];
-
- if (is_bare_repository())
- return (inside_git_dir = 1);
- if (getcwd(buffer, sizeof(buffer))) {
- const char *git_dir = get_git_dir(), *cwd = buffer;
- while (*git_dir && *git_dir == *cwd) {
- git_dir++;
- cwd++;
- }
- inside_git_dir = !*git_dir;
- } else
- inside_git_dir = 0;
+ if (inside_git_dir >= 0)
+ return inside_git_dir;
+ die("BUG: is_inside_git_dir called before setup_git_directory");
+}
+
+static int inside_work_tree = -1;
+
+int is_inside_work_tree(void)
+{
+ if (inside_git_dir >= 0)
+ return inside_work_tree;
+ die("BUG: is_inside_work_tree called before setup_git_directory");
+}
+
+static char *gitworktree_config;
+
+static int git_setup_config(const char *var, const char *value)
+{
+ if (!strcmp(var, "core.worktree")) {
+ if (gitworktree_config)
+ strlcpy(gitworktree_config, value, PATH_MAX);
+ return 0;
}
- return inside_git_dir;
+ return git_default_config(var, value);
}
const char *setup_git_directory_gently(int *nongit_ok)
{
static char cwd[PATH_MAX+1];
- const char *gitdirenv;
- int len, offset;
+ char worktree[PATH_MAX+1], gitdir[PATH_MAX+1];
+ const char *gitdirenv, *gitworktree;
+ int wt_rel_gitdir = 0;
- /*
- * If GIT_DIR is set explicitly, we're not going
- * to do any discovery, but we still do repository
- * validation.
- */
gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
- if (gitdirenv) {
- if (PATH_MAX - 40 < strlen(gitdirenv))
- die("'$%s' too big", GIT_DIR_ENVIRONMENT);
- if (is_git_directory(gitdirenv))
+ if (!gitdirenv) {
+ int len, offset;
+
+ if (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/')
+ die("Unable to read current working directory");
+
+ offset = len = strlen(cwd);
+ for (;;) {
+ if (is_git_directory(".git"))
+ break;
+ if (offset == 0) {
+ offset = -1;
+ break;
+ }
+ chdir("..");
+ while (cwd[--offset] != '/')
+ ; /* do nothing */
+ }
+
+ if (offset >= 0) {
+ inside_work_tree = 1;
+ git_config(git_default_config);
+ if (offset == len) {
+ inside_git_dir = 0;
+ return NULL;
+ }
+
+ cwd[len++] = '/';
+ cwd[len] = '\0';
+ inside_git_dir = !prefixcmp(cwd + offset + 1, ".git/");
+ return cwd + offset + 1;
+ }
+
+ if (chdir(cwd))
+ die("Cannot come back to cwd");
+ if (!is_git_directory(".")) {
+ if (nongit_ok) {
+ *nongit_ok = 1;
+ return NULL;
+ }
+ die("Not a git repository");
+ }
+ setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
+ gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
+ if (!gitdirenv)
+ die("getenv after setenv failed");
+ }
+
+ if (PATH_MAX - 40 < strlen(gitdirenv)) {
+ if (nongit_ok) {
+ *nongit_ok = 1;
return NULL;
+ }
+ die("$%s too big", GIT_DIR_ENVIRONMENT);
+ }
+ if (!is_git_directory(gitdirenv)) {
if (nongit_ok) {
*nongit_ok = 1;
return NULL;
@@ -218,41 +273,92 @@ const char *setup_git_directory_gently(int *nongit_ok)
if (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/')
die("Unable to read current working directory");
+ if (chdir(gitdirenv)) {
+ if (nongit_ok) {
+ *nongit_ok = 1;
+ return NULL;
+ }
+ die("Cannot change directory to $%s '%s'",
+ GIT_DIR_ENVIRONMENT, gitdirenv);
+ }
+ if (!getcwd(gitdir, sizeof(gitdir)-1) || gitdir[0] != '/')
+ die("Unable to read current working directory");
+ if (chdir(cwd))
+ die("Cannot come back to cwd");
- offset = len = strlen(cwd);
- for (;;) {
- if (is_git_directory(".git"))
- break;
- chdir("..");
- do {
- if (!offset) {
- if (is_git_directory(cwd)) {
- if (chdir(cwd))
- die("Cannot come back to cwd");
- setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
- inside_git_dir = 1;
- return NULL;
- }
- if (nongit_ok) {
- if (chdir(cwd))
- die("Cannot come back to cwd");
- *nongit_ok = 1;
- return NULL;
- }
- die("Not a git repository");
+ /*
+ * In case there is a work tree we may change the directory,
+ * therefore make GIT_DIR an absolute path.
+ */
+ if (gitdirenv[0] != '/') {
+ setenv(GIT_DIR_ENVIRONMENT, gitdir, 1);
+ gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
+ if (!gitdirenv)
+ die("getenv after setenv failed");
+ if (PATH_MAX - 40 < strlen(gitdirenv)) {
+ if (nongit_ok) {
+ *nongit_ok = 1;
+ return NULL;
}
- } while (cwd[--offset] != '/');
+ die("$%s too big after expansion to absolute path",
+ GIT_DIR_ENVIRONMENT);
+ }
+ }
+
+ strcat(cwd, "/");
+ strcat(gitdir, "/");
+ inside_git_dir = !prefixcmp(cwd, gitdir);
+
+ gitworktree = getenv(GIT_WORK_TREE_ENVIRONMENT);
+ if (!gitworktree) {
+ gitworktree_config = worktree;
+ worktree[0] = '\0';
+ }
+ git_config(git_setup_config);
+ if (!gitworktree) {
+ gitworktree_config = NULL;
+ if (worktree[0])
+ gitworktree = worktree;
+ if (gitworktree && gitworktree[0] != '/')
+ wt_rel_gitdir = 1;
+ }
+
+ if (wt_rel_gitdir && chdir(gitdirenv))
+ die("Cannot change directory to $%s '%s'",
+ GIT_DIR_ENVIRONMENT, gitdirenv);
+ if (gitworktree && chdir(gitworktree)) {
+ if (nongit_ok) {
+ if (wt_rel_gitdir && chdir(cwd))
+ die("Cannot come back to cwd");
+ *nongit_ok = 1;
+ return NULL;
+ }
+ if (wt_rel_gitdir)
+ die("Cannot change directory to working tree '%s'"
+ " from $%s", gitworktree, GIT_DIR_ENVIRONMENT);
+ else
+ die("Cannot change directory to working tree '%s'",
+ gitworktree);
}
+ if (!getcwd(worktree, sizeof(worktree)-1) || worktree[0] != '/')
+ die("Unable to read current working directory");
+ strcat(worktree, "/");
+ inside_work_tree = !prefixcmp(cwd, worktree);
- if (offset == len)
+ if (gitworktree && inside_work_tree && !prefixcmp(worktree, gitdir) &&
+ strcmp(worktree, gitdir)) {
+ inside_git_dir = 0;
+ }
+
+ if (!inside_work_tree) {
+ if (chdir(cwd))
+ die("Cannot come back to cwd");
return NULL;
+ }
- /* Make "offset" point to past the '/', and add a '/' at the end */
- offset++;
- cwd[len++] = '/';
- cwd[len] = 0;
- inside_git_dir = !prefixcmp(cwd + offset, ".git/");
- return cwd + offset;
+ if (!strcmp(cwd, worktree))
+ return NULL;
+ return cwd+strlen(worktree);
}
int git_config_perm(const char *var, const char *value)
diff --git a/sha1_file.c b/sha1_file.c
index 7628ee97d..f2b1ae032 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -510,7 +510,10 @@ static int check_packed_git_idx(const char *path, struct packed_git *p)
* for offsets larger than 2^31.
*/
unsigned long min_size = 8 + 4*256 + nr*(20 + 4 + 4) + 20 + 20;
- if (idx_size < min_size || idx_size > min_size + (nr - 1)*8) {
+ unsigned long max_size = min_size;
+ if (nr)
+ max_size += (nr - 1)*8;
+ if (idx_size < min_size || idx_size > max_size) {
munmap(idx_map, idx_size);
return error("wrong index file size in %s", path);
}
diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh
new file mode 100755
index 000000000..f4294d72d
--- /dev/null
+++ b/t/t0030-stripspace.sh
@@ -0,0 +1,355 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Carlos Rica
+#
+
+test_description='git-stripspace'
+
+. ./test-lib.sh
+
+t40='A quick brown fox jumps over the lazy do'
+s40=' '
+sss="$s40$s40$s40$s40$s40$s40$s40$s40$s40$s40" # 400
+ttt="$t40$t40$t40$t40$t40$t40$t40$t40$t40$t40" # 400
+
+test_expect_success \
+ 'long lines without spaces should be unchanged' '
+ echo "$ttt" >expect &&
+ git-stripspace <expect >actual &&
+ git diff expect actual &&
+
+ echo "$ttt$ttt" >expect &&
+ git-stripspace <expect >actual &&
+ git diff expect actual &&
+
+ echo "$ttt$ttt$ttt" >expect &&
+ git-stripspace <expect >actual &&
+ git diff expect actual &&
+
+ echo "$ttt$ttt$ttt$ttt" >expect &&
+ git-stripspace <expect >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'lines with spaces at the beginning should be unchanged' '
+ echo "$sss$ttt" >expect &&
+ git-stripspace <expect >actual &&
+ git diff expect actual &&
+
+ echo "$sss$sss$ttt" >expect &&
+ git-stripspace <expect >actual &&
+ git diff expect actual &&
+
+ echo "$sss$sss$sss$ttt" >expect &&
+ git-stripspace <expect >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'lines with intermediate spaces should be unchanged' '
+ echo "$ttt$sss$ttt" >expect &&
+ git-stripspace <expect >actual &&
+ git diff expect actual &&
+
+ echo "$ttt$sss$sss$ttt" >expect &&
+ git-stripspace <expect >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'consecutive blank lines should be unified' '
+ printf "$ttt\n\n$ttt\n" > expect &&
+ printf "$ttt\n\n\n\n\n$ttt\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt\n\n$ttt\n" > expect &&
+ printf "$ttt$ttt\n\n\n\n\n$ttt\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt$ttt\n\n$ttt\n" > expect &&
+ printf "$ttt$ttt$ttt\n\n\n\n\n$ttt\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n\n$ttt\n" > expect &&
+ printf "$ttt\n\n\n\n\n$ttt\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n\n$ttt$ttt\n" > expect &&
+ printf "$ttt\n\n\n\n\n$ttt$ttt\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n\n$ttt$ttt$ttt\n" > expect &&
+ printf "$ttt\n\n\n\n\n$ttt$ttt$ttt\n" | git-stripspace >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'consecutive blank lines at the beginning should be removed' '
+ printf "" > expect &&
+ printf "\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "" > expect &&
+ printf "\n\n\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "" > expect &&
+ printf "$sss\n$sss\n$sss\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "" > expect &&
+ printf "$sss$sss\n$sss\n\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "" > expect &&
+ printf "\n$sss\n$sss$sss\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "" > expect &&
+ printf "$sss$sss$sss$sss\n\n\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "" > expect &&
+ printf "\n$sss$sss$sss$sss\n\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "" > expect &&
+ printf "\n\n$sss$sss$sss$sss\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "\n$ttt\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "\n\n\n$ttt\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt\n" > expect &&
+ printf "\n\n\n$ttt$ttt\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt$ttt\n" > expect &&
+ printf "\n\n\n$ttt$ttt$ttt\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt$ttt$ttt\n" > expect &&
+ printf "\n\n\n$ttt$ttt$ttt$ttt\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "$sss\n$sss\n$sss\n$ttt\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "\n$sss\n$sss$sss\n$ttt\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "$sss$sss\n$sss\n\n$ttt\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "$sss$sss$sss\n\n\n$ttt\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "\n$sss$sss$sss\n\n$ttt\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "\n\n$sss$sss$sss\n$ttt\n" | git-stripspace >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'consecutive blank lines at the end should be removed' '
+ printf "$ttt\n" > expect &&
+ printf "$ttt\n\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "$ttt\n\n\n\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt\n" > expect &&
+ printf "$ttt$ttt\n\n\n\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt$ttt\n" > expect &&
+ printf "$ttt$ttt$ttt\n\n\n\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt$ttt$ttt\n" > expect &&
+ printf "$ttt$ttt$ttt$ttt\n\n\n\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "$ttt\n$sss\n$sss\n$sss\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "$ttt\n\n$sss\n$sss$sss\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "$ttt\n$sss$sss\n$sss\n\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "$ttt\n$sss$sss$sss\n\n\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "$ttt\n\n$sss$sss$sss\n\n" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" > expect &&
+ printf "$ttt\n\n\n$sss$sss$sss\n" | git-stripspace >actual &&
+ git diff expect actual
+'
+
+test_expect_success \
+ 'text without newline at end should end with newline' '
+ test `printf "$ttt" | git-stripspace | wc -l` -gt 0 &&
+ test `printf "$ttt$ttt" | git-stripspace | wc -l` -gt 0 &&
+ test `printf "$ttt$ttt$ttt" | git-stripspace | wc -l` -gt 0 &&
+ test `printf "$ttt$ttt$ttt$ttt" | git-stripspace | wc -l` -gt 0
+'
+
+# text plus spaces at the end:
+
+test_expect_success \
+ 'text plus spaces without newline at end should end with newline' '
+ test `printf "$ttt$sss" | git-stripspace | wc -l` -gt 0 &&
+ test `printf "$ttt$ttt$sss" | git-stripspace | wc -l` -gt 0 &&
+ test `printf "$ttt$ttt$ttt$sss" | git-stripspace | wc -l` -gt 0
+ test `printf "$ttt$sss$sss" | git-stripspace | wc -l` -gt 0 &&
+ test `printf "$ttt$ttt$sss$sss" | git-stripspace | wc -l` -gt 0 &&
+ test `printf "$ttt$sss$sss$sss" | git-stripspace | wc -l` -gt 0
+'
+
+test_expect_failure \
+ 'text plus spaces without newline at end should not show spaces' '
+ printf "$ttt$sss" | git-stripspace | grep -q " " ||
+ printf "$ttt$ttt$sss" | git-stripspace | grep -q " " ||
+ printf "$ttt$ttt$ttt$sss" | git-stripspace | grep -q " " ||
+ printf "$ttt$sss$sss" | git-stripspace | grep -q " " ||
+ printf "$ttt$ttt$sss$sss" | git-stripspace | grep -q " " ||
+ printf "$ttt$sss$sss$sss" | git-stripspace | grep -q " "
+'
+
+test_expect_success \
+ 'text plus spaces without newline should show the correct lines' '
+ printf "$ttt\n" >expect &&
+ printf "$ttt$sss" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" >expect &&
+ printf "$ttt$sss$sss" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt\n" >expect &&
+ printf "$ttt$sss$sss$sss" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt\n" >expect &&
+ printf "$ttt$ttt$sss" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt\n" >expect &&
+ printf "$ttt$ttt$sss$sss" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ printf "$ttt$ttt$ttt\n" >expect &&
+ printf "$ttt$ttt$ttt$sss" | git-stripspace >actual &&
+ git diff expect actual
+'
+
+test_expect_failure \
+ 'text plus spaces at end should not show spaces' '
+ echo "$ttt$sss" | git-stripspace | grep -q " " ||
+ echo "$ttt$ttt$sss" | git-stripspace | grep -q " " ||
+ echo "$ttt$ttt$ttt$sss" | git-stripspace | grep -q " " ||
+ echo "$ttt$sss$sss" | git-stripspace | grep -q " " ||
+ echo "$ttt$ttt$sss$sss" | git-stripspace | grep -q " " ||
+ echo "$ttt$sss$sss$sss" | git-stripspace | grep -q " "
+'
+
+test_expect_success \
+ 'text plus spaces at end should be cleaned and newline must remain' '
+ echo "$ttt" >expect &&
+ echo "$ttt$sss" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$ttt" >expect &&
+ echo "$ttt$sss$sss" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$ttt" >expect &&
+ echo "$ttt$sss$sss$sss" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$ttt$ttt" >expect &&
+ echo "$ttt$ttt$sss" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$ttt$ttt" >expect &&
+ echo "$ttt$ttt$sss$sss" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$ttt$ttt$ttt" >expect &&
+ echo "$ttt$ttt$ttt$sss" | git-stripspace >actual &&
+ git diff expect actual
+'
+
+# spaces only:
+
+test_expect_success \
+ 'spaces with newline at end should be replaced with empty string' '
+ printf "" >expect &&
+
+ echo | git-stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$sss" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$sss$sss" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$sss$sss$sss" | git-stripspace >actual &&
+ git diff expect actual &&
+
+ echo "$sss$sss$sss$sss" | git-stripspace >actual &&
+ git diff expect actual
+'
+
+test_expect_failure \
+ 'spaces without newline at end should not show spaces' '
+ printf "" | git-stripspace | grep -q " " ||
+ printf "$sss" | git-stripspace | grep -q " " ||
+ printf "$sss$sss" | git-stripspace | grep -q " " ||
+ printf "$sss$sss$sss" | git-stripspace | grep -q " " ||
+ printf "$sss$sss$sss$sss" | git-stripspace | grep -q " "
+'
+
+test_expect_success \
+ 'spaces without newline at end should be replaced with empty string' '
+ printf "" >expect &&
+
+ printf "" | git-stripspace >actual &&
+ git diff expect actual
+
+ printf "$sss$sss" | git-stripspace >actual &&
+ git diff expect actual
+
+ printf "$sss$sss$sss" | git-stripspace >actual &&
+ git diff expect actual
+
+ printf "$sss$sss$sss$sss" | git-stripspace >actual &&
+ git diff expect actual
+'
+
+test_done
diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh
index 7731fa72c..a2c11c463 100755
--- a/t/t1300-repo-config.sh
+++ b/t/t1300-repo-config.sh
@@ -283,6 +283,12 @@ EOF
test_expect_success 'get variable with no value' \
'git-config --get novalue.variable ^$'
+echo novalue.variable > expect
+
+test_expect_success 'get-regexp variable with no value' \
+ 'git-config --get-regexp novalue > output &&
+ cmp output expect'
+
git-config > output 2>&1
test_expect_success 'no arguments, but no crash' \
@@ -465,11 +471,57 @@ test_expect_success bool '
done &&
cmp expect result'
-test_expect_failure 'invalid bool' '
+test_expect_failure 'invalid bool (--get)' '
git-config bool.nobool foobar &&
git-config --bool --get bool.nobool'
+test_expect_failure 'invalid bool (set)' '
+
+ git-config --bool bool.nobool foobar'
+
+rm .git/config
+
+cat > expect <<\EOF
+[bool]
+ true1 = true
+ true2 = true
+ true3 = true
+ true4 = true
+ false1 = false
+ false2 = false
+ false3 = false
+ false4 = false
+EOF
+
+test_expect_success 'set --bool' '
+
+ git-config --bool bool.true1 01 &&
+ git-config --bool bool.true2 -1 &&
+ git-config --bool bool.true3 YeS &&
+ git-config --bool bool.true4 true &&
+ git-config --bool bool.false1 000 &&
+ git-config --bool bool.false2 "" &&
+ git-config --bool bool.false3 nO &&
+ git-config --bool bool.false4 FALSE &&
+ cmp expect .git/config'
+
+rm .git/config
+
+cat > expect <<\EOF
+[int]
+ val1 = 1
+ val2 = -1
+ val3 = 5242880
+EOF
+
+test_expect_success 'set --int' '
+
+ git-config --int int.val1 01 &&
+ git-config --int int.val2 -1 &&
+ git-config --int int.val3 5m &&
+ cmp expect .git/config'
+
rm .git/config
git-config quote.leading " test"
@@ -513,4 +565,34 @@ git config --list > result
test_expect_success 'value continued on next line' 'cmp result expect'
+cat > .git/config <<\EOF
+[section "sub=section"]
+ val1 = foo=bar
+ val2 = foo\nbar
+ val3 = \n\n
+ val4 =
+ val5
+EOF
+
+cat > expect <<\EOF
+section.sub=section.val1
+foo=barQsection.sub=section.val2
+foo
+barQsection.sub=section.val3
+
+
+Qsection.sub=section.val4
+Qsection.sub=section.val5Q
+EOF
+
+git config --null --list | tr '[\000]' 'Q' > result
+echo >>result
+
+test_expect_success '--null --list' 'cmp result expect'
+
+git config --null --get-regexp 'val[0-9]' | tr '[\000]' 'Q' > result
+echo >>result
+
+test_expect_success '--null --get-regexp' 'cmp result expect'
+
test_done
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
new file mode 100755
index 000000000..ec4996637
--- /dev/null
+++ b/t/t1500-rev-parse.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+test_description='test git rev-parse'
+. ./test-lib.sh
+
+test_rev_parse() {
+ name=$1
+ shift
+
+ test_expect_success "$name: is-bare-repository" \
+ "test '$1' = \"\$(git rev-parse --is-bare-repository)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: is-inside-git-dir" \
+ "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: is-inside-work-tree" \
+ "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: prefix" \
+ "test '$1' = \"\$(git rev-parse --show-prefix)\""
+ shift
+ [ $# -eq 0 ] && return
+}
+
+test_rev_parse toplevel false false true ''
+
+cd .git || exit 1
+test_rev_parse .git/ false true true .git/
+cd objects || exit 1
+test_rev_parse .git/objects/ false true true .git/objects/
+cd ../.. || exit 1
+
+mkdir -p sub/dir || exit 1
+cd sub/dir || exit 1
+test_rev_parse subdirectory false false true sub/dir/
+cd ../.. || exit 1
+
+git config core.bare true
+test_rev_parse 'core.bare = true' true false true
+
+git config --unset core.bare
+test_rev_parse 'core.bare undefined' false false true
+
+mkdir work || exit 1
+cd work || exit 1
+export GIT_DIR=../.git
+export GIT_CONFIG="$GIT_DIR"/config
+
+git config core.bare false
+test_rev_parse 'GIT_DIR=../.git, core.bare = false' false false true ''
+
+git config core.bare true
+test_rev_parse 'GIT_DIR=../.git, core.bare = true' true false true ''
+
+git config --unset core.bare
+test_rev_parse 'GIT_DIR=../.git, core.bare undefined' false false true ''
+
+mv ../.git ../repo.git || exit 1
+export GIT_DIR=../repo.git
+export GIT_CONFIG="$GIT_DIR"/config
+
+git config core.bare false
+test_rev_parse 'GIT_DIR=../repo.git, core.bare = false' false false true ''
+
+git config core.bare true
+test_rev_parse 'GIT_DIR=../repo.git, core.bare = true' true false true ''
+
+git config --unset core.bare
+test_rev_parse 'GIT_DIR=../repo.git, core.bare undefined' true false true ''
+
+test_done
diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
new file mode 100755
index 000000000..aadeeab9a
--- /dev/null
+++ b/t/t1501-worktree.sh
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+test_description='test separate work tree'
+. ./test-lib.sh
+
+test_rev_parse() {
+ name=$1
+ shift
+
+ test_expect_success "$name: is-bare-repository" \
+ "test '$1' = \"\$(git rev-parse --is-bare-repository)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: is-inside-git-dir" \
+ "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: is-inside-work-tree" \
+ "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\""
+ shift
+ [ $# -eq 0 ] && return
+
+ test_expect_success "$name: prefix" \
+ "test '$1' = \"\$(git rev-parse --show-prefix)\""
+ shift
+ [ $# -eq 0 ] && return
+}
+
+mkdir -p work/sub/dir || exit 1
+mv .git repo.git || exit 1
+
+say "core.worktree = relative path"
+export GIT_DIR=repo.git
+export GIT_CONFIG=$GIT_DIR/config
+unset GIT_WORK_TREE
+git config core.worktree ../work
+test_rev_parse 'outside' false false false
+cd work || exit 1
+export GIT_DIR=../repo.git
+export GIT_CONFIG=$GIT_DIR/config
+test_rev_parse 'inside' false false true ''
+cd sub/dir || exit 1
+export GIT_DIR=../../../repo.git
+export GIT_CONFIG=$GIT_DIR/config
+test_rev_parse 'subdirectory' false false true sub/dir/
+cd ../../.. || exit 1
+
+say "core.worktree = absolute path"
+export GIT_DIR=$(pwd)/repo.git
+export GIT_CONFIG=$GIT_DIR/config
+git config core.worktree "$(pwd)/work"
+test_rev_parse 'outside' false false false
+cd work || exit 1
+test_rev_parse 'inside' false false true ''
+cd sub/dir || exit 1
+test_rev_parse 'subdirectory' false false true sub/dir/
+cd ../../.. || exit 1
+
+say "GIT_WORK_TREE=relative path (override core.worktree)"
+export GIT_DIR=$(pwd)/repo.git
+export GIT_CONFIG=$GIT_DIR/config
+git config core.worktree non-existent
+export GIT_WORK_TREE=work
+test_rev_parse 'outside' false false false
+cd work || exit 1
+export GIT_WORK_TREE=.
+test_rev_parse 'inside' false false true ''
+cd sub/dir || exit 1
+export GIT_WORK_TREE=../..
+test_rev_parse 'subdirectory' false false true sub/dir/
+cd ../../.. || exit 1
+
+mv work repo.git/work
+
+say "GIT_WORK_TREE=absolute path, work tree below git dir"
+export GIT_DIR=$(pwd)/repo.git
+export GIT_CONFIG=$GIT_DIR/config
+export GIT_WORK_TREE=$(pwd)/repo.git/work
+test_rev_parse 'outside' false false false
+cd repo.git || exit 1
+test_rev_parse 'in repo.git' false true false
+cd objects || exit 1
+test_rev_parse 'in repo.git/objects' false true false
+cd ../work || exit 1
+test_rev_parse 'in repo.git/work' false false true ''
+cd sub/dir || exit 1
+test_rev_parse 'in repo.git/sub/dir' false false true sub/dir/
+cd ../../../.. || exit 1
+
+test_done
diff --git a/t/t3700-add.sh b/t/t3700-add.sh
index ad8cc7d4a..0d80c6aea 100755
--- a/t/t3700-add.sh
+++ b/t/t3700-add.sh
@@ -110,4 +110,30 @@ test_expect_success 'check correct prefix detection' '
git add 1/2/a 1/3/b 1/2/c
'
+test_expect_success 'git add and filemode=0 with unmerged entries' '
+ echo 1 > stage1 &&
+ echo 2 > stage2 &&
+ echo 3 > stage3 &&
+ for s in 1 2 3
+ do
+ echo "100755 $(git hash-object -w stage$s) $s file"
+ done | git update-index --index-info &&
+ git config core.filemode 0 &&
+ echo new > file &&
+ git add file &&
+ git ls-files --stage | grep "^100755 .* 0 file$"
+'
+
+test_expect_success 'git add and filemode=0 prefers stage 2 over stage 1' '
+ git rm --cached -f file &&
+ (
+ echo "100644 $(git hash-object -w stage1) 1 file"
+ echo "100755 $(git hash-object -w stage2) 2 file"
+ ) | git update-index --index-info &&
+ git config core.filemode 0 &&
+ echo new > file &&
+ git add file &&
+ git ls-files --stage | grep "^100755 .* 0 file$"
+'
+
test_done
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 08d58e1c8..c0fa2ba40 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -226,7 +226,7 @@ test_expect_success 'push with colon-less refspec (3)' '
git branch -f frotz master &&
git push testrepo frotz &&
check_push_result $the_commit heads/frotz &&
- test "$( cd testrepo && git show-ref | wc -l )" = 1
+ test 1 = $( cd testrepo && git show-ref | wc -l )
'
test_expect_success 'push with colon-less refspec (4)' '
@@ -239,7 +239,7 @@ test_expect_success 'push with colon-less refspec (4)' '
git tag -f frotz &&
git push testrepo frotz &&
check_push_result $the_commit tags/frotz &&
- test "$( cd testrepo && git show-ref | wc -l )" = 1
+ test 1 = $( cd testrepo && git show-ref | wc -l )
'
diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh
index b15920b85..22e089305 100755
--- a/t/t6010-merge-base.sh
+++ b/t/t6010-merge-base.sh
@@ -34,6 +34,12 @@ doit() {
echo $commit
}
+# E---D---C---B---A
+# \'-_ \ \
+# \ `---------G \
+# \ \
+# F----------------H
+
# Setup...
E=$(doit 5 E)
D=$(doit 4 D $E)
@@ -44,6 +50,18 @@ A=$(doit 1 A $B)
G=$(doit 7 G $B $E)
H=$(doit 8 H $A $F)
+test_expect_success 'compute merge-base (single)' \
+ 'MB=$(git-merge-base G H) &&
+ expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
+
+test_expect_success 'compute merge-base (all)' \
+ 'MB=$(git-merge-base --all G H) &&
+ expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
+
+test_expect_success 'compute merge-base with show-branch' \
+ 'MB=$(git-show-branch --merge-base G H) &&
+ expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
+
# Setup for second test to demonstrate that relying on timestamps in a
# distributed SCM to provide a _consistent_ partial ordering of commits
# leads to insanity.
@@ -82,18 +100,6 @@ PL=$(doit 4 PL $L2 $C2)
PR=$(doit 4 PR $C2 $R2)
test_expect_success 'compute merge-base (single)' \
- 'MB=$(git-merge-base G H) &&
- expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
-
-test_expect_success 'compute merge-base (all)' \
- 'MB=$(git-merge-base --all G H) &&
- expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
-
-test_expect_success 'compute merge-base with show-branch' \
- 'MB=$(git-show-branch --merge-base G H) &&
- expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
-
-test_expect_success 'compute merge-base (single)' \
'MB=$(git-merge-base PL PR) &&
expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/C2"'
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
new file mode 100755
index 000000000..5d15449be
--- /dev/null
+++ b/t/t7004-tag.sh
@@ -0,0 +1,686 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Carlos Rica
+#
+
+test_description='git-tag
+
+Basic tests for operations with tags.'
+
+. ./test-lib.sh
+
+# creating and listing lightweight tags:
+
+tag_exists () {
+ git show-ref --quiet --verify refs/tags/"$1"
+}
+
+# todo: git tag -l now returns always zero, when fixed, change this test
+test_expect_success 'listing all tags in an empty tree should succeed' \
+ 'git tag -l'
+
+test_expect_success 'listing all tags in an empty tree should output nothing' \
+ 'test `git-tag -l | wc -l` -eq 0'
+
+test_expect_failure 'looking for a tag in an empty tree should fail' \
+ 'tag_exists mytag'
+
+test_expect_success 'creating a tag in an empty tree should fail' '
+ ! git-tag mynotag &&
+ ! tag_exists mynotag
+'
+
+test_expect_success 'creating a tag for HEAD in an empty tree should fail' '
+ ! git-tag mytaghead HEAD &&
+ ! tag_exists mytaghead
+'
+
+test_expect_success 'creating a tag for an unknown revision should fail' '
+ ! git-tag mytagnorev aaaaaaaaaaa &&
+ ! tag_exists mytagnorev
+'
+
+# commit used in the tests, test_tick is also called here to freeze the date:
+test_expect_success 'creating a tag using default HEAD should succeed' '
+ test_tick &&
+ echo foo >foo &&
+ git add foo &&
+ git commit -m Foo &&
+ git tag mytag
+'
+
+test_expect_success 'listing all tags if one exists should succeed' \
+ 'git-tag -l'
+
+test_expect_success 'listing all tags if one exists should output that tag' \
+ 'test `git-tag -l` = mytag'
+
+# pattern matching:
+
+test_expect_success 'listing a tag using a matching pattern should succeed' \
+ 'git-tag -l mytag'
+
+test_expect_success \
+ 'listing a tag using a matching pattern should output that tag' \
+ 'test `git-tag -l mytag` = mytag'
+
+# todo: git tag -l now returns always zero, when fixed, change this test
+test_expect_success \
+ 'listing tags using a non-matching pattern should suceed' \
+ 'git-tag -l xxx'
+
+test_expect_success \
+ 'listing tags using a non-matching pattern should output nothing' \
+ 'test `git-tag -l xxx | wc -l` -eq 0'
+
+# special cases for creating tags:
+
+test_expect_failure \
+ 'trying to create a tag with the name of one existing should fail' \
+ 'git tag mytag'
+
+test_expect_success \
+ 'trying to create a tag with a non-valid name should fail' '
+ test `git-tag -l | wc -l` -eq 1 &&
+ ! git tag "" &&
+ ! git tag .othertag &&
+ ! git tag "other tag" &&
+ ! git tag "othertag^" &&
+ ! git tag "other~tag" &&
+ test `git-tag -l | wc -l` -eq 1
+'
+
+test_expect_success 'creating a tag using HEAD directly should succeed' '
+ git tag myhead HEAD &&
+ tag_exists myhead
+'
+
+# deleting tags:
+
+test_expect_success 'trying to delete an unknown tag should fail' '
+ ! tag_exists unknown-tag &&
+ ! git-tag -d unknown-tag
+'
+
+cat >expect <<EOF
+myhead
+mytag
+EOF
+test_expect_success \
+ 'trying to delete tags without params should succeed and do nothing' '
+ git tag -l > actual && git diff expect actual &&
+ git-tag -d &&
+ git tag -l > actual && git diff expect actual
+'
+
+test_expect_success \
+ 'deleting two existing tags in one command should succeed' '
+ tag_exists mytag &&
+ tag_exists myhead &&
+ git-tag -d mytag myhead &&
+ ! tag_exists mytag &&
+ ! tag_exists myhead
+'
+
+test_expect_success \
+ 'creating a tag with the name of another deleted one should succeed' '
+ ! tag_exists mytag &&
+ git-tag mytag &&
+ tag_exists mytag
+'
+
+test_expect_success \
+ 'trying to delete two tags, existing and not, should fail in the 2nd' '
+ tag_exists mytag &&
+ ! tag_exists myhead &&
+ ! git-tag -d mytag anothertag &&
+ ! tag_exists mytag &&
+ ! tag_exists myhead
+'
+
+test_expect_failure 'trying to delete an already deleted tag should fail' \
+ 'git-tag -d mytag'
+
+# listing various tags with pattern matching:
+
+cat >expect <<EOF
+a1
+aa1
+cba
+t210
+t211
+v0.2.1
+v1.0
+v1.0.1
+v1.1.3
+EOF
+test_expect_success 'listing all tags should print them ordered' '
+ git tag v1.0.1 &&
+ git tag t211 &&
+ git tag aa1 &&
+ git tag v0.2.1 &&
+ git tag v1.1.3 &&
+ git tag cba &&
+ git tag a1 &&
+ git tag v1.0 &&
+ git tag t210 &&
+ git tag -l > actual
+ git diff expect actual
+'
+
+cat >expect <<EOF
+a1
+aa1
+cba
+EOF
+test_expect_success \
+ 'listing tags with substring as pattern must print those matching' '
+ git-tag -l a > actual &&
+ git-diff expect actual
+'
+
+cat >expect <<EOF
+v0.2.1
+v1.0.1
+v1.1.3
+EOF
+test_expect_success \
+ 'listing tags with substring as pattern must print those matching' '
+ git-tag -l .1 > actual &&
+ git-diff expect actual
+'
+
+cat >expect <<EOF
+t210
+t211
+EOF
+test_expect_success \
+ 'listing tags with substring as pattern must print those matching' '
+ git-tag -l t21 > actual &&
+ git-diff expect actual
+'
+
+cat >expect <<EOF
+a1
+aa1
+EOF
+test_expect_success \
+ 'listing tags using a name as pattern must print those matching' '
+ git-tag -l a1 > actual &&
+ git-diff expect actual
+'
+
+cat >expect <<EOF
+v1.0
+v1.0.1
+EOF
+test_expect_success \
+ 'listing tags using a name as pattern must print those matching' '
+ git-tag -l v1.0 > actual &&
+ git-diff expect actual
+'
+
+cat >expect <<EOF
+v1.1.3
+EOF
+test_expect_success \
+ 'listing tags with ? in the pattern should print those matching' '
+ git-tag -l "1.1?" > actual &&
+ git-diff expect actual
+'
+
+>expect
+test_expect_success \
+ 'listing tags using v.* should print nothing because none have v.' '
+ git-tag -l "v.*" > actual &&
+ git-diff expect actual
+'
+
+cat >expect <<EOF
+v0.2.1
+v1.0
+v1.0.1
+v1.1.3
+EOF
+test_expect_success \
+ 'listing tags using v* should print only those having v' '
+ git-tag -l "v*" > actual &&
+ git-diff expect actual
+'
+
+# creating and verifying lightweight tags:
+
+test_expect_success \
+ 'a non-annotated tag created without parameters should point to HEAD' '
+ git-tag non-annotated-tag &&
+ test $(git-cat-file -t non-annotated-tag) = commit &&
+ test $(git-rev-parse non-annotated-tag) = $(git-rev-parse HEAD)
+'
+
+test_expect_failure 'trying to verify an unknown tag should fail' \
+ 'git-tag -v unknown-tag'
+
+test_expect_failure \
+ 'trying to verify a non-annotated and non-signed tag should fail' \
+ 'git-tag -v non-annotated-tag'
+
+# creating annotated tags:
+
+get_tag_msg () {
+ git cat-file tag "$1" | sed -e "/BEGIN PGP/q"
+}
+
+# run test_tick before committing always gives the time in that timezone
+get_tag_header () {
+cat <<EOF
+object $2
+type $3
+tag $1
+tagger C O Mitter <committer@example.com> $4 -0700
+
+EOF
+}
+
+commit=$(git rev-parse HEAD)
+time=$test_tick
+
+get_tag_header annotated-tag $commit commit $time >expect
+echo "A message" >>expect
+test_expect_success \
+ 'creating an annotated tag with -m message should succeed' '
+ git-tag -m "A message" annotated-tag &&
+ get_tag_msg annotated-tag >actual &&
+ git diff expect actual
+'
+
+cat >msgfile <<EOF
+Another message
+in a file.
+EOF
+get_tag_header file-annotated-tag $commit commit $time >expect
+cat msgfile >>expect
+test_expect_success \
+ 'creating an annotated tag with -F messagefile should succeed' '
+ git-tag -F msgfile file-annotated-tag &&
+ get_tag_msg file-annotated-tag >actual &&
+ git diff expect actual
+'
+
+# blank and empty messages:
+
+get_tag_header empty-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with an empty -m message should succeed' '
+ git-tag -m "" empty-annotated-tag &&
+ get_tag_msg empty-annotated-tag >actual &&
+ git diff expect actual
+'
+
+>emptyfile
+get_tag_header emptyfile-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with an empty -F messagefile should succeed' '
+ git-tag -F emptyfile emptyfile-annotated-tag &&
+ get_tag_msg emptyfile-annotated-tag >actual &&
+ git diff expect actual
+'
+
+printf '\n\n \n\t\nLeading blank lines\n' >blanksfile
+printf '\n\t \t \nRepeated blank lines\n' >>blanksfile
+printf '\n\n\nTrailing spaces \t \n' >>blanksfile
+printf '\nTrailing blank lines\n\n\t \n\n' >>blanksfile
+get_tag_header blanks-annotated-tag $commit commit $time >expect
+cat >>expect <<EOF
+Leading blank lines
+
+Repeated blank lines
+
+Trailing spaces
+
+Trailing blank lines
+EOF
+test_expect_success \
+ 'extra blanks in the message for an annotated tag should be removed' '
+ git-tag -F blanksfile blanks-annotated-tag &&
+ get_tag_msg blanks-annotated-tag >actual &&
+ git diff expect actual
+'
+
+get_tag_header blank-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with blank -m message with spaces should succeed' '
+ git-tag -m " " blank-annotated-tag &&
+ get_tag_msg blank-annotated-tag >actual &&
+ git diff expect actual
+'
+
+echo ' ' >blankfile
+echo '' >>blankfile
+echo ' ' >>blankfile
+get_tag_header blankfile-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with blank -F messagefile with spaces should succeed' '
+ git-tag -F blankfile blankfile-annotated-tag &&
+ get_tag_msg blankfile-annotated-tag >actual &&
+ git diff expect actual
+'
+
+printf ' ' >blanknonlfile
+get_tag_header blanknonlfile-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with -F file of spaces and no newline should succeed' '
+ git-tag -F blanknonlfile blanknonlfile-annotated-tag &&
+ get_tag_msg blanknonlfile-annotated-tag >actual &&
+ git diff expect actual
+'
+
+# messages with commented lines:
+
+cat >commentsfile <<EOF
+# A comment
+
+############
+The message.
+############
+One line.
+
+
+# commented lines
+# commented lines
+
+Another line.
+# comments
+
+Last line.
+EOF
+get_tag_header comments-annotated-tag $commit commit $time >expect
+cat >>expect <<EOF
+The message.
+One line.
+
+Another line.
+
+Last line.
+EOF
+test_expect_success \
+ 'creating a tag using a -F messagefile with #comments should succeed' '
+ git-tag -F commentsfile comments-annotated-tag &&
+ get_tag_msg comments-annotated-tag >actual &&
+ git diff expect actual
+'
+
+get_tag_header comment-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with a #comment in the -m message should succeed' '
+ git-tag -m "#comment" comment-annotated-tag &&
+ get_tag_msg comment-annotated-tag >actual &&
+ git diff expect actual
+'
+
+echo '#comment' >commentfile
+echo '' >>commentfile
+echo '####' >>commentfile
+get_tag_header commentfile-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with #comments in the -F messagefile should succeed' '
+ git-tag -F commentfile commentfile-annotated-tag &&
+ get_tag_msg commentfile-annotated-tag >actual &&
+ git diff expect actual
+'
+
+printf '#comment' >commentnonlfile
+get_tag_header commentnonlfile-annotated-tag $commit commit $time >expect
+test_expect_success \
+ 'creating a tag with a file of #comment and no newline should succeed' '
+ git-tag -F commentnonlfile commentnonlfile-annotated-tag &&
+ get_tag_msg commentnonlfile-annotated-tag >actual &&
+ git diff expect actual
+'
+
+# trying to verify annotated non-signed tags:
+
+test_expect_success \
+ 'trying to verify an annotated non-signed tag should fail' '
+ tag_exists annotated-tag &&
+ ! git-tag -v annotated-tag
+'
+
+test_expect_success \
+ 'trying to verify a file-annotated non-signed tag should fail' '
+ tag_exists file-annotated-tag &&
+ ! git-tag -v file-annotated-tag
+'
+
+# creating and verifying signed tags:
+
+gpg --version >/dev/null
+if [ $? -eq 127 ]; then
+ echo "Skipping signed tags tests, because gpg was not found"
+ test_done
+ exit
+fi
+
+# key generation info: gpg --homedir t/t7004 --gen-key
+# Type DSA and Elgamal, size 2048 bits, no expiration date.
+# Name and email: C O Mitter <committer@example.com>
+# No password given, to enable non-interactive operation.
+
+cp -R ../t7004 ./gpghome
+chmod 0700 gpghome
+export GNUPGHOME="$(pwd)/gpghome"
+
+get_tag_header signed-tag $commit commit $time >expect
+echo 'A signed tag message' >>expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success 'creating a signed tag with -m message should succeed' '
+ git-tag -s -m "A signed tag message" signed-tag &&
+ get_tag_msg signed-tag >actual &&
+ git-diff expect actual
+'
+
+test_expect_success 'verifying a signed tag should succeed' \
+ 'git-tag -v signed-tag'
+
+test_expect_success 'verifying a forged tag should fail' '
+ forged=$(git cat-file tag signed-tag |
+ sed -e "s/signed-tag/forged-tag/" |
+ git mktag) &&
+ git tag forged-tag $forged &&
+ ! git-tag -v forged-tag
+'
+
+# blank and empty messages for signed tags:
+
+get_tag_header empty-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with an empty -m message should succeed' '
+ git-tag -s -m "" empty-signed-tag &&
+ get_tag_msg empty-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v empty-signed-tag
+'
+
+>sigemptyfile
+get_tag_header emptyfile-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with an empty -F messagefile should succeed' '
+ git-tag -s -F sigemptyfile emptyfile-signed-tag &&
+ get_tag_msg emptyfile-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v emptyfile-signed-tag
+'
+
+printf '\n\n \n\t\nLeading blank lines\n' > sigblanksfile
+printf '\n\t \t \nRepeated blank lines\n' >>sigblanksfile
+printf '\n\n\nTrailing spaces \t \n' >>sigblanksfile
+printf '\nTrailing blank lines\n\n\t \n\n' >>sigblanksfile
+get_tag_header blanks-signed-tag $commit commit $time >expect
+cat >>expect <<EOF
+Leading blank lines
+
+Repeated blank lines
+
+Trailing spaces
+
+Trailing blank lines
+EOF
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'extra blanks in the message for a signed tag should be removed' '
+ git-tag -s -F sigblanksfile blanks-signed-tag &&
+ get_tag_msg blanks-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v blanks-signed-tag
+'
+
+get_tag_header blank-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with a blank -m message should succeed' '
+ git-tag -s -m " " blank-signed-tag &&
+ get_tag_msg blank-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v blank-signed-tag
+'
+
+echo ' ' >sigblankfile
+echo '' >>sigblankfile
+echo ' ' >>sigblankfile
+get_tag_header blankfile-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with blank -F file with spaces should succeed' '
+ git-tag -s -F sigblankfile blankfile-signed-tag &&
+ get_tag_msg blankfile-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v blankfile-signed-tag
+'
+
+printf ' ' >sigblanknonlfile
+get_tag_header blanknonlfile-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with spaces and no newline should succeed' '
+ git-tag -s -F sigblanknonlfile blanknonlfile-signed-tag &&
+ get_tag_msg blanknonlfile-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v signed-tag
+'
+
+# messages with commented lines for signed tags:
+
+cat >sigcommentsfile <<EOF
+# A comment
+
+############
+The message.
+############
+One line.
+
+
+# commented lines
+# commented lines
+
+Another line.
+# comments
+
+Last line.
+EOF
+get_tag_header comments-signed-tag $commit commit $time >expect
+cat >>expect <<EOF
+The message.
+One line.
+
+Another line.
+
+Last line.
+EOF
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with a -F file with #comments should succeed' '
+ git-tag -s -F sigcommentsfile comments-signed-tag &&
+ get_tag_msg comments-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v comments-signed-tag
+'
+
+get_tag_header comment-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with #commented -m message should succeed' '
+ git-tag -s -m "#comment" comment-signed-tag &&
+ get_tag_msg comment-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v comment-signed-tag
+'
+
+echo '#comment' >sigcommentfile
+echo '' >>sigcommentfile
+echo '####' >>sigcommentfile
+get_tag_header commentfile-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with #commented -F messagefile should succeed' '
+ git-tag -s -F sigcommentfile commentfile-signed-tag &&
+ get_tag_msg commentfile-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v commentfile-signed-tag
+'
+
+printf '#comment' >sigcommentnonlfile
+get_tag_header commentnonlfile-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag with a #comment and no newline should succeed' '
+ git-tag -s -F sigcommentnonlfile commentnonlfile-signed-tag &&
+ get_tag_msg commentnonlfile-signed-tag >actual &&
+ git diff expect actual &&
+ git-tag -v commentnonlfile-signed-tag
+'
+
+# tags pointing to objects different from commits:
+
+tree=$(git rev-parse HEAD^{tree})
+blob=$(git rev-parse HEAD:foo)
+tag=$(git rev-parse signed-tag)
+
+get_tag_header tree-signed-tag $tree tree $time >expect
+echo "A message for a tree" >>expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag pointing to a tree should succeed' '
+ git-tag -s -m "A message for a tree" tree-signed-tag HEAD^{tree} &&
+ get_tag_msg tree-signed-tag >actual &&
+ git diff expect actual
+'
+
+get_tag_header blob-signed-tag $blob blob $time >expect
+echo "A message for a blob" >>expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag pointing to a blob should succeed' '
+ git-tag -s -m "A message for a blob" blob-signed-tag HEAD:foo &&
+ get_tag_msg blob-signed-tag >actual &&
+ git diff expect actual
+'
+
+get_tag_header tag-signed-tag $tag tag $time >expect
+echo "A message for another tag" >>expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+ 'creating a signed tag pointing to another tag should succeed' '
+ git-tag -s -m "A message for another tag" tag-signed-tag signed-tag &&
+ get_tag_msg tag-signed-tag >actual &&
+ git diff expect actual
+'
+
+# try to verify without gpg:
+
+rm -rf gpghome
+test_expect_failure \
+ 'verify signed tag fails when public key is not present' \
+ 'git-tag -v signed-tag'
+
+test_done
diff --git a/t/t7004/pubring.gpg b/t/t7004/pubring.gpg
new file mode 100644
index 000000000..83855fa4e
--- /dev/null
+++ b/t/t7004/pubring.gpg
Binary files differ
diff --git a/t/t7004/random_seed b/t/t7004/random_seed
new file mode 100644
index 000000000..8fed1339e
--- /dev/null
+++ b/t/t7004/random_seed
Binary files differ
diff --git a/t/t7004/secring.gpg b/t/t7004/secring.gpg
new file mode 100644
index 000000000..d831cd9eb
--- /dev/null
+++ b/t/t7004/secring.gpg
Binary files differ
diff --git a/t/t7004/trustdb.gpg b/t/t7004/trustdb.gpg
new file mode 100644
index 000000000..abace962b
--- /dev/null
+++ b/t/t7004/trustdb.gpg
Binary files differ
diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh
index 033177068..641303e0a 100755
--- a/t/t9400-git-cvsserver-server.sh
+++ b/t/t9400-git-cvsserver-server.sh
@@ -38,7 +38,7 @@ echo >empty &&
git commit -q -m "First Commit" &&
git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
- GIT_DIR="$SERVERDIR" git config --bool gitcvs.logfile "$SERVERDIR/gitcvs.log" ||
+ GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" ||
exit 1
# note that cvs doesn't accept absolute pathnames
@@ -255,7 +255,7 @@ rm -fr "$SERVERDIR"
cd "$WORKDIR" &&
git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
-GIT_DIR="$SERVERDIR" git config --bool gitcvs.logfile "$SERVERDIR/gitcvs.log" ||
+GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" ||
exit 1
test_expect_success 'cvs update (create new file)' \
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 8bf4cf49a..78d7e87e8 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -26,6 +26,7 @@ GIT_COMMITTER_EMAIL=committer@example.com
GIT_COMMITTER_NAME='C O Mitter'
unset GIT_DIFF_OPTS
unset GIT_DIR
+unset GIT_WORK_TREE
unset GIT_EXTERNAL_DIFF
unset GIT_INDEX_FILE
unset GIT_OBJECT_DIRECTORY
diff --git a/write_or_die.c b/write_or_die.c
index 5c4bc8515..e125e11d3 100644
--- a/write_or_die.c
+++ b/write_or_die.c
@@ -1,5 +1,45 @@
#include "cache.h"
+/*
+ * Some cases use stdio, but want to flush after the write
+ * to get error handling (and to get better interactive
+ * behaviour - not buffering excessively).
+ *
+ * Of course, if the flush happened within the write itself,
+ * we've already lost the error code, and cannot report it any
+ * more. So we just ignore that case instead (and hope we get
+ * the right error code on the flush).
+ *
+ * If the file handle is stdout, and stdout is a file, then skip the
+ * flush entirely since it's not needed.
+ */
+void maybe_flush_or_die(FILE *f, const char *desc)
+{
+ static int skip_stdout_flush = -1;
+ struct stat st;
+ char *cp;
+
+ if (f == stdout) {
+ if (skip_stdout_flush < 0) {
+ cp = getenv("GIT_FLUSH");
+ if (cp)
+ skip_stdout_flush = (atoi(cp) == 0);
+ else if ((fstat(fileno(stdout), &st) == 0) &&
+ S_ISREG(st.st_mode))
+ skip_stdout_flush = 1;
+ else
+ skip_stdout_flush = 0;
+ }
+ if (skip_stdout_flush && !ferror(f))
+ return;
+ }
+ if (fflush(f)) {
+ if (errno == EPIPE)
+ exit(0);
+ die("write failure on %s: %s", desc, strerror(errno));
+ }
+}
+
int read_in_full(int fd, void *buf, size_t count)
{
char *p = buf;