diff options
113 files changed, 4198 insertions, 2118 deletions
diff --git a/.gitignore b/.gitignore index 62afef234..8670081ad 100644 --- a/.gitignore +++ b/.gitignore @@ -128,7 +128,6 @@ git-status git-stripspace git-submodule git-svn -git-svnimport git-symbolic-ref git-tag git-tar-tree @@ -37,6 +37,7 @@ Sam Vilain <sam@vilain.net> Santi Béjar <sbejar@gmail.com> Sean Estabrooks <seanlkml@sympatico.ca> Shawn O. Pearce <spearce@spearce.org> +Steven Grimm <koreth@midwinter.com> Theodore Ts'o <tytso@mit.edu> Tony Luck <tony.luck@intel.com> Uwe Kleine-König <Uwe_Zeisberger@digi.com> diff --git a/Documentation/RelNotes-1.5.3.5.txt b/Documentation/RelNotes-1.5.3.5.txt index 6a1901a96..4e46d2c2a 100644 --- a/Documentation/RelNotes-1.5.3.5.txt +++ b/Documentation/RelNotes-1.5.3.5.txt @@ -4,22 +4,91 @@ GIT v1.5.3.5 Release Notes Fixes since v1.5.3.4 -------------------- - * "git-config" silently ignored options after --list; now it wilh + * Comes with git-gui 0.8.4. + + * "git-config" silently ignored options after --list; now it will error out with a usage message. * "git-config --file" failed if the argument used a relative path as it changed directories before opening the file. + * "git-config --file" now displays a proper error message if it + cannot read the file specified on the command line. + + * "git-config", "git-diff", "git-apply" failed if run from a + subdirectory with relative GIT_DIR and GIT_WORK_TREE set. + + * "git-blame" crashed if run during a merge conflict. + * "git-add -i" did not handle single line hunks correctly. + * "git-rebase -i" and "git-stash apply" failed if external diff + drivers were used for one or more files in a commit. They now + avoid calling the external diff drivers. + * "git-log --follow" did not work unless diff generation (e.g. -p) was also requested. + * "git-log --follow -B" did not work at all. Fixed. + + * "git-log -M -B" did not correctly handle cases of very large files + being renamed and replaced by very small files in the same commit. + * "git-log" printed extra newlines between commits when a diff was generated internally (e.g. -S or --follow) but not displayed. - * Documention updates for supported (but previously undocumented) + * "git-push" error message is more helpful when pushing to a + repository with no matching refs and none specified. + + * "git-push" now respects + (force push) on wildcard refspecs, + matching the behavior of git-fetch. + + * "git-filter-branch" now updates the working directory when it + has finished filtering the current branch. + + * "git-instaweb" no longer fails on Mac OS X. + + * "git-cvsexportcommit" didn't always create new parent directories + before trying to create new child directories. Fixed. + + * "git-fetch" printed a scary (but bogus) error message while + fetching a tag that pointed to a tree or blob. The error did + not impact correctness, only user perception. The bogus error + is no longer printed. + + * "git-ls-files --ignored" did not properly descend into non-ignored + directories that themselves contained ignored files if d_type + was not supported by the filesystem. This bug impacted systems + such as AFS. Fixed. + + * Git segfaulted when reading an invalid .gitattributes file. Fixed. + + * post-receive-email example hook fixed was fixed for + non-fast-forward updates. + + * Documentation updates for supported (but previously undocumented) options of "git-archive" and "git-reflog". * "make clean" no longer deletes the configure script that ships with the git tarball, making multiple architecture builds easier. + + * "git-remote show origin" spewed a warning message from Perl + when no remote is defined for the current branch via + branch.<name>.remote configuration settings. + + * Building with NO_PERL_MAKEMAKER excessively rebuilt contents + of perl/ subdirectory by rewriting perl.mak. + + * http.sslVerify configuration settings were not used in scripted + Porcelains. + + * "git-add" leaked a bit of memory while scanning for files to add. + + * A few workarounds to squelch false warnings from recent gcc have + been added. + + * "git-send-pack $remote frotz" segfaulted when there is nothing + named 'frotz' on the local end. + + * "git-rebase -interactive" did not handle its "--strategy" option + properly. diff --git a/Documentation/RelNotes-1.5.4.txt b/Documentation/RelNotes-1.5.4.txt index ceee85723..133fa64d2 100644 --- a/Documentation/RelNotes-1.5.4.txt +++ b/Documentation/RelNotes-1.5.4.txt @@ -4,6 +4,8 @@ GIT v1.5.4 Release Notes Updates since v1.5.3 -------------------- + * Comes with much improved gitk. + * git-reset is now built-in. * git-send-email can optionally talk over ssmtp and use SMTP-AUTH. @@ -19,6 +21,29 @@ Updates since v1.5.3 * git-archive can optionally substitute keywords in files marked with export-subst attribute. + * git-for-each-ref learned %(xxxdate:<dateformat>) syntax to + show the various date fields in different formats. + + * git-gc --auto is a low-impact way to automatically run a + variant of git-repack that does not lose unreferenced objects + (read: safer than the usual one) after the user accumulates + too many loose objects. + + * git-push has been rewritten in C. + + * git-push learned --dry-run option to show what would happen + if a push is run. + + * git-remote learned "rm" subcommand. + + * git-rebase --interactive mode can now work on detached HEAD. + + * git-cvsserver can be run via git-shell. + + * git-am and git-rebase are far less verbose. + + * git-pull learned to pass --[no-]ff option to underlying git-merge. + * Various Perforce importer updates. Fixes since v1.5.3 @@ -29,7 +54,6 @@ this release, unless otherwise noted. -- exec >/var/tmp/1 -O=v1.5.3.2-99-ge4b2890 +O=v1.5.3.4-450-g952a9e5 echo O=`git describe refs/heads/master` git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint - diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl index 1061fd8bc..8d21d423e 100755 --- a/Documentation/cmd-list.perl +++ b/Documentation/cmd-list.perl @@ -185,7 +185,6 @@ git-status mainporcelain git-stripspace purehelpers git-submodule mainporcelain git-svn foreignscminterface -git-svnimport foreignscminterface git-symbolic-ref plumbingmanipulators git-tag mainporcelain git-tar-tree plumbinginterrogators diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt index 6b2590d07..5df97a1f9 100644 --- a/Documentation/core-tutorial.txt +++ b/Documentation/core-tutorial.txt @@ -553,13 +553,8 @@ can explore on your own. [NOTE] Most likely, you are not directly using the core -git Plumbing commands, but using Porcelain like Cogito on top -of it. Cogito works a bit differently and you usually do not -have to run `git-update-index` yourself for changed files (you -do tell underlying git about additions and removals via -`cg-add` and `cg-rm` commands). Just before you make a commit -with `cg-commit`, Cogito figures out which files you modified, -and runs `git-update-index` on them for you. +git Plumbing commands, but using Porcelain such as `git-add`, `git-rm' +and `git-commit'. Tagging a version @@ -686,8 +681,8 @@ $ git reset and in fact a lot of the common git command combinations can be scripted with the `git xyz` interfaces. You can learn things by just looking -at what the various git scripts do. For example, `git reset` is the -above two lines implemented in `git-reset`, but some things like +at what the various git scripts do. For example, `git reset` used to be +the above two lines implemented in `git-reset`, but some things like `git status` and `git commit` are slightly more complex scripts around the basic git commands. @@ -805,8 +800,8 @@ you have, you can say $ git branch ------------ -which is nothing more than a simple script around `ls .git/refs/heads`. -There will be asterisk in front of the branch you are currently on. +which used to be nothing more than a simple script around `ls .git/refs/heads`. +There will be an asterisk in front of the branch you are currently on. Sometimes you may wish to create a new branch _without_ actually checking it out and switching to it. If so, just use the command @@ -883,7 +878,7 @@ script called `git merge`, which wants to know which branches you want to resolve and what the merge is all about: ------------ -$ git merge "Merge work in mybranch" HEAD mybranch +$ git merge -m "Merge work in mybranch" mybranch ------------ where the first argument is going to be used as the commit message if @@ -952,7 +947,7 @@ the later output lines is used to show commits contained in the `master` branch, and the second column for the `mybranch` branch. Three commits are shown along with their log messages. All of them have non blank characters in the first column (`*` -shows an ordinary commit on the current branch, `.` is a merge commit), which +shows an ordinary commit on the current branch, `-` is a merge commit), which means they are now part of the `master` branch. Only the "Some work" commit has the plus `+` character in the second column, because `mybranch` has not been merged to incorporate these @@ -970,7 +965,7 @@ to the `master` branch. Let's go back to `mybranch`, and run ------------ $ git checkout mybranch -$ git merge "Merge upstream changes." HEAD master +$ git merge -m "Merge upstream changes." master ------------ This outputs something like this (the actual commit object names @@ -1086,7 +1081,7 @@ to help dumb transport downloaders. There are (confusingly enough) `git-ssh-fetch` and `git-ssh-upload` programs, which are 'commit walkers'; they outlived their usefulness when git Native and SSH transports were introduced, -and not used by `git pull` or `git push` scripts. +and are not used by `git pull` or `git push` scripts. Once you fetch from the remote repository, you `merge` that with your current branch. @@ -1193,7 +1188,7 @@ $ mb=$(git-merge-base HEAD mybranch) The command writes the commit object name of the common ancestor to the standard output, so we captured its output to a variable, -because we will be using it in the next step. BTW, the common +because we will be using it in the next step. By the way, the common ancestor commit is the "New day." commit in this case. You can tell it by: @@ -1459,8 +1454,7 @@ Although git is a truly distributed system, it is often convenient to organize your project with an informal hierarchy of developers. Linux kernel development is run this way. There is a nice illustration (page 17, "Merges to Mainline") in -link:http://www.xenotime.net/linux/mentor/linux-mentoring-2006.pdf -[Randy Dunlap's presentation]. +link:http://www.xenotime.net/linux/mentor/linux-mentoring-2006.pdf[Randy Dunlap's presentation]. It should be stressed that this hierarchy is purely *informal*. There is nothing fundamental in git that enforces the "chain of @@ -1613,8 +1607,8 @@ in both of them. You could merge in 'diff-fix' first and then 'commit-fix' next, like this: ------------ -$ git merge 'Merge fix in diff-fix' master diff-fix -$ git merge 'Merge fix in commit-fix' master commit-fix +$ git merge -m 'Merge fix in diff-fix' diff-fix +$ git merge -m 'Merge fix in commit-fix' commit-fix ------------ Which would result in: diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index 1072fb87d..4795349c1 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -16,8 +16,9 @@ The command takes various subcommands, and different options depending on the subcommand: git bisect start [<bad> [<good>...]] [--] [<paths>...] - git bisect bad <rev> - git bisect good <rev> + git bisect bad [<rev>] + git bisect good [<rev>...] + git bisect skip [<rev>...] git bisect reset [<branch>] git bisect visualize git bisect replay <logfile> @@ -134,6 +135,20 @@ $ git reset --hard HEAD~3 # try 3 revs before what Then compile and test the one you chose to try. After that, tell bisect what the result was as usual. +Bisect skip +~~~~~~~~~~~~ + +Instead of choosing by yourself a nearby commit, you may just want git +to do it for you using: + +------------ +$ git bisect skip # Current version cannot be tested +------------ + +But computing the commit to test may be slower afterwards and git may +eventually not be able to tell the first bad among a bad and one or +more "skip"ped commits. + Cutting down bisection by giving more parameters to bisect start ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -167,14 +182,18 @@ $ git bisect run my_script ------------ Note that the "run" script (`my_script` in the above example) should -exit with code 0 in case the current source code is good and with a -code between 1 and 127 (included) in case the current source code is -bad. +exit with code 0 in case the current source code is good. Exit with a +code between 1 and 127 (inclusive), except 125, if the current +source code is bad. Any other exit code will abort the automatic bisect process. (A program that does "exit(-1)" leaves $? = 255, see exit(3) manual page, the value is chopped with "& 0377".) +The special exit code 125 should be used when the current source code +cannot be tested. If the "run" script exits with this code, the current +revision will be skipped, see `git bisect skip` above. + You may often find that during bisect you want to have near-constant tweaks (e.g., s/#define DEBUG 0/#define DEBUG 1/ in a header file, or "revision that does not have this commit needs this patch applied to diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 47b1e8c2f..76a2edfd9 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -27,11 +27,12 @@ OPTIONS message prior committing. -x:: - Cause the command to append which commit was - cherry-picked after the original commit message when - making a commit. Do not use this option if you are - cherry-picking from your private branch because the - information is useless to the recipient. If on the + When recording the commit, append to the original commit + message a note that indicates which commit this change + was cherry-picked from. Append the note only for cherry + picks without conflicts. Do not use this option if + you are cherry-picking from your private branch because + the information is useless to the recipient. If on the other hand you are cherry-picking between two publicly visible branches (e.g. backporting a fix to a maintenance branch for an older release from a diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index 4c8d1e638..c3922f923 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -73,7 +73,7 @@ Merge one patch into CVS:: $ export GIT_DIR=~/project/.git $ cd ~/project_cvs_checkout $ git-cvsexportcommit -v <commit-sha1> -$ cvs commit -F .mgs <files> +$ cvs commit -F .msg <files> ------------ Merge pending patches into CVS automatically -- only if you really know what you are doing:: diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index c878ed395..ba9b4fbca 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -180,8 +180,7 @@ A significantly faster version: git filter-branch --index-filter 'git update-index --remove filename' HEAD -------------------------------------------------------------------------- -Now, you will get the rewritten history saved in the branch 'newbranch' -(your current branch is left untouched). +Now, you will get the rewritten history saved in HEAD. To set a commit (which typically is at the tip of another history) to be the parent of the current initial commit, in diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index b9d5660ea..872056ea0 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -19,7 +19,8 @@ created from prior invocations of gitlink:git-add[1]. Users are encouraged to run this task on a regular basis within each repository to maintain good disk space utilization and good -operating performance. +operating performance. Some git commands may automatically run +`git-gc`; see the `--auto` flag below for details. OPTIONS ------- @@ -44,18 +45,23 @@ OPTIONS few hundred changesets or so. --auto:: - With this option, `git gc` checks if there are too many - loose objects in the repository and runs - gitlink:git-repack[1] with `-d -l` option to pack them. - The threshold for loose objects is set with `gc.auto` configuration - variable, and can be disabled by setting it to 0. Some - Porcelain commands use this after they perform operation - that could create many loose objects automatically. - Additionally, when there are too many packs are present, - they are consolidated into one larger pack by running - the `git-repack` command with `-A` option. The - threshold for number of packs is set with - `gc.autopacklimit` configuration variable. + With this option, `git gc` checks whether any housekeeping is + required; if not, it exits without performing any work. + Some git commands run `git gc --auto` after performing + operations that could create many loose objects. ++ +Housekeeping is required if there are too many loose objects or +too many packs in the repository. If the number of loose objects +exceeds the value of the `gc.auto` configuration variable, then +all loose objects are combined into a single pack using +`git-repack -d -l`. Setting the value of `gc.auto` to 0 +disables automatic packing of loose objects. ++ +If the number of packs exceeds the value of `gc.autopacklimit`, +then existing packs (except those marked with a `.keep` file) +are consolidated into a single pack by using the `-A` option of +`git-repack`. Setting `gc.autopacklimit` to 0 disables +automatic consolidation of packs. Configuration ------------- diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index bca4212e5..eabd7ef33 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -11,26 +11,27 @@ SYNOPSIS [verse] 'git-merge' [-n] [--summary] [--no-commit] [--squash] [-s <strategy>]... [-m <msg>] <remote> <remote>... +'git-merge' <msg> HEAD <remote>... DESCRIPTION ----------- This is the top-level interface to the merge machinery which drives multiple merge strategy scripts. +The second syntax (<msg> `HEAD` <remote>) is supported for +historical reasons. Do not use it from the command line or in +new scripts. It is the same as `git merge -m <msg> <remote>`. + OPTIONS ------- include::merge-options.txt[] -<msg>:: +-m <msg>:: The commit message to be used for the merge commit (in case it is created). The `git-fmt-merge-msg` script can be used to give a good default for automated `git-merge` invocations. -<head>:: - Our branch head commit. This has to be `HEAD`, so new - syntax does not require it - <remote>:: Other branch head merged into our branch. You need at least one <remote>. Specifying more than one <remote> diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 6c32c6d18..a26c26016 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -25,12 +25,18 @@ OPTIONS -t or --tool=<tool>:: Use the merge resolution program specified by <tool>. Valid merge tools are: - kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, and opendiff + kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff + If a merge resolution program is not specified, 'git mergetool' will use the configuration variable merge.tool. If the configuration variable merge.tool is not set, 'git mergetool' will pick a suitable default. ++ +You can explicitly provide a full path to the tool by setting the +configuration variable mergetool.<tool>.path. For example, you +can configure the absolute path to kdiff3 by setting +mergetool.kdiff3.path. Otherwise, 'git mergetool' assumes the tool +is available in PATH. Author ------ diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index e8e75790f..e4326d332 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -28,7 +28,10 @@ The current branch is reset to <upstream>, or <newbase> if the `git reset --hard <upstream>` (or <newbase>). The commits that were previously saved into the temporary area are -then reapplied to the current branch, one by one, in order. +then reapplied to the current branch, one by one, in order. Note that +any commits in HEAD which introduce the same textual changes as a commit +in HEAD..<upstream> are omitted (i.e., a patch already accepted upstream +with a different commit message or timestamp will be skipped). It is possible that a merge failure will prevent this process from being completely automatic. You will have to resolve any such merge failure @@ -62,6 +65,26 @@ would be: The latter form is just a short-hand of `git checkout topic` followed by `git rebase master`. +If the upstream branch already contains a change you have made (e.g., +because you mailed a patch which was applied upstream), then that commit +will be skipped. For example, running `git-rebase master` on the +following history (in which A' and A introduce the same set of changes, +but have different committer information): + +------------ + A---B---C topic + / + D---E---A'---F master +------------ + +will result in: + +------------ + B'---C' topic + / + D---E---A'---F master +------------ + Here is how you would transplant a topic branch based on one branch to another, to pretend that you forked the topic branch from the latter branch, using `rebase --onto`. diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 7cd0e8913..485280423 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -34,6 +34,7 @@ SYNOPSIS [ \--pretty | \--header ] [ \--bisect ] [ \--bisect-vars ] + [ \--bisect-all ] [ \--merge ] [ \--reverse ] [ \--walk-reflogs ] @@ -354,6 +355,21 @@ the expected number of commits to be tested if `bisect_rev` turns out to be bad to `bisect_bad`, and the number of commits we are bisecting right now to `bisect_all`. +--bisect-all:: + +This outputs all the commit objects between the included and excluded +commits, ordered by their distance to the included and excluded +commits. The farthest from them is displayed first. (This is the only +one displayed by `--bisect`.) + +This is useful because it makes it easy to choose a good commit to +test when you want to avoid to test some of them for some reason (they +may not compile for example). + +This option can be used along with `--bisect-vars`, in this case, +after all the sorted commit objects, there will be the same text as if +`--bisect-vars` had been used alone. + -- Commit Ordering diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 3727776a0..e38b7021b 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -159,6 +159,9 @@ sendemail.aliasfiletype:: Format of the file(s) specified in sendemail.aliasesfile. Must be one of 'mutt', 'mailrc', 'pine', or 'gnus'. +sendemail.to:: + Email address (or alias) to always send to. + sendemail.cccmd:: Command to execute to generate per patch file specific "Cc:"s. diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 22a23bf96..10d3e3fa9 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -214,6 +214,27 @@ having tracking branches. Again, the heuristic to automatically follow such tags is a good thing. +On Backdating Tags +~~~~~~~~~~~~~~~~~~ + +If you have imported some changes from another VCS and would like +to add tags for major releases of your work, it is useful to be able +to specify the date to embed inside of the tag object. The data in +the tag object affects, for example, the ordering of tags in the +gitweb interface. + +To set the date used in future tag objects, set the environment +variable GIT_AUTHOR_DATE to one or more of the date and time. The +date and time can be specified in a number of ways; the most common +is "YYYY-MM-DD HH:MM". + +An example follows. + +------------ +$ GIT_AUTHOR_DATE="2006-10-02 10:31" git tag -s v1.0.1 +------------ + + Author ------ Written by Linus Torvalds <torvalds@osdl.org>, diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt index 10653ff89..a96403cb8 100644 --- a/Documentation/git-tools.txt +++ b/Documentation/git-tools.txt @@ -22,6 +22,9 @@ Alternative/Augmentative Porcelains providing generally smoother user experience than the "raw" Core GIT itself and indeed many other version control systems. + Cogito is no longer maintained as most of its functionality + is now in core GIT. + - *pg* (http://www.spearce.org/category/projects/scm/pg/) @@ -33,7 +36,7 @@ Alternative/Augmentative Porcelains - *StGit* (http://www.procode.org/stgit/) Stacked GIT provides a quilt-like patch management functionality in the - GIT environment. You can easily manage your patches in the scope of GIT + GIT environment. You can easily manage your patches in the scope of GIT until they get merged upstream. diff --git a/Documentation/git.txt b/Documentation/git.txt index ce8f923a1..6db7ae1ea 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -46,10 +46,12 @@ Documentation for older releases are available here: * link:v1.5.3/git.html[documentation for release 1.5.3] * release notes for + link:RelNotes-1.5.3.5.txt[1.5.3.5], link:RelNotes-1.5.3.4.txt[1.5.3.4], link:RelNotes-1.5.3.3.txt[1.5.3.3], link:RelNotes-1.5.3.2.txt[1.5.3.2], - link:RelNotes-1.5.3.1.txt[1.5.3.1]. + link:RelNotes-1.5.3.1.txt[1.5.3.1], + link:RelNotes-1.5.3.txt[1.5.3]. * release notes for link:RelNotes-1.5.2.5.txt[1.5.2.5], diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index e9f82b97b..8dbfb0d5a 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -69,7 +69,7 @@ gitk --since="2 weeks ago" \-- gitk:: The "--" is necessary to avoid confusion with the *branch* named 'gitk' -gitk --max-count=100 --all -- Makefile:: +gitk --max-count=100 --all \-- Makefile:: Show at most 100 changes made to the file 'Makefile'. Instead of only looking for changes in the current branch look in all branches. @@ -167,6 +167,7 @@ GITWEB_CONFIG = gitweb_config.perl GITWEB_HOME_LINK_STR = projects GITWEB_SITENAME = GITWEB_PROJECTROOT = /pub/git +GITWEB_PROJECT_MAXDEPTH = 2007 GITWEB_EXPORT_OK = GITWEB_STRICT_EXPORT = GITWEB_BASE_URL = @@ -224,8 +225,7 @@ SCRIPT_SH = \ SCRIPT_PERL = \ git-add--interactive.perl \ git-archimport.perl git-cvsimport.perl git-relink.perl \ - git-cvsserver.perl git-remote.perl \ - git-svnimport.perl git-cvsexportcommit.perl \ + git-cvsserver.perl git-remote.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ @@ -289,7 +289,7 @@ LIB_H = \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \ utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h \ - mailmap.h remote.h transport.h + mailmap.h remote.h transport.h diffcore.h hash.h DIFF_OBJS = \ diff.o diff-lib.o diffcore-break.o diffcore-order.o \ @@ -299,7 +299,7 @@ DIFF_OBJS = \ LIB_OBJS = \ blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \ date.o diff-delta.o entry.o exec_cmd.o ident.o \ - interpolate.o \ + interpolate.o hash.o \ lockfile.o \ patch-ids.o \ object.o pack-check.o pack-write.o patch-delta.o path.o pkt-line.o \ @@ -811,7 +811,7 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak -perl/perl.mak: GIT-CFLAGS +perl/perl.mak: GIT-CFLAGS perl/Makefile perl/Makefile.PL $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F) $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl @@ -842,6 +842,7 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \ -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ + -e 's|"++GITWEB_PROJECT_MAXDEPTH++"|$(GITWEB_PROJECT_MAXDEPTH)|g' \ -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \ -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \ -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \ @@ -915,7 +916,6 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h) -$(DIFF_OBJS): diffcore.h $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS) @@ -929,10 +929,6 @@ $(XDIFF_LIB): $(XDIFF_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(XDIFF_OBJS) -perl/Makefile: perl/Git.pm perl/Makefile.PL GIT-CFLAGS - (cd perl && $(PERL_PATH) Makefile.PL \ - PREFIX='$(prefix_SQ)') - doc: $(MAKE) -C Documentation all @@ -209,8 +209,11 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, num_attr = 0; cp = name + namelen; cp = cp + strspn(cp, blank); - while (*cp) + while (*cp) { cp = parse_attr(src, lineno, cp, &num_attr, res); + if (!cp) + return NULL; + } if (pass) break; res = xcalloc(1, diff --git a/builtin-add.c b/builtin-add.c index f9a65803d..dbbb05215 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -12,6 +12,7 @@ #include "diffcore.h" #include "commit.h" #include "revision.h" +#include "run-command.h" static const char builtin_add_usage[] = "git-add [-n] [-v] [-f] [--interactive | -i] [-u] [--refresh] [--] <filepattern>..."; @@ -44,6 +45,7 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p die("pathspec '%s' did not match any files", pathspec[i]); } + free(seen); } static void fill_directory(struct dir_struct *dir, const char **pathspec, @@ -106,7 +108,7 @@ static void update_callback(struct diff_queue_struct *q, } } -static void update(int verbose, const char *prefix, const char **files) +void add_files_to_cache(int verbose, const char *prefix, const char **files) { struct rev_info rev; init_revisions(&rev, prefix); @@ -115,8 +117,6 @@ static void update(int verbose, const char *prefix, const char **files) rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = update_callback; rev.diffopt.format_callback_data = &verbose; - if (read_cache() < 0) - die("index file corrupt"); run_diff_files(&rev, 0); } @@ -135,6 +135,7 @@ static void refresh(int verbose, const char **pathspec) if (!seen[i]) die("pathspec '%s' did not match any files", pathspec[i]); } + free(seen); } static int git_add_config(const char *var, const char *value) @@ -149,6 +150,13 @@ static int git_add_config(const char *var, const char *value) return git_default_config(var, value); } +int interactive_add(void) +{ + const char *argv[2] = { "add--interactive", NULL }; + + return run_command_v_opt(argv, RUN_GIT_CMD); +} + static struct lock_file lock_file; static const char ignore_error[] = @@ -168,12 +176,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) add_interactive++; } if (add_interactive) { - const char *args[] = { "add--interactive", NULL }; - - if (add_interactive != 1 || argc != 2) + if (argc != 2) die("add --interactive does not take any parameters"); - execv_git_cmd(args); - exit(1); + exit(interactive_add()); } git_config(git_add_config); @@ -213,7 +218,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) } if (take_worktree_changes) { - update(verbose, prefix, argv + i); + if (read_cache() < 0) + die("index file corrupt"); + add_files_to_cache(verbose, prefix, argv + i); goto finish; } diff --git a/builtin-apply.c b/builtin-apply.c index 05c6bc359..8411b38c7 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -152,7 +152,7 @@ struct patch { unsigned int is_rename:1; struct fragment *fragments; char *result; - unsigned long resultsize; + size_t resultsize; char old_sha1_prefix[41]; char new_sha1_prefix[41]; struct patch *next; diff --git a/builtin-archive.c b/builtin-archive.c index 76f8d3d83..14a1b3077 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -146,12 +146,14 @@ void *sha1_file_to_archive(const char *path, const unsigned char *sha1, buffer = read_sha1_file(sha1, type, sizep); if (buffer && S_ISREG(mode)) { struct strbuf buf; + size_t size = 0; strbuf_init(&buf, 0); strbuf_attach(&buf, buffer, *sizep, *sizep + 1); convert_to_working_tree(path, buf.buf, buf.len, &buf); convert_to_archive(path, buf.buf, buf.len, &buf, commit); - buffer = strbuf_detach(&buf, sizep); + buffer = strbuf_detach(&buf, &size); + *sizep = size; } return buffer; diff --git a/builtin-blame.c b/builtin-blame.c index e3112a2d5..8432b823e 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -2059,6 +2059,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con if (strbuf_read(&buf, 0, 0) < 0) die("read error %s from stdin", strerror(errno)); } + convert_to_git(path, buf.buf, buf.len, &buf); origin->file.ptr = buf.buf; origin->file.size = buf.len; pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1); diff --git a/builtin-config.c b/builtin-config.c index d98b6c2c4..e5e243f27 100644 --- a/builtin-config.c +++ b/builtin-config.c @@ -175,7 +175,10 @@ int cmd_config(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l")) { if (argc != 2) usage(git_config_set_usage); - return git_config(show_all_config); + if (git_config(show_all_config) < 0 && file && errno) + die("unable to read config file %s: %s", file, + strerror(errno)); + return 0; } else if (!strcmp(argv[1], "--global")) { char *home = getenv("HOME"); diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index 1e43d7922..6a7851795 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -25,7 +25,7 @@ static int update_ref_env(const char *action, unsigned char *oldval) { char msg[1024]; - char *rla = getenv("GIT_REFLOG_ACTION"); + const char *rla = getenv("GIT_REFLOG_ACTION"); if (!rla) rla = "(reflog update)"; @@ -61,7 +61,7 @@ static int update_local_ref(const char *name, } if (get_sha1(name, sha1_old)) { - char *msg; + const char *msg; just_store: /* new ref */ if (!strncmp(name, "refs/tags/", 10)) @@ -131,7 +131,7 @@ static int append_fetch_head(FILE *fp, if (get_sha1(head, sha1)) return error("Not a valid object name: %s", head); - commit = lookup_commit_reference(sha1); + commit = lookup_commit_reference_gently(sha1, 1); if (!commit) not_for_merge = 1; diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 51d8a3279..862652be9 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -12,11 +12,12 @@ static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; static int unpack_limit = 100; -static struct fetch_pack_args args; +static struct fetch_pack_args args = { + /* .uploadpack = */ "git-upload-pack", +}; static const char fetch_pack_usage[] = "git-fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]"; -static const char *uploadpack = "git-upload-pack"; #define COMPLETE (1U << 0) #define COMMON (1U << 1) @@ -746,7 +747,7 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args, st.st_mtime = 0; } - conn = git_connect(fd, (char *)dest, uploadpack, + conn = git_connect(fd, (char *)dest, args.uploadpack, args.verbose ? CONNECT_VERBOSE : 0); if (heads && nr_heads) nr_heads = remove_duplicates(nr_heads, heads); diff --git a/builtin-fetch.c b/builtin-fetch.c index b9d2b0c27..003ed76d1 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -48,15 +48,21 @@ static void add_merge_config(struct ref **head, if (rm) continue; - /* Not fetched to a tracking branch? We need to fetch + /* + * Not fetched to a tracking branch? We need to fetch * it anyway to allow this branch's "branch.$name.merge" - * to be honored by git-pull. + * to be honored by git-pull, but we do not have to + * fail if branch.$name.merge is misconfigured to point + * at a nonexisting branch. If we were indeed called by + * git-pull, it will notice the misconfiguration because + * there is no entry in the resulting FETCH_HEAD marked + * for merging. */ refspec.src = branch->merge[i]->src; refspec.dst = NULL; refspec.pattern = 0; refspec.force = 0; - get_fetch_map(remote_refs, &refspec, tail); + get_fetch_map(remote_refs, &refspec, tail, 1); for (rm = *old_tail; rm; rm = rm->next) rm->merge = 1; } @@ -75,7 +81,7 @@ static struct ref *get_ref_map(struct transport *transport, if (ref_count || tags) { for (i = 0; i < ref_count; i++) { - get_fetch_map(remote_refs, &refs[i], &tail); + get_fetch_map(remote_refs, &refs[i], &tail, 0); if (refs[i].dst && refs[i].dst[0]) *autotags = 1; } @@ -88,7 +94,7 @@ static struct ref *get_ref_map(struct transport *transport, refspec.dst = "refs/tags/"; refspec.pattern = 1; refspec.force = 0; - get_fetch_map(remote_refs, &refspec, &tail); + get_fetch_map(remote_refs, &refspec, &tail, 0); } } else { /* Use the defaults */ @@ -97,7 +103,7 @@ static struct ref *get_ref_map(struct transport *transport, int has_merge = branch_has_merge_config(branch); if (remote && (remote->fetch_refspec_nr || has_merge)) { for (i = 0; i < remote->fetch_refspec_nr; i++) { - get_fetch_map(remote_refs, &remote->fetch[i], &tail); + get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0); if (remote->fetch[i].dst && remote->fetch[i].dst[0]) *autotags = 1; @@ -110,11 +116,13 @@ static struct ref *get_ref_map(struct transport *transport, * as given in branch.<name>.remote, we add the * ref given in branch.<name>.merge, too. */ - if (has_merge && !strcmp(branch->remote_name, - remote->name)) + if (has_merge && + !strcmp(branch->remote_name, remote->name)) add_merge_config(&ref_map, remote_refs, branch, &tail); } else { ref_map = get_remote_ref(remote_refs, "HEAD"); + if (!ref_map) + die("Couldn't find remote ref HEAD"); ref_map->merge = 1; } } diff --git a/builtin-gc.c b/builtin-gc.c index 956c32d1a..3a2ca4f90 100644 --- a/builtin-gc.c +++ b/builtin-gc.c @@ -205,6 +205,10 @@ int cmd_gc(int argc, const char **argv, const char *prefix) prune = 0; if (!need_to_gc()) return 0; + fprintf(stderr, "Packing your repository for optimum " + "performance. You may also\n" + "run \"git gc\" manually. See " + "\"git help gc\" for more information.\n"); } else { /* * Use safer (for shared repos) "-A" option to diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index d7cb11dc0..fb12248f8 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -288,7 +288,7 @@ static void cleanup_space(char *buf) } static void decode_header(char *it, unsigned itsize); -static char *header[MAX_HDR_PARSED] = { +static const char *header[MAX_HDR_PARSED] = { "From","Subject","Date", }; diff --git a/builtin-rerere.c b/builtin-rerere.c index b8206744c..74493237c 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -389,18 +389,39 @@ static int is_rerere_enabled(void) return 1; } -int cmd_rerere(int argc, const char **argv, const char *prefix) +static int setup_rerere(struct path_list *merge_rr) { - struct path_list merge_rr = { NULL, 0, 0, 1 }; - int i, fd = -1; + int fd; git_config(git_rerere_config); if (!is_rerere_enabled()) - return 0; + return -1; merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR")); fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1); - read_rr(&merge_rr); + read_rr(merge_rr); + return fd; +} + +int rerere(void) +{ + struct path_list merge_rr = { NULL, 0, 0, 1 }; + int fd; + + fd = setup_rerere(&merge_rr); + if (fd < 0) + return 0; + return do_plain_rerere(&merge_rr, fd); +} + +int cmd_rerere(int argc, const char **argv, const char *prefix) +{ + struct path_list merge_rr = { NULL, 0, 0, 1 }; + int i, fd; + + fd = setup_rerere(&merge_rr); + if (fd < 0) + return 0; if (argc < 2) return do_plain_rerere(&merge_rr, fd); diff --git a/builtin-reset.c b/builtin-reset.c index 99d5c082a..e1dc31e0e 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -169,7 +169,7 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size) } enum reset_type { MIXED, SOFT, HARD, NONE }; -static char *reset_type_names[] = { "mixed", "soft", "hard", NULL }; +static const char *reset_type_names[] = { "mixed", "soft", "hard", NULL }; int cmd_reset(int argc, const char **argv, const char *prefix) { diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 33726b8d8..44393320e 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -9,6 +9,7 @@ #include "revision.h" #include "list-objects.h" #include "builtin.h" +#include "log-tree.h" /* bits #0-15 in revision.h */ @@ -38,7 +39,8 @@ static const char rev_list_usage[] = " --left-right\n" " special purpose:\n" " --bisect\n" -" --bisect-vars" +" --bisect-vars\n" +" --bisect-all" ; static struct rev_info revs; @@ -74,6 +76,7 @@ static void show_commit(struct commit *commit) parents = parents->next; } } + show_decorations(commit); if (revs.commit_format == CMIT_FMT_ONELINE) putchar(' '); else @@ -278,6 +281,57 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr) return best; } +struct commit_dist { + struct commit *commit; + int distance; +}; + +static int compare_commit_dist(const void *a_, const void *b_) +{ + struct commit_dist *a, *b; + + a = (struct commit_dist *)a_; + b = (struct commit_dist *)b_; + if (a->distance != b->distance) + return b->distance - a->distance; /* desc sort */ + return hashcmp(a->commit->object.sha1, b->commit->object.sha1); +} + +static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr) +{ + struct commit_list *p; + struct commit_dist *array = xcalloc(nr, sizeof(*array)); + int cnt, i; + + for (p = list, cnt = 0; p; p = p->next) { + int distance; + unsigned flags = p->item->object.flags; + + if (revs.prune_fn && !(flags & TREECHANGE)) + continue; + distance = weight(p); + if (nr - distance < distance) + distance = nr - distance; + array[cnt].commit = p->item; + array[cnt].distance = distance; + cnt++; + } + qsort(array, cnt, sizeof(*array), compare_commit_dist); + for (p = list, i = 0; i < cnt; i++) { + struct name_decoration *r = xmalloc(sizeof(*r) + 100); + struct object *obj = &(array[i].commit->object); + + sprintf(r->name, "dist=%d", array[i].distance); + r->next = add_decoration(&name_decoration, obj, r); + p->item = array[i].commit; + p = p->next; + } + if (p) + p->next = NULL; + free(array); + return list; +} + /* * zero or positive weight is the number of interesting commits it can * reach, including itself. Especially, weight = 0 means it does not @@ -292,7 +346,8 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr) * or positive distance. */ static struct commit_list *do_find_bisection(struct commit_list *list, - int nr, int *weights) + int nr, int *weights, + int find_all) { int n, counted; struct commit_list *p; @@ -351,7 +406,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list, clear_distance(list); /* Does it happen to be at exactly half-way? */ - if (halfway(p, nr)) + if (!find_all && halfway(p, nr)) return p; counted++; } @@ -389,19 +444,22 @@ static struct commit_list *do_find_bisection(struct commit_list *list, weight_set(p, weight(q)); /* Does it happen to be at exactly half-way? */ - if (halfway(p, nr)) + if (!find_all && halfway(p, nr)) return p; } } show_list("bisection 2 counted all", counted, nr, list); - /* Then find the best one */ - return best_bisection(list, nr); + if (!find_all) + return best_bisection(list, nr); + else + return best_bisection_sorted(list, nr); } static struct commit_list *find_bisection(struct commit_list *list, - int *reaches, int *all) + int *reaches, int *all, + int find_all) { int nr, on_list; struct commit_list *p, *best, *next, *last; @@ -434,14 +492,13 @@ static struct commit_list *find_bisection(struct commit_list *list, weights = xcalloc(on_list, sizeof(*weights)); /* Do the real work of finding bisection commit. */ - best = do_find_bisection(list, nr, weights); - + best = do_find_bisection(list, nr, weights, find_all); if (best) { - best->next = NULL; + if (!find_all) + best->next = NULL; *reaches = weight(best); } free(weights); - return best; } @@ -468,6 +525,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) int i; int read_from_stdin = 0; int bisect_show_vars = 0; + int bisect_find_all = 0; git_config(git_default_config); init_revisions(&revs, prefix); @@ -490,6 +548,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) bisect_list = 1; continue; } + if (!strcmp(arg, "--bisect-all")) { + bisect_list = 1; + bisect_find_all = 1; + continue; + } if (!strcmp(arg, "--bisect-vars")) { bisect_list = 1; bisect_show_vars = 1; @@ -536,9 +599,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (bisect_list) { int reaches = reaches, all = all; - revs.commits = find_bisection(revs.commits, &reaches, &all); + revs.commits = find_bisection(revs.commits, &reaches, &all, + bisect_find_all); if (bisect_show_vars) { int cnt; + char hex[41]; if (!revs.commits) return 1; /* @@ -550,15 +615,22 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) * A bisect set of size N has (N-1) commits further * to test, as we already know one bad one. */ - cnt = all-reaches; + cnt = all - reaches; if (cnt < reaches) cnt = reaches; + strcpy(hex, sha1_to_hex(revs.commits->item->object.sha1)); + + if (bisect_find_all) { + traverse_commit_list(&revs, show_commit, show_object); + printf("------\n"); + } + printf("bisect_rev=%s\n" "bisect_nr=%d\n" "bisect_good=%d\n" "bisect_bad=%d\n" "bisect_all=%d\n", - sha1_to_hex(revs.commits->item->object.sha1), + hex, cnt - 1, all - reaches - 1, reaches - 1, diff --git a/builtin-revert.c b/builtin-revert.c index a655c8ee2..e855b206c 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -349,7 +349,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) die ("Error wrapping up %s", defmsg); fprintf(stderr, "Automatic %s failed. " "After resolving the conflicts,\n" - "mark the corrected paths with 'git-add <paths>'\n" + "mark the corrected paths with 'git add <paths>' " "and commit the result.\n", me); if (action == CHERRY_PICK) { fprintf(stderr, "When commiting, use the option " @@ -6,6 +6,7 @@ extern const char git_version_string[]; extern const char git_usage_string[]; +extern void list_common_cmds_help(void); extern void help_unknown_cmd(const char *cmd); extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); extern void prune_packed_objects(int); @@ -135,39 +135,39 @@ int git_config_colorbool(const char *var, const char *value) return git_config_bool(var, value); } -static int color_vprintf(const char *color, const char *fmt, +static int color_vfprintf(FILE *fp, const char *color, const char *fmt, va_list args, const char *trail) { int r = 0; if (*color) - r += printf("%s", color); - r += vprintf(fmt, args); + r += fprintf(fp, "%s", color); + r += vfprintf(fp, fmt, args); if (*color) - r += printf("%s", COLOR_RESET); + r += fprintf(fp, "%s", COLOR_RESET); if (trail) - r += printf("%s", trail); + r += fprintf(fp, "%s", trail); return r; } -int color_printf(const char *color, const char *fmt, ...) +int color_fprintf(FILE *fp, const char *color, const char *fmt, ...) { va_list args; int r; va_start(args, fmt); - r = color_vprintf(color, fmt, args, NULL); + r = color_vfprintf(fp, color, fmt, args, NULL); va_end(args); return r; } -int color_printf_ln(const char *color, const char *fmt, ...) +int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...) { va_list args; int r; va_start(args, fmt); - r = color_vprintf(color, fmt, args, "\n"); + r = color_vfprintf(fp, color, fmt, args, "\n"); va_end(args); return r; } @@ -6,7 +6,7 @@ int git_config_colorbool(const char *var, const char *value); void color_parse(const char *var, const char *value, char *dst); -int color_printf(const char *color, const char *fmt, ...); -int color_printf_ln(const char *color, const char *fmt, ...); +int color_fprintf(FILE *fp, const char *color, const char *fmt, ...); +int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...); #endif /* COLOR_H */ @@ -128,4 +128,9 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads, int depth, int shallow_flag, int not_shallow_flag); int in_merge_bases(struct commit *, struct commit **, int); + +extern int interactive_add(void); +extern void add_files_to_cache(int verbose, const char *prefix, const char **files); +extern int rerere(void); + #endif /* COMMIT_H */ diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index e76093074..599b2fc57 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -346,7 +346,6 @@ __git_commands () ssh-*) : transport;; stripspace) : plumbing;; svn) : import export;; - svnimport) : import;; symbolic-ref) : plumbing;; tar-tree) : deprecated;; unpack-file) : plumbing;; diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 4286d160a..e147da059 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -796,6 +796,7 @@ Return the list of files that haven't been handled." (with-current-buffer buffer (erase-buffer)) (dolist (info files) (git-set-fileinfo-state info 'uptodate)) (git-call-process-env nil nil "rerere") + (git-call-process-env nil nil "gc" "--auto") (git-refresh-files) (git-refresh-ewoc-hf git-status) (message "Committed %s." commit) @@ -842,7 +843,8 @@ Return the list of files that haven't been handled." "Mark all files." (interactive) (unless git-status (error "Not in git-status buffer.")) - (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) t) t) git-status) + (ewoc-map (lambda (info) (unless (git-fileinfo->marked info) + (setf (git-fileinfo->marked info) t))) git-status) ; move back to goal column after invalidate (when goal-column (move-to-column goal-column))) @@ -850,7 +852,9 @@ Return the list of files that haven't been handled." "Unmark all files." (interactive) (unless git-status (error "Not in git-status buffer.")) - (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) nil) t) git-status) + (ewoc-map (lambda (info) (when (git-fileinfo->marked info) + (setf (git-fileinfo->marked info) nil) + t)) git-status) ; move back to goal column after invalidate (when goal-column (move-to-column goal-column))) @@ -955,7 +959,7 @@ Return the list of files that haven't been handled." (when modified (apply #'git-call-process-env nil nil "checkout" "HEAD" modified)) (git-update-status-files (append added modified) 'uptodate) - (git-success-message "Reverted" files)))) + (git-success-message "Reverted" (git-get-filenames files))))) (defun git-resolve-file () "Resolve conflicts in marked file(s)." @@ -1353,7 +1357,7 @@ Commands: "Update the corresponding git-status buffer when a file is saved. Meant to be used in `after-save-hook'." (let* ((file (expand-file-name buffer-file-name)) - (dir (condition-case nil (git-get-top-dir (file-name-directory file)))) + (dir (condition-case nil (git-get-top-dir (file-name-directory file)) (error nil))) (buffer (and dir (git-find-status-buffer dir)))) (when buffer (with-current-buffer buffer diff --git a/git-svnimport.perl b/contrib/examples/git-svnimport.perl index ea8c1b2f6..ea8c1b2f6 100755 --- a/git-svnimport.perl +++ b/contrib/examples/git-svnimport.perl diff --git a/Documentation/git-svnimport.txt b/contrib/examples/git-svnimport.txt index 71aad8b45..71aad8b45 100644 --- a/Documentation/git-svnimport.txt +++ b/contrib/examples/git-svnimport.txt diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index 52cd2a46b..bf33f74b7 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -399,6 +399,7 @@ class P4Submit(Command): optparse.make_option("--dry-run", action="store_true"), optparse.make_option("--direct", dest="directSubmit", action="store_true"), optparse.make_option("--trust-me-like-a-fool", dest="trustMeLikeAFool", action="store_true"), + optparse.make_option("-M", dest="detectRename", action="store_true"), ] self.description = "Submit changes from git to the perforce depot." self.usage += " [name of git branch to submit into perforce depot]" @@ -411,6 +412,7 @@ class P4Submit(Command): self.origin = "" self.directSubmit = False self.trustMeLikeAFool = False + self.detectRename = False self.verbose = False self.isWindows = (platform.system() == "Windows") @@ -491,7 +493,8 @@ class P4Submit(Command): diff = self.diffStatus else: print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id)) - diff = read_pipe_lines("git diff-tree -r --name-status \"%s^\" \"%s\"" % (id, id)) + diffOpts = ("", "-M")[self.detectRename] + diff = read_pipe_lines("git diff-tree -r --name-status %s \"%s^\" \"%s\"" % (diffOpts, id, id)) filesToAdd = set() filesToDelete = set() editedFiles = set() @@ -509,6 +512,13 @@ class P4Submit(Command): filesToDelete.add(path) if path in filesToAdd: filesToAdd.remove(path) + elif modifier == "R": + src, dest = line.strip().split("\t")[1:3] + system("p4 integrate -Dt \"%s\" \"%s\"" % (src, dest)) + system("p4 edit \"%s\"" % (dest)) + os.unlink(dest) + editedFiles.add(dest) + filesToDelete.add(src) else: die("unknown modifier %s for %s" % (modifier, path)) @@ -529,6 +539,10 @@ class P4Submit(Command): "and with .rej files / [w]rite the patch to a file (patch.txt) ") if response == "s": print "Skipping! Good luck with the next patches..." + for f in editedFiles: + system("p4 revert \"%s\"" % f); + for f in filesToAdd: + system("rm %s" %f) return elif response == "a": os.system(applyPatchCmd) diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index b188aa3d6..2aa9bb501 100644 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -331,7 +331,7 @@ generate_update_branch_email() echo " via $rev ($revtype)" done - if [ -z "$fastforward" ]; then + if [ "$fast_forward" ]; then echo " from $oldrev ($oldrev_type)" else # 1. Existing revisions were removed. In this case newrev is a @@ -9,6 +9,10 @@ #define HOST_NAME_MAX 256 #endif +#ifndef NI_MAXSERV +#define NI_MAXSERV 32 +#endif + static int log_syslog; static int verbose; static int reuseaddr; @@ -1441,9 +1441,18 @@ struct diff_filespec *alloc_filespec(const char *path) memset(spec, 0, sizeof(*spec)); spec->path = (char *)(spec + 1); memcpy(spec->path, path, namelen+1); + spec->count = 1; return spec; } +void free_filespec(struct diff_filespec *spec) +{ + if (!--spec->count) { + diff_free_filespec_data(spec); + free(spec); + } +} + void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, unsigned short mode) { @@ -1513,6 +1522,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int static int populate_from_stdin(struct diff_filespec *s) { struct strbuf buf; + size_t size = 0; strbuf_init(&buf, 0); if (strbuf_read(&buf, 0, 0) < 0) @@ -1520,7 +1530,8 @@ static int populate_from_stdin(struct diff_filespec *s) strerror(errno)); s->should_munmap = 0; - s->data = strbuf_detach(&buf, &s->size); + s->data = strbuf_detach(&buf, &size); + s->size = size; s->should_free = 1; return 0; } @@ -1610,9 +1621,11 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) */ strbuf_init(&buf, 0); if (convert_to_git(s->path, s->data, s->size, &buf)) { + size_t size = 0; munmap(s->data, s->size); s->should_munmap = 0; - s->data = strbuf_detach(&buf, &s->size); + s->data = strbuf_detach(&buf, &size); + s->size = size; s->should_free = 1; } } @@ -2399,10 +2412,8 @@ struct diff_filepair *diff_queue(struct diff_queue_struct *queue, void diff_free_filepair(struct diff_filepair *p) { - diff_free_filespec_data(p->one); - diff_free_filespec_data(p->two); - free(p->one); - free(p->two); + free_filespec(p->one); + free_filespec(p->two); free(p); } @@ -2554,9 +2565,9 @@ void diff_debug_filepair(const struct diff_filepair *p, int i) { diff_debug_filespec(p->one, i, "one"); diff_debug_filespec(p->two, i, "two"); - fprintf(stderr, "score %d, status %c stays %d broken %d\n", + fprintf(stderr, "score %d, status %c rename_used %d broken %d\n", p->score, p->status ? p->status : '?', - p->source_stays, p->broken_pair); + p->one->rename_used, p->broken_pair); } void diff_debug_queue(const char *msg, struct diff_queue_struct *q) @@ -2574,8 +2585,8 @@ void diff_debug_queue(const char *msg, struct diff_queue_struct *q) static void diff_resolve_rename_copy(void) { - int i, j; - struct diff_filepair *p, *pp; + int i; + struct diff_filepair *p; struct diff_queue_struct *q = &diff_queued_diff; diff_debug_queue("resolve-rename-copy", q); @@ -2597,27 +2608,21 @@ static void diff_resolve_rename_copy(void) * either in-place edit or rename/copy edit. */ else if (DIFF_PAIR_RENAME(p)) { - if (p->source_stays) { - p->status = DIFF_STATUS_COPIED; - continue; - } - /* See if there is some other filepair that - * copies from the same source as us. If so - * we are a copy. Otherwise we are either a - * copy if the path stays, or a rename if it - * does not, but we already handled "stays" case. + /* + * A rename might have re-connected a broken + * pair up, causing the pathnames to be the + * same again. If so, that's not a rename at + * all, just a modification.. + * + * Otherwise, see if this source was used for + * multiple renames, in which case we decrement + * the count, and call it a copy. */ - for (j = i + 1; j < q->nr; j++) { - pp = q->queue[j]; - if (strcmp(pp->one->path, p->one->path)) - continue; /* not us */ - if (!DIFF_PAIR_RENAME(pp)) - continue; /* not a rename/copy */ - /* pp is a rename/copy from the same source */ + if (!strcmp(p->one->path, p->two->path)) + p->status = DIFF_STATUS_MODIFIED; + else if (--p->one->rename_used > 0) p->status = DIFF_STATUS_COPIED; - break; - } - if (!p->status) + else p->status = DIFF_STATUS_RENAMED; } else if (hashcmp(p->one->sha1, p->two->sha1) || diff --git a/diffcore-break.c b/diffcore-break.c index ae8a7d03e..c71a22621 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -45,8 +45,8 @@ static int should_break(struct diff_filespec *src, * The value we return is 1 if we want the pair to be broken, * or 0 if we do not. */ - unsigned long delta_size, base_size, src_copied, literal_added, - src_removed; + unsigned long delta_size, base_size, max_size; + unsigned long src_copied, literal_added, src_removed; *merge_score_p = 0; /* assume no deletion --- "do not break" * is the default. @@ -63,7 +63,8 @@ static int should_break(struct diff_filespec *src, return 0; /* error but caught downstream */ base_size = ((src->size < dst->size) ? src->size : dst->size); - if (base_size < MINIMUM_BREAK_SIZE) + max_size = ((src->size > dst->size) ? src->size : dst->size); + if (max_size < MINIMUM_BREAK_SIZE) return 0; /* we do not break too small filepair */ if (diffcore_count_changes(src, dst, @@ -89,12 +90,14 @@ static int should_break(struct diff_filespec *src, * less than the minimum, after rename/copy runs. */ *merge_score_p = (int)(src_removed * MAX_SCORE / src->size); + if (*merge_score_p > break_score) + return 1; /* Extent of damage, which counts both inserts and * deletes. */ delta_size = src_removed + literal_added; - if (delta_size * MAX_SCORE / base_size < break_score) + if (delta_size * MAX_SCORE / max_size < break_score) return 0; /* If you removed a lot without adding new material, that is diff --git a/diffcore-delta.c b/diffcore-delta.c index d9729e5ec..e670f8512 100644 --- a/diffcore-delta.c +++ b/diffcore-delta.c @@ -46,22 +46,6 @@ struct spanhash_top { struct spanhash data[FLEX_ARRAY]; }; -static struct spanhash *spanhash_find(struct spanhash_top *top, - unsigned int hashval) -{ - int sz = 1 << top->alloc_log2; - int bucket = hashval & (sz - 1); - while (1) { - struct spanhash *h = &(top->data[bucket++]); - if (!h->cnt) - return NULL; - if (h->hashval == hashval) - return h; - if (sz <= bucket) - bucket = 0; - } -} - static struct spanhash_top *spanhash_rehash(struct spanhash_top *orig) { struct spanhash_top *new; @@ -122,6 +106,20 @@ static struct spanhash_top *add_spanhash(struct spanhash_top *top, } } +static int spanhash_cmp(const void *a_, const void *b_) +{ + const struct spanhash *a = a_; + const struct spanhash *b = b_; + + /* A count of zero compares at the end.. */ + if (!a->cnt) + return !b->cnt ? 0 : 1; + if (!b->cnt) + return -1; + return a->hashval < b->hashval ? -1 : + a->hashval > b->hashval ? 1 : 0; +} + static struct spanhash_top *hash_chars(struct diff_filespec *one) { int i, n; @@ -158,6 +156,10 @@ static struct spanhash_top *hash_chars(struct diff_filespec *one) n = 0; accum1 = accum2 = 0; } + qsort(hash->data, + 1ul << hash->alloc_log2, + sizeof(hash->data[0]), + spanhash_cmp); return hash; } @@ -169,7 +171,7 @@ int diffcore_count_changes(struct diff_filespec *src, unsigned long *src_copied, unsigned long *literal_added) { - int i, ssz; + struct spanhash *s, *d; struct spanhash_top *src_count, *dst_count; unsigned long sc, la; @@ -190,22 +192,26 @@ int diffcore_count_changes(struct diff_filespec *src, } sc = la = 0; - ssz = 1 << src_count->alloc_log2; - for (i = 0; i < ssz; i++) { - struct spanhash *s = &(src_count->data[i]); - struct spanhash *d; + s = src_count->data; + d = dst_count->data; + for (;;) { unsigned dst_cnt, src_cnt; if (!s->cnt) - continue; + break; /* we checked all in src */ + while (d->cnt) { + if (d->hashval >= s->hashval) + break; + d++; + } src_cnt = s->cnt; - d = spanhash_find(dst_count, s->hashval); - dst_cnt = d ? d->cnt : 0; + dst_cnt = d->hashval == s->hashval ? d->cnt : 0; if (src_cnt < dst_cnt) { la += dst_cnt - src_cnt; sc += src_cnt; } else sc += dst_cnt; + s++; } if (!src_count_p) diff --git a/diffcore-rename.c b/diffcore-rename.c index 142e5376d..f9ebea564 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -4,6 +4,7 @@ #include "cache.h" #include "diff.h" #include "diffcore.h" +#include "hash.h" /* Table of rename/copy destinations */ @@ -55,12 +56,10 @@ static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two, static struct diff_rename_src { struct diff_filespec *one; unsigned short score; /* to remember the break score */ - unsigned src_path_left : 1; } *rename_src; static int rename_src_nr, rename_src_alloc; static struct diff_rename_src *register_rename_src(struct diff_filespec *one, - int src_path_left, unsigned short score) { int first, last; @@ -92,33 +91,9 @@ static struct diff_rename_src *register_rename_src(struct diff_filespec *one, (rename_src_nr - first - 1) * sizeof(*rename_src)); rename_src[first].one = one; rename_src[first].score = score; - rename_src[first].src_path_left = src_path_left; return &(rename_src[first]); } -static int is_exact_match(struct diff_filespec *src, - struct diff_filespec *dst, - int contents_too) -{ - if (src->sha1_valid && dst->sha1_valid && - !hashcmp(src->sha1, dst->sha1)) - return 1; - if (!contents_too) - return 0; - if (diff_populate_filespec(src, 1) || diff_populate_filespec(dst, 1)) - return 0; - if (src->size != dst->size) - return 0; - if (src->sha1_valid && dst->sha1_valid) - return !hashcmp(src->sha1, dst->sha1); - if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) - return 0; - if (src->size == dst->size && - !memcmp(src->data, dst->data, src->size)) - return 1; - return 0; -} - static int basename_same(struct diff_filespec *src, struct diff_filespec *dst) { int src_len = strlen(src->path), dst_len = strlen(dst->path); @@ -169,6 +144,20 @@ static int estimate_similarity(struct diff_filespec *src, if (!S_ISREG(src->mode) || !S_ISREG(dst->mode)) return 0; + /* + * Need to check that source and destination sizes are + * filled in before comparing them. + * + * If we already have "cnt_data" filled in, we know it's + * all good (avoid checking the size for zero, as that + * is a possible size - we really should have a flag to + * say whether the size is valid or not!) + */ + if (!src->cnt_data && diff_populate_filespec(src, 0)) + return 0; + if (!dst->cnt_data && diff_populate_filespec(dst, 0)) + return 0; + max_size = ((src->size > dst->size) ? src->size : dst->size); base_size = ((src->size < dst->size) ? src->size : dst->size); delta_size = max_size - base_size; @@ -184,11 +173,6 @@ static int estimate_similarity(struct diff_filespec *src, if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE) return 0; - if ((!src->cnt_data && diff_populate_filespec(src, 0)) - || (!dst->cnt_data && diff_populate_filespec(dst, 0))) - return 0; /* error but caught downstream */ - - delta_limit = (unsigned long) (base_size * (MAX_SCORE-minimum_score) / MAX_SCORE); if (diffcore_count_changes(src, dst, @@ -209,27 +193,25 @@ static int estimate_similarity(struct diff_filespec *src, static void record_rename_pair(int dst_index, int src_index, int score) { - struct diff_filespec *one, *two, *src, *dst; + struct diff_filespec *src, *dst; struct diff_filepair *dp; if (rename_dst[dst_index].pair) die("internal error: dst already matched."); src = rename_src[src_index].one; - one = alloc_filespec(src->path); - fill_filespec(one, src->sha1, src->mode); + src->rename_used++; + src->count++; dst = rename_dst[dst_index].two; - two = alloc_filespec(dst->path); - fill_filespec(two, dst->sha1, dst->mode); + dst->count++; - dp = diff_queue(NULL, one, two); + dp = diff_queue(NULL, src, dst); dp->renamed_pair = 1; if (!strcmp(src->path, dst->path)) dp->score = rename_src[src_index].score; else dp->score = score; - dp->source_stays = rename_src[src_index].src_path_left; rename_dst[dst_index].pair = dp; } @@ -247,19 +229,155 @@ static int score_compare(const void *a_, const void *b_) return b->score - a->score; } -static int compute_stays(struct diff_queue_struct *q, - struct diff_filespec *one) +struct file_similarity { + int src_dst, index; + struct diff_filespec *filespec; + struct file_similarity *next; +}; + +static int find_identical_files(struct file_similarity *src, + struct file_similarity *dst) { - int i; - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (strcmp(one->path, p->two->path)) - continue; - if (DIFF_PAIR_RENAME(p)) { - return 0; /* something else is renamed into this */ + int renames = 0; + + /* + * Walk over all the destinations ... + */ + do { + struct diff_filespec *one = dst->filespec; + struct file_similarity *p, *best; + int i = 100; + + /* + * .. to find the best source match + */ + best = NULL; + for (p = src; p; p = p->next) { + struct diff_filespec *two = p->filespec; + + /* False hash collission? */ + if (hashcmp(one->sha1, two->sha1)) + continue; + /* Non-regular files? If so, the modes must match! */ + if (!S_ISREG(one->mode) || !S_ISREG(two->mode)) { + if (one->mode != two->mode) + continue; + } + best = p; + if (basename_same(one, two)) + break; + + /* Too many identical alternatives? Pick one */ + if (!--i) + break; + } + if (best) { + record_rename_pair(dst->index, best->index, MAX_SCORE); + renames++; } + } while ((dst = dst->next) != NULL); + return renames; +} + +static void free_similarity_list(struct file_similarity *p) +{ + while (p) { + struct file_similarity *entry = p; + p = p->next; + free(entry); } - return 1; +} + +static int find_same_files(void *ptr) +{ + int ret; + struct file_similarity *p = ptr; + struct file_similarity *src = NULL, *dst = NULL; + + /* Split the hash list up into sources and destinations */ + do { + struct file_similarity *entry = p; + p = p->next; + if (entry->src_dst < 0) { + entry->next = src; + src = entry; + } else { + entry->next = dst; + dst = entry; + } + } while (p); + + /* + * If we have both sources *and* destinations, see if + * we can match them up + */ + ret = (src && dst) ? find_identical_files(src, dst) : 0; + + /* Free the hashes and return the number of renames found */ + free_similarity_list(src); + free_similarity_list(dst); + return ret; +} + +static unsigned int hash_filespec(struct diff_filespec *filespec) +{ + unsigned int hash; + if (!filespec->sha1_valid) { + if (diff_populate_filespec(filespec, 0)) + return 0; + hash_sha1_file(filespec->data, filespec->size, "blob", filespec->sha1); + } + memcpy(&hash, filespec->sha1, sizeof(hash)); + return hash; +} + +static void insert_file_table(struct hash_table *table, int src_dst, int index, struct diff_filespec *filespec) +{ + void **pos; + unsigned int hash; + struct file_similarity *entry = xmalloc(sizeof(*entry)); + + entry->src_dst = src_dst; + entry->index = index; + entry->filespec = filespec; + entry->next = NULL; + + hash = hash_filespec(filespec); + pos = insert_hash(hash, entry, table); + + /* We already had an entry there? */ + if (pos) { + entry->next = *pos; + *pos = entry; + } +} + +/* + * Find exact renames first. + * + * The first round matches up the up-to-date entries, + * and then during the second round we try to match + * cache-dirty entries as well. + */ +static int find_exact_renames(void) +{ + int i; + struct hash_table file_table; + + init_hash(&file_table); + for (i = 0; i < rename_src_nr; i++) + insert_file_table(&file_table, -1, i, rename_src[i].one); + + for (i = 0; i < rename_dst_nr; i++) + insert_file_table(&file_table, 1, i, rename_dst[i].two); + + /* Find the renames */ + i = for_each_hash(&file_table, find_same_files); + + /* .. and free the hash data structure */ + free_hash(&file_table); + + return i; } void diffcore_rename(struct diff_options *options) @@ -270,12 +388,11 @@ void diffcore_rename(struct diff_options *options) struct diff_queue_struct *q = &diff_queued_diff; struct diff_queue_struct outq; struct diff_score *mx; - int i, j, rename_count, contents_too; + int i, j, rename_count; int num_create, num_src, dst_cnt; if (!minimum_score) minimum_score = DEFAULT_RENAME_SCORE; - rename_count = 0; for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; @@ -289,81 +406,66 @@ void diffcore_rename(struct diff_options *options) locate_rename_dst(p->two, 1); } else if (!DIFF_FILE_VALID(p->two)) { - /* If the source is a broken "delete", and + /* + * If the source is a broken "delete", and * they did not really want to get broken, * that means the source actually stays. + * So we increment the "rename_used" score + * by one, to indicate ourselves as a user + */ + if (p->broken_pair && !p->score) + p->one->rename_used++; + register_rename_src(p->one, p->score); + } + else if (detect_rename == DIFF_DETECT_COPY) { + /* + * Increment the "rename_used" score by + * one, to indicate ourselves as a user. */ - int stays = (p->broken_pair && !p->score); - register_rename_src(p->one, stays, p->score); + p->one->rename_used++; + register_rename_src(p->one, p->score); } - else if (detect_rename == DIFF_DETECT_COPY) - register_rename_src(p->one, 1, p->score); } if (rename_dst_nr == 0 || rename_src_nr == 0) goto cleanup; /* nothing to do */ /* + * We really want to cull the candidates list early + * with cheap tests in order to avoid doing deltas. + */ + rename_count = find_exact_renames(); + + /* Did we only want exact renames? */ + if (minimum_score == MAX_SCORE) + goto cleanup; + + /* + * Calculate how many renames are left (but all the source + * files still remain as options for rename/copies!) + */ + num_create = (rename_dst_nr - rename_count); + num_src = rename_src_nr; + + /* All done? */ + if (!num_create) + goto cleanup; + + /* * This basically does a test for the rename matrix not * growing larger than a "rename_limit" square matrix, ie: * - * rename_dst_nr * rename_src_nr > rename_limit * rename_limit + * num_create * num_src > rename_limit * rename_limit * * but handles the potential overflow case specially (and we * assume at least 32-bit integers) */ if (rename_limit <= 0 || rename_limit > 32767) rename_limit = 32767; - if (rename_dst_nr > rename_limit && rename_src_nr > rename_limit) + if (num_create > rename_limit && num_src > rename_limit) goto cleanup; - if (rename_dst_nr * rename_src_nr > rename_limit * rename_limit) + if (num_create * num_src > rename_limit * rename_limit) goto cleanup; - /* We really want to cull the candidates list early - * with cheap tests in order to avoid doing deltas. - * The first round matches up the up-to-date entries, - * and then during the second round we try to match - * cache-dirty entries as well. - */ - for (contents_too = 0; contents_too < 2; contents_too++) { - for (i = 0; i < rename_dst_nr; i++) { - struct diff_filespec *two = rename_dst[i].two; - if (rename_dst[i].pair) - continue; /* dealt with an earlier round */ - for (j = 0; j < rename_src_nr; j++) { - int k; - struct diff_filespec *one = rename_src[j].one; - if (!is_exact_match(one, two, contents_too)) - continue; - - /* see if there is a basename match, too */ - for (k = j; k < rename_src_nr; k++) { - one = rename_src[k].one; - if (basename_same(one, two) && - is_exact_match(one, two, - contents_too)) { - j = k; - break; - } - } - - record_rename_pair(i, j, (int)MAX_SCORE); - rename_count++; - break; /* we are done with this entry */ - } - } - } - - /* Have we run out the created file pool? If so we can avoid - * doing the delta matrix altogether. - */ - if (rename_count == rename_dst_nr) - goto cleanup; - - if (minimum_score == MAX_SCORE) - goto cleanup; - - num_create = (rename_dst_nr - rename_count); - num_src = rename_src_nr; mx = xmalloc(sizeof(*mx) * num_create * num_src); for (dst_cnt = i = 0; i < rename_dst_nr; i++) { int base = dst_cnt * num_src; @@ -452,16 +554,7 @@ void diffcore_rename(struct diff_options *options) pair_to_free = p; } else { - for (j = 0; j < rename_dst_nr; j++) { - if (!rename_dst[j].pair) - continue; - if (strcmp(rename_dst[j].pair-> - one->path, - p->one->path)) - continue; - break; - } - if (j < rename_dst_nr) + if (p->one->rename_used) /* this path remains */ pair_to_free = p; } @@ -487,27 +580,8 @@ void diffcore_rename(struct diff_options *options) *q = outq; diff_debug_queue("done collapsing", q); - /* We need to see which rename source really stays here; - * earlier we only checked if the path is left in the result, - * but even if a path remains in the result, if that is coming - * from copying something else on top of it, then the original - * source is lost and does not stay. - */ - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (DIFF_PAIR_RENAME(p) && p->source_stays) { - /* If one appears as the target of a rename-copy, - * then mark p->source_stays = 0; otherwise - * leave it as is. - */ - p->source_stays = compute_stays(q, p->one); - } - } - - for (i = 0; i < rename_dst_nr; i++) { - diff_free_filespec_data(rename_dst[i].two); - free(rename_dst[i].two); - } + for (i = 0; i < rename_dst_nr; i++) + free_filespec(rename_dst[i].two); free(rename_dst); rename_dst = NULL; diff --git a/diffcore.h b/diffcore.h index eb618b1ec..cc96c2073 100644 --- a/diffcore.h +++ b/diffcore.h @@ -29,7 +29,9 @@ struct diff_filespec { void *cnt_data; const char *funcname_pattern_ident; unsigned long size; + int count; /* Reference count */ int xfrm_flags; /* for use by the xfrm */ + int rename_used; /* Count of rename users */ unsigned short mode; /* file mode */ unsigned sha1_valid : 1; /* if true, use sha1 and trust mode; * if false, use the name and read from @@ -43,6 +45,7 @@ struct diff_filespec { }; extern struct diff_filespec *alloc_filespec(const char *); +extern void free_filespec(struct diff_filespec *); extern void fill_filespec(struct diff_filespec *, const unsigned char *, unsigned short); @@ -56,7 +59,6 @@ struct diff_filepair { struct diff_filespec *two; unsigned short int score; char status; /* M C R N D U (see Documentation/diff-format.txt) */ - unsigned source_stays : 1; /* all of R/C are copies */ unsigned broken_pair : 1; unsigned renamed_pair : 1; unsigned is_unmerged : 1; @@ -118,14 +118,32 @@ int match_pathspec(const char **pathspec, const char *name, int namelen, int pre return retval; } +static int no_wildcard(const char *string) +{ + return string[strcspn(string, "*?[{")] == '\0'; +} + void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which) { struct exclude *x = xmalloc(sizeof (*x)); + x->to_exclude = 1; + if (*string == '!') { + x->to_exclude = 0; + string++; + } x->pattern = string; + x->patternlen = strlen(string); x->base = base; x->baselen = baselen; + x->flags = 0; + if (!strchr(string, '/')) + x->flags |= EXC_FLAG_NODIR; + if (no_wildcard(string)) + x->flags |= EXC_FLAG_NOWILDCARD; + if (*string == '*' && no_wildcard(string+1)) + x->flags |= EXC_FLAG_ENDSWITH; if (which->nr == which->alloc) { which->alloc = alloc_nr(which->alloc); which->excludes = xrealloc(which->excludes, @@ -209,7 +227,7 @@ void pop_exclude_per_directory(struct dir_struct *dir, int stk) * Return 1 for exclude, 0 for include and -1 for undecided. */ static int excluded_1(const char *pathname, - int pathlen, + int pathlen, const char *basename, struct exclude_list *el) { int i; @@ -218,19 +236,21 @@ static int excluded_1(const char *pathname, for (i = el->nr - 1; 0 <= i; i--) { struct exclude *x = el->excludes[i]; const char *exclude = x->pattern; - int to_exclude = 1; - - if (*exclude == '!') { - to_exclude = 0; - exclude++; - } + int to_exclude = x->to_exclude; - if (!strchr(exclude, '/')) { + if (x->flags & EXC_FLAG_NODIR) { /* match basename */ - const char *basename = strrchr(pathname, '/'); - basename = (basename) ? basename+1 : pathname; - if (fnmatch(exclude, basename, 0) == 0) - return to_exclude; + if (x->flags & EXC_FLAG_NOWILDCARD) { + if (!strcmp(exclude, basename)) + return to_exclude; + } else if (x->flags & EXC_FLAG_ENDSWITH) { + if (x->patternlen - 1 <= pathlen && + !strcmp(exclude + 1, pathname + pathlen - x->patternlen + 1)) + return to_exclude; + } else { + if (fnmatch(exclude, basename, 0) == 0) + return to_exclude; + } } else { /* match with FNM_PATHNAME: @@ -246,9 +266,14 @@ static int excluded_1(const char *pathname, strncmp(pathname, x->base, baselen)) continue; - if (fnmatch(exclude, pathname+baselen, - FNM_PATHNAME) == 0) - return to_exclude; + if (x->flags & EXC_FLAG_NOWILDCARD) { + if (!strcmp(exclude, pathname + baselen)) + return to_exclude; + } else { + if (fnmatch(exclude, pathname+baselen, + FNM_PATHNAME) == 0) + return to_exclude; + } } } } @@ -259,9 +284,11 @@ int excluded(struct dir_struct *dir, const char *pathname) { int pathlen = strlen(pathname); int st; + const char *basename = strrchr(pathname, '/'); + basename = (basename) ? basename+1 : pathname; for (st = EXC_CMDL; st <= EXC_FILE; st++) { - switch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) { + switch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) { case 0: return 0; case 1: @@ -443,6 +470,24 @@ static int in_pathspec(const char *path, int len, const struct path_simplify *si return 0; } +static int get_dtype(struct dirent *de, const char *path) +{ + int dtype = DTYPE(de); + struct stat st; + + if (dtype != DT_UNKNOWN) + return dtype; + if (lstat(path, &st)) + return dtype; + if (S_ISREG(st.st_mode)) + return DT_REG; + if (S_ISDIR(st.st_mode)) + return DT_DIR; + if (S_ISLNK(st.st_mode)) + return DT_LNK; + return dtype; +} + /* * Read a directory tree. We currently ignore anything but * directories, regular files and symlinks. That's because git @@ -466,7 +511,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co exclude_stk = push_exclude_per_directory(dir, base, baselen); while ((de = readdir(fdir)) != NULL) { - int len; + int len, dtype; int exclude; if ((de->d_name[0] == '.') && @@ -486,24 +531,30 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co if (exclude && dir->collect_ignored && in_pathspec(fullname, baselen + len, simplify)) dir_add_ignored(dir, fullname, baselen + len); - if (exclude != dir->show_ignored) { - if (!dir->show_ignored || DTYPE(de) != DT_DIR) { + + /* + * Excluded? If we don't explicitly want to show + * ignored files, ignore it + */ + if (exclude && !dir->show_ignored) + continue; + + dtype = get_dtype(de, fullname); + + /* + * Do we want to see just the ignored files? + * We still need to recurse into directories, + * even if we don't ignore them, since the + * directory may contain files that we do.. + */ + if (!exclude && dir->show_ignored) { + if (dtype != DT_DIR) continue; - } } - switch (DTYPE(de)) { - struct stat st; + switch (dtype) { default: continue; - case DT_UNKNOWN: - if (lstat(fullname, &st)) - continue; - if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) - break; - if (!S_ISDIR(st.st_mode)) - continue; - /* fallthrough */ case DT_DIR: memcpy(fullname + baselen + len, "/", 2); len++; @@ -17,13 +17,20 @@ struct dir_entry { char name[FLEX_ARRAY]; /* more */ }; +#define EXC_FLAG_NODIR 1 +#define EXC_FLAG_NOWILDCARD 2 +#define EXC_FLAG_ENDSWITH 4 + struct exclude_list { int nr; int alloc; struct exclude { const char *pattern; + int patternlen; const char *base; int baselen; + int to_exclude; + int flags; } **excludes; }; @@ -119,8 +119,10 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout */ strbuf_init(&buf, 0); if (convert_to_working_tree(ce->name, new, size, &buf)) { + size_t newsize = 0; free(new); - new = strbuf_detach(&buf, &size); + new = strbuf_detach(&buf, &newsize); + size = newsize; } if (to_tempfile) { diff --git a/exec_cmd.c b/exec_cmd.c index 9b74ed2f4..2d0a75851 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -5,11 +5,11 @@ extern char **environ; static const char *builtin_exec_path = GIT_EXEC_PATH; -static const char *current_exec_path; +static const char *argv_exec_path; -void git_set_exec_path(const char *exec_path) +void git_set_argv_exec_path(const char *exec_path) { - current_exec_path = exec_path; + argv_exec_path = exec_path; } @@ -18,8 +18,8 @@ const char *git_exec_path(void) { const char *env; - if (current_exec_path) - return current_exec_path; + if (argv_exec_path) + return argv_exec_path; env = getenv(EXEC_PATH_ENVIRONMENT); if (env && *env) { @@ -29,85 +29,69 @@ const char *git_exec_path(void) return builtin_exec_path; } +static void add_path(struct strbuf *out, const char *path) +{ + if (path && *path) { + if (is_absolute_path(path)) + strbuf_addstr(out, path); + else + strbuf_addstr(out, make_absolute_path(path)); + + strbuf_addch(out, ':'); + } +} + +void setup_path(const char *cmd_path) +{ + const char *old_path = getenv("PATH"); + struct strbuf new_path; + + strbuf_init(&new_path, 0); + + add_path(&new_path, argv_exec_path); + add_path(&new_path, getenv(EXEC_PATH_ENVIRONMENT)); + add_path(&new_path, builtin_exec_path); + add_path(&new_path, cmd_path); + + if (old_path) + strbuf_addstr(&new_path, old_path); + else + strbuf_addstr(&new_path, "/usr/local/bin:/usr/bin:/bin"); + + setenv("PATH", new_path.buf, 1); + + strbuf_release(&new_path); +} int execv_git_cmd(const char **argv) { - char git_command[PATH_MAX + 1]; - int i; - const char *paths[] = { current_exec_path, - getenv(EXEC_PATH_ENVIRONMENT), - builtin_exec_path }; - - for (i = 0; i < ARRAY_SIZE(paths); ++i) { - size_t len; - int rc; - const char *exec_dir = paths[i]; - const char *tmp; - - if (!exec_dir || !*exec_dir) continue; - - if (*exec_dir != '/') { - if (!getcwd(git_command, sizeof(git_command))) { - fprintf(stderr, "git: cannot determine " - "current directory: %s\n", - strerror(errno)); - break; - } - len = strlen(git_command); - - /* Trivial cleanup */ - while (!prefixcmp(exec_dir, "./")) { - exec_dir += 2; - while (*exec_dir == '/') - exec_dir++; - } - - rc = snprintf(git_command + len, - sizeof(git_command) - len, "/%s", - exec_dir); - if (rc < 0 || rc >= sizeof(git_command) - len) { - fprintf(stderr, "git: command name given " - "is too long.\n"); - break; - } - } else { - if (strlen(exec_dir) + 1 > sizeof(git_command)) { - fprintf(stderr, "git: command name given " - "is too long.\n"); - break; - } - strcpy(git_command, exec_dir); - } - - len = strlen(git_command); - rc = snprintf(git_command + len, sizeof(git_command) - len, - "/git-%s", argv[0]); - if (rc < 0 || rc >= sizeof(git_command) - len) { - fprintf(stderr, - "git: command name given is too long.\n"); - break; - } + struct strbuf cmd; + const char *tmp; - /* argv[0] must be the git command, but the argv array - * belongs to the caller, and my be reused in - * subsequent loop iterations. Save argv[0] and - * restore it on error. - */ + strbuf_init(&cmd, 0); + strbuf_addf(&cmd, "git-%s", argv[0]); - tmp = argv[0]; - argv[0] = git_command; + /* + * argv[0] must be the git command, but the argv array + * belongs to the caller, and may be reused in + * subsequent loop iterations. Save argv[0] and + * restore it on error. + */ + tmp = argv[0]; + argv[0] = cmd.buf; - trace_argv_printf(argv, -1, "trace: exec:"); + trace_argv_printf(argv, -1, "trace: exec:"); - /* execve() can only ever return if it fails */ - execve(git_command, (char **)argv, environ); + /* execvp() can only ever return if it fails */ + execvp(cmd.buf, (char **)argv); - trace_printf("trace: exec failed: %s\n", strerror(errno)); + trace_printf("trace: exec failed: %s\n", strerror(errno)); - argv[0] = tmp; - } - return -1; + argv[0] = tmp; + strbuf_release(&cmd); + + return -1; } diff --git a/exec_cmd.h b/exec_cmd.h index 849a8395a..a892355c8 100644 --- a/exec_cmd.h +++ b/exec_cmd.h @@ -1,8 +1,9 @@ #ifndef GIT_EXEC_CMD_H #define GIT_EXEC_CMD_H -extern void git_set_exec_path(const char *exec_path); +extern void git_set_argv_exec_path(const char *exec_path); extern const char* git_exec_path(void); +extern void setup_path(const char *); extern int execv_git_cmd(const char **argv); /* NULL terminated */ extern int execl_git_cmd(const char *cmd, ...); diff --git a/fast-import.c b/fast-import.c index e9c80be4c..f93d7d6c9 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1616,6 +1616,7 @@ static void cmd_data(struct strbuf *sb) char *term = xstrdup(command_buf.buf + 5 + 2); size_t term_len = command_buf.len - 5 - 2; + strbuf_detach(&command_buf, NULL); for (;;) { if (strbuf_getline(&command_buf, stdin, '\n') == EOF) die("EOF in data (terminator '%s' not found)", term); @@ -1817,7 +1818,7 @@ static void file_change_m(struct branch *b) } else if (oe) { if (oe->type != OBJ_BLOB) die("Not a blob (actually a %s): %s", - command_buf.buf, typename(oe->type)); + typename(oe->type), command_buf.buf); } else { enum object_type type = sha1_object_info(sha1, NULL); if (type < 0) @@ -394,9 +394,7 @@ do stop_here $this fi - echo printf 'Applying %s\n' "$SUBJECT" - echo case "$resolved" in '') @@ -452,10 +450,8 @@ do fi tree=$(git write-tree) && - echo Wrote tree $tree && parent=$(git rev-parse --verify HEAD) && commit=$(git commit-tree $tree -p $parent <"$dotest/final-commit") && - echo Committed: $commit && git update-ref -m "$GIT_REFLOG_ACTION: $SUBJECT" HEAD $commit $parent || stop_here $this diff --git a/git-bisect.sh b/git-bisect.sh index 388887a55..b74f44df6 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -1,12 +1,14 @@ #!/bin/sh -USAGE='[start|bad|good|next|reset|visualize|replay|log|run]' +USAGE='[start|bad|good|skip|next|reset|visualize|replay|log|run]' LONG_USAGE='git bisect start [<bad> [<good>...]] [--] [<pathspec>...] reset bisect state and start bisection. git bisect bad [<rev>] mark <rev> a known-bad revision. git bisect good [<rev>...] mark <rev>... known-good revisions. +git bisect skip [<rev>...] + mark <rev>... untestable revisions. git bisect next find next bisection to test and check it out. git bisect reset [<branch>] @@ -64,7 +66,7 @@ bisect_start() { branch=`cat "$GIT_DIR/head-name"` else branch=master - fi + fi git checkout $branch || exit ;; refs/heads/*) @@ -95,75 +97,74 @@ bisect_start() { arg="$1" case "$arg" in --) - shift + shift break ;; *) - rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || { + rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || { test $has_double_dash -eq 1 && die "'$arg' does not appear to be a valid revision" break } - if [ $bad_seen -eq 0 ]; then - bad_seen=1 - bisect_write_bad "$rev" - else - bisect_write_good "$rev" - fi - shift + case $bad_seen in + 0) state='bad' ; bad_seen=1 ;; + *) state='good' ;; + esac + bisect_write "$state" "$rev" 'nolog' + shift ;; esac - done + done sq "$@" >"$GIT_DIR/BISECT_NAMES" echo "git-bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" bisect_auto_next } -bisect_bad() { - bisect_autostart - case "$#" in - 0) - rev=$(git rev-parse --verify HEAD) ;; - 1) - rev=$(git rev-parse --verify "$1^{commit}") ;; - *) - usage ;; - esac || exit - bisect_write_bad "$rev" - echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG" - bisect_auto_next -} - -bisect_write_bad() { - rev="$1" - echo "$rev" >"$GIT_DIR/refs/bisect/bad" - echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" +bisect_write() { + state="$1" + rev="$2" + nolog="$3" + case "$state" in + bad) tag="$state" ;; + good|skip) tag="$state"-"$rev" ;; + *) die "Bad bisect_write argument: $state" ;; + esac + echo "$rev" >"$GIT_DIR/refs/bisect/$tag" + echo "# $state: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" + test -z "$nolog" && echo "git-bisect $state $rev" >>"$GIT_DIR/BISECT_LOG" } -bisect_good() { +bisect_state() { bisect_autostart - case "$#" in - 0) revs=$(git rev-parse --verify HEAD) || exit ;; - *) revs=$(git rev-parse --revs-only --no-flags "$@") && - test '' != "$revs" || die "Bad rev input: $@" ;; + state=$1 + case "$#,$state" in + 0,*) + die "Please call 'bisect_state' with at least one argument." ;; + 1,bad|1,good|1,skip) + rev=$(git rev-parse --verify HEAD) || + die "Bad rev input: HEAD" + bisect_write "$state" "$rev" ;; + 2,bad) + rev=$(git rev-parse --verify "$2^{commit}") || + die "Bad rev input: $2" + bisect_write "$state" "$rev" ;; + *,good|*,skip) + shift + revs=$(git rev-parse --revs-only --no-flags "$@") && + test '' != "$revs" || die "Bad rev input: $@" + for rev in $revs + do + rev=$(git rev-parse --verify "$rev^{commit}") || + die "Bad rev commit: $rev^{commit}" + bisect_write "$state" "$rev" + done ;; + *) + usage ;; esac - for rev in $revs - do - rev=$(git rev-parse --verify "$rev^{commit}") || exit - bisect_write_good "$rev" - echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG" - - done bisect_auto_next } -bisect_write_good() { - rev="$1" - echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev" - echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" -} - bisect_next_check() { missing_good= missing_bad= git show-ref -q --verify refs/bisect/bad || missing_bad=t @@ -206,17 +207,97 @@ bisect_auto_next() { bisect_next_check && bisect_next || : } +filter_skipped() { + _eval="$1" + _skip="$2" + + if [ -z "$_skip" ]; then + eval $_eval + return + fi + + # Let's parse the output of: + # "git rev-list --bisect-vars --bisect-all ..." + eval $_eval | while read hash line + do + case "$VARS,$FOUND,$TRIED,$hash" in + # We display some vars. + 1,*,*,*) echo "$hash $line" ;; + + # Split line. + ,*,*,---*) ;; + + # We had nothing to search. + ,,,bisect_rev*) + echo "bisect_rev=" + VARS=1 + ;; + + # We did not find a good bisect rev. + # This should happen only if the "bad" + # commit is also a "skip" commit. + ,,*,bisect_rev*) + echo "bisect_rev=$TRIED" + VARS=1 + ;; + + # We are searching. + ,,*,*) + TRIED="${TRIED:+$TRIED|}$hash" + case "$_skip" in + *$hash*) ;; + *) + echo "bisect_rev=$hash" + echo "bisect_tried=\"$TRIED\"" + FOUND=1 + ;; + esac + ;; + + # We have already found a rev to be tested. + ,1,*,bisect_rev*) VARS=1 ;; + ,1,*,*) ;; + + # ??? + *) die "filter_skipped error " \ + "VARS: '$VARS' " \ + "FOUND: '$FOUND' " \ + "TRIED: '$TRIED' " \ + "hash: '$hash' " \ + "line: '$line'" + ;; + esac + done +} + +exit_if_skipped_commits () { + _tried=$1 + if expr "$_tried" : ".*[|].*" > /dev/null ; then + echo "There are only 'skip'ped commit left to test." + echo "The first bad commit could be any of:" + echo "$_tried" | sed -e 's/[|]/\n/g' + echo "We cannot bisect more!" + exit 2 + fi +} + bisect_next() { - case "$#" in 0) ;; *) usage ;; esac + case "$#" in 0) ;; *) usage ;; esac bisect_autostart bisect_next_check good + skip=$(git for-each-ref --format='%(objectname)' \ + "refs/bisect/skip-*" | tr '[\012]' ' ') || exit + + BISECT_OPT='' + test -n "$skip" && BISECT_OPT='--bisect-all' + bad=$(git rev-parse --verify refs/bisect/bad) && good=$(git for-each-ref --format='^%(objectname)' \ "refs/bisect/good-*" | tr '[\012]' ' ') && - eval="git rev-list --bisect-vars $good $bad --" && + eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" && eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" && - eval=$(eval "$eval") && + eval=$(filter_skipped "$eval" "$skip") && eval "$eval" || exit if [ -z "$bisect_rev" ]; then @@ -224,11 +305,16 @@ bisect_next() { exit 1 fi if [ "$bisect_rev" = "$bad" ]; then + exit_if_skipped_commits "$bisect_tried" echo "$bisect_rev is first bad commit" git diff-tree --pretty $bisect_rev exit 0 fi + # We should exit here only if the "bad" + # commit is also a "skip" commit (see above). + exit_if_skipped_commits "$bisect_rev" + echo "Bisecting: $bisect_nr revisions left to test after this" echo "$bisect_rev" >"$GIT_DIR/refs/heads/new-bisect" git checkout -q new-bisect || exit @@ -250,12 +336,10 @@ bisect_reset() { else branch=master fi ;; - 1) git show-ref --verify --quiet -- "refs/heads/$1" || { - echo >&2 "$1 does not seem to be a valid branch" - exit 1 - } + 1) git show-ref --verify --quiet -- "refs/heads/$1" || + die "$1 does not seem to be a valid branch" branch="$1" ;; - *) + *) usage ;; esac if git checkout "$branch"; then @@ -273,10 +357,7 @@ bisect_clean_state() { } bisect_replay () { - test -r "$1" || { - echo >&2 "cannot read $1 for replaying" - exit 1 - } + test -r "$1" || die "cannot read $1 for replaying" bisect_reset while read bisect command rev do @@ -284,21 +365,11 @@ bisect_replay () { case "$command" in start) cmd="bisect_start $rev" - eval "$cmd" - ;; - good) - echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev" - echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" - echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG" - ;; - bad) - echo "$rev" >"$GIT_DIR/refs/bisect/bad" - echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" - echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG" - ;; + eval "$cmd" ;; + good|bad|skip) + bisect_write "$command" "$rev" ;; *) - echo >&2 "?? what are you talking about?" - exit 1 ;; + die "?? what are you talking about?" ;; esac done <"$1" bisect_auto_next @@ -320,24 +391,31 @@ bisect_run () { exit $res fi - # Use "bisect_good" or "bisect_bad" - # depending on run success or failure. - if [ $res -gt 0 ]; then - next_bisect='bisect_bad' + # Find current state depending on run success or failure. + # A special exit code of 125 means cannot test. + if [ $res -eq 125 ]; then + state='skip' + elif [ $res -gt 0 ]; then + state='bad' else - next_bisect='bisect_good' + state='good' fi - # We have to use a subshell because bisect_good or - # bisect_bad functions can exit. - ( $next_bisect > "$GIT_DIR/BISECT_RUN" ) + # We have to use a subshell because "bisect_state" can exit. + ( bisect_state $state > "$GIT_DIR/BISECT_RUN" ) res=$? cat "$GIT_DIR/BISECT_RUN" + if grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \ + > /dev/null; then + echo >&2 "bisect run cannot continue any more" + exit $res + fi + if [ $res -ne 0 ]; then echo >&2 "bisect run failed:" - echo >&2 "$next_bisect exited with error code $res" + echo >&2 "'bisect_state $state' exited with error code $res" exit $res fi @@ -359,10 +437,8 @@ case "$#" in case "$cmd" in start) bisect_start "$@" ;; - bad) - bisect_bad "$@" ;; - good) - bisect_good "$@" ;; + bad|good|skip) + bisect_state "$cmd" "$@" ;; next) # Not sure we want "next" at the UI level anymore. bisect_next "$@" ;; diff --git a/git-clone.sh b/git-clone.sh index 5e582fe24..0ea3c24f5 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -28,7 +28,8 @@ get_repo_base() { ) 2>/dev/null } -if [ -n "$GIT_SSL_NO_VERIFY" ]; then +if [ -n "$GIT_SSL_NO_VERIFY" -o \ + "`git config --bool http.sslVerify`" = false ]; then curl_extra_args="-k" fi diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index a33fa8d4c..f284c88a4 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -30,11 +30,6 @@ if ($opt_d) { @cvs = ('cvs'); } -# setup a tempdir -our ($tmpdir, $tmpdirname) = tempdir('git-cvsapplycommit-XXXXXX', - TMPDIR => 1, - CLEANUP => 1); - # resolve target commit my $commit; $commit = pop @ARGV; @@ -134,7 +129,7 @@ my $context = $opt_p ? '' : '-C1'; print "Checking if patch will apply\n"; my @stat; -open APPLY, "GIT_DIR= git-apply $context --binary --summary --numstat<.cvsexportcommit.diff|" || die "cannot patch"; +open APPLY, "GIT_DIR= git-apply $context --summary --numstat<.cvsexportcommit.diff|" || die "cannot patch"; @stat=<APPLY>; close APPLY || die "Cannot patch"; my (@bfiles,@files,@afiles,@dfiles); @@ -220,10 +215,21 @@ if ($dirty) { } print "Applying\n"; -`GIT_DIR= git-apply $context --binary --summary --numstat --apply <.cvsexportcommit.diff` || die "cannot patch"; +`GIT_DIR= git-apply $context --summary --numstat --apply <.cvsexportcommit.diff` || die "cannot patch"; print "Patch applied successfully. Adding new files and directories to CVS\n"; my $dirtypatch = 0; + +# +# We have to add the directories in order otherwise we will have +# problems when we try and add the sub-directory of a directory we +# have not added yet. +# +# Luckily this is easy to deal with by sorting the directories and +# dealing with the shortest ones first. +# +@dirs = sort { length $a <=> length $b} @dirs; + foreach my $d (@dirs) { if (system(@cvs,'add',$d)) { $dirtypatch = 1; diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 13dbd27a8..0d55fec04 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -145,8 +145,10 @@ if ($state->{method} eq 'pserver') { } my $request = $1; $line = <STDIN>; chomp $line; - req_Root('root', $line) # reuse Root - or die "E Invalid root $line \n"; + unless (req_Root('root', $line)) { # reuse Root + print "E Invalid root $line \n"; + exit 1; + } $line = <STDIN>; chomp $line; unless ($line eq 'anonymous') { print "E Only anonymous user allowed via pserver\n"; diff --git a/git-filter-branch.sh b/git-filter-branch.sh index a12f6c2d4..ffcc408ee 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -94,6 +94,10 @@ USAGE="[--env-filter <command>] [--tree-filter <command>] \ . git-sh-setup +git diff-files --quiet && + git diff-index --cached --quiet HEAD || + die "Cannot rewrite branch(es) with a dirty working directory." + tempdir=.git-rewrite filter_env= filter_tree= @@ -196,6 +200,9 @@ do esac done < "$tempdir"/backup-refs +ORIG_GIT_DIR="$GIT_DIR" +ORIG_GIT_WORK_TREE="$GIT_WORK_TREE" +ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE" export GIT_DIR GIT_WORK_TREE=. # These refs should be updated if their heads were rewritten @@ -413,4 +420,12 @@ echo test $count -gt 0 && echo "These refs were rewritten:" git show-ref | grep ^"$orig_namespace" +unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE +test -z "$ORIG_GIT_DIR" || GIT_DIR="$ORIG_GIT_DIR" && export GIT_DIR +test -z "$ORIG_GIT_WORK_TREE" || GIT_WORK_TREE="$ORIG_GIT_WORK_TREE" && + export GIT_WORK_TREE +test -z "$ORIG_GIT_INDEX_FILE" || GIT_INDEX_FILE="$ORIG_GIT_INDEX_FILE" && + export GIT_INDEX_FILE +git read-tree -u -m HEAD + exit $ret diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index f789e91b6..9335a9761 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -305,7 +305,7 @@ proc _which {what} { global env _search_exe _search_path if {$_search_path eq {}} { - if {[is_Cygwin]} { + if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} { set _search_path [split [exec cygpath \ --windows \ --path \ @@ -498,7 +498,11 @@ proc rmsel_tag {text} { set _git [_which git] if {$_git eq {}} { catch {wm withdraw .} - error_popup "Cannot find git in PATH." + tk_messageBox \ + -icon error \ + -type ok \ + -title [mc "git-gui: fatal error"] \ + -message [mc "Cannot find git in PATH."] exit 1 } @@ -534,6 +538,7 @@ regsub -- {-dirty$} $_git_version {} _git_version regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version regsub {\.rc[0-9]+$} $_git_version {} _git_version regsub {\.GIT$} $_git_version {} _git_version +regsub {\.[a-zA-Z]+\.[0-9]+$} $_git_version {} _git_version if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} { catch {wm withdraw .} @@ -903,6 +908,35 @@ proc rescan {after {honor_trustmtime 1}} { } } +if {[is_Cygwin]} { + set is_git_info_link {} + set is_git_info_exclude {} + proc have_info_exclude {} { + global is_git_info_link is_git_info_exclude + + if {$is_git_info_link eq {}} { + set is_git_info_link [file isfile [gitdir info.lnk]] + } + + if {$is_git_info_link} { + if {$is_git_info_exclude eq {}} { + if {[catch {exec test -f [gitdir info exclude]}]} { + set is_git_info_exclude 0 + } else { + set is_git_info_exclude 1 + } + } + return $is_git_info_exclude + } else { + return [file readable [gitdir info exclude]] + } + } +} else { + proc have_info_exclude {} { + return [file readable [gitdir info exclude]] + } +} + proc rescan_stage2 {fd after} { global rescan_active buf_rdi buf_rdf buf_rlo @@ -913,9 +947,8 @@ proc rescan_stage2 {fd after} { } set ls_others [list --exclude-per-directory=.gitignore] - set info_exclude [gitdir info exclude] - if {[file readable $info_exclude]} { - lappend ls_others "--exclude-from=$info_exclude" + if {[have_info_exclude]} { + lappend ls_others "--exclude-from=[gitdir info exclude]" } set user_exclude [get_config core.excludesfile] if {$user_exclude ne {} && [file readable $user_exclude]} { @@ -1093,11 +1126,17 @@ proc mapdesc {state path} { } proc ui_status {msg} { - $::main_status show $msg + global main_status + if {[info exists main_status]} { + $main_status show $msg + } } proc ui_ready {{test {}}} { - $::main_status show {Ready.} $test + global main_status + if {[info exists main_status]} { + $main_status show [mc "Ready."] $test + } } proc escape_path {path} { @@ -1436,7 +1475,27 @@ proc do_gitk {revs} { if {! [file exists $exe]} { error_popup "Unable to start gitk:\n\n$exe does not exist" } else { + global env + + if {[info exists env(GIT_DIR)]} { + set old_GIT_DIR $env(GIT_DIR) + } else { + set old_GIT_DIR {} + } + + set pwd [pwd] + cd [file dirname [gitdir]] + set env(GIT_DIR) [file tail [gitdir]] + eval exec $cmd $revs & + + if {$old_GIT_DIR eq {}} { + unset env(GIT_DIR) + } else { + set env(GIT_DIR) $old_GIT_DIR + } + cd $pwd + ui_status $::starting_gitk_msg after 10000 { ui_ready $starting_gitk_msg @@ -1648,7 +1707,7 @@ proc apply_config {} { set font [lindex $option 1] if {[catch { foreach {cn cv} $repo_config(gui.$name) { - font configure $font $cn $cv + font configure $font $cn $cv -weight normal } } err]} { error_popup "Invalid font specified in gui.$name:\n\n$err" diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl index f857a2ff5..57238129e 100644 --- a/git-gui/lib/commit.tcl +++ b/git-gui/lib/commit.tcl @@ -253,7 +253,7 @@ proc commit_committree {fd_wt curHEAD msg} { global repo_config gets $fd_wt tree_id - if {$tree_id eq {} || [catch {close $fd_wt} err]} { + if {[catch {close $fd_wt} err]} { error_popup "write-tree failed:\n\n$err" ui_status {Commit failed.} unlock_index diff --git a/git-gui/lib/console.tcl b/git-gui/lib/console.tcl index 6f718fbac..b038a7835 100644 --- a/git-gui/lib/console.tcl +++ b/git-gui/lib/console.tcl @@ -122,7 +122,7 @@ method _read {fd after} { } else { $w.m.t delete $console_cr end $w.m.t insert end "\n" - $w.m.t insert end [string range $buf $c $cr] + $w.m.t insert end [string range $buf $c [expr {$cr - 1}]] set c $cr incr c } diff --git a/git-gui/lib/status_bar.tcl b/git-gui/lib/status_bar.tcl index 72a8fe1fd..3bf79eb6e 100644 --- a/git-gui/lib/status_bar.tcl +++ b/git-gui/lib/status_bar.tcl @@ -69,7 +69,10 @@ method update_meter {buf} { set prior [string range $meter 0 $r] set meter [string range $meter [expr {$r + 1}] end] - if {[regexp "\\((\\d+)/(\\d+)\\)\\s+done\r\$" $prior _j a b]} { + set p "\\((\\d+)/(\\d+)\\)" + if {[regexp ":\\s*\\d+% $p\(?:, done.\\s*\n|\\s*\r)\$" $prior _j a b]} { + update $this $a $b + } elseif {[regexp "$p\\s+done\r\$" $prior _j a b]} { update $this $a $b } } diff --git a/git-instaweb.sh b/git-instaweb.sh index 2e4eeccac..95c3e5aa1 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -30,8 +30,7 @@ test -z "$port" && port=1234 start_httpd () { httpd_only="`echo $httpd | cut -f1 -d' '`" - if test "`expr index $httpd_only /`" -eq '1' || \ - which $httpd_only >/dev/null + if case "$httpd_only" in /*) : ;; *) which $httpd_only >/dev/null;; esac then $httpd $fqgitdir/gitweb/httpd.conf else diff --git a/git-ls-remote.sh b/git-ls-remote.sh index d56cf92eb..fec70bbf8 100755 --- a/git-ls-remote.sh +++ b/git-ls-remote.sh @@ -54,9 +54,10 @@ tmpdir=$tmp-d case "$peek_repo" in http://* | https://* | ftp://* ) - if [ -n "$GIT_SSL_NO_VERIFY" ]; then - curl_extra_args="-k" - fi + if [ -n "$GIT_SSL_NO_VERIFY" -o \ + "`git config --bool http.sslVerify`" = false ]; then + curl_extra_args="-k" + fi if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \ "`git config --bool http.noEPSV`" = true ]; then curl_extra_args="${curl_extra_args} --disable-epsv" diff --git a/git-mergetool.sh b/git-mergetool.sh index 9f4f3134b..a68b40386 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -192,10 +192,10 @@ merge_file () { case "$merge_tool" in kdiff3) if base_present ; then - (kdiff3 --auto --L1 "$path (Base)" --L2 "$path (Local)" --L3 "$path (Remote)" \ + ("$merge_tool_path" --auto --L1 "$path (Base)" --L2 "$path (Local)" --L3 "$path (Remote)" \ -o "$path" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1) else - (kdiff3 --auto --L1 "$path (Local)" --L2 "$path (Remote)" \ + ("$merge_tool_path" --auto --L1 "$path (Local)" --L2 "$path (Remote)" \ -o "$path" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1) fi status=$? @@ -203,35 +203,35 @@ merge_file () { ;; tkdiff) if base_present ; then - tkdiff -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE" + "$merge_tool_path" -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE" else - tkdiff -o "$path" -- "$LOCAL" "$REMOTE" + "$merge_tool_path" -o "$path" -- "$LOCAL" "$REMOTE" fi status=$? save_backup ;; meld|vimdiff) touch "$BACKUP" - $merge_tool -- "$LOCAL" "$path" "$REMOTE" + "$merge_tool_path" -- "$LOCAL" "$path" "$REMOTE" check_unchanged save_backup ;; gvimdiff) touch "$BACKUP" - gvimdiff -f -- "$LOCAL" "$path" "$REMOTE" + "$merge_tool_path" -f -- "$LOCAL" "$path" "$REMOTE" check_unchanged save_backup ;; xxdiff) touch "$BACKUP" if base_present ; then - xxdiff -X --show-merged-pane \ + "$merge_tool_path" -X --show-merged-pane \ -R 'Accel.SaveAsMerged: "Ctrl-S"' \ -R 'Accel.Search: "Ctrl+F"' \ -R 'Accel.SearchForward: "Ctrl-G"' \ --merged-file "$path" -- "$LOCAL" "$BASE" "$REMOTE" else - xxdiff -X --show-merged-pane \ + "$merge_tool_path" -X --show-merged-pane \ -R 'Accel.SaveAsMerged: "Ctrl-S"' \ -R 'Accel.Search: "Ctrl+F"' \ -R 'Accel.SearchForward: "Ctrl-G"' \ @@ -243,18 +243,28 @@ merge_file () { opendiff) touch "$BACKUP" if base_present; then - opendiff "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$path" | cat + "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$path" | cat else - opendiff "$LOCAL" "$REMOTE" -merge "$path" | cat + "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$path" | cat + fi + check_unchanged + save_backup + ;; + ecmerge) + touch "$BACKUP" + if base_present; then + "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --mode=merge3 --to="$path" + else + "$merge_tool_path" "$LOCAL" "$REMOTE" --mode=merge2 --to="$path" fi check_unchanged save_backup ;; emerge) if base_present ; then - emacs -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$path")" + "$merge_tool_path" -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$path")" else - emacs -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$path")" + "$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$path")" fi status=$? save_backup @@ -297,17 +307,38 @@ do shift done +valid_tool() { + case "$1" in + kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge) + ;; # happy + *) + return 1 + ;; + esac +} + +init_merge_tool_path() { + merge_tool_path=`git config mergetool.$1.path` + if test -z "$merge_tool_path" ; then + case "$1" in + emerge) + merge_tool_path=emacs + ;; + *) + merge_tool_path=$1 + ;; + esac + fi +} + + if test -z "$merge_tool"; then merge_tool=`git config merge.tool` - case "$merge_tool" in - kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | "") - ;; # happy - *) + if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then echo >&2 "git config option merge.tool set to unknown tool: $merge_tool" echo >&2 "Resetting to default..." unset merge_tool - ;; - esac + fi fi if test -z "$merge_tool" ; then @@ -329,40 +360,30 @@ if test -z "$merge_tool" ; then merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff" echo "merge tool candidates: $merge_tool_candidates" for i in $merge_tool_candidates; do - if test $i = emerge ; then - cmd=emacs - else - cmd=$i - fi - if type $cmd > /dev/null 2>&1; then + init_merge_tool_path $i + if type "$merge_tool_path" > /dev/null 2>&1; then merge_tool=$i break fi done if test -z "$merge_tool" ; then - echo "No available merge resolution programs available." + echo "No known merge resolution program available." exit 1 fi +else + if ! valid_tool "$merge_tool"; then + echo >&2 "Unknown merge_tool $merge_tool" + exit 1 + fi + + init_merge_tool_path "$merge_tool" + + if ! type "$merge_tool_path" > /dev/null 2>&1; then + echo "The merge tool $merge_tool is not available as '$merge_tool_path'" + exit 1 + fi fi -case "$merge_tool" in - kdiff3|tkdiff|meld|xxdiff|vimdiff|gvimdiff|opendiff) - if ! type "$merge_tool" > /dev/null 2>&1; then - echo "The merge tool $merge_tool is not available" - exit 1 - fi - ;; - emerge) - if ! type "emacs" > /dev/null 2>&1; then - echo "Emacs is not available" - exit 1 - fi - ;; - *) - echo "Unknown merge tool: $merge_tool" - exit 1 - ;; -esac if test $# -eq 0 ; then files=`git ls-files -u | sed -e 's/^[^ ]* //' | sort -u` diff --git a/git-pull.sh b/git-pull.sh index 74bfc1674..75ec01196 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -4,7 +4,7 @@ # # Fetch one or more remote refs and merge it/them into the current HEAD. -USAGE='[-n | --no-summary] [--no-commit] [-s strategy]... [<fetch-options>] <repo> <head>...' +USAGE='[-n | --no-summary] [--[no-]commit] [--[no-]squash] [--[no-]ff] [-s strategy]... [<fetch-options>] <repo> <head>...' LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEAD.' SUBDIRECTORY_OK=Yes . git-sh-setup @@ -15,7 +15,7 @@ cd_to_toplevel test -z "$(git ls-files -u)" || die "You are in the middle of a conflicted merge." -strategy_args= no_summary= no_commit= squash= +strategy_args= no_summary= no_commit= squash= no_ff= while : do case "$1" in @@ -27,8 +27,16 @@ do ;; --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit) no_commit=--no-commit ;; + --c|--co|--com|--comm|--commi|--commit) + no_commit=--commit ;; --sq|--squ|--squa|--squas|--squash) squash=--squash ;; + --no-sq|--no-squ|--no-squa|--no-squas|--no-squash) + squash=--no-squash ;; + --ff) + no_ff=--ff ;; + --no-ff) + no_ff=--no-ff ;; -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\ --strateg=*|--strategy=*|\ -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) @@ -133,5 +141,5 @@ then fi merge_name=$(git fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit -exec git-merge $no_summary $no_commit $squash $strategy_args \ +exec git-merge $no_summary $no_commit $squash $no_ff $strategy_args \ "$merge_name" HEAD $merge_head diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index df4cedb85..76dc679e6 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -110,13 +110,13 @@ pick_one () { parent_sha1=$(git rev-parse --verify $sha1^) || die "Could not get the parent of $sha1" current_sha1=$(git rev-parse --verify HEAD) - if test $no_ff$current_sha1 = $parent_sha1; then + if test "$no_ff$current_sha1" = "$parent_sha1"; then output git reset --hard $sha1 test "a$1" = a-n && output git reset --soft $current_sha1 sha1=$(git rev-parse --short $sha1) output warn Fast forward to $sha1 else - output git cherry-pick $STRATEGY "$@" + output git cherry-pick "$@" fi } @@ -172,6 +172,8 @@ pick_one_preserving_merges () { author_script=$(get_author_ident_from_commit $sha1) eval "$author_script" msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')" + # No point in merging the first parent, that's HEAD + new_parents=${new_parents# $first_parent} # NEEDSWORK: give rerere a chance if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ @@ -184,7 +186,7 @@ pick_one_preserving_merges () { fi ;; *) - output git cherry-pick $STRATEGY "$@" || + output git cherry-pick "$@" || die_with_patch $sha1 "Could not pick $sha1" ;; esac @@ -387,7 +389,6 @@ do output git reset --hard && do_rest ;; -s|--strategy) - shift case "$#,$1" in *,*=*) STRATEGY="-s `expr "z$1" : 'z-[^=]*=\(.*\)'`" ;; diff --git a/git-rebase.sh b/git-rebase.sh index 1583402a0..224cca98e 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -59,7 +59,7 @@ continue_merge () { die "$RESOLVEMSG" fi - cmt=`cat $dotest/current` + cmt=`cat "$dotest/current"` if ! git diff-index --quiet HEAD then if ! git-commit -C "$cmt" @@ -84,14 +84,14 @@ continue_merge () { } call_merge () { - cmt="$(cat $dotest/cmt.$1)" + cmt="$(cat "$dotest/cmt.$1")" echo "$cmt" > "$dotest/current" hd=$(git rev-parse --verify HEAD) cmt_name=$(git symbolic-ref HEAD) - msgnum=$(cat $dotest/msgnum) - end=$(cat $dotest/end) + msgnum=$(cat "$dotest/msgnum") + end=$(cat "$dotest/end") eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"' - eval GITHEAD_$hd='"$(cat $dotest/onto_name)"' + eval GITHEAD_$hd='$(cat "$dotest/onto_name")' export GITHEAD_$cmt GITHEAD_$hd git-merge-$strategy "$cmt^" -- "$hd" "$cmt" rv=$? @@ -140,10 +140,10 @@ do } if test -d "$dotest" then - prev_head="`cat $dotest/prev_head`" - end="`cat $dotest/end`" - msgnum="`cat $dotest/msgnum`" - onto="`cat $dotest/onto`" + prev_head=$(cat "$dotest/prev_head") + end=$(cat "$dotest/end") + msgnum=$(cat "$dotest/msgnum") + onto=$(cat "$dotest/onto") continue_merge while test "$msgnum" -le "$end" do @@ -160,11 +160,11 @@ do if test -d "$dotest" then git rerere clear - prev_head="`cat $dotest/prev_head`" - end="`cat $dotest/end`" - msgnum="`cat $dotest/msgnum`" + prev_head=$(cat "$dotest/prev_head") + end=$(cat "$dotest/end") + msgnum=$(cat "$dotest/msgnum") msgnum=$(($msgnum + 1)) - onto="`cat $dotest/onto`" + onto=$(cat "$dotest/onto") while test "$msgnum" -le "$end" do call_merge "$msgnum" diff --git a/git-remote.perl b/git-remote.perl index 9ca3e7ef3..d13e4c1fe 100755 --- a/git-remote.perl +++ b/git-remote.perl @@ -244,7 +244,8 @@ sub show_remote { print "* remote $name\n"; print " URL: $info->{'URL'}\n"; for my $branchname (sort keys %$branch) { - next if ($branch->{$branchname}{'REMOTE'} ne $name); + next unless (defined $branch->{$branchname}{'REMOTE'} && + $branch->{$branchname}{'REMOTE'} eq $name); my @merged = map { s|^refs/heads/||; $_; diff --git a/git-send-email.perl b/git-send-email.perl index 62e142973..96051bc01 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -191,6 +191,7 @@ my %config_settings = ( "smtpserverport" => \$smtp_server_port, "smtpuser" => \$smtp_authuser, "smtppass" => \$smtp_authpass, + "to" => \@to, "cccmd" => \$cc_cmd, "aliasfiletype" => \$aliasfiletype, "bcc" => \@bcclist, diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 3c325fd13..86d7d4c4e 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -110,7 +110,7 @@ esac if [ -z "$SUBDIRECTORY_OK" ] then : ${GIT_DIR=.git} - GIT_DIR=$(GIT_DIR="$GIT_DIR" git rev-parse --git-dir) || { + test -z "$(git rev-parse --show-cdup)" || { exit=$? echo >&2 "You need to run this command from the toplevel of the working tree." exit $exit diff --git a/git-stash.sh b/git-stash.sh index 7ba61625b..5bbda47b7 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -139,7 +139,7 @@ apply_stash () { unstashed_index_tree= if test -n "$unstash_index" && test "$b_tree" != "$i_tree" then - git diff --binary $s^2^..$s^2 | git apply --cached + git diff-tree --binary $s^2^..$s^2 | git apply --cached test $? -ne 0 && die 'Conflicts in index. Try without --index.' unstashed_index_tree=$(git-write-tree) || @@ -162,7 +162,7 @@ apply_stash () { git read-tree "$unstashed_index_tree" else a="$TMP-added" && - git diff --cached --name-only --diff-filter=A $c_tree >"$a" && + git diff-index --cached --name-only --diff-filter=A $c_tree >"$a" && git read-tree --reset $c_tree && git update-index --add --stdin <"$a" || die "Cannot unstage modified files" diff --git a/git-svn.perl b/git-svn.perl index 2c8a1580f..22bb47b34 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -9,6 +9,11 @@ use vars qw/ $AUTHOR $VERSION $AUTHOR = 'Eric Wong <normalperson@yhbt.net>'; $VERSION = '@@GIT_VERSION@@'; +# From which subdir have we been invoked? +my $cmd_dir_prefix = eval { + command_oneline([qw/rev-parse --show-prefix/], STDERR => 0) +} || ''; + my $git_dir_user_set = 1 if defined $ENV{GIT_DIR}; $ENV{GIT_DIR} ||= '.git'; $Git::SVN::default_repo_id = 'svn'; @@ -19,12 +24,12 @@ $Git::SVN::Log::TZ = $ENV{TZ}; $ENV{TZ} = 'UTC'; $| = 1; # unbuffer STDOUT -sub fatal (@) { print STDERR @_; exit 1 } +sub fatal (@) { print STDERR "@_\n"; exit 1 } require SVN::Core; # use()-ing this causes segfaults for me... *shrug* require SVN::Ra; require SVN::Delta; if ($SVN::Core::VERSION lt '1.1.0') { - fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)\n"; + fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)"; } push @Git::SVN::Ra::ISA, 'SVN::Ra'; push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor'; @@ -123,6 +128,16 @@ my %cmd = ( 'set-tree' => [ \&cmd_set_tree, "Set an SVN repository to a git tree-ish", { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], + 'create-ignore' => [ \&cmd_create_ignore, + 'Create a .gitignore per svn:ignore', + { 'revision|r=i' => \$_revision + } ], + 'propget' => [ \&cmd_propget, + 'Print the value of a property on a file or directory', + { 'revision|r=i' => \$_revision } ], + 'proplist' => [ \&cmd_proplist, + 'List all properties of a file or directory', + { 'revision|r=i' => \$_revision } ], 'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], @@ -357,7 +372,7 @@ sub cmd_set_tree { } elsif (scalar @tmp > 1) { push @revs, reverse(command('rev-list',@tmp)); } else { - fatal "Failed to rev-parse $c\n"; + fatal "Failed to rev-parse $c"; } } my $gs = Git::SVN->new; @@ -367,7 +382,7 @@ sub cmd_set_tree { fatal "There are new revisions that were fetched ", "and need to be merged (or acknowledged) ", "before committing.\nlast rev: $r_last\n", - " current: $gs->{last_rev}\n"; + " current: $gs->{last_rev}"; } $gs->set_tree($_) foreach @revs; print "Done committing ",scalar @revs," revisions to SVN\n"; @@ -396,7 +411,7 @@ sub cmd_dcommit { (undef, $last_rev, undef) = cmt_metadata("$d~1"); unless (defined $last_rev) { fatal "Unable to extract revision information ", - "from commit $d~1\n"; + "from commit $d~1"; } } if ($_dry_run) { @@ -488,7 +503,100 @@ sub cmd_show_ignore { my ($url, $rev, $uuid, $gs) = working_head_info('HEAD'); $gs ||= Git::SVN->new; my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum); - $gs->traverse_ignore(\*STDOUT, $gs->{path}, $r); + $gs->prop_walk($gs->{path}, $r, sub { + my ($gs, $path, $props) = @_; + print STDOUT "\n# $path\n"; + my $s = $props->{'svn:ignore'} or return; + $s =~ s/[\r\n]+/\n/g; + chomp $s; + $s =~ s#^#$path#gm; + print STDOUT "$s\n"; + }); +} + +sub cmd_create_ignore { + my ($url, $rev, $uuid, $gs) = working_head_info('HEAD'); + $gs ||= Git::SVN->new; + my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum); + $gs->prop_walk($gs->{path}, $r, sub { + my ($gs, $path, $props) = @_; + # $path is of the form /path/to/dir/ + my $ignore = '.' . $path . '.gitignore'; + my $s = $props->{'svn:ignore'} or return; + open(GITIGNORE, '>', $ignore) + or fatal("Failed to open `$ignore' for writing: $!"); + $s =~ s/[\r\n]+/\n/g; + chomp $s; + # Prefix all patterns so that the ignore doesn't apply + # to sub-directories. + $s =~ s#^#/#gm; + print GITIGNORE "$s\n"; + close(GITIGNORE) + or fatal("Failed to close `$ignore': $!"); + command_noisy('add', $ignore); + }); +} + +# get_svnprops(PATH) +# ------------------ +# Helper for cmd_propget and cmd_proplist below. +sub get_svnprops { + my $path = shift; + my ($url, $rev, $uuid, $gs) = working_head_info('HEAD'); + $gs ||= Git::SVN->new; + + # prefix THE PATH by the sub-directory from which the user + # invoked us. + $path = $cmd_dir_prefix . $path; + fatal("No such file or directory: $path") unless -e $path; + my $is_dir = -d $path ? 1 : 0; + $path = $gs->{path} . '/' . $path; + + # canonicalize the path (otherwise libsvn will abort or fail to + # find the file) + # File::Spec->canonpath doesn't collapse x/../y into y (for a + # good reason), so let's do this manually. + $path =~ s#/+#/#g; + $path =~ s#/\.(?:/|$)#/#g; + $path =~ s#/[^/]+/\.\.##g; + $path =~ s#/$##g; + + my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum); + my $props; + if ($is_dir) { + (undef, undef, $props) = $gs->ra->get_dir($path, $r); + } + else { + (undef, $props) = $gs->ra->get_file($path, $r, undef); + } + return $props; +} + +# cmd_propget (PROP, PATH) +# ------------------------ +# Print the SVN property PROP for PATH. +sub cmd_propget { + my ($prop, $path) = @_; + $path = '.' if not defined $path; + usage(1) if not defined $prop; + my $props = get_svnprops($path); + if (not defined $props->{$prop}) { + fatal("`$path' does not have a `$prop' SVN property."); + } + print $props->{$prop} . "\n"; +} + +# cmd_proplist (PATH) +# ------------------- +# Print the list of SVN properties for PATH. +sub cmd_proplist { + my $path = shift; + $path = '.' if not defined $path; + my $props = get_svnprops($path); + print "Properties on '$path':\n"; + foreach (sort keys %{$props}) { + print " $_\n"; + } } sub cmd_multi_init { @@ -537,7 +645,7 @@ sub cmd_multi_fetch { sub cmd_commit_diff { my ($ta, $tb, $url) = @_; my $usage = "Usage: $0 commit-diff -r<revision> ". - "<tree-ish> <tree-ish> [<URL>]\n"; + "<tree-ish> <tree-ish> [<URL>]"; fatal($usage) if (!defined $ta || !defined $tb); my $svn_path; if (!defined $url) { @@ -555,7 +663,7 @@ sub cmd_commit_diff { if (defined $_message && defined $_file) { fatal("Both --message/-m and --file/-F specified ", "for the commit message.\n", - "I have no idea what you mean\n"); + "I have no idea what you mean"); } if (defined $_file) { $_message = file_to_s($_file); @@ -618,7 +726,7 @@ sub complete_svn_url { if ($path !~ m#^[a-z\+]+://#) { if (!defined $url || $url !~ m#^[a-z\+]+://#) { fatal("E: '$path' is not a complete URL ", - "and a separate URL is not specified\n"); + "and a separate URL is not specified"); } return ($url, $path); } @@ -639,7 +747,7 @@ sub complete_url_ls_init { $repo_path =~ s#^/+##; unless ($ra) { fatal("E: '$repo_path' is not a complete URL ", - "and a separate URL is not specified\n"); + "and a separate URL is not specified"); } } my $url = $ra->{url}; @@ -1480,28 +1588,45 @@ sub rel_path { $url; } -sub traverse_ignore { - my ($self, $fh, $path, $r) = @_; - $path =~ s#^/+##g; - my $ra = $self->ra; - my ($dirent, undef, $props) = $ra->get_dir($path, $r); +# prop_walk(PATH, REV, SUB) +# ------------------------- +# Recursively traverse PATH at revision REV and invoke SUB for each +# directory that contains a SVN property. SUB will be invoked as +# follows: &SUB(gs, path, props); where `gs' is this instance of +# Git::SVN, `path' the path to the directory where the properties +# `props' were found. The `path' will be relative to point of checkout, +# that is, if url://repo/trunk is the current Git branch, and that +# directory contains a sub-directory `d', SUB will be invoked with `/d/' +# as `path' (note the trailing `/'). +sub prop_walk { + my ($self, $path, $rev, $sub) = @_; + + my ($dirent, undef, $props) = $self->ra->get_dir($path, $rev); + $path =~ s#^/*#/#g; my $p = $path; - $p =~ s#^\Q$self->{path}\E(/|$)##; - print $fh length $p ? "\n# $p\n" : "\n# /\n"; - if (my $s = $props->{'svn:ignore'}) { - $s =~ s/[\r\n]+/\n/g; - chomp $s; - if (length $p == 0) { - $s =~ s#\n#\n/$p#g; - print $fh "/$s\n"; - } else { - $s =~ s#\n#\n/$p/#g; - print $fh "/$p/$s\n"; - } - } + # Strip the irrelevant part of the path. + $p =~ s#^/+\Q$self->{path}\E(/|$)#/#; + # Ensure the path is terminated by a `/'. + $p =~ s#/*$#/#; + + # The properties contain all the internal SVN stuff nobody + # (usually) cares about. + my $interesting_props = 0; + foreach (keys %{$props}) { + # If it doesn't start with `svn:', it must be a + # user-defined property. + ++$interesting_props and next if $_ !~ /^svn:/; + # FIXME: Fragile, if SVN adds new public properties, + # this needs to be updated. + ++$interesting_props if /^svn:(?:ignore|keywords|executable + |eol-style|mime-type + |externals|needs-lock)$/x; + } + &$sub($self, $p, $props) if $interesting_props; + foreach (sort keys %$dirent) { next if $dirent->{$_}->{kind} != $SVN::Node::dir; - $self->traverse_ignore($fh, "$path/$_", $r); + $self->prop_walk($path . '/' . $_, $rev, $sub); } } @@ -1628,7 +1753,7 @@ sub assert_index_clean { $x = command_oneline('write-tree'); if ($y ne $x) { ::fatal "trees ($treeish) $y != $x\n", - "Something is seriously wrong...\n"; + "Something is seriously wrong..."; } }); } @@ -2054,7 +2179,7 @@ sub set_tree { my ($self, $tree) = (shift, shift); my $log_entry = ::get_commit_entry($tree); unless ($self->{last_rev}) { - fatal("Must have an existing revision to commit\n"); + fatal("Must have an existing revision to commit"); } my %ed_opts = ( r => $self->{last_rev}, log => $log_entry->{log}, @@ -2303,23 +2428,31 @@ sub ssl_server_trust { my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_; $may_save = undef if $_no_auth_cache; print STDERR "Error validating server certificate for '$realm':\n"; - if ($failures & $SVN::Auth::SSL::UNKNOWNCA) { - print STDERR " - The certificate is not issued by a trusted ", - "authority. Use the\n", - " fingerprint to validate the certificate manually!\n"; - } - if ($failures & $SVN::Auth::SSL::CNMISMATCH) { - print STDERR " - The certificate hostname does not match.\n"; - } - if ($failures & $SVN::Auth::SSL::NOTYETVALID) { - print STDERR " - The certificate is not yet valid.\n"; - } - if ($failures & $SVN::Auth::SSL::EXPIRED) { - print STDERR " - The certificate has expired.\n"; - } - if ($failures & $SVN::Auth::SSL::OTHER) { - print STDERR " - The certificate has an unknown error.\n"; - } + { + no warnings 'once'; + # All variables SVN::Auth::SSL::* are used only once, + # so we're shutting up Perl warnings about this. + if ($failures & $SVN::Auth::SSL::UNKNOWNCA) { + print STDERR " - The certificate is not issued ", + "by a trusted authority. Use the\n", + " fingerprint to validate ", + "the certificate manually!\n"; + } + if ($failures & $SVN::Auth::SSL::CNMISMATCH) { + print STDERR " - The certificate hostname ", + "does not match.\n"; + } + if ($failures & $SVN::Auth::SSL::NOTYETVALID) { + print STDERR " - The certificate is not yet valid.\n"; + } + if ($failures & $SVN::Auth::SSL::EXPIRED) { + print STDERR " - The certificate has expired.\n"; + } + if ($failures & $SVN::Auth::SSL::OTHER) { + print STDERR " - The certificate has ", + "an unknown error.\n"; + } + } # no warnings 'once' printf STDERR "Certificate information:\n". " - Hostname: %s\n". @@ -2403,20 +2536,6 @@ sub _read_password { $password; } -package main; - -{ - my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. - $SVN::Node::dir.$SVN::Node::unknown. - $SVN::Node::none.$SVN::Node::file. - $SVN::Node::dir.$SVN::Node::unknown. - $SVN::Auth::SSL::CNMISMATCH. - $SVN::Auth::SSL::NOTYETVALID. - $SVN::Auth::SSL::EXPIRED. - $SVN::Auth::SSL::UNKNOWNCA. - $SVN::Auth::SSL::OTHER; -} - package SVN::Git::Fetcher; use vars qw/@ISA/; use strict; @@ -2833,16 +2952,21 @@ sub open_or_add_dir { if (!defined $t) { die "$full_path not known in r$self->{r} or we have a bug!\n"; } - if ($t == $SVN::Node::none) { - return $self->add_directory($full_path, $baton, - undef, -1, $self->{pool}); - } elsif ($t == $SVN::Node::dir) { - return $self->open_directory($full_path, $baton, - $self->{r}, $self->{pool}); - } - print STDERR "$full_path already exists in repository at ", - "r$self->{r} and it is not a directory (", - ($t == $SVN::Node::file ? 'file' : 'unknown'),"/$t)\n"; + { + no warnings 'once'; + # SVN::Node::none and SVN::Node::file are used only once, + # so we're shutting up Perl's warnings about them. + if ($t == $SVN::Node::none) { + return $self->add_directory($full_path, $baton, + undef, -1, $self->{pool}); + } elsif ($t == $SVN::Node::dir) { + return $self->open_directory($full_path, $baton, + $self->{r}, $self->{pool}); + } # no warnings 'once' + print STDERR "$full_path already exists in repository at ", + "r$self->{r} and it is not a directory (", + ($t == $SVN::Node::file ? 'file' : 'unknown'),"/$t)\n"; + } # no warnings 'once' exit 1; } @@ -3004,7 +3128,7 @@ sub apply_diff { if (defined $o{$f}) { $self->$f($m); } else { - fatal("Invalid change type: $f\n"); + fatal("Invalid change type: $f"); } } $self->rmdirs if $_rmdir; @@ -3068,11 +3192,11 @@ sub new { my $dont_store_passwords = 1; my $conf_t = ${$config}{'config'}; { + no warnings 'once'; # The usage of $SVN::_Core::SVN_CONFIG_* variables # produces warnings that variables are used only once. # I had not found the better way to shut them up, so - # warnings are disabled in this block. - no warnings; + # the warnings of type 'once' are disabled in this block. if (SVN::_Core::svn_config_get_bool($conf_t, $SVN::_Core::SVN_CONFIG_SECTION_AUTH, $SVN::_Core::SVN_CONFIG_OPTION_STORE_PASSWORDS, @@ -3087,7 +3211,7 @@ sub new { 1) == 0) { $Git::SVN::Prompt::_no_auth_cache = 1; } - } + } # no warnings 'once' my $self = SVN::Ra->new(url => $url, auth => $baton, config => $config, pool => SVN::Pool->new, @@ -3635,15 +3759,15 @@ sub config_pager { sub run_pager { return unless -t *STDOUT && defined $pager; pipe my $rfd, my $wfd or return; - defined(my $pid = fork) or ::fatal "Can't fork: $!\n"; + defined(my $pid = fork) or ::fatal "Can't fork: $!"; if (!$pid) { open STDOUT, '>&', $wfd or - ::fatal "Can't redirect to stdout: $!\n"; + ::fatal "Can't redirect to stdout: $!"; return; } - open STDIN, '<&', $rfd or ::fatal "Can't redirect stdin: $!\n"; + open STDIN, '<&', $rfd or ::fatal "Can't redirect stdin: $!"; $ENV{LESS} ||= 'FRSX'; - exec $pager or ::fatal "Can't run pager: $! ($pager)\n"; + exec $pager or ::fatal "Can't run pager: $! ($pager)"; } sub tz_to_s_offset { @@ -3779,7 +3903,7 @@ sub cmd_show_log { $r_min = $r_max = $::_revision; } else { ::fatal "-r$::_revision is not supported, use ", - "standard \'git log\' arguments instead\n"; + "standard 'git log' arguments instead"; } } @@ -6,28 +6,6 @@ const char git_usage_string[] = "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]"; -static void prepend_to_path(const char *dir, int len) -{ - const char *old_path = getenv("PATH"); - char *path; - int path_len = len; - - if (!old_path) - old_path = "/usr/local/bin:/usr/bin:/bin"; - - path_len = len + strlen(old_path) + 1; - - path = xmalloc(path_len + 1); - - memcpy(path, dir, len); - path[len] = ':'; - memcpy(path + len + 1, old_path, path_len - len); - - setenv("PATH", path, 1); - - free(path); -} - static int handle_options(const char*** argv, int* argc, int* envchanged) { int handled = 0; @@ -51,7 +29,7 @@ static int handle_options(const char*** argv, int* argc, int* envchanged) if (!prefixcmp(cmd, "--exec-path")) { cmd += 11; if (*cmd == '=') - git_set_exec_path(cmd + 1); + git_set_argv_exec_path(cmd + 1); else { puts(git_exec_path()); exit(0); @@ -408,19 +386,17 @@ int main(int argc, const char **argv) { const char *cmd = argv[0] ? argv[0] : "git-help"; char *slash = strrchr(cmd, '/'); - const char *exec_path = NULL; + const char *cmd_path = NULL; int done_alias = 0; /* * Take the basename of argv[0] as the command * name, and the dirname as the default exec_path - * if it's an absolute path and we don't have - * anything better. + * if we don't have anything better. */ if (slash) { *slash++ = 0; - if (*cmd == '/') - exec_path = cmd; + cmd_path = cmd; cmd = slash; } @@ -449,23 +425,20 @@ int main(int argc, const char **argv) if (!prefixcmp(argv[0], "--")) argv[0] += 2; } else { - /* Default command: "help" */ - argv[0] = "help"; - argc = 1; + /* The user didn't specify a command; give them help */ + printf("usage: %s\n\n", git_usage_string); + list_common_cmds_help(); + exit(1); } cmd = argv[0]; /* - * We execute external git command via execv_git_cmd(), - * which looks at "--exec-path" option, GIT_EXEC_PATH - * environment, and $(gitexecdir) in Makefile while built, - * in this order. For scripted commands, we prepend - * the value of the exec_path variable to the PATH. + * We use PATH to find git commands, but we prepend some higher + * precidence paths: the "--exec-path" option, the GIT_EXEC_PATH + * environment, and the $(gitexecdir) from the Makefile at build + * time. */ - if (exec_path) - prepend_to_path(exec_path, strlen(exec_path)); - exec_path = git_exec_path(); - prepend_to_path(exec_path, strlen(exec_path)); + setup_path(cmd_path); while (1) { /* See if it's an internal command */ @@ -82,17 +82,20 @@ proc dorunq {} { proc start_rev_list {view} { global startmsecs global commfd leftover tclencoding datemode - global viewargs viewfiles commitidx - global lookingforhead showlocalchanges + global viewargs viewfiles commitidx viewcomplete vnextroot + global showlocalchanges commitinterest mainheadid + global progressdirn progresscoords proglastnc curview set startmsecs [clock clicks -milliseconds] set commitidx($view) 0 + set viewcomplete($view) 0 + set vnextroot($view) 0 set order "--topo-order" if {$datemode} { set order "--date-order" } if {[catch { - set fd [open [concat | git log -z --pretty=raw $order --parents \ + set fd [open [concat | git log --no-color -z --pretty=raw $order --parents \ --boundary $viewargs($view) "--" $viewfiles($view)] r] } err]} { error_popup "Error executing git rev-list: $err" @@ -100,13 +103,20 @@ proc start_rev_list {view} { } set commfd($view) $fd set leftover($view) {} - set lookingforhead $showlocalchanges + if {$showlocalchanges} { + lappend commitinterest($mainheadid) {dodiffindex} + } fconfigure $fd -blocking 0 -translation lf -eofchar {} if {$tclencoding != {}} { fconfigure $fd -encoding $tclencoding } filerun $fd [list getcommitlines $fd $view] - nowbusy $view + nowbusy $view "Reading" + if {$view == $curview} { + set progressdirn 1 + set progresscoords {0 0} + set proglastnc 0 + } } proc stop_rev_list {} { @@ -123,7 +133,7 @@ proc stop_rev_list {} { } proc getcommits {} { - global phase canv mainfont curview + global phase canv curview set phase getcommits initlayout @@ -131,12 +141,26 @@ proc getcommits {} { show_status "Reading commits..." } +# This makes a string representation of a positive integer which +# sorts as a string in numerical order +proc strrep {n} { + if {$n < 16} { + return [format "%x" $n] + } elseif {$n < 256} { + return [format "x%.2x" $n] + } elseif {$n < 65536} { + return [format "y%.4x" $n] + } + return [format "z%.8x" $n] +} + proc getcommitlines {fd view} { - global commitlisted + global commitlisted commitinterest global leftover commfd - global displayorder commitidx commitrow commitdata + global displayorder commitidx viewcomplete commitrow commitdata global parentlist children curview hlview global vparentlist vdisporder vcmitlisted + global ordertok vnextroot idpending set stuff [read $fd 500000] # git log doesn't terminate the last commit with a null... @@ -147,9 +171,29 @@ proc getcommitlines {fd view} { if {![eof $fd]} { return 1 } - global viewname + # Check if we have seen any ids listed as parents that haven't + # appeared in the list + foreach vid [array names idpending "$view,*"] { + # should only get here if git log is buggy + set id [lindex [split $vid ","] 1] + set commitrow($vid) $commitidx($view) + incr commitidx($view) + if {$view == $curview} { + lappend parentlist {} + lappend displayorder $id + lappend commitlisted 0 + } else { + lappend vparentlist($view) {} + lappend vdisporder($view) $id + lappend vcmitlisted($view) 0 + } + } + set viewcomplete($view) 1 + global viewname progresscoords unset commfd($view) notbusy $view + set progresscoords {0 0} + adjustprogress # set it blocking so we wait for the process to terminate fconfigure $fd -blocking 1 if {[catch {close $fd} err]} { @@ -221,14 +265,35 @@ proc getcommitlines {fd view} { exit 1 } set id [lindex $ids 0] + if {![info exists ordertok($view,$id)]} { + set otok "o[strrep $vnextroot($view)]" + incr vnextroot($view) + set ordertok($view,$id) $otok + } else { + set otok $ordertok($view,$id) + unset idpending($view,$id) + } if {$listed} { set olds [lrange $ids 1 end] - set i 0 - foreach p $olds { - if {$i == 0 || [lsearch -exact $olds $p] >= $i} { - lappend children($view,$p) $id + if {[llength $olds] == 1} { + set p [lindex $olds 0] + lappend children($view,$p) $id + if {![info exists ordertok($view,$p)]} { + set ordertok($view,$p) $ordertok($view,$id) + set idpending($view,$p) 1 + } + } else { + set i 0 + foreach p $olds { + if {$i == 0 || [lsearch -exact $olds $p] >= $i} { + lappend children($view,$p) $id + } + if {![info exists ordertok($view,$p)]} { + set ordertok($view,$p) "$otok[strrep $i]]" + set idpending($view,$p) 1 + } + incr i } - incr i } } else { set olds {} @@ -248,24 +313,54 @@ proc getcommitlines {fd view} { lappend vdisporder($view) $id lappend vcmitlisted($view) $listed } + if {[info exists commitinterest($id)]} { + foreach script $commitinterest($id) { + eval [string map [list "%I" $id] $script] + } + unset commitinterest($id) + } set gotsome 1 } if {$gotsome} { run chewcommits $view + if {$view == $curview} { + # update progress bar + global progressdirn progresscoords proglastnc + set inc [expr {($commitidx($view) - $proglastnc) * 0.0002}] + set proglastnc $commitidx($view) + set l [lindex $progresscoords 0] + set r [lindex $progresscoords 1] + if {$progressdirn} { + set r [expr {$r + $inc}] + if {$r >= 1.0} { + set r 1.0 + set progressdirn 0 + } + if {$r > 0.2} { + set l [expr {$r - 0.2}] + } + } else { + set l [expr {$l - $inc}] + if {$l <= 0.0} { + set l 0.0 + set progressdirn 1 + } + set r [expr {$l + 0.2}] + } + set progresscoords [list $l $r] + adjustprogress + } } return 2 } proc chewcommits {view} { - global curview hlview commfd + global curview hlview viewcomplete 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} { + layoutmore + if {$viewcomplete($view)} { global displayorder commitidx phase global numcommits startmsecs @@ -286,7 +381,7 @@ proc chewcommits {view} { if {[info exists hlview] && $view == $hlview} { vhighlightmore } - return $more + return 0 } proc readcommit {id} { @@ -295,7 +390,7 @@ proc readcommit {id} { } proc updatecommits {} { - global viewdata curview phase displayorder + global viewdata curview phase displayorder ordertok idpending global children commitrow selectedline thickerline showneartags if {$phase ne {}} { @@ -306,6 +401,10 @@ proc updatecommits {} { foreach id $displayorder { catch {unset children($n,$id)} catch {unset commitrow($n,$id)} + catch {unset ordertok($n,$id)} + } + foreach vid [array names idpending "$n,*"] { + unset idpending($vid) } set curview -1 catch {unset selectedline} @@ -516,7 +615,7 @@ proc confirm_popup msg { proc makewindow {} { global canv canv2 canv3 linespc charspc ctext cflist - global textfont mainfont uifont tabstop + global tabstop global findtype findtypemenu findloc findstring fstring geometry global entries sha1entry sha1string sha1but global diffcontextstring diffcontext @@ -525,23 +624,26 @@ proc makewindow {} { global highlight_files gdttype global searchstring sstring global bgcolor fgcolor bglist fglist diffcolors selectbgcolor - global headctxmenu + global headctxmenu progresscanv progressitem progresscoords statusw + global fprogitem fprogcoord lastprogupdate progupdatepending + global rprogitem rprogcoord + global have_tk85 menu .bar .bar add cascade -label "File" -menu .bar.file - .bar configure -font $uifont + .bar configure -font uifont menu .bar.file .bar.file add command -label "Update" -command updatecommits .bar.file add command -label "Reread references" -command rereadrefs .bar.file add command -label "List references" -command showrefs .bar.file add command -label "Quit" -command doquit - .bar.file configure -font $uifont + .bar.file configure -font uifont menu .bar.edit .bar add cascade -label "Edit" -menu .bar.edit .bar.edit add command -label "Preferences" -command doprefs - .bar.edit configure -font $uifont + .bar.edit configure -font uifont - menu .bar.view -font $uifont + menu .bar.view -font uifont .bar add cascade -label "View" -menu .bar.view .bar.view add command -label "New view..." -command {newview 0} .bar.view add command -label "Edit view..." -command editview \ @@ -555,7 +657,7 @@ proc makewindow {} { .bar add cascade -label "Help" -menu .bar.help .bar.help add command -label "About gitk" -command about .bar.help add command -label "Key bindings" -command keys - .bar.help configure -font $uifont + .bar.help configure -font uifont . configure -menu .bar # the gui has upper and lower half, parts of a paned window. @@ -612,10 +714,10 @@ proc makewindow {} { set entries $sha1entry set sha1but .tf.bar.sha1label button $sha1but -text "SHA1 ID: " -state disabled -relief flat \ - -command gotocommit -width 8 -font $uifont + -command gotocommit -width 8 -font uifont $sha1but conf -disabledforeground [$sha1but cget -foreground] pack .tf.bar.sha1label -side left - entry $sha1entry -width 40 -font $textfont -textvariable sha1string + entry $sha1entry -width 40 -font textfont -textvariable sha1string trace add variable sha1string write sha1change pack $sha1entry -side left -pady 2 @@ -642,62 +744,61 @@ proc makewindow {} { -state disabled -width 26 pack .tf.bar.rightbut -side left -fill y - button .tf.bar.findbut -text "Find" -command dofind -font $uifont - pack .tf.bar.findbut -side left + # Status label and progress bar + set statusw .tf.bar.status + label $statusw -width 15 -relief sunken -font uifont + pack $statusw -side left -padx 5 + set h [expr {[font metrics uifont -linespace] + 2}] + set progresscanv .tf.bar.progress + canvas $progresscanv -relief sunken -height $h -borderwidth 2 + set progressitem [$progresscanv create rect -1 0 0 $h -fill green] + set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow] + set rprogitem [$progresscanv create rect -1 0 0 $h -fill red] + pack $progresscanv -side right -expand 1 -fill x + set progresscoords {0 0} + set fprogcoord 0 + set rprogcoord 0 + bind $progresscanv <Configure> adjustprogress + set lastprogupdate [clock clicks -milliseconds] + set progupdatepending 0 + + # build up the bottom bar of upper window + label .tf.lbar.flabel -text "Find " -font uifont + button .tf.lbar.fnext -text "next" -command {dofind 1 1} -font uifont + button .tf.lbar.fprev -text "prev" -command {dofind -1 1} -font uifont + label .tf.lbar.flab2 -text " commit " -font uifont + pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \ + -side left -fill y + set gdttype "containing:" + set gm [tk_optionMenu .tf.lbar.gdttype gdttype \ + "containing:" \ + "touching paths:" \ + "adding/removing string:"] + trace add variable gdttype write gdttype_change + $gm conf -font uifont + .tf.lbar.gdttype conf -font uifont + pack .tf.lbar.gdttype -side left -fill y + set findstring {} - set fstring .tf.bar.findstring + set fstring .tf.lbar.findstring lappend entries $fstring - entry $fstring -width 30 -font $textfont -textvariable findstring + entry $fstring -width 30 -font textfont -textvariable findstring trace add variable findstring write find_change - pack $fstring -side left -expand 1 -fill x -in .tf.bar set findtype Exact - set findtypemenu [tk_optionMenu .tf.bar.findtype \ + set findtypemenu [tk_optionMenu .tf.lbar.findtype \ findtype Exact IgnCase Regexp] - trace add variable findtype write find_change - .tf.bar.findtype configure -font $uifont - .tf.bar.findtype.menu configure -font $uifont + trace add variable findtype write findcom_change + .tf.lbar.findtype configure -font uifont + .tf.lbar.findtype.menu configure -font uifont set findloc "All fields" - tk_optionMenu .tf.bar.findloc findloc "All fields" Headline \ + tk_optionMenu .tf.lbar.findloc findloc "All fields" Headline \ Comments Author Committer trace add variable findloc write find_change - .tf.bar.findloc configure -font $uifont - .tf.bar.findloc.menu configure -font $uifont - pack .tf.bar.findloc -side right - pack .tf.bar.findtype -side right - - # build up the bottom bar of upper window - label .tf.lbar.flabel -text "Highlight: Commits " \ - -font $uifont - pack .tf.lbar.flabel -side left -fill y - set gdttype "touching paths:" - set gm [tk_optionMenu .tf.lbar.gdttype gdttype "touching paths:" \ - "adding/removing string:"] - trace add variable gdttype write hfiles_change - $gm conf -font $uifont - .tf.lbar.gdttype conf -font $uifont - pack .tf.lbar.gdttype -side left -fill y - entry .tf.lbar.fent -width 25 -font $textfont \ - -textvariable highlight_files - trace add variable highlight_files write hfiles_change - lappend entries .tf.lbar.fent - pack .tf.lbar.fent -side left -fill x -expand 1 - label .tf.lbar.vlabel -text " OR in view" -font $uifont - pack .tf.lbar.vlabel -side left -fill y - global viewhlmenu selectedhlview - set viewhlmenu [tk_optionMenu .tf.lbar.vhl selectedhlview None] - $viewhlmenu entryconf None -command delvhighlight - $viewhlmenu conf -font $uifont - .tf.lbar.vhl conf -font $uifont - pack .tf.lbar.vhl -side left -fill y - label .tf.lbar.rlabel -text " OR " -font $uifont - pack .tf.lbar.rlabel -side left -fill y - global highlight_related - set m [tk_optionMenu .tf.lbar.relm highlight_related None \ - "Descendent" "Not descendent" "Ancestor" "Not ancestor"] - $m conf -font $uifont - .tf.lbar.relm conf -font $uifont - trace add variable highlight_related write vrel_change - pack .tf.lbar.relm -side left -fill y + .tf.lbar.findloc configure -font uifont + .tf.lbar.findloc.menu configure -font uifont + pack .tf.lbar.findloc -side right + pack .tf.lbar.findtype -side right + pack $fstring -side left -expand 1 -fill x # Finish putting the upper half of the viewer together pack .tf.lbar -in .tf -side bottom -fill x @@ -722,23 +823,23 @@ proc makewindow {} { frame .bleft.mid button .bleft.top.search -text "Search" -command dosearch \ - -font $uifont + -font uifont pack .bleft.top.search -side left -padx 5 set sstring .bleft.top.sstring - entry $sstring -width 20 -font $textfont -textvariable searchstring + entry $sstring -width 20 -font textfont -textvariable searchstring lappend entries $sstring trace add variable searchstring write incrsearch pack $sstring -side left -expand 1 -fill x - radiobutton .bleft.mid.diff -text "Diff" \ + radiobutton .bleft.mid.diff -text "Diff" -font uifont \ -command changediffdisp -variable diffelide -value {0 0} - radiobutton .bleft.mid.old -text "Old version" \ + radiobutton .bleft.mid.old -text "Old version" -font uifont \ -command changediffdisp -variable diffelide -value {0 1} - radiobutton .bleft.mid.new -text "New version" \ + radiobutton .bleft.mid.new -text "New version" -font uifont \ -command changediffdisp -variable diffelide -value {1 0} label .bleft.mid.labeldiffcontext -text " Lines of context: " \ - -font $uifont + -font uifont pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left - spinbox .bleft.mid.diffcontext -width 5 -font $textfont \ + spinbox .bleft.mid.diffcontext -width 5 -font textfont \ -from 1 -increment 1 -to 10000000 \ -validate all -validatecommand "diffcontextvalidate %P" \ -textvariable diffcontextstring @@ -748,9 +849,11 @@ proc makewindow {} { pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left set ctext .bleft.ctext text $ctext -background $bgcolor -foreground $fgcolor \ - -tabs "[expr {$tabstop * $charspc}]" \ - -state disabled -font $textfont \ + -state disabled -font textfont \ -yscrollcommand scrolltext -wrap none + if {$have_tk85} { + $ctext conf -tabstyle wordprocessor + } scrollbar .bleft.sb -command "$ctext yview" pack .bleft.top -side top -fill x pack .bleft.mid -side top -fill x @@ -760,7 +863,7 @@ proc makewindow {} { lappend fglist $ctext $ctext tag conf comment -wrap $wrapcomment - $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa" + $ctext tag conf filesep -font textfontbold -back "#aaaaaa" $ctext tag conf hunksep -fore [lindex $diffcolors 2] $ctext tag conf d0 -fore [lindex $diffcolors 0] $ctext tag conf d1 -fore [lindex $diffcolors 1] @@ -782,8 +885,8 @@ proc makewindow {} { $ctext tag conf m15 -fore "#ff70b0" $ctext tag conf mmax -fore darkgrey set mergemax 16 - $ctext tag conf mresult -font [concat $textfont bold] - $ctext tag conf msep -font [concat $textfont bold] + $ctext tag conf mresult -font textfontbold + $ctext tag conf msep -font textfontbold $ctext tag conf found -back yellow .pwbottom add .bleft @@ -794,18 +897,18 @@ proc makewindow {} { frame .bright.mode radiobutton .bright.mode.patch -text "Patch" \ -command reselectline -variable cmitmode -value "patch" - .bright.mode.patch configure -font $uifont + .bright.mode.patch configure -font uifont radiobutton .bright.mode.tree -text "Tree" \ -command reselectline -variable cmitmode -value "tree" - .bright.mode.tree configure -font $uifont + .bright.mode.tree configure -font uifont grid .bright.mode.patch .bright.mode.tree -sticky ew pack .bright.mode -side top -fill x set cflist .bright.cfiles - set indent [font measure $mainfont "nn"] + set indent [font measure mainfont "nn"] text $cflist \ -selectbackground $selectbgcolor \ -background $bgcolor -foreground $fgcolor \ - -font $mainfont \ + -font mainfont \ -tabs [list $indent [expr {2 * $indent}]] \ -yscrollcommand ".bright.sb set" \ -cursor [. cget -cursor] \ @@ -817,7 +920,7 @@ proc makewindow {} { pack $cflist -side left -fill both -expand 1 $cflist tag configure highlight \ -background [$cflist cget -selectbackground] - $cflist tag configure bold -font [concat $mainfont bold] + $cflist tag configure bold -font mainfontbold .pwbottom add .bright .ctop add .pwbottom @@ -843,6 +946,12 @@ proc makewindow {} { } else { bindall <ButtonRelease-4> "allcanvs yview scroll -5 units" bindall <ButtonRelease-5> "allcanvs yview scroll 5 units" + if {[tk windowingsystem] eq "aqua"} { + bindall <MouseWheel> { + set delta [expr {- (%D)}] + allcanvs yview scroll $delta units + } + } } bindall <2> "canvscan mark %W %x %y" bindall <B2-Motion> "canvscan dragto %W %x %y" @@ -850,8 +959,8 @@ proc makewindow {} { bindkey <End> sellastline bind . <Key-Up> "selnextline -1" bind . <Key-Down> "selnextline 1" - bind . <Shift-Key-Up> "next_highlight -1" - bind . <Shift-Key-Down> "next_highlight 1" + bind . <Shift-Key-Up> "dofind -1 0" + bind . <Shift-Key-Down> "dofind 1 0" bindkey <Key-Right> "goforw" bindkey <Key-Left> "goback" bind . <Key-Prior> "selnextpage -1" @@ -876,14 +985,14 @@ proc makewindow {} { bindkey b "$ctext yview scroll -1 pages" bindkey d "$ctext yview scroll 18 units" bindkey u "$ctext yview scroll -18 units" - bindkey / {findnext 1} - bindkey <Key-Return> {findnext 0} - bindkey ? findprev + bindkey / {dofind 1 1} + bindkey <Key-Return> {dofind 1 1} + bindkey ? {dofind -1 1} bindkey f nextfile bindkey <F5> updatecommits bind . <$M1B-q> doquit - bind . <$M1B-f> dofind - bind . <$M1B-g> {findnext 0} + bind . <$M1B-f> {dofind 1 1} + bind . <$M1B-g> {dofind 1 0} bind . <$M1B-r> dosearchback bind . <$M1B-s> dosearch bind . <$M1B-equal> {incrfont 1} @@ -892,7 +1001,7 @@ proc makewindow {} { bind . <$M1B-KP_Subtract> {incrfont -1} wm protocol . WM_DELETE_WINDOW doquit bind . <Button-1> "click %W" - bind $fstring <Key-Return> dofind + bind $fstring <Key-Return> {dofind 1 1} bind $sha1entry <Key-Return> gotocommit bind $sha1entry <<PasteSelection>> clearsha1 bind $cflist <1> {sel_flist %W %x %y; break} @@ -1008,12 +1117,45 @@ proc click {w} { focus . } +# Adjust the progress bar for a change in requested extent or canvas size +proc adjustprogress {} { + global progresscanv progressitem progresscoords + global fprogitem fprogcoord lastprogupdate progupdatepending + global rprogitem rprogcoord + + set w [expr {[winfo width $progresscanv] - 4}] + set x0 [expr {$w * [lindex $progresscoords 0]}] + set x1 [expr {$w * [lindex $progresscoords 1]}] + set h [winfo height $progresscanv] + $progresscanv coords $progressitem $x0 0 $x1 $h + $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h + $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h + set now [clock clicks -milliseconds] + if {$now >= $lastprogupdate + 100} { + set progupdatepending 0 + update + } elseif {!$progupdatepending} { + set progupdatepending 1 + after [expr {$lastprogupdate + 100 - $now}] doprogupdate + } +} + +proc doprogupdate {} { + global lastprogupdate progupdatepending + + if {$progupdatepending} { + set progupdatepending 0 + set lastprogupdate [clock clicks -milliseconds] + update + } +} + proc savestuff {w} { - global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop + global canv canv2 canv3 mainfont textfont uifont tabstop global stuffsaved findmergefiles maxgraphpct global maxwidth showneartags showlocalchanges global viewname viewfiles viewargs viewperm nextviewnum - global cmitmode wrapcomment datetimeformat + global cmitmode wrapcomment datetimeformat limitdiffs global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor if {$stuffsaved} return @@ -1032,6 +1174,7 @@ proc savestuff {w} { puts $f [list set showneartags $showneartags] puts $f [list set showlocalchanges $showlocalchanges] puts $f [list set datetimeformat $datetimeformat] + puts $f [list set limitdiffs $limitdiffs] puts $f [list set bgcolor $bgcolor] puts $f [list set fgcolor $fgcolor] puts $f [list set colors $colors] @@ -1143,10 +1286,10 @@ Copyright © 2005-2006 Paul Mackerras Use and redistribute under the terms of the GNU General Public License} \ -justify center -aspect 400 -border 2 -bg white -relief groove pack $w.m -side top -fill x -padx 2 -pady 2 - $w.m configure -font $uifont + $w.m configure -font uifont button $w.ok -text Close -command "destroy $w" -default active pack $w.ok -side bottom - $w.ok configure -font $uifont + $w.ok configure -font uifont bind $w <Visibility> "focus $w.ok" bind $w <Key-Escape> "destroy $w" bind $w <Key-Return> "destroy $w" @@ -1184,8 +1327,8 @@ Gitk key bindings: <$M1T-Down> Scroll commit list down one line <$M1T-PageUp> Scroll commit list up one page <$M1T-PageDown> Scroll commit list down one page -<Shift-Up> Move to previous highlighted line -<Shift-Down> Move to next highlighted line +<Shift-Up> Find backwards (upwards, later commits) +<Shift-Down> Find forwards (downwards, earlier commits) <Delete>, b Scroll diff view up one page <Backspace> Scroll diff view up one page <Space> Scroll diff view down one page @@ -1207,10 +1350,10 @@ f Scroll diff view to next file " \ -justify left -bg white -border 2 -relief groove pack $w.m -side top -fill both -padx 2 -pady 2 - $w.m configure -font $uifont + $w.m configure -font uifont button $w.ok -text Close -command "destroy $w" -default active pack $w.ok -side bottom - $w.ok configure -font $uifont + $w.ok configure -font uifont bind $w <Visibility> "focus $w.ok" bind $w <Key-Escape> "destroy $w" bind $w <Key-Return> "destroy $w" @@ -1583,6 +1726,7 @@ proc pop_flist_menu {w X Y x y} { global ctext cflist cmitmode flist_menu flist_menu_file global treediffs diffids + stopfinding set l [lindex [split [$w index "@$x,$y"] "."] 0] if {$l <= 1} return if {$cmitmode eq "tree"} { @@ -1596,14 +1740,15 @@ proc pop_flist_menu {w X Y x y} { } proc flist_hl {only} { - global flist_menu_file highlight_files + global flist_menu_file findstring gdttype set x [shellquote $flist_menu_file] - if {$only || $highlight_files eq {}} { - set highlight_files $x + if {$only || $findstring eq {} || $gdttype ne "touching paths:"} { + set findstring $x } else { - append highlight_files " " $x + append findstring " " $x } + set gdttype "touching paths:" } # Functions for adding and removing shell-type quoting @@ -1740,22 +1885,22 @@ proc vieweditor {top n title} { toplevel $top wm title $top $title - label $top.nl -text "Name" -font $uifont - entry $top.name -width 20 -textvariable newviewname($n) -font $uifont + label $top.nl -text "Name" -font uifont + entry $top.name -width 20 -textvariable newviewname($n) -font uifont grid $top.nl $top.name -sticky w -pady 5 checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \ - -font $uifont + -font uifont grid $top.perm - -pady 5 -sticky w - message $top.al -aspect 1000 -font $uifont \ + message $top.al -aspect 1000 -font uifont \ -text "Commits to include (arguments to git rev-list):" grid $top.al - -sticky w -pady 5 entry $top.args -width 50 -textvariable newviewargs($n) \ - -background white -font $uifont + -background white -font uifont grid $top.args - -sticky ew -padx 5 - message $top.l -aspect 1000 -font $uifont \ + message $top.l -aspect 1000 -font uifont \ -text "Enter files and directories to include, one per line:" grid $top.l - -sticky w - text $top.t -width 40 -height 10 -background white -font $uifont + text $top.t -width 40 -height 10 -background white -font uifont if {[info exists viewfiles($n)]} { foreach f $viewfiles($n) { $top.t insert end $f @@ -1767,9 +1912,9 @@ proc vieweditor {top n title} { grid $top.t - -sticky ew -padx 5 frame $top.buts button $top.buts.ok -text "OK" -command [list newviewok $top $n] \ - -font $uifont + -font uifont button $top.buts.can -text "Cancel" -command [list destroy $top] \ - -font $uifont + -font uifont grid $top.buts.ok $top.buts.can grid columnconfigure $top.buts 0 -weight 1 -uniform a grid columnconfigure $top.buts 1 -weight 1 -uniform a @@ -1788,10 +1933,10 @@ proc doviewmenu {m first cmd op argv} { } proc allviewmenus {n op args} { - global viewhlmenu + # global viewhlmenu doviewmenu .bar.view 5 [list showview $n] $op $args - doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args + # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args } proc newviewok {top n} { @@ -1834,8 +1979,8 @@ proc newviewok {top n} { set viewname($n) $newviewname($n) doviewmenu .bar.view 5 [list showview $n] \ entryconf [list -label $viewname($n)] - doviewmenu $viewhlmenu 1 [list addvhighlight $n] \ - entryconf [list -label $viewname($n) -value $viewname($n)] + # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \ + # entryconf [list -label $viewname($n) -value $viewname($n)] } if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} { set viewfiles($n) $files @@ -1867,8 +2012,8 @@ proc addviewmenu {n} { .bar.view add radiobutton -label $viewname($n) \ -command [list showview $n] -variable selectedview -value $n - $viewhlmenu add radiobutton -label $viewname($n) \ - -command [list addvhighlight $n] -variable selectedhlview + #$viewhlmenu add radiobutton -label $viewname($n) \ + # -command [list addvhighlight $n] -variable selectedhlview } proc flatten {var} { @@ -1892,17 +2037,17 @@ proc unflatten {var l} { proc showview {n} { global curview viewdata viewfiles - global displayorder parentlist rowidlist rowoffsets + global displayorder parentlist rowidlist rowisopt rowfinal global colormap rowtextx commitrow nextcolor canvxmax - global numcommits rowrangelist commitlisted idrowranges rowchk + global numcommits commitlisted global selectedline currentid canv canvy0 global treediffs global pending_select phase - global commitidx rowlaidout rowoptim + global commitidx global commfd global selectedview selectfirst global vparentlist vdisporder vcmitlisted - global hlview selectedhlview + global hlview selectedhlview commitinterest if {$n == $curview} return set selid {} @@ -1928,15 +2073,11 @@ proc showview {n} { set vparentlist($curview) $parentlist 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] - } elseif {![info exists viewdata($curview)] - || [lindex $viewdata($curview) 0] ne {}} { + if {$phase ne {} || + ![info exists viewdata($curview)] || + [lindex $viewdata($curview) 0] ne {}} { set viewdata($curview) \ - [list {} $rowidlist $rowoffsets $rowrangelist] + [list $phase $rowidlist $rowisopt $rowfinal] } } catch {unset treediffs} @@ -1945,12 +2086,14 @@ proc showview {n} { unset hlview set selectedhlview None } + catch {unset commitinterest} set curview $n set selectedview $n .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}] .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}] + run refill_reflist if {![info exists viewdata($n)]} { if {$selid ne {}} { set pending_select $selid @@ -1965,19 +2108,9 @@ proc showview {n} { set parentlist $vparentlist($n) set commitlisted $vcmitlisted($n) set rowidlist [lindex $v 1] - set rowoffsets [lindex $v 2] - set rowrangelist [lindex $v 3] - if {$phase eq {}} { - set numcommits [llength $displayorder] - catch {unset idrowranges} - } else { - unflatten idrowranges [lindex $v 4] - unflatten idinlist [lindex $v 5] - set rowlaidout [lindex $v 6] - set rowoptim [lindex $v 7] - set numcommits [lindex $v 8] - catch {unset rowchk} - } + set rowisopt [lindex $v 2] + set rowfinal [lindex $v 3] + set numcommits $commitidx($n) catch {unset colormap} catch {unset rowtextx} @@ -2021,7 +2154,6 @@ proc showview {n} { } elseif {$numcommits == 0} { show_status "No commits selected" } - run refill_reflist } # Stuff relating to the highlighting facility @@ -2073,12 +2205,12 @@ proc bolden_name {row font} { } proc unbolden {} { - global mainfont boldrows + global boldrows set stillbold {} foreach row $boldrows { if {![ishighlighted $row]} { - bolden $row $mainfont + bolden $row mainfont } else { lappend stillbold $row } @@ -2094,7 +2226,7 @@ proc addvhighlight {n} { } set hlview $n if {$n != $curview && ![info exists viewdata($n)]} { - set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}] + set viewdata($n) [list getcommits {{}} 0 0 0] set vparentlist($n) {} set vdisporder($n) {} set vcmitlisted($n) {} @@ -2117,9 +2249,8 @@ proc delvhighlight {} { proc vhighlightmore {} { global hlview vhl_done commitidx vhighlights - global displayorder vdisporder curview mainfont + global displayorder vdisporder curview - set font [concat $mainfont bold] set max $commitidx($hlview) if {$hlview == $curview} { set disp $displayorder @@ -2135,7 +2266,7 @@ proc vhighlightmore {} { set row $commitrow($curview,$id) if {$r0 <= $row && $row <= $r1} { if {![highlighted $row]} { - bolden $row $font + bolden $row mainfontbold } set vhighlights($row) 1 } @@ -2145,11 +2276,11 @@ proc vhighlightmore {} { } proc askvhighlight {row id} { - global hlview vhighlights commitrow iddrawn mainfont + global hlview vhighlights commitrow iddrawn if {[info exists commitrow($hlview,$id)]} { if {[info exists iddrawn($id)] && ![ishighlighted $row]} { - bolden $row [concat $mainfont bold] + bolden $row mainfontbold } set vhighlights($row) 1 } else { @@ -2157,9 +2288,9 @@ proc askvhighlight {row id} { } } -proc hfiles_change {name ix op} { +proc hfiles_change {} { global highlight_files filehighlight fhighlights fh_serial - global mainfont highlight_paths + global highlight_paths gdttype if {[info exists filehighlight]} { # delete previous highlights @@ -2177,6 +2308,69 @@ proc hfiles_change {name ix op} { } } +proc gdttype_change {name ix op} { + global gdttype highlight_files findstring findpattern + + stopfinding + if {$findstring ne {}} { + if {$gdttype eq "containing:"} { + if {$highlight_files ne {}} { + set highlight_files {} + hfiles_change + } + findcom_change + } else { + if {$findpattern ne {}} { + set findpattern {} + findcom_change + } + set highlight_files $findstring + hfiles_change + } + drawvisible + } + # enable/disable findtype/findloc menus too +} + +proc find_change {name ix op} { + global gdttype findstring highlight_files + + stopfinding + if {$gdttype eq "containing:"} { + findcom_change + } else { + if {$highlight_files ne $findstring} { + set highlight_files $findstring + hfiles_change + } + } + drawvisible +} + +proc findcom_change args { + global nhighlights boldnamerows + global findpattern findtype findstring gdttype + + stopfinding + # delete previous highlights, if any + foreach row $boldnamerows { + bolden_name $row mainfont + } + set boldnamerows {} + catch {unset nhighlights} + unbolden + unmarkmatches + if {$gdttype ne "containing:" || $findstring eq {}} { + set findpattern {} + } elseif {$findtype eq "Regexp"} { + set findpattern $findstring + } else { + set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \ + $findstring] + set findpattern "*$e*" + } +} + proc makepatterns {l} { set ret {} foreach e $l { @@ -2199,8 +2393,11 @@ proc do_file_hl {serial} { set highlight_paths [makepatterns $paths] highlight_filelist set gdtargs [concat -- $paths] - } else { + } elseif {$gdttype eq "adding/removing string:"} { set gdtargs [list "-S$highlight_files"] + } else { + # must be "containing:", i.e. we're searching commit info + return } set cmd [concat | git diff-tree -r -s --stdin $gdtargs] set filehighlight [open $cmd r+] @@ -2230,8 +2427,8 @@ proc askfilehighlight {row id} { } proc readfhighlight {} { - global filehighlight fhighlights commitrow curview mainfont iddrawn - global fhl_list + global filehighlight fhighlights commitrow curview iddrawn + global fhl_list find_dirn if {![info exists filehighlight]} { return 0 @@ -2252,7 +2449,7 @@ proc readfhighlight {} { if {![info exists commitrow($curview,$line)]} continue set row $commitrow($curview,$line) if {[info exists iddrawn($line)] && ![ishighlighted $row]} { - bolden $row [concat $mainfont bold] + bolden $row mainfontbold } set fhighlights($row) 1 } @@ -2263,35 +2460,17 @@ proc readfhighlight {} { unset filehighlight return 0 } - next_hlcont - return 1 -} - -proc find_change {name ix op} { - global nhighlights mainfont boldnamerows - global findstring findpattern findtype - - # delete previous highlights, if any - foreach row $boldnamerows { - bolden_name $row $mainfont - } - set boldnamerows {} - catch {unset nhighlights} - unbolden - unmarkmatches - if {$findtype ne "Regexp"} { - set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \ - $findstring] - set findpattern "*$e*" + if {[info exists find_dirn]} { + run findmore } - drawvisible + return 1 } proc doesmatch {f} { - global findtype findstring findpattern + global findtype findpattern if {$findtype eq "Regexp"} { - return [regexp $findstring $f] + return [regexp $findpattern $f] } elseif {$findtype eq "IgnCase"} { return [string match -nocase $findpattern $f] } else { @@ -2300,7 +2479,7 @@ proc doesmatch {f} { } proc askfindhighlight {row id} { - global nhighlights commitinfo iddrawn mainfont + global nhighlights commitinfo iddrawn global findloc global markingmatches @@ -2321,11 +2500,10 @@ proc askfindhighlight {row id} { } } if {$isbold && [info exists iddrawn($id)]} { - set f [concat $mainfont bold] if {![ishighlighted $row]} { - bolden $row $f + bolden $row mainfontbold if {$isbold > 1} { - bolden_name $row $f + bolden_name $row mainfontbold } } if {$markingmatches} { @@ -2454,7 +2632,7 @@ proc is_ancestor {a} { } proc askrelhighlight {row id} { - global descendent highlight_related iddrawn mainfont rhighlights + global descendent highlight_related iddrawn rhighlights global selectedline ancestor if {![info exists selectedline]} return @@ -2478,87 +2656,12 @@ proc askrelhighlight {row id} { } if {[info exists iddrawn($id)]} { if {$isbold && ![ishighlighted $row]} { - bolden $row [concat $mainfont bold] + bolden $row mainfontbold } } set rhighlights($row) $isbold } -proc next_hlcont {} { - global fhl_row fhl_dirn displayorder numcommits - global vhighlights fhighlights nhighlights rhighlights - global hlview filehighlight findstring highlight_related - - if {![info exists fhl_dirn] || $fhl_dirn == 0} return - set row $fhl_row - while {1} { - if {$row < 0 || $row >= $numcommits} { - bell - set fhl_dirn 0 - return - } - set id [lindex $displayorder $row] - if {[info exists hlview]} { - if {![info exists vhighlights($row)]} { - askvhighlight $row $id - } - if {$vhighlights($row) > 0} break - } - if {$findstring ne {}} { - if {![info exists nhighlights($row)]} { - askfindhighlight $row $id - } - if {$nhighlights($row) > 0} break - } - if {$highlight_related ne "None"} { - if {![info exists rhighlights($row)]} { - askrelhighlight $row $id - } - if {$rhighlights($row) > 0} break - } - if {[info exists filehighlight]} { - if {![info exists fhighlights($row)]} { - # ask for a few more while we're at it... - set r $row - for {set n 0} {$n < 100} {incr n} { - if {![info exists fhighlights($r)]} { - askfilehighlight $r [lindex $displayorder $r] - } - incr r $fhl_dirn - if {$r < 0 || $r >= $numcommits} break - } - flushhighlights - } - if {$fhighlights($row) < 0} { - set fhl_row $row - return - } - if {$fhighlights($row) > 0} break - } - incr row $fhl_dirn - } - set fhl_dirn 0 - selectline $row 1 -} - -proc next_highlight {dirn} { - global selectedline fhl_row fhl_dirn - global hlview filehighlight findstring highlight_related - - if {![info exists selectedline]} return - if {!([info exists hlview] || $findstring ne {} || - $highlight_related ne "None" || [info exists filehighlight])} return - set fhl_row [expr {$selectedline + $dirn}] - set fhl_dirn $dirn - next_hlcont -} - -proc cancel_next_highlight {} { - global fhl_dirn - - set fhl_dirn 0 -} - # Graph layout functions proc shortids {ids} { @@ -2575,108 +2678,43 @@ proc shortids {ids} { return $res } -proc incrange {l x o} { - set n [llength $l] - while {$x < $n} { - set e [lindex $l $x] - if {$e ne {}} { - lset l $x [expr {$e + $o}] - } - incr x - } - return $l -} - proc ntimes {n o} { set ret {} - for {} {$n > 0} {incr n -1} { - lappend ret $o - } - return $ret -} - -proc usedinrange {id l1 l2} { - 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 $children($curview,$id) - foreach c $kids { - set r $commitrow($curview,$c) - if {$l1 <= $r && $r <= $l2} { - return [expr {$r - $l1 + 1}] + set o [list $o] + for {set mask 1} {$mask <= $n} {incr mask $mask} { + if {($n & $mask) != 0} { + set ret [concat $ret $o] } + set o [concat $o $o] } - return 0 + return $ret } -proc sanity {row {full 0}} { - global rowidlist rowoffsets +# Work out where id should go in idlist so that order-token +# values increase from left to right +proc idcol {idlist id {i 0}} { + global ordertok curview - set col -1 - set ids [lindex $rowidlist $row] - foreach id $ids { - incr col - if {$id eq {}} continue - if {$col < [llength $ids] - 1 && - [lsearch -exact -start [expr {$col+1}] $ids $id] >= 0} { - puts "oops: [shortids $id] repeated in row $row col $col: {[shortids [lindex $rowidlist $row]]}" - } - set o [lindex $rowoffsets $row $col] - set y $row - set x $col - while {$o ne {}} { - incr y -1 - incr x $o - if {[lindex $rowidlist $y $x] != $id} { - puts "oops: rowoffsets wrong at row [expr {$y+1}] col [expr {$x-$o}]" - puts " id=[shortids $id] check started at row $row" - for {set i $row} {$i >= $y} {incr i -1} { - puts " row $i ids={[shortids [lindex $rowidlist $i]]} offs={[lindex $rowoffsets $i]}" - } - break - } - if {!$full} break - set o [lindex $rowoffsets $y $x] + set t $ordertok($curview,$id) + if {$i >= [llength $idlist] || + $t < $ordertok($curview,[lindex $idlist $i])} { + if {$i > [llength $idlist]} { + set i [llength $idlist] } - } -} - -proc makeuparrow {oid x y z} { - global rowidlist rowoffsets uparrowlen idrowranges displayorder - - for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} { - incr y -1 - incr x $z - set off0 [lindex $rowoffsets $y] - for {set x0 $x} {1} {incr x0} { - if {$x0 >= [llength $off0]} { - set x0 [llength [lindex $rowoffsets [expr {$y-1}]]] - break - } - set z [lindex $off0 $x0] - if {$z ne {}} { - incr x0 $z - break - } + while {[incr i -1] >= 0 && + $t < $ordertok($curview,[lindex $idlist $i])} {} + incr i + } else { + if {$t > $ordertok($curview,[lindex $idlist $i])} { + while {[incr i] < [llength $idlist] && + $t >= $ordertok($curview,[lindex $idlist $i])} {} } - set z [expr {$x0 - $x}] - lset rowidlist $y [linsert [lindex $rowidlist $y] $x $oid] - lset rowoffsets $y [linsert [lindex $rowoffsets $y] $x $z] } - set tmp [lreplace [lindex $rowoffsets $y] $x $x {}] - lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1] - lappend idrowranges($oid) [lindex $displayorder $y] + return $i } proc initlayout {} { - global rowidlist rowoffsets displayorder commitlisted - global rowlaidout rowoptim - global idinlist rowchk rowrangelist idrowranges + global rowidlist rowisopt rowfinal displayorder commitlisted global numcommits canvxmax canv global nextcolor global parentlist @@ -2687,18 +2725,13 @@ proc initlayout {} { set displayorder {} set commitlisted {} set parentlist {} - set rowrangelist {} set nextcolor 0 - set rowidlist {{}} - set rowoffsets {{}} - catch {unset idinlist} - catch {unset rowchk} - set rowlaidout 0 - set rowoptim 0 + set rowidlist {} + set rowisopt {} + set rowfinal {} set canvxmax [$canv cget -width] catch {unset colormap} catch {unset rowtextx} - catch {unset idrowranges} set selectfirst 1 } @@ -2730,61 +2763,19 @@ proc visiblerows {} { return [list $r0 $r1] } -proc layoutmore {tmax allread} { - global rowlaidout rowoptim commitidx numcommits optim_delay - global uparrowlen curview rowidlist idinlist +proc layoutmore {} { + global commitidx viewcomplete numcommits + global uparrowlen downarrowlen mingaplen curview - set showlast 0 - set showdelay $optim_delay - set optdelay [expr {$uparrowlen + 1}] - while {1} { - 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 - } - optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}] - incr rowoptim $nr - } elseif {$commitidx($curview) > $rowlaidout} { - set nr [expr {$commitidx($curview) - $rowlaidout}] - # may need to increase this threshold if uparrowlen or - # mingaplen are increased... - if {$nr > 150} { - set nr 150 - } - set row $rowlaidout - 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 - } - if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} { - return 1 - } + set show $commitidx($curview) + if {$show > $numcommits || $viewcomplete($curview)} { + showstuff $show $viewcomplete($curview) } } proc showstuff {canshow last} { global numcommits commitrow pending_select selectedline curview - global lookingforhead mainheadid displayorder selectfirst + global mainheadid displayorder selectfirst global lastscrollset commitinterest if {$numcommits == 0} { @@ -2792,15 +2783,6 @@ proc showstuff {canshow last} { set phase "incrdraw" allcanvs delete all } - for {set l $numcommits} {$l < $canshow} {incr l} { - set id [lindex $displayorder $l] - if {[info exists commitinterest($id)]} { - foreach script $commitinterest($id) { - eval [string map [list "%I" $id] $script] - } - unset commitinterest($id) - } - } set r0 $numcommits set prev $numcommits set numcommits $canshow @@ -2831,28 +2813,22 @@ proc showstuff {canshow last} { 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 + global curview mainheadid phase commitrow if {[info exists commitrow($curview,$mainheadid)] && ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} { dodiffindex } elseif {$phase ne {}} { - set lookingforhead 1 + lappend commitinterest($mainheadid) {} } } proc dohidelocalchanges {} { - global lookingforhead localfrow localirow lserial + global localfrow localirow lserial - set lookingforhead 0 if {$localfrow >= 0} { removerow $localfrow set localfrow -1 @@ -2869,8 +2845,9 @@ proc dohidelocalchanges {} { # spawn off a process to do git diff-index --cached HEAD proc dodiffindex {} { - global localirow localfrow lserial + global localirow localfrow lserial showlocalchanges + if {!$showlocalchanges} return incr lserial set localfrow -1 set localirow -1 @@ -2941,207 +2918,325 @@ proc readdifffiles {fd serial} { return 0 } -proc layoutrows {row endrow last} { - global rowidlist rowoffsets displayorder - global uparrowlen downarrowlen maxwidth mingaplen - global children parentlist - global idrowranges - global commitidx curview - global idinlist rowchk rowrangelist +proc nextuse {id row} { + global commitrow curview children - set idlist [lindex $rowidlist $row] - set offs [lindex $rowoffsets $row] - while {$row < $endrow} { - set id [lindex $displayorder $row] - set nev [expr {[llength $idlist] - $maxwidth + 1}] - foreach p [lindex $parentlist $row] { - if {![info exists idinlist($p)] || !$idinlist($p)} { - incr nev - } - } - if {$nev > 0} { - if {!$last && - $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break - for {set x [llength $idlist]} {[incr x -1] >= 0} {} { - set i [lindex $idlist $x] - if {![info exists rowchk($i)] || $row >= $rowchk($i)} { - set r [usedinrange $i [expr {$row - $downarrowlen}] \ - [expr {$row + $uparrowlen + $mingaplen}]] - if {$r == 0} { - set idlist [lreplace $idlist $x $x] - set offs [lreplace $offs $x $x] - set offs [incrange $offs $x 1] - set idinlist($i) 0 - set rm1 [expr {$row - 1}] - lappend idrowranges($i) [lindex $displayorder $rm1] - if {[incr nev -1] <= 0} break - continue - } - set rowchk($i) [expr {$row + $r}] - } + if {[info exists children($curview,$id)]} { + foreach kid $children($curview,$id) { + if {![info exists commitrow($curview,$kid)]} { + return -1 + } + if {$commitrow($curview,$kid) > $row} { + return $commitrow($curview,$kid) } - lset rowidlist $row $idlist - lset rowoffsets $row $offs } - set oldolds {} - set newolds {} - foreach p [lindex $parentlist $row] { - if {![info exists idinlist($p)]} { - lappend newolds $p - } elseif {!$idinlist($p)} { - lappend oldolds $p + } + if {[info exists commitrow($curview,$id)]} { + return $commitrow($curview,$id) + } + return -1 +} + +proc prevuse {id row} { + global commitrow curview children + + set ret -1 + if {[info exists children($curview,$id)]} { + foreach kid $children($curview,$id) { + if {![info exists commitrow($curview,$kid)]} break + if {$commitrow($curview,$kid) < $row} { + set ret $commitrow($curview,$kid) } - set idinlist($p) 1 } - set col [lsearch -exact $idlist $id] - if {$col < 0} { - set col [llength $idlist] - lappend idlist $id - lset rowidlist $row $idlist - set z {} - if {$children($curview,$id) ne {}} { - set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}] - unset idinlist($id) - } - lappend offs $z - lset rowoffsets $row $offs - if {$z ne {}} { - makeuparrow $id $col $row $z + } + return $ret +} + +proc make_idlist {row} { + global displayorder parentlist uparrowlen downarrowlen mingaplen + global commitidx curview ordertok children commitrow + + set r [expr {$row - $mingaplen - $downarrowlen - 1}] + if {$r < 0} { + set r 0 + } + set ra [expr {$row - $downarrowlen}] + if {$ra < 0} { + set ra 0 + } + set rb [expr {$row + $uparrowlen}] + if {$rb > $commitidx($curview)} { + set rb $commitidx($curview) + } + set ids {} + for {} {$r < $ra} {incr r} { + set nextid [lindex $displayorder [expr {$r + 1}]] + foreach p [lindex $parentlist $r] { + if {$p eq $nextid} continue + set rn [nextuse $p $r] + if {$rn >= $row && + $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} { + lappend ids [list $ordertok($curview,$p) $p] } - } else { - unset idinlist($id) - } - set ranges {} - if {[info exists idrowranges($id)]} { - set ranges $idrowranges($id) - lappend ranges $id - unset idrowranges($id) - } - lappend rowrangelist $ranges - incr row - set offs [ntimes [llength $idlist] 0] - set l [llength $newolds] - set idlist [eval lreplace \$idlist $col $col $newolds] - set o 0 - if {$l != 1} { - set offs [lrange $offs 0 [expr {$col - 1}]] - foreach x $newolds { - lappend offs {} - incr o -1 - } - incr o - set tmp [expr {[llength $idlist] - [llength $offs]}] - if {$tmp > 0} { - set offs [concat $offs [ntimes $tmp $o]] + } + } + for {} {$r < $row} {incr r} { + set nextid [lindex $displayorder [expr {$r + 1}]] + foreach p [lindex $parentlist $r] { + if {$p eq $nextid} continue + set rn [nextuse $p $r] + if {$rn < 0 || $rn >= $row} { + lappend ids [list $ordertok($curview,$p) $p] } - } else { - lset offs $col {} } - foreach i $newolds { - set idrowranges($i) $id + } + set id [lindex $displayorder $row] + lappend ids [list $ordertok($curview,$id) $id] + while {$r < $rb} { + foreach p [lindex $parentlist $r] { + set firstkid [lindex $children($curview,$p) 0] + if {$commitrow($curview,$firstkid) < $row} { + lappend ids [list $ordertok($curview,$p) $p] + } } - incr col $l - foreach oid $oldolds { - set idlist [linsert $idlist $col $oid] - set offs [linsert $offs $col $o] - makeuparrow $oid $col $row $o - incr col + incr r + set id [lindex $displayorder $r] + if {$id ne {}} { + set firstkid [lindex $children($curview,$id) 0] + if {$firstkid ne {} && $commitrow($curview,$firstkid) < $row} { + lappend ids [list $ordertok($curview,$id) $id] + } } - lappend rowidlist $idlist - lappend rowoffsets $offs } - return $row + set idlist {} + foreach idx [lsort -unique $ids] { + lappend idlist [lindex $idx 1] + } + return $idlist } -proc addextraid {id row} { - global displayorder commitrow commitinfo - global commitidx commitlisted - global parentlist children curview +proc rowsequal {a b} { + while {[set i [lsearch -exact $a {}]] >= 0} { + set a [lreplace $a $i $i] + } + while {[set i [lsearch -exact $b {}]] >= 0} { + set b [lreplace $b $i $i] + } + return [expr {$a eq $b}] +} - incr commitidx($curview) - lappend displayorder $id - lappend commitlisted 0 - lappend parentlist {} - set commitrow($curview,$id) $row - readcommit $id - if {![info exists commitinfo($id)]} { - set commitinfo($id) {"No commit information available"} +proc makeupline {id row rend col} { + global rowidlist uparrowlen downarrowlen mingaplen + + for {set r $rend} {1} {set r $rstart} { + set rstart [prevuse $id $r] + if {$rstart < 0} return + if {$rstart < $row} break } - if {![info exists children($curview,$id)]} { - set children($curview,$id) {} + if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} { + set rstart [expr {$rend - $uparrowlen - 1}] + } + for {set r $rstart} {[incr r] <= $row} {} { + set idlist [lindex $rowidlist $r] + if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} { + set col [idcol $idlist $id $col] + lset rowidlist $r [linsert $idlist $col $id] + changedrow $r + } } } -proc layouttail {} { - global rowidlist rowoffsets idinlist commitidx curview - global idrowranges rowrangelist +proc layoutrows {row endrow} { + global rowidlist rowisopt rowfinal displayorder + global uparrowlen downarrowlen maxwidth mingaplen + global children parentlist + global commitidx viewcomplete curview commitrow - set row $commitidx($curview) - set idlist [lindex $rowidlist $row] - while {$idlist ne {}} { - set col [expr {[llength $idlist] - 1}] - set id [lindex $idlist $col] - addextraid $id $row - catch {unset idinlist($id)} - lappend idrowranges($id) $id - lappend rowrangelist $idrowranges($id) - unset idrowranges($id) - incr row - set offs [ntimes $col 0] - set idlist [lreplace $idlist $col $col] - lappend rowidlist $idlist - lappend rowoffsets $offs - } - - foreach id [array names idinlist] { - unset idinlist($id) - addextraid $id $row - lset rowidlist $row [list $id] - lset rowoffsets $row 0 - makeuparrow $id 0 $row 0 - lappend idrowranges($id) $id - lappend rowrangelist $idrowranges($id) - unset idrowranges($id) - incr row - lappend rowidlist {} - lappend rowoffsets {} + set idlist {} + if {$row > 0} { + set rm1 [expr {$row - 1}] + foreach id [lindex $rowidlist $rm1] { + if {$id ne {}} { + lappend idlist $id + } + } + set final [lindex $rowfinal $rm1] + } + for {} {$row < $endrow} {incr row} { + set rm1 [expr {$row - 1}] + if {$rm1 < 0 || $idlist eq {}} { + set idlist [make_idlist $row] + set final 1 + } else { + set id [lindex $displayorder $rm1] + set col [lsearch -exact $idlist $id] + set idlist [lreplace $idlist $col $col] + foreach p [lindex $parentlist $rm1] { + if {[lsearch -exact $idlist $p] < 0} { + set col [idcol $idlist $p $col] + set idlist [linsert $idlist $col $p] + # if not the first child, we have to insert a line going up + if {$id ne [lindex $children($curview,$p) 0]} { + makeupline $p $rm1 $row $col + } + } + } + set id [lindex $displayorder $row] + if {$row > $downarrowlen} { + set termrow [expr {$row - $downarrowlen - 1}] + foreach p [lindex $parentlist $termrow] { + set i [lsearch -exact $idlist $p] + if {$i < 0} continue + set nr [nextuse $p $termrow] + if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} { + set idlist [lreplace $idlist $i $i] + } + } + } + set col [lsearch -exact $idlist $id] + if {$col < 0} { + set col [idcol $idlist $id] + set idlist [linsert $idlist $col $id] + if {$children($curview,$id) ne {}} { + makeupline $id $rm1 $row $col + } + } + set r [expr {$row + $uparrowlen - 1}] + if {$r < $commitidx($curview)} { + set x $col + foreach p [lindex $parentlist $r] { + if {[lsearch -exact $idlist $p] >= 0} continue + set fk [lindex $children($curview,$p) 0] + if {$commitrow($curview,$fk) < $row} { + set x [idcol $idlist $p $x] + set idlist [linsert $idlist $x $p] + } + } + if {[incr r] < $commitidx($curview)} { + set p [lindex $displayorder $r] + if {[lsearch -exact $idlist $p] < 0} { + set fk [lindex $children($curview,$p) 0] + if {$fk ne {} && $commitrow($curview,$fk) < $row} { + set x [idcol $idlist $p $x] + set idlist [linsert $idlist $x $p] + } + } + } + } + } + if {$final && !$viewcomplete($curview) && + $row + $uparrowlen + $mingaplen + $downarrowlen + >= $commitidx($curview)} { + set final 0 + } + set l [llength $rowidlist] + if {$row == $l} { + lappend rowidlist $idlist + lappend rowisopt 0 + lappend rowfinal $final + } elseif {$row < $l} { + if {![rowsequal $idlist [lindex $rowidlist $row]]} { + lset rowidlist $row $idlist + changedrow $row + } + lset rowfinal $row $final + } else { + set pad [ntimes [expr {$row - $l}] {}] + set rowidlist [concat $rowidlist $pad] + lappend rowidlist $idlist + set rowfinal [concat $rowfinal $pad] + lappend rowfinal $final + set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]] + } + } + return $row +} + +proc changedrow {row} { + global displayorder iddrawn rowisopt need_redisplay + + set l [llength $rowisopt] + if {$row < $l} { + lset rowisopt $row 0 + if {$row + 1 < $l} { + lset rowisopt [expr {$row + 1}] 0 + if {$row + 2 < $l} { + lset rowisopt [expr {$row + 2}] 0 + } + } + } + set id [lindex $displayorder $row] + if {[info exists iddrawn($id)]} { + set need_redisplay 1 } } proc insert_pad {row col npad} { - global rowidlist rowoffsets + global rowidlist set pad [ntimes $npad {}] - lset rowidlist $row [eval linsert [list [lindex $rowidlist $row]] $col $pad] - set tmp [eval linsert [list [lindex $rowoffsets $row]] $col $pad] - lset rowoffsets $row [incrange $tmp [expr {$col + $npad}] [expr {-$npad}]] + set idlist [lindex $rowidlist $row] + set bef [lrange $idlist 0 [expr {$col - 1}]] + set aft [lrange $idlist $col end] + set i [lsearch -exact $aft {}] + if {$i > 0} { + set aft [lreplace $aft $i $i] + } + lset rowidlist $row [concat $bef $pad $aft] + changedrow $row } proc optimize_rows {row col endrow} { - global rowidlist rowoffsets displayorder + global rowidlist rowisopt displayorder curview children - for {} {$row < $endrow} {incr row} { - set idlist [lindex $rowidlist $row] - set offs [lindex $rowoffsets $row] + if {$row < 1} { + set row 1 + } + for {} {$row < $endrow} {incr row; set col 0} { + if {[lindex $rowisopt $row]} continue set haspad 0 - for {} {$col < [llength $offs]} {incr col} { - if {[lindex $idlist $col] eq {}} { + set y0 [expr {$row - 1}] + set ym [expr {$row - 2}] + set idlist [lindex $rowidlist $row] + set previdlist [lindex $rowidlist $y0] + if {$idlist eq {} || $previdlist eq {}} continue + if {$ym >= 0} { + set pprevidlist [lindex $rowidlist $ym] + if {$pprevidlist eq {}} continue + } else { + set pprevidlist {} + } + set x0 -1 + set xm -1 + for {} {$col < [llength $idlist]} {incr col} { + set id [lindex $idlist $col] + if {[lindex $previdlist $col] eq $id} continue + if {$id eq {}} { set haspad 1 continue } - set z [lindex $offs $col] - if {$z eq {}} continue + set x0 [lsearch -exact $previdlist $id] + if {$x0 < 0} continue + set z [expr {$x0 - $col}] set isarrow 0 - set x0 [expr {$col + $z}] - set y0 [expr {$row - 1}] - set z0 [lindex $rowoffsets $y0 $x0] + set z0 {} + if {$ym >= 0} { + set xm [lsearch -exact $pprevidlist $id] + if {$xm >= 0} { + set z0 [expr {$xm - $x0}] + } + } if {$z0 eq {}} { - set id [lindex $idlist $col] - set ranges [rowranges $id] - if {$ranges ne {} && $y0 > [lindex $ranges 0]} { + # if row y0 is the first child of $id then it's not an arrow + if {[lindex $children($curview,$id) 0] ne + [lindex $displayorder $y0]} { set isarrow 1 } } + if {!$isarrow && $id ne [lindex $displayorder $row] && + [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} { + 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 @@ -3150,43 +3245,32 @@ proc optimize_rows {row col endrow} { # 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 if {$y0 > 0} { optimize_rows $y0 $x0 $row } - set z [lindex $offs $col] - set x0 [expr {$col + $z}] - set z0 [lindex $rowoffsets $y0 $x0] + set previdlist [lindex $rowidlist $y0] + set x0 [lsearch -exact $previdlist $id] + set z [expr {$x0 - $col}] + if {$z0 ne {}} { + set pprevidlist [lindex $rowidlist $ym] + set xm [lsearch -exact $pprevidlist $id] + set z0 [expr {$xm - $x0}] + } } elseif {$z > 1 || ($z > 0 && $isarrow)} { # Line currently goes right too much; - # insert pads in this line and adjust the next's rowoffsets + # insert pads in this line set npad [expr {$z - 1 + $isarrow}] - set y1 [expr {$row + 1}] - set offs2 [lindex $rowoffsets $y1] - set x1 -1 - foreach z $offs2 { - incr x1 - if {$z eq {} || $x1 + $z < $col} continue - if {$x1 + $z > $col} { - incr npad - } - lset rowoffsets $y1 [incrange $offs2 $x1 $npad] - break - } - set pad [ntimes $npad {}] - set idlist [eval linsert \$idlist $col $pad] - set tmp [eval linsert \$offs $col $pad] + insert_pad $row $col $npad + set idlist [lindex $rowidlist $row] incr col $npad - set offs [incrange $tmp $col [expr {-$npad}]] - set z [lindex $offs $col] + set z [expr {$x0 - $col}] set haspad 1 } - if {$z0 eq {} && !$isarrow} { + if {$z0 eq {} && !$isarrow && $ym >= 0} { # this line links to its first child on row $row-2 - set rm2 [expr {$row - 2}] - set id [lindex $displayorder $rm2] - set xc [lsearch -exact [lindex $rowidlist $rm2] $id] + set id [lindex $displayorder $ym] + set xc [lsearch -exact $pprevidlist $id] if {$xc >= 0} { set z0 [expr {$xc - $x0}] } @@ -3194,52 +3278,35 @@ proc optimize_rows {row col endrow} { # 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] - optimize_rows $y0 [expr {$x0 + 1}] $row + incr x0 + optimize_rows $y0 $x0 $row + set previdlist [lindex $rowidlist $y0] } } 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 {}} { + set id [lindex $idlist $col] + if {$id eq {}} break + set x0 [lsearch -exact $previdlist $id] + if {$x0 < 0} { # check if this is the link to the first child - set id [lindex $idlist $col] - set ranges [rowranges $id] - if {$ranges ne {} && $row == [lindex $ranges 0]} { + set kid [lindex $displayorder $y0] + if {[lindex $children($curview,$id) 0] eq $kid} { # it is, work out offset to child - set y0 [expr {$row - 1}] - set id [lindex $displayorder $y0] - set x0 [lsearch -exact [lindex $rowidlist $y0] $id] - if {$x0 >= 0} { - set o [expr {$x0 - $col}] - } + set x0 [lsearch -exact $previdlist $kid] } } - if {$o eq {} || $o <= 0} break + if {$x0 <= $col} 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] - set x1 -1 - foreach z $offs2 { - incr x1 - if {$z eq {} || $x1 + $z < $col} continue - lset rowoffsets $y1 [incrange $offs2 $x1 1] - break - } + # isn't the last column + if {$x0 >= 0 && [incr col] < [llength $idlist]} { set idlist [linsert $idlist $col {}] - set tmp [linsert $offs $col {}] - incr col - set offs [incrange $tmp $col -1] + lset rowidlist $row $idlist + changedrow $row } } - lset rowidlist $row $idlist - lset rowoffsets $row $offs - set col 0 } } @@ -3264,51 +3331,64 @@ proc linewidth {id} { } proc rowranges {id} { - global phase idrowranges commitrow rowlaidout rowrangelist curview - - set ranges {} - if {$phase eq {} || - ([info exists commitrow($curview,$id)] - && $commitrow($curview,$id) < $rowlaidout)} { - set ranges [lindex $rowrangelist $commitrow($curview,$id)] - } elseif {[info exists idrowranges($id)]} { - set ranges $idrowranges($id) - } - set linenos {} - foreach rid $ranges { - lappend linenos $commitrow($curview,$rid) - } - if {$linenos ne {}} { - lset linenos 0 [expr {[lindex $linenos 0] + 1}] - } - return $linenos -} - -# work around tk8.4 refusal to draw arrows on diagonal segments -proc adjarrowhigh {coords} { - global linespc - - 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] + global commitrow curview children uparrowlen downarrowlen + global rowidlist + + set kids $children($curview,$id) + if {$kids eq {}} { + return {} + } + set ret {} + lappend kids $id + foreach child $kids { + if {![info exists commitrow($curview,$child)]} break + set row $commitrow($curview,$child) + if {![info exists prev]} { + lappend ret [expr {$row + 1}] } 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] + if {$row <= $prevrow} { + puts "oops children out of order [shortids $id] $row < [shortids $prev] $prevrow" + } + # see if the line extends the whole way from prevrow to row + if {$row > $prevrow + $uparrowlen + $downarrowlen && + [lsearch -exact [lindex $rowidlist \ + [expr {int(($row + $prevrow) / 2)}]] $id] < 0} { + # it doesn't, see where it ends + set r [expr {$prevrow + $downarrowlen}] + if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} { + while {[incr r -1] > $prevrow && + [lsearch -exact [lindex $rowidlist $r] $id] < 0} {} + } else { + while {[incr r] <= $row && + [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {} + incr r -1 + } + lappend ret $r + # see where it starts up again + set r [expr {$row - $uparrowlen}] + if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} { + while {[incr r] < $row && + [lsearch -exact [lindex $rowidlist $r] $id] < 0} {} + } else { + while {[incr r -1] >= $prevrow && + [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {} + incr r + } + lappend ret $r + } + } + if {$child eq $id} { + lappend ret $row } + set prev $id + set prevrow $row } - return $coords + return $ret } proc drawlineseg {id row endrow arrowlow} { global rowidlist displayorder iddrawn linesegs - global canv colormap linespc curview maxlinelen + global canv colormap linespc curview maxlinelen parentlist set cols [list [lsearch -exact [lindex $rowidlist $row] $id]] set le [expr {$row + 1}] @@ -3383,9 +3463,11 @@ proc drawlineseg {id row endrow arrowlow} { 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 + } elseif {$arrowlow} { + if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 || + [lsearch -exact [lindex $parentlist [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} {} { @@ -3404,8 +3486,19 @@ proc drawlineseg {id row endrow arrowlow} { 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} { + } elseif {$xc != $x} { + if {($arrowhigh && $le == $row + 1) || $dir == 0} { + set d [expr {int(0.5 * $linespc)}] + set x1 [xc $row $x] + if {$xc < $x} { + set x2 [expr {$x1 - $d}] + } else { + set x2 [expr {$x1 + $d}] + } + set y2 [yc $row] + set y1 [expr {$y2 + $d}] + lappend coords $x1 $y1 $x2 $y2 + } elseif {$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] @@ -3416,23 +3509,9 @@ proc drawlineseg {id row endrow arrowlow} { } 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 { - lappend coords $xn $yn - } + 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] @@ -3456,9 +3535,6 @@ proc drawlineseg {id row endrow arrowlow} { 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 @@ -3478,7 +3554,7 @@ proc drawlineseg {id row endrow arrowlow} { proc drawparentlinks {id row} { global rowidlist canv colormap curview parentlist - global idpos + global idpos linespc set rowids [lindex $rowidlist $row] set col [lsearch -exact $rowids $id] @@ -3488,6 +3564,8 @@ proc drawparentlinks {id row} { set x [xc $row $col] set y [yc $row] set y2 [yc $row2] + set d [expr {int(0.5 * $linespc)}] + set ymid [expr {$y + $d}] set ids [lindex $rowidlist $row2] # rmx = right-most X coord used set rmx 0 @@ -3501,19 +3579,37 @@ proc drawparentlinks {id row} { if {$x2 > $rmx} { set rmx $x2 } - if {[lsearch -exact $rowids $p] < 0} { + set j [lsearch -exact $rowids $p] + if {$j < 0} { # drawlineseg will do this one for us continue } assigncolor $p # should handle duplicated parents here... set coords [list $x $y] - if {$i < $col - 1} { - lappend coords [xc $row [expr {$i + 1}]] $y - } elseif {$i > $col + 1} { - lappend coords [xc $row [expr {$i - 1}]] $y + if {$i != $col} { + # if attaching to a vertical segment, draw a smaller + # slant for visual distinctness + if {$i == $j} { + if {$i < $col} { + lappend coords [expr {$x2 + $d}] $y $x2 $ymid + } else { + lappend coords [expr {$x2 - $d}] $y $x2 $ymid + } + } elseif {$i < $col && $i < $j} { + # segment slants towards us already + lappend coords [xc $row $j] $y + } else { + if {$i < $col - 1} { + lappend coords [expr {$x2 + $linespc}] $y + } elseif {$i > $col + 1} { + lappend coords [expr {$x2 - $linespc}] $y + } + lappend coords $x2 $y2 + } + } else { + lappend coords $x2 $y2 } - lappend coords $x2 $y2 set t [$canv create line $coords -width [linewidth $p] \ -fill $colormap($p) -tags lines.$p] $canv lower $t @@ -3535,8 +3631,8 @@ proc drawcmittext {id row col} { global linespc canv canv2 canv3 canvy0 fgcolor curview global commitlisted commitinfo rowidlist parentlist global rowtextx idpos idtags idheads idotherrefs - global linehtag linentag linedtag - global mainfont canvxmax boldrows boldnamerows fgcolor nullid nullid2 + global linehtag linentag linedtag selectedline + global canvxmax boldrows boldnamerows fgcolor nullid nullid2 # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right set listed [lindex $commitlisted $row] @@ -3593,15 +3689,15 @@ proc drawcmittext {id row col} { set name [lindex $commitinfo($id) 1] set date [lindex $commitinfo($id) 2] set date [formatdate $date] - set font $mainfont - set nfont $mainfont + set font mainfont + set nfont mainfont set isbold [ishighlighted $row] if {$isbold > 0} { lappend boldrows $row - lappend font bold + set font mainfontbold if {$isbold > 1} { lappend boldnamerows $row - lappend nfont bold + set nfont mainfontbold } } set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \ @@ -3610,8 +3706,11 @@ proc drawcmittext {id row col} { set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \ -text $name -font $nfont -tags text] set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \ - -text $date -font $mainfont -tags text] - set xr [expr {$xt + [font measure $mainfont $headline]}] + -text $date -font mainfont -tags text] + if {[info exists selectedline] && $selectedline == $row} { + make_secsel $row + } + set xr [expr {$xt + [font measure $font $headline]}] if {$xr > $canvxmax} { set canvxmax $xr setcanvscroll @@ -3619,10 +3718,10 @@ proc drawcmittext {id row col} { } proc drawcmitrow {row} { - global displayorder rowidlist + global displayorder rowidlist nrows_drawn global iddrawn markingmatches global commitinfo parentlist numcommits - global filehighlight fhighlights findstring nhighlights + global filehighlight fhighlights findpattern nhighlights global hlview vhighlights global highlight_related rhighlights @@ -3635,7 +3734,7 @@ proc drawcmitrow {row} { if {[info exists filehighlight] && ![info exists fhighlights($row)]} { askfilehighlight $row $id } - if {$findstring ne {} && ![info exists nhighlights($row)]} { + if {$findpattern ne {} && ![info exists nhighlights($row)]} { askfindhighlight $row $id } if {$highlight_related ne "None" && ![info exists rhighlights($row)]} { @@ -3653,6 +3752,7 @@ proc drawcmitrow {row} { assigncolor $id drawcmittext $id $row $col set iddrawn($id) 1 + incr nrows_drawn } if {$markingmatches} { markrowmatches $row $id @@ -3660,8 +3760,8 @@ proc drawcmitrow {row} { } proc drawcommits {row {endrow {}}} { - global numcommits iddrawn displayorder curview - global parentlist rowidlist + global numcommits iddrawn displayorder curview need_redisplay + global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn if {$row < 0} { set row 0 @@ -3673,6 +3773,35 @@ proc drawcommits {row {endrow {}}} { set endrow [expr {$numcommits - 1}] } + set rl1 [expr {$row - $downarrowlen - 3}] + if {$rl1 < 0} { + set rl1 0 + } + set ro1 [expr {$row - 3}] + if {$ro1 < 0} { + set ro1 0 + } + set r2 [expr {$endrow + $uparrowlen + 3}] + if {$r2 > $numcommits} { + set r2 $numcommits + } + for {set r $rl1} {$r < $r2} {incr r} { + if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} { + if {$rl1 < $r} { + layoutrows $rl1 $r + } + set rl1 [expr {$r + 1}] + } + } + if {$rl1 < $r} { + layoutrows $rl1 $r + } + optimize_rows $ro1 0 $r2 + if {$need_redisplay || $nrows_drawn > 2000} { + clear_display + drawvisible + } + # make the lines join to already-drawn rows either side set r [expr {$row - 1}] if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} { @@ -3689,34 +3818,23 @@ proc drawcommits {row {endrow {}}} { drawcmitrow $r if {$r == $er} break set nextid [lindex $displayorder [expr {$r + 1}]] - if {$wasdrawn && [info exists iddrawn($nextid)]} { - catch {unset prevlines} - continue - } + if {$wasdrawn && [info exists iddrawn($nextid)]} 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 {[info exists lineend($lid)] && $lineend($lid) > $r} 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 + set lineend($p) [drawlineseg $p $r $er 0] } } - } elseif {![info exists prevlines($lid)]} { - set le [drawlineseg $lid $r $er 1] - lappend lineends($le) $lid - set prevlines($lid) 1 + } else { + set lineend($lid) [drawlineseg $lid $r $er 1] } } } @@ -3740,7 +3858,7 @@ proc drawvisible {} { } proc clear_display {} { - global iddrawn linesegs + global iddrawn linesegs need_redisplay nrows_drawn global vhighlights fhighlights nhighlights rhighlights allcanvs delete all @@ -3750,10 +3868,12 @@ proc clear_display {} { catch {unset fhighlights} catch {unset nhighlights} catch {unset rhighlights} + set need_redisplay 0 + set nrows_drawn 0 } proc findcrossings {id} { - global rowidlist parentlist numcommits rowoffsets displayorder + global rowidlist parentlist numcommits displayorder set cross {} set ccross {} @@ -3762,12 +3882,9 @@ proc findcrossings {id} { set e [expr {$numcommits - 1}] } if {$e <= $s} continue - set x [lsearch -exact [lindex $rowidlist $e] $id] - if {$x < 0} { - puts "findcrossings: oops, no [shortids $id] in row $e" - continue - } for {set row $e} {[incr row -1] >= $s} {} { + set x [lsearch -exact [lindex $rowidlist $row] $id] + if {$x < 0} break set olds [lindex $parentlist $row] set kid [lindex $displayorder $row] set kidx [lsearch -exact [lindex $rowidlist $row] $kid] @@ -3785,9 +3902,6 @@ proc findcrossings {id} { } } } - set inc [lindex $rowoffsets $row $x] - if {$inc eq {}} break - incr x $inc } } return [concat $ccross {{}} $cross] @@ -3868,7 +3982,7 @@ proc bindline {t id} { proc drawtags {id x xt y1} { global idtags idheads idotherrefs mainhead global linespc lthickness - global canv mainfont commitrow rowtextx curview fgcolor bgcolor + global canv commitrow rowtextx curview fgcolor bgcolor set marks {} set ntags 0 @@ -3897,9 +4011,9 @@ proc drawtags {id x xt y1} { foreach tag $marks { incr i if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} { - set wid [font measure [concat $mainfont bold] $tag] + set wid [font measure mainfontbold $tag] } else { - set wid [font measure $mainfont $tag] + set wid [font measure mainfont $tag] } lappend xvals $xt lappend wvals $wid @@ -3911,7 +4025,7 @@ proc drawtags {id x xt y1} { foreach tag $marks x $xvals wid $wvals { set xl [expr {$x + $delta}] set xr [expr {$x + $delta + $wid + $lthickness}] - set font $mainfont + set font mainfont if {[incr ntags -1] >= 0} { # draw a tag set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \ @@ -3924,7 +4038,7 @@ proc drawtags {id x xt y1} { if {[incr nheads -1] >= 0} { set col green if {$tag eq $mainhead} { - lappend font bold + set font mainfontbold } } else { set col "#ddddff" @@ -3933,7 +4047,7 @@ proc drawtags {id x xt y1} { $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \ -width 1 -outline black -fill $col -tags tag.$id if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} { - set rwid [font measure $mainfont $remoteprefix] + set rwid [font measure mainfont $remoteprefix] set xi [expr {$x + 1}] set yti [expr {$yt + 1}] set xri [expr {$x + $rwid}] @@ -3965,10 +4079,10 @@ proc xcoord {i level ln} { } proc show_status {msg} { - global canv mainfont fgcolor + global canv fgcolor clear_display - $canv create text 3 3 -anchor nw -text $msg -font $mainfont \ + $canv create text 3 3 -anchor nw -text $msg -font mainfont \ -tags text -fill $fgcolor } @@ -3977,9 +4091,9 @@ proc show_status {msg} { # on that row and below will move down one row. proc insertrow {row newcmit} { global displayorder parentlist commitlisted children - global commitrow curview rowidlist rowoffsets numcommits - global rowrangelist rowlaidout rowoptim numcommits - global selectedline rowchk commitidx + global commitrow curview rowidlist rowisopt rowfinal numcommits + global numcommits + global selectedline commitidx ordertok if {$row >= $numcommits} { puts "oops, inserting new row $row but only have $numcommits rows" @@ -3999,45 +4113,24 @@ proc insertrow {row newcmit} { set commitrow($curview,$id) $r } incr commitidx($curview) + set ordertok($curview,$newcmit) $ordertok($curview,$p) - set idlist [lindex $rowidlist $row] - set offs [lindex $rowoffsets $row] - set newoffs {} - foreach x $idlist { - if {$x eq {} || ($x eq $p && [llength $kids] == 1)} { - lappend newoffs {} - } else { - lappend newoffs 0 - } - } - if {[llength $kids] == 1} { - set col [lsearch -exact $idlist $p] - lset idlist $col $newcmit - } else { - set col [llength $idlist] - lappend idlist $newcmit - lappend offs {} - lset rowoffsets $row $offs - } - set rowidlist [linsert $rowidlist $row $idlist] - set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs] - - set rowrangelist [linsert $rowrangelist $row {}] - if {[llength $kids] > 1} { - set rp1 [expr {$row + 1}] - set ranges [lindex $rowrangelist $rp1] - if {$ranges eq {}} { - set ranges [list $newcmit $p] - } elseif {[lindex $ranges end-1] eq $p} { - lset ranges end-1 $newcmit + if {$row < [llength $rowidlist]} { + set idlist [lindex $rowidlist $row] + if {$idlist ne {}} { + if {[llength $kids] == 1} { + set col [lsearch -exact $idlist $p] + lset idlist $col $newcmit + } else { + set col [llength $idlist] + lappend idlist $newcmit + } } - lset rowrangelist $rp1 $ranges + set rowidlist [linsert $rowidlist $row $idlist] + set rowisopt [linsert $rowisopt $row 0] + set rowfinal [linsert $rowfinal $row [lindex $rowfinal $row]] } - catch {unset rowchk} - - incr rowlaidout - incr rowoptim incr numcommits if {[info exists selectedline] && $selectedline >= $row} { @@ -4049,9 +4142,9 @@ proc insertrow {row newcmit} { # 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 + global commitrow curview rowidlist rowisopt rowfinal numcommits + global numcommits + global linesegends selectedline commitidx if {$row >= $numcommits} { puts "oops, removing row $row but only have $numcommits rows" @@ -4076,27 +4169,12 @@ proc removerow {row} { } 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 - } + if {$row < [llength $rowidlist]} { + set rowidlist [lreplace $rowidlist $row $row] + set rowisopt [lreplace $rowisopt $row $row] + set rowfinal [lreplace $rowfinal $row $row] } - catch {unset rowchk} - - incr rowlaidout -1 - incr rowoptim -1 incr numcommits -1 if {[info exists selectedline] && $selectedline > $row} { @@ -4116,20 +4194,30 @@ proc settextcursor {c} { set curtextcursor $c } -proc nowbusy {what} { - global isbusy +proc nowbusy {what {name {}}} { + global isbusy busyname statusw if {[array names isbusy] eq {}} { . config -cursor watch settextcursor watch } set isbusy($what) 1 + set busyname($what) $name + if {$name ne {}} { + $statusw conf -text $name + } } proc notbusy {what} { - global isbusy maincursor textcursor + global isbusy maincursor textcursor busyname statusw - catch {unset isbusy($what)} + catch { + unset isbusy($what) + if {$busyname($what) ne {} && + [$statusw cget -text] eq $busyname($what)} { + $statusw conf -text {} + } + } if {[array names isbusy] eq {}} { . config -cursor $maincursor settextcursor $textcursor @@ -4157,148 +4245,149 @@ proc findmatches {f} { return $matches } -proc dofind {{rev 0}} { +proc dofind {{dirn 1} {wrap 1}} { global findstring findstartline findcurline selectedline numcommits + global gdttype filehighlight fh_serial find_dirn findallowwrap - unmarkmatches - cancel_next_highlight + if {[info exists find_dirn]} { + if {$find_dirn == $dirn} return + stopfinding + } focus . if {$findstring eq {} || $numcommits == 0} return if {![info exists selectedline]} { - set findstartline [lindex [visiblerows] $rev] + set findstartline [lindex [visiblerows] [expr {$dirn < 0}]] } else { set findstartline $selectedline } set findcurline $findstartline - nowbusy finding - if {!$rev} { - run findmore - } else { - if {$findcurline == 0} { - set findcurline $numcommits - } - incr findcurline -1 - run findmorerev + nowbusy finding "Searching" + if {$gdttype ne "containing:" && ![info exists filehighlight]} { + after cancel do_file_hl $fh_serial + do_file_hl $fh_serial } + set find_dirn $dirn + set findallowwrap $wrap + run findmore } -proc findnext {restart} { - global findcurline - if {![info exists findcurline]} { - if {$restart} { - dofind - } else { - bell - } - } else { - run findmore - nowbusy finding - } -} +proc stopfinding {} { + global find_dirn findcurline fprogcoord -proc findprev {} { - global findcurline - if {![info exists findcurline]} { - dofind 1 - } else { - run findmorerev - nowbusy finding + if {[info exists find_dirn]} { + unset find_dirn + unset findcurline + notbusy finding + set fprogcoord 0 + adjustprogress } } proc findmore {} { - global commitdata commitinfo numcommits findstring findpattern findloc + global commitdata commitinfo numcommits findpattern findloc global findstartline findcurline displayorder + global find_dirn gdttype fhighlights fprogcoord + global findallowwrap - set fldtypes {Headline Author Date Committer CDate Comments} - set l [expr {$findcurline + 1}] - if {$l >= $numcommits} { - set l 0 - } - if {$l <= $findstartline} { - set lim [expr {$findstartline + 1}] - } else { - set lim $numcommits - } - if {$lim - $l > 500} { - set lim [expr {$l + 500}] - } - set last 0 - for {} {$l < $lim} {incr l} { - set id [lindex $displayorder $l] - # shouldn't happen unless git log doesn't give all the commits... - if {![info exists commitdata($id)]} continue - if {![doesmatch $commitdata($id)]} continue - if {![info exists commitinfo($id)]} { - getcommit $id - } - set info $commitinfo($id) - foreach f $info ty $fldtypes { - if {($findloc eq "All fields" || $findloc eq $ty) && - [doesmatch $f]} { - findselectline $l - notbusy finding - return 0 - } - } - } - if {$l == $findstartline + 1} { - bell - unset findcurline - notbusy finding + if {![info exists find_dirn]} { return 0 } - set findcurline [expr {$l - 1}] - return 1 -} - -proc findmorerev {} { - global commitdata commitinfo numcommits findstring findpattern findloc - global findstartline findcurline displayorder - set fldtypes {Headline Author Date Committer CDate Comments} set l $findcurline - if {$l == 0} { - set l $numcommits - } - incr l -1 - if {$l >= $findstartline} { - set lim [expr {$findstartline - 1}] + set moretodo 0 + if {$find_dirn > 0} { + incr l + if {$l >= $numcommits} { + set l 0 + } + if {$l <= $findstartline} { + set lim [expr {$findstartline + 1}] + } else { + set lim $numcommits + set moretodo $findallowwrap + } } else { - set lim -1 - } - if {$l - $lim > 500} { - set lim [expr {$l - 500}] - } - set last 0 - for {} {$l > $lim} {incr l -1} { - set id [lindex $displayorder $l] - if {![doesmatch $commitdata($id)]} continue - if {![info exists commitinfo($id)]} { - getcommit $id + if {$l == 0} { + set l $numcommits + } + incr l -1 + if {$l >= $findstartline} { + set lim [expr {$findstartline - 1}] + } else { + set lim -1 + set moretodo $findallowwrap + } + } + set n [expr {($lim - $l) * $find_dirn}] + if {$n > 500} { + set n 500 + set moretodo 1 + } + set found 0 + set domore 1 + if {$gdttype eq "containing:"} { + for {} {$n > 0} {incr n -1; incr l $find_dirn} { + set id [lindex $displayorder $l] + # shouldn't happen unless git log doesn't give all the commits... + if {![info exists commitdata($id)]} continue + if {![doesmatch $commitdata($id)]} continue + if {![info exists commitinfo($id)]} { + getcommit $id + } + set info $commitinfo($id) + foreach f $info ty $fldtypes { + if {($findloc eq "All fields" || $findloc eq $ty) && + [doesmatch $f]} { + set found 1 + break + } + } + if {$found} break } - set info $commitinfo($id) - foreach f $info ty $fldtypes { - if {($findloc eq "All fields" || $findloc eq $ty) && - [doesmatch $f]} { - findselectline $l - notbusy finding - return 0 + } else { + for {} {$n > 0} {incr n -1; incr l $find_dirn} { + set id [lindex $displayorder $l] + if {![info exists fhighlights($l)]} { + askfilehighlight $l $id + if {$domore} { + set domore 0 + set findcurline [expr {$l - $find_dirn}] + } + } elseif {$fhighlights($l)} { + set found $domore + break } } } - if {$l == -1} { - bell + if {$found || ($domore && !$moretodo)} { unset findcurline + unset find_dirn notbusy finding + set fprogcoord 0 + adjustprogress + if {$found} { + findselectline $l + } else { + bell + } return 0 } - set findcurline [expr {$l + 1}] - return 1 + if {!$domore} { + flushhighlights + } else { + set findcurline [expr {$l - $find_dirn}] + } + set n [expr {($findcurline - $findstartline) * $find_dirn - 1}] + if {$n < 0} { + incr n $numcommits + } + set fprogcoord [expr {$n * 1.0 / $numcommits}] + adjustprogress + return $domore } proc findselectline {l} { - global findloc commentend ctext findcurline markingmatches + global findloc commentend ctext findcurline markingmatches gdttype set markingmatches 1 set findcurline $l @@ -4341,12 +4430,11 @@ proc markmatches {canv l str tag matches font row} { } proc unmarkmatches {} { - global findids markingmatches findcurline + global markingmatches allcanvs delete matches - catch {unset findids} set markingmatches 0 - catch {unset findcurline} + stopfinding } proc selcanvline {w x y} { @@ -4382,7 +4470,7 @@ proc commit_descriptor {p} { # append some text to the ctext widget, and make any SHA1 ID # that we know about be a clickable link. proc appendwithlinks {text tags} { - global ctext commitrow linknum curview + global ctext commitrow linknum curview pendinglinks set start [$ctext index "end - 1c"] $ctext insert end $text $tags @@ -4391,17 +4479,49 @@ proc appendwithlinks {text tags} { set s [lindex $l 0] set e [lindex $l 1] set linkid [string range $text $s $e] - if {![info exists commitrow($curview,$linkid)]} continue incr e - $ctext tag add link "$start + $s c" "$start + $e c" + $ctext tag delete link$linknum $ctext tag add link$linknum "$start + $s c" "$start + $e c" - $ctext tag bind link$linknum <1> \ - [list selectline $commitrow($curview,$linkid) 1] + setlink $linkid link$linknum incr linknum } - $ctext tag conf link -foreground blue -underline 1 - $ctext tag bind link <Enter> { %W configure -cursor hand2 } - $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor } +} + +proc setlink {id lk} { + global curview commitrow ctext pendinglinks commitinterest + + if {[info exists commitrow($curview,$id)]} { + $ctext tag conf $lk -foreground blue -underline 1 + $ctext tag bind $lk <1> [list selectline $commitrow($curview,$id) 1] + $ctext tag bind $lk <Enter> {linkcursor %W 1} + $ctext tag bind $lk <Leave> {linkcursor %W -1} + } else { + lappend pendinglinks($id) $lk + lappend commitinterest($id) {makelink %I} + } +} + +proc makelink {id} { + global pendinglinks + + if {![info exists pendinglinks($id)]} return + foreach lk $pendinglinks($id) { + setlink $id $lk + } + unset pendinglinks($id) +} + +proc linkcursor {w inc} { + global linkentercount curtextcursor + + if {[incr linkentercount $inc] > 0} { + $w configure -cursor hand2 + } else { + $w configure -cursor $curtextcursor + if {$linkentercount < 0} { + set linkentercount 0 + } + } } proc viewnextline {dir} { @@ -4448,15 +4568,7 @@ proc appendrefs {pos ids var} { $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 } - } + setlink $id $lk set sep ", " } } @@ -4514,9 +4626,27 @@ proc dispnexttag {} { } } +proc make_secsel {l} { + global linehtag linentag linedtag canv canv2 canv3 + + if {![info exists linehtag($l)]} return + $canv delete secsel + set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \ + -tags secsel -fill [$canv cget -selectbackground]] + $canv lower $t + $canv2 delete secsel + set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \ + -tags secsel -fill [$canv2 cget -selectbackground]] + $canv2 lower $t + $canv3 delete secsel + set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \ + -tags secsel -fill [$canv3 cget -selectbackground]] + $canv3 lower $t +} + proc selectline {l isnew} { - global canv canv2 canv3 ctext commitinfo selectedline - global displayorder linehtag linentag linedtag + global canv ctext commitinfo selectedline + global displayorder global canvy0 linespc parentlist children curview global currentid sha1entry global commentend idtags linknum @@ -4526,8 +4656,8 @@ proc selectline {l isnew} { catch {unset pending_select} $canv delete hover normalline - cancel_next_highlight unsel_reflist + stopfinding if {$l < 0 || $l >= $numcommits} return set y [expr {$canvy0 + $l * $linespc}] set ymax [lindex [$canv cget -scrollregion] 3] @@ -4565,19 +4695,7 @@ proc selectline {l isnew} { drawvisible } - if {![info exists linehtag($l)]} return - $canv delete secsel - set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \ - -tags secsel -fill [$canv cget -selectbackground]] - $canv lower $t - $canv2 delete secsel - set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \ - -tags secsel -fill [$canv2 cget -selectbackground]] - $canv2 lower $t - $canv3 delete secsel - set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \ - -tags secsel -fill [$canv3 cget -selectbackground]] - $canv3 lower $t + make_secsel $l if {$isnew} { addtohistory [list selectline $l 0] @@ -4720,7 +4838,6 @@ proc unselectline {} { catch {unset currentid} allcanvs delete secsel rhighlight_none - cancel_next_highlight } proc reselectline {} { @@ -4889,6 +5006,7 @@ proc showfile {f} { $ctext insert end "$f\n" filesep $ctext config -state disabled $ctext yview $commentend + settabs 0 } proc getblobline {bf id} { @@ -4914,15 +5032,18 @@ proc getblobline {bf id} { } proc mergediff {id l} { - global diffmergeid diffopts mdifffd + global diffmergeid mdifffd global diffids global parentlist + global limitdiffs viewfiles curview set diffmergeid $id set diffids $id # this doesn't seem to actually affect anything... - set env(GIT_DIFF_OPTS) $diffopts set cmd [concat | git diff-tree --no-commit-id --cc $id] + if {$limitdiffs && $viewfiles($curview) ne {}} { + set cmd [concat $cmd -- $viewfiles($curview)] + } if {[catch {set mdf [open $cmd r]} err]} { error_popup "Error getting merge diffs: $err" return @@ -4930,6 +5051,7 @@ proc mergediff {id l} { fconfigure $mdf -blocking 0 set mdifffd($id) $mdf set np [llength [lindex $parentlist $l]] + settabs $np filerun $mdf [list getmergediffline $mdf $id $np] } @@ -5007,6 +5129,7 @@ proc getmergediffline {mdf id np} { proc startdiff {ids} { global treediffs diffids treepending diffmergeid nullid nullid2 + settabs 1 set diffids $ids catch {unset diffmergeid} if {![info exists treediffs($ids)] || @@ -5020,8 +5143,27 @@ proc startdiff {ids} { } } +proc path_filter {filter name} { + foreach p $filter { + set l [string length $p] + if {[string index $p end] eq "/"} { + if {[string compare -length $l $p $name] == 0} { + return 1 + } + } else { + if {[string compare -length $l $p $name] == 0 && + ([string length $name] == $l || + [string index $name $l] eq "/")} { + return 1 + } + } + } + return 0 +} + proc addtocflist {ids} { - global treediffs cflist + global treediffs + add_flist $treediffs($ids) getblobdiffs $ids } @@ -5078,7 +5220,7 @@ proc gettreediffs {ids} { proc gettreediffline {gdtf ids} { global treediff treediffs treepending diffids diffmergeid - global cmitmode + global cmitmode viewfiles curview limitdiffs set nr 0 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} { @@ -5095,7 +5237,17 @@ proc gettreediffline {gdtf ids} { return [expr {$nr >= 1000? 2: 1}] } close $gdtf - set treediffs($ids) $treediff + if {$limitdiffs && $viewfiles($curview) ne {}} { + set flist {} + foreach f $treediff { + if {[path_filter $viewfiles($curview) $f]} { + lappend flist $f + } + } + set treediffs($ids) $flist + } else { + set treediffs($ids) $treediff + } unset treepending if {$cmitmode eq "tree"} { gettree $diffids @@ -5126,12 +5278,16 @@ proc diffcontextchange {n1 n2 op} { } proc getblobdiffs {ids} { - global diffopts blobdifffd diffids env + global blobdifffd diffids env global diffinhdr treediffs global diffcontext + global limitdiffs viewfiles curview - set env(GIT_DIFF_OPTS) $diffopts - if {[catch {set bdf [open [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"] r]} err]} { + set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"] + if {$limitdiffs && $viewfiles($curview) ne {}} { + set cmd [concat $cmd -- $viewfiles($curview)] + } + if {[catch {set bdf [open $cmd r]} err]} { puts "error getting diffs: $err" return } @@ -5215,8 +5371,7 @@ proc getblobdiffline {bdf ids} { set diffinhdr 0 } elseif {$diffinhdr} { - if {![string compare -length 12 "rename from " $line] || - ![string compare -length 10 "copy from " $line]} { + if {![string compare -length 12 "rename from " $line]} { set fname [string range $line [expr 6 + [string first " from " $line] ] end] if {[string index $fname 0] eq "\""} { set fname [lindex $fname 0] @@ -5297,6 +5452,7 @@ proc nextfile {} { proc clear_ctext {{first 1.0}} { global ctext smarktop smarkbot + global pendinglinks set l [lindex [split $first .] 0] if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} { @@ -5306,6 +5462,26 @@ proc clear_ctext {{first 1.0}} { set smarkbot $l } $ctext delete $first end + if {$first eq "1.0"} { + catch {unset pendinglinks} + } +} + +proc settabs {{firstab {}}} { + global firsttabstop tabstop ctext have_tk85 + + if {$firstab ne {} && $have_tk85} { + set firsttabstop $firstab + } + set w [font measure textfont "0"] + if {$firsttabstop != 0} { + $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \ + [expr {($firsttabstop + 2 * $tabstop) * $w}]] + } elseif {$have_tk85 || $tabstop != 8} { + $ctext conf -tabs [expr {$tabstop * $w}] + } else { + $ctext conf -tabs {} + } } proc incrsearch {name ix op} { @@ -5428,11 +5604,11 @@ proc scrolltext {f0 f1} { } proc setcoords {} { - global linespc charspc canvx0 canvy0 mainfont + global linespc charspc canvx0 canvy0 global xspc1 xspc2 lthickness - set linespc [font metrics $mainfont -linespace] - set charspc [font measure $mainfont "m"] + set linespc [font metrics mainfont -linespace] + set charspc [font measure mainfont "m"] set canvy0 [expr {int(3 + 0.5 * $linespc)}] set canvx0 [expr {int(3 + 0.5 * $linespc)}] set lthickness [expr {int($linespc / 9) + 1}] @@ -5457,26 +5633,75 @@ proc redisplay {} { } } +proc parsefont {f n} { + global fontattr + + set fontattr($f,family) [lindex $n 0] + set s [lindex $n 1] + if {$s eq {} || $s == 0} { + set s 10 + } elseif {$s < 0} { + set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}] + } + set fontattr($f,size) $s + set fontattr($f,weight) normal + set fontattr($f,slant) roman + foreach style [lrange $n 2 end] { + switch -- $style { + "normal" - + "bold" {set fontattr($f,weight) $style} + "roman" - + "italic" {set fontattr($f,slant) $style} + } + } +} + +proc fontflags {f {isbold 0}} { + global fontattr + + return [list -family $fontattr($f,family) -size $fontattr($f,size) \ + -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \ + -slant $fontattr($f,slant)] +} + +proc fontname {f} { + global fontattr + + set n [list $fontattr($f,family) $fontattr($f,size)] + if {$fontattr($f,weight) eq "bold"} { + lappend n "bold" + } + if {$fontattr($f,slant) eq "italic"} { + lappend n "italic" + } + return $n +} + proc incrfont {inc} { global mainfont textfont ctext canv phase cflist showrefstop - global charspc tabstop - global stopped entries + global stopped entries fontattr + unmarkmatches - set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]] - set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]] + set s $fontattr(mainfont,size) + incr s $inc + if {$s < 1} { + set s 1 + } + set fontattr(mainfont,size) $s + font config mainfont -size $s + font config mainfontbold -size $s + set mainfont [fontname mainfont] + set s $fontattr(textfont,size) + incr s $inc + if {$s < 1} { + set s 1 + } + set fontattr(textfont,size) $s + font config textfont -size $s + font config textfontbold -size $s + set textfont [fontname textfont] setcoords - $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 - } - if {$phase eq "getcommits"} { - $canv itemconf textitems -font $mainfont - } - if {[info exists showrefstop] && [winfo exists $showrefstop]} { - $showrefstop.list conf -font $mainfont - } + settabs redisplay } @@ -5587,7 +5812,7 @@ proc lineleave {id} { proc linehover {} { global hoverx hovery hoverid hovertimer global canv linespc lthickness - global commitinfo mainfont + global commitinfo set text [lindex $commitinfo($hoverid) 0] set ymax [lindex [$canv cget -scrollregion] 3] @@ -5597,13 +5822,13 @@ proc linehover {} { set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}] set x0 [expr {$x - 2 * $lthickness}] set y0 [expr {$y - 2 * $lthickness}] - set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}] + set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}] set y1 [expr {$y + $linespc + 2 * $lthickness}] set t [$canv create rectangle $x0 $y0 $x1 $y1 \ -fill \#ffff80 -outline black -width 1 -tags hover] $canv raise $t set t [$canv create text $x $y -anchor nw -text $text -tags hover \ - -font $mainfont] + -font mainfont] $canv raise $t } @@ -5641,7 +5866,7 @@ proc arrowjump {id n y} { } proc lineclick {x y id isnew} { - global ctext commitinfo children canv thickerline curview + global ctext commitinfo children canv thickerline curview commitrow if {![info exists commitinfo($id)] && ![getcommit $id]} return unmarkmatches @@ -5669,12 +5894,10 @@ proc lineclick {x y id isnew} { # fill the details pane with info about this line $ctext conf -state normal clear_ctext - $ctext tag conf link -foreground blue -underline 1 - $ctext tag bind link <Enter> { %W configure -cursor hand2 } - $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor } + settabs 0 $ctext insert end "Parent:\t" - $ctext insert end $id [list link link0] - $ctext tag bind link0 <1> [list selbyid $id] + $ctext insert end $id link0 + setlink $id link0 set info $commitinfo($id) $ctext insert end "\n\t[lindex $info 0]\n" $ctext insert end "\tAuthor:\t[lindex $info 1]\n" @@ -5689,8 +5912,8 @@ proc lineclick {x y id isnew} { if {![info exists commitinfo($child)] && ![getcommit $child]} continue set info $commitinfo($child) $ctext insert end "\n\t" - $ctext insert end $child [list link link$i] - $ctext tag bind link$i <1> [list selbyid $child] + $ctext insert end $child link$i + setlink $child link$i $ctext insert end "\n\t[lindex $info 0]" $ctext insert end "\n\tAuthor:\t[lindex $info 1]" set date [formatdate [lindex $info 2]] @@ -5729,6 +5952,7 @@ proc rowmenu {x y id} { global rowctxmenu commitrow selectedline rowmenuid curview global nullid nullid2 fakerowmenu mainhead + stopfinding set rowmenuid $id if {![info exists selectedline] || $commitrow($curview,$id) eq $selectedline} { @@ -5771,16 +5995,13 @@ proc doseldiff {oldid newid} { clear_ctext init_flist "Top" $ctext insert end "From " - $ctext tag conf link -foreground blue -underline 1 - $ctext tag bind link <Enter> { %W configure -cursor hand2 } - $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor } - $ctext tag bind link0 <1> [list selbyid $oldid] - $ctext insert end $oldid [list link link0] + $ctext insert end $oldid link0 + setlink $oldid link0 $ctext insert end "\n " $ctext insert end [lindex $commitinfo($oldid) 0] $ctext insert end "\n\nTo " - $ctext tag bind link1 <1> [list selbyid $newid] - $ctext insert end $newid [list link link1] + $ctext insert end $newid link1 + setlink $newid link1 $ctext insert end "\n " $ctext insert end [lindex $commitinfo($newid) 0] $ctext insert end "\n" @@ -5861,6 +6082,8 @@ proc mkpatchgo {} { set newid [$patchtop.tosha1 get] set fname [$patchtop.fname get] set cmd [diffcmd [list $oldid $newid] -p] + # trim off the initial "|" + set cmd [lrange $cmd 1 end] lappend cmd >$fname & if {[catch {eval exec $cmd} err]} { error_popup "Error creating patch: $err" @@ -5941,7 +6164,7 @@ proc domktag {} { proc redrawtags {id} { global canv linehtag commitrow idpos selectedline curview - global mainfont canvxmax iddrawn + global canvxmax iddrawn if {![info exists commitrow($curview,$id)]} return if {![info exists iddrawn($id)]} return @@ -5950,7 +6173,7 @@ proc redrawtags {id} { set xt [eval drawtags $id $idpos($id)] $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2] set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text] - set xr [expr {$xt + [font measure $mainfont $text]}] + set xr [expr {$xt + [font measure mainfont $text]}] if {$xr > $canvxmax} { set canvxmax $xr setcanvscroll @@ -6093,7 +6316,7 @@ proc cherrypick {} { included in branch $mainhead -- really re-apply it?"] if {!$ok} return } - nowbusy cherrypick + nowbusy cherrypick "Cherry-picking" update # Unfortunately git-cherry-pick writes stuff to stderr even when # no error occurs, and exec takes that as an indication of error... @@ -6123,7 +6346,6 @@ proc cherrypick {} { proc resethead {} { global mainheadid mainhead rowmenuid confirm_ok resettype - global showlocalchanges set confirm_ok 0 set w ".confirmreset" @@ -6160,32 +6382,23 @@ proc resethead {} { 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 + filerun $fd [list readresetstat $fd] + nowbusy reset "Resetting" } } -proc readresetstat {fd w} { - global mainhead mainheadid showlocalchanges +proc readresetstat {fd} { + global mainhead mainheadid showlocalchanges rprogcoord 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 + set rprogcoord [expr {1.0 * $m / $n}] + adjustprogress } return 1 } - destroy $w + set rprogcoord 0 + adjustprogress notbusy reset if {[catch {close $fd} err]} { error_popup $err @@ -6209,6 +6422,7 @@ proc readresetstat {fd w} { proc headmenu {x y id head} { global headmenuid headmenuhead headctxmenu mainhead + stopfinding set headmenuid $id set headmenuhead $head set state normal @@ -6226,7 +6440,7 @@ proc cobranch {} { # check the tree is clean first?? set oldmainhead $mainhead - nowbusy checkout + nowbusy checkout "Checking out" update dohidelocalchanges if {[catch { @@ -6282,8 +6496,8 @@ proc rmbranch {} { # Display a list of tags and heads proc showrefs {} { - global showrefstop bgcolor fgcolor selectbgcolor mainfont - global bglist fglist uifont reflistfilter reflist maincursor + global showrefstop bgcolor fgcolor selectbgcolor + global bglist fglist reflistfilter reflist maincursor set top .showrefs set showrefstop $top @@ -6295,7 +6509,7 @@ proc showrefs {} { toplevel $top wm title $top "Tags and heads: [file tail [pwd]]" text $top.list -background $bgcolor -foreground $fgcolor \ - -selectbackground $selectbgcolor -font $mainfont \ + -selectbackground $selectbgcolor -font mainfont \ -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \ -width 30 -height 20 -cursor $maincursor \ -spacing1 1 -spacing3 1 -state disabled @@ -6307,15 +6521,15 @@ proc showrefs {} { grid $top.list $top.ysb -sticky nsew grid $top.xsb x -sticky ew frame $top.f - label $top.f.l -text "Filter: " -font $uifont - entry $top.f.e -width 20 -textvariable reflistfilter -font $uifont + label $top.f.l -text "Filter: " -font uifont + entry $top.f.e -width 20 -textvariable reflistfilter -font uifont set reflistfilter "*" trace add variable reflistfilter write reflistfilter_change pack $top.f.e -side right -fill x -expand 1 pack $top.f.l -side left grid $top.f - -sticky ew -pady 2 button $top.close -command [list destroy $top] -text "Close" \ - -font $uifont + -font uifont grid $top.close - grid columnconfigure $top 0 -weight 1 grid rowconfigure $top 0 -weight 1 @@ -6438,25 +6652,59 @@ proc refill_reflist {} { # Stuff for finding nearby tags proc getallcommits {} { - global allcommits allids nbmp nextarc seeds + global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate + global idheads idtags idotherrefs allparents tagobjid if {![info exists allcommits]} { - set allids {} - set nbmp 0 set nextarc 0 set allcommits 0 set seeds {} + set allcwait 0 + set cachedarcs 0 + set allccache [file join [gitdir] "gitk.cache"] + if {![catch { + set f [open $allccache r] + set allcwait 1 + getcache $f + }]} return } - set cmd [concat | git rev-list --all --parents] - foreach id $seeds { - lappend cmd "^$id" + if {$allcwait} { + return + } + set cmd [list | git rev-list --parents] + set allcupdate [expr {$seeds ne {}}] + if {!$allcupdate} { + set ids "--all" + } else { + set refs [concat [array names idheads] [array names idtags] \ + [array names idotherrefs]] + set ids {} + set tagobjs {} + foreach name [array names tagobjid] { + lappend tagobjs $tagobjid($name) + } + foreach id [lsort -unique $refs] { + if {![info exists allparents($id)] && + [lsearch -exact $tagobjs $id] < 0} { + lappend ids $id + } + } + if {$ids ne {}} { + foreach id $seeds { + lappend ids "^$id" + } + } + } + if {$ids ne {}} { + set fd [open [concat $cmd $ids] r] + fconfigure $fd -blocking 0 + incr allcommits + nowbusy allcommits + filerun $fd [list getallclines $fd] + } else { + dispneartags 0 } - 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 @@ -6475,10 +6723,10 @@ proc getallcommits {} { # coming from descendents, and "outgoing" means going towards ancestors. proc getallclines {fd} { - global allids allparents allchildren idtags idheads nextarc nbmp + global allparents allchildren idtags idheads nextarc global arcnos arcids arctags arcout arcend arcstart archeads growing - global seeds allcommits - + global seeds allcommits cachedarcs allcupdate + set nid 0 while {[incr nid] <= 1000 && [gets $fd line] >= 0} { set id [lindex $line 0] @@ -6486,7 +6734,7 @@ proc getallclines {fd} { # seen it already continue } - lappend allids $id + set cachedarcs 0 set olds [lrange $line 1 end] set allparents($id) $olds if {![info exists allchildren($id)]} { @@ -6517,7 +6765,6 @@ proc getallclines {fd} { continue } } - incr nbmp foreach a $arcnos($id) { lappend arcids($a) $id set arcend($a) $id @@ -6557,9 +6804,28 @@ proc getallclines {fd} { if {![eof $fd]} { return [expr {$nid >= 1000? 2: 1}] } - close $fd + set cacheok 1 + if {[catch { + fconfigure $fd -blocking 1 + close $fd + } err]} { + # got an error reading the list of commits + # if we were updating, try rereading the whole thing again + if {$allcupdate} { + incr allcommits -1 + dropcache $err + return + } + error_popup "Error reading commit topology information;\ + branch and preceding/following tag information\ + will be incomplete.\n($err)" + set cacheok 0 + } if {[incr allcommits -1] == 0} { notbusy allcommits + if {$cacheok} { + run savecache + } } dispneartags 0 return 0 @@ -6583,7 +6849,7 @@ proc recalcarc {a} { } proc splitarc {p} { - global arcnos arcids nextarc nbmp arctags archeads idtags idheads + global arcnos arcids nextarc arctags archeads idtags idheads global arcstart arcend arcout allparents growing set a $arcnos($p) @@ -6615,7 +6881,6 @@ proc splitarc {p} { set growing($na) 1 unset growing($a) } - incr nbmp foreach id $tail { if {[llength $arcnos($id)] == 1} { @@ -6639,17 +6904,15 @@ proc splitarc {p} { # 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 allparents allchildren idtags nextarc global arcnos arcids arctags arcout arcend arcstart archeads growing global seeds allcommits - if {![info exists allcommits]} return - lappend allids $id + if {![info exists allcommits] || ![info exists arcnos($p)]} return 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 @@ -6664,6 +6927,172 @@ proc addnewchild {id p} { set arcout($id) [list $a] } +# This implements a cache for the topology information. +# The cache saves, for each arc, the start and end of the arc, +# the ids on the arc, and the outgoing arcs from the end. +proc readcache {f} { + global arcnos arcids arcout arcstart arcend arctags archeads nextarc + global idtags idheads allparents cachedarcs possible_seeds seeds growing + global allcwait + + set a $nextarc + set lim $cachedarcs + if {$lim - $a > 500} { + set lim [expr {$a + 500}] + } + if {[catch { + if {$a == $lim} { + # finish reading the cache and setting up arctags, etc. + set line [gets $f] + if {$line ne "1"} {error "bad final version"} + close $f + foreach id [array names idtags] { + if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 && + [llength $allparents($id)] == 1} { + set a [lindex $arcnos($id) 0] + if {$arctags($a) eq {}} { + recalcarc $a + } + } + } + foreach id [array names idheads] { + if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 && + [llength $allparents($id)] == 1} { + set a [lindex $arcnos($id) 0] + if {$archeads($a) eq {}} { + recalcarc $a + } + } + } + foreach id [lsort -unique $possible_seeds] { + if {$arcnos($id) eq {}} { + lappend seeds $id + } + } + set allcwait 0 + } else { + while {[incr a] <= $lim} { + set line [gets $f] + if {[llength $line] != 3} {error "bad line"} + set s [lindex $line 0] + set arcstart($a) $s + lappend arcout($s) $a + if {![info exists arcnos($s)]} { + lappend possible_seeds $s + set arcnos($s) {} + } + set e [lindex $line 1] + if {$e eq {}} { + set growing($a) 1 + } else { + set arcend($a) $e + if {![info exists arcout($e)]} { + set arcout($e) {} + } + } + set arcids($a) [lindex $line 2] + foreach id $arcids($a) { + lappend allparents($s) $id + set s $id + lappend arcnos($id) $a + } + if {![info exists allparents($s)]} { + set allparents($s) {} + } + set arctags($a) {} + set archeads($a) {} + } + set nextarc [expr {$a - 1}] + } + } err]} { + dropcache $err + return 0 + } + if {!$allcwait} { + getallcommits + } + return $allcwait +} + +proc getcache {f} { + global nextarc cachedarcs possible_seeds + + if {[catch { + set line [gets $f] + if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"} + # make sure it's an integer + set cachedarcs [expr {int([lindex $line 1])}] + if {$cachedarcs < 0} {error "bad number of arcs"} + set nextarc 0 + set possible_seeds {} + run readcache $f + } err]} { + dropcache $err + } + return 0 +} + +proc dropcache {err} { + global allcwait nextarc cachedarcs seeds + + #puts "dropping cache ($err)" + foreach v {arcnos arcout arcids arcstart arcend growing \ + arctags archeads allparents allchildren} { + global $v + catch {unset $v} + } + set allcwait 0 + set nextarc 0 + set cachedarcs 0 + set seeds {} + getallcommits +} + +proc writecache {f} { + global cachearc cachedarcs allccache + global arcstart arcend arcnos arcids arcout + + set a $cachearc + set lim $cachedarcs + if {$lim - $a > 1000} { + set lim [expr {$a + 1000}] + } + if {[catch { + while {[incr a] <= $lim} { + if {[info exists arcend($a)]} { + puts $f [list $arcstart($a) $arcend($a) $arcids($a)] + } else { + puts $f [list $arcstart($a) {} $arcids($a)] + } + } + } err]} { + catch {close $f} + catch {file delete $allccache} + #puts "writing cache failed ($err)" + return 0 + } + set cachearc [expr {$a - 1}] + if {$a > $cachedarcs} { + puts $f "1" + close $f + return 0 + } + return 1 +} + +proc savecache {} { + global nextarc cachedarcs cachearc allccache + + if {$nextarc == $cachedarcs} return + set cachearc 0 + set cachedarcs $nextarc + catch { + set f [open $allccache w] + puts $f [list 1 $cachedarcs] + run writecache $f + } +} + # 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} { @@ -7361,6 +7790,7 @@ proc showtag {tag isnew} { } $ctext conf -state normal clear_ctext + settabs 0 set linknum 0 if {![info exists tagcontents($tag)]} { catch { @@ -7384,11 +7814,135 @@ proc doquit {} { destroy . } +proc mkfontdisp {font top which} { + global fontattr fontpref $font + + set fontpref($font) [set $font] + button $top.${font}but -text $which -font optionfont \ + -command [list choosefont $font $which] + label $top.$font -relief flat -font $font \ + -text $fontattr($font,family) -justify left + grid x $top.${font}but $top.$font -sticky w +} + +proc choosefont {font which} { + global fontparam fontlist fonttop fontattr + + set fontparam(which) $which + set fontparam(font) $font + set fontparam(family) [font actual $font -family] + set fontparam(size) $fontattr($font,size) + set fontparam(weight) $fontattr($font,weight) + set fontparam(slant) $fontattr($font,slant) + set top .gitkfont + set fonttop $top + if {![winfo exists $top]} { + font create sample + eval font config sample [font actual $font] + toplevel $top + wm title $top "Gitk font chooser" + label $top.l -textvariable fontparam(which) -font uifont + pack $top.l -side top + set fontlist [lsort [font families]] + frame $top.f + listbox $top.f.fam -listvariable fontlist \ + -yscrollcommand [list $top.f.sb set] + bind $top.f.fam <<ListboxSelect>> selfontfam + scrollbar $top.f.sb -command [list $top.f.fam yview] + pack $top.f.sb -side right -fill y + pack $top.f.fam -side left -fill both -expand 1 + pack $top.f -side top -fill both -expand 1 + frame $top.g + spinbox $top.g.size -from 4 -to 40 -width 4 \ + -textvariable fontparam(size) \ + -validatecommand {string is integer -strict %s} + checkbutton $top.g.bold -padx 5 \ + -font {{Times New Roman} 12 bold} -text "B" -indicatoron 0 \ + -variable fontparam(weight) -onvalue bold -offvalue normal + checkbutton $top.g.ital -padx 5 \ + -font {{Times New Roman} 12 italic} -text "I" -indicatoron 0 \ + -variable fontparam(slant) -onvalue italic -offvalue roman + pack $top.g.size $top.g.bold $top.g.ital -side left + pack $top.g -side top + canvas $top.c -width 150 -height 50 -border 2 -relief sunk \ + -background white + $top.c create text 100 25 -anchor center -text $which -font sample \ + -fill black -tags text + bind $top.c <Configure> [list centertext $top.c] + pack $top.c -side top -fill x + frame $top.buts + button $top.buts.ok -text "OK" -command fontok -default active \ + -font uifont + button $top.buts.can -text "Cancel" -command fontcan -default normal \ + -font uifont + grid $top.buts.ok $top.buts.can + grid columnconfigure $top.buts 0 -weight 1 -uniform a + grid columnconfigure $top.buts 1 -weight 1 -uniform a + pack $top.buts -side bottom -fill x + trace add variable fontparam write chg_fontparam + } else { + raise $top + $top.c itemconf text -text $which + } + set i [lsearch -exact $fontlist $fontparam(family)] + if {$i >= 0} { + $top.f.fam selection set $i + $top.f.fam see $i + } +} + +proc centertext {w} { + $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}] +} + +proc fontok {} { + global fontparam fontpref prefstop + + set f $fontparam(font) + set fontpref($f) [list $fontparam(family) $fontparam(size)] + if {$fontparam(weight) eq "bold"} { + lappend fontpref($f) "bold" + } + if {$fontparam(slant) eq "italic"} { + lappend fontpref($f) "italic" + } + set w $prefstop.$f + $w conf -text $fontparam(family) -font $fontpref($f) + + fontcan +} + +proc fontcan {} { + global fonttop fontparam + + if {[info exists fonttop]} { + catch {destroy $fonttop} + catch {font delete sample} + unset fonttop + unset fontparam + } +} + +proc selfontfam {} { + global fonttop fontparam + + set i [$fonttop.f.fam curselection] + if {$i ne {}} { + set fontparam(family) [$fonttop.f.fam get $i] + } +} + +proc chg_fontparam {v sub op} { + global fontparam + + font config sample -$sub $fontparam($sub) +} + proc doprefs {} { - global maxwidth maxgraphpct diffopts + global maxwidth maxgraphpct global oldprefs prefstop showneartags showlocalchanges global bgcolor fgcolor ctext diffcolors selectbgcolor - global uifont tabstop + global uifont tabstop limitdiffs set top .gitkprefs set prefstop $top @@ -7396,13 +7950,14 @@ proc doprefs {} { raise $top return } - foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} { + foreach v {maxwidth maxgraphpct showneartags showlocalchanges \ + limitdiffs tabstop} { set oldprefs($v) [set $v] } toplevel $top wm title $top "Gitk preferences" label $top.ldisp -text "Commit list display options" - $top.ldisp configure -font $uifont + $top.ldisp configure -font uifont grid $top.ldisp - -sticky w -pady 10 label $top.spacer -text " " label $top.maxwidthl -text "Maximum graph width (lines)" \ @@ -7420,23 +7975,24 @@ proc doprefs {} { grid x $top.showlocal -sticky w label $top.ddisp -text "Diff display options" - $top.ddisp configure -font $uifont + $top.ddisp configure -font uifont grid $top.ddisp - -sticky w -pady 10 - label $top.diffoptl -text "Options for diff program" \ - -font optionfont - entry $top.diffopt -width 20 -textvariable diffopts - grid x $top.diffoptl $top.diffopt -sticky w + label $top.tabstopl -text "Tab spacing" -font optionfont + spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop + grid x $top.tabstopl $top.tabstop -sticky w frame $top.ntag label $top.ntag.l -text "Display nearby tags" -font optionfont 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 + frame $top.ldiff + label $top.ldiff.l -text "Limit diffs to listed paths" -font optionfont + checkbutton $top.ldiff.b -variable limitdiffs + pack $top.ldiff.b $top.ldiff.l -side left + grid x $top.ldiff -sticky w label $top.cdisp -text "Colors: press to choose" - $top.cdisp configure -font $uifont + $top.cdisp configure -font uifont grid $top.cdisp - -sticky w -pady 10 label $top.bg -padx 40 -relief sunk -background $bgcolor button $top.bgbut -text "Background" -font optionfont \ @@ -7467,11 +8023,18 @@ proc doprefs {} { -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg] grid x $top.selbgbut $top.selbgsep -sticky w + label $top.cfont -text "Fonts: press to choose" + $top.cfont configure -font uifont + grid $top.cfont - -sticky w -pady 10 + mkfontdisp mainfont $top "Main font" + mkfontdisp textfont $top "Diff display font" + mkfontdisp uifont $top "User interface font" + frame $top.buts button $top.buts.ok -text "OK" -command prefsok -default active - $top.buts.ok configure -font $uifont + $top.buts.ok configure -font uifont button $top.buts.can -text "Cancel" -command prefscan -default normal - $top.buts.can configure -font $uifont + $top.buts.can configure -font uifont grid $top.buts.ok $top.buts.can grid columnconfigure $top.buts 0 -weight 1 -uniform a grid columnconfigure $top.buts 1 -weight 1 -uniform a @@ -7519,24 +8082,48 @@ proc setfg {c} { } proc prefscan {} { - global maxwidth maxgraphpct diffopts - global oldprefs prefstop showneartags showlocalchanges + global oldprefs prefstop - foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} { + foreach v {maxwidth maxgraphpct showneartags showlocalchanges \ + limitdiffs tabstop} { + global $v set $v $oldprefs($v) } catch {destroy $prefstop} unset prefstop + fontcan } proc prefsok {} { global maxwidth maxgraphpct global oldprefs prefstop showneartags showlocalchanges - global charspc ctext tabstop + global fontpref mainfont textfont uifont + global limitdiffs treediffs catch {destroy $prefstop} unset prefstop - $ctext configure -tabs "[expr {$tabstop * $charspc}]" + fontcan + set fontchanged 0 + if {$mainfont ne $fontpref(mainfont)} { + set mainfont $fontpref(mainfont) + parsefont mainfont $mainfont + eval font configure mainfont [fontflags mainfont] + eval font configure mainfontbold [fontflags mainfont 1] + setcoords + set fontchanged 1 + } + if {$textfont ne $fontpref(textfont)} { + set textfont $fontpref(textfont) + parsefont textfont $textfont + eval font configure textfont [fontflags textfont] + eval font configure textfontbold [fontflags textfont 1] + } + if {$uifont ne $fontpref(uifont)} { + set uifont $fontpref(uifont) + parsefont uifont $uifont + eval font configure uifont [fontflags uifont] + } + settabs if {$showlocalchanges != $oldprefs(showlocalchanges)} { if {$showlocalchanges} { doshowlocalchanges @@ -7544,10 +8131,15 @@ proc prefsok {} { dohidelocalchanges } } - if {$maxwidth != $oldprefs(maxwidth) + if {$limitdiffs != $oldprefs(limitdiffs)} { + # treediffs elements are limited by path + catch {unset treediffs} + } + if {$fontchanged || $maxwidth != $oldprefs(maxwidth) || $maxgraphpct != $oldprefs(maxgraphpct)} { redisplay - } elseif {$showneartags != $oldprefs(showneartags)} { + } elseif {$showneartags != $oldprefs(showneartags) || + $limitdiffs != $oldprefs(limitdiffs)} { reselectline } } @@ -7833,9 +8425,15 @@ proc tcl_encoding {enc} { return {} } +# First check that Tcl/Tk is recent enough +if {[catch {package require Tk 8.4} err]} { + show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\ + Gitk requires at least Tcl/Tk 8.4." + exit 1 +} + # defaults... set datemode 0 -set diffopts "-U 5 -p" set wrcomcmd "git diff-tree --stdin -p --pretty" set gitencoding {} @@ -7859,15 +8457,16 @@ set maxgraphpct 50 set maxwidth 16 set revlistorder 0 set fastdate 0 -set uparrowlen 7 -set downarrowlen 7 -set mingaplen 30 +set uparrowlen 5 +set downarrowlen 5 +set mingaplen 100 set cmitmode "patch" set wrapcomment "none" set showneartags 1 set maxrefs 20 set maxlinelen 200 set showlocalchanges 1 +set limitdiffs 1 set datetimeformat "%Y-%m-%d %H:%M:%S" set colors {green red blue magenta darkgrey brown orange} @@ -7881,6 +8480,17 @@ catch {source ~/.gitk} font create optionfont -family sans-serif -size -12 +parsefont mainfont $mainfont +eval font create mainfont [fontflags mainfont] +eval font create mainfontbold [fontflags mainfont 1] + +parsefont textfont $textfont +eval font create textfont [fontflags textfont] +eval font create textfontbold [fontflags textfont 1] + +parsefont uifont $uifont +eval font create uifont [fontflags uifont] + # check that we can find a .git directory somewhere... if {[catch {set gitdir [gitdir]}]} { show_error {} . "Cannot find a git repository here." @@ -7891,6 +8501,7 @@ if {![file isdirectory $gitdir]} { exit 1 } +set mergeonly 0 set revtreeargs {} set cmdline_files {} set i 0 @@ -7898,6 +8509,10 @@ foreach arg $argv { switch -- $arg { "" { } "-d" { set datemode 1 } + "--merge" { + set mergeonly 1 + lappend revtreeargs $arg + } "--" { set cmdline_files [lrange $argv [expr {$i + 1}] end] break @@ -7938,9 +8553,44 @@ if {$i >= [llength $argv] && $revtreeargs ne {}} { } } +if {$mergeonly} { + # find the list of unmerged files + set mlist {} + set nr_unmerged 0 + if {[catch { + set fd [open "| git ls-files -u" r] + } err]} { + show_error {} . "Couldn't get list of unmerged files: $err" + exit 1 + } + while {[gets $fd line] >= 0} { + set i [string first "\t" $line] + if {$i < 0} continue + set fname [string range $line [expr {$i+1}] end] + if {[lsearch -exact $mlist $fname] >= 0} continue + incr nr_unmerged + if {$cmdline_files eq {} || [path_filter $cmdline_files $fname]} { + lappend mlist $fname + } + } + catch {close $fd} + if {$mlist eq {}} { + if {$nr_unmerged == 0} { + show_error {} . "No files selected: --merge specified but\ + no files are unmerged." + } else { + show_error {} . "No files selected: --merge specified but\ + no unmerged files are within file limit." + } + exit 1 + } + set cmdline_files $mlist +} + set nullid "0000000000000000000000000000000000000000" set nullid2 "0000000000000000000000000000000000000001" +set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}] set runq {} set history {} @@ -7948,18 +8598,23 @@ set historyindex 0 set fh_serial 0 set nhl_names {} set highlight_paths {} +set findpattern {} set searchdirn -forwards set boldrows {} set boldnamerows {} set diffelide {0 0} set markingmatches 0 - -set optim_delay 16 +set linkentercount 0 +set need_redisplay 0 +set nrows_drawn 0 +set firsttabstop 0 set nextviewnum 1 set curview 0 set selectedview 0 set selectedhlview None +set highlight_related None +set highlight_files {} set viewfiles(0) {} set viewperm(0) 0 set viewargs(0) {} @@ -7968,7 +8623,6 @@ set cmdlineok 0 set stopped 0 set stuffsaved 0 set patchnum 0 -set lookingforhead 0 set localirow -1 set localfrow -1 set lserial 0 diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 3064298f2..2e0075627 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -35,6 +35,10 @@ our $GIT = "++GIT_BINDIR++/git"; #our $projectroot = "/pub/scm"; our $projectroot = "++GITWEB_PROJECTROOT++"; +# fs traversing limit for getting project list +# the number is relative to the projectroot +our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++"; + # target of the home link on top of all pages our $home_link = $my_uri || "/"; @@ -842,6 +846,23 @@ sub chop_str { return "$body$tail"; } +# takes the same arguments as chop_str, but also wraps a <span> around the +# result with a title attribute if it does get chopped. Additionally, the +# string is HTML-escaped. +sub chop_and_escape_str { + my $str = shift; + my $len = shift; + my $add_len = shift || 10; + + my $chopped = chop_str($str, $len, $add_len); + if ($chopped eq $str) { + return esc_html($chopped); + } else { + return qq{<span title="} . esc_html($str) . qq{">} . + esc_html($chopped) . qq{</span>}; + } +} + ## ---------------------------------------------------------------------- ## functions returning short strings @@ -1509,6 +1530,7 @@ sub git_get_projects_list { # remove the trailing "/" $dir =~ s!/+$!!; my $pfxlen = length("$dir"); + my $pfxdepth = ($dir =~ tr!/!!); File::Find::find({ follow_fast => 1, # follow symbolic links @@ -1519,6 +1541,11 @@ sub git_get_projects_list { return if (m!^[/.]$!); # only directories can be git repositories return unless (-d $_); + # don't traverse too deep (Find is super slow on os x) + if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) { + $File::Find::prune = 1; + return; + } my $subdir = substr($File::Find::name, $pfxlen + 1); # we check related file in $projectroot @@ -1990,6 +2017,19 @@ sub parse_difftree_raw_line { return wantarray ? %res : \%res; } +# wrapper: return parsed line of git-diff-tree "raw" output +# (the argument might be raw line, or parsed info) +sub parsed_difftree_line { + my $line_or_ref = shift; + + if (ref($line_or_ref) eq "HASH") { + # pre-parsed (or generated by hand) + return $line_or_ref; + } else { + return parse_difftree_raw_line($line_or_ref); + } +} + # parse line of git-ls-tree output sub parse_ls_tree_line ($;%) { my $line = shift; @@ -2033,6 +2073,7 @@ sub parse_from_to_diffinfo { } } } else { + # ordinary (not combined) diff $from->{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'}; if ($diffinfo->{'status'} ne "A") { # not new (added) file $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent, @@ -2756,6 +2797,7 @@ sub git_print_tree_entry { ## ...................................................................... ## functions printing large fragments of HTML +# get pre-image filenames for merge (combined) diff sub fill_from_file_info { my ($diff, @parents) = @_; @@ -2772,28 +2814,25 @@ sub fill_from_file_info { return $diff; } -# parameters can be strings, or references to arrays of strings -sub from_ids_eq { - my ($a, $b) = @_; - - if (ref($a) eq "ARRAY" && ref($b) eq "ARRAY" && @$a == @$b) { - for (my $i = 0; $i < @$a; ++$i) { - return 0 unless ($a->[$i] eq $b->[$i]); - } - return 1; - } elsif (!ref($a) && !ref($b)) { - return $a eq $b; - } else { - return 0; - } -} - +# is current raw difftree line of file deletion sub is_deleted { my $diffinfo = shift; return $diffinfo->{'to_id'} eq ('0' x 40); } +# does patch correspond to [previous] difftree raw line +# $diffinfo - hashref of parsed raw diff format +# $patchinfo - hashref of parsed patch diff format +# (the same keys as in $diffinfo) +sub is_patch_split { + my ($diffinfo, $patchinfo) = @_; + + return defined $diffinfo && defined $patchinfo + && ($diffinfo->{'to_file'} || $diffinfo->{'file'}) eq $patchinfo->{'to_file'}; +} + + sub git_difftree_body { my ($difftree, $hash, @parents) = @_; my ($parent) = $parents[0]; @@ -2830,13 +2869,7 @@ sub git_difftree_body { my $alternate = 1; my $patchno = 0; foreach my $line (@{$difftree}) { - my $diff; - if (ref($line) eq "HASH") { - # pre-parsed (or generated by hand) - $diff = $line; - } else { - $diff = parse_difftree_raw_line($line); - } + my $diff = parsed_difftree_line($line); if ($alternate) { print "<tr class=\"dark\">\n"; @@ -3107,10 +3140,12 @@ sub git_patchset_body { my ($fd, $difftree, $hash, @hash_parents) = @_; my ($hash_parent) = $hash_parents[0]; + my $is_combined = (@hash_parents > 1); my $patch_idx = 0; my $patch_number = 0; my $patch_line; my $diffinfo; + my $to_name; my (%from, %to); print "<div class=\"patchset\">\n"; @@ -3124,73 +3159,46 @@ sub git_patchset_body { PATCH: while ($patch_line) { - my @diff_header; - my ($from_id, $to_id); - # git diff header - #assert($patch_line =~ m/^diff /) if DEBUG; - #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed - $patch_number++; - push @diff_header, $patch_line; - - # extended diff header - EXTENDED_HEADER: - while ($patch_line = <$fd>) { - chomp $patch_line; - - last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /); - - if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) { - $from_id = $1; - $to_id = $2; - } elsif ($patch_line =~ m/^index ((?:[0-9a-fA-F]{40},)+[0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) { - $from_id = [ split(',', $1) ]; - $to_id = $2; - } - - push @diff_header, $patch_line; + # parse "git diff" header line + if ($patch_line =~ m/^diff --git (\"(?:[^\\\"]*(?:\\.[^\\\"]*)*)\"|[^ "]*) (.*)$/) { + # $1 is from_name, which we do not use + $to_name = unquote($2); + $to_name =~ s!^b/!!; + } elsif ($patch_line =~ m/^diff --(cc|combined) ("?.*"?)$/) { + # $1 is 'cc' or 'combined', which we do not use + $to_name = unquote($2); + } else { + $to_name = undef; } - my $last_patch_line = $patch_line; # check if current patch belong to current raw line # and parse raw git-diff line if needed - if (defined $diffinfo && - defined $from_id && defined $to_id && - from_ids_eq($diffinfo->{'from_id'}, $from_id) && - $diffinfo->{'to_id'} eq $to_id) { + if (is_patch_split($diffinfo, { 'to_file' => $to_name })) { # this is continuation of a split patch print "<div class=\"patch cont\">\n"; } else { # advance raw git-diff output if needed $patch_idx++ if defined $diffinfo; - # compact combined diff output can have some patches skipped - # find which patch (using pathname of result) we are at now - my $to_name; - if ($diff_header[0] =~ m!^diff --cc "?(.*)"?$!) { - $to_name = $1; - } - - do { - # read and prepare patch information - if (ref($difftree->[$patch_idx]) eq "HASH") { - # pre-parsed (or generated by hand) - $diffinfo = $difftree->[$patch_idx]; - } else { - $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]); - } + # read and prepare patch information + $diffinfo = parsed_difftree_line($difftree->[$patch_idx]); - # check if current raw line has no patch (it got simplified) - if (defined $to_name && $to_name ne $diffinfo->{'to_file'}) { + # compact combined diff output can have some patches skipped + # find which patch (using pathname of result) we are at now; + if ($is_combined) { + while ($to_name ne $diffinfo->{'to_file'}) { print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" . format_diff_cc_simplified($diffinfo, @hash_parents) . "</div>\n"; # class="patch" $patch_idx++; $patch_number++; + + last if $patch_idx > $#$difftree; + $diffinfo = parsed_difftree_line($difftree->[$patch_idx]); } - } until (!defined $to_name || $to_name eq $diffinfo->{'to_file'} || - $patch_idx > $#$difftree); + } # modifies %from, %to hashes parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents); @@ -3200,30 +3208,36 @@ sub git_patchset_body { print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n"; } + # git diff header + #assert($patch_line =~ m/^diff /) if DEBUG; + #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed + $patch_number++; # print "git diff" header - $patch_line = shift @diff_header; print format_git_diff_header_line($patch_line, $diffinfo, \%from, \%to); # print extended diff header - print "<div class=\"diff extended_header\">\n" if (@diff_header > 0); + print "<div class=\"diff extended_header\">\n"; EXTENDED_HEADER: - foreach $patch_line (@diff_header) { + while ($patch_line = <$fd>) { + chomp $patch_line; + + last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /); + print format_extended_diff_header_line($patch_line, $diffinfo, \%from, \%to); } - print "</div>\n" if (@diff_header > 0); # class="diff extended_header" + print "</div>\n"; # class="diff extended_header" # from-file/to-file diff header - $patch_line = $last_patch_line; if (! $patch_line) { print "</div>\n"; # class="patch" last PATCH; } next PATCH if ($patch_line =~ m/^diff /); #assert($patch_line =~ m/^---/) if DEBUG; - #assert($patch_line eq $last_patch_line) if DEBUG; + my $last_patch_line = $patch_line; $patch_line = <$fd>; chomp $patch_line; #assert($patch_line =~ m/^\+\+\+/) if DEBUG; @@ -3248,16 +3262,11 @@ sub git_patchset_body { # for compact combined (--cc) format, with chunk and patch simpliciaction # patchset might be empty, but there might be unprocessed raw lines - for ($patch_idx++ if $patch_number > 0; + for (++$patch_idx if $patch_number > 0; $patch_idx < @$difftree; - $patch_idx++) { + ++$patch_idx) { # read and prepare patch information - if (ref($difftree->[$patch_idx]) eq "HASH") { - # pre-parsed (or generated by hand) - $diffinfo = $difftree->[$patch_idx]; - } else { - $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]); - } + $diffinfo = parsed_difftree_line($difftree->[$patch_idx]); # generate anchor for "patch" links in difftree / whatchanged part print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" . @@ -3385,7 +3394,7 @@ sub git_project_list_body { "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), -class => "list", -title => $pr->{'descr_long'}}, esc_html($pr->{'descr'})) . "</td>\n" . - "<td><i>" . esc_html(chop_str($pr->{'owner'}, 15)) . "</i></td>\n"; + "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n"; print "<td class=\"". age_class($pr->{'age'}) . "\">" . (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" . "<td class=\"link\">" . @@ -3427,9 +3436,10 @@ sub git_shortlog_body { print "<tr class=\"light\">\n"; } $alternate ^= 1; + my $author = chop_and_escape_str($co{'author_name'}, 10); # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" . print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" . + "<td><i>" . $author . "</i></td>\n" . "<td>"; print format_subject_html($co{'title'}, $co{'title_short'}, href(action=>"commit", hash=>$commit), $ref); @@ -3477,9 +3487,10 @@ sub git_history_body { print "<tr class=\"light\">\n"; } $alternate ^= 1; + # shortlog uses chop_str($co{'author_name'}, 10) + my $author = chop_and_escape_str($co{'author_name'}, 15, 3); print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - # shortlog uses chop_str($co{'author_name'}, 10) - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" . + "<td><i>" . $author . "</i></td>\n" . "<td>"; # originally git_history used chop_str($co{'title'}, 50) print format_subject_html($co{'title'}, $co{'title_short'}, @@ -3633,11 +3644,12 @@ sub git_search_grep_body { print "<tr class=\"light\">\n"; } $alternate ^= 1; + my $author = chop_and_escape_str($co{'author_name'}, 15, 5); print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" . + "<td><i>" . $author . "</i></td>\n" . "<td>" . $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, - esc_html(chop_str($co{'title'}, 50)) . "<br/>"); + chop_and_escape_str($co{'title'}, 50) . "<br/>"); my $comment = $co{'comment'}; foreach my $line (@$comment) { if ($line =~ m/^(.*)($search_regexp)(.*)$/i) { @@ -5147,12 +5159,13 @@ sub git_search { print "<tr class=\"light\">\n"; } $alternate ^= 1; + my $author = chop_and_escape_str($co{'author_name'}, 15, 5); print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" . + "<td><i>" . $author . "</i></td>\n" . "<td>" . $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, - esc_html(chop_str($co{'title'}, 50)) . "<br/>"); + chop_and_escape_str($co{'title'}, 50) . "<br/>"); while (my $setref = shift @files) { my %set = %$setref; print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'}, @@ -0,0 +1,110 @@ +/* + * Some generic hashing helpers. + */ +#include "cache.h" +#include "hash.h" + +/* + * Look up a hash entry in the hash table. Return the pointer to + * the existing entry, or the empty slot if none existed. The caller + * can then look at the (*ptr) to see whether it existed or not. + */ +static struct hash_table_entry *lookup_hash_entry(unsigned int hash, struct hash_table *table) +{ + unsigned int size = table->size, nr = hash % size; + struct hash_table_entry *array = table->array; + + while (array[nr].ptr) { + if (array[nr].hash == hash) + break; + nr++; + if (nr >= size) + nr = 0; + } + return array + nr; +} + + +/* + * Insert a new hash entry pointer into the table. + * + * If that hash entry already existed, return the pointer to + * the existing entry (and the caller can create a list of the + * pointers or do anything else). If it didn't exist, return + * NULL (and the caller knows the pointer has been inserted). + */ +static void **insert_hash_entry(unsigned int hash, void *ptr, struct hash_table *table) +{ + struct hash_table_entry *entry = lookup_hash_entry(hash, table); + + if (!entry->ptr) { + entry->ptr = ptr; + entry->hash = hash; + table->nr++; + return NULL; + } + return &entry->ptr; +} + +static void grow_hash_table(struct hash_table *table) +{ + unsigned int i; + unsigned int old_size = table->size, new_size; + struct hash_table_entry *old_array = table->array, *new_array; + + new_size = alloc_nr(old_size); + new_array = xcalloc(sizeof(struct hash_table_entry), new_size); + table->size = new_size; + table->array = new_array; + table->nr = 0; + for (i = 0; i < old_size; i++) { + unsigned int hash = old_array[i].hash; + void *ptr = old_array[i].ptr; + if (ptr) + insert_hash_entry(hash, ptr, table); + } + free(old_array); +} + +void *lookup_hash(unsigned int hash, struct hash_table *table) +{ + if (!table->array) + return NULL; + return &lookup_hash_entry(hash, table)->ptr; +} + +void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table) +{ + unsigned int nr = table->nr; + if (nr >= table->size/2) + grow_hash_table(table); + return insert_hash_entry(hash, ptr, table); +} + +int for_each_hash(struct hash_table *table, int (*fn)(void *)) +{ + int sum = 0; + unsigned int i; + unsigned int size = table->size; + struct hash_table_entry *array = table->array; + + for (i = 0; i < size; i++) { + void *ptr = array->ptr; + array++; + if (ptr) { + int val = fn(ptr); + if (val < 0) + return val; + sum += val; + } + } + return sum; +} + +void free_hash(struct hash_table *table) +{ + free(table->array); + table->array = NULL; + table->size = 0; + table->nr = 0; +} @@ -0,0 +1,43 @@ +#ifndef HASH_H +#define HASH_H + +/* + * These are some simple generic hash table helper functions. + * Not necessarily suitable for all users, but good for things + * where you want to just keep track of a list of things, and + * have a good hash to use on them. + * + * It keeps the hash table at roughly 50-75% free, so the memory + * cost of the hash table itself is roughly + * + * 3 * 2*sizeof(void *) * nr_of_objects + * + * bytes. + * + * FIXME: on 64-bit architectures, we waste memory. It would be + * good to have just 32-bit pointers, requiring a special allocator + * for hashed entries or something. + */ +struct hash_table_entry { + unsigned int hash; + void *ptr; +}; + +struct hash_table { + unsigned int size, nr; + struct hash_table_entry *array; +}; + +extern void *lookup_hash(unsigned int hash, struct hash_table *table); +extern void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table); +extern int for_each_hash(struct hash_table *table, int (*fn)(void *)); +extern void free_hash(struct hash_table *table); + +static inline void init_hash(struct hash_table *table) +{ + table->size = 0; + table->nr = 0; + table->array = NULL; +} + +#endif @@ -37,24 +37,25 @@ static inline void mput_char(char c, unsigned int num) putchar(c); } -static struct cmdname { - size_t len; - char name[1]; -} **cmdname; -static int cmdname_alloc, cmdname_cnt; - -static void add_cmdname(const char *name, int len) +static struct cmdnames { + int alloc; + int cnt; + struct cmdname { + size_t len; + char name[1]; + } **names; +} main_cmds, other_cmds; + +static void add_cmdname(struct cmdnames *cmds, const char *name, int len) { - struct cmdname *ent; - if (cmdname_alloc <= cmdname_cnt) { - cmdname_alloc = cmdname_alloc + 200; - cmdname = xrealloc(cmdname, cmdname_alloc * sizeof(*cmdname)); - } - ent = xmalloc(sizeof(*ent) + len); + struct cmdname *ent = xmalloc(sizeof(*ent) + len); + ent->len = len; memcpy(ent->name, name, len); ent->name[len] = 0; - cmdname[cmdname_cnt++] = ent; + + ALLOC_GROW(cmds->names, cmds->cnt + 1, cmds->alloc); + cmds->names[cmds->cnt++] = ent; } static int cmdname_compare(const void *a_, const void *b_) @@ -64,7 +65,42 @@ static int cmdname_compare(const void *a_, const void *b_) return strcmp(a->name, b->name); } -static void pretty_print_string_list(struct cmdname **cmdname, int longest) +static void uniq(struct cmdnames *cmds) +{ + int i, j; + + if (!cmds->cnt) + return; + + for (i = j = 1; i < cmds->cnt; i++) + if (strcmp(cmds->names[i]->name, cmds->names[i-1]->name)) + cmds->names[j++] = cmds->names[i]; + + cmds->cnt = j; +} + +static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes) { + int ci, cj, ei; + int cmp; + + ci = cj = ei = 0; + while (ci < cmds->cnt && ei < excludes->cnt) { + cmp = strcmp(cmds->names[ci]->name, excludes->names[ei]->name); + if (cmp < 0) + cmds->names[cj++] = cmds->names[ci++]; + else if (cmp == 0) + ci++, ei++; + else if (cmp > 0) + ei++; + } + + while (ci < cmds->cnt) + cmds->names[cj++] = cmds->names[ci++]; + + cmds->cnt = cj; +} + +static void pretty_print_string_list(struct cmdnames *cmds, int longest) { int cols = 1, rows; int space = longest + 1; /* min 1 SP between words */ @@ -73,9 +109,7 @@ static void pretty_print_string_list(struct cmdname **cmdname, int longest) if (space < max_cols) cols = max_cols / space; - rows = (cmdname_cnt + cols - 1) / cols; - - qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare); + rows = (cmds->cnt + cols - 1) / cols; for (i = 0; i < rows; i++) { printf(" "); @@ -83,71 +117,112 @@ static void pretty_print_string_list(struct cmdname **cmdname, int longest) for (j = 0; j < cols; j++) { int n = j * rows + i; int size = space; - if (n >= cmdname_cnt) + if (n >= cmds->cnt) break; - if (j == cols-1 || n + rows >= cmdname_cnt) + if (j == cols-1 || n + rows >= cmds->cnt) size = 1; - printf("%-*s", size, cmdname[n]->name); + printf("%-*s", size, cmds->names[n]->name); } putchar('\n'); } } -static void list_commands(const char *exec_path, const char *pattern) +static unsigned int list_commands_in_dir(struct cmdnames *cmds, + const char *path) { unsigned int longest = 0; - char path[PATH_MAX]; - int dirlen; - DIR *dir = opendir(exec_path); + const char *prefix = "git-"; + int prefix_len = strlen(prefix); + DIR *dir = opendir(path); struct dirent *de; - if (!dir) { - fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno)); - exit(1); - } - - dirlen = strlen(exec_path); - if (PATH_MAX - 20 < dirlen) { - fprintf(stderr, "git: insanely long exec-path '%s'\n", - exec_path); - exit(1); - } - - memcpy(path, exec_path, dirlen); - path[dirlen++] = '/'; + if (!dir || chdir(path)) + return 0; while ((de = readdir(dir)) != NULL) { struct stat st; int entlen; - if (prefixcmp(de->d_name, "git-")) + if (prefixcmp(de->d_name, prefix)) continue; - strcpy(path+dirlen, de->d_name); - if (stat(path, &st) || /* stat, not lstat */ + + if (stat(de->d_name, &st) || /* stat, not lstat */ !S_ISREG(st.st_mode) || !(st.st_mode & S_IXUSR)) continue; - entlen = strlen(de->d_name); + entlen = strlen(de->d_name) - prefix_len; if (has_extension(de->d_name, ".exe")) entlen -= 4; if (longest < entlen) longest = entlen; - add_cmdname(de->d_name + 4, entlen-4); + add_cmdname(cmds, de->d_name + prefix_len, entlen); } closedir(dir); - printf("git commands available in '%s'\n", exec_path); - printf("----------------------------"); - mput_char('-', strlen(exec_path)); - putchar('\n'); - pretty_print_string_list(cmdname, longest - 4); - putchar('\n'); + return longest; } -static void list_common_cmds_help(void) +static void list_commands(void) +{ + unsigned int longest = 0; + unsigned int len; + const char *env_path = getenv("PATH"); + char *paths, *path, *colon; + const char *exec_path = git_exec_path(); + + if (exec_path) + longest = list_commands_in_dir(&main_cmds, exec_path); + + if (!env_path) { + fprintf(stderr, "PATH not set\n"); + exit(1); + } + + path = paths = xstrdup(env_path); + while (1) { + if ((colon = strchr(path, ':'))) + *colon = 0; + + len = list_commands_in_dir(&other_cmds, path); + if (len > longest) + longest = len; + + if (!colon) + break; + path = colon + 1; + } + free(paths); + + qsort(main_cmds.names, main_cmds.cnt, + sizeof(*main_cmds.names), cmdname_compare); + uniq(&main_cmds); + + qsort(other_cmds.names, other_cmds.cnt, + sizeof(*other_cmds.names), cmdname_compare); + uniq(&other_cmds); + exclude_cmds(&other_cmds, &main_cmds); + + if (main_cmds.cnt) { + printf("available git commands in '%s'\n", exec_path); + printf("----------------------------"); + mput_char('-', strlen(exec_path)); + putchar('\n'); + pretty_print_string_list(&main_cmds, longest); + putchar('\n'); + } + + if (other_cmds.cnt) { + printf("git commands available from elsewhere on your $PATH\n"); + printf("---------------------------------------------------\n"); + pretty_print_string_list(&other_cmds, longest); + putchar('\n'); + } +} + +void list_common_cmds_help(void) { int i, longest = 0; @@ -185,8 +260,7 @@ static void show_man_page(const char *git_cmd) void help_unknown_cmd(const char *cmd) { - printf("git: '%s' is not a git-command\n\n", cmd); - list_common_cmds_help(); + fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd); exit(1); } @@ -199,19 +273,17 @@ int cmd_version(int argc, const char **argv, const char *prefix) int cmd_help(int argc, const char **argv, const char *prefix) { const char *help_cmd = argc > 1 ? argv[1] : NULL; - const char *exec_path = git_exec_path(); if (!help_cmd) { printf("usage: %s\n\n", git_usage_string); list_common_cmds_help(); - exit(1); + exit(0); } else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) { printf("usage: %s\n\n", git_usage_string); - if(exec_path) - list_commands(exec_path, "git-*"); - exit(1); + list_commands(); + exit(0); } else diff --git a/log-tree.c b/log-tree.c index 62edd3445..3763ce94f 100644 --- a/log-tree.c +++ b/log-tree.c @@ -15,7 +15,7 @@ static void show_parents(struct commit *commit, int abbrev) } } -static void show_decorations(struct commit *commit) +void show_decorations(struct commit *commit) { const char *prefix; struct name_decoration *decoration; diff --git a/log-tree.h b/log-tree.h index e82b56a20..b33f7cd7a 100644 --- a/log-tree.h +++ b/log-tree.h @@ -12,5 +12,6 @@ int log_tree_diff_flush(struct rev_info *); int log_tree_commit(struct rev_info *, struct commit *); int log_tree_opt_parse(struct rev_info *, const char **, int); void show_log(struct rev_info *opt, const char *sep); +void show_decorations(struct commit *commit); #endif diff --git a/match-trees.c b/match-trees.c index d7e29c4d1..0fd6df7d6 100644 --- a/match-trees.c +++ b/match-trees.c @@ -132,7 +132,7 @@ static void match_trees(const unsigned char *hash1, const unsigned char *hash2, int *best_score, char **best_match, - char *base, + const char *base, int recurse_limit) { struct tree_desc one; diff --git a/merge-recursive.c b/merge-recursive.c index 4a5c77c3b..6c6f595fb 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1572,7 +1572,7 @@ static int merge(struct commit *h1, { struct commit_list *iter; struct commit *merged_common_ancestors; - struct tree *mrtree; + struct tree *mrtree = mrtree; int clean; if (show(4)) { diff --git a/read-cache.c b/read-cache.c index 56202d13d..056b322fb 100644 --- a/read-cache.c +++ b/read-cache.c @@ -149,6 +149,8 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st) else if (ce_compare_gitlink(ce)) changed |= DATA_CHANGED; return changed; + case 0: /* Special case: unmerged file in index */ + return MODE_CHANGED | DATA_CHANGED | TYPE_CHANGED; default: die("internal error: ce_mode is %o", ntohl(ce->ce_mode)); } diff --git a/receive-pack.c b/receive-pack.c index 61e992976..38e35c06b 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -166,7 +166,7 @@ static const char *update(struct command *cmd) struct ref_lock *lock; if (!prefixcmp(name, "refs/") && check_ref_format(name + 5)) { - error("refusing to create funny ref '%s' locally", name); + error("refusing to create funny ref '%s' remotely", name); return "funny refname"; } @@ -762,6 +762,8 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, hashcpy(dst_peer->new_sha1, src->new_sha1); } dst_peer->peer_ref = src; + if (pat) + dst_peer->force = pat->force; free_name: free(dst_name); } @@ -855,7 +857,7 @@ struct ref *get_remote_ref(struct ref *remote_refs, const char *name) struct ref *ref = find_ref_by_name_abbrev(remote_refs, name); if (!ref) - die("Couldn't find remote ref %s\n", name); + return NULL; return copy_ref(ref); } @@ -887,20 +889,24 @@ static struct ref *get_local_ref(const char *name) int get_fetch_map(struct ref *remote_refs, const struct refspec *refspec, - struct ref ***tail) + struct ref ***tail, + int missing_ok) { struct ref *ref_map, *rm; if (refspec->pattern) { ref_map = get_expanded_map(remote_refs, refspec); } else { - ref_map = get_remote_ref(remote_refs, - refspec->src[0] ? - refspec->src : "HEAD"); - - ref_map->peer_ref = get_local_ref(refspec->dst); - if (ref_map->peer_ref && refspec->force) - ref_map->peer_ref->force = 1; + const char *name = refspec->src[0] ? refspec->src : "HEAD"; + + ref_map = get_remote_ref(remote_refs, name); + if (!missing_ok && !ref_map) + die("Couldn't find remote ref %s", name); + if (ref_map) { + ref_map->peer_ref = get_local_ref(refspec->dst); + if (ref_map->peer_ref && refspec->force) + ref_map->peer_ref->force = 1; + } } for (rm = ref_map; rm; rm = rm->next) { @@ -67,9 +67,12 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, * *tail is the pointer to the tail pointer of the list of results * beforehand, and will be set to the tail pointer of the list of * results afterward. + * + * missing_ok is usually false, but when we are adding branch.$name.merge + * it is Ok if the branch is not at the remote anymore. */ int get_fetch_map(struct ref *remote_refs, const struct refspec *refspec, - struct ref ***tail); + struct ref ***tail, int missing_ok); struct ref *get_remote_ref(struct ref *remote_refs, const char *name); diff --git a/send-pack.c b/send-pack.c index 147f44b01..5e127a1b7 100644 --- a/send-pack.c +++ b/send-pack.c @@ -178,6 +178,35 @@ static int receive_status(int in) return ret; } +static void update_tracking_ref(struct remote *remote, struct ref *ref) +{ + struct refspec rs; + int will_delete_ref; + + rs.src = ref->name; + rs.dst = NULL; + + if (!ref->peer_ref) + return; + + will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1); + + if (!will_delete_ref && + !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) + return; + + if (!remote_find_tracking(remote, &rs)) { + fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst); + if (is_null_sha1(ref->peer_ref->new_sha1)) { + if (delete_ref(rs.dst, NULL)) + error("Failed to delete"); + } else + update_ref("update by push", rs.dst, + ref->new_sha1, NULL, 0, 0); + free(rs.dst); + } +} + static int send_pack(int in, int out, struct remote *remote, int nr_refspec, char **refspec) { struct ref *ref; @@ -205,7 +234,8 @@ static int send_pack(int in, int out, struct remote *remote, int nr_refspec, cha return -1; if (!remote_refs) { - fprintf(stderr, "No refs in common and none specified; doing nothing.\n"); + fprintf(stderr, "No refs in common and none specified; doing nothing.\n" + "Perhaps you should specify a branch such as 'master'.\n"); return 0; } @@ -305,22 +335,6 @@ static int send_pack(int in, int out, struct remote *remote, int nr_refspec, cha fprintf(stderr, "\n from %s\n to %s\n", old_hex, new_hex); } - if (remote && !dry_run) { - struct refspec rs; - rs.src = ref->name; - rs.dst = NULL; - if (!remote_find_tracking(remote, &rs)) { - fprintf(stderr, " Also local %s\n", rs.dst); - if (will_delete_ref) { - if (delete_ref(rs.dst, NULL)) { - error("Failed to delete"); - } - } else - update_ref("update by push", rs.dst, - ref->new_sha1, NULL, 0, 0); - free(rs.dst); - } - } } packet_flush(out); @@ -333,6 +347,11 @@ static int send_pack(int in, int out, struct remote *remote, int nr_refspec, cha ret = -4; } + if (!dry_run && remote && ret == 0) { + for (ref = remote_refs; ref; ref = ref->next) + update_tracking_ref(remote, ref); + } + if (!new_refs && ret == 0) fprintf(stderr, "Everything up-to-date\n"); return ret; @@ -227,9 +227,20 @@ const char *setup_git_directory_gently(int *nongit_ok) if (PATH_MAX - 40 < strlen(gitdirenv)) die("'$%s' too big", GIT_DIR_ENVIRONMENT); if (is_git_directory(gitdirenv)) { + static char buffer[1024 + 1]; + const char *retval; + if (!work_tree_env) return set_work_tree(gitdirenv); - return NULL; + retval = get_relative_cwd(buffer, sizeof(buffer) - 1, + get_git_work_tree()); + if (!retval || !*retval) + return NULL; + set_git_dir(make_absolute_path(gitdirenv)); + if (chdir(work_tree_env) < 0) + die ("Could not chdir to %s", work_tree_env); + strcat(buffer, "/"); + return retval; } if (nongit_ok) { *nongit_ok = 1; diff --git a/sha1_file.c b/sha1_file.c index 83a06a7ae..f007874cb 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -521,13 +521,15 @@ static int check_packed_git_idx(const char *path, struct packed_git *p) munmap(idx_map, idx_size); return error("wrong index v2 file size in %s", path); } - if (idx_size != min_size) { - /* make sure we can deal with large pack offsets */ - off_t x = 0x7fffffffUL, y = 0xffffffffUL; - if (x > (x + 1) || y > (y + 1)) { - munmap(idx_map, idx_size); - return error("pack too large for current definition of off_t in %s", path); - } + if (idx_size != min_size && + /* + * make sure we can deal with large pack offsets. + * 31-bit signed offset won't be enough, neither + * 32-bit unsigned one will be. + */ + (sizeof(off_t) <= 4)) { + munmap(idx_map, idx_size); + return error("pack too large for current definition of off_t in %s", path); } } @@ -24,17 +24,11 @@ static int do_cvs_cmd(const char *me, char *arg) const char *cvsserver_argv[3] = { "cvsserver", "server", NULL }; - const char *oldpath = getenv("PATH"); - struct strbuf newpath = STRBUF_INIT; if (!arg || strcmp(arg, "server")) die("git-cvsserver only handles server: %s", arg); - strbuf_addstr(&newpath, git_exec_path()); - strbuf_addch(&newpath, ':'); - strbuf_addstr(&newpath, oldpath); - - setenv("PATH", strbuf_detach(&newpath, NULL), 1); + setup_path(NULL); return execv_git_cmd(cvsserver_argv); } diff --git a/show-index.c b/show-index.c index 57ed9e87b..7253991ff 100644 --- a/show-index.c +++ b/show-index.c @@ -68,7 +68,7 @@ int main(int argc, char **argv) ntohl(off64[1]); off64_nr++; } - printf("%llu %s (%08x)\n", (unsigned long long) offset, + printf("%" PRIuMAX " %s (%08x)\n", (uintmax_t) offset, sha1_to_hex(entries[i].sha1), ntohl(entries[i].crc)); } diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index 0807d9f01..62bc4bb07 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -371,4 +371,11 @@ test_expect_success 'in-tree .gitattributes (4)' ' } ' +test_expect_success 'invalid .gitattributes (must not crash)' ' + + echo "three +crlf" >>.gitattributes && + git diff + +' + test_done diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh index 732216184..7ee3820ce 100755 --- a/t/t1501-worktree.sh +++ b/t/t1501-worktree.sh @@ -103,4 +103,13 @@ test_expect_success 'repo finds its work tree from work tree, too' ' test sub/dir/tracked = "$(git ls-files)") ' +test_expect_success '_gently() groks relative GIT_DIR & GIT_WORK_TREE' ' + cd repo.git/work/sub/dir && + GIT_DIR=../../.. GIT_WORK_TREE=../.. GIT_PAGER= \ + git diff --exit-code tracked && + echo changed > tracked && + ! GIT_DIR=../../.. GIT_WORK_TREE=../.. GIT_PAGER= \ + git diff --exit-code tracked +' + test_done diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 57c6397be..2d0c07fd6 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -123,4 +123,52 @@ test_expect_success \ git-branch -a >branches && ! grep -q origin/master branches ' +rewound_push_setup() { + rm -rf parent child && + mkdir parent && cd parent && + git-init && echo one >file && git-add file && git-commit -m one && + echo two >file && git-commit -a -m two && + cd .. && + git-clone parent child && cd child && git-reset --hard HEAD^ +} + +rewound_push_succeeded() { + cmp ../parent/.git/refs/heads/master .git/refs/heads/master +} + +rewound_push_failed() { + if rewound_push_succeeded + then + false + else + true + fi +} + +test_expect_success \ + 'pushing explicit refspecs respects forcing' ' + rewound_push_setup && + if git-send-pack ../parent/.git refs/heads/master:refs/heads/master + then + false + else + true + fi && rewound_push_failed && + git-send-pack ../parent/.git +refs/heads/master:refs/heads/master && + rewound_push_succeeded +' + +test_expect_success \ + 'pushing wildcard refspecs respects forcing' ' + rewound_push_setup && + if git-send-pack ../parent/.git refs/heads/*:refs/heads/* + then + false + else + true + fi && rewound_push_failed && + git-send-pack ../parent/.git +refs/heads/*:refs/heads/* && + rewound_push_succeeded +' + test_done diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 4fbd5b1f4..86f9b5346 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -254,4 +254,32 @@ test_expect_success 'push with dry-run' ' check_push_result $old_commit heads/master ' +test_expect_success 'push updates local refs' ' + + rm -rf parent child && + mkdir parent && cd parent && git init && + echo one >foo && git add foo && git commit -m one && + cd .. && + git clone parent child && cd child && + echo two >foo && git commit -a -m two && + git push && + test $(git rev-parse master) = $(git rev-parse remotes/origin/master) + +' + +test_expect_success 'push does not update local refs on failure' ' + + rm -rf parent child && + mkdir parent && cd parent && git init && + echo one >foo && git add foo && git commit -m one && + echo exit 1 >.git/hooks/pre-receive && + chmod +x .git/hooks/pre-receive && + cd .. && + git clone parent child && cd child && + echo two >foo && git commit -a -m two || exit 1 + git push && exit 1 + test $(git rev-parse master) != $(git rev-parse remotes/origin/master) + +' + test_done diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 03cdba580..53956c08e 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -71,6 +71,63 @@ test_expect_success 'bisect start with one bad and good' ' git bisect next ' +# $HASH1 is good, $HASH4 is bad, we skip $HASH3 +# but $HASH2 is bad, +# so we should find $HASH2 as the first bad commit +test_expect_success 'bisect skip: successfull result' ' + git bisect reset && + git bisect start $HASH4 $HASH1 && + git bisect skip && + git bisect bad > my_bisect_log.txt && + grep "$HASH2 is first bad commit" my_bisect_log.txt && + git bisect reset +' + +# $HASH1 is good, $HASH4 is bad, we skip $HASH3 and $HASH2 +# so we should not be able to tell the first bad commit +# among $HASH2, $HASH3 and $HASH4 +test_expect_success 'bisect skip: cannot tell between 3 commits' ' + git bisect start $HASH4 $HASH1 && + git bisect skip || return 1 + + if git bisect skip > my_bisect_log.txt + then + echo Oops, should have failed. + false + else + test $? -eq 2 && + grep "first bad commit could be any of" my_bisect_log.txt && + ! grep $HASH1 my_bisect_log.txt && + grep $HASH2 my_bisect_log.txt && + grep $HASH3 my_bisect_log.txt && + grep $HASH4 my_bisect_log.txt && + git bisect reset + fi +' + +# $HASH1 is good, $HASH4 is bad, we skip $HASH3 +# but $HASH2 is good, +# so we should not be able to tell the first bad commit +# among $HASH3 and $HASH4 +test_expect_success 'bisect skip: cannot tell between 2 commits' ' + git bisect start $HASH4 $HASH1 && + git bisect skip || return 1 + + if git bisect good > my_bisect_log.txt + then + echo Oops, should have failed. + false + else + test $? -eq 2 && + grep "first bad commit could be any of" my_bisect_log.txt && + ! grep $HASH1 my_bisect_log.txt && + ! grep $HASH2 my_bisect_log.txt && + grep $HASH3 my_bisect_log.txt && + grep $HASH4 my_bisect_log.txt && + git bisect reset + fi +' + # We want to automatically find the commit that # introduced "Another" into hello. test_expect_success \ @@ -99,6 +156,67 @@ test_expect_success \ grep "$HASH4 is first bad commit" my_bisect_log.txt && git bisect reset' +# $HASH1 is good, $HASH5 is bad, we skip $HASH3 +# but $HASH4 is good, +# so we should find $HASH5 as the first bad commit +HASH5= +test_expect_success 'bisect skip: add line and then a new test' ' + add_line_into_file "5: Another new line." hello && + HASH5=$(git rev-parse --verify HEAD) && + git bisect start $HASH5 $HASH1 && + git bisect skip && + git bisect good > my_bisect_log.txt && + grep "$HASH5 is first bad commit" my_bisect_log.txt && + git bisect log > log_to_replay.txt + git bisect reset +' + +test_expect_success 'bisect skip and bisect replay' ' + git bisect replay log_to_replay.txt > my_bisect_log.txt && + grep "$HASH5 is first bad commit" my_bisect_log.txt && + git bisect reset +' + +HASH6= +test_expect_success 'bisect run & skip: cannot tell between 2' ' + add_line_into_file "6: Yet a line." hello && + HASH6=$(git rev-parse --verify HEAD) && + echo "#"\!"/bin/sh" > test_script.sh && + echo "tail -1 hello | grep Ciao > /dev/null && exit 125" >> test_script.sh && + echo "grep line hello > /dev/null" >> test_script.sh && + echo "test \$? -ne 0" >> test_script.sh && + chmod +x test_script.sh && + git bisect start $HASH6 $HASH1 && + if git bisect run ./test_script.sh > my_bisect_log.txt + then + echo Oops, should have failed. + false + else + test $? -eq 2 && + grep "first bad commit could be any of" my_bisect_log.txt && + ! grep $HASH3 my_bisect_log.txt && + ! grep $HASH6 my_bisect_log.txt && + grep $HASH4 my_bisect_log.txt && + grep $HASH5 my_bisect_log.txt + fi +' + +HASH7= +test_expect_success 'bisect run & skip: find first bad' ' + git bisect reset && + add_line_into_file "7: Should be the last line." hello && + HASH7=$(git rev-parse --verify HEAD) && + echo "#"\!"/bin/sh" > test_script.sh && + echo "tail -1 hello | grep Ciao > /dev/null && exit 125" >> test_script.sh && + echo "tail -1 hello | grep day > /dev/null && exit 125" >> test_script.sh && + echo "grep Yet hello > /dev/null" >> test_script.sh && + echo "test \$? -ne 0" >> test_script.sh && + chmod +x test_script.sh && + git bisect start $HASH7 $HASH1 && + git bisect run ./test_script.sh > my_bisect_log.txt && + grep "$HASH6 is first bad commit" my_bisect_log.txt +' + # # test_done diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index e935b2000..2089351f7 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -41,7 +41,9 @@ test_expect_success 'rewrite, renaming a specific file' ' ' test_expect_success 'test that the file was renamed' ' - test d = $(git show HEAD:doh) + test d = $(git show HEAD:doh) && + test -f doh && + test d = $(cat doh) ' git tag oldD HEAD~4 diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh index 28643b0da..01cc0c02b 100755 --- a/t/t7005-editor.sh +++ b/t/t7005-editor.sh @@ -4,6 +4,8 @@ test_description='GIT_EDITOR, core.editor, and stuff' . ./test-lib.sh +OLD_TERM="$TERM" + for i in GIT_EDITOR core_editor EDITOR VISUAL vi do cat >e-$i.sh <<-EOF @@ -88,4 +90,6 @@ do ' done +TERM="$OLD_TERM" + test_done diff --git a/t/t8004-blame.sh b/t/t8004-blame.sh new file mode 100755 index 000000000..ba19ac127 --- /dev/null +++ b/t/t8004-blame.sh @@ -0,0 +1,73 @@ +#!/bin/sh + +# Based on a test case submitted by Björn Steinbrink. + +test_description='git blame on conflicted files' +. ./test-lib.sh + +test_expect_success 'setup first case' ' + # Create the old file + echo "Old line" > file1 && + git add file1 && + git commit --author "Old Line <ol@localhost>" -m file1.a && + + # Branch + git checkout -b foo && + + # Do an ugly move and change + git rm file1 && + echo "New line ..." > file2 && + echo "... and more" >> file2 && + git add file2 && + git commit --author "U Gly <ug@localhost>" -m ugly && + + # Back to master and change something + git checkout master && + echo " + +bla" >> file1 && + git commit --author "Old Line <ol@localhost>" -a -m file1.b && + + # Back to foo and merge master + git checkout foo && + if git merge master; then + echo needed conflict here + exit 1 + else + echo merge failed - resolving automatically + fi && + echo "New line ... +... and more + +bla +Even more" > file2 && + git rm file1 && + git commit --author "M Result <mr@localhost>" -a -m merged && + + # Back to master and change file1 again + git checkout master && + sed s/bla/foo/ <file1 >X && + rm file1 && + mv X file1 && + git commit --author "No Bla <nb@localhost>" -a -m replace && + + # Try to merge into foo again + git checkout foo && + if git merge master; then + echo needed conflict here + exit 1 + else + echo merge failed - test is setup + fi +' + +test_expect_success \ + 'blame runs on unconflicted file while other file has conflicts' ' + git blame file2 +' + +test_expect_success 'blame runs on conflicted file in stages 1,3' ' + git blame file1 +' + +test_done diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh index 5aac64422..3c83127a0 100755 --- a/t/t9101-git-svn-props.sh +++ b/t/t9101-git-svn-props.sh @@ -126,19 +126,20 @@ cat > show-ignore.expect <<\EOF # / /no-such-file* -# deeply +# /deeply/ /deeply/no-such-file* -# deeply/nested +# /deeply/nested/ /deeply/nested/no-such-file* -# deeply/nested/directory +# /deeply/nested/directory/ /deeply/nested/directory/no-such-file* EOF test_expect_success 'test show-ignore' " cd test_wc && mkdir -p deeply/nested/directory && + touch deeply/nested/directory/.keep && svn add deeply && svn up && svn propset -R svn:ignore 'no-such-file*' . @@ -148,4 +149,69 @@ test_expect_success 'test show-ignore' " cmp show-ignore.expect show-ignore.got " +cat >create-ignore.expect <<\EOF +/no-such-file* +EOF + +cat >create-ignore-index.expect <<\EOF +100644 8c52e5dfcd0a8b6b6bcfe6b41b89bcbf493718a5 0 .gitignore +100644 8c52e5dfcd0a8b6b6bcfe6b41b89bcbf493718a5 0 deeply/.gitignore +100644 8c52e5dfcd0a8b6b6bcfe6b41b89bcbf493718a5 0 deeply/nested/.gitignore +100644 8c52e5dfcd0a8b6b6bcfe6b41b89bcbf493718a5 0 deeply/nested/directory/.gitignore +EOF + +test_expect_success 'test create-ignore' " + git-svn fetch && git pull . remotes/git-svn && + git-svn create-ignore && + cmp ./.gitignore create-ignore.expect && + cmp ./deeply/.gitignore create-ignore.expect && + cmp ./deeply/nested/.gitignore create-ignore.expect && + cmp ./deeply/nested/directory/.gitignore create-ignore.expect && + git ls-files -s | grep gitignore | cmp - create-ignore-index.expect + " + +cat >prop.expect <<\EOF +no-such-file* + +EOF +cat >prop2.expect <<\EOF +8 +EOF + +# This test can be improved: since all the svn:ignore contain the same +# pattern, it can pass even though the propget did not execute on the +# right directory. +test_expect_success 'test propget' " + git-svn propget svn:ignore . | cmp - prop.expect && + cd deeply && + git-svn propget svn:ignore . | cmp - ../prop.expect && + git-svn propget svn:entry:committed-rev nested/directory/.keep \ + | cmp - ../prop2.expect && + git-svn propget svn:ignore .. | cmp - ../prop.expect && + git-svn propget svn:ignore nested/ | cmp - ../prop.expect && + git-svn propget svn:ignore ./nested | cmp - ../prop.expect && + git-svn propget svn:ignore .././deeply/nested | cmp - ../prop.expect + " + +cat >prop.expect <<\EOF +Properties on '.': + svn:entry:committed-date + svn:entry:committed-rev + svn:entry:last-author + svn:entry:uuid + svn:ignore +EOF +cat >prop2.expect <<\EOF +Properties on 'nested/directory/.keep': + svn:entry:committed-date + svn:entry:committed-rev + svn:entry:last-author + svn:entry:uuid +EOF + +test_expect_success 'test proplist' " + git-svn proplist . | cmp - prop.expect && + git-svn proplist nested/directory/.keep | cmp - prop2.expect + " + test_done diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 642b836d6..f7bad5bb2 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -18,6 +18,7 @@ gitweb_init () { our \$version = "current"; our \$GIT = "git"; our \$projectroot = "$(pwd)"; +our \$project_maxdepth = 8; our \$home_link_str = "projects"; our \$site_name = "[localhost]"; our \$site_header = ""; diff --git a/t/test-lib.sh b/t/test-lib.sh index cc1253cca..714de6e57 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -59,15 +59,11 @@ esac # ' # . ./test-lib.sh -error () { - echo "* error: $*" - trap - exit - exit 1 -} - -say () { - echo "* $*" -} +[ "x$TERM" != "xdumb" ] && + tput bold >/dev/null 2>&1 && + tput setaf 1 >/dev/null 2>&1 && + tput sgr0 >/dev/null 2>&1 && + color=t test "${test_description}" != "" || error "Test script did not set test_description." @@ -84,6 +80,10 @@ do exit 0 ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t; shift ;; + -q|--q|--qu|--qui|--quie|--quiet) + quiet=t; shift ;; + --no-color) + color=; shift ;; --no-python) # noop now... shift ;; @@ -92,6 +92,37 @@ do esac done +if test -n "$color"; then + say_color () { + case "$1" in + error) tput bold; tput setaf 1;; # bold red + skip) tput bold; tput setaf 2;; # bold green + pass) tput setaf 2;; # green + info) tput setaf 3;; # brown + *) test -n "$quiet" && return;; + esac + shift + echo "* $*" + tput sgr0 + } +else + say_color() { + test -z "$1" && test -n "$quiet" && return + shift + echo "* $*" + } +fi + +error () { + say_color error "error: $*" + trap - exit + exit 1 +} + +say () { + say_color info "$*" +} + exec 5>&1 if test "$verbose" = "t" then @@ -122,13 +153,13 @@ test_tick () { test_ok_ () { test_count=$(expr "$test_count" + 1) - say " ok $test_count: $@" + say_color "" " ok $test_count: $@" } test_failure_ () { test_count=$(expr "$test_count" + 1) test_failure=$(expr "$test_failure" + 1); - say "FAIL $test_count: $1" + say_color error "FAIL $test_count: $1" shift echo "$@" | sed -e 's/^/ /' test "$immediate" = "" || { trap - exit; exit 1; } @@ -158,9 +189,9 @@ test_skip () { done case "$to_skip" in t) - say >&3 "skipping test: $@" + say_color skip >&3 "skipping test: $@" test_count=$(expr "$test_count" + 1) - say "skip $test_count: $1" + say_color skip "skip $test_count: $1" : true ;; *) @@ -247,11 +278,11 @@ test_done () { # The Makefile provided will clean this test area so # we will leave things as they are. - say "passed all $test_count test(s)" + say_color pass "passed all $test_count test(s)" exit 0 ;; *) - say "failed $test_failure among $test_count test(s)" + say_color error "failed $test_failure among $test_count test(s)" exit 1 ;; esac @@ -296,8 +327,8 @@ do done case "$to_skip" in t) - say >&3 "skipping test $this_test altogether" - say "skip all tests in $this_test" + say_color skip >&3 "skipping test $this_test altogether" + say_color skip "skip all tests in $this_test" test_done esac done diff --git a/templates/hooks--pre-commit b/templates/hooks--pre-commit index 18b87309f..a19279b3e 100644 --- a/templates/hooks--pre-commit +++ b/templates/hooks--pre-commit @@ -58,7 +58,7 @@ perl -e ' if (/\s$/) { bad_line("trailing whitespace", $_); } - if (/^\s* /) { + if (/^\s* \t/) { bad_line("indent SP followed by a TAB", $_); } if (/^(?:[<>=]){7}/) { diff --git a/tree-diff.c b/tree-diff.c index 26bdbdd2b..7c261fd7c 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -319,6 +319,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co diff_opts.detect_rename = DIFF_DETECT_RENAME; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_opts.single_follow = opt->paths[0]; + diff_opts.break_opt = opt->break_opt; paths[0] = NULL; diff_tree_setup_paths(paths, &diff_opts); if (diff_setup_done(&diff_opts) < 0) diff --git a/wt-status.c b/wt-status.c index 10ce6eedc..03b5ec448 100644 --- a/wt-status.c +++ b/wt-status.c @@ -52,31 +52,34 @@ void wt_status_prepare(struct wt_status *s) head = resolve_ref("HEAD", sha1, 0, NULL); s->branch = head ? xstrdup(head) : NULL; s->reference = "HEAD"; + s->fp = stdout; + s->index_file = get_index_file(); } -static void wt_status_print_cached_header(const char *reference) +static void wt_status_print_cached_header(struct wt_status *s) { const char *c = color(WT_STATUS_HEADER); - color_printf_ln(c, "# Changes to be committed:"); - if (reference) { - color_printf_ln(c, "# (use \"git reset %s <file>...\" to unstage)", reference); + color_fprintf_ln(s->fp, c, "# Changes to be committed:"); + if (s->reference) { + color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); } else { - color_printf_ln(c, "# (use \"git rm --cached <file>...\" to unstage)"); + color_fprintf_ln(s->fp, c, "# (use \"git rm --cached <file>...\" to unstage)"); } - color_printf_ln(c, "#"); + color_fprintf_ln(s->fp, c, "#"); } -static void wt_status_print_header(const char *main, const char *sub) +static void wt_status_print_header(struct wt_status *s, + const char *main, const char *sub) { const char *c = color(WT_STATUS_HEADER); - color_printf_ln(c, "# %s:", main); - color_printf_ln(c, "# (%s)", sub); - color_printf_ln(c, "#"); + color_fprintf_ln(s->fp, c, "# %s:", main); + color_fprintf_ln(s->fp, c, "# (%s)", sub); + color_fprintf_ln(s->fp, c, "#"); } -static void wt_status_print_trailer(void) +static void wt_status_print_trailer(struct wt_status *s) { - color_printf_ln(color(WT_STATUS_HEADER), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); } static const char *quote_crlf(const char *in, char *buf, size_t sz) @@ -108,7 +111,8 @@ static const char *quote_crlf(const char *in, char *buf, size_t sz) return ret; } -static void wt_status_print_filepair(int t, struct diff_filepair *p) +static void wt_status_print_filepair(struct wt_status *s, + int t, struct diff_filepair *p) { const char *c = color(t); const char *one, *two; @@ -117,36 +121,36 @@ static void wt_status_print_filepair(int t, struct diff_filepair *p) one = quote_crlf(p->one->path, onebuf, sizeof(onebuf)); two = quote_crlf(p->two->path, twobuf, sizeof(twobuf)); - color_printf(color(WT_STATUS_HEADER), "#\t"); + color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); switch (p->status) { case DIFF_STATUS_ADDED: - color_printf(c, "new file: %s", one); + color_fprintf(s->fp, c, "new file: %s", one); break; case DIFF_STATUS_COPIED: - color_printf(c, "copied: %s -> %s", one, two); + color_fprintf(s->fp, c, "copied: %s -> %s", one, two); break; case DIFF_STATUS_DELETED: - color_printf(c, "deleted: %s", one); + color_fprintf(s->fp, c, "deleted: %s", one); break; case DIFF_STATUS_MODIFIED: - color_printf(c, "modified: %s", one); + color_fprintf(s->fp, c, "modified: %s", one); break; case DIFF_STATUS_RENAMED: - color_printf(c, "renamed: %s -> %s", one, two); + color_fprintf(s->fp, c, "renamed: %s -> %s", one, two); break; case DIFF_STATUS_TYPE_CHANGED: - color_printf(c, "typechange: %s", one); + color_fprintf(s->fp, c, "typechange: %s", one); break; case DIFF_STATUS_UNKNOWN: - color_printf(c, "unknown: %s", one); + color_fprintf(s->fp, c, "unknown: %s", one); break; case DIFF_STATUS_UNMERGED: - color_printf(c, "unmerged: %s", one); + color_fprintf(s->fp, c, "unmerged: %s", one); break; default: die("bug: unhandled diff status %c", p->status); } - printf("\n"); + fprintf(s->fp, "\n"); } static void wt_status_print_updated_cb(struct diff_queue_struct *q, @@ -160,14 +164,14 @@ static void wt_status_print_updated_cb(struct diff_queue_struct *q, if (q->queue[i]->status == 'U') continue; if (!shown_header) { - wt_status_print_cached_header(s->reference); + wt_status_print_cached_header(s); s->commitable = 1; shown_header = 1; } - wt_status_print_filepair(WT_STATUS_UPDATED, q->queue[i]); + wt_status_print_filepair(s, WT_STATUS_UPDATED, q->queue[i]); } if (shown_header) - wt_status_print_trailer(); + wt_status_print_trailer(s); } static void wt_status_print_changed_cb(struct diff_queue_struct *q, @@ -184,18 +188,18 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q, msg = use_add_rm_msg; break; } - wt_status_print_header("Changed but not updated", msg); + wt_status_print_header(s, "Changed but not updated", msg); } for (i = 0; i < q->nr; i++) - wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]); + wt_status_print_filepair(s, WT_STATUS_CHANGED, q->queue[i]); if (q->nr) - wt_status_print_trailer(); + wt_status_print_trailer(s); } static void wt_read_cache(struct wt_status *s) { discard_cache(); - read_cache(); + read_cache_from(s->index_file); } static void wt_status_print_initial(struct wt_status *s) @@ -206,16 +210,16 @@ static void wt_status_print_initial(struct wt_status *s) wt_read_cache(s); if (active_nr) { s->commitable = 1; - wt_status_print_cached_header(NULL); + wt_status_print_cached_header(s); } for (i = 0; i < active_nr; i++) { - color_printf(color(WT_STATUS_HEADER), "#\t"); - color_printf_ln(color(WT_STATUS_UPDATED), "new file: %s", + color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); + color_fprintf_ln(s->fp, color(WT_STATUS_UPDATED), "new file: %s", quote_crlf(active_cache[i]->name, buf, sizeof(buf))); } if (active_nr) - wt_status_print_trailer(); + wt_status_print_trailer(s); } static void wt_status_print_updated(struct wt_status *s) @@ -282,12 +286,12 @@ static void wt_status_print_untracked(struct wt_status *s) } if (!shown_header) { s->workdir_untracked = 1; - wt_status_print_header("Untracked files", + wt_status_print_header(s, "Untracked files", use_add_to_include_msg); shown_header = 1; } - color_printf(color(WT_STATUS_HEADER), "#\t"); - color_printf_ln(color(WT_STATUS_UNTRACKED), "%.*s", + color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); + color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%.*s", ent->len, ent->name); } } @@ -317,14 +321,14 @@ void wt_status_print(struct wt_status *s) branch_name = ""; on_what = "Not currently on any branch."; } - color_printf_ln(color(WT_STATUS_HEADER), + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# %s%s", on_what, branch_name); } if (s->is_initial) { - color_printf_ln(color(WT_STATUS_HEADER), "#"); - color_printf_ln(color(WT_STATUS_HEADER), "# Initial commit"); - color_printf_ln(color(WT_STATUS_HEADER), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# Initial commit"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); wt_status_print_initial(s); } else { @@ -338,7 +342,7 @@ void wt_status_print(struct wt_status *s) wt_status_print_verbose(s); if (!s->commitable) { if (s->amend) - printf("# No changes\n"); + fprintf(s->fp, "# No changes\n"); else if (s->workdir_dirty) printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); else if (s->workdir_untracked) diff --git a/wt-status.h b/wt-status.h index cfea4ae68..77449326d 100644 --- a/wt-status.h +++ b/wt-status.h @@ -1,6 +1,8 @@ #ifndef STATUS_H #define STATUS_H +#include <stdio.h> + enum color_wt_status { WT_STATUS_HEADER, WT_STATUS_UPDATED, @@ -19,6 +21,8 @@ struct wt_status { int commitable; int workdir_dirty; int workdir_untracked; + const char *index_file; + FILE *fp; }; int git_status_config(const char *var, const char *value); |